Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions modules/ack_acm_controller/legacy/1.0/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# ACK ACM Controller (Legacy)

Deploys the AWS Controllers for Kubernetes (ACK) ACM Controller on EKS clusters, enabling management of AWS Certificate Manager certificates as Kubernetes resources.

## Overview

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.

This is primarily used by the `nginx_gateway_fabric_legacy_aws` module to handle domains with ACM ARN `certificate_reference`.

## Architecture

```
┌──────────────────────────────────────────┐
│ ACK ACM Controller │
│ │
│ 1. Watches Certificate CRDs │
│ 2. Calls ACM API via IRSA │
│ 3. Requests/validates certificates │
│ 4. Exports cert to K8s TLS secret │
│ │
│ IAM: acm:RequestCertificate, │
│ acm:DescribeCertificate, │
│ acm:GetCertificate, ... │
└──────────────────────────────────────────┘
│ │
▼ ▼
AWS ACM (Certificate) K8s TLS Secret
(DNS validation) (exported cert)
```

## What It Creates

| Resource | Description |
|----------|-------------|
| `aws_iam_policy` | IAM policy granting ACM permissions |
| IRSA module | IAM role bound to the controller's service account |
| `helm_release` | ACK ACM Controller deployment from `oci://public.ecr.aws/aws-controllers-k8s/acm-chart` |

## Configuration

### Basic Example

```json
{
"kind": "ack_acm_controller",
"flavor": "legacy",
"version": "1.0",
"spec": {
"chart_version": "1.3.4"
}
}
```

### With Custom Namespace

```json
{
"kind": "ack_acm_controller",
"flavor": "legacy",
"version": "1.0",
"spec": {
"chart_version": "1.3.4",
"namespace": "cert-manager"
}
}
```

## Spec Options

| Field | Type | Default | Required | Description |
|-------|------|---------|----------|-------------|
| `chart_version` | string | `1.3.4` | Yes | Helm chart version of the ACK ACM Controller |
| `namespace` | string | environment namespace | No | Kubernetes namespace for the controller |
| `helm_values` | object | - | No | Additional Helm values to override defaults |

## Inputs

| Input | Type | Required | Description |
|-------|------|----------|-------------|
| `kubernetes_details` | `@outputs/kubernetes` | Yes | Kubernetes cluster connection (provides OIDC provider ARN for IRSA) |

## Outputs

| Attribute | Description |
|-----------|-------------|
| `namespace` | Namespace where the controller is deployed |
| `release_name` | Helm release name |
| `chart_version` | Deployed chart version |
| `role_arn` | IAM role ARN used by the controller |
| `helm_release_id` | Helm release ID |

## Usage with nginx_gateway_fabric_legacy_aws

Once deployed, the `nginx_gateway_fabric_legacy_aws` module can use ACM ARNs as `certificate_reference` on domains:

```json
{
"kind": "ingress",
"flavor": "nginx_gateway_fabric_legacy_aws",
"version": "1.0",
"spec": {
"domains": {
"production": {
"domain": "api.example.com",
"alias": "prod",
"certificate_reference": "arn:aws:acm:us-east-1:123456789:certificate/abc-123"
}
}
}
}
```

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.

## Prerequisites

- EKS cluster with OIDC provider configured
- Route53 hosted zone for DNS validation of ACM certificates

## Troubleshooting

### Check Controller Status

```bash
kubectl get pods -n <namespace> -l app.kubernetes.io/name=acm-chart
kubectl logs -n <namespace> -l app.kubernetes.io/name=acm-chart
```

### Check ACM Certificate CRDs

```bash
kubectl get certificate.acm.services.k8s.aws -n <namespace>
kubectl describe certificate.acm.services.k8s.aws <name> -n <namespace>
```

### Check IRSA

```bash
kubectl get sa ack-acm-controller -n <namespace> -o yaml | grep eks.amazonaws.com/role-arn
```

### Common Issues

- **Certificate stuck in pending**: Check Route53 DNS validation records are created. The ACM controller needs DNS validation to complete.
- **IAM errors**: Verify the IRSA role ARN in the service account annotation matches the IAM role with ACM permissions.
- **Export failure**: The target K8s secret must exist before the controller can export. The ingress module pre-creates empty TLS secrets for this purpose.
58 changes: 58 additions & 0 deletions modules/ack_acm_controller/legacy/1.0/facets.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
intent: ack_acm_controller
flavor: legacy
version: "1.0"
description: |
Deploys the AWS ACK ACM Controller on EKS clusters via Helm.
This controller manages AWS Certificate Manager (ACM) certificates as Kubernetes resources
and can export issued certificates to Kubernetes TLS secrets for use with Gateway API or Ingress.
Uses IRSA for secure IAM authentication. Supports DNS validation via Route53.
clouds:
- aws
- kubernetes
inputs:
kubernetes_details:
type: "@outputs/kubernetes"
optional: false
default:
resource_type: kubernetes_cluster
resource_name: default
providers:
- kubernetes
- kubernetes-alpha
- helm
outputs:
default:
type: "@outputs/ack_acm_controller"
spec:
title: ACK ACM Controller Configuration
type: object
properties:
chart_version:
type: string
default: "1.3.4"
title: Chart Version
description: Helm chart version of the ACK ACM Controller
namespace:
type: string
title: Namespace
description: Kubernetes namespace for the controller (defaults to environment namespace)
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
maxLength: 63
helm_values:
type: object
title: Helm Values
description: Additional Helm values to override defaults
x-ui-yaml-editor: true
required:
- chart_version
x-ui-order:
- chart_version
- namespace
- helm_values
sample:
kind: ack_acm_controller
flavor: legacy
version: "1.0"
disabled: true
spec:
chart_version: "1.3.4"
118 changes: 118 additions & 0 deletions modules/ack_acm_controller/legacy/1.0/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
data "aws_region" "current" {}

