Merge branch 'master' into role-docker-ubuntu
This commit is contained in:
37
roles/sshd/README.md
Normal file
37
roles/sshd/README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
ansible-sshd
|
||||
=========
|
||||
|
||||
Deploy a hardened sshd server
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
None
|
||||
|
||||
Role Variables
|
||||
--------------
|
||||
|
||||
None
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
None
|
||||
|
||||
Example Playbook
|
||||
----------------
|
||||
|
||||
```yaml
|
||||
roles:
|
||||
- role: genlab.common.sshd
|
||||
```
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
BSD
|
||||
|
||||
Author Information
|
||||
------------------
|
||||
|
||||
corvus-migratorius@proton.me
|
||||
5
roles/sshd/defaults/main.yml
Normal file
5
roles/sshd/defaults/main.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
sshd_disable_pam: false
|
||||
sshd_password_auth: false
|
||||
sshd_challenge_response_auth: false
|
||||
sshd_gss_api_auth: false
|
||||
7
roles/sshd/handlers/main.yml
Normal file
7
roles/sshd/handlers/main.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
- name: "Restart sshd"
|
||||
ansible.builtin.service:
|
||||
name: ssh
|
||||
state: restarted
|
||||
enabled: true
|
||||
daemon_reload: true
|
||||
17
roles/sshd/meta/main.yml
Normal file
17
roles/sshd/meta/main.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
galaxy_info:
|
||||
role_name: sshd
|
||||
namespace: genlab
|
||||
author: "Alexander Gorelyshev"
|
||||
company: "Genlab, LLC"
|
||||
description: "Deploy a hardened sshd server"
|
||||
license: "MIT"
|
||||
min_ansible_version: "2.1"
|
||||
|
||||
platforms:
|
||||
- name: "Ubuntu"
|
||||
versions: ["jammy", "noble"]
|
||||
|
||||
galaxy_tags: []
|
||||
|
||||
dependencies: []
|
||||
5
roles/sshd/molecule/default/converge.yml
Normal file
5
roles/sshd/molecule/default/converge.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: Converge
|
||||
hosts: all
|
||||
roles:
|
||||
- role: "genlab.common.sshd"
|
||||
27
roles/sshd/molecule/default/molecule.yml
Normal file
27
roles/sshd/molecule/default/molecule.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
dependency:
|
||||
name: galaxy
|
||||
|
||||
driver:
|
||||
name: docker
|
||||
|
||||
platforms:
|
||||
- name: ubuntu
|
||||
image: geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2404}-ansible:latest
|
||||
pre_build_image: true
|
||||
command: ${MOLECULE_DOCKER_COMMAND:-""}
|
||||
volumes:
|
||||
- /sys/fs/cgroup:/sys/fs/cgroup:rw
|
||||
cgroupns_mode: host
|
||||
privileged: true
|
||||
|
||||
provisioner:
|
||||
name: ansible
|
||||
|
||||
verifier:
|
||||
name: ansible
|
||||
|
||||
lint: |
|
||||
set -e
|
||||
yamllint .
|
||||
ansible-lint .
|
||||
76
roles/sshd/molecule/default/verify.yml
Normal file
76
roles/sshd/molecule/default/verify.yml
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
|
||||
- name: Verify
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
any_errors_fatal: true
|
||||
|
||||
tasks:
|
||||
- name: Gather service facts
|
||||
ansible.builtin.service_facts:
|
||||
|
||||
- name: Ensure sshd is running
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_facts.services['ssh.service'].state == 'running'
|
||||
|
||||
- name: Ensure sshd_config syntax is OK
|
||||
ansible.builtin.command: sshd -t -f /etc/ssh/sshd_config
|
||||
changed_when: false
|
||||
|
||||
- name: Ensure main parameteres are applied
|
||||
ansible.builtin.shell: |
|
||||
set -o pipefail ;
|
||||
sshd -T | egrep -i '
|
||||
^protocol 2|
|
||||
^permitrootlogin no|
|
||||
^passwordauthentication no|
|
||||
^pubkeyauthentication yes
|
||||
'
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: false
|
||||
|
||||
- name: Get sshd_config stats
|
||||
ansible.builtin.stat:
|
||||
path: /etc/ssh/sshd_config
|
||||
register: sshd_conf
|
||||
|
||||
- name: Ensure sshd_config file is secure
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- sshd_conf.stat.uid == 0
|
||||
- sshd_conf.stat.gid == 0
|
||||
- sshd_conf.stat.mode == '0600'
|
||||
|
||||
- name: Create test user
|
||||
ansible.builtin.user:
|
||||
name: test
|
||||
create_home: true
|
||||
shell: /bin/bash
|
||||
|
||||
- name: Create .ssh directory
|
||||
ansible.builtin.file:
|
||||
path: /home/test/.ssh/
|
||||
state: directory
|
||||
mode: '0700'
|
||||
owner: test
|
||||
group: test
|
||||
|
||||
- name: Generate ssh keys
|
||||
community.crypto.openssh_keypair:
|
||||
path: /home/test/.ssh/id_rsa
|
||||
owner: test
|
||||
group: test
|
||||
mode: '0600'
|
||||
register: sshd_key_result
|
||||
|
||||
- name: Put public key to test user
|
||||
ansible.posix.authorized_key:
|
||||
user: test
|
||||
key: "{{ sshd_key_result.public_key }}"
|
||||
state: present
|
||||
|
||||
- name: Test ssh connection
|
||||
ansible.builtin.command: ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /home/test/.ssh/id_rsa test@localhost hostname
|
||||
changed_when: false
|
||||
42
roles/sshd/tasks/algorithms.yml
Normal file
42
roles/sshd/tasks/algorithms.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
# next task requires this directory to exist for sshd -t flag
|
||||
- name: Ensure /run/sshd exists
|
||||
ansible.builtin.file:
|
||||
path: /run/sshd
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0755'
|
||||
|
||||
# NOTE: order of preference for openssh-server ed25519 -> rsa
|
||||
- name: "Algorithms | enable ed25519 authentication algorithm"
|
||||
notify: Restart sshd
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: '^HostKey /etc/ssh/ssh_host_ed25519_key'
|
||||
line: 'HostKey /etc/ssh/ssh_host_ed25519_key'
|
||||
validate: sshd -f %s -t
|
||||
|
||||
- name: "Algorithms | enable the RSA authentication algorithm"
|
||||
notify: Restart sshd
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: '^HostKey /etc/ssh/ssh_host_rsa_key'
|
||||
line: 'HostKey /etc/ssh/ssh_host_rsa_key'
|
||||
validate: sshd -f %s -t
|
||||
|
||||
- name: "Algorithms | disable the ECDSA algorithm (deemed to be less safe)"
|
||||
notify: Restart sshd
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: '^HostKey /etc/ssh/ssh_host_ecdsa_key'
|
||||
state: absent
|
||||
validate: sshd -f %s -t
|
||||
|
||||
- name: "Algorithms | disable the DSA algorithm (considered to be defunct)"
|
||||
notify: Restart sshd
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: '^HostKey /etc/ssh/ssh_host_dsa_key'
|
||||
state: absent
|
||||
validate: sshd -f %s -t
|
||||
31
roles/sshd/tasks/authentication.yml
Normal file
31
roles/sshd/tasks/authentication.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
- name: "Authentication | Configure SSH authentication settings"
|
||||
notify: Restart sshd
|
||||
loop:
|
||||
- { regexp: '^#?\s*PubkeyAuthentication\s+', line: 'PubkeyAuthentication yes' }
|
||||
- { regexp: '^#?\s*PasswordAuthentication\s+', line: 'PasswordAuthentication {{ sshd_password_auth | ternary("yes", "no") }}' }
|
||||
- { regexp: '^#?\s*PermitEmptyPasswords\s+', line: 'PermitEmptyPasswords no' }
|
||||
- { regexp: '^#?\s*ChallengeResponseAuthentication\s+', line: 'ChallengeResponseAuthentication {{ sshd_challenge_response_auth | ternary("yes", "no") }}' }
|
||||
- { regexp: '^#?\s*GSSAPIAuthentication\s+', line: 'GSSAPIAuthentication {{ sshd_gss_api_auth | ternary("yes", "no") }}' }
|
||||
- {
|
||||
regexp: '^#?\s*AuthenticationMethods\s+',
|
||||
line: "{{ 'AuthenticationMethods publickey password' if sshd_password_auth else 'AuthenticationMethods publickey' }}"
|
||||
}
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: "{{ item.regexp }}"
|
||||
line: "{{ item.line }}"
|
||||
validate: /usr/sbin/sshd -t -f %s
|
||||
|
||||
- name: "Check if there is an SSH config forced by cloud-init"
|
||||
register: sshd_cloud_init
|
||||
ansible.builtin.stat:
|
||||
path: "/etc/ssh/sshd_config.d/50-cloud-init.conf"
|
||||
|
||||
- name: "Authentication | override password authentication by cloud-init to '{{ sshd_password_auth | ternary('yes', 'no') }}'"
|
||||
when: sshd_cloud_init.stat.exists
|
||||
notify: Restart sshd
|
||||
ansible.builtin.lineinfile:
|
||||
path: "/etc/ssh/sshd_config.d/50-cloud-init.conf"
|
||||
regexp: '^#?PasswordAuthentication'
|
||||
line: 'PasswordAuthentication {{ sshd_password_auth | ternary("yes", "no") }}'
|
||||
11
roles/sshd/tasks/encryption.yml
Normal file
11
roles/sshd/tasks/encryption.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: "Encryption | remove unsafe host keys"
|
||||
loop:
|
||||
- /etc/ssh/ssh_host_ecdsa_key
|
||||
- /etc/ssh/ssh_host_ecdsa_key.pub
|
||||
- /etc/ssh/ssh_host_dsa_key
|
||||
- /etc/ssh/ssh_host_dsa_key.pub
|
||||
notify: Restart sshd
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
13
roles/sshd/tasks/install.yml
Normal file
13
roles/sshd/tasks/install.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
- name: "Install | install OpenSSH (RHEL flavours)"
|
||||
when: ansible_os_family == "RHEL"
|
||||
ansible.builtin.dnf:
|
||||
name: openssh
|
||||
state: installed
|
||||
|
||||
- name: "Install | Install OpenSSH (Debian flavours)"
|
||||
when: ansible_os_family == "Debian"
|
||||
ansible.builtin.apt:
|
||||
name: openssh-server
|
||||
state: present
|
||||
update_cache: true
|
||||
23
roles/sshd/tasks/main.yml
Normal file
23
roles/sshd/tasks/main.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
- name: "Install an OpenSSH server"
|
||||
ansible.builtin.include_tasks: "install.yml"
|
||||
|
||||
- name: "Configure SSH algorithms"
|
||||
ansible.builtin.include_tasks: "algorithms.yml"
|
||||
|
||||
- name: "Configure authentication methods"
|
||||
ansible.builtin.include_tasks: "authentication.yml"
|
||||
|
||||
- name: "Configure SSH encryption keys"
|
||||
ansible.builtin.include_tasks: "encryption.yml"
|
||||
|
||||
- name: "Configure additional restrictions"
|
||||
ansible.builtin.include_tasks: "restrictions.yml"
|
||||
|
||||
- name: "Log at VERBOSE level"
|
||||
notify: Restart sshd
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: '^#?LogLevel'
|
||||
line: 'LogLevel VERBOSE'
|
||||
validate: sshd -f %s -t
|
||||
30
roles/sshd/tasks/restrictions.yml
Normal file
30
roles/sshd/tasks/restrictions.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
- name: "Restrictions | Configure SSH security restrictions"
|
||||
loop:
|
||||
- { regexp: '^#?Protocol\s+', line: 'Protocol 2' }
|
||||
- { regexp: '^PermitRootLogin yes', line: 'PermitRootLogin no' }
|
||||
- { regexp: '^#?X11Forwarding', line: 'X11Forwarding no' }
|
||||
- { regexp: '^#?IgnoreRhosts', line: 'IgnoreRhosts yes' }
|
||||
- { regexp: '^#?DebianBanner\s+', line: 'DebianBanner no' }
|
||||
notify: Restart sshd
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: "{{ item.regexp }}"
|
||||
line: "{{ item.line }}"
|
||||
validate: /usr/sbin/sshd -t -f %s
|
||||
|
||||
- name: "Restrictions | toggle PAM"
|
||||
notify: Restart sshd
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: '^#?UsePAM'
|
||||
line: "UsePAM {{ sshd_disable_pam | ternary('no', 'yes') }}"
|
||||
validate: sshd -f %s -t
|
||||
|
||||
- name: "Restrictions | ensure the SSHD config is restricted to the root user"
|
||||
notify: Restart sshd
|
||||
ansible.builtin.file:
|
||||
path: /etc/ssh/sshd_config
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
1
roles/sshd/vars/main.yml
Normal file
1
roles/sshd/vars/main.yml
Normal file
@@ -0,0 +1 @@
|
||||
---
|
||||
Reference in New Issue
Block a user