Infrastructure as Code Security
Policy In Code, Niet In Hoop
In de cloud is consistentie cruciaal: policy in code, minimale rechten en zicht op drift.
Bij Infrastructure as Code Security is succes afhankelijk van policy-as-code en controles die continu meedraaien in de delivery-keten.
Zo houd je snelheid in de cloud, zonder dat veiligheid afhankelijk wordt van handmatig geluk.
Directe maatregelen (15 minuten)
Waarom dit telt
De kern van Infrastructure as Code Security is risicoreductie in de praktijk. Technische context ondersteunt de maatregelkeuze, maar implementatie en borging staan centraal.
Waarom IaC-beveiliging
Infrastructure as Code heeft de manier waarop we infrastructuur beheren fundamenteel veranderd. In plaats van handmatig servers configureren beschrijven we onze omgeving in declaratieve code. Dat brengt voordelen – versiebeheer, herhaalbaarheid, auditeerbaarheid – maar ook nieuwe risico’s.
Infrastructure drift ontstaat wanneer de werkelijke staat van je infrastructuur afwijkt van wat in je code staat, meestal door handmatige console-wijzigingen buiten het IaC-proces om. Elke handmatige wijziging is een potentiele misconfiguratie die niet door je security-pipeline gaat.
Misconfiguratie is aanvalsvector nummer 1 in de cloud. Niet zero-days, niet geavanceerde exploits – gewoon verkeerd geconfigureerde resources. IaC biedt de mogelijkheid om deze misconfiguraties systematisch te voorkomen, maar alleen als de IaC-code zelf veilig is.
Shift-left security betekent dat je
beveiligingsfouten vindt voordat ze in productie terechtkomen – al in de
IDE, in de pull request, of in de CI/CD pipeline, lang voordat
terraform apply draait.
Terraform-beveiliging
State file bescherming
Het Terraform state file bevat de volledige mapping tussen code en infrastructuur, inclusief gevoelige waarden zoals wachtwoorden en access keys – in plain text.
# Veilige remote backend configuratie met S3 + DynamoDB
terraform {
backend "s3" {
bucket = "company-terraform-state"
key = "production/network/terraform.tfstate"
region = "eu-west-1"
encrypt = true
kms_key_id = "arn:aws:kms:eu-west-1:111111111111:key/abcd-1234"
dynamodb_table = "terraform-state-lock"
}
}
| Maatregel | Implementatie | Prioriteit |
|---|---|---|
| Remote backend | S3, GCS, Azure Blob of Terraform Cloud | Kritiek |
| Encryptie at rest | SSE-KMS (S3), CMEK (GCS) | Kritiek |
| Locking | DynamoDB (AWS), native (GCS, Azure) | Kritiek |
| Toegangscontrole | IAM policies, bucket policies | Kritiek |
| Versioning | S3 bucket versioning inschakelen | Hoog |
| State file niet in git | .gitignore altijd bijwerken |
Kritiek |
Sensitive values en provider credentials
# Markeer variabelen als sensitive
variable "db_password" {
type = string
sensitive = true # Voorkomt weergave in CLI-output, NIET in state file
}
# FOUT: credentials hardcoded
provider "aws" {
access_key = "AKIAIOSFODNN7EXAMPLE" # NOOIT DOEN
secret_key = "wJalrXUtnFEMI/K7MDENG/bPx" # NOOIT DOEN
}
# GOED: credentials uit de omgeving
provider "aws" {
region = "eu-west-1"
# Via AWS_ACCESS_KEY_ID env vars, credential file, instance profile of OIDC
}
Module pinning
# FOUT: zonder versie-pinning (supply chain risico)
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
}
# GOED: specifieke versie + lock file
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.5.1"
# Commit .terraform.lock.hcl in git voor reproduceerbare builds
}
# GOED: Git module met specifieke tag
module "custom" {
source = "git::https://github.com/company/tf-modules.git//vpc?ref=v2.1.0"
}
CloudFormation-beveiliging
NoEcho parameters en Secrets Manager
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
DatabasePassword:
Type: String
NoEcho: true
MinLength: 16
Resources:
# Beter: laat Secrets Manager het wachtwoord genereren
DatabaseSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: /app/production/db-password
GenerateSecretString:
PasswordLength: 32
ExcludeCharacters: '"@/\'Stack policies
{
"Statement": [
{
"Effect": "Allow",
"Action": "Update:Modify",
"Principal": "*",
"Resource": "*"
},
{
"Effect": "Deny",
"Action": ["Update:Replace", "Update:Delete"],
"Principal": "*",
"Resource": "LogicalResourceId/ProductionDatabase"
}
]
}StackSets guardrails: gebruik SERVICE_MANAGED permission
model met auto-deployment en stel
FailureTolerancePercentage=0 in zodat een enkele fout de
uitrol stopt.
Policy as Code
| Framework | Taal | Platform | Integratie |
|---|---|---|---|
| Open Policy Agent (OPA) | Rego | Multi-cloud | Conftest, Terraform, K8s |
| HashiCorp Sentinel | Sentinel | Terraform Enterprise/Cloud | Native |
| AWS SCP | JSON | AWS Organizations | Native |
| Azure Policy | JSON | Azure | Native |
| Google Org Policies | YAML/JSON | GCP | Native |
Rego-voorbeeld: blokkeer publieke S3-buckets
package terraform.s3
deny[msg] {
resource := input.resource.aws_s3_bucket[name]
resource.acl == "public-read"
msg := sprintf("S3 bucket '%s' heeft publieke ACL.", [name])
}
deny[msg] {
resource := input.resource.aws_s3_bucket_public_access_block[name]
resource.block_public_acls != true
msg := sprintf("S3 bucket '%s' blokkeert geen publieke ACLs.", [name])
}
Sentinel-voorbeeld: verplichte encryptie
import "tfplan/v2" as tfplan
mandatory_encryption = rule {
all tfplan.resource_changes as _, rc {
rc.type is "aws_ebs_volume" implies
rc.change.after.encrypted is true
}
}
main = rule { mandatory_encryption }Static analysis tools
| Tool | Ondersteunde talen | CI/CD integratie | Bijzonderheden |
|---|---|---|---|
| tfsec | Terraform (HCL) | GitHub Actions, GitLab CI, Jenkins | Snel, goede Terraform-dekking |
| Checkov | TF, CFN, K8s, ARM, Helm | Alle grote CI/CD platforms | Breedste taalondersteuning |
| KICS | 15+ IaC-talen | GitHub Actions, GitLab CI | Docker, Ansible, Pulumi |
| Terrascan | TF, K8s, Helm, Dockerfiles | GitHub Actions, ArgoCD | OPA-gebaseerd |
| Snyk IaC | TF, CFN, K8s, ARM | Alle grote platforms | Fix-suggesties, IDE-plugins |
Pipeline-integratie (GitHub Actions)
name: IaC Security Scan
on:
pull_request:
paths: ['terraform/**']
jobs:
iac-security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: tfsec scan
uses: aquasecurity/tfsec-action@v1.0.3
with:
working_directory: terraform/
soft_fail: false
- name: Checkov scan
uses: bridgecrewio/checkov-action@v12
with:
directory: terraform/
framework: terraform
soft_fail: falseDrift-detectie
Terraform plan als detectiemiddel
# Detecteer drift zonder te applyen
terraform plan -detailed-exitcode
# Exit codes: 0 = geen drift, 1 = fout, 2 = drift gedetecteerd# GitHub Actions: dagelijkse drift-detectie
name: Drift Detection
on:
schedule:
- cron: '0 6 * * *'
jobs:
detect-drift:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Detect Drift
run: |
terraform init
terraform plan -detailed-exitcode -no-color
working-directory: terraform/production
continue-on-error: true
# Bij exit code 2: notificatie naar Slack/TeamsRemediation
| Strategie | Wanneer gebruiken | Commando |
|---|---|---|
| Importeren | Handmatig aangemaakte resource beheren | terraform import aws_instance.web i-123456 |
| State verwijderen | Resource niet meer via Terraform beheren | terraform state rm aws_instance.legacy |
| Apply forceren | Terraform is de bron van waarheid | terraform apply |
Secrets in IaC
Secrets horen niet in IaC-code of state files. Toch is dit een van de meest voorkomende fouten: een secret verwijderen uit de huidige versie haalt het niet uit de git-historie.
# Optie 1: HashiCorp Vault
data "vault_generic_secret" "database" {
path = "secret/production/database"
}
resource "aws_db_instance" "main" {
username = data.vault_generic_secret.database.data["username"]
password = data.vault_generic_secret.database.data["password"]
}
# Optie 2: AWS Secrets Manager
data "aws_secretsmanager_secret_version" "db_creds" {
secret_id = "production/database/credentials"
}
# Optie 3: Environment variables
# export TF_VAR_db_password="geheim"
# Terraform leest automatisch TF_VAR_* variabelen
Pre-commit hooks en .gitignore
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks# Terraform state en secrets
*.tfstate
*.tfstate.*
*.tfvars
*.tfvars.json
!example.tfvars
.terraform/
crash.log
CI/CD pipeline voor IaC
Een veilige IaC-pipeline volgt het principe: plan in de pull request, apply alleen na goedkeuring.
name: Terraform Pipeline
on:
pull_request:
paths: ['terraform/**']
push:
branches: [main]
paths: ['terraform/**']
permissions:
contents: read
pull-requests: write
id-token: write
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform fmt -check -recursive
- run: terraform init -backend=false && terraform validate
- uses: aquasecurity/tfsec-action@v1.0.3
plan:
needs: validate
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::111111111111:role/TerraformPlanRole
aws-region: eu-west-1
- run: terraform init && terraform plan -no-color
apply:
needs: validate
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production # Vereist handmatige approval
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::111111111111:role/TerraformApplyRole
aws-region: eu-west-1
- run: terraform init && terraform apply -auto-approveBranch protection regels: minimaal 1 goedkeuring, security scan moet
slagen, geen directe pushes naar main.
Veelvoorkomende fouten
| Fout | Risico | Oplossing |
|---|---|---|
| State file in git | Alle secrets zichtbaar in versiebeheer | Remote backend met encryptie |
| Credentials in HCL/YAML | Hardcoded secrets | Environment variables of vault |
acl = "public-read" op S3 |
Data breach | Policy as Code blokkering |
Wildcard IAM policies (*) |
Privilege escalation | Least privilege, tfsec-regels |
| Geen module pinning | Supply chain aanval | Versie-pinning + lock file |
| Lokale state file | Geen locking/encryptie/backup | Remote backend afdwingen |
apply zonder plan review |
Destructieve wijzigingen | Plan-in-PR workflow |
| Geen drift-detectie | Wijzigingen onopgemerkt | Scheduled plan-checks |
.tfvars met secrets in git |
Wachtwoorden in plain text | Vault, SOPS of env vars |
Security groups 0.0.0.0/0 |
Onbeperkte netwerktoegang | Checkov/tfsec + SCP |
| Geen state locking | State corruption | DynamoDB of native locking |
destroy zonder bescherming |
Hele omgeving weg | prevent_destroy + stack policies |
Checklist
| # | Maatregel | Prioriteit |
|---|---|---|
| 1 | Remote backend met encryptie voor state files | Kritiek |
| 2 | Geen credentials in IaC-code | Kritiek |
| 3 | State locking ingeschakeld | Kritiek |
| 4 | .gitignore voor state files en
.tfvars |
Kritiek |
| 5 | Pre-commit hooks voor secret-detectie (gitleaks) | Hoog |
| 6 | Static analysis in CI/CD pipeline (tfsec/Checkov) | Hoog |
| 7 | Plan-in-PR, apply-na-approval workflow | Hoog |
| 8 | Module versie-pinning met lock file | Hoog |
| 9 | OIDC federation voor CI/CD credentials | Hoog |
| 10 | Branch protection op IaC-repositories | Hoog |
| 11 | Policy as Code (OPA/Sentinel/SCP) | Hoog |
| 12 | Externe secret management (Vault/Secrets Manager) | Hoog |
| 13 | Dagelijkse drift-detectie | Middel |
| 14 | sensitive = true op gevoelige variabelen en
outputs |
Middel |
| 15 | prevent_destroy op kritieke resources |
Middel |
| 16 | Stack policies voor CloudFormation | Middel |
| 17 | State file access auditing | Middel |
| 18 | Terraform versie-pinning (required_version) |
Middel |
We hebben onze infrastructuur geautomatiseerd. Fantastisch. We
hebben ook onze misconfiguraties geautomatiseerd, maar dan op schaal en
met versiebeheer zodat we ze voor altijd kunnen bewonderen. Het
Terraform state file is een meesterwerk van ironie: een bestand dat
bedoeld is om je infrastructuur te beheren, maar dat tegelijkertijd elke
secret in plain text opslaat alsof het 1999 is. Vroeger kon een fout op
een server hooguit die ene server slopen. Nu kan een enkel
git push naar main – gevolgd door een auto-apply pipeline
die iemand “voor het gemak” heeft ingericht – een volledig AWS-account
met de grond gelijkmaken. We noemen dat vooruitgang. De state file, dat
heilige document, wordt door de helft van alle teams nog steeds lokaal
opgeslagen of per ongeluk in git gecommit, compleet met
database-wachtwoorden en API-keys. En als je dan vraagt waarom er geen
encryptie op zit, krijg je te horen: “Dat staat op de backlog.” Net als
de rest van de beveiliging.
Samenvatting
Infrastructure as Code is een krachtig middel om infrastructuur beheersbaar, herhaalbaar en auditeerbaar te maken – maar alleen als de code zelf veilig is. De kern van IaC-beveiliging rust op vier pijlers: bescherm je state files alsof het kroonjuwelen zijn (remote backend, encryptie, locking), houd secrets uit je code en versiebeheer (vault, environment variables, SOPS), scan je configuraties automatisch op misconfiguraties (tfsec, Checkov, Policy as Code), en richt een veilige pipeline in met plan-review en approval-gates. Drift-detectie voorkomt dat handmatige wijzigingen je beveiligingsbaseline ondermijnen. Zonder deze maatregelen automatiseer je niet alleen je infrastructuur, maar ook je kwetsbaarheden.
In het volgende hoofdstuk behandelen we secrets management: hoe je gevoelige waarden veilig opslaat, roteert en distribueert over je cloud-omgeving zonder dat ze in code, logs of state files terechtkomen.
Verder lezen in de kennisbank
Deze artikelen in het portaal geven je meer achtergrond en praktische context:
- De cloud — andermans computer, jouw verantwoordelijkheid
- Containers en Docker — wat het is en waarom je het moet beveiligen
- Encryptie — de kunst van het onleesbaar maken
- Least Privilege — geef mensen alleen wat ze nodig hebben
Je hebt een account nodig om de kennisbank te openen. Inloggen of registreren.
Gerelateerde securitymaatregelen
Deze artikelen bieden aanvullende context en verdieping: