Skip to main content

Terraform for Data Sovereignty: Geopatriation and Sovereign Cloud on AWS

Key Takeaway

Implement data sovereignty and geopatriation with Terraform on AWS. Enforce data residency with SCPs, deploy region-locked infrastructure, and meet GDPR and sovereign cloud requirements.

Table of Contents

Geopatriation — localizing data, compute, and cloud infrastructure for regulatory and resilience reasons — is a top Gartner 2026 strategic trend. Countries are increasingly requiring that certain data stays within their borders. The EU’s GDPR, China’s PIPL, India’s DPDPA, and sector-specific regulations in finance and healthcare all drive data residency requirements.

Terraform is the ideal tool for enforcing data sovereignty because infrastructure-as-code can be audited, policy-checked, and consistently deployed to specific regions.

Architecture: Region-Locked Infrastructure

┌─────────────────────────────────────┐
│  AWS Organization                    │
│                                      │
│  SCP: Deny all regions except        │
│       eu-central-1, eu-west-1        │
│                                      │
│  ┌──────────────┐  ┌──────────────┐ │
│  │ eu-central-1  │  │ eu-west-1    │ │
│  │ (Frankfurt)   │  │ (Ireland)    │ │
│  │               │  │              │ │
│  │ Production    │  │ DR/Backup    │ │
│  │ VPC + RDS     │  │ S3 Replicas  │ │
│  │ + S3          │  │              │ │
│  └──────────────┘  └──────────────┘ │
└─────────────────────────────────────┘

Service Control Policy: Restrict Regions

resource "aws_organizations_policy" "eu_only" {
  name = "eu-region-restriction"
  type = "SERVICE_CONTROL_POLICY"

  content = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid       = "DenyNonEURegions"
        Effect    = "Deny"
        Action    = "*"
        Resource  = "*"
        Condition = {
          StringNotEquals = {
            "aws:RequestedRegion" = [
              "eu-central-1",   # Frankfurt
              "eu-west-1",      # Ireland
              "eu-west-2",      # London
              "eu-west-3",      # Paris
              "eu-north-1",     # Stockholm
              "eu-south-1",     # Milan
              "eu-south-2",     # Spain
              "eu-central-2"    # Zurich
            ]
          }
          # Exempt global services
          StringNotLike = {
            "aws:PrincipalArn" = "arn:aws:iam::*:role/OrganizationAdmin"
          }
        }
      },
      {
        Sid       = "AllowGlobalServices"
        Effect    = "Allow"
        Action = [
          "iam:*",
          "sts:*",
          "organizations:*",
          "support:*",
          "budgets:*",
          "waf:*",
          "cloudfront:*",
          "route53:*",
          "health:*"
        ]
        Resource = "*"
      }
    ]
  })
}

resource "aws_organizations_policy_attachment" "eu_only" {
  policy_id = aws_organizations_policy.eu_only.id
  target_id = var.eu_ou_id  # Attach to EU organizational unit
}

Region-Locked Provider Configuration

# Enforce region at the Terraform level
variable "allowed_regions" {
  type    = list(string)
  default = ["eu-central-1", "eu-west-1"]

  validation {
    condition = alltrue([
      for r in var.allowed_regions : can(regex("^eu-", r))
    ])
    error_message = "Only EU regions are allowed for this deployment."
  }
}

provider "aws" {
  region = var.allowed_regions[0]  # Primary: Frankfurt

  default_tags {
    tags = {
      DataResidency = "EU"
      Compliance    = "GDPR"
      ManagedBy     = "Terraform"
    }
  }
}

provider "aws" {
  alias  = "dr"
  region = var.allowed_regions[1]  # DR: Ireland

  default_tags {
    tags = {
      DataResidency = "EU"
      Compliance    = "GDPR"
    }
  }
}

S3 with Region Lock and Replication

resource "aws_s3_bucket" "data" {
  provider = aws
  bucket   = "eu-customer-data-${data.aws_caller_identity.current.account_id}"

  tags = {
    DataClassification = "personal-data"
    DataResidency      = "eu-central-1"
    Regulation         = "GDPR-Article-44"
  }
}

