jan-karel.nl
Home / Securitymaatregelen / Cloudbeveiliging / Infrastructure as Code Security

Infrastructure as Code Security

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])
}
# Evalueer met Conftest
conftest test --policy policy/ tfplan.json

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: false

Drift-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/Teams

Remediation

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-approve

Branch 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.

Op de hoogte blijven?

Ontvang maandelijks cybersecurity-inzichten in je inbox.

← Cloudbeveiliging ← Home