Skip to content

Commit 24ad786

Browse files
committed
Remove host key env vars
1 parent b2217a7 commit 24ad786

3 files changed

Lines changed: 47 additions & 33 deletions

File tree

README.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,20 @@ Connection closed by UNKNOWN port 65535
3131

3232
sshgate doesn't care about the username used for the jump hop. It doesn't care about the usernames in subsequent hops either; that information is opaque, it simply forwards the traffic to the client.
3333

34-
Since we didn't pass any SSH host keys to sshgate earlier, it generated an ephemeral ED25519 host key on startup. For the server to have a persistent identity, generate an SSH keypair and set `SSHGATE_HOST_KEY_PATH_*` to the path of the private key.
34+
Since we didn't pass any SSH host keys to sshgate earlier, it generated an ephemeral ED25519 host key on startup. For the server to have a persistent identity, generate an SSH keypair and set `host_key_paths` in the config file accordingly. You may set an ED25519, ECDSA, and RSA host key.
3535

3636
For example, to set the server's ED25519 identity:
3737

3838
```
3939
ssh-keygen -t ed25519 -f sshgate
40-
export SSHGATE_HOST_KEY_PATH_ED25519=./sshgate
4140
```
4241

43-
You can also set an RSA and ECDSA identity if you wish.
42+
Add to the config:
4443

45-
- `SSHGATE_HOST_KEY_PATH_RSA`
46-
- `SSHGATE_HOST_KEY_PATH_ED25519`
47-
- `SSHGATE_HOST_KEY_PATH_ECDSA`
44+
```json
45+
{
46+
"host_key_paths": {
47+
"ed25519": "./sshgate"
48+
}
49+
}
50+
```

pkg/sshgate/config.go

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package sshgate
22

33
import (
4-
"crypto/ed25519"
5-
"crypto/rand"
64
"encoding/json"
75
"fmt"
86
"os"
@@ -20,8 +18,15 @@ type RawRule struct {
2018
Ports []int `json:"ports,omitempty"`
2119
}
2220

21+
type HostKeyPaths struct {
22+
ECDSA string `json:"ecdsa,omitempty"`
23+
ED25519 string `json:"ed25519,omitempty"`
24+
RSA string `json:"rsa,omitempty"`
25+
}
26+
2327
type Config struct {
24-
Identities []Identity `json:"identities,omitempty"`
28+
Identities []Identity `json:"identities,omitempty"`
29+
HostKeyPaths HostKeyPaths `json:"host_key_paths"`
2530

2631
signers []ssh.Signer
2732
identityRulesets map[string][]*Ruleset
@@ -63,7 +68,7 @@ func ReadConfig(path string) (*Config, error) {
6368
return nil, err
6469
}
6570

66-
config.signers, err = parseHostKeys()
71+
config.signers, err = parseHostKeys(config.HostKeyPaths)
6772
if err != nil {
6873
return nil, fmt.Errorf("invalid host keys: %w", err)
6974
}
@@ -73,27 +78,31 @@ func ReadConfig(path string) (*Config, error) {
7378
return &config, nil
7479
}
7580

76-
func parseHostKeys() ([]ssh.Signer, error) {
81+
func parseHostKeys(hostKeyPaths HostKeyPaths) ([]ssh.Signer, error) {
7782
var signers []ssh.Signer
7883

79-
for _, envVar := range []string{
80-
"SSHGATE_HOST_KEY_PATH_RSA",
81-
"SSHGATE_HOST_KEY_PATH_ED25519",
82-
"SSHGATE_HOST_KEY_PATH_ECDSA",
84+
for keyType, keyPath := range map[string]string{
85+
"ssh-ecdsa": hostKeyPaths.ECDSA,
86+
"ssh-ed25519": hostKeyPaths.ED25519,
87+
"ssh-rsa": hostKeyPaths.RSA,
8388
} {
84-
keyPath := os.Getenv(envVar)
8589
if keyPath == "" {
8690
continue
8791
}
8892

8993
key, err := os.ReadFile(keyPath)
9094
if err != nil {
91-
return nil, fmt.Errorf("failed to read host key %s: %w", envVar, err)
95+
return nil, fmt.Errorf("failed to read host key %s: %w", keyPath, err)
9296
}
9397

9498
signer, err := ssh.ParsePrivateKey([]byte(key))
9599
if err != nil {
96-
return nil, fmt.Errorf("failed to parse host key %s: %w", envVar, err)
100+
return nil, fmt.Errorf("failed to parse host key %s: %w", keyPath, err)
101+
}
102+
103+
// Don't allow key of unexpected type to be smuggled in
104+
if signer.PublicKey().Type() != keyType {
105+
return nil, fmt.Errorf("expected key of type %s but got %s", keyType, signer.PublicKey().Type())
97106
}
98107

99108
signers = append(signers, signer)
@@ -102,20 +111,6 @@ func parseHostKeys() ([]ssh.Signer, error) {
102111
return signers, nil
103112
}
104113

105-
func generateSigner() (ssh.Signer, error) {
106-
_, privKey, err := ed25519.GenerateKey(rand.Reader)
107-
if err != nil {
108-
return nil, fmt.Errorf("failed to generate Ed25519 key: %w", err)
109-
}
110-
111-
signer, err := ssh.NewSignerFromSigner(privKey)
112-
if err != nil {
113-
return nil, fmt.Errorf("failed to create signer: %w", err)
114-
}
115-
116-
return signer, nil
117-
}
118-
119114
func parseIdentities(identities []Identity) map[string][]*Ruleset {
120115
rulesByFingerprints := make(map[string][]*Ruleset)
121116

pkg/sshgate/sshgate.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package sshgate
33
import (
44
"bytes"
55
"context"
6+
"crypto/ed25519"
7+
"crypto/rand"
68
"encoding/binary"
79
"errors"
810
"fmt"
@@ -51,6 +53,20 @@ func readUint32(r *bytes.Reader) (uint32, error) {
5153
return v, nil
5254
}
5355

56+
func generateSigner() (ssh.Signer, error) {
57+
_, privKey, err := ed25519.GenerateKey(rand.Reader)
58+
if err != nil {
59+
return nil, fmt.Errorf("failed to generate Ed25519 key: %w", err)
60+
}
61+
62+
signer, err := ssh.NewSignerFromSigner(privKey)
63+
if err != nil {
64+
return nil, fmt.Errorf("failed to create signer: %w", err)
65+
}
66+
67+
return signer, nil
68+
}
69+
5470
type directTCPIPExtraData struct {
5571
DestHost string
5672
DestPort int
@@ -107,7 +123,7 @@ func (s *Server) ListenAndServe(ctx context.Context) error {
107123
PublicKeyCallback: s.pubkeyCallback,
108124
}
109125

110-
var signers []ssh.Signer
126+
signers := make([]ssh.Signer, len(s.config.signers))
111127
if copy(signers, s.config.signers) == 0 {
112128
slog.Warn("no host keys provided in config, generating ephemeral ed25519 host key")
113129

0 commit comments

Comments
 (0)