blob: 7a8d08ab7f8b3e3e3e6eb8f8b4f27e526159e642 [file] [log] [blame]
Tim Windelschmidtb6308cd2023-10-10 21:19:03 +02001package manager
2
3import (
4 "crypto/ed25519"
5 "crypto/rand"
6 "flag"
7 "fmt"
8 "os"
9 "sync"
10
11 "golang.org/x/crypto/ssh"
12 "k8s.io/klog/v2"
13)
14
15type SSHKey struct {
16 // myKey guards Key.
17 muKey sync.Mutex
18
19 // SSH key to use when creating machines and then connecting to them. If not
20 // provided, it will be automatically loaded from KeyPersistPath, and if that
21 // doesn't exist either, it will be first generated and persisted there.
22 Key ed25519.PrivateKey
23
24 // Path at which the SSH key will be loaded from and persisted to, if Key is not
25 // explicitly set. Either KeyPersistPath or Key must be set.
26 KeyPersistPath string
27}
28
29func (c *SSHKey) RegisterFlags() {
30 flag.StringVar(&c.KeyPersistPath, "ssh_key_path", "", "Local filesystem path to read SSH key from, and save generated key to")
31}
32
33// sshKey returns the SSH key as defined by the Key and KeyPersistPath options,
34// loading/generating/persisting it as necessary.
35func (c *SSHKey) sshKey() (ed25519.PrivateKey, error) {
36 c.muKey.Lock()
37 defer c.muKey.Unlock()
38
39 if c.Key != nil {
40 return c.Key, nil
41 }
42 if c.KeyPersistPath == "" {
43 return nil, fmt.Errorf("-ssh_key_path must be set")
44 }
45
46 data, err := os.ReadFile(c.KeyPersistPath)
47 switch {
48 case err == nil:
49 if len(data) != ed25519.PrivateKeySize {
50 return nil, fmt.Errorf("%s is not a valid ed25519 private key", c.KeyPersistPath)
51 }
52 c.Key = data
53 klog.Infof("Loaded SSH key from %s", c.KeyPersistPath)
54 return c.Key, nil
55 case os.IsNotExist(err):
56 if err := c.sshGenerateUnlocked(); err != nil {
57 return nil, err
58 }
59 if err := os.WriteFile(c.KeyPersistPath, c.Key, 0400); err != nil {
60 return nil, fmt.Errorf("could not persist key: %w", err)
61 }
62 return c.Key, nil
63 default:
64 return nil, fmt.Errorf("could not load peristed key: %w", err)
65 }
66}
67
68// PublicKey returns the SSH public key marshaled for use, based on sshKey.
69func (c *SSHKey) PublicKey() (string, error) {
70 private, err := c.sshKey()
71 if err != nil {
72 return "", err
73 }
74 // Marshal the public key part in OpenSSH authorized_keys.
75 sshpub, err := ssh.NewPublicKey(private.Public())
76 if err != nil {
77 return "", fmt.Errorf("while building SSH public key: %w", err)
78 }
79 return string(ssh.MarshalAuthorizedKey(sshpub)), nil
80}
81
82// Signer builds an ssh.Signer (for use in SSH connections) based on sshKey.
83func (c *SSHKey) Signer() (ssh.Signer, error) {
84 private, err := c.sshKey()
85 if err != nil {
86 return nil, err
87 }
88 // Set up the internal ssh.Signer to be later used to initiate SSH
89 // connections with newly provided hosts.
90 signer, err := ssh.NewSignerFromKey(private)
91 if err != nil {
92 return nil, fmt.Errorf("while building SSH signer: %w", err)
93 }
94 return signer, nil
95}
96
97// sshGenerateUnlocked saves a new private key into SharedConfig.Key.
98func (c *SSHKey) sshGenerateUnlocked() error {
99 if c.Key != nil {
100 return nil
101 }
102 _, priv, err := ed25519.GenerateKey(rand.Reader)
103 if err != nil {
104 return fmt.Errorf("while generating SSH key: %w", err)
105 }
106 c.Key = priv
107 return nil
108}