# Block public access
resource "aws_s3_bucket_public_access_block" "data" {
  bucket = aws_s3_bucket.data.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# Replicate only within EU
resource "aws_s3_bucket_replication_configuration" "eu_replica" {
  bucket = aws_s3_bucket.data.id
  role   = aws_iam_role.replication.arn

  rule {
    id     = "eu-replication"
    status = "Enabled"

    destination {
      bucket        = aws_s3_bucket.data_replica.arn
      storage_class = "STANDARD_IA"

      encryption_configuration {
        replica_kms_key_id = aws_kms_key.eu_west.arn
      }
    }

    source_selection_criteria {
      sse_kms_encrypted_objects {
        status = "Enabled"
      }
    }
  }

  depends_on = [aws_s3_bucket_versioning.data]
}

resource "aws_s3_bucket" "data_replica" {
  provider = aws.dr
  bucket   = "eu-customer-data-replica-${data.aws_caller_identity.current.account_id}"

  tags = {
    DataResidency = "eu-west-1"
    Purpose       = "disaster-recovery"
  }
}

RDS with EU-Only Encryption

resource "aws_kms_key" "eu_database" {
  provider    = aws
  description = "EU-only database encryption key"

  # Key cannot be shared outside EU regions
  tags = {
    DataResidency = "eu-central-1"
    KeyPurpose    = "database-encryption"
  }
}

resource "aws_db_instance" "main" {
  identifier     = "eu-customer-db"
  engine         = "postgres"
  engine_version = "16.3"
  instance_class = "db.r6g.large"

  allocated_storage     = 100
  storage_encrypted     = true
  kms_key_id            = aws_kms_key.eu_database.arn

  # Multi-AZ within the same region (stays in Frankfurt)
  multi_az = true

  # No cross-region read replicas (data stays in EU)
  # Use aws_db_instance_automated_backups_replication for EU-only backup replication

  deletion_protection = true

  tags = {
    DataResidency      = "eu-central-1"
    DataClassification = "personal-data"
  }

  lifecycle {
    prevent_destroy = true
  }
}

Terraform Sentinel / OPA Policy

Enforce data residency in CI/CD:

# policy/data-residency.rego (Open Policy Agent)
package terraform.analysis

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_s3_bucket"

  # Check region from provider config
  not startswith(resource.provider_name, "aws.eu")

  msg := sprintf(
    "S3 bucket '%s' must be deployed in an EU region for GDPR compliance",
    [resource.address]
  )
}

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_db_instance"

  not resource.change.after.storage_encrypted

  msg := sprintf(
    "RDS instance '%s' must have encryption enabled for compliance",
    [resource.address]
  )
}

Compliance Tagging Module

module "compliance_tags" {
  source = "./modules/compliance-tags"

  data_residency     = "EU"
  regulation         = "GDPR"
  data_classification = "personal-data"
  data_controller    = var.company_name
  retention_days     = 2555  # 7 years
}

# modules/compliance-tags/main.tf
variable "data_residency" { type = string }
variable "regulation" { type = string }
variable "data_classification" { type = string }
variable "data_controller" { type = string }
variable "retention_days" { type = number }

output "tags" {
  value = {
    DataResidency      = var.data_residency
    Regulation         = var.regulation
    DataClassification = var.data_classification
    DataController     = var.data_controller
    RetentionDays      = tostring(var.retention_days)
    ManagedBy          = "Terraform"
    LastAudit          = formatdate("YYYY-MM-DD", timestamp())
  }
}

Hands-On Courses

Conclusion

Data sovereignty requires enforcement at multiple layers: SCPs to restrict regions at the AWS Organizations level, Terraform provider configuration to lock deployments to specific regions, S3 replication rules that stay within allowed geographies, and OPA/Sentinel policies in CI/CD. Terraform makes these constraints auditable and repeatable — critical as geopatriation regulations expand globally in 2026.

🚀

Level Up Your Terraform Skills

Hands-on courses, books, and resources from Luca Berton

Luca Berton
Written by

Luca Berton

DevOps Engineer, AWS Partner, Terraform expert, and author. Creator of Ansible Pilot, Terraform Pilot, and CopyPasteLearn.