Skip to Content
OperationsPCI Architecture

PCI Architecture

This page is the technical reference for Gatelithix’s PCI DSS 4.0.1 architecture. It covers the CDE boundary definition, PAN data flow from ingress to destruction, network segmentation, encryption scheme, audit logging, and access control — with source evidence for each control.

CDE edit policy: The vault service (apps/vault/) is the CDE boundary. Any code change inside this boundary requires explicit confirmation before proceeding. Do not modify vault internals as a side effect of other work. See CLAUDE.md for the full rule set.


CDE Boundary Definition

The Cardholder Data Environment (CDE) is strictly limited to the gatelithix-pci GCP project. No system outside this project ever stores, processes, or receives raw PAN data.

Systems In CDE (gatelithix-pci project)

SystemRoleStores PAN?Processes PAN?Transmits PAN?
Token Vault (Cloud Run, apps/vault/)PAN encryption, tokenization, resolveNo (in-memory only)YesYes (to connectors, TLS)
PCI Cloud SQL (PostgreSQL 16)Encrypted PAN storageYes (ciphertext only)NoNo
Cloud KMS / HSMKey management, encrypt/decrypt APINoYes (envelope encryption)No
PCI VPC (10.1.0.0/20)Network isolation boundaryNoNoYes (transit only)
Cloud NAT (PCI)Auditable egress routingNoNoNo

Systems Connected to CDE (gatelithix-core project)

These systems communicate with the CDE but never handle raw PANs. They are in scope for PCI assessment with a reduced applicable control set.

SystemRoleWhy Connected?
Gateway Public APIAPI gateway, routing, orchestrationProxies tokenization to vault; resolves tokens
Stripe ConnectorPSP integrationReceives decrypted PAN from vault for processor tokenization
NMI ConnectorPSP integrationSame as above
FluidPay ConnectorPSP integrationSame as above
TSYS Connector (planned)PSP integration (Transnox REST API)Same as above
Core Cloud SQLPayment state, merchant configStores gateway tokens (tok_...) that reference vault records

Systems Out of Scope

SystemWhy Out of Scope?
Dashboard PortalCommunicates with Gateway API only; never touches vault or PAN
Docs SiteStatic content; no API or data connectivity
Auth0 (IdP)Authentication only; no cardholder data
Cloudflare (DNS)DNS resolution only; does not proxy API traffic
Billing & InvoicingOperates on aggregated transaction counts; no PAN or token access
Reporting / ExportsReads from core DB (tokens, metadata); no vault access

PAN Data Flow

Flow 1: Card Tokenization (PAN Ingress)

When a merchant tokenizes a card, the PAN follows this path:

Key guarantee: The raw PAN exists only in vault process memory for the duration of the encrypt call. The Go []byte slice is zeroed with defer before the handler returns. It is never written to disk, logged, or transmitted to any non-PCI system.

Flow 2: Payment Authorization (PAN Egress via Processor Token)

When a payment is authorized, the gateway resolves a processor token from the vault:

PAN Existence Matrix

Every location where a PAN exists, in any form:

LocationFormMax DurationProtectionDestruction
TLS channel (merchant → LB)Plaintext in TLS envelopeMillisecondsTLS 1.2+Connection close
TLS channel (LB → gateway)Plaintext in TLS envelopeMillisecondsTLS 1.2+ (Cloud Run)Connection close
TLS channel (gateway → vault)Plaintext in TLS envelopeMillisecondsTLS 1.2+ + identity tokenConnection close
Vault process memory (tokenize)Plaintext bytes, Go []byte< 100msProcess isolation (Cloud Run)defer byte-zero
Cloud KMS HSMInside HSM boundaryMicrosecondsFIPS 140-2 Level 3 HSMHSM internal wipe
PCI Cloud SQLAES-256-GCM ciphertext + wrapped DEKIndefinitePrivate IP, TLS, CMEKCrypto-shredding via key rotation
Vault process memory (resolve)Plaintext bytes, Go []byte< 100msProcess isolation (Cloud Run)defer byte-zero
TLS channel (vault → connector)Plaintext in TLS envelopeMillisecondsTLS 1.2+ + identity tokenConnection close
Connector process memoryPlaintext string< 200msProcess isolation (Cloud Run)GC after request scope
TLS channel (connector → PSP)Plaintext in TLS envelopeMillisecondsTLS 1.2+ (PSP-enforced)Connection close

