diff --git a/changelogs/fragments/5883-sudoers-add-support-for-setenv-parameter.yml b/changelogs/fragments/5883-sudoers-add-support-for-setenv-parameter.yml new file mode 100644 index 0000000000..f713428136 --- /dev/null +++ b/changelogs/fragments/5883-sudoers-add-support-for-setenv-parameter.yml @@ -0,0 +1,2 @@ +minor_changes: + - sudoers - add ``setenv`` parameters to support passing environment variables via sudo. (https://github.com/ansible-collections/community.general/pull/5883) diff --git a/plugins/modules/sudoers.py b/plugins/modules/sudoers.py index f2bcb20b75..fd29b189f0 100644 --- a/plugins/modules/sudoers.py +++ b/plugins/modules/sudoers.py @@ -43,6 +43,12 @@ options: - 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. @@ -123,6 +129,13 @@ EXAMPLES = ''' 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 @@ -143,6 +156,7 @@ class Sudoers(object): 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'] @@ -185,12 +199,14 @@ class Sudoers(object): 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} {commands}\n".format( + 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 ) @@ -239,6 +255,10 @@ def main(): 'type': 'bool', 'default': True, }, + 'setenv': { + 'type': 'bool', + 'default': False, + }, 'host': { 'type': 'str', 'default': 'ALL', diff --git a/tests/integration/targets/sudoers/tasks/main.yml b/tests/integration/targets/sudoers/tasks/main.yml index a44307ad9e..dd62025d5e 100644 --- a/tests/integration/targets/sudoers/tasks/main.yml +++ b/tests/integration/targets/sudoers/tasks/main.yml @@ -145,6 +145,20 @@ src: "{{ sudoers_path }}/my-sudo-rule-7" register: rule_7_contents +- name: Create rule with setenv parameters + community.general.sudoers: + name: my-sudo-rule-8 + state: present + user: alice + commands: /usr/local/bin/command + setenv: true + register: rule_8 + +- name: Grab contents of my-sudo-rule-8 + ansible.builtin.slurp: + src: "{{ sudoers_path }}/my-sudo-rule-8" + register: rule_8_contents + - name: Revoke rule 1 community.general.sudoers: name: my-sudo-rule-1 @@ -202,7 +216,6 @@ when: ansible_os_family != 'Darwin' register: edge_case_3 - - name: Revoke non-existing rule community.general.sudoers: name: non-existing-rule @@ -243,6 +256,7 @@ - "rule_5_contents['content'] | b64decode == 'alice ALL=NOPASSWD: /usr/local/bin/command\n'" - "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'" - name: Check revocation stat ansible.builtin.assert: