mirror of
https://github.com/kemko/nomad.git
synced 2026-01-07 19:05:42 +03:00
This adds artifact inspection after download to detect any issues with the content fetched. Currently this means checking for any symlinks within the artifact that resolve outside the task or allocation directories. On platforms where lockdown is available (some Linux) this inspection is not performed. The inspection can be disabled with the DisableArtifactInspection option. A dedicated option for disabling this behavior allows the DisableFilesystemIsolation option to be enabled but still have artifacts inspected after download.
290 lines
6.7 KiB
Go
290 lines
6.7 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package getter
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/go-getter"
|
|
"github.com/hashicorp/nomad/ci"
|
|
"github.com/hashicorp/nomad/client/testutil"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
"github.com/mitchellh/go-homedir"
|
|
"github.com/shoenig/test/must"
|
|
)
|
|
|
|
func TestUtil_getURL(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
name string
|
|
artifact *structs.TaskArtifact
|
|
expURL string
|
|
expErr *Error
|
|
}{{
|
|
name: "basic http",
|
|
artifact: &structs.TaskArtifact{GetterSource: "example.com"},
|
|
expURL: "example.com",
|
|
expErr: nil,
|
|
}, {
|
|
name: "bad url",
|
|
artifact: &structs.TaskArtifact{GetterSource: "::example.com"},
|
|
expURL: "",
|
|
expErr: &Error{
|
|
URL: "::example.com",
|
|
Err: errors.New(`failed to parse source URL "::example.com": parse "::example.com": missing protocol scheme`),
|
|
Recoverable: false,
|
|
},
|
|
}, {
|
|
name: "option",
|
|
artifact: &structs.TaskArtifact{
|
|
GetterSource: "git::github.com/hashicorp/nomad",
|
|
GetterOptions: map[string]string{"sshkey": "abc123"},
|
|
},
|
|
expURL: "git::github.com/hashicorp/nomad?sshkey=abc123",
|
|
expErr: nil,
|
|
}, {
|
|
name: "github case",
|
|
artifact: &structs.TaskArtifact{
|
|
GetterSource: "git@github.com:hashicorp/nomad.git",
|
|
GetterOptions: map[string]string{"sshkey": "abc123"},
|
|
},
|
|
expURL: "git@github.com:hashicorp/nomad.git?sshkey=abc123",
|
|
expErr: nil,
|
|
}}
|
|
|
|
env := noopTaskEnv("/path/to/task")
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
result, err := getURL(env, tc.artifact)
|
|
err2, _ := err.(*Error)
|
|
must.Equal(t, tc.expErr, err2)
|
|
must.Eq(t, tc.expURL, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUtil_getDestination(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
env := noopTaskEnv("/path/to/task")
|
|
t.Run("ok", func(t *testing.T) {
|
|
result, err := getDestination(env, &structs.TaskArtifact{
|
|
RelativeDest: "local/downloads",
|
|
})
|
|
must.NoError(t, err)
|
|
must.Eq(t, "/path/to/task/local/downloads", result)
|
|
})
|
|
|
|
t.Run("escapes", func(t *testing.T) {
|
|
result, err := getDestination(env, &structs.TaskArtifact{
|
|
RelativeDest: "../../../../../../../etc",
|
|
})
|
|
must.EqError(t, err, "artifact destination path escapes alloc directory")
|
|
must.Eq(t, "", result)
|
|
})
|
|
}
|
|
|
|
func TestUtil_getMode(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
cases := []struct {
|
|
mode string
|
|
exp getter.ClientMode
|
|
}{
|
|
{mode: structs.GetterModeFile, exp: getter.ClientModeFile},
|
|
{mode: structs.GetterModeDir, exp: getter.ClientModeDir},
|
|
{mode: structs.GetterModeAny, exp: getter.ClientModeAny},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.mode, func(t *testing.T) {
|
|
artifact := &structs.TaskArtifact{GetterMode: tc.mode}
|
|
result := getMode(artifact)
|
|
must.Eq(t, tc.exp, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUtil_getHeaders(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
env := upTaskEnv("/path/to/task")
|
|
|
|
t.Run("empty", func(t *testing.T) {
|
|
result := getHeaders(env, &structs.TaskArtifact{
|
|
GetterHeaders: nil,
|
|
})
|
|
must.Nil(t, result)
|
|
})
|
|
|
|
t.Run("replacements", func(t *testing.T) {
|
|
result := getHeaders(env, &structs.TaskArtifact{
|
|
GetterHeaders: map[string]string{
|
|
"color": "red",
|
|
"number": "six",
|
|
},
|
|
})
|
|
must.MapEq(t, map[string][]string{
|
|
"Color": {"RED"},
|
|
"Number": {"SIX"},
|
|
}, result)
|
|
})
|
|
}
|
|
|
|
func TestUtil_getTaskDir(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
env := noopTaskEnv("/path/to/alloc/task")
|
|
allocDir, taskDir := getWritableDirs(env)
|
|
must.Eq(t, "/path/to/alloc", allocDir)
|
|
must.Eq(t, "/path/to/alloc/task", taskDir)
|
|
}
|
|
|
|
func TestUtil_environment(t *testing.T) {
|
|
// not parallel
|
|
|
|
testutil.RequireLinux(t)
|
|
|
|
homedir.DisableCache = true
|
|
t.Cleanup(func() {
|
|
homedir.DisableCache = false
|
|
})
|
|
|
|
t.Run("default", func(t *testing.T) {
|
|
t.Setenv("HOME", "/test")
|
|
result := environment("/a/b/c", "")
|
|
must.Eq(t, []string{
|
|
"HOME=/test",
|
|
"PATH=/usr/local/bin:/usr/bin:/bin",
|
|
"TMPDIR=/a/b/c/tmp",
|
|
}, result)
|
|
})
|
|
|
|
t.Run("append", func(t *testing.T) {
|
|
t.Setenv("HOME", "/test")
|
|
t.Setenv("ONE", "1")
|
|
t.Setenv("TWO", "2")
|
|
result := environment("/a/b/c", "ONE,TWO")
|
|
must.Eq(t, []string{
|
|
"HOME=/test",
|
|
"ONE=1",
|
|
"PATH=/usr/local/bin:/usr/bin:/bin",
|
|
"TMPDIR=/a/b/c/tmp",
|
|
"TWO=2",
|
|
}, result)
|
|
})
|
|
|
|
t.Run("override", func(t *testing.T) {
|
|
t.Setenv("HOME", "/test")
|
|
t.Setenv("PATH", "/opt/bin")
|
|
t.Setenv("TMPDIR", "/scratch")
|
|
result := environment("/a/b/c", "PATH,TMPDIR")
|
|
must.Eq(t, []string{
|
|
"HOME=/test",
|
|
"PATH=/opt/bin",
|
|
"TMPDIR=/scratch",
|
|
}, result)
|
|
})
|
|
|
|
t.Run("missing", func(t *testing.T) {
|
|
t.Setenv("HOME", "/test")
|
|
result := environment("/a/b/c", "DOES_NOT_EXIST")
|
|
must.Eq(t, []string{
|
|
"DOES_NOT_EXIST=",
|
|
"HOME=/test",
|
|
"PATH=/usr/local/bin:/usr/bin:/bin",
|
|
"TMPDIR=/a/b/c/tmp",
|
|
}, result)
|
|
})
|
|
|
|
t.Run("homeless non-root", func(t *testing.T) {
|
|
testutil.RequireNonRoot(t)
|
|
|
|
// assert we fallback via go-homdir ...
|
|
userHome, err := homedir.Dir()
|
|
must.NoError(t, err)
|
|
|
|
// ... when HOME env var is not set, as is the case in some systemd setups
|
|
t.Setenv("HOME", "")
|
|
|
|
result := environment("/a/b/c", "")
|
|
must.Eq(t, []string{
|
|
fmt.Sprintf("HOME=%s", userHome),
|
|
"PATH=/usr/local/bin:/usr/bin:/bin",
|
|
"TMPDIR=/a/b/c/tmp",
|
|
}, result)
|
|
})
|
|
|
|
t.Run("homeless root", func(t *testing.T) {
|
|
testutil.RequireRoot(t)
|
|
|
|
t.Setenv("HOME", "/root") // fake running as full root
|
|
|
|
// assert we fallback via go-homdir ...
|
|
userHome, err := homedir.Dir()
|
|
must.NoError(t, err)
|
|
|
|
// ... when HOME env var is not set, as is the case in some systemd setups
|
|
t.Setenv("HOME", "")
|
|
|
|
result := environment("/a/b/c", "")
|
|
must.Eq(t, []string{
|
|
fmt.Sprintf("HOME=%s", userHome),
|
|
"PATH=/usr/local/bin:/usr/bin:/bin",
|
|
"TMPDIR=/a/b/c/tmp",
|
|
}, result)
|
|
})
|
|
}
|
|
|
|
func TestUtil_isPathWithin(t *testing.T) {
|
|
tdir := t.TempDir()
|
|
pathFn := func(parent string) string {
|
|
dir, err := os.MkdirTemp(parent, "testing-path")
|
|
must.NoError(t, err, must.Sprint("failed to create temporary directory"))
|
|
return dir
|
|
}
|
|
|
|
t.Run("when path not within root", func(t *testing.T) {
|
|
root := pathFn(tdir)
|
|
check := pathFn(tdir)
|
|
result, err := isPathWithin(root, check)
|
|
|
|
must.NoError(t, err)
|
|
must.False(t, result)
|
|
})
|
|
|
|
t.Run("when path within root", func(t *testing.T) {
|
|
root := pathFn(tdir)
|
|
check := pathFn(root)
|
|
result, err := isPathWithin(root, check)
|
|
|
|
must.NoError(t, err)
|
|
must.True(t, result)
|
|
})
|
|
|
|
t.Run("when root within path", func(t *testing.T) {
|
|
check := pathFn(tdir)
|
|
root := pathFn(check)
|
|
result, err := isPathWithin(root, check)
|
|
|
|
must.NoError(t, err)
|
|
must.False(t, result)
|
|
})
|
|
|
|
t.Run("when path does not exist", func(t *testing.T) {
|
|
root := filepath.Join(tdir, "missing")
|
|
check := filepath.Join(root, "unknown")
|
|
result, err := isPathWithin(root, check)
|
|
|
|
must.ErrorContains(t, err, "no such file or directory")
|
|
must.False(t, result)
|
|
})
|
|
}
|