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 {
return fmt.Errorf("create default cluster: %w", err)
}
fmt.Printf("Created %q cluster\n", cluster.Name)
} else {
return fmt.Errorf("get current cluster: %w", err)
}
+32 -9
View File
@@ -9,6 +9,7 @@ import (
"uncloud/internal/cli/config"
"uncloud/internal/cmdexec"
"uncloud/internal/machine"
"uncloud/internal/machine/network"
"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 {
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)
}
}
if userPrivateKey == nil {
user, err := NewUser(nil)
if err != nil {
return nil, fmt.Errorf("generate user: %w", err)
}
userPrivateKey = user.PrivateKey()
}
c := &Cluster{
Name: name,
privateKey: privateKey,
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 {
return nil, err
}
@@ -56,7 +66,7 @@ func (cli *CLI) CreateCluster(name string, privateKey ed25519.PrivateKey) (*Clus
}
func (cli *CLI) CreateDefaultCluster() (*Cluster, error) {
c, err := cli.CreateCluster("default", nil)
c, err := cli.CreateCluster("default", nil, nil)
if err != nil {
return nil, err
}
@@ -116,7 +126,19 @@ func (c *Cluster) AddMachine(ctx context.Context, name, user, host string, port
_ = 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 {
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 {
return "", fmt.Errorf("start uncloudd: %w: %s", err, out)
}
fmt.Println("uncloudd started")
fmt.Println("uncloudd daemon started")
connConfig := config.MachineConnection{
User: user,
Host: host,
Port: port,
SSHKey: sshKeyPath,
User: user,
Host: host,
Port: port,
SSHKey: sshKeyPath,
PublicKey: mcfg.Network.PublicKey,
}
c.config.Clusters[c.Name].Machines = append(c.config.Clusters[c.Name].Machines, connConfig)
if err = c.config.Save(); err != nil {
+2
View File
@@ -6,4 +6,6 @@ type Cluster struct {
Name string `toml:"-"`
Machines []MachineConnection `toml:"machines"`
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
import "uncloud/internal/secret"
type MachineConnection struct {
User string `toml:"user,omitempty"`
Host string `toml:"host"`
Port int `toml:"port"`
SSHKey string `toml:"ssh_key,omitempty"`
User string `toml:"user,omitempty"`
Host string `toml:"host"`
Port int `toml:"port"`
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.
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()
if err != nil {
return nil, fmt.Errorf("generate machine ID: %w", err)
@@ -75,6 +75,7 @@ func NewBootstrapConfig(name string, subnet netip.Prefix) (*Config, error) {
Subnet: subnet,
PrivateKey: privKey,
PublicKey: pubKey,
Peers: peers,
},
}, nil
}
+9 -8
View File
@@ -19,13 +19,13 @@ type Config struct {
Subnet netip.Prefix
PrivateKey secret.Secret
PublicKey secret.Secret
Peers []PeerConfig
Peers []PeerConfig `json:",omitempty"`
}
type PeerConfig struct {
Subnet netip.Prefix
Endpoint netip.AddrPort
AllEndpoints []netip.AddrPort
Endpoint *netip.AddrPort `json:",omitempty"`
AllEndpoints []netip.AddrPort `json:",omitempty"`
PublicKey secret.Secret
}
@@ -43,17 +43,18 @@ func (c Config) toDeviceConfig() (wgtypes.Config, error) {
if kErr != nil {
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{
PublicKey: peerPublicKey,
Endpoint: endpoint,
ReplaceAllowedIPs: true,
AllowedIPs: []net.IPNet{prefixToIPNet(peerConfig.Subnet)},
PersistentKeepaliveInterval: &persistentKeepalive,
}
if peerConfig.Endpoint != nil {
wgPeerConfigs[i].Endpoint = &net.UDPAddr{
IP: peerConfig.Endpoint.Addr().AsSlice(),
Port: int(peerConfig.Endpoint.Port()),
}
}
}
return wgtypes.Config{