diff --git a/molecule/default/create.yml b/molecule/default/create.yml index 1e3aa7c..1618247 100644 --- a/molecule/default/create.yml +++ b/molecule/default/create.yml @@ -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: diff --git a/molecule/default/verify.yml b/molecule/default/verify.yml index 173fac2..2e7a315 100644 --- a/molecule/default/verify.yml +++ b/molecule/default/verify.yml @@ -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 }}"