Skip to content
Closed
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
2 changes: 1 addition & 1 deletion modules/common/cert_manager/standard/1.0/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ This module adapts to different cloud environments:
- **Azure**: Uses existing DNS credentials for certificate validation
- **GCP**: Integrates with Google Cloud DNS for certificate validation and supports GTS certificates

The module automatically detects the cloud provider from `var.cc_metadata.cc_tenant_provider` and configures appropriate DNS solvers and authentication mechanisms.
The module automatically detects the cloud provider from the `external_dns_details` input (if provided) or from `kubernetes_details.attributes.cloud_provider` and configures appropriate DNS solvers and authentication mechanisms.

## Nodepool Integration

Expand Down
12 changes: 10 additions & 2 deletions modules/common/cert_manager/standard/1.0/facets.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
intent: cert_manager
flavor: standard
version: '1.0'
version: '2.0'
description: Deploys Cert Manager for managing ssl certificates
clouds:
- aws
Expand Down Expand Up @@ -30,6 +30,12 @@ inputs:
type: '@facets/kubernetes_nodepool'
displayName: Node Pool
optional: false
external_dns_details:
type: '@facets/external_dns'
displayName: External DNS
optional: true
description: External DNS module providing DNS credentials for cert-manager DNS-01
challenges
outputs:
default:
type: '@facets/cert_manager'
Expand Down Expand Up @@ -85,10 +91,12 @@ sample:
flavor: standard
disabled: true
metadata: {}
version: '1.0'
version: '2.0'
spec:
cname_strategy: Follow
cert_manager: {}
iac:
validated_files:
- main.tf
- variables.tf
- outputs.tf
127 changes: 82 additions & 45 deletions modules/common/cert_manager/standard/1.0/locals.tf
Original file line number Diff line number Diff line change
@@ -1,48 +1,79 @@
# Define your locals here
locals {
tenant_provider = lower(try(var.cc_metadata.cc_tenant_provider, "aws"))
spec = lookup(var.instance, "spec", {})
user_supplied_helm_values = try(local.spec.cert_manager.values, try(var.instance.advanced.cert_manager.values, {}))
cert_manager = lookup(local.spec, "cert_manager", try(var.instance.advanced.cert_manager, {}))
# Determine tenant provider from external_dns module output (primary) or kubernetes_details (cluster module output)
tenant_provider = lower(
try(var.inputs.external_dns_details.attributes.provider,
try(var.inputs.kubernetes_details.attributes.cloud_provider, "aws"))
)

spec = lookup(var.instance, "spec", {})
advanced = lookup(var.instance, "advanced", {})

# Helm values configuration
cert_manager_advanced = lookup(local.advanced, "cert_manager", {})
user_supplied_helm_values = lookup(local.cert_manager_advanced, "values", {})
cert_mgr_namespace = "cert-manager"
advanced = lookup(lookup(var.instance, "advanced", {}), "cert_manager", {})
cnameStrategy = lookup(local.spec, "cname_strategy", "Follow")
disable_dns_validation = lookup(local.spec, "disable_dns_validation", lookup(local.advanced, "disable_dns_validation", false))
user_defined_tags = try(local.cert_manager.tags, {})
deploy_aws_resources = local.tenant_provider == "aws" ? local.disable_dns_validation ? false : true : false
dns_providers = {
aws = {

# DNS validation settings
cnameStrategy = lookup(local.spec, "cname_strategy", "Follow")
disable_dns_validation = lookup(local.spec, "disable_dns_validation", false)

# External DNS configuration (if provided)
external_dns = try(var.inputs.external_dns_details.attributes, null)
has_external_dns = local.external_dns != null && !local.disable_dns_validation

# Build DNS provider configuration from external_dns input
# This creates the provider-specific configuration block for cert-manager
dns_providers = local.has_external_dns ? merge(
local.external_dns.provider == "aws" ? {
route53 = {
region = try(var.cc_metadata.cc_region, null)
region = local.external_dns.region
accessKeyIDSecretRef = {
key = "access-key-id"
name = local.disable_dns_validation ? "na" : kubernetes_secret.cert_manager_r53_secret[0].metadata[0].name
name = local.external_dns.secret_name
key = local.external_dns.aws_access_key_id_key
namespace = local.external_dns.secret_namespace
}
secretAccessKeySecretRef = {
key = "secret-access-key"
name = local.disable_dns_validation ? "na" : kubernetes_secret.cert_manager_r53_secret[0].metadata[0].name
name = local.external_dns.secret_name
key = local.external_dns.aws_secret_access_key_key
namespace = local.external_dns.secret_namespace
}
}
}
google = {
} : {},
local.external_dns.provider == "gcp" ? {
cloudDNS = {
project = lookup(try(data.kubernetes_secret_v1.dns[0].data, {}), "project", "")
project = local.external_dns.project_id
serviceAccountSecretRef = {
key = "credentials.json"
name = local.disable_dns_validation ? "na" : kubernetes_secret.cert_manager_r53_secret[0].metadata[0].name
name = local.external_dns.secret_name
key = local.external_dns.gcp_credentials_json_key
namespace = local.external_dns.secret_namespace
}
}
}
}
} : {},
local.external_dns.provider == "azure" ? {
azureDNS = {
subscriptionID = local.external_dns.subscription_id
tenantID = local.external_dns.tenant_id
clientID = local.external_dns.client_id
resourceGroupName = local.external_dns.resource_group_name
clientSecretSecretRef = {
name = local.external_dns.secret_name
key = local.external_dns.azure_credentials_json_key
namespace = local.external_dns.secret_namespace
}
}
} : {}
) : {}
# Let's Encrypt DNS01 validation cluster issuers
dns01_validations = {
staging = {
name = "letsencrypt-staging"
url = "https://acme-staging-v02.api.letsencrypt.org/directory"
solvers = [
{
dns01 = merge({
cnameStrategy = "Follow"
}, lookup(local.dns_providers, local.tenant_provider, {}))
cnameStrategy = local.cnameStrategy
}, local.dns_providers)
},
]
}
Expand All @@ -52,12 +83,14 @@ locals {
solvers = [
{
dns01 = merge({
cnameStrategy = "Follow"
}, lookup(local.dns_providers, local.tenant_provider, {}))
cnameStrategy = local.cnameStrategy
}, local.dns_providers)
},
]
}
}

