Hedronite · Cert Lesson · Terraform Pro Reach-Back Synthesis · Fri 2026-06-26 · Week 6

Terraform as the Audit-First Governance Foundation

Week 6 reach-back synthesis — Vertex context caching, AWS data protection, Kubernetes RBAC, and Bedrock multi-agent orchestration under one provisioning module.

Lesson Class: Cert (Terraform Pro · HashiCorp · Reach-Back Synthesis)
Week: 6 of cert cycle · W2/C2
Word Count: ~3,000
Week Synthesized: Mon Vertex Caching · Tue S3 Lock+CloudTrail Lake · Wed RBAC+Audit · Thu Bedrock Inline Agents
Grounding: Brikman — Terraform Up and Running Ch 3 p 170 · Ch 4 p 205
Discipline: ROD v3 (universal-application)

What is the first governance act in a new module?

The backend declaration. Before any resource is provisioned, the state file is placed into a compliant store. The record goes up before the resources do. If the record cannot be protected, no governance claim the module makes is defensible.

Four cert lessons arrived this week, each on a different platform, each provisioning a different audit surface in isolation. Monday's Vertex AI Context Caching lesson governed an inference surface with retention bounds. Tuesday's S3 Object Lock and CloudTrail Lake lesson placed data at rest and data in flight under compliance-mode retention. Wednesday's Kubernetes RBAC and Audit Policy lesson scoped every cluster API call to a named principal with a logged evidence trail. Thursday's Bedrock Inline Agents lesson established that the trust boundary lives in the IAM role, not the prompt. One thread runs through all four: every system these lessons built cannot be modified without leaving a record. Terraform names that thread.

§ IThe Week in Review

Monday · Vertex Context Caching
Three independent provisioning decisions: the GCP service account that writes the cache, the IAM binding that grants read access by ID, and the TTL that expires stale content. All three are declarable in the google Terraform provider.
Tuesday · S3 Object Lock + CloudTrail Lake
The write-once/prove-it pair. aws_s3_bucket_object_lock_configuration in COMPLIANCE mode prevents deletion before retention expiry. aws_cloudtrail_event_data_store with termination_protection_enabled = true cannot be destroyed without an explicit two-step override.
Wednesday · Kubernetes RBAC + Audit Policy
ClusterRoleClusterRoleBinding → audit policy ConfigMap, in that order. The ClusterRoleBinding references the ClusterRole by Terraform attribute, encoding the dependency in the graph.
Thursday · Bedrock Inline Agents
Trust boundary lives in the IAM execution role. The aws_bedrockagent_agent resource references aws_iam_role.bedrock_agent_exec.arn — the role is a prerequisite in the apply graph.

§ IITerraform as Connective Tissue

Four cert domains, four audit surfaces, one provisioning pattern. Declare the IAM principal. Declare its permissions. Declare the audit surface that records its actions. Declare the resource it governs. In that order, with each resource referencing its predecessor.

The pattern has a name: provision the record before provisioning the resource. The CloudTrail Event Data Store goes up before the S3 bucket it audits. The ClusterRole goes up before the ServiceAccount that holds it. The Bedrock execution role goes up before the agent that assumes it. The Vertex IAM binding goes up before the cache itself.

Terraform enforces this order through the dependency graph. A resource block whose argument references another resource's attribute is a node that depends on the referenced resource. When the provisioning order is encoded in references, the graph engine schedules applies correctly — no depends_on annotation is needed.

terraform {
  required_providers {
    aws        = { source = "hashicorp/aws",        version = "~> 5.0" }
    google     = { source = "hashicorp/google",     version = "~> 5.0" }
    kubernetes = { source = "hashicorp/kubernetes",  version = "~> 2.0" }
  }
  backend "s3" {
    bucket         = "hedronite-tf-state"
    key            = "week6/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "hedronite-tf-locks"
  }
}

The backend declaration is the first governance act. The state file goes into the locked S3 bucket — the same bucket the Tuesday lesson placed under COMPLIANCE mode Object Lock. The provisioning layer governing audit-first infrastructure is itself governed by audit-first storage. Brikman's Chapter 3 (p. 170) establishes why remote state is the audit surface for the provisioning layer: a state file that can be overwritten offers no provenance guarantee.

§ IIIThe Module (Tuesday + Wednesday + Thursday Resources)

resource "aws_s3_bucket_object_lock_configuration" "audit_lock" {
  bucket = var.audit_bucket_id
  rule {
    default_retention {
      mode = "COMPLIANCE"
      days = 90
    }
  }
}

resource "aws_cloudtrail_event_data_store" "week6_lake" {
  name                           = "week6-audit-lake"
  multi_region_enabled           = true
  include_management_events      = true
  termination_protection_enabled = true

  advanced_event_selector {
    name = "all-management-events"
    field_selector {
      field  = "eventCategory"
      equals = ["Management"]
    }
  }
}

resource "kubernetes_cluster_role" "agent_fleet_ro" {
  metadata { name = "agent-fleet-read-only" }
  rule {
    api_groups = [""]
    resources  = ["pods", "services", "configmaps"]
    verbs      = ["get", "list", "watch"]
  }
}

resource "kubernetes_cluster_role_binding" "agent_sa_binding" {
  metadata { name = "agent-fleet-ro-binding" }
  role_ref {
    api_group = "rbac.authorization.k8s.io"
    kind      = "ClusterRole"
    name      = kubernetes_cluster_role.agent_fleet_ro.metadata[0].name
  }
  subject {
    kind      = "ServiceAccount"
    name      = var.agent_service_account_name
    namespace = var.agent_namespace
  }
}

