diff --git a/changelogs/fragments/5703-sudoers-host-support.yml b/changelogs/fragments/5703-sudoers-host-support.yml new file mode 100644 index 00000000000..1aaa30d8d9b --- /dev/null +++ b/changelogs/fragments/5703-sudoers-host-support.yml @@ -0,0 +1,2 @@ +minor_changes: + - sudoers - adds ``host`` parameter for setting hostname restrictions in sudoers rules (https://github.com/ansible-collections/community.general/issues/5702). diff --git a/plugins/modules/sudoers.py b/plugins/modules/sudoers.py index 2c0aa879bca..f2bcb20b75b 100644 --- a/plugins/modules/sudoers.py +++ b/plugins/modules/sudoers.py @@ -43,6 +43,12 @@ - Whether a password will be required to run the sudo'd command. default: true type: bool + 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. @@ -95,10 +101,11 @@ - name: >- Allow the monitoring group to run sudo /usr/local/bin/gather-app-metrics - without requiring a password + 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: >- @@ -136,6 +143,7 @@ def __init__(self, module): self.group = module.params['group'] self.state = module.params['state'] self.nopassword = module.params['nopassword'] + 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) @@ -178,7 +186,13 @@ def content(self): commands_str = ', '.join(self.commands) nopasswd_str = 'NOPASSWD:' if self.nopassword else '' runas_str = '({runas})'.format(runas=self.runas) if self.runas is not None else '' - return "{owner} ALL={runas}{nopasswd} {commands}\n".format(owner=owner, runas=runas_str, nopasswd=nopasswd_str, commands=commands_str) + return "{owner} {host}={runas}{nopasswd} {commands}\n".format( + owner=owner, + host=self.host, + runas=runas_str, + nopasswd=nopasswd_str, + commands=commands_str + ) def validate(self): if self.validation == 'absent': @@ -225,6 +239,10 @@ def main(): 'type': 'bool', 'default': True, }, + 'host': { + 'type': 'str', + 'default': 'ALL', + }, 'runas': { 'type': 'str', 'default': None, diff --git a/tests/integration/targets/sudoers/tasks/main.yml b/tests/integration/targets/sudoers/tasks/main.yml index 682bd7efffe..a44307ad9ef 100644 --- a/tests/integration/targets/sudoers/tasks/main.yml +++ b/tests/integration/targets/sudoers/tasks/main.yml @@ -131,6 +131,19 @@ src: "{{ sudoers_path }}/my-sudo-rule-6" register: rule_6_contents +- name: Create rule to allow user to sudo just on host-1 + community.general.sudoers: + name: my-sudo-rule-7 + state: present + user: alice + host: host-1 + commands: /usr/local/bin/command + register: rule_7 + +- name: Grab contents of my-sudo-rule-7 + ansible.builtin.slurp: + src: "{{ sudoers_path }}/my-sudo-rule-7" + register: rule_7_contents - name: Revoke rule 1 community.general.sudoers: @@ -229,6 +242,7 @@ - "rule_4_contents['content'] | b64decode == '%students ALL=NOPASSWD: /usr/local/bin/command\n'" - "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'" - name: Check revocation stat ansible.builtin.assert: