Fintech - PCI DSS v4.0
For a payment platform, neobank, buy-now-pay-later provider or other fintech preparing for a PCI DSS v4.0 assessment. The kit deploys from the pcidss.compliance.tf registry host and builds in the decisions that are hardest to change later: cardholder data environment (CDE) segmentation, RDS encryption at creation and a private data layer. When you finish, the stack is applied and every PCI DSS v4.0 control its modules enforce has passed terraform plan.
Repository: compliancetf/starter-kit-fintech-pcidss · About 130 resources
Before you start
- The shared prerequisites on Compliance starter kits: an account whose plan includes PCI DSS v4.0, Terraform at a version every module accepts, AWS credentials, a Route 53 hosted zone, the replication role and destination bucket, and account-level CloudTrail. PCI DSS Req. 10.2 expects the CloudTrail trail alongside the resource logs this kit configures.
- Leave RDS encryption on.
storage_encrypted = truehas to be set when the database is created; AWS cannot encrypt an existing instance without a snapshot and restore to a new one. The kit enforces encryption by default. - Decide how you will handle MFA delete before the first apply. The kit requests MFA delete on the data and logs buckets, and
s3_bucket_mfa_delete_enabledenforces it, but AWS accepts that change only from the root user with an MFA code, and does not allow lifecycle rules on a bucket with MFA delete, while the logs bucket ships a 365-day lifecycle. The usual path: turn the control off for the rollout, keep the lifecycle, and if your QSA requires MFA delete, have the root user enable it on a bucket without lifecycle rules. Do not run the whole apply as root. Turning the control off means switching thes3_bucket_dataands3_bucket_logssources inmain.tfto the HTTPS form with?disable=s3_bucket_mfa_delete_enabled(see Turn off a control) and adding a.netrcentry forpcidss.compliance.tf, becauseterraform logindoes not cover HTTPS sources; see Registry endpoints.
Deploy
git clone https://github.com/compliancetf/starter-kit-fintech-pcidss.git
cd starter-kit-fintech-pcidss
cp terraform.tfvars.example terraform.tfvars
# Set project_name, domain_name, route53_zone_id, availability_zones,
# s3_replication_role_arn and s3_replication_destination_bucket_arn
terraform login pcidss.compliance.tf
terraform init
terraform plan
terraform applyIf a value in terraform.tfvars violates a PCI DSS v4.0 control, terraform plan fails. The error names the control and the module. Fix the value and plan again.
What it deploys

