Skip to Content
CompliancePCI Verification Evidence

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:

  1. Source Evidence — Terraform resources and code that implement the control
  2. Deployed Verificationgcloud commands to confirm live infrastructure matches

Environment variables: All commands use $PCI_PROJECT and $CORE_PROJECT placeholders. Set these before running:

export PCI_PROJECT="gatelithix-pci" export CORE_PROJECT="gatelithix-core"

Requirement 1: Network Segmentation

Source Evidence

PCI ControlRequirementEvidence TypeEvidence LocationStatus
1.2.1Network segmentation controls installedTerraforminfra/terraform/modules/vpc/main.tfdeny_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.1Inbound CDE traffic restrictedTerraforminfra/terraform/pci/network.tfallow_core_to_pci (priority 900, TCP 443 only, source_ranges 10.0.0.0/20)Verified
1.3.2Outbound CDE traffic restrictedTerraforminfra/terraform/pci/network.tfallow_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.1Firewall between internet and CDETerraforminfra/terraform/pci/cloudrun.tf — vault service ingress = "INGRESS_TRAFFIC_ALL". Network isolation enforced at VPC firewall level: pci-vpc-allow-core-ingress restricts inbound traffic to source 10.0.0.0/20 (core VPC only), TCP 443 only. Cloud Run ingress is ALL because the gateway calls vault over VPC peering (private IPs).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: all

Note: Vault uses INGRESS_TRAFFIC_ALL; network isolation is enforced at the VPC firewall level (pci-vpc-allow-core-ingress), not at the Cloud Run ingress setting.

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.

Verify Cloud NAT exists on core VPC (v1.3 addition):

gcloud compute routers nats list \ --router=core-vpc-router \ --region=us-central1 \ --project=$CORE_PROJECT \ --format="table(name, natIpAllocateOption, sourceSubnetworkIpRangesToNat)"

Expected output: NAT config exists with AUTO_ONLY IP allocation and ALL_SUBNETWORKS_ALL_IP_RANGES source range. This confirms public egress from Cloud Run routes through Cloud NAT with auditable source IPs.

Verify Cloud NAT exists on PCI VPC (v1.3 addition):

gcloud compute routers nats list \ --router=pci-vpc-router \ --region=us-central1 \ --project=$PCI_PROJECT \ --format="table(name, natIpAllocateOption, sourceSubnetworkIpRangesToNat)"

Expected output: NAT config exists on the PCI VPC router. Confirms CDE egress through Cloud NAT with auditable source IPs.

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-connector for core, pci-vpc-connector for 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 as vault-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 ControlRequirementEvidence TypeEvidence LocationStatus
3.5.1PAN encrypted with strong cryptographyTerraforminfra/terraform/pci/kms.tfpan_encryption_key with protection_level = "HSM" and algorithm = "GOOGLE_SYMMETRIC_ENCRYPTION"Verified
3.5.1.1PAN fingerprint uses one-way hashTerraforminfra/terraform/pci/kms.tfpan_fingerprint_key with purpose = "MAC", algorithm = "HMAC_SHA256", protection_level = "HSM"Verified
3.6.1Key management procedures documentedTerraforminfra/terraform/pci/kms.tfrotation_period = "7776000s" (90 days automatic rotation), prevent_destroy = true lifecycleVerified

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 ControlRequirementEvidence TypeEvidence LocationStatus
4.2.1PAN encrypted during transmissionTerraform + CodeExternal: Google-managed TLS on Global HTTPS LB (Cloud Armor). Internal: Cloud SQL ssl_mode = ENCRYPTED_ONLY enforces TLS on all database connections; app uses sslmode=require (pgx). Cross-zone calls: VPC peering + TCP 443 only. No public internet transit for PAN.Verified
4.2.1.1Trusted certificates for PAN transmissionTerraformGoogle-managed TLS certificates on Cloud Run services and Cloud SQL instances. Cloud SQL server certs issued by Google CA. Internal service-to-service calls use Cloud Run identity tokens over HTTPS.Verified

Architecture note: PAN is transmitted only on two paths:

  1. Client to vault — via gateway proxy, HTTPS enforced by Cloud Load Balancer + Cloud Armor WAF
  2. Vault to KMS — internal Google API call over private network (restricted.googleapis.com at 199.36.153.4/30)
  3. App to Cloud SQL — TLS enforced client-side (sslmode=require in pgx and goose) and server-side (ssl_mode=ENCRYPTED_ONLY on Cloud SQL)

No PAN traverses the public internet in plaintext at any point. All database connections are encrypted even over private VPC networks (defense-in-depth).

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 Cloud SQL enforces TLS (both instances):

gcloud sql instances describe gatelithix-core-pg \ --project=$CORE_PROJECT \ --format="value(settings.ipConfiguration.sslMode)" gcloud sql instances describe gatelithix-pci-pg \ --project=$PCI_PROJECT \ --format="value(settings.ipConfiguration.sslMode)"

