generate user key for new cluster and add it to peers on bootstrap machine

This commit is contained in:
Pavel Sviderski
2024-08-29 15:38:13 +10:00
parent afe9c13283
commit eed2c8809c
7 changed files with 104 additions and 22 deletions
+1
View File
@@ -61,6 +61,7 @@ func add(ctx context.Context, uncli *cli.CLI, host string, opts addOptions) erro
if err != nil { if err != nil {
return fmt.Errorf("create default cluster: %w", err) return fmt.Errorf("create default cluster: %w", err)
} }
fmt.Printf("Created %q cluster\n", cluster.Name)
} else { } else {
return fmt.Errorf("get current cluster: %w", err) return fmt.Errorf("get current cluster: %w", err)
} }
+32 -9
View File
@@ -9,6 +9,7 @@ import (
"uncloud/internal/cli/config" "uncloud/internal/cli/config"
"uncloud/internal/cmdexec" "uncloud/internal/cmdexec"
"uncloud/internal/machine" "uncloud/internal/machine"
"uncloud/internal/machine/network"
"uncloud/internal/secret" "uncloud/internal/secret"
) )
@@ -30,7 +31,7 @@ func (c *Cluster) toConfig() *config.Cluster {
} }
} }
func (cli *CLI) CreateCluster(name string, privateKey ed25519.PrivateKey) (*Cluster, error) { func (cli *CLI) CreateCluster(name string, privateKey ed25519.PrivateKey, userPrivateKey secret.Secret) (*Cluster, error) {
if _, ok := cli.config.Clusters[name]; ok { if _, ok := cli.config.Clusters[name]; ok {
return nil, fmt.Errorf("cluster %q already exists", name) return nil, fmt.Errorf("cluster %q already exists", name)
} }
@@ -41,13 +42,22 @@ func (cli *CLI) CreateCluster(name string, privateKey ed25519.PrivateKey) (*Clus
return nil, fmt.Errorf("generate cluster secret: %w", err) return nil, fmt.Errorf("generate cluster secret: %w", err)
} }
} }
if userPrivateKey == nil {
user, err := NewUser(nil)
if err != nil {
return nil, fmt.Errorf("generate user: %w", err)
}
userPrivateKey = user.PrivateKey()
}
c := &Cluster{ c := &Cluster{
Name: name, Name: name,
privateKey: privateKey, privateKey: privateKey,
config: cli.config, config: cli.config,
} }
cli.config.Clusters[name] = c.toConfig() cfg := c.toConfig()
cfg.UserKey = userPrivateKey
cli.config.Clusters[name] = cfg
if err := cli.config.Save(); err != nil { if err := cli.config.Save(); err != nil {
return nil, err return nil, err
} }
@@ -56,7 +66,7 @@ func (cli *CLI) CreateCluster(name string, privateKey ed25519.PrivateKey) (*Clus
} }
func (cli *CLI) CreateDefaultCluster() (*Cluster, error) { func (cli *CLI) CreateDefaultCluster() (*Cluster, error) {
c, err := cli.CreateCluster("default", nil) c, err := cli.CreateCluster("default", nil, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -116,7 +126,19 @@ func (c *Cluster) AddMachine(ctx context.Context, name, user, host string, port
_ = exec.Close() _ = exec.Close()
}() }()
mcfg, err := machine.NewBootstrapConfig(name, netip.Prefix{}) wgUserKey := c.config.Clusters[c.Name].UserKey
if wgUserKey == nil {
return "", errors.New("cluster user_key must be set in the config")
}
wgUser, err := NewUser(wgUserKey)
if err != nil {
return "", fmt.Errorf("create user from key: %w", err)
}
userPeerCfg := network.PeerConfig{
Subnet: netip.PrefixFrom(wgUser.Address(), 128),
PublicKey: wgUser.PublicKey(),
}
mcfg, err := machine.NewBootstrapConfig(name, netip.Prefix{}, userPeerCfg)
if err != nil { if err != nil {
return "", fmt.Errorf("generate machine bootstrap config: %w", err) return "", fmt.Errorf("generate machine bootstrap config: %w", err)
} }
@@ -153,13 +175,14 @@ func (c *Cluster) AddMachine(ctx context.Context, name, user, host string, port
if err != nil { if err != nil {
return "", fmt.Errorf("start uncloudd: %w: %s", err, out) return "", fmt.Errorf("start uncloudd: %w: %s", err, out)
} }
fmt.Println("uncloudd started") fmt.Println("uncloudd daemon started")
connConfig := config.MachineConnection{ connConfig := config.MachineConnection{
User: user, User: user,
Host: host, Host: host,
Port: port, Port: port,
SSHKey: sshKeyPath, SSHKey: sshKeyPath,
PublicKey: mcfg.Network.PublicKey,
} }
c.config.Clusters[c.Name].Machines = append(c.config.Clusters[c.Name].Machines, connConfig) c.config.Clusters[c.Name].Machines = append(c.config.Clusters[c.Name].Machines, connConfig)
if err = c.config.Save(); err != nil { if err = c.config.Save(); err != nil {
+2
View File
@@ -6,4 +6,6 @@ type Cluster struct {
Name string `toml:"-"` Name string `toml:"-"`
Machines []MachineConnection `toml:"machines"` Machines []MachineConnection `toml:"machines"`
Secret secret.Secret `toml:"secret"` Secret secret.Secret `toml:"secret"`
// UserKey is the user's WireGuard private key used to connect to cluster machines.
UserKey secret.Secret `toml:"user_key"`
} }
+7 -4
View File
@@ -1,8 +1,11 @@
package config package config
import "uncloud/internal/secret"
type MachineConnection struct { type MachineConnection struct {
User string `toml:"user,omitempty"` User string `toml:"user,omitempty"`
Host string `toml:"host"` Host string `toml:"host"`
Port int `toml:"port"` Port int `toml:"port"`
SSHKey string `toml:"ssh_key,omitempty"` SSHKey string `toml:"ssh_key,omitempty"`
PublicKey secret.Secret `toml:"public_key,omitempty"`
} }
+51
View File
@@ -0,0 +1,51 @@
package cli
import (
"fmt"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"net/netip"
"uncloud/internal/secret"
)
type User struct {
privateKey wgtypes.Key
}
func NewUser(privateKey secret.Secret) (*User, error) {
var (
wgKey wgtypes.Key
err error
)
if privateKey == nil {
wgKey, err = wgtypes.GeneratePrivateKey()
if err != nil {
return nil, fmt.Errorf("generate key for user: %w", err)
}
privateKey = wgKey[:]
} else {
wgKey, err = wgtypes.NewKey(privateKey)
if err != nil {
return nil, fmt.Errorf("invalid key: %w", err)
}
privateKey = wgKey[:]
}
return &User{
privateKey: wgKey,
}, nil
}
func (u *User) PrivateKey() secret.Secret {
return u.privateKey[:]
}
func (u *User) PublicKey() secret.Secret {
pubKey := u.privateKey.PublicKey()
return pubKey[:]
}
func (u *User) Address() netip.Addr {
pubKey := u.PublicKey()
bytes := [16]byte{0xfd, 0xcc}
copy(bytes[2:], pubKey[:14])
return netip.AddrFrom16(bytes)
}
+2 -1
View File
@@ -48,7 +48,7 @@ func ConfigPath(dataDir string) string {
} }
// NewBootstrapConfig returns a new machine configuration that should be applied to the first machine in a cluster. // NewBootstrapConfig returns a new machine configuration that should be applied to the first machine in a cluster.
func NewBootstrapConfig(name string, subnet netip.Prefix) (*Config, error) { func NewBootstrapConfig(name string, subnet netip.Prefix, peers ...network.PeerConfig) (*Config, error) {
mid, err := secret.NewID() mid, err := secret.NewID()
if err != nil { if err != nil {
return nil, fmt.Errorf("generate machine ID: %w", err) return nil, fmt.Errorf("generate machine ID: %w", err)
@@ -75,6 +75,7 @@ func NewBootstrapConfig(name string, subnet netip.Prefix) (*Config, error) {
Subnet: subnet, Subnet: subnet,
PrivateKey: privKey, PrivateKey: privKey,
PublicKey: pubKey, PublicKey: pubKey,
Peers: peers,
}, },
}, nil }, nil
} }
+9 -8
View File
@@ -19,13 +19,13 @@ type Config struct {
Subnet netip.Prefix Subnet netip.Prefix
PrivateKey secret.Secret PrivateKey secret.Secret
PublicKey secret.Secret PublicKey secret.Secret
Peers []PeerConfig Peers []PeerConfig `json:",omitempty"`
} }
type PeerConfig struct { type PeerConfig struct {
Subnet netip.Prefix Subnet netip.Prefix
Endpoint netip.AddrPort Endpoint *netip.AddrPort `json:",omitempty"`
AllEndpoints []netip.AddrPort AllEndpoints []netip.AddrPort `json:",omitempty"`
PublicKey secret.Secret PublicKey secret.Secret
} }
@@ -43,17 +43,18 @@ func (c Config) toDeviceConfig() (wgtypes.Config, error) {
if kErr != nil { if kErr != nil {
return wgtypes.Config{}, fmt.Errorf("parse peer public key: %w", kErr) return wgtypes.Config{}, fmt.Errorf("parse peer public key: %w", kErr)
} }
endpoint := &net.UDPAddr{
IP: peerConfig.Endpoint.Addr().AsSlice(),
Port: int(peerConfig.Endpoint.Port()),
}
wgPeerConfigs[i] = wgtypes.PeerConfig{ wgPeerConfigs[i] = wgtypes.PeerConfig{
PublicKey: peerPublicKey, PublicKey: peerPublicKey,
Endpoint: endpoint,
ReplaceAllowedIPs: true, ReplaceAllowedIPs: true,
AllowedIPs: []net.IPNet{prefixToIPNet(peerConfig.Subnet)}, AllowedIPs: []net.IPNet{prefixToIPNet(peerConfig.Subnet)},
PersistentKeepaliveInterval: &persistentKeepalive, PersistentKeepaliveInterval: &persistentKeepalive,
} }
if peerConfig.Endpoint != nil {
wgPeerConfigs[i].Endpoint = &net.UDPAddr{
IP: peerConfig.Endpoint.Addr().AsSlice(),
Port: int(peerConfig.Endpoint.Port()),
}
}
} }
return wgtypes.Config{ return wgtypes.Config{