1use std::collections::{BTreeMap, BTreeSet};
8
9use k8s_openapi::api::core::v1::{ConfigMap, Namespace, Pod, Secret, Service};
10use k8s_openapi::api::discovery::v1::EndpointSlice;
11use k8s_openapi::apimachinery::pkg::apis::meta::v1::{Condition, LabelSelector, Time};
12use kube::ResourceExt;
13
14use super::resources::{BackendTlsPolicy, Gateway, GatewayClass, GrpcRoute, HttpRoute, ListenerSet, ReferenceGrant, TcpRoute, TlsRoute, UdpRoute};
15use super::routes::CompiledHttpRoute;
16use super::services::ServiceSettings;
17use super::types::*;
18
19#[derive(Clone, Debug)]
21pub struct Settings {
22 pub controller_name: String,
23 pub services: ServiceSettings,
24 pub provision: Option<super::provision::ProvisionSettings>,
26 pub mesh: bool,
28 pub experimental: bool,
32}
33
34impl Default for Settings {
35 fn default() -> Self {
36 Settings { controller_name: super::CONTROLLER_NAME.to_string(), services: ServiceSettings::default(), provision: None, mesh: false, experimental: false }
37 }
38}
39
40#[derive(Clone, Default)]
42pub struct Snapshot {
43 pub classes: Vec<GatewayClass>,
44 pub gateways: Vec<Gateway>,
45 pub http_routes: Vec<HttpRoute>,
46 pub grpc_routes: Vec<GrpcRoute>,
47 pub tls_routes: Vec<TlsRoute>,
48 pub tcp_routes: Vec<TcpRoute>,
49 pub udp_routes: Vec<UdpRoute>,
50 pub listener_sets: Vec<ListenerSet>,
51 pub backend_tls_policies: Vec<BackendTlsPolicy>,
52 pub reference_grants: Vec<ReferenceGrant>,
53 pub services: Vec<Service>,
54 pub namespaces: Vec<Namespace>,
55 pub secrets: Vec<Secret>,
56 pub config_maps: Vec<ConfigMap>,
58 pub endpoint_slices: Vec<EndpointSlice>,
59 pub pods: Vec<Pod>,
61 pub deployments: Vec<k8s_openapi::api::apps::v1::Deployment>,
63 pub cluster_trust_bundles_served: bool,
65 pub cluster_trust_bundles: Vec<(String, String)>,
67}
68
69pub const GATEWAY_NAME_LABEL: &str = "gateway.networking.k8s.io/gateway-name";
72pub const MANAGED_BY: &str = "rws-gateway";
74
75pub fn gateway_addresses(snapshot: &Snapshot, namespace: &str, name: &str) -> Vec<GatewayStatusAddress> {
77 let service = snapshot.services.iter().find(|s| {
78 s.namespace().as_deref() == Some(namespace)
79 && s.labels().get(GATEWAY_NAME_LABEL).map(String::as_str) == Some(name)
80 && s.labels().get("app.kubernetes.io/managed-by").map(String::as_str) == Some(MANAGED_BY)
81 });
82 let ingress = service.and_then(|s| s.status.as_ref()).and_then(|s| s.load_balancer.as_ref()).and_then(|lb| lb.ingress.clone()).unwrap_or_default();
83 ingress
84 .into_iter()
85 .filter_map(|i| match (i.ip, i.hostname) {
86 (Some(ip), _) => Some(GatewayStatusAddress { address_type: Some("IPAddress".to_string()), value: ip, routability: None }),
87 (None, Some(hostname)) => Some(GatewayStatusAddress { address_type: Some("Hostname".to_string()), value: hostname, routability: None }),
88 _ => None,
89 })
90 .collect()
91}
92
93#[derive(Clone, Debug, Default, PartialEq)]
95pub struct Desired {
96 pub classes: BTreeMap<String, GatewayClassStatus>,
98 pub gateways: BTreeMap<(String, String), GatewayStatus>,
100 pub http_routes: BTreeMap<(String, String), Vec<RouteParentStatus>>,
103 pub grpc_routes: BTreeMap<(String, String), Vec<RouteParentStatus>>,
105 pub tls_routes: BTreeMap<(String, String), Vec<RouteParentStatus>>,
106 pub tcp_routes: BTreeMap<(String, String), Vec<RouteParentStatus>>,
107 pub udp_routes: BTreeMap<(String, String), Vec<RouteParentStatus>>,
108 pub listener_sets: BTreeMap<(String, String), ListenerSetStatus>,
110 pub backend_tls_policies: BTreeMap<(String, String), Vec<PolicyAncestorStatus>>,
112 pub services: BTreeMap<(String, String), Service>,
114 pub endpoint_slices: BTreeMap<(String, String), EndpointSlice>,
116 pub deployments: BTreeMap<(String, String), k8s_openapi::api::apps::v1::Deployment>,
118}
119
120#[derive(Clone, Debug, Default, PartialEq)]
122pub struct GatewayConfig {
123 pub listeners: Vec<ListenerConfig>,
124}
125
126#[derive(Clone, Debug, PartialEq)]
130pub struct ListenerConfig {
131 pub gateway: (String, String),
132 pub name: String,
133 pub port: i32,
134 pub protocol: String,
135 pub hostname: Option<String>,
136 pub container_port: u16,
137 pub tls: Option<(Vec<u8>, Vec<u8>)>,
139 pub client_ca: Option<ClientCa>,
141 pub backend_client_cert: Option<(Vec<u8>, Vec<u8>)>,
144 pub request_filters: Vec<super::ext_auth::CompiledAuth>,
146 pub http_routes: Vec<CompiledHttpRoute>,
147}
148
149#[derive(Clone, Debug, PartialEq)]
151pub struct ClientCa {
152 pub pem: Vec<u8>,
154 pub insecure_fallback: bool,
156}
157
158pub const EXPERIMENTAL_FEATURES: &[&str] = &["ClusterTrustBundle", "GatewayAddressRoutability", "GatewayAddressRoutabilityCluster", "GatewayListenerFilters", "HTTPRouteDestinationPortMatching"];
165
166pub const SUPPORTED_FEATURES: &[&str] = &[
167 "BackendTLSPolicy",
168 "BackendTLSPolicySANValidation",
169 "GRPCRoute",
170 "GRPCRouteNamedRouteRule",
171 "GRPCRouteRequestHeaderModifier",
172 "Gateway",
173 "GatewayAddressEmpty",
174 "GatewayBackendClientCertificate",
175 "GatewayFrontendClientCertificateValidation",
176 "GatewayFrontendClientCertificateValidationInsecureFallback",
177 "GatewayHTTPListenerIsolation",
178 "GatewayHTTPSListenerDetectMisdirectedRequests",
179 "GatewayInfrastructure",
180 "GatewayPort8080",
181 "GatewayRouteHostnameIntersectionPrecedence",
182 "GatewayStaticAddresses",
183 "HTTPRoute",
184 "HTTPRoute303RedirectStatusCode",
185 "HTTPRoute307RedirectStatusCode",
186 "HTTPRoute308RedirectStatusCode",
187 "HTTPRouteBackendProtocolH2C",
188 "HTTPRouteBackendProtocolWebSocket",
189 "HTTPRouteBackendRequestHeaderModification",
190 "HTTPRouteBackendTimeout",
191 "HTTPRouteBackendURLRewrite",
192 "HTTPRouteCORS",
193 "HTTPRouteHostRewrite",
194 "HTTPRouteMethodMatching",
195 "HTTPRouteNamedRouteRule",
196 "HTTPRouteParentRefPort",
197 "HTTPRoutePathRedirect",
198 "HTTPRoutePathRewrite",
199 "HTTPRoutePortRedirect",
200 "HTTPRouteQueryParamMatching",
201 "HTTPRouteRequestMirror",
202 "HTTPRouteRequestMultipleMirrors",
203 "HTTPRouteRequestPercentageMirror",
204 "HTTPRouteRequestTimeout",
205 "HTTPRouteResponseHeaderModification",
206 "HTTPRouteRetryCodes",
207 "HTTPRouteRetryConnectionError",
208 "HTTPRouteSchemeRedirect",
209 "ListenerSet",
210 "ReferenceGrant",
211 "TCPRoute",
212 "TLSRoute",
213 "TLSRouteModeMixed",
214 "TLSRouteModeTerminate",
215 "UDPRoute",
216];
217
218pub fn translate(snapshot: &Snapshot, settings: &Settings, now: &Time) -> (GatewayConfig, Desired) {
219 let controller_name = settings.controller_name.as_str();
220 let mut desired = Desired::default();
221 let mut config = GatewayConfig::default();
222
223 let mut our_classes = BTreeSet::new();
225 for class in &snapshot.classes {
226 if class.spec.controller_name != controller_name {
227 continue;
228 }
229 let name = class.name_any();
230 our_classes.insert(name.clone());
231 let previous = class.status.as_ref().map(|s| s.conditions.as_slice()).unwrap_or_default();
232 let generation = class.metadata.generation;
233 let conditions = vec![condition(previous, "Accepted", true, "Accepted", "Handled by rws", generation, now)];
234 let mut names: Vec<&str> = SUPPORTED_FEATURES.to_vec();
235 if settings.experimental {
236 names.extend(EXPERIMENTAL_FEATURES.iter().filter(|f| **f != "ClusterTrustBundle" || snapshot.cluster_trust_bundles_served));
238 names.sort();
239 }
240 let supported_features = names.into_iter().map(|f| SupportedFeature { name: f.to_string() }).collect();
241 desired.classes.insert(name, GatewayClassStatus { conditions, supported_features });
242 }
243
244 let mut listeners: Vec<ListenerView> = Vec::new();
247 let mut listener_sets: Vec<(&ListenerSet, (String, String), bool)> = Vec::new();
248 let mut merged_gateways: Vec<Gateway> = Vec::new();
249 for gateway in &snapshot.gateways {
250 if !our_classes.contains(&gateway.spec.gateway_class_name) {
251 continue;
252 }
253 let key = (gateway.namespace().unwrap_or_default(), gateway.name_any());
254 let mut views = listener_views(gateway, &gateway.spec.listeners, &key.0, None, snapshot);
255 let mut merged = gateway.clone();
256 for (set, allowed) in attached_listener_sets(gateway, snapshot) {
257 let set_key = (set.namespace().unwrap_or_default(), set.name_any());
258 if allowed {
259 views.extend(listener_views(gateway, &set.spec.listeners, &set_key.0, Some(set_key.clone()), snapshot));
260 merged.spec.listeners.extend(set.spec.listeners.iter().cloned());
261 }
262 listener_sets.push((set, key.clone(), allowed));
263 }
264 mark_conflicts(&mut views);
265 listeners.extend(views);
266 merged_gateways.push(merged);
267 }
268
269 let our_gateways: Vec<&Gateway> = merged_gateways.iter().collect();
272 desired.services = super::services::desired(snapshot, &our_gateways, &settings.services);
273 desired.endpoint_slices = super::services::endpoint_slices(snapshot, &desired.services, &settings.services);
274 if let Some(provision) = &settings.provision {
275 let gateways: Vec<&Gateway> = snapshot.gateways.iter().filter(|g| our_classes.contains(&g.spec.gateway_class_name)).collect();
276 desired.deployments = super::provision::desired(&gateways, &settings.services.pod_namespace, controller_name, settings.services.port_range, provision);
277 }
278
279 let routes = snapshot
281 .http_routes
282 .iter()
283 .map(|r| RouteIn::new("HTTPRoute", r, &r.spec.parent_refs, &r.spec.hostnames, http_references(&r.spec.rules), super::routes::compile(r, snapshot)))
284 .chain(snapshot.grpc_routes.iter().map(|r| RouteIn::new("GRPCRoute", r, &r.spec.parent_refs, &r.spec.hostnames, grpc_references(&r.spec.rules), super::routes::compile_grpc(r, snapshot))))
285 .chain([("TLSRoute", &snapshot.tls_routes), ("TCPRoute", &snapshot.tcp_routes), ("UDPRoute", &snapshot.udp_routes)].into_iter().flat_map(|(kind, routes)| {
286 routes.iter().map(move |r| RouteIn::new(kind, r, &r.spec.parent_refs, &r.spec.hostnames, l4_references(&r.spec.rules), super::routes::compile_l4(r, kind, snapshot)))
287 }));
288 let mut attached: BTreeMap<ListenerKey, i32> = BTreeMap::new();
289 let mut service_gateways: BTreeMap<(String, String), BTreeSet<(String, String)>> = BTreeMap::new();
291 for route in routes {
292 let namespace = route.namespace.clone();
293 let resolved = resolve_references(route.kind, &namespace, &route.references, snapshot);
294 let mut parents = Vec::new();
295 for parent_ref in route.parent_refs {
296 if settings.mesh && matches!(route.kind, "HTTPRoute" | "GRPCRoute") && super::mesh::is_service_parent(parent_ref) {
298 let old = route
299 .previous
300 .iter()
301 .find(|p| p.controller_name == controller_name && same_parent(&p.parent_ref, parent_ref, &namespace))
302 .map(|p| p.conditions.as_slice())
303 .unwrap_or_default();
304 let (accepted, reason, message) = super::mesh::parent_acceptance(parent_ref, &namespace, &snapshot.services);
305 let resolved = match &resolved {
308 Err(("RefNotPermitted", _)) if super::mesh::only_parent_references(&route.references, route.parent_refs, &namespace) => {
309 let local: Vec<&BackendObjectReference> = route.references.iter().filter(|r| r.namespace.as_deref().is_none_or(|n| n == namespace)).copied().collect();
310 resolve_references(route.kind, &namespace, &local, snapshot)
311 }
312 other => other.clone(),
313 };
314 let conditions = vec![
315 condition(old, "Accepted", accepted, reason, &message, route.generation, now),
316 match &resolved {
317 Ok(()) => condition(old, "ResolvedRefs", true, "ResolvedRefs", "All references resolved", route.generation, now),
318 Err((reason, message)) => condition(old, "ResolvedRefs", false, reason, message, route.generation, now),
319 },
320 ];
321 parents.push(RouteParentStatus { parent_ref: parent_ref.clone(), controller_name: controller_name.to_string(), conditions });
322 continue;
323 }
324 if parent_ref.group() != GATEWAY_GROUP {
325 continue;
326 }
327 let parent_key = (parent_ref.namespace.clone().unwrap_or_else(|| namespace.clone()), parent_ref.name.clone());
328 let (gateway_key, owner) = match parent_ref.kind() {
330 "Gateway" => (parent_key.clone(), None),
331 "ListenerSet" => match listener_sets.iter().find(|(set, _, _)| is_named(*set, &parent_key)) {
332 Some((_, gateway, _)) => (gateway.clone(), Some(parent_key.clone())),
333 None => continue, },
335 _ => continue,
336 };
337 let candidates: Vec<&ListenerView> = listeners.iter().filter(|l| l.gateway == gateway_key && l.owner == owner).collect();
338 if candidates.is_empty() && !snapshot.gateways.iter().any(|g| is_named(g, &gateway_key) && our_classes.contains(&g.spec.gateway_class_name)) {
339 continue; }
341 let old = route
342 .previous
343 .iter()
344 .find(|p| p.controller_name == controller_name && same_parent(&p.parent_ref, parent_ref, &namespace))
345 .map(|p| p.conditions.as_slice())
346 .unwrap_or_default();
347 let (accepted, reason, message, attached_to) = attach(route.kind, route.hostnames, parent_ref, &namespace, &candidates, snapshot);
348 if !attached_to.is_empty() {
349 for reference in &route.references {
350 let service = (reference.namespace.clone().unwrap_or_else(|| namespace.clone()), reference.name.clone());
351 service_gateways.entry(service).or_default().insert(gateway_key.clone());
352 }
353 }
354 for listener in &attached_to {
355 *attached.entry((gateway_key.clone(), owner.clone(), listener.clone())).or_default() += 1;
356 if let Some(view) = listeners.iter_mut().find(|l| l.gateway == gateway_key && l.owner == owner && &l.name == listener) {
357 view.http_routes.push(route.compiled.clone());
358 }
359 }
360 let conditions = vec![
361 condition(old, "Accepted", accepted, reason, &message, route.generation, now),
362 match &resolved {
363 Ok(()) => condition(old, "ResolvedRefs", true, "ResolvedRefs", "All references resolved", route.generation, now),
364 Err((reason, message)) => condition(old, "ResolvedRefs", false, reason, message, route.generation, now),
365 },
366 ];
367 parents.push(RouteParentStatus { parent_ref: parent_ref.clone(), controller_name: controller_name.to_string(), conditions });
368 }
369 if !parents.is_empty() {
370 let statuses = match route.kind {
371 "GRPCRoute" => &mut desired.grpc_routes,
372 "TLSRoute" => &mut desired.tls_routes,
373 "TCPRoute" => &mut desired.tcp_routes,
374 "UDPRoute" => &mut desired.udp_routes,
375 _ => &mut desired.http_routes,
376 };
377 statuses.insert((namespace, route.name), parents);
378 }
379 }
380
381 for ((namespace, name), evaluated) in super::backend_tls::evaluate(snapshot) {
383 let Some(policy) = snapshot.backend_tls_policies.iter().find(|p| p.namespace().as_deref() == Some(namespace.as_str()) && p.name_any() == name) else { continue };
384 let generation = policy.metadata.generation;
385 let gateways: BTreeSet<(String, String)> = policy
386 .spec
387 .target_refs
388 .iter()
389 .filter_map(|t| service_gateways.get(&(namespace.clone(), t.name.clone())))
390 .flatten()
391 .cloned()
392 .collect();
393 let previous = policy.status.as_ref().map(|s| s.ancestors.as_slice()).unwrap_or_default();
394 let ancestors: Vec<PolicyAncestorStatus> = gateways
395 .into_iter()
396 .map(|(gateway_namespace, gateway_name)| {
397 let ancestor_ref = ParentReference {
398 group: Some(GATEWAY_GROUP.to_string()),
399 kind: Some("Gateway".to_string()),
400 namespace: Some(gateway_namespace),
401 name: gateway_name,
402 section_name: None,
403 port: None,
404 };
405 let old = previous.iter().find(|a| a.controller_name == controller_name && a.ancestor_ref == ancestor_ref).map(|a| a.conditions.as_slice()).unwrap_or_default();
406 let conditions = vec![
407 match &evaluated.accepted {
408 Ok(()) => condition(old, "Accepted", true, "Accepted", "", generation, now),
409 Err((reason, message)) => condition(old, "Accepted", false, reason, message, generation, now),
410 },
411 match &evaluated.resolved {
412 Ok(()) => condition(old, "ResolvedRefs", true, "ResolvedRefs", "", generation, now),
413 Err((reason, message)) => condition(old, "ResolvedRefs", false, reason, message, generation, now),
414 },
415 ];
416 PolicyAncestorStatus { ancestor_ref, controller_name: controller_name.to_string(), conditions }
417 })
418 .collect();
419 if !ancestors.is_empty() {
420 desired.backend_tls_policies.insert((namespace, name), ancestors);
421 }
422 }
423
424 for gateway in &snapshot.gateways {
426 if !our_classes.contains(&gateway.spec.gateway_class_name) {
427 continue;
428 }
429 let key = (gateway.namespace().unwrap_or_default(), gateway.name_any());
430 let generation = gateway.metadata.generation;
431 let previous = gateway.status.clone().unwrap_or_default();
432 let container_ports = super::services::container_ports(super::services::current(snapshot, gateway));
433 let backend_client_cert = backend_client_certificate(gateway, snapshot);
434 let mut listener_statuses = Vec::new();
435 let mut set_statuses: BTreeMap<(String, String), Vec<ListenerStatus>> = BTreeMap::new();
436 let mut any_valid = false;
437 let (mut any_accepted, mut any_rejected) = (false, false);
438 for view in listeners.iter().filter(|l| l.gateway == key) {
439 let count = attached.get(&(key.clone(), view.owner.clone(), view.name.clone())).copied().unwrap_or(0);
440 match &view.owner {
441 None => {
442 any_valid |= view.accepted && view.programmed;
443 any_accepted |= view.accepted;
444 any_rejected |= !view.accepted;
445 let old = previous.listeners.iter().find(|l| l.name == view.name).map(|l| l.conditions.as_slice()).unwrap_or_default();
446 listener_statuses.push(listener_status(view, count, old, generation, now));
447 }
448 Some(set_key) => {
449 let set = snapshot.listener_sets.iter().find(|s| is_named(*s, set_key));
450 let old = set.and_then(|s| s.status.as_ref()).and_then(|s| s.listeners.iter().find(|l| l.name == view.name)).map(|l| l.conditions.clone()).unwrap_or_default();
451 let set_generation = set.and_then(|s| s.metadata.generation);
452 set_statuses.entry(set_key.clone()).or_default().push(listener_status(view, count, &old, set_generation, now));
453 }
454 }
455 let container_port = container_ports.get(&(view.port, super::services::transport(&view.protocol).to_string()));
456 if let (true, true, Some(container_port)) = (view.accepted, view.programmed, container_port) {
457 config.listeners.push(ListenerConfig {
458 gateway: key.clone(),
459 name: view.name.clone(),
460 port: view.port,
461 protocol: view.protocol.clone(),
462 hostname: view.hostname.clone(),
463 container_port: *container_port,
464 tls: view.tls.clone(),
465 client_ca: view.client_ca.clone(),
466 backend_client_cert: backend_client_cert.as_ref().and_then(|c| c.as_ref().ok()).cloned(),
467 request_filters: view.request_filters.clone(),
468 http_routes: view.http_routes.clone(),
469 });
470 }
471 }
472 let mut addresses = gateway_addresses(snapshot, &key.0, &key.1);
473 let unsupported_address = gateway.spec.addresses.iter().find(|a| !super::services::address_supported(a));
474 let requested = super::services::requested_addresses(gateway);
476 let mut missing: Option<String> = requested.iter().find(|r| !addresses.iter().any(|a| &a.value == *r)).map(|r| r.to_string());
477 let mut addresses_assigned = None;
479 if settings.experimental {
480 let service = super::services::current(snapshot, gateway);
481 let cluster_ips: Vec<String> = service
482 .and_then(|s| s.spec.as_ref())
483 .map(|s| s.cluster_ips.clone().unwrap_or_else(|| s.cluster_ip.clone().into_iter().collect()))
484 .unwrap_or_default()
485 .into_iter()
486 .filter(|ip| !ip.is_empty() && ip != "None")
487 .collect();
488 let assignment = super::addresses::assign(&gateway.spec.addresses, &addresses, &cluster_ips);
489 if service.is_some() {
490 addresses_assigned = Some(assignment.condition.clone());
491 }
492 if !assignment.condition.0 && missing.is_none() && !assignment.addresses.is_empty() {
493 missing = Some(assignment.condition.2.trim_start_matches("Not assigned: ").to_string());
494 }
495 addresses = assignment.addresses;
496 }
497 let addressed = !addresses.is_empty();
498 let programmed = any_valid && addressed && missing.is_none() && unsupported_address.is_none();
499 let (programmed_reason, programmed_message) = if let Some(address) = &missing {
500 ("AddressNotUsable", format!("The load balancer hasn't assigned {address}"))
501 } else if !addressed {
502 ("AddressNotAssigned", "Waiting for the Service's address".to_string())
503 } else if unsupported_address.is_some() {
504 ("Invalid", "An address type isn't supported".to_string())
505 } else if !any_valid {
506 ("Invalid", "No valid listener".to_string())
507 } else {
508 ("Programmed", "Serving".to_string())
509 };
510 let invalid_parameters = gateway.spec.infrastructure.as_ref().is_some_and(|i| i.parameters_ref.is_some());
513 let gateway_accepted = (gateway.spec.listeners.is_empty() || any_accepted) && unsupported_address.is_none() && !invalid_parameters;
514 let accepted_reason = if invalid_parameters {
515 "InvalidParameters"
516 } else if unsupported_address.is_some() {
517 "UnsupportedAddress"
518 } else if any_rejected {
519 "ListenersNotValid"
520 } else {
521 "Accepted"
522 };
523
524 let mut attached_sets = 0;
526 for (set, _, allowed) in listener_sets.iter().filter(|(_, gateway, _)| *gateway == key) {
527 let set_key = (set.namespace().unwrap_or_default(), set.name_any());
528 let statuses = set_statuses.remove(&set_key).unwrap_or_default();
529 let valid = statuses.iter().any(|l| l.conditions.iter().all(|c| c.type_ == "Conflicted" || c.type_ == "ResolvedRefs" || c.status == "True"));
530 let (ok, reason, message) = if !allowed {
531 (false, "NotAllowed", "The Gateway's allowedListeners doesn't admit this ListenerSet")
532 } else if !valid {
533 (false, "ListenersNotValid", "No valid listener")
534 } else {
535 (true, "Accepted", "")
536 };
537 attached_sets += ok as i32;
538 let old = set.status.as_ref().map(|s| s.conditions.as_slice()).unwrap_or_default();
539 let set_generation = set.metadata.generation;
540 let conditions = vec![
541 condition(old, "Accepted", ok, reason, message, set_generation, now),
542 condition(old, "Programmed", ok, if ok { "Programmed" } else { reason }, message, set_generation, now),
543 ];
544 desired.listener_sets.insert(set_key, ListenerSetStatus { conditions, listeners: statuses });
545 }
546 let mut conditions = vec![
547 condition(&previous.conditions, "Accepted", gateway_accepted, accepted_reason, "", generation, now),
548 condition(&previous.conditions, "Programmed", programmed, programmed_reason, &programmed_message, generation, now),
549 ];
550 if let Some(certificate) = &backend_client_cert {
551 conditions.push(match certificate {
552 Ok(_) => condition(&previous.conditions, "ResolvedRefs", true, "ResolvedRefs", "", generation, now),
553 Err((reason, message)) => condition(&previous.conditions, "ResolvedRefs", false, reason, message, generation, now),
554 });
555 }
556 if let Some((assigned, reason, message)) = &addresses_assigned {
557 conditions.push(condition(&previous.conditions, "AddressesAssigned", *assigned, reason, message, generation, now));
558 }
559 if insecure_frontend(gateway) {
560 let message = "Client certificate validation allows clients without a valid certificate (AllowInsecureFallback)";
561 conditions.push(condition(&previous.conditions, "InsecureFrontendValidationMode", true, "ConfigurationChanged", message, generation, now));
562 }
563 let conditions = with_foreign(conditions, &previous.conditions);
564 desired.gateways.insert(
565 key,
566 GatewayStatus { addresses, conditions, listeners: listener_statuses, attached_listener_sets: Some(attached_sets) },
567 );
568 }
569 (config, desired)
570}
571
572type ListenerKey = ((String, String), Option<(String, String)>, String);
576
577struct ListenerView {
578 gateway: (String, String),
579 owner: Option<(String, String)>,
581 namespace: String,
583 name: String,
584 port: i32,
585 protocol: String,
586 hostname: Option<String>,
587 allowed: AllowedRoutes,
588 supported_kinds: Vec<RouteGroupKind>,
589 accepted: bool,
590 accepted_reason: &'static str,
591 programmed: bool,
592 resolved_refs: Result<(), (&'static str, String)>,
593 conflicted: Option<&'static str>,
594 message: String,
595 tls: Option<(Vec<u8>, Vec<u8>)>,
596 client_ca: Option<ClientCa>,
597 request_filters: Vec<super::ext_auth::CompiledAuth>,
598 http_routes: Vec<CompiledHttpRoute>,
599}
600
601fn terminates_tls(listener: &Listener) -> bool {
604 match listener.protocol.as_str() {
605 "HTTPS" => true,
606 "TLS" => listener.tls.as_ref().and_then(|t| t.mode.as_deref()).unwrap_or("Terminate") == "Terminate",
607 _ => false,
608 }
609}
610
611fn kinds_for(protocol: &str) -> &'static [&'static str] {
613 match protocol {
614 "HTTP" | "HTTPS" => &["HTTPRoute", "GRPCRoute"],
615 "TLS" => &["TLSRoute"],
616 "TCP" => &["TCPRoute"],
617 "UDP" => &["UDPRoute"],
618 _ => &[],
619 }
620}
621
622fn listener_views(gateway: &Gateway, listeners: &[Listener], namespace: &str, owner: Option<(String, String)>, snapshot: &Snapshot) -> Vec<ListenerView> {
626 let key = (gateway.namespace().unwrap_or_default(), gateway.name_any());
627 listeners
628 .iter()
629 .map(|l| {
630 let allowed = l.allowed_routes.clone().unwrap_or_default();
631 let carried = kinds_for(&l.protocol);
632 let mut supported_kinds = Vec::new();
633 let mut resolved_refs = Ok(());
634 if allowed.kinds.is_empty() {
635 supported_kinds = carried.iter().map(|k| RouteGroupKind { group: Some(GATEWAY_GROUP.to_string()), kind: k.to_string() }).collect();
636 } else {
637 for kind in &allowed.kinds {
638 let group_ok = kind.group.as_deref().unwrap_or(GATEWAY_GROUP) == GATEWAY_GROUP;
639 if group_ok && carried.contains(&kind.kind.as_str()) {
640 supported_kinds.push(RouteGroupKind { group: Some(GATEWAY_GROUP.to_string()), kind: kind.kind.clone() });
641 } else {
642 resolved_refs = Err(("InvalidRouteKinds", format!("{} isn't served on a {} listener", kind.kind, l.protocol)));
643 }
644 }
645 }
646 let (mut accepted, mut accepted_reason, message) = if carried.is_empty() {
647 (false, "UnsupportedProtocol", format!("protocol {} isn't supported", l.protocol))
648 } else {
649 (true, "Accepted", String::new())
650 };
651 let (mut tls, mut client_ca) = (None, None);
652 if accepted && terminates_tls(l) {
653 match certificates(l, namespace, snapshot) {
654 Ok(material) => tls = Some(material),
655 Err(e) => resolved_refs = Err(e),
656 }
657 if let Some(validation) = frontend_validation(gateway, l.port) {
658 match client_cas(validation, &key.0, snapshot) {
659 Ok((ca, skipped)) => {
660 client_ca = Some(ca);
661 if let Some(e) = skipped {
663 resolved_refs = Err(e);
664 }
665 }
666 Err(e) => {
667 resolved_refs = Err(e);
668 accepted = false;
669 accepted_reason = "NoValidCACertificate";
670 }
671 }
672 }
673 }
674 let mut request_filters = Vec::new();
679 let mut message = message;
680 for filter in l.filters.as_ref().map(|f| f.requests.as_slice()).unwrap_or_default() {
681 let auth = filter.external_auth.as_ref().filter(|_| filter.filter_type == "ExternalAuth" && matches!(l.protocol.as_str(), "HTTP" | "HTTPS"));
682 let Some(auth) = auth else {
683 accepted = false;
684 accepted_reason = "UnsupportedValue";
685 message = format!("listener filter {} isn't supported on a {} listener", filter.filter_type, l.protocol);
686 break;
687 };
688 let resolved = super::routes::resolve(&auth.backend_ref, namespace, "Gateway", snapshot);
689 request_filters.push(super::ext_auth::CompiledAuth { filter: auth.clone(), endpoints: resolved.endpoints, tls: resolved.tls });
690 }
691 let programmed = accepted && resolved_refs.as_ref().err().is_none_or(|(reason, _)| *reason == "InvalidRouteKinds" || (client_ca.is_some() && matches!(*reason, "InvalidCACertificateRef" | "InvalidKind")));
693 ListenerView {
694 gateway: key.clone(),
695 owner: owner.clone(),
696 namespace: namespace.to_string(),
697 name: l.name.clone(),
698 port: l.port,
699 protocol: l.protocol.clone(),
700 hostname: l.hostname.clone(),
701 allowed,
702 supported_kinds,
703 accepted,
704 accepted_reason,
705 programmed,
706 resolved_refs,
707 conflicted: None,
708 message,
709 tls,
710 client_ca,
711 request_filters,
712 http_routes: Vec::new(),
713 }
714 })
715 .collect()
716}
717
718fn mark_conflicts(views: &mut [ListenerView]) {
724 for j in 0..views.len() {
725 for i in 0..views.len() {
726 if i == j || views[i].port != views[j].port {
727 continue;
728 }
729 if super::services::transport(&views[i].protocol) != super::services::transport(&views[j].protocol) {
731 continue;
732 }
733 if views[j].owner.is_some() && (i > j || views[i].conflicted.is_some()) {
735 continue;
736 }
737 if views[j].owner.is_none() && views[i].owner.is_some() {
738 continue;
739 }
740 let reason = if views[i].protocol != views[j].protocol {
743 "ProtocolConflict"
744 } else if views[i].hostname == views[j].hostname {
745 "HostnameConflict"
746 } else {
747 continue;
748 };
749 views[j].conflicted = Some(reason);
750 views[j].programmed = false;
751 if views[j].owner.is_some() {
752 views[j].accepted = false;
753 views[j].accepted_reason = reason;
754 }
755 }
756 }
757}
758
759fn attached_listener_sets<'a>(gateway: &Gateway, snapshot: &'a Snapshot) -> Vec<(&'a ListenerSet, bool)> {
762 let gateway_namespace = gateway.namespace().unwrap_or_default();
763 let mut sets: Vec<&ListenerSet> = snapshot
764 .listener_sets
765 .iter()
766 .filter(|set| {
767 let parent = &set.spec.parent_ref;
768 let namespace = parent.namespace.clone().or_else(|| set.namespace()).unwrap_or_default();
769 parent.group.as_deref().unwrap_or(GATEWAY_GROUP) == GATEWAY_GROUP
770 && parent.kind.as_deref().unwrap_or("Gateway") == "Gateway"
771 && parent.name == gateway.name_any()
772 && namespace == gateway_namespace
773 })
774 .collect();
775 sets.sort_by_key(|set| (set.metadata.creation_timestamp.as_ref().map(|t| t.0.to_string()).unwrap_or_default(), set.namespace(), set.name_any()));
776 let admitted = gateway.spec.allowed_listeners.as_ref().and_then(|a| a.namespaces.as_ref());
777 sets.into_iter()
778 .map(|set| {
779 let namespace = set.namespace().unwrap_or_default();
780 let allowed = namespace_admitted(admitted.and_then(|n| n.from.as_deref()).unwrap_or("None"), admitted.and_then(|n| n.selector.as_ref()), &namespace, &gateway_namespace, snapshot);
781 (set, allowed)
782 })
783 .collect()
784}
785
786fn namespace_admitted(from: &str, selector: Option<&LabelSelector>, namespace: &str, home: &str, snapshot: &Snapshot) -> bool {
789 match from {
790 "All" => true,
791 "Same" => namespace == home,
792 "Selector" => {
793 let mut labels = snapshot.namespaces.iter().find(|n| n.name_any() == namespace).map(|n| n.labels().clone()).unwrap_or_default();
794 labels.entry("kubernetes.io/metadata.name".to_string()).or_insert_with(|| namespace.to_string());
796 selector.is_some_and(|s| selector_matches(s, &labels))
797 }
798 _ => false,
799 }
800}
801
802fn with_foreign(mut ours: Vec<Condition>, previous: &[Condition]) -> Vec<Condition> {
807 ours.extend(previous.iter().filter(|c| c.type_.contains('/') && !ours.iter().any(|o| o.type_ == c.type_)).cloned().collect::<Vec<_>>());
808 ours
809}
810
811fn listener_status(view: &ListenerView, attached_routes: i32, old: &[Condition], generation: Option<i64>, now: &Time) -> ListenerStatus {
812 let programmed_reason = match (view.programmed, view.conflicted) {
813 (true, _) => "Programmed",
814 (false, Some(reason)) if view.owner.is_some() => reason,
815 _ => "Invalid",
816 };
817 ListenerStatus {
818 name: view.name.clone(),
819 supported_kinds: view.supported_kinds.clone(),
820 attached_routes,
821 conditions: with_foreign(
822 vec![
823 condition(old, "Accepted", view.accepted, view.accepted_reason, &view.message, generation, now),
824 condition(old, "Programmed", view.programmed, programmed_reason, &view.message, generation, now),
825 condition(old, "ResolvedRefs", view.resolved_refs.is_ok(), view.resolved_refs.as_ref().err().map_or("ResolvedRefs", |(r, _)| r), view.resolved_refs.as_ref().err().map_or("", |(_, m)| m.as_str()), generation, now),
826 condition(old, "Conflicted", view.conflicted.is_some(), view.conflicted.unwrap_or("NoConflicts"), "", generation, now),
827 ],
828 old,
829 ),
830 }
831}
832
833type TlsMaterial = (Vec<u8>, Vec<u8>);
837
838fn certificates(listener: &Listener, gateway_namespace: &str, snapshot: &Snapshot) -> Result<TlsMaterial, (&'static str, String)> {
839 let Some(tls) = &listener.tls else {
840 return Err(("InvalidCertificateRef", "a listener that terminates TLS needs tls.certificateRefs".to_string()));
841 };
842 if tls.certificate_refs.is_empty() {
843 return Err(("InvalidCertificateRef", "a listener that terminates TLS needs tls.certificateRefs".to_string()));
844 }
845 let mut material = None;
846 for cert in &tls.certificate_refs {
847 let group = cert.group.as_deref().unwrap_or("");
848 let kind = cert.kind.as_deref().unwrap_or("Secret");
849 if !group.is_empty() || kind != "Secret" {
850 return Err(("InvalidCertificateRef", format!("{kind} isn't a supported certificate reference")));
851 }
852 let namespace = cert.namespace.clone().unwrap_or_else(|| gateway_namespace.to_string());
853 if namespace != gateway_namespace && !granted(snapshot, GATEWAY_GROUP, "Gateway", gateway_namespace, "", "Secret", &namespace, &cert.name) {
854 return Err(("RefNotPermitted", format!("no ReferenceGrant in {namespace} lets Gateways in {gateway_namespace} use Secret {}", cert.name)));
855 }
856 let secret = snapshot.secrets.iter().find(|s| s.namespace().as_deref() == Some(namespace.as_str()) && s.name_any() == cert.name);
857 let valid = secret.is_some_and(|s| {
858 let data = s.data.as_ref();
859 let pem = |key: &str| data.and_then(|d| d.get(key)).is_some_and(|v| !v.0.is_empty());
860 pem("tls.crt") && pem("tls.key") && crate::core::x509::cert_info_from_pem(&data.unwrap()["tls.crt"].0).is_some()
861 });
862 if !valid {
863 return Err(("InvalidCertificateRef", format!("Secret {namespace}/{} isn't a valid TLS Secret", cert.name)));
864 }
865 if material.is_none() {
866 let data = secret.and_then(|s| s.data.as_ref()).expect("checked above");
867 material = Some((data["tls.crt"].0.clone(), data["tls.key"].0.clone()));
868 }
869 }
870 material.ok_or(("InvalidCertificateRef", "no certificate".to_string()))
871}
872
873fn frontend_validation(gateway: &Gateway, port: i32) -> Option<&FrontendTlsValidation> {
876 let frontend = gateway.spec.tls.as_ref()?.frontend.as_ref()?;
877 match frontend.per_port.iter().find(|p| p.port == port) {
878 Some(per_port) => per_port.tls.validation.as_ref(),
879 None => frontend.default.validation.as_ref(),
880 }
881}
882
883fn backend_client_certificate(gateway: &Gateway, snapshot: &Snapshot) -> Option<Result<TlsMaterial, (&'static str, String)>> {
886 let reference = gateway.spec.tls.as_ref()?.backend.as_ref()?.client_certificate_ref.as_ref()?;
887 let gateway_namespace = gateway.namespace().unwrap_or_default();
888 let group = reference.group.as_deref().unwrap_or("");
889 let kind = reference.kind.as_deref().unwrap_or("Secret");
890 if !group.is_empty() || kind != "Secret" {
891 return Some(Err(("InvalidClientCertificateRef", format!("{group}/{kind} isn't a supported client certificate reference"))));
892 }
893 let namespace = reference.namespace.clone().unwrap_or_else(|| gateway_namespace.clone());
894 if namespace != gateway_namespace && !granted(snapshot, GATEWAY_GROUP, "Gateway", &gateway_namespace, "", "Secret", &namespace, &reference.name) {
895 return Some(Err(("RefNotPermitted", format!("no ReferenceGrant in {namespace} lets Gateways in {gateway_namespace} use Secret {}", reference.name))));
896 }
897 let data = snapshot
898 .secrets
899 .iter()
900 .find(|s| s.namespace().as_deref() == Some(namespace.as_str()) && s.name_any() == reference.name)
901 .and_then(|s| s.data.as_ref());
902 let pair = data.and_then(|d| Some((d.get("tls.crt")?.0.clone(), d.get("tls.key")?.0.clone())));
903 let valid = pair.as_ref().filter(|(cert, key)| crate::tls::certified_key_from_pem(cert, key).is_ok());
904 Some(valid.cloned().ok_or(("InvalidClientCertificateRef", format!("Secret {namespace}/{} isn't a valid TLS Secret", reference.name))))
905}
906
907fn insecure_frontend(gateway: &Gateway) -> bool {
909 let Some(frontend) = gateway.spec.tls.as_ref().and_then(|t| t.frontend.as_ref()) else { return false };
910 std::iter::once(&frontend.default)
911 .chain(frontend.per_port.iter().map(|p| &p.tls))
912 .filter_map(|t| t.validation.as_ref())
913 .any(|v| v.mode.as_deref() == Some("AllowInsecureFallback"))
914}
915
916pub const CLUSTER_TRUST_BUNDLE_GROUP: &str = "certificates.k8s.io";
918
919pub fn cluster_trust_bundle<'a>(snapshot: &'a Snapshot, name: &str) -> Result<&'a str, (&'static str, String)> {
923 if !snapshot.cluster_trust_bundles_served {
924 return Err(("InvalidKind", "this cluster doesn't serve ClusterTrustBundles (certificates.k8s.io)".to_string()));
925 }
926 snapshot
927 .cluster_trust_bundles
928 .iter()
929 .find(|(n, _)| n == name)
930 .map(|(_, pem)| pem.as_str())
931 .filter(|pem| crate::core::x509::cert_info_from_pem(pem.as_bytes()).is_some())
932 .ok_or_else(|| ("InvalidCACertificateRef", format!("ClusterTrustBundle {name} doesn't exist or holds no valid certificate")))
933}
934
935fn client_cas(validation: &FrontendTlsValidation, gateway_namespace: &str, snapshot: &Snapshot) -> Result<(ClientCa, Option<(&'static str, String)>), (&'static str, String)> {
941 let mut pem = Vec::new();
942 let mut skipped = None;
943 for reference in &validation.ca_certificate_refs {
944 if reference.group == CLUSTER_TRUST_BUNDLE_GROUP && reference.kind == "ClusterTrustBundle" {
945 match cluster_trust_bundle(snapshot, &reference.name) {
946 Ok(bundle) => {
947 pem.extend_from_slice(bundle.as_bytes());
948 pem.push(b'\n');
949 }
950 Err(e) => skipped = Some(e),
951 }
952 continue;
953 }
954 if !reference.group.is_empty() || reference.kind != "ConfigMap" {
955 return Err(("InvalidCACertificateKind", format!("{} isn't a supported CA certificate reference", reference.kind)));
956 }
957 let namespace = reference.namespace.clone().unwrap_or_else(|| gateway_namespace.to_string());
958 if namespace != gateway_namespace && !granted(snapshot, GATEWAY_GROUP, "Gateway", gateway_namespace, "", "ConfigMap", &namespace, &reference.name) {
959 return Err(("RefNotPermitted", format!("no ReferenceGrant in {namespace} lets Gateways in {gateway_namespace} use ConfigMap {}", reference.name)));
960 }
961 let ca = snapshot
962 .config_maps
963 .iter()
964 .find(|c| c.namespace().as_deref() == Some(namespace.as_str()) && c.name_any() == reference.name)
965 .and_then(|c| c.data.as_ref())
966 .and_then(|d| d.get("ca.crt"))
967 .filter(|ca| crate::core::x509::cert_info_from_pem(ca.as_bytes()).is_some());
968 let Some(ca) = ca else {
969 return Err(("InvalidCACertificateRef", format!("ConfigMap {namespace}/{} has no valid ca.crt", reference.name)));
970 };
971 pem.extend_from_slice(ca.as_bytes());
972 pem.push(b'\n');
973 }
974 if pem.is_empty() {
975 return Err(skipped.unwrap_or(("InvalidCACertificateRef", "no caCertificateRefs".to_string())));
976 }
977 Ok((ClientCa { pem, insecure_fallback: validation.mode.as_deref() == Some("AllowInsecureFallback") }, skipped))
978}
979
980fn same_parent(a: &ParentReference, b: &ParentReference, route_namespace: &str) -> bool {
984 a.group() == b.group()
985 && a.kind() == b.kind()
986 && a.name == b.name
987 && a.namespace.as_deref().unwrap_or(route_namespace) == b.namespace.as_deref().unwrap_or(route_namespace)
988 && a.section_name == b.section_name
989 && a.port == b.port
990}
991
992fn is_named<K: kube::Resource>(object: &K, key: &(String, String)) -> bool {
993 object.meta().namespace.as_deref() == Some(key.0.as_str()) && object.meta().name.as_deref() == Some(key.1.as_str())
994}
995
996fn attach(kind: &str, hostnames: &[String], parent_ref: &ParentReference, route_namespace: &str, candidates: &[&ListenerView], snapshot: &Snapshot) -> (bool, &'static str, String, Vec<String>) {
999 let mut named: Vec<&&ListenerView> = candidates
1000 .iter()
1001 .filter(|l| parent_ref.section_name.as_ref().is_none_or(|s| *s == l.name))
1002 .filter(|l| parent_ref.port.is_none_or(|p| p == l.port))
1003 .collect();
1004 if named.is_empty() {
1005 return (false, "NoMatchingParent", "no listener matches the parentRef's sectionName/port".to_string(), Vec::new());
1006 }
1007 named.retain(|l| l.accepted && l.conflicted.is_none());
1008 let allowed: Vec<&&ListenerView> = named.iter().copied().filter(|l| allows(l, kind, route_namespace, snapshot)).collect();
1009 if allowed.is_empty() {
1010 return (false, "NotAllowedByListeners", "no listener allows this route".to_string(), Vec::new());
1011 }
1012 let matching: Vec<String> = allowed
1013 .iter()
1014 .filter(|l| hostnames_intersect(l.hostname.as_deref(), hostnames))
1015 .map(|l| l.name.clone())
1016 .collect();
1017 if matching.is_empty() {
1018 return (false, "NoMatchingListenerHostname", "no listener hostname matches the route's hostnames".to_string(), Vec::new());
1019 }
1020 (true, "Accepted", String::new(), matching)
1021}
1022
1023fn allows(listener: &ListenerView, kind: &str, namespace: &str, snapshot: &Snapshot) -> bool {
1025 if !listener.supported_kinds.iter().any(|k| k.kind == kind) {
1026 return false;
1027 }
1028 let namespaces = listener.allowed.namespaces.as_ref();
1029 let from = namespaces.and_then(|n| n.from.as_deref()).unwrap_or("Same");
1030 namespace_admitted(from, namespaces.and_then(|n| n.selector.as_ref()), namespace, &listener.namespace, snapshot)
1031}
1032
1033#[doc(hidden)]
1034pub fn selector_matches(selector: &LabelSelector, labels: &BTreeMap<String, String>) -> bool {
1035 let labels_ok = selector.match_labels.as_ref().is_none_or(|m| m.iter().all(|(k, v)| labels.get(k) == Some(v)));
1036 let expressions_ok = selector.match_expressions.as_ref().is_none_or(|exprs| {
1037 exprs.iter().all(|e| {
1038 let value = labels.get(&e.key);
1039 let values = e.values.clone().unwrap_or_default();
1040 match e.operator.as_str() {
1041 "In" => value.is_some_and(|v| values.contains(v)),
1042 "NotIn" => value.is_none_or(|v| !values.contains(v)),
1043 "Exists" => value.is_some(),
1044 "DoesNotExist" => value.is_none(),
1045 _ => false,
1046 }
1047 })
1048 });
1049 labels_ok && expressions_ok
1050}
1051
1052#[doc(hidden)]
1055pub fn hostnames_intersect(listener: Option<&str>, route: &[String]) -> bool {
1056 let Some(listener) = listener.filter(|h| !h.is_empty()) else { return true };
1057 route.is_empty() || route.iter().any(|r| hostname_overlap(listener, r))
1058}
1059
1060fn hostname_overlap(a: &str, b: &str) -> bool {
1061 let a = a.to_ascii_lowercase();
1062 let b = b.to_ascii_lowercase();
1063 match (a.strip_prefix("*."), b.strip_prefix("*.")) {
1064 (None, None) => a == b,
1065 (Some(suffix), None) => b.ends_with(&format!(".{suffix}")),
1066 (None, Some(suffix)) => a.ends_with(&format!(".{suffix}")),
1067 (Some(x), Some(y)) => x == y || x.ends_with(&format!(".{y}")) || y.ends_with(&format!(".{x}")),
1068 }
1069}
1070
1071struct RouteIn<'a> {
1073 kind: &'static str,
1074 namespace: String,
1075 name: String,
1076 generation: Option<i64>,
1077 parent_refs: &'a [ParentReference],
1078 hostnames: &'a [String],
1079 previous: Vec<RouteParentStatus>,
1080 references: Vec<&'a BackendObjectReference>,
1082 compiled: CompiledHttpRoute,
1083}
1084
1085impl<'a> RouteIn<'a> {
1086 fn new<S>(
1087 kind: &'static str,
1088 route: &'a kube::core::Object<S, RouteStatus>,
1089 parent_refs: &'a [ParentReference],
1090 hostnames: &'a [String],
1091 references: Vec<&'a BackendObjectReference>,
1092 compiled: CompiledHttpRoute,
1093 ) -> RouteIn<'a>
1094 where
1095 S: Clone,
1096 {
1097 RouteIn {
1098 kind,
1099 namespace: route.namespace().unwrap_or_default(),
1100 name: route.name_any(),
1101 generation: route.metadata.generation,
1102 parent_refs,
1103 hostnames,
1104 previous: route.status.as_ref().map(|s| s.parents.clone()).unwrap_or_default(),
1105 references,
1106 compiled,
1107 }
1108 }
1109}
1110
1111fn rule_references<'a>(backend_refs: &'a [HttpBackendRef], filters: &'a [HttpRouteFilter]) -> impl Iterator<Item = &'a BackendObjectReference> {
1114 let mirrors = filters.iter().chain(backend_refs.iter().flat_map(|b| &b.filters)).filter_map(|f| f.request_mirror.as_ref());
1115 backend_refs.iter().map(|b| &b.backend_ref.object).chain(mirrors.map(|m| &m.backend_ref))
1116}
1117
1118fn http_references(rules: &[HttpRouteRule]) -> Vec<&BackendObjectReference> {
1119 rules.iter().flat_map(|r| rule_references(&r.backend_refs, &r.filters)).collect()
1120}
1121
1122fn l4_references(rules: &[L4RouteRule]) -> Vec<&BackendObjectReference> {
1123 rules.iter().flat_map(|r| r.backend_refs.iter().map(|b| &b.object)).collect()
1124}
1125
1126fn grpc_references(rules: &[GrpcRouteRule]) -> Vec<&BackendObjectReference> {
1127 rules.iter().flat_map(|r| rule_references(&r.backend_refs, &r.filters)).collect()
1128}
1129
1130fn resolve_references(kind: &str, namespace: &str, references: &[&BackendObjectReference], snapshot: &Snapshot) -> Result<(), (&'static str, String)> {
1133 for reference in references {
1134 if !reference.group().is_empty() || reference.kind() != "Service" {
1135 return Err(("InvalidKind", format!("{} isn't a supported backend kind", reference.kind())));
1136 }
1137 let target = reference.namespace.clone().unwrap_or_else(|| namespace.to_string());
1138 if target != namespace && !granted(snapshot, GATEWAY_GROUP, kind, namespace, "", "Service", &target, &reference.name) {
1139 return Err(("RefNotPermitted", format!("no ReferenceGrant in {target} lets {kind}s in {namespace} use Service {}", reference.name)));
1140 }
1141 if !snapshot.services.iter().any(|s| s.namespace().as_deref() == Some(target.as_str()) && s.name_any() == reference.name) {
1142 return Err(("BackendNotFound", format!("Service {target}/{} doesn't exist", reference.name)));
1143 }
1144 }
1145 Ok(())
1146}
1147
1148#[allow(clippy::too_many_arguments)]
1151#[doc(hidden)]
1152pub fn granted(snapshot: &Snapshot, from_group: &str, from_kind: &str, from_namespace: &str, to_group: &str, to_kind: &str, to_namespace: &str, to_name: &str) -> bool {
1153 snapshot.reference_grants.iter().filter(|g| g.namespace().as_deref() == Some(to_namespace)).any(|g| {
1154 g.spec.from.iter().any(|f| f.group == from_group && f.kind == from_kind && f.namespace == from_namespace)
1155 && g.spec.to.iter().any(|t| t.group == to_group && t.kind == to_kind && t.name.as_deref().is_none_or(|n| n == to_name))
1156 })
1157}
1158
1159fn condition(previous: &[Condition], kind: &str, status: bool, reason: &str, message: &str, generation: Option<i64>, now: &Time) -> Condition {
1164 let status = if status { "True" } else { "False" }.to_string();
1165 let since = previous
1166 .iter()
1167 .find(|c| c.type_ == kind && c.status == status)
1168 .map(|c| c.last_transition_time.clone())
1169 .unwrap_or_else(|| now.clone());
1170 Condition {
1171 type_: kind.to_string(),
1172 status,
1173 reason: reason.to_string(),
1174 message: message.to_string(),
1175 observed_generation: generation,
1176 last_transition_time: since,
1177 }
1178}
1179
1180#[cfg(test)]
1181mod tests;