Browse code

Add missing test support plugin.

Matt Clay authored on 2020/03/05 10:01:09
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,298 @@
0
+#!/usr/bin/python
1
+# -*- coding: utf-8 -*-
2
+
3
+# Copyright: (c) 2016, Dag Wieers (@dagwieers) <dag@wieers.com>
4
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5
+
6
+from __future__ import absolute_import, division, print_function
7
+__metaclass__ = type
8
+
9
+ANSIBLE_METADATA = {'metadata_version': '1.1',
10
+                    'status': ['preview'],
11
+                    'supported_by': 'community'}
12
+
13
+DOCUMENTATION = r'''
14
+---
15
+module: sefcontext
16
+short_description: Manages SELinux file context mapping definitions
17
+description:
18
+- Manages SELinux file context mapping definitions.
19
+- Similar to the C(semanage fcontext) command.
20
+version_added: '2.2'
21
+options:
22
+  target:
23
+    description:
24
+    - Target path (expression).
25
+    type: str
26
+    required: yes
27
+    aliases: [ path ]
28
+  ftype:
29
+    description:
30
+    - The file type that should have SELinux contexts applied.
31
+    - "The following file type options are available:"
32
+    - C(a) for all files,
33
+    - C(b) for block devices,
34
+    - C(c) for character devices,
35
+    - C(d) for directories,
36
+    - C(f) for regular files,
37
+    - C(l) for symbolic links,
38
+    - C(p) for named pipes,
39
+    - C(s) for socket files.
40
+    type: str
41
+    choices: [ a, b, c, d, f, l, p, s ]
42
+    default: a
43
+  setype:
44
+    description:
45
+    - SELinux type for the specified target.
46
+    type: str
47
+    required: yes
48
+  seuser:
49
+    description:
50
+    - SELinux user for the specified target.
51
+    type: str
52
+  selevel:
53
+    description:
54
+    - SELinux range for the specified target.
55
+    type: str
56
+    aliases: [ serange ]
57
+  state:
58
+    description:
59
+    - Whether the SELinux file context must be C(absent) or C(present).
60
+    type: str
61
+    choices: [ absent, present ]
62
+    default: present
63
+  reload:
64
+    description:
65
+    - Reload SELinux policy after commit.
66
+    - Note that this does not apply SELinux file contexts to existing files.
67
+    type: bool
68
+    default: yes
69
+  ignore_selinux_state:
70
+    description:
71
+    - Useful for scenarios (chrooted environment) that you can't get the real SELinux state.
72
+    type: bool
73
+    default: no
74
+    version_added: '2.8'
75
+notes:
76
+- The changes are persistent across reboots.
77
+- The M(sefcontext) module does not modify existing files to the new
78
+  SELinux context(s), so it is advisable to first create the SELinux
79
+  file contexts before creating files, or run C(restorecon) manually
80
+  for the existing files that require the new SELinux file contexts.
81
+- Not applying SELinux fcontexts to existing files is a deliberate
82
+  decision as it would be unclear what reported changes would entail
83
+  to, and there's no guarantee that applying SELinux fcontext does
84
+  not pick up other unrelated prior changes.
85
+requirements:
86
+- libselinux-python
87
+- policycoreutils-python
88
+author:
89
+- Dag Wieers (@dagwieers)
90
+'''
91
+
92
+EXAMPLES = r'''
93
+- name: Allow apache to modify files in /srv/git_repos
94
+  sefcontext:
95
+    target: '/srv/git_repos(/.*)?'
96
+    setype: httpd_git_rw_content_t
97
+    state: present
98
+
99
+- name: Apply new SELinux file context to filesystem
100
+  command: restorecon -irv /srv/git_repos
101
+'''
102
+
103
+RETURN = r'''
104
+# Default return values
105
+'''
106
+
107
+import traceback
108
+
109
+from ansible.module_utils.basic import AnsibleModule, missing_required_lib
110
+from ansible.module_utils._text import to_native
111
+
112
+SELINUX_IMP_ERR = None
113
+try:
114
+    import selinux
115
+    HAVE_SELINUX = True
116
+except ImportError:
117
+    SELINUX_IMP_ERR = traceback.format_exc()
118
+    HAVE_SELINUX = False
119
+
120
+SEOBJECT_IMP_ERR = None
121
+try:
122
+    import seobject
123
+    HAVE_SEOBJECT = True
124
+except ImportError:
125
+    SEOBJECT_IMP_ERR = traceback.format_exc()
126
+    HAVE_SEOBJECT = False
127
+
128
+# Add missing entries (backward compatible)
129
+if HAVE_SEOBJECT:
130
+    seobject.file_types.update(
131
+        a=seobject.SEMANAGE_FCONTEXT_ALL,
132
+        b=seobject.SEMANAGE_FCONTEXT_BLOCK,
133
+        c=seobject.SEMANAGE_FCONTEXT_CHAR,
134
+        d=seobject.SEMANAGE_FCONTEXT_DIR,
135
+        f=seobject.SEMANAGE_FCONTEXT_REG,
136
+        l=seobject.SEMANAGE_FCONTEXT_LINK,
137
+        p=seobject.SEMANAGE_FCONTEXT_PIPE,
138
+        s=seobject.SEMANAGE_FCONTEXT_SOCK,
139
+    )
140
+
141
+# Make backward compatible
142
+option_to_file_type_str = dict(
143
+    a='all files',
144
+    b='block device',
145
+    c='character device',
146
+    d='directory',
147
+    f='regular file',
148
+    l='symbolic link',
149
+    p='named pipe',
150
+    s='socket',
151
+)
152
+
153
+
154
+def get_runtime_status(ignore_selinux_state=False):
155
+    return True if ignore_selinux_state is True else selinux.is_selinux_enabled()
156
+
157
+
158
+def semanage_fcontext_exists(sefcontext, target, ftype):
159
+    ''' Get the SELinux file context mapping definition from policy. Return None if it does not exist. '''
160
+
161
+    # Beware that records comprise of a string representation of the file_type
162
+    record = (target, option_to_file_type_str[ftype])
163
+    records = sefcontext.get_all()
164
+    try:
165
+        return records[record]
166
+    except KeyError:
167
+        return None
168
+
169
+
170
+def semanage_fcontext_modify(module, result, target, ftype, setype, do_reload, serange, seuser, sestore=''):
171
+    ''' Add or modify SELinux file context mapping definition to the policy. '''
172
+
173
+    changed = False
174
+    prepared_diff = ''
175
+
176
+    try:
177
+        sefcontext = seobject.fcontextRecords(sestore)
178
+        sefcontext.set_reload(do_reload)
179
+        exists = semanage_fcontext_exists(sefcontext, target, ftype)
180
+        if exists:
181
+            # Modify existing entry
182
+            orig_seuser, orig_serole, orig_setype, orig_serange = exists
183
+
184
+            if seuser is None:
185
+                seuser = orig_seuser
186
+            if serange is None:
187
+                serange = orig_serange
188
+
189
+            if setype != orig_setype or seuser != orig_seuser or serange != orig_serange:
190
+                if not module.check_mode:
191
+                    sefcontext.modify(target, setype, ftype, serange, seuser)
192
+                changed = True
193
+
194
+                if module._diff:
195
+                    prepared_diff += '# Change to semanage file context mappings\n'
196
+                    prepared_diff += '-%s      %s      %s:%s:%s:%s\n' % (target, ftype, orig_seuser, orig_serole, orig_setype, orig_serange)
197
+                    prepared_diff += '+%s      %s      %s:%s:%s:%s\n' % (target, ftype, seuser, orig_serole, setype, serange)
198
+        else:
199
+            # Add missing entry
200
+            if seuser is None:
201
+                seuser = 'system_u'
202
+            if serange is None:
203
+                serange = 's0'
204
+
205
+            if not module.check_mode:
206
+                sefcontext.add(target, setype, ftype, serange, seuser)
207
+            changed = True
208
+
209
+            if module._diff:
210
+                prepared_diff += '# Addition to semanage file context mappings\n'
211
+                prepared_diff += '+%s      %s      %s:%s:%s:%s\n' % (target, ftype, seuser, 'object_r', setype, serange)
212
+
213
+    except Exception as e:
214
+        module.fail_json(msg="%s: %s\n" % (e.__class__.__name__, to_native(e)))
215
+
216
+    if module._diff and prepared_diff:
217
+        result['diff'] = dict(prepared=prepared_diff)
218
+
219
+    module.exit_json(changed=changed, seuser=seuser, serange=serange, **result)
220
+
221
+
222
+def semanage_fcontext_delete(module, result, target, ftype, do_reload, sestore=''):
223
+    ''' Delete SELinux file context mapping definition from the policy. '''
224
+
225
+    changed = False
226
+    prepared_diff = ''
227
+
228
+    try:
229
+        sefcontext = seobject.fcontextRecords(sestore)
230
+        sefcontext.set_reload(do_reload)
231
+        exists = semanage_fcontext_exists(sefcontext, target, ftype)
232
+        if exists:
233
+            # Remove existing entry
234
+            orig_seuser, orig_serole, orig_setype, orig_serange = exists
235
+
236
+            if not module.check_mode:
237
+                sefcontext.delete(target, ftype)
238
+            changed = True
239
+
240
+            if module._diff:
241
+                prepared_diff += '# Deletion to semanage file context mappings\n'
242
+                prepared_diff += '-%s      %s      %s:%s:%s:%s\n' % (target, ftype, exists[0], exists[1], exists[2], exists[3])
243
+
244
+    except Exception as e:
245
+        module.fail_json(msg="%s: %s\n" % (e.__class__.__name__, to_native(e)))
246
+
247
+    if module._diff and prepared_diff:
248
+        result['diff'] = dict(prepared=prepared_diff)
249
+
250
+    module.exit_json(changed=changed, **result)
251
+
252
+
253
+def main():
254
+    module = AnsibleModule(
255
+        argument_spec=dict(
256
+            ignore_selinux_state=dict(type='bool', default=False),
257
+            target=dict(type='str', required=True, aliases=['path']),
258
+            ftype=dict(type='str', default='a', choices=option_to_file_type_str.keys()),
259
+            setype=dict(type='str', required=True),
260
+            seuser=dict(type='str'),
261
+            selevel=dict(type='str', aliases=['serange']),
262
+            state=dict(type='str', default='present', choices=['absent', 'present']),
263
+            reload=dict(type='bool', default=True),
264
+        ),
265
+        supports_check_mode=True,
266
+    )
267
+    if not HAVE_SELINUX:
268
+        module.fail_json(msg=missing_required_lib("libselinux-python"), exception=SELINUX_IMP_ERR)
269
+
270
+    if not HAVE_SEOBJECT:
271
+        module.fail_json(msg=missing_required_lib("policycoreutils-python"), exception=SEOBJECT_IMP_ERR)
272
+
273
+    ignore_selinux_state = module.params['ignore_selinux_state']
274
+
275
+    if not get_runtime_status(ignore_selinux_state):
276
+        module.fail_json(msg="SELinux is disabled on this host.")
277
+
278
+    target = module.params['target']
279
+    ftype = module.params['ftype']
280
+    setype = module.params['setype']
281
+    seuser = module.params['seuser']
282
+    serange = module.params['selevel']
283
+    state = module.params['state']
284
+    do_reload = module.params['reload']
285
+
286
+    result = dict(target=target, ftype=ftype, setype=setype, state=state)
287
+
288
+    if state == 'present':
289
+        semanage_fcontext_modify(module, result, target, ftype, setype, do_reload, serange, seuser)
290
+    elif state == 'absent':
291
+        semanage_fcontext_delete(module, result, target, ftype, do_reload)
292
+    else:
293
+        module.fail_json(msg='Invalid value of argument "state": {0}'.format(state))
294
+
295
+
296
+if __name__ == '__main__':
297
+    main()