From 979365f42854d2d387eb3eb348a72ead8477a4a6 Mon Sep 17 00:00:00 2001 From: Alec Wilson Date: Wed, 10 Jun 2026 09:38:31 +0100 Subject: [PATCH] Reference deployment for a workload trust zone cluster in aws --- AGENTS.md | 6 +- control-plane/lint.sh => lint.sh | 11 +- workload/deployment/aws/infra/stack/README.md | 87 +++++++++++ .../aws/infra/stack/common.local.hcl.example | 7 + .../aws/infra/stack/connect/README.md | 42 ++++++ .../connect/trust-zone/.terraform.lock.hcl | 42 ++++++ .../trust-zone/common.local.hcl.example | 14 ++ .../infra/stack/connect/trust-zone/main.tf | 17 +++ .../infra/stack/connect/trust-zone/outputs.tf | 24 +++ .../stack/connect/trust-zone/providers.tf | 4 + .../stack/connect/trust-zone/terragrunt.hcl | 54 +++++++ .../stack/connect/trust-zone/variables.tf | 24 +++ .../stack/connect/trust-zone/versions.tf | 14 ++ .../eks-cluster/cluster/.terraform.lock.hcl | 46 ++++++ .../infra/stack/eks-cluster/cluster/README.md | 96 ++++++++++++ .../cluster/common.local.hcl.example | 74 +++++++++ .../stack/eks-cluster/cluster/terragrunt.hcl | 140 ++++++++++++++++++ workload/deployment/aws/infra/stack/root.hcl | 52 +++++++ .../aws/infra/stack/spire-server/README.md | 17 +++ .../spire-server/iam-role/.terraform.lock.hcl | 25 ++++ .../iam-role/common.local.hcl.example | 21 +++ .../spire-server/iam-role/terragrunt.hcl | 49 ++++++ workload/deployment/aws/k8s/README.md | 60 ++++++++ .../k8s/cert-manager/cluster-issuer/apply.sh | 7 + .../cluster-issuer/cluster-issuer.yaml | 6 + .../cluster-issuer/kustomization.yaml | 4 + .../aws/k8s/cert-manager/install.sh | 25 ++++ .../aws/k8s/cert-manager/values.yaml | 9 ++ .../aws/k8s/cert-manager/versions.yaml | 1 + .../spire/generate-local-values.sh | 120 +++++++++++++++ .../aws/k8s/spire-server/spire/install.sh | 26 ++++ .../spire/values.local.yaml.example | 65 ++++++++ .../aws/k8s/spire-server/spire/values.yaml | 63 ++++++++ .../aws/k8s/spire-server/spire/versions.yaml | 2 + 34 files changed, 1249 insertions(+), 5 deletions(-) rename control-plane/lint.sh => lint.sh (81%) create mode 100644 workload/deployment/aws/infra/stack/README.md create mode 100644 workload/deployment/aws/infra/stack/common.local.hcl.example create mode 100644 workload/deployment/aws/infra/stack/connect/README.md create mode 100644 workload/deployment/aws/infra/stack/connect/trust-zone/.terraform.lock.hcl create mode 100644 workload/deployment/aws/infra/stack/connect/trust-zone/common.local.hcl.example create mode 100644 workload/deployment/aws/infra/stack/connect/trust-zone/main.tf create mode 100644 workload/deployment/aws/infra/stack/connect/trust-zone/outputs.tf create mode 100644 workload/deployment/aws/infra/stack/connect/trust-zone/providers.tf create mode 100644 workload/deployment/aws/infra/stack/connect/trust-zone/terragrunt.hcl create mode 100644 workload/deployment/aws/infra/stack/connect/trust-zone/variables.tf create mode 100644 workload/deployment/aws/infra/stack/connect/trust-zone/versions.tf create mode 100644 workload/deployment/aws/infra/stack/eks-cluster/cluster/.terraform.lock.hcl create mode 100644 workload/deployment/aws/infra/stack/eks-cluster/cluster/README.md create mode 100644 workload/deployment/aws/infra/stack/eks-cluster/cluster/common.local.hcl.example create mode 100644 workload/deployment/aws/infra/stack/eks-cluster/cluster/terragrunt.hcl create mode 100644 workload/deployment/aws/infra/stack/root.hcl create mode 100644 workload/deployment/aws/infra/stack/spire-server/README.md create mode 100644 workload/deployment/aws/infra/stack/spire-server/iam-role/.terraform.lock.hcl create mode 100644 workload/deployment/aws/infra/stack/spire-server/iam-role/common.local.hcl.example create mode 100644 workload/deployment/aws/infra/stack/spire-server/iam-role/terragrunt.hcl create mode 100644 workload/deployment/aws/k8s/README.md create mode 100755 workload/deployment/aws/k8s/cert-manager/cluster-issuer/apply.sh create mode 100644 workload/deployment/aws/k8s/cert-manager/cluster-issuer/cluster-issuer.yaml create mode 100644 workload/deployment/aws/k8s/cert-manager/cluster-issuer/kustomization.yaml create mode 100755 workload/deployment/aws/k8s/cert-manager/install.sh create mode 100644 workload/deployment/aws/k8s/cert-manager/values.yaml create mode 100644 workload/deployment/aws/k8s/cert-manager/versions.yaml create mode 100755 workload/deployment/aws/k8s/spire-server/spire/generate-local-values.sh create mode 100755 workload/deployment/aws/k8s/spire-server/spire/install.sh create mode 100644 workload/deployment/aws/k8s/spire-server/spire/values.local.yaml.example create mode 100644 workload/deployment/aws/k8s/spire-server/spire/values.yaml create mode 100644 workload/deployment/aws/k8s/spire-server/spire/versions.yaml diff --git a/AGENTS.md b/AGENTS.md index 4fdffc2..d73f80e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,17 +28,17 @@ When making changes that affect documentation: ## Linting and formatting -`control-plane/lint.sh` runs all linting and formatting tools across the control plane: +`lint.sh` runs all linting and formatting tools across the `control-plane/` and `workload/` directories: - `terraform fmt` on all Terraform modules and stack units - `terragrunt hcl fmt` on all Terragrunt stacks - `terraform-docs` on each module, writing output to `README.md` in the module directory. Module-level description prose is written as a `/** ... */` block comment at the very top of `main.tf` — terraform-docs picks this up as the header automatically. -- `shellcheck` on all shell scripts under `control-plane/` +- `shellcheck` on all shell scripts under `control-plane/` and `workload/` Stack roots are discovered via `root.hcl` and module directories are expected to be siblings of each stack root named `modules/`. Running the script requires `terraform`, `terragrunt`, `terraform-docs`, and `shellcheck` to be installed; it will report any missing tools before exiting. ```sh -./control-plane/lint.sh +./lint.sh ``` ## Workloads diff --git a/control-plane/lint.sh b/lint.sh similarity index 81% rename from control-plane/lint.sh rename to lint.sh index 60cb224..ea0e80e 100755 --- a/control-plane/lint.sh +++ b/lint.sh @@ -14,7 +14,11 @@ if [[ ${#missing[@]} -gt 0 ]]; then exit 1 fi -mapfile -t STACK_DIRS < <(find "${SCRIPT_DIR}" -name "root.hcl" -not -path "*/.terragrunt-cache/*" -exec dirname {} \; | sort) +mapfile -t STACK_DIRS < <( + find "${SCRIPT_DIR}/control-plane" "${SCRIPT_DIR}/workload" \ + -name "root.hcl" -not -path "*/.terragrunt-cache/*" \ + -exec dirname {} \; | sort +) mapfile -t MODULES_DIRS < <( for stack_dir in "${STACK_DIRS[@]}"; do @@ -51,4 +55,7 @@ done echo "==> shellcheck: scripts" while IFS= read -r script; do shellcheck "${script}" -done < <(find "${SCRIPT_DIR}" -name "*.sh" -not -path "*/.terragrunt-cache/*" | sort) +done < <( + find "${SCRIPT_DIR}/control-plane" "${SCRIPT_DIR}/workload" \ + -name "*.sh" -not -path "*/.terragrunt-cache/*" | sort +) diff --git a/workload/deployment/aws/infra/stack/README.md b/workload/deployment/aws/infra/stack/README.md new file mode 100644 index 0000000..030a4d5 --- /dev/null +++ b/workload/deployment/aws/infra/stack/README.md @@ -0,0 +1,87 @@ +# Trust Zone Infrastructure + +Terragrunt stack that provisions the AWS infrastructure for a Cofide Connect trust zone workload cluster. The SPIRE server is deployed onto this cluster and registered as a trust zone with the Connect control plane. + +The stack reuses the VPC, jump instance, and Route53 hosted zone from the control-plane stack by default. Cross-stack dependencies are optional — set the relevant values in a unit's `common.local.hcl` to deploy against different infrastructure. + +## Configuration + +### Root configuration + +Create the root `common.local.hcl` before running any unit: + +```sh +cp common.local.hcl.example common.local.hcl +``` + +Edit it to set your AWS account, region, and S3 remote state bucket: + +```hcl +locals { + aws_account_id = "123456789012" + aws_region = "eu-west-2" + tf_state_bucket_region = "eu-west-2" + tf_state_bucket_name = "my-tf-state-bucket" + tf_state_key_prefix = "connect-trust-zone" +} +``` + +Use a different `tf_state_key_prefix` from the control-plane stack to avoid state key collisions. + +### Per-unit configuration + +Each unit directory contains a `common.local.hcl.example` documenting the available configuration. Copy it to `common.local.hcl` and edit it before applying. At minimum, set `cluster_admin_role_arns` in `eks-cluster/cluster/common.local.hcl`. + +### IAM mode + +The `spire-server/iam-role` unit supports two authentication modes, configured via `iam_mode` in `common.local.hcl`: + +- **`pod_identity`** (default) — EKS Pod Identity. Requires the `eks-pod-identity-agent` add-on, which is enabled by default in the cluster unit. +- **`irsa`** — IAM Roles for Service Accounts. Requires `enable_irsa = true` in the cluster unit's `common.local.hcl` and `oidc_provider_arn` set in the controller unit's `common.local.hcl`. + +--- + +## Deployment + +Before running any Terragrunt command, create and populate the root configuration file: + +```sh +cp common.local.hcl.example common.local.hcl +# Set aws_account_id, aws_region, tf_state_bucket_region, tf_state_bucket_name, tf_state_key_prefix +``` + +### EKS cluster + +The cluster unit creates the EKS control plane, node group, and add-ons. Apply it first; the controller and SPIRE units depend on its outputs. + +```sh +cd eks-cluster/cluster +cp common.local.hcl.example common.local.hcl +# Set cluster_admin_role_arns to your IAM role ARN. +terragrunt apply +``` + +See [`eks-cluster/cluster/README.md`](eks-cluster/cluster/README.md) for how to access the cluster. + +### SPIRE server IAM role + +Creates the IAM role for the SPIRE server with KMS permissions. Apply this after the cluster unit. See [`spire-server/README.md`](spire-server/README.md) for details. + +```sh +cd spire-server/iam-role && terragrunt apply +``` + +### Connect registration + +Registers the trust zone, cluster, and trust zone server with Cofide Connect in a single apply. See [`connect/README.md`](connect/README.md) for details. + +```sh +export COFIDE_API_TOKEN="" + +cd connect/trust-zone +cp common.local.hcl.example common.local.hcl +# Set trust_domain. +terragrunt apply +``` + +Once applied, run `generate-local-values.sh` in `k8s/spire-server/spire/` to populate the SPIRE server configuration — see the [k8s guide](../../k8s/README.md). diff --git a/workload/deployment/aws/infra/stack/common.local.hcl.example b/workload/deployment/aws/infra/stack/common.local.hcl.example new file mode 100644 index 0000000..d85fc97 --- /dev/null +++ b/workload/deployment/aws/infra/stack/common.local.hcl.example @@ -0,0 +1,7 @@ +locals { + aws_account_id = "1234567890" + aws_region = "eu-west-2" + tf_state_bucket_region = "eu-west-2" + tf_state_bucket_name = "example-bucket-name" + tf_state_key_prefix = "connect-trust-zone" +} diff --git a/workload/deployment/aws/infra/stack/connect/README.md b/workload/deployment/aws/infra/stack/connect/README.md new file mode 100644 index 0000000..dd92a54 --- /dev/null +++ b/workload/deployment/aws/infra/stack/connect/README.md @@ -0,0 +1,42 @@ +# Connect Registration + +Terragrunt unit that registers the trust zone and cluster with Cofide Connect using the [`cofide/cofide`](https://registry.terraform.io/providers/cofide/cofide/latest/docs) Terraform provider. + +This unit is applied after the AWS infrastructure is provisioned and before deploying the SPIRE server onto Kubernetes. The IDs and PSAT configuration it outputs are used to populate the SPIRE server's datastore configuration. + +## Prerequisites + +- `COFIDE_API_TOKEN` — API token for the Connect API; must be set as an environment variable. +- The control-plane `base/dns` unit must be applied — the Connect gRPC address (`:443`) is derived from its `zone_name` output. The provider prepends the `connect.` subdomain automatically. Set `connect_url` in `common.local.hcl` to override (format: `host:port`, no scheme). +- The `eks-cluster/cluster` unit must be applied — the OIDC issuer URL is read from its outputs. Set `oidc_issuer_url` in `common.local.hcl` to override. + +## `trust-zone/` + +Registers a trust zone and cluster with Cofide Connect. The cluster is registered in the same apply as the trust zone because the SPIRE server for this trust zone lives on this cluster. + +**Required values in `common.local.hcl`:** + +| Local | Description | +|-------|-------------| +| `trust_domain` | SPIFFE trust domain for the trust zone (e.g. `trust-zone.example.cofide.dev`) | + +**Outputs:** + +| Name | Description | +|------|-------------| +| `trust_zone_id` | Trust zone ID — used in the SPIRE server datastore config | +| `cluster_id` | Cluster ID — used in the SPIRE server datastore config | +| `bundle_endpoint_url` | Bundle endpoint URL assigned by Connect | + +## Deployment + +```sh +export COFIDE_API_TOKEN="" + +cd trust-zone +cp common.local.hcl.example common.local.hcl +# Set trust_domain. +terragrunt apply +``` + +Once applied, run `generate-local-values.sh` in `k8s/spire-server/spire/` to populate the SPIRE server configuration — see the [k8s guide](../../../k8s/README.md). diff --git a/workload/deployment/aws/infra/stack/connect/trust-zone/.terraform.lock.hcl b/workload/deployment/aws/infra/stack/connect/trust-zone/.terraform.lock.hcl new file mode 100644 index 0000000..ba5bccf --- /dev/null +++ b/workload/deployment/aws/infra/stack/connect/trust-zone/.terraform.lock.hcl @@ -0,0 +1,42 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/cofide/cofide" { + version = "0.10.0" + constraints = "~> 0.9" + hashes = [ + "h1:QP4yF3u09uVHvkUhTiFr5YkWWD/Tey5Wpj0ssjultmU=", + "zh:3d3189e7594fad1b5ecf110b5c4ec802de9f1328487eec106408cecf0ba70284", + "zh:3e7afaff3a0e9c26877400f0e5978f14f62687b0a1ec9cbf57b4710738dbcfba", + "zh:6a023e6866b53a8539251fa2d5b077730637f387291b3a9708ccaeca0d80e6da", + "zh:6cf2446a0035a2531a251dad2a17efb964cc60e42085c2df987f55679d76b320", + "zh:79214fe521515584141cba4b8cbe595bcf06672ea8dc0f663445c96df0d3b03d", + "zh:a01ad42a7275248220d2c8d36411730e0ddb5e353728caabdd5feda3b9b5e9dc", + "zh:a4949d94348dde64a3e7fe828f833bb99ae652d8ee04f3f9b2b521c01ea58ed3", + "zh:bf6332522990a3cade376610bc780c79739a5610aa9cc67b64c5eb7ab91e5132", + "zh:dbeabda7c58b0e4f51aa81df10ca7af9eb451e4fe819c3145d071d2884d2377a", + ] +} + +provider "registry.terraform.io/hashicorp/aws" { + version = "6.49.0" + constraints = "~> 6.0" + hashes = [ + "h1:gW/1w7xNATTgTXKN9Du926VKZ84YgV6BLJDTPimMYkk=", + "zh:11a636bb415bf780f0ad300cab83d687aebdc51381112ae7b29862e0bee43017", + "zh:2d6c4bb861c073d9900a2afc39cc1e38492c6996653e53c7a2083b526fb10ae9", + "zh:49f7ee4a7488f3d31342c5e9dbb577c40e0847a0cab152a0082e9aeef45f5c0f", + "zh:561283c9c9bd36b9d09832e50769b941eb45c43c6ab031f27c8bf78256af4af1", + "zh:576bf944e66d097b29fc45b25a5bbc53e7d4e71a486e2cb126304cf77b51fe79", + "zh:6c6bf8860773c121b9ca22743f733feea943f890fa3aba8740a59579dea16fc4", + "zh:88b963a659e42daac7384a6abb99b3383f9f6c8abad5dedafcd443536b122b84", + "zh:947e9404235ca094e39e7f3f464f99436320a168e1607c550e581fbc70553d48", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:a9c5a37bd1e5e81e85ad3dc3141f1b453b96e9cb8e500bc15bfb9c488fc95dcc", + "zh:b7b8a028fb2eafb98c676f9438808318f7ff0ea76eba03a6653d0940848a31c7", + "zh:be29f31827d6d5567aaec26034e6cceb994460446778ba1d0a435fc7ccd8a9f5", + "zh:c24c60ecffe3a7d44c762e62a29a802aec604096715ad3110b7ca2207124eb3a", + "zh:e2a247c5c7815437969e87cdce1f27f323eea75b97ed53f7605fef05d6406071", + "zh:e590ce9aca3f4fd964f7bf908a24dc3bc88e0b03a8554ccc81b9edcc84690670", + ] +} diff --git a/workload/deployment/aws/infra/stack/connect/trust-zone/common.local.hcl.example b/workload/deployment/aws/infra/stack/connect/trust-zone/common.local.hcl.example new file mode 100644 index 0000000..6252b95 --- /dev/null +++ b/workload/deployment/aws/infra/stack/connect/trust-zone/common.local.hcl.example @@ -0,0 +1,14 @@ +locals { + # Name for the trust zone in Cofide Connect. + trust_zone_name = "connect-trust-zone-aws-reference-arch" + + # SPIFFE trust domain for this trust zone. Required — no default. + trust_domain = "trust-zone.example.cofide.dev" + + # Name for the cluster in Cofide Connect. + cluster_name = "connect-trust-zone-aws-reference-arch" + + # Optional: override values if not reading from their respective dependency units. + # connect_url = "example.cofide.dev:443" + # oidc_issuer_url = "https://oidc.eks.eu-west-2.amazonaws.com/id/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +} diff --git a/workload/deployment/aws/infra/stack/connect/trust-zone/main.tf b/workload/deployment/aws/infra/stack/connect/trust-zone/main.tf new file mode 100644 index 0000000..f5dcd95 --- /dev/null +++ b/workload/deployment/aws/infra/stack/connect/trust-zone/main.tf @@ -0,0 +1,17 @@ +resource "cofide_connect_trust_zone" "this" { + name = var.trust_zone_name + trust_domain = var.trust_domain +} + +resource "cofide_connect_cluster" "this" { + name = var.cluster_name + trust_zone_id = cofide_connect_trust_zone.this.id + profile = "kubernetes" + oidc_issuer_url = var.oidc_issuer_url + external_server = false + kubernetes_context = "" + + trust_provider = { + kind = "kubernetes" + } +} diff --git a/workload/deployment/aws/infra/stack/connect/trust-zone/outputs.tf b/workload/deployment/aws/infra/stack/connect/trust-zone/outputs.tf new file mode 100644 index 0000000..987b556 --- /dev/null +++ b/workload/deployment/aws/infra/stack/connect/trust-zone/outputs.tf @@ -0,0 +1,24 @@ +output "trust_domain" { + description = "The SPIFFE trust domain for this trust zone." + value = cofide_connect_trust_zone.this.trust_domain +} + +output "trust_zone_id" { + description = "The ID of the trust zone registered with Cofide Connect." + value = cofide_connect_trust_zone.this.id +} + +output "bundle_endpoint_url" { + description = "The bundle endpoint URL for this trust zone, as assigned by Cofide Connect." + value = cofide_connect_trust_zone.this.bundle_endpoint_url +} + +output "cluster_id" { + description = "The ID of the cluster registered with Cofide Connect." + value = cofide_connect_cluster.this.id +} + +output "cluster_name" { + description = "The name of the cluster as registered with Cofide Connect." + value = cofide_connect_cluster.this.name +} diff --git a/workload/deployment/aws/infra/stack/connect/trust-zone/providers.tf b/workload/deployment/aws/infra/stack/connect/trust-zone/providers.tf new file mode 100644 index 0000000..1b5abba --- /dev/null +++ b/workload/deployment/aws/infra/stack/connect/trust-zone/providers.tf @@ -0,0 +1,4 @@ +provider "cofide" { + connect_url = var.connect_url + # api_token is configured via the COFIDE_API_TOKEN environment variable. +} diff --git a/workload/deployment/aws/infra/stack/connect/trust-zone/terragrunt.hcl b/workload/deployment/aws/infra/stack/connect/trust-zone/terragrunt.hcl new file mode 100644 index 0000000..b5bc8ed --- /dev/null +++ b/workload/deployment/aws/infra/stack/connect/trust-zone/terragrunt.hcl @@ -0,0 +1,54 @@ +include "root" { + path = find_in_parent_folders("root.hcl") + expose = true +} + +dependency "cluster" { + config_path = "../../eks-cluster/cluster" + + mock_outputs_allowed_terraform_commands = ["apply", "destroy", "plan", "validate"] + mock_outputs = { + oidc_issuer_url = "https://oidc.eks.eu-west-2.amazonaws.com/id/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + } +} + +dependency "cp_dns" { + config_path = "${get_repo_root()}/control-plane/deployment/aws/infra/stack/base/dns" + + mock_outputs_allowed_terraform_commands = ["apply", "destroy", "plan", "validate"] + mock_outputs = { + zone_name = "example.cofide.dev" + } +} + +locals { + default_config = { + trust_zone_name = "connect-trust-zone-aws-reference-arch" + cluster_name = "connect-trust-zone-aws-reference-arch" + } + + unit_config_path = "${get_terragrunt_dir()}/common.local.hcl" + has_local_config = fileexists(local.unit_config_path) + user_config = local.has_local_config ? read_terragrunt_config(local.unit_config_path).locals : {} + merged_config = merge(local.default_config, local.user_config) + + # trust_domain has no default — it must be set in common.local.hcl. + trust_domain = local.merged_config.trust_domain + + user_connect_url = try(local.merged_config.connect_url, null) + user_oidc_issuer_url = try(local.merged_config.oidc_issuer_url, null) +} + +terraform { + source = "." +} + +inputs = { + connect_url = local.user_connect_url != null ? local.user_connect_url : "${dependency.cp_dns.outputs.zone_name}:443" + + trust_zone_name = local.merged_config.trust_zone_name + trust_domain = local.trust_domain + cluster_name = local.merged_config.cluster_name + + oidc_issuer_url = local.user_oidc_issuer_url != null ? local.user_oidc_issuer_url : dependency.cluster.outputs.oidc_issuer_url +} diff --git a/workload/deployment/aws/infra/stack/connect/trust-zone/variables.tf b/workload/deployment/aws/infra/stack/connect/trust-zone/variables.tf new file mode 100644 index 0000000..2557dd4 --- /dev/null +++ b/workload/deployment/aws/infra/stack/connect/trust-zone/variables.tf @@ -0,0 +1,24 @@ +variable "connect_url" { + type = string + description = "Cofide Connect API gRPC address in host:port form (e.g. example.cofide.dev:443). The provider prepends the connect. subdomain. No scheme." +} + +variable "trust_zone_name" { + type = string + description = "The name of the trust zone to register with Cofide Connect." +} + +variable "trust_domain" { + type = string + description = "The SPIFFE trust domain for this trust zone (e.g. trust-zone.example.cofide.dev)." +} + +variable "cluster_name" { + type = string + description = "The name of the cluster to register with Cofide Connect." +} + +variable "oidc_issuer_url" { + type = string + description = "OIDC issuer URL of the EKS cluster (e.g. https://oidc.eks..amazonaws.com/id/)." +} diff --git a/workload/deployment/aws/infra/stack/connect/trust-zone/versions.tf b/workload/deployment/aws/infra/stack/connect/trust-zone/versions.tf new file mode 100644 index 0000000..f33c310 --- /dev/null +++ b/workload/deployment/aws/infra/stack/connect/trust-zone/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.10" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + cofide = { + source = "cofide/cofide" + version = "~> 0.9" + } + } +} diff --git a/workload/deployment/aws/infra/stack/eks-cluster/cluster/.terraform.lock.hcl b/workload/deployment/aws/infra/stack/eks-cluster/cluster/.terraform.lock.hcl new file mode 100644 index 0000000..b9712c5 --- /dev/null +++ b/workload/deployment/aws/infra/stack/eks-cluster/cluster/.terraform.lock.hcl @@ -0,0 +1,46 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "6.49.0" + constraints = "~> 6.0" + hashes = [ + "h1:gW/1w7xNATTgTXKN9Du926VKZ84YgV6BLJDTPimMYkk=", + "zh:11a636bb415bf780f0ad300cab83d687aebdc51381112ae7b29862e0bee43017", + "zh:2d6c4bb861c073d9900a2afc39cc1e38492c6996653e53c7a2083b526fb10ae9", + "zh:49f7ee4a7488f3d31342c5e9dbb577c40e0847a0cab152a0082e9aeef45f5c0f", + "zh:561283c9c9bd36b9d09832e50769b941eb45c43c6ab031f27c8bf78256af4af1", + "zh:576bf944e66d097b29fc45b25a5bbc53e7d4e71a486e2cb126304cf77b51fe79", + "zh:6c6bf8860773c121b9ca22743f733feea943f890fa3aba8740a59579dea16fc4", + "zh:88b963a659e42daac7384a6abb99b3383f9f6c8abad5dedafcd443536b122b84", + "zh:947e9404235ca094e39e7f3f464f99436320a168e1607c550e581fbc70553d48", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:a9c5a37bd1e5e81e85ad3dc3141f1b453b96e9cb8e500bc15bfb9c488fc95dcc", + "zh:b7b8a028fb2eafb98c676f9438808318f7ff0ea76eba03a6653d0940848a31c7", + "zh:be29f31827d6d5567aaec26034e6cceb994460446778ba1d0a435fc7ccd8a9f5", + "zh:c24c60ecffe3a7d44c762e62a29a802aec604096715ad3110b7ca2207124eb3a", + "zh:e2a247c5c7815437969e87cdce1f27f323eea75b97ed53f7605fef05d6406071", + "zh:e590ce9aca3f4fd964f7bf908a24dc3bc88e0b03a8554ccc81b9edcc84690670", + ] +} + +provider "registry.terraform.io/hashicorp/tls" { + version = "4.3.0" + constraints = "~> 4.0" + hashes = [ + "h1:j/BqLS2N2AScZyotd9nZpHdieJ7e5S8y+A+ZfIu8kL8=", + "zh:0ab58d6f8991d436c7d2dbd89ed814709b949b07ac5a54ee53b0aec1fa772a8b", + "zh:60b347abcb56f45d97c56f14d895069cd15a83993f199777f571b79fea3642ee", + "zh:6889be32640349230de3f23856e6f04e0e9ced4a84a27d3f552fa54684448218", + "zh:73f8e1ecf7135033165fb14b7e8bf4d656f3ce13065ec35762ea0481975328c7", + "zh:94ce25ee253eca0b42cae9c856b36bca8103b6453012d1b279c3623c805f2d42", + "zh:96bc6de9fd67bc446fd11257872e1ffb1029a996ed1d65a3f6b43f6d408ad9ab", + "zh:97c609a310a51bfd504d704e036d72064a84bf0bdb36cc08cd4cc66098212b41", + "zh:a12c16e94533c5bd123f75032576b9dc91dd5d5ccd5f7cf331d0f2e1adc55cf8", + "zh:c4f014f876adf7af57188795050bda5b0029d8c7d7773031102b6c36dcf1fc21", + "zh:d9b0a21583aaa3df3a95394fb949a3c515ff71c2ff5a1fc4a73d364aa90bfca5", + "zh:da510d22f0c6d71ad19a76406f106b782448f512375787ecfabb338ed1e311a7", + "zh:f0e9447a9ce3a24cdaa113089e65663c836d8b9bfdb915a1c0284e0112cab5c0", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} diff --git a/workload/deployment/aws/infra/stack/eks-cluster/cluster/README.md b/workload/deployment/aws/infra/stack/eks-cluster/cluster/README.md new file mode 100644 index 0000000..8648feb --- /dev/null +++ b/workload/deployment/aws/infra/stack/eks-cluster/cluster/README.md @@ -0,0 +1,96 @@ +# eks-cluster/cluster + +Terragrunt unit that provisions the EKS cluster for a Cofide Connect trust zone. Creates: + +- EKS control plane with managed add-ons (CoreDNS, kube-proxy, VPC CNI, EKS Pod Identity Agent) +- Managed node group in private subnets +- KMS key for EKS secret encryption +- IAM roles for the cluster and node group +- Security groups for cluster and node communication +- EKS access entries granting cluster-admin to the configured IAM role ARNs +- OIDC provider (required for IRSA; optional when using Pod Identity) + +By default the cluster is deployed into the same VPC and uses the same SSM jump instance as the Connect control-plane stack. Set `vpc_id`, `private_subnet_ids`, and `jump_security_group_id` in `common.local.hcl` to use a different VPC or jump host. + +## Configuration + +Copy `common.local.hcl.example` to `common.local.hcl`. At minimum, set `cluster_admin_role_arns` to include the IAM role ARN you use to access AWS: + +```hcl +locals { + cluster_admin_role_arns = [ + "arn:aws:iam::123456789012:role/aws-reserved/sso.amazonaws.com/eu-west-1/AWSReservedSSO_AdministratorAccess_abc123", + ] +} +``` + +Find your current role ARN with: + +```sh +aws sts get-caller-identity --query Arn --output text +``` + +## Cluster Access + +The cluster is deployed with `cluster_access_mode` controlling how the EKS API server is reached. Set this in `common.local.hcl` before applying. + +### SSM mode (default) + +The API server has no public endpoint. Access from developer machines is via SSM port forwarding through the control-plane jump instance — no public IPs, no open inbound ports, no SSH keys. + +#### Prerequisites + +- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) configured with credentials for the target account +- [Session Manager plugin](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html) for the AWS CLI +- `kubectl` +- Your IAM role ARN must be present in `cluster_admin_role_arns` in the Terragrunt config + +#### One-time kubeconfig setup + +Retrieve the Terraform outputs: + +```sh +CLUSTER_NAME=$(terragrunt output -raw cluster_name) +CLUSTER_HOST=$(terragrunt output -raw cluster_endpoint_hostname) +JUMP_INSTANCE_ID=$(cd ../../../../../../../control-plane/deployment/aws/infra/stack/base/jump && terragrunt output -raw instance_id) +``` + +Generate the kubeconfig and redirect kubectl to the local tunnel port: + +```sh +aws eks update-kubeconfig --name "$CLUSTER_NAME" --region + +kubectl config set-cluster "$(kubectl config current-context)" \ + --server=https://127.0.0.1:6443 \ + --tls-server-name="$CLUSTER_HOST" +``` + +#### Accessing the cluster + +Each session requires an active SSM tunnel. Open a dedicated terminal and run: + +```sh +aws ssm start-session \ + --target "$JUMP_INSTANCE_ID" \ + --document-name AWS-StartPortForwardingSessionToRemoteHost \ + --parameters "{\"host\":[\"$CLUSTER_HOST\"],\"portNumber\":[\"443\"],\"localPortNumber\":[\"6443\"]}" \ + --region +``` + +Leave this running. In any other terminal, kubectl commands work normally: + +```sh +kubectl get nodes +``` + +### Public mode + +Set `cluster_access_mode = "public"` in `common.local.hcl` to expose the API server endpoint publicly. The SSM jump instance is not required in this mode. + +#### One-time kubeconfig setup + +```sh +CLUSTER_NAME=$(terragrunt output -raw cluster_name) + +aws eks update-kubeconfig --name "$CLUSTER_NAME" --region +``` diff --git a/workload/deployment/aws/infra/stack/eks-cluster/cluster/common.local.hcl.example b/workload/deployment/aws/infra/stack/eks-cluster/cluster/common.local.hcl.example new file mode 100644 index 0000000..c03cbd0 --- /dev/null +++ b/workload/deployment/aws/infra/stack/eks-cluster/cluster/common.local.hcl.example @@ -0,0 +1,74 @@ +# Optional overrides for the cluster unit. Copy to common.local.hcl and uncomment +# the values you want to change. All fields are optional — omitting a field uses +# the default defined in terragrunt.hcl. +# +# To deploy into an existing VPC rather than the control-plane networking unit, set +# vpc_id and private_subnet_ids. When these are present the networking unit dependency +# is not used. + +locals { + # --- Cluster access --- + + # Set to "public" to expose the API server endpoint publicly (no SSM tunnel required). + # cluster_access_mode = "public" + + # Restrict the public endpoint to known source CIDRs. Only used when cluster_access_mode + # is "public". Defaults to unrestricted ("0.0.0.0/0"). + # public_access_cidrs = ["203.0.113.0/24"] + + # IAM role ARNs to grant cluster-admin access via EKS access entries. + # Find your role ARN with: + # aws iam get-role --role-name --query Role.Arn --output text + # For IAM Identity Center, the role name is visible in `aws sts get-caller-identity`. + # cluster_admin_role_arns = [ + # "arn:aws:iam::123456789012:role/aws-reserved/sso.amazonaws.com/eu-west-1/AWSReservedSSO_AdministratorAccess_abc123", + # ] + + # --- Existing VPC --- + + # vpc_id = "vpc-0123456789abcdef0" + # private_subnet_ids = ["subnet-0123456789abcdef0", "subnet-0123456789abcdef1", "subnet-0123456789abcdef2"] + + # --- IAM mode --- + + # Set to true to enable IAM Roles for Service Accounts (IRSA). Required when any + # controller unit uses iam_mode = "irsa". The default Pod Identity mode does not + # require this. + # enable_irsa = true + + # --- Node groups --- + + # Defaults to a single Graviton3 (ARM64) node group. All components in this reference + # deployment support ARM64. To use x86_64 instead, change ami_type and instance_types: + # node_groups = { + # workers = { + # name = "cofide-connect-trust-zone-aws-reference-arch-workers" + # ami_type = "AL2023_x86_64_STANDARD" + # instance_types = ["m7i.xlarge"] + # subnet_ids = [] + # min_size = 2 + # max_size = 3 + # } + # } + + # --- Add-on versions --- + + # Add-on versions must be compatible with cluster_version. + # Run: aws eks describe-addon-versions --kubernetes-version + # addon_versions = { + # coredns = "v1.14.2-eksbuild.4" + # kube_proxy = "v1.35.3-eksbuild.8" + # vpc_cni = "v1.21.2-eksbuild.2" + # pod_identity_agent = "v1.3.10-eksbuild.3" + # } + + # --- Logging --- + + # log_retention_days = 30 + + # --- Jump instance --- + + # Optional: provide the jump unit security group ID directly instead of using the + # jump unit dependency. Leave unset to pick it up automatically from the jump unit. + # jump_security_group_id = "sg-0123456789abcdef0" +} diff --git a/workload/deployment/aws/infra/stack/eks-cluster/cluster/terragrunt.hcl b/workload/deployment/aws/infra/stack/eks-cluster/cluster/terragrunt.hcl new file mode 100644 index 0000000..5883c45 --- /dev/null +++ b/workload/deployment/aws/infra/stack/eks-cluster/cluster/terragrunt.hcl @@ -0,0 +1,140 @@ +include "root" { + path = find_in_parent_folders("root.hcl") + expose = true +} + +locals { + # Path to the control-plane Terragrunt stack, used for cross-stack dependencies. + # VPC and jump dependencies are read from there by default; set vpc_id / + # private_subnet_ids / jump_security_group_id in common.local.hcl to deploy + # into a different VPC without this cross-stack reference. + control_plane_stack = "${get_repo_root()}/control-plane/deployment/aws/infra/stack" + + default_config = { + cluster_name = "cofide-connect-trust-zone-aws-reference-arch" + cluster_version = "1.35" + + cluster_role_name = "cofide-connect-trust-zone-aws-reference-arch-cluster" + node_role_name = "cofide-connect-trust-zone-aws-reference-arch-node" + cluster_sg_name = "cofide-connect-trust-zone-aws-reference-arch-cluster" + node_sg_name = "cofide-connect-trust-zone-aws-reference-arch-nodes" + kms_key_alias = "alias/cofide-connect-trust-zone-aws-reference-arch-eks-secrets" + + # Addon versions must be compatible with cluster_version. + # Run `aws eks describe-addon-versions --kubernetes-version ` to list available versions. + # Set pod_identity_agent to a version string to enable EKS Pod Identity; omit (null) to use IRSA instead. + addon_versions = { + coredns = "v1.14.2-eksbuild.4" + kube_proxy = "v1.35.3-eksbuild.8" + vpc_cni = "v1.21.2-eksbuild.2" + pod_identity_agent = "v1.3.10-eksbuild.3" + } + + # Set to "public" to expose the API server endpoint publicly and skip the + # SSM jump instance. Useful for developer environments without VPC access. + cluster_access_mode = "ssm" + + # Restrict to known source CIDRs (e.g. a corporate egress IP) when + # cluster_access_mode is "public". Defaults to unrestricted. + public_access_cidrs = ["0.0.0.0/0"] + + cluster_admin_role_arns = [] + + log_retention_days = 7 + + # Set to true to create an IAM OIDC provider, enabling IRSA for controller pods. + # Leave false when using EKS Pod Identity (the default). + enable_irsa = false + + node_groups = { + workers = { + name = "cofide-connect-trust-zone-aws-reference-arch-workers" + ami_type = "AL2023_ARM_64_STANDARD" + # m7g.xlarge: 4 vCPU / 16 GB RAM. Graviton3 offers better price/performance + # than equivalent x86_64 instances. Requires ARM64 container images — all + # components in this reference architecture support ARM64. + instance_types = ["m7g.xlarge"] + # subnet_ids defaults to all private subnets; set explicitly to pin to specific AZs. + subnet_ids = [] + min_size = 2 + max_size = 3 + } + } + } + + unit_config_path = "${get_terragrunt_dir()}/common.local.hcl" + has_local_config = fileexists(local.unit_config_path) + user_config = local.has_local_config ? read_terragrunt_config(local.unit_config_path).locals : {} + merged_config = merge(local.default_config, local.user_config) + + # Extracted from merged_config if set; null otherwise. The actual selection + # between these and the networking dependency outputs happens in inputs below, + # where dependency references are valid. + user_vpc_id = try(local.merged_config.vpc_id, null) + user_private_subnet_ids = try(local.merged_config.private_subnet_ids, null) + user_jump_sg_id = try(local.merged_config.jump_security_group_id, null) +} + +dependency "vpc" { + config_path = "${local.control_plane_stack}/base/vpc" + + # Allow this dependency to be skipped when vpc_id and private_subnet_ids are + # provided directly in common.local.hcl (e.g. deploying into a different VPC). + mock_outputs_allowed_terraform_commands = ["apply", "destroy", "plan", "validate"] + mock_outputs = { + vpc_id = "vpc-00000000000000000" + private_subnet_ids = { "mock" = "subnet-00000000000000000" } + } +} + +dependency "jump" { + config_path = "${local.control_plane_stack}/base/jump" + + # Allow this dependency to be skipped when jump_security_group_id is provided + # directly in common.local.hcl. + mock_outputs_allowed_terraform_commands = ["apply", "destroy", "plan", "validate"] + mock_outputs = { + security_group_id = "sg-00000000000000000" + } +} + +terraform { + source = "${get_repo_root()}//control-plane/deployment/aws/infra/modules/eks-cluster" +} + +inputs = { + cluster_name = local.merged_config.cluster_name + cluster_version = local.merged_config.cluster_version + + # Use VPC config from common.local.hcl if provided, otherwise read from the + # control-plane networking unit. Set vpc_id and private_subnet_ids in + # common.local.hcl to deploy into a different VPC. + vpc_id = local.user_vpc_id != null ? local.user_vpc_id : dependency.vpc.outputs.vpc_id + private_subnet_ids = local.user_private_subnet_ids != null ? local.user_private_subnet_ids : values(dependency.vpc.outputs.private_subnet_ids) + + cluster_role_name = local.merged_config.cluster_role_name + node_role_name = local.merged_config.node_role_name + cluster_sg_name = local.merged_config.cluster_sg_name + node_sg_name = local.merged_config.node_sg_name + kms_key_alias = local.merged_config.kms_key_alias + + jump_security_group_id = local.user_jump_sg_id != null ? local.user_jump_sg_id : try(dependency.jump.outputs.security_group_id, null) + + addon_versions = local.merged_config.addon_versions + cluster_access_mode = local.merged_config.cluster_access_mode + public_access_cidrs = local.merged_config.public_access_cidrs + cluster_admin_role_arns = local.merged_config.cluster_admin_role_arns + log_retention_days = local.merged_config.log_retention_days + enable_irsa = local.merged_config.enable_irsa + + # subnet_ids is injected into each node group from the resolved private subnets. + # Node groups in common.local.hcl can set subnet_ids explicitly to pin to + # specific AZs; an empty list falls back to all private subnets. + node_groups = { + for k, ng in local.merged_config.node_groups : k => merge(ng, { + subnet_ids = length(ng.subnet_ids) > 0 ? ng.subnet_ids : ( + local.user_private_subnet_ids != null ? local.user_private_subnet_ids : values(dependency.vpc.outputs.private_subnet_ids) + ) + }) + } +} diff --git a/workload/deployment/aws/infra/stack/root.hcl b/workload/deployment/aws/infra/stack/root.hcl new file mode 100644 index 0000000..f712866 --- /dev/null +++ b/workload/deployment/aws/infra/stack/root.hcl @@ -0,0 +1,52 @@ +locals { + # Strictly load the global config from the root directory + root_dir = get_parent_terragrunt_dir() + root_config = read_terragrunt_config("${local.root_dir}/common.local.hcl") + + aws_account_id = local.root_config.locals.aws_account_id + aws_region = local.root_config.locals.aws_region + tf_state_bucket_region = local.root_config.locals.tf_state_bucket_region + tf_state_bucket_name = local.root_config.locals.tf_state_bucket_name + tf_state_key_prefix = local.root_config.locals.tf_state_key_prefix + tf_state_key = "${local.tf_state_key_prefix}/${path_relative_to_include()}/tf.tfstate" + + repo_root = get_repo_root() + stack_in_repo = trimprefix(local.root_dir, "${local.repo_root}/") + unit_repo_path = "${local.stack_in_repo}/${path_relative_to_include()}" +} + +generate "aws_provider" { + path = "aws_provider.tf" + if_exists = "overwrite_terragrunt" + contents = < +``` + +Where: +- `ca-country` — two-letter country code for the upstream CA subject +- `ca-organization` — organisation name for the upstream CA subject +- `ca-common-name` — common name for the upstream CA subject + +All values are derived automatically from the Terragrunt stack outputs and deployed Helm releases. The following must be in place before running this script: + +- The AWS infrastructure units (`eks-cluster/cluster`, `spire-server/iam-role`) must be applied. +- The `connect/trust-zone` unit must be applied — see [`infra/stack/connect/README.md`](../infra/stack/connect/README.md). +- `COFIDE_API_TOKEN` must be set. +- `kubectl` must be configured for the control-plane cluster (to read `connectPSATAudience` from the `connect-api` Helm release). + +Alternatively, copy `values.local.yaml.example` to `values.local.yaml` and fill in the values manually. + +Then install: + +```sh +./install.sh +``` + +The SPIRE server uses Connect as its datastore and AWS KMS for key management. The OIDC discovery provider is deployed as a cluster-internal `ClusterIP` service. If external JWT validation is required, change `spiffe-oidc-discovery-provider.service.type` to `LoadBalancer` in `values.yaml` and add an external-dns hostname annotation in `values.local.yaml`. diff --git a/workload/deployment/aws/k8s/cert-manager/cluster-issuer/apply.sh b/workload/deployment/aws/k8s/cert-manager/cluster-issuer/apply.sh new file mode 100755 index 0000000..02bf999 --- /dev/null +++ b/workload/deployment/aws/k8s/cert-manager/cluster-issuer/apply.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +kubectl apply -k "${SCRIPT_DIR}" diff --git a/workload/deployment/aws/k8s/cert-manager/cluster-issuer/cluster-issuer.yaml b/workload/deployment/aws/k8s/cert-manager/cluster-issuer/cluster-issuer.yaml new file mode 100644 index 0000000..8120e82 --- /dev/null +++ b/workload/deployment/aws/k8s/cert-manager/cluster-issuer/cluster-issuer.yaml @@ -0,0 +1,6 @@ +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: selfsigned +spec: + selfSigned: {} diff --git a/workload/deployment/aws/k8s/cert-manager/cluster-issuer/kustomization.yaml b/workload/deployment/aws/k8s/cert-manager/cluster-issuer/kustomization.yaml new file mode 100644 index 0000000..b88cca6 --- /dev/null +++ b/workload/deployment/aws/k8s/cert-manager/cluster-issuer/kustomization.yaml @@ -0,0 +1,4 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - cluster-issuer.yaml diff --git a/workload/deployment/aws/k8s/cert-manager/install.sh b/workload/deployment/aws/k8s/cert-manager/install.sh new file mode 100755 index 0000000..e8c0eca --- /dev/null +++ b/workload/deployment/aws/k8s/cert-manager/install.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +CHART_VERSION=$(yq '.chartVersion' "${SCRIPT_DIR}/versions.yaml") +if [[ -f "${SCRIPT_DIR}/versions.local.yaml" ]]; then + CHART_VERSION=$(yq '.chartVersion' "${SCRIPT_DIR}/versions.local.yaml") +fi + +helm repo add jetstack https://charts.jetstack.io +helm repo update jetstack + +local_values_args=() +if [[ -f "${SCRIPT_DIR}/values.local.yaml" ]]; then + local_values_args=(-f "${SCRIPT_DIR}/values.local.yaml") +fi + +helm upgrade --install cert-manager jetstack/cert-manager \ + --version "${CHART_VERSION}" \ + --namespace cert-manager \ + --create-namespace \ + -f "${SCRIPT_DIR}/values.yaml" \ + "${local_values_args[@]+"${local_values_args[@]}"}" diff --git a/workload/deployment/aws/k8s/cert-manager/values.yaml b/workload/deployment/aws/k8s/cert-manager/values.yaml new file mode 100644 index 0000000..b02be17 --- /dev/null +++ b/workload/deployment/aws/k8s/cert-manager/values.yaml @@ -0,0 +1,9 @@ +# Helm values for cert-manager. +# https://github.com/cert-manager/cert-manager/tree/master/deploy/charts/cert-manager +# +# After installing cert-manager, apply the ClusterIssuer: +# kubectl apply -k cluster-issuer/ + +# Install cert-manager CRDs as part of the Helm release. +crds: + enabled: true diff --git a/workload/deployment/aws/k8s/cert-manager/versions.yaml b/workload/deployment/aws/k8s/cert-manager/versions.yaml new file mode 100644 index 0000000..f784161 --- /dev/null +++ b/workload/deployment/aws/k8s/cert-manager/versions.yaml @@ -0,0 +1 @@ +chartVersion: v1.20.2 diff --git a/workload/deployment/aws/k8s/spire-server/spire/generate-local-values.sh b/workload/deployment/aws/k8s/spire-server/spire/generate-local-values.sh new file mode 100755 index 0000000..7cd51d6 --- /dev/null +++ b/workload/deployment/aws/k8s/spire-server/spire/generate-local-values.sh @@ -0,0 +1,120 @@ +#!/bin/bash + +# Generates values.local.yaml from the Terragrunt infra stack outputs and +# command-line arguments. +# +# Requires the following units to have been applied: +# infra/stack/eks-cluster/cluster/ +# infra/stack/spire-server/iam-role/ +# infra/stack/connect/trust-zone/ +# control-plane/deployment/aws/infra/stack/base/dns/ +# control-plane/deployment/aws/infra/stack/connect/bundle-distribution/ +# +# COFIDE_API_TOKEN must be set for the connect/ Terragrunt units. +# kubectl must be configured for the control-plane cluster to read the connect Helm release. +# +# Usage: ./generate-local-values.sh +# ca-country: Country code for the upstream CA certificate subject (e.g. GB) +# ca-organization: Organization name for the upstream CA certificate subject +# ca-common-name: Common name for the upstream CA certificate subject +# +# The PSAT audience is read from the connect-api Helm release in the connect namespace. +# kubectl must be configured for the control-plane cluster when this script runs. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +STACK_DIR="${SCRIPT_DIR}/../../../infra/stack" +REPO_ROOT="$(git -C "${SCRIPT_DIR}" rev-parse --show-toplevel)" +CP_STACK_DIR="${REPO_ROOT}/control-plane/deployment/aws/infra/stack" + +if [[ $# -lt 3 ]]; then + echo "Usage: $(basename "$0") " + exit 1 +fi +CA_COUNTRY="$1" +CA_ORGANIZATION="$2" +CA_COMMON_NAME="$3" + +echo "Reading region from eks-cluster/cluster..." +REGION=$(terragrunt --working-dir "${STACK_DIR}/eks-cluster/cluster" output -raw region) +echo " region: ${REGION}" + +echo "Reading IAM role ARN from spire-server/iam-role..." +ROLE_ARN=$(terragrunt --working-dir "${STACK_DIR}/spire-server/iam-role" output -raw role_arn) +echo " role_arn: ${ROLE_ARN}" + +echo "Reading zone name from control-plane base/dns..." +CP_ZONE_NAME=$(terragrunt --working-dir "${CP_STACK_DIR}/base/dns" output -raw zone_name) +CONNECT_URL="${CP_ZONE_NAME}:443" +echo " connect_url: ${CONNECT_URL}" + +echo "Reading bundle distribution domain from control-plane connect/bundle-distribution..." +CP_DIST_DOMAIN=$(terragrunt --working-dir "${CP_STACK_DIR}/connect/bundle-distribution" output -raw distribution_domain_name) +echo " distribution_domain: ${CP_DIST_DOMAIN}" + +echo "Reading Connect trust domain from deployed spire Helm release (control-plane cluster)..." +CONNECT_TRUST_DOMAIN=$(helm get values spire --namespace spire-mgmt -o json | yq '.global.spire.trustDomain') +echo " connect_trust_domain: ${CONNECT_TRUST_DOMAIN}" + +CONNECT_BUNDLE_ENDPOINT_URL="https://${CP_DIST_DOMAIN}/${CONNECT_TRUST_DOMAIN}/bundle" +echo " bundle_endpoint_url: ${CONNECT_BUNDLE_ENDPOINT_URL}" + +echo "Reading PSAT audience from deployed connect-api Helm release (control-plane cluster)..." +PSAT_AUDIENCE=$(helm get values connect --namespace connect -o json | yq '.connect.connectPSATAudience') +echo " psat_audience: ${PSAT_AUDIENCE}" + +echo "Reading trust domain, trust zone ID, cluster ID, and cluster name from connect/trust-zone..." +TRUST_DOMAIN=$(terragrunt --working-dir "${STACK_DIR}/connect/trust-zone" output -raw trust_domain) +echo " trust_domain: ${TRUST_DOMAIN}" +TRUST_ZONE_ID=$(terragrunt --working-dir "${STACK_DIR}/connect/trust-zone" output -raw trust_zone_id) +echo " trust_zone_id: ${TRUST_ZONE_ID}" +CLUSTER_ID=$(terragrunt --working-dir "${STACK_DIR}/connect/trust-zone" output -raw cluster_id) +echo " cluster_id: ${CLUSTER_ID}" +CLUSTER_NAME=$(terragrunt --working-dir "${STACK_DIR}/connect/trust-zone" output -raw cluster_name) +echo " cluster_name: ${CLUSTER_NAME}" + +OUTPUT="${SCRIPT_DIR}/values.local.yaml" +cat > "${OUTPUT}" <: ] — run: terragrunt --working-dir infra/stack/ output -raw +# [cp-tf: : ] — run: terragrunt --working-dir /infra/stack/ output -raw +# [cp-helm: ] — run: helm get values -n spire-mgmt -o json | yq '' +# [you] — value you choose or already know +# [connect] — value from the Connect control plane configuration + +global: + spire: + # [tf: connect/trust-zone: cluster_name] + clusterName: my-cluster + # [tf: connect/trust-zone: trust_domain] + trustDomain: trust-zone.example.cofide.dev + # [you] OIDC discovery provider URL. If the provider is cluster-internal (ClusterIP), + # use its in-cluster service address. If exposed externally, use the public hostname. + jwtIssuer: https://spire-spiffe-oidc-discovery-provider.spire-system.svc.cluster.local + caSubject: + # [you] two-letter country code + country: GB + # [you] organisation name + organization: Example + # [you] common name for the upstream CA certificate + commonName: example.cofide.dev + +spire-server: + # Connect datastore configuration. + dataStore: + connect: + # [cp-tf: base/dns: zone_name] → :443 (gRPC address; connect. subdomain is prepended by Cofide SPIRE) + url: "" + # [cp-helm: spire] .global.spire.trustDomain + trustDomain: "" + # [cp-tf: connect/bundle-distribution: distribution_domain_name] + [cp-helm: spire] .global.spire.trustDomain + # → https:////bundle + bundleEndpointURL: "" + # [tf: connect/trust-zone: cluster_id] + clusterID: "" + # [tf: connect/trust-zone: trust_zone_id] + trustZoneID: "" + auth: + method: psat + psatExpirationSeconds: 900 + # [cp-helm: connect] .connect.connectPSATAudience + psatAudience: "" + keyManager: + awsKMS: + # [tf: eks-cluster/cluster: region] + region: eu-west-2 + upstreamAuthority: + certManager: + ca: + issuerRef: + # Must match the ClusterIssuer name in cert-manager/cluster-issuer/. + name: selfsigned + kind: ClusterIssuer + +# Optional: uncomment to use IRSA instead of EKS Pod Identity. +# spire-server: +# serviceAccount: +# annotations: +# # [tf: spire-server/iam-role: role_arn] +# eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/cofide-connect-trust-zone-aws-reference-arch-spire-server diff --git a/workload/deployment/aws/k8s/spire-server/spire/values.yaml b/workload/deployment/aws/k8s/spire-server/spire/values.yaml new file mode 100644 index 0000000..f515f15 --- /dev/null +++ b/workload/deployment/aws/k8s/spire-server/spire/values.yaml @@ -0,0 +1,63 @@ +# Helm values for the Cofide SPIRE server (cofide/spire chart). +# https://charts.cofide.dev +# +# Cluster-specific values must be supplied in values.local.yaml. +# Copy values.local.yaml.example to values.local.yaml and fill in your values. + +global: + spire: + namespaces: + create: true + recommendations: + enabled: true + strictMode: true + +spiffe-oidc-discovery-provider: + enabled: false + +spire-server: + image: + registry: 010438484483.dkr.ecr.eu-west-1.amazonaws.com + repository: cofide/spire-server + tag: v1.14.2-cofide.4 + kind: deployment + replicaCount: 2 + persistence: + type: emptyDir + dataStore: + plugin: connect + # databaseType must be non-sqlite3 to pass the chart's deployment-mode guard, + # even though the sql plugin is not used here. + sql: + databaseType: postgres + keyManager: + awsKMS: + enabled: true + keyIdentifierFile: + enabled: false + keyIdentifierValue: + enabled: true + identifier: "$POD_NAME" + disk: + enabled: false + extraEnv: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + upstreamAuthority: + certManager: + enabled: true + rbac: + create: true + ca: + create: true + nodeAttestor: + k8sPSAT: + audience: + - spire-server + controllerManager: + enabled: false + telemetry: + prometheus: + enabled: true diff --git a/workload/deployment/aws/k8s/spire-server/spire/versions.yaml b/workload/deployment/aws/k8s/spire-server/spire/versions.yaml new file mode 100644 index 0000000..360f24a --- /dev/null +++ b/workload/deployment/aws/k8s/spire-server/spire/versions.yaml @@ -0,0 +1,2 @@ +# Latest version at time of writing. To find available versions: helm search repo cofide/spire --devel +chartVersion: 0.28.3-cofide.3