Skip to content

Commit d3ac9b6

Browse files
authored
Merge pull request #521 from Facets-cloud/fabric-fixes
feat: add ACK ACM controller and nginx_gateway_fabric_legacy_aws with DNS-01 support
2 parents fcfdbb5 + 97a1742 commit d3ac9b6

26 files changed

Lines changed: 4040 additions & 2013 deletions

File tree

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# ACK ACM Controller (Legacy)
2+
3+
Deploys the AWS Controllers for Kubernetes (ACK) ACM Controller on EKS clusters, enabling management of AWS Certificate Manager certificates as Kubernetes resources.
4+
5+
## Overview
6+
7+
This module installs the ACK ACM Controller via Helm and configures IAM Role for Service Accounts (IRSA) so the controller can manage ACM certificates. Once deployed, you can create `Certificate` custom resources in Kubernetes that provision real ACM certificates and export them to Kubernetes TLS secrets.
8+
9+
This is primarily used by the `nginx_gateway_fabric_legacy_aws` module to handle domains with ACM ARN `certificate_reference`.
10+
11+
## Architecture
12+
13+
```
14+
┌──────────────────────────────────────────┐
15+
│ ACK ACM Controller │
16+
│ │
17+
│ 1. Watches Certificate CRDs │
18+
│ 2. Calls ACM API via IRSA │
19+
│ 3. Requests/validates certificates │
20+
│ 4. Exports cert to K8s TLS secret │
21+
│ │
22+
│ IAM: acm:RequestCertificate, │
23+
│ acm:DescribeCertificate, │
24+
│ acm:GetCertificate, ... │
25+
└──────────────────────────────────────────┘
26+
│ │
27+
▼ ▼
28+
AWS ACM (Certificate) K8s TLS Secret
29+
(DNS validation) (exported cert)
30+
```
31+
32+
## What It Creates
33+
34+
| Resource | Description |
35+
|----------|-------------|
36+
| `aws_iam_policy` | IAM policy granting ACM permissions |
37+
| IRSA module | IAM role bound to the controller's service account |
38+
| `helm_release` | ACK ACM Controller deployment from `oci://public.ecr.aws/aws-controllers-k8s/acm-chart` |
39+
40+
## Configuration
41+
42+
### Basic Example
43+
44+
```json
45+
{
46+
"kind": "ack_acm_controller",
47+
"flavor": "legacy",
48+
"version": "1.0",
49+
"spec": {
50+
"chart_version": "1.3.4"
51+
}
52+
}
53+
```
54+
55+
### With Custom Namespace
56+
57+
```json
58+
{
59+
"kind": "ack_acm_controller",
60+
"flavor": "legacy",
61+
"version": "1.0",
62+
"spec": {
63+
"chart_version": "1.3.4",
64+
"namespace": "cert-manager"
65+
}
66+
}
67+
```
68+
69+
## Spec Options
70+
71+
| Field | Type | Default | Required | Description |
72+
|-------|------|---------|----------|-------------|
73+
| `chart_version` | string | `1.3.4` | Yes | Helm chart version of the ACK ACM Controller |
74+
| `namespace` | string | environment namespace | No | Kubernetes namespace for the controller |
75+
| `helm_values` | object | - | No | Additional Helm values to override defaults |
76+
77+
## Inputs
78+
79+
| Input | Type | Required | Description |
80+
|-------|------|----------|-------------|
81+
| `kubernetes_details` | `@outputs/kubernetes` | Yes | Kubernetes cluster connection (provides OIDC provider ARN for IRSA) |
82+
83+
## Outputs
84+
85+
| Attribute | Description |
86+
|-----------|-------------|
87+
| `namespace` | Namespace where the controller is deployed |
88+
| `release_name` | Helm release name |
89+
| `chart_version` | Deployed chart version |
90+
| `role_arn` | IAM role ARN used by the controller |
91+
| `helm_release_id` | Helm release ID |
92+
93+
## Usage with nginx_gateway_fabric_legacy_aws
94+
95+
Once deployed, the `nginx_gateway_fabric_legacy_aws` module can use ACM ARNs as `certificate_reference` on domains:
96+
97+
```json
98+
{
99+
"kind": "ingress",
100+
"flavor": "nginx_gateway_fabric_legacy_aws",
101+
"version": "1.0",
102+
"spec": {
103+
"domains": {
104+
"production": {
105+
"domain": "api.example.com",
106+
"alias": "prod",
107+
"certificate_reference": "arn:aws:acm:us-east-1:123456789:certificate/abc-123"
108+
}
109+
}
110+
}
111+
}
112+
```
113+
114+
The ingress module detects the ACM ARN, creates a `Certificate` CRD (kind: `acm.services.k8s.aws/v1alpha1`), and the ACK controller provisions the certificate and exports it to a K8s TLS secret.
115+
116+
## Prerequisites
117+
118+
- EKS cluster with OIDC provider configured
119+
- Route53 hosted zone for DNS validation of ACM certificates
120+
121+
## Troubleshooting
122+
123+
### Check Controller Status
124+
125+
```bash
126+
kubectl get pods -n <namespace> -l app.kubernetes.io/name=acm-chart
127+
kubectl logs -n <namespace> -l app.kubernetes.io/name=acm-chart
128+
```
129+
130+
### Check ACM Certificate CRDs
131+
132+
```bash
133+
kubectl get certificate.acm.services.k8s.aws -n <namespace>
134+
kubectl describe certificate.acm.services.k8s.aws <name> -n <namespace>
135+
```
136+
137+
### Check IRSA
138+
139+
```bash
140+
kubectl get sa ack-acm-controller -n <namespace> -o yaml | grep eks.amazonaws.com/role-arn
141+
```
142+
143+
### Common Issues
144+
145+
- **Certificate stuck in pending**: Check Route53 DNS validation records are created. The ACM controller needs DNS validation to complete.
146+
- **IAM errors**: Verify the IRSA role ARN in the service account annotation matches the IAM role with ACM permissions.
147+
- **Export failure**: The target K8s secret must exist before the controller can export. The ingress module pre-creates empty TLS secrets for this purpose.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
intent: ack_acm_controller
2+
flavor: legacy
3+
version: "1.0"
4+
description: |
5+
Deploys the AWS ACK ACM Controller on EKS clusters via Helm.
6+
This controller manages AWS Certificate Manager (ACM) certificates as Kubernetes resources
7+
and can export issued certificates to Kubernetes TLS secrets for use with Gateway API or Ingress.
8+
Uses IRSA for secure IAM authentication. Supports DNS validation via Route53.
9+
clouds:
10+
- aws
11+
- kubernetes
12+
inputs:
13+
kubernetes_details:
14+
type: "@outputs/kubernetes"
15+
optional: false
16+
default:
17+
resource_type: kubernetes_cluster
18+
resource_name: default
19+
providers:
20+
- kubernetes
21+
- kubernetes-alpha
22+
- helm
23+
outputs:
24+
default:
25+
type: "@outputs/ack_acm_controller"
26+
spec:
27+
title: ACK ACM Controller Configuration
28+
type: object
29+
properties:
30+
chart_version:
31+
type: string
32+
default: "1.3.4"
33+
title: Chart Version
34+
description: Helm chart version of the ACK ACM Controller
35+
namespace:
36+
type: string
37+
title: Namespace
38+
description: Kubernetes namespace for the controller (defaults to environment namespace)
39+
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
40+
maxLength: 63
41+
helm_values:
42+
type: object
43+
title: Helm Values
44+
description: Additional Helm values to override defaults
45+
x-ui-yaml-editor: true
46+
required:
47+
- chart_version
48+
x-ui-order:
49+
- chart_version
50+
- namespace
51+
- helm_values
52+
sample:
53+
kind: ack_acm_controller
54+
flavor: legacy
55+
version: "1.0"
56+
disabled: true
57+
spec:
58+
chart_version: "1.3.4"
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
data "aws_region" "current" {}
2+
3+
module "name" {
4+
source = "github.com/Facets-cloud/facets-utility-modules//name"
5+
environment = var.environment
6+
limit = 48
7+
resource_name = var.instance_name
8+
resource_type = "ack_acm_controller"
9+
globally_unique = true
10+
}
11+
12+
locals {
13+
name = module.name.name
14+
controller_namespace = coalesce(lookup(var.instance.spec, "namespace", null), var.environment.namespace)
15+
controller_service_account = "ack-acm-controller"
16+
eks_oidc_provider_arn = try(var.inputs.kubernetes_details.attributes.legacy_outputs.k8s_details.oidc_provider_arn, var.baseinfra.k8s_details.oidc_provider_arn)
17+
aws_region = data.aws_region.current.name
18+
19+
# Tolerations: merge environment defaults with facets dedicated tolerations
20+
tolerations = concat(
21+
lookup(var.environment, "default_tolerations", []),
22+
try(var.inputs.kubernetes_details.attributes.legacy_outputs.facets_dedicated_tolerations, [])
23+
)
24+
25+
# Node selector from kubernetes_details legacy outputs
26+
node_selector = try(var.inputs.kubernetes_details.attributes.legacy_outputs.facets_dedicated_node_selectors, {})
27+
28+
controller_tolerations = [
29+
for taint in local.tolerations : {
30+
key = taint.key
31+
operator = "Equal"
32+
value = lookup(taint, "value", "")
33+
effect = taint.effect
34+
}
35+
]
36+
}
37+
38+
# IAM Policy for ACK ACM Controller
39+
resource "aws_iam_policy" "ack_acm" {
40+
name = "${local.name}-ack-acm"
41+
description = "IAM policy for the ACK ACM Controller"
42+
43+
policy = jsonencode({
44+
Version = "2012-10-17"
45+
Statement = [
46+
{
47+
Effect = "Allow"
48+
Action = [
49+
"acm:RequestCertificate",
50+
"acm:DescribeCertificate",
51+
"acm:ListCertificates",
52+
"acm:DeleteCertificate",
53+
"acm:ExportCertificate",
54+
"acm:GetCertificate",
55+
"acm:ListTagsForCertificate",
56+
"acm:AddTagsToCertificate",
57+
"acm:RemoveTagsFromCertificate",
58+
"acm:UpdateCertificateOptions",
59+
"acm:ImportCertificate",
60+
"acm:RenewCertificate"
61+
]
62+
Resource = "*"
63+
}
64+
]
65+
})
66+
}
67+
68+
# IRSA - IAM Role for Service Account
69+
module "irsa" {
70+
source = "github.com/Facets-cloud/facets-utility-modules//aws_irsa"
71+
eks_oidc_provider_arn = local.eks_oidc_provider_arn
72+
iam_role_name = local.name
73+
namespace = local.controller_namespace
74+
sa_name = local.controller_service_account
75+
iam_arns = {
76+
(local.controller_service_account) = {
77+
arn = aws_iam_policy.ack_acm.arn
78+
}
79+
}
80+
}
81+
82+
# Deploy ACK ACM Controller via Helm
83+
resource "helm_release" "ack_acm" {
84+
namespace = local.controller_namespace
85+
create_namespace = true
86+
name = "ack-acm-controller"
87+
repository = "oci://public.ecr.aws/aws-controllers-k8s"
88+
chart = "acm-chart"
89+
version = var.instance.spec.chart_version
90+
wait = true
91+
timeout = 600
92+
93+
values = [
94+
yamlencode(merge({
95+
aws = {
96+
region = local.aws_region
97+
}
98+
serviceAccount = {
99+
create = true
100+
name = local.controller_service_account
101+
annotations = {
102+
"eks.amazonaws.com/role-arn" = module.irsa.iam_role_arn
103+
}
104+
}
105+
resources = {
106+
requests = {
107+
cpu = "50m"
108+
memory = "64Mi"
109+
}
110+
limits = {
111+
cpu = "100m"
112+
memory = "128Mi"
113+
}
114+
}
115+
nodeSelector = local.node_selector
116+
tolerations = local.controller_tolerations
117+
installScope = "cluster"
118+
}, lookup(var.instance.spec, "helm_values", {})))
119+
]
120+
121+
depends_on = [
122+
module.irsa
123+
]
124+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
locals {
2+
output_attributes = {
3+
namespace = local.controller_namespace
4+
release_name = helm_release.ack_acm.name
5+
chart_version = var.instance.spec.chart_version
6+
role_arn = module.irsa.iam_role_arn
7+
helm_release_id = helm_release.ack_acm.id
8+
}
9+
10+
output_interfaces = {}
11+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
variable "cluster" {
2+
type = any
3+
default = {}
4+
}
5+
6+
variable "baseinfra" {
7+
type = any
8+
default = {}
9+
}
10+
11+
variable "cc_metadata" {
12+
type = any
13+
default = {}
14+
}
15+
16+
variable "instance" {
17+
type = any
18+
}
19+
20+
variable "instance_name" {
21+
type = string
22+
default = "test_instance"
23+
}
24+
25+
variable "environment" {
26+
type = any
27+
default = {}
28+
}
29+
30+
variable "inputs" {
31+
type = any
32+
default = {}
33+
}

0 commit comments

Comments
 (0)