PCI DSS 4.0.1 Verification Evidence
This document provides control-by-control evidence for PCI DSS 4.0.1 compliance. Each requirement section includes two layers of verification:
- Source Evidence — Terraform resources and code that implement the control
- Deployed Verification —
gcloudcommands to confirm live infrastructure matches
Environment variables: All commands use
$PCI_PROJECTand$CORE_PROJECTplaceholders. Set these before running:export PCI_PROJECT="gatelithix-pci" export CORE_PROJECT="gatelithix-core"
Requirement 1: Network Segmentation
Source Evidence
| PCI Control | Requirement | Evidence Type | Evidence Location | Status |
|---|---|---|---|---|
| 1.2.1 | Network segmentation controls installed | Terraform | infra/terraform/modules/vpc/main.tf — deny_all_ingress (priority 65534, deny all protocols, source 0.0.0.0/0) and deny_all_egress (priority 65534, deny all protocols, dest 0.0.0.0/0) | Verified |
| 1.3.1 | Inbound CDE traffic restricted | Terraform | infra/terraform/pci/network.tf — allow_core_to_pci (priority 900, TCP 443 only, source_ranges 10.0.0.0/20) | Verified |
| 1.3.2 | Outbound CDE traffic restricted | Terraform | infra/terraform/pci/network.tf — allow_psp_egress (priority 900, TCP 443 only, target_tags psp-egress) and allow_restricted_apis_egress (TCP 443, dest 199.36.153.4/30) | Verified |
| 1.4.1 | Firewall between internet and CDE | Terraform | infra/terraform/pci/cloudrun.tf — vault service ingress = "INGRESS_TRAFFIC_INTERNAL_ONLY" (no direct internet access to CDE) | Verified |
VPC peering: Core and PCI VPCs are connected via bidirectional peering (core_to_pci / pci_to_core in infra/terraform/core/network.tf). Custom route import/export is disabled — only private IP traffic crosses the peering link.
Deployed Verification
List all PCI VPC firewall rules:
gcloud compute firewall-rules list \
--project=$PCI_PROJECT \
--filter="network:pci-vpc" \
--format="table(name, direction, priority, allowed[].map().firewall_rule().list():label=ALLOWED, sourceRanges, destinationRanges)"Expected output: pci-vpc-deny-all-ingress and pci-vpc-deny-all-egress at priority 65534 (deny all). pci-vpc-allow-core-ingress, pci-vpc-allow-psp-egress, pci-vpc-allow-restricted-apis at priority 900 (TCP 443 only).
Verify vault service ingress restriction:
gcloud run services describe vault \
--project=$PCI_PROJECT \
--region=us-central1 \
--format="value(spec.template.metadata.annotations['run.googleapis.com/ingress'])"Expected output: internal
Verify VPC peering exists:
gcloud compute networks peerings list \
--project=$CORE_PROJECT \
--network=core-vpc \
--format="table(name, network, peerNetwork, state)"Expected output: core-to-pci peering in ACTIVE state.
Migration Pipeline CDE Isolation
The automated database migration pipeline enforces CDE isolation at every layer:
- Image isolation: Core and PCI migration images are built and stored in separate Artifact Registry repositories. PCI Cloud Run Jobs pull exclusively from the PCI registry — no cross-CDE image dependencies.
- VPC-internal execution: Migration Cloud Run Jobs run inside the VPC via VPC connectors (
core-vpc-connectorfor core,pci-vpc-connectorfor PCI). No public network access to databases. - Credential handling: Database passwords are injected at runtime from Secret Manager (
core-db-password,pci-db-password). No credentials are baked into container images. - Schema-only migrations: Migration files contain only DDL statements (CREATE TABLE, ALTER TABLE). No SELECT, INSERT, UPDATE, or DELETE of cardholder data. Verified by CI migration validation job.
- Least-privilege execution: Core migrations run as
gateway-sa, PCI migrations run asvault-sa— each with only the permissions needed for their respective database. - Audit trail: Every migration execution is logged in Cloud Run Job execution history and Cloud Logging with full traceability to the triggering commit SHA.
Deployed Verification:
# Verify migration job uses VPC connector (no public access)
gcloud run jobs describe migrate-vault --project=$PCI_PROJECT --region=us-central1 --format="value(template.vpcAccess.connector)"
# Verify migration image comes from PCI registry (not core)
gcloud run jobs describe migrate-vault --project=$PCI_PROJECT --region=us-central1 --format="value(template.containers[0].image)"
# Verify no secrets baked into image
gcloud run jobs describe migrate-vault --project=$PCI_PROJECT --region=us-central1 --format="yaml(template.containers[0].env)"Expected output: VPC connector should be pci-vpc-connector. Image URI should reference the PCI Artifact Registry (gatelithix-pci). Environment variables should reference Secret Manager secret references, not plaintext values.
Requirement 3: Protect Stored Account Data
Source Evidence
| PCI Control | Requirement | Evidence Type | Evidence Location | Status |
|---|---|---|---|---|
| 3.5.1 | PAN encrypted with strong cryptography | Terraform | infra/terraform/pci/kms.tf — pan_encryption_key with protection_level = "HSM" and algorithm = "GOOGLE_SYMMETRIC_ENCRYPTION" | Verified |
| 3.5.1.1 | PAN fingerprint uses one-way hash | Terraform | infra/terraform/pci/kms.tf — pan_fingerprint_key with purpose = "MAC", algorithm = "HMAC_SHA256", protection_level = "HSM" | Verified |
| 3.6.1 | Key management procedures documented | Terraform | infra/terraform/pci/kms.tf — rotation_period = "7776000s" (90 days automatic rotation), prevent_destroy = true lifecycle | Verified |
PAN data flow: PAN enters the system only through the vault service. The vault encrypts PAN at ingress using Cloud KMS HSM-backed keys. The gateway and all other services receive only token references (UUIDs) — PAN never leaves the CDE unencrypted.
Webhook signing secrets: Connector webhook signing secrets (Stripe, NMI, FluidPay) are stored in Google Secret Manager and injected into Cloud Run services at deployment time. This follows the same Secret Manager pattern as other sensitive credentials, satisfying PCI Req 3-4 cryptographic key management requirements. No webhook signing secrets are hardcoded or stored in source control.
Deployed Verification
Verify PAN encryption key configuration:
gcloud kms keys describe pan-encryption-key \
--keyring=gatelithix-vault \
--location=us-central1 \
--project=$PCI_PROJECT \
--format="yaml(purpose, versionTemplate.protectionLevel, versionTemplate.algorithm, rotationPeriod, nextRotationTime)"Expected output: protectionLevel: HSM, algorithm: GOOGLE_SYMMETRIC_ENCRYPTION, rotationPeriod: 7776000s.
Verify PAN fingerprint key configuration:
gcloud kms keys describe pan-fingerprint-key \
--keyring=gatelithix-vault \
--location=us-central1 \
--project=$PCI_PROJECT \
--format="yaml(purpose, versionTemplate.protectionLevel, versionTemplate.algorithm)"Expected output: purpose: MAC, protectionLevel: HSM, algorithm: HMAC_SHA256.
List all key versions (verify rotation is occurring):
gcloud kms keys versions list \
--key=pan-encryption-key \
--keyring=gatelithix-vault \
--location=us-central1 \
--project=$PCI_PROJECT \
--format="table(name, state, createTime)"Requirement 4: Encrypt Transmission of Cardholder Data
Source Evidence
| PCI Control | Requirement | Evidence Type | Evidence Location | Status |
|---|---|---|---|---|
| 4.2.1 | PAN encrypted during transmission over open networks | Terraform + Architecture | VPC peering (private network, no internet transit) + firewall rules restricting to TCP 443 only. Gateway-to-vault communication is internal HTTPS over VPC peering. | Verified |
| 4.2.1.1 | Trusted certificates for PAN transmission | Cloud Run | Cloud Run services use Google-managed TLS certificates. Internal service-to-service calls use Cloud Run identity tokens over HTTPS. | Verified |
Architecture note: PAN is transmitted only on two paths:
- Client to vault — via gateway proxy, HTTPS enforced by Cloud Load Balancer + Cloud Armor WAF
- Vault to KMS — internal Google API call over private network (
restricted.googleapis.comat 199.36.153.4/30)
No PAN traverses the public internet in plaintext at any point.
Deployed Verification
Verify Cloud Run services use HTTPS:
gcloud run services describe vault \
--project=$PCI_PROJECT \
--region=us-central1 \
--format="value(status.url)"Expected output: URL starting with https://.
Verify restricted Google API egress:
gcloud compute firewall-rules describe pci-vpc-allow-restricted-apis \
--project=$PCI_PROJECT \
--format="yaml(allowed, destinationRanges, direction)"Expected output: destinationRanges: 199.36.153.4/30, allowed: tcp:443.
Requirement 7: Restrict Access to System Components
Source Evidence
| PCI Control | Requirement | Evidence Type | Evidence Location | Status |
|---|---|---|---|---|
| 7.2.1 | Access based on business need to know | Terraform | infra/terraform/pci/kms.tf — IAM binding vault_encrypter restricts roles/cloudkms.cryptoKeyEncrypterDecrypter to vault_sa_email only. IAM binding vault_mac_signer restricts roles/cloudkms.signerVerifier to vault_sa_email only. | Verified |
| 7.2.2 | Privileges assigned by job function | Terraform | Per-service service accounts: gateway_sa_email (gateway), vault_sa_email (vault), connector_sa_email (all connectors). Each SA has only the roles needed for its function. | Verified |
| 7.2.3 | Default deny on all system components | Terraform | infra/terraform/modules/vpc/main.tf — deny-all firewall baseline. Cloud Run IAM — only explicitly granted invokers can call services. | Verified |
Cross-service access matrix:
| Source Service | Target Service | IAM Role | Evidence |
|---|---|---|---|
| gateway_sa | vault | roles/run.invoker | pci/cloudrun.tf — gateway_invokes_vault |
| gateway_sa | stripe-connector | roles/run.invoker | core/cloudrun.tf — gateway_invokes_stripe |
| gateway_sa | nmi-connector | roles/run.invoker | core/cloudrun.tf — gateway_invokes_nmi |
| gateway_sa | fluidpay-connector | roles/run.invoker | core/cloudrun.tf — gateway_invokes_fluidpay |
| vault_sa | KMS pan-encryption-key | roles/cloudkms.cryptoKeyEncrypterDecrypter | pci/kms.tf — vault_encrypter |
| vault_sa | KMS pan-fingerprint-key | roles/cloudkms.signerVerifier | pci/kms.tf — vault_mac_signer |
Deployed Verification
Verify KMS IAM policy (only vault SA has access):
gcloud kms keys get-iam-policy pan-encryption-key \
--keyring=gatelithix-vault \
--location=us-central1 \
--project=$PCI_PROJECT \
--format="yaml(bindings)"Expected output: Single binding with role: roles/cloudkms.cryptoKeyEncrypterDecrypter and only vault_sa_email as member.
List service accounts in PCI project:
gcloud iam service-accounts list \
--project=$PCI_PROJECT \
--format="table(email, displayName, disabled)"List service accounts in core project:
gcloud iam service-accounts list \
--project=$CORE_PROJECT \
--format="table(email, displayName, disabled)"Verify Cloud Run service account assignments:
gcloud run services describe vault \
--project=$PCI_PROJECT \
--region=us-central1 \
--format="value(spec.template.spec.serviceAccountName)"
gcloud run services describe api-gateway \
--project=$CORE_PROJECT \
--region=us-central1 \
--format="value(spec.template.spec.serviceAccountName)"Requirement 8: Identify Users and Authenticate Access
Source Evidence
| PCI Control | Requirement | Evidence Type | Evidence Location | Status |
|---|---|---|---|---|
| 8.3.1 | MFA for all access into CDE | Configuration | Auth0 tenant MFA policy (Guardian push + TOTP). All admin users must complete MFA to access the merchant portal. | Verified |
| 8.3.2 | Strong cryptography for authentication | Code | apps/gateway/ — Auth0 JWT validation using JWKS endpoint with RS256 algorithm. API keys use SHA-256 hashing. | Verified |
| 8.4.2 | MFA for remote network access to CDE | Configuration | GCP IAM — Organization policy requires MFA for all console and API access. Cloud Run admin operations require authenticated IAM principals. | Verified |
| 8.6.1 | Service accounts managed with least privilege | Terraform | Per-service SAs with specific role bindings. No wildcard roles. No service account key files (workload identity for CI/CD). | Verified |
Deployed Verification
Verify GCP IAM policy on PCI project (no allUsers/allAuthenticatedUsers):
gcloud projects get-iam-policy $PCI_PROJECT \
--format="yaml(bindings)" | grep -E "allUsers|allAuthenticatedUsers" || echo "PASS: No public access"Verify Cloud Run services require authentication:
gcloud run services get-iam-policy vault \
--project=$PCI_PROJECT \
--region=us-central1 \
--format="yaml(bindings)"Expected output: No allUsers binding — only specific service accounts.
Requirement 10: Log and Monitor All Access
Source Evidence
| PCI Control | Requirement | Evidence Type | Evidence Location | Status |
|---|---|---|---|---|
| 10.2.1 | Audit logs enabled for all CDE components | Terraform | infra/terraform/pci/logging.tf — pci_audit_sink with filter logName:"cloudaudit.googleapis.com" routes all Cloud Audit Logs to locked bucket | Verified |
| 10.3.3 | Audit logs protected from modification | Terraform | infra/terraform/pci/logging.tf — locked = true on pci_audit_logs bucket, prevent_destroy = true lifecycle | Verified |
| 10.5.1 | Audit log retention >= 12 months | Terraform | infra/terraform/pci/logging.tf — Cloud Logging bucket retention_days = 365. Cloud Storage archive bucket retention_period = 31536000 (365 days), is_locked = true (cannot be shortened) | Verified |
| 10.6.3 | Archival of audit logs | Terraform | infra/terraform/pci/logging.tf — pci_audit_archive Cloud Storage bucket with NEARLINE storage class, versioning enabled, locked retention policy. pci_audit_archive_sink exports to storage. | Verified |
Deployed Verification
Verify locked logging bucket:
gcloud logging buckets describe pci-audit-logs \
--location=us-central1 \
--project=$PCI_PROJECT \
--format="yaml(retentionDays, locked, lifecycleState)"Expected output: retentionDays: 365, locked: true, lifecycleState: ACTIVE.
Verify log sink is routing audit logs:
gcloud logging sinks describe pci-audit-sink \
--project=$PCI_PROJECT \
--format="yaml(destination, filter)"Expected output: filter: logName:"cloudaudit.googleapis.com", destination pointing to pci-audit-logs bucket.
Verify Cloud Storage archive bucket retention:
gcloud storage buckets describe gs://${PCI_PROJECT}-pci-audit-archive \
--format="yaml(retention_policy)"Expected output: retentionPeriod: 31536000, isLocked: true.
Requirement 11: Test Security of Systems and Networks
Source Evidence
| PCI Control | Requirement | Evidence Type | Evidence Location | Status |
|---|---|---|---|---|
| 11.3.1 | Internal vulnerability scans performed | CI/CD | .github/workflows/ci.yml — security job runs gosec ./... on every PR and push to develop. Static analysis for Go security vulnerabilities (SQL injection, hardcoded credentials, weak crypto). | Verified |
| 11.3.2 | External vulnerability scans performed | CI/CD | .github/workflows/ci.yml — scan job runs Trivy container scan (aquasecurity/trivy-action) with severity HIGH,CRITICAL and exit-code: 1 (blocks merge). Scans against CVE databases. | Verified |
| 11.6.1 | Change detection on payment pages | N/A | Not applicable — Gatelithix Gateway is API-only. No hosted payment pages. Merchants integrate via API and SDKs. | N/A |
CI scanning pipeline:
| Scan Type | Tool | Trigger | Severity Threshold | Evidence |
|---|---|---|---|---|
| Static analysis | gosec | PR + push to develop | All findings | .github/workflows/ci.yml — security job |
| Container scan | Trivy (image mode) | Push to develop | HIGH, CRITICAL | .github/workflows/ci.yml — scan job |
| Dependency audit | Go module verification | PR + push | Build failure on vuln | go test -race catches incompatible deps |
Deployed Verification
Verify CI workflow exists and contains scan jobs:
# Review the CI workflow file for scanning steps
cat .github/workflows/ci.yml | grep -A 5 "gosec\|trivy\|vulnerability"Verify recent CI runs include security scans:
Review GitHub Actions workflow runs at:
https://github.com/<org>/gatelithix-gateway/actions/workflows/ci.yml
Each successful run should show Security Scan and Container Scan jobs completed.
Requirement 9: Physical Security (Skipped)
Not in scope. Physical security of data centers is the responsibility of Google Cloud Platform. GCP data centers undergo independent SOC 2 Type II and PCI DSS audits. Reference: Google Cloud Compliance .
Gatelithix Gateway runs entirely on Cloud Run (serverless) with no customer-managed physical infrastructure.