From 49bd9cbd3c9f888244163cf2eddc1a5984e3503e Mon Sep 17 00:00:00 2001 From: adaniaud Date: Fri, 23 Feb 2024 05:36:45 +0000 Subject: [PATCH] Add noexec support to sudoers (#7983) * Add noexec support to sudoers * Add changelog fragment #7983 * Fix yml formatting in fragment 7983 * Apply suggestions from code review Co-authored-by: Felix Fontein --------- Co-authored-by: Felix Fontein --- .../7983-sudoers-add-support-noexec.yml | 2 ++ plugins/modules/sudoers.py | 24 ++++++++++++++++++- .../targets/sudoers/tasks/main.yml | 15 ++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/7983-sudoers-add-support-noexec.yml diff --git a/changelogs/fragments/7983-sudoers-add-support-noexec.yml b/changelogs/fragments/7983-sudoers-add-support-noexec.yml new file mode 100644 index 0000000000..f58e6f7ec8 --- /dev/null +++ b/changelogs/fragments/7983-sudoers-add-support-noexec.yml @@ -0,0 +1,2 @@ +minor_changes: + - sudoers - add support for the ``NOEXEC`` tag in sudoers rules (https://github.com/ansible-collections/community.general/pull/7983). diff --git a/plugins/modules/sudoers.py b/plugins/modules/sudoers.py index f76f6ccc60..a392b4adfa 100644 --- a/plugins/modules/sudoers.py +++ b/plugins/modules/sudoers.py @@ -45,6 +45,12 @@ options: - The name of the sudoers rule. - This will be used for the filename for the sudoers file managed by this rule. type: str + noexec: + description: + - Whether a command is prevented to run further commands itself. + default: false + type: bool + version_added: 8.4.0 nopassword: description: - Whether a password will be required to run the sudo'd command. @@ -143,6 +149,15 @@ EXAMPLES = ''' user: alice commands: /usr/local/bin/upload setenv: true + +- name: >- + Allow alice to sudo /usr/bin/less but prevent less from + running further commands itself + community.general.sudoers: + name: allow-alice-restricted-less + user: alice + commands: /usr/bin/less + noexec: true ''' import os @@ -162,6 +177,7 @@ class Sudoers(object): self.user = module.params['user'] self.group = module.params['group'] self.state = module.params['state'] + self.noexec = module.params['noexec'] self.nopassword = module.params['nopassword'] self.setenv = module.params['setenv'] self.host = module.params['host'] @@ -205,13 +221,15 @@ class Sudoers(object): owner = '%{group}'.format(group=self.group) commands_str = ', '.join(self.commands) + noexec_str = 'NOEXEC:' if self.noexec else '' 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( + return "{owner} {host}={runas}{noexec}{nopasswd}{setenv} {commands}\n".format( owner=owner, host=self.host, runas=runas_str, + noexec=noexec_str, nopasswd=nopasswd_str, setenv=setenv_str, commands=commands_str @@ -258,6 +276,10 @@ def main(): 'name': { 'required': True, }, + 'noexec': { + 'type': 'bool', + 'default': False, + }, 'nopassword': { 'type': 'bool', 'default': True, diff --git a/tests/integration/targets/sudoers/tasks/main.yml b/tests/integration/targets/sudoers/tasks/main.yml index dd62025d5e..36397f41ad 100644 --- a/tests/integration/targets/sudoers/tasks/main.yml +++ b/tests/integration/targets/sudoers/tasks/main.yml @@ -159,6 +159,20 @@ src: "{{ sudoers_path }}/my-sudo-rule-8" register: rule_8_contents +- name: Create rule with noexec parameters + community.general.sudoers: + name: my-sudo-rule-9 + state: present + user: alice + commands: /usr/local/bin/command + noexec: true + register: rule_9 + +- name: Grab contents of my-sudo-rule-9 + ansible.builtin.slurp: + src: "{{ sudoers_path }}/my-sudo-rule-9" + register: rule_9_contents + - name: Revoke rule 1 community.general.sudoers: name: my-sudo-rule-1 @@ -257,6 +271,7 @@ - "rule_6_contents['content'] | b64decode == 'alice ALL=(bob)NOPASSWD: /usr/local/bin/command\n'" - "rule_7_contents['content'] | b64decode == 'alice host-1=NOPASSWD: /usr/local/bin/command\n'" - "rule_8_contents['content'] | b64decode == 'alice ALL=NOPASSWD:SETENV: /usr/local/bin/command\n'" + - "rule_9_contents['content'] | b64decode == 'alice ALL=NOEXEC:NOPASSWD: /usr/local/bin/command\n'" - name: Check revocation stat ansible.builtin.assert: