Ansible: Criptografando Senhas de Usuários

As vezes queremos gerar uma senha fixa para usuários, a qual vamos reutilizar em várias máquinas.

Um exemplo típico é criar usuário administrador, capaz de realizar login com senha. Dessa forma, caso ocorra algum problema com o servidor SSH ou perca a sua chave privada junto com o seu HD, ainda é possível fazer login direto na máquina, por VNC ou por outros meios de acesso remoto.

Porém essa é a senha de administrador de muitos computadores e não queremos colocá-la diretamente em scripts de configuração, como os do Ansible, onde podem ser lidas diretamente.

Podemos, no entanto, criar uma versão encriptada da senha (a mesma usada no arquivo /etc/shadow) e aí sim colocá-la no script.

O código abaixo cria uma senha segura 24 caracteres utilizando pwgen. A opção -s indica que a senha deve ser completamente aleatória.

$ pwgen -s 24 1
> VONga6jUFoXCMQNhDlHgTHQY

Por fim usamos openssl passwd para encriptar a senha gerada Usamos a opção -6 para criptografar a senha com SHA512.

$ openssl passwd -6 'VONga6jUFoXCMQNhDlHgTHQY'
> $6$80Ll7KOwC/XDQVgZ$NwTjYG8cXcMTr9NI1EbtaX7OEAIBq3ULUSJPmpjtk/vUiyB9duYO3aNqmWmBnS.mjgaN37/mo34R8COzNrFBh0

Dessa forma, a preciosa senha está segura, longe de olhos curiosos, e podemos colocá-la no script de criação de usuários.

# Arquivo:  tasks/default-tasks.yml
---
- name: Criar usuário admin com senha
  ansible.builtin.user:
    name: admin
    comment: Usuário Administrador
    password: "$6$80Ll7KOwC/XDQVgZ$NwTjYG8cXcMTr9NI1EbtaX7OEAIBq3ULUSJPmpjtk/vUiyB9duYO3aNqmWmBnS.mjgaN37/mo34R8COzNrFBh0"

Ansible: Testando Playbooks

Ansible é uma excelente ferramenta para automatização de tarefas.

Cria-se uma lista de computadores e instalando neles um servidor SSH e Python 3, é possível automatizar a instalação de programas, alteração de configurações e, o meu favorito, atualização dos computadores.

No maior estilo do TDD (Test Driven Development), muitas vezes, queremos ter certeza que algo está funcionando antes de prosseguir com a execução do playbook.

Exemplos destes casos são:

  • A nova configuração funciona?
  • O usuário realmente pode (não pode) usar usar sudo?
  • Login do usuário root está mesmo desativado?
  • O firewall liberou/bloqueou a porta HTTP?

Podemos criar testes como esses com certa facilidade e inclusive podemos escolher entre a máquina remota ou local para executar os comandos, o que é essencial para alguns testes.

Abaixo mostro dois exemplos, testando as permissões de um usuário do servidor remoto e testando se o computador local pode fazer login como root.

# Arquivo: tasks/testes.yml
---
- name: TESTE Verificar se o usuário 'admin' pode usar SUDO sem senha
  # Teste no servidor remoto:
  # - su admin -c "xxx": executa o comando xxx como admin;
  # - whoami: imprime o nome do usuário; e
  # - Esperamos que imprima 'admin' (sem o sudo) e em seguida
  #   imprima 'root' (com sudo).
  ansible.builtin.command: |
    su admin -c "whoami; sudo whoami"
  register: result
  changed_when: false
  failed_when: >
    result.stdout_lines[0] != 'admin'
    or result.stdout_lines[1] != 'root'
  timeout: 5

- name: TESTE Negar login do usuário 'root' port SSH
  # Teste no computador local:
  # - SSH, ativando senha e desativando chave pública;
  # - whoami: um comando qualquer para ser executado; e
  # - Esperamos que a tentativa de login seja negada.
  ansible.builtin.command: >
    ssh -oPasswordAuthentication=yes -oPubkeyAuthentication=no
    -p{{ ansible_port }} root@{{ ansible_host }} whoami
  register: result
  changed_when: false
  failed_when: "'Permission denied (publickey)' not in result.stderr"
  timeout: 5
  # Esta parte final definie que o teste é executado no computador local
  # (que executa o Ansible).
  # Desativamos ansible_become para que use o usuário atual, em vez de
  # usar sudo para se tornar root.
  delegate_to: localhost
  vars:
    ansible_become: false

