Skip to content

zama-ai/terraform-coprocessor-modules

Repository files navigation

Terraform-Coprocessor-Modules

This repo provides a Terraform Module for deploying the base layer infrastructure for the Coprocessor of the Zama Protocol.


Architecture

Infrastructure deployed by the examples/testnet-complete example.

Coprocessor Infrastructure - terraform-corprocessor-mdoules v0.2.0


Requirements

Requirement Notes
AWS account IAM permissions to create VPC, EKS, RDS, S3, IAM, SQS, and EventBridge resources
Terraform ≥ 1.11 Required for write-only variables (password_wo) and S3 native state locking
AWS CLI Configured with credentials for the target account (aws configure or env vars)
kubectl For post-deployment cluster access (aws eks update-kubeconfig ...)
S3 bucket for remote state Pre-created; referenced in the example versions.tf backend block

Usage

Two ready-to-deploy examples are provided. Each is a fully-formed, immediately deployable configuration — not a showcase of every available parameter, but a production-ready starting point that covers the standard Coprocessor deployment.

Example Use case
examples/testnet-complete Greenfield — creates VPC, EKS, RDS, and S3 from scratch
examples/testnet-existing-infra Bring-your-own VPC and EKS — only deploys RDS and S3

For the full set of available inputs and their defaults, see the Inputs table below or terraform.tfvars.example at the repo root.

Steps:

  1. Copy the relevant example directory into your own infrastructure repository.
  2. Update the source in main.tf to reference the remote versioned release: git::https://github.com/zama-ai/terraform-coprocessor-modules.git?ref=<version>
  3. Replace every value marked # CHANGE ME in terraform.tfvars — primarily partner_name, aws_region, and any account-specific IDs.
  4. Update the backend bucket/key/region in versions.tf.
  5. All other values are pre-configured with sensible defaults and require no changes for a standard testnet deployment.

Kubernetes provider authentication:

When eks.enabled = true (testnet-complete), the Kubernetes and Helm providers are configured automatically using the EKS cluster outputs — no manual credential configuration is needed.

When eks.enabled = false (testnet-existing-infra), you must supply kubernetes_provider in terraform.tfvars:

kubernetes_provider = {
  host                   = "https://<cluster-endpoint>"
  cluster_ca_certificate = "<base64-encoded-ca-cert>"
  cluster_name           = "<cluster-name>"
  oidc_provider_arn      = "arn:aws:iam::<account>:oidc-provider/<oidc-id>"
}

Retrieve these values from your existing cluster:

aws eks describe-cluster --name <cluster-name> --query 'cluster.{endpoint:endpoint,ca:certificateAuthority.data}'
aws eks describe-cluster --name <cluster-name> --query 'cluster.identity.oidc.issuer'
terraform init
terraform plan
terraform apply

Tests

Uses the native Terraform test framework (requires Terraform ≥ 1.10). All tests use mock providers and command = plan — no real AWS credentials needed.

Run all tests:

terraform test                        # root module
cd modules/<name> && terraform test   # individual submodule

Tests live in tests/unit.tftest.hcl within each module directory.


Pre-commit

This repo uses pre-commit to enforce consistency on every commit.

Dependencies — must be on your PATH:

brew install pre-commit terraform-docs tflint

Hooks that run automatically:

Hook What it does
terraform_fmt Formats all .tf files
terraform_validate Validates module configuration
terraform_tflint Lints for common mistakes and best practices
terraform_docs Regenerates the BEGIN_TF_DOCS sections in all README.md files
check-merge-conflict Blocks commits containing unresolved merge conflict markers
end-of-file-fixer Ensures files end with a newline
trailing-whitespace Removes trailing whitespace

To run all hooks manually: pre-commit run --all-files


Requirements

Name Version
terraform >= 1.11
aws ~> 6.0
helm ~> 3.0
kubernetes ~> 2.0
random ~> 3.0
time ~> 0.13

Providers

Name Version
aws ~> 6.0
time ~> 0.13

Modules

Name Source Version
eks ./modules/eks n/a
k8s_coprocessor_deps ./modules/k8s-coprocessor-deps n/a
k8s_system_charts ./modules/k8s-system-charts n/a
kms ./modules/kms n/a
networking ./modules/networking n/a
rds ./modules/rds n/a
s3 ./modules/s3 n/a

Resources

Name Type
aws_vpc_security_group_ingress_rule.cluster_from_rds_client resource
aws_vpc_security_group_ingress_rule.node_from_rds_client resource
time_sleep.wait_for_tx_sender_iam_propagation resource
aws_caller_identity.current data source
aws_partition.current data source

Inputs

Name Description Type Default Required
aws_region AWS region where resources will be deployed. string n/a yes
default_tags Tags to apply to all resources. map(string) {} no
eks EKS cluster configuration. Set enabled = false to skip all EKS resources.
object({
enabled = optional(bool, false)

cluster = optional(object({
# Naming
version = optional(string, "1.35")
name_override = optional(string, null) # overrides computed "-" cluster name

# Endpoint access
endpoint_public_access = optional(bool, false)
endpoint_private_access = optional(bool, true)
endpoint_public_access_cidrs = optional(list(string), [])

# Auth
enable_irsa = optional(bool, true)
enable_creator_admin_permissions = optional(bool, true) # grants the Terraform caller admin access
admin_role_arns = optional(list(string), [])
}), {})

addons = optional(object({
# Standard managed addons; each value is passed verbatim to the upstream eks module
defaults = optional(map(any), {
aws-ebs-csi-driver = { most_recent = true }
coredns = { most_recent = true }
vpc-cni = { most_recent = true, before_compute = true }
kube-proxy = { most_recent = true }
eks-pod-identity-agent = { most_recent = true, before_compute = true }
})

# Additional addons merged on top of defaults
extra = optional(map(any), {})

# VPC CNI environment tuning
vpc_cni_config = optional(object({
init = optional(object({
env = optional(object({
DISABLE_TCP_EARLY_DEMUX = optional(string, "true")
}), {})
}), {})
env = optional(object({
ENABLE_POD_ENI = optional(string, "true")
POD_SECURITY_GROUP_ENFORCING_MODE = optional(string, "standard")
ENABLE_PREFIX_DELEGATION = optional(string, "true")
WARM_PREFIX_TARGET = optional(string, "1")
}), {})
}), {})
}), {})

node_groups = optional(object({
# Defaults merged into every node group (same schema as groups entries)
defaults = optional(map(any), {})

# IAM policies attached to every node group's IAM role
default_iam_policies = optional(map(string), {
AmazonEBSCSIDriverPolicy = "arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy"
AmazonEC2ContainerRegistryReadOnly = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
AmazonEKSWorkerNodePolicy = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
AmazonEKS_CNI_Policy = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
})

groups = optional(map(object({
# Capacity
capacity_type = optional(string, "ON_DEMAND") # "ON_DEMAND"
"SPOT"
min_size = optional(number, 1)
max_size = optional(number, 3)
desired_size = optional(number, 1)

# Instance
instance_types = optional(list(string), ["m6i.large"])
ami_type = optional(string, "AL2023_x86_64_STANDARD")
use_custom_launch_template = optional(bool, true)

# Storage
disk_size = optional(number, 30)
disk_type = optional(string, "gp3")

# Scheduling
labels = optional(map(string), {})
tags = optional(map(string), {})
use_additional_subnets = optional(bool, false) # place group in additional_subnet_ids instead of private
taints = optional(map(object({
key = string
value = optional(string)
effect = string # "NO_SCHEDULE"
"NO_EXECUTE"
environment Deployment environment (e.g. testnet, mainnet). string n/a yes
k8s_coprocessor_deps Kubernetes coprocessor resource configuration (namespaces, service accounts, storage classes, ExternalName services).
object({
enabled = optional(bool, false)

default_namespace = optional(string, "coproc")

# Namespaces
namespaces = optional(map(object({
enabled = optional(bool, true)
labels = optional(map(string), {})
annotations = optional(map(string), {})
})), {})

# Service accounts — built-in toggles + custom extras.
service_accounts = optional(object({
# coprocessor: legacy IRSA role with S3 access (s3:*Object + s3:ListBucket).
coprocessor = optional(object({
enabled = optional(bool, false)
s3_bucket_key = optional(string, "coprocessor")
}), {})

# sns_worker: IRSA role with S3 access (s3:*Object + s3:ListBucket).
sns_worker = optional(object({
enabled = optional(bool, true)
s3_bucket_key = optional(string, "coprocessor")
}), {})

# db_admin: IRSA role with RDS master secret (GetSecretValue + DescribeSecret).
db_admin = optional(object({
enabled = optional(bool, true)
}), {})

# tx_sender: IRSA role with KMS Sign/Verify on the coprocessor keypair.
# Set kms_key_access = false to omit the KMS policy (e.g. when the key lives in another account).
tx_sender = optional(object({
enabled = optional(bool, true)
kms_key_access = optional(bool, true)
}), {})

# Additional service accounts. An entry with the same key as a built-in overrides it.
extra = optional(map(object({
name = string
namespace = optional(string, null)
iam_role_name_override = optional(string, null)
s3_bucket_access = optional(map(object({
actions = list(string)
})), {})
rds_master_secret_access = optional(bool, false)
kms_key_access = optional(bool, false)
iam_policy_statements = optional(list(object({
sid = optional(string, "")
effect = string
actions = list(string)
resources = list(string)
conditions = optional(list(object({
test = string
variable = string
values = list(string)
})), [])
})), [])
labels = optional(map(string), {})
annotations = optional(map(string), {})
})), {})
}), {})

# Storage classes — built-in toggles + custom extras.
storage_classes = optional(object({
# gp3: encrypted EBS gp3, WaitForFirstConsumer, set as cluster default.
gp3 = optional(object({
enabled = optional(bool, true)
}), {})

# Additional storage classes. An entry with the same key as a built-in overrides it.
extra = optional(map(object({
provisioner = string
reclaim_policy = optional(string, "Delete")
volume_binding_mode = optional(string, "WaitForFirstConsumer")
allow_volume_expansion = optional(bool, true)
parameters = optional(map(string), {})
annotations = optional(map(string), {})
labels = optional(map(string), {})
})), {})
}), {})

# ExternalName services — map key becomes the Service name.
# When endpoint is omitted the root module resolves it from the matching module output (see local.module_endpoints).
external_name_services = optional(map(object({
enabled = optional(bool, true)
endpoint = optional(string, null)
namespace = optional(string, null)
annotations = optional(map(string), {})
})), {})
})
{
"enabled": false
}
no
k8s_system_charts Kubernetes system-level applications to deploy via Helm.
object({
enabled = optional(bool, false)

# Toggle built-in applications on/off. See modules/k8s-system-charts for full docs.
defaults = optional(object({
karpenter_nodepools = optional(object({
enabled = optional(bool, true)
}), {})
prometheus_operator_crds = optional(object({
enabled = optional(bool, true)
repository = optional(string, "https://prometheus-community.github.io/helm-charts")
chart = optional(string, "prometheus-operator-crds")
version = optional(string, "28.0.1")
}), {})
metrics_server = optional(object({
enabled = optional(bool, true)
repository = optional(string, "https://kubernetes-sigs.github.io/metrics-server")
chart = optional(string, "metrics-server")
version = optional(string, "3.13.0")
image_tag = optional(string, "v0.8.0")
values = optional(string, "")
}), {})
karpenter = optional(object({
enabled = optional(bool, true)
repository = optional(string, "oci://public.ecr.aws/karpenter")
chart = optional(string, "karpenter")
version = optional(string, "1.8.2")
controller_image_tag = optional(string, "v1.11.0")
values = optional(string, "")
}), {})
k8s_monitoring = optional(object({
enabled = optional(bool, false)
repository = optional(string, "https://grafana.github.io/helm-charts")
chart = optional(string, "k8s-monitoring")
version = optional(string, "4.0.1")
prometheus_url = optional(string, "")
loki_url = optional(string, "")
otlp_url = optional(string, "")
alloy_operator_image_tag = optional(string, "v0.5.3")
alloy_image_tag = optional(string, "v1.15.0")
node_exporter_image_tag = optional(string, "v1.11.0")
values = optional(string, "")
}), {})
prometheus_rds_exporter = optional(object({
enabled = optional(bool, false)
repository = optional(string, "oci://public.ecr.aws/qonto")
chart = optional(string, "prometheus-rds-exporter-chart")
version = optional(string, "0.16.0")
values = optional(string, "")
}), {})
prometheus_postgres_exporter = optional(object({
enabled = optional(bool, false)
repository = optional(string, "https://prometheus-community.github.io/helm-charts")
chart = optional(string, "prometheus-postgres-exporter")
version = optional(string, "7.3.0")
image_tag = optional(string, "v0.19.1")
values = optional(string, "")
}), {})
coprocessor_sql_exporter = optional(object({
enabled = optional(bool, false)
repository = optional(string, "oci://hub.zama.org/zama-protocol/zama.ai")
chart = optional(string, "fhevm-sql-exporter")
version = optional(string, "2.0.0")
image_tag = optional(string, "0.23.0")
values = optional(string, "")
}), {})
}), {})

# Additional custom applications. An entry with the same key as a built-in overrides it.
extra = optional(map(object({
namespace = object({
name = string
create = optional(bool, false)
})
service_account = optional(object({
create = optional(bool, false)
name = optional(string, null)
labels = optional(map(string), {})
annotations = optional(map(string), {})
}), null)
irsa = optional(object({
enabled = optional(bool, false)
role_name = optional(string, null)
policy_statements = optional(list(object({
sid = optional(string, "")
effect = string
actions = list(string)
resources = list(string)
})), [])
}), { enabled = false })
helm_chart = optional(object({
enabled = optional(bool, true)
repository = string
chart = string
version = string
values = optional(string, "")
set = optional(map(string), {})
crd_chart = optional(bool, false)
create_namespace = optional(bool, false)
atomic = optional(bool, true)
wait = optional(bool, true)
timeout = optional(number, 300)
}), null)
additional_manifests = optional(object({
enabled = optional(bool, false)
manifests = optional(map(string), {})
}), { enabled = false })
})), {})
})
{
"enabled": false
}
no
kms Coprocessor KMS keypair configuration.

Creates an asymmetric AWS KMS key (ECC_SECG_P256K1, SIGN_VERIFY) with
EXTERNAL origin so an Ethereum secp256k1 private key can be imported,
plus an alias alias/<partner_name>-<environment>-coprocessor-keypair.

Cross-account deployments are handled out-of-band: invoke the kms
submodule directly with an AWS provider configured for the target
account. The root module always creates the key in the same account
as the rest of the infrastructure.
object({
enabled = optional(bool, false)
consumer_role_arns = optional(list(string), [])
deletion_window_in_days = optional(number, 30)
tags = optional(map(string), {})
})
{
"enabled": false
}
no
kubernetes_provider Kubernetes provider configuration. When eks.enabled = true these are resolved automatically from the EKS module. Set explicitly when bringing your own cluster.
object({
host = optional(string, null)
cluster_ca_certificate = optional(string, null)
cluster_name = optional(string, null)
oidc_provider_arn = optional(string, null)
})
{} no
networking VPC and subnet configuration. Set enabled = false to skip all networking resources.
object({
enabled = optional(bool, false)

vpc = optional(object({
# Base
cidr = string
availability_zones = optional(list(string), []) # leave empty to auto-discover AZs
single_nat_gateway = optional(bool, false) # true = one NAT GW shared across AZs (cheaper, less resilient)

# Subnet CIDR calculation
private_subnet_cidr_mask = optional(number, 20)
public_subnet_cidr_mask = optional(number, 20)

# Flow logs
flow_log_enabled = optional(bool, false)
flow_log_destination_arn = optional(string, null)
}), null)

additional_subnets = optional(object({
enabled = optional(bool, false)
cidr_mask = optional(number, 20)

# AZs to place additional subnets in. Defaults to vpc.availability_zones.
# Set to a subset (e.g. ["eu-west-1c"]) to add VPC presence in new AZs
# without touching the EKS cluster's vpc_config (AWS does not allow
# changing the AZ set of an existing cluster).
availability_zones_for_additional_subnets = optional(list(string), null)

# EKS integration
expose_for_eks = optional(bool, false) # add karpenter.sh/discovery tag
elb_role = optional(string, null) # "internal"
"public" null
tags = optional(map(string), {})
node_groups = optional(list(string), [])
}), { enabled = false })

# For usage of an existing VPC (bypasses networking module for RDS)
existing_vpc = optional(object({
vpc_id = string
private_subnet_ids = list(string)
private_subnet_cidr_blocks = optional(list(string), [])
}))
})
partner_name Partner identifier — used as a name prefix across all resources. string n/a yes
rds RDS instance configuration. Set enabled = false to skip.
object({
enabled = optional(bool, false)

# Naming
db_name = optional(string, null)
identifier_override = optional(string, null)

# Engine
engine = optional(string, "postgres")
engine_version = optional(string, "17")

# Instance
instance_class = optional(string, "db.m5.4xlarge")
allocated_storage = optional(number, 400)
max_allocated_storage = optional(number, 1000)
multi_az = optional(bool, false)
port = optional(number, 5432)

# Credentials
username = optional(string, "postgres")
manage_master_user_password = optional(bool, true) # true = Secrets Manager managed (recommended)
password_wo = optional(string, null) # write-only; only used when manage_master_user_password = false
password_wo_version = optional(number, 1) # increment to rotate a non-managed password
enable_master_password_rotation = optional(bool, true)
master_password_rotation_days = optional(number, 7)
iam_database_authentication_enabled = optional(bool, true)

# Maintenance & backups
maintenance_window = optional(string, "Mon:00:00-Mon:03:00")
backup_retention_period = optional(number, 30)
deletion_protection = optional(bool, true)

# Monitoring
monitoring_interval = optional(number, 60)
create_monitoring_role = optional(bool, true)
monitoring_role_name = optional(string, null)
existing_monitoring_role_arn = optional(string, null)

# Parameters
# NOTE: rds.force_ssl = 0 is a temporary workaround for binary issues with
# SSL connections; remove once resolved.
parameters = optional(list(object({
name = string
value = string
})), [{ name = "rds.force_ssl", value = "0" }])

# Security group
additional_allowed_cidr_blocks = optional(list(string), [])
})
{
"enabled": true
}
no
s3 S3 configuration.

- buckets: Map of S3 buckets to create.
The map key is a short logical name (e.g. "coprocessor", "raw-data").
Each entry defines configuration and behavior for that bucket.
object({
buckets = map(object({
# Human-readable description (used for tagging)
purpose = optional(string, "coprocessor-storage")

# Override the computed bucket name (use when importing a pre-existing bucket)
name_override = optional(string, null)

# Allow deletion even if objects exist
force_destroy = optional(bool, false)

# Enable object versioning
versioning = optional(bool, true)

# Preconfigured bundle of public_access + cors + policy_statements.
# When set, these three fields MUST be left unset. Allowed values:
# - "public": bucket is publicly readable, CORS open, with PublicRead + ZamaList policy statements.
preconfigured_bucket_access_profile = optional(string, null)

# Public access configuration. Leave unset when preconfigured_bucket_access_profile is set.
public_access = optional(object({
enabled = bool
}), null)

# CORS configuration. Leave unset when preconfigured_bucket_access_profile is set.
cors = optional(object({
enabled = bool
allowed_origins = list(string)
allowed_methods = list(string)
allowed_headers = list(string)
expose_headers = list(string)
}), null)

# CloudFront distribution
cloudfront = optional(object({
enabled = optional(bool, false)
price_class = optional(string, "PriceClass_All")
compress = optional(bool, true)
viewer_protocol_policy = optional(string, "redirect-to-https")
allowed_methods = optional(list(string), ["GET", "HEAD"])
cached_methods = optional(list(string), ["GET", "HEAD"])
cache_policy_id = optional(string, "658327ea-f89d-4fab-a63d-7e88639e58f6") # AWS managed CachingOptimized
geo_restriction_type = optional(string, "none")
geo_restriction_locations = optional(list(string), [])
aliases = optional(list(string), []) # custom hostnames (CNAMEs) for the distribution; requires acm_certificate_arn
acm_certificate_arn = optional(string, null) # if set, used instead of default CloudFront certificate
ssl_support_method = optional(string, "sni-only") # only relevant when acm_certificate_arn is set
minimum_protocol_version = optional(string, "TLSv1.2_2021") # only relevant when acm_certificate_arn is set
}), { enabled = false })

# Bucket policies. Leave unset when preconfigured_bucket_access_profile is set.
policy_statements = optional(list(object({
sid = string
effect = string
principals = map(list(string))
actions = list(string)
resources = list(string)
conditions = optional(list(object({
test = string
variable = string
values = list(string)
})), [])
})), null)
}))
})
{
"buckets": {}
}
no

Outputs

Name Description
additional_subnet_ids List of additional subnet IDs.
eks_cluster_certificate_authority_data Base64-encoded certificate authority data for the EKS cluster.
eks_cluster_endpoint The EKS cluster API endpoint.
eks_cluster_name The EKS cluster name.
eks_karpenter_iam_role_arn IAM role ARN for the Karpenter controller.
eks_karpenter_node_iam_role_arn IAM role ARN for Karpenter-managed nodes.
eks_karpenter_queue_name SQS queue name for Karpenter interruption handling.
eks_oidc_provider_arn The ARN of the OIDC provider for IRSA.
kms_alias_arn KMS alias ARN. Null when kms.enabled = false.
kms_alias_name KMS alias name. Null when kms.enabled = false.
kms_key_arn KMS key ARN of the coprocessor keypair. Null when kms.enabled = false.
kms_key_id KMS key ID of the coprocessor keypair. Null when kms.enabled = false.
private_subnet_ids List of private subnet IDs.
rds_client_security_group_id ID of the rds-client SG attached to pods (via SecurityGroupPolicy) that need DB access.
rds_db_instance_address The RDS instance hostname (without port).
rds_db_instance_arn The ARN of the RDS instance.
rds_db_instance_endpoint The RDS instance connection endpoint (host:port).
rds_db_instance_identifier The identifier of the RDS instance.
rds_db_instance_port The port the RDS instance is listening on.
rds_master_secret_arn ARN of the Secrets Manager secret containing the RDS master user password. Null when manage_master_user_password = false or rds.enabled = false.
rds_server_security_group_id ID of the rds-server SG attached to the RDS instance.
s3_bucket_arns Map of logical bucket key to bucket ARN.
s3_bucket_names Map of logical bucket key to bucket name.
s3_cloudfront_distribution_ids Map of logical bucket key to CloudFront distribution ID.
s3_cloudfront_domain_names Map of logical bucket key to CloudFront distribution domain name.
vpc_id The ID of the VPC.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages