func: Allow custom paths to be added the the getter landlock (#20349)

* func: Allow custom paths to be added the the getter landlock

Fixes: 20315

* fix: slices imports
fix: more meaningful examples
fix: improve documentation
fix: quote error output
This commit is contained in:
astudentofblake
2024-04-11 21:17:33 +01:00
committed by GitHub
parent 5612ab46c3
commit 7b7ed12326
14 changed files with 229 additions and 71 deletions

3
.changelog/20315.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:improvement
func: Allow custom paths to be added the the getter landlock
```

View File

@@ -14,6 +14,7 @@ import (
"time" "time"
"github.com/hashicorp/go-getter" "github.com/hashicorp/go-getter"
"github.com/hashicorp/nomad/helper"
) )
// parameters is encoded by the Nomad client and decoded by the getter sub-process // parameters is encoded by the Nomad client and decoded by the getter sub-process
@@ -22,16 +23,17 @@ import (
// e.g. https://www.opencve.io/cve/CVE-2022-41716 // e.g. https://www.opencve.io/cve/CVE-2022-41716
type parameters struct { type parameters struct {
// Config // Config
HTTPReadTimeout time.Duration `json:"http_read_timeout"` HTTPReadTimeout time.Duration `json:"http_read_timeout"`
HTTPMaxBytes int64 `json:"http_max_bytes"` HTTPMaxBytes int64 `json:"http_max_bytes"`
GCSTimeout time.Duration `json:"gcs_timeout"` GCSTimeout time.Duration `json:"gcs_timeout"`
GitTimeout time.Duration `json:"git_timeout"` GitTimeout time.Duration `json:"git_timeout"`
HgTimeout time.Duration `json:"hg_timeout"` HgTimeout time.Duration `json:"hg_timeout"`
S3Timeout time.Duration `json:"s3_timeout"` S3Timeout time.Duration `json:"s3_timeout"`
DecompressionLimitFileCount int `json:"decompression_limit_file_count"` DecompressionLimitFileCount int `json:"decompression_limit_file_count"`
DecompressionLimitSize int64 `json:"decompression_limit_size"` DecompressionLimitSize int64 `json:"decompression_limit_size"`
DisableFilesystemIsolation bool `json:"disable_filesystem_isolation"` DisableFilesystemIsolation bool `json:"disable_filesystem_isolation"`
SetEnvironmentVariables string `json:"set_environment_variables"` FilesystemIsolationExtraPaths []string `json:"filesystem_isolation_extra_paths"`
SetEnvironmentVariables string `json:"set_environment_variables"`
// Artifact // Artifact
Mode getter.ClientMode `json:"artifact_mode"` Mode getter.ClientMode `json:"artifact_mode"`
@@ -98,6 +100,8 @@ func (p *parameters) Equal(o *parameters) bool {
return false return false
case p.DisableFilesystemIsolation != o.DisableFilesystemIsolation: case p.DisableFilesystemIsolation != o.DisableFilesystemIsolation:
return false return false
case !helper.SliceSetEq(p.FilesystemIsolationExtraPaths, o.FilesystemIsolationExtraPaths):
return false
case p.SetEnvironmentVariables != o.SetEnvironmentVariables: case p.SetEnvironmentVariables != o.SetEnvironmentVariables:
return false return false
case p.Mode != o.Mode: case p.Mode != o.Mode:

View File

@@ -25,6 +25,11 @@ const paramsAsJSON = `
"decompression_limit_file_count": 3, "decompression_limit_file_count": 3,
"decompression_limit_size": 98765, "decompression_limit_size": 98765,
"disable_filesystem_isolation": true, "disable_filesystem_isolation": true,
"filesystem_isolation_extra_paths": [
"f:r:/dev/urandom",
"d:rx:/opt/bin",
"d:r:/tmp/stash"
],
"set_environment_variables": "", "set_environment_variables": "",
"artifact_mode": 2, "artifact_mode": 2,
"artifact_insecure": false, "artifact_insecure": false,
@@ -47,7 +52,11 @@ var paramsAsStruct = &parameters{
DecompressionLimitFileCount: 3, DecompressionLimitFileCount: 3,
DecompressionLimitSize: 98765, DecompressionLimitSize: 98765,
DisableFilesystemIsolation: true, DisableFilesystemIsolation: true,
FilesystemIsolationExtraPaths: []string{
"f:r:/dev/urandom",
"d:rx:/opt/bin",
"d:r:/tmp/stash",
},
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",

View File

@@ -44,16 +44,17 @@ func (s *Sandbox) Get(env interfaces.EnvReplacer, artifact *structs.TaskArtifact
params := &parameters{ params := &parameters{
// downloader configuration // downloader configuration
HTTPReadTimeout: s.ac.HTTPReadTimeout, HTTPReadTimeout: s.ac.HTTPReadTimeout,
HTTPMaxBytes: s.ac.HTTPMaxBytes, HTTPMaxBytes: s.ac.HTTPMaxBytes,
GCSTimeout: s.ac.GCSTimeout, GCSTimeout: s.ac.GCSTimeout,
GitTimeout: s.ac.GitTimeout, GitTimeout: s.ac.GitTimeout,
HgTimeout: s.ac.HgTimeout, HgTimeout: s.ac.HgTimeout,
S3Timeout: s.ac.S3Timeout, S3Timeout: s.ac.S3Timeout,
DecompressionLimitFileCount: s.ac.DecompressionLimitFileCount, DecompressionLimitFileCount: s.ac.DecompressionLimitFileCount,
DecompressionLimitSize: s.ac.DecompressionLimitSize, DecompressionLimitSize: s.ac.DecompressionLimitSize,
DisableFilesystemIsolation: s.ac.DisableFilesystemIsolation, DisableFilesystemIsolation: s.ac.DisableFilesystemIsolation,
SetEnvironmentVariables: s.ac.SetEnvironmentVariables, FilesystemIsolationExtraPaths: s.ac.FilesystemIsolationExtraPaths,
SetEnvironmentVariables: s.ac.SetEnvironmentVariables,
// artifact configuration // artifact configuration
Mode: mode, Mode: mode,

View File

@@ -10,7 +10,7 @@ import (
) )
// lockdown is not implemented by default // lockdown is not implemented by default
func lockdown(string, string) error { func lockdown(string, string, []string) error {
return nil return nil
} }

View File

@@ -51,7 +51,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(allocDir, taskDir string) error { func lockdown(allocDir, taskDir string, extra []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
@@ -68,6 +68,13 @@ func lockdown(allocDir, taskDir string) error {
landlock.Dir(taskDir, "rwc"), landlock.Dir(taskDir, "rwc"),
} }
for _, p := range extra {
path, err := landlock.ParsePath(p)
if err != nil {
return err
}
paths = append(paths, path)
}
paths = append(paths, additionalFilesForVCS()...) paths = append(paths, additionalFilesForVCS()...)
locker := landlock.New(paths...) locker := landlock.New(paths...)
return locker.Lock(landlock.Mandatory) return locker.Lock(landlock.Mandatory)

View File

@@ -11,7 +11,7 @@ import (
) )
// lockdown is not implemented on Windows // lockdown is not implemented on Windows
func lockdown(string, string) error { func lockdown(string, string, []string) error {
return nil return nil
} }

View File

@@ -34,7 +34,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.AllocDir, env.TaskDir); err != nil { if err := lockdown(env.AllocDir, env.TaskDir, env.FilesystemIsolationExtraPaths); 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

@@ -5,6 +5,7 @@ package config
import ( import (
"fmt" "fmt"
"slices"
"time" "time"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
@@ -25,8 +26,9 @@ type ArtifactConfig struct {
DecompressionLimitFileCount int DecompressionLimitFileCount int
DecompressionLimitSize int64 DecompressionLimitSize int64
DisableFilesystemIsolation bool DisableFilesystemIsolation bool
SetEnvironmentVariables string FilesystemIsolationExtraPaths []string
SetEnvironmentVariables string
} }
// ArtifactConfigFromAgent creates a new internal readonly copy of the client // ArtifactConfigFromAgent creates a new internal readonly copy of the client
@@ -68,17 +70,19 @@ func ArtifactConfigFromAgent(c *config.ArtifactConfig) (*ArtifactConfig, error)
} }
return &ArtifactConfig{ return &ArtifactConfig{
HTTPReadTimeout: httpReadTimeout, HTTPReadTimeout: httpReadTimeout,
HTTPMaxBytes: int64(httpMaxSize), HTTPMaxBytes: int64(httpMaxSize),
GCSTimeout: gcsTimeout, GCSTimeout: gcsTimeout,
GitTimeout: gitTimeout, GitTimeout: gitTimeout,
HgTimeout: hgTimeout, HgTimeout: hgTimeout,
S3Timeout: s3Timeout, S3Timeout: s3Timeout,
DecompressionLimitFileCount: *c.DecompressionFileCountLimit, DecompressionLimitFileCount: *c.DecompressionFileCountLimit,
DecompressionLimitSize: int64(decompressionSizeLimit), DecompressionLimitSize: int64(decompressionSizeLimit),
DisableFilesystemIsolation: *c.DisableFilesystemIsolation, DisableFilesystemIsolation: *c.DisableFilesystemIsolation,
SetEnvironmentVariables: *c.SetEnvironmentVariables, FilesystemIsolationExtraPaths: slices.Clone(c.FilesystemIsolationExtraPaths),
SetEnvironmentVariables: *c.SetEnvironmentVariables,
}, nil }, nil
} }
func (a *ArtifactConfig) Copy() *ArtifactConfig { func (a *ArtifactConfig) Copy() *ArtifactConfig {

View File

@@ -129,14 +129,15 @@ func TestArtifactConfig_Copy(t *testing.T) {
ci.Parallel(t) ci.Parallel(t)
ac := &ArtifactConfig{ ac := &ArtifactConfig{
HTTPReadTimeout: time.Minute, HTTPReadTimeout: time.Minute,
HTTPMaxBytes: 1000, HTTPMaxBytes: 1000,
GCSTimeout: 2 * time.Minute, GCSTimeout: 2 * time.Minute,
GitTimeout: time.Second, GitTimeout: time.Second,
HgTimeout: time.Hour, HgTimeout: time.Hour,
S3Timeout: 5 * time.Minute, S3Timeout: 5 * time.Minute,
DisableFilesystemIsolation: true, DisableFilesystemIsolation: true,
SetEnvironmentVariables: "FOO,BAR", FilesystemIsolationExtraPaths: []string{"f:r:/dev/urandom"},
SetEnvironmentVariables: "FOO,BAR",
} }
// make sure values are copied. // make sure values are copied.
@@ -151,16 +152,18 @@ func TestArtifactConfig_Copy(t *testing.T) {
configCopy.HgTimeout = 2 * time.Hour configCopy.HgTimeout = 2 * time.Hour
configCopy.S3Timeout = 10 * time.Minute configCopy.S3Timeout = 10 * time.Minute
configCopy.DisableFilesystemIsolation = false configCopy.DisableFilesystemIsolation = false
configCopy.FilesystemIsolationExtraPaths = []string{"f:rx:/opt/bin/runme"}
configCopy.SetEnvironmentVariables = "BAZ" configCopy.SetEnvironmentVariables = "BAZ"
must.Eq(t, &ArtifactConfig{ must.Eq(t, &ArtifactConfig{
HTTPReadTimeout: time.Minute, HTTPReadTimeout: time.Minute,
HTTPMaxBytes: 1000, HTTPMaxBytes: 1000,
GCSTimeout: 2 * time.Minute, GCSTimeout: 2 * time.Minute,
GitTimeout: time.Second, GitTimeout: time.Second,
HgTimeout: time.Hour, HgTimeout: time.Hour,
S3Timeout: 5 * time.Minute, S3Timeout: 5 * time.Minute,
DisableFilesystemIsolation: true, DisableFilesystemIsolation: true,
SetEnvironmentVariables: "FOO,BAR", FilesystemIsolationExtraPaths: []string{"f:r:/dev/urandom"},
SetEnvironmentVariables: "FOO,BAR",
}, ac) }, ac)
} }

View File

@@ -898,6 +898,7 @@ func convertClientConfig(agentConfig *Config) (*clientconfig.Config, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid artifact config: %v", err) return nil, fmt.Errorf("invalid artifact config: %v", err)
} }
conf.Artifact = artifactConfig conf.Artifact = artifactConfig
drainConfig, err := clientconfig.DrainConfigFromAgent(agentConfig.Client.Drain) drainConfig, err := clientconfig.DrainConfigFromAgent(agentConfig.Client.Drain)

View File

@@ -6,10 +6,13 @@ package config
import ( import (
"fmt" "fmt"
"math" "math"
"slices"
"time" "time"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/helper/pointer" "github.com/hashicorp/nomad/helper/pointer"
"github.com/shoenig/go-landlock"
) )
// ArtifactConfig is the configuration specific to the Artifact block // ArtifactConfig is the configuration specific to the Artifact block
@@ -55,6 +58,10 @@ type ArtifactConfig struct {
// read only from specific locations on the host filesystem. // read only from specific locations on the host filesystem.
DisableFilesystemIsolation *bool `hcl:"disable_filesystem_isolation"` DisableFilesystemIsolation *bool `hcl:"disable_filesystem_isolation"`
// FilesystemIsolationExtraPaths allows extra paths to be included in
// the sandbox used by the artifact downloader
FilesystemIsolationExtraPaths []string `hcl:"filesystem_isolation_extra_paths"`
// SetEnvironmentVariables is a comma-separated list of environment // SetEnvironmentVariables is a comma-separated list of environment
// variable names to inherit from the Nomad Client and set in the artifact // variable names to inherit from the Nomad Client and set in the artifact
// download sandbox process. // download sandbox process.
@@ -66,16 +73,17 @@ func (a *ArtifactConfig) Copy() *ArtifactConfig {
return nil return nil
} }
return &ArtifactConfig{ return &ArtifactConfig{
HTTPReadTimeout: pointer.Copy(a.HTTPReadTimeout), HTTPReadTimeout: pointer.Copy(a.HTTPReadTimeout),
HTTPMaxSize: pointer.Copy(a.HTTPMaxSize), HTTPMaxSize: pointer.Copy(a.HTTPMaxSize),
GCSTimeout: pointer.Copy(a.GCSTimeout), GCSTimeout: pointer.Copy(a.GCSTimeout),
GitTimeout: pointer.Copy(a.GitTimeout), GitTimeout: pointer.Copy(a.GitTimeout),
HgTimeout: pointer.Copy(a.HgTimeout), HgTimeout: pointer.Copy(a.HgTimeout),
S3Timeout: pointer.Copy(a.S3Timeout), S3Timeout: pointer.Copy(a.S3Timeout),
DecompressionFileCountLimit: pointer.Copy(a.DecompressionFileCountLimit), DecompressionFileCountLimit: pointer.Copy(a.DecompressionFileCountLimit),
DecompressionSizeLimit: pointer.Copy(a.DecompressionSizeLimit), DecompressionSizeLimit: pointer.Copy(a.DecompressionSizeLimit),
DisableFilesystemIsolation: pointer.Copy(a.DisableFilesystemIsolation), DisableFilesystemIsolation: pointer.Copy(a.DisableFilesystemIsolation),
SetEnvironmentVariables: pointer.Copy(a.SetEnvironmentVariables), FilesystemIsolationExtraPaths: slices.Clone(a.FilesystemIsolationExtraPaths),
SetEnvironmentVariables: pointer.Copy(a.SetEnvironmentVariables),
} }
} }
@@ -86,7 +94,7 @@ func (a *ArtifactConfig) Merge(o *ArtifactConfig) *ArtifactConfig {
case o == nil: case o == nil:
return a.Copy() return a.Copy()
default: default:
return &ArtifactConfig{ result := &ArtifactConfig{
HTTPReadTimeout: pointer.Merge(a.HTTPReadTimeout, o.HTTPReadTimeout), HTTPReadTimeout: pointer.Merge(a.HTTPReadTimeout, o.HTTPReadTimeout),
HTTPMaxSize: pointer.Merge(a.HTTPMaxSize, o.HTTPMaxSize), HTTPMaxSize: pointer.Merge(a.HTTPMaxSize, o.HTTPMaxSize),
GCSTimeout: pointer.Merge(a.GCSTimeout, o.GCSTimeout), GCSTimeout: pointer.Merge(a.GCSTimeout, o.GCSTimeout),
@@ -98,6 +106,14 @@ func (a *ArtifactConfig) Merge(o *ArtifactConfig) *ArtifactConfig {
DisableFilesystemIsolation: pointer.Merge(a.DisableFilesystemIsolation, o.DisableFilesystemIsolation), DisableFilesystemIsolation: pointer.Merge(a.DisableFilesystemIsolation, o.DisableFilesystemIsolation),
SetEnvironmentVariables: pointer.Merge(a.SetEnvironmentVariables, o.SetEnvironmentVariables), SetEnvironmentVariables: pointer.Merge(a.SetEnvironmentVariables, o.SetEnvironmentVariables),
} }
if o.FilesystemIsolationExtraPaths != nil {
result.FilesystemIsolationExtraPaths = slices.Clone(o.FilesystemIsolationExtraPaths)
} else {
result.FilesystemIsolationExtraPaths = slices.Clone(a.FilesystemIsolationExtraPaths)
}
return result
} }
} }
@@ -124,6 +140,8 @@ func (a *ArtifactConfig) Equal(o *ArtifactConfig) bool {
return false return false
case !pointer.Eq(a.DisableFilesystemIsolation, o.DisableFilesystemIsolation): case !pointer.Eq(a.DisableFilesystemIsolation, o.DisableFilesystemIsolation):
return false return false
case !helper.SliceSetEq(a.FilesystemIsolationExtraPaths, o.FilesystemIsolationExtraPaths):
return false
case !pointer.Eq(a.SetEnvironmentVariables, o.SetEnvironmentVariables): case !pointer.Eq(a.SetEnvironmentVariables, o.SetEnvironmentVariables):
return false return false
} }
@@ -209,6 +227,12 @@ func (a *ArtifactConfig) Validate() error {
return fmt.Errorf("disable_filesystem_isolation must be set") return fmt.Errorf("disable_filesystem_isolation must be set")
} }
for _, p := range a.FilesystemIsolationExtraPaths {
if _, err := landlock.ParsePath(p); err != nil {
return fmt.Errorf("filesystem_isolation_extra_paths contains invalid lockdown path %q", p)
}
}
if a.SetEnvironmentVariables == nil { if a.SetEnvironmentVariables == nil {
return fmt.Errorf("set_environment_variables must be set") return fmt.Errorf("set_environment_variables must be set")
} }
@@ -254,6 +278,9 @@ func DefaultArtifactConfig() *ArtifactConfig {
// Toggle for disabling filesystem isolation, where available. // Toggle for disabling filesystem isolation, where available.
DisableFilesystemIsolation: pointer.Of(false), DisableFilesystemIsolation: pointer.Of(false),
// No Filesystem Isolation Extra Locations by default
FilesystemIsolationExtraPaths: nil,
// No environment variables are inherited from Client by default. // No environment variables are inherited from Client by default.
SetEnvironmentVariables: pointer.Of(""), SetEnvironmentVariables: pointer.Of(""),
} }

