From a0546c219fd10dc53f297d2c5dd324a12399baa7 Mon Sep 17 00:00:00 2001 From: Anuar Ustayev Date: Sun, 13 Sep 2026 22:49:06 +0500 Subject: [PATCH] Pin the nodes' SSH host keys so CI can verify them The first CI deploy got all the way to SSH and failed on "Host key verification failed": host_key_checking is on, and a fresh runner has no cached keys, so strict checking correctly refused an unknown host. The keys are now pinned in infra/ansible/known_hosts and ansible.cfg points UserKnownHostsFile at it with StrictHostKeyChecking=yes. This does not weaken anything -- it strengthens it: CI and every laptop now verify against the same committed keys, instead of trusting whatever each operator accepted on first connection. The connection was already authenticated end-to-end by Cloudflare Access before SSH; this closes the one remaining gap without turning checking off. They are public keys, safe to commit. A reprovision changes a node's key and fails the deploy loudly here until the file is updated -- which is the correct behaviour, not a silent accept-new. Verified: `ansible all -m ping` connects to both nodes through the pinned known_hosts (pong from control and execution). Co-Authored-By: Claude Opus 5 (1M context) --- infra/ansible/ansible.cfg | 2 +- infra/ansible/known_hosts | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 infra/ansible/known_hosts diff --git a/infra/ansible/ansible.cfg b/infra/ansible/ansible.cfg index 88c63cf..40ef556 100644 --- a/infra/ansible/ansible.cfg +++ b/infra/ansible/ansible.cfg @@ -61,4 +61,4 @@ retries = 3 # traffic on the channel, and a silently dropped session is reported as a lost # host rather than a timeout. CountMax=6 gives up after three minutes, so a # genuinely dead connection is detected rather than hung on. -ssh_args = -o ProxyCommand="cloudflared access ssh --hostname %h" -o ControlMaster=auto -o ControlPersist=300s -o Compression=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=6 +ssh_args = -o ProxyCommand="cloudflared access ssh --hostname %h" -o UserKnownHostsFile=known_hosts -o StrictHostKeyChecking=yes -o ControlMaster=auto -o ControlPersist=300s -o Compression=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=6 diff --git a/infra/ansible/known_hosts b/infra/ansible/known_hosts new file mode 100644 index 0000000..4d007aa --- /dev/null +++ b/infra/ansible/known_hosts @@ -0,0 +1,13 @@ +# Pinned SSH host keys for the nodes, reached at their Cloudflare hostnames. +# +# host_key_checking is on (ansible.cfg), and a fresh CI runner has no cached +# keys, so the first CI deploy failed with "Host key verification failed". +# Rather than turn checking off, the keys are pinned here and verified against +# -- by CI and by every laptop, so verification no longer depends on whoever +# happened to accept the key first. +# +# These are PUBLIC keys; committing them is safe and is the point. If a node is +# reprovisioned its host key changes and the deploy fails loudly here until +# this file is updated (fetch /etc/ssh/ssh_host_ed25519_key.pub from the node). +ssh-staging.openbases.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIcVIXO7Wzd+sIdcrIUTI0huAq3rRZirc7Umuztmyh8U +ssh-exec-staging.openbases.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOWqJpxd18JhrYb6I/XlriG+M1OG2ZdkFFecZn84DSW5