feat(ssh)!: configure keys with filename and content
Accept SSH key content directly so callers no longer need role-specific source directories. Update Molecule coverage and documentation. BREAKING CHANGE: private_keys entries must be objects with filename and content instead of file names. authorized_keys entries must provide content alongside filename. Public keys are also written to ~/.ssh. Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
6 files changed
+87
-32
No files matched your search
@@ -138,10 +138,11 @@ environment variables can be espcilitly defined, to consume it during execution
|
|||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> To allow consuming environment variables must be set `PermitUserEnvironment yes` in `/etc/ssh/sshd_config`.
|
> To allow consuming environment variables must be set `PermitUserEnvironment yes` in `/etc/ssh/sshd_config`.
|
||||||
|
|
||||||
The private key `toor@toor-pc.ed25519.key` must be stored in `ssh/private_keys`. The public key will be automatically
|
Private and public keys are configured directly using `filename` and `content`. The role writes each key to
|
||||||
extracted from the private key.
|
`~/.ssh/<filename>`. Store private key content encrypted with Ansible Vault.
|
||||||
|
|
||||||
The public keys `claire@claire-pc.pub` as well as `bob@bob-pc.pub` must be stored in `ssh/authorized_keys`.
|
Entries in `authorized_keys` are also added to `~/.ssh/authorized_keys`, including their optional command and environment
|
||||||
|
settings. Public keys are still automatically extracted from private keys into `~/.ssh/<filename>.pub`.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
unix_users:
|
unix_users:
|
||||||
@@ -157,13 +158,19 @@ unix_users:
|
|||||||
UserKnownHostFile: /dev/null
|
UserKnownHostFile: /dev/null
|
||||||
authorized_keys:
|
authorized_keys:
|
||||||
- filename: claire@claire-pc.pub
|
- filename: claire@claire-pc.pub
|
||||||
|
content: ssh-ed25519 AAAAC3NzaC... claire
|
||||||
- command: /usr/local/bin/upload-file.sh
|
- command: /usr/local/bin/upload-file.sh
|
||||||
envs:
|
envs:
|
||||||
- key: SSH_KEY_NAME
|
- key: SSH_KEY_NAME
|
||||||
value: bob@bob-pc
|
value: bob@bob-pc
|
||||||
filename: bob@bob-pc.pub
|
filename: bob@bob-pc.pub
|
||||||
|
content: ssh-ed25519 AAAAC3NzaC... bob
|
||||||
private_keys:
|
private_keys:
|
||||||
- toor@toor-pc.ed25519.key
|
- filename: toor@toor-pc.ed25519.key
|
||||||
|
content: |
|
||||||
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
...
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
||||||
shell: /bin/bash
|
shell: /bin/bash
|
||||||
password: toor
|
password: toor
|
||||||
group: toor
|
group: toor
|
||||||
|
|||||||
+20
-15
@@ -8,21 +8,26 @@ unix_groups: {}
|
|||||||
# gid: 1001
|
# gid: 1001
|
||||||
# state: absent
|
# state: absent
|
||||||
|
|
||||||
unix_users: {}
|
unix_users:
|
||||||
# alice:
|
alice:
|
||||||
# state: "present"
|
state: "present"
|
||||||
# name: "Alice"
|
name: "Alice"
|
||||||
# uid: "1000"
|
uid: "1000"
|
||||||
# ssh:
|
ssh:
|
||||||
# config:
|
config:
|
||||||
# - Host: "*"
|
- Host: "*"
|
||||||
# StrictHostKeyChecking: "no"
|
StrictHostKeyChecking: "no"
|
||||||
# UserKnownHostFile: /dev/null
|
UserKnownHostFile: /dev/null
|
||||||
# authorized_keys:
|
authorized_keys:
|
||||||
# - filename: alice@alice-pc.pub
|
- filename: alice@alice-pc.pub
|
||||||
# # command: "" # optional
|
content: ssh-ed25519 AAAAC3NzaC... alice
|
||||||
# private_keys:
|
# command: "" # optional
|
||||||
# - alice@alice-pc.ed25519.key
|
private_keys:
|
||||||
|
- filename: alice@alice-pc.ed25519.key
|
||||||
|
content: |
|
||||||
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
...
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
||||||
# home: /home/alice
|
# home: /home/alice
|
||||||
# btrfs: false
|
# btrfs: false
|
||||||
# netrc:
|
# netrc:
|
||||||
|
|||||||
@@ -28,13 +28,17 @@
|
|||||||
- Host: "*"
|
- Host: "*"
|
||||||
StrictHostKeyChecking: "no"
|
StrictHostKeyChecking: "no"
|
||||||
authorized_keys:
|
authorized_keys:
|
||||||
- filename: molecule.pub
|
- filename: alice.pub
|
||||||
|
content: "{{ lookup('ansible.builtin.file', molecule_scenario_directory + '/files/ssh/authorized_keys/molecule.pub', rstrip=false) }}"
|
||||||
command: "/usr/bin/true"
|
command: "/usr/bin/true"
|
||||||
envs:
|
envs:
|
||||||
- key: EDITOR
|
- key: EDITOR
|
||||||
value: vi
|
value: vi
|
||||||
|
- filename: alice-login.pub
|
||||||
|
content: "{{ lookup('ansible.builtin.file', molecule_scenario_directory + '/files/ssh/authorized_keys/molecule.pub', rstrip=false) }}"
|
||||||
private_keys:
|
private_keys:
|
||||||
- molecule.ed25519.key
|
- filename: alice.ed25519.key
|
||||||
|
content: "{{ lookup('ansible.builtin.file', molecule_scenario_directory + '/files/ssh/private_keys/molecule.ed25519.key', rstrip=false) }}"
|
||||||
netrc:
|
netrc:
|
||||||
- machine: hostname.local
|
- machine: hostname.local
|
||||||
login: alice
|
login: alice
|
||||||
|
|||||||
@@ -9,8 +9,10 @@
|
|||||||
/home/molecule-alice/.ssh: "0700"
|
/home/molecule-alice/.ssh: "0700"
|
||||||
/home/molecule-alice/.ssh/authorized_keys: "0600"
|
/home/molecule-alice/.ssh/authorized_keys: "0600"
|
||||||
/home/molecule-alice/.ssh/config: "0644"
|
/home/molecule-alice/.ssh/config: "0644"
|
||||||
/home/molecule-alice/.ssh/molecule.ed25519.key: "0600"
|
/home/molecule-alice/.ssh/alice.ed25519.key: "0600"
|
||||||
/home/molecule-alice/.ssh/molecule.ed25519.key.pub: "0644"
|
/home/molecule-alice/.ssh/alice.ed25519.key.pub: "0644"
|
||||||
|
/home/molecule-alice/.ssh/alice.pub: "0644"
|
||||||
|
/home/molecule-alice/.ssh/alice-login.pub: "0644"
|
||||||
/home/molecule-alice/.forward: "0644"
|
/home/molecule-alice/.forward: "0644"
|
||||||
/home/molecule-alice/.netrc: "0600"
|
/home/molecule-alice/.netrc: "0600"
|
||||||
/home/molecule-alice/.bashrc.d: "0755"
|
/home/molecule-alice/.bashrc.d: "0755"
|
||||||
@@ -72,7 +74,7 @@
|
|||||||
register: _alice_keys
|
register: _alice_keys
|
||||||
loop:
|
loop:
|
||||||
- "{{ _alice_home }}/.ssh/authorized_keys"
|
- "{{ _alice_home }}/.ssh/authorized_keys"
|
||||||
- "{{ _alice_home }}/.ssh/molecule.ed25519.key.pub"
|
- "{{ _alice_home }}/.ssh/alice.ed25519.key.pub"
|
||||||
|
|
||||||
- name: Assert that the authorized key carries its options and matches the extracted public key
|
- name: Assert that the authorized key carries its options and matches the extracted public key
|
||||||
vars:
|
vars:
|
||||||
@@ -83,8 +85,31 @@
|
|||||||
- _authorized_key.startswith('command="/usr/bin/true",environment="EDITOR=vi" ')
|
- _authorized_key.startswith('command="/usr/bin/true",environment="EDITOR=vi" ')
|
||||||
- _public_key.startswith('ssh-ed25519 ')
|
- _public_key.startswith('ssh-ed25519 ')
|
||||||
- _public_key.split()[1] == _authorized_key.split()[2]
|
- _public_key.split()[1] == _authorized_key.split()[2]
|
||||||
|
- (_alice_keys.results[0].content | b64decode).splitlines() | select('match', '^ssh-ed25519 ') | list | length == 1
|
||||||
fail_msg: "the authorized key of molecule-alice does not match the key extracted from its private key"
|
fail_msg: "the authorized key of molecule-alice does not match the key extracted from its private key"
|
||||||
|
|
||||||
|
- name: Read the configured private and public SSH key files
|
||||||
|
ansible.builtin.slurp:
|
||||||
|
src: "{{ _alice_home }}/.ssh/{{ item }}"
|
||||||
|
loop:
|
||||||
|
- alice.ed25519.key
|
||||||
|
- alice.pub
|
||||||
|
- alice-login.pub
|
||||||
|
no_log: true
|
||||||
|
register: _alice_key_contents
|
||||||
|
|
||||||
|
- name: Assert that SSH key files contain the exact configured content
|
||||||
|
vars:
|
||||||
|
_private_key_content: "{{ lookup('ansible.builtin.file', molecule_scenario_directory + '/files/ssh/private_keys/molecule.ed25519.key', rstrip=false) }}"
|
||||||
|
_public_key_content: "{{ lookup('ansible.builtin.file', molecule_scenario_directory + '/files/ssh/authorized_keys/molecule.pub', rstrip=false) }}"
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- (_alice_key_contents.results[0].content | b64decode) == _private_key_content
|
||||||
|
- (_alice_key_contents.results[1].content | b64decode) == _public_key_content
|
||||||
|
- (_alice_key_contents.results[2].content | b64decode) == _public_key_content
|
||||||
|
- _public_key_content | trim in (_alice_keys.results[0].content | b64decode).splitlines()
|
||||||
|
no_log: true
|
||||||
|
|
||||||
- name: Read the user and group database
|
- name: Read the user and group database
|
||||||
ansible.builtin.getent:
|
ansible.builtin.getent:
|
||||||
database: "{{ item }}"
|
database: "{{ item }}"
|
||||||
|
|||||||
@@ -95,11 +95,21 @@
|
|||||||
state: absent
|
state: absent
|
||||||
when: unix_user.value.ssh.authorized_keys is not defined or unix_user.value.ssh.authorized_keys | length <= 0
|
when: unix_user.value.ssh.authorized_keys is not defined or unix_user.value.ssh.authorized_keys | length <= 0
|
||||||
|
|
||||||
# The relative source is resolved against the files directory of the playbook, like the lookup in authorized_keys.j2.
|
- name: "Create public SSH keys for unix user: {{ unix_user.key }}"
|
||||||
|
ansible.builtin.copy:
|
||||||
|
content: "{{ item.content }}"
|
||||||
|
dest: "{{ _unix_users_home }}/.ssh/{{ item.filename }}"
|
||||||
|
owner: "{{ unix_user.key }}"
|
||||||
|
group: "{{ unix_user.value.group | default('users') }}"
|
||||||
|
mode: "0644"
|
||||||
|
loop: "{{ unix_user.value.ssh.authorized_keys | default([]) }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.filename }}"
|
||||||
|
|
||||||
- name: "Create private SSH keys for unix user: {{ unix_user.key }}"
|
- name: "Create private SSH keys for unix user: {{ unix_user.key }}"
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
src: "ssh/private_keys/{{ item }}"
|
content: "{{ item.content }}"
|
||||||
dest: "{{ _unix_users_home }}/.ssh/{{ item }}"
|
dest: "{{ _unix_users_home }}/.ssh/{{ item.filename }}"
|
||||||
owner: "{{ unix_user.key }}"
|
owner: "{{ unix_user.key }}"
|
||||||
group: "{{ unix_user.value.group | default('users') }}"
|
group: "{{ unix_user.value.group | default('users') }}"
|
||||||
mode: "0600"
|
mode: "0600"
|
||||||
@@ -110,18 +120,22 @@
|
|||||||
|
|
||||||
- name: "Extract public SSH keys from private keys for unix user: {{ unix_user.key }}"
|
- name: "Extract public SSH keys from private keys for unix user: {{ unix_user.key }}"
|
||||||
ansible.builtin.shell:
|
ansible.builtin.shell:
|
||||||
cmd: "ssh-keygen -y -f {{ _unix_users_home }}/.ssh/{{ item }} > {{ _unix_users_home }}/.ssh/{{ item }}.pub"
|
cmd: "ssh-keygen -y -f {{ (_unix_users_home + '/.ssh/' + item.filename) | quote }} > {{ (_unix_users_home + '/.ssh/' + item.filename + '.pub') | quote }}"
|
||||||
creates: "{{ _unix_users_home }}/.ssh/{{ item }}.pub"
|
creates: "{{ _unix_users_home }}/.ssh/{{ item.filename }}.pub"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.filename }}"
|
||||||
with_items:
|
with_items:
|
||||||
- "{{ unix_user.value.ssh.private_keys }}"
|
- "{{ unix_user.value.ssh.private_keys }}"
|
||||||
when: unix_user.value.ssh.private_keys is defined and unix_user.value.ssh.private_keys | length > 0
|
when: unix_user.value.ssh.private_keys is defined and unix_user.value.ssh.private_keys | length > 0
|
||||||
|
|
||||||
- name: "Correct permissions of public SSH keys for unix user: {{ unix_user.key }}"
|
- name: "Correct permissions of public SSH keys for unix user: {{ unix_user.key }}"
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: "{{ _unix_users_home }}/.ssh/{{ item }}.pub"
|
path: "{{ _unix_users_home }}/.ssh/{{ item.filename }}.pub"
|
||||||
owner: "{{ unix_user.key }}"
|
owner: "{{ unix_user.key }}"
|
||||||
group: "{{ unix_user.value.group | default('users') }}"
|
group: "{{ unix_user.value.group | default('users') }}"
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.filename }}"
|
||||||
with_items:
|
with_items:
|
||||||
- "{{ unix_user.value.ssh.private_keys }}"
|
- "{{ unix_user.value.ssh.private_keys }}"
|
||||||
when: unix_user.value.ssh.private_keys is defined and unix_user.value.ssh.private_keys | length > 0
|
when: unix_user.value.ssh.private_keys is defined and unix_user.value.ssh.private_keys | length > 0
|
||||||
|
|||||||
@@ -21,8 +21,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if _args | length > 0 %}
|
{% if _args | length > 0 %}
|
||||||
{{ _args | join(',') }} {{ lookup('file', 'ssh/authorized_keys/' + authorized_key.filename ) }}
|
{{ _args | join(',') }} {{ authorized_key.content | trim }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ lookup('file', 'ssh/authorized_keys/' + authorized_key.filename ) }}
|
{{ authorized_key.content | trim }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
Reference in new issue
Block a user