diff --git a/.github/workflows/ansible-centos-check.yml b/.github/workflows/ansible-centos-check.yml index 7a5931c..1bf67a5 100644 --- a/.github/workflows/ansible-centos-check.yml +++ b/.github/workflows/ansible-centos-check.yml @@ -17,7 +17,11 @@ jobs: ' __sshd_skip_virt_env: "{{ __sshd_skip_virt_env }}"' ' __sshd_config_file: "{{ __sshd_config_file }}"' >> tasks/variables.yml - - run: "sed -i -e '/public: true/d' tests/tasks/restore.yml tests/tests_duplicate_role.yml" + - run: >- + sed -i -e '/public: true/d' + tests/tasks/restore.yml + tests/tests_duplicate_role.yml + tests/tests_os_defaults.yml - run: "sed -i -e 's/ansible.builtin.//g' */*.yml */*/*.yml" - name: ansible check with centos 6 diff --git a/README.md b/README.md index bfc4f91..f541156 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,7 @@ If set to *false*, the role will be completely disabled. Defaults to *true*. If set to *true*, don't apply default values. This means that you must have a complete set of configuration defaults via either the `sshd` dict, or `sshd_Key` variables. Defaults to *false* unless `sshd_config_namespace` is -set or `sshd_config_file` points to the drop-in directory defined by -`__sshd_drop_in_dir` to avoid recursive include. +set or `sshd_config_file` points to a drop-in directory to avoid recursive include. * `sshd_manage_service` @@ -167,17 +166,17 @@ The path where the openssh configuration produced by this role should be saved. This is useful mostly when generating configuration snippets to Include from drop-in directory (default in Fedora and RHEL9). -When this path points to system drop-in directory (defined with internal -variable `__sshd_drop_in_dir`), the main configuration file (defined with -internal variable `__sshd_main_config_file`) is checked to contain the -default `Include` directive from `__sshd_defaults` dict. +When this path points to a drop-in directory (like +`/etc/ssh/sshd_confg.d/00-custom.conf`), the main configuration file (defined +with the variable `sshd_main_config_file`) is checked to contain a proper +`Include` directive. * `sshd_config_namespace` By default (*null*), the role defines whole content of the configuration file including system defaults. You can use this variable to invoke this role from -other roles or from multiple places in a single playbook on systems that do not -support drop-in directory. The `sshd_skip_defaults` is ignored and no system +other roles or from multiple places in a single playbook as an alternative to +using a drop-in directory. The `sshd_skip_defaults` is ignored and no system defaults are used in this case. When this variable is set, the role places the configuration that you specify diff --git a/defaults/main.yml b/defaults/main.yml index d12e7ce..f35e3b3 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -57,6 +57,9 @@ sshd_binary: "{{ __sshd_binary }}" sshd_service: "{{ __sshd_service }}" sshd_sftp_server: "{{ __sshd_sftp_server }}" +sshd_drop_in_dir_mode: "{{ __sshd_drop_in_dir_mode }}" +sshd_main_config_file: "{{ __sshd_main_config_file }}" + # This lists by default all hostkeys as rendered in the generated configuration # file ("auto"). Before attempting to run sshd (either for verification of # configuration or restarting), we make sure the keys exist and have correct diff --git a/meta/10_top.j2 b/meta/10_top.j2 index 6b78bff..1e297fe 100644 --- a/meta/10_top.j2 +++ b/meta/10_top.j2 @@ -20,8 +20,9 @@ {% set value = override %} {% elif sshd[key] is defined %} {% set value = sshd[key] %} -{% elif __sshd_drop_in_dir and sshd_config_file.startswith(__sshd_drop_in_dir) %} -{# The drop-in directory does not use the defaults from main file to avoid recursion #} +{% elif sshd_main_config_file is not none + and sshd_config_file | dirname != sshd_main_config_file | dirname %} +{# Do not use the defaults from main file to avoid recursion #} {% elif __sshd_defaults[key] is defined and not sshd_skip_defaults %} {% if key == 'HostKey' and __sshd_fips_mode %} {% set value = __sshd_defaults[key] | difference(__sshd_hostkeys_nofips) %} diff --git a/tasks/install.yml b/tasks/install.yml index 8d249a5..1ec071a 100644 --- a/tasks/install.yml +++ b/tasks/install.yml @@ -119,7 +119,7 @@ group: root mode: "{{ __sshd_runtime_directory_mode }}" when: - - __sshd_runtime_directory | d(false) + - __sshd_runtime_directory is not none - name: Create the complete configuration file ansible.builtin.include_tasks: install_config.yml diff --git a/tasks/install_config.yml b/tasks/install_config.yml index fd222c7..0067e40 100644 --- a/tasks/install_config.yml +++ b/tasks/install_config.yml @@ -1,4 +1,13 @@ --- +- name: Create a directory for drop-in configuration snippets + ansible.builtin.file: + path: "{{ sshd_config_file | dirname }}" + state: directory + mode: "{{ sshd_drop_in_dir_mode }}" + when: + - sshd_main_config_file is not none + - sshd_config_file | dirname != sshd_main_config_file | dirname + - name: Create the complete configuration file ansible.builtin.template: src: sshd_config.j2 @@ -18,8 +27,8 @@ - name: Make sure the include path is present in the main sshd_config ansible.builtin.lineinfile: insertbefore: BOF - line: "Include {{ __sshd_defaults['Include'] }}" - path: "{{ __sshd_main_config_file }}" + line: "Include {{ sshd_config_file | dirname }}/*.conf" + path: "{{ sshd_main_config_file }}" owner: "{{ sshd_config_owner }}" group: "{{ sshd_config_group }}" mode: "{{ sshd_config_mode }}" @@ -32,7 +41,5 @@ backup: "{{ sshd_backup }}" notify: reload_sshd when: - - __sshd_defaults['Include'] | d(false) - - __sshd_main_config_file is not none - - __sshd_drop_in_dir is not none - - sshd_config_file.startswith(__sshd_drop_in_dir) + - sshd_main_config_file is not none + - sshd_config_file | dirname != sshd_main_config_file | dirname diff --git a/templates/sshd_config.j2 b/templates/sshd_config.j2 index c18b6d3..2899f0a 100644 --- a/templates/sshd_config.j2 +++ b/templates/sshd_config.j2 @@ -21,8 +21,9 @@ {% set value = override %} {% elif sshd[key] is defined %} {% set value = sshd[key] %} -{% elif __sshd_drop_in_dir and sshd_config_file.startswith(__sshd_drop_in_dir) %} -{# The drop-in directory does not use the defaults from main file to avoid recursion #} +{% elif sshd_main_config_file is not none + and sshd_config_file | dirname != sshd_main_config_file | dirname %} +{# Do not use the defaults from main file to avoid recursion #} {% elif __sshd_defaults[key] is defined and not sshd_skip_defaults %} {% if key == 'HostKey' and __sshd_fips_mode %} {% set value = __sshd_defaults[key] | difference(__sshd_hostkeys_nofips) %} diff --git a/templates/sshd_config_snippet.j2 b/templates/sshd_config_snippet.j2 index f437802..0ece8ed 100644 --- a/templates/sshd_config_snippet.j2 +++ b/templates/sshd_config_snippet.j2 @@ -20,8 +20,9 @@ {% set value = override %} {% elif sshd[key] is defined %} {% set value = sshd[key] %} -{% elif __sshd_drop_in_dir and sshd_config_file.startswith(__sshd_drop_in_dir) %} -{# The drop-in directory does not use the defaults from main file to avoid recursion #} +{% elif sshd_main_config_file is not none + and sshd_config_file | dirname != sshd_main_config_file | dirname %} +{# Do not use the defaults from main file to avoid recursion #} {% elif __sshd_defaults[key] is defined and not sshd_skip_defaults %} {% if key == 'HostKey' and __sshd_fips_mode %} {% set value = __sshd_defaults[key] | difference(__sshd_hostkeys_nofips) %} diff --git a/tests/tests_include_present.yml b/tests/tests_include_present.yml index c54221b..08ad532 100644 --- a/tests/tests_include_present.yml +++ b/tests/tests_include_present.yml @@ -81,3 +81,85 @@ - name: "Restore configuration files" ansible.builtin.include_tasks: tasks/restore.yml + +- hosts: all + vars: + __sshd_test_backup_files: + - /etc/ssh/custom_sshd_config + - /etc/ssh/custom_sshd_config.d/custom-drop-in + + tasks: + - name: Run only for a specific OS with an capable OpenSSH version + ansible.builtin.meta: end_play + when: + ansible_facts['distribution'] != 'Ubuntu' + or ansible_facts['distribution_major_version']|int != 20 + + - name: "Backup configuration files" + ansible.builtin.include_tasks: tasks/backup.yml + + - name: Create dummy main configuration file + # Normally, this should not be needed. For test, however, we need a file + # different to the one in the first play. + file: + path: /etc/ssh/custom_sshd_config + state: touch + + - name: Create a new configuration in a custom drop-in directory + ansible.builtin.include_role: + name: ansible-sshd + vars: + sshd_config_file: /etc/ssh/custom_sshd_config.d/custom-drop-in + sshd_main_config_file: /etc/ssh/custom_sshd_config + sshd_drop_in_dir_mode: '0770' + sshd: + Banner: /etc/include-issue + Ciphers: aes192-ctr + + - name: Verify the options are correctly set + block: + - name: Flush handlers + ansible.builtin.meta: flush_handlers + + - name: Print custom drop-in configuration file + ansible.builtin.slurp: + src: /etc/ssh/custom_sshd_config.d/custom-drop-in + register: custom_drop_in + + - name: Print the custom configuration file + ansible.builtin.slurp: + src: /etc/ssh/custom_sshd_config + register: custom_config + + - name: Check content of custom drop-in configuration file + ansible.builtin.assert: + that: + - "'Banner /etc/include-issue' in custom_drop_in.content | b64decode" + - "'Ciphers aes192-ctr' in custom_drop_in.content | b64decode" + - "'Include /etc/ssh/custom_sshd_config.d/*.conf' not in custom_drop_in.content | b64decode" + - "'Subsystem sftp /usr/libexec/openssh/sftp-server' not in custom_drop_in.content | b64decode" + - "'Subsystem sftp /usr/lib/openssh/sftp-server' not in custom_drop_in.content | b64decode" + + - name: Check common content of the custom configuration file + ansible.builtin.assert: + that: + - "'Banner /etc/include-issue' not in custom_config.content | b64decode" + - "'Ciphers aes192-ctr' not in custom_config.content | b64decode" + - "'Include /etc/ssh/custom_sshd_config.d/*.conf' in custom_config.content | b64decode" + + - name: Read drop in directory mode + ansible.builtin.stat: + path: "/etc/ssh/custom_sshd_config.d" + register: drop_in_dir_stat + + - name: Check drop in directory mode has been set correctly + assert: + that: + - drop_in_dir_stat.stat.isdir | bool + - drop_in_dir_stat.stat.mode == '0770' + msg: "effective mode: {{ drop_in_dir_stat.stat.mode }}, desired mode: 0770" + + tags: tests::verify + + - name: "Restore configuration files" + ansible.builtin.include_tasks: tasks/restore.yml diff --git a/tests/tests_os_defaults.yml b/tests/tests_os_defaults.yml index 3d7e38f..f4ac537 100644 --- a/tests/tests_os_defaults.yml +++ b/tests/tests_os_defaults.yml @@ -27,6 +27,7 @@ - name: Configure sshd ansible.builtin.include_role: name: ansible-sshd + public: true - name: Show effective configuration after running role (role defaults) ansible.builtin.command: sshd -T @@ -41,5 +42,17 @@ # RHEL6/CentOS6 images have modified sshd_config, different from what is in rpm package - not (ansible_facts['os_family'] == 'RedHat' and ansible_facts['distribution_major_version'] == '6') + - name: Read drop in directory mode + ansible.builtin.stat: + path: "{{ __sshd_defaults.Include | dirname }}" + register: drop_in_dir_stat + when: __sshd_defaults.Include is defined + + - name: Check drop in directory mode has not changed + assert: + that: + - drop_in_dir_stat.stat.mode == __sshd_drop_in_dir_mode + when: __sshd_defaults.Include is defined + - name: Restore configuration files ansible.builtin.include_tasks: tasks/restore.yml diff --git a/vars/Fedora.yml b/vars/Fedora.yml index cbf709c..5dbad3e 100644 --- a/vars/Fedora.yml +++ b/vars/Fedora.yml @@ -23,5 +23,5 @@ __sshd_hostkeys_nofips: __sshd_hostkey_group: ssh_keys __sshd_hostkey_mode: "0640" -__sshd_drop_in_dir: /etc/ssh/sshd_config.d/ +__sshd_drop_in_dir_mode: '0700' __sshd_main_config_file: /etc/ssh/sshd_config diff --git a/vars/RedHat_9.yml b/vars/RedHat_9.yml index 6f4ca73..7c4fc1d 100644 --- a/vars/RedHat_9.yml +++ b/vars/RedHat_9.yml @@ -23,5 +23,5 @@ __sshd_hostkeys_nofips: __sshd_hostkey_group: ssh_keys __sshd_hostkey_mode: "0640" -__sshd_drop_in_dir: /etc/ssh/sshd_config.d/ +__sshd_drop_in_dir_mode: '0700' __sshd_main_config_file: /etc/ssh/sshd_config diff --git a/vars/Ubuntu_22.yml b/vars/Ubuntu_22.yml index 5658e14..fc3df96 100644 --- a/vars/Ubuntu_22.yml +++ b/vars/Ubuntu_22.yml @@ -19,5 +19,5 @@ __sshd_defaults: __sshd_runtime_directory: /run/sshd -__sshd_drop_in_dir: /etc/ssh/sshd_config.d/ +__sshd_drop_in_dir_mode: '0755' __sshd_main_config_file: /etc/ssh/sshd_config diff --git a/vars/main.yml b/vars/main.yml index a44b5fb..b9ceb08 100644 --- a/vars/main.yml +++ b/vars/main.yml @@ -31,15 +31,14 @@ __sshd_os_supported: no __sshd_sysconfig_supports_crypto_policy: false __sshd_sysconfig_supports_use_strong_rng: false -__sshd_runtime_directory: false +__sshd_runtime_directory: ~ __sshd_runtime_directory_mode: "0755" -# If the system supports drop-in directory, it is configured in this variable. It is used -# to distinguish if we are writing a configuration snippet or we should write defaults. -__sshd_drop_in_dir: false # this is the path to the main sshd_config which is checked for Include directive when # drop-in directory is used -__sshd_main_config_file: false +__sshd_main_config_file: ~ + +__sshd_drop_in_dir_mode: '0755' # The list of hostkeys to check when there are none listed in configuration file. # This is usually the case when the selection is up to the OpenSSH defaults or