open-nomad/client/allocrunner/taskrunner/getter/util_linux.go
Seth Hoenig ff4503aac6
client: disable running artifact downloader as nobody (#16375)
* client: disable running artifact downloader as nobody

This PR reverts a change from Nomad 1.5 where artifact downloads were
executed as the nobody user on Linux systems. This was done as an attempt
to improve the security model of artifact downloading where third party
tools such as git or mercurial would be run as the root user with all
the security implications thereof.

However, doing so conflicts with Nomad's own advice for securing the
Client data directory - which when setup with the recommended directory
permissions structure prevents artifact downloads from working as intended.

Artifact downloads are at least still now executed as a child process of
the Nomad agent, and on modern Linux systems make use of the kernel Landlock
feature for limiting filesystem access of the child process.

* docs: update upgrade guide for 1.5.1 sandboxing

* docs: add cl

* docs: add title to upgrade guide fix
2023-03-08 15:58:43 -06:00

105 lines
2.7 KiB
Go

//go:build linux
package getter
import (
"os"
"path/filepath"
"syscall"
"github.com/shoenig/go-landlock"
)
var (
// userUID is the current user's uid
userUID uint32
// userGID is the current user's gid
userGID uint32
)
func init() {
userUID = uint32(syscall.Getuid())
userGID = uint32(syscall.Getgid())
}
// attributes returns the system process attributes to run
// the sandbox process with
func attributes() *syscall.SysProcAttr {
uid, gid := credentials()
return &syscall.SysProcAttr{
Credential: &syscall.Credential{
Uid: uid,
Gid: gid,
},
}
}
// credentials returns the UID and GID of the user the child process
// will run as - for now this is always the same user the Nomad agent is
// running as.
func credentials() (uint32, uint32) {
return userUID, userGID
}
// defaultEnvironment is the default minimal environment variables for Linux.
func defaultEnvironment(taskDir string) map[string]string {
tmpDir := filepath.Join(taskDir, "tmp")
return map[string]string{
"PATH": "/usr/local/bin:/usr/bin:/bin",
"TMPDIR": tmpDir,
}
}
// lockdown isolates this process to only be able to write and
// create files in the task's task directory.
// dir - the task directory
//
// Only applies to Linux, when available.
func lockdown(allocDir, taskDir string) error {
// landlock not present in the kernel, do not sandbox
if !landlock.Available() {
return nil
}
paths := []*landlock.Path{
landlock.DNS(),
landlock.Certs(),
landlock.Shared(),
landlock.Dir("/bin", "rx"),
landlock.Dir("/usr/bin", "rx"),
landlock.Dir("/usr/local/bin", "rx"),
landlock.Dir(allocDir, "rwc"),
landlock.Dir(taskDir, "rwc"),
}
paths = append(paths, systemVersionControlGlobalConfigs()...)
locker := landlock.New(paths...)
return locker.Lock(landlock.Mandatory)
}
func systemVersionControlGlobalConfigs() []*landlock.Path {
const (
gitGlobalFile = "/etc/gitconfig" // https://git-scm.com/docs/git-config#SCOPES
hgGlobalFile = "/etc/mercurial/hgrc" // https://www.mercurial-scm.org/doc/hgrc.5.html#files
hgGlobalDir = "/etc/mercurial/hgrc.d" // https://www.mercurial-scm.org/doc/hgrc.5.html#files
)
return loadVersionControlGlobalConfigs(gitGlobalFile, hgGlobalFile, hgGlobalDir)
}
func loadVersionControlGlobalConfigs(gitGlobalFile, hgGlobalFile, hgGlobalDir string) []*landlock.Path {
exists := func(p string) bool {
_, err := os.Stat(p)
return err == nil
}
result := make([]*landlock.Path, 0, 3)
if exists(gitGlobalFile) {
result = append(result, landlock.File(gitGlobalFile, "r"))
}
if exists(hgGlobalFile) {
result = append(result, landlock.File(hgGlobalFile, "r"))
}
if exists(hgGlobalDir) {
result = append(result, landlock.Dir(hgGlobalDir, "r"))
}
return result
}