summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/general/plugins/modules/capabilities.py
diff options
context:
space:
mode:
Diffstat (limited to 'ansible_collections/community/general/plugins/modules/capabilities.py')
-rw-r--r--ansible_collections/community/general/plugins/modules/capabilities.py188
1 files changed, 188 insertions, 0 deletions
diff --git a/ansible_collections/community/general/plugins/modules/capabilities.py b/ansible_collections/community/general/plugins/modules/capabilities.py
new file mode 100644
index 000000000..9b72ac6ea
--- /dev/null
+++ b/ansible_collections/community/general/plugins/modules/capabilities.py
@@ -0,0 +1,188 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2014, Nate Coraor <nate@bx.psu.edu>
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+---
+module: capabilities
+short_description: Manage Linux capabilities
+description:
+ - This module manipulates files privileges using the Linux capabilities(7) system.
+extends_documentation_fragment:
+ - community.general.attributes
+attributes:
+ check_mode:
+ support: full
+ diff_mode:
+ support: none
+options:
+ path:
+ description:
+ - Specifies the path to the file to be managed.
+ type: str
+ required: true
+ aliases: [ key ]
+ capability:
+ description:
+ - Desired capability to set (with operator and flags, if state is C(present)) or remove (if state is C(absent))
+ type: str
+ required: true
+ aliases: [ cap ]
+ state:
+ description:
+ - Whether the entry should be present or absent in the file's capabilities.
+ type: str
+ choices: [ absent, present ]
+ default: present
+notes:
+ - The capabilities system will automatically transform operators and flags into the effective set,
+ so for example, C(cap_foo=ep) will probably become C(cap_foo+ep).
+ - This module does not attempt to determine the final operator and flags to compare,
+ so you will want to ensure that your capabilities argument matches the final capabilities.
+author:
+- Nate Coraor (@natefoo)
+'''
+
+EXAMPLES = r'''
+- name: Set cap_sys_chroot+ep on /foo
+ community.general.capabilities:
+ path: /foo
+ capability: cap_sys_chroot+ep
+ state: present
+
+- name: Remove cap_net_bind_service from /bar
+ community.general.capabilities:
+ path: /bar
+ capability: cap_net_bind_service
+ state: absent
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+
+OPS = ('=', '-', '+')
+
+
+class CapabilitiesModule(object):
+ platform = 'Linux'
+ distribution = None
+
+ def __init__(self, module):
+ self.module = module
+ self.path = module.params['path'].strip()
+ self.capability = module.params['capability'].strip().lower()
+ self.state = module.params['state']
+ self.getcap_cmd = module.get_bin_path('getcap', required=True)
+ self.setcap_cmd = module.get_bin_path('setcap', required=True)
+ self.capability_tup = self._parse_cap(self.capability, op_required=self.state == 'present')
+
+ self.run()
+
+ def run(self):
+
+ current = self.getcap(self.path)
+ caps = [cap[0] for cap in current]
+
+ if self.state == 'present' and self.capability_tup not in current:
+ # need to add capability
+ if self.module.check_mode:
+ self.module.exit_json(changed=True, msg='capabilities changed')
+ else:
+ # remove from current cap list if it's already set (but op/flags differ)
+ current = list(filter(lambda x: x[0] != self.capability_tup[0], current))
+ # add new cap with correct op/flags
+ current.append(self.capability_tup)
+ self.module.exit_json(changed=True, state=self.state, msg='capabilities changed', stdout=self.setcap(self.path, current))
+ elif self.state == 'absent' and self.capability_tup[0] in caps:
+ # need to remove capability
+ if self.module.check_mode:
+ self.module.exit_json(changed=True, msg='capabilities changed')
+ else:
+ # remove from current cap list and then set current list
+ current = filter(lambda x: x[0] != self.capability_tup[0], current)
+ self.module.exit_json(changed=True, state=self.state, msg='capabilities changed', stdout=self.setcap(self.path, current))
+ self.module.exit_json(changed=False, state=self.state)
+
+ def getcap(self, path):
+ rval = []
+ cmd = "%s -v %s" % (self.getcap_cmd, path)
+ rc, stdout, stderr = self.module.run_command(cmd)
+ # If file xattrs are set but no caps are set the output will be:
+ # '/foo ='
+ # If file xattrs are unset the output will be:
+ # '/foo'
+ # If the file does not exist, the stderr will be (with rc == 0...):
+ # '/foo (No such file or directory)'
+ if rc != 0 or stderr != "":
+ self.module.fail_json(msg="Unable to get capabilities of %s" % path, stdout=stdout.strip(), stderr=stderr)
+ if stdout.strip() != path:
+ if ' =' in stdout:
+ # process output of an older version of libcap
+ caps = stdout.split(' =')[1].strip().split()
+ else:
+ # otherwise, we have a newer version here
+ # see original commit message of cap/v0.2.40-18-g177cd41 in libcap.git
+ caps = stdout.split()[1].strip().split()
+ for cap in caps:
+ cap = cap.lower()
+ # getcap condenses capabilities with the same op/flags into a
+ # comma-separated list, so we have to parse that
+ if ',' in cap:
+ cap_group = cap.split(',')
+ cap_group[-1], op, flags = self._parse_cap(cap_group[-1])
+ for subcap in cap_group:
+ rval.append((subcap, op, flags))
+ else:
+ rval.append(self._parse_cap(cap))
+ return rval
+
+ def setcap(self, path, caps):
+ caps = ' '.join([''.join(cap) for cap in caps])
+ cmd = "%s '%s' %s" % (self.setcap_cmd, caps, path)
+ rc, stdout, stderr = self.module.run_command(cmd)
+ if rc != 0:
+ self.module.fail_json(msg="Unable to set capabilities of %s" % path, stdout=stdout, stderr=stderr)
+ else:
+ return stdout
+
+ def _parse_cap(self, cap, op_required=True):
+ opind = -1
+ try:
+ i = 0
+ while opind == -1:
+ opind = cap.find(OPS[i])
+ i += 1
+ except Exception:
+ if op_required:
+ self.module.fail_json(msg="Couldn't find operator (one of: %s)" % str(OPS))
+ else:
+ return (cap, None, None)
+ op = cap[opind]
+ cap, flags = cap.split(op)
+ return (cap, op, flags)
+
+
+# ==============================================================
+# main
+
+def main():
+ # defining module
+ module = AnsibleModule(
+ argument_spec=dict(
+ path=dict(type='str', required=True, aliases=['key']),
+ capability=dict(type='str', required=True, aliases=['cap']),
+ state=dict(type='str', default='present', choices=['absent', 'present']),
+ ),
+ supports_check_mode=True,
+ )
+
+ CapabilitiesModule(module)
+
+
+if __name__ == '__main__':
+ main()