Security7 min read

IaC Security

Securing Infrastructure as Code - automated security scanning, policy as code, and GitOps principles for safe and auditable infrastructure.

What is IaC Security?

Infrastructure as Code (IaC) Security encompasses the processes and tools for securing your infrastructure defined in code. When you write infrastructure as code (e.g., Terraform, CloudFormation), you automatically create documentation of your infrastructure and can use tools to validate security configurations before they are ever deployed.

Why is IaC Important for Security?

Imagine your infrastructure was larger - you had development, staging, and production environments, multiple GitLab runners, database servers, and more. If everything were configured manually, problems would arise:

  • Slow configuration: Manually creating resources is time-consuming and difficult to scale
  • Inconsistency: Difficult to ensure the same configuration across environments
  • Missing documentation: Without IaC you have no record of how infrastructure was created
  • Misconfiguration risk: People make mistakes - they forget to close a port, remove an unnecessary user
  • No audit trail: Without version control you don't know who changed what and why
  • Difficult recovery: After an attack you don't know how to restore the original state

Benefits of Infrastructure as Code

  • The code itself serves as documentation of your infrastructure
  • You can use tools to validate security issues
  • Peer reviews and security audits of the code
  • Version control and audit trail of all changes
  • Fast recovery after an incident - just apply the code

GitOps Principles for IaC

GitOps is an approach where a Git repository serves as the single source of truth for your infrastructure. All changes go through pull requests, code reviews, and automated tests.

GitOps Workflow for IaC

1. Code & Version Control

All infrastructure lives in a Git repository

  • Terraform/CloudFormation/Pulumi code
  • Branch protection rules
  • Required reviews before merge

2. Automated Validation

CI pipeline automatically validates changes

  • terraform validate - syntax and structure
  • Trivy/Checkov - security scanning
  • terraform plan - preview of changes

3. Review & Approval

Human review before deployment

  • Code review from colleagues
  • Security review for critical changes
  • Approval gates in the pipeline

4. Automated Deployment

Centralized deployment through the pipeline

  • terraform apply only from the pipeline
  • Remote state management
  • Audit log of all deployments

IaC Security Scanning Tools

Open Source

Trivy (formerly tfsec)

Aqua Security moved tfsec into Trivy and directs its engineering work there. The trivy config command scans Terraform and other IaC formats.

  • Terraform, CloudFormation, Kubernetes, Dockerfile
  • Custom checks in Rego
  • CI/CD integration
  • JSON and SARIF output
Open Source

Checkov

Policy-as-code tool for IaC scanning - Terraform, CloudFormation, Kubernetes, and more.

  • Terraform, OpenTofu, CloudFormation, Kubernetes, Helm and more
  • 1000+ built-in policies
  • Custom policies in Python and YAML
  • Graph-based analysis
Open Source

Conftest (OPA)

Configuration tests written in Rego from Open Policy Agent. Terrascan, which also used OPA policies, was archived by Tenable in November 2025 and is no longer maintained.

  • Rego policies
  • Terraform (HCL2), YAML, JSON, Dockerfile
  • Custom rules for internal standards
  • Easy to run in CI
Terraform Native

terraform validate

Built-in Terraform command for syntax and structure validation.

  • Syntax validation
  • Variable references
  • Module validation
  • Provider requirements

Licensing: in August 2023 HashiCorp moved new Terraform releases from MPL 2.0 to BSL 1.1. The community fork OpenTofu, a Linux Foundation project, stays under MPL 2.0 and shipped its first stable release in January 2024. The scanners above work on the same HCL code; Checkov lists OpenTofu support explicitly.

Practical Example: CI/CD Pipeline for Terraform

The jobs use rules instead of the deprecated only/except and override the entrypoint of the official images. Scanner images are pinned to a fixed version: in March 2026 attackers with stolen credentials published compromised Trivy releases and rewrote trivy-action tags (CVE-2026-33634). Checkov writes its results to the directory given by --output-file-path.

# .gitlab-ci.yml - DevSecOps Pipeline for Terraform stages: - init - test - plan - deploy variables: TF_ROOT: ${CI_PROJECT_DIR}/terraform # Shared base for Terraform jobs. The image's ENTRYPOINT is terraform, # so GitLab has to override it. .terraform: image: name: hashicorp/terraform:1.16 entrypoint: [""] # Terraform initialization init: extends: .terraform stage: init script: - cd $TF_ROOT - terraform init -backend-config="bucket=$TF_STATE_BUCKET" artifacts: paths: - $TF_ROOT/.terraform - $TF_ROOT/.terraform.lock.hcl # Syntax validation validate: extends: .terraform stage: test script: - cd $TF_ROOT - terraform validate allow_failure: false # Security scanning with Trivy (successor of tfsec) # Always pin the image version, ideally to a digest. trivy: stage: test image: name: aquasec/trivy:0.76.0 entrypoint: [""] script: - trivy config --format sarif --output trivy-results.sarif $TF_ROOT artifacts: paths: - trivy-results.sarif when: always allow_failure: true # Does not block pipeline, but reports issues # Security scanning with Checkov checkov: stage: test image: name: bridgecrew/checkov:3.3.29 entrypoint: [""] script: - checkov -d $TF_ROOT --output json --output-file-path checkov-results artifacts: paths: - checkov-results/ when: always allow_failure: true # Terraform plan plan: extends: .terraform stage: plan script: - cd $TF_ROOT - terraform plan -out=tfplan artifacts: paths: - $TF_ROOT/tfplan dependencies: - init # Terraform apply - only on main branch with manual approval deploy: extends: .terraform stage: deploy script: - cd $TF_ROOT - terraform apply -auto-approve tfplan dependencies: - init - plan rules: - if: $CI_COMMIT_BRANCH == "main" when: manual environment: name: production

