Give the forge an ssh name of its own, so it no longer merges with the operator's entry for its machine (issue 955, ADR 0409)
mesh/merge-gate head 661b83249c2a merged into trunk bb291b5ad8af — pass: builds gitea, ssh-client → ace, g14, novox, shanks; no bus step; every machine com…
mesh/repo-check head 661b83249c2a merged into trunk bb291b5ad8af — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered

This commit is contained in:
2026-10-17 00:01:42 +00:00
parent bb291b5ad8
commit 661b83249c
8 changed files with 24 additions and 10 deletions
+1 -1
View File
@@ -228,7 +228,7 @@ Gitea provides `forge-account`, and `cmd/forge-account-provider` answers its con
machine). The provision has no credential: a public key is no secret. For a machine whose ssh-client has reported its
key in the state `keys` (read, `ssh-client.keys`), the provider makes the account `agent-<node>` in the agents team
(`internal/forgeaccount`, the code `cmd/agent-token` shares), registers the key under the title `mesh:<node>.<account>`,
and serves the entry values `user`, `ssh_host` (the forge machine's mesh name, from the fact `forge-host`), `ssh_port`,
and serves the entry values `user`, `ssh_host` (the forge machine's mesh name, from the fact `forge-host`), `ssh_port`, `ssh_alias` (`mesh-forge`, the forge's own name in ssh's configuration, ADR 0409),
`known_host` (read from the forge's own sshd with `ssh-keyscan`), `ssh_base` and `https_base`. A machine that has
reported no key gets no entry, and its client says it waits. Keys titled `mesh:` that are no longer wanted are removed,
a key added by hand never is. Every consumer is made again every ten minutes and at once when a key changes.
@@ -315,7 +315,7 @@ func TestTheProviderWholeAnswersAMachineThatReportedItsKey(t *testing.T) {
return ok
})
if e.Values["user"] != "agent-g14" || e.Values["ssh_host"] != "novox.internal" ||
e.Values["known_host"] != "[novox.internal]:222 ssh-ed25519 AAAAC3HOSTKEY" ||
e.Values["known_host"] != "[novox.internal]:222 ssh-ed25519 AAAAC3HOSTKEY" || e.Values["host_key"] != "ssh-ed25519 AAAAC3HOSTKEY" || e.Values["ssh_alias"] != "mesh-forge" ||
e.Values["ssh_base"] != "ssh://git@novox.internal:222/" || e.Values["https_base"] != "https://git.novox.be/" {
t.Fatalf("values = %v", e.Values)
}
@@ -320,7 +320,12 @@ func (s *Service) scan(ctx context.Context) (string, error) {
return knownHostLine(string(out), host, s.cfg.Port)
}
// knownHostLine is the ed25519 line of ssh-keyscan's output with its host written as the mesh name and port.
// sshAlias is the forge's own name in ssh's configuration (issue 955, ADR 0409): the machines' ssh-client writes a
// Host of this name, so no other Host (the operator's own entry for the forge machine) is merged with it.
const sshAlias = "mesh-forge"
// knownHostLine is the ed25519 line of ssh-keyscan's output with its host written as the mesh name and port. It is
// the old value of `known_host`, kept for machines whose ssh-client has not moved to ssh_alias and host_key yet.
func knownHostLine(scanned, host string, port int) (string, error) {
for _, line := range strings.Split(scanned, "\n") {
f := strings.Fields(line)
@@ -348,7 +353,11 @@ func (s *Service) valuesFor(node string) (map[string]any, error) {
if user == "" || known == "" {
return nil, fmt.Errorf("nothing is known to serve %s yet", node)
}
hostKey := strings.Join(strings.Fields(known)[1:], " ")
return map[string]any{"user": user, "ssh_host": host, "ssh_port": s.cfg.Port, "known_host": known,
"ssh_alias": sshAlias, "host_key": hostKey,
// known_host and ssh_base keep their old values: a machine whose ssh-client has not moved yet renders its old
// templates from them. They are dropped in a later change once every machine uses ssh_alias and host_key (ADR 0409).
"ssh_base": fmt.Sprintf("ssh://git@%s:%d/", host, s.cfg.Port), "https_base": https}, nil
}
@@ -240,7 +240,8 @@ func TestAConsumerWithAKeyGetsItsEntryAndItsKeyIsRegistered(t *testing.T) {
r.pass()
e := r.bus.entry(t, "g14.ssh-client.forge-account")
want := map[string]any{"user": "agent-g14", "ssh_host": "novox.internal", "ssh_port": float64(222),
"known_host": "[novox.internal]:222 ssh-ed25519 AAAAC3HOSTKEY", "ssh_base": "ssh://git@novox.internal:222/",
"known_host": "[novox.internal]:222 ssh-ed25519 AAAAC3HOSTKEY", "ssh_alias": "mesh-forge",
"host_key": "ssh-ed25519 AAAAC3HOSTKEY", "ssh_base": "ssh://git@novox.internal:222/",
"https_base": "https://git.novox.be/"}
for k, v := range want {
if e.Values[k] != v {
@@ -93,7 +93,9 @@ func TestManifestKeepsKeysAndRequiresForgeAccount(t *testing.T) {
// Issue 955, area S3: the three files the forge account fills in — known_hosts, the forge's Host, git's rewrite.
var sampleBound = map[string]string{
"known_host": "[novox.internal]:222 ssh-ed25519 AAAAC3Nza",
"host_key": "ssh-ed25519 AAAAC3Nza",
"ssh_host": "novox.internal",
"ssh_alias": "mesh-forge",
"ssh_port": "222",
"ssh_base": "ssh://git@novox.internal:222/",
"https_base": "https://git.novox.be/",
@@ -141,7 +143,7 @@ func TestForgeFilesRenderFromTheBoundValues(t *testing.T) {
if kh["into"] != "block" {
t.Errorf("known_hosts is %v, want a block", kh["into"])
}
if got := strings.TrimSpace(renderBound(t, templateText(t, kh))); got != "[novox.internal]:222 ssh-ed25519 AAAAC3Nza" {
if got := strings.TrimSpace(renderBound(t, templateText(t, kh))); got != "mesh-forge ssh-ed25519 AAAAC3Nza" {
t.Errorf("known_hosts block = %q", got)
}
@@ -149,7 +151,7 @@ func TestForgeFilesRenderFromTheBoundValues(t *testing.T) {
if forge["into"] != nil {
t.Errorf("10-forge is a block, want the whole file")
}
want := "Host novox.internal\n Port 222\n User git\n IdentityFile ~/.ssh/mesh_ed25519\n IdentitiesOnly yes\n"
want := "Host mesh-forge\n HostName novox.internal\n Port 222\n HostKeyAlias mesh-forge\n User git\n IdentityFile ~/.ssh/mesh_ed25519\n IdentitiesOnly yes\n"
if got := renderBound(t, templateText(t, forge)); got != want {
t.Errorf("10-forge = %q, want %q", got, want)
}
@@ -159,7 +161,7 @@ func TestForgeFilesRenderFromTheBoundValues(t *testing.T) {
if g["into"] != "block" {
t.Errorf("git config is %v, want a block", g["into"])
}
wantGit := "[url \"ssh://git@novox.internal:222/\"]\n\tinsteadOf = https://git.novox.be/\n"
wantGit := "[url \"ssh://mesh-forge/\"]\n\tinsteadOf = https://git.novox.be/\n"
if got := renderBound(t, templateText(t, g)); got != wantGit {
t.Errorf("git config block = %q, want %q", got, wantGit)
}
+3 -1
View File
@@ -1,5 +1,7 @@
Host ${bound:forge-account:ssh_host}
Host ${bound:forge-account:ssh_alias}
HostName ${bound:forge-account:ssh_host}
Port ${bound:forge-account:ssh_port}
HostKeyAlias ${bound:forge-account:ssh_alias}
User git
IdentityFile ~/.ssh/mesh_ed25519
IdentitiesOnly yes
+1 -1
View File
@@ -1,2 +1,2 @@
[url "${bound:forge-account:ssh_base}"]
[url "ssh://${bound:forge-account:ssh_alias}/"]
insteadOf = ${bound:forge-account:https_base}
+1 -1
View File
@@ -1 +1 @@
${bound:forge-account:known_host}
${bound:forge-account:ssh_alias} ${bound:forge-account:host_key}