run corrosion in Docker as uncloud user, bind mount data dir

This commit is contained in:
Pavel Sviderski
2024-11-27 16:35:35 +10:00
parent 871fd8c035
commit c3d3a4242e
4 changed files with 80 additions and 48 deletions
+21 -29
View File
@@ -4,44 +4,36 @@ import (
"fmt" "fmt"
"os" "os"
"os/user" "os/user"
"path/filepath"
"strconv" "strconv"
) )
func MkDataDir(dir, owner string) error { // LookupUIDGID returns the user and group IDs for the given username.
parent, _ := filepath.Split(dir) func LookupUIDGID(username string) (uid, gid int, err error) {
// Use 0711 for parent directories to allow `owner` to access its nested data directory. usr, err := user.Lookup(username)
if err := os.MkdirAll(parent, 0711); err != nil { if err != nil {
return fmt.Errorf("create directory %q: %w", parent, err) err = fmt.Errorf("lookup user %q: %w", username, err)
return
} }
if err := os.Mkdir(dir, 0700); err != nil { uid, err = strconv.Atoi(usr.Uid)
if !os.IsExist(err) { if err != nil {
return fmt.Errorf("create directory %q: %w", dir, err) err = fmt.Errorf("parse %q user ID (UID) %q: %w", username, usr.Uid, err)
} return
} }
if err := Chown(dir, owner); err != nil { gid, err = strconv.Atoi(usr.Gid)
return err 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 { func Chown(path, owner string) error {
if owner != "" { uid, gid, err := LookupUIDGID(owner)
usr, err := user.Lookup(owner) if err != nil {
if err != nil { return err
return fmt.Errorf("lookup user %q: %w", owner, err) }
} if err = os.Chown(path, uid, gid); err != nil {
uid, err := strconv.Atoi(usr.Uid) return fmt.Errorf("chown %q: %w", path, err)
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)
}
} }
return nil return nil
} }
+21
View File
@@ -6,6 +6,7 @@ import (
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"net/netip" "net/netip"
"os" "os"
"path/filepath"
"uncloud/internal/fs" "uncloud/internal/fs"
) )
@@ -57,3 +58,23 @@ func (c *Config) Write(path, owner string) error {
} }
return nil 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
}
+11 -1
View File
@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image" "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/api/types/network"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"io" "io"
@@ -22,7 +23,7 @@ type DockerService struct {
Image string Image string
Name string Name string
DataDir string DataDir string
// TODO: uid/guid User string
} }
func NewDockerService(cli *client.Client, image, name, dataDir string) *DockerService { func NewDockerService(cli *client.Client, image, name, dataDir string) *DockerService {
@@ -71,6 +72,7 @@ func (s *DockerService) containerConfig() *container.Config {
return &container.Config{ return &container.Config{
Image: s.Image, Image: s.Image,
Cmd: []string{"corrosion", "agent", "-c", filepath.Join(s.DataDir, "config.toml")}, 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{ RestartPolicy: container.RestartPolicy{
Name: container.RestartPolicyAlways, 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,
},
},
} }
} }
+27 -18
View File
@@ -32,9 +32,9 @@ import (
) )
const ( const (
DefaultMachineSockPath = "/run/uncloud/machine.sock" DefaultMachineSockPath = "/run/uncloud/machine.sock"
DefaultUncloudSockPath = "/run/uncloud/uncloud.sock" DefaultUncloudSockPath = "/run/uncloud/uncloud.sock"
DefaultUncloudSockGroup = "uncloud" DefaultSockGroup = "uncloud"
) )
type Config struct { type Config struct {
@@ -48,6 +48,8 @@ type Config struct {
CorrosionAPIAddr netip.AddrPort CorrosionAPIAddr netip.AddrPort
CorrosionAdminSockPath string CorrosionAdminSockPath string
CorrosionService corroservice.Service 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 manages system and user containers using the local Docker daemon.
DockerClient *client.Client DockerClient *client.Client
@@ -90,15 +92,24 @@ func (c *Config) SetDefaults() (*Config, error) {
if cfg.CorrosionAdminSockPath == "" { if cfg.CorrosionAdminSockPath == "" {
cfg.CorrosionAdminSockPath = filepath.Join(cfg.CorrosionDir, "admin.sock") cfg.CorrosionAdminSockPath = filepath.Join(cfg.CorrosionDir, "admin.sock")
} }
if cfg.CorrosionUser == "" {
cfg.CorrosionUser = corroservice.DefaultUser
}
if cfg.CorrosionService == nil { if cfg.CorrosionService == nil {
if isRunningInDocker() { if isRunningInDocker() {
// Run corrosion in a nested Docker container if the machine is running in a container. // Run corrosion in a nested Docker container if the machine is running in a container.
cfg.CorrosionService = corroservice.NewDockerService( uid, gid, err := fs.LookupUIDGID(cfg.CorrosionUser)
cfg.DockerClient, if err != nil {
corroservice.LatestImage, return nil, fmt.Errorf("lookup corrosion user %q: %w", cfg.CorrosionUser, err)
"uncloud-corrosion", }
cfg.CorrosionDir,
) cfg.CorrosionService = &corroservice.DockerService{
Client: cfg.DockerClient,
Image: corroservice.LatestImage,
Name: "uncloud-corrosion",
DataDir: cfg.CorrosionDir,
User: fmt.Sprintf("%d:%d", uid, gid),
}
} else { } else {
cfg.CorrosionService = corroservice.DefaultSystemdService(cfg.CorrosionDir) 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. // 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) { func listenUnixSocket(path string) (net.Listener, error) {
gid := 0 // Fall back to the root group if the uncloud group is not found. 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 { if err != nil {
//goland:noinspection GoTypeAssertionOnErrors //goland:noinspection GoTypeAssertionOnErrors
if _, ok := err.(user.UnknownGroupError); ok { if _, ok := err.(user.UnknownGroupError); ok {
slog.Info( slog.Info(
"Specified group not found, using root group for the API socket.", "Specified group not found, using root group for the API socket.",
"group", DefaultUncloudSockGroup, "path", path, "group", DefaultSockGroup, "path", path,
) )
} else { } 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 { } else {
gid, err = strconv.Atoi(group.Gid) gid, err = strconv.Atoi(group.Gid)
if err != nil { 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 { 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) return fmt.Errorf("create corrosion data directory: %w", err)
} }
configPath := filepath.Join(m.config.CorrosionDir, "config.toml") 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"), 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) return fmt.Errorf("write corrosion config: %w", err)
} }
if err := os.WriteFile(schemaPath, []byte(store.Schema), 0644); err != nil { if err := os.WriteFile(schemaPath, []byte(store.Schema), 0644); err != nil {
return fmt.Errorf("write corrosion schema: %w", err) 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 return nil
} }