# Let's Encrypt HTTP01 validation cluster issuers
http_validations = {
staging-http01 = {
name = "letsencrypt-staging-http01"
Expand All @@ -68,8 +101,8 @@ locals {
ingress = {
podTemplate = {
spec = {
nodeSelector = local.nodepool_labels
tolerations = local.nodepool_tolerations
nodeSelector = local.nodeSelector
tolerations = local.tolerations
}
}
}
Expand All @@ -86,8 +119,8 @@ locals {
ingress = {
podTemplate = {
spec = {
nodeSelector = local.nodepool_labels
tolerations = local.nodepool_tolerations
nodeSelector = local.nodeSelector
tolerations = local.tolerations
}
}
}
Expand All @@ -96,12 +129,25 @@ locals {
]
}
}
environments = merge(local.http_validations, local.disable_dns_validation ? {} : local.dns01_validations)

# Combine HTTP and DNS validations (skip DNS if disabled)
environments = merge(
local.http_validations,
local.disable_dns_validation ? {} : local.dns01_validations
)

# Nodepool configuration from inputs
nodepool_config = lookup(var.inputs, "kubernetes_node_pool_details", null)
nodepool_tolerations = lookup(local.nodepool_config, "taints", [])
nodepool_labels = lookup(local.nodepool_config, "node_selector", {})
nodepool_config = try(var.inputs.kubernetes_node_pool_details.attributes, null)

# Handle taints: convert null/object to empty list, ensure it's always a list
# taints can come as: null, {}, [], or list of objects with {key, value, effect}
# Check if taints exists and is a list, otherwise return empty list
# Use can() to safely check if we can convert to list (works for lists, fails for objects)
nodepool_tolerations = local.nodepool_config != null && local.nodepool_config.taints != null ? (
can(tolist(local.nodepool_config.taints)) ? tolist(local.nodepool_config.taints) : []
) : []

nodepool_labels = local.nodepool_config != null ? try(local.nodepool_config.node_selector, {}) : {}

# Use only nodepool configuration (no fallback to default tolerations)
tolerations = local.nodepool_tolerations
Expand All @@ -110,14 +156,5 @@ locals {
# GTS and ACME configuration
use_gts = lookup(local.spec, "use_gts", false)
gts_private_key = lookup(local.spec, "gts_private_key", "")
acme_email = lookup(local.spec, "acme_email", "") != "" ? lookup(local.spec, "acme_email", "") : try(var.cluster.createdBy, null)
}

data "kubernetes_secret_v1" "dns" {
count = local.tenant_provider == "aws" ? 0 : 1
metadata {
name = "facets-tenant-dns"
namespace = "default"
}
provider = kubernetes.release-pod
acme_email = lookup(local.spec, "acme_email", "") != "" ? lookup(local.spec, "acme_email", "") : null
}
103 changes: 9 additions & 94 deletions modules/common/cert_manager/standard/1.0/main.tf
Original file line number Diff line number Diff line change
@@ -1,111 +1,26 @@
# Define your terraform resources here
module "iam_user_name" {
count = local.disable_dns_validation ? 0 : 1
source = "github.com/Facets-cloud/facets-utility-modules//name"
environment = var.environment
limit = 64
globally_unique = false
resource_name = var.inputs.kubernetes_details.attributes.cluster_name
resource_type = ""
is_k8s = false
}

module "iam_policy_name" {
count = local.disable_dns_validation ? 0 : 1
source = "github.com/Facets-cloud/facets-utility-modules//name"
environment = var.environment
limit = 128
globally_unique = false
resource_name = var.inputs.kubernetes_details.attributes.cluster_name
resource_type = ""
is_k8s = false
}

resource "aws_iam_user" "cert_manager_iam_user" {
count = local.deploy_aws_resources ? 1 : 0
provider = "aws3tooling"
name = lower(module.iam_user_name[0].name)
tags = merge(local.user_defined_tags, var.environment.cloud_tags)
}

resource "aws_iam_user_policy" "cert_manager_r53_policy" {
count = local.deploy_aws_resources ? 1 : 0
provider = "aws3tooling"
name = lower(module.iam_policy_name[0].name)
user = try(aws_iam_user.cert_manager_iam_user[0].name, "na")
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "route53:GetChange",
"Resource": "arn:aws:route53:::change/*"
},
{
"Effect": "Allow",
"Action": [
"route53:ChangeResourceRecordSets",
"route53:ListResourceRecordSets"
],
"Resource": "arn:aws:route53:::hostedzone/${try(var.cc_metadata.tenant_base_domain_id, "*")}"
},
{
"Effect": "Allow",
"Action": "route53:ListHostedZonesByName",
"Resource": "*"
}
]
}
EOF
}

