Skip to content

Commit 3ee667f

Browse files
committed
provisioner: don't pass CODER_ variables
1 parent 0a5e554 commit 3ee667f

File tree

3 files changed

+112
-2
lines changed

3 files changed

+112
-2
lines changed

provisioner/terraform/executor.go

+36-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,41 @@ func (e executor) basicEnv() []string {
4141
return env
4242
}
4343

44+
func envName(env string) string {
45+
parts := strings.Split(env, "=")
46+
47+
if len(parts) > 0 {
48+
return parts[0]
49+
}
50+
return ""
51+
}
52+
53+
// sanitizeCoderEnv removes CODER_ environment variables to prevent accidentally
54+
// passing in secrets. See https://github.com/coder/coder/issues/4635.
55+
func sanitizeCoderEnv(env []string) []string {
56+
var coderSafeEnv = map[string]struct{}{
57+
"CODER_AGENT_URL": {},
58+
"CODER_WORKSPACE_TRANSITION": {},
59+
"CODER_WORKSPACE_NAME": {},
60+
"CODER_WORKSPACE_OWNER": {},
61+
"CODER_WORKSPACE_OWNER_EMAIL": {},
62+
"CODER_WORKSPACE_ID": {},
63+
"CODER_WORKSPACE_OWNER_ID": {},
64+
}
65+
66+
strippedEnv := make([]string, 0, len(env))
67+
for _, e := range env {
68+
name := envName(e)
69+
if strings.HasPrefix(name, "CODER_") {
70+
if _, isSafe := coderSafeEnv[name]; !isSafe {
71+
continue
72+
}
73+
}
74+
strippedEnv = append(strippedEnv, e)
75+
}
76+
return strippedEnv
77+
}
78+
4479
func (e executor) execWriteOutput(ctx, killCtx context.Context, args, env []string, stdOutWriter, stdErrWriter io.WriteCloser) (err error) {
4580
defer func() {
4681
closeErr := stdOutWriter.Close()
@@ -59,7 +94,7 @@ func (e executor) execWriteOutput(ctx, killCtx context.Context, args, env []stri
5994
// #nosec
6095
cmd := exec.CommandContext(killCtx, e.binaryPath, args...)
6196
cmd.Dir = e.workdir
62-
cmd.Env = env
97+
cmd.Env = sanitizeCoderEnv(env)
6398

6499
// We want logs to be written in the correct order, so we wrap all logging
65100
// in a sync.Mutex.

provisioner/terraform/provision.go

+2
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,8 @@ func provisionVars(start *proto.Provision_Start) ([]string, error) {
189189

190190
func provisionEnv(start *proto.Provision_Start) ([]string, error) {
191191
env := os.Environ()
192+
// Be sure to add any values here to `sanitizeCoderEnv`, otherwise they will not
193+
// get passed into the execute.
192194
env = append(env,
193195
"CODER_AGENT_URL="+start.Metadata.CoderUrl,
194196
"CODER_WORKSPACE_TRANSITION="+strings.ToLower(start.Metadata.WorkspaceTransition.String()),

provisioner/terraform/provision_test.go

+74-1
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ func TestProvision(t *testing.T) {
415415
// nolint:paralleltest
416416
func TestProvision_ExtraEnv(t *testing.T) {
417417
// #nosec
418-
secretValue := "oinae3uinxase"
418+
const secretValue = "oinae3uinxase"
419419
t.Setenv("TF_LOG", "INFO")
420420
t.Setenv("TF_SUPERSECRET", secretValue)
421421

@@ -459,3 +459,76 @@ func TestProvision_ExtraEnv(t *testing.T) {
459459
}
460460
require.True(t, found)
461461
}
462+
463+
// nolint:paralleltest
464+
func TestProvision_SafeEnv(t *testing.T) {
465+
// #nosec
466+
const (
467+
passedValue = "superautopets"
468+
secretValue = "oinae3uinxase"
469+
)
470+
471+
t.Setenv("VALID_USER_ENV", passedValue)
472+
473+
// We ensure random CODER_ variables aren't passed through to avoid leaking
474+
// control plane secrets (e.g. PG URL).
475+
t.Setenv("CODER_SECRET", secretValue)
476+
477+
const echoResource = `
478+
resource "null_resource" "a" {
479+
provisioner "local-exec" {
480+
command = "env"
481+
}
482+
}
483+
484+
`
485+
486+
ctx, api := setupProvisioner(t, nil)
487+
488+
directory := t.TempDir()
489+
path := filepath.Join(directory, "main.tf")
490+
err := os.WriteFile(path, []byte(echoResource), 0o600)
491+
require.NoError(t, err)
492+
493+
request := &proto.Provision_Request{
494+
Type: &proto.Provision_Request_Start{
495+
Start: &proto.Provision_Start{
496+
Directory: directory,
497+
Metadata: &proto.Provision_Metadata{
498+
WorkspaceTransition: proto.WorkspaceTransition_START,
499+
},
500+
},
501+
},
502+
}
503+
response, err := api.Provision(ctx)
504+
require.NoError(t, err)
505+
err = response.Send(request)
506+
require.NoError(t, err)
507+
var (
508+
foundUserEnv = false
509+
// Some CODER_ environment variables used by our Terraform provider
510+
// must make it through.
511+
foundCoderEnv = false
512+
)
513+
for {
514+
msg, err := response.Recv()
515+
require.NoError(t, err)
516+
517+
if log := msg.GetLog(); log != nil {
518+
t.Log(log.Level.String(), log.Output)
519+
if strings.Contains(log.Output, passedValue) {
520+
foundUserEnv = true
521+
}
522+
if strings.Contains(log.Output, "CODER_") {
523+
foundCoderEnv = true
524+
}
525+
require.NotContains(t, log.Output, secretValue)
526+
}
527+
if c := msg.GetComplete(); c != nil {
528+
require.Empty(t, c.Error)
529+
break
530+
}
531+
}
532+
require.True(t, foundUserEnv)
533+
require.True(t, foundCoderEnv)
534+
}

0 commit comments

Comments
 (0)