Tengo un libro de jugadas de Ansible que uso para administrar nuestros archivos sudoers en nuestro entorno. Nos gusta mantener un archivo sudoers mínimo en /etc/sudoers, luego todo lo que queramos agregar se coloca en archivos separados en /etc/sudoers.d.
Mi manual de Ansible contiene la siguiente tarea para enviar estos archivos:
- name: copy sudoers files
copy:
src: "{{ item }}"
dest: "/etc/sudoers.d/{{ item }}"
backup: yes
owner: root
group: root
mode: 0440
validate: /usr/sbin/visudo -cf %s
with_items:
- admins
- apache
- monitor
La tarea contiene una cláusula de validación para garantizar que el archivo sea válido antes de confirmarlo y, en general, esto ha funcionado bien. Sin embargo, hoy me encontré con un problema donde una actualización rompió sudo. El archivo pasó el paso de validación, pero contenía un User_Alias con el mismo nombre que un User_Alias en el archivo principal /etc/sudoers. Cualquier intento de ejecutar sudo después de eso resultó en un error de análisis.
Mi pregunta es la siguiente: ¿cómo pruebo las actualizaciones de mis archivos sudoers de Ansible que puedan detectar errores como este? Una vez que el archivo está en su lugar, el error se puede detectar ejecutando visudo -c
, pero incluirlo como paso de validación no funciona. Ansible requiere el %s
marcador de posición, e incluso si no lo hiciera, la validación se realiza antes de copiar el archivo en su lugar para que visudo -c
no lo detecte.
Respuesta1
¿Has probado esto?
- copy:
src: '{{ item }}'
dest: '/etc/sudoers.d/{{ item }}'
owner: root
group: root
mode: 0440
validate: 'bash -c "cat /etc/sudoers %s | visudo -cf-"'
Esto funciona para mi.
Respuesta2
Lo hice funcionar. Esto es lo que hice. Primero agregué un conjunto de tareas de Ansible para crear un directorio provisional en /etc/sudoers.stage.d y copié el contenido de /etc/sudoers.d en él. Luego subo los archivos a esta área de preparación y, si alguno de ellos cambia, ejecuto un script personalizado para activarlos.
Así es como se ve ahora la lógica del libro de jugadas
- name: delete staging area
file:
path: "/etc/sudoers.stage.d"
state: absent
changed_when: false
- name: copy /etc/sudoers.d to staging area
shell: "cp -rp /etc/sudoers.d /etc/sudoers.stage.d"
changed_when: false
- name: stage sudoers files
copy:
src: "{{item}}"
dest: "/etc/sudoers.stage.d/{{item}}"
backup: yes
owner: root
group: root
mode: 0440
validate: /usr/sbin/visudo -cf %s
with_items:
- admins
- apache
- monitor
register: sudoers_d
- block:
- name: push out activate script
copy:
src: activate_sudoers.sh
dest: /usr/local/bin/activate_sudoers.sh
owner: root
group: root
mode: 0700
- name: activate change
shell: /bin/sh /usr/local/bin/activate_sudoers.sh /etc/sudoers.stage.d
when: sudoers_d.changed
y así es como se ve el script enable_sudoers.sh.
#!/bin/sh
function usage {
echo "Usage: $0 <stage directory>" >&2
exit 1
}
function abort {
echo "*** Error detected" >&2
[ "$#" -gt 0 ] && echo "***" $@ >&2
exit 1
}
PATH=/usr/bin:/bin:/usr/sbin:/sbin
export PATH
test $# -eq 1 || usage
test -d "$1" || abort "Stage directory $1: missing or not a directory"
test -d /etc/sudoers.old.d && rm -rf /etc/sudoers.old.d
test -d /etc/sudoers.old.d && abort "Failed to remove /etc/sudoers.old.d"
mv /etc/sudoers.d /etc/sudoers.old.d \
&& mv "$1" /etc/sudoers.d \
&& visudo -c
if [ $? -eq 0 ]; then
# Success - clean up
rm -rf /etc/sudoers.old.d
exit 0
else
# Failure - roll back
rm -rf /etc/sudoers.d
mv /etc/sudoers.old.d /etc/sudoers.d
abort "sudoers update failed"
fi
Es un poco más largo y complejo de lo que esperaba, pero hace el trabajo. Con suerte, esto será útil para cualquiera que tenga el mismo problema.
Respuesta3
Haciendo lo mismo solo con los módulos de Ansible.
- name: Creating assemble directory
file:
path: /etc/sudoers.stage.d
state: directory
mode: 0600
- name: Applying sudoers.d files on assemble directory
template:
src: "{{ item }}.j2"
dest: /etc/sudoers.stage.d/{{ item }}
mode: 0400
loop:
- 10-wheel
- 20-sys
register: check
- name: Copying original sudoers to the assemble directory
copy:
remote_src: yes
src: /etc/sudoers
dest: /etc/sudoers.stage.d/99-sudoers
when: check.changed
- name: Removing include line from 99-sudoers
lineinfile:
path: /etc/sudoers.stage.d/99-sudoers
state: absent
regex: ^#includedir /etc/sudoers.d
when: check.changed
- name: Assembling unique config for validation
assemble:
src: /etc/sudoers.stage.d
dest: /etc/sudoers.stage
mode: 0600
validate: visudo -cf %s
when: check.changed
- name: Applying sudoers configurations
template:
src: "{{ item }}.j2"
dest: /etc/sudoers.d/{{ item }}
mode: 0440
loop:
- 10-wheel
- 20-sys
when: check.changed