From 3fe6f6831613f664b3f96047874b88df4060ee77 Mon Sep 17 00:00:00 2001 From: sjat Date: Thu, 18 Jun 2026 21:36:31 +0200 Subject: [PATCH] feat(base): codify AI-worker NOPASSWD sudo (ADR-015 amended) Add base__ai_worker_user var (default empty), a new operational_access.yml task file that drops a validated sudoers file for the named user, and wire it into base/tasks/main.yml after the hardening includes under the `users` tag. Set base__ai_worker_user: claude in group_vars/control so that applying base to ubongo is idempotent with the manual /etc/sudoers.d/claude-ai-worker drop-in already in place. Password remains locked; NOPASSWD is the only sudo path; actions are attributed via auditd (ADR-021). Co-Authored-By: Claude Opus 4.8 (1M context) --- inventories/production/group_vars/control/vars.yml | 3 +++ roles/base/defaults/main.yml | 5 +++++ roles/base/tasks/main.yml | 7 +++++++ roles/base/tasks/operational_access.yml | 11 +++++++++++ 4 files changed, 26 insertions(+) create mode 100644 roles/base/tasks/operational_access.yml diff --git a/inventories/production/group_vars/control/vars.yml b/inventories/production/group_vars/control/vars.yml index b4e64ee..6f06074 100644 --- a/inventories/production/group_vars/control/vars.yml +++ b/inventories/production/group_vars/control/vars.yml @@ -12,6 +12,9 @@ dev_env__users: # group only. ansible_user: sjat +# ubongo's AI-worker; passwordless sudo for the claude user (ADR-015 amended). +base__ai_worker_user: claude + # ubongo is a NetBird mesh peer (ADR-016, M5) — enrol the agent via base's `mesh` concern. # Enrollment only; the host firewall default-deny stays deferred (the mesh-hardening # follow-on), so this brings up wt0 without changing SSH exposure. diff --git a/roles/base/defaults/main.yml b/roles/base/defaults/main.yml index 6073a1a..301dee7 100644 --- a/roles/base/defaults/main.yml +++ b/roles/base/defaults/main.yml @@ -29,6 +29,11 @@ base__ssh_authorised_keys: [] base__ssh_listen_mesh_only: false base__ssh_listen_addr: "" +# The automation/AI-worker user granted passwordless sudo (ADR-015 amended / ADR-021). +# Empty = no AI-worker sudo. Set per-group (e.g. group_vars/control: claude). The user's +# password should be locked so NOPASSWD is its only sudo path; actions are auditd-attributed. +base__ai_worker_user: "" + # NetBird mesh agent enrollment (ADR-016). Opt-in: default off so applying `base` to a # host not on the mesh is a no-op for this concern. The live actions (apt install over # the network, `netbird up` against the coordinator) are additionally gated by diff --git a/roles/base/tasks/main.yml b/roles/base/tasks/main.yml index b022586..b40e305 100644 --- a/roles/base/tasks/main.yml +++ b/roles/base/tasks/main.yml @@ -23,6 +23,13 @@ tags: [hardening] tags: [hardening] +- name: AI-worker operational access (sudoers drop-in) + ansible.builtin.include_tasks: + file: operational_access.yml + apply: + tags: [users] + tags: [users] + - name: NetBird mesh enrollment ansible.builtin.include_tasks: file: mesh.yml diff --git a/roles/base/tasks/operational_access.yml b/roles/base/tasks/operational_access.yml new file mode 100644 index 0000000..3a27a18 --- /dev/null +++ b/roles/base/tasks/operational_access.yml @@ -0,0 +1,11 @@ +--- +- name: Grant the AI-worker user passwordless sudo (ADR-015 amended / ADR-021) + ansible.builtin.copy: + content: "{{ base__ai_worker_user }} ALL=(ALL) NOPASSWD:ALL\n" + dest: "/etc/sudoers.d/{{ base__ai_worker_user }}-ai-worker" + owner: root + group: root + mode: "0440" + validate: "visudo -cf %s" + when: base__ai_worker_user | length > 0 + tags: [users]