Suponha que um cliente tenha solicitado que você ajude na migração de script para implementar um arquivo de sudoers centralizados nos servidores RHEL e AIX.

Bem, esse é um cenário muito comum e, com seu exemplo, você pode demonstrar o uso dos recursos avançados do Ansible, bem como a maneira como a abordagem muda - de um script que executa uma determinada tarefa, a uma descrição idempotente (sem fazer alterações) e a monitorar a conformidade com o estado da instância.
Veja o script:
Existem 212 linhas de código, sem controle de versão no arquivo sudoers. O cliente já possui um processo que é executado uma vez por semana e verifica a soma de verificação do arquivo para garantir a segurança. Embora o script contenha uma referência ao Solaris, para este cliente, não precisamos transferir esse requisito.
Vamos começar criando uma função e colocando o arquivo sudoers no Git para controle de versão. Entre outras coisas, isso nos permitirá livrar da necessidade de montar volumes NFS.
Com os parâmetros de validação e backup dos módulos de
cópia e
modelo , podemos eliminar a necessidade de escrever código para fazer backup e restaurar o arquivo. Nesse caso, a validação é realizada antes do arquivo ser colocado no ponto de destino e, se a validação falhar, o módulo emitirá um erro.
Para cada função, precisamos especificar tarefas, modelos e variáveis. Aqui está a estrutura do arquivo correspondente:

