Skip to content

Commit dcc95af

Browse files
committed
cert-manager and external-dns
1 parent d9cd310 commit dcc95af

File tree

20 files changed

+481
-109
lines changed

20 files changed

+481
-109
lines changed

modules/common/cert_manager/standard/1.0/facets.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ inputs:
3030
type: '@facets/kubernetes_nodepool'
3131
displayName: Node Pool
3232
optional: false
33+
external_dns_details:
34+
type: '@facets/external_dns'
35+
displayName: External DNS
36+
optional: true
37+
description: External DNS module providing DNS credentials for cert-manager DNS-01 challenges
3338
outputs:
3439
default:
3540
type: '@facets/cert_manager'
@@ -91,4 +96,6 @@ sample:
9196
cert_manager: {}
9297
iac:
9398
validated_files:
99+
- main.tf
94100
- variables.tf
101+
- outputs.tf

modules/common/cert_manager/standard/1.0/locals.tf

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Define your locals here
22
locals {
3-
tenant_provider = lower(try(var.cc_metadata.cc_tenant_provider, "aws"))
3+
tenant_provider = lower(try(var.cc_metadata.cc_tenant_provider, try(var.inputs.external_dns_details.attributes.provider, "aws")))
44
spec = lookup(var.instance, "spec", {})
55
user_supplied_helm_values = try(local.spec.cert_manager.values, try(var.instance.advanced.cert_manager.values, {}))
66
cert_manager = lookup(local.spec, "cert_manager", try(var.instance.advanced.cert_manager, {}))
@@ -9,31 +9,45 @@ locals {
99
cnameStrategy = lookup(local.spec, "cname_strategy", "Follow")
1010
disable_dns_validation = lookup(local.spec, "disable_dns_validation", lookup(local.advanced, "disable_dns_validation", false))
1111
user_defined_tags = try(local.cert_manager.tags, {})
12-
deploy_aws_resources = local.tenant_provider == "aws" ? local.disable_dns_validation ? false : true : false
13-
dns_providers = {
14-
aws = {
12+
13+
# External DNS configuration (if provided)
14+
external_dns = try(var.inputs.external_dns_details.attributes, null)
15+
has_external_dns = local.external_dns != null && !local.disable_dns_validation
16+
17+
# Build DNS provider configuration from external_dns input
18+
dns_providers = local.has_external_dns ? (
19+
local.external_dns.provider == "aws" ? {
1520
route53 = {
16-
region = try(var.cc_metadata.cc_region, null)
21+
region = local.external_dns.region
1722
accessKeyIDSecretRef = {
18-
key = "access-key-id"
19-
name = local.disable_dns_validation ? "na" : kubernetes_secret.cert_manager_r53_secret[0].metadata[0].name
23+
key = local.external_dns.aws_access_key_id_key
24+
name = local.external_dns.secret_name
2025
}
2126
secretAccessKeySecretRef = {
22-
key = "secret-access-key"
23-
name = local.disable_dns_validation ? "na" : kubernetes_secret.cert_manager_r53_secret[0].metadata[0].name
27+
key = local.external_dns.aws_secret_access_key_key
28+
name = local.external_dns.secret_name
2429
}
2530
}
26-
}
27-
google = {
31+
} : local.external_dns.provider == "gcp" ? {
2832
cloudDNS = {
29-
project = lookup(try(data.kubernetes_secret_v1.dns[0].data, {}), "project", "")
33+
project = try(var.inputs.cloud_account.attributes.project, "")
3034
serviceAccountSecretRef = {
31-
key = "credentials.json"
32-
name = local.disable_dns_validation ? "na" : kubernetes_secret.cert_manager_r53_secret[0].metadata[0].name
35+
key = local.external_dns.gcp_credentials_json_key
36+
name = local.external_dns.secret_name
3337
}
3438
}
35-
}
36-
}
39+
} : local.external_dns.provider == "azure" ? {
40+
azureDNS = {
41+
subscriptionID = try(var.inputs.cloud_account.attributes.subscription_id, "")
42+
tenantID = try(var.inputs.cloud_account.attributes.tenant_id, "")
43+
clientID = try(var.inputs.cloud_account.attributes.client_id, "")
44+
clientSecretSecretRef = {
45+
key = local.external_dns.azure_credentials_json_key
46+
name = local.external_dns.secret_name
47+
}
48+
}
49+
} : {}
50+
) : {}
3751
dns01_validations = {
3852
staging = {
3953
name = "letsencrypt-staging"
@@ -113,8 +127,9 @@ locals {
113127
acme_email = lookup(local.spec, "acme_email", "") != "" ? lookup(local.spec, "acme_email", "") : try(var.cluster.createdBy, null)
114128
}
115129

130+
# Fallback: Read existing DNS credentials secret if external_dns not provided (for GCP/Azure)
116131
data "kubernetes_secret_v1" "dns" {
117-
count = local.tenant_provider == "aws" ? 0 : 1
132+
count = local.has_external_dns ? 0 : (local.tenant_provider == "aws" ? 0 : 1)
118133
metadata {
119134
name = "facets-tenant-dns"
120135
namespace = "default"

modules/common/cert_manager/standard/1.0/main.tf

Lines changed: 0 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,10 @@
11
# Define your terraform resources here
2-
module "iam_user_name" {
3-
count = local.disable_dns_validation ? 0 : 1
4-
source = "github.com/Facets-cloud/facets-utility-modules//name"
5-
environment = var.environment
6-
limit = 64
7-
globally_unique = false
8-
resource_name = var.inputs.kubernetes_details.attributes.cluster_name
9-
resource_type = ""
10-
is_k8s = false
11-
}
12-
13-
module "iam_policy_name" {
14-
count = local.disable_dns_validation ? 0 : 1
15-
source = "github.com/Facets-cloud/facets-utility-modules//name"
16-
environment = var.environment
17-
limit = 128
18-
globally_unique = false
19-
resource_name = var.inputs.kubernetes_details.attributes.cluster_name
20-
resource_type = ""
21-
is_k8s = false
22-
}
23-
24-
resource "aws_iam_user" "cert_manager_iam_user" {
25-
count = local.deploy_aws_resources ? 1 : 0
26-
provider = "aws3tooling"
27-
name = lower(module.iam_user_name[0].name)
28-
tags = merge(local.user_defined_tags, var.environment.cloud_tags)
29-
}
30-
31-
resource "aws_iam_user_policy" "cert_manager_r53_policy" {
32-
count = local.deploy_aws_resources ? 1 : 0
33-
provider = "aws3tooling"
34-
name = lower(module.iam_policy_name[0].name)
35-
user = try(aws_iam_user.cert_manager_iam_user[0].name, "na")
36-
policy = <<EOF
37-
{
38-
"Version": "2012-10-17",
39-
"Statement": [
40-
{
41-
"Effect": "Allow",
42-
"Action": "route53:GetChange",
43-
"Resource": "arn:aws:route53:::change/*"
44-
},
45-
{
46-
"Effect": "Allow",
47-
"Action": [
48-
"route53:ChangeResourceRecordSets",
49-
"route53:ListResourceRecordSets"
50-
],
51-
"Resource": "arn:aws:route53:::hostedzone/${try(var.cc_metadata.tenant_base_domain_id, "*")}"
52-
},
53-
{
54-
"Effect": "Allow",
55-
"Action": "route53:ListHostedZonesByName",
56-
"Resource": "*"
57-
}
58-
]
59-
}
60-
EOF
61-
}
62-
63-
resource "aws_iam_access_key" "cert_manager_access_key" {
64-
count = local.deploy_aws_resources ? 1 : 0
65-
provider = "aws3tooling"
66-
user = try(aws_iam_user.cert_manager_iam_user[0].name, "na")
67-
}
68-
692
resource "kubernetes_namespace" "namespace" {
703
metadata {
714
name = local.cert_mgr_namespace
725
}
736
}
747

75-
resource "kubernetes_secret" "cert_manager_r53_secret" {
76-
count = local.disable_dns_validation ? 0 : 1
77-
depends_on = [kubernetes_namespace.namespace]
78-
metadata {
79-
name = "${lower(module.iam_user_name[0].name)}-secret"
80-
namespace = local.cert_mgr_namespace
81-
}
82-
data = jsondecode(local.tenant_provider == "aws" ? jsonencode({
83-
"access-key-id" = aws_iam_access_key.cert_manager_access_key[0].id
84-
"secret-access-key" = aws_iam_access_key.cert_manager_access_key[0].secret
85-
}) : jsonencode({
86-
"credentials.json" = lookup(lookup(try(data.kubernetes_secret_v1.dns[0], {}), "data", {}), "credentials.json", "{}")
87-
}))
88-
}
89-
908
resource "helm_release" "cert_manager" {
919
depends_on = [kubernetes_namespace.namespace]
9210
name = "cert-manager"

modules/common/cert_manager/standard/1.0/variables.tf

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,5 @@
11
variable "instance" {
22
type = any
3-
4-
validation {
5-
condition = contains(["Follow", "None"], var.instance.spec.cname_strategy)
6-
error_message = "cname_strategy must be either 'Follow' or 'None'."
7-
}
8-
9-
validation {
10-
condition = lookup(var.instance.spec, "use_gts", false) ? lookup(var.instance.spec, "gts_private_key", "") != "" : true
11-
error_message = "gts_private_key is required when use_gts is enabled."
12-
}
133
}
144

155
variable "instance_name" {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
intent: external_dns
2+
flavor: aws
3+
version: '1.0'
4+
description: Manages DNS credentials and IAM resources for Route53 DNS operations in Kubernetes clusters
5+
clouds:
6+
- aws
7+
inputs:
8+
kubernetes_details:
9+
optional: false
10+
type: '@facets/kubernetes-details'
11+
displayName: Kubernetes Cluster
12+
default:
13+
resource_type: kubernetes_cluster
14+
resource_name: default
15+
providers:
16+
- kubernetes
17+
cloud_account:
18+
optional: false
19+
type: '@facets/aws_cloud_account'
20+
displayName: AWS Cloud Account
21+
providers:
22+
- aws
23+
spec:
24+
title: External DNS Controller for AWS Route53
25+
description: Manages DNS credentials and IAM resources for Route53 DNS operations
26+
type: object
27+
properties:
28+
hosted_zone_id:
29+
type: string
30+
title: Route53 Hosted Zone ID
31+
description: AWS Route53 hosted zone ID for DNS operations
32+
x-ui-overrides-only: true
33+
required: []
34+
outputs:
35+
default:
36+
type: '@facets/external_dns'
37+
title: External DNS Details
38+
sample:
39+
kind: external_dns
40+
flavor: aws
41+
version: '1.0'
42+
disabled: true
43+
spec: {}
44+
iac:
45+
validated_files:
46+
- main.tf
47+
- variables.tf
48+
- outputs.tf
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
locals {
2+
spec = lookup(var.instance, "spec", {})
3+
hosted_zone_id = lookup(local.spec, "hosted_zone_id", try(var.cc_metadata.tenant_base_domain_id, "*"))
4+
cluster_name = var.inputs.kubernetes_details.attributes.cluster_name
5+
namespace = "external-dns"
6+
secret_name = "${lower(var.instance_name)}-dns-secret"
7+
aws_region = var.inputs.cloud_account.attributes.aws_region
8+
}
9+
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Name generation for IAM resources
2+
module "iam_user_name" {
3+
source = "github.com/Facets-cloud/facets-utility-modules//name"
4+
environment = var.environment
5+
limit = 64
6+
globally_unique = false
7+
resource_name = local.cluster_name
8+
resource_type = "external-dns"
9+
is_k8s = false
10+
}
11+
12+
module "iam_policy_name" {
13+
source = "github.com/Facets-cloud/facets-utility-modules//name"
14+
environment = var.environment
15+
limit = 128
16+
globally_unique = false
17+
resource_name = local.cluster_name
18+
resource_type = "external-dns-policy"
19+
is_k8s = false
20+
}
21+
22+
# IAM User for Route53 access
23+
resource "aws_iam_user" "external_dns_user" {
24+
name = lower(module.iam_user_name.name)
25+
tags = merge(var.environment.cloud_tags, {
26+
Name = "external-dns-${local.cluster_name}"
27+
Purpose = "Route53 DNS management"
28+
ManagedBy = "facets"
29+
})
30+
}
31+
32+
# IAM Policy for Route53 permissions
33+
resource "aws_iam_user_policy" "external_dns_r53_policy" {
34+
name = lower(module.iam_policy_name.name)
35+
user = aws_iam_user.external_dns_user.name
36+
policy = jsonencode({
37+
Version = "2012-10-17"
38+
Statement = [
39+
{
40+
Effect = "Allow"
41+
Action = "route53:GetChange"
42+
Resource = "arn:aws:route53:::change/*"
43+
},
44+
{
45+
Effect = "Allow"
46+
Action = [
47+
"route53:ChangeResourceRecordSets",
48+
"route53:ListResourceRecordSets"
49+
]
50+
Resource = "arn:aws:route53:::hostedzone/${local.hosted_zone_id}"
51+
},
52+
{
53+
Effect = "Allow"
54+
Action = "route53:ListHostedZonesByName"
55+
Resource = "*"
56+
}
57+
]
58+
})
59+
}
60+
61+
# IAM Access Key
62+
resource "aws_iam_access_key" "external_dns_access_key" {
63+
user = aws_iam_user.external_dns_user.name
64+
}
65+
66+
# Kubernetes namespace for external-dns
67+
resource "kubernetes_namespace" "namespace" {
68+
metadata {
69+
name = local.namespace
70+
}
71+
}
72+
73+
# Kubernetes secret with Route53 credentials
74+
resource "kubernetes_secret" "external_dns_r53_secret" {
75+
depends_on = [kubernetes_namespace.namespace]
76+
metadata {
77+
name = local.secret_name
78+
namespace = local.namespace
79+
}
80+
data = {
81+
"access-key-id" = aws_iam_access_key.external_dns_access_key.id
82+
"secret-access-key" = aws_iam_access_key.external_dns_access_key.secret
83+
}
84+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
locals {
2+
output_attributes = {
3+
secret_name = kubernetes_secret.external_dns_r53_secret.metadata[0].name
4+
secret_namespace = local.namespace
5+
aws_access_key_id_key = "access-key-id"
6+
aws_secret_access_key_key = "secret-access-key"
7+
gcp_credentials_json_key = ""
8+
azure_credentials_json_key = ""
9+
hosted_zone_id = local.hosted_zone_id
10+
region = local.aws_region
11+
provider = "aws"
12+
}
13+
output_interfaces = {}
14+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
variable "instance" {
2+
type = object({
3+
spec = object({
4+
hosted_zone_id = optional(string, "")
5+
})
6+
})
7+
}
8+
9+
variable "instance_name" {
10+
type = string
11+
default = "test_instance"
12+
}
13+
14+
variable "environment" {
15+
type = any
16+
default = {
17+
namespace = "default"
18+
}
19+
}
20+
21+
variable "inputs" {
22+
type = object({
23+
kubernetes_details = any
24+
cloud_account = any
25+
})
26+
}
27+
28+
variable "cc_metadata" {
29+
type = any
30+
default = {}
31+
}

0 commit comments

Comments
 (0)