summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/general/plugins/modules/sudoers.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
commit975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch)
tree89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/community/general/plugins/modules/sudoers.py
parentInitial commit. (diff)
downloadansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz
ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/community/general/plugins/modules/sudoers.py')
-rw-r--r--ansible_collections/community/general/plugins/modules/sudoers.py309
1 files changed, 309 insertions, 0 deletions
diff --git a/ansible_collections/community/general/plugins/modules/sudoers.py b/ansible_collections/community/general/plugins/modules/sudoers.py
new file mode 100644
index 000000000..fd8289b1c
--- /dev/null
+++ b/ansible_collections/community/general/plugins/modules/sudoers.py
@@ -0,0 +1,309 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+
+# Copyright (c) 2019, Jon Ellis (@JonEllis) <ellis.jp@gmail.com>
+# 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 = '''
+---
+module: sudoers
+short_description: Manage sudoers files
+version_added: "4.3.0"
+description:
+ - This module allows for the manipulation of sudoers files.
+author:
+ - "Jon Ellis (@JonEllis) <ellis.jp@gmail.com>"
+extends_documentation_fragment:
+ - community.general.attributes
+attributes:
+ check_mode:
+ support: full
+ diff_mode:
+ support: none
+options:
+ commands:
+ description:
+ - The commands allowed by the sudoers rule.
+ - Multiple can be added by passing a list of commands.
+ - Use C(ALL) for all commands.
+ type: list
+ elements: str
+ group:
+ description:
+ - The name of the group for the sudoers rule.
+ - This option cannot be used in conjunction with I(user).
+ type: str
+ name:
+ required: true
+ description:
+ - The name of the sudoers rule.
+ - This will be used for the filename for the sudoers file managed by this rule.
+ type: str
+ nopassword:
+ description:
+ - Whether a password will be required to run the sudo'd command.
+ default: true
+ type: bool
+ setenv:
+ description:
+ - Whether to allow keeping the environment when command is run with sudo.
+ default: false
+ type: bool
+ version_added: 6.3.0
+ host:
+ description:
+ - Specify the host the rule is for.
+ default: ALL
+ type: str
+ version_added: 6.2.0
+ runas:
+ description:
+ - Specify the target user the command(s) will run as.
+ type: str
+ version_added: 4.7.0
+ sudoers_path:
+ description:
+ - The path which sudoers config files will be managed in.
+ default: /etc/sudoers.d
+ type: str
+ state:
+ default: "present"
+ choices:
+ - present
+ - absent
+ description:
+ - Whether the rule should exist or not.
+ type: str
+ user:
+ description:
+ - The name of the user for the sudoers rule.
+ - This option cannot be used in conjunction with I(group).
+ type: str
+ validation:
+ description:
+ - If C(absent), the sudoers rule will be added without validation.
+ - If C(detect) and visudo is available, then the sudoers rule will be validated by visudo.
+ - If C(required), visudo must be available to validate the sudoers rule.
+ type: str
+ default: detect
+ choices: [ absent, detect, required ]
+ version_added: 5.2.0
+'''
+
+EXAMPLES = '''
+- name: Allow the backup user to sudo /usr/local/bin/backup
+ community.general.sudoers:
+ name: allow-backup
+ state: present
+ user: backup
+ commands: /usr/local/bin/backup
+
+- name: Allow the bob user to run any commands as alice with sudo -u alice
+ community.general.sudoers:
+ name: bob-do-as-alice
+ state: present
+ user: bob
+ runas: alice
+ commands: ALL
+
+- name: >-
+ Allow the monitoring group to run sudo /usr/local/bin/gather-app-metrics
+ without requiring a password on the host called webserver
+ community.general.sudoers:
+ name: monitor-app
+ group: monitoring
+ host: webserver
+ commands: /usr/local/bin/gather-app-metrics
+
+- name: >-
+ Allow the alice user to run sudo /bin/systemctl restart my-service or
+ sudo /bin/systemctl reload my-service, but a password is required
+ community.general.sudoers:
+ name: alice-service
+ user: alice
+ commands:
+ - /bin/systemctl restart my-service
+ - /bin/systemctl reload my-service
+ nopassword: false
+
+- name: Revoke the previous sudo grants given to the alice user
+ community.general.sudoers:
+ name: alice-service
+ state: absent
+
+- name: Allow alice to sudo /usr/local/bin/upload and keep env variables
+ community.general.sudoers:
+ name: allow-alice-upload
+ user: alice
+ commands: /usr/local/bin/upload
+ setenv: true
+'''
+
+import os
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.common.text.converters import to_native
+
+
+class Sudoers(object):
+
+ FILE_MODE = 0o440
+
+ def __init__(self, module):
+ self.module = module
+
+ self.check_mode = module.check_mode
+ self.name = module.params['name']
+ self.user = module.params['user']
+ self.group = module.params['group']
+ self.state = module.params['state']
+ self.nopassword = module.params['nopassword']
+ self.setenv = module.params['setenv']
+ self.host = module.params['host']
+ self.runas = module.params['runas']
+ self.sudoers_path = module.params['sudoers_path']
+ self.file = os.path.join(self.sudoers_path, self.name)
+ self.commands = module.params['commands']
+ self.validation = module.params['validation']
+
+ def write(self):
+ if self.check_mode:
+ return
+
+ with open(self.file, 'w') as f:
+ f.write(self.content())
+
+ os.chmod(self.file, self.FILE_MODE)
+
+ def delete(self):
+ if self.check_mode:
+ return
+
+ os.remove(self.file)
+
+ def exists(self):
+ return os.path.exists(self.file)
+
+ def matches(self):
+ with open(self.file, 'r') as f:
+ content_matches = f.read() == self.content()
+
+ current_mode = os.stat(self.file).st_mode & 0o777
+ mode_matches = current_mode == self.FILE_MODE
+
+ return content_matches and mode_matches
+
+ def content(self):
+ if self.user:
+ owner = self.user
+ elif self.group:
+ owner = '%{group}'.format(group=self.group)
+
+ commands_str = ', '.join(self.commands)
+ nopasswd_str = 'NOPASSWD:' if self.nopassword else ''
+ setenv_str = 'SETENV:' if self.setenv else ''
+ runas_str = '({runas})'.format(runas=self.runas) if self.runas is not None else ''
+ return "{owner} {host}={runas}{nopasswd}{setenv} {commands}\n".format(
+ owner=owner,
+ host=self.host,
+ runas=runas_str,
+ nopasswd=nopasswd_str,
+ setenv=setenv_str,
+ commands=commands_str
+ )
+
+ def validate(self):
+ if self.validation == 'absent':
+ return
+
+ visudo_path = self.module.get_bin_path('visudo', required=self.validation == 'required')
+ if visudo_path is None:
+ return
+
+ check_command = [visudo_path, '-c', '-f', '-']
+ rc, stdout, stderr = self.module.run_command(check_command, data=self.content())
+
+ if rc != 0:
+ raise Exception('Failed to validate sudoers rule:\n{stdout}'.format(stdout=stdout))
+
+ def run(self):
+ if self.state == 'absent':
+ if self.exists():
+ self.delete()
+ return True
+ else:
+ return False
+
+ self.validate()
+
+ if self.exists() and self.matches():
+ return False
+
+ self.write()
+ return True
+
+
+def main():
+ argument_spec = {
+ 'commands': {
+ 'type': 'list',
+ 'elements': 'str',
+ },
+ 'group': {},
+ 'name': {
+ 'required': True,
+ },
+ 'nopassword': {
+ 'type': 'bool',
+ 'default': True,
+ },
+ 'setenv': {
+ 'type': 'bool',
+ 'default': False,
+ },
+ 'host': {
+ 'type': 'str',
+ 'default': 'ALL',
+ },
+ 'runas': {
+ 'type': 'str',
+ 'default': None,
+ },
+ 'sudoers_path': {
+ 'type': 'str',
+ 'default': '/etc/sudoers.d',
+ },
+ 'state': {
+ 'default': 'present',
+ 'choices': ['present', 'absent'],
+ },
+ 'user': {},
+ 'validation': {
+ 'default': 'detect',
+ 'choices': ['absent', 'detect', 'required']
+ },
+ }
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=[['user', 'group']],
+ supports_check_mode=True,
+ required_if=[('state', 'present', ['commands'])],
+ )
+
+ sudoers = Sudoers(module)
+
+ try:
+ changed = sudoers.run()
+ module.exit_json(changed=changed)
+ except Exception as e:
+ module.fail_json(msg=to_native(e))
+
+
+if __name__ == '__main__':
+ main()