Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

template: disallow writeToFile by default #12312

Merged
merged 2 commits into from
Mar 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changelog/12312.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
```release-note:bug
template: Fixed a bug preventing allowing all consul-template functions.
```
```release-note:improvement
template: Upgraded to from consul-template v0.25.2 to v0.28.0 which includes the sprig library of functions and more.
```
108 changes: 104 additions & 4 deletions client/allocrunner/taskrunner/template/template_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package template

import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"os"
"os/user"
"path/filepath"
"reflect"
"regexp"
"runtime"
"sort"
"strconv"
"strings"
Expand Down Expand Up @@ -55,7 +58,7 @@ type MockTaskHooks struct {
UnblockCh chan struct{}

KillEvent *structs.TaskEvent
KillCh chan struct{}
KillCh chan *structs.TaskEvent

Events []*structs.TaskEvent
EmitEventCh chan *structs.TaskEvent
Expand All @@ -69,7 +72,7 @@ func NewMockTaskHooks() *MockTaskHooks {
UnblockCh: make(chan struct{}, 1),
RestartCh: make(chan struct{}, 1),
SignalCh: make(chan struct{}, 1),
KillCh: make(chan struct{}, 1),
KillCh: make(chan *structs.TaskEvent, 1),
EmitEventCh: make(chan *structs.TaskEvent, 1),
}
}
Expand Down Expand Up @@ -97,7 +100,7 @@ func (m *MockTaskHooks) Signal(event *structs.TaskEvent, s string) error {
func (m *MockTaskHooks) Kill(ctx context.Context, event *structs.TaskEvent) error {
m.KillEvent = event
select {
case m.KillCh <- struct{}{}:
case m.KillCh <- event:
default:
}
return nil
Expand Down Expand Up @@ -145,7 +148,7 @@ func newTestHarness(t *testing.T, templates []*structs.Template, consul, vault b
config: &config.Config{
Region: region,
TemplateConfig: &config.ClientTemplateConfig{
FunctionDenylist: []string{"plugin"},
FunctionDenylist: config.DefaultTemplateFunctionDenylist,
DisableSandbox: false,
ConsulRetry: &config.RetryConfig{Backoff: helper.TimeToPtr(10 * time.Millisecond)},
}},
Expand Down Expand Up @@ -2157,3 +2160,100 @@ func TestTaskTemplateManager_Template_Wait_Set(t *testing.T) {
require.Equal(t, 10*time.Second, *k.Wait.Max)
}
}

// TestTaskTemplateManager_writeToFile_Disabled asserts the consul-template function
// writeToFile is disabled by default.
func TestTaskTemplateManager_writeToFile_Disabled(t *testing.T) {
ci.Parallel(t)

file := "my.tmpl"
template := &structs.Template{
EmbeddedTmpl: `Testing writeToFile...
{{ "if i exist writeToFile is enabled" | writeToFile "/tmp/NOMAD-TEST-SHOULD-NOT-EXIST" "" "" "0644" }}
...done
`,
DestPath: file,
ChangeMode: structs.TemplateChangeModeNoop,
}

harness := newTestHarness(t, []*structs.Template{template}, false, false)
require.NoError(t, harness.startWithErr(), "couldn't setup initial harness")
defer harness.stop()

// Using writeToFile should cause a kill
select {
case <-harness.mockHooks.UnblockCh:
t.Fatalf("Task unblock should have not have been called")
case <-harness.mockHooks.EmitEventCh:
t.Fatalf("Task event should not have been emitted")
case e := <-harness.mockHooks.KillCh:
require.Contains(t, e.DisplayMessage, "writeToFile: function is disabled")
case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
t.Fatalf("timeout")
}

// Check the file is not there
path := filepath.Join(harness.taskDir, file)
_, err := ioutil.ReadFile(path)
require.Error(t, err)
}

// TestTaskTemplateManager_writeToFile asserts the consul-template function
// writeToFile can be enabled.
func TestTaskTemplateManager_writeToFile(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("username and group lookup assume linux platform")
}

ci.Parallel(t)

cu, err := user.Current()
require.NoError(t, err)

cg, err := user.LookupGroupId(cu.Gid)
require.NoError(t, err)

file := "my.tmpl"
template := &structs.Template{
// EmbeddedTmpl set below as it needs the taskDir
DestPath: file,
ChangeMode: structs.TemplateChangeModeNoop,
}

harness := newTestHarness(t, []*structs.Template{template}, false, false)

// Add template now that we know the taskDir
harness.templates[0].EmbeddedTmpl = fmt.Sprintf(`Testing writeToFile...
{{ "hello" | writeToFile "%s" "`+cu.Username+`" "`+cg.Name+`" "0644" }}
...done
`, filepath.Join(harness.taskDir, "writetofile.out"))

// Enable all funcs
harness.config.TemplateConfig.FunctionDenylist = []string{}

require.NoError(t, harness.startWithErr(), "couldn't setup initial harness")
defer harness.stop()

// Using writeToFile should not cause a kill
select {
case <-harness.mockHooks.UnblockCh:
case <-harness.mockHooks.EmitEventCh:
t.Fatalf("Task event should not have been emitted")
case e := <-harness.mockHooks.KillCh:
t.Fatalf("Task should not have been killed: %v", e.DisplayMessage)
case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
t.Fatalf("timeout")
}

// Check the templated file is there
path := filepath.Join(harness.taskDir, file)
r, err := ioutil.ReadFile(path)
require.NoError(t, err)
require.True(t, bytes.HasSuffix(r, []byte("...done\n")), string(r))

// Check that writeToFile was allowed
path = filepath.Join(harness.taskDir, "writetofile.out")
r, err = ioutil.ReadFile(path)
require.NoError(t, err)
require.Equal(t, "hello", string(r))
}
22 changes: 18 additions & 4 deletions client/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ var (
}

DefaultTemplateMaxStale = 5 * time.Second

DefaultTemplateFunctionDenylist = []string{"plugin", "writeToFile"}
)

