From 9c5b7da7ad1cfd192e562447dc001df216048e87 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sat, 31 Oct 2026 14:38:28 -0700 Subject: [PATCH] feat(staging): scope staging sign-in to the team Staging register + login were ungated: REGISTRATION_INVITE_ONLY was on but AUTH_ALLOWED_DOMAINS/EMAILS were unset, so instanceAccessPolicy.isInstanceAccessRestricted() returned false and any invite code let any email register and sign in. Scope sign-in to the team on staging only: - values-staging: AUTH_ALLOWED_DOMAINS = seed.staging.commonly.me (seed accounts) + ops.commonly.invalid (both CI smoke accounts, so staging-smoke stays green), plus a flag to surface AUTH_ALLOWED_EMAILS from the secret. - backend-deployment + ops-runner: both env vars behind their own {{- if }} guards (ops-runner mirrors backend per opsRunnerEnvMirror.test.js). - api-keys ExternalSecret: a gated auth-allowed-emails entry (remoteRef commonly-staging-auth-allowed-emails), same "remote key must exist first" rule as the Slack block. Team-member emails live only in the GCP Secret Manager secret, never in the repo. Prod/values-dev sets none of these: both templates render zero AUTH_ALLOWED lines and no new ExternalSecret key, so prod stays public and its api-keys sync is unchanged (verified via helm template on both value sets). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_015AKgpNr8tFMMj8aFvFPUdA --- .../templates/core/backend-deployment.yaml | 14 ++++++++++++++ .../templates/ops/ops-runner-deployment.yaml | 14 ++++++++++++++ k8s/helm/commonly/templates/secrets/api-keys.yaml | 10 ++++++++++ k8s/helm/commonly/values-staging.yaml | 9 +++++++++ 4 files changed, 47 insertions(+) diff --git a/k8s/helm/commonly/templates/core/backend-deployment.yaml b/k8s/helm/commonly/templates/core/backend-deployment.yaml index 09aadf4afd..b14682ff32 100644 --- a/k8s/helm/commonly/templates/core/backend-deployment.yaml +++ b/k8s/helm/commonly/templates/core/backend-deployment.yaml @@ -119,6 +119,20 @@ spec: - name: REGISTRATION_INVITE_ONLY value: {{ .Values.backend.env.registrationInviteOnly | quote }} {{- end }} + {{- if .Values.backend.env.authAllowedDomains }} + # Instance access allowlist. Any value here flips + # isInstanceAccessRestricted() true and gates BOTH register and login. + # Unset (prod/values-dev) = public. Staging scopes sign-in to the team. + - name: AUTH_ALLOWED_DOMAINS + value: {{ .Values.backend.env.authAllowedDomains | quote }} + {{- end }} + {{- if .Values.backend.env.authAllowedEmailsFromSecret }} + - name: AUTH_ALLOWED_EMAILS + valueFrom: + secretKeyRef: + name: api-keys + key: auth-allowed-emails + {{- end }} # Database Connections - name: MONGO_URI diff --git a/k8s/helm/commonly/templates/ops/ops-runner-deployment.yaml b/k8s/helm/commonly/templates/ops/ops-runner-deployment.yaml index 1e8c169d99..1aced8bcd9 100644 --- a/k8s/helm/commonly/templates/ops/ops-runner-deployment.yaml +++ b/k8s/helm/commonly/templates/ops/ops-runner-deployment.yaml @@ -86,6 +86,20 @@ spec: - name: REGISTRATION_INVITE_ONLY value: {{ .Values.backend.env.registrationInviteOnly | quote }} {{- end }} + {{- if .Values.backend.env.authAllowedDomains }} + # Mirrors backend (ops-runner env drift guard). The allowlist is read by + # the serving API, not the sleeping ops-runner, but the two env blocks + # must stay tuple-identical — see opsRunnerEnvMirror.test.js. + - name: AUTH_ALLOWED_DOMAINS + value: {{ .Values.backend.env.authAllowedDomains | quote }} + {{- end }} + {{- if .Values.backend.env.authAllowedEmailsFromSecret }} + - name: AUTH_ALLOWED_EMAILS + valueFrom: + secretKeyRef: + name: api-keys + key: auth-allowed-emails + {{- end }} # Database Connections - name: MONGO_URI diff --git a/k8s/helm/commonly/templates/secrets/api-keys.yaml b/k8s/helm/commonly/templates/secrets/api-keys.yaml index 9da58fa87a..796d08b0c3 100644 --- a/k8s/helm/commonly/templates/secrets/api-keys.yaml +++ b/k8s/helm/commonly/templates/secrets/api-keys.yaml @@ -318,4 +318,14 @@ spec: - secretKey: session-secret remoteRef: key: {{ $keyPrefix }}session-secret + + {{- if .Values.backend.env.authAllowedEmailsFromSecret }} + # Team-only sign-in allowlist. The remote key must exist in Secret + # Manager before this flips — a missing remoteRef prevents ESO from syncing + # the whole api-keys secret (same rule as the Slack block above). Only + # staging sets the flag; prod leaves it unset and stays public. + - secretKey: auth-allowed-emails + remoteRef: + key: {{ $keyPrefix }}auth-allowed-emails + {{- end }} {{- end }} diff --git a/k8s/helm/commonly/values-staging.yaml b/k8s/helm/commonly/values-staging.yaml index e0149a4c20..0df2880a8e 100644 --- a/k8s/helm/commonly/values-staging.yaml +++ b/k8s/helm/commonly/values-staging.yaml @@ -176,6 +176,15 @@ backend: agentComputerSteelApiUrlTemplate: "http://steel-{userId}.commonly-staging.svc.cluster.local:3000" agentComputerSteelCdpUrlTemplate: "http://steel-{userId}.commonly-staging.svc.cluster.local:9223" agentComputerAllowedHosts: "linkedin.com,www.linkedin.com" + # Team-only staging sign-in. Any value flips isInstanceAccessRestricted() + # true and gates BOTH register and login; prod (values-dev) sets neither and + # stays public. Domains cover the seed accounts (@seed.staging.commonly.me) and + # both CI smoke accounts (@ops.commonly.invalid) so staging-smoke stays green. + # Individual team emails are PII and live in the GCP SM secret + # commonly-staging-auth-allowed-emails (NOT in this repo), surfaced via the + # api-keys ExternalSecret → AUTH_ALLOWED_EMAILS when the flag below is true. + authAllowedDomains: "seed.staging.commonly.me,ops.commonly.invalid" + authAllowedEmailsFromSecret: true # Smaller Postgres pool for staging's Aiven db (fewer connections than prod). pgPoolMax: 5 # Non-secret db name; host/user/password come from the private values +