From 9439900782b6d7883cea532f36d016a1d3c2f8c2 Mon Sep 17 00:00:00 2001 From: Madeline Shao Date: Mon, 25 Aug 2025 23:45:00 +0000 Subject: [PATCH] only resolve DNS on client create --- src/config/kube.rs | 66 +++++++++------------------------------------- src/k8s.rs | 50 ++++++++++++++++++++++++++--------- 2 files changed, 50 insertions(+), 66 deletions(-) diff --git a/src/config/kube.rs b/src/config/kube.rs index 267df30..0edc5e0 100644 --- a/src/config/kube.rs +++ b/src/config/kube.rs @@ -17,14 +17,12 @@ use base64::engine::{general_purpose::STANDARD, Engine}; -use hickory_resolver::{config::*, Resolver}; use std::collections::BTreeMap; use std::collections::HashMap; use std::convert::From; use std::env; use std::fs::File; use std::io::{BufReader, Read}; -use std::net::IpAddr; //use crate::certs::{get_cert, get_cert_from_pem, get_key_from_str, get_private_key}; use super::kubefile::{AuthProvider, ExecProvider}; @@ -38,7 +36,6 @@ pub struct ClusterConf { pub server: String, pub tls_server_name: Option, pub insecure_skip_tls_verify: bool, - pub custom_dns_mapping: Option<(String, IpAddr)>, // (hostname, resolved_ip) } impl ClusterConf { @@ -52,22 +49,19 @@ impl ClusterConf { server, tls_server_name, insecure_skip_tls_verify: true, - custom_dns_mapping: None, } } - fn new_with_custom_dns( + fn new_secure( cert: Option, server: String, tls_server_name: Option, - custom_dns_mapping: Option<(String, IpAddr)>, ) -> ClusterConf { ClusterConf { cert, server, tls_server_name, insecure_skip_tls_verify: false, - custom_dns_mapping, } } } @@ -133,19 +127,6 @@ pub struct Config { pub users: HashMap, } -// Helper function to create custom DNS mapping from server URL and TLS server name -fn create_custom_dns_mapping(server_url: &str, tls_server_name: &str) -> Option<(String, IpAddr)> { - let url = reqwest::Url::parse(server_url).ok()?; - let proxy_host = url.host_str()?; - - // Resolve the proxy host to its IP address - let resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default()).ok()?; - let response = resolver.lookup_ip(proxy_host).ok()?; - let proxy_ip = response.iter().next()?; - - Some((tls_server_name.to_string(), proxy_ip)) -} - // some utility functions fn get_full_path(path: String) -> Result { if path.is_empty() { @@ -210,18 +191,12 @@ impl Config { let mut s = String::new(); br.read_to_string(&mut s).expect("Couldn't read cert"); - let custom_dns = - cluster.conf.tls_server_name.as_ref().and_then(|tls_name| { - create_custom_dns_mapping(&cluster.conf.server, tls_name) - }); - cluster_map.insert( cluster.name.clone(), - ClusterConf::new_with_custom_dns( + ClusterConf::new_secure( Some(s), cluster.conf.server.clone(), cluster.conf.tls_server_name.clone(), - custom_dns, ), ); } @@ -243,18 +218,12 @@ impl Config { )) })?; - let custom_dns = - cluster.conf.tls_server_name.as_ref().and_then(|tls_name| { - create_custom_dns_mapping(&cluster.conf.server, tls_name) - }); - cluster_map.insert( cluster.name.clone(), - ClusterConf::new_with_custom_dns( + ClusterConf::new_secure( Some(cert_pem), cluster.conf.server.clone(), cluster.conf.tls_server_name.clone(), - custom_dns, ), ); } @@ -263,31 +232,20 @@ impl Config { } }, (None, None) => { - let custom_dns = - cluster.conf.tls_server_name.as_ref().and_then(|tls_name| { - create_custom_dns_mapping(&cluster.conf.server, tls_name) - }); - - let mut conf = if cluster.conf.skip_tls { + let conf = if cluster.conf.skip_tls { ClusterConf::new_insecure( None, cluster.conf.server.clone(), cluster.conf.tls_server_name.clone(), ) } else { - ClusterConf::new_with_custom_dns( + ClusterConf::new_secure( None, cluster.conf.server.clone(), cluster.conf.tls_server_name.clone(), - custom_dns.clone(), ) }; - // For insecure connections, we still want the custom DNS mapping - if cluster.conf.skip_tls && custom_dns.is_some() { - conf.custom_dns_mapping = custom_dns; - } - cluster_map.insert(cluster.name.clone(), conf); } } @@ -338,11 +296,10 @@ impl Config { let mut endpoint = reqwest::Url::parse(&cluster.server)?; - // When using custom DNS mapping, we keep the original hostname in the URL + // When using TLS server name, we need to update the endpoint URL to use the TLS server name // The DNS resolver will handle mapping it to the correct IP - // We also need to ensure we're using the TLS server name for proper hostname - if let Some((tls_hostname, _)) = &cluster.custom_dns_mapping { - endpoint.set_host(Some(tls_hostname))?; + if let Some(ref tls_server_name) = cluster.tls_server_name { + endpoint.set_host(Some(tls_server_name))?; } let ca_certs = match &cluster.cert { @@ -400,7 +357,8 @@ impl Config { user.impersonate_user.clone(), click_conf.connect_timeout_secs, click_conf.read_timeout_secs, - cluster.custom_dns_mapping.clone(), + cluster.server.clone(), + cluster.tls_server_name.clone(), ) }) } @@ -533,7 +491,7 @@ bW1EDp3zdHSo1TRJ6V6e6bR64eVaH4QwnNOfpSXY assert!(ctx.is_ok()); let ctx = ctx.unwrap(); - // The original server hostname should be preserved when DNS resolution fails - assert!(ctx.endpoint.host_str() == Some("proxy.example.com")); + // When TLS server name is provided, the endpoint should use the TLS server name -- custom resolver will resolve to the correct IP + assert!(ctx.endpoint.host_str() == Some("api.example.com")); } } diff --git a/src/k8s.rs b/src/k8s.rs index 4f1ecf0..e87a973 100644 --- a/src/k8s.rs +++ b/src/k8s.rs @@ -14,6 +14,7 @@ use base64::engine::{general_purpose::STANDARD, Engine}; use bytes::Bytes; +use hickory_resolver::{config::*, Resolver}; use k8s_openapi::{http, List, ListableResource}; use reqwest::blocking::Client; use reqwest::{Certificate, Identity, Url}; @@ -34,6 +35,20 @@ use crate::{ error::{ClickErrNo, ClickError}, }; +// Helper function to create custom DNS mapping from server URL and TLS server name +// This is called lazily when the client is created +fn create_custom_dns_mapping(server_url: &str, tls_server_name: &str) -> Option<(String, IpAddr)> { + let url = reqwest::Url::parse(server_url).ok()?; + let proxy_host = url.host_str()?; + + // Resolve the proxy host to its IP address + let resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default()).ok()?; + let response = resolver.lookup_ip(proxy_host).ok()?; + let proxy_ip = response.iter().next()?; + + Some((tls_server_name.to_string(), proxy_ip)) +} + #[derive(Clone)] pub enum UserAuth { AuthProvider(Box), @@ -176,7 +191,8 @@ pub struct Context { impersonate_user: Option, connect_timeout_secs: u32, read_timeout_secs: u32, - custom_dns_mapping: Option<(String, IpAddr)>, + server_url: String, + tls_server_name: Option, } impl Context { @@ -189,7 +205,8 @@ impl Context { impersonate_user: Option, connect_timeout_secs: u32, read_timeout_secs: u32, - custom_dns_mapping: Option<(String, IpAddr)>, + server_url: String, + tls_server_name: Option, ) -> Context { let (client, client_auth) = Context::get_client( &endpoint, @@ -198,7 +215,8 @@ impl Context { None, connect_timeout_secs, read_timeout_secs, - custom_dns_mapping.clone(), + &server_url, + &tls_server_name, ); // have to create a special client for logs until // https://github.com/seanmonstar/reqwest/issues/1380 @@ -210,7 +228,8 @@ impl Context { None, u32::MAX, u32::MAX, - custom_dns_mapping.clone(), + &server_url, + &tls_server_name, ); let client = RefCell::new(client); let log_client = RefCell::new(log_client); @@ -225,10 +244,12 @@ impl Context { impersonate_user, connect_timeout_secs, read_timeout_secs, - custom_dns_mapping, + server_url, + tls_server_name, } } + #[allow(clippy::too_many_arguments)] fn get_client( endpoint: &Url, root_cas: Option>, @@ -236,7 +257,8 @@ impl Context { id: Option, connect_timeout_secs: u32, read_timeout_secs: u32, - custom_dns_mapping: Option<(String, IpAddr)>, + server_url: &str, + tls_server_name: &Option, ) -> (Client, Option) { let host = endpoint.host().unwrap(); let mut client = match host { @@ -244,10 +266,12 @@ impl Context { _ => Client::builder().use_native_tls(), }; - // Use custom DNS mapping if we have one - if let Some((hostname, ip)) = custom_dns_mapping { - // reqwest's resolve method allows mapping specific hostnames to IP addresses - client = client.resolve(&hostname, SocketAddr::new(ip, 443)); + // Create custom DNS mapping if we have a TLS server name + if let Some(tls_name) = tls_server_name { + if let Some((hostname, ip)) = create_custom_dns_mapping(server_url, tls_name) { + // reqwest's resolve method allows mapping specific hostnames to IP addresses + client = client.resolve(&hostname, SocketAddr::new(ip, 443)); + } } let client = match root_cas { Some(cas) => { @@ -306,7 +330,8 @@ impl Context { Some(id.clone()), self.connect_timeout_secs, self.read_timeout_secs, - self.custom_dns_mapping.clone(), + &self.server_url, + &self.tls_server_name, ); let (new_log_client, _) = Context::get_client( &self.endpoint, @@ -315,7 +340,8 @@ impl Context { Some(id), u32::MAX, u32::MAX, - self.custom_dns_mapping.clone(), + &self.server_url, + &self.tls_server_name, ); *self.client.borrow_mut() = new_client; *self.log_client.borrow_mut() = new_log_client;