Ansible: Variáveis Dependentes do Sistema Operacional

Ansible é uma excelente ferramenta para automatização de tarefas.

Cria-se uma lista de computadores e instalando neles um servidor SSH e Python 3, é possível automatizar a instalação de programas, alteração de configurações e, o meu favorito, atualização dos computadores.

No entanto, há coisas que são diferentes de um sistema para outro, por exemplo caminhos de arquivos de configuração e nomes de pacotes.

Para evitar criar várias e várias tarefas que fazem a mesma coisa ajustadas para cada sistema (when: ansible_distribution == ...), enchendo o playbook com código repetido, Ansible permite a importação de arquivos de variáveis com o módulo include_vars, onde podemos escolher dinamicamente o arquivo de variáveis correspondente ao sistema.

Então podemos criar um arquivo para cada um dos sistemas em uso e seguir em frente. Porém isso acaba por se tornar um pesadelo de manutenção: cada nova variável precisa ser adicionada em TODOS os arquivos de variáveis e, ao criar um arquivo de variáveis novo, é preciso atualizar todas as variáveis possíveis.

Uma alternativa interessante seria uma criar estrutura hierárquica, onde definimos valores padrão para todos, em seguida substituímos com os valores para a família, então com os valores para a distribuição e quem sabe até com valores para certas versões.

Padrão          default
                 /   \
Família      Debian  FreeBSD
              / \
Distro  Debian   Ubuntu

Além disso, é importante que a inexistência de algum desses arquivos de especialização não seja um empecilho.

Com um pouco de pesquisa e um pouco de tentativa-e-erro, e com "um pouco" quero dizer interminável, cheguei no código abaixo:

# Arquivo: tasks/default-tasks.yml
---
- name: Carrega variáveis padrão, da família e da distro
  ansible.builtin.include_vars: "{{ item }}"
  with_fileglob:
    - vars/default.yml
    - vars/family_{{ ansible_os_family }}.yml
    - vars/distro_{{ ansible_distribution }}.yml

Usamos include_vars, como de costume, porém junto com with_fileglob. Este geralmente é usado para encontrar vários arquivos usando coringas (wildcards) como vars/*.yml. Mas aqui não usamos coringas, damos direto o nome do arquivo. Se o arquivo não é encontrado ele é ignorado.

Abaixo estão exemplos dos arquivos de variáveis padrão e especializado.

# Arquivo: vars/default.yml
---
doas_config_file: /etc/doas.conf
# Arquivo: vars/family_FreeBSD.yml
---
doas_config_file: /usr/local/etc/doas.conf

Copiando a suas chaves SSH do GitHub ou GitLab

As vezes queremos copiar uma chave pública SSH para um computador.

Esse é um jeito fácil de adicionar a sua própria chave ou de adicionar a chave de alguém para te ajudar com algum problema.

Tendo uma conexão com a internet, é possível fazer o download das chaves públicas SSH de qualquer usuário do GitHub ou GitLab.

Com os comandos abaixo é possível fazer o download da chave adicioná-las nas chaves permitidas para acesso SSH. Basta trocar USUARIO pelo nome do usuário desejado.

  1. Configure a máscara de permissões para 077: leitura e escrita apenas para o usuário dono.
    • umask 077
  2. Crie a pasta ~/.ssh, se ela não existir.
    • mkdir -p ~/.ssh
  3. Faça o download das chaves:
    • GitHub: curl https://github.com/USUARIO.keys >>~/.ssh/authorized_keys
    • GitLab: curl https://gitlab.com/USUARIO.keys >>~/.ssh/authorized_keys

Também podemos usar wget em vez de curl, substituindo curl por wget -O-.