forked from tailscale/tailscale
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ssh.go
147 lines (134 loc) · 4 KB
/
ssh.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux || (darwin && !ios)
// +build linux darwin,!ios
package ipnlocal
import (
"bytes"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"github.com/tailscale/golang-x-crypto/ssh"
"tailscale.com/envknob"
"tailscale.com/util/mak"
)
var useHostKeys = envknob.Bool("TS_USE_SYSTEM_SSH_HOST_KEYS")
// keyTypes are the SSH key types that we either try to read from the
// system's OpenSSH keys or try to generate for ourselves when not
// running as root.
var keyTypes = []string{"rsa", "ecdsa", "ed25519"}
func (b *LocalBackend) GetSSH_HostKeys() (keys []ssh.Signer, err error) {
var existing map[string]ssh.Signer
if os.Geteuid() == 0 {
existing = b.getSystemSSH_HostKeys()
}
return b.getTailscaleSSH_HostKeys(existing)
}
// getTailscaleSSH_HostKeys returns the three (rsa, ecdsa, ed25519) SSH host
// keys, reusing the provided ones in existing if present in the map.
func (b *LocalBackend) getTailscaleSSH_HostKeys(existing map[string]ssh.Signer) (keys []ssh.Signer, err error) {
var keyDir string // lazily initialized $TAILSCALE_VAR/ssh dir.
for _, typ := range keyTypes {
if s, ok := existing[typ]; ok {
keys = append(keys, s)
continue
}
if keyDir == "" {
root := b.TailscaleVarRoot()
if root == "" {
return nil, errors.New("no var root for ssh keys")
}
keyDir = filepath.Join(root, "ssh")
if err := os.MkdirAll(keyDir, 0700); err != nil {
return nil, err
}
}
hostKey, err := b.hostKeyFileOrCreate(keyDir, typ)
if err != nil {
return nil, fmt.Errorf("error creating SSH host key type %q in %q: %w", typ, keyDir, err)
}
signer, err := ssh.ParsePrivateKey(hostKey)
if err != nil {
return nil, fmt.Errorf("error parsing SSH host key type %q from %q: %w", typ, keyDir, err)
}
keys = append(keys, signer)
}
return keys, nil
}
var keyGenMu sync.Mutex
func (b *LocalBackend) hostKeyFileOrCreate(keyDir, typ string) ([]byte, error) {
keyGenMu.Lock()
defer keyGenMu.Unlock()
path := filepath.Join(keyDir, "ssh_host_"+typ+"_key")
v, err := ioutil.ReadFile(path)
if err == nil {
return v, nil
}
if !os.IsNotExist(err) {
return nil, err
}
var priv any
switch typ {
default:
return nil, fmt.Errorf("unsupported key type %q", typ)
case "ed25519":
_, priv, err = ed25519.GenerateKey(rand.Reader)
case "ecdsa":
// curve is arbitrary. We pick whatever will at
// least pacify clients as the actual encryption
// doesn't matter: it's all over WireGuard anyway.
curve := elliptic.P256()
priv, err = ecdsa.GenerateKey(curve, rand.Reader)
case "rsa":
// keySize is arbitrary. We pick whatever will at
// least pacify clients as the actual encryption
// doesn't matter: it's all over WireGuard anyway.
const keySize = 2048
priv, err = rsa.GenerateKey(rand.Reader, keySize)
}
if err != nil {
return nil, err
}
mk, err := x509.MarshalPKCS8PrivateKey(priv)
if err != nil {
return nil, err
}
pemGen := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: mk})
err = os.WriteFile(path, pemGen, 0700)
return pemGen, err
}
func (b *LocalBackend) getSystemSSH_HostKeys() (ret map[string]ssh.Signer) {
for _, typ := range keyTypes {
filename := "/etc/ssh/ssh_host_" + typ + "_key"
hostKey, err := ioutil.ReadFile(filename)
if err != nil || len(bytes.TrimSpace(hostKey)) == 0 {
continue
}
signer, err := ssh.ParsePrivateKey(hostKey)
if err != nil {
b.logf("warning: error reading host key %s: %v (generating one instead)", filename, err)
continue
}
mak.Set(&ret, typ, signer)
}
return ret
}
func (b *LocalBackend) getSSHHostKeyPublicStrings() (ret []string) {
signers, _ := b.GetSSH_HostKeys()
for _, signer := range signers {
ret = append(ret, strings.TrimSpace(string(ssh.MarshalAuthorizedKey(signer.PublicKey()))))
}
return ret
}