feat: plumb user secrets through provisioner chain to terraform#24542
feat: plumb user secrets through provisioner chain to terraform#24542
Conversation
b7bc9ed to
e457dc3
Compare
6fad51d to
01ca924
Compare
Passes user secrets from coderd to the Terraform process at workspace build time so the `data.coder_secret` data source in terraform-provider-coder can resolve values at plan time. Secrets traverse two proto hops: `provisionerdserver` fetches them via `ListUserSecretsWithValues`, attaches them to `AcquiredJob.WorkspaceBuild.user_secrets` on `provisionerd.proto`; `runner.go` forwards into `PlanRequest.user_secrets` on `provisioner.proto`; the Terraform provisioner encodes each as `CODER_SECRET_ENV_<name>` or `CODER_SECRET_FILE_<hex(path)>` before invoking `terraform plan`. Only plan requests carry secrets; apply runs with `nil` because values are baked into plan state. Fetch is gated on `WorkspaceTransitionStart`: stop and delete transitions never carry secrets, so revoking or deleting a stored secret cannot make a workspace unstoppable. DB errors on the fetch fail the job outright rather than silently continuing with an empty secret set.
01ca924 to
a8372f7
Compare
johnstcn
left a comment
There was a problem hiding this comment.
need to update provisionerd/proto/version.go
Updated, thanks! I'm going to follow up this PR with a short doc comment in the proto file somewhere that notes the API version must be updated. Myself and an agent missed updating this. I suspect an agent would have caught it when asking "what have I missed?". |
|
/coder-agents-review |
There was a problem hiding this comment.
Well-structured plumbing change. The proto chain is correctly layered, the transition gating is sound, the error path is explicit (fail-fast, not silent-empty), and the test suite covers the key scenarios with genuine assertions. The InTx-aware DB wrapper in the test harness shows attention to the project's documented pitfalls. Melody walked all six serialization pairings (DB to proto to proto to env vars) and found every layer agrees on types, optionality, and filtering. Kurapika verified authorization, identity binding, input validation, logging, and transport.
4 P3, 2 P4, 4 Nit. No blockers.
Hisoka, tracing the canary path in safeenv.go: "A function that looks like it splits on = but doesn't. A safety net that was never connected. I've been waiting to find one of these."
provisioner/terraform/safeenv.go:26
P4 [DEREM-7] Pre-existing: SplitN(env, "=", 1) returns at most 1 element, which is the whole key=value string. So envName("CODER_DONT_PASS=true") returns "CODER_DONT_PASS=true", not "CODER_DONT_PASS". This means isCanarySet always returns false (it compares the full string against just the name), and the canary check at executor.go:85 is a no-op.
safeEnviron() still works correctly because HasPrefix(name, "CODER_") matches the full string. But the canary was designed to detect regressions if the stripping logic ever breaks. With this PR routing secret values through the same env slice, the consequence of a safeEnviron regression includes leaking user secrets alongside host env vars, with no canary to catch it.
Fix: SplitN(env, "=", 2). Not introduced by this PR, but the consequence is elevated. (Hisoka)
🤖
🤖 This review was automatically generated with Coder Agents.
Update comments and add test coverage for delete transition.
dylanhuff-at-coder
left a comment
There was a problem hiding this comment.
First pass, will look more thoroughly tomorrow, but my understanding is that this will store unencrypted secrets in workspace_builds.provisioner_state. If that is true, should this PR scrub the secret before storing it or is that slated for future work?
|
It's a good point, provisioner state is extremely sensitive and can contain secrets already. I'd view this as a follow-up and out of scope of this change. |
|
RE: provisioner state, I proposed in the RFC that we scrub secrets from Terraform state before it persists https://www.notion.so/coderhq/User-Secrets-32cd579be59280a98192dec7b3b5daf1?source=copy_link#335d579be5928097bb40c10862694eb4 I was planning to tackle this in a subsequent PR to prevent this one from blowing up too much. |
Passes user secrets from coderd to the Terraform process at workspace build time so the
data.coder_secretdata source in terraform-provider-coder can resolve values at plan time.Secrets traverse two proto hops:
provisionerdserverfetches them viaListUserSecretsWithValues, attaches them toAcquiredJob.WorkspaceBuild.user_secretsonprovisionerd.proto;runner.goforwards intoPlanRequest.user_secretsonprovisioner.proto; the Terraform provisioner encodes each asCODER_SECRET_ENV_<name>orCODER_SECRET_FILE_<hex(path)>before invokingterraform plan. Only plan requests carry secrets; apply runs withnilbecause values are baked into plan state.Fetch is gated on a workspace transitioning to start. stop and delete transitions never carry secrets, so revoking or deleting a stored secret cannot make a workspace unstoppable. DB errors on the fetch fail the job outright rather than silently continuing with an empty secret set.
Corresponding terraform-provider-coder change: coder/terraform-provider-coder#501