diff --git a/contexts/aws-example/blueprint.yaml b/contexts/aws-example/blueprint.yaml index 512d1079..77c6bae7 100644 --- a/contexts/aws-example/blueprint.yaml +++ b/contexts/aws-example/blueprint.yaml @@ -16,7 +16,10 @@ sources: terraform: - path: network/aws-vpc - path: cluster/aws-eks +- path: cluster/aws-eks/additions + destroy: false - path: gitops/flux + destroy: false kustomize: - name: telemetry-base path: telemetry/base @@ -53,7 +56,6 @@ kustomize: source: core dependsOn: - pki-resources - force: true components: - nginx - nginx/flux-webhook @@ -66,7 +68,6 @@ kustomize: source: core dependsOn: - policy-resources - force: true components: - cert-manager - trust-manager @@ -75,18 +76,15 @@ kustomize: source: core dependsOn: - pki-base - force: true components: - private-issuer/ca - public-issuer/selfsigned -- name: gitops - path: gitops/flux +- name: dns + path: dns source: core - dependsOn: - - ingress-base - force: true components: - - webhook + - external-dns + - external-dns/route53 - name: observability path: observability source: core diff --git a/contexts/aws-example/terraform/cluster/aws-eks/additions.tfvars b/contexts/aws-example/terraform/cluster/aws-eks/additions.tfvars new file mode 100644 index 00000000..443c73b4 --- /dev/null +++ b/contexts/aws-example/terraform/cluster/aws-eks/additions.tfvars @@ -0,0 +1,10 @@ +# Managed by Windsor CLI: This file is partially managed by the windsor CLI. Your changes will not be overwritten. + +# ARN of the IAM role for external-dns. If not provided, will be looked up from the cluster. +# external_dns_role_arn = null + +# AWS region where the Route53 hosted zone is located. If not provided, will use the cluster's region. +# route53_region = null + +# Name of the EKS cluster. +# cluster_name = "" diff --git a/kustomize/dns/external-dns/route53/README.md b/kustomize/dns/external-dns/route53/README.md new file mode 100644 index 00000000..533af0bc --- /dev/null +++ b/kustomize/dns/external-dns/route53/README.md @@ -0,0 +1,43 @@ +# External DNS Route53 Configuration + +This component configures external-dns to use AWS Route53 for DNS management in EKS clusters. + +## Dependencies + +This component requires the `aws-eks/additions` Terraform module to be applied first, as it creates a ConfigMap with required configuration values: + +```hcl +resource "kubernetes_config_map" "external_dns" { + metadata { + name = "external-dns" + namespace = "system-dns" + } + + data = { + aws_role_arn = "arn:aws:iam::${account_id}:role/${cluster_name}-external-dns" + aws_region = "us-west-2" # or cluster's region + txt_owner_id = "${cluster_name}-${context_id}" + } +} +``` + +## Configuration Values + +The HelmRelease uses the following values from the ConfigMap: + +| ConfigMap Key | Helm Value Path | Description | +|---------------|----------------|-------------| +| aws_role_arn | aws.role_arn | IAM role ARN for external-dns | +| aws_region | aws.region | AWS region for Route53 operations | +| txt_owner_id | txtOwnerId | Unique identifier for TXT records | + +## Usage + +1. Apply the `aws-eks/additions` Terraform module to create the ConfigMap +2. Apply this kustomization to deploy external-dns with Route53 configuration + +## Notes + +- The ConfigMap must exist in the `system-dns` namespace +- The IAM role referenced by `aws_role_arn` must have appropriate Route53 permissions +- The `txt_owner_id` is constructed as `${cluster_name}-${context_id}` to ensure uniqueness across clusters diff --git a/kustomize/dns/external-dns/route53/kustomization.yaml b/kustomize/dns/external-dns/route53/kustomization.yaml new file mode 100644 index 00000000..47249ed4 --- /dev/null +++ b/kustomize/dns/external-dns/route53/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component +patches: + - target: + group: helm.toolkit.fluxcd.io + version: v2 + kind: HelmRelease + name: external-dns + namespace: system-dns + path: patches/helm-release.yaml diff --git a/kustomize/dns/external-dns/route53/patches/helm-release.yaml b/kustomize/dns/external-dns/route53/patches/helm-release.yaml new file mode 100644 index 00000000..aebe112a --- /dev/null +++ b/kustomize/dns/external-dns/route53/patches/helm-release.yaml @@ -0,0 +1,31 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: external-dns + namespace: system-dns +spec: + valuesFrom: + - kind: ConfigMap + name: external-dns + valuesKey: aws_region + targetPath: aws.region + - kind: ConfigMap + name: external-dns + valuesKey: txt_owner_id + targetPath: txtOwnerId + values: + provider: + aws: + usePodIdentity: true + sources: + - ingress + - service + policy: sync + registry: txt + domainFilters: + - ${DOMAIN} + serviceAccount: + create: true + name: external-dns + annotations: {} diff --git a/terraform/cluster/aws-eks/README.md b/terraform/cluster/aws-eks/README.md index e1f5b784..ce8f28e6 100644 --- a/terraform/cluster/aws-eks/README.md +++ b/terraform/cluster/aws-eks/README.md @@ -67,6 +67,7 @@ No modules. | [cluster\_name](#input\_cluster\_name) | The name of the EKS cluster. | `string` | `""` | no | | [context\_id](#input\_context\_id) | The windsor context id for this deployment | `string` | `""` | no | | [context\_path](#input\_context\_path) | The path to the context folder, where kubeconfig is stored | `string` | `""` | no | +| [endpoint\_private\_access](#input\_endpoint\_private\_access) | Whether to enable private access to the EKS cluster. | `bool` | `false` | no | | [endpoint\_public\_access](#input\_endpoint\_public\_access) | Whether to enable public access to the EKS cluster. | `bool` | `true` | no | | [fargate\_profiles](#input\_fargate\_profiles) | Map of EKS Fargate profile definitions to create. |
map(object({
selectors = list(object({
namespace = string
labels = optional(map(string), {})
}))
tags = optional(map(string), {})
}))
| `{}` | no | | [kubernetes\_version](#input\_kubernetes\_version) | The kubernetes version to deploy. | `string` | `"1.32"` | no | diff --git a/terraform/cluster/aws-eks/_templates/kubeconfig.tpl b/terraform/cluster/aws-eks/_templates/kubeconfig.tpl new file mode 100644 index 00000000..9354987d --- /dev/null +++ b/terraform/cluster/aws-eks/_templates/kubeconfig.tpl @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: Config +clusters: +- name: ${cluster_name} + cluster: + server: ${cluster_endpoint} + certificate-authority-data: ${cluster_ca} +contexts: +- name: ${cluster_name} + context: + cluster: ${cluster_name} + user: ${cluster_name} +current-context: ${cluster_name} +users: +- name: ${cluster_name} + user: + exec: + apiVersion: client.authentication.k8s.io/v1beta1 + command: aws + args: + - eks + - get-token + - --cluster-name + - ${cluster_name} + - --region + - ${region} diff --git a/terraform/cluster/aws-eks/additions/.terraform.lock.hcl b/terraform/cluster/aws-eks/additions/.terraform.lock.hcl new file mode 100644 index 00000000..6456939e --- /dev/null +++ b/terraform/cluster/aws-eks/additions/.terraform.lock.hcl @@ -0,0 +1,45 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.97.0" + constraints = "5.97.0" + hashes = [ + "h1:rUDE0OgA+6IiEA+w0cPp3/QQNH4SpjFjYcQ6p7byKS4=", + "zh:02790ad98b767d8f24d28e8be623f348bcb45590205708334d52de2fb14f5a95", + "zh:088b4398a161e45762dc28784fcc41c4fa95bd6549cb708b82de577f2d39ffc7", + "zh:0c381a457b7af391c43fc0167919443f6105ad2702bde4d02ddea9fd7c9d3539", + "zh:1a4b57a5043dcca64d8b8bae8b30ef4f6b98ed2144f792f39c4e816d3f1e2c56", + "zh:1bf00a67f39e67664337bde065180d41d952242801ebcd1c777061d4ffaa1cc1", + "zh:24c549f53d6bd022af31426d3e78f21264d8a72409821669e7fd41966ae68b2b", + "zh:3abda50bbddb35d86081fe39522e995280aea7f004582c4af22112c03ac8b375", + "zh:7388ed7f21ce2eb46bd9066626ce5f3e2a5705f67f643acce8ae71972f66eaf6", + "zh:96740f2ff94e5df2b2d29a5035a1a1026fe821f61712b2099b224fb2c2277663", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:9f399f8e8683a3a3a6d63a41c7c3a5a5f266eedef40ea69eba75bacf03699879", + "zh:bcf2b288d4706ebd198f75d2159663d657535483331107f2cdef381f10688baf", + "zh:cc76c8a9fc3bad05a8779c1f80fe8c388734f1ec1dd0affa863343490527b466", + "zh:de4359cf1b057bfe7a563be93829ec64bf72e7a2b85a72d075238081ef5eb1db", + "zh:e208fa77051a1f9fa1eff6c5c58aabdcab0de1695b97cdea7b8dd81df3e0ed73", + ] +} + +provider "registry.terraform.io/hashicorp/kubernetes" { + version = "2.27.0" + constraints = "2.27.0" + hashes = [ + "h1:GzU0FzYAT/+IgAhnSBcFH3bT+4I5N6oSga6iZgNJAus=", + "zh:3bdba30ae67c55dc7e9a317ac0da3b208ea7926fe9c2f0ae6587ee88dcc58d1f", + "zh:3f35138a831c00b188d2ffee27111dd0cf59afad2dd5653ed9e67d59646de12c", + "zh:64066d18f6ae9a316c2bc840ef3e641d7ab94e1ea3a41d12523e77345ad442ef", + "zh:653063d44b44881af3a480f7f8eaa94fa300e0229df2072d30f606bddcc9f025", + "zh:87f306e37efb61d13efa6da53a1e45e97e5996ebc0568b1caf8c3c5e54c05809", + "zh:8c428b9708f9634391e52300218771eab3fe942bb1295d8c0ad50ca4b33db3d9", + "zh:a44e87119a0337ded15479851786a13f412b413d9a463ba550d1210249206b0f", + "zh:aa2c4d110b0de6ef997c0d45f3f23f8a98f5530753095d6eff439a6d91a8ea31", + "zh:eb15ed8781ac6a0dec2f7d03cf090e23cfa05e3225806c6231ff2c574662fd63", + "zh:eb81c563f93bd3303f9620d11cd49f21f3f89ac3475c6d3e821b239feb9c217d", + "zh:f1a344a7f16131123577e4ec994d04a34ea458ec16c1ccac53fe7946bd817b18", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} diff --git a/terraform/cluster/aws-eks/additions/README.md b/terraform/cluster/aws-eks/additions/README.md new file mode 100644 index 00000000..d974fa7b --- /dev/null +++ b/terraform/cluster/aws-eks/additions/README.md @@ -0,0 +1,43 @@ + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >=1.8 | +| [aws](#requirement\_aws) | 5.97.0 | +| [kubernetes](#requirement\_kubernetes) | 2.27.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | 5.97.0 | +| [kubernetes](#provider\_kubernetes) | 2.27.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [kubernetes_config_map.external_dns](https://registry.terraform.io/providers/hashicorp/kubernetes/2.27.0/docs/resources/config_map) | resource | +| [kubernetes_namespace.system_dns](https://registry.terraform.io/providers/hashicorp/kubernetes/2.27.0/docs/resources/namespace) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/5.97.0/docs/data-sources/caller_identity) | data source | +| [aws_eks_cluster.current](https://registry.terraform.io/providers/hashicorp/aws/5.97.0/docs/data-sources/eks_cluster) | data source | +| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/5.97.0/docs/data-sources/region) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [cluster\_name](#input\_cluster\_name) | Name of the EKS cluster. | `string` | `""` | no | +| [context\_id](#input\_context\_id) | The windsor context id for this deployment | `string` | `""` | no | +| [external\_dns\_role\_arn](#input\_external\_dns\_role\_arn) | ARN of the IAM role for external-dns. If not provided, will be looked up from the cluster. | `string` | `null` | no | +| [route53\_region](#input\_route53\_region) | AWS region where the Route53 hosted zone is located. If not provided, will use the cluster's region. | `string` | `null` | no | + +## Outputs + +No outputs. + diff --git a/terraform/cluster/aws-eks/additions/main.tf b/terraform/cluster/aws-eks/additions/main.tf new file mode 100644 index 00000000..1c4cf4f9 --- /dev/null +++ b/terraform/cluster/aws-eks/additions/main.tf @@ -0,0 +1,71 @@ +# The AWS EKS Config module manages Kubernetes resources for AWS EKS clusters +# It provides configuration for system components like external-dns +# This module bridges Terraform and Kubernetes resources +# Key features: namespace management, configmap creation, auto-import of existing resources + +terraform { + required_version = ">=1.8" + required_providers { + kubernetes = { + source = "hashicorp/kubernetes" + version = "2.27.0" + } + aws = { + source = "hashicorp/aws" + version = "5.97.0" + } + } +} + +locals { + cluster_name = var.cluster_name != "" ? var.cluster_name : "cluster-${var.context_id}" +} + +data "aws_region" "current" {} + +data "aws_caller_identity" "current" {} + +data "aws_eks_cluster" "current" { + name = local.cluster_name +} + +# ============================================================================= +# Namespace Resources +# ============================================================================= + +# The system-dns namespace hosts DNS-related components +# It provides isolation and security context for DNS services +resource "kubernetes_namespace" "system_dns" { + metadata { + name = "system-dns" + labels = { + "pod-security.kubernetes.io/enforce" = "baseline" + "pod-security.kubernetes.io/audit" = "baseline" + "pod-security.kubernetes.io/warn" = "baseline" + } + } + + lifecycle { + ignore_changes = [ + metadata[0].labels + ] + } +} + +# ============================================================================= +# ConfigMap Resources +# ============================================================================= + +# The external-dns configmap provides configuration for the external-dns service +# It contains AWS-specific settings and credentials +resource "kubernetes_config_map" "external_dns" { + metadata { + name = "external-dns" + namespace = kubernetes_namespace.system_dns.metadata[0].name + } + + data = { + aws_region = var.route53_region != null ? var.route53_region : data.aws_region.current.name + txt_owner_id = local.cluster_name + } +} diff --git a/terraform/cluster/aws-eks/additions/test.tftest.hcl b/terraform/cluster/aws-eks/additions/test.tftest.hcl new file mode 100644 index 00000000..fc1e8bcd --- /dev/null +++ b/terraform/cluster/aws-eks/additions/test.tftest.hcl @@ -0,0 +1,76 @@ +mock_provider "aws" { + mock_data "aws_caller_identity" { + defaults = { + account_id = "123456789012" + } + } + mock_data "aws_region" { + defaults = { + name = "us-west-2" + } + } + mock_data "aws_eks_cluster" { + defaults = { + name = "test-cluster" + arn = "arn:aws:eks:us-west-2:123456789012:cluster/test-cluster" + } + } +} + +mock_provider "kubernetes" {} + +# Verifies that the module creates resources with minimal configuration, +# ensuring that all default values are correctly applied and only required variables are set. +run "minimal_configuration" { + command = plan + + variables { + context_id = "test" + } + + assert { + condition = kubernetes_namespace.system_dns.metadata[0].name == "system-dns" + error_message = "Namespace should be created with default name 'system-dns'" + } + + assert { + condition = kubernetes_config_map.external_dns.metadata[0].name == "external-dns" + error_message = "ConfigMap should be created with name 'external-dns'" + } + + assert { + condition = kubernetes_config_map.external_dns.data.aws_region == "us-west-2" + error_message = "ConfigMap should have correct AWS region" + } + + assert { + condition = kubernetes_config_map.external_dns.data.txt_owner_id == "cluster-test" + error_message = "ConfigMap should have correct txt owner ID" + } +} + +# Verifies that the module handles all optional variables correctly +run "full_configuration" { + command = plan + + variables { + context_id = "test" + cluster_name = "custom-cluster" + route53_region = "us-east-1" + } + + assert { + condition = kubernetes_config_map.external_dns.metadata[0].name == "external-dns" + error_message = "ConfigMap should be created with name 'external-dns'" + } + + assert { + condition = kubernetes_config_map.external_dns.data.aws_region == "us-east-1" + error_message = "ConfigMap should use provided AWS region" + } + + assert { + condition = kubernetes_config_map.external_dns.data.txt_owner_id == "custom-cluster" + error_message = "ConfigMap should have correct txt owner ID" + } +} diff --git a/terraform/cluster/aws-eks/additions/variables.tf b/terraform/cluster/aws-eks/additions/variables.tf new file mode 100644 index 00000000..faa5958a --- /dev/null +++ b/terraform/cluster/aws-eks/additions/variables.tf @@ -0,0 +1,27 @@ +# ============================================================================= +# Input Variables +# ============================================================================= + +variable "external_dns_role_arn" { + description = "ARN of the IAM role for external-dns. If not provided, will be looked up from the cluster." + type = string + default = null +} + +variable "route53_region" { + description = "AWS region where the Route53 hosted zone is located. If not provided, will use the cluster's region." + type = string + default = null +} + +variable "cluster_name" { + description = "Name of the EKS cluster." + type = string + default = "" +} + +variable "context_id" { + description = "The windsor context id for this deployment" + type = string + default = "" +} diff --git a/terraform/cluster/aws-eks/variables.tf b/terraform/cluster/aws-eks/variables.tf index 624da090..9f2b16e3 100644 --- a/terraform/cluster/aws-eks/variables.tf +++ b/terraform/cluster/aws-eks/variables.tf @@ -39,6 +39,12 @@ variable "endpoint_public_access" { default = true } +variable "endpoint_private_access" { + description = "Whether to enable private access to the EKS cluster." + type = bool + default = false +} + variable "cluster_api_access_cidr_block" { description = "The CIDR block for the cluster API access." type = string