This project demonstrates an enterprise-grade Infrastructure as Code (IaC) security pipeline for QuantumTrade, a fintech platform processing cryptocurrency transactions. It implements a defence-in-depth scanning approach using three independent tools — Checkov, tfsec, and OPA — to identify and block security misconfigurations before they ever reach AWS.
The pipeline runs automatically on every push and pull request via GitHub Actions. Findings are uploaded to the GitHub Security tab as SARIF so they're visible at the PR level, not buried in logs.
Branch strategy:
test-security-scan— the intentionally vulnerable baseline. Run the tools here to reproduce every finding.main— the fully remediated, hardened state. This is the production-ready solution.
All five security jobs passing — Checkov, tfsec, OPA, Terraform Validate, and Security Summary. Every scanner ✅ Passed with findings uploaded to the GitHub Security tab as SARIF.
GitHub Actions Annotations panel from the latest green run — 4 Node.js 20 deprecation warnings for actions/checkout (no security findings). All Checkov, tfsec, OPA, and Terraform Validate jobs passed with hard gates enforced on main.
| Item | Detail |
|---|---|
| Client | QuantumTrade — Cryptocurrency Trading Platform |
| Problem | S3 data exposure risk, approaching SOC 2 compliance deadline |
| Solution | Automated IaC scanning integrated into CI/CD |
| Region | AWS eu-west-2 (London) |
| Stack | Terraform + AWS (S3, EC2, RDS PostgreSQL, VPC, KMS) |
Push / Pull Request
│
▼
┌─────────────────────────────────────────────────┐
│ GitHub Actions Pipeline │
│ │
│ ┌──────────┐ ┌──────────┐ ┌─────────────────┐ │
│ │ Checkov │ │ tfsec │ │ OPA (Rego) │ │
│ │ 2,500+ │ │ AWS-spec │ │ Custom policy │ │
│ │ CIS rules│ │ severity │ │ as code │ │
│ └────┬─────┘ └────┬─────┘ └────────┬────────┘ │
│ │ │ │ │
│ └─────────────┴──────────────────┘ │
│ │ │
│ ┌───────▼────────┐ │
│ │ Terraform │ │
│ │ Validate + fmt │ │
│ └───────┬────────┘ │
│ │ │
│ ┌───────▼────────┐ │
│ │ Security │ │
│ │ Summary │ │
│ └────────────────┘ │
└─────────────────────────────────────────────────┘
│ │
▼ ▼
GitHub Security Tab Findings uploaded
(SARIF findings) to Security tab
| Tool | Passed | Failed | Critical |
|---|---|---|---|
| Checkov v3.2.510 | 14 | 19 | 0 |
| tfsec v1.28.11 | 9 | 19 | 2 |
| OPA v0.63.0 | — | 23 tag violations | — |
| Tool | Passed | Ignored | Failed | Improvement |
|---|---|---|---|---|
| Checkov v3.2.510 | 33 | 9 | 0 | 100% of critical/high resolved |
| tfsec v1.28.11 | 32 | 12 | 0 | 100% of critical/high resolved |
| OPA v0.63.0 | 0 violations | — | — | 100% resolved |
Ignored checks are documented false positives or out-of-scope controls — see Skipped & Out-of-Scope Checks below.
terraform-security-project/
├── .github/
│ └── workflows/
│ └── iac-security.yml # Full CI/CD pipeline definition
├── .checkov.yaml # Checkov configuration and justified skip rules
├── .tfsec/ # tfsec severity overrides
├── terraform/
│ ├── main.tf # Root module (VPC, KMS, SG, flow logs, module calls)
│ ├── rds.tf # RDS subnet group + module invocation
│ ├── variables.tf # Input variables with validation
│ ├── outputs.tf # Key resource identifiers
│ ├── tfplan.json # Pre-generated plan for OPA evaluation (see note below)
│ └── modules/
│ ├── s3/ # Hardened S3 module (encryption, versioning, logging, lifecycle)
│ ├── ec2/ # Hardened EC2 module (IMDSv2, KMS EBS, SSM-only access)
│ └── rds/ # Hardened RDS module (encryption, Multi-AZ, IAM auth, gp3)
├── policies/
│ └── opa/
│ ├── required_tags.rego # Enforce Owner, Environment, CostCenter
│ ├── ec2_instance_types.rego # Block t2 instances in production
│ └── s3_versioning.rego # Require versioning on production buckets
├── screenshots/
│ ├── pipeline-overview 2.png # Live pipeline run — all jobs passing
│ └── pipeline-annotations 2.png # GitHub Actions annotations — green run
├── docs/
│ ├── ARCHITECTURE.md # Infrastructure diagrams, module design, design decisions
│ ├── SOC2_CONTROL_MAPPING.md # SOC 2 Trust Service Criteria mapping with evidence
│ └── SECURITY_REPORT.md # Full security assessment report
├── CONTRIBUTING.md # Local scan commands, branching conventions, module rules
├── SECURITY.md # Responsible disclosure policy
└── SECURITY-FINDINGS.md # Full findings register — 19 Checkov + 19 tfsec
Static analysis against 2,500+ CIS Benchmark and compliance policies. Scans Terraform files without requiring AWS credentials or a live deployment. Results uploaded to GitHub Security tab as SARIF.
checkov -d terraform/
checkov -d terraform/modules/AWS-specific security rules with severity ratings (CRITICAL/HIGH/MEDIUM/LOW). Catches issues Checkov misses including VPC flow logs and unrestricted egress. Runs with a minimum severity threshold of MEDIUM in CI, with key checks elevated to CRITICAL via .tfsec/config.yml.
tfsec terraform/
tfsec terraform/ --minimum-severity HIGHCustom Rego policies enforcing organisation-specific rules that no commercial tool knows — tagging standards, approved instance types, and S3 versioning requirements. Evaluates against the Terraform plan JSON so it catches dynamic values that static scanners miss.
opa eval --format pretty \
--data policies/opa/ \
--input terraform/tfplan.json \
"data.terraform"Note on
tfplan.json: This file is pre-generated for portfolio CI — no AWS credentials are available in this environment. In production,terraform planwould run in CI with credentials injected as GitHub Secrets (AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY), generating a fresh plan on every push and ensuring OPA always evaluates the current infrastructure state.
| # | Issue | Tool | Check ID | Real-World Risk |
|---|---|---|---|---|
| 1 | SSH open to 0.0.0.0/0 on port 22 |
tfsec | aws-ec2-no-public-ingress-sgr | Full internet brute-force attack surface |
| 2 | Unrestricted egress all ports | tfsec | aws-ec2-no-public-ingress-sgr | Compromised instance can exfiltrate data anywhere |
| # | Issue | Tool | Check ID |
|---|---|---|---|
| 1 | S3 bucket no encryption | Checkov + tfsec | CKV_AWS_19 / aws-s3-enable-bucket-encryption |
| 2 | All 4 S3 public access blocks disabled | Checkov | CKV_AWS_53-56 |
| 3 | RDS storage not encrypted | Checkov + tfsec | CKV_AWS_16 |
| 4 | EC2 EBS root volume not encrypted | Checkov + tfsec | CKV_AWS_8 |
| 5 | IMDSv1 enabled on EC2 | Checkov + tfsec | CKV_AWS_79 |
| 6 | RDS no deletion protection | Checkov | CKV_AWS_293 |
| 7 | RDS no Multi-AZ | Checkov | CKV_AWS_157 |
| 8 | RDS no IAM authentication | Checkov | CKV_AWS_161 |
| 9 | EC2 detailed monitoring disabled | Checkov | CKV_AWS_126 |
Why IMDSv1 matters: IMDSv1 was the attack vector in the 2019 Capital One breach — 100 million customer records exposed via SSRF through the EC2 metadata endpoint, resulting in an $80M fine. IMDSv2 with
http_tokens = "required"eliminates this attack class entirely.
Every resource in the vulnerable baseline was missing mandatory business tags, making cost attribution and incident response significantly harder.
| Resource | Environment | Owner | CostCenter |
|---|---|---|---|
aws_db_instance.main |
❌ | ❌ | ❌ |
aws_instance.app_server |
❌ | ❌ | ❌ |
aws_s3_bucket.data_bucket |
✅ | ❌ | ❌ |
aws_security_group.app_sg |
❌ | ❌ | ❌ |
aws_vpc.main |
❌ | ❌ | ❌ |
All critical and high severity findings are resolved on main. The checks below are either documented false positives or medium/low severity controls outside the scope of an IaC security hardening engagement. Each is explicitly configured in .checkov.yaml or .tfsec/config.yml with a written justification.
| Check ID | Description | Reason |
|---|---|---|
| CKV_AWS_135 | EBS optimisation not enabled | False positive — t3.medium enables EBS optimisation natively. Checkov does not account for instance types with built-in optimisation. |
| CKV2_AWS_6 | S3 public access block | False positive — enforced inside the S3 module via aws_s3_bucket_public_access_block. Checkov misses resource-level blocks when scanning at the module call site. |
| aws-iam-no-policy-wildcards | IAM policy wildcard | False positive — the ${log_group_arn}:* suffix in the flow log IAM policy is the AWS-required ARN format for CloudWatch Logs stream operations, not a blanket wildcard. |
| CKV_AWS_144 | S3 cross-region replication | Out of scope — disaster recovery control requiring a second AWS region, separate KMS keys, and IAM replication roles. A business continuity decision, not a security hardening baseline. |
| CKV2_AWS_62 | S3 event notifications | Out of scope — observability control requiring a downstream SNS/SQS/Lambda consumer. Separate workstream from IaC security hardening. |
| CKV2_AWS_60 | RDS copy tags to snapshots | Out of scope — operational tagging control for snapshot management. Not a direct attack surface issue. |
| CKV2_AWS_30 | RDS query logging | Out of scope — audit logging control. Sits under a separate logging and monitoring workstream. |
| CKV_AWS_353 | RDS performance insights | Out of scope — observability control, not a security misconfiguration. |
| CKV_AWS_118 | RDS enhanced monitoring | Out of scope — operational monitoring, not a security misconfiguration. |
| CKV_AWS_338 | CloudWatch log retention ≥ 1 year | Out of scope — compliance control (SOC 2, PCI-DSS) set as part of a formal log retention policy, not IaC hardening. |
In a production engagement, out-of-scope items would be tracked in a risk register with documented acceptance, owner, and review date.
| Metric | Before | After | Change |
|---|---|---|---|
| Checkov failures | 19 | 0 | −100% |
| tfsec failures | 19 | 0 | −100% |
| Critical issues | 2 | 0 | −100% |
| OPA violations | 23 | 0 | −100% |
Every bucket created with this module enforces by default:
- ✅ KMS server-side encryption with customer-managed key (
kms_master_key_idrequired) - ✅ All 4 public access block settings set to
true - ✅ Versioning enabled
- ✅ Access logging to dedicated log bucket
- ✅ Lifecycle rules (Standard-IA at 90 days, Glacier at 180 days)
- ✅
Owner,Environment,CostCentertags required as module inputs
Every instance created with this module enforces by default:
- ✅ IMDSv2 required (
http_tokens = "required", hop limit = 1) - ✅ EBS root volume encrypted with KMS CMK
- ✅ Detailed CloudWatch monitoring enabled
- ✅ No public IP address
- ✅ t2 instance family blocked via Terraform input validation
- ✅
Owner,Environment,CostCentertags required as module inputs
Every database instance created with this module enforces by default:
- ✅ Storage encrypted with KMS CMK (
kms_key_idrequired input) - ✅
publicly_accessible = false— enforced via validation (rejectstrue) - ✅ Deletion protection enabled
- ✅ Final snapshot on destroy (
skip_final_snapshot = false) - ✅ Automated backups — 7-day minimum enforced via validation
- ✅ Multi-AZ deployment
- ✅ IAM database authentication enabled
- ✅ Auto minor version upgrades
- ✅ CloudWatch logs exports (postgresql, upgrade)
- ✅
gp3storage type (better performance and cost vs gp2) - ✅
Owner,Environment,CostCentertags required as module inputs
Denies any resource missing Environment, Owner, or CostCenter tags. Warns if the Environment value is not one of production, staging, or development.
Denies t2 family instances in production environments. Warns about micro-sized instances in production.
Denies production S3 buckets without versioning enabled. Warns about any S3 bucket missing lifecycle rules.
Policies use modern OPA v0.60+ syntax (contains, if keywords) and are pinned to v0.63.0 in CI for deterministic evaluation.
pip install checkov
brew install tfsec opa terraform # macOSgit clone https://github.com/AurelienKumarathas/terraform-security-project.git
cd terraform-security-project
git checkout test-security-scan
checkov -d terraform/
tfsec terraform/git checkout main
checkov -d terraform/modules/
tfsec terraform/modules/
# OPA — requires a tfplan.json (see below)
cd terraform
terraform init -backend=false
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
cd ..
opa eval --format pretty \
--data policies/opa/ \
--input terraform/tfplan.json \
"data.terraform"| Document | Description |
|---|---|
docs/ARCHITECTURE.md |
Infrastructure diagrams, module design, pipeline flow, and key design decisions |
docs/SOC2_CONTROL_MAPPING.md |
SOC 2 TSC mapping — CC6, CC7, CC8, CC9 with evidence |
docs/SECURITY_REPORT.md |
Full security assessment with all findings and remediation status |
SECURITY-FINDINGS.md |
Full findings register — 19 Checkov + 19 tfsec with severity, fix, and justification |
CONTRIBUTING.md |
Local scan commands, branching conventions, PR checklist, and module rules |
SECURITY.md |
Responsible disclosure policy |
This project covers the IaC static analysis and policy-as-code layer. In a real production engagement, the next workstreams would be:
- AWS GuardDuty — enable runtime threat detection across EC2, S3, and RDS. GuardDuty uses ML to detect anomalous API calls, credential misuse, and data exfiltration patterns that static scanning cannot catch, because they only emerge from live behaviour.
- AWS Config Rules — continuous compliance monitoring post-deployment. Where Checkov and tfsec scan code before it deploys, Config rules verify the live infrastructure matches the intended state and alert on drift — e.g. if someone manually opens port 22 via the console, Config catches it immediately.
- Snyk or Trivy (container scanning) — extend the defence-in-depth approach to container images and application dependencies. IaC hardening secures the infrastructure layer; image scanning secures the workload running on top of it. Both layers need coverage for a complete DevSecOps pipeline.
- RDS Performance Insights + Enhanced Monitoring — resolves the two remaining Checkov LOW findings (CKV_AWS_353, CKV_AWS_118) and provides the database observability layer needed for SOC 2 CC7.1 evidence.
- S3 abort incomplete multipart upload rule — adds
abort_incomplete_multipart_upload { days_after_initiation = 7 }to the S3 module lifecycle block, clearing the final Checkov LOW finding (CKV_AWS_300) and preventing silent cost accumulation from abandoned uploads.
- IaC Security — Static analysis of Terraform using Checkov and tfsec across 2,500+ rules
- Policy as Code — Custom Rego policies in OPA enforcing business-specific rules no commercial tool covers
- Threat Modelling — Identifying real attack vectors: SSRF via IMDSv1, data exfiltration via unrestricted egress, brute-force SSH
- Secure Module Design — Reusable hardened Terraform modules (S3, EC2, RDS) that make the secure path the default path
- CI/CD Integration — GitHub Actions pipeline with SARIF upload to GitHub Security tab; hard gates block merges on any CRITICAL or HIGH finding
- Compliance Mapping — SOC 2 Trust Service Criteria mapped to specific scan findings with evidence
- Defence in Depth — Three independent tools each catching different issue classes; no single point of failure in the scanning strategy
- Risk Management — Documented false positives and out-of-scope items with written justification, mirroring real-world risk acceptance processes
All vulnerabilities on the test-security-scan branch are intentional for demonstration. The hardened modules on main represent production-ready patterns.

