security: always restore sudoers after makepkg, drop dead paru block

The archlinux and zfs roles temporarily write `%wheel ALL=(ALL) NOPASSWD: ALL`
to /etc/sudoers so makepkg/paru can run unattended, then restore it in a
following task. Since the restore was a plain sibling task, any failure in
between aborted the play and left the host with passwordless root for the
whole wheel group. Move the restore into `always:` so it runs on failure too,
and narrow the window in yay.yml to just the makepkg call (dependency install,
directory setup and git clone no longer run inside it).

This is not hypothetical: a provisioning run of omega failed on paru's git
clone and left the sudoers hack in place.

The clone failed because the deprecated compile-from-source paru block, which
was commented out in tree, had been re-enabled. It fed paru_git_repo into the
git module, but that variable now holds a GitHub owner/repo slug for the
releases API rather than a clone URL. Remove the block outright, along with
the orphaned paru_src_path default; paru is installed from the release binary.

Also fix the ansible-lint violations this surfaced so roles/archlinux passes
the production profile: explicit `become` next to `become_user`, a pinned
branch on the yay clone, and `changed_when` on the command/shell tasks.
This commit is contained in:
Clément Désiles
2026-08-18 21:52:49 +02:00
parent 1970564c8f
commit 174a96eefc
5 changed files with 43 additions and 82 deletions
+1 -2
View File
@@ -2,6 +2,5 @@
arch_locale: en_US.UTF-8 arch_locale: en_US.UTF-8
yay_src_path: /opt/yay yay_src_path: /opt/yay
yay_git_repo: https://aur.archlinux.org/yay.git yay_git_repo: https://aur.archlinux.org/yay.git
paru_git_repo: Morganamilo/paru paru_git_repo: Morganamilo/paru # GitHub owner/repo slug, used for the releases API
paru_src_path: /opt/paru
os_arch: x86_64 # or aarch64 os_arch: x86_64 # or aarch64
+1
View File
@@ -34,6 +34,7 @@
reflector --latest 20 --protocol https --sort rate reflector --latest 20 --protocol https --sort rate
--save /etc/pacman.d/mirrorlist --save /etc/pacman.d/mirrorlist
when: mirrorlist_bak.stat.exists is false or (mirrorlist.stat.exists and (ansible_date_time.epoch | int - mirrorlist.stat.mtime) > 604800) when: mirrorlist_bak.stat.exists is false or (mirrorlist.stat.exists and (ansible_date_time.epoch | int - mirrorlist.stat.mtime) > 604800)
changed_when: true
- name: Configure pacman to output colors - name: Configure pacman to output colors
ansible.builtin.lineinfile: ansible.builtin.lineinfile:
+4 -57
View File
@@ -6,53 +6,6 @@
- name: Install paru - name: Install paru
when: not paru.stat.exists when: not paru.stat.exists
##
## Deprecated version with compilation
##
# - name: Install paru
# block:
# - name: Install build dependencies
# package:
# name:
# - base-devel
# - git
# state: present
# - name: Disable sudo password prompt (makepkg sudoers hack)
# lineinfile:
# dest: /etc/sudoers
# state: present
# regexp: "^#?%wheel"
# line: "%wheel ALL=(ALL) NOPASSWD: ALL"
# validate: /usr/sbin/visudo -cf %s
# - name: Create paru sources dir
# file:
# path: "{{ paru_src_path }}"
# state: directory
# owner: "{{ ansible_user }}"
# - name: Clone git sources
# become: false
# git:
# repo: "{{ paru_git_repo }}"
# dest: "{{ paru_src_path }}"
# # note: this only works because SUDOERS password prompt is disabled
# - name: Build and install
# become: false
# command:
# chdir: "{{ paru_src_path }}"
# cmd: "makepkg -si -f --noconfirm"
# - name: Restore sudo with password prompt
# lineinfile:
# dest: /etc/sudoers
# state: present
# regexp: "^#?%wheel"
# line: "%wheel ALL=(ALL:ALL) ALL"
# validate: /usr/sbin/visudo -cf %s
# when: not paru.stat.exists
block: block:
- name: Get the last github release - name: Get the last github release
ansible.builtin.uri: ansible.builtin.uri:
@@ -60,10 +13,6 @@
return_content: true return_content: true
register: paru_release register: paru_release
- name: Extract tag_name
ansible.builtin.set_fact:
paru_version: "{{ (paru_release.json.tag_name | regex_replace('^v', '')) }}"
- name: Get the binary URL ({{ os_arch }}) - name: Get the binary URL ({{ os_arch }})
ansible.builtin.set_fact: ansible.builtin.set_fact:
paru_url: "{{ item.browser_download_url }}" paru_url: "{{ item.browser_download_url }}"
@@ -91,12 +40,10 @@
remote_src: true remote_src: true
mode: "0755" mode: "0755"
- name: Ensure permissions
ansible.builtin.file:
path: /usr/bin/paru
mode: "0755"
- name: Cleanup - name: Cleanup
ansible.builtin.file: ansible.builtin.file:
path: "/tmp/paru-{{ os_arch }}.tar.zst" path: "{{ item }}"
state: absent state: absent
loop:
- "/tmp/paru-{{ os_arch }}.tar.zst"
- /tmp/paru
+31 -21
View File
@@ -14,14 +14,6 @@
- git - git
state: present state: present
- name: Disable sudo password prompt (makepkg sudoers hack)
ansible.builtin.lineinfile:
dest: /etc/sudoers
state: present
regexp: "^#?%wheel"
line: "%wheel ALL=(ALL) NOPASSWD: ALL"
validate: /usr/sbin/visudo -cf %s
- name: Clean up stale yay sources dir - name: Clean up stale yay sources dir
ansible.builtin.file: ansible.builtin.file:
path: "{{ yay_src_path }}" path: "{{ yay_src_path }}"
@@ -35,22 +27,40 @@
mode: "0755" mode: "0755"
- name: Clone git sources - name: Clone git sources
become: true
become_user: "{{ ansible_user }}" become_user: "{{ ansible_user }}"
ansible.builtin.git: ansible.builtin.git:
repo: "{{ yay_git_repo }}" repo: "{{ yay_git_repo }}"
dest: "{{ yay_src_path }}" dest: "{{ yay_src_path }}"
version: master
# note: this only works because SUDOERS password prompt is disabled # makepkg refuses to run as root and shells out to sudo to install the
- name: Build and install # built package, so `--noconfirm` needs a passwordless sudo window.
become_user: "{{ ansible_user }}" # Keep that window as small as possible and always close it: the restore
ansible.builtin.command: # lives in `always` so it runs even when the build fails, which otherwise
chdir: "{{ yay_src_path }}" # leaves the host with passwordless root for the whole wheel group.
cmd: "makepkg -si -f --noconfirm" - name: Build and install yay
block:
- name: Disable sudo password prompt (makepkg sudoers hack)
ansible.builtin.lineinfile:
dest: /etc/sudoers
state: present
regexp: "^#?%wheel"
line: "%wheel ALL=(ALL) NOPASSWD: ALL"
validate: /usr/sbin/visudo -cf %s
- name: Restore sudo with password prompt - name: Build and install
ansible.builtin.lineinfile: become: true
dest: /etc/sudoers become_user: "{{ ansible_user }}"
state: present ansible.builtin.command:
regexp: "^#?%wheel" chdir: "{{ yay_src_path }}"
line: "%wheel ALL=(ALL:ALL) ALL" cmd: "makepkg -si -f --noconfirm"
validate: /usr/sbin/visudo -cf %s changed_when: true
always:
- name: Restore sudo with password prompt
ansible.builtin.lineinfile:
dest: /etc/sudoers
state: present
regexp: "^#?%wheel"
line: "%wheel ALL=(ALL:ALL) ALL"
validate: /usr/sbin/visudo -cf %s
+6 -2
View File
@@ -5,6 +5,8 @@
changed_when: false changed_when: false
failed_when: false failed_when: false
# The restore lives in `always` so it runs even when paru fails. Otherwise a
# failed build leaves the host with passwordless root for the whole wheel group.
- name: Install zfs - name: Install zfs
when: zfs_dkms_installed.stderr | length > 0 when: zfs_dkms_installed.stderr | length > 0
block: block:
@@ -24,7 +26,8 @@
become: false become: false
ansible.builtin.command: ansible.builtin.command:
cmd: "paru -S --noconfirm zfs-dkms zfs-utils" cmd: "paru -S --noconfirm zfs-dkms zfs-utils"
changed_when: true
always:
- name: Restore SUDOERS password prompt after paru - name: Restore SUDOERS password prompt after paru
no_log: true no_log: true
ansible.builtin.lineinfile: ansible.builtin.lineinfile:
@@ -65,7 +68,8 @@
become: false become: false
ansible.builtin.command: ansible.builtin.command:
cmd: "paru -S --noconfirm zrepl" cmd: "paru -S --noconfirm zrepl"
changed_when: true
always:
- name: Restore SUDOERS password prompt after paru - name: Restore SUDOERS password prompt after paru
no_log: true no_log: true
ansible.builtin.lineinfile: ansible.builtin.lineinfile: