From 9aa45a0050fe9baf1e38fccdf986e9e96ebecc9a Mon Sep 17 00:00:00 2001 From: Tim Gross Date: Fri, 2 Aug 2019 15:20:14 -0400 Subject: [PATCH] client/template: configuration for function blacklist and sandboxing When rendering a task template, the `plugin` function is no longer permitted by default and will raise an error. An operator can opt-in to permitting this function with the new `template.function_blacklist` field in the client configuration. When rendering a task template, path parameters for the `file` function will be treated as relative to the task directory by default. Relative paths or symlinks that point outside the task directory will raise an error. An operator can opt-out of this protection with the new `template.disable_file_sandbox` field in the client configuration. --- .../taskrunner/template/template.go | 14 +++--- client/config/config.go | 44 ++++++++++++------- command/agent/agent.go | 2 + command/agent/config.go | 29 ++++++++++++ command/agent/config_test.go | 8 ++++ .../source/docs/configuration/client.html.md | 17 +++++++ 6 files changed, 93 insertions(+), 21 deletions(-) diff --git a/client/allocrunner/taskrunner/template/template.go b/client/allocrunner/taskrunner/template/template.go index fe1338c40b9c..e22bea0ce4b4 100644 --- a/client/allocrunner/taskrunner/template/template.go +++ b/client/allocrunner/taskrunner/template/template.go @@ -545,11 +545,11 @@ func maskProcessEnv(env map[string]string) map[string]string { // parseTemplateConfigs converts the tasks templates in the config into // consul-templates -func parseTemplateConfigs(config *TaskTemplateManagerConfig) (map[ctconf.TemplateConfig]*structs.Template, error) { +func parseTemplateConfigs(config *TaskTemplateManagerConfig) (map[*ctconf.TemplateConfig]*structs.Template, error) { allowAbs := config.ClientConfig.ReadBoolDefault(hostSrcOption, true) taskEnv := config.EnvBuilder.Build() - ctmpls := make(map[ctconf.TemplateConfig]*structs.Template, len(config.Templates)) + ctmpls := make(map[*ctconf.TemplateConfig]*structs.Template, len(config.Templates)) for _, tmpl := range config.Templates { var src, dest string if tmpl.SourcePath != "" { @@ -573,6 +573,10 @@ func parseTemplateConfigs(config *TaskTemplateManagerConfig) (map[ctconf.Templat ct.Contents = &tmpl.EmbeddedTmpl ct.LeftDelim = &tmpl.LeftDelim ct.RightDelim = &tmpl.RightDelim + ct.FunctionBlacklist = config.ClientConfig.TemplateConfig.FunctionBlacklist + if !config.ClientConfig.TemplateConfig.DisableSandbox { + ct.SandboxPath = &config.TaskDir + } // Set the permissions if tmpl.Perms != "" { @@ -585,7 +589,7 @@ func parseTemplateConfigs(config *TaskTemplateManagerConfig) (map[ctconf.Templat } ct.Finalize() - ctmpls[*ct] = tmpl + ctmpls[ct] = tmpl } return ctmpls, nil @@ -594,7 +598,7 @@ func parseTemplateConfigs(config *TaskTemplateManagerConfig) (map[ctconf.Templat // newRunnerConfig returns a consul-template runner configuration, setting the // Vault and Consul configurations based on the clients configs. func newRunnerConfig(config *TaskTemplateManagerConfig, - templateMapping map[ctconf.TemplateConfig]*structs.Template) (*ctconf.Config, error) { + templateMapping map[*ctconf.TemplateConfig]*structs.Template) (*ctconf.Config, error) { cc := config.ClientConfig conf := ctconf.DefaultConfig() @@ -603,7 +607,7 @@ func newRunnerConfig(config *TaskTemplateManagerConfig, flat := ctconf.TemplateConfigs(make([]*ctconf.TemplateConfig, 0, len(templateMapping))) for ctmpl := range templateMapping { local := ctmpl - flat = append(flat, &local) + flat = append(flat, local) } conf.Templates = &flat diff --git a/client/config/config.go b/client/config/config.go index 9bac1803a031..f2c2ed0976be 100644 --- a/client/config/config.go +++ b/client/config/config.go @@ -201,6 +201,9 @@ type Config struct { // DisableRemoteExec disables remote exec targeting tasks on this client DisableRemoteExec bool + // TemplateConfig includes configuration for template rendering + TemplateConfig *ClientTemplateConfig + // BackwardsCompatibleMetrics determines whether to show methods of // displaying metrics for older versions, or to only show the new format BackwardsCompatibleMetrics bool @@ -236,6 +239,11 @@ type Config struct { BridgeNetworkAllocSubnet string } +type ClientTemplateConfig struct { + FunctionBlacklist []string + DisableSandbox bool +} + func (c *Config) Copy() *Config { nc := new(Config) *nc = *c @@ -250,22 +258,26 @@ func (c *Config) Copy() *Config { // DefaultConfig returns the default configuration func DefaultConfig() *Config { return &Config{ - Version: version.GetVersion(), - VaultConfig: config.DefaultVaultConfig(), - ConsulConfig: config.DefaultConsulConfig(), - LogOutput: os.Stderr, - Region: "global", - StatsCollectionInterval: 1 * time.Second, - TLSConfig: &config.TLSConfig{}, - LogLevel: "DEBUG", - GCInterval: 1 * time.Minute, - GCParallelDestroys: 2, - GCDiskUsageThreshold: 80, - GCInodeUsageThreshold: 70, - GCMaxAllocs: 50, - NoHostUUID: true, - DisableTaggedMetrics: false, - DisableRemoteExec: false, + Version: version.GetVersion(), + VaultConfig: config.DefaultVaultConfig(), + ConsulConfig: config.DefaultConsulConfig(), + LogOutput: os.Stderr, + Region: "global", + StatsCollectionInterval: 1 * time.Second, + TLSConfig: &config.TLSConfig{}, + LogLevel: "DEBUG", + GCInterval: 1 * time.Minute, + GCParallelDestroys: 2, + GCDiskUsageThreshold: 80, + GCInodeUsageThreshold: 70, + GCMaxAllocs: 50, + NoHostUUID: true, + DisableTaggedMetrics: false, + DisableRemoteExec: false, + TemplateConfig: &ClientTemplateConfig{ + FunctionBlacklist: []string{"plugin"}, + DisableSandbox: false, + }, BackwardsCompatibleMetrics: false, RPCHoldTimeout: 5 * time.Second, } diff --git a/command/agent/agent.go b/command/agent/agent.go index f0f2cebf528c..27e63381a81e 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -468,6 +468,8 @@ func convertClientConfig(agentConfig *Config) (*clientconfig.Config, error) { conf.ClientMaxPort = uint(agentConfig.Client.ClientMaxPort) conf.ClientMinPort = uint(agentConfig.Client.ClientMinPort) conf.DisableRemoteExec = agentConfig.Client.DisableRemoteExec + conf.TemplateConfig.FunctionBlacklist = agentConfig.Client.TemplateConfig.FunctionBlacklist + conf.TemplateConfig.DisableSandbox = agentConfig.Client.TemplateConfig.DisableSandbox // Setup the node conf.Node = new(structs.Node) diff --git a/command/agent/config.go b/command/agent/config.go index fe3f74c4bfa7..32bd26cf2e84 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -242,6 +242,9 @@ type ClientConfig struct { // DisableRemoteExec disables remote exec targeting tasks on this client DisableRemoteExec bool `hcl:"disable_remote_exec"` + // TemplateConfig includes configuration for template rendering + TemplateConfig *ClientTemplateConfig `hcl:"template"` + // ServerJoin contains information that is used to attempt to join servers ServerJoin *ServerJoin `hcl:"server_join"` @@ -262,6 +265,20 @@ type ClientConfig struct { BridgeNetworkSubnet string `hcl:"bridge_network_subnet"` } +// ClientTemplateConfig is configuration on the client specific to template +// rendering +type ClientTemplateConfig struct { + + // FunctionBlacklist disables functions in consul-template that + // are unsafe because they expose information from the client host. + FunctionBlacklist []string `hcl:"function_blacklist"` + + // DisableSandbox allows templates to access arbitrary files on the + // client host. By default templates can access files only within + // the task directory. + DisableSandbox bool `hcl:"disable_file_sandbox"` +} + // ACLConfig is configuration specific to the ACL system type ACLConfig struct { // Enabled controls if we are enforce and manage ACLs @@ -671,6 +688,10 @@ func DevConfig() *Config { conf.Client.GCDiskUsageThreshold = 99 conf.Client.GCInodeUsageThreshold = 99 conf.Client.GCMaxAllocs = 50 + conf.Client.TemplateConfig = &ClientTemplateConfig{ + FunctionBlacklist: []string{"plugin"}, + DisableSandbox: false, + } conf.Telemetry.PrometheusMetrics = true conf.Telemetry.PublishAllocationMetrics = true conf.Telemetry.PublishNodeMetrics = true @@ -712,6 +733,10 @@ func DefaultConfig() *Config { RetryInterval: 30 * time.Second, RetryMaxAttempts: 0, }, + TemplateConfig: &ClientTemplateConfig{ + FunctionBlacklist: []string{"plugin"}, + DisableSandbox: false, + }, }, Server: &ServerConfig{ Enabled: false, @@ -1291,6 +1316,10 @@ func (a *ClientConfig) Merge(b *ClientConfig) *ClientConfig { result.DisableRemoteExec = b.DisableRemoteExec } + if b.TemplateConfig != nil { + result.TemplateConfig = b.TemplateConfig + } + // Add the servers result.Servers = append(result.Servers, b.Servers...) diff --git a/command/agent/config_test.go b/command/agent/config_test.go index d4fea75110a3..d5d49bc4be5e 100644 --- a/command/agent/config_test.go +++ b/command/agent/config_test.go @@ -94,6 +94,10 @@ func TestConfig_Merge(t *testing.T) { MaxKillTimeout: "20s", ClientMaxPort: 19996, DisableRemoteExec: false, + TemplateConfig: &ClientTemplateConfig{ + FunctionBlacklist: []string{"plugin"}, + DisableSandbox: false, + }, Reserved: &Resources{ CPU: 10, MemoryMB: 10, @@ -253,6 +257,10 @@ func TestConfig_Merge(t *testing.T) { MemoryMB: 105, MaxKillTimeout: "50s", DisableRemoteExec: false, + TemplateConfig: &ClientTemplateConfig{ + FunctionBlacklist: []string{"plugin"}, + DisableSandbox: false, + }, Reserved: &Resources{ CPU: 15, MemoryMB: 15, diff --git a/website/source/docs/configuration/client.html.md b/website/source/docs/configuration/client.html.md index 8e50f53404c4..b40af69ac086 100644 --- a/website/source/docs/configuration/client.html.md +++ b/website/source/docs/configuration/client.html.md @@ -147,6 +147,10 @@ driver) but will be removed in a future release. - `bridge_network_subnet` `(string: "172.26.66.0/23")` - Specifies the subnet which the client will use to allocate IP addresses from. +- `template` ([Template](#template-parameters): nil) - Specifies + controls on the behavior of task [`template`](/docs/job-specification/template.html) stanzas. + + ### `chroot_env` Parameters Drivers based on [isolated fork/exec](/docs/drivers/exec.html) implement file @@ -329,6 +333,19 @@ see the [drivers documentation](/docs/drivers/index.html). reserve on all fingerprinted network devices. Ranges can be specified by using a hyphen separated the two inclusive ends. + +### `template` Parameters + +- `function_blacklist` `([]string: ["plugin"])` - Specifies a list of template + rendering functions that should be disallowed in job specs. By default the + `plugin` function is disallowed as it allows running arbitrary commands on + the host as root (unless Nomad is configured to run as a non-root user). + +- `disable_file_sandbox` `(bool: false)` - Allows templates access to arbitrary + files on the client host via the `file` function. By default templates can + access files only within the task directory. + + ## `client` Examples ### Common Setup