From 661b83249c2af9d6b57a2edf1b2f3916f8509d81 Mon Sep 17 00:00:00 2001 From: mesh-tools Date: Sat, 17 Oct 2026 00:01:42 +0000 Subject: [PATCH] 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) --- modules/gitea/README.md | 2 +- modules/gitea/cmd/forge-account-provider/bus_test.go | 2 +- modules/gitea/cmd/forge-account-provider/service.go | 11 ++++++++++- .../gitea/cmd/forge-account-provider/service_test.go | 3 ++- .../ssh-client/cmd/ssh-client-tools/templates_test.go | 8 +++++--- modules/ssh-client/files/forge | 4 +++- modules/ssh-client/files/gitconfig | 2 +- modules/ssh-client/files/known_hosts | 2 +- 8 files changed, 24 insertions(+), 10 deletions(-) diff --git a/modules/gitea/README.md b/modules/gitea/README.md index fb7b319d..dca279c0 100644 --- a/modules/gitea/README.md +++ b/modules/gitea/README.md @@ -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-` in the agents team (`internal/forgeaccount`, the code `cmd/agent-token` shares), registers the key under the title `mesh:.`, -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. diff --git a/modules/gitea/cmd/forge-account-provider/bus_test.go b/modules/gitea/cmd/forge-account-provider/bus_test.go index b332cae7..a912e92e 100644 --- a/modules/gitea/cmd/forge-account-provider/bus_test.go +++ b/modules/gitea/cmd/forge-account-provider/bus_test.go @@ -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) } diff --git a/modules/gitea/cmd/forge-account-provider/service.go b/modules/gitea/cmd/forge-account-provider/service.go index 11716bf3..49fe84a5 100644 --- a/modules/gitea/cmd/forge-account-provider/service.go +++ b/modules/gitea/cmd/forge-account-provider/service.go @@ -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 } diff --git a/modules/gitea/cmd/forge-account-provider/service_test.go b/modules/gitea/cmd/forge-account-provider/service_test.go index d8203833..61b8404e 100644 --- a/modules/gitea/cmd/forge-account-provider/service_test.go +++ b/modules/gitea/cmd/forge-account-provider/service_test.go @@ -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 { diff --git a/modules/ssh-client/cmd/ssh-client-tools/templates_test.go b/modules/ssh-client/cmd/ssh-client-tools/templates_test.go index ea270636..4b8be920 100644 --- a/modules/ssh-client/cmd/ssh-client-tools/templates_test.go +++ b/modules/ssh-client/cmd/ssh-client-tools/templates_test.go @@ -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) } diff --git a/modules/ssh-client/files/forge b/modules/ssh-client/files/forge index 2da188cf..6bbf4e20 100644 --- a/modules/ssh-client/files/forge +++ b/modules/ssh-client/files/forge @@ -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 diff --git a/modules/ssh-client/files/gitconfig b/modules/ssh-client/files/gitconfig index d13ccb34..05252302 100644 --- a/modules/ssh-client/files/gitconfig +++ b/modules/ssh-client/files/gitconfig @@ -1,2 +1,2 @@ -[url "${bound:forge-account:ssh_base}"] +[url "ssh://${bound:forge-account:ssh_alias}/"] insteadOf = ${bound:forge-account:https_base} diff --git a/modules/ssh-client/files/known_hosts b/modules/ssh-client/files/known_hosts index aa12b501..e0df6d81 100644 --- a/modules/ssh-client/files/known_hosts +++ b/modules/ssh-client/files/known_hosts @@ -1 +1 @@ -${bound:forge-account:known_host} +${bound:forge-account:ssh_alias} ${bound:forge-account:host_key}