Skip to content

Commit f9f7f5a

Browse files
feat: add configurable SSH host key algorithm support
Adds support for configurable SSH host key algorithms to improve compatibility with modern SSH clients like Visual Studio 2022. Changes: - Add HostKeyAlgorithm field to agentssh.Config - Implement Ed25519 key generation in CoderSigner function - Add CLI flag --ssh-host-key-algorithm with env var support - Support both 'rsa' (default) and 'ed25519' algorithms - Maintain backward compatibility with RSA as default Resolves compatibility issues with SSH clients that no longer support RSA host keys. Co-authored-by: kylecarbs <7122116+kylecarbs@users.noreply.github.com>
1 parent de4a270 commit f9f7f5a

File tree

3 files changed

+45
-8
lines changed

3 files changed

+45
-8
lines changed

agent/agent.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ type Options struct {
8282
IgnorePorts map[int]string
8383
PortCacheDuration time.Duration
8484
SSHMaxTimeout time.Duration
85+
SSHHostKeyAlgorithm string
8586
TailnetListenPort uint16
8687
Subsystems []codersdk.AgentSubsystem
8788
PrometheusRegistry *prometheus.Registry
@@ -186,6 +187,7 @@ func New(options Options) Agent {
186187
reportMetadataInterval: options.ReportMetadataInterval,
187188
announcementBannersRefreshInterval: options.ServiceBannerRefreshInterval,
188189
sshMaxTimeout: options.SSHMaxTimeout,
190+
sshHostKeyAlgorithm: options.SSHHostKeyAlgorithm,
189191
subsystems: options.Subsystems,
190192
logSender: agentsdk.NewLogSender(options.Logger),
191193
blockFileTransfer: options.BlockFileTransfer,
@@ -257,6 +259,7 @@ type agent struct {
257259
sessionToken atomic.Pointer[string]
258260
sshServer *agentssh.Server
259261
sshMaxTimeout time.Duration
262+
sshHostKeyAlgorithm string
260263
blockFileTransfer bool
261264

262265
lifecycleUpdate chan struct{}
@@ -292,6 +295,7 @@ func (a *agent) init() {
292295
// pass the "hard" context because we explicitly close the SSH server as part of graceful shutdown.
293296
sshSrv, err := agentssh.NewServer(a.hardCtx, a.logger.Named("ssh-server"), a.prometheusRegistry, a.filesystem, a.execer, &agentssh.Config{
294297
MaxTimeout: a.sshMaxTimeout,
298+
HostKeyAlgorithm: a.sshHostKeyAlgorithm,
295299
MOTDFile: func() string { return a.manifest.Load().MOTDFile },
296300
AnnouncementBanners: func() *[]codersdk.BannerConfig { return a.announcementBanners.Load() },
297301
UpdateEnv: a.updateCommandEnv,

agent/agentssh/agentssh.go

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package agentssh
33
import (
44
"bufio"
55
"context"
6+
"crypto/ed25519"
67
"errors"
78
"fmt"
89
"io"
10+
"math/rand"
911
"net"
1012
"os"
1113
"os/exec"
@@ -113,6 +115,9 @@ type Config struct {
113115
BlockFileTransfer bool
114116
// ReportConnection.
115117
ReportConnection reportConnectionFunc
118+
// HostKeyAlgorithm specifies the SSH host key algorithm to use.
119+
// Valid values: "rsa", "ed25519". Default: "rsa" for backward compatibility.
120+
HostKeyAlgorithm string
116121
// Experimental: allow connecting to running containers via Docker exec.
117122
// Note that this is different from the devcontainers feature, which uses
118123
// subagents.
@@ -1312,7 +1317,11 @@ func userHomeDir() (string, error) {
13121317
// UpdateHostSigner updates the host signer with a new key generated from the provided seed.
13131318
// If an existing host key exists with the same algorithm, it is overwritten
13141319
func (s *Server) UpdateHostSigner(seed int64) error {
1315-
key, err := CoderSigner(seed)
1320+
algorithm := s.config.HostKeyAlgorithm
1321+
if algorithm == "" {
1322+
algorithm = "rsa" // Default to RSA for backward compatibility
1323+
}
1324+
key, err := CoderSigner(seed, algorithm)
13161325
if err != nil {
13171326
return err
13181327
}
@@ -1325,14 +1334,29 @@ func (s *Server) UpdateHostSigner(seed int64) error {
13251334
return nil
13261335
}
13271336

1328-
// CoderSigner generates a deterministic SSH signer based on the provided seed.
1329-
// It uses RSA with a key size of 2048 bits.
1330-
func CoderSigner(seed int64) (gossh.Signer, error) {
1337+
// CoderSigner generates a deterministic SSH signer based on the provided seed and algorithm.
1338+
// Supported algorithms: "rsa" (2048 bits), "ed25519".
1339+
func CoderSigner(seed int64, algorithm string) (gossh.Signer, error) {
13311340
// Clients should ignore the host key when connecting.
13321341
// The agent needs to authenticate with coderd to SSH,
13331342
// so SSH authentication doesn't improve security.
1334-
coderHostKey := agentrsa.GenerateDeterministicKey(seed)
1335-
1336-
coderSigner, err := gossh.NewSignerFromKey(coderHostKey)
1337-
return coderSigner, err
1343+
1344+
switch algorithm {
1345+
case "ed25519":
1346+
// Generate deterministic Ed25519 key
1347+
rand := rand.New(rand.NewSource(seed))
1348+
_, privateKey, err := ed25519.GenerateKey(rand)
1349+
if err != nil {
1350+
return nil, err
1351+
}
1352+
return gossh.NewSignerFromKey(privateKey)
1353+
1354+
case "rsa", "":
1355+
// Default to RSA for backward compatibility
1356+
coderHostKey := agentrsa.GenerateDeterministicKey(seed)
1357+
return gossh.NewSignerFromKey(coderHostKey)
1358+
1359+
default:
1360+
return nil, fmt.Errorf("unsupported host key algorithm: %s", algorithm)
1361+
}
13381362
}

cli/agent.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ func (r *RootCmd) workspaceAgent() *serpent.Command {
4646
pprofAddress string
4747
noReap bool
4848
sshMaxTimeout time.Duration
49+
sshHostKeyAlgorithm string
4950
tailnetListenPort int64
5051
prometheusAddress string
5152
debugAddress string
@@ -356,6 +357,7 @@ func (r *RootCmd) workspaceAgent() *serpent.Command {
356357
EnvironmentVariables: environmentVariables,
357358
IgnorePorts: ignorePorts,
358359
SSHMaxTimeout: sshMaxTimeout,
360+
SSHHostKeyAlgorithm: sshHostKeyAlgorithm,
359361
Subsystems: subsystems,
360362

361363
PrometheusRegistry: prometheusRegistry,
@@ -451,6 +453,13 @@ func (r *RootCmd) workspaceAgent() *serpent.Command {
451453
Description: "Specify the max timeout for a SSH connection, it is advisable to set it to a minimum of 60s, but no more than 72h.",
452454
Value: serpent.DurationOf(&sshMaxTimeout),
453455
},
456+
{
457+
Flag: "ssh-host-key-algorithm",
458+
Default: "rsa",
459+
Env: "CODER_AGENT_SSH_HOST_KEY_ALGORITHM",
460+
Description: "Specify the SSH host key algorithm to use. Valid values: rsa, ed25519.",
461+
Value: serpent.StringOf(&sshHostKeyAlgorithm),
462+
},
454463
{
455464
Flag: "tailnet-listen-port",
456465
Default: "0",

0 commit comments

Comments
 (0)