diff --git a/meta/options_body b/meta/options_body index 23a00f4b..09f6c75f 100644 --- a/meta/options_body +++ b/meta/options_body @@ -28,12 +28,14 @@ DebianBanner DenyGroups DenyUsers DisableForwarding +ExposeAuthenticationMethods ExposeAuthInfo FingerprintHash ForceCommand GatewayPorts GSSAPIAuthentication GSSAPICleanupCredentials +GSSAPIEnablek5users GSSAPIKeyExchange GSSAPIKexAlgorithms GSSAPIStoreCredentialsOnRekey @@ -57,6 +59,8 @@ KerberosGetAFSToken KerberosOrLocalPasswd KerberosTicketCleanup KerberosUniqueTicket +KerberosUniqueCCache +KerberosUseKuserok KexAlgorithms KeyRegenerationInterval LogLevel @@ -96,6 +100,7 @@ RhostsRSAAuthentication SecurityKeyProvider SetEnv ServerKeyBits +ShowPatchLevel StreamLocalBindMask StreamLocalBindUnlink StrictModes diff --git a/tasks/install.yml b/tasks/install.yml index 8ef10928..e02fe94e 100644 --- a/tasks/install.yml +++ b/tasks/install.yml @@ -103,6 +103,7 @@ changed_when: false when: - __sshd_hostkeys_from_config | from_json == [] + - __sshd_supports_validate - name: Generate temporary hostkey ansible.builtin.command: > diff --git a/tasks/install_config.yml b/tasks/install_config.yml index 0067e407..2a4dcea1 100644 --- a/tasks/install_config.yml +++ b/tasks/install_config.yml @@ -16,7 +16,9 @@ group: "{{ sshd_config_group }}" mode: "{{ sshd_config_mode }}" validate: >- - {% if sshd_test_hostkey is defined and sshd_test_hostkey.path is defined %} + {% if not __sshd_supports_validate %} + true %s + {% elif sshd_test_hostkey is defined and sshd_test_hostkey.path is defined %} {{ sshd_binary }} -t -f %s -h {{ sshd_test_hostkey.path }}/rsa_key {% else %} {{ sshd_binary }} -t -f %s @@ -33,7 +35,9 @@ group: "{{ sshd_config_group }}" mode: "{{ sshd_config_mode }}" validate: >- - {% if sshd_test_hostkey is defined and sshd_test_hostkey.path is defined %} + {% if not __sshd_supports_validate %} + true %s + {% elif sshd_test_hostkey is defined and sshd_test_hostkey.path is defined %} {{ sshd_binary }} -t -f %s -h {{ sshd_test_hostkey.path }}/rsa_key {% else %} {{ sshd_binary }} -t -f %s diff --git a/tasks/install_namespace.yml b/tasks/install_namespace.yml index 08ef3198..801d0505 100644 --- a/tasks/install_namespace.yml +++ b/tasks/install_namespace.yml @@ -13,7 +13,9 @@ create: yes marker: "# {mark} sshd system role managed block: namespace {{ sshd_config_namespace }}" validate: >- - {% if sshd_test_hostkey is defined and sshd_test_hostkey.path is defined %} + {% if not __sshd_supports_validate %} + true %s + {% elif sshd_test_hostkey is defined and sshd_test_hostkey.path is defined %} {{ sshd_binary }} -t -f %s -h {{ sshd_test_hostkey.path }}/rsa_key {% else %} {{ sshd_binary }} -t -f %s diff --git a/templates/sshd.service.j2 b/templates/sshd.service.j2 index a969ebb8..b229bd90 100644 --- a/templates/sshd.service.j2 +++ b/templates/sshd.service.j2 @@ -1,7 +1,9 @@ [Unit] Description=OpenBSD Secure Shell server +Documentation=man:sshd(8) man:sshd_config(5) [Service] +Type=notify ExecStartPre={{ sshd_binary }} -t ExecStart={{ sshd_binary }} -D -f {{ sshd_config_file }} ExecReload={{ sshd_binary }} -t @@ -9,9 +11,8 @@ ExecReload=/bin/kill -HUP $MAINPID KillMode=process Restart=on-failure RestartPreventExitStatus=255 -Type=notify -RuntimeDirectory={{ sshd_binary | basename }} -RuntimeDirectoryMode=0755 +RuntimeDirectory={{ __sshd_runtime_directory }} +RuntimeDirectoryMode={{ __sshd_runtime_directory_mode }} [Install] WantedBy=multi-user.target diff --git a/templates/sshd.socket.j2 b/templates/sshd.socket.j2 index add47313..30d424f0 100644 --- a/templates/sshd.socket.j2 +++ b/templates/sshd.socket.j2 @@ -1,7 +1,8 @@ [Unit] Description=OpenBSD Secure Shell server socket +Documentation=man:sshd(8) man:sshd_config(5) Before={{ sshd_service }}.service -Conflicts={{sshd_service }}.service +Conflicts={{ sshd_service }}.service [Socket] ListenStream=22 diff --git a/templates/sshd@.service.j2 b/templates/sshd@.service.j2 index d76fddea..f5a6ce68 100644 --- a/templates/sshd@.service.j2 +++ b/templates/sshd@.service.j2 @@ -1,9 +1,10 @@ [Unit] Description=OpenBSD Secure Shell server per-connection daemon +Documentation=man:sshd(8) man:sshd_config(5) After=auditd.service [Service] ExecStart=-{{ sshd_binary }} -i -f {{ sshd_config_file }} StandardInput=socket -RuntimeDirectory={{ sshd_binary }} -RuntimeDirectoryMode=0755 +RuntimeDirectory={{ __sshd_runtime_directory }} +RuntimeDirectoryMode={{ __sshd_runtime_directory_mode }} diff --git a/templates/sshd_config.j2 b/templates/sshd_config.j2 index a3b2465c..400a967f 100644 --- a/templates/sshd_config.j2 +++ b/templates/sshd_config.j2 @@ -143,12 +143,14 @@ Match {{ match["Condition"] }} {{ body_option("DenyGroups",sshd_DenyGroups) -}} {{ body_option("DenyUsers",sshd_DenyUsers) -}} {{ body_option("DisableForwarding",sshd_DisableForwarding) -}} +{{ body_option("ExposeAuthenticationMethods",sshd_ExposeAuthenticationMethods) -}} {{ body_option("ExposeAuthInfo",sshd_ExposeAuthInfo) -}} {{ body_option("FingerprintHash",sshd_FingerprintHash) -}} {{ body_option("ForceCommand",sshd_ForceCommand) -}} {{ body_option("GatewayPorts",sshd_GatewayPorts) -}} {{ body_option("GSSAPIAuthentication",sshd_GSSAPIAuthentication) -}} {{ body_option("GSSAPICleanupCredentials",sshd_GSSAPICleanupCredentials) -}} +{{ body_option("GSSAPIEnablek5users",sshd_GSSAPIEnablek5users) -}} {{ body_option("GSSAPIKeyExchange",sshd_GSSAPIKeyExchange) -}} {{ body_option("GSSAPIKexAlgorithms",sshd_GSSAPIKexAlgorithms) -}} {{ body_option("GSSAPIStoreCredentialsOnRekey",sshd_GSSAPIStoreCredentialsOnRekey) -}} @@ -172,6 +174,8 @@ Match {{ match["Condition"] }} {{ body_option("KerberosOrLocalPasswd",sshd_KerberosOrLocalPasswd) -}} {{ body_option("KerberosTicketCleanup",sshd_KerberosTicketCleanup) -}} {{ body_option("KerberosUniqueTicket",sshd_KerberosUniqueTicket) -}} +{{ body_option("KerberosUniqueCCache",sshd_KerberosUniqueCCache) -}} +{{ body_option("KerberosUseKuserok",sshd_KerberosUseKuserok) -}} {{ body_option("KexAlgorithms",sshd_KexAlgorithms) -}} {{ body_option("KeyRegenerationInterval",sshd_KeyRegenerationInterval) -}} {{ body_option("LogLevel",sshd_LogLevel) -}} @@ -211,6 +215,7 @@ Match {{ match["Condition"] }} {{ body_option("SecurityKeyProvider",sshd_SecurityKeyProvider) -}} {{ body_option("SetEnv",sshd_SetEnv) -}} {{ body_option("ServerKeyBits",sshd_ServerKeyBits) -}} +{{ body_option("ShowPatchLevel",sshd_ShowPatchLevel) -}} {{ body_option("StreamLocalBindMask",sshd_StreamLocalBindMask) -}} {{ body_option("StreamLocalBindUnlink",sshd_StreamLocalBindUnlink) -}} {{ body_option("StrictModes",sshd_StrictModes) -}} diff --git a/templates/sshd_config_snippet.j2 b/templates/sshd_config_snippet.j2 index a12cb3bd..e6973fff 100644 --- a/templates/sshd_config_snippet.j2 +++ b/templates/sshd_config_snippet.j2 @@ -142,12 +142,14 @@ Match {{ match["Condition"] }} {{ body_option("DenyGroups",sshd_DenyGroups) -}} {{ body_option("DenyUsers",sshd_DenyUsers) -}} {{ body_option("DisableForwarding",sshd_DisableForwarding) -}} +{{ body_option("ExposeAuthenticationMethods",sshd_ExposeAuthenticationMethods) -}} {{ body_option("ExposeAuthInfo",sshd_ExposeAuthInfo) -}} {{ body_option("FingerprintHash",sshd_FingerprintHash) -}} {{ body_option("ForceCommand",sshd_ForceCommand) -}} {{ body_option("GatewayPorts",sshd_GatewayPorts) -}} {{ body_option("GSSAPIAuthentication",sshd_GSSAPIAuthentication) -}} {{ body_option("GSSAPICleanupCredentials",sshd_GSSAPICleanupCredentials) -}} +{{ body_option("GSSAPIEnablek5users",sshd_GSSAPIEnablek5users) -}} {{ body_option("GSSAPIKeyExchange",sshd_GSSAPIKeyExchange) -}} {{ body_option("GSSAPIKexAlgorithms",sshd_GSSAPIKexAlgorithms) -}} {{ body_option("GSSAPIStoreCredentialsOnRekey",sshd_GSSAPIStoreCredentialsOnRekey) -}} @@ -171,6 +173,8 @@ Match {{ match["Condition"] }} {{ body_option("KerberosOrLocalPasswd",sshd_KerberosOrLocalPasswd) -}} {{ body_option("KerberosTicketCleanup",sshd_KerberosTicketCleanup) -}} {{ body_option("KerberosUniqueTicket",sshd_KerberosUniqueTicket) -}} +{{ body_option("KerberosUniqueCCache",sshd_KerberosUniqueCCache) -}} +{{ body_option("KerberosUseKuserok",sshd_KerberosUseKuserok) -}} {{ body_option("KexAlgorithms",sshd_KexAlgorithms) -}} {{ body_option("KeyRegenerationInterval",sshd_KeyRegenerationInterval) -}} {{ body_option("LogLevel",sshd_LogLevel) -}} @@ -210,6 +214,7 @@ Match {{ match["Condition"] }} {{ body_option("SecurityKeyProvider",sshd_SecurityKeyProvider) -}} {{ body_option("SetEnv",sshd_SetEnv) -}} {{ body_option("ServerKeyBits",sshd_ServerKeyBits) -}} +{{ body_option("ShowPatchLevel",sshd_ShowPatchLevel) -}} {{ body_option("StreamLocalBindMask",sshd_StreamLocalBindMask) -}} {{ body_option("StreamLocalBindUnlink",sshd_StreamLocalBindUnlink) -}} {{ body_option("StrictModes",sshd_StrictModes) -}} diff --git a/tests/tests_all_options.yml b/tests/tests_all_options.yml new file mode 100644 index 00000000..7eff5f50 --- /dev/null +++ b/tests/tests_all_options.yml @@ -0,0 +1,105 @@ +--- +- name: Test we can handle all configuration options documented in manual page + hosts: all + gather_facts: true + vars: + __sshd_test_backup_files: + - /etc/dnf/dnf.conf + - /etc/yum.conf + - /tmp/sshd_config + sshd_c: {} + sshd_skip_test: false + pkg_mgr: "{{ 'dnf' if ansible_facts['distribution_version'] | int > 7 else 'yum' }}" + tasks: + - name: Backup configuration files + ansible.builtin.include_tasks: tasks/backup.yml + + - name: Skip test on EL6 as it has some crippled manpages + ansible.builtin.set_fact: + sshd_skip_test: true + when: + - ansible_facts['os_family'] == "RedHat" + - ansible_facts['distribution_version'] | int <= 6 + + - name: Enable installation of manual pages on Fedora/RHEL + ansible.builtin.lineinfile: + line: tsflags=nodocs + path: "{{ '/etc/dnf/dnf.conf' if ansible_facts['distribution_version'] | int > 7 else '/etc/yum.conf' }}" + state: absent + when: + - ansible_facts['os_family'] == "RedHat" + + - name: Reinstall manual pages for openssh-server on RHEL + ansible.builtin.command: "{{ pkg_mgr|quote }} reinstall -y openssh-server" + when: + - ansible_facts['os_family'] == "RedHat" + + - name: Unminimize image on Debian. It looks like there is no simpler way to get manual pages + ansible.builtin.shell: yes | unminimize + when: + - ansible_facts['distribution'] == "Ubuntu" + + - name: Make sure manual pages and bash are installed + ansible.builtin.package: + name: + - man + - bash + state: present + + - name: Get list of options from manual page + ansible.builtin.shell: >- + man sshd_config |cat + + - name: Get list of options from manual page + ansible.builtin.shell: >- + set -o pipefail && man sshd_config \ + | grep -o '^ [A-Z][A-Za-z0-9]*\(.\| \)' \ + | grep -v "[A-Za-z0-9] $" | grep -v "[^A-Za-z0-9 ]$" \ + | awk '{ print $1 }' \ + | grep -v '^$' | grep -v "^Match$" + args: + executable: /bin/bash + register: sshd_options + changed_when: false + when: not sshd_skip_test + + - name: Print all the possible options + ansible.builtin.debug: + var: ssh_options.stdout_lines + + - name: Construct the configuration list + ansible.builtin.set_fact: + sshd_c: "{{ sshd_c | combine({item: 'yes'}) }}" + loop: + "{{ sshd_options.stdout_lines }}" + when: not sshd_skip_test + + - name: Run role + ansible.builtin.include_role: + name: ansible-sshd + vars: + # The configuration is not valid as we are using bogus values + __sshd_supports_validate: false + # The hostkeys are not valid either so do not validate them + sshd_verify_hostkeys: [] + sshd_config_file: /tmp/sshd_config + sshd: + "{{ sshd_c }}" + when: not sshd_skip_test + + - name: Download the configuration file + ansible.builtin.slurp: + src: /tmp/sshd_config + register: config + when: not sshd_skip_test + + - name: Verify the options are in the file + ansible.builtin.assert: + that: + - "'{{ item }} yes' in config.content | b64decode " + loop: + "{{ sshd_options.stdout_lines }}" + when: not sshd_skip_test + + - name: Restore configuration files + ansible.builtin.include_tasks: tasks/restore.yml diff --git a/vars/main.yml b/vars/main.yml index b9ceb081..6a109546 100644 --- a/vars/main.yml +++ b/vars/main.yml @@ -31,6 +31,9 @@ __sshd_os_supported: no __sshd_sysconfig_supports_crypto_policy: false __sshd_sysconfig_supports_use_strong_rng: false +# The runtime directory is used by systemd to provide termoporary directory for the service +# This is used as a RuntimeDirectory= option in the service file and it needs to exist +# before running sshd for example in the validate mode. __sshd_runtime_directory: ~ __sshd_runtime_directory_mode: "0755" @@ -44,3 +47,7 @@ __sshd_drop_in_dir_mode: '0755' # This is usually the case when the selection is up to the OpenSSH defaults or # drop-in directory is used. __sshd_verify_hostkeys_default: [] + +# This switch can control if the validate step is supported by the target OS. +# This is useful for very old OpenSSH or for tests that generate invalid configurations +__sshd_supports_validate: true