diff --git a/agent/agent.go b/agent/agent.go index eddebc5d6b26d..1e18290d84b6a 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -1186,9 +1186,9 @@ func (a *agent) createOrUpdateNetwork(manifestOK, networkOK *checkpoint) func(co network := a.network a.closeMutex.Unlock() if network == nil { - keySeed, err := WorkspaceKeySeed(manifest.WorkspaceID, manifest.AgentName) + keySeed, err := SSHKeySeed(manifest.OwnerName, manifest.WorkspaceName, manifest.AgentName) if err != nil { - return xerrors.Errorf("generate seed from workspace id: %w", err) + return xerrors.Errorf("generate SSH key seed: %w", err) } // use the graceful context here, because creating the tailnet is not itself tied to the // agent API. @@ -2068,12 +2068,31 @@ func PrometheusMetricsHandler(prometheusRegistry *prometheus.Registry, logger sl }) } -// WorkspaceKeySeed converts a WorkspaceID UUID and agent name to an int64 hash. +// SSHKeySeed converts an owner userName, workspaceName and agentName to an int64 hash. // This uses the FNV-1a hash algorithm which provides decent distribution and collision // resistance for string inputs. -func WorkspaceKeySeed(workspaceID uuid.UUID, agentName string) (int64, error) { +// +// Why owner username, workspace name, and agent name? These are the components that are used in hostnames for the +// workspace over SSH, and so we want the workspace to have a stable key with respect to these. We don't use the +// respective UUIDs. The workspace UUID would be different if you delete and recreate a workspace with the same name. +// The agent UUID is regenerated on each build. Since Coder's Tailnet networking is handling the authentication, we +// should not be showing users warnings about host SSH keys. +func SSHKeySeed(userName, workspaceName, agentName string) (int64, error) { h := fnv.New64a() - _, err := h.Write(workspaceID[:]) + _, err := h.Write([]byte(userName)) + if err != nil { + return 42, err + } + // null separators between strings so that (dog, foodstuff) is distinct from (dogfood, stuff) + _, err = h.Write([]byte{0}) + if err != nil { + return 42, err + } + _, err = h.Write([]byte(workspaceName)) + if err != nil { + return 42, err + } + _, err = h.Write([]byte{0}) if err != nil { return 42, err } diff --git a/cli/ssh_test.go b/cli/ssh_test.go index 109733807849b..4bd7682067f94 100644 --- a/cli/ssh_test.go +++ b/cli/ssh_test.go @@ -479,6 +479,9 @@ func TestSSH(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() + user, err := client.User(ctx, codersdk.Me) + require.NoError(t, err) + inv, root := clitest.New(t, "ssh", "--stdio", workspace.Name) clitest.SetupConfig(t, client, root) inv.Stdin = clientOutput @@ -490,7 +493,7 @@ func TestSSH(t *testing.T) { assert.NoError(t, err) }) - keySeed, err := agent.WorkspaceKeySeed(workspace.ID, "dev") + keySeed, err := agent.SSHKeySeed(user.Username, workspace.Name, "dev") assert.NoError(t, err) signer, err := agentssh.CoderSigner(keySeed)