Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 38f9dfc

Browse files
committed
fix(agent/agentssh): pin random seed for RSA key generation
Change-Id: I8c7e3070324e5d558374fd6891eea9d48660e1e9 Signed-off-by: Thomas Kosiewski <[email protected]>
1 parent dedc32f commit 38f9dfc

File tree

2 files changed

+59
-8
lines changed

2 files changed

+59
-8
lines changed

agent/agent.go

+22-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"errors"
88
"fmt"
9+
"hash/fnv"
910
"io"
1011
"net/http"
1112
"net/netip"
@@ -372,7 +373,6 @@ func (a *agent) collectMetadata(ctx context.Context, md codersdk.WorkspaceAgentM
372373
// Important: if the command times out, we may see a misleading error like
373374
// "exit status 1", so it's important to include the context error.
374375
err = errors.Join(err, ctx.Err())
375-
376376
if err != nil {
377377
result.Error = fmt.Sprintf("run cmd: %+v", err)
378378
}
@@ -906,6 +906,14 @@ func (a *agent) handleManifest(manifestOK *checkpoint) func(ctx context.Context,
906906
}
907907
a.client.RewriteDERPMap(manifest.DERPMap)
908908

909+
seed, err := stringToInt64Hash(manifest.WorkspaceID)
910+
if err != nil {
911+
return xerrors.Errorf("generate hash from workspace id: %w", err)
912+
}
913+
if err := a.sshServer.UpdateHostSigner(seed); err != nil {
914+
return xerrors.Errorf("update host signer: %w", err)
915+
}
916+
909917
// Expand the directory and send it back to coderd so external
910918
// applications that rely on the directory can use it.
911919
//
@@ -1850,3 +1858,16 @@ func PrometheusMetricsHandler(prometheusRegistry *prometheus.Registry, logger sl
18501858
}
18511859
})
18521860
}
1861+
1862+
// stringToInt64Hash converts a WorkspaceID UUID to an int64 hash.
1863+
// This uses the FNV-1a hash algorithm which provides decent distribution and collision
1864+
// resistance for string inputs.
1865+
func stringToInt64Hash(workspaceID uuid.UUID) (int64, error) {
1866+
h := fnv.New64a()
1867+
_, err := h.Write(workspaceID[:])
1868+
if err != nil {
1869+
return 42, err
1870+
}
1871+
1872+
return int64(h.Sum64()), nil
1873+
}

agent/agentssh/agentssh.go

+37-7
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ package agentssh
33
import (
44
"bufio"
55
"context"
6-
"crypto/rand"
76
"crypto/rsa"
87
"errors"
98
"fmt"
109
"io"
10+
"math/rand"
1111
"net"
1212
"os"
1313
"os/exec"
@@ -131,14 +131,15 @@ func NewServer(ctx context.Context, logger slog.Logger, prometheusRegistry *prom
131131
// Clients' should ignore the host key when connecting.
132132
// The agent needs to authenticate with coderd to SSH,
133133
// so SSH authentication doesn't improve security.
134-
randomHostKey, err := rsa.GenerateKey(rand.Reader, 2048)
135-
if err != nil {
136-
return nil, err
137-
}
138-
randomSigner, err := gossh.NewSignerFromKey(randomHostKey)
134+
135+
// Create a fixed host key, as at least one host key has to
136+
// exist for the SSH server to start. We'll later replace
137+
// the host key with one that's based off the workspace uuid.
138+
coderSigner, err := coderSigner(42)
139139
if err != nil {
140140
return nil, err
141141
}
142+
142143
if config == nil {
143144
config = &Config{}
144145
}
@@ -206,7 +207,7 @@ func NewServer(ctx context.Context, logger slog.Logger, prometheusRegistry *prom
206207
slog.Error(err))
207208
},
208209
Handler: s.sessionHandler,
209-
HostSigners: []ssh.Signer{randomSigner},
210+
HostSigners: []ssh.Signer{coderSigner},
210211
LocalPortForwardingCallback: func(ctx ssh.Context, destinationHost string, destinationPort uint32) bool {
211212
// Allow local port forwarding all!
212213
s.logger.Debug(ctx, "local port forward",
@@ -1099,3 +1100,32 @@ func userHomeDir() (string, error) {
10991100
}
11001101
return u.HomeDir, nil
11011102
}
1103+
1104+
// UpdateHostSigner updates the host signer with a new key generated from the provided seed.
1105+
// This function replaces the previously used host key with the newly generated one.
1106+
func (s *Server) UpdateHostSigner(seed int64) error {
1107+
key, err := coderSigner(seed)
1108+
if err != nil {
1109+
return err
1110+
}
1111+
1112+
s.mu.Lock()
1113+
defer s.mu.Unlock()
1114+
1115+
s.srv.AddHostKey(key)
1116+
1117+
return nil
1118+
}
1119+
1120+
// coderSigner generates a deterministic SSH signer based on the provided seed.
1121+
// It uses RSA with a key size of 2048 bits.
1122+
func coderSigner(seed int64) (gossh.Signer, error) {
1123+
// nolint: gosec
1124+
deterministicRand := rand.New(rand.NewSource(seed))
1125+
coderHostKey, err := rsa.GenerateKey(deterministicRand, 2048)
1126+
if err != nil {
1127+
return nil, err
1128+
}
1129+
coderSigner, err := gossh.NewSignerFromKey(coderHostKey)
1130+
return coderSigner, err
1131+
}

0 commit comments

Comments
 (0)