|
|
|
|
@ -8,10 +8,20 @@ |
|
|
|
|
package ipnlocal |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"crypto/ecdsa" |
|
|
|
|
"crypto/ed25519" |
|
|
|
|
"crypto/elliptic" |
|
|
|
|
"crypto/rand" |
|
|
|
|
"crypto/rsa" |
|
|
|
|
"crypto/x509" |
|
|
|
|
"encoding/pem" |
|
|
|
|
"errors" |
|
|
|
|
"fmt" |
|
|
|
|
"io/ioutil" |
|
|
|
|
"os" |
|
|
|
|
"path/filepath" |
|
|
|
|
"strings" |
|
|
|
|
"sync" |
|
|
|
|
|
|
|
|
|
"golang.org/x/crypto/ssh" |
|
|
|
|
"tailscale.com/envknob" |
|
|
|
|
@ -19,17 +29,94 @@ import ( |
|
|
|
|
|
|
|
|
|
var useHostKeys = envknob.Bool("TS_USE_SYSTEM_SSH_HOST_KEYS") |
|
|
|
|
|
|
|
|
|
func (b *LocalBackend) GetSSH_HostKeys() ([]ssh.Signer, error) { |
|
|
|
|
// TODO(bradfitz): generate host keys, at least as needed if
|
|
|
|
|
// an existing SSH server didn't put them on disk. But also
|
|
|
|
|
// because people may want tailscale-specific ones. For now be
|
|
|
|
|
// lazy and reuse the host ones.
|
|
|
|
|
return b.getSystemSSH_HostKeys() |
|
|
|
|
// 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) { |
|
|
|
|
if os.Geteuid() == 0 { |
|
|
|
|
keys, err = b.getSystemSSH_HostKeys() |
|
|
|
|
if err != nil || len(keys) > 0 { |
|
|
|
|
return keys, err |
|
|
|
|
} |
|
|
|
|
// Otherwise, perhaps they don't have OpenSSH etc installed.
|
|
|
|
|
// Generate our own keys...
|
|
|
|
|
} |
|
|
|
|
return b.getTailscaleSSH_HostKeys() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (b *LocalBackend) getTailscaleSSH_HostKeys() (keys []ssh.Signer, err error) { |
|
|
|
|
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 |
|
|
|
|
} |
|
|
|
|
for _, typ := range keyTypes { |
|
|
|
|
hostKey, err := b.hostKeyFileOrCreate(keyDir, typ) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
signer, err := ssh.ParsePrivateKey(hostKey) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, 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 interface{} |
|
|
|
|
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 []ssh.Signer, err error) { |
|
|
|
|
// TODO(bradfitz): cache this?
|
|
|
|
|
for _, typ := range []string{"rsa", "ecdsa", "ed25519"} { |
|
|
|
|
for _, typ := range keyTypes { |
|
|
|
|
hostKey, err := ioutil.ReadFile("/etc/ssh/ssh_host_" + typ + "_key") |
|
|
|
|
if os.IsNotExist(err) { |
|
|
|
|
continue |
|
|
|
|
@ -43,9 +130,6 @@ func (b *LocalBackend) getSystemSSH_HostKeys() (ret []ssh.Signer, err error) { |
|
|
|
|
} |
|
|
|
|
ret = append(ret, signer) |
|
|
|
|
} |
|
|
|
|
if len(ret) == 0 { |
|
|
|
|
return nil, errors.New("no system SSH host keys found") |
|
|
|
|
} |
|
|
|
|
return ret, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|