diff options
Diffstat (limited to 'ansible_collections/community/general/plugins/modules/capabilities.py')
-rw-r--r-- | ansible_collections/community/general/plugins/modules/capabilities.py | 188 |
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() |