From d7399489891f15ce85d9b1b8b93a6423080247ff Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Wed, 24 Jul 2024 15:12:42 -0600 Subject: [PATCH] feat: add ability to mount generated passwd files to containers (#197) This PR will add an option to mount: - `/etc/group` - `/etc/passwd` Signed-off-by: Matthew Penner --- cmd/root.go | 3 +++ config/config.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ server/mounts.go | 17 +++++++++++++-- 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 62620c2..4bb5f45 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -112,6 +112,9 @@ func rootCmdRun(cmd *cobra.Command, _ []string) { if err := config.EnsurePterodactylUser(); err != nil { log.WithField("error", err).Fatal("failed to create pterodactyl system user") } + if err := config.ConfigurePasswd(); err != nil { + log.WithField("error", err).Fatal("failed to configure container passwd file") + } log.WithFields(log.Fields{ "username": config.Get().System.Username, "uid": config.Get().System.User.Uid, diff --git a/config/config.go b/config/config.go index 3770115..395b7ec 100644 --- a/config/config.go +++ b/config/config.go @@ -172,6 +172,25 @@ type SystemConfiguration struct { Gid int `yaml:"gid"` } `yaml:"user"` + // Passwd controls the mounting of a generated passwd files into containers started by Wings. + Passwd struct { + // Enable controls whether generated passwd files should be mounted into containers. + // + // By default this option is disabled and Wings will not mount any additional passwd + // files into containers. + Enable bool `yaml:"enabled" default:"false"` + + // Directory is the directory on disk where the generated files will be stored. + // This directory may be temporary as it will be re-created whenever Wings is started. + // + // This path **WILL** be both written to by Wings and mounted into containers created by + // Wings. If you are running Wings itself in a container, this path will need to be mounted + // into the Wings container as the exact path on the host, which should match the value + // specified here. If you are using SELinux, you will need to make sure this file has the + // correct SELinux context in order for containers to use it. + Directory string `yaml:"directory" default:"/run/wings/etc"` + } `yaml:"passwd"` + // The amount of time in seconds that can elapse before a server's disk space calculation is // considered stale and a re-check should occur. DANGER: setting this value too low can seriously // impact system performance and cause massive I/O bottlenecks and high CPU usage for the Wings @@ -497,6 +516,37 @@ func EnsurePterodactylUser() error { return nil } +// ConfigurePasswd generates required passwd files for use with containers started by Wings. +func ConfigurePasswd() error { + passwd := _config.System.Passwd + if !passwd.Enable { + return nil + } + + v := []byte(fmt.Sprintf( + `root:x:0: +container:x:%d: +nogroup:x:65534:`, + _config.System.User.Gid, + )) + if err := os.WriteFile(filepath.Join(passwd.Directory, "group"), v, 0o644); err != nil { + return fmt.Errorf("failed to write file to %s/group: %v", passwd.Directory, err) + } + + v = []byte(fmt.Sprintf( + `root:x:0:0::/root:/bin/sh +container:x:%d:%d::/home/container:/bin/sh +nobody:x:65534:65534::/var/empty:/bin/sh +`, + _config.System.User.Uid, + _config.System.User.Gid, + )) + if err := os.WriteFile(filepath.Join(passwd.Directory, "passwd"), v, 0o644); err != nil { + return fmt.Errorf("failed to write file to %s/passwd: %v", passwd.Directory, err) + } + return nil +} + // FromFile reads the configuration from the provided file and stores it in the // global singleton for this instance. func FromFile(path string) error { @@ -561,6 +611,13 @@ func ConfigureDirectories() error { return err } + if _config.System.Passwd.Enable { + log.WithField("path", _config.System.Passwd.Directory).Debug("ensuring passwd directory exists") + if err := os.MkdirAll(_config.System.Passwd.Directory, 0o755); err != nil { + return err + } + } + return nil } diff --git a/server/mounts.go b/server/mounts.go index 97c8094..b263028 100644 --- a/server/mounts.go +++ b/server/mounts.go @@ -29,6 +29,21 @@ func (s *Server) Mounts() []environment.Mount { }, } + // Handle mounting a generated `/etc/passwd` if the feature is enabled. + if passwd := config.Get().System.Passwd; passwd.Enable { + s.Log().WithFields(log.Fields{"source_path": passwd.Directory}).Info("mouting generated /etc/{group,passwd} to workaround UID/GID issues") + m = append(m, environment.Mount{ + Source: filepath.Join(passwd.Directory, "group"), + Target: "/etc/group", + ReadOnly: true, + }) + m = append(m, environment.Mount{ + Source: filepath.Join(passwd.Directory, "passwd"), + Target: "/etc/passwd", + ReadOnly: true, + }) + } + // Also include any of this server's custom mounts when returning them. return append(m, s.customMounts()...) } @@ -56,14 +71,12 @@ func (s *Server) customMounts() []environment.Mount { if !strings.HasPrefix(source, filepath.Clean(allowed)) { continue } - mounted = true mounts = append(mounts, environment.Mount{ Source: source, Target: target, ReadOnly: m.ReadOnly, }) - break }