From ddf7d2335170263d528d92e4fd8946e48179b19a Mon Sep 17 00:00:00 2001 From: Pavel Sviderski Date: Tue, 10 Sep 2024 16:13:20 +1000 Subject: [PATCH] set uncloud group on the unix socket to allow configured non-root users to access it --- go.mod | 1 + go.sum | 2 ++ internal/cli/cli.go | 44 +++++++++++++--------------- internal/cli/client/connector/ssh.go | 6 +++- internal/cli/config/cluster.go | 1 - internal/machine/machine.go | 33 +++++++++++++++++++-- 6 files changed, 59 insertions(+), 28 deletions(-) diff --git a/go.mod b/go.mod index 7a0ee17d..77da7c96 100644 --- a/go.mod +++ b/go.mod @@ -47,6 +47,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fatih/color v1.17.0 // indirect diff --git a/go.sum b/go.sum index 6e9eb114..48696a4c 100644 --- a/go.sum +++ b/go.sum @@ -85,6 +85,8 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczC github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/docker/docker v27.1.2+incompatible h1:AhGzR1xaQIy53qCkxARaFluI00WPGtXn0AJuoQsVYTY= github.com/docker/docker v27.1.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 10843d52..db87bbbd 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -2,7 +2,6 @@ package cli import ( "context" - "crypto/ed25519" "errors" "fmt" "net/netip" @@ -13,6 +12,8 @@ import ( "uncloud/internal/secret" ) +const defaultClusterName = "default" + var ( ErrNotFound = errors.New("not found") ) @@ -31,19 +32,10 @@ func New(configPath string) (*CLI, error) { }, nil } -func (cli *CLI) CreateCluster( - name string, privateKey ed25519.PrivateKey, userPrivateKey secret.Secret, -) (*client.ClusterClient, error) { +func (cli *CLI) CreateCluster(name string, userPrivateKey secret.Secret) (*client.ClusterClient, error) { if _, ok := cli.config.Clusters[name]; ok { return nil, fmt.Errorf("cluster %q already exists", name) } - if privateKey == nil { - var err error - _, privateKey, err = ed25519.GenerateKey(nil) - if err != nil { - return nil, fmt.Errorf("generate cluster secret: %w", err) - } - } if userPrivateKey == nil { user, err := client.NewUser(nil) if err != nil { @@ -54,7 +46,6 @@ func (cli *CLI) CreateCluster( cli.config.Clusters[name] = &config.Cluster{ Name: name, - Secret: privateKey.Seed(), UserPrivateKey: userPrivateKey, } if err := cli.config.Save(); err != nil { @@ -65,7 +56,7 @@ func (cli *CLI) CreateCluster( } func (cli *CLI) CreateDefaultCluster() (*client.ClusterClient, error) { - c, err := cli.CreateCluster("default", nil, nil) + c, err := cli.CreateCluster("default", nil) if err != nil { return nil, err } @@ -142,18 +133,15 @@ func (cli *CLI) initRemoteMachine( _ = c.Close() }() - var cluster *client.ClusterClient if clusterName == "" { - cluster, err = cli.CreateDefaultCluster() - } else { - cluster, err = cli.CreateCluster(clusterName, nil, nil) + clusterName = defaultClusterName } - if err != nil { - return err + if _, ok := cli.config.Clusters[clusterName]; ok { + return fmt.Errorf("cluster %q already exists", clusterName) } - user, err := cluster.User() + user, err := client.NewUser(nil) if err != nil { - return fmt.Errorf("get cluster user: %w", err) + return fmt.Errorf("generate cluster user: %w", err) } // TODO: download and install the latest uncloudd binary by running the install shell script from GitHub. @@ -174,13 +162,23 @@ func (cli *CLI) initRemoteMachine( if err != nil { return fmt.Errorf("init cluster: %w", err) } - fmt.Printf("Cluster %q initialised with machine %q\n", cluster.Name(), resp.Machine.Name) + fmt.Printf("Cluster %q initialised with machine %q\n", clusterName, resp.Machine.Name) + _, err = cli.CreateCluster(clusterName, user.PrivateKey()) + if err != nil { + return fmt.Errorf("save cluster to config: %w", err) + } + // Set the current cluster to the just created one if it is the only cluster in the config. + if len(cli.config.Clusters) == 1 { + if err = cli.SetCurrentCluster(clusterName); err != nil { + return fmt.Errorf("set current cluster: %w", err) + } + } // Save the machine's SSH connection details in the cluster config. connCfg := config.MachineConnection{ SSH: config.NewSSHDestination(remoteMachine.User, remoteMachine.Host, remoteMachine.Port), } - cli.config.Clusters[cluster.Name()].Machines = append(cli.config.Clusters[cluster.Name()].Machines, connCfg) + cli.config.Clusters[clusterName].Machines = append(cli.config.Clusters[clusterName].Machines, connCfg) if err = cli.config.Save(); err != nil { return fmt.Errorf("save config: %w", err) } diff --git a/internal/cli/client/connector/ssh.go b/internal/cli/client/connector/ssh.go index fcaa4ad3..6a20db95 100644 --- a/internal/cli/client/connector/ssh.go +++ b/internal/cli/client/connector/ssh.go @@ -57,7 +57,11 @@ func (c *SSHConnector) Connect(ctx context.Context) (*grpc.ClientConn, error) { addr = strings.TrimPrefix(addr, "unix://") conn, dErr := c.client.DialContext(ctx, "unix", addr) if dErr != nil { - return nil, fmt.Errorf("connect to machine API socket %s through SSH tunnel: %w", addr, dErr) + return nil, fmt.Errorf( + "connect to machine API socket %s through SSH tunnel (is the Uncloud daemon running "+ + "on the remote machine and does the SSH user have permissions to access the socket?): %w", + addr, dErr, + ) } return conn, nil }, diff --git a/internal/cli/config/cluster.go b/internal/cli/config/cluster.go index 28ff5238..45153366 100644 --- a/internal/cli/config/cluster.go +++ b/internal/cli/config/cluster.go @@ -5,7 +5,6 @@ import "uncloud/internal/secret" type Cluster struct { Name string `toml:"-"` Machines []MachineConnection `toml:"machines"` - Secret secret.Secret `toml:"secret"` // UserPrivateKey is the user's WireGuard private key used to connect to cluster machines. UserPrivateKey secret.Secret `toml:"user_private_key"` } diff --git a/internal/machine/machine.go b/internal/machine/machine.go index 6f163fc2..ae1d2525 100644 --- a/internal/machine/machine.go +++ b/internal/machine/machine.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/docker/go-connections/sockets" "golang.org/x/sync/errgroup" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -12,6 +13,7 @@ import ( "net" "net/netip" "os" + "os/user" "strconv" "uncloud/internal/machine/api/pb" "uncloud/internal/machine/cluster" @@ -19,7 +21,8 @@ import ( ) const ( - DefaultAPISockPath = "/run/uncloud.sock" + DefaultAPISockPath = "/run/uncloud.sock" + DefaultAPISockGroup = "uncloud" ) type Config struct { @@ -133,9 +136,9 @@ func (m *Machine) Run(ctx context.Context) error { if m.config.APISockPath != "" { apiSockPath = m.config.APISockPath } - localListener, err := net.Listen("unix", apiSockPath) + localListener, err := listenUnixSocket(apiSockPath) if err != nil { - return fmt.Errorf("listen API socket: %w", err) + return fmt.Errorf("listen API unix socket %q: %w", apiSockPath, err) } errGroup.Go( func() error { @@ -186,6 +189,30 @@ func (m *Machine) Run(ctx context.Context) error { return errGroup.Wait() } +// listenUnixSocket creates a new Unix socket listener with the specified path. The socket file is created with 0660 +// access mode and uncloud group if the group is found, otherwise it falls back to the root group. +func listenUnixSocket(path string) (net.Listener, error) { + gid := 0 // Fall back to the root group if the uncloud group is not found. + group, err := user.LookupGroup(DefaultAPISockGroup) + if err != nil { + if _, ok := err.(user.UnknownGroupError); ok { + slog.Info( + "Specified group not found, using root group for the API socket.", "group", DefaultAPISockGroup, "path", + path, + ) + } else { + return nil, fmt.Errorf("lookup %q group ID (GID): %w", DefaultAPISockGroup, err) + } + } else { + gid, err = strconv.Atoi(group.Gid) + if err != nil { + return nil, fmt.Errorf("parse %q group ID (GID) %q: %w", DefaultAPISockGroup, group.Gid, err) + } + } + + return sockets.NewUnixSocket(path, gid) +} + // InitCluster resets the local machine and initialises a new cluster with it. func (m *Machine) InitCluster(ctx context.Context, req *pb.InitClusterRequest) (*pb.InitClusterResponse, error) { var err error