Skip to content

sec(iac/gcp): fail closed on Cloud Run scheduled-task auth mode - #385

Merged
cristim merged 1 commit into
mainfrom
sec/123-gcp-scheduled-task-auth-mode
Sep 28, 2026
Merged

cristim merged 1 commit into
mainfrom
sec/123-gcp-scheduled-task-auth-mode

Conversation

@cristim

@cristim cristim commented Sep 28, 2026 •

Copy link
Copy Markdown
Member

Summary

terraform/modules/compute/gcp/cloud-run derived SCHEDULED_TASK_AUTH_MODE from whether a Cloud Scheduler job was enabled, falling back to a bare "disabled" whenever both var.enable_scheduled_tasks and var.enable_ri_exchange_schedule were false.

Root cause

That conflates "does Terraform create a Cloud Scheduler job" with "does the application authenticate requests to /api/scheduled/*". The Cloud Run service is reachable from the open internet independently of the scheduler flags whenever var.allow_unauthenticated = true (the module default) or ingress = INGRESS_TRAFFIC_ALL, so a tfvars flip that disables both schedulers (a rollout, or a cost-saving change) silently turns off request authentication on money-path handlers (ri_exchange, ladder_run, fire_scheduled_purchases) while the service stays publicly reachable.

Verified against current main: all three GCP environments (github-dev.tfvars, github-staging.tfvars, github-prod.tfvars) currently set enable_scheduled_tasks = true, so this has not fired in any live environment, but nothing in the code prevented it.

terraform/modules/compute/gcp/gke already fixed the identical defect for its own ingress path (see gke/main.tf:35-48 and gke/variables.tf:270-289, scheduled_task_auth_mode_override), so this PR ports the same, already-reviewed fix to the Cloud Run module instead of inventing a different approach.

Fix

  • cloud-run/main.tf: scheduled_task_auth_mode now falls back to var.scheduled_task_auth_mode_override instead of the bare "disabled" literal when neither scheduler is enabled.
  • cloud-run/variables.tf: new scheduled_task_auth_mode_override variable, default "oidc", validated to "oidc" | "bearer" | "disabled" (the exact set internal/server/scheduledauth accepts).

