mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-09-30 08:53:15 +00:00
chore(infra): prep proposal-system for seahaven-prod deployment (#227)
* chore(infra): retarget prod to seahaven-prod account + OIDC deploy-role artifacts Retarget the CDK prod env from mgmt (328440206208, now frozen for workloads) to the dedicated seahaven-prod workload account (011934824531). proposal-system is the org's first prod tenant. Hard-block env=staging (still targets frozen mgmt) in resolveConfig until it is retargeted to seahaven-dev (710827005802). Add a WARN-only out-of-pipeline deploy guard in bin/app.ts. Add infra/deploy-role/: OIDC trust policy (sub scoped to Sea-Haven-Industries/proposal-system:ref:refs/heads/main), least-privilege permissions policy (AssumeRole on the verified cdk-hnb659fds bootstrap roles, deterministic site bucket, account-scoped CloudFront invalidation), and an idempotent creation script. Verified against live prod: bootstrap qualifier hnb659fds v32, OIDC provider present. Passed GPT-4.1 cross-review (APPROVE) and workflow red-team (CLEAN). Role NOT yet created — gated on /sh-security-review + the deploy go-ahead. Docs: README + CLAUDE.md reflect the prod account and pipeline-only deploy. * chore(infra): region-bound deploy-role DescribeStacks to us-east-1 (sh-security-review IAM-L2) * feat(infra): Aurora prod backup retention 14d + window; prod-only CDK context Bump Aurora automated-backup (PITR) retention 7->14d and set a preferred backup window for the prod tenant. Dedicated AWS Backup vault + cross-account restore test is a tracked follow-up (no org central-backup design exists yet). Prune the stale mgmt-account AZ context; prod (011934824531) is the only deploy target.
This commit is contained in:
parent
cac4384f0d
commit
0f9d27fbf0
10 changed files with 260 additions and 17 deletions
|
|
@ -35,7 +35,7 @@ Internal: Lambdas → .NET Function URL with Secrets Manager API key via custom
|
||||||
|
|
||||||
## Infrastructure
|
## Infrastructure
|
||||||
|
|
||||||
AWS us-east-1, Aurora PostgreSQL 15 Serverless v2 (RDS Data API + pgvector), S3, SQS+DLQ, Cognito+Google OAuth, Bedrock KB (Aurora pgvector store — ADR 0001), GitHub Actions OIDC, CloudFront+S3 OAC
|
Deploys to the **seahaven-prod** account (`011934824531`), us-east-1. (mgmt `328440206208` is frozen for workloads; staging is hard-disabled in `infra/lib/config.ts` until retargeted to seahaven-dev `710827005802`.) Aurora PostgreSQL 15 Serverless v2 (RDS Data API + pgvector), S3, SQS+DLQ, Cognito+Google OAuth, Bedrock KB (Aurora pgvector store — ADR 0001), GitHub Actions OIDC, CloudFront+S3 OAC. Prod deploys run via the `deploy.yaml` pipeline (workflow_dispatch) only — no local `cdk deploy` to prod.
|
||||||
|
|
||||||
## Agent Delegation Rules
|
## Agent Delegation Rules
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,11 @@ proposal-system/
|
||||||
|
|
||||||
## AWS Resources
|
## AWS Resources
|
||||||
|
|
||||||
All resources are in **us-east-1** (account 328440206208).
|
All resources deploy to **us-east-1** in the **seahaven-prod** workload account (`011934824531`).
|
||||||
|
The mgmt account (`328440206208`) is frozen for new workloads. Staging is not currently
|
||||||
|
deployable — it still references the frozen mgmt account and is hard-blocked in `infra/lib/config.ts`
|
||||||
|
until retargeted to seahaven-dev (`710827005802`). Prod deploys go through the `deploy.yaml`
|
||||||
|
pipeline (workflow_dispatch) only; no manual/local `cdk deploy` to prod.
|
||||||
|
|
||||||
| CDK Stack | Key Resources |
|
| CDK Stack | Key Resources |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,24 @@ import { resolveConfig } from '../lib/config';
|
||||||
|
|
||||||
const app = new cdk.App();
|
const app = new cdk.App();
|
||||||
|
|
||||||
// Multi-env (PR2): `-c env=staging` (default prod). prod keeps the exact construct IDs
|
// Multi-env (PR2): `-c env=staging` (default prod). prod targets seahaven-prod
|
||||||
// and stack names of the deployed stacks (stackSuffix=''); only staging is suffixed.
|
// (011934824531) with no stack-name suffix; staging is suffixed and currently hard-disabled
|
||||||
|
// (see resolveConfig — mgmt account is frozen).
|
||||||
const config = resolveConfig(app);
|
const config = resolveConfig(app);
|
||||||
const { env, stackSuffix } = config;
|
const { env, stackSuffix } = config;
|
||||||
|
|
||||||
|
// Pipeline-only deploy signal (WARN, not block). Prod deploys must go through the cd-cdk
|
||||||
|
// pipeline (GitHub Actions sets CI/GITHUB_ACTIONS). A local synth/deploy to prod is a
|
||||||
|
// drift risk + "no manual prod deploys" violation. True enforcement is an org-baseline SCP
|
||||||
|
// (tracked follow-up); this is the cheap in-repo backstop.
|
||||||
|
if (config.envName === 'prod' && !process.env.CI && !process.env.GITHUB_ACTIONS) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn(
|
||||||
|
'\n⚠️ Running the PROD CDK app outside CI. Prod deploys must go through the cd-cdk ' +
|
||||||
|
'pipeline (deploy.yaml, workflow_dispatch). Do NOT `cdk deploy` to prod locally.\n',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const foundation = new FoundationStack(app, `proposal-system-foundation${stackSuffix}`, {
|
const foundation = new FoundationStack(app, `proposal-system-foundation${stackSuffix}`, {
|
||||||
stackName: `proposal-system-foundation${stackSuffix}`,
|
stackName: `proposal-system-foundation${stackSuffix}`,
|
||||||
env,
|
env,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"availability-zones:account=328440206208:region=us-east-1": [
|
"availability-zones:account=011934824531:region=us-east-1": [
|
||||||
"us-east-1a",
|
"us-east-1a",
|
||||||
"us-east-1b",
|
"us-east-1b",
|
||||||
"us-east-1c",
|
"us-east-1c",
|
||||||
|
|
|
||||||
64
infra/deploy-role/README.md
Normal file
64
infra/deploy-role/README.md
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
# seahaven-prod OIDC deploy role — proposal-system
|
||||||
|
|
||||||
|
IAM artifacts for the GitHub Actions OIDC deploy role that lets the
|
||||||
|
`Sea-Haven-Industries/proposal-system` repo deploy the CDK stacks and sync the
|
||||||
|
frontend site bucket into **seahaven-prod**. Artifacts only — nothing here has
|
||||||
|
been applied to AWS.
|
||||||
|
|
||||||
|
## Resolved facts (Phase 0, read-only verification)
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|---|---|
|
||||||
|
| Account | `011934824531` (seahaven-prod) |
|
||||||
|
| Region | `us-east-1` |
|
||||||
|
| CDK qualifier | `hnb659fds` (AWS CDK **default** — bootstrap v32; no custom synthesizer needed) |
|
||||||
|
| OIDC provider ARN | `arn:aws:iam::011934824531:oidc-provider/token.actions.githubusercontent.com` (EXISTS) |
|
||||||
|
| Role name | `githubdeploy-proposal-system` |
|
||||||
|
| Subject scope | `repo:Sea-Haven-Industries/proposal-system:ref:refs/heads/main` (exact, no wildcard) |
|
||||||
|
| Site bucket | `proposal-system-web-011934824531` (frontend-stack convention) |
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- **trust-policy.json** — Web-identity trust policy. Federated principal is the
|
||||||
|
existing GitHub OIDC provider. `sts:AssumeRoleWithWebIdentity` gated by two
|
||||||
|
StringEquals conditions: `aud == sts.amazonaws.com` and an **exact** `sub`
|
||||||
|
match on the proposal-system repo's `main` branch (no `StringLike`, no
|
||||||
|
wildcard). Mirrors the structure of the existing
|
||||||
|
`githubdeploy-seahaven-org-baseline` role.
|
||||||
|
|
||||||
|
- **permissions-policy.json** — Least-privilege inline policy:
|
||||||
|
- `sts:AssumeRole` on the four CDK bootstrap roles (deploy, file-publishing,
|
||||||
|
lookup, image-publishing) scoped to qualifier `hnb659fds`, account, and
|
||||||
|
region. This is how a CDK deploy actually gains its power — no direct
|
||||||
|
service permissions are granted to the deploy role itself.
|
||||||
|
- `cloudformation:DescribeStacks` on `*` for the `cdk deploy` /
|
||||||
|
change-set health check.
|
||||||
|
- `s3:PutObject`/`DeleteObject`/`ListBucket` on the site bucket and its
|
||||||
|
objects for the frontend asset sync.
|
||||||
|
- `cloudfront:CreateInvalidation` on `*`, constrained by
|
||||||
|
`aws:ResourceAccount == 011934824531` (distribution ARNs aren't known at
|
||||||
|
author time; the account condition prevents cross-account use).
|
||||||
|
|
||||||
|
- **create-deploy-role.sh** — Idempotent bash (`aws --profile prod`). Verifies
|
||||||
|
the profile resolves to `011934824531`, then create-role (or
|
||||||
|
update-assume-role-policy if it exists) + put-role-policy. Safe to re-run.
|
||||||
|
**Gated:** do not execute until GPT-4.1 cross-review AND `/sh-security-review`
|
||||||
|
pass (IAM/trust change).
|
||||||
|
|
||||||
|
## Applying (after gates pass)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./create-deploy-role.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Then point the GitHub Actions workflow's `aws-actions/configure-aws-credentials`
|
||||||
|
step at the printed role ARN.
|
||||||
|
|
||||||
|
## Placeholders / follow-ups
|
||||||
|
|
||||||
|
- **None outstanding.** Qualifier resolved to the real value `hnb659fds`; no
|
||||||
|
`<QUALIFIER>` placeholder remains. OIDC provider exists, so no provider
|
||||||
|
creation prerequisite.
|
||||||
|
- CloudFront invalidation is scoped by account, not by distribution ARN — tighten
|
||||||
|
to the specific distribution ARN once frontend-stack is deployed if you want
|
||||||
|
per-resource least privilege.
|
||||||
71
infra/deploy-role/create-deploy-role.sh
Executable file
71
infra/deploy-role/create-deploy-role.sh
Executable file
|
|
@ -0,0 +1,71 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
###############################################################################
|
||||||
|
# create-deploy-role.sh
|
||||||
|
#
|
||||||
|
# Creates / updates the GitHub Actions OIDC deploy role
|
||||||
|
# `githubdeploy-proposal-system` in AWS account 011934824531 (seahaven-prod),
|
||||||
|
# us-east-1, for the Sea-Haven-Industries/proposal-system repo (main branch).
|
||||||
|
#
|
||||||
|
# GATE — DO NOT EXECUTE until BOTH of the following have passed:
|
||||||
|
# 1. GPT-4.1 cross-family review (IAM policy / trust-policy change), via:
|
||||||
|
# python3 ~/Documents/repositories/seahaven/security-review/cross_review.py \
|
||||||
|
# "Review this IAM deploy-role trust+permissions for over-permission: <artifacts>"
|
||||||
|
# (STATUS 2026-07-14: RUN — verdict APPROVE, no BLOCK.)
|
||||||
|
# 2. /sh-security-review (deep agentic pass — IaC/IAM is a gated surface)
|
||||||
|
#
|
||||||
|
# This is an IAM/trust change: run BOTH gates and resolve every confirmed
|
||||||
|
# critical/high before running. This script mutates AWS; the artifact-authoring
|
||||||
|
# task did NOT run it. It is idempotent and safe to re-run.
|
||||||
|
#
|
||||||
|
# Resolved facts (Phase 0, read-only verification):
|
||||||
|
# Account : 011934824531 (seahaven-prod)
|
||||||
|
# Region : us-east-1
|
||||||
|
# Qualifier : hnb659fds (AWS CDK DEFAULT — no custom synthesizer needed)
|
||||||
|
# OIDC prov : arn:aws:iam::011934824531:oidc-provider/token.actions.githubusercontent.com
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PROFILE="prod"
|
||||||
|
ROLE_NAME="githubdeploy-proposal-system"
|
||||||
|
POLICY_NAME="proposal-system-deploy"
|
||||||
|
ACCOUNT_ID="011934824531"
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
TRUST_POLICY="file://${SCRIPT_DIR}/trust-policy.json"
|
||||||
|
PERMS_POLICY="file://${SCRIPT_DIR}/permissions-policy.json"
|
||||||
|
|
||||||
|
echo "==> Verifying active account for profile '${PROFILE}'..."
|
||||||
|
CALLER_ACCOUNT="$(aws --profile "${PROFILE}" sts get-caller-identity --query Account --output text)"
|
||||||
|
if [[ "${CALLER_ACCOUNT}" != "${ACCOUNT_ID}" ]]; then
|
||||||
|
echo "ERROR: profile '${PROFILE}' resolves to account ${CALLER_ACCOUNT}, expected ${ACCOUNT_ID}. Aborting." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> Ensuring role '${ROLE_NAME}' exists with the correct trust policy..."
|
||||||
|
if aws --profile "${PROFILE}" iam get-role --role-name "${ROLE_NAME}" >/dev/null 2>&1; then
|
||||||
|
echo " Role exists — updating assume-role (trust) policy."
|
||||||
|
aws --profile "${PROFILE}" iam update-assume-role-policy \
|
||||||
|
--role-name "${ROLE_NAME}" \
|
||||||
|
--policy-document "${TRUST_POLICY}"
|
||||||
|
else
|
||||||
|
echo " Role absent — creating."
|
||||||
|
aws --profile "${PROFILE}" iam create-role \
|
||||||
|
--role-name "${ROLE_NAME}" \
|
||||||
|
--assume-role-policy-document "${TRUST_POLICY}" \
|
||||||
|
--description "GitHub Actions OIDC deploy role for Sea-Haven-Industries/proposal-system (main)" \
|
||||||
|
--max-session-duration 3600 \
|
||||||
|
--tags Key=project,Value=proposal-system Key=managed-by,Value=create-deploy-role.sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> Putting inline permissions policy '${POLICY_NAME}' (create-or-replace)..."
|
||||||
|
aws --profile "${PROFILE}" iam put-role-policy \
|
||||||
|
--role-name "${ROLE_NAME}" \
|
||||||
|
--policy-name "${POLICY_NAME}" \
|
||||||
|
--policy-document "${PERMS_POLICY}"
|
||||||
|
|
||||||
|
ROLE_ARN="$(aws --profile "${PROFILE}" iam get-role --role-name "${ROLE_NAME}" \
|
||||||
|
--query Role.Arn --output text)"
|
||||||
|
|
||||||
|
echo "==> Done. Deploy role ready:"
|
||||||
|
echo " ${ROLE_ARN}"
|
||||||
|
echo " Configure the GitHub Actions workflow to assume this ARN via aws-actions/configure-aws-credentials."
|
||||||
51
infra/deploy-role/permissions-policy.json
Normal file
51
infra/deploy-role/permissions-policy.json
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Sid": "AssumeCdkBootstrapRoles",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": "sts:AssumeRole",
|
||||||
|
"Resource": [
|
||||||
|
"arn:aws:iam::011934824531:role/cdk-hnb659fds-deploy-role-011934824531-us-east-1",
|
||||||
|
"arn:aws:iam::011934824531:role/cdk-hnb659fds-file-publishing-role-011934824531-us-east-1",
|
||||||
|
"arn:aws:iam::011934824531:role/cdk-hnb659fds-lookup-role-011934824531-us-east-1",
|
||||||
|
"arn:aws:iam::011934824531:role/cdk-hnb659fds-image-publishing-role-011934824531-us-east-1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Sid": "CdkDeployHealthCheck",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": "cloudformation:DescribeStacks",
|
||||||
|
"Resource": "*",
|
||||||
|
"Condition": {
|
||||||
|
"StringEquals": {
|
||||||
|
"aws:RequestedRegion": "us-east-1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Sid": "FrontendSiteBucketSync",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"s3:PutObject",
|
||||||
|
"s3:DeleteObject",
|
||||||
|
"s3:ListBucket"
|
||||||
|
],
|
||||||
|
"Resource": [
|
||||||
|
"arn:aws:s3:::proposal-system-web-011934824531",
|
||||||
|
"arn:aws:s3:::proposal-system-web-011934824531/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Sid": "CloudFrontInvalidation",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": "cloudfront:CreateInvalidation",
|
||||||
|
"Resource": "*",
|
||||||
|
"Condition": {
|
||||||
|
"StringEquals": {
|
||||||
|
"aws:ResourceAccount": "011934824531"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
18
infra/deploy-role/trust-policy.json
Normal file
18
infra/deploy-role/trust-policy.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Principal": {
|
||||||
|
"Federated": "arn:aws:iam::011934824531:oidc-provider/token.actions.githubusercontent.com"
|
||||||
|
},
|
||||||
|
"Action": "sts:AssumeRoleWithWebIdentity",
|
||||||
|
"Condition": {
|
||||||
|
"StringEquals": {
|
||||||
|
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
|
||||||
|
"token.actions.githubusercontent.com:sub": "repo:Sea-Haven-Industries/proposal-system:ref:refs/heads/main"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,17 @@
|
||||||
import * as cdk from 'aws-cdk-lib';
|
import * as cdk from 'aws-cdk-lib';
|
||||||
|
|
||||||
// Multi-environment configuration (PR2). Resolved from CDK context: `-c env=staging`,
|
// Multi-environment configuration (PR2). Resolved from CDK context: `-c env=staging`,
|
||||||
// default `prod`. CRITICAL: the prod config MUST keep the exact construct IDs, stack
|
// default `prod`. Only non-prod environments take a stack-name suffix.
|
||||||
// names, and resource settings the deployed stacks already use — renaming a deployed
|
//
|
||||||
// CloudFormation stack triggers replace-and-delete, which would destroy the RDS instance.
|
// prod now targets the dedicated seahaven-prod workload account (011934824531); the mgmt
|
||||||
// Only non-prod environments take a stack-name suffix.
|
// account (328440206208) is FROZEN for new workloads. The prior proposal-system footprint
|
||||||
|
// was fully torn down 2026-07-14, so THIS first prod deploy is the last safe window to
|
||||||
|
// rename construct IDs / stack names. After prod go-live, renaming a deployed stack (or a
|
||||||
|
// stateful construct ID) triggers replace-and-delete, which would destroy the Aurora cluster.
|
||||||
|
//
|
||||||
|
// STAGING is intentionally NOT deployable: it still points at the frozen mgmt account, and
|
||||||
|
// resolveConfig() hard-throws on `env=staging` until it is retargeted to seahaven-dev
|
||||||
|
// (710827005802). See the guard in resolveConfig below.
|
||||||
|
|
||||||
export type EnvName = 'prod' | 'staging';
|
export type EnvName = 'prod' | 'staging';
|
||||||
|
|
||||||
|
|
@ -28,14 +35,15 @@ export interface EnvConfig {
|
||||||
readonly retainData: boolean;
|
readonly retainData: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ACCOUNT = '328440206208';
|
// seahaven-prod workload account (org prod OU). mgmt 328440206208 is frozen for workloads.
|
||||||
|
const PROD_ACCOUNT = '011934824531';
|
||||||
|
// Frozen mgmt account — staging still references it and must NOT be deployed there.
|
||||||
|
const MGMT_ACCOUNT_FROZEN = '328440206208';
|
||||||
const REGION = 'us-east-1';
|
const REGION = 'us-east-1';
|
||||||
|
|
||||||
// Prod values reproduce the currently-deployed stacks EXACTLY (verified against
|
|
||||||
// foundation-stack.ts / compute-stack.ts) so `cdk diff` shows no prod changes.
|
|
||||||
const PROD: EnvConfig = {
|
const PROD: EnvConfig = {
|
||||||
envName: 'prod',
|
envName: 'prod',
|
||||||
env: { account: ACCOUNT, region: REGION },
|
env: { account: PROD_ACCOUNT, region: REGION },
|
||||||
stackSuffix: '',
|
stackSuffix: '',
|
||||||
s3CorsOrigins: ['https://proposals.seahaven.com', 'http://localhost:5173'],
|
s3CorsOrigins: ['https://proposals.seahaven.com', 'http://localhost:5173'],
|
||||||
apiCorsOrigins: [
|
apiCorsOrigins: [
|
||||||
|
|
@ -53,11 +61,13 @@ const PROD: EnvConfig = {
|
||||||
retainData: true,
|
retainData: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Staging: same AWS account (Adam, 2026-06-12), suffixed stacks, no permanent domain
|
// Staging: ⚠️ STILL points at the FROZEN mgmt account (MGMT_ACCOUNT_FROZEN). It is NOT
|
||||||
// yet (CloudFront default URL + localhost), data not retained.
|
// deployable — resolveConfig() throws on env=staging. Retarget to seahaven-dev
|
||||||
|
// (710827005802) in a dedicated follow-up before ever enabling staging deploys.
|
||||||
|
// Suffixed stacks, CloudFront default URL + localhost, data not retained.
|
||||||
const STAGING: EnvConfig = {
|
const STAGING: EnvConfig = {
|
||||||
envName: 'staging',
|
envName: 'staging',
|
||||||
env: { account: ACCOUNT, region: REGION },
|
env: { account: MGMT_ACCOUNT_FROZEN, region: REGION },
|
||||||
stackSuffix: '-staging',
|
stackSuffix: '-staging',
|
||||||
s3CorsOrigins: ['http://localhost:5173'],
|
s3CorsOrigins: ['http://localhost:5173'],
|
||||||
apiCorsOrigins: ['http://localhost:5173'],
|
apiCorsOrigins: ['http://localhost:5173'],
|
||||||
|
|
@ -76,5 +86,14 @@ export function resolveConfig(app: cdk.App): EnvConfig {
|
||||||
if (!config) {
|
if (!config) {
|
||||||
throw new Error(`Unknown env '${name}'. Use -c env=prod (default) or -c env=staging.`);
|
throw new Error(`Unknown env '${name}'. Use -c env=prod (default) or -c env=staging.`);
|
||||||
}
|
}
|
||||||
|
// Hard guard: staging still targets the frozen mgmt account (328440206208). Block any
|
||||||
|
// synth/deploy under env=staging until it is retargeted to seahaven-dev (710827005802),
|
||||||
|
// so an accidental `-c env=staging` deploy can never land in the frozen mgmt account.
|
||||||
|
if (name === 'staging') {
|
||||||
|
throw new Error(
|
||||||
|
'env=staging is disabled: it targets the FROZEN mgmt account (328440206208). ' +
|
||||||
|
'Retarget STAGING to seahaven-dev (710827005802) in config.ts before using it.',
|
||||||
|
);
|
||||||
|
}
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,10 @@ export class FoundationStack extends cdk.Stack {
|
||||||
serverlessV2MaxCapacity: 4,
|
serverlessV2MaxCapacity: 4,
|
||||||
enableDataApi: true,
|
enableDataApi: true,
|
||||||
storageEncrypted: true,
|
storageEncrypted: true,
|
||||||
backup: { retention: cdk.Duration.days(7) },
|
// Aurora native automated backups (PITR). 14-day retention for the prod tenant;
|
||||||
|
// a dedicated AWS Backup vault + cross-account restore test is a tracked follow-up
|
||||||
|
// (no org central-backup design exists yet — see the prod-deploy plan Phase 4 fallback).
|
||||||
|
backup: { retention: cdk.Duration.days(14), preferredWindow: '07:00-08:00' },
|
||||||
deletionProtection: config.retainData,
|
deletionProtection: config.retainData,
|
||||||
removalPolicy: config.retainData ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY,
|
removalPolicy: config.retainData ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY,
|
||||||
defaultDatabaseName: 'proposals',
|
defaultDatabaseName: 'proposals',
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue