Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f483c7066b
|
||
|
|
d3fa0d0178
|
||
|
|
ff11874008
|
||
|
|
7683306d5f
|
||
|
|
8efe3d6ee0
|
||
|
|
5ff6db1c79
|
||
|
|
efce92c31e
|
||
|
|
89e36b89d6
|
No files matched your search
File renamed without changes.
@@ -16,7 +16,7 @@ jobs:
|
|||||||
- name: Run ansible-lint
|
- name: Run ansible-lint
|
||||||
uses: ansible/ansible-lint@9bce2bfb8f4a984e6b13767bc0d6bf0a49492164 # v26.10.1
|
uses: ansible/ansible-lint@9bce2bfb8f4a984e6b13767bc0d6bf0a49492164 # v26.10.1
|
||||||
with:
|
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.
|
# The molecule scenario is linted as well, so its collections are required beside the ones of the role.
|
||||||
requirements_file: "molecule/default/collections.yml"
|
requirements_file: "molecule/default/collections.yml"
|
||||||
setup_python: "true"
|
setup_python: "true"
|
||||||
@@ -138,11 +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`.
|
||||||
|
|
||||||
Private and public keys are configured directly using `filename` and `content`. The role writes each key to
|
Private keys are configured using `filename` and `content`; the role writes them to `~/.ssh/<filename>` and extracts
|
||||||
`~/.ssh/<filename>`. Store private key content encrypted with Ansible Vault.
|
their public keys to `~/.ssh/<filename>.pub`. Store private key content encrypted with Ansible Vault.
|
||||||
|
|
||||||
Entries in `authorized_keys` are also added to `~/.ssh/authorized_keys`, including their optional command and environment
|
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`.
|
settings.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
unix_users:
|
unix_users:
|
||||||
@@ -157,13 +157,11 @@ unix_users:
|
|||||||
StrictHostKeyChecking: "no"
|
StrictHostKeyChecking: "no"
|
||||||
UserKnownHostFile: /dev/null
|
UserKnownHostFile: /dev/null
|
||||||
authorized_keys:
|
authorized_keys:
|
||||||
- filename: claire@claire-pc.pub
|
- content: ssh-ed25519 AAAAC3NzaC... claire
|
||||||
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
|
|
||||||
content: ssh-ed25519 AAAAC3NzaC... bob
|
content: ssh-ed25519 AAAAC3NzaC... bob
|
||||||
private_keys:
|
private_keys:
|
||||||
- filename: toor@toor-pc.ed25519.key
|
- filename: toor@toor-pc.ed25519.key
|
||||||
|
|||||||
+19
-20
@@ -8,26 +8,25 @@ 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
|
# - content: ssh-ed25519 AAAAC3NzaC... alice
|
||||||
content: ssh-ed25519 AAAAC3NzaC... alice
|
# # command: "" # optional
|
||||||
# command: "" # optional
|
# private_keys:
|
||||||
private_keys:
|
# - filename: alice@alice-pc.ed25519.key
|
||||||
- filename: alice@alice-pc.ed25519.key
|
# content: |
|
||||||
content: |
|
# -----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
# ...
|
||||||
...
|
# -----END OPENSSH PRIVATE KEY-----
|
||||||
-----END OPENSSH PRIVATE KEY-----
|
|
||||||
# home: /home/alice
|
# home: /home/alice
|
||||||
# btrfs: false
|
# btrfs: false
|
||||||
# netrc:
|
# netrc:
|
||||||
|
|||||||
@@ -28,14 +28,12 @@
|
|||||||
- Host: "*"
|
- Host: "*"
|
||||||
StrictHostKeyChecking: "no"
|
StrictHostKeyChecking: "no"
|
||||||
authorized_keys:
|
authorized_keys:
|
||||||
- filename: alice.pub
|
- content: "{{ lookup('ansible.builtin.file', molecule_scenario_directory + '/files/ssh/authorized_keys/molecule.pub', rstrip=false) }}"
|
||||||
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) }}"
|
||||||
content: "{{ lookup('ansible.builtin.file', molecule_scenario_directory + '/files/ssh/authorized_keys/molecule.pub', rstrip=false) }}"
|
|
||||||
private_keys:
|
private_keys:
|
||||||
- filename: alice.ed25519.key
|
- filename: alice.ed25519.key
|
||||||
content: "{{ lookup('ansible.builtin.file', molecule_scenario_directory + '/files/ssh/private_keys/molecule.ed25519.key', rstrip=false) }}"
|
content: "{{ lookup('ansible.builtin.file', molecule_scenario_directory + '/files/ssh/private_keys/molecule.ed25519.key', rstrip=false) }}"
|
||||||
|
|||||||
@@ -37,10 +37,22 @@
|
|||||||
- "{{ _authorized_key }}"
|
- "{{ _authorized_key }}"
|
||||||
|
|
||||||
# The key pair is generated instead of committed, private key material does not belong into a repository.
|
# 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:
|
ansible.builtin.command:
|
||||||
cmd: "ssh-keygen -t ed25519 -N '' -C molecule -f {{ _private_key }}"
|
argv:
|
||||||
creates: "{{ _private_key }}"
|
- 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
|
- name: Offer the public key as authorized key fixture
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
|
|||||||
+54
-24
@@ -11,8 +11,6 @@
|
|||||||
/home/molecule-alice/.ssh/config: "0644"
|
/home/molecule-alice/.ssh/config: "0644"
|
||||||
/home/molecule-alice/.ssh/alice.ed25519.key: "0600"
|
/home/molecule-alice/.ssh/alice.ed25519.key: "0600"
|
||||||
/home/molecule-alice/.ssh/alice.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"
|
||||||
@@ -88,28 +86,6 @@
|
|||||||
- (_alice_keys.results[0].content | b64decode).splitlines() | select('match', '^ssh-ed25519 ') | list | length == 1
|
- (_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 }}"
|
||||||
@@ -166,3 +142,57 @@
|
|||||||
- _bob_files.results[0].stat.exists
|
- _bob_files.results[0].stat.exists
|
||||||
- not _bob_files.results[1:] | map(attribute='stat.exists') | select | list
|
- not _bob_files.results[1:] | map(attribute='stat.exists') | select | list
|
||||||
fail_msg: "molecule-bob has files that were never declared for him"
|
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
-20
@@ -95,17 +95,6 @@
|
|||||||
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
|
||||||
|
|
||||||
- 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:
|
||||||
content: "{{ item.content }}"
|
content: "{{ item.content }}"
|
||||||
@@ -119,25 +108,26 @@
|
|||||||
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: "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.command:
|
||||||
cmd: "ssh-keygen -y -f {{ (_unix_users_home + '/.ssh/' + item.filename) | quote }} > {{ (_unix_users_home + '/.ssh/' + item.filename + '.pub') | quote }}"
|
argv: [ "ssh-keygen", "-y", "-f", "{{ _unix_users_home }}/.ssh/{{ item.filename }}" ]
|
||||||
creates: "{{ _unix_users_home }}/.ssh/{{ item.filename }}.pub"
|
changed_when: false
|
||||||
|
register: _unix_users_ssh_pub_key_extraction
|
||||||
loop_control:
|
loop_control:
|
||||||
label: "{{ item.filename }}"
|
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: "Write derived public SSH keys for unix user: {{ unix_user.key }}"
|
||||||
ansible.builtin.file:
|
ansible.builtin.copy:
|
||||||
path: "{{ _unix_users_home }}/.ssh/{{ item.filename }}.pub"
|
content: "{{ item.stdout }}\n"
|
||||||
|
dest: "{{ _unix_users_home }}/.ssh/{{ item.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:
|
loop_control:
|
||||||
label: "{{ item.filename }}"
|
label: "{{ item.item.filename }}"
|
||||||
with_items:
|
loop: "{{ _unix_users_ssh_pub_key_extraction.results }}"
|
||||||
- "{{ 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: "Create custom SSH client config for unix user: {{ unix_user.key }}"
|
- name: "Create custom SSH client config for unix user: {{ unix_user.key }}"
|
||||||
@@ -190,6 +180,7 @@
|
|||||||
- "{{ unix_user.value.shell_rc_files }}"
|
- "{{ unix_user.value.shell_rc_files }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
loop_var: shell_rc_file
|
loop_var: shell_rc_file
|
||||||
|
no_log: true
|
||||||
|
|
||||||
- name: "Create .netrc file"
|
- name: "Create .netrc file"
|
||||||
when: unix_user.value.netrc is defined and unix_user.value.netrc | length > 0
|
when: unix_user.value.netrc is defined and unix_user.value.netrc | length > 0
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
- name: Remove unix user
|
- name: Remove unix user
|
||||||
ansible.builtin.include_tasks: remove_unix_user.yml
|
ansible.builtin.include_tasks: remove_unix_user.yml
|
||||||
|
no_log: true
|
||||||
with_dict: "{{ unix_users }}"
|
with_dict: "{{ unix_users }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
loop_var: unix_user
|
loop_var: unix_user
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
|
|
||||||
- name: Create unix groups
|
- name: Create unix groups
|
||||||
ansible.builtin.include_tasks: create_unix_group.yml
|
ansible.builtin.include_tasks: create_unix_group.yml
|
||||||
|
no_log: true
|
||||||
with_dict: "{{ unix_groups }}"
|
with_dict: "{{ unix_groups }}"
|
||||||
loop_control:
|
loop_control:
|
||||||
loop_var: unix_group
|
loop_var: unix_group
|
||||||
|
|||||||
Reference in new issue
Block a user