artifact: fix sandbox behavior when destination is shared alloc directory (#15712)

This PR fixes the artifact sandbox (new in Nomad 1.5) to allow downloading
artifacts into the shared 'alloc' directory made available to each task in
a common allocation. Previously we assumed the 'alloc' dir would be mounted
under the 'task' dir, but this is only the case in fs isolation: chroot; in
other modes the alloc dir is elsewhere.
This commit is contained in:
Seth Hoenig 2023-01-09 09:46:32 -06:00 committed by GitHub
parent 84cb5fb03d
commit 2a7c7d85a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 104 additions and 18 deletions

View File

@ -36,8 +36,9 @@ type parameters struct {
Destination string `json:"artifact_destination"` Destination string `json:"artifact_destination"`
Headers map[string][]string `json:"artifact_headers"` Headers map[string][]string `json:"artifact_headers"`
// Task Environment // Task Filesystem
TaskDir string `json:"task_dir"` AllocDir string `json:"alloc_dir"`
TaskDir string `json:"task_dir"`
} }
func (p *parameters) reader() io.Reader { func (p *parameters) reader() io.Reader {

View File

@ -27,7 +27,8 @@ const paramsAsJSON = `
"artifact_headers": { "artifact_headers": {
"X-Nomad-Artifact": ["hi"] "X-Nomad-Artifact": ["hi"]
}, },
"task_dir": "/path/to/task" "alloc_dir": "/path/to/alloc",
"task_dir": "/path/to/alloc/task"
}` }`
var paramsAsStruct = &parameters{ var paramsAsStruct = &parameters{
@ -42,7 +43,8 @@ var paramsAsStruct = &parameters{
Mode: getter.ClientModeFile, Mode: getter.ClientModeFile,
Source: "https://example.com/file.txt", Source: "https://example.com/file.txt",
Destination: "local/out.txt", Destination: "local/out.txt",
TaskDir: "/path/to/task", AllocDir: "/path/to/alloc",
TaskDir: "/path/to/alloc/task",
Headers: map[string][]string{ Headers: map[string][]string{
"X-Nomad-Artifact": {"hi"}, "X-Nomad-Artifact": {"hi"},
}, },

View File

@ -36,7 +36,7 @@ func (s *Sandbox) Get(env interfaces.EnvReplacer, artifact *structs.TaskArtifact
mode := getMode(artifact) mode := getMode(artifact)
headers := getHeaders(env, artifact) headers := getHeaders(env, artifact)
dir := getTaskDir(env) allocDir, taskDir := getWritableDirs(env)
params := &parameters{ params := &parameters{
// downloader configuration // downloader configuration
@ -55,8 +55,9 @@ func (s *Sandbox) Get(env interfaces.EnvReplacer, artifact *structs.TaskArtifact
Destination: destination, Destination: destination,
Headers: headers, Headers: headers,
// task environment // task filesystem
TaskDir: dir, AllocDir: allocDir,
TaskDir: taskDir,
} }
if err = s.runCmd(params); err != nil { if err = s.runCmd(params); err != nil {

View File

@ -93,9 +93,13 @@ func getHeaders(env interfaces.EnvReplacer, artifact *structs.TaskArtifact) map[
return headers return headers
} }
func getTaskDir(env interfaces.EnvReplacer) string { // getWritableDirs returns host paths to the task's allocation and task specific
p, _ := env.ClientPath("stub", false) // directories - the locations into which a Task is allowed to download an artifact.
return filepath.Dir(p) func getWritableDirs(env interfaces.EnvReplacer) (string, string) {
stub, _ := env.ClientPath("stub", false)
taskDir := filepath.Dir(stub)
allocDir := filepath.Dir(taskDir)
return allocDir, taskDir
} }
// environment merges the default minimal environment per-OS with the set of // environment merges the default minimal environment per-OS with the set of

View File

@ -37,6 +37,6 @@ func defaultEnvironment(taskDir string) map[string]string {
} }
// lockdown applies only to Linux // lockdown applies only to Linux
func lockdown(string) error { func lockdown(string, string) error {
return nil return nil
} }

View File

@ -62,7 +62,7 @@ func defaultEnvironment(taskDir string) map[string]string {
// dir - the task directory // dir - the task directory
// //
// Only applies to Linux, when available. // Only applies to Linux, when available.
func lockdown(dir string) error { func lockdown(allocDir, taskDir string) error {
// landlock not present in the kernel, do not sandbox // landlock not present in the kernel, do not sandbox
if !landlock.Available() { if !landlock.Available() {
return nil return nil
@ -74,7 +74,8 @@ func lockdown(dir string) error {
landlock.Dir("/bin", "rx"), landlock.Dir("/bin", "rx"),
landlock.Dir("/usr/bin", "rx"), landlock.Dir("/usr/bin", "rx"),
landlock.Dir("/usr/local/bin", "rx"), landlock.Dir("/usr/local/bin", "rx"),
landlock.Dir(dir, "rwc"), landlock.Dir(allocDir, "rwc"),
landlock.Dir(taskDir, "rwc"),
} }
locker := landlock.New(paths...) locker := landlock.New(paths...)
return locker.Lock(landlock.Mandatory) return locker.Lock(landlock.Mandatory)

View File

@ -134,9 +134,10 @@ func TestUtil_getHeaders(t *testing.T) {
func TestUtil_getTaskDir(t *testing.T) { func TestUtil_getTaskDir(t *testing.T) {
ci.Parallel(t) ci.Parallel(t)
env := noopTaskEnv("/path/to/task") env := noopTaskEnv("/path/to/alloc/task")
result := getTaskDir(env) allocDir, taskDir := getWritableDirs(env)
must.Eq(t, "/path/to/task", result) must.Eq(t, "/path/to/alloc", allocDir)
must.Eq(t, "/path/to/alloc/task", taskDir)
} }
func TestUtil_environment(t *testing.T) { func TestUtil_environment(t *testing.T) {

View File

@ -19,7 +19,7 @@ func credentials() (uint32, uint32) {
} }
// lockdown has no effect on windows // lockdown has no effect on windows
func lockdown(string) error { func lockdown(string, string) error {
return nil return nil
} }

View File

@ -31,7 +31,7 @@ func init() {
// sandbox the host filesystem for this process // sandbox the host filesystem for this process
if !env.DisableFilesystemIsolation { if !env.DisableFilesystemIsolation {
if err := lockdown(env.TaskDir); err != nil { if err := lockdown(env.AllocDir, env.TaskDir); err != nil {
subproc.Print("failed to sandbox %s process: %v", SubCommand, err) subproc.Print("failed to sandbox %s process: %v", SubCommand, err)
return subproc.ExitFailure return subproc.ExitFailure
} }

View File

@ -93,18 +93,22 @@ func testLinux(t *testing.T) {
check("rawexec", "rawexec_file_default") check("rawexec", "rawexec_file_default")
check("rawexec", "rawexec_file_custom") check("rawexec", "rawexec_file_custom")
check("rawexec", "rawexec_file_alloc_dots")
check("rawexec", "rawexec_file_alloc_env")
check("rawexec", "rawexec_zip_default") check("rawexec", "rawexec_zip_default")
check("rawexec", "rawexec_zip_custom") check("rawexec", "rawexec_zip_custom")
check("rawexec", "rawexec_git_custom") check("rawexec", "rawexec_git_custom")
check("exec", "exec_file_default") check("exec", "exec_file_default")
check("exec", "exec_file_custom") check("exec", "exec_file_custom")
check("exec", "exec_file_alloc")
check("exec", "exec_zip_default") check("exec", "exec_zip_default")
check("exec", "exec_zip_custom") check("exec", "exec_zip_custom")
check("exec", "exec_git_custom") check("exec", "exec_git_custom")
check("docker", "docker_file_default") check("docker", "docker_file_default")
check("docker", "docker_file_custom") check("docker", "docker_file_custom")
check("docker", "docker_file_alloc")
check("docker", "docker_zip_default") check("docker", "docker_zip_default")
check("docker", "docker_zip_custom") check("docker", "docker_zip_custom")
check("docker", "docker_git_custom") check("docker", "docker_git_custom")

View File

@ -42,6 +42,42 @@ job "linux" {
} }
} }
task "rawexec_file_alloc_dots" {
artifact {
source = "https://raw.githubusercontent.com/hashicorp/go-set/main/go.mod"
destination = "../alloc/go.mod"
mode = "file"
}
driver = "raw_exec"
config {
command = "cat"
args = ["../alloc/go.mod"]
}
resources {
cpu = 16
memory = 32
disk = 64
}
}
task "rawexec_file_alloc_env" {
artifact {
source = "https://raw.githubusercontent.com/hashicorp/go-set/main/go.mod"
destination = "${NOMAD_ALLOC_DIR}/go.mod"
mode = "file"
}
driver = "raw_exec"
config {
command = "cat"
args = ["${NOMAD_ALLOC_DIR}/go.mod"]
}
resources {
cpu = 16
memory = 32
disk = 64
}
}
task "rawexec_zip_default" { task "rawexec_zip_default" {
artifact { artifact {
source = "https://github.com/hashicorp/go-set/archive/refs/heads/main.zip" source = "https://github.com/hashicorp/go-set/archive/refs/heads/main.zip"
@ -127,6 +163,24 @@ job "linux" {
} }
} }
task "exec_file_alloc" {
artifact {
source = "https://raw.githubusercontent.com/hashicorp/go-set/main/go.mod"
destination = "${NOMAD_ALLOC_DIR}/go.mod"
mode = "file"
}
driver = "exec"
config {
command = "cat"
args = ["${NOMAD_ALLOC_DIR}/go.mod"]
}
resources {
cpu = 16
memory = 32
disk = 64
}
}
task "exec_zip_default" { task "exec_zip_default" {
artifact { artifact {
source = "https://github.com/hashicorp/go-set/archive/refs/heads/main.zip" source = "https://github.com/hashicorp/go-set/archive/refs/heads/main.zip"
@ -212,6 +266,24 @@ job "linux" {
} }
} }
task "docker_file_alloc" {
artifact {
source = "https://raw.githubusercontent.com/hashicorp/go-set/main/go.mod"
destination = "${NOMAD_ALLOC_DIR}/go.mod"
mode = "file"
}
driver = "docker"
config {
image = "bash:5"
args = ["cat", "${NOMAD_ALLOC_DIR}/go.mod"]
}
resources {
cpu = 16
memory = 32
disk = 64
}
}
task "docker_zip_default" { task "docker_zip_default" {
artifact { artifact {
source = "https://github.com/hashicorp/go-set/archive/refs/heads/main.zip" source = "https://github.com/hashicorp/go-set/archive/refs/heads/main.zip"