diff --git a/.github/workflows/ansible-debian-buster.yml b/.github/workflows/ansible-debian-buster.yml new file mode 100644 index 0000000..2bd9696 --- /dev/null +++ b/.github/workflows/ansible-debian-buster.yml @@ -0,0 +1,17 @@ +name: Run tests on Debian buster (10) + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + # Important: This sets up your GITHUB_WORKSPACE environment variable + - uses: actions/checkout@v2 + + - name: ansible check with debian:buster (10) + uses: roles-ansible/check-ansible-debian-buster-action@master + with: + group: local + hosts: localhost + targets: "tests/*.yml" diff --git a/.github/workflows/ansible-debian-stretch.yml b/.github/workflows/ansible-debian-stretch.yml new file mode 100644 index 0000000..8ae0ccf --- /dev/null +++ b/.github/workflows/ansible-debian-stretch.yml @@ -0,0 +1,17 @@ +name: Run tests on Debian stretch (9) + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + # Important: This sets up your GITHUB_WORKSPACE environment variable + - uses: actions/checkout@v2 + + - name: ansible check with debian:stretch (9) + uses: roles-ansible/check-ansible-debian-stretch-action@master + with: + group: local + hosts: localhost + targets: "tests/*.yml" diff --git a/.github/workflows/ansible-debian.yml b/.github/workflows/ansible-debian.yml new file mode 100644 index 0000000..fd439e2 --- /dev/null +++ b/.github/workflows/ansible-debian.yml @@ -0,0 +1,17 @@ +name: Run tests on Debian latest + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + # Important: This sets up your GITHUB_WORKSPACE environment variable + - uses: actions/checkout@v2 + + - name: ansible check with debian:latest + uses: roles-ansible/check-ansible-debian-latest-action@master + with: + group: local + hosts: localhost + targets: "tests/*.yml" diff --git a/.github/workflows/ansible-ubuntu.yml b/.github/workflows/ansible-ubuntu.yml new file mode 100644 index 0000000..27b4b99 --- /dev/null +++ b/.github/workflows/ansible-ubuntu.yml @@ -0,0 +1,17 @@ +name: Run tests on Ubuntu latest + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + # Important: This sets up your GITHUB_WORKSPACE environment variable + - uses: actions/checkout@v2 + + - name: ansible check with ubuntu:latest + uses: roles-ansible/check-ansible-ubuntu-latest-action@master + with: + group: local + hosts: localhost + targets: "tests/*.yml" diff --git a/.travis.yml b/.travis.yml index 1d08e2f..dc7852d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,15 +24,18 @@ script: - wget https://raw.githubusercontent.com/ansible/galaxy/devel/galaxy/importer/linters/yamllint.yaml - "yamllint -c yamllint.yaml **/*.yml" + # Test 0.5: OS defaults: Travis images have heavily updated (even with invalid configuration options) + # sshd_config so it does not make sense to test OS defaults here + # Test 1a: Run the role - "ANSIBLE_FORCE_COLOR=1 ansible-playbook -i tests/inventory tests/tests_default.yml --connection=local --become -v" - # Test 1b: Run the role through include - - "ANSIBLE_FORCE_COLOR=1 ansible-playbook -i tests/inventory tests/tests_default_include.yml --connection=local --become -v" + # Test 1b: Run the role through include (skipping backup) + - "ANSIBLE_FORCE_COLOR=1 ansible-playbook -i tests/inventory tests/tests_default_include.yml -e sshd_test_backup_skip=yes --connection=local --become -v" - # Test 2: Run the role/playbook again, checking to make sure it's idempotent. + # Test 2: Run the role/playbook again, checking to make sure it's idempotent (skipping backup) - > - ansible-playbook -i tests/inventory tests/tests_default.yml --connection=local --become | grep -q 'changed=0.*failed=0' + ANSIBLE_FORCE_COLOR=1 ansible-playbook -i tests/inventory tests/tests_default.yml --connection=local -e sshd_test_backup_skip=yes --become | grep -q 'changed=0.*failed=0' && (echo 'Idempotence test: pass' && exit 0) || (echo 'Idempotence test: fail' && exit 1) @@ -78,3 +81,21 @@ script: ANSIBLE_FORCE_COLOR=1 ansible-playbook -i tests/inventory tests/tests_hostkeys_missing.yml --connection=local --become -v && (echo 'Missing hostkeys test: pass' && exit 0) || (echo 'Missing hostkeys test: fail' && exit 1) + + # Test 10: Test sshd_enable has effect + - > + ANSIBLE_FORCE_COLOR=1 ansible-playbook -i tests/inventory tests/tests_sshd_enable.yml --connection=local --become -v + && (echo 'Test sshd_enable: pass' && exit 0) + || (echo 'Test sshd_enable: fail' && exit 1) + + # Test 11: Test variable precedence + - > + ANSIBLE_FORCE_COLOR=1 ansible-playbook -i tests/inventory tests/tests_precedence.yml --connection=local --become -v + && (echo 'Variable precedence test: pass' && exit 0) + || (echo 'Variable precedence test: fail' && exit 1) + + # Test 12: Verify backups are created + - > + ANSIBLE_FORCE_COLOR=1 ansible-playbook -i tests/inventory tests/tests_backup.yml --connection=local --become -v + && (echo 'Backup test: pass' && exit 0) + || (echo 'Backup test: fail' && exit 1) diff --git a/README.md b/README.md index 263d136..676ad72 100644 --- a/README.md +++ b/README.md @@ -122,13 +122,11 @@ ListenAddress 0.0.0.0 ListenAddress :: ``` -* `sshd_match` +* `sshd_match`, `sshd_match_1` through `sshd_match_9` -A list of dicts for a match section. See the example playbook. - -* `sshd_match_1` through `sshd_match_9` - -A list of dicts or just a dict for a Match section. +A list of dicts or just a dict for a Match section. Note, that these variables +do not override match blocks as defined in the `sshd` dict. All of the sources +will be reflected in the resulting configuration file. * `sshd_backup` diff --git a/defaults/main.yml b/defaults/main.yml index 704dca3..81a9919 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -70,3 +70,7 @@ __sshd_defaults: {} __sshd_os_supported: no __sshd_sysconfig_supports_crypto_policy: false __sshd_sysconfig_supports_use_strong_rng: false + + +__sshd_runtime_directory: false +__sshd_runtime_directory_mode: "0755" diff --git a/handlers/main.yml b/handlers/main.yml index 8c0eb7d..2b89cdc 100644 --- a/handlers/main.yml +++ b/handlers/main.yml @@ -7,6 +7,7 @@ when: - sshd_allow_reload|bool - ansible_virtualization_type|default(None) != 'docker' + - ansible_virtualization_type|default(None) != 'podman' - ansible_virtualization_type|default(None) != 'VirtualPC' # for Github Actions - ansible_connection != 'chroot' - ansible_os_family != 'AIX' diff --git a/tasks/install.yml b/tasks/install.yml index 2b7f6fd..e08508a 100644 --- a/tasks/install.yml +++ b/tasks/install.yml @@ -19,6 +19,7 @@ backup: "{{ sshd_backup }}" when: - sshd_sysconfig|bool + - __sshd_sysconfig_supports_use_strong_rng or __sshd_sysconfig_supports_crypto_policy notify: reload_sshd - name: Make sure hostkeys are available and have expected permissions @@ -39,7 +40,11 @@ {% if not sshd_verify_hostkeys %} [] {% elif sshd_verify_hostkeys == 'auto' %} - {{ __sshd_hostkeys_from_config }} + {% if sshd_HostKey is string %} + [ {{ __sshd_hostkeys_from_config }} ] + {% else %} + {{ __sshd_hostkeys_from_config }} + {% endif %} {% else %} {{ sshd_verify_hostkeys | to_json }} {% endif %} @@ -80,6 +85,16 @@ changed_when: False when: sshd_test_hostkey.path is defined + - name: Make sure sshd runtime directory is present + file: + path: "{{ __sshd_runtime_directory }}" + state: directory + owner: root + group: root + mode: "{{ __sshd_runtime_directory_mode }}" + when: + - __sshd_runtime_directory | d(false) | bool + - name: Create the configuration file template: src: sshd_config.j2 @@ -143,6 +158,7 @@ when: - sshd_manage_service|bool - ansible_virtualization_type|default(None) != 'docker' + - ansible_virtualization_type|default(None) != 'podman' - ansible_virtualization_type|default(None) != 'VirtualPC' # for Github Actions - ansible_connection != 'chroot' diff --git a/tasks/variables.yml b/tasks/variables.yml index 9d9aa1d..47ce361 100644 --- a/tasks/variables.yml +++ b/tasks/variables.yml @@ -18,8 +18,8 @@ params: files: - "{{ ansible_distribution }}_{{ ansible_distribution_lts_version }}.yml" - - "{{ ansible_distribution }}.yml" - "{{ ansible_os_family }}_{{ ansible_distribution_major_version }}.yml" + - "{{ ansible_distribution }}.yml" - "{{ ansible_os_family }}.yml" - default.yml paths: diff --git a/tests/tasks/backup.yml b/tests/tasks/backup.yml new file mode 100644 index 0000000..16d3e22 --- /dev/null +++ b/tests/tasks/backup.yml @@ -0,0 +1,23 @@ +--- +- name: Setup + include_tasks: setup.yml + +- name: Create a temporary directory for backup files + tempfile: + state: directory + register: __sshd_test_backup + changed_when: False + when: + - sshd_test_backup_skip is not defined + +- name: Backup files + shell: > + if test -f {{ item }}; then + mkdir -p {{ __sshd_test_backup.path }}/$(dirname {{ item }}); + cp {{ item }} {{ __sshd_test_backup.path }}/$(dirname {{ item }}) + fi + changed_when: False + loop: "{{ __sshd_test_backup_files | d([]) }}" + when: + - __sshd_test_backup is defined + - __sshd_test_backup.path is defined diff --git a/tests/tasks/restore.yml b/tests/tasks/restore.yml new file mode 100644 index 0000000..7c955b3 --- /dev/null +++ b/tests/tasks/restore.yml @@ -0,0 +1,35 @@ +--- +- name: Restore backed up files and remove what was not present + shell: > + if test -f {{ __sshd_test_backup.path }}/{{ item }}; then + cp {{ __sshd_test_backup.path }}/{{ item }} $(dirname {{ item }}) + elif test -f {{ item }}; then + rm {{ item }} + fi + changed_when: False + loop: "{{ __sshd_test_backup_files | d([]) }}" + when: + - __sshd_test_backup is defined + - __sshd_test_backup.path is defined + +- name: Remove temporary directory for backup files + file: + path: "{{ __sshd_test_backup.path }}" + state: absent + changed_when: False + when: + - __sshd_test_backup is defined + - __sshd_test_backup.path is defined + +- name: Restart sshd service + service: + name: sshd + state: reloaded + changed_when: False + when: + - __sshd_test_backup is defined + - ansible_virtualization_type|default(None) != 'docker' + - ansible_virtualization_type|default(None) != 'podman' + - ansible_virtualization_type|default(None) != 'VirtualPC' # for Github Actions + - ansible_connection != 'chroot' + - ansible_os_family != 'AIX' diff --git a/tests/tasks/setup.yml b/tests/tasks/setup.yml new file mode 100644 index 0000000..35fb451 --- /dev/null +++ b/tests/tasks/setup.yml @@ -0,0 +1,15 @@ +--- +- name: Make sure openssh is installed before creating backup + package: + name: openssh-server + state: present + +- name: Make sure openssh has runtime directory on debian + file: + path: /run/sshd + state: directory + owner: root + group: root + mode: "0755" + when: + - ansible_facts['os_family'] == 'Debian' diff --git a/tests/tests_alternative_file.yml b/tests/tests_alternative_file.yml index 0272fce..aca4c6e 100644 --- a/tests/tests_alternative_file.yml +++ b/tests/tests_alternative_file.yml @@ -1,6 +1,15 @@ --- - hosts: all + vars: + __sshd_test_backup_files: + - /etc/ssh/sshd_config + - /etc/ssh/sshd_config.d/00-ansible_system_role.conf + - /etc/ssh/sshd_config_custom + - /etc/ssh/sshd_config_custom_second tasks: + - name: "Backup configuration files" + include_tasks: tasks/backup.yml + - name: Configure alternative sshd_config file include_role: name: ansible-sshd @@ -11,7 +20,7 @@ sshd: AcceptEnv: LANG Banner: /etc/issue - Ciphers: aes256-gcm@openssh.com + Ciphers: aes256-ctr sshd_Compression: no - name: Configure second alternative sshd_config file include_role: @@ -22,7 +31,7 @@ sshd_skip_defaults: true sshd: Banner: /etc/issue2 - Ciphers: aes128-gcm@openssh.com + Ciphers: aes128-ctr sshd_MaxStartups: 100 - name: Now configure the main sshd_config file include_role: @@ -30,7 +39,7 @@ vars: sshd: Banner: /etc/issue - Ciphers: aes128-ctr + Ciphers: aes192-ctr HostKey: - /tmp/ssh_host_ecdsa_key sshd_PasswordAuthentication: no @@ -66,7 +75,7 @@ that: - "'AcceptEnv LANG' in config.content | b64decode" - "'Banner /etc/issue' in config.content | b64decode" - - "'Ciphers aes256-gcm@openssh.com' in config.content | b64decode" + - "'Ciphers aes256-ctr' in config.content | b64decode" - "'HostKey' not in config.content | b64decode" - "'Compression no' in config.content | b64decode" - "'MaxStartups 100' not in config.content | b64decode" @@ -75,7 +84,7 @@ assert: that: - "'Banner /etc/issue2' in config2.content | b64decode" - - "'Ciphers aes128-gcm@openssh.com' in config2.content | b64decode" + - "'Ciphers aes128-ctr' in config2.content | b64decode" - "'HostKey' not in config2.content | b64decode" - "'MaxStartups 100' in config2.content | b64decode" - "'Compression no' not in config2.content | b64decode" @@ -84,9 +93,12 @@ assert: that: - "'Banner /etc/issue' in config3.content | b64decode" - - "'Ciphers aes128-ctr' in config3.content | b64decode" + - "'Ciphers aes192-ctr' in config3.content | b64decode" - "'HostKey /tmp/ssh_host_ecdsa_key' in config3.content | b64decode" - "'PasswordAuthentication no' in config3.content | b64decode" - "'MaxStartups 100' not in config3.content | b64decode" - "'Compression no' not in config3.content | b64decode" tags: tests::verify + + - name: "Restore configuration files" + include_tasks: tasks/restore.yml diff --git a/tests/tests_backup.yml b/tests/tests_backup.yml new file mode 100644 index 0000000..40fe281 --- /dev/null +++ b/tests/tests_backup.yml @@ -0,0 +1,67 @@ +--- +- hosts: all + vars: + __sshd_test_backup_files: + - /etc/ssh/sshd_config + - /etc/ssh/sshd_config.d/00-ansible_system_role.conf + main_sshd_config: >- + {{ + "00-ansible_system_role.conf" + if ansible_facts['distribution'] == 'Fedora' + else "sshd_config" + }} + main_sshd_config_path: >- + {{ + "/etc/ssh/sshd_config.d/" + if ansible_facts['distribution'] == 'Fedora' + else "/etc/ssh/" + }} + tasks: + - name: Backup configuration files + include_tasks: tasks/backup.yml + + - name: Find old backups files + find: + paths: "{{ main_sshd_config_path }}" + patterns: "{{ main_sshd_config }}.*@*~" + register: backup_files + + - name: Remove old backup files + file: + path: "{{ item.path }}" + state: absent + with_items: "{{ backup_files.files }}" + + - name: Configure sshd without creating backup + include_role: + name: ansible-sshd + vars: + sshd_backup: false + + - name: Find new backups files + find: + paths: "{{ main_sshd_config_path }}" + patterns: "{{ main_sshd_config }}.*@*~" + register: no_backup + + - name: Configure sshd again with different configuration and with backup + include_role: + name: ansible-sshd + vars: + sshd_Banner: /tmp/banner + register: second_run + + - name: Find new backups files + find: + paths: "{{ main_sshd_config_path }}" + patterns: "{{ main_sshd_config }}.*@*~" + register: new_backup + + - name: Verify the backup was not done in the first attempt, but in the second one + assert: + that: + - no_backup.files == [] + - new_backup.files != [] + + - name: Restore configuration files + include_tasks: tasks/restore.yml diff --git a/tests/tests_default.yml b/tests/tests_default.yml index dfb0f89..2400fee 100644 --- a/tests/tests_default.yml +++ b/tests/tests_default.yml @@ -1,4 +1,22 @@ --- +- hosts: all + vars: + __sshd_test_backup_files: + - /etc/ssh/sshd_config + - /etc/ssh/sshd_config.d/00-ansible_system_role.conf + tasks: + - name: "Backup configuration files" + include_tasks: tasks/backup.yml + - hosts: all roles: - ansible-sshd + +- hosts: all + vars: + __sshd_test_backup_files: + - /etc/ssh/sshd_config + - /etc/ssh/sshd_config.d/00-ansible_system_role.conf + tasks: + - name: "Restore configuration files" + include_tasks: tasks/restore.yml diff --git a/tests/tests_default_include.yml b/tests/tests_default_include.yml index d3d98d7..8c0333f 100644 --- a/tests/tests_default_include.yml +++ b/tests/tests_default_include.yml @@ -1,6 +1,16 @@ --- - hosts: all + vars: + __sshd_test_backup_files: + - /etc/ssh/sshd_config + - /etc/ssh/sshd_config.d/00-ansible_system_role.conf tasks: + - name: "Backup configuration files" + include_tasks: tasks/backup.yml + - name: "Configure sshd" include_role: name: ansible-sshd + + - name: "Restore configuration files" + include_tasks: tasks/restore.yml diff --git a/tests/tests_hostkeys.yml b/tests/tests_hostkeys.yml index 2e73538..4ccbdba 100644 --- a/tests/tests_hostkeys.yml +++ b/tests/tests_hostkeys.yml @@ -1,9 +1,17 @@ --- - hosts: all + vars: + __sshd_test_backup_files: + - /etc/ssh/sshd_config + - /etc/ssh/sshd_config.d/00-ansible_system_role.conf + - /tmp/ssh_host_rsa_key2 tasks: + - name: "Backup configuration files" + include_tasks: tasks/backup.yml + - name: Remove host key before the test file: - path: /tmp/ssh_host_ed25519_key + path: /tmp/ssh_host_rsa_key2 state: absent - name: Ensure group 'nobody' exists @@ -28,7 +36,7 @@ sshd_hostkey_mode: "0664" sshd: HostKey: - - /tmp/ssh_host_ed25519_key + - /tmp/ssh_host_rsa_key2 - name: Verify the options are correctly set vars: @@ -47,17 +55,17 @@ register: config - stat: - path: /tmp/ssh_host_ed25519_key + path: /tmp/ssh_host_rsa_key2 register: privkey - stat: - path: /tmp/ssh_host_ed25519_key.pub + path: /tmp/ssh_host_rsa_key2.pub register: pubkey - name: Check the options are in configuration file assert: that: - - "'HostKey /tmp/ssh_host_ed25519_key' in config.content | b64decode" + - "'HostKey /tmp/ssh_host_rsa_key2' in config.content | b64decode" - name: Check the generated host key has requested properties assert: @@ -68,3 +76,6 @@ - privkey.stat.mode == '0664' - pubkey.stat.exists tags: tests::verify + + - name: "Restore configuration files" + include_tasks: tasks/restore.yml diff --git a/tests/tests_hostkeys_missing.yml b/tests/tests_hostkeys_missing.yml index 513ee19..9dfe77b 100644 --- a/tests/tests_hostkeys_missing.yml +++ b/tests/tests_hostkeys_missing.yml @@ -1,6 +1,14 @@ --- - hosts: all + vars: + __sshd_test_backup_files: + - /etc/ssh/sshd_config + - /etc/ssh/sshd_config.d/00-ansible_system_role.conf + - /tmp/missing_ssh_host_rsa_key tasks: + - name: "Backup configuration files" + include_tasks: tasks/backup.yml + - name: Configure sshd with missing host keys and prevent their creation block: - name: Configure missing hostkey @@ -24,10 +32,26 @@ - ansible_failed_result.msg != 'UNREACH' - not role_result.changed msg: "Role has not failed when it should have" + when: + - ansible_facts['os_family'] != 'Debian' + - not (ansible_facts['distribution'] == 'RedHat' and ansible_facts['distribution_major_version'] == '6') + tags: tests::verify - - name: Make sure service is still running - service: - name: sshd - state: started - register: result - failed_when: result.changed + - name: Make sure the key was not created + file: + path: /tmp/missing_ssh_host_rsa_key + state: missing + register: key + failed_when: key.changed + tags: tests::verify + + - name: Make sure service is still running + service: + name: sshd + state: started + register: result + failed_when: result.changed + tags: tests::verify + + - name: "Restore configuration files" + include_tasks: tasks/restore.yml diff --git a/tests/tests_match.yml b/tests/tests_match.yml index 829e628..f47418f 100644 --- a/tests/tests_match.yml +++ b/tests/tests_match.yml @@ -1,6 +1,13 @@ --- - hosts: all + vars: + __sshd_test_backup_files: + - /etc/ssh/sshd_config + - /etc/ssh/sshd_config.d/00-ansible_system_role.conf tasks: + - name: "Backup configuration files" + include_tasks: tasks/backup.yml + - name: Configure sshd include_role: name: ansible-sshd @@ -10,7 +17,7 @@ - /etc/ssh/ssh_host_rsa_key sshd: Match: - - Condition: "User xusers" + Condition: "User xusers" X11Forwarding: yes Banner: /tmp/xusers-banner sshd_match: @@ -24,7 +31,7 @@ sshd_match_2: - Condition: "User root" PasswordAuthentication: no - PermitTunnel: yes + AllowTcpForwarding: yes - name: Verify the options are correctly set vars: @@ -69,7 +76,7 @@ - "'forcecommand internal-sftp' in sftponly_effective.stdout" - "'chrootdirectory /var/uploads/' in sftponly_effective.stdout" - "'passwordauthentication no' in root_effective.stdout" - - "'permittunnel yes' in root_effective.stdout" + - "'allowtcpforwarding yes' in root_effective.stdout" - name: Check the options are in configuration file assert: @@ -79,3 +86,6 @@ - "'Match User sftponly' in config.content | b64decode" - "'Match User root' in config.content | b64decode" tags: tests::verify + + - name: "Restore configuration files" + include_tasks: tasks/restore.yml diff --git a/tests/tests_match_iterate.yml b/tests/tests_match_iterate.yml index 7c23564..c010258 100644 --- a/tests/tests_match_iterate.yml +++ b/tests/tests_match_iterate.yml @@ -1,6 +1,13 @@ --- - hosts: all + vars: + __sshd_test_backup_files: + - /etc/ssh/sshd_config + - /etc/ssh/sshd_config.d/00-ansible_system_role.conf tasks: + - name: "Backup configuration files" + include_tasks: tasks/backup.yml + - name: Configure sshd include_role: name: ansible-sshd @@ -22,7 +29,7 @@ ChrootDirectory: "/var/uploads/" - Condition: "User root" PasswordAuthentication: no - PermitTunnel: yes + AllowTcpForwarding: yes - name: Verify the options are correctly set vars: @@ -67,7 +74,7 @@ - "'forcecommand internal-sftp' in sftponly_effective.stdout" - "'chrootdirectory /var/uploads/' in sftponly_effective.stdout" - "'passwordauthentication no' in root_effective.stdout" - - "'permittunnel yes' in root_effective.stdout" + - "'allowtcpforwarding yes' in root_effective.stdout" - name: Check the options are in configuration file assert: @@ -77,3 +84,6 @@ - "'Match User sftponly' in config.content | b64decode" - "'Match User root' in config.content | b64decode" tags: tests::verify + + - name: "Restore configuration files" + include_tasks: tasks/restore.yml diff --git a/tests/tests_os_defaults.yml b/tests/tests_os_defaults.yml new file mode 100644 index 0000000..dde76d9 --- /dev/null +++ b/tests/tests_os_defaults.yml @@ -0,0 +1,38 @@ +--- +- hosts: all + vars: + __sshd_test_backup_files: + - /etc/ssh/sshd_config + - /etc/ssh/sshd_config.d/00-ansible_system_role.conf + - /etc/ssh/ssh_host_rsa_key + - /etc/ssh/ssh_host_rsa_key.pub + tasks: + - name: Backup configuration files + include_tasks: tasks/backup.yml + + - name: Show effective configuration before running role (system defaults) + shell: > + if test ! -f /etc/ssh/ssh_host_rsa_key; then + ssh-keygen -q -t rsa -f /etc/ssh/ssh_host_rsa_key -C '' -N '' + fi; + sshd -T + register: runtime_before + - name: Configure sshd + include_role: + name: ansible-sshd + - name: Show effective configuration after running role (role defaults) + shell: sshd -T + register: runtime_after + - debug: + var: ansible_facts['distribution'] + - debug: + var: ansible_facts['distribution_major_version'] + - name: Check that the effective configuration did not change from OS defaults + assert: + that: + - runtime_before.stdout == runtime_after.stdout + when: + - not (ansible_facts['distribution'] == 'RedHat' and ansible_facts['distribution_major_version'] == '6') + + - name: Restore configuration files + include_tasks: tasks/restore.yml diff --git a/tests/tests_precedence.yml b/tests/tests_precedence.yml new file mode 100644 index 0000000..8d6b82a --- /dev/null +++ b/tests/tests_precedence.yml @@ -0,0 +1,66 @@ +--- +- hosts: all + vars: + __sshd_test_backup_files: + - /etc/ssh/sshd_config + - /etc/ssh/sshd_config.d/00-ansible_system_role.conf + - /tmp/ssh_host_rsa_key + tasks: + - name: "Backup configuration files" + include_tasks: tasks/backup.yml + + - name: Remove host key before the test + file: + path: /tmp/ssh_host_rsa_key + state: absent + + - name: Configure sshd + include_role: + name: ansible-sshd + vars: + sshd: + Banner: /etc/issue + Ciphers: aes256-ctr + HostKey: /etc/ssh/ssh_host_rsa_key + sshd_Ciphers: aes128-ctr + sshd_Banner: /etc/good-issue + sshd_HostKey: /tmp/ssh_host_rsa_key + + - name: Verify the options are correctly set + vars: + main_sshd_config: >- + {{ + "/etc/ssh/sshd_config.d/00-ansible_system_role.conf" + if ansible_facts['distribution'] == 'Fedora' + else "/etc/ssh/sshd_config" + }} + block: + - meta: flush_handlers + + - name: List effective configuration using sshd -T + command: sshd -T + register: runtime + + - name: Print current configuration file + slurp: + src: "{{ main_sshd_config }}" + register: config + + - name: Check the sshd_* values are effective in runtime + # note, the options are in lower-case here + assert: + that: + - "'banner /etc/good-issue' in runtime.stdout" + - "'ciphers aes128-ctr' in runtime.stdout" + - "'hostkey /tmp/ssh_host_rsa_key' in runtime.stdout" + + - name: Check the options are in configuration file + assert: + that: + - "'Banner /etc/good-issue' in config.content | b64decode" + - "'Ciphers aes128-ctr' in config.content | b64decode" + - "'HostKey /tmp/ssh_host_rsa_key' in config.content | b64decode" + tags: tests::verify + + - name: "Restore configuration files" + include_tasks: tasks/restore.yml diff --git a/tests/tests_set_common.yml b/tests/tests_set_common.yml index 845bf76..7bed95d 100644 --- a/tests/tests_set_common.yml +++ b/tests/tests_set_common.yml @@ -1,6 +1,13 @@ --- - hosts: all + vars: + __sshd_test_backup_files: + - /etc/ssh/sshd_config + - /etc/ssh/sshd_config.d/00-ansible_system_role.conf tasks: + - name: "Backup configuration files" + include_tasks: tasks/backup.yml + - name: Configure sshd include_role: name: ansible-sshd @@ -8,7 +15,7 @@ sshd: AcceptEnv: LANG Banner: /etc/issue - Ciphers: aes256-gcm@openssh.com + Ciphers: aes256-ctr Subsystem: "sftp internal-sftp" sshd_config_file: /etc/ssh/sshd_config @@ -31,7 +38,7 @@ that: - "'acceptenv LANG' in runtime.stdout" - "'banner /etc/issue' in runtime.stdout" - - "'ciphers aes256-gcm@openssh.com' in runtime.stdout" + - "'ciphers aes256-ctr' in runtime.stdout" - "'subsystem sftp internal-sftp' in runtime.stdout" - name: Check the options are in configuration file @@ -39,6 +46,9 @@ that: - "'AcceptEnv LANG' in config.content | b64decode" - "'Banner /etc/issue' in config.content | b64decode" - - "'Ciphers aes256-gcm@openssh.com' in config.content | b64decode" + - "'Ciphers aes256-ctr' in config.content | b64decode" - "'Subsystem sftp internal-sftp' in config.content | b64decode" tags: tests::verify + + - name: "Restore configuration files" + include_tasks: tasks/restore.yml diff --git a/tests/tests_set_uncommon.yml b/tests/tests_set_uncommon.yml index 13586f5..7e3b4eb 100644 --- a/tests/tests_set_uncommon.yml +++ b/tests/tests_set_uncommon.yml @@ -1,6 +1,13 @@ --- - hosts: all + vars: + __sshd_test_backup_files: + - /etc/ssh/sshd_config + - /etc/ssh/sshd_config.d/00-ansible_system_role.conf tasks: + - name: "Backup configuration files" + include_tasks: tasks/backup.yml + - name: Configure sshd with uncommon options, making sure it keeps running block: - name: Configure ssh with unsupported options @@ -42,9 +49,13 @@ - not role_result.changed msg: "Role has not failed when it should have" - - name: Make sure service is still running - service: - name: sshd - state: started - register: result - failed_when: result.changed + - name: Make sure service is still running + service: + name: sshd + state: started + register: result + failed_when: result.changed + tags: tests::verify + + - name: "Restore configuration files" + include_tasks: tasks/restore.yml diff --git a/tests/tests_sshd_enable.yml b/tests/tests_sshd_enable.yml new file mode 100644 index 0000000..7a09d13 --- /dev/null +++ b/tests/tests_sshd_enable.yml @@ -0,0 +1,47 @@ +--- +- hosts: all + vars: + __sshd_test_backup_files: + - /etc/ssh/sshd_config + - /etc/ssh/sshd_config.d/00-ansible_system_role.conf + tasks: + - name: "Backup configuration files" + include_tasks: tasks/backup.yml + + - name: Configure sshd with the role disabled + include_role: + name: ansible-sshd + vars: + sshd_enable: false + sshd: + AcceptEnv: XDG_* + Banner: /etc/issue + Ciphers: aes256-ctr,aes128-ctr + sshd_config_file: /etc/ssh/sshd_config + + - name: Print current configuration file + slurp: + src: /etc/ssh/sshd_config + register: config + + - name: Print effective configuration + shell: > + if test ! -f /etc/ssh/ssh_host_rsa_key; then + ssh-keygen -q -t rsa -f /etc/ssh/ssh_host_rsa_key -C '' -N '' + fi; + sshd -T + register: runtime + + - name: Check the options were not applied + # note, the options are in lower-case here + assert: + that: + - "'Acceptenv XDG_*' not in config.content | b64decode" + - "'Banner /etc/issue' not in config.content | b64decode" + - "'Ciphers aes256-ctr,aes128-ctr' not in config.content | b64decode" + - "'acceptenv XDG_*' not in runtime.stdout" + - "'banner /etc/issue' not in runtime.stdout" + - "'ciphers aes256-ctr,aes128-ctr' not in runtime.stdout" + + - name: "Restore configuration files" + include_tasks: tasks/restore.yml diff --git a/tests/tests_sysconfig.yml b/tests/tests_sysconfig.yml index 872958d..009740b 100644 --- a/tests/tests_sysconfig.yml +++ b/tests/tests_sysconfig.yml @@ -1,6 +1,14 @@ --- - hosts: all + vars: + __sshd_test_backup_files: + - /etc/ssh/sshd_config + - /etc/ssh/sshd_config.d/00-ansible_system_role.conf + - /etc/sysconfig/sshd tasks: + - name: "Backup configuration files" + include_tasks: tasks/backup.yml + - name: Configure sshd include_role: name: ansible-sshd @@ -18,12 +26,31 @@ src: /etc/sysconfig/sshd register: config + - name: Evaluate sysconfig similarly as systemd + shell: | + source /etc/sysconfig/sshd + echo "CP=|$CRYPTO_POLICY|" + echo "RNG=|$SSH_USE_STRONG_RNG|" + register: evaluation + + - name: Evaluate sysconfig similarly as systemd on RHEL 8 + shell: | + source /etc/crypto-policies/back-ends/opensshserver.config + source /etc/sysconfig/sshd + echo "CP=|$CRYPTO_POLICY|" + echo "RNG=|$SSH_USE_STRONG_RNG|" + register: evaluation8 + when: + - ansible_facts['os_family'] == "RedHat" + - ansible_facts['distribution_major_version'] == "8" + - name: Check the crypto policies is overridden in RHEL 8 assert: that: - "'CRYPTO_POLICY=' in config.content | b64decode" # these are string variants in default configuration file - "'# CRYPTO_POLICY=' not in config.content | b64decode" + - "'CP=||' in evaluation8.stdout" when: - ansible_facts['os_family'] == "RedHat" - ansible_facts['distribution_major_version'] == "8" @@ -35,7 +62,11 @@ # these are string variants in default configuration file - "'SSH_USE_STRONG_RNG=0' not in config.content | b64decode" - "'# SSH_USE_STRONG_RNG=1' not in config.content | b64decode" - when: - - ansible_facts['os_family'] == "RedHat" - - ansible_facts['distribution'] != 'Fedora' + - "'RNG=|32|' in evaluation.stdout" tags: tests::verify + when: + - ansible_facts['os_family'] == "RedHat" + - ansible_facts['distribution'] != 'Fedora' + + - name: "Restore configuration files" + include_tasks: tasks/restore.yml diff --git a/vars/Debian.yml b/vars/Debian.yml index a95c39b..4aed470 100644 --- a/vars/Debian.yml +++ b/vars/Debian.yml @@ -34,3 +34,4 @@ __sshd_defaults: Subsystem: "sftp {{ sshd_sftp_server }}" UsePAM: yes __sshd_os_supported: yes +__sshd_runtime_directory: /run/sshd diff --git a/vars/Debian_10.yml b/vars/Debian_10.yml index cca5691..edb63bc 100644 --- a/vars/Debian_10.yml +++ b/vars/Debian_10.yml @@ -5,30 +5,11 @@ sshd_packages: - openssh-sftp-server sshd_config_mode: "0644" __sshd_defaults: - Port: 22 - Protocol: 2 - HostKey: - - /etc/ssh/ssh_host_rsa_key - - /etc/ssh/ssh_host_ed25519_key - HostKeyAlgorithms: ssh-ed25519,ecdsa-sha2-nistp256,ssh-rsa,ssh-ed25519-cert-v01@openssh.com - KexAlgorithms: curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,diffie-hellman-group-exchange-sha256 - MACs: umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com - SyslogFacility: AUTH - LogLevel: INFO - LoginGraceTime: 120 - PermitRootLogin: without-password - StrictModes: yes - PubkeyAuthentication: yes - IgnoreRhosts: yes - HostbasedAuthentication: no - PermitEmptyPasswords: no ChallengeResponseAuthentication: no X11Forwarding: yes - X11DisplayOffset: 10 PrintMotd: no - PrintLastLog: yes - TCPKeepAlive: yes AcceptEnv: LANG LC_* Subsystem: "sftp {{ sshd_sftp_server }}" UsePAM: yes __sshd_os_supported: yes +__sshd_runtime_directory: /run/sshd diff --git a/vars/Debian_8.yml b/vars/Debian_8.yml index f559c00..c5cace0 100644 --- a/vars/Debian_8.yml +++ b/vars/Debian_8.yml @@ -36,3 +36,4 @@ __sshd_defaults: Subsystem: "sftp {{ sshd_sftp_server }}" UsePAM: yes __sshd_os_supported: yes +__sshd_runtime_directory: /run/sshd diff --git a/vars/Debian_9.yml b/vars/Debian_9.yml index 10745d2..edb63bc 100644 --- a/vars/Debian_9.yml +++ b/vars/Debian_9.yml @@ -5,30 +5,11 @@ sshd_packages: - openssh-sftp-server sshd_config_mode: "0644" __sshd_defaults: - Port: 22 - Protocol: 2 - HostKey: - - /etc/ssh/ssh_host_rsa_key - - /etc/ssh/ssh_host_dsa_key - - /etc/ssh/ssh_host_ecdsa_key - - /etc/ssh/ssh_host_ed25519_key - UsePrivilegeSeparation: yes - SyslogFacility: AUTH - LogLevel: INFO - LoginGraceTime: 120 - PermitRootLogin: without-password - StrictModes: yes - PubkeyAuthentication: yes - IgnoreRhosts: yes - HostbasedAuthentication: no - PermitEmptyPasswords: no ChallengeResponseAuthentication: no X11Forwarding: yes - X11DisplayOffset: 10 PrintMotd: no - PrintLastLog: yes - TCPKeepAlive: yes AcceptEnv: LANG LC_* Subsystem: "sftp {{ sshd_sftp_server }}" UsePAM: yes __sshd_os_supported: yes +__sshd_runtime_directory: /run/sshd diff --git a/vars/RedHat_6.yml b/vars/RedHat_6.yml index 5b9ccb7..03ae474 100644 --- a/vars/RedHat_6.yml +++ b/vars/RedHat_6.yml @@ -4,6 +4,8 @@ sshd_packages: - openssh-server sshd_sftp_server: /usr/libexec/openssh/sftp-server __sshd_defaults: + HostKey: + - /etc/ssh/ssh_host_rsa_key Protocol: 2 SyslogFacility: AUTHPRIV PasswordAuthentication: yes @@ -20,5 +22,3 @@ __sshd_defaults: Subsystem: "sftp {{ sshd_sftp_server }}" __sshd_os_supported: yes __sshd_sysconfig_supports_use_strong_rng: true -sshd_hostkey_group: ssh_keys -sshd_hostkey_mode: "0640" diff --git a/vars/RedHat_8.yml b/vars/RedHat_8.yml index 909338f..7596ca5 100644 --- a/vars/RedHat_8.yml +++ b/vars/RedHat_8.yml @@ -11,6 +11,7 @@ __sshd_defaults: SyslogFacility: AUTHPRIV AuthorizedKeysFile: .ssh/authorized_keys PasswordAuthentication: yes + PermitRootLogin: yes ChallengeResponseAuthentication: no GSSAPIAuthentication: yes GSSAPICleanupCredentials: no diff --git a/vars/Ubuntu_18.yml b/vars/Ubuntu_18.yml index da8a005..46a500a 100644 --- a/vars/Ubuntu_18.yml +++ b/vars/Ubuntu_18.yml @@ -13,3 +13,4 @@ __sshd_defaults: AcceptEnv: LANG LC_* Subsystem: "sftp {{ sshd_sftp_server }}" __sshd_os_supported: yes +__sshd_runtime_directory: /run/sshd diff --git a/vars/Ubuntu_20.yml b/vars/Ubuntu_20.yml index a60fba4..57a6057 100644 --- a/vars/Ubuntu_20.yml +++ b/vars/Ubuntu_20.yml @@ -12,3 +12,4 @@ __sshd_defaults: AcceptEnv: LANG LC_* Subsystem: "sftp /usr/lib/openssh/sftp-server" __sshd_os_supported: yes +__sshd_runtime_directory: /run/sshd