resource "aws_iam_access_key" "cert_manager_access_key" {
count = local.deploy_aws_resources ? 1 : 0
provider = "aws3tooling"
user = try(aws_iam_user.cert_manager_iam_user[0].name, "na")
}

resource "kubernetes_namespace" "namespace" {
metadata {
name = local.cert_mgr_namespace
}
}

resource "kubernetes_secret" "cert_manager_r53_secret" {
count = local.disable_dns_validation ? 0 : 1
depends_on = [kubernetes_namespace.namespace]
metadata {
name = "${lower(module.iam_user_name[0].name)}-secret"
namespace = local.cert_mgr_namespace
}
data = jsondecode(local.tenant_provider == "aws" ? jsonencode({
"access-key-id" = aws_iam_access_key.cert_manager_access_key[0].id
"secret-access-key" = aws_iam_access_key.cert_manager_access_key[0].secret
}) : jsonencode({
"credentials.json" = lookup(lookup(try(data.kubernetes_secret_v1.dns[0], {}), "data", {}), "credentials.json", "{}")
}))
}

resource "helm_release" "cert_manager" {
depends_on = [kubernetes_namespace.namespace]
name = "cert-manager"
# repository = "https://charts.jetstack.io"
chart = "${path.module}/cert-manager-v1.17.1.tgz"
namespace = local.cert_mgr_namespace
create_namespace = false
# version = lookup(local.cert_manager, "version", "1.13.3")
cleanup_on_fail = lookup(local.cert_manager, "cleanup_on_fail", true)
wait = lookup(local.cert_manager, "wait", true)
atomic = lookup(local.cert_manager, "atomic", false)
timeout = lookup(local.cert_manager, "timeout", 600)
recreate_pods = lookup(local.cert_manager, "recreate_pods", false)
# version = lookup(local.cert_manager_advanced, "version", "1.13.3")
cleanup_on_fail = lookup(local.cert_manager_advanced, "cleanup_on_fail", true)
wait = lookup(local.cert_manager_advanced, "wait", true)
atomic = lookup(local.cert_manager_advanced, "atomic", false)
timeout = lookup(local.cert_manager_advanced, "timeout", 600)
recreate_pods = lookup(local.cert_manager_advanced, "recreate_pods", false)

values = [
<<EOF
prometheus_id: ${try(var.inputs.prometheus_details.attributes.helm_release_id, "")}
EOF
, yamlencode({
yamlencode({
installCRDs = true
nodeSelector = local.nodeSelector
tolerations = local.tolerations
Expand Down Expand Up @@ -195,7 +110,7 @@ module "cluster-issuer-gts-prod" {
spec = {
acme = {
email = local.acme_email
server = local.use_gts ? "https://dv.acme-v02.api.pki.goog/directory" : "https://acme-v02.api.letsencrypt.org/directory"
server = "https://dv.acme-v02.api.pki.goog/directory"
disableAccountKeyGeneration = true
privateKeySecretRef = {
name = kubernetes_secret.google-trust-services-prod-account-key[0].metadata[0].name
Expand All @@ -204,7 +119,7 @@ module "cluster-issuer-gts-prod" {
{
dns01 = merge({
cnameStrategy = local.cnameStrategy
}, lookup(local.dns_providers, local.tenant_provider, {}))
}, local.dns_providers)
},
]
}
Expand Down
Loading