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}
27
28impl Default for Settings {
29 fn default() -> Self {
30 Settings { controller_name: super::CONTROLLER_NAME.to_string(), services: ServiceSettings::default(), provision: None }
31 }
32}
33
34#[derive(Clone, Default)]
36pub struct Snapshot {
37 pub classes: Vec<GatewayClass>,
38 pub gateways: Vec<Gateway>,
39 pub http_routes: Vec<HttpRoute>,
40 pub grpc_routes: Vec<GrpcRoute>,
41 pub tls_routes: Vec<TlsRoute>,
42 pub tcp_routes: Vec<TcpRoute>,
43 pub udp_routes: Vec<UdpRoute>,
44 pub listener_sets: Vec<ListenerSet>,
45 pub backend_tls_policies: Vec<BackendTlsPolicy>,
46 pub reference_grants: Vec<ReferenceGrant>,
47 pub services: Vec<Service>,
48 pub namespaces: Vec<Namespace>,
49 pub secrets: Vec<Secret>,
50 pub config_maps: Vec<ConfigMap>,
52 pub endpoint_slices: Vec<EndpointSlice>,
53 pub pods: Vec<Pod>,
55 pub deployments: Vec<k8s_openapi::api::apps::v1::Deployment>,
57}
58
59pub const GATEWAY_NAME_LABEL: &str = "gateway.networking.k8s.io/gateway-name";
62pub const MANAGED_BY: &str = "rws-gateway";
64
65pub fn gateway_addresses(snapshot: &Snapshot, namespace: &str, name: &str) -> Vec<GatewayStatusAddress> {
67 let service = snapshot.services.iter().find(|s| {
68 s.namespace().as_deref() == Some(namespace)
69 && s.labels().get(GATEWAY_NAME_LABEL).map(String::as_str) == Some(name)
70 && s.labels().get("app.kubernetes.io/managed-by").map(String::as_str) == Some(MANAGED_BY)
71 });
72 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();
73 ingress
74 .into_iter()
75 .filter_map(|i| match (i.ip, i.hostname) {
76 (Some(ip), _) => Some(GatewayStatusAddress { address_type: Some("IPAddress".to_string()), value: ip }),
77 (None, Some(hostname)) => Some(GatewayStatusAddress { address_type: Some("Hostname".to_string()), value: hostname }),
78 _ => None,
79 })
80 .collect()
81}
82
83#[derive(Clone, Debug, Default, PartialEq)]
85pub struct Desired {
86 pub classes: BTreeMap<String, GatewayClassStatus>,
88 pub gateways: BTreeMap<(String, String), GatewayStatus>,
90 pub http_routes: BTreeMap<(String, String), Vec<RouteParentStatus>>,
93 pub grpc_routes: BTreeMap<(String, String), Vec<RouteParentStatus>>,
95 pub tls_routes: BTreeMap<(String, String), Vec<RouteParentStatus>>,
96 pub tcp_routes: BTreeMap<(String, String), Vec<RouteParentStatus>>,
97 pub udp_routes: BTreeMap<(String, String), Vec<RouteParentStatus>>,
98 pub listener_sets: BTreeMap<(String, String), ListenerSetStatus>,
100 pub backend_tls_policies: BTreeMap<(String, String), Vec<PolicyAncestorStatus>>,
102 pub services: BTreeMap<(String, String), Service>,
104 pub endpoint_slices: BTreeMap<(String, String), EndpointSlice>,
106 pub deployments: BTreeMap<(String, String), k8s_openapi::api::apps::v1::Deployment>,
108}
109
110#[derive(Clone, Debug, Default, PartialEq)]
112pub struct GatewayConfig {
113 pub listeners: Vec<ListenerConfig>,
114}
115
116#[derive(Clone, Debug, PartialEq)]
120pub struct ListenerConfig {
121 pub gateway: (String, String),
122 pub name: String,
123 pub port: i32,
124 pub protocol: String,
125 pub hostname: Option<String>,
126 pub container_port: u16,
127 pub tls: Option<(Vec<u8>, Vec<u8>)>,
129 pub client_ca: Option<ClientCa>,
131 pub backend_client_cert: Option<(Vec<u8>, Vec<u8>)>,
134 pub http_routes: Vec<CompiledHttpRoute>,
135}
136
137#[derive(Clone, Debug, PartialEq)]
139pub struct ClientCa {
140 pub pem: Vec<u8>,
142 pub insecure_fallback: bool,
144}
145
146pub const SUPPORTED_FEATURES: &[&str] = &[
150 "BackendTLSPolicy",
151 "BackendTLSPolicySANValidation",
152 "GRPCRoute",
153 "GRPCRouteNamedRouteRule",
154 "Gateway",
155 "GatewayAddressEmpty",
156 "GatewayBackendClientCertificate",
157 "GatewayFrontendClientCertificateValidation",
158 "GatewayFrontendClientCertificateValidationInsecureFallback",
159 "GatewayHTTPListenerIsolation",
160 "GatewayHTTPSListenerDetectMisdirectedRequests",
161 "GatewayInfrastructure",
162 "GatewayPort8080",
163 "GatewayStaticAddresses",
164 "HTTPRoute",
165 "HTTPRoute303RedirectStatusCode",
166 "HTTPRoute307RedirectStatusCode",
167 "HTTPRoute308RedirectStatusCode",
168 "HTTPRouteBackendProtocolH2C",
169 "HTTPRouteBackendProtocolWebSocket",
170 "HTTPRouteBackendRequestHeaderModification",
171 "HTTPRouteBackendTimeout",
172 "HTTPRouteCORS",
173 "HTTPRouteHostRewrite",
174 "HTTPRouteMethodMatching",
175 "HTTPRouteNamedRouteRule",
176 "HTTPRouteParentRefPort",
177 "HTTPRoutePathRedirect",
178 "HTTPRoutePathRewrite",
179 "HTTPRoutePortRedirect",
180 "HTTPRouteQueryParamMatching",
181 "HTTPRouteRequestMirror",
182 "HTTPRouteRequestMultipleMirrors",
183 "HTTPRouteRequestPercentageMirror",
184 "HTTPRouteRequestTimeout",
185 "HTTPRouteResponseHeaderModification",
186 "HTTPRouteSchemeRedirect",
187 "ListenerSet",
188 "ReferenceGrant",
189 "TCPRoute",
190 "TLSRoute",
191 "TLSRouteModeTerminate",
192 "UDPRoute",
193];
194
195pub fn translate(snapshot: &Snapshot, settings: &Settings, now: &Time) -> (GatewayConfig, Desired) {
196 let controller_name = settings.controller_name.as_str();
197 let mut desired = Desired::default();
198 let mut config = GatewayConfig::default();
199
200 let mut our_classes = BTreeSet::new();
202 for class in &snapshot.classes {
203 if class.spec.controller_name != controller_name {
204 continue;
205 }
206 let name = class.name_any();
207 our_classes.insert(name.clone());
208 let previous = class.status.as_ref().map(|s| s.conditions.as_slice()).unwrap_or_default();
209 let generation = class.metadata.generation;
210 let conditions = vec![condition(previous, "Accepted", true, "Accepted", "Handled by rws", generation, now)];
211 let supported_features = SUPPORTED_FEATURES.iter().map(|f| SupportedFeature { name: f.to_string() }).collect();
212 desired.classes.insert(name, GatewayClassStatus { conditions, supported_features });
213 }
214
215 let mut listeners: Vec<ListenerView> = Vec::new();
218 let mut listener_sets: Vec<(&ListenerSet, (String, String), bool)> = Vec::new();
219 let mut merged_gateways: Vec<Gateway> = Vec::new();
220 for gateway in &snapshot.gateways {
221 if !our_classes.contains(&gateway.spec.gateway_class_name) {
222 continue;
223 }
224 let key = (gateway.namespace().unwrap_or_default(), gateway.name_any());
225 let mut views = listener_views(gateway, &gateway.spec.listeners, &key.0, None, snapshot);
226 let mut merged = gateway.clone();
227 for (set, allowed) in attached_listener_sets(gateway, snapshot) {
228 let set_key = (set.namespace().unwrap_or_default(), set.name_any());
229 if allowed {
230 views.extend(listener_views(gateway, &set.spec.listeners, &set_key.0, Some(set_key.clone()), snapshot));
231 merged.spec.listeners.extend(set.spec.listeners.iter().cloned());
232 }
233 listener_sets.push((set, key.clone(), allowed));
234 }
235 mark_conflicts(&mut views);
236 listeners.extend(views);
237 merged_gateways.push(merged);
238 }
239
240 let our_gateways: Vec<&Gateway> = merged_gateways.iter().collect();
243 desired.services = super::services::desired(snapshot, &our_gateways, &settings.services);
244 desired.endpoint_slices = super::services::endpoint_slices(snapshot, &desired.services, &settings.services);
245 if let Some(provision) = &settings.provision {
246 let gateways: Vec<&Gateway> = snapshot.gateways.iter().filter(|g| our_classes.contains(&g.spec.gateway_class_name)).collect();
247 desired.deployments = super::provision::desired(&gateways, &settings.services.pod_namespace, controller_name, settings.services.port_range, provision);
248 }
249
250 let routes = snapshot
252 .http_routes
253 .iter()
254 .map(|r| RouteIn::new("HTTPRoute", r, &r.spec.parent_refs, &r.spec.hostnames, http_references(&r.spec.rules), super::routes::compile(r, snapshot)))
255 .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))))
256 .chain([("TLSRoute", &snapshot.tls_routes), ("TCPRoute", &snapshot.tcp_routes), ("UDPRoute", &snapshot.udp_routes)].into_iter().flat_map(|(kind, routes)| {
257 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)))
258 }));
259 let mut attached: BTreeMap<ListenerKey, i32> = BTreeMap::new();
260 let mut service_gateways: BTreeMap<(String, String), BTreeSet<(String, String)>> = BTreeMap::new();
262 for route in routes {
263 let namespace = route.namespace.clone();
264 let resolved = resolve_references(route.kind, &namespace, &route.references, snapshot);
265 let mut parents = Vec::new();
266 for parent_ref in route.parent_refs {
267 if parent_ref.group() != GATEWAY_GROUP {
268 continue;
269 }
270 let parent_key = (parent_ref.namespace.clone().unwrap_or_else(|| namespace.clone()), parent_ref.name.clone());
271 let (gateway_key, owner) = match parent_ref.kind() {
273 "Gateway" => (parent_key.clone(), None),
274 "ListenerSet" => match listener_sets.iter().find(|(set, _, _)| is_named(*set, &parent_key)) {
275 Some((_, gateway, _)) => (gateway.clone(), Some(parent_key.clone())),
276 None => continue, },
278 _ => continue,
279 };
280 let candidates: Vec<&ListenerView> = listeners.iter().filter(|l| l.gateway == gateway_key && l.owner == owner).collect();
281 if candidates.is_empty() && !snapshot.gateways.iter().any(|g| is_named(g, &gateway_key) && our_classes.contains(&g.spec.gateway_class_name)) {
282 continue; }
284 let old = route
285 .previous
286 .iter()
287 .find(|p| p.controller_name == controller_name && same_parent(&p.parent_ref, parent_ref, &namespace))
288 .map(|p| p.conditions.as_slice())
289 .unwrap_or_default();
290 let (accepted, reason, message, attached_to) = attach(route.kind, route.hostnames, parent_ref, &namespace, &candidates, snapshot);
291 if !attached_to.is_empty() {
292 for reference in &route.references {
293 let service = (reference.namespace.clone().unwrap_or_else(|| namespace.clone()), reference.name.clone());
294 service_gateways.entry(service).or_default().insert(gateway_key.clone());
295 }
296 }
297 for listener in &attached_to {
298 *attached.entry((gateway_key.clone(), owner.clone(), listener.clone())).or_default() += 1;
299 if let Some(view) = listeners.iter_mut().find(|l| l.gateway == gateway_key && l.owner == owner && &l.name == listener) {
300 view.http_routes.push(route.compiled.clone());
301 }
302 }
303 let conditions = vec![
304 condition(old, "Accepted", accepted, reason, &message, route.generation, now),
305 match &resolved {
306 Ok(()) => condition(old, "ResolvedRefs", true, "ResolvedRefs", "All references resolved", route.generation, now),
307 Err((reason, message)) => condition(old, "ResolvedRefs", false, reason, message, route.generation, now),
308 },
309 ];
310 parents.push(RouteParentStatus { parent_ref: parent_ref.clone(), controller_name: controller_name.to_string(), conditions });
311 }
312 if !parents.is_empty() {
313 let statuses = match route.kind {
314 "GRPCRoute" => &mut desired.grpc_routes,
315 "TLSRoute" => &mut desired.tls_routes,
316 "TCPRoute" => &mut desired.tcp_routes,
317 "UDPRoute" => &mut desired.udp_routes,
318 _ => &mut desired.http_routes,
319 };
320 statuses.insert((namespace, route.name), parents);
321 }
322 }
323
324 for ((namespace, name), evaluated) in super::backend_tls::evaluate(snapshot) {
326 let Some(policy) = snapshot.backend_tls_policies.iter().find(|p| p.namespace().as_deref() == Some(namespace.as_str()) && p.name_any() == name) else { continue };
327 let generation = policy.metadata.generation;
328 let gateways: BTreeSet<(String, String)> = policy
329 .spec
330 .target_refs
331 .iter()
332 .filter_map(|t| service_gateways.get(&(namespace.clone(), t.name.clone())))
333 .flatten()
334 .cloned()
335 .collect();
336 let previous = policy.status.as_ref().map(|s| s.ancestors.as_slice()).unwrap_or_default();
337 let ancestors: Vec<PolicyAncestorStatus> = gateways
338 .into_iter()
339 .map(|(gateway_namespace, gateway_name)| {
340 let ancestor_ref = ParentReference {
341 group: Some(GATEWAY_GROUP.to_string()),
342 kind: Some("Gateway".to_string()),
343 namespace: Some(gateway_namespace),
344 name: gateway_name,
345 section_name: None,
346 port: None,
347 };
348 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();
349 let conditions = vec![
350 match &evaluated.accepted {
351 Ok(()) => condition(old, "Accepted", true, "Accepted", "", generation, now),
352 Err((reason, message)) => condition(old, "Accepted", false, reason, message, generation, now),
353 },
354 match &evaluated.resolved {
355 Ok(()) => condition(old, "ResolvedRefs", true, "ResolvedRefs", "", generation, now),
356 Err((reason, message)) => condition(old, "ResolvedRefs", false, reason, message, generation, now),
357 },
358 ];
359 PolicyAncestorStatus { ancestor_ref, controller_name: controller_name.to_string(), conditions }
360 })
361 .collect();
362 if !ancestors.is_empty() {
363 desired.backend_tls_policies.insert((namespace, name), ancestors);
364 }
365 }
366
367 for gateway in &snapshot.gateways {
369 if !our_classes.contains(&gateway.spec.gateway_class_name) {
370 continue;
371 }
372 let key = (gateway.namespace().unwrap_or_default(), gateway.name_any());
373 let generation = gateway.metadata.generation;
374 let previous = gateway.status.clone().unwrap_or_default();
375 let container_ports = super::services::container_ports(super::services::current(snapshot, gateway));
376 let backend_client_cert = backend_client_certificate(gateway, snapshot);
377 let mut listener_statuses = Vec::new();
378 let mut set_statuses: BTreeMap<(String, String), Vec<ListenerStatus>> = BTreeMap::new();
379 let mut any_valid = false;
380 let (mut any_accepted, mut any_rejected) = (false, false);
381 for view in listeners.iter().filter(|l| l.gateway == key) {
382 let count = attached.get(&(key.clone(), view.owner.clone(), view.name.clone())).copied().unwrap_or(0);
383 match &view.owner {
384 None => {
385 any_valid |= view.accepted && view.programmed;
386 any_accepted |= view.accepted;
387 any_rejected |= !view.accepted;
388 let old = previous.listeners.iter().find(|l| l.name == view.name).map(|l| l.conditions.as_slice()).unwrap_or_default();
389 listener_statuses.push(listener_status(view, count, old, generation, now));
390 }
391 Some(set_key) => {
392 let set = snapshot.listener_sets.iter().find(|s| is_named(*s, set_key));
393 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();
394 let set_generation = set.and_then(|s| s.metadata.generation);
395 set_statuses.entry(set_key.clone()).or_default().push(listener_status(view, count, &old, set_generation, now));
396 }
397 }
398 let container_port = container_ports.get(&(view.port, super::services::transport(&view.protocol).to_string()));
399 if let (true, true, Some(container_port)) = (view.accepted, view.programmed, container_port) {
400 config.listeners.push(ListenerConfig {
401 gateway: key.clone(),
402 name: view.name.clone(),
403 port: view.port,
404 protocol: view.protocol.clone(),
405 hostname: view.hostname.clone(),
406 container_port: *container_port,
407 tls: view.tls.clone(),
408 client_ca: view.client_ca.clone(),
409 backend_client_cert: backend_client_cert.as_ref().and_then(|c| c.as_ref().ok()).cloned(),
410 http_routes: view.http_routes.clone(),
411 });
412 }
413 }
414 let addresses = gateway_addresses(snapshot, &key.0, &key.1);
415 let addressed = !addresses.is_empty();
416 let unsupported_address = gateway.spec.addresses.iter().find(|a| !super::services::address_supported(a));
417 let requested = super::services::requested_addresses(gateway);
419 let missing = requested.iter().find(|r| !addresses.iter().any(|a| &a.value == *r));
420 let programmed = any_valid && addressed && missing.is_none() && unsupported_address.is_none();
421 let (programmed_reason, programmed_message) = if let Some(address) = missing {
422 ("AddressNotUsable", format!("The load balancer hasn't assigned {address}"))
423 } else if !addressed {
424 ("AddressNotAssigned", "Waiting for the Service's address".to_string())
425 } else if unsupported_address.is_some() {
426 ("Invalid", "An address type isn't supported".to_string())
427 } else if !any_valid {
428 ("Invalid", "No valid listener".to_string())
429 } else {
430 ("Programmed", "Serving".to_string())
431 };
432 let invalid_parameters = gateway.spec.infrastructure.as_ref().is_some_and(|i| i.parameters_ref.is_some());
435 let gateway_accepted = (gateway.spec.listeners.is_empty() || any_accepted) && unsupported_address.is_none() && !invalid_parameters;
436 let accepted_reason = if invalid_parameters {
437 "InvalidParameters"
438 } else if unsupported_address.is_some() {
439 "UnsupportedAddress"
440 } else if any_rejected {
441 "ListenersNotValid"
442 } else {
443 "Accepted"
444 };
445
446 let mut attached_sets = 0;
448 for (set, _, allowed) in listener_sets.iter().filter(|(_, gateway, _)| *gateway == key) {
449 let set_key = (set.namespace().unwrap_or_default(), set.name_any());
450 let statuses = set_statuses.remove(&set_key).unwrap_or_default();
451 let valid = statuses.iter().any(|l| l.conditions.iter().all(|c| c.type_ == "Conflicted" || c.type_ == "ResolvedRefs" || c.status == "True"));
452 let (ok, reason, message) = if !allowed {
453 (false, "NotAllowed", "The Gateway's allowedListeners doesn't admit this ListenerSet")
454 } else if !valid {
455 (false, "ListenersNotValid", "No valid listener")
456 } else {
457 (true, "Accepted", "")
458 };
459 attached_sets += ok as i32;
460 let old = set.status.as_ref().map(|s| s.conditions.as_slice()).unwrap_or_default();
461 let set_generation = set.metadata.generation;
462 let conditions = vec![
463 condition(old, "Accepted", ok, reason, message, set_generation, now),
464 condition(old, "Programmed", ok, if ok { "Programmed" } else { reason }, message, set_generation, now),
465 ];
466 desired.listener_sets.insert(set_key, ListenerSetStatus { conditions, listeners: statuses });
467 }
468 let mut conditions = vec![
469 condition(&previous.conditions, "Accepted", gateway_accepted, accepted_reason, "", generation, now),
470 condition(&previous.conditions, "Programmed", programmed, programmed_reason, &programmed_message, generation, now),
471 ];
472 if let Some(certificate) = &backend_client_cert {
473 conditions.push(match certificate {
474 Ok(_) => condition(&previous.conditions, "ResolvedRefs", true, "ResolvedRefs", "", generation, now),
475 Err((reason, message)) => condition(&previous.conditions, "ResolvedRefs", false, reason, message, generation, now),
476 });
477 }
478 if insecure_frontend(gateway) {
479 let message = "Client certificate validation allows clients without a valid certificate (AllowInsecureFallback)";
480 conditions.push(condition(&previous.conditions, "InsecureFrontendValidationMode", true, "ConfigurationChanged", message, generation, now));
481 }
482 desired.gateways.insert(
483 key,
484 GatewayStatus { addresses, conditions, listeners: listener_statuses, attached_listener_sets: Some(attached_sets) },
485 );
486 }
487 (config, desired)
488}
489
490type ListenerKey = ((String, String), Option<(String, String)>, String);
494
495struct ListenerView {
496 gateway: (String, String),
497 owner: Option<(String, String)>,
499 namespace: String,
501 name: String,
502 port: i32,
503 protocol: String,
504 hostname: Option<String>,
505 allowed: AllowedRoutes,
506 supported_kinds: Vec<RouteGroupKind>,
507 accepted: bool,
508 accepted_reason: &'static str,
509 programmed: bool,
510 resolved_refs: Result<(), (&'static str, String)>,
511 conflicted: Option<&'static str>,
512 message: String,
513 tls: Option<(Vec<u8>, Vec<u8>)>,
514 client_ca: Option<ClientCa>,
515 passthrough: bool,
517 http_routes: Vec<CompiledHttpRoute>,
518}
519
520fn terminates_tls(listener: &Listener) -> bool {
523 match listener.protocol.as_str() {
524 "HTTPS" => true,
525 "TLS" => listener.tls.as_ref().and_then(|t| t.mode.as_deref()).unwrap_or("Terminate") == "Terminate",
526 _ => false,
527 }
528}
529
530fn kinds_for(protocol: &str) -> &'static [&'static str] {
532 match protocol {
533 "HTTP" | "HTTPS" => &["HTTPRoute", "GRPCRoute"],
534 "TLS" => &["TLSRoute"],
535 "TCP" => &["TCPRoute"],
536 "UDP" => &["UDPRoute"],
537 _ => &[],
538 }
539}
540
541fn listener_views(gateway: &Gateway, listeners: &[Listener], namespace: &str, owner: Option<(String, String)>, snapshot: &Snapshot) -> Vec<ListenerView> {
545 let key = (gateway.namespace().unwrap_or_default(), gateway.name_any());
546 listeners
547 .iter()
548 .map(|l| {
549 let allowed = l.allowed_routes.clone().unwrap_or_default();
550 let carried = kinds_for(&l.protocol);
551 let mut supported_kinds = Vec::new();
552 let mut resolved_refs = Ok(());
553 if allowed.kinds.is_empty() {
554 supported_kinds = carried.iter().map(|k| RouteGroupKind { group: Some(GATEWAY_GROUP.to_string()), kind: k.to_string() }).collect();
555 } else {
556 for kind in &allowed.kinds {
557 let group_ok = kind.group.as_deref().unwrap_or(GATEWAY_GROUP) == GATEWAY_GROUP;
558 if group_ok && carried.contains(&kind.kind.as_str()) {
559 supported_kinds.push(RouteGroupKind { group: Some(GATEWAY_GROUP.to_string()), kind: kind.kind.clone() });
560 } else {
561 resolved_refs = Err(("InvalidRouteKinds", format!("{} isn't served on a {} listener", kind.kind, l.protocol)));
562 }
563 }
564 }
565 let (mut accepted, mut accepted_reason, message) = if carried.is_empty() {
566 (false, "UnsupportedProtocol", format!("protocol {} isn't supported", l.protocol))
567 } else {
568 (true, "Accepted", String::new())
569 };
570 let (mut tls, mut client_ca) = (None, None);
571 if accepted && terminates_tls(l) {
572 match certificates(l, namespace, snapshot) {
573 Ok(material) => tls = Some(material),
574 Err(e) => resolved_refs = Err(e),
575 }
576 if let Some(validation) = frontend_validation(gateway, l.port) {
577 match client_cas(validation, &key.0, snapshot) {
578 Ok(ca) => client_ca = Some(ca),
579 Err(e) => {
580 resolved_refs = Err(e);
581 accepted = false;
582 accepted_reason = "NoValidCACertificate";
583 }
584 }
585 }
586 }
587 let programmed = accepted && resolved_refs.as_ref().err().is_none_or(|(reason, _)| *reason == "InvalidRouteKinds");
589 ListenerView {
590 gateway: key.clone(),
591 owner: owner.clone(),
592 namespace: namespace.to_string(),
593 name: l.name.clone(),
594 port: l.port,
595 protocol: l.protocol.clone(),
596 hostname: l.hostname.clone(),
597 allowed,
598 supported_kinds,
599 accepted,
600 accepted_reason,
601 programmed,
602 resolved_refs,
603 conflicted: None,
604 message,
605 tls,
606 client_ca,
607 passthrough: l.protocol == "TLS" && !terminates_tls(l),
608 http_routes: Vec::new(),
609 }
610 })
611 .collect()
612}
613
614fn mark_conflicts(views: &mut [ListenerView]) {
620 for j in 0..views.len() {
621 for i in 0..views.len() {
622 if i == j || views[i].port != views[j].port {
623 continue;
624 }
625 if super::services::transport(&views[i].protocol) != super::services::transport(&views[j].protocol) {
627 continue;
628 }
629 if views[j].owner.is_some() && (i > j || views[i].conflicted.is_some()) {
631 continue;
632 }
633 if views[j].owner.is_none() && views[i].owner.is_some() {
634 continue;
635 }
636 let mixed_tls = views[i].protocol == "TLS" && views[j].protocol == "TLS" && views[i].passthrough != views[j].passthrough;
638 let reason = if views[i].protocol != views[j].protocol || mixed_tls {
639 "ProtocolConflict"
640 } else if views[i].hostname == views[j].hostname {
641 "HostnameConflict"
642 } else {
643 continue;
644 };
645 views[j].conflicted = Some(reason);
646 views[j].programmed = false;
647 if views[j].owner.is_some() || mixed_tls {
648 views[j].accepted = false;
649 views[j].accepted_reason = reason;
650 }
651 if mixed_tls {
654 views[j].supported_kinds.clear();
655 }
656 }
657 }
658}
659
660fn attached_listener_sets<'a>(gateway: &Gateway, snapshot: &'a Snapshot) -> Vec<(&'a ListenerSet, bool)> {
663 let gateway_namespace = gateway.namespace().unwrap_or_default();
664 let mut sets: Vec<&ListenerSet> = snapshot
665 .listener_sets
666 .iter()
667 .filter(|set| {
668 let parent = &set.spec.parent_ref;
669 let namespace = parent.namespace.clone().or_else(|| set.namespace()).unwrap_or_default();
670 parent.group.as_deref().unwrap_or(GATEWAY_GROUP) == GATEWAY_GROUP
671 && parent.kind.as_deref().unwrap_or("Gateway") == "Gateway"
672 && parent.name == gateway.name_any()
673 && namespace == gateway_namespace
674 })
675 .collect();
676 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()));
677 let admitted = gateway.spec.allowed_listeners.as_ref().and_then(|a| a.namespaces.as_ref());
678 sets.into_iter()
679 .map(|set| {
680 let namespace = set.namespace().unwrap_or_default();
681 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);
682 (set, allowed)
683 })
684 .collect()
685}
686
687fn namespace_admitted(from: &str, selector: Option<&LabelSelector>, namespace: &str, home: &str, snapshot: &Snapshot) -> bool {
690 match from {
691 "All" => true,
692 "Same" => namespace == home,
693 "Selector" => {
694 let mut labels = snapshot.namespaces.iter().find(|n| n.name_any() == namespace).map(|n| n.labels().clone()).unwrap_or_default();
695 labels.entry("kubernetes.io/metadata.name".to_string()).or_insert_with(|| namespace.to_string());
697 selector.is_some_and(|s| selector_matches(s, &labels))
698 }
699 _ => false,
700 }
701}
702
703fn listener_status(view: &ListenerView, attached_routes: i32, old: &[Condition], generation: Option<i64>, now: &Time) -> ListenerStatus {
705 let programmed_reason = match (view.programmed, view.conflicted) {
706 (true, _) => "Programmed",
707 (false, Some(reason)) if view.owner.is_some() => reason,
708 _ => "Invalid",
709 };
710 ListenerStatus {
711 name: view.name.clone(),
712 supported_kinds: view.supported_kinds.clone(),
713 attached_routes,
714 conditions: vec![
715 condition(old, "Accepted", view.accepted, view.accepted_reason, &view.message, generation, now),
716 condition(old, "Programmed", view.programmed, programmed_reason, &view.message, generation, now),
717 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),
718 condition(old, "Conflicted", view.conflicted.is_some(), view.conflicted.unwrap_or("NoConflicts"), "", generation, now),
719 ],
720 }
721}
722
723type TlsMaterial = (Vec<u8>, Vec<u8>);
727
728fn certificates(listener: &Listener, gateway_namespace: &str, snapshot: &Snapshot) -> Result<TlsMaterial, (&'static str, String)> {
729 let Some(tls) = &listener.tls else {
730 return Err(("InvalidCertificateRef", "a listener that terminates TLS needs tls.certificateRefs".to_string()));
731 };
732 if tls.certificate_refs.is_empty() {
733 return Err(("InvalidCertificateRef", "a listener that terminates TLS needs tls.certificateRefs".to_string()));
734 }
735 let mut material = None;
736 for cert in &tls.certificate_refs {
737 let group = cert.group.as_deref().unwrap_or("");
738 let kind = cert.kind.as_deref().unwrap_or("Secret");
739 if !group.is_empty() || kind != "Secret" {
740 return Err(("InvalidCertificateRef", format!("{kind} isn't a supported certificate reference")));
741 }
742 let namespace = cert.namespace.clone().unwrap_or_else(|| gateway_namespace.to_string());
743 if namespace != gateway_namespace && !granted(snapshot, GATEWAY_GROUP, "Gateway", gateway_namespace, "", "Secret", &namespace, &cert.name) {
744 return Err(("RefNotPermitted", format!("no ReferenceGrant in {namespace} lets Gateways in {gateway_namespace} use Secret {}", cert.name)));
745 }
746 let secret = snapshot.secrets.iter().find(|s| s.namespace().as_deref() == Some(namespace.as_str()) && s.name_any() == cert.name);
747 let valid = secret.is_some_and(|s| {
748 let data = s.data.as_ref();
749 let pem = |key: &str| data.and_then(|d| d.get(key)).is_some_and(|v| !v.0.is_empty());
750 pem("tls.crt") && pem("tls.key") && crate::core::x509::cert_info_from_pem(&data.unwrap()["tls.crt"].0).is_some()
751 });
752 if !valid {
753 return Err(("InvalidCertificateRef", format!("Secret {namespace}/{} isn't a valid TLS Secret", cert.name)));
754 }
755 if material.is_none() {
756 let data = secret.and_then(|s| s.data.as_ref()).expect("checked above");
757 material = Some((data["tls.crt"].0.clone(), data["tls.key"].0.clone()));
758 }
759 }
760 material.ok_or(("InvalidCertificateRef", "no certificate".to_string()))
761}
762
763fn frontend_validation(gateway: &Gateway, port: i32) -> Option<&FrontendTlsValidation> {
766 let frontend = gateway.spec.tls.as_ref()?.frontend.as_ref()?;
767 match frontend.per_port.iter().find(|p| p.port == port) {
768 Some(per_port) => per_port.tls.validation.as_ref(),
769 None => frontend.default.validation.as_ref(),
770 }
771}
772
773fn backend_client_certificate(gateway: &Gateway, snapshot: &Snapshot) -> Option<Result<TlsMaterial, (&'static str, String)>> {
776 let reference = gateway.spec.tls.as_ref()?.backend.as_ref()?.client_certificate_ref.as_ref()?;
777 let gateway_namespace = gateway.namespace().unwrap_or_default();
778 let group = reference.group.as_deref().unwrap_or("");
779 let kind = reference.kind.as_deref().unwrap_or("Secret");
780 if !group.is_empty() || kind != "Secret" {
781 return Some(Err(("InvalidClientCertificateRef", format!("{group}/{kind} isn't a supported client certificate reference"))));
782 }
783 let namespace = reference.namespace.clone().unwrap_or_else(|| gateway_namespace.clone());
784 if namespace != gateway_namespace && !granted(snapshot, GATEWAY_GROUP, "Gateway", &gateway_namespace, "", "Secret", &namespace, &reference.name) {
785 return Some(Err(("RefNotPermitted", format!("no ReferenceGrant in {namespace} lets Gateways in {gateway_namespace} use Secret {}", reference.name))));
786 }
787 let data = snapshot
788 .secrets
789 .iter()
790 .find(|s| s.namespace().as_deref() == Some(namespace.as_str()) && s.name_any() == reference.name)
791 .and_then(|s| s.data.as_ref());
792 let pair = data.and_then(|d| Some((d.get("tls.crt")?.0.clone(), d.get("tls.key")?.0.clone())));
793 let valid = pair.as_ref().filter(|(cert, key)| crate::tls::certified_key_from_pem(cert, key).is_ok());
794 Some(valid.cloned().ok_or(("InvalidClientCertificateRef", format!("Secret {namespace}/{} isn't a valid TLS Secret", reference.name))))
795}
796
797fn insecure_frontend(gateway: &Gateway) -> bool {
799 let Some(frontend) = gateway.spec.tls.as_ref().and_then(|t| t.frontend.as_ref()) else { return false };
800 std::iter::once(&frontend.default)
801 .chain(frontend.per_port.iter().map(|p| &p.tls))
802 .filter_map(|t| t.validation.as_ref())
803 .any(|v| v.mode.as_deref() == Some("AllowInsecureFallback"))
804}
805
806fn client_cas(validation: &FrontendTlsValidation, gateway_namespace: &str, snapshot: &Snapshot) -> Result<ClientCa, (&'static str, String)> {
809 let mut pem = Vec::new();
810 for reference in &validation.ca_certificate_refs {
811 if !reference.group.is_empty() || reference.kind != "ConfigMap" {
812 return Err(("InvalidCACertificateKind", format!("{} isn't a supported CA certificate reference", reference.kind)));
813 }
814 let namespace = reference.namespace.clone().unwrap_or_else(|| gateway_namespace.to_string());
815 if namespace != gateway_namespace && !granted(snapshot, GATEWAY_GROUP, "Gateway", gateway_namespace, "", "ConfigMap", &namespace, &reference.name) {
816 return Err(("RefNotPermitted", format!("no ReferenceGrant in {namespace} lets Gateways in {gateway_namespace} use ConfigMap {}", reference.name)));
817 }
818 let ca = snapshot
819 .config_maps
820 .iter()
821 .find(|c| c.namespace().as_deref() == Some(namespace.as_str()) && c.name_any() == reference.name)
822 .and_then(|c| c.data.as_ref())
823 .and_then(|d| d.get("ca.crt"))
824 .filter(|ca| crate::core::x509::cert_info_from_pem(ca.as_bytes()).is_some());
825 let Some(ca) = ca else {
826 return Err(("InvalidCACertificateRef", format!("ConfigMap {namespace}/{} has no valid ca.crt", reference.name)));
827 };
828 pem.extend_from_slice(ca.as_bytes());
829 pem.push(b'\n');
830 }
831 if pem.is_empty() {
832 return Err(("InvalidCACertificateRef", "no caCertificateRefs".to_string()));
833 }
834 Ok(ClientCa { pem, insecure_fallback: validation.mode.as_deref() == Some("AllowInsecureFallback") })
835}
836
837fn same_parent(a: &ParentReference, b: &ParentReference, route_namespace: &str) -> bool {
841 a.group() == b.group()
842 && a.kind() == b.kind()
843 && a.name == b.name
844 && a.namespace.as_deref().unwrap_or(route_namespace) == b.namespace.as_deref().unwrap_or(route_namespace)
845 && a.section_name == b.section_name
846 && a.port == b.port
847}
848
849fn is_named<K: kube::Resource>(object: &K, key: &(String, String)) -> bool {
850 object.meta().namespace.as_deref() == Some(key.0.as_str()) && object.meta().name.as_deref() == Some(key.1.as_str())
851}
852
853fn attach(kind: &str, hostnames: &[String], parent_ref: &ParentReference, route_namespace: &str, candidates: &[&ListenerView], snapshot: &Snapshot) -> (bool, &'static str, String, Vec<String>) {
856 let mut named: Vec<&&ListenerView> = candidates
857 .iter()
858 .filter(|l| parent_ref.section_name.as_ref().is_none_or(|s| *s == l.name))
859 .filter(|l| parent_ref.port.is_none_or(|p| p == l.port))
860 .collect();
861 if named.is_empty() {
862 return (false, "NoMatchingParent", "no listener matches the parentRef's sectionName/port".to_string(), Vec::new());
863 }
864 named.retain(|l| l.accepted && l.conflicted.is_none());
865 let allowed: Vec<&&ListenerView> = named.iter().copied().filter(|l| allows(l, kind, route_namespace, snapshot)).collect();
866 if allowed.is_empty() {
867 return (false, "NotAllowedByListeners", "no listener allows this route".to_string(), Vec::new());
868 }
869 let matching: Vec<String> = allowed
870 .iter()
871 .filter(|l| hostnames_intersect(l.hostname.as_deref(), hostnames))
872 .map(|l| l.name.clone())
873 .collect();
874 if matching.is_empty() {
875 return (false, "NoMatchingListenerHostname", "no listener hostname matches the route's hostnames".to_string(), Vec::new());
876 }
877 (true, "Accepted", String::new(), matching)
878}
879
880fn allows(listener: &ListenerView, kind: &str, namespace: &str, snapshot: &Snapshot) -> bool {
882 if !listener.supported_kinds.iter().any(|k| k.kind == kind) {
883 return false;
884 }
885 let namespaces = listener.allowed.namespaces.as_ref();
886 let from = namespaces.and_then(|n| n.from.as_deref()).unwrap_or("Same");
887 namespace_admitted(from, namespaces.and_then(|n| n.selector.as_ref()), namespace, &listener.namespace, snapshot)
888}
889
890#[doc(hidden)]
891pub fn selector_matches(selector: &LabelSelector, labels: &BTreeMap<String, String>) -> bool {
892 let labels_ok = selector.match_labels.as_ref().is_none_or(|m| m.iter().all(|(k, v)| labels.get(k) == Some(v)));
893 let expressions_ok = selector.match_expressions.as_ref().is_none_or(|exprs| {
894 exprs.iter().all(|e| {
895 let value = labels.get(&e.key);
896 let values = e.values.clone().unwrap_or_default();
897 match e.operator.as_str() {
898 "In" => value.is_some_and(|v| values.contains(v)),
899 "NotIn" => value.is_none_or(|v| !values.contains(v)),
900 "Exists" => value.is_some(),
901 "DoesNotExist" => value.is_none(),
902 _ => false,
903 }
904 })
905 });
906 labels_ok && expressions_ok
907}
908
909#[doc(hidden)]
912pub fn hostnames_intersect(listener: Option<&str>, route: &[String]) -> bool {
913 let Some(listener) = listener.filter(|h| !h.is_empty()) else { return true };
914 route.is_empty() || route.iter().any(|r| hostname_overlap(listener, r))
915}
916
917fn hostname_overlap(a: &str, b: &str) -> bool {
918 let a = a.to_ascii_lowercase();
919 let b = b.to_ascii_lowercase();
920 match (a.strip_prefix("*."), b.strip_prefix("*.")) {
921 (None, None) => a == b,
922 (Some(suffix), None) => b.ends_with(&format!(".{suffix}")),
923 (None, Some(suffix)) => a.ends_with(&format!(".{suffix}")),
924 (Some(x), Some(y)) => x == y || x.ends_with(&format!(".{y}")) || y.ends_with(&format!(".{x}")),
925 }
926}
927
928struct RouteIn<'a> {
930 kind: &'static str,
931 namespace: String,
932 name: String,
933 generation: Option<i64>,
934 parent_refs: &'a [ParentReference],
935 hostnames: &'a [String],
936 previous: Vec<RouteParentStatus>,
937 references: Vec<&'a BackendObjectReference>,
939 compiled: CompiledHttpRoute,
940}
941
942impl<'a> RouteIn<'a> {
943 fn new<S>(
944 kind: &'static str,
945 route: &'a kube::core::Object<S, RouteStatus>,
946 parent_refs: &'a [ParentReference],
947 hostnames: &'a [String],
948 references: Vec<&'a BackendObjectReference>,
949 compiled: CompiledHttpRoute,
950 ) -> RouteIn<'a>
951 where
952 S: Clone,
953 {
954 RouteIn {
955 kind,
956 namespace: route.namespace().unwrap_or_default(),
957 name: route.name_any(),
958 generation: route.metadata.generation,
959 parent_refs,
960 hostnames,
961 previous: route.status.as_ref().map(|s| s.parents.clone()).unwrap_or_default(),
962 references,
963 compiled,
964 }
965 }
966}
967
968fn rule_references<'a>(backend_refs: &'a [HttpBackendRef], filters: &'a [HttpRouteFilter]) -> impl Iterator<Item = &'a BackendObjectReference> {
971 let mirrors = filters.iter().chain(backend_refs.iter().flat_map(|b| &b.filters)).filter_map(|f| f.request_mirror.as_ref());
972 backend_refs.iter().map(|b| &b.backend_ref.object).chain(mirrors.map(|m| &m.backend_ref))
973}
974
975fn http_references(rules: &[HttpRouteRule]) -> Vec<&BackendObjectReference> {
976 rules.iter().flat_map(|r| rule_references(&r.backend_refs, &r.filters)).collect()
977}
978
979fn l4_references(rules: &[L4RouteRule]) -> Vec<&BackendObjectReference> {
980 rules.iter().flat_map(|r| r.backend_refs.iter().map(|b| &b.object)).collect()
981}
982
983fn grpc_references(rules: &[GrpcRouteRule]) -> Vec<&BackendObjectReference> {
984 rules.iter().flat_map(|r| rule_references(&r.backend_refs, &r.filters)).collect()
985}
986
987fn resolve_references(kind: &str, namespace: &str, references: &[&BackendObjectReference], snapshot: &Snapshot) -> Result<(), (&'static str, String)> {
990 for reference in references {
991 if !reference.group().is_empty() || reference.kind() != "Service" {
992 return Err(("InvalidKind", format!("{} isn't a supported backend kind", reference.kind())));
993 }
994 let target = reference.namespace.clone().unwrap_or_else(|| namespace.to_string());
995 if target != namespace && !granted(snapshot, GATEWAY_GROUP, kind, namespace, "", "Service", &target, &reference.name) {
996 return Err(("RefNotPermitted", format!("no ReferenceGrant in {target} lets {kind}s in {namespace} use Service {}", reference.name)));
997 }
998 if !snapshot.services.iter().any(|s| s.namespace().as_deref() == Some(target.as_str()) && s.name_any() == reference.name) {
999 return Err(("BackendNotFound", format!("Service {target}/{} doesn't exist", reference.name)));
1000 }
1001 }
1002 Ok(())
1003}
1004
1005#[allow(clippy::too_many_arguments)]
1008#[doc(hidden)]
1009pub 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 {
1010 snapshot.reference_grants.iter().filter(|g| g.namespace().as_deref() == Some(to_namespace)).any(|g| {
1011 g.spec.from.iter().any(|f| f.group == from_group && f.kind == from_kind && f.namespace == from_namespace)
1012 && 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))
1013 })
1014}
1015
1016fn condition(previous: &[Condition], kind: &str, status: bool, reason: &str, message: &str, generation: Option<i64>, now: &Time) -> Condition {
1021 let status = if status { "True" } else { "False" }.to_string();
1022 let since = previous
1023 .iter()
1024 .find(|c| c.type_ == kind && c.status == status)
1025 .map(|c| c.last_transition_time.clone())
1026 .unwrap_or_else(|| now.clone());
1027 Condition {
1028 type_: kind.to_string(),
1029 status,
1030 reason: reason.to_string(),
1031 message: message.to_string(),
1032 observed_generation: generation,
1033 last_transition_time: since,
1034 }
1035}
1036
1037#[cfg(test)]
1038mod tests;