open-nomad/client/allocrunner/taskrunner/getter/params.go
Seth Hoenig 2a7c7d85a5
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.
2023-01-09 09:46:32 -06:00

165 lines
4.5 KiB
Go

package getter
import (
"context"
"encoding/json"
"io"
"io/fs"
"strings"
"time"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/go-getter"
"github.com/hashicorp/nomad/helper"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)
// parameters is encoded by the Nomad client and decoded by the getter sub-process
// so it can know what to do. We use standard IO to pass configuration to achieve
// better control over input sanitization risks.
// e.g. https://www.opencve.io/cve/CVE-2022-41716
type parameters struct {
// Config
HTTPReadTimeout time.Duration `json:"http_read_timeout"`
HTTPMaxBytes int64 `json:"http_max_bytes"`
GCSTimeout time.Duration `json:"gcs_timeout"`
GitTimeout time.Duration `json:"git_timeout"`
HgTimeout time.Duration `json:"hg_timeout"`
S3Timeout time.Duration `json:"s3_timeout"`
DisableFilesystemIsolation bool `json:"disable_filesystem_isolation"`
SetEnvironmentVariables string `json:"set_environment_variables"`
// Artifact
Mode getter.ClientMode `json:"artifact_mode"`
Source string `json:"artifact_source"`
Destination string `json:"artifact_destination"`
Headers map[string][]string `json:"artifact_headers"`
// Task Filesystem
AllocDir string `json:"alloc_dir"`
TaskDir string `json:"task_dir"`
}
func (p *parameters) reader() io.Reader {
b, err := json.Marshal(p)
if err != nil {
b = nil
}
return strings.NewReader(string(b))
}
func (p *parameters) read(r io.Reader) error {
return json.NewDecoder(r).Decode(p)
}
// deadline returns an absolute deadline before the artifact download
// sub-process forcefully terminates. The default is 1/2 hour, unless one or
// more getter configurations is set higher. A 1 minute grace period is added
// so that an internal timeout has a moment to complete before the process is
// terminated via signal.
func (p *parameters) deadline() time.Duration {
const minimum = 30 * time.Minute
max := minimum
max = helper.Max(max, p.HTTPReadTimeout)
max = helper.Max(max, p.GCSTimeout)
max = helper.Max(max, p.GitTimeout)
max = helper.Max(max, p.HgTimeout)
max = helper.Max(max, p.S3Timeout)
return max + 1*time.Minute
}
// Equal returns whether p and o are the same.
func (p *parameters) Equal(o *parameters) bool {
if p == nil || o == nil {
return p == o
}
switch {
case p.HTTPReadTimeout != o.HTTPReadTimeout:
return false
case p.HTTPMaxBytes != o.HTTPMaxBytes:
return false
case p.GCSTimeout != o.GCSTimeout:
return false
case p.GitTimeout != o.GitTimeout:
return false
case p.HgTimeout != o.HgTimeout:
return false
case p.S3Timeout != o.S3Timeout:
return false
case p.DisableFilesystemIsolation != o.DisableFilesystemIsolation:
return false
case p.SetEnvironmentVariables != o.SetEnvironmentVariables:
return false
case p.Mode != o.Mode:
return false
case p.Source != o.Source:
return false
case p.Destination != o.Destination:
return false
case p.TaskDir != o.TaskDir:
return false
case !maps.EqualFunc(p.Headers, o.Headers, slices.Equal[string]):
return false
}
return true
}
const (
// stop privilege escalation via setuid/setgid
// https://github.com/hashicorp/nomad/issues/6176
umask = fs.ModeSetuid | fs.ModeSetgid
)
func (p *parameters) client(ctx context.Context) *getter.Client {
httpGetter := &getter.HttpGetter{
Netrc: true,
Client: cleanhttp.DefaultClient(),
Header: p.Headers,
// Do not support the custom X-Terraform-Get header and
// associated logic.
XTerraformGetDisabled: true,
// Disable HEAD requests as they can produce corrupt files when
// retrying a download of a resource that has changed.
// hashicorp/go-getter#219
DoNotCheckHeadFirst: true,
// Read timeout for HTTP operations. Must be long enough to
// accommodate large/slow downloads.
ReadTimeout: p.HTTPReadTimeout,
// Maximum download size. Must be large enough to accommodate
// large downloads.
MaxBytes: p.HTTPMaxBytes,
}
return &getter.Client{
Ctx: ctx,
Src: p.Source,
Dst: p.Destination,
Mode: p.Mode,
Umask: umask,
Insecure: false,
DisableSymlinks: true,
Getters: map[string]getter.Getter{
"git": &getter.GitGetter{
Timeout: p.GitTimeout,
},
"hg": &getter.HgGetter{
Timeout: p.HgTimeout,
},
"gcs": &getter.GCSGetter{
Timeout: p.GCSTimeout,
},
"s3": &getter.S3Getter{
Timeout: p.S3Timeout,
},
"http": httpGetter,
"https": httpGetter,
},
}
}