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 <tk@coder.com>
1 parent dedc32f commit 38f9dfc

File tree

2 files changed

+59
-8
lines changed

2 files changed

+59
-8
lines changed

agent/agent.go

Lines changed: 22 additions & 1 deletion
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

Lines changed: 37 additions & 7 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy