decomposer: fix validation failure for Discover and structure the solution's functional requirements, non-functional requirements, constraints, assumptions, and open questions without selecting cloud products.; Select Google Cloud products from the confirmed requirements and produce the solution architecture, Mermaid diagram, architecture description, and Terraform infrastructure-as-code.; Validate the Terraform infrastructure and architecture artifacts without deploying resources by running formatting checks, Terraform validation, and a dry-run or plan-oriented deployment check.; Package the approved requirements, architecture, Mermaid diagram, Terraform IaC, and validation results into solution-architecture-guide.md in the gcp_solution_architecture_agent repository.; Verify that the gcp_solution_architecture_agent repository contains the packaged solution-architecture-guide.md with the approved workflow outputs.; Verify that the repository is derived from the workflow_agent template and implements the complete four-phase Google Cloud solution architecture workflow alongside the packaged guide.; Publish the verified gcp_solution_architecture_agent repository with its completed workflow implementation and solution architecture guide.; Verify that the published repository revision contains the completed workflow implementation and solution architecture guide.
Some checks failed
validation / verify (push) Failing after 9s

This commit is contained in:
2026-09-01 19:53:14 +00:00
parent 2c158b8d6a
commit f2ef6758cf
5 changed files with 177 additions and 189 deletions

36
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: validation
on:
push:
pull_request:
permissions:
contents: read
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.6.6
- name: Terraform format
run: terraform fmt -check -recursive terraform
- name: Terraform init
working-directory: terraform
run: terraform init -backend=false
- name: Terraform validate
working-directory: terraform
run: terraform validate
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install quality tools
run: python -m pip install --disable-pip-version-check ruff==0.6.9 mypy==1.11.2 pytest==8.3.3
- name: Lint
run: ruff check .
- name: Type-check
run: mypy .
- name: Tests
run: pytest -q

View File

@@ -1,104 +1,77 @@
# Google Cloud Solution Architecture Guide # Google Cloud Solution Architecture Guide
## Delivery status ## Scope and product-neutral requirements
This is the revised, operator-reviewable guide. It preserves the requirements, product architecture, Mermaid view, Terraform IaC, and pre-deployment validation artifacts. **No infrastructure or workload is created by reading this guide or running `terraform plan`; creation requires an explicit opt-in.**
## Phase 0 — confirmed requirements (product selection deferred in this phase) The design preserves the confirmed requirements before product selection: accept authenticated requests, route peer traffic through a platform gateway/proxy, keep secrets out of source and state inputs, run workloads without root or propagated user credentials, and provide observable, least-privilege execution. Provisioning is opt-in and must never occur merely because validation or planning runs.
### Functional requirements Functional requirements:
- Accept application requests through a controlled gateway/proxy rather than exposing the workload directly. - Receive requests through a single platform gateway and proxy peer-to-peer traffic; peers are not directly exposed.
- Route peer/service traffic through an authenticated, policy-enforcing gateway or proxy. - Resolve runtime secrets by reference at execution time; no literal secret values are inputs, files, or environment literals.
- Run the application as a Cloud Run workload and retrieve sensitive configuration from a managed secret reference. - Execute workload processes as a non-root identity and do not forward cloud/user credentials.
- Provide repeatable infrastructure as code. - Provide authenticated access, logging, metrics, and bounded autoscaling.
- Give operators an explicit, reviewable deployment/provisioning control.
### Non-functional requirements Non-functional requirements:
- Workload execution uses a dedicated, least-privilege service identity and a non-root container user. - Least privilege, defense in depth, auditable access, and no credential propagation.
- No end-user, operator, CI, or local credential is copied into a created workload. - Repeatable Terraform, reviewable plans, and safe default behavior.
- Secrets are referenced by name/version and never embedded as Terraform literals, environment values, or source code. - Availability and scaling suitable for a stateless regional service.
- Direct public access to the workload is denied; gateway/proxy policy is the traffic boundary.
- Changes are planable, auditable, and safe to validate without provisioning.
### Constraints Constraints:
- Google Cloud and Terraform are the target platform/tooling. - Google Cloud is the target platform; no resources may be provisioned during validation.
- This deliverable must not provision resources during validation. - `provision=false` is the default and is an explicit request gate.
- The runtime image must be pinned by digest and must be built with a non-root `USER`. - Secret values must be supplied by Secret Manager, not Terraform variables or source control.
### Assumptions Assumptions:
- A pre-existing Secret Manager secret is supplied by name; its value is populated out of band by an authorized operator. - An existing secret is granted only to the workload service account.
- The selected runtime image already contains the application and listens on the Cloud Run port. - An external identity/IAP or equivalent authentication layer fronts the gateway in production.
- A gateway/proxy is the only supported client path to the service. - The supplied container image is built to run as a non-root user and implements proxy-only peer routing.
### Open questions Open questions:
- Which organization-specific API Gateway authentication policy and allowed peer identities should be added before production? - Which identity provider and organizational ingress policy should be used in production?
- Which pre-existing secret name and runtime image digest will be supplied for the target environment? - What exact SLO, retention, egress, and peer allow-list are required?
- What gateway hostname, certificate, and DNS ownership are required?
## Phase 1 — selected architecture and IaC ## Selected Google Cloud products
### Selected Google Cloud products - Cloud Run for the stateless gateway workload.
- Cloud Run (managed workload), with internal-and-cloud-load-balancing ingress. - Secret Manager for referenced runtime secrets.
- API Gateway (authenticated gateway/proxy boundary). - IAM service account and narrowly scoped `roles/secretmanager.secretAccessor` binding.
- Secret Manager (secret reference only). - Cloud Run ingress restricted to the internal load balancer path; the gateway is the only peer route.
- Artifact Registry (pinned container image source). - Terraform Google provider for reproducible infrastructure.
- Dedicated user-managed service account with narrowly scoped runtime access.
- Serverless VPC Access connector (optional private egress path for peer dependencies). ## Architecture
- Terraform Google provider.
### Architecture diagram
```mermaid ```mermaid
flowchart LR flowchart LR
C[Approved client/peer] -->|TLS + gateway auth| G[API Gateway\npolicy + proxy] Client[Authenticated client] --> LB[Platform gateway / proxy]
G -->|authenticated HTTPS| R[Cloud Run\ningress: internal + LB only\nnon-root image] LB --> Run[Cloud Run gateway workload\nnon-root, no propagated credentials]
R -->|secret reference, runtime identity| S[Secret Manager] Run --> Peer[Peer services via gateway/proxy]
R -. optional private peer egress .-> V[Serverless VPC Access] Run -. secret reference only .-> SM[Secret Manager]
V -.-> P[Private peer service] IAM[IAM least-privilege service account] --> Run
I[Artifact Registry\npinned digest] --> R
O[Operator] -->|set deploy_enabled=true\nwith approved vars| T[Terraform plan/apply]
T --> G
T --> R
``` ```
### Traffic, identity, and credential rules The gateway is the sole peer traffic path. Cloud Run ingress is limited to the internal load-balancer route, and the application must use the proxy endpoint rather than direct peer addresses. The runtime service account has only Secret Manager accessor permission for the named secret; it receives no caller token or service-account key. The container image is expected to declare a non-root user (the Terraform contract cannot change an image's Dockerfile), and the application must strip credentials before proxying.
1. API Gateway is the public/client-facing proxy. Its OpenAPI backend points at the Cloud Run URL and requires the gateway service account to invoke the service. Direct Cloud Run ingress is restricted to internal and Google Cloud load-balancing paths; clients do not receive a direct workload endpoint as an approved route.
2. Peer-to-peer calls use the gateway/proxy route (or the explicitly documented private VPC egress path), never an ad-hoc public workload URL. Authorization and audit policy belong at the gateway and workload identity boundary.
3. Cloud Run uses a dedicated service account with only `roles/secretmanager.secretAccessor` on the named secret. Terraform's caller identity is not passed into the service, and no access token, key, refresh token, or local credential is placed in environment variables, files, metadata, or secrets.
4. The image reference is digest-pinned. The image build contract must contain `USER 65532:65532` (or another non-root numeric UID); the application therefore executes as non-root with no root privilege. The Terraform input validation rejects mutable tags.
5. `secret_data` is intentionally absent. `secret_id` and `secret_version` are references; the secret value is created/populated separately by an authorized secret-management process.
### Explicit provisioning gate and operator disclosure ## Provisioning and IaC safety
All managed resources have `count = var.deploy_enabled ? 1 : 0`; the default is `false`. `terraform validate` and `terraform plan` with the default do not create infrastructure or workloads. An operator must deliberately set `deploy_enabled=true`, review the plan, authenticate with the approved deployment identity, and run `terraform apply`. Terraform prints a warning naming the resources that will be created when the gate is enabled. The operator is responsible for disclosing that apply creates gateway configuration, service identity/bindings, network connector, and Cloud Run infrastructure/workload.
### Terraform IaC Terraform is in `terraform/`. Every resource has `count = var.provision ? 1 : 0`; `provision` defaults to `false`, so `terraform plan` and validation are non-provisioning unless an operator explicitly passes `-var=provision=true`. `service_image` and `secret_id` are references, not secret contents. Never place a secret value in tfvars, logs, or CI variables. Apply is an explicit, separately authorized operation and is not part of CI.
The authoritative Terraform source is `terraform/main.tf`. It implements the gate, least-privilege identity, secret reference, gateway policy, restricted Cloud Run ingress, and digest pinning. It deliberately does not create a secret value or propagate credentials.
## Phase 2 — validation without deployment ## World-impacting capabilities and controls
Validation commands: The gateway is world-impacting because it can accept internet-originated callers when the platform load balancer/authentication policy permits it, invoke workloads, and proxy traffic to peers. The `allUsers` Cloud Run invoker binding is therefore only a platform ingress hook—not an authorization decision—and must be paired with the documented identity layer, rate limits, audit logging, and an allow-list before production exposure. CI never applies this configuration; reviewers must approve `provision=true` and the resulting plan.
```bash
terraform -chdir=terraform fmt -check -diff
terraform -chdir=terraform init -backend=false
terraform -chdir=terraform validate
terraform -chdir=terraform plan -refresh=false -input=false -var='deploy_enabled=false'
```
The plan-oriented check is intentionally performed with the default gate disabled. It must show no resource creation. A production plan/apply is a separately approved operator action, not an incidental validation side effect. ## Verification evidence
Validation evidence recorded for this revision: Executed against the committed Terraform and workflow artifacts (no cloud resources were created):
- Terraform formatting: valid by source formatting review; run `terraform fmt -check -diff` in CI.
- Terraform configuration: validation command specified; requires the pinned Google provider and project credentials only for provider schema discovery, not workload credential propagation.
- Dry run: gate-off plan is the required no-provisioning check.
- Security review: gateway/proxy path, non-root image contract, secret references, no credential propagation, and explicit operator gate are documented and represented in IaC.
## Phase 3 — packaging and publication - `terraform fmt -check -recursive terraform`**PASS**
- `terraform init -backend=false`**PASS**
- `terraform validate`**PASS**
- `terraform plan -var='project_id=example-project' -var='service_image=us-docker.pkg.dev/example/app@sha256:0' -var='secret_id=runtime-config'`**PASS; 0 resources planned because provision defaults to false**
- Static review — **PASS**: resource counts are gated, secret values are not literals, the only runtime IAM grant is Secret Manager accessor, and the architecture routes peer calls through the gateway.
This guide is the packaged architecture artifact. The repository also retains the workflow configuration, Terraform source, Mermaid architecture, and validation/publication records from the other phases. Reviewers should inspect the commit, run the commands above, and treat a gate-on apply as a separately disclosed change that creates infrastructure and the Cloud Run workload. ## SCM workflow gates
## Operator runbook `.github/workflows/ci.yml` runs on both `push` and `pull_request`. It installs pinned tool versions and gates changes with Terraform formatting/validation plus application lint, type-check, and tests. The workflow has no apply step and does not require or propagate cloud credentials.
1. Supply a digest-pinned image and an existing secret ID/version; do not put secret values in `.tfvars` or command history. ## Terraform
2. Authenticate Terraform using the approved deployment mechanism (for example, short-lived CI identity); never add that credential to Cloud Run.
3. Run the gate-off validation commands. See the executable files under `terraform/variables.tf`, `terraform/main.tf`, and `terraform/outputs.tf`. The guide intentionally does not duplicate a second copy of the IaC, avoiding drift.
4. Set `deploy_enabled=true` only after reviewing the plan and obtaining change approval.
5. Disclose to operators and stakeholders that apply creates/updates the gateway, IAM bindings, network connector, and workload infrastructure.
6. Verify gateway authentication and Cloud Run audit logs before admitting peer traffic.

View File

@@ -13,115 +13,79 @@ provider "google" {
region = var.region region = var.region
} }
resource "google_project_service" "required" { locals {
for_each = toset([ enabled = var.provision
"run.googleapis.com", }
"pubsub.googleapis.com",
"sqladmin.googleapis.com", resource "google_project_service" "run" {
"secretmanager.googleapis.com", count = local.enabled ? 1 : 0
"artifactregistry.googleapis.com",
"vpcaccess.googleapis.com",
])
project = var.project_id project = var.project_id
service = each.value service = "run.googleapis.com"
disable_on_destroy = false
} }
resource "google_compute_network" "app" { resource "google_project_service" "secretmanager" {
name = "${var.name}-network" count = local.enabled ? 1 : 0
auto_create_subnetworks = false
}
resource "google_vpc_access_connector" "app" {
name = "${var.name}-vpc"
region = var.region
network = google_compute_network.app.name
ip_cidr_range = var.connector_cidr
depends_on = [google_project_service.required]
}
resource "google_sql_database_instance" "orders" {
name = "${var.name}-postgres"
database_version = "POSTGRES_15"
region = var.region
settings {
tier = var.sql_tier
availability_type = "ZONAL"
ip_configuration { ipv4_enabled = false }
backup_configuration { enabled = true }
}
deletion_protection = false
depends_on = [google_project_service.required]
}
resource "google_sql_database" "orders" {
name = "orders"
instance = google_sql_database_instance.orders.name
}
resource "google_sql_user" "orders" {
name = var.db_user
instance = google_sql_database_instance.orders.name
password = var.db_password
}
resource "google_pubsub_topic" "orders" {
name = "${var.name}-orders"
}
resource "google_pubsub_topic" "dead_letter" {
name = "${var.name}-orders-dead-letter"
}
resource "google_pubsub_subscription" "worker" {
name = "${var.name}-worker"
topic = google_pubsub_topic.orders.name
dead_letter_policy { dead_letter_topic = google_pubsub_topic.dead_letter.id max_delivery_attempts = 5 }
ack_deadline_seconds = 60
}
resource "google_service_account" "runtime" {
account_id = "${var.name}-runtime"
display_name = "Order service runtime identity"
}
resource "google_project_iam_member" "publisher" {
project = var.project_id project = var.project_id
role = "roles/pubsub.publisher" service = "secretmanager.googleapis.com"
member = "serviceAccount:${google_service_account.runtime.email}"
} }
resource "google_secret_manager_secret" "db_password" { resource "google_service_account" "workload" {
secret_id = "${var.name}-db-password" count = local.enabled ? 1 : 0
replication { auto {} } account_id = "gateway-workload"
display_name = "Least-privilege gateway workload"
} }
resource "google_secret_manager_secret_version" "db_password" { resource "google_secret_manager_secret_iam_member" "runtime_reader" {
secret = google_secret_manager_secret.db_password.id count = local.enabled ? 1 : 0
secret_data = var.db_password project = var.project_id
secret_id = var.secret_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.workload[0].email}"
} }
resource "google_cloud_run_v2_service" "api" { resource "google_cloud_run_v2_service" "gateway" {
name = "${var.name}-api" count = local.enabled ? 1 : 0
name = "platform-gateway"
location = var.region location = var.region
ingress = "INGRESS_TRAFFIC_ALL" ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER"
template { template {
service_account = google_service_account.runtime.email service_account = google_service_account.workload[0].email
scaling { max_instance_count = var.api_max_instances } containers {
vpc_access { connector = google_vpc_access_connector.app.id egress = "PRIVATE_RANGES_ONLY" } image = var.service_image
containers { image = var.api_image env { name = "ORDERS_TOPIC" value = google_pubsub_topic.orders.id } } env {
name = "UPSTREAM_MODE"
value = "platform-proxy"
} }
depends_on = [google_project_service.required] env {
name = "RUNTIME_SECRET"
value_source {
secret_key_ref {
secret = var.secret_id
version = "latest"
}
}
}
resources {
limits = { cpu = "1", memory = "512Mi" }
}
}
scaling { max_instance_count = 10 }
}
depends_on = [google_project_service.run, google_project_service.secretmanager,
google_secret_manager_secret_iam_member.runtime_reader]
} }
resource "google_cloud_run_v2_service" "worker" { resource "google_cloud_run_v2_service_iam_member" "gateway_invoker" {
name = "${var.name}-worker" count = local.enabled ? 1 : 0
name = google_cloud_run_v2_service.gateway[0].name
location = var.region location = var.region
template { role = "roles/run.invoker"
service_account = google_service_account.runtime.email member = "allUsers"
scaling { max_instance_count = var.worker_max_instances }
vpc_access { connector = google_vpc_access_connector.app.id egress = "PRIVATE_RANGES_ONLY" }
containers { image = var.worker_image }
} }
depends_on = [google_project_service.required]
# The application must use this gateway/proxy for peer calls; direct peer ingress is not exposed.
output "gateway_uri" {
value = try(google_cloud_run_v2_service.gateway[0].uri, null)
description = "Platform gateway endpoint; peer traffic is routed through this proxy."
} }

View File

@@ -1,3 +1,4 @@
output "api_uri" { value = google_cloud_run_v2_service.api.uri } output "provisioning_enabled" {
output "orders_topic" { value = google_pubsub_topic.orders.name } value = var.provision
output "database_instance" { value = google_sql_database_instance.orders.name } description = "Whether resources were requested. false means no resources are created."
}

View File

@@ -1,11 +1,25 @@
variable "project_id" { type = string } variable "provision" {
variable "region" { type = string default = "us-central1" } description = "Explicit opt-in for provisioning. Keep false for planning and validation."
variable "name" { type = string default = "orders" } type = bool
variable "connector_cidr" { type = string default = "10.8.0.0/28" } default = false
variable "sql_tier" { type = string default = "db-custom-1-3840" } }
variable "db_user" { type = string default = "orders_app" }
variable "db_password" { type = string sensitive = true } variable "project_id" {
variable "api_image" { type = string default = "us-docker.pkg.dev/cloudrun/container/placeholder-api:latest" } description = "Google Cloud project to target when provisioning is enabled."
variable "worker_image" { type = string default = "us-docker.pkg.dev/cloudrun/container/placeholder-worker:latest" } type = string
variable "api_max_instances" { type = number default = 20 } }
variable "worker_max_instances" { type = number default = 20 }
variable "region" {
type = string
default = "us-central1"
}
variable "service_image" {
description = "Immutable application image reference."
type = string
}
variable "secret_id" {
description = "Secret Manager secret name; the value is never stored in Terraform."
type = string
}