From 3072cea3bc45d3db36d78c413e5933852a5774b0 Mon Sep 17 00:00:00 2001
From: Nico Boehr <nico@nicoboehr.de>
Date: Thu, 29 May 2025 23:59:13 +0200
Subject: [PATCH] reorganize SSH key management

- read the SSH keys only from /etc and not from users home for better
  auditability. This also makes generating the key lists much easier.
- move deployment of root ssh keys to the ssh role
- improve deployment of user ssh keys in the user role
---
 inventory/ffspveguests                        |  1 +
 .../files/sshd-10-disable-password-auth.conf  |  2 +
 roles/ssh/files/sshd-11-pubkeys-from-etc.conf |  5 +++
 roles/ssh/tasks/main.yml                      | 42 +++++++++++++++++--
 roles/ssh/templates/ssh-root-authorized-keys  |  8 ++++
 roles/users/tasks/main.yml                    | 12 ------
 roles/users/tasks/root_pubkey.yml             | 19 ---------
 roles/users/tasks/user.yml                    | 21 ++++------
 .../users/templates/ssh-user-authorized-keys  |  4 ++
 9 files changed, 65 insertions(+), 49 deletions(-)
 create mode 100644 roles/ssh/files/sshd-10-disable-password-auth.conf
 create mode 100644 roles/ssh/files/sshd-11-pubkeys-from-etc.conf
 create mode 100644 roles/ssh/templates/ssh-root-authorized-keys
 delete mode 100644 roles/users/tasks/root_pubkey.yml
 create mode 100644 roles/users/templates/ssh-user-authorized-keys

diff --git a/inventory/ffspveguests b/inventory/ffspveguests
index 3cca7f9..d6c4f24 100644
--- a/inventory/ffspveguests
+++ b/inventory/ffspveguests
@@ -71,3 +71,4 @@ ffspveguests:
     nrb-backbonetest2.vm.freifunk-stuttgart.de:
       ansible_ssh_host: 2a01:4f8:172:feff:be24:11ff:fe8b:8979
       ansible_ssh_user: root
+    test-ansible01.vm.freifunk-stuttgart.de:
diff --git a/roles/ssh/files/sshd-10-disable-password-auth.conf b/roles/ssh/files/sshd-10-disable-password-auth.conf
new file mode 100644
index 0000000..861ba74
--- /dev/null
+++ b/roles/ssh/files/sshd-10-disable-password-auth.conf
@@ -0,0 +1,2 @@
+# ANSIBLE managed
+PasswordAuthentication no
diff --git a/roles/ssh/files/sshd-11-pubkeys-from-etc.conf b/roles/ssh/files/sshd-11-pubkeys-from-etc.conf
new file mode 100644
index 0000000..e3b7141
--- /dev/null
+++ b/roles/ssh/files/sshd-11-pubkeys-from-etc.conf
@@ -0,0 +1,5 @@
+# ANSIBLE managed
+AuthorizedKeysFile /etc/ssh/authorized_keys.d/%u
+Match User root
+	AuthorizedKeysFile /etc/ssh/authorized_keys.d/%u /etc/pve/priv/authorized_keys
+
diff --git a/roles/ssh/tasks/main.yml b/roles/ssh/tasks/main.yml
index 4c77b63..0968ec0 100644
--- a/roles/ssh/tasks/main.yml
+++ b/roles/ssh/tasks/main.yml
@@ -1,9 +1,43 @@
 ---
+- name: Fail when no root public keys would be deployed
+  ansible.builtin.fail:
+    msg: No public keys would be deployed
+  when: "users_root|default([]) == [] and users_root_group|default([]) == []"
+
+- name: Ensure SSH dropin configuration file directory exists
+  ansible.builtin.file:
+    path: /etc/ssh/sshd_config.d
+    state: directory
+    mode: "0755"
+
 - name: Disable SSH password login
-  lineinfile:
-    regexp: "^#?PasswordAuthentication"
-    line: "PasswordAuthentication no"
-    path: /etc/ssh/sshd_config
+  ansible.builtin.copy:
+    dest: /etc/ssh/sshd_config.d/10-disable-password-auth.conf
+    mode: "0644"
+    src: sshd-10-disable-password-auth.conf
   notify:
     - restart ssh
   when: ssh_disable_password_login
