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 <me@matthewp.io>
This commit is contained in:
Matthew Penner 2024-07-24 15:12:42 -06:00 committed by GitHub
parent ac260bd5ee
commit d739948989
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 75 additions and 2 deletions

View File

@ -112,6 +112,9 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
if err := config.EnsurePterodactylUser(); err != nil { if err := config.EnsurePterodactylUser(); err != nil {
log.WithField("error", err).Fatal("failed to create pterodactyl system user") 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{ log.WithFields(log.Fields{
"username": config.Get().System.Username, "username": config.Get().System.Username,
"uid": config.Get().System.User.Uid, "uid": config.Get().System.User.Uid,

View File

@ -172,6 +172,25 @@ type SystemConfiguration struct {
Gid int `yaml:"gid"` Gid int `yaml:"gid"`
} `yaml:"user"` } `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 // 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 // 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 // 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 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 // FromFile reads the configuration from the provided file and stores it in the
// global singleton for this instance. // global singleton for this instance.
func FromFile(path string) error { func FromFile(path string) error {
@ -561,6 +611,13 @@ func ConfigureDirectories() error {
return err 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 return nil
} }

View File

@ -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. // Also include any of this server's custom mounts when returning them.
return append(m, s.customMounts()...) return append(m, s.customMounts()...)
} }
@ -56,14 +71,12 @@ func (s *Server) customMounts() []environment.Mount {
if !strings.HasPrefix(source, filepath.Clean(allowed)) { if !strings.HasPrefix(source, filepath.Clean(allowed)) {
continue continue
} }
mounted = true mounted = true
mounts = append(mounts, environment.Mount{ mounts = append(mounts, environment.Mount{
Source: source, Source: source,
Target: target, Target: target,
ReadOnly: m.ReadOnly, ReadOnly: m.ReadOnly,
}) })
break break
} }