Skip to content

provisioner: don't pass CODER_ variables #4638

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

Merged
merged 2 commits into from
Oct 19, 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: 5 additions & 1 deletion provisioner/terraform/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type executor struct {
func (e executor) basicEnv() []string {
// Required for "terraform init" to find "git" to
// clone Terraform modules.
env := os.Environ()
env := safeEnviron()
// Only Linux reliably works with the Terraform plugin
// cache directory. It's unknown why this is.
if e.cachePath != "" && runtime.GOOS == "linux" {
Expand All @@ -56,6 +56,10 @@ func (e executor) execWriteOutput(ctx, killCtx context.Context, args, env []stri
return ctx.Err()
}

if isCanarySet(env) {
return xerrors.New("environment variables not sanitized, this is a bug within Coder")
}

// #nosec
cmd := exec.CommandContext(killCtx, e.binaryPath, args...)
cmd.Dir = e.workdir
Expand Down
6 changes: 3 additions & 3 deletions provisioner/terraform/provision.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ func provisionVars(start *proto.Provision_Start) ([]string, error) {
}

func provisionEnv(start *proto.Provision_Start) ([]string, error) {
env := os.Environ()
env := safeEnviron()
env = append(env,
"CODER_AGENT_URL="+start.Metadata.CoderUrl,
"CODER_WORKSPACE_TRANSITION="+strings.ToLower(start.Metadata.WorkspaceTransition.String()),
Expand Down Expand Up @@ -232,12 +232,12 @@ var (
)

func logTerraformEnvVars(logr logger) error {
env := os.Environ()
env := safeEnviron()
for _, e := range env {
if strings.HasPrefix(e, "TF_") {
parts := strings.SplitN(e, "=", 2)
if len(parts) != 2 {
panic("os.Environ() returned vars not in key=value form")
panic("safeEnviron() returned vars not in key=value form")
}
if !tfEnvSafeToPrint[parts[0]] {
parts[1] = "<value redacted>"
Expand Down
75 changes: 74 additions & 1 deletion provisioner/terraform/provision_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ func TestProvision(t *testing.T) {
// nolint:paralleltest
func TestProvision_ExtraEnv(t *testing.T) {
// #nosec
secretValue := "oinae3uinxase"
const secretValue = "oinae3uinxase"
t.Setenv("TF_LOG", "INFO")
t.Setenv("TF_SUPERSECRET", secretValue)

Expand Down Expand Up @@ -459,3 +459,76 @@ func TestProvision_ExtraEnv(t *testing.T) {
}
require.True(t, found)
}

// nolint:paralleltest
func TestProvision_SafeEnv(t *testing.T) {
// #nosec
const (
passedValue = "superautopets"
secretValue = "oinae3uinxase"
)

t.Setenv("VALID_USER_ENV", passedValue)

// We ensure random CODER_ variables aren't passed through to avoid leaking
// control plane secrets (e.g. PG URL).
t.Setenv("CODER_SECRET", secretValue)

const echoResource = `
resource "null_resource" "a" {
provisioner "local-exec" {
command = "env"
}
}

`

ctx, api := setupProvisioner(t, nil)

directory := t.TempDir()
path := filepath.Join(directory, "main.tf")
err := os.WriteFile(path, []byte(echoResource), 0o600)
require.NoError(t, err)

request := &proto.Provision_Request{
Type: &proto.Provision_Request_Start{
Start: &proto.Provision_Start{
Directory: directory,
Metadata: &proto.Provision_Metadata{
WorkspaceTransition: proto.WorkspaceTransition_START,
},
},
},
}
response, err := api.Provision(ctx)
require.NoError(t, err)
err = response.Send(request)
require.NoError(t, err)
var (
foundUserEnv = false
// Some CODER_ environment variables used by our Terraform provider
// must make it through.
foundCoderEnv = false
)
for {
msg, err := response.Recv()
require.NoError(t, err)

if log := msg.GetLog(); log != nil {
t.Log(log.Level.String(), log.Output)
if strings.Contains(log.Output, passedValue) {
foundUserEnv = true
}
if strings.Contains(log.Output, "CODER_") {
foundCoderEnv = true
}
require.NotContains(t, log.Output, secretValue)
}
if c := msg.GetComplete(); c != nil {
require.Empty(t, c.Error)
break
}
}
require.True(t, foundUserEnv)
require.True(t, foundCoderEnv)
}
55 changes: 55 additions & 0 deletions provisioner/terraform/safeenv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package terraform

import (
"os"
"strings"
)

// We must clean CODER_ environment variables to avoid accidentally passing in
// secrets like the Postgres connection string. See
// https://github.com/coder/coder/issues/4635.
//
// safeEnviron() is provided as an os.Environ() alternative that strips CODER_
// variables. As an additional precaution, we check a canary variable before
// provisioner exec.
//
// We cannot strip all CODER_ variables at exec because some are used to
// configure the provisioner.

const unsafeEnvCanary = "CODER_DONT_PASS"

func init() {
os.Setenv(unsafeEnvCanary, "true")
}

func envName(env string) string {
parts := strings.SplitN(env, "=", 1)
if len(parts) > 0 {
return parts[0]
}
return ""
}

func isCanarySet(env []string) bool {
for _, e := range env {
if envName(e) == unsafeEnvCanary {
return true
}
}
return false
}

// safeEnviron wraps os.Environ but removes CODER_ environment variables.
func safeEnviron() []string {
env := os.Environ()
strippedEnv := make([]string, 0, len(env))

for _, e := range env {
name := envName(e)
if strings.HasPrefix(name, "CODER_") {
continue
}
strippedEnv = append(strippedEnv, e)
}
return strippedEnv
}
4 changes: 2 additions & 2 deletions site/src/components/Resources/AgentLatency.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ export const AgentLatency: FC<{ agent: WorkspaceAgent }> = ({ agent }) => {
>
<HelpTooltipTitle>Latency</HelpTooltipTitle>
<HelpTooltipText>
Latency from relay servers, used when connections cannot connect
peer-to-peer. Star indicates the preferred relay.
This is the latency overhead on non peer to peer connections. The star
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This is the latency overhead on non peer to peer connections. The star
This is the latency overhead on non peer-to-peer connections. The star

indicates the preferred relay.
</HelpTooltipText>

<HelpTooltipText>
Expand Down
46 changes: 0 additions & 46 deletions site/src/components/Resources/ResourceAgentLatency.stories.tsx

This file was deleted.

70 changes: 0 additions & 70 deletions site/src/components/Resources/ResourceAgentLatency.tsx

This file was deleted.