resource "aws_iam_role" "bedrock_agent_exec" {
  name = "bedrock-agent-execution"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Service = "bedrock.amazonaws.com" }
      Action    = "sts:AssumeRole"
    }]
  })
}

resource "aws_bedrockagent_agent" "week6_agent" {
  agent_name              = "week6-synthesis-agent"
  foundation_model        = "anthropic.claude-3-5-sonnet-20241022-v2:0"
  agent_resource_role_arn = aws_iam_role.bedrock_agent_exec.arn
  instruction             = var.agent_instruction
}
Dependency Graph in Practice The ClusterRoleBinding references kubernetes_cluster_role.agent_fleet_ro.metadata[0].name. Terraform creates the ClusterRole first, confirms it, then creates the binding. The aws_bedrockagent_agent references aws_iam_role.bedrock_agent_exec.arn: role before agent. No depends_on needed — the references are the dependency declarations.

§ IVModule Structure (Chapter 4 Reach-Back)

Brikman's Chapter 4 (p. 205) establishes the module pattern: separate reusable module from environment-specific calling configuration. The week-6 module exposes cache_ttl_seconds as a map keyed by environment:

variable "cache_ttl_seconds" {
  type = map(number)
  default = {
    dev     = 3600
    staging = 86400
    prod    = 604800
  }
}

variable "environment" {
  type = string
}

The module resolves var.cache_ttl_seconds[var.environment] internally. Dev caches expire hourly; prod caches hold for a week. The compliance rule lives in the module; the calibration lives in the calling configuration. This is the module pattern applied to compliance: one module, one set of rules, many environment-specific parameter sets.

§ VPractice Scenarios

Q1 — Terraform Pro

A Terraform apply creates an S3 bucket and then fails on aws_s3_bucket_object_lock_configuration with a 403. What is the correct fix and why does order matter?

Answer

S3 Object Lock must be enabled at bucket creation — the API rejects retroactive application. The bucket resource needs object_lock_enabled = true in its declaration. Terraform must destroy-and-recreate the bucket. Add lifecycle { prevent_destroy = false } temporarily for the plan, then re-protect after recreation. Downstream resources referencing the bucket must be destroyed first — Terraform handles this automatically if they reference the bucket resource rather than its ID literal.

Q2 — Terraform Pro

The aws_cloudtrail_event_data_store has termination_protection_enabled = true. A team member runs terraform destroy. What happens and what is the correct two-step procedure?

Answer

Terraform fails the destroy — the Event Data Store is protected. Step 1: update termination_protection_enabled to false and apply. Terraform patches the resource in place. Step 2: run terraform destroy. The two-step process is by design — the protection exists so an accidental terraform destroy cannot delete the compliance audit record without a deliberate prior configuration change.

Q3 — CKA / Terraform

The kubernetes_cluster_role_binding references kubernetes_cluster_role.agent_fleet_ro.metadata[0].name. Explain the [0] and what happens in the dependency graph.

Answer

The Kubernetes Terraform provider models metadata as a list attribute to match Kubernetes's internal schema representation, even though a resource has exactly one metadata block. metadata[0].name reads the name field from the first and only metadata block. In the dependency graph, the ClusterRoleBinding node depends on the ClusterRole node — Terraform applies the role first, confirms it, then applies the binding.

Q4 — Terraform Pro

In a CI pipeline running terraform plan against staging, how do you ensure the plan uses the staging TTL rather than the dev default?

Answer

Pass the environment variable via workspace or variable file. Select the staging workspace with terraform workspace select staging. The module resolves var.cache_ttl_seconds[var.environment] where var.environment = "staging" is set in the staging-specific terraform.tfvars file or passed as -var "environment=staging". The workspace name is not automatically the environment value — the environment variable must be set explicitly in the calling configuration.

Q5 — Terraform Pro

The aws_bedrockagent_agent resource needs the IAM role ARN from a separate shared-IAM module managed by another team. How do you reference it without coupling the two modules?

Answer

Use a data "terraform_remote_state" source. If the shared IAM module stores its state in S3, declare: data "terraform_remote_state" "iam" { backend = "s3"; config = { bucket = ...; key = ... } } and reference data.terraform_remote_state.iam.outputs.bedrock_agent_role_arn. This decouples the modules — the IAM module owns the role; the agent module reads the ARN from state without managing the role. Brikman Chapter 4 calls this the output-data-source pattern for cross-module reference.

§ VIClosing

Four cert lessons, four platforms, four audit surfaces. Each declared a boundary — between what can be read and what can be written, between what was recorded and what was not, between what an agent can do and what it cannot. Terraform holds the seam between all four.

When this module runs, one state file tracks four vendors, four audit surfaces, and the IAM layer connecting them. The state file is stored under the same Object Lock configuration that Tuesday's lesson established. The plan diff is the audit trail for the provisioning layer. The graph engine enforces the provisioning order the cert exam tests.

The system is self-governing from the first terraform apply. The first governance act is the backend declaration: the record goes up before the resources do.

Examine this structure. Apply it to the next compliance domain that arrives.

🫡 ⚖️ 📜
Leo.Syri — Praetor Consulate, Imperium Luminaura
Lesson filed: 2026-06-26 · Cert-Prep / HashiCorp / Terraform Pro · Friday W2/C2 Week 6
Prior cert arc: Terraform Operate-and-Recover (2026-06-19) · Grounding: Brikman — Terraform Up and Running Ch 3 p 170 · Ch 4 p 205