O arquivo de script de função (playbook),
sudoers.yml , possui uma estrutura simples:
---
##
# Role playbook
##
- hosts: all
roles:
- sudoers
...
As variáveis de função estão localizadas no
arquivo vars / main.yml . Esse arquivo contém o arquivo de soma de verificação e inclui / exclui diretivas que serão usadas para criar lógica especial para ignorar os hosts Lawson e incluir o arquivo sudoers.d apenas nos hosts hd.
Aqui está o conteúdo do
arquivo vars / main.yml :
---
MD5FILE: /root/.sudoer.md5
EXCLUDE: la
INCLUDE: hd
...
Se usarmos os
módulos copy e
lineinfile , a função não será idempotente. O módulo de cópia instalará o arquivo base e o lineinfile será reinserido sempre que for iniciado. Como essa função será lançada na
Ansible Tower , a idempotência é uma obrigação. Converteremos o arquivo em um modelo jinja2.
Na primeira linha, adicionamos o seguinte comando para
controlar espaços e recuos :
#jinja2: lstrip_blocks: True, trim_blocks: True
Observe que as versões mais recentes do módulo de
modelo incluem parâmetros para
trim_blocks (adicionados no Ansible 2.4).
Aqui está o código que insere a linha de
inclusão no final do arquivo:
{% if ansible_hostname[0:2] == INCLUDE %}
#includedir /etc/sudoers.d
{% endif %}
Usamos a construção condicional ({% if%}, {% endif%}) para o comando shell que insere uma linha para hosts cujos nomes começam com os caracteres "hd". Usamos fatos Ansible e um filtro [0: 2] para analisar o nome do host.
Agora vamos às tarefas. Primeiro, você precisa estabelecer um fato para analisar o nome do host. Usaremos o fato "parhost" na construção condicional.
---
##
# Parse hostnames to grab 1st 2 characters
##
- name: "Parse hostname's 1st 2 characters"
set_fact: parhost={{ ansible_hostname[0:2] }}
Não há parâmetro
csum no servidor de estoque RHEL. Se necessário, podemos usar outro fato para indicar condicionalmente o nome do arquivo binário com a soma de verificação. Observe que código adicional pode ser necessário se esses recursos forem diferentes no AIX, Solaris e Linux.
Além disso, temos que resolver o problema com as diferenças nos grupos raiz no AIX e RHEL.
##
# Conditionally set name of checksum binary
##
- name: "set checksum binary"
set_fact:
csbin: "{{ 'cksum' if (ansible_distribution == 'RedHat') else 'csum' }}"
##
# Conditionally set name of root group
##
- name: "set system group"
set_fact:
sysgroup: "{{ 'root' if (ansible_distribution == 'RedHat') else 'sys' }}"
O uso de blocos nos permite definir a condição para toda a tarefa. Usaremos a condição no final do bloco para excluir hosts "la".
##
# Enclose in block so we can use parhost to exclude hosts
##
- block:
O módulo de modelo valida e instala o arquivo. Fixamos o resultado para que possamos determinar se a tarefa foi alterada. O uso do parâmetro validate neste módulo permite verificar a validade do novo arquivo sudoer antes de colocá-lo no host.
##
# Validate will prevent bad files, no need to revert
# Jinja2 template will add include line
##
- name: Ensure sudoers file
template:
src: sudoers.j2
dest: /etc/sudoers
owner: root
group: "{{ sysgroup }}"
mode: 0440
backup: yes
validate: /usr/sbin/visudo -cf %s
register: sudochg
Se um novo modelo foi instalado, execute o script de shell para gerar o arquivo de soma de verificação. A construção condicional atualiza o arquivo de soma de verificação ao instalar o modelo sudoers ou se o arquivo de soma de verificação estiver ausente. Como o processo em execução também monitora outros arquivos, usamos o código do shell fornecido no script de origem:
- name: sudoers checksum
shell: "grep -v '/etc/sudoers' {{ MD5FILE }} > {{ MD5FILE }}.tmp ; {{ csbin }} /etc/sudoers >> {{ MD5FILE }} ; mv {{ MD5FILE }}.tmp {{ MD5FILE }}"
when: sudochg.changed or MD5STAT.exists == false
O módulo de arquivo verifica a instalação das permissões necessárias:
- name: Ensure MD5FILE permissions
file:
path: "{{ MD5FILE }}"
owner: root
group: "{{ sysgroup }}"
mode: 0600
state: file
Como o parâmetro backup não fornece nenhuma opção para o processamento de backups anteriores, teremos que cuidar de criar o código apropriado. No exemplo abaixo, usamos o parâmetro "register" e o campo "stdout_lines" para isso.
##
# List and clean up backup files. Retain 3 copies.
##
- name: List /etc/sudoers.*~ files
shell: "ls -t /etc/sudoers*~ |tail -n +4"
register: LIST_SUDOERS
changed_when: false
- name: Cleanup /etc/sudoers.*~ files
file:
path: "{{ item }}"
state: absent
loop: "{{ LIST_SUDOERS.stdout_lines }}"
when: LIST_SUDOERS.stdout_lines != ""
Conclusão do bloco:
##
# This conditional restricts what hosts this block runs on
##
when: parhost != EXCLUDE
...
O caso de uso pretendido é desempenhar esse papel na Ansible Tower. Os alertas de torre da Ansible podem ser configurados para que, no caso de uma falha na execução da tarefa, os alertas sejam enviados para o e-mail, para o Slack ou de alguma outra maneira. Esta função é executada em Ansible, Ansible Engine ou Ansible Tower.
Como resultado, removemos tudo o que era supérfluo do script e criamos uma função completamente idempotente que pode fornecer o estado desejado do arquivo sudoers. O uso do SCM permite controle de versão, fornece gerenciamento de mudanças e transparência mais eficientes. CI / CDs com Jenkins ou outras ferramentas permitem testes automatizados de código Ansible para alterações futuras. A função de Auditor na Ansible Tower permite monitorar e reforçar os requisitos organizacionais.
O código para trabalhar com somas de verificação pode ser removido do script, mas, para isso, o cliente precisaria primeiro consultar seu serviço de segurança. Se necessário, o padrão sudoers pode ser protegido com o Ansible Vault. Por fim, o uso de grupos evita a escrita da lógica usando inclusões e exclusões.
→ Você pode baixar a função do GitHub neste
link