mirror of
https://github.com/willshersystems/ansible-sshd
synced 2024-11-08 12:53:29 +01:00
feat: manage ssh certificates (#252)
* Role configured to accept SSH connection via SSH certificates * Works with or without principals and ansible-lint updated * add test for SSH certificates authentication with principals * Add configuration to run tests for SSH certificates authentication with principals * tasks to use SSH certificates grouped into one file * Update README.md
This commit is contained in:
parent
d54f51f32a
commit
0bc6d8f40b
9 changed files with 303 additions and 0 deletions
56
README.md
56
README.md
|
@ -282,6 +282,62 @@ Default path to the sftp server binary.
|
||||||
|
|
||||||
This variable is set to *true* after the role was successfully executed.
|
This variable is set to *true* after the role was successfully executed.
|
||||||
|
|
||||||
|
## Configure SSH certificate authentication
|
||||||
|
|
||||||
|
To configure SSH certificate authentication on your SSH server, you need to provide at least the trusted user CA key, which will be used to validate client certificates against.
|
||||||
|
This is done with the `sshd_trusted_user_ca_keys_list` variable.
|
||||||
|
|
||||||
|
If you need to map some of the authorized principals to system users, you can do that using the `sshd_principals` variable.
|
||||||
|
|
||||||
|
### Additional variables
|
||||||
|
|
||||||
|
#### sshd_trusted_user_ca_keys_list
|
||||||
|
|
||||||
|
List of the trusted user CA public keys in OpenSSH (one-line) format (mandatory).
|
||||||
|
|
||||||
|
#### sshd_trustedusercakeys_directory_owner, shsd_trustedusercakeys_directory_group, sshd_trustedusercakeys_directory_mode
|
||||||
|
|
||||||
|
Use these variables to set the ownership and permissions for the Trusted User CA Keys directory. Defaults are respectively *root*, *root* and *0755*.
|
||||||
|
|
||||||
|
#### sshd_trustedusercakeys_file_owner, shsd_trustedusercakeys_file_group, sshd_trustedusercakeys_file_mode
|
||||||
|
|
||||||
|
Use these variables to set the ownership and permissions for the Trusted User CA Keys file. Defaults are respectively *root*, *root* and *0640*.
|
||||||
|
|
||||||
|
#### sshd_principals
|
||||||
|
|
||||||
|
A dict containing principals for users in the os (optional). e.g.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
sshd_principals:
|
||||||
|
admin:
|
||||||
|
- frontend-admin
|
||||||
|
- backend-admin
|
||||||
|
somelinuxuser:
|
||||||
|
- some-principal-defined-in-certificate
|
||||||
|
```
|
||||||
|
|
||||||
|
#### sshd_authorizedprincipals_directory_owner, shsd_authorizedprincipals_directory_group, sshd_authorizedprincipals_directory_mode
|
||||||
|
|
||||||
|
Use these variables to set the ownership and permissions for the Authorized Principals directory. Defaults are respectively *root*, *root* and *0755*.
|
||||||
|
|
||||||
|
#### sshd_authorizedprincipals_file_owner, shsd_authorizedprincipals_file_group, sshd_authorizedprincipals_file_mode
|
||||||
|
|
||||||
|
Use these variables to set the ownership and permissions for the Authorized Principals file. Defaults are respectively *root*, *root* and *0644*.
|
||||||
|
|
||||||
|
### Additional configuration
|
||||||
|
|
||||||
|
The SSH server needs this information stored in files so in addition to the above variables, respective configuration options `TrustedUserCAKeys` (mandatory) and `AuthorizedPrincipalsFile` (optional) need to be present the `sshd` dictionary when invoking the role. For example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
sshd:
|
||||||
|
TrustedUserCAKeys: /etc/ssh/path-to-trusted-user-ca-keys/trusted-user-ca-keys.pub
|
||||||
|
AuthorizedPrincipalsFile: "/etc/ssh/path-to-auth-principals/auth_principals/%u"
|
||||||
|
```
|
||||||
|
|
||||||
|
To learn more about SSH Certificates, here is a [nice tutorial to pure SSH certificates, from wikibooks](https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Certificate-based_Authentication).
|
||||||
|
|
||||||
|
To understand principals and to set up SSH certificates with Vault, this is a [well-explained tutorial from Hashicorp](https://www.hashicorp.com/blog/managing-ssh-access-at-scale-with-hashicorp-vault).
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
None
|
None
|
||||||
|
|
|
@ -44,6 +44,12 @@ sshd: {}
|
||||||
# configuration file snippet or configuring second sshd service
|
# configuration file snippet or configuring second sshd service
|
||||||
sshd_config_file: "{{ __sshd_config_file }}"
|
sshd_config_file: "{{ __sshd_config_file }}"
|
||||||
|
|
||||||
|
# If not empty, list of trusted CA keys
|
||||||
|
sshd_trusted_user_ca_keys_list: []
|
||||||
|
|
||||||
|
# If not empty, dict containing principals for users in the os
|
||||||
|
sshd_principals: {}
|
||||||
|
|
||||||
### VARS DEFAULTS
|
### VARS DEFAULTS
|
||||||
### The following are defaults for OS specific configuration in var files in
|
### The following are defaults for OS specific configuration in var files in
|
||||||
### this role. They should not be set directly by role users, unless they know
|
### this role. They should not be set directly by role users, unless they know
|
||||||
|
@ -60,6 +66,20 @@ sshd_sftp_server: "{{ __sshd_sftp_server }}"
|
||||||
sshd_drop_in_dir_mode: "{{ __sshd_drop_in_dir_mode }}"
|
sshd_drop_in_dir_mode: "{{ __sshd_drop_in_dir_mode }}"
|
||||||
sshd_main_config_file: "{{ __sshd_main_config_file }}"
|
sshd_main_config_file: "{{ __sshd_main_config_file }}"
|
||||||
|
|
||||||
|
sshd_trustedusercakeys_directory_owner: "{{ __sshd_trustedusercakeys_directory_owner }}"
|
||||||
|
sshd_trustedusercakeys_directory_group: "{{ __sshd_trustedusercakeys_directory_group }}"
|
||||||
|
sshd_trustedusercakeys_directory_mode: "{{ __sshd_trustedusercakeys_directory_mode }}"
|
||||||
|
sshd_trustedusercakeys_file_owner: "{{ __sshd_trustedusercakeys_file_owner }}"
|
||||||
|
sshd_trustedusercakeys_file_group: "{{ __sshd_trustedusercakeys_file_group }}"
|
||||||
|
sshd_trustedusercakeys_file_mode: "{{ __sshd_trustedusercakeys_file_mode }}"
|
||||||
|
|
||||||
|
sshd_authorizedprincipals_directory_owner: "{{ __sshd_authorizedprincipals_directory_owner }}"
|
||||||
|
sshd_authorizedprincipals_directory_group: "{{ __sshd_authorizedprincipals_directory_group }}"
|
||||||
|
sshd_authorizedprincipals_directory_mode: "{{ __sshd_authorizedprincipals_directory_mode }}"
|
||||||
|
sshd_authorizedprincipals_file_owner: "{{ __sshd_authorizedprincipals_file_owner }}"
|
||||||
|
sshd_authorizedprincipals_file_group: "{{ __sshd_authorizedprincipals_file_group }}"
|
||||||
|
sshd_authorizedprincipals_file_mode: "{{ __sshd_authorizedprincipals_file_mode }}"
|
||||||
|
|
||||||
# This lists by default all hostkeys as rendered in the generated configuration
|
# This lists by default all hostkeys as rendered in the generated configuration
|
||||||
# file ("auto"). Before attempting to run sshd (either for verification of
|
# file ("auto"). Before attempting to run sshd (either for verification of
|
||||||
# configuration or restarting), we make sure the keys exist and have correct
|
# configuration or restarting), we make sure the keys exist and have correct
|
||||||
|
|
23
examples/example-use-certificates.yml
Normal file
23
examples/example-use-certificates.yml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
- name: Use SSH certificates
|
||||||
|
hosts: all
|
||||||
|
tasks:
|
||||||
|
- name: Configure sshd to enable SSH Certificate login
|
||||||
|
ansible.builtin.include_role:
|
||||||
|
name: ansible-sshd
|
||||||
|
vars:
|
||||||
|
sshd:
|
||||||
|
# Disable password authentication, use SSH Certificates and configure authorized principals
|
||||||
|
PasswordAuthentication: false
|
||||||
|
TrustedUserCAKeys: /etc/ssh/trusted-user-ca-keys.pub
|
||||||
|
AuthorizedPrincipalsFile: "/etc/ssh/auth_principals/%u"
|
||||||
|
# List of trusted user CA keys
|
||||||
|
sshd_trusted_user_ca_keys_list:
|
||||||
|
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICwqRjI9gAwkQF9iIylhRVAOFy2Joodh3fXJ7CbGWqUd
|
||||||
|
# Key is the user in the os, values are *Principals* defined in the certificate
|
||||||
|
sshd_principals:
|
||||||
|
admin:
|
||||||
|
- frontend-admin
|
||||||
|
- backend-admin
|
||||||
|
somelinuxuser:
|
||||||
|
- some-principal-defined-in-certificate
|
54
tasks/certificates.yml
Normal file
54
tasks/certificates.yml
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
---
|
||||||
|
- name: Configure Trusted user CA Keys
|
||||||
|
vars:
|
||||||
|
# The explicit to_json filter is needed for Python 2 compatibility
|
||||||
|
__sshd_trustedusercakeys_from_config: >-
|
||||||
|
{% if sshd_TrustedUserCAKeys is defined %}
|
||||||
|
{{ sshd_TrustedUserCAKeys | to_json }}
|
||||||
|
{% else %}
|
||||||
|
{{ sshd['TrustedUserCAKeys'] | to_json }}
|
||||||
|
{% endif %}
|
||||||
|
block:
|
||||||
|
- name: Create Trusted user CA Keys directory
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ (__sshd_trustedusercakeys_from_config | from_json) | dirname }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ sshd_trustedusercakeys_directory_owner }}"
|
||||||
|
group: "{{ sshd_trustedusercakeys_directory_group }}"
|
||||||
|
mode: "{{ sshd_trustedusercakeys_directory_mode }}"
|
||||||
|
|
||||||
|
- name: Copy Trusted user CA Keys
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: "trusted-user-ca-keys.pub.j2"
|
||||||
|
dest: "{{ __sshd_trustedusercakeys_from_config | from_json }}"
|
||||||
|
owner: "{{ sshd_trustedusercakeys_file_owner }}"
|
||||||
|
group: "{{ sshd_trustedusercakeys_file_group }}"
|
||||||
|
mode: "{{ sshd_trustedusercakeys_file_mode }}"
|
||||||
|
|
||||||
|
- name: Configure Principals
|
||||||
|
vars:
|
||||||
|
# The explicit to_json filter is needed for Python 2 compatibility
|
||||||
|
__sshd_authorizedprincipalsfile_from_config: >-
|
||||||
|
{% if sshd_AuthorizedPrincipalsFile is defined %}
|
||||||
|
{{ sshd_AuthorizedPrincipalsFile | to_json }}
|
||||||
|
{% else %}
|
||||||
|
{{ sshd['AuthorizedPrincipalsFile'] | to_json }}
|
||||||
|
{% endif %}
|
||||||
|
when: sshd_principals != {}
|
||||||
|
block:
|
||||||
|
- name: Create Principals directory
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ (__sshd_authorizedprincipalsfile_from_config | from_json) | dirname }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ sshd_authorizedprincipals_directory_owner }}"
|
||||||
|
group: "{{ sshd_authorizedprincipals_directory_group }}"
|
||||||
|
mode: "{{ sshd_authorizedprincipals_directory_mode }}"
|
||||||
|
|
||||||
|
- name: Copy Principals files
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: "auth_principals.j2"
|
||||||
|
dest: "{{ (__sshd_authorizedprincipalsfile_from_config | from_json) | dirname }}/{{ item.key }}"
|
||||||
|
owner: "{{ sshd_authorizedprincipals_file_owner }}"
|
||||||
|
group: "{{ sshd_authorizedprincipals_file_group }}"
|
||||||
|
mode: "{{ sshd_authorizedprincipals_file_mode }}"
|
||||||
|
with_dict: "{{ sshd_principals }}"
|
|
@ -147,6 +147,10 @@
|
||||||
ansible.builtin.include_tasks: install_namespace.yml
|
ansible.builtin.include_tasks: install_namespace.yml
|
||||||
when: sshd_config_namespace is not none
|
when: sshd_config_namespace is not none
|
||||||
|
|
||||||
|
- name: Configure sshd to use SSH certificates
|
||||||
|
ansible.builtin.include_tasks: certificates.yml
|
||||||
|
when: sshd_trusted_user_ca_keys_list != []
|
||||||
|
|
||||||
rescue:
|
rescue:
|
||||||
- name: Re-raise the error
|
- name: Re-raise the error
|
||||||
ansible.builtin.fail:
|
ansible.builtin.fail:
|
||||||
|
|
5
templates/auth_principals.j2
Normal file
5
templates/auth_principals.j2
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{{ ansible_managed | comment }}
|
||||||
|
{{ "willshersystems:ansible-sshd" | comment(prefix="", postfix="") }}
|
||||||
|
{% for principal in item.value %}
|
||||||
|
{{ principal }}
|
||||||
|
{% endfor %}
|
5
templates/trusted-user-ca-keys.pub.j2
Normal file
5
templates/trusted-user-ca-keys.pub.j2
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{{ ansible_managed | comment }}
|
||||||
|
{{ "willshersystems:ansible-sshd" | comment(prefix="", postfix="") }}
|
||||||
|
{% for key in sshd_trusted_user_ca_keys_list %}
|
||||||
|
{{ key }}
|
||||||
|
{% endfor %}
|
124
tests/tests_certificates.yml
Normal file
124
tests/tests_certificates.yml
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
---
|
||||||
|
- name: Test SSH certificates options
|
||||||
|
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"
|
||||||
|
ansible.builtin.include_tasks: tasks/backup.yml
|
||||||
|
|
||||||
|
- name: Ensure group 'nobody' exists
|
||||||
|
ansible.builtin.group:
|
||||||
|
name: nobody
|
||||||
|
|
||||||
|
- name: Ensure the user 'nobody' exists
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: nobody
|
||||||
|
group: nobody
|
||||||
|
comment: nobody
|
||||||
|
create_home: false
|
||||||
|
shell: /sbin/nologin
|
||||||
|
|
||||||
|
- name: Configure sshd
|
||||||
|
ansible.builtin.include_role:
|
||||||
|
name: ansible-sshd
|
||||||
|
vars:
|
||||||
|
sshd:
|
||||||
|
PasswordAuthentication: false
|
||||||
|
TrustedUserCAKeys: /etc/ssh/ca-keys/trusted-user-ca-keys.pub
|
||||||
|
AuthorizedPrincipalsFile: "/etc/ssh/auth_principals/%u"
|
||||||
|
sshd_trusted_user_ca_keys_list:
|
||||||
|
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICwqRjI9gAwkQF9iIylhRVAOFy2Joodh3fXJ7CbGWqUd
|
||||||
|
# Key is the user in the os, values are *Principals* defined in the certificate
|
||||||
|
sshd_principals:
|
||||||
|
user:
|
||||||
|
- principal
|
||||||
|
sshd_config_file: /etc/ssh/sshd_config
|
||||||
|
# very BAD example
|
||||||
|
sshd_trustedusercakeys_directory_owner: "nobody"
|
||||||
|
sshd_trustedusercakeys_directory_group: "nobody"
|
||||||
|
sshd_trustedusercakeys_directory_mode: "0770"
|
||||||
|
sshd_trustedusercakeys_file_owner: "nobody"
|
||||||
|
sshd_trustedusercakeys_file_group: "nobody"
|
||||||
|
sshd_trustedusercakeys_file_mode: "0750"
|
||||||
|
sshd_authorizedprincipals_directory_owner: "nobody"
|
||||||
|
sshd_authorizedprincipals_directory_group: "nobody"
|
||||||
|
sshd_authorizedprincipals_directory_mode: "0777"
|
||||||
|
sshd_authorizedprincipals_file_owner: "nobody"
|
||||||
|
sshd_authorizedprincipals_file_group: "nobody"
|
||||||
|
sshd_authorizedprincipals_file_mode: "0755"
|
||||||
|
|
||||||
|
- name: Verify the options are correctly set
|
||||||
|
tags: tests::verify
|
||||||
|
block:
|
||||||
|
- name: Flush handlers
|
||||||
|
ansible.builtin.meta: flush_handlers
|
||||||
|
|
||||||
|
- name: Print current configuration file
|
||||||
|
ansible.builtin.slurp:
|
||||||
|
src: /etc/ssh/sshd_config
|
||||||
|
register: config
|
||||||
|
|
||||||
|
- name: Check the options are in configuration file
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- "'PasswordAuthentication no' in config.content | b64decode"
|
||||||
|
- "'TrustedUserCAKeys /etc/ssh/ca-keys/trusted-user-ca-keys.pub' in config.content | b64decode"
|
||||||
|
- "'AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u' in config.content | b64decode"
|
||||||
|
|
||||||
|
- name: Get trusted user CA keys directory stat
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: /etc/ssh/ca-keys
|
||||||
|
register: trustedusercakeys_directory_stat
|
||||||
|
|
||||||
|
- name: Check trusted user CA keys directory has requested properties
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- trustedusercakeys_directory_stat.stat.isdir
|
||||||
|
- trustedusercakeys_directory_stat.stat.pw_name == "nobody"
|
||||||
|
- trustedusercakeys_directory_stat.stat.gr_name == "nobody"
|
||||||
|
- trustedusercakeys_directory_stat.stat.mode == "0770"
|
||||||
|
|
||||||
|
- name: Get trusted user CA keys file stat
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: /etc/ssh/ca-keys/trusted-user-ca-keys.pub
|
||||||
|
register: trustedusercakeys_file_stat
|
||||||
|
|
||||||
|
- name: Check trusted user CA keys file has requested properties
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- trustedusercakeys_file_stat.stat.exists
|
||||||
|
- trustedusercakeys_file_stat.stat.pw_name == "nobody"
|
||||||
|
- trustedusercakeys_file_stat.stat.gr_name == "nobody"
|
||||||
|
- trustedusercakeys_file_stat.stat.mode == "0750"
|
||||||
|
|
||||||
|
- name: Get authorized principals directory stat
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: /etc/ssh/auth_principals
|
||||||
|
register: authorizedprincipals_directory_stat
|
||||||
|
|
||||||
|
- name: Check authorized principals directory has requested properties
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- authorizedprincipals_directory_stat.stat.isdir
|
||||||
|
- authorizedprincipals_directory_stat.stat.pw_name == "nobody"
|
||||||
|
- authorizedprincipals_directory_stat.stat.gr_name == "nobody"
|
||||||
|
- authorizedprincipals_directory_stat.stat.mode == "0777"
|
||||||
|
|
||||||
|
- name: Get authorized principals file stat
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: /etc/ssh/auth_principals/user
|
||||||
|
register: authorizedprincipals_file_stat
|
||||||
|
|
||||||
|
- name: Check authorized principals file has requested properties
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- authorizedprincipals_file_stat.stat.exists
|
||||||
|
- authorizedprincipals_file_stat.stat.pw_name == "nobody"
|
||||||
|
- authorizedprincipals_file_stat.stat.gr_name == "nobody"
|
||||||
|
- authorizedprincipals_file_stat.stat.mode == "0755"
|
||||||
|
|
||||||
|
- name: "Restore configuration files"
|
||||||
|
ansible.builtin.include_tasks: tasks/restore.yml
|
|
@ -5,6 +5,18 @@ __sshd_config_mode: "0600"
|
||||||
__sshd_hostkey_owner: "root"
|
__sshd_hostkey_owner: "root"
|
||||||
__sshd_hostkey_group: "root"
|
__sshd_hostkey_group: "root"
|
||||||
__sshd_hostkey_mode: "0600"
|
__sshd_hostkey_mode: "0600"
|
||||||
|
__sshd_trustedusercakeys_directory_owner: "root"
|
||||||
|
__sshd_trustedusercakeys_directory_group: "root"
|
||||||
|
__sshd_trustedusercakeys_directory_mode: "0755"
|
||||||
|
__sshd_trustedusercakeys_file_owner: "root"
|
||||||
|
__sshd_trustedusercakeys_file_group: "root"
|
||||||
|
__sshd_trustedusercakeys_file_mode: "0640"
|
||||||
|
__sshd_authorizedprincipals_directory_owner: "root"
|
||||||
|
__sshd_authorizedprincipals_directory_group: "root"
|
||||||
|
__sshd_authorizedprincipals_directory_mode: "0755"
|
||||||
|
__sshd_authorizedprincipals_file_owner: "root"
|
||||||
|
__sshd_authorizedprincipals_file_group: "root"
|
||||||
|
__sshd_authorizedprincipals_file_mode: "0644"
|
||||||
# The OpenSSH 5.3 in RHEL6 does not support "Match all" so we need a workaround
|
# The OpenSSH 5.3 in RHEL6 does not support "Match all" so we need a workaround
|
||||||
__sshd_compat_match_all: Match all
|
__sshd_compat_match_all: Match all
|
||||||
# The hostkeys not supported in FIPS mode, if applicable
|
# The hostkeys not supported in FIPS mode, if applicable
|
||||||
|
|
Loading…
Reference in a new issue