|
|
@@ -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
|
|
|
}
|
|
|
|