artifact: fix numerous go-getter security issues

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.
This commit is contained in:
Michael Schurter
2022-05-03 15:38:32 -07:00
committed by Luiz Aoqui
parent 94abe338e9
commit 3968509886
29 changed files with 1092 additions and 77 deletions

View File

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

View File

@@ -406,6 +406,11 @@ func (c *Command) IsValidConfig(config, cmdConfig *Config) bool {
}
}
if err := config.Client.Artifact.Validate(); err != nil {
c.Ui.Error(fmt.Sprintf("client.artifact stanza invalid: %v", err))
return false
}
if !config.DevMode {
// Ensure that we have the directories we need to run.
if config.Server.Enabled && config.DataDir == "" {

View File

@@ -8,11 +8,13 @@ import (
"testing"
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/helper"
"github.com/mitchellh/cli"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/nomad/structs/config"
"github.com/hashicorp/nomad/version"
)
@@ -382,6 +384,18 @@ func TestIsValidConfig(t *testing.T) {
},
err: `host_network["test"].reserved_ports "3-2147483647" invalid: port must be < 65536 but found 2147483647`,
},
{
name: "BadArtifact",
conf: Config{
Client: &ClientConfig{
Enabled: true,
Artifact: &config.ArtifactConfig{
HTTPReadTimeout: helper.StringToPtr("-10m"),
},
},
},
err: "client.artifact stanza invalid: http_read_timeout must be > 0",
},
}
for _, tc := range cases {

View File

@@ -324,6 +324,9 @@ type ClientConfig struct {
// correct scheduling decisions on allocations which require this.
NomadServiceDiscovery *bool `hcl:"nomad_service_discovery"`
// Artifact contains the configuration for artifacts.
Artifact *config.ArtifactConfig `hcl:"artifact"`
// ExtraKeysHCL is used by hcl to surface unexpected keys
ExtraKeysHCL []string `hcl:",unusedKeys" json:"-"`
}
@@ -974,6 +977,7 @@ func DefaultConfig() *Config {
CNIPath: "/opt/cni/bin",
CNIConfigDir: "/opt/cni/config",
NomadServiceDiscovery: helper.BoolToPtr(true),
Artifact: config.DefaultArtifactConfig(),
},
Server: &ServerConfig{
Enabled: false,
@@ -1779,6 +1783,8 @@ func (a *ClientConfig) Merge(b *ClientConfig) *ClientConfig {
result.CgroupParent = b.CgroupParent
}
result.Artifact = a.Artifact.Merge(b.Artifact)
return &result
}