// RPCHandler can be provided to the Client if there is a local server
Expand Down Expand Up @@ -358,7 +360,13 @@ func (c *ClientTemplateConfig) Copy() *ClientTemplateConfig {

nc := new(ClientTemplateConfig)
*nc = *c
nc.FunctionDenylist = helper.CopySliceString(nc.FunctionDenylist)

if len(c.FunctionDenylist) > 0 {
nc.FunctionDenylist = helper.CopySliceString(nc.FunctionDenylist)
} else if c.FunctionDenylist != nil {
// Explicitly no functions denied (which is different than nil)
nc.FunctionDenylist = []string{}
}

if c.BlockQueryWaitTime != nil {
nc.BlockQueryWaitTime = &*c.BlockQueryWaitTime
Expand Down Expand Up @@ -416,6 +424,9 @@ func (c *ClientTemplateConfig) Merge(b *ClientTemplateConfig) *ClientTemplateCon
result.FunctionBlacklist = append(result.FunctionBlacklist, fn)
}
}
} else if b.FunctionBlacklist != nil {
// No funcs denied
result.FunctionBlacklist = []string{}
}

if len(b.FunctionDenylist) > 0 {
Expand All @@ -424,6 +435,9 @@ func (c *ClientTemplateConfig) Merge(b *ClientTemplateConfig) *ClientTemplateCon
result.FunctionDenylist = append(result.FunctionDenylist, fn)
}
}
} else if b.FunctionDenylist != nil {
// No funcs denied
result.FunctionDenylist = []string{}
}

