Compare commits

...
6 Commits
Author SHA1 Message Date
volker.raschek f483c7066b fix(tasks): avoid logging of environment variables and functions
Lint Markdown files / markdown-lint (push) Successful in 7s
Ansible Linter / ansible-lint (push) Successful in 3m11s
Molecule / Molecule (push) Successful in 5m51s
Release Ansible Role / Release Ansible Role (push) Successful in 3m16s
2026-10-19 21:25:21 +02:00
volker.raschek d3fa0d0178 fix(tasks): avoid logging of private ssh keys
Ansible Linter / ansible-lint (push) Successful in 1m59s
Lint Markdown files / markdown-lint (push) Successful in 7s
Molecule / Molecule (push) Successful in 6m5s
Release Ansible Role / Release Ansible Role (push) Successful in 1m39s
2026-10-19 21:18:40 +02:00
volker.raschekandCopilot ff11874008 test(ssh): cover key rotation with Molecule
Ansible Linter / ansible-lint (push) Successful in 2m18s
Lint Markdown files / markdown-lint (push) Successful in 15s
Release Ansible Role / Release Ansible Role (push) Successful in 3m25s
Molecule / Molecule (push) Successful in 6m1s
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>
2026-10-18 15:10:40 +02:00
volker.raschekandCopilot 7683306d5f fix(ssh): refresh public keys after key rotation
Recompute public keys from the configured private keys so existing public key files cannot become stale.
Use copy to preserve idempotence, ownership and permissions.

Co-authored-by: Copilot <copilot@github.com>
2026-10-18 15:10:40 +02:00
volker.raschekandCopilot 8efe3d6ee0 fix(ci): use renamed ansible-lint configuration
Ansible Linter / ansible-lint (push) Successful in 2m21s
Lint Markdown files / markdown-lint (push) Successful in 11s
Molecule / Molecule (push) Successful in 5m53s
Point the Gitea lint workflow at .ansible-lint.yml so it can load the renamed configuration.

Co-authored-by: Copilot <copilot@github.com>
2026-10-18 14:51:01 +02:00
volker.raschekandCopilot 5ff6db1c79 chore: rename ansible-lint configuration
Ansible Linter / ansible-lint (push) Failing after 1m40s
Lint Markdown files / markdown-lint (push) Successful in 11s
Molecule / Molecule (push) Successful in 5m20s
Use a YAML extension for explicit file type detection.

Co-authored-by: Copilot <copilot@github.com>
2026-10-18 14:44:27 +02:00
6 changed files with 83 additions and 13 deletions

No files matched your search

File renamed without changes.
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
- name: Run ansible-lint
uses: ansible/ansible-lint@9bce2bfb8f4a984e6b13767bc0d6bf0a49492164 # v26.10.1
with:
args: "--config-file .ansible-lint"
args: "--config-file .ansible-lint.yml"
# The molecule scenario is linted as well, so its collections are required beside the ones of the role.
requirements_file: "molecule/default/collections.yml"
setup_python: "true"
+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 }}"
+11 -9
View File
@@ -108,25 +108,26 @@
when: unix_user.value.ssh.private_keys is defined and unix_user.value.ssh.private_keys | length > 0
- name: "Extract public SSH keys from private keys for unix user: {{ unix_user.key }}"
ansible.builtin.shell:
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.filename }}.pub"
ansible.builtin.command:
argv: [ "ssh-keygen", "-y", "-f", "{{ _unix_users_home }}/.ssh/{{ item.filename }}" ]
changed_when: false
register: _unix_users_ssh_pub_key_extraction
loop_control:
label: "{{ item.filename }}"
with_items:
- "{{ unix_user.value.ssh.private_keys }}"
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 }}"
ansible.builtin.file:
path: "{{ _unix_users_home }}/.ssh/{{ item.filename }}.pub"
- name: "Write derived public SSH keys for unix user: {{ unix_user.key }}"
ansible.builtin.copy:
content: "{{ item.stdout }}\n"
dest: "{{ _unix_users_home }}/.ssh/{{ item.item.filename }}.pub"
owner: "{{ unix_user.key }}"
group: "{{ unix_user.value.group | default('users') }}"
mode: "0644"
loop_control:
label: "{{ item.filename }}"
with_items:
- "{{ unix_user.value.ssh.private_keys }}"
label: "{{ item.item.filename }}"
loop: "{{ _unix_users_ssh_pub_key_extraction.results }}"
when: unix_user.value.ssh.private_keys is defined and unix_user.value.ssh.private_keys | length > 0
- name: "Create custom SSH client config for unix user: {{ unix_user.key }}"
@@ -179,6 +180,7 @@
- "{{ unix_user.value.shell_rc_files }}"
loop_control:
loop_var: shell_rc_file
no_log: true
- name: "Create .netrc file"
when: unix_user.value.netrc is defined and unix_user.value.netrc | length > 0
+2
View File
@@ -2,6 +2,7 @@
- name: Remove unix user
ansible.builtin.include_tasks: remove_unix_user.yml
no_log: true
with_dict: "{{ unix_users }}"
loop_control:
loop_var: unix_user
@@ -22,6 +23,7 @@
- name: Create unix groups
ansible.builtin.include_tasks: create_unix_group.yml
no_log: true
with_dict: "{{ unix_groups }}"
loop_control:
loop_var: unix_group