Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 12 additions & 54 deletions src/config/kube.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -38,7 +36,6 @@ pub struct ClusterConf {
pub server: String,
pub tls_server_name: Option<String>,
pub insecure_skip_tls_verify: bool,
pub custom_dns_mapping: Option<(String, IpAddr)>, // (hostname, resolved_ip)
}

impl ClusterConf {
Expand All @@ -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<String>,
server: String,
tls_server_name: Option<String>,
custom_dns_mapping: Option<(String, IpAddr)>,
) -> ClusterConf {
ClusterConf {
cert,
server,
tls_server_name,
insecure_skip_tls_verify: false,
custom_dns_mapping,
}
}
}
Expand Down Expand Up @@ -133,19 +127,6 @@ pub struct Config {
pub users: HashMap<String, UserConf>,
}

// 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<String, ClickError> {
if path.is_empty() {
Expand Down Expand Up @@ -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,
),
);
}
Expand All @@ -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,
),
);
}
Expand All @@ -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);
}
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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(),
)
})
}
Expand Down Expand Up @@ -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"));
}
}
50 changes: 38 additions & 12 deletions src/k8s.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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<AuthProvider>),
Expand Down Expand Up @@ -176,7 +191,8 @@ pub struct Context {
impersonate_user: Option<String>,
connect_timeout_secs: u32,
read_timeout_secs: u32,
custom_dns_mapping: Option<(String, IpAddr)>,
server_url: String,
tls_server_name: Option<String>,
}

impl Context {
Expand All @@ -189,7 +205,8 @@ impl Context {
impersonate_user: Option<String>,
connect_timeout_secs: u32,
read_timeout_secs: u32,
custom_dns_mapping: Option<(String, IpAddr)>,
server_url: String,
tls_server_name: Option<String>,
) -> Context {
let (client, client_auth) = Context::get_client(
&endpoint,
Expand All @@ -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
Expand All @@ -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);
Expand All @@ -225,29 +244,34 @@ 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<Vec<Certificate>>,
auth: Option<UserAuth>,
id: Option<Identity>,
connect_timeout_secs: u32,
read_timeout_secs: u32,
custom_dns_mapping: Option<(String, IpAddr)>,
server_url: &str,
tls_server_name: &Option<String>,
) -> (Client, Option<UserAuth>) {
let host = endpoint.host().unwrap();
let mut client = match host {
Host::Domain(_) => Client::builder().use_rustls_tls(),
_ => 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) => {
Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand Down
Loading