@@ -3,11 +3,11 @@ package agentssh
3
3
import (
4
4
"bufio"
5
5
"context"
6
- "crypto/rand"
7
6
"crypto/rsa"
8
7
"errors"
9
8
"fmt"
10
9
"io"
10
+ "math/rand"
11
11
"net"
12
12
"os"
13
13
"os/exec"
@@ -128,17 +128,6 @@ type Server struct {
128
128
}
129
129
130
130
func NewServer (ctx context.Context , logger slog.Logger , prometheusRegistry * prometheus.Registry , fs afero.Fs , execer agentexec.Execer , config * Config ) (* Server , error ) {
131
- // Clients' should ignore the host key when connecting.
132
- // The agent needs to authenticate with coderd to SSH,
133
- // 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 )
139
- if err != nil {
140
- return nil , err
141
- }
142
131
if config == nil {
143
132
config = & Config {}
144
133
}
@@ -205,8 +194,10 @@ func NewServer(ctx context.Context, logger slog.Logger, prometheusRegistry *prom
205
194
slog .F ("local_addr" , conn .LocalAddr ()),
206
195
slog .Error (err ))
207
196
},
208
- Handler : s .sessionHandler ,
209
- HostSigners : []ssh.Signer {randomSigner },
197
+ Handler : s .sessionHandler ,
198
+ // HostSigners are intentionally empty, as the host key will
199
+ // be set before we start listening.
200
+ HostSigners : []ssh.Signer {},
210
201
LocalPortForwardingCallback : func (ctx ssh.Context , destinationHost string , destinationPort uint32 ) bool {
211
202
// Allow local port forwarding all!
212
203
s .logger .Debug (ctx , "local port forward" ,
@@ -844,7 +835,13 @@ func (s *Server) CreateCommand(ctx context.Context, script string, env []string,
844
835
return cmd , nil
845
836
}
846
837
838
+ // Serve starts the server to handle incoming connections on the provided listener.
839
+ // It returns an error if no host keys are set or if there is an issue accepting connections.
847
840
func (s * Server ) Serve (l net.Listener ) (retErr error ) {
841
+ if len (s .srv .HostSigners ) == 0 {
842
+ return xerrors .New ("no host keys set" )
843
+ }
844
+
848
845
s .logger .Info (context .Background (), "started serving listener" , slog .F ("listen_addr" , l .Addr ()))
849
846
defer func () {
850
847
s .logger .Info (context .Background (), "stopped serving listener" ,
@@ -1099,3 +1096,36 @@ func userHomeDir() (string, error) {
1099
1096
}
1100
1097
return u .HomeDir , nil
1101
1098
}
1099
+
1100
+ // UpdateHostSigner updates the host signer with a new key generated from the provided seed.
1101
+ // If an existing host key exists with the same algorithm, it is overwritten
1102
+ func (s * Server ) UpdateHostSigner (seed int64 ) error {
1103
+ key , err := CoderSigner (seed )
1104
+ if err != nil {
1105
+ return err
1106
+ }
1107
+
1108
+ s .mu .Lock ()
1109
+ defer s .mu .Unlock ()
1110
+
1111
+ s .srv .AddHostKey (key )
1112
+
1113
+ return nil
1114
+ }
1115
+
1116
+ // CoderSigner generates a deterministic SSH signer based on the provided seed.
1117
+ // It uses RSA with a key size of 2048 bits.
1118
+ func CoderSigner (seed int64 ) (gossh.Signer , error ) {
1119
+ // Clients should ignore the host key when connecting.
1120
+ // The agent needs to authenticate with coderd to SSH,
1121
+ // so SSH authentication doesn't improve security.
1122
+
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