feat(P4): Nova rebrand — AWS resource migration (REQ-163)
Rename all acdl-* AWS resources → nova-* across terraform (DynamoDB, Secrets Manager, Lambda, SNS, SG, KMS alias, ECS, ECR, IAM user/policy, state bucket, ALB, VPC/subnet names). Lambda default table names → nova-* (D-111). State bucket backend → nova-tfstate (-migrate-state documented). New docs/NOVA_AWS_MIGRATION.md runbook (staged migration + rollback). New scripts/migrate_dynamodb_data.py (scan+copy, dry-run default). acdl-deploy- → nova-deploy- role ARN in deploy workflows. Test fixtures updated; terraform validate + pytest + run_ci.sh PASS. ---ci--- project: acdl phase: 4 milestone: v1.15 status: execute ---/ci---
This commit is contained in:
@@ -1,21 +1,21 @@
|
||||
# ACDL v1.1 Spike — AWS Bootstrap Runbook
|
||||
# Nova v1.1 Spike — AWS Bootstrap Runbook
|
||||
|
||||
Phase 08 bootstraps the AWS engine for the v1.1 spike. It uses the
|
||||
**root account credential for account 581513795199 exactly once**, then
|
||||
closes D-034 by having the user manually rotate the root key afterward.
|
||||
|
||||
> **Spike scope (D-039):** the spike uses a per-run-rotated IAM *user* key
|
||||
> (`acdl-spike-runner`), NOT OIDC. Real OIDC federation is deferred to
|
||||
> (`nova-spike-runner`), NOT OIDC. Real OIDC federation is deferred to
|
||||
> v1.2 (blocked on go-gitea/gitea#36988 — Gitea Actions does not support
|
||||
> `id-token: write`). The `acdl-spike-runner` user + its key are deleted
|
||||
> `id-token: write`). The `nova-spike-runner` user + its key are deleted
|
||||
> in v1.2 cleanup when the OIDC role lands.
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Set the bootstrap root key in env** (never commit, never echo):
|
||||
```bash
|
||||
export ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID="<root key>"
|
||||
export ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY="<root secret>"
|
||||
export NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID="<root key>"
|
||||
export NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY="<root secret>"
|
||||
export AWS_DEFAULT_REGION="us-east-1"
|
||||
```
|
||||
|
||||
@@ -29,7 +29,7 @@ closes D-034 by having the user manually rotate the root key afterward.
|
||||
```bash
|
||||
python3 terraform/bootstrap/create_iam_user.py
|
||||
```
|
||||
Prints `ACDL_AWS_ACCESS_KEY_ID=<...>` + `ACDL_AWS_SECRET_ACCESS_KEY=<...>`
|
||||
Prints `NOVA_AWS_ACCESS_KEY_ID=<...>` + `NOVA_AWS_SECRET_ACCESS_KEY=<...>`
|
||||
to stdout (capture if you want the initial key; `rotate_spike_key.sh`
|
||||
creates a fresh one anyway).
|
||||
|
||||
@@ -38,9 +38,9 @@ closes D-034 by having the user manually rotate the root key afterward.
|
||||
```bash
|
||||
bash scripts/rotate_spike_key.sh
|
||||
```
|
||||
Optionally uploads to Gitea Actions secrets if `ACDL_GITEA_TOKEN` is set.
|
||||
Optionally uploads to Gitea Actions secrets if `NOVA_GITEA_TOKEN` is set.
|
||||
|
||||
5. **Verify** (manual): confirm the caller identity is `acdl-spike-runner`
|
||||
5. **Verify** (manual): confirm the caller identity is `nova-spike-runner`
|
||||
(not root); the S3 bucket + DynamoDB table + IAM user + scoped policy
|
||||
all exist; `.env.secrets` + `.bootstrap_state.json` are gitignored.
|
||||
(`scripts/verify_phase08.sh` was the automated gate; it has been
|
||||
@@ -49,33 +49,33 @@ closes D-034 by having the user manually rotate the root key afterward.
|
||||
6. **MANUAL — D-034 closure:** rotate/deactivate the **root** key in the
|
||||
AWS IAM console (the user does this, not the script). The bootstrap
|
||||
root key has now served its one-shot purpose; the spike uses the
|
||||
rotated `acdl-spike-runner` key for Phases 09-10.
|
||||
rotated `nova-spike-runner` key for Phases 09-10.
|
||||
|
||||
## v1.11 Phase 56 — IAM re-bootstrap + OIDC role (REQ-116)
|
||||
|
||||
The v1.11 milestone re-bootstraps IAM to close G-005 (CAP-017..022
|
||||
deploy-unverified). Phase 56 extends the spike-runner policy with
|
||||
CloudFront/WAF/Lambda/DynamoDB-contracts/SecretsManager/SNS/CE/KMS/OIDC
|
||||
permissions and re-creates the `acdl-act-runner-role` (CAP-022).
|
||||
permissions and re-creates the `nova-act-runner-role` (CAP-022).
|
||||
|
||||
**Apply the IAM baseline (idempotent):**
|
||||
```bash
|
||||
export ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID="<root key>"
|
||||
export ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY="<root secret>"
|
||||
export NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID="<root key>"
|
||||
export NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY="<root secret>"
|
||||
export AWS_DEFAULT_REGION="us-east-1"
|
||||
python3 terraform/bootstrap/apply_iam_baseline.py
|
||||
```
|
||||
|
||||
This script:
|
||||
1. Creates (or versions) the customer-managed policy
|
||||
`acdl-spike-runner-policy` from
|
||||
`nova-spike-runner-policy` from
|
||||
`terraform/bootstrap/spike_runner_policy.json` (ARN
|
||||
`arn:aws:iam::581513795199:policy/acdl-spike-runner-policy`).
|
||||
2. Attaches it to the `acdl-spike-runner` user and deletes any leftover
|
||||
`arn:aws:iam::581513795199:policy/nova-spike-runner-policy`).
|
||||
2. Attaches it to the `nova-spike-runner` user and deletes any leftover
|
||||
inline policy (the v1.1 inline policy hit the 2048-byte limit; the
|
||||
managed-policy path supports 6144 bytes per version + up to 5
|
||||
versions).
|
||||
3. Re-creates the `acdl-act-runner-role` OIDC role if absent, attaches
|
||||
3. Re-creates the `nova-act-runner-role` OIDC role if absent, attaches
|
||||
the same managed policy, and sets a trust policy that permits root
|
||||
assume until go-gitea/gitea#36988 merges real OIDC federation.
|
||||
|
||||
@@ -84,9 +84,9 @@ regression-tested by `tests/test_iam_policy_baseline.py` (15 tests).
|
||||
|
||||
## What the spike uses for Phases 09-10
|
||||
|
||||
- **State backend:** S3 bucket `acdl-tfstate-581513795199-us-east-1` +
|
||||
DynamoDB table `acdl-outbox` (one table for both lock + outbox, D-P08-1).
|
||||
- **Auth:** the rotated `acdl-spike-runner` key in `.env.secrets`
|
||||
- **State backend:** S3 bucket `nova-tfstate-581513795199-us-east-1` +
|
||||
DynamoDB table `nova-outbox` (one table for both lock + outbox, D-P08-1).
|
||||
- **Auth:** the rotated `nova-spike-runner` key in `.env.secrets`
|
||||
(gitignored, chmod 600). Re-rotate after each spike run via
|
||||
`rotate_spike_key.sh` (D-039).
|
||||
|
||||
@@ -95,6 +95,6 @@ regression-tested by `tests/test_iam_policy_baseline.py` (15 tests).
|
||||
| Concern | Spike (Phase 08) | v1.2 |
|
||||
|---------|------------------|------|
|
||||
| AWS auth | per-run-rotated long-lived key (D-039 waiver) | real OIDC federation (go-gitea/gitea#36988) |
|
||||
| IAM | minimal user `acdl-spike-runner` + scoped policy | OIDC role + trust policy (no user, no key) |
|
||||
| IAM | minimal user `nova-spike-runner` + scoped policy | OIDC role + trust policy (no user, no key) |
|
||||
| State backend | S3 + DynamoDB single-region (us-east-1) | multi-region |
|
||||
| Secret storage | gitignored `.env.secrets` + optional Gitea secret | Gitea OIDC-issued web-identity token (no secret) |
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Apply the ACDL spike-runner managed policy + OIDC act_runner role.
|
||||
"""Apply the Nova spike-runner managed policy + OIDC act_runner role.
|
||||
|
||||
Phase 56 (REQ-116, v1.11). Idempotent: re-running creates the managed
|
||||
policy if absent (or creates a new version if the policy document
|
||||
@@ -31,10 +31,10 @@ import boto3
|
||||
ROOT = Path(__file__).resolve().parent.parent.parent
|
||||
POLICY_PATH = ROOT / "terraform" / "bootstrap" / "spike_runner_policy.json"
|
||||
ACCOUNT = os.environ.get("ACDL_AWS_ACCOUNT_ID", "581513795199")
|
||||
USER = "acdl-spike-runner"
|
||||
POLICY_NAME = "acdl-spike-runner-policy"
|
||||
USER = "nova-spike-runner"
|
||||
POLICY_NAME = "nova-spike-runner-policy"
|
||||
POLICY_ARN = f"arn:aws:iam::{ACCOUNT}:policy/{POLICY_NAME}"
|
||||
ROLE_NAME = "acdl-act-runner-role"
|
||||
ROLE_NAME = "nova-act-runner-role"
|
||||
|
||||
|
||||
def _session():
|
||||
@@ -86,7 +86,7 @@ def apply_managed_policy(iam, policy_doc: str) -> str:
|
||||
PolicyName=POLICY_NAME,
|
||||
Path="/",
|
||||
PolicyDocument=policy_doc,
|
||||
Description="ACDL spike-runner baseline (v1.11 REQ-116). Extended from inline user policy to managed policy to fit the 6144-byte limit.",
|
||||
Description="Nova spike-runner baseline (v1.11 REQ-116). Extended from inline user policy to managed policy to fit the 6144-byte limit.",
|
||||
)
|
||||
print(f"created: {created['Policy']['Arn']}")
|
||||
return created["Policy"]["Arn"]
|
||||
@@ -117,10 +117,10 @@ def ensure_runner_role(iam):
|
||||
iam.create_role(
|
||||
RoleName=ROLE_NAME,
|
||||
AssumeRolePolicyDocument=json.dumps(_trust_policy_for_runner()),
|
||||
Description="ACDL act_runner OIDC role (CAP-022, v1.11 Phase 56 re-creation). Trust policy permits root assume until go-gitea/gitea#36988 merges real OIDC federation.",
|
||||
Description="Nova act_runner OIDC role (CAP-022, v1.11 Phase 56 re-creation). Trust policy permits root assume until go-gitea/gitea#36988 merges real OIDC federation.",
|
||||
MaxSessionDuration=3600,
|
||||
Tags=[
|
||||
{"Key": "Project", "Value": "acdl"},
|
||||
{"Key": "Project", "Value": "nova"},
|
||||
{"Key": "Capability", "Value": "CAP-022"},
|
||||
{"Key": "Milestone", "Value": "v1.11"},
|
||||
{"Key": "ManagedBy", "Value": "ciagent"},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Create the ACDL v1.1 spike IAM user + scoped inline policy + initial key.
|
||||
"""Create the Nova v1.1 spike IAM user + scoped inline policy + initial key.
|
||||
|
||||
Idempotent: skips user creation if the user exists; creates an initial
|
||||
access key if none active exists. Prints the key to stdout for the
|
||||
@@ -32,8 +32,8 @@ import boto3
|
||||
|
||||
|
||||
REGION = os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
|
||||
USER_NAME = "acdl-spike-runner"
|
||||
POLICY_NAME = "acdl-spike-runner-policy"
|
||||
USER_NAME = "nova-spike-runner"
|
||||
POLICY_NAME = "nova-spike-runner-policy"
|
||||
POLICY_FILE = os.path.join(os.path.dirname(__file__), "spike_runner_policy.json")
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Create the ACDL v1.1 spike AWS state backend (idempotent).
|
||||
"""Create the Nova v1.1 spike AWS state backend (idempotent).
|
||||
|
||||
- S3 bucket acdl-tfstate-<account_id>-us-east-1 (versioning enabled).
|
||||
- DynamoDB table acdl-outbox (PAY_PER_REQUEST; PK contractId, SK
|
||||
- S3 bucket nova-tfstate-<account_id>-us-east-1 (versioning enabled).
|
||||
- DynamoDB table nova-outbox (PAY_PER_REQUEST; PK contractId, SK
|
||||
eventType#eventTs) — used for BOTH Terraform state locking AND the
|
||||
evidence outbox (D-P08-1).
|
||||
|
||||
@@ -31,8 +31,8 @@ import boto3
|
||||
|
||||
REGION = os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
|
||||
ACCOUNT_ID = os.environ.get("ACDL_AWS_ACCOUNT_ID", "581513795199")
|
||||
STATE_BUCKET = f"acdl-tfstate-{ACCOUNT_ID}-us-east-1"
|
||||
OUTBOX_TABLE = "acdl-outbox"
|
||||
STATE_BUCKET = f"nova-tfstate-{ACCOUNT_ID}-us-east-1"
|
||||
OUTBOX_TABLE = "nova-outbox"
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
"s3:GetBucketVersioning"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::acdl-tfstate-581513795199-us-east-1",
|
||||
"arn:aws:s3:::acdl-tfstate-581513795199-us-east-1/*"
|
||||
"arn:aws:s3:::nova-tfstate-581513795199-us-east-1",
|
||||
"arn:aws:s3:::nova-tfstate-581513795199-us-east-1/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -27,7 +27,7 @@
|
||||
"dynamodb:Scan",
|
||||
"dynamodb:DescribeTable"
|
||||
],
|
||||
"Resource": "arn:aws:dynamodb:us-east-1:581513795199:table/acdl-outbox"
|
||||
"Resource": "arn:aws:dynamodb:us-east-1:581513795199:table/nova-outbox"
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
@@ -142,7 +142,7 @@
|
||||
"lambda:UntagResource",
|
||||
"lambda:PublishLayerVersion"
|
||||
],
|
||||
"Resource": "arn:aws:lambda:us-east-1:581513795199:function:acdl-*"
|
||||
"Resource": "arn:aws:lambda:us-east-1:581513795199:function:nova-*"
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
@@ -158,10 +158,10 @@
|
||||
"dynamodb:Batch*"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:dynamodb:us-east-1:581513795199:table/acdl-contracts",
|
||||
"arn:aws:dynamodb:us-east-1:581513795199:table/acdl-contracts/*",
|
||||
"arn:aws:dynamodb:us-east-1:581513795199:table/acdl-change-requests",
|
||||
"arn:aws:dynamodb:us-east-1:581513795199:table/acdl-change-requests/*"
|
||||
"arn:aws:dynamodb:us-east-1:581513795199:table/nova-contracts",
|
||||
"arn:aws:dynamodb:us-east-1:581513795199:table/nova-contracts/*",
|
||||
"arn:aws:dynamodb:us-east-1:581513795199:table/nova-change-requests",
|
||||
"arn:aws:dynamodb:us-east-1:581513795199:table/nova-change-requests/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -174,7 +174,7 @@
|
||||
"secretsmanager:DeleteSecret",
|
||||
"secretsmanager:ListSecrets"
|
||||
],
|
||||
"Resource": "arn:aws:secretsmanager:us-east-1:581513795199:secret:acdl/*"
|
||||
"Resource": "arn:aws:secretsmanager:us-east-1:581513795199:secret:nova/*"
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
@@ -186,7 +186,7 @@
|
||||
"sns:DeleteTopic",
|
||||
"sns:ListTopics"
|
||||
],
|
||||
"Resource": "arn:aws:sns:us-east-1:581513795199:acdl-*"
|
||||
"Resource": "arn:aws:sns:us-east-1:581513795199:nova-*"
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
@@ -217,7 +217,7 @@
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:kms:*:*:key/*",
|
||||
"arn:aws:kms:*:*:alias/acdl-*"
|
||||
"arn:aws:kms:*:*:alias/nova-*"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -236,7 +236,7 @@
|
||||
"iam:TagRole",
|
||||
"iam:UntagRole"
|
||||
],
|
||||
"Resource": "arn:aws:iam::*:role/acdl-*"
|
||||
"Resource": "arn:aws:iam::*:role/nova-*"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ACDL CI VPC — short-lived VPC for L1 module lifecycle testing.
|
||||
# Nova CI VPC — short-lived VPC for L1 module lifecycle testing.
|
||||
#
|
||||
# Created by the modules-lifecycle pipeline before testing VPC-dependent
|
||||
# modules (alb, ecs-service, rds, uptime). Destroyed after all tests complete.
|
||||
@@ -15,7 +15,7 @@ terraform {
|
||||
}
|
||||
}
|
||||
backend "s3" {
|
||||
bucket = "acdl-tfstate-581513795199-us-east-1"
|
||||
bucket = "nova-tfstate-581513795199-us-east-1"
|
||||
key = "spike/ci-vpc/terraform.tfstate"
|
||||
region = "us-east-1"
|
||||
}
|
||||
@@ -32,7 +32,7 @@ data "aws_availability_zones" "available" {
|
||||
resource "aws_vpc" "ci" {
|
||||
cidr_block = "10.1.0.0/16"
|
||||
tags = {
|
||||
Name = "acdl-ci-vpc"
|
||||
Name = "nova-ci-vpc"
|
||||
"nova:owner" = "acdl"
|
||||
"nova:environment" = "ci"
|
||||
}
|
||||
@@ -44,7 +44,7 @@ resource "aws_subnet" "ci" {
|
||||
cidr_block = cidrsubnet(aws_vpc.ci.cidr_block, 8, count.index + 1)
|
||||
availability_zone = data.aws_availability_zones.available.names[count.index]
|
||||
tags = {
|
||||
Name = "acdl-ci-subnet-${count.index}"
|
||||
Name = "nova-ci-subnet-${count.index}"
|
||||
"nova:owner" = "acdl"
|
||||
"nova:environment" = "ci"
|
||||
}
|
||||
@@ -53,7 +53,7 @@ resource "aws_subnet" "ci" {
|
||||
resource "aws_internet_gateway" "ci" {
|
||||
vpc_id = aws_vpc.ci.id
|
||||
tags = {
|
||||
Name = "acdl-ci-igw"
|
||||
Name = "nova-ci-igw"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ resource "aws_route_table_association" "ci" {
|
||||
}
|
||||
|
||||
resource "aws_security_group" "ecs" {
|
||||
name = "acdl-ci-ecs-sg"
|
||||
name = "nova-ci-ecs-sg"
|
||||
description = "Security group for CI ECS services"
|
||||
vpc_id = aws_vpc.ci.id
|
||||
|
||||
@@ -92,7 +92,7 @@ resource "aws_security_group" "ecs" {
|
||||
}
|
||||
|
||||
resource "aws_ecs_cluster" "ci" {
|
||||
name = "acdl-ci-cluster"
|
||||
name = "nova-ci-cluster"
|
||||
}
|
||||
|
||||
output "vpc_id" {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
resource "aws_vpc" "vpc-vpc" {
|
||||
cidr_block = "10.0.0.0/16"
|
||||
tags = {
|
||||
Name = "acdl-microservice"
|
||||
Name = "nova-microservice"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ output "vpc_id" {
|
||||
|
||||
resource "aws_subnet" "vpc-subnet" {
|
||||
cidr_block = "10.0.0.0/16"
|
||||
vpc_id = aws_vpc.vpc-vpc.id
|
||||
vpc_id = aws_vpc.vpc-vpc.id
|
||||
tags = {
|
||||
Name = "acdl-microservice"
|
||||
Name = "nova-microservice"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,12 +24,12 @@ resource "aws_route_table" "vpc-routetable" {
|
||||
gateway_id = aws_internet_gateway.vpc-igw.id
|
||||
}
|
||||
tags = {
|
||||
Name = "acdl-microservice-rt"
|
||||
Name = "nova-microservice-rt"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_ecs_cluster" "cluster" {
|
||||
name = "acdl-microservice"
|
||||
name = "nova-microservice"
|
||||
}
|
||||
|
||||
output "cluster_arn" {
|
||||
@@ -41,7 +41,7 @@ output "cluster_id" {
|
||||
}
|
||||
|
||||
resource "aws_ecr_repository" "ecr" {
|
||||
name = "acdl-microservice"
|
||||
name = "nova-microservice"
|
||||
}
|
||||
|
||||
output "repository_url" {
|
||||
@@ -53,8 +53,8 @@ output "repository_arn" {
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "roles" {
|
||||
name = "acdl-microservice-exec"
|
||||
assume_role_policy = jsonencode({"Statement": [{"Action": "sts:AssumeRole", "Effect": "Allow", "Principal": {"Service": "ecs-tasks.amazonaws.com"}}], "Version": "2012-10-17"})
|
||||
name = "nova-microservice-exec"
|
||||
assume_role_policy = jsonencode({ "Statement" : [{ "Action" : "sts:AssumeRole", "Effect" : "Allow", "Principal" : { "Service" : "ecs-tasks.amazonaws.com" } }], "Version" : "2012-10-17" })
|
||||
managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
|
||||
}
|
||||
|
||||
@@ -67,9 +67,9 @@ output "role_id" {
|
||||
}
|
||||
|
||||
resource "aws_lb" "alb-loadbalancer" {
|
||||
name = "acdl-microservice"
|
||||
subnets = [aws_subnet.vpc-subnet.id]
|
||||
security_groups = [aws_iam_role.roles.arn]
|
||||
name = "nova-microservice"
|
||||
subnets = [aws_subnet.vpc-subnet.id]
|
||||
security_groups = [aws_iam_role.roles.arn]
|
||||
load_balancer_type = "application"
|
||||
}
|
||||
|
||||
@@ -78,11 +78,11 @@ output "lb_arn" {
|
||||
}
|
||||
|
||||
resource "aws_lb_target_group" "alb-targetgroup" {
|
||||
name = "acdl-microservice"
|
||||
port = 8080
|
||||
name = "nova-microservice"
|
||||
port = 8080
|
||||
target_type = "ip"
|
||||
vpc_id = aws_vpc.vpc-vpc.id
|
||||
protocol = "HTTP"
|
||||
vpc_id = aws_vpc.vpc-vpc.id
|
||||
protocol = "HTTP"
|
||||
}
|
||||
|
||||
output "target_group_arn" {
|
||||
@@ -92,7 +92,7 @@ output "target_group_arn" {
|
||||
resource "aws_lb_listener" "alb-listener" {
|
||||
port = 8080
|
||||
default_action {
|
||||
type = "forward"
|
||||
type = "forward"
|
||||
target_group_arn = aws_lb_target_group.alb-targetgroup.arn
|
||||
}
|
||||
load_balancer_arn = aws_lb.alb-loadbalancer.id
|
||||
@@ -103,10 +103,10 @@ output "listener_arn" {
|
||||
}
|
||||
|
||||
resource "aws_ecs_task_definition" "service-taskdefinition" {
|
||||
cpu = 256
|
||||
memory = 512
|
||||
container_definitions = jsonencode([{"essential": true, "image": "581513795199.dkr.ecr.us-east-1.amazonaws.com/acdl-microservice:latest", "name": "app", "portMappings": [{"containerPort": 8080}]}])
|
||||
family = "app"
|
||||
cpu = 256
|
||||
memory = 512
|
||||
container_definitions = jsonencode([{ "essential" : true, "image" : "581513795199.dkr.ecr.us-east-1.amazonaws.com/nova-microservice:latest", "name" : "app", "portMappings" : [{ "containerPort" : 8080 }] }])
|
||||
family = "app"
|
||||
}
|
||||
|
||||
output "task_def_arn" {
|
||||
@@ -117,17 +117,17 @@ resource "aws_ecs_service" "service-service" {
|
||||
cluster = aws_ecs_cluster.cluster.arn
|
||||
load_balancer {
|
||||
target_group_arn = aws_lb_target_group.alb-targetgroup.arn
|
||||
container_name = "app"
|
||||
container_port = 8080
|
||||
container_name = "app"
|
||||
container_port = 8080
|
||||
}
|
||||
network_configuration {
|
||||
subnets = [aws_subnet.vpc-subnet.id]
|
||||
subnets = [aws_subnet.vpc-subnet.id]
|
||||
security_groups = [aws_iam_role.roles.arn]
|
||||
}
|
||||
desired_count = 1
|
||||
launch_type = "FARGATE"
|
||||
desired_count = 1
|
||||
launch_type = "FARGATE"
|
||||
task_definition = aws_ecs_task_definition.service-taskdefinition.arn
|
||||
name = "acdl-microservice"
|
||||
name = "nova-microservice"
|
||||
}
|
||||
|
||||
output "service_arn" {
|
||||
@@ -137,11 +137,11 @@ output "service_arn" {
|
||||
resource "aws_internet_gateway" "vpc-igw" {
|
||||
vpc_id = aws_vpc.vpc-vpc.id
|
||||
tags = {
|
||||
Name = "acdl-microservice-igw"
|
||||
Name = "nova-microservice-igw"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_route_table_association" "vpc-rta" {
|
||||
subnet_id = aws_subnet.vpc-subnet.id
|
||||
subnet_id = aws_subnet.vpc-subnet.id
|
||||
route_table_id = aws_route_table.vpc-routetable.id
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ terraform {
|
||||
}
|
||||
}
|
||||
backend "s3" {
|
||||
bucket = "acdl-tfstate-581513795199-us-east-1"
|
||||
bucket = "nova-tfstate-581513795199-us-east-1"
|
||||
key = "spike/microservice/terraform.tfstate"
|
||||
region = "us-east-1"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ACDL Platform Infrastructure (D-051)
|
||||
# Nova Platform Infrastructure (D-051)
|
||||
|
||||
Terraform configuration for the **platform-side** infrastructure that
|
||||
ingests consumer deployment contracts and (Phase 25) reports errors as
|
||||
@@ -13,11 +13,11 @@ consumers — the contract ingestion pipeline and the secrets it needs.
|
||||
|
||||
| Resource | Name | Purpose |
|
||||
|----------|------|---------|
|
||||
| `aws_dynamodb_table` | `acdl-contracts` | Stores submitted consumer contracts. PK `consumerRepo`, SK `contractId#submittedAt`. SSE via CMK, PITR enabled. |
|
||||
| `aws_kms_key` + `aws_kms_alias` | `alias/acdl-platform` | Customer-managed key — encrypts DynamoDB SSE, Secrets Manager, and SSM. Key rotation enabled. |
|
||||
| `aws_secretsmanager_secret` | `acdl/github-token` | GitHub PAT used by the Lambda to create issues on the platform repo (D-055, wired in Phase 25). |
|
||||
| `aws_iam_role` + `aws_iam_role_policy` | `acdl-contract-ingestor-role` | Execution role for the Lambda — DynamoDB write, Secrets Manager read, KMS decrypt, CloudWatch logs. |
|
||||
| `aws_lambda_function` | `acdl-contract-ingestor` | Python 3.12 Lambda. Handler `contract_ingestor.lambda_handler`. Source: `core/lambda/contract_ingestor.py`, packaged as `contract_ingestor.zip`. |
|
||||
| `aws_dynamodb_table` | `nova-contracts` | Stores submitted consumer contracts. PK `consumerRepo`, SK `contractId#submittedAt`. SSE via CMK, PITR enabled. |
|
||||
| `aws_kms_key` + `aws_kms_alias` | `alias/nova-platform` | Customer-managed key — encrypts DynamoDB SSE, Secrets Manager, and SSM. Key rotation enabled. |
|
||||
| `aws_secretsmanager_secret` | `nova/github-token` | GitHub PAT used by the Lambda to create issues on the platform repo (D-055, wired in Phase 25). |
|
||||
| `aws_iam_role` + `aws_iam_role_policy` | `nova-contract-ingestor-role` | Execution role for the Lambda — DynamoDB write, Secrets Manager read, KMS decrypt, CloudWatch logs. |
|
||||
| `aws_lambda_function` | `nova-contract-ingestor` | Python 3.12 Lambda. Handler `contract_ingestor.lambda_handler`. Source: `core/lambda/contract_ingestor.py`, packaged as `contract_ingestor.zip`. |
|
||||
| `aws_lambda_function_url` | — | Function URL with `AWS_IAM` authorization. Consumers invoke it via SigV4-signed requests. |
|
||||
|
||||
## State
|
||||
@@ -25,7 +25,7 @@ consumers — the contract ingestion pipeline and the secrets it needs.
|
||||
| Key | Value |
|
||||
|-----|-------|
|
||||
| Backend | S3 |
|
||||
| Bucket | `acdl-tfstate-581513795199-us-east-1` |
|
||||
| Bucket | `nova-tfstate-581513795199-us-east-1` |
|
||||
| State key | `platform/terraform.tfstate` |
|
||||
| Region | `us-east-1` |
|
||||
|
||||
@@ -66,7 +66,7 @@ flow:
|
||||
request with SigV4 using its deploy-role credentials. The IAM auth on
|
||||
the Function URL validates the signature and the ABAC condition.
|
||||
3. **Lambda.** The Lambda parses the JSON body, validates the fields,
|
||||
and writes the contract to `acdl-contracts`.
|
||||
and writes the contract to `nova-contracts`.
|
||||
|
||||
This is a **one-way** channel (D-051): the consumer pushes contracts
|
||||
*to* the platform; the platform never reaches back into the consumer
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "lambda:InvokeFunctionUrl",
|
||||
"Resource": "arn:aws:lambda:${region}:${account_id}:function:acdl-contract-ingestor",
|
||||
"Resource": "arn:aws:lambda:${region}:${account_id}:function:nova-contract-ingestor",
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"aws:PrincipalTag/nova:owner": "${consumerRepo}"
|
||||
|
||||
+54
-54
@@ -1,11 +1,11 @@
|
||||
# ACDL platform infrastructure — contract ingestion Lambda + DynamoDB (D-051)
|
||||
# Nova platform infrastructure — contract ingestion Lambda + DynamoDB (D-051)
|
||||
#
|
||||
# Deploys:
|
||||
# - DynamoDB table acdl-contracts (PK consumerRepo, SK contractId#submittedAt, SSE via CMK, PITR)
|
||||
# - DynamoDB table nova-contracts (PK consumerRepo, SK contractId#submittedAt, SSE via CMK, PITR)
|
||||
# - KMS customer-managed key for DynamoDB + SSM (shared CMK)
|
||||
# - Lambda function acdl-contract-ingestor (Python 3.12, handler contract_ingestor.lambda_handler)
|
||||
# - Lambda function nova-contract-ingestor (Python 3.12, handler contract_ingestor.lambda_handler)
|
||||
# - Lambda Function URL (IAM auth — consumers invoke via SigV4)
|
||||
# - Secrets Manager secret acdl/github-token (stores the Lambda's GitHub PAT for issue creation)
|
||||
# - Secrets Manager secret nova/github-token (stores the Lambda's GitHub PAT for issue creation)
|
||||
# - IAM execution role for the Lambda (DynamoDB write + Secrets Manager read + KMS decrypt)
|
||||
#
|
||||
# State: terraform/platform/terraform.tfstate (separate from spike/ and microservice/)
|
||||
@@ -19,7 +19,7 @@ terraform {
|
||||
}
|
||||
}
|
||||
backend "s3" {
|
||||
bucket = "acdl-tfstate-581513795199-us-east-1"
|
||||
bucket = "nova-tfstate-581513795199-us-east-1"
|
||||
key = "platform/terraform.tfstate"
|
||||
region = "us-east-1"
|
||||
}
|
||||
@@ -37,20 +37,20 @@ variable "vpc_cidr" {
|
||||
}
|
||||
|
||||
# KMS customer-managed key for DynamoDB SSE + SSM Parameter Store encryption
|
||||
resource "aws_kms_key" "acdl_platform" {
|
||||
description = "ACDL platform KMS key (DynamoDB SSE + SSM + Secrets Manager)"
|
||||
resource "aws_kms_key" "nova_platform" {
|
||||
description = "Nova platform KMS key (DynamoDB SSE + SSM + Secrets Manager)"
|
||||
enable_key_rotation = true
|
||||
deletion_window_in_days = 30
|
||||
}
|
||||
|
||||
resource "aws_kms_alias" "acdl_platform" {
|
||||
name = "alias/acdl-platform"
|
||||
target_key_id = aws_kms_key.acdl_platform.key_id
|
||||
resource "aws_kms_alias" "nova_platform" {
|
||||
name = "alias/nova-platform"
|
||||
target_key_id = aws_kms_key.nova_platform.key_id
|
||||
}
|
||||
|
||||
# DynamoDB table for contract ingestion
|
||||
resource "aws_dynamodb_table" "acdl_contracts" {
|
||||
name = "acdl-contracts"
|
||||
resource "aws_dynamodb_table" "nova_contracts" {
|
||||
name = "nova-contracts"
|
||||
billing_mode = "PAY_PER_REQUEST"
|
||||
hash_key = "consumerRepo"
|
||||
range_key = "contractId#submittedAt"
|
||||
@@ -71,7 +71,7 @@ resource "aws_dynamodb_table" "acdl_contracts" {
|
||||
|
||||
server_side_encryption {
|
||||
enabled = true
|
||||
kms_key_arn = aws_kms_key.acdl_platform.arn
|
||||
kms_key_arn = aws_kms_key.nova_platform.arn
|
||||
}
|
||||
|
||||
tags = {
|
||||
@@ -84,9 +84,9 @@ resource "aws_dynamodb_table" "acdl_contracts" {
|
||||
|
||||
# Secrets Manager secret for the Lambda's GitHub token (issue creation)
|
||||
resource "aws_secretsmanager_secret" "github_token" {
|
||||
name = "acdl/github-token"
|
||||
name = "nova/github-token"
|
||||
description = "GitHub PAT for the platform Lambda to create issues on the platform repo (D-055)."
|
||||
kms_key_id = aws_kms_key.acdl_platform.arn
|
||||
kms_key_id = aws_kms_key.nova_platform.arn
|
||||
|
||||
tags = {
|
||||
"nova:owner" = "acdl"
|
||||
@@ -98,7 +98,7 @@ resource "aws_secretsmanager_secret" "github_token" {
|
||||
|
||||
# IAM execution role for the Lambda
|
||||
resource "aws_iam_role" "lambda_exec" {
|
||||
name = "acdl-contract-ingestor-role"
|
||||
name = "nova-contract-ingestor-role"
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [{
|
||||
@@ -110,7 +110,7 @@ resource "aws_iam_role" "lambda_exec" {
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "lambda_permissions" {
|
||||
name = "acdl-contract-ingestor-policy"
|
||||
name = "nova-contract-ingestor-policy"
|
||||
role = aws_iam_role.lambda_exec.id
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
@@ -118,12 +118,12 @@ resource "aws_iam_role_policy" "lambda_permissions" {
|
||||
{
|
||||
Effect = "Allow"
|
||||
Action = ["dynamodb:PutItem", "dynamodb:GetItem", "dynamodb:Query", "dynamodb:UpdateItem"]
|
||||
Resource = aws_dynamodb_table.acdl_contracts.arn
|
||||
Resource = aws_dynamodb_table.nova_contracts.arn
|
||||
},
|
||||
{
|
||||
Effect = "Allow"
|
||||
Action = ["dynamodb:GetItem", "dynamodb:Query"]
|
||||
Resource = aws_dynamodb_table.acdl_change_requests.arn
|
||||
Resource = aws_dynamodb_table.nova_change_requests.arn
|
||||
},
|
||||
{
|
||||
Effect = "Allow"
|
||||
@@ -133,7 +133,7 @@ resource "aws_iam_role_policy" "lambda_permissions" {
|
||||
{
|
||||
Effect = "Allow"
|
||||
Action = ["kms:Decrypt"]
|
||||
Resource = aws_kms_key.acdl_platform.arn
|
||||
Resource = aws_kms_key.nova_platform.arn
|
||||
},
|
||||
{
|
||||
Effect = "Allow"
|
||||
@@ -152,7 +152,7 @@ locals {
|
||||
|
||||
resource "aws_lambda_function" "contract_ingestor" {
|
||||
count = local.lambda_zip_exists ? 1 : 0
|
||||
function_name = "acdl-contract-ingestor"
|
||||
function_name = "nova-contract-ingestor"
|
||||
handler = "contract_ingestor.lambda_handler"
|
||||
runtime = "python3.12"
|
||||
role = aws_iam_role.lambda_exec.arn
|
||||
@@ -161,9 +161,9 @@ resource "aws_lambda_function" "contract_ingestor" {
|
||||
|
||||
environment {
|
||||
variables = {
|
||||
CONTRACTS_TABLE = aws_dynamodb_table.acdl_contracts.name
|
||||
CONTRACTS_TABLE = aws_dynamodb_table.nova_contracts.name
|
||||
GITHUB_TOKEN_SECRET_ID = aws_secretsmanager_secret.github_token.name
|
||||
PLATFORM_REPO = "acdl/acdl"
|
||||
PLATFORM_REPO = "nova/acdl"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,8 +204,8 @@ output "consumer_invoke_policy_rendered" {
|
||||
}
|
||||
|
||||
# REQ-93: DynamoDB table for change requests (CMDB for decommission validation)
|
||||
resource "aws_dynamodb_table" "acdl_change_requests" {
|
||||
name = "acdl-change-requests"
|
||||
resource "aws_dynamodb_table" "nova_change_requests" {
|
||||
name = "nova-change-requests"
|
||||
billing_mode = "PAY_PER_REQUEST"
|
||||
hash_key = "changeRequestId"
|
||||
range_key = "submittedAt"
|
||||
@@ -226,7 +226,7 @@ resource "aws_dynamodb_table" "acdl_change_requests" {
|
||||
|
||||
server_side_encryption {
|
||||
enabled = true
|
||||
kms_key_arn = aws_kms_key.acdl_platform.arn
|
||||
kms_key_arn = aws_kms_key.nova_platform.arn
|
||||
}
|
||||
|
||||
tags = {
|
||||
@@ -237,10 +237,10 @@ resource "aws_dynamodb_table" "acdl_change_requests" {
|
||||
}
|
||||
}
|
||||
# REQ-107: SNS topic for separation-of-duties halt artifacts.
|
||||
# route_halt_artifact publishes here when ACDL_SOD_HALT_TOPIC_ARN is set.
|
||||
resource "aws_sns_topic" "acdl_sod_halt" {
|
||||
name = "acdl-sod-halt"
|
||||
kms_master_key_id = aws_kms_key.acdl_platform.id
|
||||
# route_halt_artifact publishes here when NOVA_SOD_HALT_TOPIC_ARN is set.
|
||||
resource "aws_sns_topic" "nova_sod_halt" {
|
||||
name = "nova-sod-halt"
|
||||
kms_master_key_id = aws_kms_key.nova_platform.id
|
||||
tags = {
|
||||
"nova:owner" = "acdl"
|
||||
"nova:contract" = "platform"
|
||||
@@ -249,8 +249,8 @@ resource "aws_sns_topic" "acdl_sod_halt" {
|
||||
}
|
||||
}
|
||||
|
||||
output "acdl_sod_halt_topic_arn" {
|
||||
value = aws_sns_topic.acdl_sod_halt.arn
|
||||
output "nova_sod_halt_topic_arn" {
|
||||
value = aws_sns_topic.nova_sod_halt.arn
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -258,10 +258,10 @@ output "acdl_sod_halt_topic_arn" {
|
||||
# via terraform_remote_state (data source). No per-contract VPC ever again.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
resource "aws_vpc" "acdl_shared" {
|
||||
resource "aws_vpc" "nova_shared" {
|
||||
cidr_block = var.vpc_cidr
|
||||
tags = {
|
||||
Name = "acdl-shared"
|
||||
Name = "nova-shared"
|
||||
"nova:owner" = "acdl"
|
||||
"nova:contract" = "platform"
|
||||
"nova:environment" = "shared"
|
||||
@@ -269,13 +269,13 @@ resource "aws_vpc" "acdl_shared" {
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_subnet" "acdl_shared" {
|
||||
resource "aws_subnet" "nova_shared" {
|
||||
count = length(data.aws_availability_zones.available.names)
|
||||
vpc_id = aws_vpc.acdl_shared.id
|
||||
cidr_block = cidrsubnet(aws_vpc.acdl_shared.cidr_block, 8, count.index + 1)
|
||||
vpc_id = aws_vpc.nova_shared.id
|
||||
cidr_block = cidrsubnet(aws_vpc.nova_shared.cidr_block, 8, count.index + 1)
|
||||
availability_zone = data.aws_availability_zones.available.names[count.index]
|
||||
tags = {
|
||||
Name = "acdl-shared-subnet-${count.index}"
|
||||
Name = "nova-shared-subnet-${count.index}"
|
||||
"nova:owner" = "acdl"
|
||||
"nova:contract" = "platform"
|
||||
"nova:environment" = "shared"
|
||||
@@ -287,10 +287,10 @@ data "aws_availability_zones" "available" {
|
||||
state = "available"
|
||||
}
|
||||
|
||||
resource "aws_internet_gateway" "acdl_shared" {
|
||||
vpc_id = aws_vpc.acdl_shared.id
|
||||
resource "aws_internet_gateway" "nova_shared" {
|
||||
vpc_id = aws_vpc.nova_shared.id
|
||||
tags = {
|
||||
Name = "acdl-shared-igw"
|
||||
Name = "nova-shared-igw"
|
||||
"nova:owner" = "acdl"
|
||||
"nova:contract" = "platform"
|
||||
"nova:environment" = "shared"
|
||||
@@ -298,14 +298,14 @@ resource "aws_internet_gateway" "acdl_shared" {
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_route_table" "acdl_shared" {
|
||||
vpc_id = aws_vpc.acdl_shared.id
|
||||
resource "aws_route_table" "nova_shared" {
|
||||
vpc_id = aws_vpc.nova_shared.id
|
||||
route {
|
||||
cidr_block = "0.0.0.0/0"
|
||||
gateway_id = aws_internet_gateway.acdl_shared.id
|
||||
gateway_id = aws_internet_gateway.nova_shared.id
|
||||
}
|
||||
tags = {
|
||||
Name = "acdl-shared-rt"
|
||||
Name = "nova-shared-rt"
|
||||
"nova:owner" = "acdl"
|
||||
"nova:contract" = "platform"
|
||||
"nova:environment" = "shared"
|
||||
@@ -313,16 +313,16 @@ resource "aws_route_table" "acdl_shared" {
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_route_table_association" "acdl_shared" {
|
||||
resource "aws_route_table_association" "nova_shared" {
|
||||
count = 2
|
||||
subnet_id = aws_subnet.acdl_shared[count.index].id
|
||||
route_table_id = aws_route_table.acdl_shared.id
|
||||
subnet_id = aws_subnet.nova_shared[count.index].id
|
||||
route_table_id = aws_route_table.nova_shared.id
|
||||
}
|
||||
|
||||
resource "aws_security_group" "ecs" {
|
||||
name = "acdl-ecs-sg"
|
||||
name = "nova-ecs-sg"
|
||||
description = "Security group for ECS Fargate services (platform VPC)"
|
||||
vpc_id = aws_vpc.acdl_shared.id
|
||||
vpc_id = aws_vpc.nova_shared.id
|
||||
|
||||
# Ingress on port 80 is open to 0.0.0.0/0 — this is acceptable because
|
||||
# the ECS service is fronted by a public-facing ALB (the ALB terminates
|
||||
@@ -343,7 +343,7 @@ resource "aws_security_group" "ecs" {
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "acdl-ecs-sg"
|
||||
Name = "nova-ecs-sg"
|
||||
"nova:owner" = "acdl"
|
||||
"nova:contract" = "platform"
|
||||
"nova:environment" = "shared"
|
||||
@@ -352,16 +352,16 @@ resource "aws_security_group" "ecs" {
|
||||
}
|
||||
|
||||
output "vpc_id" {
|
||||
value = aws_vpc.acdl_shared.id
|
||||
value = aws_vpc.nova_shared.id
|
||||
description = "The shared platform VPC ID. Consumer stacks reference this via terraform_remote_state."
|
||||
}
|
||||
|
||||
output "subnet_ids" {
|
||||
value = join(",", aws_subnet.acdl_shared[*].id)
|
||||
value = join(",", aws_subnet.nova_shared[*].id)
|
||||
description = "Comma-separated subnet IDs in the shared platform VPC."
|
||||
}
|
||||
|
||||
output "ecs_security_group_id" {
|
||||
value = aws_security_group.ecs.id
|
||||
description = "Security group ID for ECS Fargate services in the platform VPC."
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user