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.
This commit is contained in:
Tim Gross
2019-08-02 15:20:14 -04:00
parent e4e7ca074d
commit ffb83e1ef1
7 changed files with 100 additions and 23 deletions

View File

@@ -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

View File

@@ -125,8 +125,13 @@ func newTestHarness(t *testing.T, templates []*structs.Template, consul, vault b
mockHooks: NewMockTaskHooks(),
templates: templates,
node: mock.Node(),
config: &config.Config{Region: region},
emitRate: DefaultMaxTemplateEventRate,
config: &config.Config{
Region: region,
TemplateConfig: &config.ClientTemplateConfig{
FunctionBlacklist: []string{"plugin"},
DisableSandbox: false,
}},
emitRate: DefaultMaxTemplateEventRate,
}
// Build the task environment

View File

@@ -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
@@ -239,6 +242,11 @@ type Config struct {
HostVolumes map[string]*structs.ClientHostVolumeConfig
}
type ClientTemplateConfig struct {
FunctionBlacklist []string
DisableSandbox bool
}
func (c *Config) Copy() *Config {
nc := new(Config)
*nc = *c
@@ -254,22 +262,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,
}

View File

@@ -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
hvMap := make(map[string]*structs.ClientHostVolumeConfig, len(agentConfig.Client.HostVolumes))
for _, v := range agentConfig.Client.HostVolumes {

View File

@@ -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"`
@@ -266,6 +269,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
@@ -675,6 +692,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
@@ -716,6 +737,10 @@ func DefaultConfig() *Config {
RetryInterval: 30 * time.Second,
RetryMaxAttempts: 0,
},
TemplateConfig: &ClientTemplateConfig{
FunctionBlacklist: []string{"plugin"},
DisableSandbox: false,
},
},
Server: &ServerConfig{
Enabled: false,
@@ -1295,6 +1320,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...)

View File

@@ -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,

View File

@@ -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` <code>([Template](#template-parameters): nil)</code> - 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