PAN is NEVER present in:

  • Core Cloud SQL (gateway database)
  • Redis (rate limiting / caching)
  • Cloud Pub/Sub messages
  • Application logs (PAN-filter slog handler)
  • Error responses to merchants
  • Audit log entries (log token ref, BIN, last4 — never PAN)
  • Dashboard or reporting services

Code references:

FilePAN Handling
apps/vault/handlers/tokenize.go:104-110PAN received as []byte, defer byte-zero
apps/vault/handlers/tokenize.go:194encryptor.EncryptPAN(ctx, pan) — KMS encryption
apps/vault/handlers/resolve.go:142encryptor.DecryptPAN(ctx, card.ID, encryptedPAN) — KMS decryption
apps/vault/handlers/resolve.go:153-157defer byte-zero of decrypted PAN
apps/vault/handlers/resolve.go:160-166PAN sent to connector via TokenizeRequest
apps/connectors/stripe/client.goTest mode: PAN mapped to test token; Production: PAN sent to Stripe API

Network Segmentation

VPC Architecture

Two separate GCP projects, each with its own VPC, connected via VPC peering:

VPCCIDRProjectPurpose
core-vpc10.0.0.0/20gatelithix-coreGateway API, connectors, Core Cloud SQL, Redis
pci-vpc10.1.0.0/20gatelithix-pciVault service, PCI Cloud SQL, Cloud KMS

VPC peering is bidirectional (core-to-pci / pci-to-core) with custom route import/export disabled. Only private IP traffic crosses the peering link. The PCI VPC cannot reach arbitrary core resources.

Firewall Rules

Both VPCs inherit a deny-all baseline at priority 65534 (deny all ingress from 0.0.0.0/0, deny all egress to 0.0.0.0/0). Explicit allow rules at priority 900 permit only minimum required traffic:

RuleDirectionSource/DestinationPortPurpose
pci-vpc-allow-core-ingressIngress10.0.0.0/20TCP 443Gateway → vault calls
pci-vpc-allow-psp-egressEgress0.0.0.0/0TCP 443Vault → PSP APIs
pci-vpc-allow-restricted-apisEgress199.36.153.4/30TCP 443Vault → Cloud KMS (restricted APIs)
core-vpc-allow-pci-egressEgress10.1.0.0/20TCP 443Gateway → vault calls
core-vpc-allow-psp-egressEgress0.0.0.0/0TCP 443Connectors → PSP APIs

Evidence: infra/terraform/pci/network.tf, infra/terraform/modules/vpc/main.tf

PCI Cloud SQL Isolation

  • ipv4_enabled = false — no public IP address; private IP only
  • ssl_mode = ENCRYPTED_ONLY — rejects non-TLS database connections
  • Application uses pgx with sslmode=require
  • Accessible only from the PCI VPC serverless connector (10.1.32.0/28)

Evidence: infra/terraform/pci/database.tf

Vault Cloud Run Ingress

The vault Cloud Run service uses INGRESS_TRAFFIC_ALL (required because gateway calls arrive via VPC peering private IPs, not through Cloud Run’s public URL). Network isolation is enforced at the VPC firewall level: pci-vpc-allow-core-ingress restricts inbound traffic to source 10.0.0.0/20, TCP 443 only. There is no public internet path to the vault.

Evidence: infra/terraform/pci/cloudrun.tf


Encryption

Scheme Overview

Gatelithix uses envelope encryption via Google Cloud KMS backed by Cloud HSM:

  1. Data Encryption Key (DEK) — A per-PAN AES-256-GCM key generated for each card.
  2. Key Encryption Key (KEK) — The Cloud KMS master key (pan-encryption-key) wraps the DEK. The KEK never leaves the HSM.
  3. Storage — PCI Cloud SQL stores the encrypted PAN ciphertext and the wrapped DEK together. Neither is useful without the other, and the wrapped DEK is useless without the KMS key.

