From 05777e9f8158752391bc51d0f76594dc91d1b9be Mon Sep 17 00:00:00 2001 From: Laurence Date: Sun, 18 Dec 2022 11:33:57 +0000 Subject: [PATCH 1/6] Add support to restrict privileges by host --- plugins/modules/sudoers.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/plugins/modules/sudoers.py b/plugins/modules/sudoers.py index 2c0aa879bca..b1c8a6f14e4 100644 --- a/plugins/modules/sudoers.py +++ b/plugins/modules/sudoers.py @@ -43,6 +43,11 @@ - 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 runas: description: - Specify the target user the command(s) will run as. @@ -95,10 +100,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 +142,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 +185,7 @@ 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 +232,10 @@ def main(): 'type': 'bool', 'default': True, }, + 'host': { + 'type': 'str', + 'default': 'ALL', + } 'runas': { 'type': 'str', 'default': None, From 93b105c62abbdb0a5f668dcd40f5e2a743182953 Mon Sep 17 00:00:00 2001 From: Laurence Date: Sun, 18 Dec 2022 11:35:47 +0000 Subject: [PATCH 2/6] Missing comma --- plugins/modules/sudoers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/sudoers.py b/plugins/modules/sudoers.py index b1c8a6f14e4..e6d4a2388a4 100644 --- a/plugins/modules/sudoers.py +++ b/plugins/modules/sudoers.py @@ -235,7 +235,7 @@ def main(): 'host': { 'type': 'str', 'default': 'ALL', - } + }, 'runas': { 'type': 'str', 'default': None, From ef18e1d400c7b0bb3c6f0510f3da68f19ddad340 Mon Sep 17 00:00:00 2001 From: Laurence Date: Sun, 18 Dec 2022 11:45:48 +0000 Subject: [PATCH 3/6] Making linter happy. --- plugins/modules/sudoers.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/plugins/modules/sudoers.py b/plugins/modules/sudoers.py index e6d4a2388a4..b5e17889c2f 100644 --- a/plugins/modules/sudoers.py +++ b/plugins/modules/sudoers.py @@ -185,7 +185,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} {host}={runas}{nopasswd} {commands}\n".format(owner=owner, host=self.host, 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': @@ -233,8 +239,8 @@ def main(): 'default': True, }, 'host': { - 'type': 'str', - 'default': 'ALL', + 'type': 'str', + 'default': 'ALL', }, 'runas': { 'type': 'str', From 8a914c0e8dc03407cda082bf03629e4ef7dfabb9 Mon Sep 17 00:00:00 2001 From: Laurence Date: Mon, 19 Dec 2022 09:11:22 +0000 Subject: [PATCH 4/6] Add version 6.2.0 as when sudoers host parameter added Co-authored-by: Felix Fontein --- plugins/modules/sudoers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/modules/sudoers.py b/plugins/modules/sudoers.py index b5e17889c2f..f2bcb20b75b 100644 --- a/plugins/modules/sudoers.py +++ b/plugins/modules/sudoers.py @@ -48,6 +48,7 @@ - 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. From 0d4a3097d54a3fb471b0119b49051d166ec0daa7 Mon Sep 17 00:00:00 2001 From: Laurence Date: Mon, 19 Dec 2022 10:28:37 +0000 Subject: [PATCH 5/6] Changelog fragment for PR #5703 --- changelogs/fragments/5703-sudoers-host-support.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelogs/fragments/5703-sudoers-host-support.yml 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). From cd0bb5922eb3f28dd1d660c13b527d61a1df5c3a Mon Sep 17 00:00:00 2001 From: Laurence Date: Tue, 20 Dec 2022 09:20:46 +0000 Subject: [PATCH 6/6] Test for sudoers host-based restriction --- tests/integration/targets/sudoers/tasks/main.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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: