mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
b78254fe24
instead of whitelisting some subset of known existing permissions, just
allow any string to be used as permissions. this way, any permission
supported by the underlying zfs commands can be used, eg. 'bookmark',
'load-key', 'change-key' and all property permissions, which were
missing from the choices list.
(cherry picked from commit dc0a56141f
)
Co-authored-by: Lauri Tirkkonen <lauri@hacktheplanet.fi>
265 lines
9.1 KiB
Python
265 lines
9.1 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright: (c) 2015, Nate Coraor <nate@coraor.org>
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
__metaclass__ = type
|
|
|
|
DOCUMENTATION = r'''
|
|
---
|
|
module: zfs_delegate_admin
|
|
short_description: Manage ZFS delegated administration (user admin privileges)
|
|
description:
|
|
- Manages ZFS file system delegated administration permissions, which allow unprivileged users to perform ZFS
|
|
operations normally restricted to the superuser.
|
|
- See the C(zfs allow) section of C(zfs(1M)) for detailed explanations of options.
|
|
- This module attempts to adhere to the behavior of the command line tool as much as possible.
|
|
requirements:
|
|
- "A ZFS/OpenZFS implementation that supports delegation with `zfs allow`, including: Solaris >= 10, illumos (all
|
|
versions), FreeBSD >= 8.0R, ZFS on Linux >= 0.7.0."
|
|
options:
|
|
name:
|
|
description:
|
|
- File system or volume name e.g. C(rpool/myfs).
|
|
required: true
|
|
type: str
|
|
state:
|
|
description:
|
|
- Whether to allow (C(present)), or unallow (C(absent)) a permission.
|
|
- When set to C(present), at least one "entity" param of I(users), I(groups), or I(everyone) are required.
|
|
- When set to C(absent), removes permissions from the specified entities, or removes all permissions if no entity params are specified.
|
|
choices: [ absent, present ]
|
|
default: present
|
|
type: str
|
|
users:
|
|
description:
|
|
- List of users to whom permission(s) should be granted.
|
|
type: list
|
|
elements: str
|
|
groups:
|
|
description:
|
|
- List of groups to whom permission(s) should be granted.
|
|
type: list
|
|
elements: str
|
|
everyone:
|
|
description:
|
|
- Apply permissions to everyone.
|
|
type: bool
|
|
default: no
|
|
permissions:
|
|
description:
|
|
- The list of permission(s) to delegate (required if C(state) is C(present)).
|
|
- Supported permissions depend on the ZFS version in use. See for example
|
|
U(https://openzfs.github.io/openzfs-docs/man/8/zfs-allow.8.html) for OpenZFS.
|
|
type: list
|
|
elements: str
|
|
local:
|
|
description:
|
|
- Apply permissions to C(name) locally (C(zfs allow -l)).
|
|
type: bool
|
|
descendents:
|
|
description:
|
|
- Apply permissions to C(name)'s descendents (C(zfs allow -d)).
|
|
type: bool
|
|
recursive:
|
|
description:
|
|
- Unallow permissions recursively (ignored when C(state) is C(present)).
|
|
type: bool
|
|
default: no
|
|
author:
|
|
- Nate Coraor (@natefoo)
|
|
'''
|
|
|
|
EXAMPLES = r'''
|
|
- name: Grant `zfs allow` and `unallow` permission to the `adm` user with the default local+descendents scope
|
|
community.general.zfs_delegate_admin:
|
|
name: rpool/myfs
|
|
users: adm
|
|
permissions: allow,unallow
|
|
|
|
- name: Grant `zfs send` to everyone, plus the group `backup`
|
|
community.general.zfs_delegate_admin:
|
|
name: rpool/myvol
|
|
groups: backup
|
|
everyone: yes
|
|
permissions: send
|
|
|
|
- name: Grant `zfs send,receive` to users `foo` and `bar` with local scope only
|
|
community.general.zfs_delegate_admin:
|
|
name: rpool/myfs
|
|
users: foo,bar
|
|
permissions: send,receive
|
|
local: yes
|
|
|
|
- name: Revoke all permissions from everyone (permissions specifically assigned to users and groups remain)
|
|
community.general.zfs_delegate_admin:
|
|
name: rpool/myfs
|
|
everyone: yes
|
|
state: absent
|
|
'''
|
|
|
|
# This module does not return anything other than the standard
|
|
# changed/state/msg/stdout
|
|
RETURN = '''
|
|
'''
|
|
|
|
from itertools import product
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
|
|
|
|
class ZfsDelegateAdmin(object):
|
|
def __init__(self, module):
|
|
self.module = module
|
|
self.name = module.params.get('name')
|
|
self.state = module.params.get('state')
|
|
self.users = module.params.get('users')
|
|
self.groups = module.params.get('groups')
|
|
self.everyone = module.params.get('everyone')
|
|
self.perms = module.params.get('permissions')
|
|
self.scope = None
|
|
self.changed = False
|
|
self.initial_perms = None
|
|
self.subcommand = 'allow'
|
|
self.recursive_opt = []
|
|
self.run_method = self.update
|
|
|
|
self.setup(module)
|
|
|
|
def setup(self, module):
|
|
""" Validate params and set up for run.
|
|
"""
|
|
if self.state == 'absent':
|
|
self.subcommand = 'unallow'
|
|
if module.params.get('recursive'):
|
|
self.recursive_opt = ['-r']
|
|
|
|
local = module.params.get('local')
|
|
descendents = module.params.get('descendents')
|
|
if (local and descendents) or (not local and not descendents):
|
|
self.scope = 'ld'
|
|
elif local:
|
|
self.scope = 'l'
|
|
elif descendents:
|
|
self.scope = 'd'
|
|
else:
|
|
self.module.fail_json(msg='Impossible value for local and descendents')
|
|
|
|
if not (self.users or self.groups or self.everyone):
|
|
if self.state == 'present':
|
|
self.module.fail_json(msg='One of `users`, `groups`, or `everyone` must be set')
|
|
elif self.state == 'absent':
|
|
self.run_method = self.clear
|
|
# ansible ensures the else cannot happen here
|
|
|
|
self.zfs_path = module.get_bin_path('zfs', True)
|
|
|
|
@property
|
|
def current_perms(self):
|
|
""" Parse the output of `zfs allow <name>` to retrieve current permissions.
|
|
"""
|
|
out = self.run_zfs_raw(subcommand='allow')
|
|
perms = {
|
|
'l': {'u': {}, 'g': {}, 'e': []},
|
|
'd': {'u': {}, 'g': {}, 'e': []},
|
|
'ld': {'u': {}, 'g': {}, 'e': []},
|
|
}
|
|
linemap = {
|
|
'Local permissions:': 'l',
|
|
'Descendent permissions:': 'd',
|
|
'Local+Descendent permissions:': 'ld',
|
|
}
|
|
scope = None
|
|
for line in out.splitlines():
|
|
scope = linemap.get(line, scope)
|
|
if not scope:
|
|
continue
|
|
try:
|
|
if line.startswith('\tuser ') or line.startswith('\tgroup '):
|
|
ent_type, ent, cur_perms = line.split()
|
|
perms[scope][ent_type[0]][ent] = cur_perms.split(',')
|
|
elif line.startswith('\teveryone '):
|
|
perms[scope]['e'] = line.split()[1].split(',')
|
|
except ValueError:
|
|
self.module.fail_json(msg="Cannot parse user/group permission output by `zfs allow`: '%s'" % line)
|
|
return perms
|
|
|
|
def run_zfs_raw(self, subcommand=None, args=None):
|
|
""" Run a raw zfs command, fail on error.
|
|
"""
|
|
cmd = [self.zfs_path, subcommand or self.subcommand] + (args or []) + [self.name]
|
|
rc, out, err = self.module.run_command(cmd)
|
|
if rc:
|
|
self.module.fail_json(msg='Command `%s` failed: %s' % (' '.join(cmd), err))
|
|
return out
|
|
|
|
def run_zfs(self, args):
|
|
""" Run zfs allow/unallow with appropriate options as per module arguments.
|
|
"""
|
|
args = self.recursive_opt + ['-' + self.scope] + args
|
|
if self.perms:
|
|
args.append(','.join(self.perms))
|
|
return self.run_zfs_raw(args=args)
|
|
|
|
def clear(self):
|
|
""" Called by run() to clear all permissions.
|
|
"""
|
|
changed = False
|
|
stdout = ''
|
|
for scope, ent_type in product(('ld', 'l', 'd'), ('u', 'g')):
|
|
for ent in self.initial_perms[scope][ent_type].keys():
|
|
stdout += self.run_zfs(['-%s' % ent_type, ent])
|
|
changed = True
|
|
for scope in ('ld', 'l', 'd'):
|
|
if self.initial_perms[scope]['e']:
|
|
stdout += self.run_zfs(['-e'])
|
|
changed = True
|
|
return (changed, stdout)
|
|
|
|
def update(self):
|
|
""" Update permissions as per module arguments.
|
|
"""
|
|
stdout = ''
|
|
for ent_type, entities in (('u', self.users), ('g', self.groups)):
|
|
if entities:
|
|
stdout += self.run_zfs(['-%s' % ent_type, ','.join(entities)])
|
|
if self.everyone:
|
|
stdout += self.run_zfs(['-e'])
|
|
return (self.initial_perms != self.current_perms, stdout)
|
|
|
|
def run(self):
|
|
""" Run an operation, return results for Ansible.
|
|
"""
|
|
exit_args = {'state': self.state}
|
|
self.initial_perms = self.current_perms
|
|
exit_args['changed'], stdout = self.run_method()
|
|
if exit_args['changed']:
|
|
exit_args['msg'] = 'ZFS delegated admin permissions updated'
|
|
exit_args['stdout'] = stdout
|
|
self.module.exit_json(**exit_args)
|
|
|
|
|
|
def main():
|
|
module = AnsibleModule(
|
|
argument_spec=dict(
|
|
name=dict(type='str', required=True),
|
|
state=dict(type='str', default='present', choices=['absent', 'present']),
|
|
users=dict(type='list', elements='str'),
|
|
groups=dict(type='list', elements='str'),
|
|
everyone=dict(type='bool', default=False),
|
|
permissions=dict(type='list', elements='str'),
|
|
local=dict(type='bool'),
|
|
descendents=dict(type='bool'),
|
|
recursive=dict(type='bool', default=False),
|
|
),
|
|
supports_check_mode=False,
|
|
required_if=[('state', 'present', ['permissions'])],
|
|
)
|
|
zfs_delegate_admin = ZfsDelegateAdmin(module)
|
|
zfs_delegate_admin.run()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|