KMS Keys

KeyAlgorithmProtectionRotationPurpose
pan-encryption-keyAES-256-GCMHSM (FIPS 140-2 Level 3)90-day auto-rotationEncrypt/decrypt PAN ciphertext
pan-fingerprint-keyHMAC-SHA256HSM90-day auto-rotationPAN deduplication fingerprint

Key ring: gatelithix-vault in gatelithix-pci project.

Evidence: infra/terraform/pci/kms.tf

IAM Restriction on KMS Keys

Only the vault service account (vault-sa@gatelithix-pci.iam.gserviceaccount.com) holds the following roles on PAN keys:

  • roles/cloudkms.cryptoKeyEncrypterDecrypter on pan-encryption-key
  • roles/cloudkms.signerVerifier on pan-fingerprint-key

No other service account, user, or service has access to these keys. Gateway, connector, and other service accounts have no KMS bindings.

Evidence: infra/terraform/pci/kms.tf IAM bindings section

Crypto-Shredding

Because the master key (pan-encryption-key) wraps all DEKs, destroying a key version makes all PANs encrypted with that version permanently unrecoverable. This enables crypto-shredding for GDPR right-to-erasure without physically deleting encrypted ciphertext rows.

Encryption in Transit

All paths carrying cardholder data use TLS 1.2 or higher:

PathTLS Enforcement
Merchant → Load BalancerGoogle-managed TLS (Cloud Run Global LB)
Load Balancer → GatewayCloud Run enforces TLS
Gateway → VaultTLS via Serverless VPC Connector, identity token auth
Vault → PCI Cloud SQLssl_mode=ENCRYPTED_ONLY + sslmode=require in pgx
Vault → Cloud KMSHTTPS to restricted.googleapis.com
Vault → ConnectorsTLS via Serverless VPC Connector, identity token auth
Connectors → PSP APIsTLS 1.2+ enforced by PSP (Stripe, NMI, FluidPay)

Evidence: infra/terraform/pci/cloudrun.tf, infra/terraform/pci/database.tf, apps/vault/db/db.go (sslmode=require)


Audit Logging

What Is Logged

Every access to the vault decrypt endpoint produces an audit entry. The vault never logs the PAN itself. Each audit record contains:

FieldPurpose
event_typetoken.create, processor_token.resolve, token.lookup, token.delete
gateway_tokenThe tok_... reference — identifies which card was accessed without revealing PAN
merchant_idWhich merchant owns this token
connector_idWhich connector/PSP the PAN was resolved for (on resolve events)
binFirst 6 digits of card (not PAN; permitted metadata)
last4Last 4 digits (not PAN; permitted metadata)
timestampRFC 3339 UTC
request_idTrace correlation ID
actor_saGCP service account that made the vault call

Evidence: apps/vault/handlers/tokenize.go:263-278, apps/vault/handlers/resolve.go audit call

Log Storage and Retention

Audit logs flow through two sinks:

  1. PCI Audit Log Bucket — Cloud Logging bucket with locked = true (tamper-resistant), 365-day retention.
  2. PCI Audit Archive — Cloud Storage NEARLINE bucket with versioning and locked retention (365 days), for long-term archival.

Both sinks capture cloudaudit.googleapis.com log entries from the gatelithix-pci project.

Evidence: infra/terraform/pci/logging.tf

Tamper Protection

The audit log bucket uses locked = true on its retention policy. This prevents retention period reduction and log deletion, including by project owners. Once a log entry reaches the bucket, it cannot be modified or deleted before the retention period expires.

GCP Cloud Audit Logs also records all Admin Activity in the gatelithix-pci project (IAM changes, resource creation/deletion), providing an additional immutable audit trail.

Evidence: infra/terraform/pci/logging.tflocked = true, retention_days = 365


Access Control

Service Account Model

Each service runs with a dedicated service account. No service account holds roles beyond what it needs.

Service AccountProjectRoles
gateway-sagatelithix-coreroles/run.invoker on vault; Cloud SQL user on core DB; Secret Manager accessor
vault-sagatelithix-pcicloudkms.cryptoKeyEncrypterDecrypter + cloudkms.signerVerifier on PAN keys; Cloud SQL user on PCI DB; Secret Manager accessor
connector-sagatelithix-coreCloud SQL user on core DB (read-only for connector config); no KMS access; no vault access

No service account uses default credentials or has wildcard roles (roles/editor, roles/owner). All authentication uses Workload Identity — no service account key files are downloaded or stored.

Evidence: infra/terraform/pci/kms.tf, infra/terraform/core/iam.tf

Application RBAC

The gateway enforces role-based access on all API endpoints via pkg/auth/rbac.go:

RoleAccess Level
platform_adminFull platform access (internal only)
platform_supportRead-only platform access (internal only)
merchant_adminFull access to own merchant’s resources
merchant_userRead-only access to own merchant’s resources

All payment read queries filter by merchant_id from the auth context. Cross-tenant payment IDs return 404 (not 403) to prevent information disclosure.

Evidence: pkg/auth/rbac.go

MFA Enforcement

SystemMFA Policy
GCP ConsoleGCP IAM org policy requires MFA for all human users
Auth0 (merchant portal)Guardian + TOTP MFA required for merchant_admin role
Stripe DashboardMFA required for all team members with access to live keys

Evidence: GCP IAM org policy configuration; Auth0 tenant MFA policy

API Key Security

API keys are stored hashed with SHA-256 in Core Cloud SQL. The plaintext key is shown once at creation and never stored. Each API key has a unique per-key HMAC secret for webhook signature generation (resolved at request time by pkg/hmac/resolver.go). Keys can be revoked instantly from the dashboard.

Evidence: db/migrations/023_add_hmac_secret_to_api_keys.sql, pkg/hmac/resolver.go


Evidence Summary for QSA

ControlRequirementEvidence Location
Network segmentationReq 1.2.1, 1.3.1, 1.3.2infra/terraform/pci/network.tf, infra/terraform/modules/vpc/main.tf
No public IP on PCI DBReq 1.4.1infra/terraform/pci/database.tfipv4_enabled = false
PAN encrypted at restReq 3.5.1infra/terraform/pci/kms.tf, apps/vault/handlers/tokenize.go:194
HMAC keyed fingerprintReq 3.5.1.1infra/terraform/pci/kms.tfpan_fingerprint_key
Key rotation policyReq 3.6.1infra/terraform/pci/kms.tfrotation_period = "7776000s" (90 days)
TLS 1.2+ everywhereReq 4.2.1Cloud Run managed TLS; infra/terraform/pci/database.tfssl_mode = ENCRYPTED_ONLY
Container / dep scanningReq 6.3.2.github/workflows/ci.yml — Trivy, gosec, govulncheck
KMS access restrictionReq 7.2.1infra/terraform/pci/kms.tf — IAM binding to vault_sa_email only
Separate service accountsReq 7.2.2infra/terraform/core/iam.tf, infra/terraform/pci/iam.tf
Default-deny firewallReq 7.2.3infra/terraform/modules/vpc/main.tf — deny-all rules
RBAC on API endpointsReq 7.2.4pkg/auth/rbac.go
MFA for CDE accessReq 8.3.1, 8.4.2Auth0 MFA policy; GCP org IAM MFA policy
Audit logs enabledReq 10.2.1infra/terraform/pci/logging.tfpci_audit_sink
Tamper-proof audit logsReq 10.3.3infra/terraform/pci/logging.tflocked = true
12-month log retentionReq 10.5.1infra/terraform/pci/logging.tfretention_days = 365
Archived audit logsReq 10.6.3infra/terraform/pci/logging.tf — NEARLINE archive bucket
Internal vuln scansReq 11.3.1.github/workflows/ci.yml — gosec on every PR
External vuln scansReq 11.3.2.github/workflows/ci.yml — Trivy container scan
Byte-zero of PAN in memoryReq 3.3.xapps/vault/handlers/tokenize.go:106-109, resolve.go:153-157
No PAN in logsReq 3.3.xapps/vault/handlers/tokenize.go:263-278 (audit metadata)

For deployed verification commands (gcloud, terraform) for each of these controls, see PCI Verification Evidence.