+
+- name: Ensure SSH authorized keys directory exists
+  ansible.builtin.file:
+    path: /etc/ssh/authorized_keys.d
+    state: directory
+    mode: "0755"
+
+- name: Read public keys /etc and not from users home directory
+  ansible.builtin.copy:
+    dest: /etc/ssh/sshd_config.d/11-pubkeys-from-etc.conf
+    mode: "0644"
+    src: sshd-11-pubkeys-from-etc.conf
+  notify:
+    - restart ssh
+  when: ssh_disable_password_login
+
+- name: Authorize public keys for root
+  ansible.builtin.template:
+    dest: /etc/ssh/authorized_keys.d/root
+    src: ssh-root-authorized-keys
+    owner: root
+    group: root
+    mode: "0644"
diff --git a/roles/ssh/templates/ssh-root-authorized-keys b/roles/ssh/templates/ssh-root-authorized-keys
new file mode 100644
index 0000000..15ffdad
--- /dev/null
+++ b/roles/ssh/templates/ssh-root-authorized-keys
@@ -0,0 +1,8 @@
+# ANSIBLE managed
+{% for user in user_database.keys()|list %}
+{% for pubkey in user_database[user].pubkeys %}
+{% if user in users_root|default([]) or user in users_root_group|default([]) %}
+{{ pubkey }} {{ user }}
+{% endif %}
+{% endfor %}
+{% endfor %}
diff --git a/roles/users/tasks/main.yml b/roles/users/tasks/main.yml
index f9fc4a7..9c24959 100644
--- a/roles/users/tasks/main.yml
+++ b/roles/users/tasks/main.yml
@@ -4,11 +4,6 @@
     msg: No users are in users nor in users_group
   when: "users|default([]) == [] and users_group|default([]) == []"
 
-- name: Fail when no root public keys would be deployed
-  ansible.builtin.fail:
-    msg: No public keys would be deployed
-  when: "users_root|default([]) == [] and users_root_group|default([]) == []"
-
 - name: Creating users
   include_tasks: user.yml 
   loop: "{{ users|default([]) + users_group|default([]) }}" 
@@ -20,10 +15,3 @@
   loop: '{{ user_database | dict2items }}'
   loop_control:
     loop_var: user
-
-- name: Deploying public key for users with root access
-  include_tasks: root_pubkey.yml
-  vars:
-  loop: '{{ user_database.keys() | list }}'
-  loop_control:
-    loop_var: user
diff --git a/roles/users/tasks/root_pubkey.yml b/roles/users/tasks/root_pubkey.yml
deleted file mode 100644
index 07d7567..0000000
--- a/roles/users/tasks/root_pubkey.yml
+++ /dev/null
@@ -1,19 +0,0 @@
----
-- name: "Deploying {{ user }} public keys for root"
-  authorized_key:
-    user: "root"
-    state: '{{ (user in users_root|default([]) or user in users_root_group|default([]) ) | ternary("present", "absent") }}'
-    key: '{{ item }}'
-    comment: '{{ user }} {{ (item|split(" "))[2]|default("") }}'
-    follow: yes
-  loop: '{{ user_database[user].pubkeys }}'
-
-- name: "Remove revoked {{ user }} public keys for root"
-  authorized_key:
-    user: "root"
-    state: absent
-    key: '{{ item }}'
-    comment: '{{ user }}'
-    follow: yes
-  loop: '{{ user_database[user].revoked_pubkeys }}'
-  when: 'user_database[user].revoked_pubkeys is defined'
diff --git a/roles/users/tasks/user.yml b/roles/users/tasks/user.yml
index 67c5aa2..1f54621 100644
--- a/roles/users/tasks/user.yml
+++ b/roles/users/tasks/user.yml
@@ -11,17 +11,10 @@
     group: "{{ user }}"
     shell: /bin/bash
 
-- name: "Add ssh keys for {{ user }}"
-  authorized_key:
-    user: "{{ user }}"
-    state: present
-    key: '{{ item }}'
-  loop: '{{ user_database[user].pubkeys }}'
-
-- name: "Remove revoked ssh keys for {{ user }}"
-  authorized_key:
-    user: "{{ user }}"
-    state: absent
-    key: '{{ item }}'
-  loop: '{{ user_database[user].revoked_pubkeys }}'
-  when: 'user_database[user].revoked_pubkeys is defined'
+- name: "Authorize public keys for user {{ user }}"
+  ansible.builtin.template:
+    dest: "/etc/ssh/authorized_keys.d/{{ user|quote }}"
+    src: ssh-user-authorized-keys
+    owner: root
+    group: root
+    mode: "0644"
diff --git a/roles/users/templates/ssh-user-authorized-keys b/roles/users/templates/ssh-user-authorized-keys
new file mode 100644
index 0000000..4a95e86
--- /dev/null
+++ b/roles/users/templates/ssh-user-authorized-keys
@@ -0,0 +1,4 @@
+# ANSIBLE managed
+{% for pubkey in user_database[user].pubkeys %}
+{{ pubkey }} {{ user }}
+{% endfor %}
-- 
GitLab