Introduction
PCI-DSS compliance isn't optional when handling payment card data. I built a platform that processes millions of transactions while maintaining Level 1 compliance - the highest certification level. This required rethinking everything from network architecture to logging.
This post covers the technical implementation of:
- Network segmentation - Isolating the Cardholder Data Environment (CDE)
- Encryption everywhere - Data at rest, in transit, and in use
- Comprehensive logging - Every access, every action, forever
- Automated compliance - Continuous monitoring with Security Hub
- Access controls - Least privilege, MFA, just-in-time access
Architecture Overview
flowchart TB
subgraph Internet["Internet"]
CUSTOMER[Customer]
PARTNER[Payment Partners]
end
subgraph AWSCloud["AWS Cloud - PCI Scope"]
subgraph EdgeLayer["Edge Layer"]
WAF[AWS WAF]
SHIELD[AWS Shield]
CF[CloudFront]
end
subgraph DMZ["DMZ VPC - 10.100.0.0/16"]
ALB[Application Load Balancer]
APIGW[API Gateway]
end
subgraph CDE["Cardholder Data Environment - 10.200.0.0/16"]
subgraph CDEPrivate["Private Subnets"]
EKS_CDE[EKS - Payment Services]
TOKENIZER[Tokenization Service]
HSM[CloudHSM]
end
subgraph CDEData["Data Subnets"]
RDS_CDE[(RDS - Encrypted)]
REDIS_CDE[ElastiCache - Encrypted]
end
end
subgraph NonCDE["Non-CDE VPC - 10.300.0.0/16"]
EKS_APP[EKS - Non-sensitive Apps]
RDS_APP[(RDS - Non-PCI)]
end
subgraph Security["Security VPC - 10.240.0.0/16"]
SIEM[SIEM / Splunk]
VAULT[HashiCorp Vault]
SCANNER[Vulnerability Scanner]
end
subgraph Logging["Centralized Logging"]
CLOUDTRAIL[(CloudTrail)]
FLOWLOGS[(VPC Flow Logs)]
APPLOGS[(Application Logs)]
S3_LOGS[(S3 - Immutable)]
end
end
CUSTOMER --> CF --> WAF --> ALB
PARTNER --> APIGW
ALB --> EKS_CDE
APIGW --> EKS_CDE
EKS_CDE --> TOKENIZER
TOKENIZER --> HSM
EKS_CDE --> RDS_CDE
EKS_CDE --> SIEM
CLOUDTRAIL --> S3_LOGS
FLOWLOGS --> S3_LOGS
style CDE fill:#e63946,stroke:#fff,stroke-width:3px,color:#fff
style DMZ fill:#f77f00,stroke:#fff,stroke-width:2px,color:#fff
style NonCDE fill:#2a9d8f,stroke:#fff,stroke-width:2px,color:#fff
style Security fill:#264653,stroke:#fff,stroke-width:2px,color:#fff
style Logging fill:#1a1a2e,stroke:#00d9ff,stroke-width:2px,color:#fff
PCI-DSS Requirements Mapping
flowchart LR
subgraph Requirements["PCI-DSS Requirements"]
direction TB
R1["Req 1: Firewall"]
R2["Req 2: No Defaults"]
R3["Req 3: Protect CHD"]
R4["Req 4: Encrypt Transit"]
R5["Req 5: Anti-malware"]
R6["Req 6: Secure Systems"]
R7["Req 7: Access Control"]
R8["Req 8: Authentication"]
R9["Req 9: Physical"]
R10["Req 10: Logging"]
R11["Req 11: Testing"]
R12["Req 12: Policies"]
end
subgraph Implementation["AWS Implementation"]
direction TB
I1["Security Groups<br/>NACLs<br/>Network Firewall"]
I2["Config Rules<br/>SSM Automation<br/>AMI Hardening"]
I3["KMS Encryption<br/>CloudHSM<br/>Tokenization"]
I4["TLS 1.3<br/>ACM Certificates<br/>PrivateLink"]
I5["GuardDuty<br/>Inspector<br/>ECR Scanning"]
I6["Patch Manager<br/>CodeGuru<br/>SAST/DAST"]
I7["IAM Policies<br/>RBAC<br/>Attribute-based"]
I8["MFA<br/>SSO<br/>Short-lived creds"]
I9["AWS Responsibility<br/>SOC 2 Reports"]
I10["CloudTrail<br/>VPC Flow Logs<br/>EKS Audit"]
I11["Security Hub<br/>Penetration Tests<br/>Vulnerability Scans"]
I12["Control Tower<br/>SCPs<br/>Guardrails"]
end
R1 --> I1
R2 --> I2
R3 --> I3
R4 --> I4
R5 --> I5
R6 --> I6
R7 --> I7
R8 --> I8
R9 --> I9
R10 --> I10
R11 --> I11
R12 --> I12
style Requirements fill:#e63946,stroke:#fff,stroke-width:2px,color:#fff
style Implementation fill:#2a9d8f,stroke:#fff,stroke-width:2px,color:#fff
Network Segmentation
CDE VPC Configuration
# cde-vpc/main.tf
module "cde_vpc" {
source = "../../modules/vpc"
name = "cde-vpc"
cidr = "10.200.0.0/16"
azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
private_subnets = ["10.200.1.0/24", "10.200.2.0/24", "10.200.3.0/24"]
data_subnets = ["10.200.10.0/24", "10.200.11.0/24", "10.200.12.0/24"]
# No public subnets in CDE
public_subnets = []
# No NAT Gateway - all egress through Transit Gateway
enable_nat_gateway = false
# VPC Flow Logs - required for PCI
enable_flow_log = true
flow_log_destination_type = "s3"
flow_log_destination_arn = var.flow_logs_bucket_arn
flow_log_traffic_type = "ALL"
flow_log_max_aggregation_interval = 60
tags = {
Environment = "production"
Compliance = "pci-dss"
DataClass = "cardholder-data"
CostCenter = "security"
}
}
# Network ACLs - defense in depth
resource "aws_network_acl" "cde_private" {
vpc_id = module.cde_vpc.vpc_id
subnet_ids = module.cde_vpc.private_subnet_ids
# Allow inbound from DMZ only
ingress {
protocol = "tcp"
rule_no = 100
action = "allow"
cidr_block = "10.100.0.0/16" # DMZ VPC
from_port = 443
to_port = 443
}
# Allow inbound from Security VPC (monitoring)
ingress {
protocol = "tcp"
rule_no = 110
action = "allow"
cidr_block = "10.240.0.0/16"
from_port = 443
to_port = 443
}
# Deny all other inbound
ingress {
protocol = "-1"
rule_no = 200
action = "deny"
cidr_block = "0.0.0.0/0"
from_port = 0
to_port = 0
}
# Allow outbound to data subnets
egress {
protocol = "tcp"
rule_no = 100
action = "allow"
cidr_block = "10.200.10.0/24"
from_port = 5432
to_port = 5432
}
egress {
protocol = "tcp"
rule_no = 110
action = "allow"
cidr_block = "10.200.10.0/24"
from_port = 6379
to_port = 6379
}
# Allow outbound to Security VPC (logging)
egress {
protocol = "tcp"
rule_no = 120
action = "allow"
cidr_block = "10.240.0.0/16"
from_port = 443
to_port = 443
}
tags = {
Name = "cde-private-nacl"
Compliance = "pci-dss"
}
}Security Groups
# cde-vpc/security-groups.tf
# EKS nodes in CDE
resource "aws_security_group" "cde_eks_nodes" {
name_prefix = "cde-eks-nodes-"
vpc_id = module.cde_vpc.vpc_id
description = "Security group for EKS nodes in CDE"
# Inbound from ALB only
ingress {
description = "HTTPS from ALB"
from_port = 443
to_port = 443
protocol = "tcp"
security_groups = [var.alb_security_group_id]
}
# Node to node communication
ingress {
description = "Node to node"
from_port = 0
to_port = 65535
protocol = "tcp"
self = true
}
# Outbound to RDS
egress {
description = "PostgreSQL to RDS"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.cde_rds.id]
}
# Outbound to ElastiCache
egress {
description = "Redis to ElastiCache"
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [aws_security_group.cde_redis.id]
}
# Outbound to CloudHSM
egress {
description = "CloudHSM"
from_port = 2223
to_port = 2225
protocol = "tcp"
security_groups = [aws_security_group.cloudhsm.id]
}
# Outbound to VPC endpoints only
egress {
description = "VPC Endpoints"
from_port = 443
to_port = 443
protocol = "tcp"
prefix_list_ids = [var.vpc_endpoint_prefix_list_id]
}
# NO general internet egress
tags = {
Name = "cde-eks-nodes-sg"
Compliance = "pci-dss"
}
lifecycle {
create_before_destroy = true
}
}
# RDS in CDE
resource "aws_security_group" "cde_rds" {
name_prefix = "cde-rds-"
vpc_id = module.cde_vpc.vpc_id
description = "Security group for RDS in CDE"
# Inbound from EKS nodes only
ingress {
description = "PostgreSQL from EKS"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.cde_eks_nodes.id]
}
# No egress needed for RDS
tags = {
Name = "cde-rds-sg"
Compliance = "pci-dss"
}
}Encryption Configuration
KMS Keys
# encryption/kms.tf
# Master key for CDE data
resource "aws_kms_key" "cde_master" {
description = "Master key for CDE data encryption"
deletion_window_in_days = 30
enable_key_rotation = true
multi_region = false
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowRootAccess"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
}
Action = "kms:*"
Resource = "*"
},
{
Sid = "AllowCDEServices"
Effect = "Allow"
Principal = {
AWS = [
aws_iam_role.cde_eks_node.arn,
aws_iam_role.cde_rds.arn,
]
}
Action = [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
]
Resource = "*"
},
{
Sid = "AllowCloudWatchLogs"
Effect = "Allow"
Principal = {
Service = "logs.${var.region}.amazonaws.com"
}
Action = [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
]
Resource = "*"
Condition = {
ArnLike = {
"kms:EncryptionContext:aws:logs:arn" = "arn:aws:logs:${var.region}:${data.aws_caller_identity.current.account_id}:log-group:*"
}
}
}
]
})
tags = {
Name = "cde-master-key"
Compliance = "pci-dss"
Purpose = "cardholder-data-encryption"
}
}
resource "aws_kms_alias" "cde_master" {
name = "alias/cde-master"
target_key_id = aws_kms_key.cde_master.key_id
}CloudHSM for Key Management
# encryption/cloudhsm.tf
resource "aws_cloudhsm_v2_cluster" "cde" {
hsm_type = "hsm1.medium"
subnet_ids = module.cde_vpc.data_subnet_ids
tags = {
Name = "cde-hsm-cluster"
Compliance = "pci-dss"
}
}
resource "aws_cloudhsm_v2_hsm" "cde" {
count = 2 # HA configuration
cluster_id = aws_cloudhsm_v2_cluster.cde.cluster_id
subnet_id = module.cde_vpc.data_subnet_ids[count.index]
}
resource "aws_security_group" "cloudhsm" {
name_prefix = "cloudhsm-"
vpc_id = module.cde_vpc.vpc_id
description = "Security group for CloudHSM"
ingress {
description = "HSM client connections"
from_port = 2223
to_port = 2225
protocol = "tcp"
security_groups = [aws_security_group.cde_eks_nodes.id]
}
tags = {
Name = "cloudhsm-sg"
Compliance = "pci-dss"
}
}RDS Encryption
# database/rds.tf
resource "aws_db_instance" "cde_primary" {
identifier = "cde-primary"
engine = "postgres"
engine_version = "15.4"
instance_class = "db.r6i.xlarge"
allocated_storage = 100
max_allocated_storage = 500
storage_type = "gp3"
storage_encrypted = true
kms_key_id = aws_kms_key.cde_master.arn
db_name = "payments"
username = "admin"
password = random_password.rds_master.result
multi_az = true
db_subnet_group_name = aws_db_subnet_group.cde.name
vpc_security_group_ids = [aws_security_group.cde_rds.id]
# No public access
publicly_accessible = false
# Backup configuration
backup_retention_period = 35 # PCI requires minimum 1 year, but use S3 for long-term
backup_window = "03:00-04:00"
maintenance_window = "Mon:04:00-Mon:05:00"
# Enhanced monitoring
monitoring_interval = 60
monitoring_role_arn = aws_iam_role.rds_monitoring.arn
# Performance insights (encrypted)
performance_insights_enabled = true
performance_insights_kms_key_id = aws_kms_key.cde_master.arn
performance_insights_retention_period = 731 # 2 years
# Audit logging
enabled_cloudwatch_logs_exports = [
"postgresql",
"upgrade"
]
# Deletion protection
deletion_protection = true
skip_final_snapshot = false
final_snapshot_identifier = "cde-primary-final-${formatdate("YYYY-MM-DD", timestamp())}"
# Force SSL
parameter_group_name = aws_db_parameter_group.cde.name
tags = {
Name = "cde-primary"
Compliance = "pci-dss"
DataClass = "cardholder-data"
BackupRequired = "true"
}
}
resource "aws_db_parameter_group" "cde" {
name = "cde-postgres15"
family = "postgres15"
# Force SSL connections
parameter {
name = "rds.force_ssl"
value = "1"
}
# Audit logging
parameter {
name = "log_connections"
value = "1"
}
parameter {
name = "log_disconnections"
value = "1"
}
parameter {
name = "log_statement"
value = "all"
}
parameter {
name = "log_min_duration_statement"
value = "0" # Log all statements
}
tags = {
Compliance = "pci-dss"
}
}Audit Logging
flowchart TB
subgraph Sources["Log Sources"]
CT[CloudTrail<br/>API Calls]
VPC[VPC Flow Logs<br/>Network Traffic]
EKS[EKS Audit Logs<br/>K8s API]
APP[Application Logs<br/>Business Events]
RDS[RDS Logs<br/>Database Access]
WAF[WAF Logs<br/>Web Attacks]
end
subgraph Collection["Log Collection"]
CW[CloudWatch Logs]
KINESIS[Kinesis Firehose]
end
subgraph Storage["Immutable Storage"]
S3[S3 Bucket<br/>Object Lock Enabled]
GLACIER[S3 Glacier<br/>7 Year Retention]
end
subgraph Analysis["Analysis"]
ATHENA[Athena<br/>Ad-hoc Queries]
SIEM[SIEM<br/>Real-time Alerts]
SECURITYHUB[Security Hub<br/>Compliance Dashboard]
end
CT & VPC & EKS & APP & RDS & WAF --> CW
CW --> KINESIS
KINESIS --> S3
S3 --> GLACIER
S3 --> ATHENA
CW --> SIEM
CT --> SECURITYHUB
style Sources fill:#264653,stroke:#fff,stroke-width:2px,color:#fff
style Collection fill:#f77f00,stroke:#fff,stroke-width:2px,color:#fff
style Storage fill:#e63946,stroke:#fff,stroke-width:2px,color:#fff
style Analysis fill:#2a9d8f,stroke:#fff,stroke-width:2px,color:#fff
Immutable Log Storage
# logging/s3-logs.tf
resource "aws_s3_bucket" "audit_logs" {
bucket = "company-pci-audit-logs-${data.aws_caller_identity.current.account_id}"
tags = {
Name = "pci-audit-logs"
Compliance = "pci-dss"
Retention = "7-years"
}
}
# Enable versioning
resource "aws_s3_bucket_versioning" "audit_logs" {
bucket = aws_s3_bucket.audit_logs.id
versioning_configuration {
status = "Enabled"
}
}
# Object lock - makes logs immutable
resource "aws_s3_bucket_object_lock_configuration" "audit_logs" {
bucket = aws_s3_bucket.audit_logs.id
rule {
default_retention {
mode = "GOVERNANCE"
years = 7
}
}
}
# Encryption
resource "aws_s3_bucket_server_side_encryption_configuration" "audit_logs" {
bucket = aws_s3_bucket.audit_logs.id
rule {
apply_server_side_encryption_by_default {
kms_master_key_id = aws_kms_key.logs.arn
sse_algorithm = "aws:kms"
}
bucket_key_enabled = true
}
}
# Block public access
resource "aws_s3_bucket_public_access_block" "audit_logs" {
bucket = aws_s3_bucket.audit_logs.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Lifecycle - transition to Glacier after 90 days
resource "aws_s3_bucket_lifecycle_configuration" "audit_logs" {
bucket = aws_s3_bucket.audit_logs.id
rule {
id = "archive-logs"
status = "Enabled"
transition {
days = 90
storage_class = "GLACIER"
}
# Never expire - regulatory requirement
# expiration block intentionally omitted
}
}
# Bucket policy - deny deletion
resource "aws_s3_bucket_policy" "audit_logs" {
bucket = aws_s3_bucket.audit_logs.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "DenyDeletion"
Effect = "Deny"
Principal = "*"
Action = [
"s3:DeleteObject",
"s3:DeleteObjectVersion"
]
Resource = "${aws_s3_bucket.audit_logs.arn}/*"
},
{
Sid = "RequireSSL"
Effect = "Deny"
Principal = "*"
Action = "s3:*"
Resource = [
aws_s3_bucket.audit_logs.arn,
"${aws_s3_bucket.audit_logs.arn}/*"
]
Condition = {
Bool = {
"aws:SecureTransport" = "false"
}
}
}
]
})
}CloudTrail Configuration
# logging/cloudtrail.tf
resource "aws_cloudtrail" "pci" {
name = "pci-audit-trail"
s3_bucket_name = aws_s3_bucket.audit_logs.id
s3_key_prefix = "cloudtrail"
include_global_service_events = true
is_multi_region_trail = true
is_organization_trail = false
enable_log_file_validation = true
kms_key_id = aws_kms_key.logs.arn
# CloudWatch Logs integration for real-time alerting
cloud_watch_logs_group_arn = "${aws_cloudwatch_log_group.cloudtrail.arn}:*"
cloud_watch_logs_role_arn = aws_iam_role.cloudtrail_cloudwatch.arn
# Log all events
event_selector {
read_write_type = "All"
include_management_events = true
data_resource {
type = "AWS::S3::Object"
values = ["arn:aws:s3"]
}
data_resource {
type = "AWS::Lambda::Function"
values = ["arn:aws:lambda"]
}
}
# Insights for anomaly detection
insight_selector {
insight_type = "ApiCallRateInsight"
}
insight_selector {
insight_type = "ApiErrorRateInsight"
}
tags = {
Name = "pci-audit-trail"
Compliance = "pci-dss"
}
}Security Hub and Compliance
# compliance/security-hub.tf
resource "aws_securityhub_account" "main" {}
# Enable PCI-DSS standard
resource "aws_securityhub_standards_subscription" "pci_dss" {
standards_arn = "arn:aws:securityhub:${var.region}::standards/pci-dss/v/3.2.1"
depends_on = [aws_securityhub_account.main]
}
# Enable CIS AWS Foundations
resource "aws_securityhub_standards_subscription" "cis" {
standards_arn = "arn:aws:securityhub:${var.region}::standards/cis-aws-foundations-benchmark/v/1.4.0"
depends_on = [aws_securityhub_account.main]
}
# Enable AWS Foundational Security Best Practices
resource "aws_securityhub_standards_subscription" "aws_foundational" {
standards_arn = "arn:aws:securityhub:${var.region}::standards/aws-foundational-security-best-practices/v/1.0.0"
depends_on = [aws_securityhub_account.main]
}
# Custom action for remediation
resource "aws_securityhub_action_target" "remediate" {
name = "Remediate"
identifier = "Remediate"
description = "Trigger automated remediation"
depends_on = [aws_securityhub_account.main]
}AWS Config Rules for PCI
# compliance/config-rules.tf
# Encryption at rest
resource "aws_config_config_rule" "s3_bucket_encryption" {
name = "s3-bucket-server-side-encryption-enabled"
source {
owner = "AWS"
source_identifier = "S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED"
}
tags = {
Compliance = "pci-dss"
Requirement = "3.4"
}
}
resource "aws_config_config_rule" "rds_encryption" {
name = "rds-storage-encrypted"
source {
owner = "AWS"
source_identifier = "RDS_STORAGE_ENCRYPTED"
}
tags = {
Compliance = "pci-dss"
Requirement = "3.4"
}
}
# Encryption in transit
resource "aws_config_config_rule" "alb_https" {
name = "alb-http-to-https-redirection-check"
source {
owner = "AWS"
source_identifier = "ALB_HTTP_TO_HTTPS_REDIRECTION_CHECK"
}
tags = {
Compliance = "pci-dss"
Requirement = "4.1"
}
}
# Access control
resource "aws_config_config_rule" "iam_root_mfa" {
name = "root-account-mfa-enabled"
source {
owner = "AWS"
source_identifier = "ROOT_ACCOUNT_MFA_ENABLED"
}
tags = {
Compliance = "pci-dss"
Requirement = "8.3"
}
}
resource "aws_config_config_rule" "iam_user_mfa" {
name = "iam-user-mfa-enabled"
source {
owner = "AWS"
source_identifier = "IAM_USER_MFA_ENABLED"
}
tags = {
Compliance = "pci-dss"
Requirement = "8.3"
}
}
# Network security
resource "aws_config_config_rule" "restricted_ssh" {
name = "restricted-ssh"
source {
owner = "AWS"
source_identifier = "INCOMING_SSH_DISABLED"
}
tags = {
Compliance = "pci-dss"
Requirement = "1.2"
}
}
resource "aws_config_config_rule" "vpc_flow_logs" {
name = "vpc-flow-logs-enabled"
source {
owner = "AWS"
source_identifier = "VPC_FLOW_LOGS_ENABLED"
}
tags = {
Compliance = "pci-dss"
Requirement = "10.1"
}
}Access Control
flowchart TD
subgraph AccessFlow["Access Control Flow"]
USER[User] --> SSO[AWS SSO]
SSO --> MFA{MFA Required}
MFA -->|Pass| ROLE[Assume Role]
MFA -->|Fail| DENY[Access Denied]
ROLE --> POLICY{Policy Check}
POLICY -->|Allow| ACCESS[Access Granted]
POLICY -->|Deny| DENY
ACCESS --> AUDIT[Audit Log]
end
subgraph Policies["Policy Layers"]
SCP[Service Control Policy]
IAM[IAM Policy]
RBAC[K8s RBAC]
SCP --> IAM --> RBAC
end
style AccessFlow fill:#264653,stroke:#fff,stroke-width:2px,color:#fff
style Policies fill:#e63946,stroke:#fff,stroke-width:2px,color:#fff
IAM Policies for CDE Access
# iam/cde-access.tf
# Policy for CDE administrators
resource "aws_iam_policy" "cde_admin" {
name = "CDEAdministrator"
description = "Full access to CDE resources with MFA required"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "RequireMFA"
Effect = "Deny"
Action = "*"
Resource = "*"
Condition = {
BoolIfExists = {
"aws:MultiFactorAuthPresent" = "false"
}
}
},
{
Sid = "CDEResourceAccess"
Effect = "Allow"
Action = [
"ec2:*",
"rds:*",
"elasticache:*",
"eks:*",
"kms:*"
]
Resource = "*"
Condition = {
StringEquals = {
"aws:ResourceTag/Compliance" = "pci-dss"
}
}
},
{
Sid = "DenyUnencryptedResources"
Effect = "Deny"
Action = [
"rds:CreateDBInstance",
"rds:CreateDBCluster"
]
Resource = "*"
Condition = {
Bool = {
"rds:StorageEncrypted" = "false"
}
}
}
]
})
}
# Read-only policy for auditors
resource "aws_iam_policy" "cde_auditor" {
name = "CDEAuditor"
description = "Read-only access for PCI auditors"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "ReadOnlyAccess"
Effect = "Allow"
Action = [
"ec2:Describe*",
"rds:Describe*",
"elasticache:Describe*",
"eks:Describe*",
"eks:List*",
"kms:Describe*",
"kms:List*",
"cloudtrail:LookupEvents",
"cloudtrail:GetTrailStatus",
"config:Get*",
"config:Describe*",
"config:List*",
"securityhub:Get*",
"securityhub:List*",
"logs:Get*",
"logs:Describe*",
"logs:FilterLogEvents"
]
Resource = "*"
},
{
Sid = "DenyDataAccess"
Effect = "Deny"
Action = [
"s3:GetObject",
"rds:Download*",
"logs:GetLogEvents"
]
Resource = [
"arn:aws:s3:::*cardholder*/*",
"arn:aws:rds:*:*:*cde*"
]
}
]
})
}Best Practices
| Practice | PCI Requirement | Implementation |
|---|---|---|
| Network segmentation | 1.3 | Separate VPCs, NACLs, Security Groups |
| Encryption at rest | 3.4 | KMS, CloudHSM, RDS encryption |
| Encryption in transit | 4.1 | TLS 1.3, ACM, no HTTP |
| Access logging | 10.1 | CloudTrail, VPC Flow Logs |
| Log retention | 10.7 | 7 year retention, immutable storage |
| MFA everywhere | 8.3 | IAM MFA, SSO MFA |
| Vulnerability scanning | 11.2 | Inspector, ECR scanning |
| Change detection | 11.5 | Config Rules, Security Hub |
Troubleshooting
"Security Hub finding: S3 bucket not encrypted"
# Check bucket encryption
aws s3api get-bucket-encryption --bucket my-bucket
# Enable encryption
aws s3api put-bucket-encryption --bucket my-bucket \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "alias/cde-master"
}
}]
}'"Config rule non-compliant: VPC flow logs"
# Check flow logs
aws ec2 describe-flow-logs --filter "Name=resource-id,Values=vpc-xxxxx"
# Enable flow logs
aws ec2 create-flow-logs \
--resource-type VPC \
--resource-ids vpc-xxxxx \
--traffic-type ALL \
--log-destination-type s3 \
--log-destination arn:aws:s3:::audit-logs/vpc-flow-logsConclusion
PCI-DSS compliance on AWS is achievable with the right architecture. The key principles:
- Defense in depth - Multiple layers of security controls
- Encryption everywhere - At rest, in transit, in use
- Log everything - Immutable, long-term retention
- Automate compliance - Continuous monitoring, not point-in-time audits
- Least privilege - MFA required, short-lived credentials
The combination of AWS native services (KMS, CloudHSM, Security Hub) with proper network segmentation creates a platform that satisfies even the most demanding QSAs while remaining operationally manageable.