module "name" {
source = "github.com/Facets-cloud/facets-utility-modules//name"
environment = var.environment
limit = 48
resource_name = var.instance_name
resource_type = "ack_acm_controller"
globally_unique = true
}

locals {
name = module.name.name
controller_namespace = coalesce(lookup(var.instance.spec, "namespace", null), var.environment.namespace)
controller_service_account = "ack-acm-controller"
eks_oidc_provider_arn = try(var.inputs.kubernetes_details.attributes.legacy_outputs.k8s_details.oidc_provider_arn, var.baseinfra.k8s_details.oidc_provider_arn)
aws_region = data.aws_region.current.name

# Tolerations: merge environment defaults with facets dedicated tolerations
tolerations = concat(
lookup(var.environment, "default_tolerations", []),
try(var.inputs.kubernetes_details.attributes.legacy_outputs.facets_dedicated_tolerations, [])
)

# Node selector from kubernetes_details legacy outputs
node_selector = try(var.inputs.kubernetes_details.attributes.legacy_outputs.facets_dedicated_node_selectors, {})

controller_tolerations = [
for taint in local.tolerations : {
key = taint.key
operator = "Equal"
value = lookup(taint, "value", "")
effect = taint.effect
}
]
}

# IAM Policy for ACK ACM Controller
resource "aws_iam_policy" "ack_acm" {
name = "${local.name}-ack-acm"
description = "IAM policy for the ACK ACM Controller"

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"acm:DescribeCertificate",
"acm:GetCertificate",
"acm:ListCertificates",
"acm:ListTagsForCertificate",
"acm:AddTagsToCertificate",
"acm:RemoveTagsFromCertificate"
]
Resource = "*"
}
]
})
}

# IRSA - IAM Role for Service Account
module "irsa" {
source = "github.com/Facets-cloud/facets-utility-modules//aws_irsa"
eks_oidc_provider_arn = local.eks_oidc_provider_arn
iam_role_name = local.name
namespace = local.controller_namespace
sa_name = local.controller_service_account
iam_arns = {
(local.controller_service_account) = {
arn = aws_iam_policy.ack_acm.arn
}
}
}

# Deploy ACK ACM Controller via Helm
resource "helm_release" "ack_acm" {
namespace = local.controller_namespace
create_namespace = true
name = "ack-acm-controller"
repository = "oci://public.ecr.aws/aws-controllers-k8s"
chart = "acm-chart"
version = var.instance.spec.chart_version
wait = true
timeout = 600

values = [
yamlencode(merge({
aws = {
region = local.aws_region
}
serviceAccount = {
create = true
name = local.controller_service_account
annotations = {
"eks.amazonaws.com/role-arn" = module.irsa.iam_role_arn
}
}
resources = {
requests = {
cpu = "50m"
memory = "64Mi"
}
limits = {
cpu = "100m"
memory = "128Mi"
}
}
nodeSelector = local.node_selector
tolerations = local.controller_tolerations
installScope = "cluster"
}, lookup(var.instance.spec, "helm_values", {})))
]

depends_on = [
module.irsa
]
}
11 changes: 11 additions & 0 deletions modules/ack_acm_controller/legacy/1.0/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
locals {
output_attributes = {
namespace = local.controller_namespace
release_name = helm_release.ack_acm.name
chart_version = var.instance.spec.chart_version
role_arn = module.irsa.iam_role_arn
helm_release_id = helm_release.ack_acm.id
}

output_interfaces = {}
}
33 changes: 33 additions & 0 deletions modules/ack_acm_controller/legacy/1.0/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
variable "cluster" {
type = any
default = {}
}

variable "baseinfra" {
type = any
default = {}
}

variable "cc_metadata" {
type = any
default = {}
}

variable "instance" {
type = any
}

variable "instance_name" {
type = string
default = "test_instance"
}

variable "environment" {
type = any
default = {}
}

variable "inputs" {
type = any
default = {}
}
Loading
Loading