@@ -907,7 +907,7 @@ func (a *agent) run() (retErr error) {
907
907
defer func () {
908
908
cErr := aAPI .DRPCConn ().Close ()
909
909
if cErr != nil {
910
- a .logger .Debug (a .hardCtx , "error closing drpc connection" , slog .Error (err ))
910
+ a .logger .Debug (a .hardCtx , "error closing drpc connection" , slog .Error (cErr ))
911
911
}
912
912
}()
913
913
@@ -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 ("generate seed from workspace id : %w" , err )
1191
+ return xerrors .Errorf ("generate SSH 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.
@@ -1408,7 +1408,7 @@ func (a *agent) createTailnet(
1408
1408
if rPTYServeErr != nil &&
1409
1409
a .gracefulCtx .Err () == nil &&
1410
1410
! strings .Contains (rPTYServeErr .Error (), "use of closed network connection" ) {
1411
- a .logger .Error (ctx , "error serving reconnecting PTY" , slog .Error (err ))
1411
+ a .logger .Error (ctx , "error serving reconnecting PTY" , slog .Error (rPTYServeErr ))
1412
1412
}
1413
1413
}); err != nil {
1414
1414
return nil , err
@@ -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 converts a WorkspaceID UUID and agent name to an int64 hash.
2079
+ // SSHKeySeed converts an owner userName, workspaceName and agentName 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
}
0 commit comments