View File

@@ -15,6 +15,11 @@ func TestArtifactConfig_Copy(t *testing.T) {
ci.Parallel(t) ci.Parallel(t)
a := DefaultArtifactConfig() a := DefaultArtifactConfig()
a.FilesystemIsolationExtraPaths = []string{
"f:r:/dev/urandom",
"d:rx:/opt/bin",
"d:r:/tmp/stash",
}
b := a.Copy() b := a.Copy()
must.Equal(t, a, b) must.Equal(t, a, b)
must.Equal(t, b, a) must.Equal(t, b, a)
@@ -26,6 +31,10 @@ func TestArtifactConfig_Copy(t *testing.T) {
b.DecompressionFileCountLimit = pointer.Of(7) b.DecompressionFileCountLimit = pointer.Of(7)
b.DecompressionSizeLimit = pointer.Of("2GB") b.DecompressionSizeLimit = pointer.Of("2GB")
must.NotEqual(t, a, b) must.NotEqual(t, a, b)
b = a.Copy()
b.FilesystemIsolationExtraPaths[1] = "f:rx:/opt/bin/runme"
must.NotEqual(t, a, b)
} }
func TestArtifactConfig_Merge(t *testing.T) { func TestArtifactConfig_Merge(t *testing.T) {
@@ -49,7 +58,12 @@ func TestArtifactConfig_Merge(t *testing.T) {
DecompressionFileCountLimit: pointer.Of(4096), DecompressionFileCountLimit: pointer.Of(4096),
DecompressionSizeLimit: pointer.Of("100GB"), DecompressionSizeLimit: pointer.Of("100GB"),
DisableFilesystemIsolation: pointer.Of(false), DisableFilesystemIsolation: pointer.Of(false),
SetEnvironmentVariables: pointer.Of(""), FilesystemIsolationExtraPaths: []string{
"f:r:/dev/urandom",
"d:rx:/opt/bin",
"d:r:/tmp/stash",
},
SetEnvironmentVariables: pointer.Of(""),
}, },
other: &ArtifactConfig{ other: &ArtifactConfig{
HTTPReadTimeout: pointer.Of("5m"), HTTPReadTimeout: pointer.Of("5m"),
@@ -61,7 +75,11 @@ func TestArtifactConfig_Merge(t *testing.T) {
DecompressionFileCountLimit: pointer.Of(100), DecompressionFileCountLimit: pointer.Of(100),
DecompressionSizeLimit: pointer.Of("8GB"), DecompressionSizeLimit: pointer.Of("8GB"),
DisableFilesystemIsolation: pointer.Of(true), DisableFilesystemIsolation: pointer.Of(true),
SetEnvironmentVariables: pointer.Of("FOO,BAR"), FilesystemIsolationExtraPaths: []string{
"d:rw:/opt/certs",
"f:rx:/opt/bin/runme",
},
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
}, },
expected: &ArtifactConfig{ expected: &ArtifactConfig{
HTTPReadTimeout: pointer.Of("5m"), HTTPReadTimeout: pointer.Of("5m"),
@@ -73,7 +91,11 @@ func TestArtifactConfig_Merge(t *testing.T) {
DecompressionFileCountLimit: pointer.Of(100), DecompressionFileCountLimit: pointer.Of(100),
DecompressionSizeLimit: pointer.Of("8GB"), DecompressionSizeLimit: pointer.Of("8GB"),
DisableFilesystemIsolation: pointer.Of(true), DisableFilesystemIsolation: pointer.Of(true),
SetEnvironmentVariables: pointer.Of("FOO,BAR"), FilesystemIsolationExtraPaths: []string{
"d:rw:/opt/certs",
"f:rx:/opt/bin/runme",
},
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
}, },
}, },
{ {
@@ -89,7 +111,11 @@ func TestArtifactConfig_Merge(t *testing.T) {
DecompressionFileCountLimit: pointer.Of(100), DecompressionFileCountLimit: pointer.Of(100),
DecompressionSizeLimit: pointer.Of("8GB"), DecompressionSizeLimit: pointer.Of("8GB"),
DisableFilesystemIsolation: pointer.Of(true), DisableFilesystemIsolation: pointer.Of(true),
SetEnvironmentVariables: pointer.Of("FOO,BAR"), FilesystemIsolationExtraPaths: []string{
"d:rw:/opt/certs",
"f:rx:/opt/bin/runme",
},
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
}, },
expected: &ArtifactConfig{ expected: &ArtifactConfig{
HTTPReadTimeout: pointer.Of("5m"), HTTPReadTimeout: pointer.Of("5m"),
@@ -101,7 +127,11 @@ func TestArtifactConfig_Merge(t *testing.T) {
DecompressionFileCountLimit: pointer.Of(100), DecompressionFileCountLimit: pointer.Of(100),
DecompressionSizeLimit: pointer.Of("8GB"), DecompressionSizeLimit: pointer.Of("8GB"),
DisableFilesystemIsolation: pointer.Of(true), DisableFilesystemIsolation: pointer.Of(true),
SetEnvironmentVariables: pointer.Of("FOO,BAR"), FilesystemIsolationExtraPaths: []string{
"d:rw:/opt/certs",
"f:rx:/opt/bin/runme",
},
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
}, },
}, },
{ {
@@ -116,7 +146,12 @@ func TestArtifactConfig_Merge(t *testing.T) {
DecompressionFileCountLimit: pointer.Of(4096), DecompressionFileCountLimit: pointer.Of(4096),
DecompressionSizeLimit: pointer.Of("100GB"), DecompressionSizeLimit: pointer.Of("100GB"),
DisableFilesystemIsolation: pointer.Of(true), DisableFilesystemIsolation: pointer.Of(true),
SetEnvironmentVariables: pointer.Of("FOO,BAR"), FilesystemIsolationExtraPaths: []string{
"f:r:/dev/urandom",
"d:rx:/opt/bin",
"d:r:/tmp/stash",
},
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
}, },
other: nil, other: nil,
expected: &ArtifactConfig{ expected: &ArtifactConfig{
@@ -129,7 +164,54 @@ func TestArtifactConfig_Merge(t *testing.T) {
DecompressionFileCountLimit: pointer.Of(4096), DecompressionFileCountLimit: pointer.Of(4096),
DecompressionSizeLimit: pointer.Of("100GB"), DecompressionSizeLimit: pointer.Of("100GB"),
DisableFilesystemIsolation: pointer.Of(true), DisableFilesystemIsolation: pointer.Of(true),
SetEnvironmentVariables: pointer.Of("FOO,BAR"), FilesystemIsolationExtraPaths: []string{
"f:r:/dev/urandom",
"d:rx:/opt/bin",
"d:r:/tmp/stash",
},
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
},
},
{
name: "null fsIsolationLocation",
source: &ArtifactConfig{
HTTPReadTimeout: pointer.Of("30m"),
HTTPMaxSize: pointer.Of("100GB"),
GCSTimeout: pointer.Of("30m"),
GitTimeout: pointer.Of("30m"),
HgTimeout: pointer.Of("30m"),
S3Timeout: pointer.Of("30m"),
DecompressionFileCountLimit: pointer.Of(4096),
DecompressionSizeLimit: pointer.Of("100GB"),
DisableFilesystemIsolation: pointer.Of(false),
FilesystemIsolationExtraPaths: nil,
SetEnvironmentVariables: pointer.Of(""),
},
other: &ArtifactConfig{
HTTPReadTimeout: pointer.Of("5m"),
HTTPMaxSize: pointer.Of("2GB"),
GCSTimeout: pointer.Of("1m"),
GitTimeout: pointer.Of("2m"),
HgTimeout: pointer.Of("3m"),
S3Timeout: pointer.Of("4m"),
DecompressionFileCountLimit: pointer.Of(100),
DecompressionSizeLimit: pointer.Of("8GB"),
DisableFilesystemIsolation: pointer.Of(true),
FilesystemIsolationExtraPaths: nil,
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
},
expected: &ArtifactConfig{
HTTPReadTimeout: pointer.Of("5m"),
HTTPMaxSize: pointer.Of("2GB"),
GCSTimeout: pointer.Of("1m"),
GitTimeout: pointer.Of("2m"),
HgTimeout: pointer.Of("3m"),
S3Timeout: pointer.Of("4m"),
DecompressionFileCountLimit: pointer.Of(100),
DecompressionSizeLimit: pointer.Of("8GB"),
DisableFilesystemIsolation: pointer.Of(true),
FilesystemIsolationExtraPaths: nil,
SetEnvironmentVariables: pointer.Of("FOO,BAR"),
}, },
}, },
} }
@@ -400,6 +482,16 @@ func TestArtifactConfig_Validate(t *testing.T) {
}, },
expErr: "disable_filesystem_isolation must be set", expErr: "disable_filesystem_isolation must be set",
}, },
{
name: "fs isolation extra paths contains invalid path",
config: func(a *ArtifactConfig) {
a.FilesystemIsolationExtraPaths = []string{
"f:r:/dev/urandom",
"failure",
}
},
expErr: "filesystem_isolation_extra_paths contains invalid lockdown path \"failure\"",
},
{ {
name: "env not set", name: "env not set",
config: func(a *ArtifactConfig) { config: func(a *ArtifactConfig) {

View File

@@ -436,6 +436,13 @@ see the [drivers documentation](/nomad/docs/drivers).
isolation should be disabled for artifact downloads. Applies only to systems isolation should be disabled for artifact downloads. Applies only to systems
where filesystem isolation via [landlock] is possible (Linux kernel 5.13+). where filesystem isolation via [landlock] is possible (Linux kernel 5.13+).
- `filesystem_isolation_extra_paths` `([]string: nil)` - Allow extra paths
in the filesystem isolation. Paths are specified in the form `[kind]:[mode]:[path]`
where `kind` must be either `f` or `d` (file or directory) and
`mode` must be zero or more of `r`, `w`, `c`, `x` (read, write, create, execute) e.g.
`f:r:/dev/urandom` would enable reading the /dev/urandom file,
`d:rx:/opt/bin` would enable reading and executing from the /opt/bin directory
- `set_environment_variables` `(string:"")` - Specifies a comma separated list - `set_environment_variables` `(string:"")` - Specifies a comma separated list
of environment variables that should be inherited by the artifact sandbox from of environment variables that should be inherited by the artifact sandbox from
the Nomad client's environment. By default a minimal environment is set including the Nomad client's environment. By default a minimal environment is set including