feat(role): Secure SSH config

This commit is contained in:
hygienic-books 2022-06-12 00:15:27 +02:00
parent 3d856af8ea
commit 2e551bdd7c
13 changed files with 159 additions and 2 deletions

6
.gitignore vendored
View File

@ -1,3 +1,7 @@
# ---> Ansible # ---> Ansible
*.retry *.retry
ansible/hosts.yml
ansible/group_vars/all/*
ansible/roles/20-common-20-ssh/defaults/*
ansible/roles/20-common-20-ssh/files/root/.ssh/*
!*.example

View File

@ -1,3 +1,41 @@
# ansible-mail-infra # ansible-mail-infra
Set up infrastructure for all things e-mail Set up infrastructure for all things e-mail
## Prep
On your Ansible controller make sure the `sshpass` binary exists if Ansible has to connect to target machines via SSH username-password authentication instead of SSH key authentication. The binary usually comes with a package of the same name.
We're assuming that you're running Ansible as a Python package inside a virtual environment. Install Ansible like so:
```
pip install ansible
```
We're also assuming that secrets are stored in a HashiCorp Vault instance to which you have access. These role access Vault via `hvac`, the HashiCorp Vault API client for Python 3.x, see [github.com/hvac/hvac](https://github.com/hvac/hvac) for reference. Install it like so:
```
pip install hvac
```
## Vars
* For Vault access copy [ansible/roles/20-common-20-ssh/defaults/main.yml.example](ansible/roles/20-common-20-ssh/defaults/main.yml.example) to a proper `ansible/roles/20-common-20-ssh/defaults/main.yml` and set Vault credentials and locations as needed.
* Create your inventory, copy [ansible/hosts.yml.example](ansible/hosts.yml.example) into a proper `ansible/hosts.yml` file with at least one host in host group `all`.
* Replace [ansible/group_vars/all/vars.yml.example](ansible/group_vars/all/vars.yml.example) with a proper `ansible/group_vars/all/vars.yml` file and set at least `ansible_user`. It defaults to `ansible_user: 'root'`.
* In [ansible/roles/20-common-20-ssh/files/root/.ssh](ansible/roles/20-common-20-ssh/files/root/.ssh) copy both [authorized_keys.example](ansible/roles/20-common-20-ssh/files/root/.ssh/authorized_keys.example) and [known_hosts.example](ansible/roles/20-common-20-ssh/files/root/.ssh/known_hosts.example) to proper files. They contain SSH authorized_keys and public SSH host keys you want installed on target machines.
## Run it
On first run execute it like so:
```
ansible-playbook --tags 'first_run' --inventory hosts.yml playbook.yml
```
The `first_run` tag ensures that `ansible_password` variable gets set which in turn causes Ansible to log in to target machines via SSH username-password authentication.
On subsequent runs like so:
```
ansible-playbook --inventory hosts.yml playbook.yml
```

View File

@ -0,0 +1 @@
ansible_user: 'root'

View File

@ -0,0 +1,3 @@
all:
hosts:
fully.qualified.domain.name:

4
ansible/playbook.yml Normal file
View File

@ -0,0 +1,4 @@
- name: 'Set up SSH'
hosts: all
roles:
- role: 20-common-20-ssh

View File

@ -0,0 +1,6 @@
root_home_dir_abs: '/root'
ansible_hashi_vault_auth_method: 'token'
ansible_hashi_vault_token: 'hvs.xxxxxxxxxx'
ansible_hashi_vault_engine_mount_point: 'kv'
ansible_hashi_vault_token_validate: 'false'
ansible_hashi_vault_url: 'http://localhost:8200/'

View File

@ -0,0 +1,6 @@
PubkeyAuthentication yes
PasswordAuthentication no
PermitEmptyPasswords no
UsePAM no
PermitRootLogin without-password
PrintMotd yes

View File

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZ... comment

View File

@ -0,0 +1 @@
host.name ssh-rsa AAAAB3NzaC1...

View File

@ -0,0 +1,5 @@
- name: 'Restart sshd'
listen: 'Restart sshd.service'
ansible.builtin.service:
name: 'sshd.service'
state: 'restarted'

View File

@ -0,0 +1,85 @@
- name: 'Get secrets'
no_log: 'true'
loop_control:
loop_var: 'server'
with_community.hashi_vault.vault_kv2_get:
- '{{ inventory_hostname | split(".") | reverse | join("/") }}/os/root/creds'
ansible.builtin.set_fact:
vault_data: '{{ server.secret }}'
- name: 'If first run: set SSH password'
tags:
- 'first_run'
- 'never'
no_log: 'true'
ansible.builtin.set_fact:
ansible_password: '{{ vault_data.initial_password }}'
- name: 'Make sure ''{{ root_home_dir_abs }}/.ssh'' exists with correct permissions'
file:
path: '{{ root_home_dir_abs }}/.ssh'
state: 'directory'
mode: 'u=rwX,go='
owner: '{{ ansible_user }}'
group: '{{ ansible_user }}'
recurse: 'yes'
- name: 'Copy ''authorized_keys'' file to server'
copy:
src: 'root/.ssh/authorized_keys'
dest: '{{ root_home_dir_abs }}/.ssh/authorized_keys'
mode: '0600'
owner: '{{ ansible_user }}'
group: '{{ ansible_user }}'
- name: 'Copy ''known_hosts'' file to server'
copy:
src: 'root/.ssh/known_hosts'
dest: '{{ root_home_dir_abs }}/.ssh/known_hosts'
mode: '0600'
owner: '{{ ansible_user }}'
group: '{{ ansible_user }}'
- name: 'If on Red Hat or derivative OS: secure sshd'
register: 'rv_secure_sshd'
when: '(ansible_facts[''os_family''] | lower == ''redhat'')'
blockinfile:
block: "{{ lookup('file', 'etc/ssh/sshd_config') }}"
dest: "/etc/ssh/sshd_config"
state: 'present'
insertbefore: 'BOF'
marker: '{mark}'
marker_begin: '####### Managed remotely via config management ####### quico-ops start'
marker_end: '####### Managed remotely via config management ####### quico-ops end'
validate: '/usr/sbin/sshd -T -f %s'
notify:
- 'Restart sshd.service'
- name: 'Flush handlers'
meta: flush_handlers
- name: 'Reset connection'
ansible.builtin.meta: 'reset_connection'
- name: 'Wait for SSH connection to return'
when: '(rv_secure_sshd.changed)'
ansible.builtin.wait_for_connection:
connect_timeout: '1'
delay: '1'
sleep: '2'

View File

@ -0,0 +1,3 @@
- import_tasks: '20-ssh.yml'
tags:
- 'first_run'