@@ -174,6 +174,16 @@ locals {
174174 local. gateway_api_crd_labels
175175 )
176176
177+ # Domains that need bootstrap TLS certificates so the HTTPS listener is valid before
178+ # cert-manager issues. Without a placeholder secret the listener is rejected
179+ # (InvalidCertificateRef) and never accepted, so the gateway-shim never issues the
180+ # real cert — a deadlock. cert-manager overwrites this secret once HTTP-01 succeeds.
181+ bootstrap_tls_domains = var. external_tls_termination ? {} : {
182+ for domain_key , domain in local . domains :
183+ domain_key = > domain
184+ if can (domain. domain ) && lookup (domain, " certificate_reference" , " " ) == " "
185+ }
186+
177187 # Domains that need cert-manager to issue certificates
178188 # Only domains WITHOUT certificate_reference
179189 # Not needed when TLS is terminated externally
@@ -1037,6 +1047,104 @@ locals {
10371047 )
10381048}
10391049
1050+ # Bootstrap TLS for the base domain — placeholder self-signed cert so the HTTPS
1051+ # listener is valid and accepted before cert-manager issues the real cert (breaks
1052+ # the listener-invalid / cert-not-issued deadlock). cert-manager overwrites it.
1053+ resource "tls_private_key" "bootstrap" {
1054+ for_each = local. bootstrap_tls_domains
1055+ algorithm = " RSA"
1056+ rsa_bits = 2048
1057+ }
1058+
1059+ resource "tls_self_signed_cert" "bootstrap" {
1060+ for_each = local. bootstrap_tls_domains
1061+ private_key_pem = tls_private_key. bootstrap [each . key ]. private_key_pem
1062+
1063+ subject {
1064+ common_name = each. value . domain
1065+ }
1066+
1067+ validity_period_hours = 8760 # 1 year
1068+
1069+ dns_names = [
1070+ each . value . domain ,
1071+ " *.${ each . value . domain } "
1072+ ]
1073+
1074+ allowed_uses = [
1075+ " key_encipherment" ,
1076+ " digital_signature" ,
1077+ " server_auth"
1078+ ]
1079+ }
1080+
1081+ resource "kubernetes_secret_v1" "bootstrap_tls" {
1082+ for_each = local. bootstrap_tls_domains
1083+
1084+ metadata {
1085+ name = " ${ local . name } -${ each . key } -tls-cert"
1086+ namespace = var. environment . namespace
1087+ }
1088+
1089+ data = {
1090+ " tls.crt" = tls_self_signed_cert.bootstrap[each.key].cert_pem
1091+ " tls.key" = tls_private_key.bootstrap[each.key].private_key_pem
1092+ }
1093+
1094+ type = " kubernetes.io/tls"
1095+
1096+ lifecycle {
1097+ ignore_changes = [data , metadata [0 ]. annotations , metadata [0 ]. labels ]
1098+ }
1099+ }
1100+
1101+ # Bootstrap TLS for additional hostnames (from domain_prefix in rules)
1102+ # Only for additional hostnames that did NOT inherit a certificate_reference from parent domain
1103+ resource "tls_private_key" "bootstrap_additional" {
1104+ for_each = local. additional_hostname_configs
1105+ algorithm = " RSA"
1106+ rsa_bits = 2048
1107+ }
1108+
1109+ resource "tls_self_signed_cert" "bootstrap_additional" {
1110+ for_each = local. additional_hostname_configs
1111+ private_key_pem = tls_private_key. bootstrap_additional [each . key ]. private_key_pem
1112+
1113+ subject {
1114+ common_name = each. value . hostname
1115+ }
1116+
1117+ validity_period_hours = 8760 # 1 year
1118+
1119+ dns_names = [each . value . hostname ]
1120+
1121+ allowed_uses = [
1122+ " key_encipherment" ,
1123+ " digital_signature" ,
1124+ " server_auth"
1125+ ]
1126+ }
1127+
1128+ resource "kubernetes_secret_v1" "bootstrap_tls_additional" {
1129+ for_each = local. additional_hostname_configs
1130+
1131+ metadata {
1132+ name = each. value . secret_name
1133+ namespace = var. environment . namespace
1134+ }
1135+
1136+ data = {
1137+ " tls.crt" = tls_self_signed_cert.bootstrap_additional[each.key].cert_pem
1138+ " tls.key" = tls_private_key.bootstrap_additional[each.key].private_key_pem
1139+ }
1140+
1141+ type = " kubernetes.io/tls"
1142+
1143+ lifecycle {
1144+ ignore_changes = [data , metadata [0 ]. annotations , metadata [0 ]. labels ]
1145+ }
1146+ }
1147+
10401148# Helm release name - keep under 34 chars so that fullname (release + "-" + chart name = 34+1+20 = 55)
10411149# plus the longest suffix (-certgen, 8 chars) stays within the 63-char k8s label value limit.
10421150#
@@ -1274,6 +1382,11 @@ resource "helm_release" "nginx_gateway_fabric" {
12741382 }),
12751383 yamlencode (local. base_helm_values )
12761384 ]
1385+
1386+ depends_on = [
1387+ kubernetes_secret_v1 . bootstrap_tls ,
1388+ kubernetes_secret_v1 . bootstrap_tls_additional
1389+ ]
12771390}
12781391
12791392# Deploy all Gateway API resources using facets-utility-modules
0 commit comments