1use std::sync::Arc;
2
3use http::{HeaderValue, header::HeaderName};
4#[cfg(feature = "openssl-tls")] use hyper::rt::{Read, Write};
5use hyper_util::client::legacy::connect::HttpConnector;
6use jiff::Timestamp;
7use secrecy::ExposeSecret;
8use tower::{filter::AsyncFilterLayer, util::Either};
9
10#[cfg(any(feature = "rustls-tls", feature = "openssl-tls"))] use super::tls;
11use super::{
12 auth::Auth,
13 middleware::{AddAuthorizationLayer, AuthLayer, BaseUriLayer, ExtraHeadersLayer},
14};
15use crate::{Config, Error, Result};
16
17pub trait ConfigExt: private::Sealed {
23 fn base_uri_layer(&self) -> BaseUriLayer;
25
26 fn auth_layer(&self) -> Result<Option<AuthLayer>>;
28
29 fn extra_headers_layer(&self) -> Result<ExtraHeadersLayer>;
31
32 #[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
47 #[cfg(feature = "rustls-tls")]
48 fn rustls_https_connector(&self) -> Result<hyper_rustls::HttpsConnector<HttpConnector>>;
49
50 #[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
67 #[cfg(feature = "rustls-tls")]
68 fn rustls_https_connector_with_connector<H>(
69 &self,
70 connector: H,
71 ) -> Result<hyper_rustls::HttpsConnector<H>>;
72
73 #[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
91 #[cfg(feature = "rustls-tls")]
92 fn rustls_client_config(&self) -> Result<rustls::ClientConfig>;
93
94 #[cfg_attr(docsrs, doc(cfg(feature = "openssl-tls")))]
106 #[cfg(feature = "openssl-tls")]
107 fn openssl_https_connector(&self)
108 -> Result<hyper_openssl::client::legacy::HttpsConnector<HttpConnector>>;
109
110 #[cfg_attr(docsrs, doc(cfg(feature = "openssl-tls")))]
125 #[cfg(feature = "openssl-tls")]
126 fn openssl_https_connector_with_connector<H>(
127 &self,
128 connector: H,
129 ) -> Result<hyper_openssl::client::legacy::HttpsConnector<H>>
130 where
131 H: tower::Service<http::Uri> + Send,
132 H::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
133 H::Future: Send + 'static,
134 H::Response: Read + Write + hyper_util::client::legacy::connect::Connection + Unpin;
135
136 #[cfg_attr(docsrs, doc(cfg(feature = "openssl-tls")))]
154 #[cfg(feature = "openssl-tls")]
155 fn openssl_ssl_connector_builder(&self) -> Result<openssl::ssl::SslConnectorBuilder>;
156}
157
158#[cfg(all(test, feature = "openssl-tls"))]
159mod openssl_tls_server_name_tests {
160 use std::{
161 net::TcpListener,
162 sync::{Arc, Mutex},
163 };
164
165 use openssl::{
166 asn1::Asn1Time,
167 hash::MessageDigest,
168 pkey::{PKey, Private},
169 rsa::Rsa,
170 ssl::{NameType, SslAcceptor, SslMethod},
171 x509::{
172 extension::{BasicConstraints, SubjectAlternativeName},
173 X509NameBuilder, X509,
174 },
175 };
176 use tower::ServiceExt as _;
177
178 use super::*;
179
180 enum San<'a> {
181 Dns(&'a str),
182 Ip(&'a str),
183 }
184
185 fn self_signed_cert(san: San) -> (X509, PKey<Private>) {
188 let cn = match san {
189 San::Dns(s) | San::Ip(s) => s,
190 };
191 let pkey = PKey::from_rsa(Rsa::generate(2048).unwrap()).unwrap();
192
193 let mut name = X509NameBuilder::new().unwrap();
194 name.append_entry_by_text("CN", cn).unwrap();
195 let name = name.build();
196
197 let mut builder = X509::builder().unwrap();
198 builder.set_version(2).unwrap();
199 builder.set_subject_name(&name).unwrap();
200 builder.set_issuer_name(&name).unwrap();
201 builder.set_pubkey(&pkey).unwrap();
202 builder.set_not_before(&Asn1Time::days_from_now(0).unwrap()).unwrap();
203 builder.set_not_after(&Asn1Time::days_from_now(1).unwrap()).unwrap();
204 builder
205 .append_extension(BasicConstraints::new().critical().ca().build().unwrap())
206 .unwrap();
207 let mut san_ext = SubjectAlternativeName::new();
208 match san {
209 San::Dns(s) => san_ext.dns(s),
210 San::Ip(s) => san_ext.ip(s),
211 };
212 let san_ext = san_ext.build(&builder.x509v3_context(None, None)).unwrap();
213 builder.append_extension(san_ext).unwrap();
214 builder.sign(&pkey, MessageDigest::sha256()).unwrap();
215
216 (builder.build(), pkey)
217 }
218
219 fn spawn_tls_server(cert: X509, key: PKey<Private>) -> (u16, Arc<Mutex<Option<String>>>) {
221 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
222 let port = listener.local_addr().unwrap().port();
223 let captured: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
224
225 let captured_in_cb = captured.clone();
226 let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
227 acceptor.set_private_key(&key).unwrap();
228 acceptor.set_certificate(&cert).unwrap();
229 acceptor.set_servername_callback(move |ssl, _alert| {
230 *captured_in_cb.lock().unwrap() = ssl.servername(NameType::HOST_NAME).map(str::to_owned);
231 Ok(())
232 });
233 let acceptor = acceptor.build();
234
235 std::thread::spawn(move || {
236 if let Ok((stream, _)) = listener.accept() {
237 let _ = acceptor.accept(stream);
239 }
240 });
241
242 (port, captured)
243 }
244
245 fn config_for(port: u16, ca: &X509, tls_server_name: Option<&str>) -> Config {
246 let mut config = Config::new(format!("https://127.0.0.1:{port}").parse().unwrap());
247 config.root_cert = Some(vec![ca.to_der().unwrap()]);
248 config.tls_server_name = tls_server_name.map(str::to_owned);
249 config
250 }
251
252 fn connector_for(config: &Config) -> hyper_openssl::client::legacy::HttpsConnector<HttpConnector> {
253 let mut http = HttpConnector::new();
254 http.enforce_http(false);
255 config.openssl_https_connector_with_connector(http).unwrap()
256 }
257
258 #[tokio::test]
261 async fn tls_server_name_drives_sni_and_verification() {
262 let server_name = "kubernetes.example.com";
263 let (cert, key) = self_signed_cert(San::Dns(server_name));
264 let (port, captured_sni) = spawn_tls_server(cert.clone(), key);
265
266 let config = config_for(port, &cert, Some(server_name));
267 let uri: http::Uri = config.cluster_url.clone();
268
269 connector_for(&config)
270 .oneshot(uri)
271 .await
272 .expect("handshake should succeed when verification targets tls_server_name");
273
274 assert_eq!(
275 captured_sni.lock().unwrap().as_deref(),
276 Some(server_name),
277 "ClientHello SNI must equal tls_server_name, not the connection host"
278 );
279 }
280
281 #[tokio::test]
285 async fn without_tls_server_name_verification_uses_connection_host() {
286 let server_name = "kubernetes.example.com";
287 let (cert, key) = self_signed_cert(San::Dns(server_name));
288 let (port, _captured_sni) = spawn_tls_server(cert.clone(), key);
289
290 let config = config_for(port, &cert, None);
291 let uri: http::Uri = config.cluster_url.clone();
292
293 let result = connector_for(&config).oneshot(uri).await;
294 assert!(
295 result.is_err(),
296 "handshake must fail when the cert does not match the connection host"
297 );
298 }
299
300 #[tokio::test]
302 async fn tls_server_name_as_ip_verifies_without_sni() {
303 let (cert, key) = self_signed_cert(San::Ip("127.0.0.1"));
304 let (port, captured_sni) = spawn_tls_server(cert.clone(), key);
305
306 let config = config_for(port, &cert, Some("127.0.0.1"));
307 let uri: http::Uri = config.cluster_url.clone();
308
309 connector_for(&config)
310 .oneshot(uri)
311 .await
312 .expect("handshake should succeed when the IP tls_server_name matches the cert");
313
314 assert_eq!(
315 *captured_sni.lock().unwrap(),
316 None,
317 "SNI must not be sent for an IP tls_server_name"
318 );
319 }
320
321 #[tokio::test]
323 async fn accept_invalid_certs_skips_verification() {
324 let (cert, key) = self_signed_cert(San::Dns("kubernetes.example.com"));
325 let (port, _captured_sni) = spawn_tls_server(cert.clone(), key);
326
327 let mut config = config_for(port, &cert, None);
328 config.accept_invalid_certs = true;
329 let uri: http::Uri = config.cluster_url.clone();
330
331 connector_for(&config)
332 .oneshot(uri)
333 .await
334 .expect("handshake should succeed when accept_invalid_certs disables verification");
335 }
336}
337
338mod private {
339 pub trait Sealed {}
340 impl Sealed for super::Config {}
341}
342
343impl ConfigExt for Config {
344 fn base_uri_layer(&self) -> BaseUriLayer {
345 BaseUriLayer::new(self.cluster_url.clone())
346 }
347
348 fn auth_layer(&self) -> Result<Option<AuthLayer>> {
349 Ok(match Auth::try_from(&self.auth_info).map_err(Error::Auth)? {
350 Auth::None => None,
351 Auth::Basic(user, pass) => Some(AuthLayer(Either::Left(
352 AddAuthorizationLayer::basic(&user, pass.expose_secret()).as_sensitive(true),
353 ))),
354 Auth::Bearer(token) => Some(AuthLayer(Either::Left(
355 AddAuthorizationLayer::bearer(token.expose_secret()).as_sensitive(true),
356 ))),
357 Auth::RefreshableToken(refreshable) => {
358 Some(AuthLayer(Either::Right(AsyncFilterLayer::new(refreshable))))
359 }
360 Auth::Certificate(_client_certificate_data, _client_key_data, _) => None,
361 })
362 }
363
364 fn extra_headers_layer(&self) -> Result<ExtraHeadersLayer> {
365 let mut headers = self.headers.clone();
366 if let Some(impersonate_user) = &self.auth_info.impersonate {
367 headers.push((
368 HeaderName::from_static("impersonate-user"),
369 HeaderValue::from_str(impersonate_user)
370 .map_err(http::Error::from)
371 .map_err(Error::HttpError)?,
372 ));
373 }
374 if let Some(impersonate_groups) = &self.auth_info.impersonate_groups {
375 for group in impersonate_groups {
376 headers.push((
377 HeaderName::from_static("impersonate-group"),
378 HeaderValue::from_str(group)
379 .map_err(http::Error::from)
380 .map_err(Error::HttpError)?,
381 ));
382 }
383 }
384 Ok(ExtraHeadersLayer {
385 headers: Arc::new(headers),
386 })
387 }
388
389 #[cfg(feature = "rustls-tls")]
390 fn rustls_client_config(&self) -> Result<rustls::ClientConfig> {
391 let identity = match self.exec_identity_pem().0 {
392 Some(identity) => Some(identity),
393 None => self.identity_pem()?,
394 };
395 let mut config = tls::rustls_tls::rustls_client_config(
396 identity.as_deref(),
397 self.root_cert.as_deref(),
398 self.accept_invalid_certs,
399 )
400 .map_err(Error::RustlsTls)?;
401
402 if !self.accept_invalid_certs
407 && let Some(path) = &self.root_cert_file
408 {
409 let verifier =
410 tls::rustls_tls::ReloadingVerifier::new(path.clone()).map_err(Error::RustlsTls)?;
411 config
412 .dangerous()
413 .set_certificate_verifier(Arc::new(verifier));
414 }
415 Ok(config)
416 }
417
418 #[cfg(feature = "rustls-tls")]
419 fn rustls_https_connector(&self) -> Result<hyper_rustls::HttpsConnector<HttpConnector>> {
420 let mut connector = HttpConnector::new();
421 connector.enforce_http(false);
422 self.rustls_https_connector_with_connector(connector)
423 }
424
425 #[cfg(feature = "rustls-tls")]
426 fn rustls_https_connector_with_connector<H>(
427 &self,
428 connector: H,
429 ) -> Result<hyper_rustls::HttpsConnector<H>> {
430 use hyper_rustls::FixedServerNameResolver;
431
432 use crate::client::tls::rustls_tls;
433
434 let rustls_config = self.rustls_client_config()?;
435 let mut builder = hyper_rustls::HttpsConnectorBuilder::new()
436 .with_tls_config(rustls_config)
437 .https_or_http();
438 if let Some(tsn) = self.tls_server_name.as_ref() {
439 builder = builder.with_server_name_resolver(FixedServerNameResolver::new(
440 tsn.clone()
441 .try_into()
442 .map_err(rustls_tls::Error::InvalidServerName)
443 .map_err(Error::RustlsTls)?,
444 ));
445 }
446 Ok(builder.enable_http1().wrap_connector(connector))
447 }
448
449 #[cfg(feature = "openssl-tls")]
450 fn openssl_ssl_connector_builder(&self) -> Result<openssl::ssl::SslConnectorBuilder> {
451 let identity = match self.exec_identity_pem().0 {
452 Some(identity) => Some(identity),
453 None => self.identity_pem()?,
454 };
455
456 tls::openssl_tls::ssl_connector_builder(identity.as_ref(), self.root_cert.as_ref())
459 .map_err(|e| Error::OpensslTls(tls::openssl_tls::Error::CreateSslConnector(e)))
460 }
461
462 #[cfg(feature = "openssl-tls")]
463 fn openssl_https_connector(
464 &self,
465 ) -> Result<hyper_openssl::client::legacy::HttpsConnector<HttpConnector>> {
466 let mut connector = HttpConnector::new();
467 connector.enforce_http(false);
468 self.openssl_https_connector_with_connector(connector)
469 }
470
471 #[cfg(feature = "openssl-tls")]
472 fn openssl_https_connector_with_connector<H>(
473 &self,
474 connector: H,
475 ) -> Result<hyper_openssl::client::legacy::HttpsConnector<H>>
476 where
477 H: tower::Service<http::Uri> + Send,
478 H::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
479 H::Future: Send + 'static,
480 H::Response: Read + Write + hyper_util::client::legacy::connect::Connection + Unpin,
481 {
482 let mut https = hyper_openssl::client::legacy::HttpsConnector::with_connector(
483 connector,
484 self.openssl_ssl_connector_builder()?,
485 )
486 .map_err(|e| Error::OpensslTls(tls::openssl_tls::Error::CreateHttpsConnector(e)))?;
487 let accept_invalid_certs = self.accept_invalid_certs;
490 let tls_server_name = self.tls_server_name.clone();
491 if accept_invalid_certs || tls_server_name.is_some() {
492 https.set_callback(move |ssl, _uri| {
493 if accept_invalid_certs {
494 ssl.set_verify(openssl::ssl::SslVerifyMode::NONE);
495 }
496 if let Some(name) = &tls_server_name {
497 use std::net::IpAddr;
498
499 use openssl::x509::verify::X509CheckFlags;
500 ssl.set_use_server_name_indication(false);
503 ssl.set_verify_hostname(false);
504 if name.parse::<IpAddr>().is_err() {
506 ssl.set_hostname(name)?;
507 }
508 let param = ssl.param_mut();
509 param.set_hostflags(X509CheckFlags::NO_PARTIAL_WILDCARDS);
510 match name.parse::<IpAddr>() {
511 Ok(ip) => param.set_ip(ip)?,
512 Err(_) => param.set_host(name)?,
513 }
514 }
515 Ok(())
516 });
517 }
518 Ok(https)
519 }
520}
521
522impl Config {
523 pub(crate) fn exec_identity_pem(&self) -> (Option<Vec<u8>>, Option<Timestamp>) {
528 match Auth::try_from(&self.auth_info) {
529 Ok(Auth::Certificate(client_certificate_data, client_key_data, expiration)) => {
530 const NEW_LINE: u8 = b'\n';
531
532 let mut buffer = client_key_data.expose_secret().as_bytes().to_vec();
533 buffer.push(NEW_LINE);
534 buffer.extend_from_slice(client_certificate_data.as_bytes());
535 buffer.push(NEW_LINE);
536 (Some(buffer), expiration)
537 }
538 _ => (None, None),
539 }
540 }
541}