Build an AI Terraform Safety Gate
Build a local Terraform gate that tests AI-generated S3 security settings.
Introduction
30 Second Summary
A polished AI answer can look ready to use while one tiny setting leaves a serious security gap. The danger is easy to miss when the file still passes a basic check.
In this project, you will build a local Terraform safety gate for an AWS S3 artifacts bucket from a draft generated by Claude. Deterministic tests catch a realistic security regression without AWS credentials or deployed infrastructure.
What You'll Build
Run your finished safety gate to watch a realistic public-access regression fail before the corrected configuration passes every check.
By the end of this project, you'll have:
- A reviewed AWS S3 configuration you can format before validating it locally.
- A visible security regression that proves structural validation can accept an unsafe Boolean setting.
- Mocked, plan-only tests that enforce all four public-access safeguards without AWS credentials or deployed resources.
- Secret Mission: Add an organization-style bucket-name policy that rejects names without the required prefix.
Are there any prerequisites?
You do not need prior Terraform experience or an AWS account. You need a Linux device with internet access plus permission to install packages.
Before We Start
This checkpoint captures the purpose of your Terraform safety gate before the hands-on work begins. It will protect an S3 artifacts bucket's intended public-access controls because AI-generated infrastructure must be tested before deployment.
Set Up Your Linux Workstation
Your safety gate can only be trusted when every local tool behaves predictably before you test the Amazon S3 artifacts bucket.
You will prepare Claude Free, Visual Studio Code, and Terraform CLI 1.16.4. Access to AWS stays outside this project.
In this step, get ready to:
- Sign in to Claude Free through the web.
- Install Visual Studio Code on Linux.
- Prepare the ai-terraform-safety-gate workspace with Terraform CLI 1.16.4.
Create your Claude Free access
Claude provides the web chat that drafts your Terraform files in the next step. You remain responsible for reviewing every suggestion.
This sign-in creates browser access only. You do not need to create or expose an API key.
- Switch to the browser already used for this learning session.
- Open claude.ai.
- Choose either Continue with Google or Continue with email.
- Complete the sign-in flow in your browser.
- Keep your account on the Free plan.
Your Claude chat workspace is ready for the constrained drafting prompt you will use next.
Why Use Claude Free?
Claude Free provides web chat at no cost. This project uses it for suggestions while Terraform remains the deterministic source of validation.
Install Visual Studio Code
Visual Studio Code gives the project one place for files and terminal commands. Choose the installation path that matches your Linux distribution.
Debian or Ubuntu
- Open the official Visual Studio Code download page in your browser.
- Download the .deb package that matches your device architecture.
- Open the Downloads folder in your Linux file manager.
- Use your file manager's terminal action to start a terminal in the Downloads folder.
- Replace <file> with the downloaded package filename when you run this command:
sudo apt install ./<file>.deb
What Does This Command Do?
The command asks the Debian package manager to install the downloaded Visual Studio Code package. The ./ prefix points to a file in the current Downloads folder.
- Read the final terminal lines to confirm the package installation completes.
Visual Studio Code is installed when the package manager finishes without reporting a failed installation.
Package Installation Failing?
Check that your terminal is in the folder containing the downloaded .deb file. Confirm that the filename you substituted includes the .deb extension.
If your distribution reports a permissions or package dependency problem, ask for help with the installation output.
Fedora or RHEL
- Open the official Visual Studio Code download page in your browser.
- Download the .rpm package that matches your device architecture.
- Open the Downloads folder in your Linux file manager.
- Use your file manager's terminal action to start a terminal in the Downloads folder.
- Replace <file> with the downloaded package filename when you run this command:
sudo dnf install <file>.rpm
What Does This Command Do?
The command asks the RPM package manager to install the downloaded Visual Studio Code package. The package provides the editor plus its terminal launcher.
- Read the final terminal lines to confirm the package installation completes.
Visual Studio Code is installed when the package manager finishes without reporting a failed installation.
RPM Installation Failing?
Check that your terminal is in the folder containing the downloaded .rpm file. Confirm that the filename you substituted includes the .rpm extension.
If your distribution reports a permissions or dependency problem, ask for help with the installation output.
Snap Already Available
Snap offers a direct installation path when it is already available on your Linux device.
- Open a terminal through your Linux application launcher.
- Install Visual Studio Code through Snap by running this command:
sudo snap install --classic code
What Does This Command Do?
Snap downloads the official Visual Studio Code package. The installation also makes the code terminal command available.
- Read the final terminal lines to confirm the Snap installation completes.
Visual Studio Code is installed when Snap reports that the package was installed.
Snap Command Unavailable?
Use the Debian or RPM tab when Snap is unavailable on your device. Select the package format that matches your Linux distribution.
If Snap starts but cannot complete the installation, ask for help with the Snap output.
Install Terraform and open the workspace
Terraform CLI reads the project files and performs the local checks used by your safety gate. This project pins version 1.16.4 so every learner uses the same command behavior.
The version check can report the required release or an older release. It can also show that the command is unavailable.
- Check the current Terraform installation by running this command:
terraform version
What Does This Command Check?
The command reports the Terraform binary version. It also reports the platform plus any installed providers.
✔️ I see Terraform v1.16.4
Your terminal shows Terraform v1.16.4. The required Terraform binary is ready for the final workspace check.
ⓧ I see an older version
An older Terraform release can behave differently from the pinned project environment. Upgrade through HashiCorp's package repository using the path for your distribution.
Debian or Ubuntu
- Upgrade Terraform through the official package repository by running these commands:
wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(grep -oP '(?<=UBUNTU_CODENAME=).*' /etc/os-release || lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
What Do These Commands Do?
- The first command installs HashiCorp's package-signing key.
- The second command adds the official package repository for your Linux release.
- The final command refreshes available packages before upgrading Terraform.
- Read the final terminal lines to confirm the Terraform package finishes installing.
Debian Upgrade Failing?
Check your internet connection if the repository cannot be reached. Confirm that each command was copied as one complete line.
For distribution-specific help, ask about the terminal output.
RHEL or CentOS
- Upgrade Terraform through the official package repository by running these commands:
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo yum -y install terraform
What Do These Commands Do?
- The first command installs the repository management utility.
- The second command adds HashiCorp's official RHEL repository.
- The final command upgrades Terraform from that repository.
- Read the final terminal lines to confirm the Terraform package finishes installing.
RHEL Upgrade Failing?
Check your internet connection if the repository cannot be reached. Confirm that the repository command completed before the installation command ran.
For distribution-specific help, ask about the terminal output.
Fedora
- Upgrade Terraform through the official package repository by running these commands:
wget -O- https://rpm.releases.hashicorp.com/fedora/hashicorp.repo | sudo tee /etc/yum.repos.d/hashicorp.repo
sudo yum list available | grep hashicorp
sudo dnf -y install terraform
What Do These Commands Do?
- The first command adds HashiCorp's official Fedora repository.
- The second command confirms that HashiCorp packages are available.
- The final command upgrades Terraform through Fedora's package manager.
- Read the final terminal lines to confirm the Terraform package finishes installing.
Fedora Upgrade Failing?
Check your internet connection if the repository cannot be reached. Confirm that HashiCorp appears in the available package output.
For distribution-specific help, ask about the terminal output.
ⓧ Command not found
The Terraform binary is not installed yet. Add HashiCorp's official package repository using the path for your distribution.
Debian or Ubuntu
- Install Terraform through the official package repository by running these commands:
wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(grep -oP '(?<=UBUNTU_CODENAME=).*' /etc/os-release || lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
What Do These Commands Do?
- The first command installs HashiCorp's package-signing key.
- The second command adds the official package repository for your Linux release.
- The final command refreshes available packages before installing Terraform.
- Read the final terminal lines to confirm the Terraform package finishes installing.
Debian Installation Failing?
Check your internet connection if the repository cannot be reached. Confirm that each command was copied as one complete line.
For distribution-specific help, ask about the terminal output.
RHEL or CentOS
- Install Terraform through the official package repository by running these commands:
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo yum -y install terraform
What Do These Commands Do?
- The first command installs the repository management utility.
- The second command adds HashiCorp's official RHEL repository.
- The final command installs Terraform from that repository.
- Read the final terminal lines to confirm the Terraform package finishes installing.
RHEL Installation Failing?
Check your internet connection if the repository cannot be reached. Confirm that the repository command completed before the installation command ran.
For distribution-specific help, ask about the terminal output.
Fedora
- Install Terraform through the official package repository by running these commands:
wget -O- https://rpm.releases.hashicorp.com/fedora/hashicorp.repo | sudo tee /etc/yum.repos.d/hashicorp.repo
sudo yum list available | grep hashicorp
sudo dnf -y install terraform
What Do These Commands Do?
- The first command adds HashiCorp's official Fedora repository.
- The second command confirms that HashiCorp packages are available.
- The final command installs Terraform through Fedora's package manager.
- Read the final terminal lines to confirm the Terraform package finishes installing.
Fedora Installation Failing?
Check your internet connection if the repository cannot be reached. Confirm that HashiCorp appears in the available package output.
For distribution-specific help, ask about the terminal output.
The project workspace keeps every future configuration file inside one named folder. Creating it on your Desktop makes it easy to find throughout the project.
- Open a terminal through your Linux application launcher.
- Move to your Desktop by running this command:
cd ~/Desktop
What Does This Command Do?
The command changes the terminal's current location to your Desktop. The project folder created next appears there.
- Create the project folder and move into it by running these commands:
mkdir ai-terraform-safety-gate
cd ai-terraform-safety-gate
What Do These Commands Do?
The first command creates the empty ai-terraform-safety-gate folder. The second command makes that folder the terminal's current location.
Before you run the final check, what version do you expect the terminal to report? Which folder do you expect Visual Studio Code to open?
- Verify Terraform and open the current project folder in Visual Studio Code by running these commands:
terraform version
code .
What Does the Final Check Prove?
- The first command proves that the pinned Terraform binary is available in your terminal.
- The second command opens the terminal's current folder in Visual Studio Code.
You'll see Terraform v1.16.4 in the terminal. Visual Studio Code opens with the empty ai-terraform-safety-gate folder in its Explorer sidebar.
Final Check Not Working?
If the Terraform version is wrong, repeat the installation path for your distribution. If Visual Studio Code does not open, confirm its package installation completed.
If the wrong folder opens, return to the terminal that is inside ai-terraform-safety-gate before retrying the final command.
For help matching the result to your setup, share the non-sensitive terminal output with AI.
That's your local setup complete. Next up, you will turn a constrained Claude draft into a Terraform configuration that passes local validation.
Generate and Validate the First Terraform Draft
Your workstation now has the tools for a local safety workflow. The next challenge is turning an AI suggestion into infrastructure code you can inspect.
You'll ask Claude to draft three files under tight constraints. You will review them in Visual Studio Code.
Terraform then formats and validates your reviewed configuration locally. A pinned AWS Provider for Terraform keeps the dependency version repeatable.
In this step, get ready to:
- Generate a constrained Terraform draft with Claude.
- Create the three reviewed Terraform files in Visual Studio Code.
- Run the local Terraform checks.
Ask Claude for a constrained draft
AI-assisted code generation works best when the request fixes important names and versions in advance. The response remains a draft until you review it against the project requirements.
- Switch back to the Claude Free conversation in your browser.
- Give Claude the project constraints by pasting this prompt:
Generate Terraform for an AWS S3 artifacts bucket. Use Terraform 1.16.4 and hashicorp/aws 6.66.0. Use the resource labels aws_s3_bucket.artifacts and aws_s3_bucket_public_access_block.artifacts, an input variable named bucket_name with the default ai-devops-artifacts-demo, and set block_public_acls, block_public_policy, ignore_public_acls, and restrict_public_buckets to true. Return separate versions.tf, variables.tf, and main.tf files. Do not include provider credentials, a provider block, terraform plan, or terraform apply.
Why constrain the draft?
- The version constraints establish provider pinning before Terraform downloads a dependency.
- The resource labels give later tests stable addresses to inspect.
- The four Boolean settings define the intended public-access posture for AWS S3.
- The exclusions keep credentials and deployment actions outside this workflow.
- Submit the prompt.
- Review the response for separate versions.tf, variables.tf, and main.tf sections.
- Confirm the response omits credentials.
- Confirm the response omits deployment instructions.
Good start. You now have an AI-generated draft with clear boundaries for human review.
Draft ignored the constraints?
Claude may add extra configuration or change a requested label.
- Ask Claude to regenerate the draft using every original name and version exactly.
- Keep credentials and deployment commands out of the response.
Help me correct my Terraform draft.
Create the deterministic project files
The draft gives you raw material. The reviewed files below establish the exact configuration that your later safety checks depend on.
- Use the file creation control in Visual Studio Code's file list sidebar to create versions.tf inside ai-terraform-safety-gate.
- Fill versions.tf with the reviewed version constraints by using this code:
terraform {
required_version = "1.16.4"
required_providers {
aws = {
source = "hashicorp/aws"
version = "6.66.0"
}
}
}
What does this file control?
- The required_version setting pins the project to Terraform 1.16.4.
- The source setting selects hashicorp/aws.
- The provider version setting pins the AWS provider to 6.66.0.
- Save versions.tf.
- Confirm versions.tf is listed inside the ai-terraform-safety-gate folder.
Version file looks different?
A changed version or provider source can select a different dependency environment.
- Compare every value in versions.tf with the reviewed block above.
Help me compare my version constraints.
- Use the file creation control in the same sidebar to create variables.tf inside ai-terraform-safety-gate.
- Fill variables.tf with the reviewed bucket input by using this code:
variable "bucket_name" {
type = string
description = "Name for the AI DevOps artifacts bucket."
default = "ai-devops-artifacts-demo"
}
What does this variable hold?
- The bucket_name variable gives the bucket resource a reusable input.
- The type setting requires a text value.
- The default setting supplies ai-devops-artifacts-demo for local checks.
- Save variables.tf.
- Confirm variables.tf appears beside versions.tf in the file list.
Variable file looks different?
A renamed variable or changed default breaks the references used by the bucket resource.
- Check the variable name.
- Check the default bucket name.
Help me find the mismatch in variables.tf.
- Use the file creation control in the same sidebar to create main.tf inside ai-terraform-safety-gate.
- Add the artifacts bucket resource to main.tf by using this code:
resource "aws_s3_bucket" "artifacts" {
bucket = var.bucket_name
tags = {
Environment = "learning"
ManagedBy = "terraform"
}
}
What does this resource describe?
- The aws_s3_bucket.artifacts address gives the bucket a stable project label.
- The bucket argument reads the name from var.bucket_name.
- The tags map records the learning environment.
- The ManagedBy tag records Terraform as the configuration owner.
- Save main.tf.
- Confirm the editor shows one bucket resource in main.tf.
Bucket resource shows an editor warning?
The provider schema becomes available to the project after initialization.
- Check that every brace in the bucket resource has a matching closing brace.
- Check that var.bucket_name matches the variable in variables.tf.
Help me inspect my bucket resource.
- Place your cursor below the closing brace of the bucket resource in main.tf.
- Add the public-access block by using this code:
resource "aws_s3_bucket_public_access_block" "artifacts" {
bucket = aws_s3_bucket.artifacts.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
What does this safety block enforce?
- The bucket argument connects the public-access settings to aws_s3_bucket.artifacts.
- The block_public_acls setting blocks public access-control lists.
- The block_public_policy setting blocks public bucket policies.
- The ignore_public_acls setting ignores public access-control lists.
- The restrict_public_buckets setting restricts public bucket access.
- Save main.tf.
- Confirm main.tf now contains two resource blocks.
Public-access settings do not match?
All four public-access arguments must use the reviewed value for this first safe draft.
- Compare each public-access argument with the block above.
- Check the reference to aws_s3_bucket.artifacts.id.
Help me inspect the public-access block.
- Return to the Claude conversation from earlier.
- Ask Claude to explain the reviewed file boundaries by sending this prompt:
Explain the role of versions.tf, variables.tf, and main.tf in plain language.
What should the explanation cover?
- The explanation should connect versions.tf to tool and provider constraints.
- It should connect variables.tf to reusable input values.
- It should connect main.tf to the bucket configuration.
You should now see how each file has one clear responsibility. That separation makes the configuration easier to inspect before a local check.
✔️ Awesome, I've got everything!
Great. Double check that all three Terraform files are saved inside ai-terraform-safety-gate.
ⓧ I'd like to double check the full code
The reference below is the reviewed state for all three files.
terraform {
required_version = "1.16.4"
required_providers {
aws = {
source = "hashicorp/aws"
version = "6.66.0"
}
}
}
variable "bucket_name" {
type = string
description = "Name for the AI DevOps artifacts bucket."
default = "ai-devops-artifacts-demo"
}
resource "aws_s3_bucket" "artifacts" {
bucket = var.bucket_name
tags = {
Environment = "learning"
ManagedBy = "terraform"
}
}
resource "aws_s3_bucket_public_access_block" "artifacts" {
bucket = aws_s3_bucket.artifacts.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
Initialize, format, and validate the project
The files now describe the intended configuration. Terraform needs to format them and install the pinned provider schema before it can check their structure.
- Switch back to the terminal that opened the ai-terraform-safety-gate folder earlier.
- Apply Terraform's canonical formatting by running this command:
terraform fmt
What does this command do?
Terraform's formatter rewrites configuration files into its canonical layout. Consistent formatting makes later changes easier to review.
- Confirm the command returns to the terminal prompt without an error.
- Save any files Visual Studio Code marks as changed.
Formatting command failed?
A formatting failure usually points to incomplete HCL syntax or a terminal in the wrong folder.
- Confirm the terminal path ends with ai-terraform-safety-gate.
- Check each file for a missing brace or quote.
Help me fix my formatting failure.
Initialization installs the provider selected by versions.tf. The backend-free option keeps this check focused on local configuration.
- Initialize the provider without configuring a backend by running this command:
terraform init -backend=false
What happens during initialization?
- The -backend=false option skips backend access.
- Terraform installs the pinned hashicorp/aws provider at version 6.66.0.
- Terraform records the selected provider in .terraform.lock.hcl.
- The provider package is cached under .terraform.
- Confirm the terminal shows that hashicorp/aws version 6.66.0 was installed.
- Confirm .terraform.lock.hcl appears in the Visual Studio Code file list.
No AWS credential prompt appears. Initialization installs the provider without contacting AWS APIs.
Provider installation failed?
Provider installation needs internet access to reach the package repository. A wrong terminal location can also hide the version declaration.
- Confirm your Linux device has internet access.
- Confirm versions.tf is inside ai-terraform-safety-gate.
- Retry the initialization command after correcting the issue.
Help me troubleshoot provider initialization.
Before you run the final check, do you expect Terraform to accept all three reviewed files?
- Validate the local configuration by running this command:
terraform validate
What does validation prove?
Local validation checks Terraform syntax and internal consistency. It checks attribute names and value types against the installed provider schema.
The command does not validate remote services. It does not create infrastructure.
Terraform reports the local configuration as valid. That's your first gate working: an AI-assisted draft has become a formatted and structurally checked Terraform project.
Configuration is not valid?
A validation failure identifies a structural mismatch in one of the reviewed files.
- Read the file name and line number in the terminal output.
- Compare that location with the full-code reference above.
- Run the validation command again after saving the correction.
Help me diagnose my validation result.
Your first Terraform draft is pinned and locally valid. Next, you'll challenge this gate with a security regression that valid syntax alone cannot catch.
Create an Unsafe Regression
Your initialized Terraform project is open in Visual Studio Code. Formatting plus structural validation already pass.
Now you will challenge that baseline with a deliberate regression in one public-access setting.
The local validator checks syntax. This experiment reveals whether it also enforces your chosen security policy.
In this step, get ready to:
- Change one public-access safeguard in main.tf.
- Run Terraform's existing local gate.
- Use Claude to identify the policy gap.
Weaken one public-access safeguard
The aws_s3_bucket_public_access_block.artifacts resource holds four Boolean safeguards for your AWS S3 bucket. You will change only the setting that blocks public bucket policies.
- Select main.tf in the Explorer sidebar.
- Find this line inside aws_s3_bucket_public_access_block.artifacts:
block_public_policy = true
What does this setting control?
This line currently blocks public bucket policies. Its Boolean value expresses one part of the project's security intent.
- Replace that line with this one:
block_public_policy = false
What changed?
The false value disables the safeguard for public bucket policies. The other three safeguards remain set to true.
- Save main.tf.
✔️ Awesome, I've got everything!
Your saved main.tf now contains the single intentional change.
ⓧ I'd like to double check the full code
Compare your main.tf file with this complete reference.
resource "aws_s3_bucket" "artifacts" {
bucket = var.bucket_name
tags = {
Environment = "learning"
ManagedBy = "terraform"
}
}
resource "aws_s3_bucket_public_access_block" "artifacts" {
bucket = aws_s3_bucket.artifacts.id
block_public_acls = true
block_public_policy = false
ignore_public_acls = true
restrict_public_buckets = true
}
What should match?
This reference preserves both resource blocks. Only block_public_policy has changed from true to false.
Only main.tf changes in this step.
Test the regression with the existing gate
Formatting normalizes the file's layout. Validation checks whether the configuration is structurally acceptable.
- Return to the integrated terminal from initialization.
- Format the changed file by running this command:
terraform fmt
What does formatting check?
The formatting command applies Terraform's canonical layout to the configuration. This keeps the intentional value change separate from accidental spacing changes.
- Confirm the terminal returns to a prompt without errors.
- Save main.tf if the formatter changed its spacing.
Formatting failed?
- Compare the edited line with the full-file reference above.
- Check that the value is the unquoted Boolean false.
- Save main.tf before trying again.
Help me diagnose the formatting failure.
Before you run validation, decide whether you expect Terraform to accept the weakened Boolean value.
- Test the weakened configuration by running this command:
terraform validate
What does validation check?
The validation command checks syntax plus internal consistency. It also checks whether attributes use accepted value types.
Terraform reports that the configuration is valid.
You have exposed the exact gap this step targets. The validator accepts the Boolean value while your security intent remains untested.
Validation fails instead?
- Confirm that you changed only block_public_policy.
- Compare main.tf with the full-file reference above.
- Run the formatting command again after correcting any accidental edit.
Help me find the validation error.
Diagnose the validation gap with Claude
Claude can help you explain the evidence from your local gate. Your project output keeps its role limited to interpretation.
- Switch back to the Claude Free chat from earlier.
- Paste the relevant resource block with the terminal validation result into the message field.
- Append this question to the message:
Why can terraform validate accept this configuration even though block_public_policy is false, and what deterministic test should enforce our intent?
Why ask this question?
The question gives Claude the observed result plus the weakened setting. It asks for an explicit policy check without handing control of your infrastructure to the AI.
Before you submit, decide whether a valid result alone gives Claude enough evidence to call the configuration safe.
- Submit the message.
- Confirm the response distinguishes structural correctness from your explicit project policy.
You'll see Claude identify the missing deterministic check. That check must enforce the public-access policy directly.
Your unsafe configuration passes structural validation because the project policy has no executable test. Next, you will write the deterministic Terraform check that catches this regression.
Test the Public-Access Policy
Your last check proved that terraform validate accepts the weakened setting in main.tf. The configuration is structurally valid even though its public-access policy is unsafe.
Now you will encode that policy as a Terraform test. A mocked AWS Provider for Terraform lets the test inspect a plan without credentials or cloud infrastructure.
In this step, get ready to:
- Create a mocked plan-only test for the four public-access settings.
- Run the local safety gate against the current regression.
- Review the failed policy check with Claude.
Create the mocked policy test
A Terraform test turns each security requirement into an executable assertion. The mocked provider supplies the expected resource schema while keeping the entire check local.
- Use Visual Studio Code's file sidebar to create a folder named tests inside ai-terraform-safety-gate.
The file sidebar now shows tests beneath the project folder.
- Create public_access.tftest.hcl inside the tests folder.
The new file appears at tests/public_access.tftest.hcl in the file sidebar.
- Define the mocked plan-only policy checks by pasting this code into tests/public_access.tftest.hcl:
mock_provider "aws" {}
run "public_access_is_blocked" {
command = plan
assert {
condition = aws_s3_bucket_public_access_block.artifacts.block_public_acls
error_message = "Public ACLs must be blocked."
}
assert {
condition = aws_s3_bucket_public_access_block.artifacts.block_public_policy
error_message = "Public bucket policies must be blocked."
}
assert {
condition = aws_s3_bucket_public_access_block.artifacts.ignore_public_acls
error_message = "Public ACLs must be ignored."
}
assert {
condition = aws_s3_bucket_public_access_block.artifacts.restrict_public_buckets
error_message = "Public buckets must be restricted."
}
}
What Does This Test Enforce?
- The mock_provider "aws" {} block replaces provider behavior for this test. Terraform can inspect the AWS resource schema locally.
- The public_access_is_blocked run groups the public-access policy checks.
- The command = plan setting evaluates planned values without creating infrastructure.
- The first assertion requires public ACL blocking.
- The second assertion requires public bucket policy blocking.
- The third assertion requires public ACLs to be ignored.
- The fourth assertion requires public buckets to be restricted.
- Save tests/public_access.tftest.hcl.
- Confirm the editor tab no longer shows an unsaved-change indicator.
Can't Find the Test File?
- Confirm the file is inside the top-level tests folder.
- Check that the filename ends with .tftest.hcl.
- Compare every closing brace with the reference code.
Help me check why Terraform cannot discover or parse my test file.
Run the local safety gate
The test now compares the planned public-access values with your explicit policy. This is the check that structural validation could not provide.
Before you run the safety gate, do you expect every assertion to agree with the current main.tf values? Your next command tests that prediction.
- Run the local policy test from the integrated terminal by using this command:
terraform test
The Gate Caught the Regression
The test run fails. The authored message identifies the policy gap: Public bucket policies must be blocked.
The other three conditions match their required values. The failed condition points directly to block_public_policy.
You have the key proof now. Your security policy produces a precise local failure before any deployment can happen.
No AWS credentials are requested. No infrastructure is created.
🙋♀️ Test Failing for a Different Reason?
- Confirm tests/public_access.tftest.hcl matches the reference code exactly.
- Check that the initialized project still contains .terraform.lock.hcl.
- Confirm the terminal is running inside ai-terraform-safety-gate.
Help me diagnose my unexpected Terraform test failure.
✔️ Awesome, I've got everything!
Your test file is saved. The local safety gate reports the intended public-access policy failure.
ⓧ I'd like to double check the full code
Compare your project with this exact state:
resource "aws_s3_bucket" "artifacts" {
bucket = var.bucket_name
tags = {
Environment = "learning"
ManagedBy = "terraform"
}
}
resource "aws_s3_bucket_public_access_block" "artifacts" {
bucket = aws_s3_bucket.artifacts.id
block_public_acls = true
block_public_policy = false
ignore_public_acls = true
restrict_public_buckets = true
}
mock_provider "aws" {}
run "public_access_is_blocked" {
command = plan
assert {
condition = aws_s3_bucket_public_access_block.artifacts.block_public_acls
error_message = "Public ACLs must be blocked."
}
assert {
condition = aws_s3_bucket_public_access_block.artifacts.block_public_policy
error_message = "Public bucket policies must be blocked."
}
assert {
condition = aws_s3_bucket_public_access_block.artifacts.ignore_public_acls
error_message = "Public ACLs must be ignored."
}
assert {
condition = aws_s3_bucket_public_access_block.artifacts.restrict_public_buckets
error_message = "Public buckets must be restricted."
}
}
variable "bucket_name" {
type = string
description = "Name for the AI DevOps artifacts bucket."
default = "ai-devops-artifacts-demo"
}
terraform {
required_version = "1.16.4"
required_providers {
aws = {
source = "hashicorp/aws"
version = "6.66.0"
}
}
}
Use Claude to isolate the repair
The failed assertion narrows the review to one policy value. Claude's role is to interpret that evidence while you keep control of the code.
- Copy the failed assertion details from the terminal.
- Copy the aws_s3_bucket_public_access_block.artifacts resource block from main.tf.
- Switch back to the Claude Free chat from earlier.
- Ask Claude to identify the smallest corrective change using only those two snippets.
- Keep main.tf unchanged during this section.
Claude should identify one line: change block_public_policy = false to block_public_policy = true.
- Return to main.tf in Visual Studio Code.
- Confirm block_public_policy = false remains in the public-access block.
The test remains intentionally red with Public bucket policies must be blocked. This proves your safety gate can stop the regression before a human-approved repair.
Your local gate now detects the security setting that basic validation accepted. Next, you will apply the one-line repair and prove the complete safety gate passes.
Repair and Verify the Safety Gate
Your mocked Terraform test has already pinpointed the weakened public-policy safeguard. The failed assertion gives you precise evidence for a minimal repair.
The smallest human-reviewed repair restores one Boolean value. A complete local gate then proves the configuration’s structure plus its security intent before anyone approves the change proposed with Claude.
In this step, get ready to:
- Restore the public-policy safeguard with a human-reviewed repair.
- Run the complete local safety gate.
- Record the responsibility of each review layer.
Restore the public-policy safeguard
The failed assertion identified one unsafe value in main.tf. Keeping the edit small makes the human review clear.
- Select main.tf from Visual Studio Code’s file sidebar.
- Locate the aws_s3_bucket_public_access_block.artifacts resource.
- Replace false with true on the block_public_policy line.
- Confirm the repaired resource matches this reference:
resource "aws_s3_bucket_public_access_block" "artifacts" {
bucket = aws_s3_bucket.artifacts.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
What does the repair protect?
- The block_public_policy argument restores the safeguard required by the failed assertion.
- The other three public-access arguments remain set to true.
- Reviewing the four values together confirms that the edit stayed within the failed policy.
- Save main.tf.
- Apply canonical formatting across the project by running this command:
terraform fmt -recursive
What does this command do?
The recursive formatting pass applies Terraform’s canonical style to the configuration plus the test directory. Consistent formatting keeps the reviewed change easy to compare.
The formatting command completes locally. Your configuration plus test files now use Terraform’s canonical layout.
Formatting reports a problem?
Compare the braces plus argument values in main.tf with the repaired resource above. A missing brace or changed argument can prevent formatting.
Use this if you need help locating the formatting problem: Help me diagnose why Terraform cannot format my repaired main.tf file.
Before you validate, make a prediction about whether the one-value repair preserves the configuration’s structural validity.
- Check the repaired configuration’s structure by running this command:
terraform validate
What does validation prove?
Validation checks syntax plus internal consistency against the installed provider schema. This confirms that the repaired value fits the configuration structure.
Security intent still belongs to your authored assertions. The test provides that stronger evidence in the next check.
You should see a success result confirming that the local configuration is valid. This proves the repair preserved structural correctness.
Validation still fails?
Confirm that main.tf is saved. Check that only the value for block_public_policy changed.
Use this if the validation result points to another file: Help me interpret my Terraform validation failure without changing the project’s intended resources.
Run and explain the complete local gate
Structural validation has passed again. The decisive check is whether the repaired value now satisfies every public-access assertion in the mocked plan-only test.
Before you run the full gate, make a prediction about whether all four assertions now pass.
- Execute the mocked safety test from the ai-terraform-safety-gate folder by running this command:
terraform test
What does the passing test prove?
- The public_access_is_blocked run evaluates a plan against the mocked AWS provider.
- The four assertions enforce the public-access behavior your project requires.
- The mocked plan creates no infrastructure. It also requires no AWS credentials.
You should see the public_access_is_blocked run pass with all four assertions satisfied. That closes the loop: your repaired configuration now passes both structural validation plus the security-intent gate.
Does the public-policy assertion still fail?
Return to main.tf if you still see Public bucket policies must be blocked. Confirm that the saved block_public_policy value is true.
Run the test from the ai-terraform-safety-gate folder so Terraform loads the intended configuration plus the tests directory.
Use this if the test still fails after those checks: Help me diagnose why the block_public_policy assertion still fails after I set it to true.
- Paste the outputs from your formatting, validation, plus test runs into the Claude Free chat from earlier.
- Ask Claude to summarize the purpose of each command using those outputs as evidence.
Claude’s summary should separate formatting, structural validation, plus intent testing. Your successful test output remains the deterministic evidence.
- Record the review workflow below in your project notes.
Your Review Workflow
- AI proposes.
- Terraform validates structure.
- Tests enforce intent.
- A human approves the change.
✔️ Awesome, I've got everything!
Your repair is saved. Formatting, validation, plus the mocked test now pass locally.
ⓧ I'd like to double check the full code
Compare your four project files with these exact references.
terraform {
required_version = "1.16.4"
required_providers {
aws = {
source = "hashicorp/aws"
version = "6.66.0"
}
}
}
variable "bucket_name" {
type = string
description = "Name for the AI DevOps artifacts bucket."
default = "ai-devops-artifacts-demo"
}
resource "aws_s3_bucket" "artifacts" {
bucket = var.bucket_name
tags = {
Environment = "learning"
ManagedBy = "terraform"
}
}
resource "aws_s3_bucket_public_access_block" "artifacts" {
bucket = aws_s3_bucket.artifacts.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
mock_provider "aws" {}
run "public_access_is_blocked" {
command = plan
assert {
condition = aws_s3_bucket_public_access_block.artifacts.block_public_acls
error_message = "Public ACLs must be blocked."
}
assert {
condition = aws_s3_bucket_public_access_block.artifacts.block_public_policy
error_message = "Public bucket policies must be blocked."
}
assert {
condition = aws_s3_bucket_public_access_block.artifacts.ignore_public_acls
error_message = "Public ACLs must be ignored."
}
assert {
condition = aws_s3_bucket_public_access_block.artifacts.restrict_public_buckets
error_message = "Public buckets must be restricted."
}
}
Your local safety gate is complete. The repaired configuration now has human approval backed by repeatable evidence.
Secret mission
Reject Noncompliant Bucket Names
Extend your local safety gate with an organization-style naming policy for artifact buckets. A negative test will prove that demo-bucket is rejected while the approved default still passes.
Clean Up Your Resources
Clean Up Your Resources
This project stayed on your Linux device with no cloud resources or ongoing charges. Choose the cleanup option that matches what you want to do next.
Resources you used:
- Local ai-terraform-safety-gate project folder.
- Local .terraform.lock.hcl dependency lock file created by Terraform.
- Local .terraform provider cache inside the project folder.
Keep everything running
No action is needed. Choose this option if you want to reuse the safety-gate pattern.
- Keep the ai-terraform-safety-gate folder for future experiments.
- Leave Visual Studio Code installed for future projects.
- Leave Terraform CLI 1.16.4 installed on your Linux device.
- Keep your Claude Free account available for future learning.
Pause - I'll come back to this later
Terraform has no running process to pause. This option closes your workspace while preserving every file.
- Close Visual Studio Code.
- Close the Claude browser tab.
- Keep the ai-terraform-safety-gate folder in its current location.
Delete - I don't want to use this again
Deleting the project folder is permanent. It removes the configuration files, tests, dependency lock file, and provider cache.
- Switch back to the terminal from earlier in the ai-terraform-safety-gate project.
- Delete the project folder with a terminal sequence that ends by listing its former parent directory:
cd ..
rm -rf ai-terraform-safety-gate
ls
What Gets Deleted?
The cd command moves the terminal to the folder that contains your project. The rm command recursively removes the ai-terraform-safety-gate folder.
The final ls command lists the remaining entries. You should no longer see ai-terraform-safety-gate in that output.
No cloud cleanup is required because the mock provider created no infrastructure. No stateful backend exists.
- Leave Visual Studio Code installed for future projects.
- Leave Terraform CLI installed for future projects.
- Keep your Claude Free account available for future learning.
Nice Work!
Nice Work!
You pulled it off! Your AI-assisted Terraform safety gate now turns infrastructure intent into local tests that catch unsafe changes before deployment.
What you learned:
- Created a reproducible AI draft for an AWS S3 artifacts bucket using Claude Free with Terraform CLI 1.16.4 plus AWS Provider for Terraform 6.66.0.
- Exposed the validation gap when terraform validate accepted an intentionally weakened public-access setting.
- Built a mocked safety gate that enforces all four public-access controls through plan-only tests while creating zero cloud resources.
- Secret Mission: Added a bucket-name policy that rejects demo-bucket when it lacks the required ai-devops- prefix.
Ready to quiz yourself?