From 8db90fcb5f87e20c761f68a70451e9ad29476ef6 Mon Sep 17 00:00:00 2001 From: Mahmood Ali Date: Thu, 1 Aug 2019 05:25:04 -0500 Subject: [PATCH] Render consul templates using task env only When rendering a task consul template, ensure that only task environment variables are used. Currently, `consul-template` always falls back to host process environment variables when key isn't a task env var[1]. Thus, we add an empty entry for each host process env-var not found in task env-vars. [1] https://github.com/hashicorp/consul-template/blob/bfa5d0e133688920afd1e012404f765182e3d5e0/template/funcs.go#L61-L75 --- .../taskrunner/template/template.go | 16 ++++++- .../taskrunner/template/template_test.go | 47 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/client/allocrunner/taskrunner/template/template.go b/client/allocrunner/taskrunner/template/template.go index d0b2d1a4b43f..9549a67a983a 100644 --- a/client/allocrunner/taskrunner/template/template.go +++ b/client/allocrunner/taskrunner/template/template.go @@ -509,7 +509,7 @@ func templateRunner(config *TaskTemplateManagerConfig) ( } // Set Nomad's environment variables - runner.Env = config.EnvBuilder.Build().All() + runner.Env = maskProcessEnv(config.EnvBuilder.Build().All()) // Build the lookup idMap := runner.TemplateConfigMapping() @@ -525,6 +525,20 @@ func templateRunner(config *TaskTemplateManagerConfig) ( return runner, lookup, nil } +// maskProcessEnv masks away any environment variable not found in task env. +// It manipulates the parameter directly and returns it without copying. +func maskProcessEnv(env map[string]string) map[string]string { + procEnvs := os.Environ() + for _, e := range procEnvs { + ekv := strings.SplitN(e, "=", 2) + if _, ok := env[ekv[0]]; !ok { + env[ekv[0]] = "" + } + } + + return env +} + // parseTemplateConfigs converts the tasks templates in the config into // consul-templates func parseTemplateConfigs(config *TaskTemplateManagerConfig) (map[ctconf.TemplateConfig]*structs.Template, error) { diff --git a/client/allocrunner/taskrunner/template/template_test.go b/client/allocrunner/taskrunner/template/template_test.go index db8c1ba01c9d..08452bd1561b 100644 --- a/client/allocrunner/taskrunner/template/template_test.go +++ b/client/allocrunner/taskrunner/template/template_test.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/nomad/client/config" "github.com/hashicorp/nomad/client/taskenv" "github.com/hashicorp/nomad/helper" + "github.com/hashicorp/nomad/helper/uuid" "github.com/hashicorp/nomad/nomad/mock" "github.com/hashicorp/nomad/nomad/structs" sconfig "github.com/hashicorp/nomad/nomad/structs/config" @@ -1022,6 +1023,52 @@ func TestTaskTemplateManager_Signal_Error(t *testing.T) { require.Contains(harness.mockHooks.KillEvent.DisplayMessage, "failed to send signals") } +// TestTaskTemplateManager_FiltersProcessEnvVars asserts that we only render +// environment variables found in task env-vars and not read the nomad host +// process environment variables. nomad host process environment variables +// are to be treated the same as not found environment variables. +func TestTaskTemplateManager_FiltersEnvVars(t *testing.T) { + t.Parallel() + + defer os.Setenv("NOMAD_TASK_NAME", os.Getenv("NOMAD_TASK_NAME")) + os.Setenv("NOMAD_TASK_NAME", "should be overriden by task") + + testenv := "TESTENV_" + strings.ReplaceAll(uuid.Generate(), "-", "") + os.Setenv(testenv, "MY_TEST_VALUE") + defer os.Unsetenv(testenv) + + // Make a template that will render immediately + content := `Hello Nomad Task: {{env "NOMAD_TASK_NAME"}} +TEST_ENV: {{ env "` + testenv + `" }} +TEST_ENV_NOT_FOUND: {{env "` + testenv + `_NOTFOUND" }}` + expected := fmt.Sprintf("Hello Nomad Task: %s\nTEST_ENV: \nTEST_ENV_NOT_FOUND: ", TestTaskName) + + file := "my.tmpl" + template := &structs.Template{ + EmbeddedTmpl: content, + DestPath: file, + ChangeMode: structs.TemplateChangeModeNoop, + } + + harness := newTestHarness(t, []*structs.Template{template}, false, false) + harness.start(t) + defer harness.stop() + + // Wait for the unblock + select { + case <-harness.mockHooks.UnblockCh: + case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): + require.Fail(t, "Task unblock should have been called") + } + + // Check the file is there + path := filepath.Join(harness.taskDir, file) + raw, err := ioutil.ReadFile(path) + require.NoError(t, err) + + require.Equal(t, expected, string(raw)) +} + // TestTaskTemplateManager_Env asserts templates with the env flag set are read // into the task's environment. func TestTaskTemplateManager_Env(t *testing.T) {