@@ -135,6 +135,14 @@ func makeIngressSpec(
135135 featuresConfig := config .FromContextOrDefaults (ctx ).Features
136136 networkConfig := config .FromContextOrDefaults (ctx ).Network
137137
138+ // Build ACME lookup map: host -> []HTTPIngressPath
139+ acmePathsByHost := make (map [string ][]netv1alpha1.HTTPIngressPath )
140+ for _ , challenge := range acmeChallenges {
141+ host := challenge .URL .Host
142+ acmePath := MakeACMEIngressPath (challenge )
143+ acmePathsByHost [host ] = append (acmePathsByHost [host ], acmePath )
144+ }
145+
138146 for _ , name := range names {
139147 visibilities := []netv1alpha1.IngressVisibility {netv1alpha1 .IngressVisibilityClusterLocal }
140148 // If this is a public target (or not being marked as cluster-local), we also make public rule.
@@ -146,49 +154,58 @@ func makeIngressSpec(
146154 if err != nil {
147155 return netv1alpha1.IngressSpec {}, err
148156 }
149- rule := makeIngressRule (domains , r .Namespace ,
157+ domainRules := makeIngressRules (domains , r .Namespace ,
150158 visibility , tc .Targets [name ], ro .RolloutsByTag (name ), networkConfig .SystemInternalTLSEnabled ())
151- if featuresConfig .TagHeaderBasedRouting == apicfg .Enabled {
152- if rule .HTTP .Paths [0 ].AppendHeaders == nil {
153- rule .HTTP .Paths [0 ].AppendHeaders = make (map [string ]string , 1 )
159+
160+ // Apply tag header routing and ACME merging to each rule
161+ for i := range domainRules {
162+ rule := & domainRules [i ]
163+
164+ if featuresConfig .TagHeaderBasedRouting == apicfg .Enabled {
165+ if rule .HTTP .Paths [0 ].AppendHeaders == nil {
166+ rule .HTTP .Paths [0 ].AppendHeaders = make (map [string ]string , 1 )
167+ }
168+
169+ if name == traffic .DefaultTarget {
170+ // To provide information if a request is routed via the "default route" or not,
171+ // the header "Knative-Serving-Default-Route: true" is appended here.
172+ // If the header has "true" and there is a "Knative-Serving-Tag" header,
173+ // then the request is having the undefined tag header,
174+ // which will be observed in queue-proxy.
175+ rule .HTTP .Paths [0 ].AppendHeaders [netheader .DefaultRouteKey ] = "true"
176+
177+ // Add ingress paths for a request with the tag header.
178+ // If a request has one of the `names` (tag name), specified as the
179+ // Knative-Serving-Tag header, except for the DefaultTarget,
180+ // the request will be routed to that target.
181+ // corresponding to the tag name.
182+ // Since names are sorted `DefaultTarget == ""` is the first one,
183+ // so just pass the subslice.
184+ rule .HTTP .Paths = append (
185+ makeTagBasedRoutingIngressPaths (r .Namespace , tc , ro , networkConfig .SystemInternalTLSEnabled (), names [1 :]), rule .HTTP .Paths ... )
186+ } else {
187+ // If a request is routed by a tag-attached hostname instead of the tag header,
188+ // the request may not have the tag header "Knative-Serving-Tag",
189+ // even though the ingress path used in the case is also originated
190+ // from the same Knative route with the ingress path for the tag based routing.
191+ //
192+ // To prevent such inconsistency,
193+ // the tag header is appended with the tag corresponding to the tag-attached hostname
194+ rule .HTTP .Paths [0 ].AppendHeaders [netheader .RouteTagKey ] = name
195+ }
154196 }
155197
156- if name == traffic .DefaultTarget {
157- // To provide information if a request is routed via the "default route" or not,
158- // the header "Knative-Serving-Default-Route: true" is appended here.
159- // If the header has "true" and there is a "Knative-Serving-Tag" header,
160- // then the request is having the undefined tag header,
161- // which will be observed in queue-proxy.
162- rule .HTTP .Paths [0 ].AppendHeaders [netheader .DefaultRouteKey ] = "true"
163-
164- // Add ingress paths for a request with the tag header.
165- // If a request has one of the `names` (tag name), specified as the
166- // Knative-Serving-Tag header, except for the DefaultTarget,
167- // the request will be routed to that target.
168- // corresponding to the tag name.
169- // Since names are sorted `DefaultTarget == ""` is the first one,
170- // so just pass the subslice.
171- rule .HTTP .Paths = append (
172- makeTagBasedRoutingIngressPaths (r .Namespace , tc , ro , networkConfig .SystemInternalTLSEnabled (), names [1 :]), rule .HTTP .Paths ... )
173- } else {
174- // If a request is routed by a tag-attached hostname instead of the tag header,
175- // the request may not have the tag header "Knative-Serving-Tag",
176- // even though the ingress path used in the case is also originated
177- // from the same Knative route with the ingress path for the tag based routing.
178- //
179- // To prevent such inconsistency,
180- // the tag header is appended with the tag corresponding to the tag-attached hostname
181- rule .HTTP .Paths [0 ].AppendHeaders [netheader .RouteTagKey ] = name
198+ // Merge ACME paths for ExternalIP single-host rules
199+ // Note: ACME challenges without matching traffic rules are silently ignored
200+ if visibility == netv1alpha1 .IngressVisibilityExternalIP && len (rule .Hosts ) == 1 {
201+ if acmePaths , exists := acmePathsByHost [rule .Hosts [0 ]]; exists {
202+ rule .HTTP .Paths = append (acmePaths , rule .HTTP .Paths ... )
203+ }
182204 }
183205 }
184- rules = append (rules , rule )
185- }
186- }
187206
188- // Create dedicated rules for ACME challenges
189- if len (acmeChallenges ) > 0 {
190- acmeRules := MakeACMEChallengeRules (acmeChallenges )
191- rules = append (rules , acmeRules ... )
207+ rules = append (rules , domainRules ... )
208+ }
192209 }
193210
194211 httpOption , err := servingnetworking .GetHTTPOption (ctx , config .FromContext (ctx ).Network , r .GetAnnotations ())
@@ -203,46 +220,49 @@ func makeIngressSpec(
203220 }, nil
204221}
205222
206- // MakeACMEChallengeRules creates one dedicated rule per ACME challenge domain.
207- func MakeACMEChallengeRules (acmeChallenges []netv1alpha1.HTTP01Challenge ) []netv1alpha1.IngressRule {
208- rules := make ([]netv1alpha1.IngressRule , 0 , len (acmeChallenges ))
209-
210- for _ , challenge := range acmeChallenges {
211- rules = append (rules , netv1alpha1.IngressRule {
212- Hosts : []string {challenge .URL .Host },
213- Visibility : netv1alpha1 .IngressVisibilityExternalIP ,
214- HTTP : & netv1alpha1.HTTPIngressRuleValue {
215- Paths : []netv1alpha1.HTTPIngressPath {{
216- Splits : []netv1alpha1.IngressBackendSplit {{
217- IngressBackend : netv1alpha1.IngressBackend {
218- ServiceNamespace : challenge .ServiceNamespace ,
219- ServiceName : challenge .ServiceName ,
220- ServicePort : challenge .ServicePort ,
221- },
222- Percent : 100 ,
223- }},
224- Path : challenge .URL .Path ,
225- }},
223+ // MakeACMEIngressPath converts an ACME challenge into an HTTPIngressPath.
224+ func MakeACMEIngressPath (challenge netv1alpha1.HTTP01Challenge ) netv1alpha1.HTTPIngressPath {
225+ return netv1alpha1.HTTPIngressPath {
226+ Path : challenge .URL .Path ,
227+ Splits : []netv1alpha1.IngressBackendSplit {{
228+ IngressBackend : netv1alpha1.IngressBackend {
229+ ServiceNamespace : challenge .ServiceNamespace ,
230+ ServiceName : challenge .ServiceName ,
231+ ServicePort : challenge .ServicePort ,
226232 },
227- })
233+ Percent : 100 ,
234+ }},
228235 }
229-
230- return rules
231236}
232237
233- func makeIngressRule (domains sets.Set [string ], ns string ,
238+ func makeIngressRules (domains sets.Set [string ], ns string ,
234239 visibility netv1alpha1.IngressVisibility ,
235240 targets traffic.RevisionTargets ,
236241 roCfgs []* traffic.ConfigurationRollout ,
237242 encryption bool ,
238- ) netv1alpha1.IngressRule {
243+ ) []netv1alpha1.IngressRule {
244+ basePath := makeBaseIngressPath (ns , targets , roCfgs , encryption )
245+
246+ // ClusterLocal: keep multi-host (no ACME challenges needed)
247+ if visibility == netv1alpha1 .IngressVisibilityClusterLocal {
248+ return []netv1alpha1.IngressRule {makeIngressRuleForHosts (sets .List (domains ), visibility , basePath )}
249+ }
250+
251+ // ExternalIP: create one rule per domain (enables per-host ACME challenge merging)
252+ domainList := sets .List (domains )
253+ rules := make ([]netv1alpha1.IngressRule , 0 , len (domainList ))
254+ for _ , domain := range domainList {
255+ rules = append (rules , makeIngressRuleForHosts ([]string {domain }, visibility , basePath ))
256+ }
257+ return rules
258+ }
259+
260+ func makeIngressRuleForHosts (hosts []string , visibility netv1alpha1.IngressVisibility , basePath * netv1alpha1.HTTPIngressPath ) netv1alpha1.IngressRule {
239261 return netv1alpha1.IngressRule {
240- Hosts : sets . List ( domains ) ,
262+ Hosts : hosts ,
241263 Visibility : visibility ,
242264 HTTP : & netv1alpha1.HTTPIngressRuleValue {
243- Paths : []netv1alpha1.HTTPIngressPath {
244- * makeBaseIngressPath (ns , targets , roCfgs , encryption ),
245- },
265+ Paths : []netv1alpha1.HTTPIngressPath {* basePath },
246266 },
247267 }
248268}
0 commit comments