From 821cf38049a89c66beac16b9edd2ef55e7468c86 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 6 Nov 2026 13:09:45 +0100 Subject: [PATCH] Replaced ssh-keyscan with reading host keys in federation setup status OpenSSH 9.8+ PerSourcePenalties penalise repeated ssh-keyscan connections from localhost, so the scan often returned nothing and setup status was not written. Host key paths now come from sshd -T and the public keys are read from disk. Co-Authored-By: Claude Opus 5.5 Ticket: ENT-14407 Changelog: Federation setup status no longer fails to collect the SSH host key fingerprint on OpenSSH 9.8+ Signed-off-by: Lars Erik Wik --- .../enterprise/federation/federation.cf | 48 ++++++++++++++----- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/cfe_internal/enterprise/federation/federation.cf b/cfe_internal/enterprise/federation/federation.cf index 7f244f4998..2df7ff6331 100644 --- a/cfe_internal/enterprise/federation/federation.cf +++ b/cfe_internal/enterprise/federation/federation.cf @@ -1187,17 +1187,38 @@ bundle agent setup_status "$(cfengine_enterprise_federation:transport_user.ssh_pub_key)" ); - "ssh_server_fingerprint" - # ssh-keyscan is used because it's more reliable/easy than trying to - # parse sshd config to find the file and then readfile(): - string => execresult( - "ssh-keyscan localhost 2>/dev/null | sed 's/localhost //g' | sort", - useshell + # Read the host keys sshd uses from disk instead of ssh-keyscan localhost. + # OpenSSH 9.8+ PerSourcePenalties penalise repeated unauthenticated + # connections, so ssh-keyscan starts returning nothing. + "_sshd_config" + slist => string_split( + execresult("/usr/sbin/sshd -T", "noshell"), "$(const.n)", "inf" ); + "_hostkey_paths" + slist => maplist( + regex_replace($(this), "^hostkey\s+", "", ""), + grep("hostkey\s+.+", _sshd_config) + ); + + # Keep " " and drop the trailing comment + "_host_public_key[$(_hostkey_paths)]" + string => regex_replace( + readfile("$(_hostkey_paths).pub"), "^(\S+\s+\S+).*", "$1", "s" + ), + if => fileexists("$(_hostkey_paths).pub"); + + "_host_public_keys" slist => sort(getvalues(_host_public_key), "lex"); + + # Same format as ssh-keyscan output: " " per line, sorted + "ssh_server_fingerprint" string => join("$(const.n)", _host_public_keys); + classes: "ssh_server_fingerprint_collected" - expression => not(strcmp("$(ssh_server_fingerprint)", "")); + expression => and( + isvariable("ssh_server_fingerprint"), + not(strcmp("$(ssh_server_fingerprint)", "")) + ); "superhub_setup_status_complete" expression => "any", @@ -1210,11 +1231,12 @@ bundle agent setup_status }; files: - # An empty fingerprint (ssh-keyscan returned nothing, e.g. sshd not yet - # reachable on localhost) must not be advertised: a superhub would render - # an empty known_hosts entry for this host and its rsync pull would later - # fail with "Host key verification failed". Skip the status update until - # the fingerprint is collected; the next agent run will retry. + # An empty fingerprint (no readable host public keys, e.g. sshd not + # installed or host keys not yet generated) must not be advertised: a + # superhub would render an empty known_hosts entry for this host and its + # rsync pull would later fail with "Host key verification failed". Skip + # the status update until the fingerprint is collected; the next agent + # run will retry. superhub_setup_status_complete.ssh_server_fingerprint_collected:: "$(cfengine_enterprise_federation:config.path_setup_status)" create => "true", @@ -1232,7 +1254,7 @@ bundle agent setup_status reports: superhub_setup_status_complete.!ssh_server_fingerprint_collected:: - "warning: 'ssh-keyscan localhost' returned no SSH host key; federation setup status not written yet. Will retry next run."; + "warning: Found no readable SSH host public keys (from 'sshd -T'); federation setup status not written yet. Will retry next run."; } bundle agent distributed_cleanup_setup