PCI-DSS 4.0 Req 10.3 requires that audit logs are protected from destruction and unauthorized modifications; Req 10.4 mandates that log content is reviewed at least once daily. Logs stored only on the same server they monitor can be deleted by an attacker to cover their tracks. CWE-778 (Insufficient Logging) combined with writable log storage means a compromised host can silence its own forensic record. NIST AU-9 requires protection of audit information. S3 Object Lock (WORM) combined with KMS encryption satisfies Req 10.3 — an attacker with application-level access cannot delete an Object Lock-protected log archive.
Medium because logs stored in mutable, non-centralized locations can be deleted by a compromised component, eliminating forensic evidence and violating PCI-DSS 4.0 Req 10.3's tamper-evidence requirement.
Forward logs from at least two sources (application, database) to a centralized log group, then export to an S3 bucket with Object Lock enabled so logs are write-once and cannot be deleted. Encrypt the archive with a KMS key that is managed separately from the CDE application credentials.
resource "aws_s3_bucket_object_lock_configuration" "log_archive" {
bucket = aws_s3_bucket.log_archive.id
rule {
default_retention {
mode = "GOVERNANCE"
days = 365
}
}
}
Add a CloudWatch Subscription Filter to stream CDE log groups into the central archive log group. Document the centralization topology in docs/pci-compliance.md — a QSA will ask to see how Req 10.3 tamper protection is implemented.
ID: ecommerce-pci.monitoring-compliance.log-centralization
Severity: medium
What to look for: Count all log sources in the project (application logs, database logs, infrastructure logs, access logs). For each source, check whether logs are forwarded to a central location. Count the number of integrity verification mechanisms: HMAC/hash signing, immutable storage (S3 Object Lock, WORM), checksums, or audit trail services. Enumerate all log stream/subscription/forwarding configurations.
Pass criteria: At least 2 log sources are forwarded to a central location (CloudWatch, ELK, Splunk, Datadog, or equivalent). At least 1 log integrity verification mechanism exists (cryptographic signing, immutable storage with object lock, or checksum validation). Report: "X log sources centralized, Y integrity verification mechanisms."
Fail criteria: Logs stored in isolated locations without centralization (each service logs locally), or centralized but no integrity verification (0 signing, hashing, or immutable storage configs).
Skip (N/A) when: Payment processing fully delegated to third-party (no local CDE logs generated, no infrastructure logging needed).
Detail on fail: Describe the centralization gap. Example: "Application logs written to local stdout only. 0 of 3 log sources forwarded to central location. No integrity verification." or "2 log sources centralized to CloudWatch but 0 integrity verification mechanisms (no HMAC, no immutable storage)."
Remediation: Implement log centralization with integrity checks. Using AWS CloudWatch + S3 + Athena:
# Central log group
resource "aws_cloudwatch_log_group" "central_audit" {
name = "/audit/central"
retention_in_days = 365
tags = { Name = "central-audit-logs" }
}
# Log stream from multiple sources
resource "aws_cloudwatch_log_stream" "cde_db" {
name = "cde-database"
log_group_name = aws_cloudwatch_log_group.central_audit.name
}
# S3 export for immutable storage
resource "aws_s3_bucket" "log_archive" {
bucket = "audit-log-archive-${data.aws_caller_identity.current.account_id}"
tags = { Name = "immutable-log-archive" }
}
# Prevent object deletion
resource "aws_s3_bucket_object_lock_configuration" "log_archive" {
bucket = aws_s3_bucket.log_archive.id
rule {
default_retention {
mode = "GOVERNANCE"
days = 2555 # ~7 years
}
}
}
# KMS key for encryption
resource "aws_kms_key" "log_key" {
description = "Key for log encryption"
deletion_window_in_days = 10
enable_key_rotation = true
}
resource "aws_s3_bucket_server_side_encryption_configuration" "log_archive" {
bucket = aws_s3_bucket.log_archive.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.log_key.arn
}
}
}
For log integrity verification, use a log signing service:
// src/app/api/audit-log/route.ts
import crypto from 'crypto';
export async function POST(req) {
const { event } = await req.json();
// Create HMAC signature for integrity
const hmac = crypto
.createHmac('sha256', process.env.LOG_SIGNING_KEY)
.update(JSON.stringify(event))
.digest('hex');
const signedLog = {
...event,
signature: `sha256:${hmac}`,
timestamp: new Date().toISOString(),
};
// Store in database and export to immutable storage
await logCentralService.store(signedLog);
return new Response(JSON.stringify({ signed: true }));
}