@@ -1186,9 +1186,9 @@ func (a *agent) createOrUpdateNetwork(manifestOK, networkOK *checkpoint) func(co
1186
1186
network := a .network
1187
1187
a .closeMutex .Unlock ()
1188
1188
if network == nil {
1189
- keySeed ,err := WorkspaceKeySeed (manifest .WorkspaceID ,manifest .AgentName )
1189
+ keySeed ,err := SSHKeySeed (manifest .OwnerName , manifest . WorkspaceName ,manifest .AgentName )
1190
1190
if err != nil {
1191
- return xerrors .Errorf ("generateseed from workspace id : %w" ,err )
1191
+ return xerrors .Errorf ("generateSSH key seed : %w" ,err )
1192
1192
}
1193
1193
// use the graceful context here, because creating the tailnet is not itself tied to the
1194
1194
// agent API.
@@ -1518,14 +1518,11 @@ func (a *agent) runCoordinator(ctx context.Context, tClient tailnetproto.DRPCTai
1518
1518
a .logger .Info (ctx ,"connected to coordination RPC" )
1519
1519
1520
1520
// This allows the Close() routine to wait for the coordinator to gracefully disconnect.
1521
- a . closeMutex . Lock ()
1522
- if a . isClosed () {
1523
- return nil
1521
+ disconnected := a . setCoordDisconnected ()
1522
+ if disconnected == nil {
1523
+ return nil // already closed by something else
1524
1524
}
1525
- disconnected := make (chan struct {})
1526
- a .coordDisconnected = disconnected
1527
1525
defer close (disconnected )
1528
- a .closeMutex .Unlock ()
1529
1526
1530
1527
ctrl := tailnet .NewAgentCoordinationController (a .logger ,network )
1531
1528
coordination := ctrl .New (coordinate )
@@ -1547,6 +1544,17 @@ func (a *agent) runCoordinator(ctx context.Context, tClient tailnetproto.DRPCTai
1547
1544
return <- errCh
1548
1545
}
1549
1546
1547
+ func (a * agent )setCoordDisconnected ()chan struct {} {
1548
+ a .closeMutex .Lock ()
1549
+ defer a .closeMutex .Unlock ()
1550
+ if a .isClosed () {
1551
+ return nil
1552
+ }
1553
+ disconnected := make (chan struct {})
1554
+ a .coordDisconnected = disconnected
1555
+ return disconnected
1556
+ }
1557
+
1550
1558
// runDERPMapSubscriber runs a coordinator and returns if a reconnect should occur.
1551
1559
func (a * agent )runDERPMapSubscriber (ctx context.Context ,tClient tailnetproto.DRPCTailnetClient24 ,network * tailnet.Conn )error {
1552
1560
defer a .logger .Debug (ctx ,"disconnected from derp map RPC" )
@@ -2068,12 +2076,31 @@ func PrometheusMetricsHandler(prometheusRegistry *prometheus.Registry, logger sl
2068
2076
})
2069
2077
}
2070
2078
2071
- //WorkspaceKeySeed convertsa WorkspaceID UUID andagent name to an int64 hash.
2079
+ //SSHKeySeed convertsan owner userName, workspaceName andagentName to an int64 hash.
2072
2080
// This uses the FNV-1a hash algorithm which provides decent distribution and collision
2073
2081
// resistance for string inputs.
2074
- func WorkspaceKeySeed (workspaceID uuid.UUID ,agentName string ) (int64 ,error ) {
2082
+ //
2083
+ // Why owner username, workspace name, and agent name? These are the components that are used in hostnames for the
2084
+ // workspace over SSH, and so we want the workspace to have a stable key with respect to these. We don't use the
2085
+ // respective UUIDs. The workspace UUID would be different if you delete and recreate a workspace with the same name.
2086
+ // The agent UUID is regenerated on each build. Since Coder's Tailnet networking is handling the authentication, we
2087
+ // should not be showing users warnings about host SSH keys.
2088
+ func SSHKeySeed (userName ,workspaceName ,agentName string ) (int64 ,error ) {
2075
2089
h := fnv .New64a ()
2076
- _ ,err := h .Write (workspaceID [:])
2090
+ _ ,err := h .Write ([]byte (userName ))
2091
+ if err != nil {
2092
+ return 42 ,err
2093
+ }
2094
+ // null separators between strings so that (dog, foodstuff) is distinct from (dogfood, stuff)
2095
+ _ ,err = h .Write ([]byte {0 })
2096
+ if err != nil {
2097
+ return 42 ,err
2098
+ }
2099
+ _ ,err = h .Write ([]byte (workspaceName ))
2100
+ if err != nil {
2101
+ return 42 ,err
2102
+ }
2103
+ _ ,err = h .Write ([]byte {0 })
2077
2104
if err != nil {
2078
2105
return 42 ,err
2079
2106
}