2965dc6a1a
Fix numerous go-getter security issues: - Add timeouts to http, git, and hg operations to prevent DoS - Add size limit to http to prevent resource exhaustion - Disable following symlinks in both artifacts and `job run` - Stop performing initial HEAD request to avoid file corruption on retries and DoS opportunities. **Approach** Since Nomad has no ability to differentiate a DoS-via-large-artifact vs a legitimate workload, all of the new limits are configurable at the client agent level. The max size of HTTP downloads is also exposed as a node attribute so that if some workloads have large artifacts they can specify a high limit in their jobspecs. In the future all of this plumbing could be extended to enable/disable specific getters or artifact downloading entirely on a per-node basis.
187 lines
5.1 KiB
Go
187 lines
5.1 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"time"
|
|
|
|
"github.com/dustin/go-humanize"
|
|
"github.com/hashicorp/nomad/helper"
|
|
)
|
|
|
|
// ArtifactConfig is the configuration specific to the Artifact stanza
|
|
type ArtifactConfig struct {
|
|
// HTTPReadTimeout is the duration in which a download must complete or
|
|
// it will be canceled. Defaults to 30m.
|
|
HTTPReadTimeout *string `hcl:"http_read_timeout"`
|
|
|
|
// HTTPMaxSize is the maximum size of an artifact that will be downloaded.
|
|
// Defaults to 100GB.
|
|
HTTPMaxSize *string `hcl:"http_max_size"`
|
|
|
|
// GCSTimeout is the duration in which a GCS operation must complete or
|
|
// it will be canceled. Defaults to 30m.
|
|
GCSTimeout *string `hcl:"gcs_timeout"`
|
|
|
|
// GitTimeout is the duration in which a git operation must complete or
|
|
// it will be canceled. Defaults to 30m.
|
|
GitTimeout *string `hcl:"git_timeout"`
|
|
|
|
// HgTimeout is the duration in which an hg operation must complete or
|
|
// it will be canceled. Defaults to 30m.
|
|
HgTimeout *string `hcl:"hg_timeout"`
|
|
|
|
// S3Timeout is the duration in which an S3 operation must complete or
|
|
// it will be canceled. Defaults to 30m.
|
|
S3Timeout *string `hcl:"s3_timeout"`
|
|
}
|
|
|
|
func (a *ArtifactConfig) Copy() *ArtifactConfig {
|
|
if a == nil {
|
|
return nil
|
|
}
|
|
|
|
newCopy := &ArtifactConfig{}
|
|
if a.HTTPReadTimeout != nil {
|
|
newCopy.HTTPReadTimeout = helper.StringToPtr(*a.HTTPReadTimeout)
|
|
}
|
|
if a.HTTPMaxSize != nil {
|
|
newCopy.HTTPMaxSize = helper.StringToPtr(*a.HTTPMaxSize)
|
|
}
|
|
if a.GCSTimeout != nil {
|
|
newCopy.GCSTimeout = helper.StringToPtr(*a.GCSTimeout)
|
|
}
|
|
if a.GitTimeout != nil {
|
|
newCopy.GitTimeout = helper.StringToPtr(*a.GitTimeout)
|
|
}
|
|
if a.HgTimeout != nil {
|
|
newCopy.HgTimeout = helper.StringToPtr(*a.HgTimeout)
|
|
}
|
|
if a.S3Timeout != nil {
|
|
newCopy.S3Timeout = helper.StringToPtr(*a.S3Timeout)
|
|
}
|
|
|
|
return newCopy
|
|
}
|
|
|
|
func (a *ArtifactConfig) Merge(o *ArtifactConfig) *ArtifactConfig {
|
|
if a == nil {
|
|
return o.Copy()
|
|
}
|
|
if o == nil {
|
|
return a.Copy()
|
|
}
|
|
|
|
newCopy := a.Copy()
|
|
if o.HTTPReadTimeout != nil {
|
|
newCopy.HTTPReadTimeout = helper.StringToPtr(*o.HTTPReadTimeout)
|
|
}
|
|
if o.HTTPMaxSize != nil {
|
|
newCopy.HTTPMaxSize = helper.StringToPtr(*o.HTTPMaxSize)
|
|
}
|
|
if o.GCSTimeout != nil {
|
|
newCopy.GCSTimeout = helper.StringToPtr(*o.GCSTimeout)
|
|
}
|
|
if o.GitTimeout != nil {
|
|
newCopy.GitTimeout = helper.StringToPtr(*o.GitTimeout)
|
|
}
|
|
if o.HgTimeout != nil {
|
|
newCopy.HgTimeout = helper.StringToPtr(*o.HgTimeout)
|
|
}
|
|
if o.S3Timeout != nil {
|
|
newCopy.S3Timeout = helper.StringToPtr(*o.S3Timeout)
|
|
}
|
|
|
|
return newCopy
|
|
}
|
|
|
|
func (a *ArtifactConfig) Validate() error {
|
|
if a == nil {
|
|
return fmt.Errorf("artifact must not be nil")
|
|
}
|
|
|
|
if a.HTTPReadTimeout == nil {
|
|
return fmt.Errorf("http_read_timeout must be set")
|
|
}
|
|
if v, err := time.ParseDuration(*a.HTTPReadTimeout); err != nil {
|
|
return fmt.Errorf("http_read_timeout not a valid duration: %w", err)
|
|
} else if v < 0 {
|
|
return fmt.Errorf("http_read_timeout must be > 0")
|
|
}
|
|
|
|
if a.HTTPMaxSize == nil {
|
|
return fmt.Errorf("http_max_size must be set")
|
|
}
|
|
if v, err := humanize.ParseBytes(*a.HTTPMaxSize); err != nil {
|
|
return fmt.Errorf("http_max_size not a valid size: %w", err)
|
|
} else if v > math.MaxInt64 {
|
|
return fmt.Errorf("http_max_size must be < %d but found %d", int64(math.MaxInt64), v)
|
|
}
|
|
|
|
if a.GCSTimeout == nil {
|
|
return fmt.Errorf("gcs_timeout must be set")
|
|
}
|
|
if v, err := time.ParseDuration(*a.GCSTimeout); err != nil {
|
|
return fmt.Errorf("gcs_timeout not a valid duration: %w", err)
|
|
} else if v < 0 {
|
|
return fmt.Errorf("gcs_timeout must be > 0")
|
|
}
|
|
|
|
if a.GitTimeout == nil {
|
|
return fmt.Errorf("git_timeout must be set")
|
|
}
|
|
if v, err := time.ParseDuration(*a.GitTimeout); err != nil {
|
|
return fmt.Errorf("git_timeout not a valid duration: %w", err)
|
|
} else if v < 0 {
|
|
return fmt.Errorf("git_timeout must be > 0")
|
|
}
|
|
|
|
if a.HgTimeout == nil {
|
|
return fmt.Errorf("hg_timeout must be set")
|
|
}
|
|
if v, err := time.ParseDuration(*a.HgTimeout); err != nil {
|
|
return fmt.Errorf("hg_timeout not a valid duration: %w", err)
|
|
} else if v < 0 {
|
|
return fmt.Errorf("hg_timeout must be > 0")
|
|
}
|
|
|
|
if a.S3Timeout == nil {
|
|
return fmt.Errorf("s3_timeout must be set")
|
|
}
|
|
if v, err := time.ParseDuration(*a.S3Timeout); err != nil {
|
|
return fmt.Errorf("s3_timeout not a valid duration: %w", err)
|
|
} else if v < 0 {
|
|
return fmt.Errorf("s3_timeout must be > 0")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func DefaultArtifactConfig() *ArtifactConfig {
|
|
return &ArtifactConfig{
|
|
// Read timeout for HTTP operations. Must be long enough to
|
|
// accommodate large/slow downloads.
|
|
HTTPReadTimeout: helper.StringToPtr("30m"),
|
|
|
|
// Maximum download size. Must be large enough to accommodate
|
|
// large downloads.
|
|
HTTPMaxSize: helper.StringToPtr("100GB"),
|
|
|
|
// Timeout for GCS operations. Must be long enough to
|
|
// accommodate large/slow downloads.
|
|
GCSTimeout: helper.StringToPtr("30m"),
|
|
|
|
// Timeout for Git operations. Must be long enough to
|
|
// accommodate large/slow clones.
|
|
GitTimeout: helper.StringToPtr("30m"),
|
|
|
|
// Timeout for Hg operations. Must be long enough to
|
|
// accommodate large/slow clones.
|
|
HgTimeout: helper.StringToPtr("30m"),
|
|
|
|
// Timeout for S3 operations. Must be long enough to
|
|
// accommodate large/slow downloads.
|
|
S3Timeout: helper.StringToPtr("30m"),
|
|
}
|
|
}
|