mirror of
https://github.com/kemko/nomad.git
synced 2026-01-04 01:15:43 +03:00
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:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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...)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user