From c9ec79474c68764a824d3e1b811a138d5ba3f5b6 Mon Sep 17 00:00:00 2001 From: Josh Deeden Date: Mon, 6 Jul 2026 10:10:38 -0700 Subject: [PATCH 1/2] feat(cilium): gated WireGuard transparent encryption support (ADR-057 Phase 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds east-west in-transit encryption to the cilium module — the buildable half of ADR-057 (service identity & east-west zero trust). Pod traffic between nodes is encrypted on the wire; the mTLS/SPIFFE workload-identity half (Phase 2) stays Proposed + parked (regulated-tier only, no consumer yet, needs a Cilium-mTLS-vs- SPIRE spike). - New gated vars (default OFF, pure no-op until a unit opts in): encryption_enabled, encryption_type ("wireguard" default | "ipsec"), node_encryption. - Wired as a distinct top-level `encryption` fragment in datapath_values. - WireGuard chosen: in-kernel on the AL2023 6.x nodes (kernel 6.12, no key mgmt) and simplest on the overlay datapath (tunnel/VXLAN + cluster-pool) — avoids the native-ENI WireGuard caveats. Cilium stacks it under VXLAN and auto-adjusts MTU. - ADR-057 amended in place: Phase 1 concretized (WireGuard, fleet-default — the open fleet-vs-tier question resolved to fleet-default — pod-to-pod only); Status flips to Accepted once applied + verified. - README + Inputs table synced. Capability only — enabling on a cluster is a separate, deliberate step: flipping a unit input triggers a rolling Cilium restart (highest blast radius), so it rolls out preprod-first in a maintenance window with a perf measurement, from the main checkout (never a worktree). Not applied here. Toward ADR-057 Phase 1 (east-west encryption). Co-Authored-By: Claude Opus 4.8 (1M context) --- ...rvice-identity-and-east-west-zero-trust.md | 19 +++++++++++ infra/modules/cilium/README.md | 7 ++++ infra/modules/cilium/main.tf | 2 ++ infra/modules/cilium/variables.tf | 32 +++++++++++++++++++ 4 files changed, 60 insertions(+) diff --git a/docs/adrs/057-service-identity-and-east-west-zero-trust.md b/docs/adrs/057-service-identity-and-east-west-zero-trust.md index 6b0091e5..778babae 100644 --- a/docs/adrs/057-service-identity-and-east-west-zero-trust.md +++ b/docs/adrs/057-service-identity-and-east-west-zero-trust.md @@ -9,6 +9,25 @@ identity ([ADR-041](041-pod-identity-for-tenant-workloads.md)/[ADR-047](047-pod- and human identity ([ADR-053](053-identity-and-cross-system-authorization-strategy.md)) planes. The mTLS posture becomes part of the tier's **network** isolation column ([ADR-049](049-tenant-model-team-tenant-zone.md)). +> **Amendment (2026-07-06, #1193) — Phase 1 (transparent encryption) split out and built into the module.** +> Decision #1's *encryption* half is concretized and made buildable now; the *mTLS / SPIFFE* half (decisions +> #2–#4) stays Proposed and parked (regulated-tier only — no regulated tenant exists yet — and it still needs +> the open Cilium-mTLS-vs-SPIRE spike). +> +> - **Backend: WireGuard** (`encryption.type=wireguard`) — in-kernel on the AL2023 6.x nodes (kernel 6.12, no +> key management) and simplest on the platform's **overlay datapath** (tunnel/VXLAN + cluster-pool IPAM), +> which sidesteps the WireGuard caveats that live in native-ENI mode. Cilium stacks it under the VXLAN +> tunnel and auto-adjusts MTU for the added overhead. +> - **Open question resolved: fleet-default, not tier-gated.** Encryption-in-transit is a blanket good and +> decision #3 already puts it in the `standard` tier, so it is enabled cluster-wide rather than per-tier. +> - **Scope: pod-to-pod only** — `node_encryption` (host-to-host) stays off for Phase 1 (it also covers host + +> health-check traffic and is a more invasive later step). +> - **Delivered here:** gated module support in the `cilium` module (`encryption_enabled` / `encryption_type` +> / `node_encryption`, default off). **Enablement is a separate step:** flipping a unit's input triggers a +> **rolling Cilium restart** on the CNI — highest blast radius — so it rolls out **preprod-first**, in a +> maintenance window, from the main checkout, with a **perf-overhead measurement** before the platform +> cluster follows. Status flips to Accepted once applied + verified. + ## Context North-south (ingress) is Gateway API + TLS. East-west is governed by Cilium **L3/L4 NetworkPolicy + diff --git a/infra/modules/cilium/README.md b/infra/modules/cilium/README.md index 3d52e4fe..4f6d9a47 100644 --- a/infra/modules/cilium/README.md +++ b/infra/modules/cilium/README.md @@ -13,6 +13,10 @@ Deploys Cilium CNI via Helm with a **configurable datapath** and cloud-specific | `pod_cidr_mask_size` | `24` (256 IPs/node) | _(n/a)_ | | `egress_masquerade_interfaces` | `""` (all) | `"ens+"` | +## Transparent encryption (in-transit) + +East-west zero trust ([ADR-057](../../../docs/adrs/057-service-identity-and-east-west-zero-trust.md) Phase 1). Set `encryption_enabled = true` to encrypt pod traffic on the wire between nodes; `encryption_type` defaults to **WireGuard** (in-kernel on the AL2023 6.x nodes, no key management, simplest on the overlay datapath — Cilium stacks it under the VXLAN tunnel and auto-adjusts MTU). `node_encryption` (host-to-host) stays off for Phase 1. Off by default; enabling is a **rolling Cilium restart**, so roll it out per environment (preprod first) and verify with `cilium status` (`Encryption: Wireguard`). Cryptographic workload identity (Cilium mTLS / SPIFFE) is ADR-057 Phase 2 — not in this module yet. + **Overlay** decouples pod IPs from the VPC (pods draw from `pod_cidr`, encapsulated VXLAN between nodes) — pod density is bounded by `pod_cidr`, not the node subnet. **ENI native** gives pods routable VPC IPs (no encapsulation, VPC-level flow visibility) but density is bounded by the node subnet size and instance ENI limits. Per-cluster `pod_cidr`s must not overlap (keeps the design ClusterMesh-ready). See `infra/docs/08-kubernetes-network-design.md`. ## Usage @@ -142,6 +146,8 @@ No modules. | [debug](#input\_debug) | Debug configuration for Cilium | `any` |
{
"enabled": false
}
| no | | [egress\_masquerade\_interfaces](#input\_egress\_masquerade\_interfaces) | Interface glob to masquerade on (e.g. 'ens+' for ENI native). Empty = all interfaces (overlay default). Must be empty when bpf\_masquerade = true. | `string` | `""` | no | | [enable\_ipv4\_masquerade](#input\_enable\_ipv4\_masquerade) | Masquerade pod egress to the node IP for traffic leaving the cluster. | `bool` | `true` | no | +| [encryption\_enabled](#input\_encryption\_enabled) | Enable Cilium transparent encryption of pod traffic in transit between nodes (ADR-057 Phase 1). Off by default. | `bool` | `false` | no | +| [encryption\_type](#input\_encryption\_type) | Transparent-encryption backend when encryption\_enabled = true. 'wireguard' (platform default — in-kernel, no key management) or 'ipsec'. | `string` | `"wireguard"` | no | | [environment](#input\_environment) | Environment name (e.g., dev, test, prod) | `string` | `"dev"` | no | | [external\_ips\_enabled](#input\_external\_ips\_enabled) | Enable ExternalIPs service support | `bool` | `true` | no | | [gateway\_api\_crd\_version](#input\_gateway\_api\_crd\_version) | Gateway API CRD version to install (experimental channel). Set to empty string to skip CRD installation. | `string` | `"v1.3.0"` | no | @@ -169,6 +175,7 @@ No modules. | [kube\_proxy\_replacement](#input\_kube\_proxy\_replacement) | KubeProxy replacement mode (false, 'strict', 'partial', 'probe') | `string` | `"false"` | no | | [kubeconfig\_path](#input\_kubeconfig\_path) | Path to a kubeconfig file for kubectl operations. If empty, uses the default kubeconfig. | `string` | `""` | no | | [mtu](#input\_mtu) | Override the device MTU. 0 = auto-detect (Cilium subtracts tunnel overhead). Pin to 1500 if jumbo frames cause PMTU black-holing on egress. | `number` | `0` | no | +| [node\_encryption](#input\_node\_encryption) | Also encrypt node-to-node (host) traffic, not just pod-to-pod. Off for Phase 1 — node encryption also covers host + health-check traffic and is a later, more invasive step. | `bool` | `false` | no | | [namespace](#input\_namespace) | Kubernetes namespace to install Cilium into | `string` | `"kube-system"` | no | | [native\_routing\_cidr](#input\_native\_routing\_cidr) | CIDR that Cilium treats as natively routable (no masquerade within it). Required when routing\_mode = native AND ipam\_mode != eni; ENI mode auto-derives it. | `string` | `""` | no | | [node\_port\_enabled](#input\_node\_port\_enabled) | Enable NodePort service support | `bool` | `true` | no | diff --git a/infra/modules/cilium/main.tf b/infra/modules/cilium/main.tf index 34105718..cbdf920a 100644 --- a/infra/modules/cilium/main.tf +++ b/infra/modules/cilium/main.tf @@ -34,6 +34,8 @@ locals { var.egress_masquerade_interfaces != "" ? { egressMasqueradeInterfaces = var.egress_masquerade_interfaces } : {}, var.routing_mode == "native" && var.native_routing_cidr != "" ? { ipv4NativeRoutingCIDR = var.native_routing_cidr } : {}, var.mtu > 0 ? { MTU = var.mtu } : {}, + # Transparent encryption (ADR-057 Phase 1) — WireGuard/IPsec on the wire between nodes. + var.encryption_enabled ? { encryption = { enabled = true, type = var.encryption_type, nodeEncryption = var.node_encryption } } : {}, ) # Irreducible per-cloud plumbing — NOT the datapath (that's variable-driven above). diff --git a/infra/modules/cilium/variables.tf b/infra/modules/cilium/variables.tf index 89ca84ff..a7a165e8 100644 --- a/infra/modules/cilium/variables.tf +++ b/infra/modules/cilium/variables.tf @@ -118,6 +118,38 @@ variable "mtu" { } } +# --------------------------------------------------------------------------- +# Transparent encryption (in-transit) — ADR-057 Phase 1 (east-west zero trust) +# +# Encrypts pod traffic on the wire between nodes. Off by default; opted into +# per-unit. WireGuard is the platform default: in-kernel on the AL2023 6.x +# nodes (no key management), and simplest on the overlay (tunnel/cluster-pool) +# datapath — Cilium stacks it under the VXLAN tunnel and auto-adjusts MTU for +# the added overhead. Enabling is a rolling Cilium restart; roll out per env. +# --------------------------------------------------------------------------- + +variable "encryption_enabled" { + description = "Enable Cilium transparent encryption of pod traffic in transit between nodes (ADR-057 Phase 1). Off by default." + type = bool + default = false +} + +variable "encryption_type" { + description = "Transparent-encryption backend when encryption_enabled = true. 'wireguard' (platform default — in-kernel, no key management) or 'ipsec'." + type = string + default = "wireguard" + validation { + condition = contains(["wireguard", "ipsec"], var.encryption_type) + error_message = "encryption_type must be 'wireguard' or 'ipsec'." + } +} + +variable "node_encryption" { + description = "Also encrypt node-to-node (host) traffic, not just pod-to-pod. Off for Phase 1 — node encryption also covers host + health-check traffic and is a later, more invasive step." + type = bool + default = false +} + variable "k8s_service_host" { description = "Kubernetes API server hostname (required for BYOCNI — in-cluster service IP unreachable before CNI exists)" type = string From 19097ea9a01ca9d9a939705a1f4ed037b26ee32b Mon Sep 17 00:00:00 2001 From: Josh Deeden Date: Mon, 6 Jul 2026 13:17:47 -0700 Subject: [PATCH 2/2] docs(cilium): regenerate terraform-docs README block (doc-currency gate) Hand-added the encryption Inputs rows; regenerate via terraform-docs so the BEGIN_TF_DOCS block matches canonical output (the doc-currency CI gate). --- infra/modules/cilium/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/modules/cilium/README.md b/infra/modules/cilium/README.md index 4f6d9a47..d1cc39af 100644 --- a/infra/modules/cilium/README.md +++ b/infra/modules/cilium/README.md @@ -175,9 +175,9 @@ No modules. | [kube\_proxy\_replacement](#input\_kube\_proxy\_replacement) | KubeProxy replacement mode (false, 'strict', 'partial', 'probe') | `string` | `"false"` | no | | [kubeconfig\_path](#input\_kubeconfig\_path) | Path to a kubeconfig file for kubectl operations. If empty, uses the default kubeconfig. | `string` | `""` | no | | [mtu](#input\_mtu) | Override the device MTU. 0 = auto-detect (Cilium subtracts tunnel overhead). Pin to 1500 if jumbo frames cause PMTU black-holing on egress. | `number` | `0` | no | -| [node\_encryption](#input\_node\_encryption) | Also encrypt node-to-node (host) traffic, not just pod-to-pod. Off for Phase 1 — node encryption also covers host + health-check traffic and is a later, more invasive step. | `bool` | `false` | no | | [namespace](#input\_namespace) | Kubernetes namespace to install Cilium into | `string` | `"kube-system"` | no | | [native\_routing\_cidr](#input\_native\_routing\_cidr) | CIDR that Cilium treats as natively routable (no masquerade within it). Required when routing\_mode = native AND ipam\_mode != eni; ENI mode auto-derives it. | `string` | `""` | no | +| [node\_encryption](#input\_node\_encryption) | Also encrypt node-to-node (host) traffic, not just pod-to-pod. Off for Phase 1 — node encryption also covers host + health-check traffic and is a later, more invasive step. | `bool` | `false` | no | | [node\_port\_enabled](#input\_node\_port\_enabled) | Enable NodePort service support | `bool` | `true` | no | | [operator\_prometheus\_enabled](#input\_operator\_prometheus\_enabled) | Enable Prometheus metrics for Cilium operator | `bool` | `true` | no | | [operator\_resources\_limits\_cpu](#input\_operator\_resources\_limits\_cpu) | CPU limit for Cilium operator | `string` | `"500m"` | no |