AWS CloudFormation EU Alternative 2026: Infrastructure Templates, GDPR Compliance, and CLOUD Act Risk
Post #717 in the sota.io EU Compliance Series
AWS CloudFormation is the foundational layer of most AWS deployments: you describe your infrastructure in JSON or YAML templates, and CloudFormation creates, updates, and deletes resources to match that description. It is elegant infrastructure-as-code that most AWS-native teams take for granted.
What teams less often consider is that CloudFormation, in doing its job, accumulates a comprehensive record of your entire infrastructure architecture — every template you have ever deployed, every change you have ever made, every resource relationship, and the parameter values that configure your systems. That record lives in your AWS account, under US jurisdiction, where it is subject to CLOUD Act compelled disclosure.
For organizations processing EU personal data, this creates a risk that is easy to miss: your infrastructure blueprints contain information about every system that processes personal data, potentially including connection strings, encryption key references, and service configurations. A compelled disclosure of your CloudFormation state is effectively a map of your personal data processing architecture.
What AWS CloudFormation Stores
Stack Templates and State
When you create a CloudFormation stack, AWS stores the template in S3 under your account. CloudFormation maintains the full current state of each stack — which resources exist, what their configurations are, and how they relate to each other. This is necessary for CloudFormation to calculate change sets: it needs to know the current state to determine what actions are required to move from state A to state B.
The stored state includes:
{
"StackId": "arn:aws:cloudformation:eu-central-1:123456789:stack/production-api/abc123",
"StackName": "production-api",
"Description": "Production API stack — processes user profiles, payment data",
"Parameters": [
{
"ParameterKey": "DatabaseEndpoint",
"ParameterValue": "prod-db.cluster-abc123.eu-central-1.rds.amazonaws.com"
},
{
"ParameterKey": "RedisEndpoint",
"ParameterValue": "prod-cache.abc123.0001.euc1.cache.amazonaws.com"
},
{
"ParameterKey": "UserDataBucketName",
"ParameterValue": "prod-user-uploads-eu-central-1"
}
],
"Resources": {
"UserAPIFunction": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Handler": "index.handler",
"Role": "arn:aws:iam::123456789:role/UserAPIRole",
"Environment": {
"Variables": {
"STRIPE_PUBLISHABLE_KEY": "pk_live_...",
"SEGMENT_WRITE_KEY": "..."
}
}
}
}
}
}
Parameter values passed to CloudFormation stacks are stored in plaintext unless explicitly marked as NoEcho. In practice, many CloudFormation deployments pass endpoint URLs, queue names, bucket names, and service identifiers as parameters — all of which reveal your data processing architecture.
Change Sets and Deployment History
CloudFormation stores the complete history of every change set: what was proposed, what was approved or rejected, what resources were modified, created, or deleted, and when. This history persists indefinitely by default.
Change set history creates a timeline of your infrastructure evolution. Reviewing it reveals when you added a new database, when you moved from one queue service to another, when you scaled up your processing capacity. Combined with the resource ARNs and service identifiers in each change set, this creates a longitudinal record of your personal data processing systems.
{
"ChangeSetId": "arn:aws:cloudformation:eu-central-1:123456789:changeSet/add-gdpr-processor/...",
"ChangeSetName": "add-gdpr-processor",
"Description": "Add GDPR data export Lambda + DynamoDB table for user data requests",
"Changes": [
{
"Type": "Resource",
"ResourceChange": {
"Action": "Add",
"LogicalResourceId": "GDPRExportFunction",
"ResourceType": "AWS::Lambda::Function",
"Details": [
{
"Target": {
"Attribute": "Properties",
"Name": "Environment"
},
"ChangeSource": "DirectModification"
}
]
}
}
]
}
The change set name in this example explicitly reveals the purpose: GDPR data export processing. CloudFormation history effectively documents your compliance architecture — and stores it under US jurisdiction.
Stack Events
Every resource creation, update, and deletion generates a stack event. CloudFormation retains these events and makes them available via API. Stack events include timestamps, resource types, resource IDs, status messages, and reason strings.
Status reason strings are particularly revealing. When a resource creation fails, CloudFormation records the error message as the reason — often including configuration details, endpoint names, and service-specific error messages that reveal what you were trying to build and why it failed.
Drift Detection Results
CloudFormation's drift detection feature compares your deployed infrastructure against the expected state defined in your template. When it detects drift, it records exactly which properties have changed and what the actual values are.
A drift detection result for a modified security group shows the actual current inbound rules — including which IP ranges can access which ports, revealing your network perimeter configuration. For a Lambda function, drift results show the current environment variables, which may include service endpoints and configuration values not in your template.
The GDPR Risk Analysis
Infrastructure State as Personal Data
The direct argument that CloudFormation state contains personal data is straightforward: if your infrastructure processes personal data, then a complete description of that infrastructure — which services exist, how they connect, what they are named, and how they are configured — constitutes information from which your data processing activities can be identified.
Under GDPR, this is relevant because:
Article 32 requires organizations to implement appropriate technical measures to ensure security of processing. This includes protecting information about your processing systems from unauthorized access. An infrastructure blueprint under extraterritorial jurisdiction is a security risk to those systems.
Article 25 (Data Protection by Design) requires considering the risks of processing at the point of system design. Designing infrastructure that places detailed records of your personal data processing systems under US jurisdiction — even if the systems themselves are in the EU — creates a structural GDPR tension.
Article 28 requires that your processors (AWS) process data only on your instructions. The CLOUD Act creates a parallel authority structure: US government agencies can compel AWS to produce data on their instructions, independently of yours. CloudFormation state held under this parallel authority structure creates an Article 28 gap.
The Parameter Value Problem
CloudFormation parameters are designed to externalize configuration from templates. In practice, parameters often contain:
- Database endpoint URLs (which reveal your data store locations)
- S3 bucket names (which reveal where you store user data)
- SQS and SNS ARNs (which reveal your data processing pipelines)
- KMS key ARNs (which reveal your encryption architecture)
- Third-party service identifiers (Stripe publishable keys, analytics write keys)
Parameters marked NoEcho are not returned by the DescribeStacks API — but they are still stored by CloudFormation and are potentially accessible through internal AWS mechanisms under a CLOUD Act request.
The critical point is that NoEcho is an API parameter, not a cryptographic protection. It controls what the CloudFormation API returns to you. It does not determine what CloudFormation stores internally or what AWS can produce under a government order.
Template Secrets: A Common Antipattern
Despite security guidance discouraging it, CloudFormation templates frequently contain secrets embedded directly in resource definitions. Lambda environment variables are a common vector:
Resources:
ProcessingFunction:
Type: AWS::Lambda::Function
Properties:
Environment:
Variables:
DATABASE_PASSWORD: !Ref DatabasePassword
JWT_SECRET: !Ref JWTSecret
ENCRYPTION_KEY: !Ref EncryptionKey
Even when using Parameter Store or Secrets Manager references ({{resolve:ssm:/prod/db/password}}), the template itself reveals that these secrets exist and what their parameter paths are — enabling a sophisticated adversary to target them specifically.
Cross-Stack References and StackSets
CloudFormation supports cross-stack references via Outputs and Exports. When Stack A exports a value that Stack B imports, CloudFormation maintains the relationship. An export map reveals:
- Which stacks depend on which
- What values are shared between stacks
- The organizational structure of your infrastructure
For multi-account deployments using CloudFormation StackSets, AWS Organizations is used to manage deployment across accounts. StackSets state reveals your account structure, which environments you have, and which regions you deploy to — all under the same CLOUD Act exposure.
CLOUD Act Risk Profile
The specific CLOUD Act risk with CloudFormation is distinct from data-at-rest risks like S3 or RDS. Here, the exposed data is architectural intelligence:
What a compelled disclosure delivers:
- Complete inventory of every AWS resource in your account
- History of every infrastructure change for the retention period
- Parameter values for current and historical deployments
- Resource relationships and dependencies
- Deployment timing and patterns
- Error messages and configuration details from failed deployments
This is not user data in the conventional sense. But it is intelligence about your systems that process user data — intelligence that could be used to:
- Identify which systems hold personal data for specific user populations
- Understand the architecture for targeting subsequent requests more precisely
- Map data flows to understand which services transmit which categories of data
For regulated industries (healthcare, finance, legal), this architectural intelligence may be independently sensitive — separate from the personal data itself.
EU-Sovereign IaC Alternatives
The IaC ecosystem has several mature alternatives that can be operated entirely within EU-sovereign infrastructure.
| Capability | AWS CloudFormation | OpenTofu | Terraform (EU backend) | Pulumi (Self-hosted) | Crossplane |
|---|---|---|---|---|---|
| State storage | AWS (US jurisdiction) | Self-hosted (EU) | Self-hosted (EU) | Self-hosted (EU) | Kubernetes CRDs (EU) |
| Provider support | AWS-only | Multi-cloud | Multi-cloud | Multi-cloud | Multi-cloud |
| Template language | JSON/YAML | HCL | HCL | TypeScript/Python/Go | YAML |
| Secret handling | Parameter Store / NoEcho | Vault / SOPS integration | Vault / SOPS integration | ESC (Environments, Secrets, Config) | External Secrets Operator |
| Drift detection | Built-in | Manual (terraform plan) | Manual (terraform plan) | Built-in | Continuous reconciliation |
| EU-sovereign? | No (AWS US parent) | Yes (Linux Foundation) | No (HashiCorp/IBM) — state backend can be EU | Yes (self-hosted) | Yes (CNCF, self-hosted) |
| License | AWS proprietary | MPL 2.0 | BSL 1.1 (post-1.5.x) | Apache 2.0 | Apache 2.0 |
OpenTofu
OpenTofu is the Linux Foundation-governed fork of Terraform, created after HashiCorp's license change in 2023. It is functionally equivalent to Terraform for most use cases and maintains binary compatibility with existing Terraform providers and modules.
EU-sovereignty characteristics:
- State backends: S3-compatible (MinIO, Hetzner Object Storage), PostgreSQL, Kubernetes — all can be EU-hosted
- No telemetry by default
- Linux Foundation governance (not a US commercial entity with investors)
- Fully open-source under MPL 2.0
terraform {
backend "s3" {
bucket = "your-eu-state-bucket"
key = "production/terraform.tfstate"
region = "eu-central-1"
endpoint = "https://your-eu-s3-compatible.storage"
}
}
For organizations currently using Terraform, migration to OpenTofu requires only changing the binary — state files and providers are compatible.
Pulumi (Self-Hosted Backend)
Pulumi allows you to define infrastructure using general-purpose programming languages: TypeScript, Python, Go, C#. This enables more expressive infrastructure definitions than HCL, with full IDE support, testing frameworks, and code reuse patterns.
EU-sovereignty characteristics:
- Pulumi backend can be self-hosted: S3-compatible object storage for state, or the self-hosted Pulumi Service
- ESC (Environments, Secrets, Config) handles secrets separately from state
- No data leaves your environment if using a self-hosted backend
import * as aws from "@pulumi/aws";
const vpc = new aws.ec2.Vpc("eu-vpc", {
cidrBlock: "10.0.0.0/16",
enableDnsHostnames: true,
tags: { Environment: "production", DataClassification: "personal-data" },
});
const privateSubnet = new aws.ec2.Subnet("private-subnet", {
vpcId: vpc.id,
cidrBlock: "10.0.1.0/24",
availabilityZone: "eu-central-1a",
});
Migration from CloudFormation: AWS provides a CloudFormation migration guide, and Pulumi's cf2pulumi tool can convert existing templates.
Crossplane
Crossplane is a CNCF project that extends Kubernetes to manage external infrastructure. Your infrastructure definitions live as Kubernetes custom resources (CRDs), and Crossplane controllers reconcile the actual state of your cloud resources to match.
EU-sovereignty characteristics:
- All state is in Kubernetes etcd — hosted wherever you host Kubernetes
- Continuous reconciliation detects and corrects drift automatically
- External Secrets Operator handles secret injection from Vault or other EU-sovereign stores
- CNCF governance, not a US commercial entity
apiVersion: ec2.aws.upbound.io/v1beta1
kind: VPC
metadata:
name: production-vpc
annotations:
crossplane.io/external-name: vpc-id
spec:
forProvider:
region: eu-central-1
cidrBlock: 10.0.0.0/16
enableDnsHostnames: true
providerConfigRef:
name: eu-aws-provider
Crossplane is appropriate for teams already running Kubernetes, as it adds infrastructure management to an existing cluster rather than introducing a new tool chain.
Ansible (Procedural Alternative)
For organizations that prefer procedural over declarative infrastructure management, Ansible provides a mature alternative that stores no state externally. Playbook runs are stateless by default — Ansible connects to resources, reads their current state, and applies changes. There is no central state file that must be stored.
EU-sovereignty characteristics:
- No persistent state storage (or state in your own database via custom facts)
- Red Hat (IBM) ownership — but Ansible is open-source and the AWX controller (open-source Ansible Tower) can be self-hosted
- Works with any cloud provider via modules
For EU sovereignty, Ansible + AWX hosted on EU infrastructure is a fully sovereign option with no US-jurisdiction dependencies.
Migration Strategy
Phase 1: State Backend Migration (Lowest Risk)
If you are using Terraform/OpenTofu and currently storing state in S3 or Terraform Cloud:
- Export current state:
terraform state pull > terraform.tfstate - Configure new EU-sovereign backend (MinIO on Hetzner, PostgreSQL on EU-hosted RDS, S3-compatible on OVH)
- Push state to new backend:
terraform state push terraform.tfstate - Update all CI/CD pipelines to use new backend configuration
This migration preserves all existing Terraform code and simply moves state storage to EU jurisdiction.
Phase 2: CloudFormation Template Conversion
For existing CloudFormation stacks, conversion to OpenTofu/Terraform involves:
- Import existing resources:
terraform import aws_vpc.main vpc-abc123— map existing resources to new Terraform definitions without recreation - Generate template from state:
terraform show -json | jqto understand current resource configurations - Verify with plan:
terraform planshould show no changes after successful import
AWS's former2 tool (community project) can generate Terraform/CDK from existing CloudFormation stacks, accelerating large migrations.
Phase 3: Secret Sanitization
Before migrating templates, audit all CloudFormation parameters and template literal values:
# Find potentially embedded secrets in CloudFormation templates
grep -r -i "password\|secret\|key\|token\|credential" cloudformation/ \
--include="*.yaml" --include="*.json" \
| grep -v "NoEcho\|ParameterType\|Description"
Secrets found in templates should be migrated to:
- SOPS (Mozilla, open-source): encrypts secret files, stores encrypted values in git
- Vault (HashiCorp, BSL; or OpenBao, CNCF fork): dynamic secrets, short-lived credentials
- External Secrets Operator: Kubernetes-native secret injection from any secrets backend
Deployment on sota.io
sota.io deploys directly from your git repository. Your IaC definitions remain in your version control system — no deployment state leaves your infrastructure. Deploy containers defined with any IaC tool, with secrets injected at runtime via environment variables you control:
# Infrastructure definition in your repo (Pulumi/OpenTofu/Ansible)
# sota.io deploys the resulting container with your env vars
sota deploy --env DATABASE_URL=$EU_DB_URL --env ENCRYPTION_KEY=$VAULT_SECRET
The stack state — what was deployed, with which configuration, when — stays in your EU-sovereign state backend. sota.io never holds your infrastructure definitions or deployment history.
Implementation Checklist
For teams evaluating CloudFormation's GDPR posture:
Immediate (1 week):
- Audit all CloudFormation stacks for
NoEcho: falseparameters containing sensitive values - Review stack event history for secrets in status reason strings
- Identify which stacks define resources that process personal data categories (Art. 9/10)
- Enable CloudTrail logging for all CloudFormation API calls
Short-term (1 month):
- Evaluate OpenTofu or Pulumi as migration target based on team language preferences
- Select EU-sovereign state backend (MinIO/PostgreSQL/Hetzner Object Storage)
- Pilot migration of one non-production stack to EU-sovereign IaC
- Establish secret management policy (SOPS or Vault for all IaC secrets)
Strategic (3 months):
- Complete migration of personal-data-adjacent stacks to EU-sovereign IaC
- Update DPA/DPIA to reflect reduced CloudFormation dependency
- Implement policy-as-code (OPA/Conftest) to prevent future secret embedding in templates
Summary
AWS CloudFormation is convenient and deeply integrated with the AWS ecosystem. That integration is also its GDPR risk: CloudFormation state is stored by AWS, subject to the same CLOUD Act exposure as any other AWS-managed data. The difference from a database backup is that CloudFormation state describes your processing systems rather than your users' data — but under GDPR's broad definition of personal data and its requirements for processing security, the distinction matters less than you might assume.
OpenTofu (for HCL users), Pulumi with a self-hosted backend (for TypeScript/Python developers), and Crossplane (for Kubernetes-native teams) all provide CloudFormation-equivalent capabilities with EU-sovereign state storage. The migration path is incremental: start with state backend migration to EU jurisdiction, then convert templates at your own pace.
The IaC migration is not urgent in the same way a live database migration is. But if you are deploying on AWS, your CloudFormation state is accumulating under US jurisdiction every day — growing in both scope and sensitivity as your infrastructure evolves.
EU-Native Hosting
Ready to move to EU-sovereign infrastructure?
sota.io is a German-hosted PaaS — no CLOUD Act exposure, no US jurisdiction, full GDPR compliance by design. Deploy your first app in minutes.