Code Review — deployments/
Date: 2026-08-21
Scope: all code under deployments/ — shell scripts (deploy-all.sh, set-domain.sh, */deploy*.sh, run-sql.sh, seed_data.sh, lib/*.sh), Terraform (*/*.tf), and Python bootstrap scripts. Excluded: .terraform/, *.tfstate*, terraform.tfstate.d/, *.tfvars/.env (secrets, not opened), SQL files, Caddyfile.
Method: independent fresh-eyes pass over every in-scope file, then each load-bearing finding curated and spot-verified against the source (file:line confirmed for H1, M4, M6, L2, and others).
Security scan note: every file was checked for AI/agent-directed instruction text (prompt injection). None found — the only
llmhits are a legitimatedata-view-for-llm.sqlreference. (The earlier/code-reviewrun that surfaced ajava-thetailor-review“system-reminder” was a skill-router artifact in that agent’s output, not content planted in the repo.)
Overall assessment
A carefully written, well-commented deployment layer. The four SSH/container app-deploy scripts share a consistent structure; Terraform modules use preconditions + workspaces for env isolation; secrets are kept out of committed files and written to chmod 600 env-files on the VMs. Core logic is sound. The material weaknesses are: (1) an insecure-by-default OAuth wildcard redirect, (2) a pervasive pattern of passing secrets as command-line arguments (base64 is obfuscation, not encryption), and (3) Terraform state gaps — no locking on the S3 backend and load_balancer having no remote backend despite being an orchestrated IaC step. Everything else is robustness/consistency polish.
Severity counts: 1 High · 5 Medium · 7 Low · 4 Nit.
High
H1 — Wildcard OAuth redirect URI shipped by default
sso/bootstrap-realm.py:26, :90 · deploy-all.sh:157
- Issue: If
sso_redirect_urisis not set in the overlay, the realm bootstrap registersredirectUris: ["*"]for the confidentialcustomer360-apiclient (standard flow + direct-access grants enabled), withwebOrigins: ["+"]. A*redirect URI is the canonical OAuth misconfiguration (auth-code interception / open redirect);webOrigins:["+"]broadens CORS to every registered origin. The client secret partially mitigates code redemption, but this is insecure-by-default and flagged by Keycloak and every scanner. - Fix: Require an explicit, non-wildcard redirect list. In
deploy-all.sh:157drop the:-*fallback anddieifsso_redirect_urisis unset; inbootstrap-realm.pyreject"*"/empty forREDIRECT_URIS. ScopewebOriginsto the real public origin.
Medium
M2 — Secrets passed as command-line arguments to ssh/bash -s
server/deploy-api.sh:115 · server/deploy-backend.sh:71 · server/seed_data.sh:70 · cache/deploy.sh:76 · sso/deploy-sso.sh:89-91 · ads-server/deploy-ads.sh:118 · frontend/deploy-frontend.sh:88 · postgres/run-sql.sh:70-74 · monitoring/deploy-monitoring.sh:100,137-146
- Issue: DB master password,
GHCR_TOKEN/GITHUB_TOKEN, Keycloak admin + client secrets, Portainer admin password, and oauth2 secrets are base64-encoded and passed as argv tossh→bash -s. base64 is trivially reversible, and argv is world-readable viaps -ef//proc/<pid>/cmdlineon both the deploy host and the target VM for the process lifetime. Real exposure on a shared CI runner or multi-tenant VM. (The GHCR-token argv on the app scripts was introduced with the CD work; the DB-password pattern predates it.) - Fix: Send secret material on a separate stdin channel the remote reads with
readbefore running the body, or viassh -o SetEnvwithAcceptEnvon the box — not positional args.
M3 — oauth2-proxy secrets in container argv + cookie over plain HTTP
monitoring/deploy-monitoring.sh:214-215
- Issue: The OIDC client secret and cookie secret sit in the running container’s argv for its whole lifetime (readable via
docker inspect/ps).--cookie-secure=falsesends the session cookie over the plain-HTTP L4-LB path — interceptable/replayable (dashboard session hijack). - Fix: Pass via
--client-secret-file/ env-file (OAUTH2_PROXY_*); terminate TLS in front of the proxy and set--cookie-secure=true.
M4 — S3 remote backend has no state locking
server/backend.tf · postgres/backend.tf · cache/backend.tf
- Issue: The
backend "s3"blocks have no lock mechanism (no DynamoDB, nouse_lockfile). Concurrent applies (a local run + CI, or two operators) can interleave and corrupt state. The-lock-timeout="$LOCK_TIMEOUT"the moduledeploy.shscripts pass has nothing to lock against — it’s a no-op. This contradicts the modules’ own header note (“back this with … locking”). - Fix (revised 2026-08-21 —
use_lockfileNOT viable): A probe confirmed vStorage does not enforce S3 conditional PUT (If-None-Match) — a secondPutObjectwithIfNoneMatch:*succeeded instead of returning412. So Terraform’s S3-native locking (use_lockfile) would give false safety, and there is no DynamoDB. Resolution: mitigate operationally — CD already serialises via itsconcurrencygroup (single writer), and operators must never run twoapplys against the same module/workspace at once. The-lock-timeoutflags are harmless no-ops. (Revisit if vStorage gains conditional-PUT support.)
M5 — Floating-IP discovery is inconsistent across scripts
App scripts (deploy-api.sh:39, deploy-backend.sh:33, seed_data.sh:42, cache/deploy.sh:53, sso/deploy-sso.sh:56, proxy/deploy-caddy.sh:56, frontend/deploy-frontend.sh:46, ads-server/deploy-ads.sh:45, monitoring/deploy-monitoring.sh:85) read floating_ip only from internal_interfaces; but postgres/run-sql.sh:49-62 scans both external_interfaces and internal_interfaces.
- Issue: Contradictory assumptions about where the provider surfaces the public IP. If the schema ever places it under
external_interfaces(whichrun-sql.shanticipates), all app scripts silently fail with “no floating IP for server key …”; converselyrun-sql.shcould target a different IP than the deploy scripts. (Provider schema not verifiable in this review.) - Fix: Extract one shared helper that searches both interface lists (matching
run-sql.sh) and use it everywhere.
M6 — load_balancer has no remote backend but is an orchestrated IaC step
load_balancer/ (no backend.tf) · deploy-all.sh:174 · lib/tfstate.sh:17
- Issue: The LB module keeps local state, yet
deploy-all.shapplies it viatf_stepandlib/tfstate.shaligns onlyserver postgres cacheto remote. From CI (fresh checkout) or a second machine there is no shared LB state → a re-apply creates a duplicate load balancer (or can’t manage/destroy the existing one). (storage/also has local state, but that’s the justified bootstrap chicken-and-egg — it creates the state bucket.) - Fix: Add
backend "s3"toload_balancer(key = "load_balancer/terraform.tfstate") mirroring the other three, and addload_balancertoTF_REMOTE_MODULESinlib/tfstate.sh:17.
Low
- L1 — SSH host-key verification disabled everywhere (
StrictHostKeyChecking=no,UserKnownHostsFile=/dev/nullin everySSH_OPTS, andrun-sql.sh:74). MITM risk on connect — most impactful inrun-sql.sh/deploy-api.shwhere a spoofed bastion receives the DB password. Fix: pin host keys for stable hosts, or useaccept-new. - L2 —
required_versionunderstates the real minimum.server/,postgres/,cache/,load_balancer/,storage/provider.tfall say>= 1.3, but the S3backend.tfneeds ≥ 1.6 (endpoints,use_path_style,skip_s3_checksum). Running under 1.3–1.5 passes the gate then fails obscurely. Fix: setrequired_version = ">= 1.6"on the S3-backend modules. - L3 — GHCR credential persists on the VM.
deploy-api.sh:167,deploy-backend.sh:95,frontend/deploy-frontend.sh:105,ads-server/deploy-ads.sh:137docker login ghcr.iowith nologout; token lingers in~/.docker/config.json. Fine for CI’s ephemeral token, risky for a long-lived PAT. Fix:docker logout ghcr.ioafter pull, or use short-lived tokens. (Introduced with the CD work.) - L4 — Reading a sibling module’s output mutates its selected workspace globally.
terraform workspace select "$ENV"writes.terraform/environment, so deployingapiforuatsilently switches../postgres/../cacheto another workspace (footgun for a later manualterraformthere). Not a script correctness bug. Fix: read outputs with an explicit workspace in one invocation, or restore the prior workspace. - L5 —
run-sql.shpicks an arbitrary server as bastion (postgres/run-sql.sh:49-63,out[0]). Non-deterministic which VM runs the SQL bootstrap. Fix: select by an explicit*_SERVER_KEYlike the other scripts. - L6 — Bootstrap scripts continue after a failed client create.
sso/bootstrap-realm.py:97-98,monitoring/bootstrap-oauth2-client.py:81-83:cuidderived fromLocationwith no status check → on failurecuid=""→ malformed.../clients//...calls fail silently while the script prints “DONE”. Fix:sys.exitwhen create status isn’t 201/204 orcuidis empty. - L7 —
cacheprod apply skips the plan/review guard.cache/deploy.sh:124runsterraform apply -auto-approvedirectly, whereaspostgres/server/load_balancer/storageplan with-detailed-exitcode -out=tfplanthen apply the saved plan. Fix: mirror the plan-then-apply-saved-plan pattern for cache prod.
Nits
- N1 —
storage/undeploy.sh:71:${FORCE:+ (force)}prints “(force)” even whenFORCE=0(non-empty string). The actual-var=force_destroygate at:58uses-eq 1and is correct — only the log label is wrong. Use$([[ $FORCE -eq 1 ]] && echo ' (force)'). - N2 —
server/deploy-api.sh:68vs:142: prodREDIS_PORTdefaults to6379locally but the remote heredoc defaults to6580— the remote default is dead/inconsistent. - N3 —
server/deploy-api.sh:67,ads-server/deploy-ads.sh:71: hardcodeterraform workspace select prodinstead of"$ENV". Correct only becauseprodis the sole non-uatenv today; breaks if another is added. - N4 —
set-domain.sh:23:--dry-runis recognised only as the first argument;./set-domain.sh newdomain uat --dry-runwould write for real. Parse the flag in any position.
Recommended priority
- H1 — remove the wildcard redirect default (security, quick).
- M6 + M4 —
load_balancerremote backend +use_lockfile = trueon all four (state integrity; directly hardens the CD work landed this session). - M2 / M3 — move secrets off argv / harden oauth2-proxy (defense-in-depth).
- L2, L3, N1–N4 — quick correctness/consistency wins.
- M5, L4–L7 — consistency refactors (shared IP helper, explicit workspaces/server keys, plan guards).
Findings are advisory; none block current operation — the pipeline is deploying uat successfully. Items tagged “introduced with the CD work” (M2 GHCR-token argv, L3) came from this session’s changes; the rest predate it.
Remediation applied (2026-08-21)
A focused hardening pass fixed the top items:
- H1 — FIXED.
bootstrap-realm.pynow defaultsREDIRECT_URISto empty and exits if the list is empty or contains*;deploy-all.shrealm step drops the:-*fallback anddies unlesssso_redirect_urisis set to an explicit, non-wildcard list. No more wildcard redirect by default. - M6 — FIXED. Added
load_balancer/backend.tf(S3/vStorage remote backend,key=load_balancer/terraform.tfstate), bumped itsrequired_versionto>= 1.6, addedload_balancertoTF_REMOTE_MODULESinlib/tfstate.sh, and migrated its existing local state to the remote bucket (uat workspace + outputs verified). CI/second-machine can no longer spawn a duplicate LB. - M4 — RESOLVED as won’t-fix-with-mitigation.
use_lockfilewas investigated and rejected: vStorage does not enforceIf-None-Match(verified probe), so native locking is not possible. Mitigation is operational (CDconcurrencyserialisation + no concurrent applies), now documented above and in the backend files.
Remaining M/L/Nit items are unaddressed and tracked in this report for a future pass.