Compare commits

..
Author SHA1 Message Date
claude-botandClaude Opus 5.5 1b63cb8970 feat(gitea): trust the OpenBao SSH CA (phase 3b)
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
Step A is verified: Secret gitea-ssh-ca holds exactly the CA key
(SHA256:xEhuqx..., one line, 81 bytes like OpenBao's content-length),
so the webhook provider passes a text/plain body through unchanged.

- StatefulSet: SSH_TRUSTED_USER_CA_KEYS from the Secret,
  SSH_AUTHORIZED_PRINCIPALS_ALLOW=username. The built-in SSH server
  looks a cert's principals up as principal keys, so each user
  registers their username once as a principal.
- ExternalSecret: trim the trailing newline before it reaches app.ini.
- Reloader annotation: Gitea reads env only at start, so a CA rotation
  restarts it. The step A comment claimed the refresh alone reaches
  Gitea; corrected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 11:40:05 +01:00
k.renner 4ba3f03b18 Merge pull request 'feat(gitea): sync the OpenBao SSH CA public key into a Secret (SSH CA phase 3a)' (#1474) from gitea-ssh-ca-secret into main
Reviewed-on: #1474
2026-10-27 10:35:31 +00:00
claude-botandClaude Opus 5.5 739b41b623 feat(gitea): sync the OpenBao SSH CA public key into a Secret
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
Phase 3, step A of the SSH CA: Gitea's built-in SSH server needs the CA
key as SSH_TRUSTED_USER_CA_KEYS. A namespaced ESO webhook SecretStore
reads OpenBao's unauthenticated /v1/ssh-client-signer/public_key from
the in-cluster Service into Secret gitea-ssh-ca, so the key has one
source. Gitea is not wired to it yet; step B follows once the Secret
is verified to hold exactly the CA key (the ESO docs do not say how a
text/plain body without result.jsonPath is handled).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 11:22:47 +01:00
security-bot f8796b181f chore(image-automation): bump gitea.renner.uno/konairius/wiki:build.1793094262
Updates managed by image-automation-controller.
2026-10-27 09:47:02 +00:00
k.renner e8c72cd1f1 Merge pull request 'feat(ansible): SSH CA on edge-1' (#1473) from ssh-ca-edge-1 into main
ci/woodpecker/push/deploy-ssh-ca Pipeline was successful
Reviewed-on: #1473
2026-10-27 09:46:56 +00:00
claude-botandClaude Opus 5.5 2da554d40e feat(ansible): SSH CA on edge-1
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
netbird verified (Ubuntu, ssh.service): claude-bot cert login, sudo
journalctl allowed, systemctl restart refused. edge-1 is the last
Ansible-managed host (OpenSSH_10.6 banner like the proxies).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:44:45 +01:00
k.renner 35461f1f54 Merge pull request 'fix(ansible): ssh-ca reloads the sshd unit that is running' (#1472) from ssh-ca-running-unit into main
ci/woodpecker/push/deploy-ssh-ca Pipeline was successful
Reviewed-on: #1472
Reviewed-by: Review Bot <review-bot@renner.uno>
2026-10-27 09:42:25 +00:00
claude-botandClaude Opus 5.5 adedc98b50 fix(ansible): ssh-ca reloads the sshd unit that is running
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
The netbird run (pipeline 14795) failed at the reload: 'Could not find
the requested service sshd.service'. Ubuntu's service_facts list
sshd.service as a not-found alias next to the real ssh.service
(running; disabled because ssh.socket activates it), and the role only
checked that the name exists.

The unit is now the one of sshd.service/ssh.service whose state is
running, asserted to be exactly one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:41:06 +01:00
k.renner 1f228c94ef Merge pull request 'feat(ansible): SSH CA on netbird.renner.uno' (#1471) from ssh-ca-netbird into main
ci/woodpecker/push/deploy-ssh-ca Pipeline failed
Reviewed-on: #1471
2026-10-27 09:37:16 +00:00
claude-botandClaude Opus 5.5 9232937b85 feat(ansible): SSH CA on netbird.renner.uno
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
proxy-2 verified (claude-bot cert login, sudo allow/deny as on
proxy-1). netbird is the first Ubuntu host (OpenSSH_9.6p1
Ubuntu-3ubuntu13.20): unit ssh.service, which the role picks from
service_facts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:36:21 +01:00
k.renner 707f6ab840 Merge pull request 'feat(ansible): SSH CA on proxy-2' (#1470) from ssh-ca-proxy-2 into main
ci/woodpecker/push/deploy-ssh-ca Pipeline was successful
Reviewed-on: #1470
2026-10-27 09:33:35 +00:00
claude-botandClaude Opus 5.5 7600a9dbf9 feat(ansible): SSH CA on proxy-2
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
proxy-1 is verified: owner cert login as root (sk-ed25519 cert) and
claude-bot cert login with the expected sudo rules both work. proxy-2
is its twin (same OpenSSH_10.6 banner).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:31:42 +01:00
k.renner b12ef67002 Merge pull request 'fix(ansible): ssh-ca compares sshd -T output case-insensitively' (#1469) from ssh-ca-assert-case into main
ci/woodpecker/push/deploy-ssh-ca Pipeline was successful
Reviewed-on: #1469
Reviewed-by: Review Bot <review-bot@renner.uno>
2026-10-27 09:25:47 +00:00
claude-botandClaude Opus 5.5 e6e0e44b6a fix(ansible): compare sshd -T output case-insensitively
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
The second deploy-ssh-ca run on proxy-1 failed at the assert although
all three settings were active: OpenSSH 10.6 prints them as
TrustedUserCAKeys, AuthorizedPrincipalsFile and RevokedKeys, the check
expected lowercase keywords. The run stopped before the reload, so the
running sshd still has the old config.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:24:41 +01:00
k.renner 39b7fc7fc3 Merge pull request 'fix(ansible): ssh-ca reloads sshd by its real unit name' (#1468) from ssh-ca-reload-fix into main
ci/woodpecker/push/deploy-ssh-ca Pipeline failed
Reviewed-on: #1468
Reviewed-by: Review Bot <review-bot@renner.uno>
2026-10-27 09:09:51 +00:00
claude-botandClaude Opus 5.5 33dd968a25 fix(ansible): reload sshd by its real unit name on every run
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
The first deploy to proxy-1 failed at the reload: 'Could not find the
requested service ssh'. proxy-1 is not Debian (banner OpenSSH_10.6
without distro suffix, root shell /usr/bin/bash), so the unit is
sshd.service; the handler only special-cased RedHat.

The unit is now picked from service_facts. The reload runs on every
run instead of via a handler: sshd -T only parses the files, so after
a failed reload a re-run with nothing changed would otherwise leave the
running daemon on the old config.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:05:19 +01:00
k.renner d8813a68b7 Merge pull request 'feat(ansible): hosts trust the OpenBao SSH user CA (SSH CA phase 2)' (#1467) from ssh-ca-phase2 into main
ci/woodpecker/push/deploy-ssh-ca Pipeline failed
Reviewed-on: #1467
2026-10-27 08:53:33 +00:00
security-bot d033503fc3 chore(image-automation): bump gotenberg/gotenberg:8.39.0
Updates managed by image-automation-controller.
2026-10-27 08:50:46 +00:00
8 changed files with 86 additions and 17 deletions
+3
View File
@@ -75,3 +75,6 @@ claude-ws ansible_host=10.3.40.78 ansible_user=root
# was verified.
[ssh_ca]
proxy-1.renner.uno
proxy-2.renner.uno
netbird.renner.uno
edge-1.iot.renner.uno
-6
View File
@@ -1,6 +0,0 @@
---
- name: Reload sshd
ansible.builtin.service:
name: "{{ 'sshd' if ansible_facts['os_family'] | default('') == 'RedHat' else 'ssh' }}"
state: reloaded
listen: "reload sshd"
+29 -9
View File
@@ -22,7 +22,6 @@
owner: root
group: root
mode: "0644"
notify: reload sshd
# sshd refuses all public key logins when RevokedKeys is set but unreadable,
# so the file is always deployed, even when empty.
@@ -33,7 +32,6 @@
owner: root
group: root
mode: "0644"
notify: reload sshd
- name: Create the Claude account
ansible.builtin.user:
@@ -98,7 +96,6 @@
owner: root
group: root
mode: "0644"
notify: reload sshd
- name: Test the full sshd config
ansible.builtin.command: sshd -t
@@ -113,18 +110,41 @@
ansible.builtin.fail:
msg: "sshd -t failed with the SSH CA drop-in; it was removed again"
- name: Reload sshd now
ansible.builtin.meta: flush_handlers
- name: Read the effective sshd settings
ansible.builtin.command: sshd -T
changed_when: false
register: ssh_ca_effective
# Keyword case in sshd -T output differs between OpenSSH versions (mixed case
# on 10.6), so the lines are compared lowercased.
- name: Check sshd uses the CA, principals and revocation list
vars:
ssh_ca_effective_lines: "{{ ssh_ca_effective.stdout_lines | map('lower') | list }}"
ansible.builtin.assert:
that:
- "'trustedusercakeys /etc/ssh/trusted_user_ca_keys' in ssh_ca_effective.stdout_lines"
- "'authorizedprincipalsfile /etc/ssh/auth_principals/%u' in ssh_ca_effective.stdout_lines"
- "'revokedkeys /etc/ssh/revoked_keys' in ssh_ca_effective.stdout_lines"
- "'trustedusercakeys /etc/ssh/trusted_user_ca_keys' in ssh_ca_effective_lines"
- "'authorizedprincipalsfile /etc/ssh/auth_principals/%u' in ssh_ca_effective_lines"
- "'revokedkeys /etc/ssh/revoked_keys' in ssh_ca_effective_lines"
fail_msg: "sshd -T does not show the SSH CA settings; another config line takes precedence"
# The unit is sshd.service on Arch and RedHat, ssh.service on Debian/Ubuntu.
# Ubuntu also lists sshd.service as a not-found alias, so the running one is
# picked. sshd -T only parses the files, so the running daemon is reloaded on
# every run; a reload keeps open sessions.
- name: Read the service list
ansible.builtin.service_facts:
- name: Reload the running sshd unit
vars:
ssh_ca_sshd_units: "{{ ansible_facts.services | dict2items | selectattr('key', 'in', ['sshd.service', 'ssh.service']) | selectattr('value.state', 'equalto', 'running') | map(attribute='key') | list }}"
block:
- name: Check exactly one sshd unit is running
ansible.builtin.assert:
that:
- ssh_ca_sshd_units | length == 1
fail_msg: "Expected one running sshd.service or ssh.service, found {{ ssh_ca_sshd_units }}"
- name: Reload sshd so the running daemon uses the CA config
ansible.builtin.systemd:
name: "{{ ssh_ca_sshd_units | first }}"
state: reloaded
+2
View File
@@ -16,6 +16,8 @@ resources:
- networkpolicy.yaml
# Nightly prune of old CI image builds (REN-1203).
- package-retention.yaml
# OpenBao SSH CA public key for certificate logins.
- ssh-ca.yaml
- kumamonitors.yaml
configMapGenerator:
- name: gitea-package-retention
+37
View File
@@ -0,0 +1,37 @@
# Public key of the OpenBao SSH user CA (ssh-client-signer), for Gitea's
# built-in SSH server to accept owner and claude-bot certificates. OpenBao
# serves it unauthenticated; ESO reads it from the in-cluster Service, so the
# key has one source. Gitea reads it from env at start; Reloader restarts the
# StatefulSet when the Secret changes.
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: openbao-ssh-ca
namespace: gitea
spec:
provider:
webhook:
url: http://openbao.openbao.svc.cluster.local:8200/v1/ssh-client-signer/public_key
method: GET
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: gitea-ssh-ca
namespace: gitea
spec:
refreshInterval: 1h
secretStoreRef:
kind: SecretStore
name: openbao-ssh-ca
target:
name: gitea-ssh-ca
# OpenBao ends the body with a newline; it must not reach app.ini.
template:
engineVersion: v2
data:
trusted-user-ca-keys: "{{ .ca | trim }}"
data:
- secretKey: ca
remoteRef:
key: public_key
+13
View File
@@ -5,6 +5,9 @@ metadata:
namespace: gitea
labels:
app.kubernetes.io/name: gitea
annotations:
# A CA rotation changes gitea-ssh-ca; Gitea reads it only at start.
secret.reloader.stakater.com/reload: gitea-ssh-ca
spec:
serviceName: gitea
replicas: 1 # SQLite => single writer. Do NOT scale up without moving to Postgres first.
@@ -85,6 +88,16 @@ spec:
# HAS CHANGED". Paths are relative to APP_DATA_PATH (/var/lib/gitea/data).
- name: GITEA__server__SSH_SERVER_HOST_KEYS
value: "ssh/ssh_host_ed25519_key,ssh/ssh_host_rsa_key"
# OpenBao SSH CA (ssh-ca.yaml): certificates log in as the user whose
# name is a principal of the cert. Each user registers that
# principal once (Settings > SSH / GPG Keys > Manage principals).
- name: GITEA__server__SSH_TRUSTED_USER_CA_KEYS
valueFrom:
secretKeyRef:
name: gitea-ssh-ca
key: trusted-user-ca-keys
- name: GITEA__server__SSH_AUTHORIZED_PRINCIPALS_ALLOW
value: username
# Gitea Actions was decommissioned — keep it off.
- name: GITEA__actions__ENABLED
value: "false"
+1 -1
View File
@@ -22,7 +22,7 @@ spec:
type: RuntimeDefault
containers:
- name: gotenberg
image: gotenberg/gotenberg:8.38.1 # {"$imagepolicy": "flux-system:gotenberg"}
image: gotenberg/gotenberg:8.39.0 # {"$imagepolicy": "flux-system:gotenberg"}
args:
- "gotenberg"
- "--chromium-disable-javascript=true"
+1 -1
View File
@@ -53,7 +53,7 @@ spec:
type: RuntimeDefault
containers:
- name: wiki
image: gitea.renner.uno/konairius/wiki:build.1793086240 # {"$imagepolicy": "flux-system:wiki"}
image: gitea.renner.uno/konairius/wiki:build.1793094262 # {"$imagepolicy": "flux-system:wiki"}
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true