mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
set uncloud group on the unix socket to allow configured non-root users to access it
This commit is contained in:
@@ -47,6 +47,7 @@ require (
|
|||||||
github.com/containerd/log v0.1.0 // indirect
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
||||||
github.com/dgraph-io/ristretto v0.1.1 // 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/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/fatih/color v1.17.0 // indirect
|
github.com/fatih/color v1.17.0 // indirect
|
||||||
|
|||||||
@@ -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/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 h1:AhGzR1xaQIy53qCkxARaFluI00WPGtXn0AJuoQsVYTY=
|
||||||
github.com/docker/docker v27.1.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
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 h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
|
||||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
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=
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
|
|||||||
+21
-23
@@ -2,7 +2,6 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/ed25519"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@@ -13,6 +12,8 @@ import (
|
|||||||
"uncloud/internal/secret"
|
"uncloud/internal/secret"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const defaultClusterName = "default"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrNotFound = errors.New("not found")
|
ErrNotFound = errors.New("not found")
|
||||||
)
|
)
|
||||||
@@ -31,19 +32,10 @@ func New(configPath string) (*CLI, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *CLI) CreateCluster(
|
func (cli *CLI) CreateCluster(name string, userPrivateKey secret.Secret) (*client.ClusterClient, error) {
|
||||||
name string, privateKey ed25519.PrivateKey, userPrivateKey secret.Secret,
|
|
||||||
) (*client.ClusterClient, 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)
|
||||||
}
|
}
|
||||||
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 {
|
if userPrivateKey == nil {
|
||||||
user, err := client.NewUser(nil)
|
user, err := client.NewUser(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -54,7 +46,6 @@ func (cli *CLI) CreateCluster(
|
|||||||
|
|
||||||
cli.config.Clusters[name] = &config.Cluster{
|
cli.config.Clusters[name] = &config.Cluster{
|
||||||
Name: name,
|
Name: name,
|
||||||
Secret: privateKey.Seed(),
|
|
||||||
UserPrivateKey: userPrivateKey,
|
UserPrivateKey: userPrivateKey,
|
||||||
}
|
}
|
||||||
if err := cli.config.Save(); err != nil {
|
if err := cli.config.Save(); err != nil {
|
||||||
@@ -65,7 +56,7 @@ func (cli *CLI) CreateCluster(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cli *CLI) CreateDefaultCluster() (*client.ClusterClient, error) {
|
func (cli *CLI) CreateDefaultCluster() (*client.ClusterClient, error) {
|
||||||
c, err := cli.CreateCluster("default", nil, nil)
|
c, err := cli.CreateCluster("default", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -142,18 +133,15 @@ func (cli *CLI) initRemoteMachine(
|
|||||||
_ = c.Close()
|
_ = c.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var cluster *client.ClusterClient
|
|
||||||
if clusterName == "" {
|
if clusterName == "" {
|
||||||
cluster, err = cli.CreateDefaultCluster()
|
clusterName = defaultClusterName
|
||||||
} else {
|
|
||||||
cluster, err = cli.CreateCluster(clusterName, nil, nil)
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if _, ok := cli.config.Clusters[clusterName]; ok {
|
||||||
return err
|
return fmt.Errorf("cluster %q already exists", clusterName)
|
||||||
}
|
}
|
||||||
user, err := cluster.User()
|
user, err := client.NewUser(nil)
|
||||||
if err != 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.
|
// 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 {
|
if err != nil {
|
||||||
return fmt.Errorf("init cluster: %w", err)
|
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.
|
// Save the machine's SSH connection details in the cluster config.
|
||||||
connCfg := config.MachineConnection{
|
connCfg := config.MachineConnection{
|
||||||
SSH: config.NewSSHDestination(remoteMachine.User, remoteMachine.Host, remoteMachine.Port),
|
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 {
|
if err = cli.config.Save(); err != nil {
|
||||||
return fmt.Errorf("save config: %w", err)
|
return fmt.Errorf("save config: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,11 @@ func (c *SSHConnector) Connect(ctx context.Context) (*grpc.ClientConn, error) {
|
|||||||
addr = strings.TrimPrefix(addr, "unix://")
|
addr = strings.TrimPrefix(addr, "unix://")
|
||||||
conn, dErr := c.client.DialContext(ctx, "unix", addr)
|
conn, dErr := c.client.DialContext(ctx, "unix", addr)
|
||||||
if dErr != nil {
|
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
|
return conn, nil
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import "uncloud/internal/secret"
|
|||||||
type Cluster struct {
|
type Cluster struct {
|
||||||
Name string `toml:"-"`
|
Name string `toml:"-"`
|
||||||
Machines []MachineConnection `toml:"machines"`
|
Machines []MachineConnection `toml:"machines"`
|
||||||
Secret secret.Secret `toml:"secret"`
|
|
||||||
// UserPrivateKey is the user's WireGuard private key used to connect to cluster machines.
|
// UserPrivateKey is the user's WireGuard private key used to connect to cluster machines.
|
||||||
UserPrivateKey secret.Secret `toml:"user_private_key"`
|
UserPrivateKey secret.Secret `toml:"user_private_key"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/docker/go-connections/sockets"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
@@ -12,6 +13,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"os/user"
|
||||||
"strconv"
|
"strconv"
|
||||||
"uncloud/internal/machine/api/pb"
|
"uncloud/internal/machine/api/pb"
|
||||||
"uncloud/internal/machine/cluster"
|
"uncloud/internal/machine/cluster"
|
||||||
@@ -20,6 +22,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
DefaultAPISockPath = "/run/uncloud.sock"
|
DefaultAPISockPath = "/run/uncloud.sock"
|
||||||
|
DefaultAPISockGroup = "uncloud"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@@ -133,9 +136,9 @@ func (m *Machine) Run(ctx context.Context) error {
|
|||||||
if m.config.APISockPath != "" {
|
if m.config.APISockPath != "" {
|
||||||
apiSockPath = m.config.APISockPath
|
apiSockPath = m.config.APISockPath
|
||||||
}
|
}
|
||||||
localListener, err := net.Listen("unix", apiSockPath)
|
localListener, err := listenUnixSocket(apiSockPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("listen API socket: %w", err)
|
return fmt.Errorf("listen API unix socket %q: %w", apiSockPath, err)
|
||||||
}
|
}
|
||||||
errGroup.Go(
|
errGroup.Go(
|
||||||
func() error {
|
func() error {
|
||||||
@@ -186,6 +189,30 @@ func (m *Machine) Run(ctx context.Context) error {
|
|||||||
return errGroup.Wait()
|
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.
|
// 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) {
|
func (m *Machine) InitCluster(ctx context.Context, req *pb.InitClusterRequest) (*pb.InitClusterResponse, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|||||||
Reference in New Issue
Block a user