This fails closed by construction rather than by an explicit plan-time guard: with the override left at its default "oidc" and no scheduler SA, SCHEDULED_TASK_OIDC_SUBJECTS is empty, and internal/server/scheduledauth.configureOIDC (internal/server/scheduledauth/validator.go:157) returns ErrConfigInvalid at startup rather than letting the app boot with /api/scheduled/* unauthenticated. Setting the override to "disabled" is now a deliberate, explicit opt-in (local-dev / dry-run), never a side effect of flipping an unrelated feature flag.

No environment tfvars changes are needed: none of the three GCP envs sets this new variable, so all three keep their current behavior (enable_scheduled_tasks = true still forces "oidc" unconditionally, as before) and only gain the fail-closed fallback for the untested "both schedulers off" path.

Regression test

terraform/modules/compute/gcp/cloud_run_scheduled_task_auth_test.go (new) asserts:

  • main.tf no longer derives ... ? "oidc" : "disabled" directly from the scheduler flags.
  • main.tf does fall back to var.scheduled_task_auth_mode_override.
  • variables.tf declares that variable, defaulting to "oidc", with the three-value validation.

Proved it fails pre-fix: git stash of the main.tf/variables.tf changes, ran go test ./terraform/modules/compute/gcp/... -run TestCloudRunScheduledTaskAuth -v and the test failed on all three assertions. Restored the fix and both tests in the package pass.

Verification

  • terraform fmt -check -diff terraform/modules/compute/gcp/cloud-run/: clean.
  • terraform init -backend=false && terraform validate in terraform/modules/compute/gcp/cloud-run/: Success! The configuration is valid.
  • terraform init -backend=false && terraform validate in terraform/environments/gcp/ (the module's real consumer): Success! The configuration is valid.
  • tflint in terraform/modules/compute/gcp/cloud-run/: no findings.
  • GOWORK=off GOTOOLCHAIN=go1.26.6 go test ./terraform/modules/compute/gcp/... -v: 2/2 pass.
  • GOWORK=off GOTOOLCHAIN=go1.26.6 go vet ./terraform/modules/compute/gcp/...: clean.
  • gofmt -l on the new test file: no output.

Never ran terraform plan/apply against real infra, per this project's convention.

Closes #123

Summary by CodeRabbit

  • Security
    • Scheduled-task authentication now defaults to OIDC even when both scheduler options are disabled. The authentication mode can be explicitly overridden in that case, with options for OIDC, bearer authentication, or disabled authentication.
    • When either scheduler option is enabled, OIDC remains required and the override is ignored. A missing subject allow-list can cause startup to fail; disabling authentication is intended for local development or dry runs.

terraform/modules/compute/gcp/cloud-run derived SCHEDULED_TASK_AUTH_MODE
from whether a Cloud Scheduler job was enabled:

  scheduled_task_auth_mode = (var.enable_scheduled_tasks ||
    var.enable_ri_exchange_schedule) ? "oidc" : "disabled"

That conflates "does Terraform create a scheduler" with "does the app
authenticate /api/scheduled/*". The service is reachable from the open
internet independently of the scheduler flags whenever
var.allow_unauthenticated = true (its default) or ingress is
INGRESS_TRAFFIC_ALL, so a tfvars flip that disables both schedulers
silently turns off request auth on money-path handlers (ri_exchange,
ladder_run, fire_scheduled_purchases) on an already-public service. All
three GCP envs currently set enable_scheduled_tasks = true, so this has
not fired in prod, but nothing guarded against it firing.

terraform/modules/compute/gcp/gke already fixed the identical defect for
its own Cloud Run analogue (kubernetes ingress) by falling back to a
fail-closed override variable instead of a bare "disabled" literal. Port
the same fix: when neither scheduler is enabled, fall back to the new
var.scheduled_task_auth_mode_override (default "oidc"). With no scheduler
SA, that means an empty SCHEDULED_TASK_OIDC_SUBJECTS, which
internal/server/scheduledauth's configureOIDC rejects at startup
(ErrConfigInvalid) rather than booting the app with /api/scheduled/*
unauthenticated. "disabled" now requires an explicit, deliberate opt-in.

Regression test: terraform/modules/compute/gcp/cloud_run_scheduled_task_auth_test.go
asserts main.tf no longer derives the bare ... ? "oidc" : "disabled" form,
does fall back to var.scheduled_task_auth_mode_override, and that the new
variable defaults to "oidc" with the same three-value validation the Go
validator accepts. Confirmed it fails against the pre-fix main.tf/variables.tf
(git stash of the fix) and passes after.

Closes #123
@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: LeanerCloud/cloud-commitments-platform/.coderabbit.yaml

Review profile: CHILL

Plan: Essentials

Run ID: 89af7b6f-ae24-450c-9ebf-e66784155cf7

📥 Commits

Reviewing files that changed from the base of the PR and between f774e17 and ef64fd1.

📒 Files selected for processing (3)
  • terraform/modules/compute/gcp/cloud-run/main.tf
  • terraform/modules/compute/gcp/cloud-run/variables.tf
  • terraform/modules/compute/gcp/cloud_run_scheduled_task_auth_test.go

Included review availability: This review used your included allowance. 2 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 4 reviews per hour.


📝 Walkthrough

Walkthrough

The Cloud Run module now defaults scheduled-task authentication to oidc when both scheduler flags are off. An explicit override can select oidc, bearer, or disabled. A Go regression test checks the fallback and variable constraints.

Changes

Cloud Run scheduled-task authentication

Layer / File(s) Summary
Define and verify the authentication-mode fallback
terraform/modules/compute/gcp/cloud-run/variables.tf, terraform/modules/compute/gcp/cloud-run/main.tf, terraform/modules/compute/gcp/cloud_run_scheduled_task_auth_test.go
The new override defaults to oidc and accepts oidc, bearer, or disabled. The module uses the override when both scheduler flags are off. The regression test checks the fallback and variable constraints.

Priority: ⬆️ High

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: High

Merge Risk: ⚪ Minimal · up to ef64f

Disabling both schedulers no longer implicitly disables scheduled-task authentication. No actionable merge-blocking risk remains after normal checks.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 1 files. (2 skipped: 2 … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR satisfies the coding objective in #123. main.tf no longer derives "disabled" from both scheduler flags. When both flags are false, it uses var.scheduled_task_auth_mode_override. `variable…
Out of Scope Changes check ✅ Passed The changes stay within #123. They update the GCP Cloud Run auth-mode selection, add the required explicit variable, and add a regression test for the security behavior. The comments document the same…
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: making Cloud Run scheduled-task authentication fail closed.
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 1 files. (2 skipped: 2 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Comment @coderabbitai help to get the list of available commands.

@cristim cristim added urgency/this-sprint Within the current sprint priority/p1 Next up; this sprint severity/high Significant harm impact/all-users Affects every user effort/s Hours type/security Security finding triaged Item has been triaged labels Sep 28, 2026
@cristim

cristim commented Sep 28, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Already reviewed the last commit. Use @coderabbitai full review to rerun a review of the entire changeset.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim

cristim commented Sep 28, 2026

Copy link
Copy Markdown
Member Author

Independent adversarial review + local verification: MERGE at ef64fd1. Cloud Run's scheduled-task auth mode now falls back to an explicit, validated override defaulting to oidc (the same set scheduledauth.New accepts) instead of 'disabled'; scheduler oidc_token blocks untouched; all three envs keep enable_scheduled_tasks so no plan diff; with both schedulers off, startup now fails rather than serving /api/scheduled/* unauthenticated. Test fails pre-fix (3 assertions); fmt/validate/tflint clean.

@cristim
cristim merged commit f8927c8 into main Sep 28, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/all-users Affects every user priority/p1 Next up; this sprint severity/high Significant harm triaged Item has been triaged type/security Security finding urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sec(iac/gcp): Cloud Run scheduled-task auth mode is derived from scheduler flags, not a real gate

1 participant