-
Notifications
You must be signed in to change notification settings - Fork 4
Infrastructure as Code Security
Infrastructure as Code (IaC) enables teams to define and provision infrastructure through machine-readable definition files rather than manual processes. While IaC offers numerous benefits for consistency and scalability, it also introduces unique security challenges. This guide covers comprehensive security practices for IaC implementations.
- Consistency - Every deployment follows the same secure configuration
- Version control - Track changes and maintain audit history
- Automated testing - Verify security before deployment
- Reduced human error - Eliminate manual misconfigurations
- Compliance as Code - Embed compliance requirements in templates
- Insecure defaults - Templates with weak security configurations
- Secrets in code - Hardcoded credentials and sensitive data
- Excessive permissions - Overprivileged resources and service accounts
- Misconfiguration - Security options disabled or improperly set
- Unpatched components - Outdated base images or software versions
- Insufficient monitoring - Lack of security logging and alerting
Terraform is a widely-used IaC tool that supports multiple cloud providers and services.
- Structure and Organization
# Modular structure for better security management
├── environments/
│ ├── production/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── terraform.tfvars
│ └── staging/
│ ├── main.tf
│ ├── variables.tf
│ └── terraform.tfvars
├── modules/
│ ├── networking/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ └── security/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── shared/
└── iam/
├── roles.tf
└── policies.tf
- Secure State Management
# Use encrypted remote state with access controls
terraform {
backend "s3" {
bucket = "terraform-state-secure"
key = "prod/terraform.tfstate"
region = "us-west-2"
encrypt = true
dynamodb_table = "terraform-locks"
# Use IAM roles rather than static credentials
role_arn = "arn:aws:iam::123456789012:role/TerraformStateManager"
}
}
- Secure Variable Handling
# Define sensitive variables
variable "database_password" {
description = "Password for database access"
type = string
sensitive = true # Marks as sensitive in logs and outputs
}
# Use environment variables for secrets (don't hardcode)
# TF_VAR_database_password=securepassword terraform apply
- Provider Configuration Security
# Provider configuration with secure settings
provider "aws" {
region = "us-west-2"
# Enforce minimum TLS version
default_tags {
tags = {
Environment = "Production"
ManagedBy = "Terraform"
}
}
# Use assume role instead of static credentials
assume_role {
role_arn = "arn:aws:iam::123456789012:role/TerraformDeploymentRole"
}
}
# Version constraints for providers
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0" # Only accept 4.x versions
}
}
}
- Secure S3 Bucket Configuration
resource "aws_s3_bucket" "data_bucket" {
bucket = "my-secure-data-bucket"
# Explicitly disable public access
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Enable bucket encryption
resource "aws_s3_bucket_server_side_encryption_configuration" "bucket_encryption" {
bucket = aws_s3_bucket.data_bucket.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
# Enable versioning
resource "aws_s3_bucket_versioning" "bucket_versioning" {
bucket = aws_s3_bucket.data_bucket.id
versioning_configuration {
status = "Enabled"
}
}
# Enable access logging
resource "aws_s3_bucket_logging" "bucket_logging" {
bucket = aws_s3_bucket.data_bucket.id
target_bucket = aws_s3_bucket.log_bucket.id
target_prefix = "log/data-bucket/"
}
- Secure EC2 Instance Configuration
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0" # Use vetted, up-to-date AMIs
instance_type = "t3.micro"
# Use secure networking
subnet_id = aws_subnet.private_subnet.id
# Enable detailed monitoring
monitoring = true
# Secure IAM instance profile (least privilege)
iam_instance_profile = aws_iam_instance_profile.web_server_profile.name
root_block_device {
encrypted = true # Enable EBS encryption
volume_size = 20
}
metadata_options {
http_tokens = "required" # Require IMDSv2
http_endpoint = "enabled"
}
# Use security groups with least privilege
vpc_security_group_ids = [aws_security_group.web_server_sg.id]
# User data for hardening
user_data = <<-EOF
#!/bin/bash
# Update system
yum update -y
# Set secure SSH configuration
sed -i 's/#PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
systemctl restart sshd
EOF
tags = {
Name = "SecureWebServer"
}
}
# Restrictive security group
resource "aws_security_group" "web_server_sg" {
name = "web-server-sg"
description = "Security group for web servers"
vpc_id = aws_vpc.main.id
# Allow HTTP/HTTPS only
ingress {
description = "HTTPS"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# Restrict SSH access to VPN IP range
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["10.0.0.0/16"] # Corporate VPN range
}
# Allow all outbound traffic
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
- Secure IAM Configuration
# Create role with least privilege
resource "aws_iam_role" "app_role" {
name = "app-service-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
},
]
})
}
# Custom policy with minimal permissions
resource "aws_iam_policy" "app_policy" {
name = "app-service-policy"
description = "Minimal permissions for application"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"s3:GetObject",
"s3:ListBucket",
]
Effect = "Allow"
Resource = [
"${aws_s3_bucket.data_bucket.arn}",
"${aws_s3_bucket.data_bucket.arn}/*",
]
},
{
Action = [
"cloudwatch:PutMetricData",
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Effect = "Allow"
Resource = "*"
}
]
})
}
# Attach policy to role
resource "aws_iam_role_policy_attachment" "app_attach" {
role = aws_iam_role.app_role.name
policy_arn = aws_iam_policy.app_policy.arn
}
# Create instance profile
resource "aws_iam_instance_profile" "web_server_profile" {
name = "web-server-profile"
role = aws_iam_role.app_role.name
}
AWS CloudFormation allows you to define your AWS infrastructure as code.
- Template Structure and Organization
# secure-web-app.yaml - Example of well-structured template
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Secure web application stack with VPC, security groups, and web servers'
# Parameter section with proper constraints
Parameters:
EnvironmentType:
Description: Environment type
Type: String
Default: Development
AllowedValues:
- Development
- Production
ConstraintDescription: Must be Development or Production
DBPassword:
Description: Database admin password
Type: String
NoEcho: true # Hide sensitive parameter
MinLength: 12
MaxLength: 41
ConstraintDescription: Password must be between 12 and 41 characters
# Organize resources by type for clarity and management
Resources:
# Network resources
VPC:
Type: AWS::EC2::VPC
Properties:
# ...
# Security resources
WebServerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
# ...
# Instance resources
WebServer:
Type: AWS::EC2::Instance
Properties:
# ...
# Output only necessary information
Outputs:
WebsiteURL:
Description: URL for the web application
Value: !GetAtt [LoadBalancer, DNSName]
- Secure VPC and Network Configuration
# Secure VPC configuration
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: SecureVPC
# Public and private subnets
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: !Select [0, !GetAZs '']
MapPublicIpOnLaunch: false # Don't auto-assign public IPs
Tags:
- Key: Name
Value: PublicSubnet1
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.2.0/24
AvailabilityZone: !Select [0, !GetAZs '']
Tags:
- Key: Name
Value: PrivateSubnet1
# Network ACL with restrictive rules
PrivateSubnetNetworkAcl:
Type: AWS::EC2::NetworkAcl
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: PrivateSubnetNetworkAcl
# Inbound rule - Allow only HTTP/HTTPS from public subnet
InboundHTTPSNetworkAclEntry:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref PrivateSubnetNetworkAcl
RuleNumber: 100
Protocol: 6 # TCP
RuleAction: allow
Egress: false
CidrBlock: 10.0.1.0/24
PortRange:
From: 443
To: 443
- Secure Parameter Handling
# Use SecureString parameter store for sensitive data
DBPasswordParameter:
Type: AWS::SSM::Parameter
Properties:
Name: /app/database/password
Type: SecureString
Value: !Ref DBPassword
Description: Database password stored securely
Tier: Standard
# Reference stored parameter instead of using direct value
RDSDBInstance:
Type: AWS::RDS::DBInstance
Properties:
DBName: !Ref DatabaseName
Engine: mysql
MasterUsername: admin
MasterUserPassword: !Sub '{{resolve:ssm-secure:/app/database/password}}'
StorageEncrypted: true # Enable encryption
BackupRetentionPeriod: 7
MultiAZ: true
CloudFormation Guard lets you define policy rules for CloudFormation templates.
# cfn-guard rule file (security-rules.guard)
# Ensure S3 buckets have encryption enabled
rule s3_bucket_encryption_enabled {
AWS::S3::Bucket {
# Check for encryption configuration
BucketEncryption exists
BucketEncryption is_struct
BucketEncryption {
ServerSideEncryptionConfiguration exists
ServerSideEncryptionConfiguration is_list
ServerSideEncryptionConfiguration[*] {
ServerSideEncryptionByDefault exists
}
}
}
}
# Ensure RDS instances are encrypted
rule rds_storage_encrypted {
AWS::RDS::DBInstance {
StorageEncrypted exists
StorageEncrypted == true
}
}
# Ensure security groups don't allow unrestricted access (0.0.0.0/0) to risky ports
rule no_unrestricted_ssh_access {
AWS::EC2::SecurityGroup {
SecurityGroupIngress[*] {
CidrIp != "0.0.0.0/0" or FromPort != 22
}
}
}
# Ensure IAM policies don't grant admin (*) permissions
rule no_iam_wildcards {
AWS::IAM::Policy {
PolicyDocument {
Statement[*] {
Effect == "Allow" {
Action != "*"
Resource != "*"
}
}
}
}
}
Kubernetes manifests are YAML or JSON files that describe the desired state of Kubernetes resources.
- Secure Pod Configuration
# secure-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
labels:
app: secure-app
spec:
# Run as non-root user
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
containers:
- name: secure-container
image: myapp:1.0.0 # Specify exact version, not 'latest'
# Set container security context
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
# Set resource limits
resources:
limits:
cpu: "500m"
memory: "512Mi"
requests:
cpu: "250m"
memory: "256Mi"
# Liveness and readiness probes
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
# Secure environment variables
env:
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: app-config
key: environment
# Use secrets for sensitive data
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: db-password
# Mount volumes securely
volumeMounts:
- name: config-volume
mountPath: /config
readOnly: true
# Volumes
volumes:
- name: config-volume
configMap:
name: app-config
# Restrict which nodes can run this pod
nodeSelector:
environment: production
- Network Policies
# Restrictive network policy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-allow-policy
namespace: production
spec:
# Select pods this policy applies to
podSelector:
matchLabels:
app: api-service
# Default deny all ingress and egress
policyTypes:
- Ingress
- Egress
# Allow specific ingress
ingress:
- from:
# Only allow traffic from frontend pods
- podSelector:
matchLabels:
app: frontend
# On specific ports
ports:
- protocol: TCP
port: 443
# Allow specific egress
egress:
- to:
# Only allow traffic to database pods
- podSelector:
matchLabels:
app: database
# On specific ports
ports:
- protocol: TCP
port: 5432
# Allow DNS resolution
- to:
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
- Role-Based Access Control (RBAC)
# Create a limited role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: app-reader
rules:
- apiGroups: [""]
resources: ["pods", "services"]
verbs: ["get", "list", "watch"]
---
# Create a service account
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-service
namespace: production
---
# Bind the role to the service account
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: app-service-binding
namespace: production
subjects:
- kind: ServiceAccount
name: app-service
namespace: production
roleRef:
kind: Role
name: app-reader
apiGroup: rbac.authorization.k8s.io
- Pod Security Standards
# Pod Security Policy (used via admission controllers)
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted
spec:
privileged: false
allowPrivilegeEscalation: false
requiredDropCapabilities:
- ALL
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
- 'persistentVolumeClaim'
hostNetwork: false
hostIPC: false
hostPID: false
runAsUser:
rule: 'MustRunAsNonRoot'
seLinux:
rule: 'RunAsAny'
supplementalGroups:
rule: 'MustRunAs'
ranges:
- min: 1
max: 65535
fsGroup:
rule: 'MustRunAs'
ranges:
- min: 1
max: 65535
readOnlyRootFilesystem: true
Checkov is an open-source static code analysis tool for scanning IaC files.
# Install Checkov
pip install checkov
# Scan a Terraform directory
checkov -d /path/to/terraform/files
# Scan a specific file
checkov -f main.tf
# Generate report in various formats
checkov -d /path/to/terraform/files --output json --output-file report.json
# Scan with specific checks enabled/disabled
checkov -d /path/to/terraform/files --check CKV_AWS_1,CKV_AWS_2
# Integrate with CI/CD (GitHub Actions example)
GitHub Actions workflow for Checkov scanning:
name: Terraform Security Scan
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install Checkov
run: pip install checkov
- name: Run Checkov
run: checkov -d . --output github_actions
- name: Upload scan results
if: success() || failure()
uses: actions/upload-artifact@v2
with:
name: checkov-scan-results
path: checkov-report.json
cfn-nag is a linting tool for CloudFormation templates.
# Install cfn-nag
gem install cfn-nag
# Scan a template
cfn_nag_scan --input-path template.yaml
# Scan a directory of templates
cfn_nag_scan --input-path /path/to/templates
# Output results in various formats
cfn_nag_scan --input-path template.yaml --output-format json --output-file results.json
# Create custom rules
# rules/my_rule.rb
require 'cfn-nag/violation'
require 'cfn-nag/custom_rules/base'
class MySecurityGroupRule < BaseRule
def rule_text
'Security group should not allow unrestricted access to ports'
end
def rule_type
Violation::WARNING
end
def rule_id
'W999'
end
def audit_impl(cfn_model)
violations = []
cfn_model.resources_by_type('AWS::EC2::SecurityGroup').each do |sg|
sg.ingress.each do |ingress|
if ingress.cidrIp == '0.0.0.0/0' && (ingress.fromPort == 22 || ingress.fromPort == 3389)
violations << Violation.new(id: rule_id,
type: rule_type,
message: rule_text,
logical_resource_ids: [sg.logical_resource_id])
end
end
end
violations
end
end
Kubesec is a security risk analysis tool for Kubernetes resources.
# Install Kubesec
docker pull kubesec/kubesec
# Scan a manifest
kubesec scan deployment.yaml
# Scan multiple manifests
kubesec scan pod.yaml deployment.yaml
# Integration with CI (example pipeline command)
docker run -i kubesec/kubesec:v2 scan /dev/stdin < deployment.yaml > results.json
name: IaC Security Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
terraform-security:
name: Terraform Security Scan
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Terraform
uses: hashicorp/setup-terraform@v1
with:
terraform_version: 1.0.0
- name: Terraform Init
run: terraform init -backend=false
working-directory: ./terraform
- name: Terraform Validate
run: terraform validate
working-directory: ./terraform
- name: Run tfsec
uses: tfsec/tfsec-action@v1.0.0
with:
working_directory: ./terraform
- name: Run Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: ./terraform
framework: terraform
output_format: github_failed_only
kubernetes-security:
name: Kubernetes Security Scan
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Lint Kubernetes manifests
uses: azure/k8s-lint@v1
with:
manifests: |
kubernetes/*.yaml
- name: Kubesec Scan
uses: controlplaneio/kubesec-action@master
with:
input: kubernetes/deployment.yaml
format: json
output: kubesec-results.json
- name: Run Kube-Score
run: |
curl -L -o kube-score https://github.com/zegl/kube-score/releases/download/v1.10.0/kube-score_1.10.0_linux_amd64
chmod +x kube-score
./kube-score score kubernetes/*.yaml > kube-score-results.txt
- name: Upload scan results
uses: actions/upload-artifact@v2
with:
name: security-scan-results
path: |
kubesec-results.json
kube-score-results.txt
cloudformation-security:
name: CloudFormation Security Scan
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
- name: Install cfn-nag
run: gem install cfn-nag
- name: Run cfn-nag
run: |
cfn_nag_scan --input-path cloudformation/ --template-pattern '.*\.yaml' --output-format json > cfn-nag-results.json
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install CloudFormation Guard
run: pip install cloudformation-guard
- name: Run CloudFormation Guard
run: |
cfn-guard check -r guard-rules/*.guard -t cloudformation/*.yaml > cfn-guard-results.txt
- name: Upload scan results
uses: actions/upload-artifact@v2
with:
name: cfn-security-results
path: |
cfn-nag-results.json
cfn-guard-results.txt
stages:
- validate
- scan
- deploy
variables:
TERRAFORM_VERSION: "1.0.0"
terraform-validate:
stage: validate
image: hashicorp/terraform:$TERRAFORM_VERSION
script:
- cd terraform/
- terraform init -backend=false
- terraform validate
only:
changes:
- terraform/**/*
terraform-scan:
stage: scan
image: bridgecrew/checkov:latest
script:
- cd terraform/
- checkov -d . --output cli --quiet
- checkov -d . --output json > /tmp/checkov-report.json
artifacts:
paths:
- /tmp/checkov-report.json
when: always
only:
changes:
- terraform/**/*
kubernetes-scan:
stage: scan
image: kubesec/kubesec:v2
script:
- mkdir -p reports
- for file in kubernetes/*.yaml; do
kubesec scan $file > reports/$(basename $file).json;
done
artifacts:
paths:
- reports/
when: always
only:
changes:
- kubernetes/**/*
cloudformation-scan:
stage: scan
image: ruby:2.7
before_script:
- gem install cfn-nag
script:
- mkdir -p reports
- cfn_nag_scan --input-path cloudformation/ --output-format json > reports/cfn-nag-report.json
artifacts:
paths:
- reports/
when: always
only:
changes:
- cloudformation/**/*
security-report:
stage: deploy
image: python:3.8
script:
- pip install jinja2 markdown
- python scripts/generate_security_report.py
- echo "Security report generated"
artifacts:
paths:
- security-report.html
needs:
- terraform-scan
- kubernetes-scan
- cloudformation-scan
only:
- main
- merge_requests
Rego is a policy language used by OPA to define policies for IaC validation.
# terraform_aws_security.rego
package terraform.aws.security
# Deny S3 buckets without encryption
deny[msg] {
resource := input.resource.aws_s3_bucket[name]
not resource.server_side_encryption_configuration
msg := sprintf("S3 bucket '%v' does not have encryption enabled", [name])
}
# Deny security groups with unrestricted SSH access
deny[msg] {
sg := input.resource.aws_security_group[name]
ingress := sg.ingress[_]
ingress.from_port <= 22
ingress.to_port >= 22
ingress.cidr_blocks[_] == "0.0.0.0/0"
msg := sprintf("Security group '%v' allows SSH from the internet", [name])
}
# Deny IAM policies with full admin access
deny[msg] {
policy := input.resource.aws_iam_policy[name]
statement := policy.policy[_].Statement[_]
statement.Effect == "Allow"
statement.Action == "*"
statement.Resource == "*"
msg := sprintf("IAM policy '%v' grants full admin privileges", [name])
}
# Require encryption for EBS volumes
deny[msg] {
volume := input.resource.aws_ebs_volume[name]
not volume.encrypted
msg := sprintf("EBS volume '%v' is not encrypted", [name])
}
# Ensure RDS instances are encrypted
deny[msg] {
rds := input.resource.aws_db_instance[name]
not rds.storage_encrypted
msg := sprintf("RDS instance '%v' is not encrypted", [name])
}
Conftest is a utility for writing tests against structured configuration data.
# Install Conftest
brew install conftest
# Run policies against Terraform plan
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
conftest test tfplan.json -p policies/
# Run policies against Kubernetes manifests
conftest test kubernetes/ -p policies/
# Run policies against CloudFormation templates
conftest test cloudformation/ -p policies/
Example Conftest policy for Kubernetes:
# kubernetes_security.rego
package main
# Deny privileged containers
deny[msg] {
input.kind == "Pod"
container := input.spec.containers[_]
container.securityContext.privileged == true
msg := sprintf("Container '%v' is running as privileged", [container.name])
}
# Require resource limits
deny[msg] {
input.kind == "Deployment"
container := input.spec.template.spec.containers[_]
not container.resources.limits
msg := sprintf("Container '%v' does not have resource limits", [container.name])
}
# Require network policies in namespaces
warn[msg] {
input.kind == "Namespace"
name := input.metadata.name
not data.network_policies[name]
msg := sprintf("Namespace '%v' does not have a NetworkPolicy", [name])
}
# Deny use of latest tag
deny[msg] {
input.kind == "Deployment"
container := input.spec.template.spec.containers[_]
endswith(container.image, ":latest")
msg := sprintf("Container '%v' uses the 'latest' tag which is prohibited", [container.name])
}
#!/bin/bash
# .git/hooks/pre-commit
# Make this file executable with: chmod +x .git/hooks/pre-commit
echo "Running security checks on staged IaC files..."
# Check for Terraform files
if git diff --cached --name-only | grep -q "\.tf$"; then
echo "Checking Terraform files..."
# Run terraform validate
terraform validate || { echo "Terraform validation failed!"; exit 1; }
# Run tfsec
tfsec . || { echo "tfsec found security issues!"; exit 1; }
fi
# Check for Kubernetes manifests
if git diff --cached --name-only | grep -q "\.yaml$"; then
echo "Checking Kubernetes manifests..."
# Run kubeval
for file in $(git diff --cached --name-only | grep "\.yaml$"); do
kubeval "$file" || { echo "Kubernetes validation failed for $file!"; exit 1; }
done
# Run kubesec
for file in $(git diff --cached --name-only | grep "\.yaml$"); do
kubesec scan "$file" > /dev/null || { echo "Kubesec found security issues in $file!"; exit 1; }
done
fi
# Check for CloudFormation templates
if git diff --cached --name-only | grep -q "\.(yaml|json)$"; then
echo "Checking CloudFormation templates..."
for file in $(git diff --cached --name-only | grep "\.(yaml|json)$"); do
if grep -q "AWSTemplateFormatVersion" "$file"; then
cfn-lint "$file" || { echo "CloudFormation linting failed for $file!"; exit 1; }
cfn_nag_scan --input-path "$file" || { echo "cfn-nag found security issues in $file!"; exit 1; }
fi
done
fi
# Check for secrets
if git diff --cached --name-only | xargs grep -l ""; then
echo "Checking for hardcoded secrets..."
git diff --cached --name-only | xargs grep -l "AWS_SECRET_ACCESS_KEY\|password\|token\|secret" > /dev/null && {
echo "Possible hardcoded secrets found in changes! Please review."
exit 1
}
fi
echo "All security checks passed!"
exit 0
# IaC Security Code Review Checklist
## General Security
- [ ] No hardcoded secrets/credentials
- [ ] Resources follow naming conventions
- [ ] Tags/labels applied appropriately
- [ ] Documentation is up-to-date
## Access Control
- [ ] IAM roles/policies follow least privilege
- [ ] Authentication mechanisms are secure
- [ ] Authorization controls properly implemented
- [ ] No overly permissive policies (wildcards)
## Data Protection
- [ ] Encryption enabled for sensitive data
- [ ] Proper key management for secrets
- [ ] Sensitive data is not logged or exposed
- [ ] Backup and retention policies configured
## Network Security
- [ ] No unnecessary public exposure
- [ ] Network ACLs/security groups properly configured
- [ ] Traffic is appropriately restricted
- [ ] TLS/HTTPS enforced for communications
## Logging & Monitoring
- [ ] Appropriate audit logging enabled
- [ ] Monitoring and alerting configured
- [ ] Log storage and retention defined
- [ ] Access to logs appropriately restricted
## Compliance
- [ ] Required compliance controls implemented
- [ ] Necessary documentation for compliance included
- [ ] Regulatory requirements addressed
- [NIST SP 800-204C: Implementation of DevSecOps for a Cloud Native Environment](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-204C.pdf)
- [AWS Well-Architected Framework - Security Pillar](https://docs.aws.amazon.com/wellarchitected/latest/security-pillar/welcome.html)
- [Google Cloud Architecture Framework - Security, Privacy, and Compliance](https://cloud.google.com/architecture/framework/security)
- [Microsoft Azure Well-Architected Framework - Security](https://docs.microsoft.com/azure/architecture/framework/security/overview)
- [OWASP Top 10 for Infrastructure as Code](https://github.com/OWASP/www-project-top-10-for-iac)
- [Terraform Best Practices](https://www.terraform-best-practices.com/)
- [Kubernetes Security Best Practices](https://kubernetes.io/docs/concepts/security/security-best-practices/)