test(ssh): cover key rotation with Molecule

Replace a private key while retaining its public key file to detect stale derived keys.
Verify the replacement public key, ownership and file permissions.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
volker.raschekandCopilot committed 2026-10-18 15:10:40 +02:00
1 parent 7683306d5f
commit 8674cd20aa
2 files changed
+69 -3

No files matched your search

+15 -3
View File
@@ -37,10 +37,22 @@
- "{{ _authorized_key }}"
# The key pair is generated instead of committed, private key material does not belong into a repository.
- name: Generate the ssh key pair the role is fed with
- name: Generate the SSH key pairs for initial configuration and rotation
ansible.builtin.command:
cmd: "ssh-keygen -t ed25519 -N '' -C molecule -f {{ _private_key }}"
creates: "{{ _private_key }}"
argv:
- ssh-keygen
- -t
- ed25519
- -N
- ''
- -C
- molecule
- -f
- "{{ item }}"
creates: "{{ item }}"
loop:
- "{{ _private_key }}"
- "{{ _private_key }}.replacement"
- name: Offer the public key as authorized key fixture
ansible.builtin.copy:
+54
View File
@@ -142,3 +142,57 @@
- _bob_files.results[0].stat.exists
- not _bob_files.results[1:] | map(attribute='stat.exists') | select | list
fail_msg: "molecule-bob has files that were never declared for him"
- name: Verify SSH key rotation
hosts: all
vars:
_replacement_key: "{{ molecule_scenario_directory }}/files/ssh/private_keys/molecule.ed25519.key.replacement"
unix_groups: {}
unix_users:
molecule-alice:
group: molecule-alice
password: alice
ssh:
private_keys:
- filename: alice.ed25519.key
content: "{{ lookup('ansible.builtin.file', _replacement_key, rstrip=false) }}"
state: present
tasks:
- name: Replace the private key while keeping the existing public key file
ansible.builtin.include_role:
name: unix-users
- name: Read the public key after rotation
ansible.builtin.slurp:
src: /home/molecule-alice/.ssh/alice.ed25519.key.pub
register: _rotated_public_key
- name: Assert the existing public key matches the replacement key and differs from the original
vars:
_expected_public_key: "{{ lookup('ansible.builtin.file', _replacement_key + '.pub') }}"
ansible.builtin.assert:
that:
- (_rotated_public_key.content | b64decode).split()[1] == _expected_public_key.split()[1]
- _rotated_public_key.content != _alice_keys.results[1].content
- name: Stat the rotated private and public keys
ansible.builtin.stat:
path: "{{ item.path }}"
loop:
- path: /home/molecule-alice/.ssh/alice.ed25519.key
mode: '0600'
- path: /home/molecule-alice/.ssh/alice.ed25519.key.pub
mode: '0644'
loop_control:
label: "{{ item.path }}"
register: _rotated_key_files
- name: Assert rotated keys retain their owner and permissions
ansible.builtin.assert:
that:
- item.stat.pw_name == 'molecule-alice'
- item.stat.gr_name == 'molecule-alice'
- item.stat.mode == item.item.mode
loop: "{{ _rotated_key_files.results }}"
loop_control:
label: "{{ item.item.path }}"