if b.MaxStale != nil {
Expand Down Expand Up @@ -455,8 +469,8 @@ func (c *ClientTemplateConfig) IsEmpty() bool {
}

return !c.DisableSandbox &&
len(c.FunctionDenylist) == 0 &&
len(c.FunctionBlacklist) == 0 &&
c.FunctionDenylist == nil &&
c.FunctionBlacklist == nil &&
c.BlockQueryWaitTime == nil &&
c.BlockQueryWaitTimeHCL == "" &&
c.MaxStale == nil &&
Expand Down Expand Up @@ -770,7 +784,7 @@ func DefaultConfig() *Config {
NoHostUUID: true,
DisableRemoteExec: false,
TemplateConfig: &ClientTemplateConfig{
FunctionDenylist: []string{"plugin"},
FunctionDenylist: DefaultTemplateFunctionDenylist,
DisableSandbox: false,
},
RPCHoldTimeout: 5 * time.Second,
Expand Down
4 changes: 2 additions & 2 deletions command/agent/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -917,7 +917,7 @@ func DevConfig(mode *devModeConfig) *Config {
conf.Client.GCInodeUsageThreshold = 99
conf.Client.GCMaxAllocs = 50
conf.Client.TemplateConfig = &client.ClientTemplateConfig{
FunctionDenylist: []string{"plugin"},
FunctionDenylist: client.DefaultTemplateFunctionDenylist,
DisableSandbox: false,
}
conf.Client.BindWildcardDefaultHostNetwork = true
Expand Down Expand Up @@ -967,7 +967,7 @@ func DefaultConfig() *Config {
RetryMaxAttempts: 0,
},
TemplateConfig: &client.ClientTemplateConfig{
FunctionDenylist: []string{"plugin"},
FunctionDenylist: client.DefaultTemplateFunctionDenylist,
DisableSandbox: false,
},
BindWildcardDefaultHostNetwork: true,
Expand Down
10 changes: 6 additions & 4 deletions command/agent/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func TestConfig_Merge(t *testing.T) {
ClientMaxPort: 19996,
DisableRemoteExec: false,
TemplateConfig: &client.ClientTemplateConfig{
FunctionDenylist: []string{"plugin"},
FunctionDenylist: client.DefaultTemplateFunctionDenylist,
DisableSandbox: false,
},
Reserved: &Resources{
Expand Down Expand Up @@ -305,7 +305,7 @@ func TestConfig_Merge(t *testing.T) {
MaxKillTimeout: "50s",
DisableRemoteExec: false,
TemplateConfig: &client.ClientTemplateConfig{
FunctionDenylist: []string{"plugin"},
FunctionDenylist: client.DefaultTemplateFunctionDenylist,
DisableSandbox: false,
},
Reserved: &Resources{
Expand Down Expand Up @@ -1465,6 +1465,8 @@ func TestConfig_LoadConsulTemplateBasic(t *testing.T) {
require.NotNil(t, agentConfig.Client.TemplateConfig)

agentConfig = defaultConfig.Merge(agentConfig)
require.Len(t, agentConfig.Client.TemplateConfig.FunctionDenylist, 0)
require.NotNil(t, agentConfig.Client.TemplateConfig.FunctionDenylist)

clientAgent := Agent{config: agentConfig}
clientConfig, err := clientAgent.clientConfig()
Expand All @@ -1473,7 +1475,7 @@ func TestConfig_LoadConsulTemplateBasic(t *testing.T) {
templateConfig := clientConfig.TemplateConfig
require.NotNil(t, templateConfig)
require.True(t, templateConfig.DisableSandbox)
require.Len(t, templateConfig.FunctionDenylist, 1)
require.Len(t, templateConfig.FunctionDenylist, 0)

// json
agentConfig, err = LoadConfig("test-resources/client_with_basic_template.json")
Expand All @@ -1488,7 +1490,7 @@ func TestConfig_LoadConsulTemplateBasic(t *testing.T) {
templateConfig = clientConfig.TemplateConfig
require.NotNil(t, templateConfig)
require.True(t, templateConfig.DisableSandbox)
require.Len(t, templateConfig.FunctionDenylist, 1)
require.Len(t, templateConfig.FunctionDenylist, 0)
}

func TestParseMultipleIPTemplates(t *testing.T) {
Expand Down
Loading