Example Trivy Output

# trivy config ./terraform (abridged output) main.tf (terraform) =================== Tests: 25 (SUCCESSES: 17, FAILURES: 8) Failures: 8 (UNKNOWN: 0, LOW: 2, MEDIUM: 3, HIGH: 3, CRITICAL: 0) AVD-AWS-0107 (HIGH): Security group rule allows ingress from public internet ---------------------------------------- Opening a security group to the public internet allows any IP address to potentially access resources behind the security group. See https://avd.aquasec.com/misconfig/aws-0107 ---------------------------------------- main.tf:173 AVD-AWS-0178 (MEDIUM): VPC Flow Logs is not enabled for VPC ---------------------------------------- VPC Flow Logs capture information about IP traffic going to and from network interfaces in a VPC. See https://avd.aquasec.com/misconfig/aws-0178 ---------------------------------------- main.tf:45

Secure Terraform Code - Examples

Security Group - Right vs Wrong

# WRONG - overly open security group resource "aws_security_group" "bad_example" { name = "allow-all" ingress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] # All ports open to the world! } } # RIGHT - minimum required permissions resource "aws_security_group" "good_example" { name = "app-security-group" description = "Security group for web application" vpc_id = aws_vpc.main.id # HTTPS only ingress { description = "HTTPS from internet" from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } # Only necessary egress egress { description = "HTTPS outbound" from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "app-sg" Environment = var.environment } }

S3 Bucket - Encryption and Access Control

# Secure S3 bucket resource "aws_s3_bucket" "secure_bucket" { bucket = "my-secure-bucket-${var.environment}" tags = { Name = "secure-bucket" Environment = var.environment } } # Block public access resource "aws_s3_bucket_public_access_block" "secure_bucket" { bucket = aws_s3_bucket.secure_bucket.id block_public_acls = true block_public_policy = true ignore_public_acls = true restrict_public_buckets = true } # Enable encryption resource "aws_s3_bucket_server_side_encryption_configuration" "secure_bucket" { bucket = aws_s3_bucket.secure_bucket.id rule { apply_server_side_encryption_by_default { sse_algorithm = "aws:kms" kms_master_key_id = aws_kms_key.s3_key.arn } bucket_key_enabled = true } } # Enable versioning resource "aws_s3_bucket_versioning" "secure_bucket" { bucket = aws_s3_bucket.secure_bucket.id versioning_configuration { status = "Enabled" } } # Access logging resource "aws_s3_bucket_logging" "secure_bucket" { bucket = aws_s3_bucket.secure_bucket.id target_bucket = aws_s3_bucket.log_bucket.id target_prefix = "s3-access-logs/" }

IaC Security Best Practices - Summary

GitOps & Version Control

  • All infrastructure in a Git repository
  • Branch protection - no direct pushes to main
  • Required code reviews before merge
  • Signed commits for audit trail
  • Semantic versioning for releases

Automated Security Scanning

  • Trivy or Checkov in every PR
  • Pin scanner images and GitHub Actions to a version or commit SHA
  • Block merge on critical findings
  • Generate reports for audit
  • Regularly update rules
  • Custom policies for your requirements

Secure Terraform Practices

What NOT to Do

  • Hardcoded secrets in Terraform code
  • terraform apply from a local machine
  • Ignoring security scan results
  • Wildcard permissions (0.0.0.0/0 on all ports)
  • Disabled encryption for storage
  • Missing tags for audit and cost tracking

Sources

  1. Aqua Security (GitHub): tfsec, tfsec to Trivy Migration (August 2023)
  2. Trivy: Misconfiguration Scanning (November 2025)
  3. Aqua Security (GitHub Advisory): GHSA-69fq-xp46-6x23 / CVE-2026-33634 (March 2026)
  4. Bridgecrew / Prisma Cloud (GitHub): Checkov (October 2025)
  5. Checkov: CLI Command Reference (October 2026)
  6. Tenable (GitHub): Terrascan (archived) (November 2025)
  7. Open Policy Agent: Conftest (September 2026)
  8. HashiCorp: Terraform Backend Type s3 (August 2026)
  9. HashiCorp: HashiCorp adopts Business Source License (August 2023)
  10. OpenTofu: OpenTofu is going GA (January 2024)
  11. GitLab Docs: Deprecated keywords (only / except) (October 2026)
  12. GitLab Docs: Run your CI/CD jobs in Docker containers (October 2026)

Want this handled without building your own DevOps team?

Our four-person DevOps team runs your platform for a fixed monthly price.

Or write to info@devsecops.cz. We process your data under our privacy policy.