IAC-SECURITY
Authorized use only. Offensive reference for systems you own or are explicitly permitted to test. You are responsible for staying within the law.
Security scanning, hardening, and best practices for Terraform, Ansible, CloudFormation, Kubernetes, and policy-as-code frameworks.
TERRAFORM SECURITY#
Common Terraform Misconfigurations:
1. Public S3 buckets:
# BAD
resource "aws_s3_bucket_acl" "example" {
acl = "public-read"
}
# GOOD
resource "aws_s3_bucket_public_access_block" "example" {
bucket = aws_s3_bucket.example.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
2. Open security groups:
# BAD
ingress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
# GOOD
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
}
3. Unencrypted resources:
# BAD - no encryption
resource "aws_ebs_volume" "example" {
availability_zone = "us-east-1a"
size = 40
}
# GOOD
resource "aws_ebs_volume" "example" {
availability_zone = "us-east-1a"
size = 40
encrypted = true
kms_key_id = aws_kms_key.example.arn
}
4. Hardcoded secrets in .tf files:
# BAD
password = "SuperSecret123"
# GOOD
password = var.db_password # from tfvars or vault
5. Missing logging:
# Ensure CloudTrail, VPC flow logs, S3 access logging enabled
tfsec (Aqua Security):
Installation:
brew install tfsec
# or
go install github.com/aquasecurity/tfsec/cmd/tfsec@latest
Usage:
tfsec . # scan current directory
tfsec . --format json # JSON output
tfsec . --severity HIGH,CRITICAL # filter by severity
tfsec . --exclude-downloaded-modules # skip modules
tfsec . --config-file tfsec.yml # custom config
Ignore specific findings:
resource "aws_s3_bucket" "example" {
#tfsec:ignore:aws-s3-enable-versioning
bucket = "my-bucket"
}
CI/CD (GitHub Actions):
- name: tfsec
uses: aquasecurity/tfsec-action@v1.0.3
with:
soft_fail: false
Checkov (Bridgecrew/Prisma Cloud):
Installation:
pip install checkov
Usage:
checkov -d . # scan directory
checkov -f main.tf # scan specific file
checkov -d . --framework terraform # Terraform only
checkov -d . --check CKV_AWS_18 # specific check
checkov -d . --skip-check CKV_AWS_18 # skip check
checkov -d . -o json # JSON output
checkov -d . --compact # compact output
Supported frameworks:
terraform, cloudformation, kubernetes, arm, helm,
dockerfile, serverless, bicep, openapi, github_actions
CI/CD (GitHub Actions):
- name: Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: terraform/
framework: terraform
Terraform state security:
- Store state in encrypted remote backend (S3 + DynamoDB)
- Enable state locking
- Restrict access to state files (contain secrets)
- Use terraform plan output review before apply
- Enable audit logging on state backend
# Secure S3 backend
terraform {
backend "s3" {
bucket = "terraform-state-prod"
key = "infra/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
kms_key_id = "arn:aws:kms:..."
}
}
ANSIBLE HARDENING#
Security best practices:
1. Use Ansible Vault for secrets:
ansible-vault create secrets.yml
ansible-vault edit secrets.yml
ansible-vault encrypt_string 'mypassword' --name 'db_password'
ansible-playbook site.yml --ask-vault-pass
2. Least privilege:
- Use become only when needed
- Don't run everything as root
- Use become_method: sudo with specific commands
3. Avoid shell/command modules when possible:
# BAD
- shell: "useradd -m {{ username }}"
# GOOD
- user:
name: "{{ username }}"
state: present
create_home: yes
4. Validate input:
- assert:
that:
- username is match("^[a-zA-Z0-9_]+$")
- port | int > 0
- port | int < 65536
5. Security-focused roles:
- dev-sec.os-hardening (CIS benchmarks)
- dev-sec.ssh-hardening
- geerlingguy.firewall
Ansible-lint security rules:
pip install ansible-lint
ansible-lint playbook.yml
# Checks for: command/shell usage, become misuse,
# hardcoded passwords, insecure permissions
CLOUDFORMATION SECURITY#
Common misconfigurations:
1. IAM policies with wildcards:
# BAD
Statement:
- Effect: Allow
Action: "*"
Resource: "*"
# GOOD
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
Resource: "arn:aws:s3:::my-bucket/*"
2. Unencrypted resources:
# Ensure encryption on RDS, EBS, S3, SQS, SNS, etc.
Properties:
StorageEncrypted: true
KmsKeyId: !Ref MyKmsKey
3. Public access:
# Check SecurityGroupIngress for 0.0.0.0/0
# Check S3 bucket policies
# Check RDS PubliclyAccessible: false
Scanning tools:
cfn-lint:
pip install cfn-lint
cfn-lint template.yaml
cfn-nag:
gem install cfn-nag
cfn_nag_scan --input-path template.yaml
checkov:
checkov -f template.yaml --framework cloudformation
KUBERNETES MANIFEST SECURITY#
Common misconfigurations:
1. Running as root:
# BAD
securityContext: {}
# GOOD
securityContext:
runAsNonRoot: true
runAsUser: 1000
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
2. Missing resource limits:
# GOOD
resources:
limits:
memory: "256Mi"
cpu: "500m"
requests:
memory: "128Mi"
cpu: "250m"
3. Missing network policies:
# Default deny all ingress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
4. Using latest tag:
# BAD
image: nginx:latest
# GOOD
image: nginx:1.25.3@sha256:abc123...
5. Secrets in environment variables:
# BAD
env:
- name: DB_PASSWORD
value: "plaintext_password"
# GOOD - use Secrets with mounted volumes
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
kubesec:
# Online
curl -sSX POST --data-binary @deployment.yaml https://v2.kubesec.io/scan
# Local
docker run -i kubesec/kubesec:v2 scan /dev/stdin < deployment.yaml
# Scores security posture of manifests (0-100+)
kube-bench (CIS Benchmarks):
docker run --pid=host -v /etc:/etc:ro -v /var:/var:ro \
aquasec/kube-bench:latest run
kube-hunter (penetration testing):
docker run -it --rm aquasec/kube-hunter --remote <cluster_ip>
Trivy for Kubernetes:
trivy k8s --report=summary cluster # scan running cluster
trivy config ./k8s-manifests/ # scan manifest files
COMMON MISCONFIGURATIONS CHECKLIST#
AWS: [ ] S3 buckets public access blocked [ ] S3 bucket versioning enabled [ ] S3 bucket encryption enabled (SSE-S3 or SSE-KMS) [ ] EBS volumes encrypted [ ] RDS instances encrypted and not publicly accessible [ ] RDS automated backups enabled [ ] Security groups: no 0.0.0.0/0 on SSH (22) or RDP (3389) [ ] CloudTrail enabled in all regions [ ] VPC flow logs enabled [ ] IAM: no wildcard (*) policies [ ] IAM: MFA enforced for console access [ ] IAM: access keys rotated regularly [ ] ELB/ALB using TLS 1.2+ [ ] Lambda functions not using admin IAM roles [ ] SNS/SQS encrypted [ ] Secrets in Secrets Manager or Parameter Store (not env vars) Azure: [ ] Storage accounts: no public blob access [ ] Storage accounts: encryption enabled [ ] NSGs: no open management ports [ ] SQL Server: auditing enabled [ ] Key Vault: soft delete enabled [ ] Activity log alerts configured GCP: [ ] Cloud Storage: no allUsers/allAuthenticatedUsers [ ] Compute: no default service account [ ] VPC: firewall rules reviewed [ ] Cloud SQL: no public IP [ ] Audit logging enabled
POLICY-AS-CODE#
Open Policy Agent (OPA):
Concept: General-purpose policy engine using Rego language.
Example Rego policy (deny public S3):
package terraform.aws
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket_acl"
resource.change.after.acl == "public-read"
msg := sprintf("S3 bucket '%s' must not be public", [resource.name])
}
Usage with conftest:
pip install conftest
conftest test main.tf -p policy/
conftest test deployment.yaml -p policy/
Kubernetes admission control (OPA Gatekeeper):
# Install Gatekeeper
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.14/deploy/gatekeeper.yaml
# Create constraint template
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: string
HashiCorp Sentinel:
Concept: Policy-as-code for HashiCorp products (Terraform Cloud/Enterprise)
Example policy:
import "tfplan/v2" as tfplan
main = rule {
all tfplan.resource_changes as _, rc {
rc.type is "aws_security_group_rule" implies
rc.change.after.cidr_blocks not contains "0.0.0.0/0"
}
}
Enforcement levels:
- advisory: warn but allow
- soft-mandatory: can be overridden
- hard-mandatory: cannot be overridden
Kyverno (Kubernetes-native):
# Simpler alternative to OPA for Kubernetes
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-latest-tag
spec:
validationFailureAction: Enforce
rules:
- name: require-image-tag
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Using 'latest' tag is not allowed."
pattern:
spec:
containers:
- image: "!*:latest"
SCANNING PIPELINE SETUP#
Recommended IaC security pipeline:
1. Pre-commit:
- tflint (Terraform linting)
- terraform fmt -check
- checkov (quick scan)
2. CI/CD Pipeline:
- terraform plan (save plan file)
- tfsec / checkov on plan file
- conftest with OPA policies
- Trivy config scan
- Block deploy on critical findings
3. Runtime:
- AWS Config Rules / Azure Policy
- Cloud Custodian (automated remediation)
- Prisma Cloud / Wiz (CSPM)
Example pre-commit config (.pre-commit-config.yaml):
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.86.0
hooks:
- id: terraform_fmt
- id: terraform_validate
- id: terraform_tflint
- id: terraform_tfsec
- id: terraform_checkov
GitHub Actions full pipeline:
jobs:
iac-security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: tfsec
uses: aquasecurity/tfsec-action@v1.0.3
- name: Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: terraform/
- name: Trivy Config
uses: aquasecurity/trivy-action@master
with:
scan-type: 'config'
scan-ref: '.'
REFERENCES#
- tfsec: https://aquasecurity.github.io/tfsec/ - Checkov: https://www.checkov.io/ - OPA: https://www.openpolicyagent.org/ - Kyverno: https://kyverno.io/ - CIS Benchmarks: https://www.cisecurity.org/benchmark - Trivy: https://aquasecurity.github.io/trivy/ - Cloud Custodian: https://cloudcustodian.io/