diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 95835966..77a302d6 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -4,44 +4,36 @@ import ( "fmt" "os" "os/user" - "path/filepath" "strconv" ) -func MkDataDir(dir, owner string) error { - parent, _ := filepath.Split(dir) - // Use 0711 for parent directories to allow `owner` to access its nested data directory. - if err := os.MkdirAll(parent, 0711); err != nil { - return fmt.Errorf("create directory %q: %w", parent, err) +// LookupUIDGID returns the user and group IDs for the given username. +func LookupUIDGID(username string) (uid, gid int, err error) { + usr, err := user.Lookup(username) + if err != nil { + err = fmt.Errorf("lookup user %q: %w", username, err) + return } - if err := os.Mkdir(dir, 0700); err != nil { - if !os.IsExist(err) { - return fmt.Errorf("create directory %q: %w", dir, err) - } + uid, err = strconv.Atoi(usr.Uid) + if err != nil { + err = fmt.Errorf("parse %q user ID (UID) %q: %w", username, usr.Uid, err) + return } - if err := Chown(dir, owner); err != nil { - return err + gid, err = strconv.Atoi(usr.Gid) + if err != nil { + err = fmt.Errorf("parse %q user group ID (GID) %q: %w", username, usr.Gid, err) + return } - return nil + return } func Chown(path, owner string) error { - if owner != "" { - usr, err := user.Lookup(owner) - if err != nil { - return fmt.Errorf("lookup user %q: %w", owner, err) - } - uid, err := strconv.Atoi(usr.Uid) - if err != nil { - return fmt.Errorf("parse %q user ID (UID) %q: %w", owner, usr.Uid, err) - } - gid, err := strconv.Atoi(usr.Gid) - if err != nil { - return fmt.Errorf("parse %q user group ID (GID) %q: %w", owner, usr.Gid, err) - } - if err = os.Chown(path, uid, gid); err != nil { - return fmt.Errorf("chown %q: %w", path, err) - } + uid, gid, err := LookupUIDGID(owner) + if err != nil { + return err + } + if err = os.Chown(path, uid, gid); err != nil { + return fmt.Errorf("chown %q: %w", path, err) } return nil } diff --git a/internal/machine/corroservice/config.go b/internal/machine/corroservice/config.go index d5a571f4..749c2028 100644 --- a/internal/machine/corroservice/config.go +++ b/internal/machine/corroservice/config.go @@ -6,6 +6,7 @@ import ( "github.com/BurntSushi/toml" "net/netip" "os" + "path/filepath" "uncloud/internal/fs" ) @@ -57,3 +58,23 @@ func (c *Config) Write(path, owner string) error { } return nil } + +func MkDataDir(dir, owner string) error { + parent, _ := filepath.Split(dir) + // Use 0711 for parent directories to allow `owner` to access its nested data directory. + if err := os.MkdirAll(parent, 0711); err != nil { + return fmt.Errorf("create directory %q: %w", parent, err) + } + if err := os.Mkdir(dir, 0700); err != nil { + if !os.IsExist(err) { + return fmt.Errorf("create directory %q: %w", dir, err) + } + } + + if owner != "" { + if err := fs.Chown(dir, owner); err != nil { + return err + } + } + return nil +} diff --git a/internal/machine/corroservice/docker.go b/internal/machine/corroservice/docker.go index 30c7b740..459d1063 100644 --- a/internal/machine/corroservice/docker.go +++ b/internal/machine/corroservice/docker.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/image" + "github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/network" "github.com/docker/docker/client" "io" @@ -22,7 +23,7 @@ type DockerService struct { Image string Name string DataDir string - // TODO: uid/guid + User string } func NewDockerService(cli *client.Client, image, name, dataDir string) *DockerService { @@ -71,6 +72,7 @@ func (s *DockerService) containerConfig() *container.Config { return &container.Config{ Image: s.Image, Cmd: []string{"corrosion", "agent", "-c", filepath.Join(s.DataDir, "config.toml")}, + User: s.User, } } @@ -80,6 +82,14 @@ func (s *DockerService) hostConfig() *container.HostConfig { RestartPolicy: container.RestartPolicy{ Name: container.RestartPolicyAlways, }, + Mounts: []mount.Mount{ + // Bind mount the data directory at the same path inside the container to simplify path handling. + { + Type: mount.TypeBind, + Source: s.DataDir, + Target: s.DataDir, + }, + }, } } diff --git a/internal/machine/machine.go b/internal/machine/machine.go index f1cc4277..a7ae400e 100644 --- a/internal/machine/machine.go +++ b/internal/machine/machine.go @@ -32,9 +32,9 @@ import ( ) const ( - DefaultMachineSockPath = "/run/uncloud/machine.sock" - DefaultUncloudSockPath = "/run/uncloud/uncloud.sock" - DefaultUncloudSockGroup = "uncloud" + DefaultMachineSockPath = "/run/uncloud/machine.sock" + DefaultUncloudSockPath = "/run/uncloud/uncloud.sock" + DefaultSockGroup = "uncloud" ) type Config struct { @@ -48,6 +48,8 @@ type Config struct { CorrosionAPIAddr netip.AddrPort CorrosionAdminSockPath string CorrosionService corroservice.Service + // CorrosionUser sets the Linux user for running the corrosion service. + CorrosionUser string // DockerClient manages system and user containers using the local Docker daemon. DockerClient *client.Client @@ -90,15 +92,24 @@ func (c *Config) SetDefaults() (*Config, error) { if cfg.CorrosionAdminSockPath == "" { cfg.CorrosionAdminSockPath = filepath.Join(cfg.CorrosionDir, "admin.sock") } + if cfg.CorrosionUser == "" { + cfg.CorrosionUser = corroservice.DefaultUser + } if cfg.CorrosionService == nil { if isRunningInDocker() { // Run corrosion in a nested Docker container if the machine is running in a container. - cfg.CorrosionService = corroservice.NewDockerService( - cfg.DockerClient, - corroservice.LatestImage, - "uncloud-corrosion", - cfg.CorrosionDir, - ) + uid, gid, err := fs.LookupUIDGID(cfg.CorrosionUser) + if err != nil { + return nil, fmt.Errorf("lookup corrosion user %q: %w", cfg.CorrosionUser, err) + } + + cfg.CorrosionService = &corroservice.DockerService{ + Client: cfg.DockerClient, + Image: corroservice.LatestImage, + Name: "uncloud-corrosion", + DataDir: cfg.CorrosionDir, + User: fmt.Sprintf("%d:%d", uid, gid), + } } else { cfg.CorrosionService = corroservice.DefaultSystemdService(cfg.CorrosionDir) } @@ -400,21 +411,21 @@ func (m *Machine) Run(ctx context.Context) error { // 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(DefaultUncloudSockGroup) + group, err := user.LookupGroup(DefaultSockGroup) if err != nil { //goland:noinspection GoTypeAssertionOnErrors if _, ok := err.(user.UnknownGroupError); ok { slog.Info( "Specified group not found, using root group for the API socket.", - "group", DefaultUncloudSockGroup, "path", path, + "group", DefaultSockGroup, "path", path, ) } else { - return nil, fmt.Errorf("lookup %q group ID (GID): %w", DefaultUncloudSockGroup, err) + return nil, fmt.Errorf("lookup %q group ID (GID): %w", DefaultSockGroup, err) } } else { gid, err = strconv.Atoi(group.Gid) if err != nil { - return nil, fmt.Errorf("parse %q group ID (GID) %q: %w", DefaultUncloudSockGroup, group.Gid, err) + return nil, fmt.Errorf("parse %q group ID (GID) %q: %w", DefaultSockGroup, group.Gid, err) } } @@ -431,7 +442,7 @@ func listenUnixSocket(path string) (net.Listener, error) { } func (m *Machine) configureCorrosion() error { - if err := fs.MkDataDir(m.config.CorrosionDir, corroservice.DefaultUser); err != nil { + if err := corroservice.MkDataDir(m.config.CorrosionDir, m.config.CorrosionUser); err != nil { return fmt.Errorf("create corrosion data directory: %w", err) } configPath := filepath.Join(m.config.CorrosionDir, "config.toml") @@ -469,16 +480,14 @@ func (m *Machine) configureCorrosion() error { Path: filepath.Join(m.config.CorrosionDir, "admin.sock"), }, } - if err := cfg.Write(configPath, corroservice.DefaultUser); err != nil { + // TODO: change file permissions to 0640 root:uncloud to emphasize the owner is the machine, not corrosion. + if err := cfg.Write(configPath, m.config.CorrosionUser); err != nil { return fmt.Errorf("write corrosion config: %w", err) } if err := os.WriteFile(schemaPath, []byte(store.Schema), 0644); err != nil { return fmt.Errorf("write corrosion schema: %w", err) } - if err := fs.Chown(schemaPath, corroservice.DefaultUser); err != nil { - return fmt.Errorf("chown corrosion schema: %w", err) - } return nil }