rws_gateway_api/gateway_api/
backend_tls.rs1use std::collections::HashMap;
9use std::sync::{Arc, Mutex, OnceLock};
10
11use kube::ResourceExt;
12use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
13use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
14
15use super::resources::BackendTlsPolicy;
16use super::translate::Snapshot;
17use super::types::*;
18use crate::core::x509::SubjectAltName;
19
20#[derive(Clone)]
22pub struct BackendTls {
23 pub label: String,
25 pub config: Arc<rustls::ClientConfig>,
26 pub server_name: String,
28 verifier: Arc<dyn ServerCertVerifier>,
31}
32
33impl PartialEq for BackendTls {
34 fn eq(&self, other: &Self) -> bool {
35 self.label == other.label
36 }
37}
38
39impl std::fmt::Debug for BackendTls {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 f.debug_struct("BackendTls").field("label", &self.label).field("server_name", &self.server_name).finish()
42 }
43}
44
45#[derive(Clone, Debug, PartialEq)]
47pub struct Evaluated {
48 pub accepted: Result<(), (&'static str, String)>,
50 pub resolved: Result<(), (&'static str, String)>,
52 pub tls: Option<BackendTls>,
54}
55
56pub fn evaluate(snapshot: &Snapshot) -> Vec<((String, String), Evaluated)> {
58 let mut policies: Vec<&BackendTlsPolicy> = snapshot.backend_tls_policies.iter().collect();
59 policies.sort_by_key(|p| (p.metadata.creation_timestamp.as_ref().map(|t| t.0.to_string()).unwrap_or_default(), p.namespace(), p.name_any()));
60 let mut claimed: Vec<(String, String, Option<String>)> = Vec::new();
61 policies
62 .into_iter()
63 .map(|policy| {
64 let namespace = policy.namespace().unwrap_or_default();
65 let mut evaluated = evaluate_one(policy, &namespace, snapshot);
66 let targets: Vec<(String, String, Option<String>)> = policy
67 .spec
68 .target_refs
69 .iter()
70 .filter(|t| t.group.is_empty() && t.kind == "Service")
71 .map(|t| (namespace.clone(), t.name.clone(), t.section_name.clone()))
72 .collect();
73 if evaluated.accepted.is_ok() {
74 if targets.iter().any(|t| claimed.contains(t)) {
75 evaluated.accepted = Err(("Conflicted", "An older BackendTLSPolicy targets the same Service and section".to_string()));
76 evaluated.tls = None;
77 } else {
78 claimed.extend(targets);
79 }
80 }
81 ((namespace, policy.name_any()), evaluated)
82 })
83 .collect()
84}
85
86fn evaluate_one(policy: &BackendTlsPolicy, namespace: &str, snapshot: &Snapshot) -> Evaluated {
87 let validation = &policy.spec.validation;
88 let fail = |accepted: &'static str, resolved: Option<(&'static str, String)>, message: String| Evaluated {
89 accepted: Err((accepted, message.clone())),
90 resolved: resolved.map_or(Ok(()), Err),
91 tls: None,
92 };
93 let mut pem = Vec::new();
94 for reference in &validation.ca_certificate_refs {
95 if !reference.group.is_empty() || reference.kind != "ConfigMap" {
96 let message = format!("{} isn't a supported CA certificate reference", reference.kind);
97 return fail("NoValidCACertificate", Some(("InvalidKind", message.clone())), message);
98 }
99 let ca = snapshot
100 .config_maps
101 .iter()
102 .find(|c| c.namespace().as_deref() == Some(namespace) && c.name_any() == reference.name)
103 .and_then(|c| c.data.as_ref())
104 .and_then(|d| d.get("ca.crt"))
105 .filter(|ca| crate::core::x509::cert_info_from_pem(ca.as_bytes()).is_some());
106 let Some(ca) = ca else {
107 let message = format!("ConfigMap {namespace}/{} has no valid ca.crt", reference.name);
108 return fail("NoValidCACertificate", Some(("InvalidCACertificateRef", message.clone())), message);
109 };
110 pem.extend_from_slice(ca.as_bytes());
111 pem.push(b'\n');
112 }
113 let system = validation.well_known_ca_certificates.as_deref() == Some("System");
114 if pem.is_empty() && !system {
115 return fail("NoValidCACertificate", None, "Neither caCertificateRefs nor wellKnownCACertificates: System".to_string());
116 }
117 match build(if system && pem.is_empty() { None } else { Some(&pem) }, validation) {
118 Ok(tls) => Evaluated { accepted: Ok(()), resolved: Ok(()), tls: Some(tls) },
119 Err(e) => fail("NoValidCACertificate", Some(("InvalidCACertificateRef", e.clone())), e),
120 }
121}
122
123pub fn for_service(evaluated: &[((String, String), Evaluated)], snapshot: &Snapshot, namespace: &str, service: &str, port_name: Option<&str>) -> Option<Result<BackendTls, String>> {
127 let targets = |section: Option<&str>| {
128 evaluated.iter().find(|(key, e)| {
129 let policy = snapshot.backend_tls_policies.iter().find(|p| p.namespace().as_deref() == Some(key.0.as_str()) && p.name_any() == key.1);
130 key.0 == namespace
131 && policy.is_some_and(|p| p.spec.target_refs.iter().any(|t| t.group.is_empty() && t.kind == "Service" && t.name == service && t.section_name.as_deref() == section))
132 && !matches!(&e.accepted, Err((reason, _)) if *reason == "Conflicted")
133 })
134 };
135 let (_, found) = port_name.and_then(|p| targets(Some(p))).or_else(|| targets(None))?;
136 Some(found.tls.clone().ok_or_else(|| found.accepted.as_ref().err().map(|(_, m)| m.clone()).unwrap_or_default()))
137}
138
139fn cache() -> &'static Mutex<HashMap<String, BackendTls>> {
141 static CACHE: OnceLock<Mutex<HashMap<String, BackendTls>>> = OnceLock::new();
142 CACHE.get_or_init(Default::default)
143}
144
145pub fn build(ca_pem: Option<&[u8]>, validation: &BackendTlsValidation) -> Result<BackendTls, String> {
148 use std::hash::{Hash, Hasher};
149 let mut hasher = std::collections::hash_map::DefaultHasher::new();
150 ca_pem.hash(&mut hasher);
151 validation.hostname.hash(&mut hasher);
152 for san in &validation.subject_alt_names {
153 (&san.san_type, &san.hostname, &san.uri).hash(&mut hasher);
154 }
155 let label = format!("backend-tls:{:016x}", hasher.finish());
156 if let Some(found) = cache().lock().unwrap_or_else(|e| e.into_inner()).get(&label) {
157 return Ok(found.clone());
158 }
159 let mut roots = rustls::RootCertStore::empty();
160 match ca_pem {
161 Some(pem) => {
162 use rustls::pki_types::pem::PemObject;
163 for cert in CertificateDer::pem_slice_iter(pem) {
164 roots.add(cert.map_err(|e| e.to_string())?).map_err(|e| e.to_string())?;
165 }
166 }
167 None => roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()),
168 }
169 let _ = crate::crypto_provider().install_default();
170 let webpki = rustls::client::WebPkiServerVerifier::builder(Arc::new(roots)).build().map_err(|e| e.to_string())?;
171 let verifier: Arc<dyn ServerCertVerifier> = if validation.subject_alt_names.is_empty() {
172 webpki
173 } else {
174 let sans = validation
175 .subject_alt_names
176 .iter()
177 .filter_map(|s| match s.san_type.as_str() {
178 "Hostname" => Some(SubjectAltName::Dns(s.hostname.to_ascii_lowercase())),
179 "URI" => Some(SubjectAltName::Uri(s.uri.clone())),
180 _ => None,
181 })
182 .collect();
183 Arc::new(SanVerifier { webpki, sans })
184 };
185 let config = crate::tls_client_builder().dangerous().with_custom_certificate_verifier(Arc::clone(&verifier)).with_no_client_auth();
186 let tls = BackendTls { label: label.clone(), config: Arc::new(config), server_name: validation.hostname.clone(), verifier };
187 cache().lock().unwrap_or_else(|e| e.into_inner()).insert(label, tls.clone());
188 Ok(tls)
189}
190
191impl BackendTls {
192 pub fn with_client_cert(&self, chain_pem: &[u8], key_pem: &[u8]) -> Result<BackendTls, String> {
195 use rustls::pki_types::pem::PemObject;
196 use std::hash::{Hash, Hasher};
197 let mut hasher = std::collections::hash_map::DefaultHasher::new();
198 (chain_pem, key_pem).hash(&mut hasher);
199 let label = format!("{}+client:{:016x}", self.label, hasher.finish());
200 if let Some(found) = cache().lock().unwrap_or_else(|e| e.into_inner()).get(&label) {
201 return Ok(found.clone());
202 }
203 let chain = CertificateDer::pem_slice_iter(chain_pem).collect::<Result<Vec<_>, _>>().map_err(|e| e.to_string())?;
204 let key = rustls::pki_types::PrivateKeyDer::from_pem_slice(key_pem).map_err(|e| e.to_string())?;
205 let config = crate::tls_client_builder()
206 .dangerous()
207 .with_custom_certificate_verifier(Arc::clone(&self.verifier))
208 .with_client_auth_cert(chain, key)
209 .map_err(|e| e.to_string())?;
210 let tls = BackendTls { label: label.clone(), config: Arc::new(config), server_name: self.server_name.clone(), verifier: Arc::clone(&self.verifier) };
211 cache().lock().unwrap_or_else(|e| e.into_inner()).insert(label, tls.clone());
212 Ok(tls)
213 }
214}
215
216#[derive(Debug)]
219struct SanVerifier {
220 webpki: Arc<rustls::client::WebPkiServerVerifier>,
221 sans: Vec<SubjectAltName>,
222}
223
224fn dns_matches(certificate: &str, wanted: &str) -> bool {
226 let certificate = certificate.to_ascii_lowercase();
227 match certificate.strip_prefix("*.") {
228 Some(suffix) => wanted.split_once('.').is_some_and(|(label, rest)| !label.is_empty() && rest == suffix),
229 None => certificate == wanted,
230 }
231}
232
233impl ServerCertVerifier for SanVerifier {
234 fn verify_server_cert(
235 &self,
236 end_entity: &CertificateDer<'_>,
237 intermediates: &[CertificateDer<'_>],
238 server_name: &ServerName<'_>,
239 ocsp_response: &[u8],
240 now: UnixTime,
241 ) -> Result<ServerCertVerified, rustls::Error> {
242 match self.webpki.verify_server_cert(end_entity, intermediates, server_name, ocsp_response, now) {
244 Ok(_) | Err(rustls::Error::InvalidCertificate(rustls::CertificateError::NotValidForName | rustls::CertificateError::NotValidForNameContext { .. })) => {}
245 Err(e) => return Err(e),
246 }
247 let names = crate::core::x509::subject_alt_names(end_entity.as_ref()).unwrap_or_default();
248 let matched = self.sans.iter().any(|wanted| {
249 names.iter().any(|name| match (wanted, name) {
250 (SubjectAltName::Dns(w), SubjectAltName::Dns(n)) => dns_matches(n, w),
251 (SubjectAltName::Uri(w), SubjectAltName::Uri(n)) => w == n,
252 _ => false,
253 })
254 });
255 if matched {
256 Ok(ServerCertVerified::assertion())
257 } else {
258 Err(rustls::Error::InvalidCertificate(rustls::CertificateError::NotValidForName))
259 }
260 }
261
262 fn verify_tls12_signature(&self, message: &[u8], cert: &CertificateDer<'_>, dss: &rustls::DigitallySignedStruct) -> Result<HandshakeSignatureValid, rustls::Error> {
263 self.webpki.verify_tls12_signature(message, cert, dss)
264 }
265
266 fn verify_tls13_signature(&self, message: &[u8], cert: &CertificateDer<'_>, dss: &rustls::DigitallySignedStruct) -> Result<HandshakeSignatureValid, rustls::Error> {
267 self.webpki.verify_tls13_signature(message, cert, dss)
268 }
269
270 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
271 self.webpki.supported_verify_schemes()
272 }
273}
274
275#[cfg(test)]
276mod tests;