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:
parent
84cb5fb03d
commit
2a7c7d85a5
|
@ -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 {
|
||||||
|
|
|
@ -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 = ¶meters{
|
var paramsAsStruct = ¶meters{
|
||||||
|
@ -42,7 +43,8 @@ var paramsAsStruct = ¶meters{
|
||||||
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"},
|
||||||
},
|
},
|
||||||
|
|
|
@ -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 := ¶meters{
|
params := ¶meters{
|
||||||
// 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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue