Policy-as-code platform enforcing Mexico's LFPDPPP (DOF 20 marzo 2025) data residency law across US–Mexico Azure deployments. Three enforcement layers — IaC variables, CI policy gates, and runtime Azure Policy — ensure personal data never leaves its authorized region.
In March 2025, Mexico completely rewrote its data protection law (LFPDPPP), introducing criminal penalties of up to 5 years and fines up to $3.86M USD (doubled for sensitive data).¹ Following the January 2026 cyber incidents, the new enforcement authority is signaling aggressive action.²
The US–Mexico corridor is booming, but this rapid growth has created a massive, unmitigated infrastructure gap:
- The Scale: $872.8 billion in US–Mexico goods trade in 2025 — the largest bilateral trade relationship in the world.³ Over 5,200 companies operate under the IMMEX nearshore program, handling regulated personal data daily.⁴
- The Infrastructure Gap: Microsoft launched Azure Mexico Central in 2024 for in-country data residency,⁵ but there are no standardized IaC patterns to enforce the new LFPDPPP compliance across multi-region deployments.
- The Threat: A single misconfigured Terraform file — a geo-replicated storage account, an unauthorized VNet peering, or a cross-tenant replication toggle — is all it takes to trigger an international data transfer violation.
The Solution: This project codifies the 2025 LFPDPPP mandates directly into infrastructure-as-code, ensuring cross-border data violations are caught at the Pull Request stage — not during a legal audit.
¹ LFPDPPP penalty framework — Fines from 100 to 320,000 UMA (~$3.86M USD), doubled for sensitive data. Criminal penalties per Recording Law.
² Recording Law — LFPDPPP 2025 Guide — No formal sanctions published yet under the 2025 law as of May 2026, but early SABG proceedings after the January 2026 cyber incidents indicate the authority will enforce aggressively.
³ USTR — Mexico trade data — U.S. goods trade with Mexico totaled $872.8 billion in 2025. Confirmed by U.S. Census Bureau data.
⁴ IMMEX program data — Approximately 5,220 companies operate under IMMEX, employing an estimated 2.94 million workers.
⁵ Azure Mexico Central — Microsoft's first hyperscale cloud region in Mexico, launched May 2024.
Three enforcement layers catch violations at different stages, so nothing reaches production unchecked:
┌──────────────────────────┐ ┌──────────────────────────┐ ┌──────────────────────────┐
│ Layer 1 — IaC │ → │ Layer 2 — CI │ → │ Layer 3 — Runtime │
│ Terraform modules │ │ GitHub Actions PR gate │ │ Azure Policy │
│ Variable validation │ │ OPA (Conftest) + Checkov│ │ Drift detection │
│ Catches at plan time │ │ Catches at PR merge │ │ Catches post-deploy │
│ modules/ │ │ .github/workflows/ │ │ (planned — ADR-002) │
└──────────────────────────┘ └──────────────────────────┘ └──────────────────────────┘
| Control | Enforcement Layer | Mechanism |
|---|---|---|
| Data residency (Art. 35) | IaC | Terraform variable validation — only mexicocentral and eastus2 allowed |
| Geo-replication ban (Art. 36) | IaC + CI | Storage accounts hardcoded to LRS + OPA rule rejects anything else |
| Cross-tenant replication (Art. 36) | IaC + CI | Disabled at resource level + OPA rule validates plan output |
| VNet peering prohibition (Art. 36) | CI | OPA rule blocks azurerm_virtual_network_peering resources entirely |
| Runtime drift detection | Runtime | Azure Policy assignments per environment (planned — see ADR-002) |
| Audit log residency (Art. 35) | IaC | Log Analytics Workspace and diagnostic settings co-located with resources |
LFPDPPP citations are enforced in the infrastructure itself. Here is how Article 35 enforces region locking:
variable "location" {
description = "Azure region where the storage account will be created."
type = string
validation {
condition = contains(["mexicocentral", "eastus2"], var.location)
error_message = "LFPDPPP Art. 35 (DOF 20 marzo 2025): storage must be deployed to mexicocentral or eastus2 only."
}
}And the OPA policy that catches geo-replication in CI before any PR can merge:
# Real Terraform plans nest resources under root_module.child_modules[_]
# when calling modules — walk() collects them regardless of nesting depth.
all_resources contains resource if {
walk(input.planned_values.root_module, [path, value])
path[count(path) - 1] == "resources"
resource := value[_]
}
deny contains msg if {
resource := all_resources[_]
resource.type == "azurerm_storage_account"
replication := resource.values.account_replication_type
not allowed_replication_types[replication]
msg := sprintf(
"LFPDPPP Art. 36 violation: Storage account '%s' uses replication type '%s'. Only LRS is permitted — geo-replication transfers data across borders without explicit authorization.",
[resource.name, replication]
)
}crossborder-iac/
├── modules/
│ ├── compliant-storage/ # Storage account — LRS only, Art. 36
│ ├── compliant-keyvault/ # Key Vault — purge protection, network ACLs, tenant-locked
│ ├── compliant-network/ # VNet + subnets — no peering by design
│ └── observability-baseline/ # Log Analytics — region-local logs, Art. 35
├── environments/
│ ├── mx-central/ # Mexico Central — data_classification = "personal"
│ └── us-east2/ # East US 2 — data_classification = "non-personal"
├── policies/
│ └── storage_residency.rego # 4 OPA rules enforcing Arts. 35-36
├── tests/
│ └── fixtures/ # Terraform plan JSON for policy testing
├── scripts/
│ └── bootstrap-state-backend.sh # Idempotent state storage setup (West US 2)
├── docs/
│ └── adr/ # Architecture Decision Records
├── .github/
│ └── workflows/ # PR checks: terraform plan + OPA + Checkov
└── conftest.toml
compliant-storage — Azure Storage Account locked to LRS replication. Variable validation rejects GRS/ZRS/GZRS at plan time. Cross-tenant replication disabled. Enforces Art. 36.
compliant-keyvault — Azure Key Vault with purge protection enabled, 90-day soft delete, network ACLs denying public access, and tenant-level locking. Secrets never leave the authorized tenant boundary.
compliant-network — VNet and subnets with non-overlapping address spaces per region (Mexico: 10.0.0.0/16, US: 10.1.0.0/16). No peering resources by design — cross-region network connectivity is architecturally prohibited, not just policy-blocked.
observability-baseline — Log Analytics Workspace with diagnostic settings ensuring audit logs stay in the same region as the resources they monitor. Enforces Art. 35 data residency for compliance evidence.
All four rules run on every PR via Conftest against terraform plan output:
| Rule | LFPDPPP Article | What It Catches |
|---|---|---|
| Region allowlist | Art. 35 | Storage accounts outside mexicocentral or eastus2 |
| LRS replication only | Art. 36 | Any replication type other than LRS |
| Cross-tenant replication disabled | Art. 36 | Cross-tenant replication left enabled |
| VNet peering prohibited | Art. 36 | Any azurerm_virtual_network_peering resource in the plan |
Every pull request triggers:
- terraform plan — Generates a plan JSON for the target environment
- Conftest OPA check — Runs all Rego policies against the plan output
- Checkov static analysis — Scans HCL for security misconfigurations
Branch protection on main requires all checks to pass. No direct pushes.
| ADR | Decision | Rationale |
|---|---|---|
| ADR-001 | Local state backend | Personal Azure account auth constraints prevent remote backend; bootstrap script provisions state storage for future migration |
| ADR-002 | Dual enforcement: OPA + Azure Policy | OPA catches violations pre-deploy in CI; Azure Policy detects drift post-deploy at runtime |
| ADR-003 | Bootstrap script outside Terraform | State backend cannot be managed by the Terraform that depends on it — circular dependency resolved with idempotent shell script |
| ADR-004 | LFPDPPP 2025 article migration | Mexico's complete law rewrite (DOF 20 marzo 2025) renumbered the residency and transfer articles; compliance citations across all code and docs were migrated to stay legally accurate |
- Terraform >= 1.5
- Azure CLI (az login with active subscription)
- Conftest (for OPA policy checks)
- Checkov (for static analysis)
git clone https://github.com/Esiono/crossborder-iac.git
cd crossborder-iac
chmod +x scripts/bootstrap-state-backend.sh
./scripts/bootstrap-state-backend.sh
cd environments/mx-central
terraform init
terraform plan -out=plan.tfplan
terraform show -json plan.tfplan > plan.json
conftest test plan.json -p ../../policies/ --namespace crossborder.storageThis is real output from running Conftest against the noncompliant fixtures in tests/fixtures/ — a storage account in the wrong region with GRS replication and cross-tenant replication enabled, the same misconfiguration nested inside a child module, and a prohibited VNet peering resource:
$ conftest test tests/fixtures/ --policy policies/ --namespace crossborder.storage
FAIL - tests/fixtures/noncompliant_storage.json - crossborder.storage - LFPDPPP Art. 35 violation: Storage account 'bad' is in region 'westeurope'. Allowed regions: {"eastus2", "mexicocentral"}
FAIL - tests/fixtures/noncompliant_storage.json - crossborder.storage - LFPDPPP Art. 36 violation: Storage account 'bad' has cross-tenant replication enabled. This permits data transfer to foreign tenants without explicit authorization.
FAIL - tests/fixtures/noncompliant_storage.json - crossborder.storage - LFPDPPP Art. 36 violation: Storage account 'bad' uses replication type 'GRS'. Only LRS is permitted — geo-replication transfers data across borders without explicit authorization.
FAIL - tests/fixtures/noncompliant_storage_module.json - crossborder.storage - LFPDPPP Art. 35 violation: Storage account 'main' is in region 'westeurope'. Allowed regions: {"eastus2", "mexicocentral"}
FAIL - tests/fixtures/noncompliant_storage_module.json - crossborder.storage - LFPDPPP Art. 36 violation: Storage account 'main' has cross-tenant replication enabled. This permits data transfer to foreign tenants without explicit authorization.
FAIL - tests/fixtures/noncompliant_storage_module.json - crossborder.storage - LFPDPPP Art. 36 violation: Storage account 'main' uses replication type 'GRS'. Only LRS is permitted — geo-replication transfers data across borders without explicit authorization.
FAIL - tests/fixtures/noncompliant_peering.json - crossborder.storage - LFPDPPP Art. 36 violation: VNet peering resource 'mx_to_us' detected. Cross-region VNet peering creates unauthorized data paths across borders. Peering between mexicocentral and eastus2 is prohibited.
12 tests, 5 passed, 0 warnings, 7 failures, 0 exceptions
A non-zero exit code blocks the pull request — this is the check that runs in compliance-mx-central and compliance-us-east2 on every PR.
This is a reference implementation, not a finished platform. Planned work:
- Azure Policy as enforcement Layer 3 — runtime drift detection per ADR-002, currently the one layer of defense-in-depth that exists on paper but not in Terraform.
- Remote state migration to Azure Blob — replace the local backend once a service principal is in place, per ADR-001 and ADR-003.
- Private endpoints for Storage and Key Vault — both resources already block public network access; private endpoints would close the resulting connectivity gap for legitimate access.
- Expanded OPA policies for additional resource types — the current four rules cover storage and networking; Key Vault and Log Analytics configuration drift aren't yet policy-checked.
- CI enhancements —
terraform fmt -check,terraform validate, andtflintas fast pre-checks ahead of the plan/OPA/Checkov pipeline.
Eduardo Ayala Siono · Data Analyst / Data Engineer
6+ years ensuring production data integrity at scale. Based in Mexicali, on the US–Mexico border.
Built this project after researching the operational gaps US companies face under Mexico's 2025 LFPDPPP reform: $3.86M fines, criminal penalties, and no standardized infrastructure patterns to enforce them.
📍 Mexicali, MX · US Pacific · EN/ES C2
Licensed under MIT. This is a reference implementation for portfolio purposes. LFPDPPP (DOF 20 marzo 2025) compliance requirements should be validated with legal counsel for production deployments.