Two layers. The public edge (CloudFront, WAF and ALB) routes requests and enforces protocols. The CDE layer (EKS, Lambda, EC2, Aurora PostgreSQL, DynamoDB and ElastiCache) is private, with no public route to any data store.
| Module | Version | Purpose | PCI DSS v4.0 requirements |
|---|---|---|---|
vpc | ~> 6.0 | CDE network isolation, flow logs | Req. 1.3 (restrict CDE access), Req. 10.2 (audit logs) |
alb | ~> 10.0 | HTTPS load balancing, access logs | Req. 4.2.1 (TLS 1.2+), Req. 6.4.1 (WAF association) |
cloudfront | ~> 6.0 | CDN edge with TLS and WAF | Req. 4.2.1 (TLS at edge), Req. 6.4.1 (WAF) |
waf | ~> 1.0 | Regional WAF (ALB), OWASP Top 10 | Req. 6.4.1 (web application protection) |
waf_global | ~> 1.0 | Global WAF (CloudFront, us-east-1) | Req. 6.4.1 (web application protection at edge) |
acm | ~> 6.0 | Regional TLS certificate | Req. 4.2.1 (strong cryptography in transit) |
acm_us_east_1 | ~> 6.0 | Global TLS certificate for CloudFront | Req. 4.2.1 (strong cryptography in transit) |
kms | ~> 4.0 | Customer-managed key wired to EKS, Lambda, and OpenSearch | Req. 3.5.1 (PAN rendered unreadable) |
eks | ~> 21.0 | Kubernetes cluster, private endpoint, encrypted secrets | Req. 2.2 (secure configuration), Req. 10.2 (audit logs) |
lambda | ~> 8.0 | VPC-attached functions, KMS environment encryption | Req. 1.3 (restrict CDE access), Req. 3.5.1 (encryption) |
ec2_instance | ~> 6.0 | Private worker nodes, IMDSv2, IAM instance profile | Req. 2.2 (no public IP, IMDSv2 enforced) |
rds_aurora | ~> 10.0 | Aurora PostgreSQL, encrypted at creation, Multi-AZ, IAM auth | Req. 3.5.1 (encryption at rest), Req. 10.2 (audit logs) |
dynamodb | ~> 5.0 | Key-value store, KMS encryption, PITR | Req. 3.5.1 (encryption at rest) |
elasticache | ~> 1.0 | Redis, encrypted at rest and in transit | Req. 4.2.1 (TLS in transit) |
s3_bucket_data | ~> 5.0 | CDE data storage, KMS, versioning, cross-region replication | Req. 3.5.1 (encryption at rest), Req. 3.5 (no public access) |
s3_bucket_logs | ~> 5.0 | Audit log storage, versioning (MFA delete requested, see Before you start), cross-region replication, 365-day lifecycle | Req. 10.3 (log protection), Req. 10.5.1 (log retention) |
sqs | ~> 5.0 | Message queue with dead-letter queue | Req. 3.5.1 (SSE encryption) |
opensearch | ~> 2.0 | VPC-only search, KMS, HTTPS, fine-grained access, audit logs | Req. 10.2 (audit logs), Req. 10.3 (log protection) |
cloudwatch | ~> 5.0 | Application log group, 365-day retention | Req. 10.2 (audit logs), Req. 10.5.1 (log retention) |
CloudFront accepts certificates only from us-east-1, wherever your other resources are, so the kit provisions two ACM certificates: a regional one for the ALB and a global one for CloudFront, through a provider alias. Both validate against the same Route 53 zone.
Control coverage
| PCI DSS v4.0 requirement | Description | Modules | What the kit configures |
|---|---|---|---|
| Req. 1.3 | Network access to and from the cardholder data environment is restricted | VPC, Lambda, RDS Aurora, ElastiCache | Private and database subnets, no public routes to any CDE data store, VPC-attached functions |
| Req. 2.2 | System components are configured and managed securely | EC2, EKS | IMDSv2 required, no public IPs, IAM instance profiles, private cluster endpoint |
| Req. 3.5 | Primary account number (PAN) is secured wherever it is stored | S3, RDS Aurora | Public access blocked, encryption enforced |
| Req. 3.5.1 | Primary account numbers (PAN) are secured with strong cryptography wherever stored | S3, RDS Aurora, DynamoDB, Lambda, KMS | Encryption at rest on every CDE data store: the kit's customer-managed key on EKS, Lambda and OpenSearch, the service's AWS encryption elsewhere unless you pass key ARNs |
| Req. 4.2.1 | Strong cryptography is used to safeguard PAN during transmission | ALB, CloudFront, ElastiCache | TLS 1.2+ enforced on all connections |
| Req. 6.4.1 | Public-facing web applications are protected against attacks on an ongoing basis | ALB, CloudFront | WAF with OWASP Top 10, known bad inputs, IP reputation |
| Req. 10.2 | Audit logs are implemented to support detection of anomalies and suspicious activity and the forensic analysis of events | All modules | VPC flow logs, ALB access logs, EKS audit logs, CloudWatch log groups |
| Req. 10.3 | Audit logs are protected from destruction and unauthorized modifications | S3 (logs), OpenSearch | S3 versioning, MFA delete once the root user enables it, cross-region replication once the rule covers KMS objects (see Before you start), OpenSearch index retention |
| Req. 10.5.1 | Audit log history is retained for at least 12 months, with the most recent 3 months immediately available | S3 (logs), CloudWatch | 365-day retention on the log group and the logs bucket; the 90-day Glacier transition keeps the most recent three months in standard storage (immediately retrievable) while older logs stay retained through 365 days — confirm these thresholds with your QSA, since 365/90 days approximate the calendar months the requirement names |
Settings a module enforces as a control fail the plan if you change them. The rest are kit defaults you can change. The PCI DSS v4.0 framework page lists the controls modules enforce. It also lists the mapped-only controls your team handles.
What you own
The kit configures the infrastructure settings above. These stay with you:
- Tokenization and PAN handling in application code (compliance.tf does not tokenize)
- Key rotation procedures and key administrator access policies
- User access reviews and role assignments
- Change management approvals and evidence collection
- Annual penetration testing (PCI DSS Req. 11.4 requires both internal and external testing at least once per 12 months)
- QSA engagement and Report on Compliance (ROC) preparation
- Cardholder data discovery and formal CDE scope documentation
CDE scoping
A smaller cardholder data environment means fewer QSA hours and a cheaper assessment.
In CDE scope: VPC private subnets, EKS, Lambda, EC2, RDS Aurora, DynamoDB, ElastiCache, S3 data bucket, OpenSearch
Typically out of CDE scope: SQS (application queuing for non-PAN data) and the CloudWatch log group (aggregated logs only). CloudFront is out of scope only when it serves static assets — a distribution that transmits cardholder data is in scope (covered under AWS's PCI attestation for the AWS-managed portion). Confirm scoping decisions with your QSA.
The modules in CDE scope have KMS encryption at rest, checked at terraform plan (the kit's customer-managed key is wired to EKS, Lambda, and OpenSearch; other services use their respective AWS encryption defaults unless you pass key ARNs). The out-of-scope modules use standard server-side encryption.
QSA evidence
When your QSA asks about infrastructure controls, this is where the evidence is:
| QSA question | Where to find the evidence |
|---|---|
| Is cardholder data encrypted at rest? | Terraform state: aws_kms_key, aws_rds_cluster, aws_s3_bucket_server_side_encryption_configuration |
| Is TLS 1.2+ enforced on all endpoints? | ALB listener policy: ELBSecurityPolicy-TLS13-1-2-2021-06; CloudFront security policy: TLSv1.2_2021 |
| Are databases in private subnets with no public endpoint? | VPC subnets: aws_subnet with map_public_ip_on_launch = false; RDS publicly_accessible = false |
| Is WAF protecting web-facing applications? | aws_wafv2_web_acl association with ALB and CloudFront distribution |
| Are audit logs retained and protected? | S3 logs bucket versioning and replication configuration, plus replication metrics showing encrypted objects arrive; OpenSearch index retention settings |
| Is IMDSv2 enforced on EC2 instances? | aws_instance with metadata_options.http_tokens = "required" |
Customize
Turn off a control
Use the HTTPS source with ?disable=:
module "s3_bucket_data" {
source = "https://pcidss.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=5.0.0&disable=s3_bucket_event_notifications_enabled"
# ...
}That example turns off the S3 event notification control, which has nothing to do unless something in your account consumes those events. Do not turn off an encryption, public-access or logging control on a module in CDE scope: your QSA will ask why it is absent, and the answer has to be a scoping decision, not a disabled check. Only a control the module enforces can be turned off; Turn controls on and off explains the limits and how to record the reason.
Adjust sizes or remove a module
Instance sizes and counts are variables; override them in terraform.tfvars, with the names in the repository's variables.tf. To drop a module the application does not use, delete its block from main.tf and its outputs from outputs.tf, then check that no other module references it.
Cost
| Component | Dev | Prod |
|---|---|---|
| VPC + NAT Gateway | ~$35 | ~$100 |
| ALB | ~$25 | ~$25+ |
| CloudFront | ~$1 | ~$5+ |
| EKS control plane | ~$75 | ~$75 |
| EKS nodes (t3.medium) | ~$60 | ~$120+ |
| Aurora PostgreSQL (db.r6g.large) | ~$200 | ~$400 |
| ElastiCache (cache.t4g.micro) | ~$10 | ~$20 |
| OpenSearch (t3.small.search) | ~$20 | ~$60 |
| S3 + Lambda + SQS + EC2 | ~$30 | ~$60+ |
| Total of the rows | ~$456 | ~$865+ |
The total is the sum of the rows above. The table has no rows for the two WAF web ACLs, DynamoDB, KMS or CloudWatch Logs, so add those. Usage charges come on top: data transfer, NAT gateway data processing, log ingestion, and KMS and WAF requests. Actual costs depend on usage, data transfer and region. Infracost estimates them from your own terraform plan.