Expected output: ENCRYPTED_ONLY for both instances. This rejects any non-TLS connection attempt.

Verify app code uses sslmode=require:

grep "sslmode" pkg/database/database.go db/Dockerfile.migrate

Expected output: sslmode=require in both the Go pool builder and the goose migration entrypoint.

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 ControlRequirementEvidence TypeEvidence LocationStatus
7.2.1Access based on business need to knowTerraforminfra/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.2Privileges assigned by job functionTerraformPer-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.3Default deny on all system componentsTerraforminfra/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 ServiceTarget ServiceIAM RoleEvidence
gateway_savaultroles/run.invokerpci/cloudrun.tfgateway_invokes_vault
gateway_sastripe-connectorroles/run.invokercore/cloudrun.tfgateway_invokes_stripe
gateway_sanmi-connectorroles/run.invokercore/cloudrun.tfgateway_invokes_nmi
gateway_safluidpay-connectorroles/run.invokercore/cloudrun.tfgateway_invokes_fluidpay
vault_saKMS pan-encryption-keyroles/cloudkms.cryptoKeyEncrypterDecrypterpci/kms.tfvault_encrypter
vault_saKMS pan-fingerprint-keyroles/cloudkms.signerVerifierpci/kms.tfvault_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)"

Application-Level Access Control (v1.3 addition)

Verify RBAC middleware covers /admin endpoints:

grep -n "admin\|RBAC\|RequireRole\|Forbidden" \ apps/gateway/main.go \ pkg/auth/rbac.go

Expected output: Lines showing RequireRole middleware applied to /admin route groups, with role checks and 403 responses for insufficient permissions.

Verify merchant-scoped payment reads:

grep -rn "merchant_id\|MerchantID" \ pkg/payment/ \ --include="*.go" | grep -i "where\|filter\|query"

Expected output: Payment store query methods include merchant_id as a filter parameter, confirming tenant isolation at the data access layer.


Requirement 8: Identify Users and Authenticate Access

Source Evidence

PCI ControlRequirementEvidence TypeEvidence LocationStatus
8.3.1MFA for all access into CDEConfigurationAuth0 tenant MFA policy (Guardian push + TOTP). All admin users must complete MFA to access the merchant portal.Verified
8.3.2Strong cryptography for authenticationCodeapps/gateway/ — Auth0 JWT validation using JWKS endpoint with RS256 algorithm. API keys use SHA-256 hashing.Verified
8.4.2MFA for remote network access to CDEConfigurationGCP IAM — Organization policy requires MFA for all console and API access. Cloud Run admin operations require authenticated IAM principals.Verified
8.6.1Service accounts managed with least privilegeTerraformPer-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.

Per-Merchant HMAC Secrets (v1.3 addition — Req 8.3.2)

Verify hmac_secret column exists in api_keys table:

grep -n "hmac_secret" \ db/migrations/023_add_hmac_secret_to_api_keys.sql

Expected output: Column definition adding hmac_secret to api_keys table.

Verify per-key HMAC resolver does not use global secret:

grep -n "hmac_secret\|LookupSecret\|ResolveSecret\|ResolveHMACSecret" \ pkg/hmac/resolver.go

Expected output: Resolver queries api_keys table to load per-key secret via store.GetByID, with no reference to a global environment variable for HMAC signing.


Requirement 10: Log and Monitor All Access

Source Evidence

PCI ControlRequirementEvidence TypeEvidence LocationStatus
10.2.1Audit logs enabled for all CDE componentsTerraforminfra/terraform/pci/logging.tfpci_audit_sink with filter logName:"cloudaudit.googleapis.com" routes all Cloud Audit Logs to locked bucketVerified
10.3.3Audit logs protected from modificationTerraforminfra/terraform/pci/logging.tflocked = true on pci_audit_logs bucket, prevent_destroy = true lifecycleVerified
10.5.1Audit log retention >= 12 monthsTerraforminfra/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.3Archival of audit logsTerraforminfra/terraform/pci/logging.tfpci_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 ControlRequirementEvidence TypeEvidence LocationStatus
11.3.1Internal vulnerability scans performedCI/CD.github/workflows/ci.ymlsecurity 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.2External vulnerability scans performedCI/CD.github/workflows/ci.ymlscan 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.1Change detection on payment pagesN/ANot applicable — Gatelithix Gateway is API-only. No hosted payment pages. Merchants integrate via API and SDKs.N/A

CI scanning pipeline:

Scan TypeToolTriggerSeverity ThresholdEvidence
Static analysisgosecPR + push to developAll findings.github/workflows/ci.ymlsecurity job
Container scanTrivy (image mode)Push to developHIGH, CRITICAL.github/workflows/ci.ymlscan job
Dependency auditGo module verificationPR + pushBuild failure on vulngo 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.