From 4e3743e9ec40ea4c95295da725cfc495b3a1dcb0 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Wed, 7 Sep 2022 22:25:06 +0000 Subject: [PATCH 1/9] client: Expose a Config constructor to support legacy configurations The `Config::from_cluster_env` constructor is misleadingly named: it doesn't use the environment, it uses the default cluster configurations. This change deprecates the `Config::from_cluster_env` constructor in favor of `Config::load_in_cluster`. An additional constructor, `Config::load_in_cluster_from_legacy_env`, uses the `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` environment variables to match client-go's behavior. This changes does NOT alter the default inferred configuration in any way. It simply allows users to opt-in to using the old behavior. Related to kubernetes/kubernetes#112263 Closes #1000 Signed-off-by: Oliver Gould --- .devcontainer/devcontainer.json | 2 +- kube-client/src/config/incluster_config.rs | 43 +++++++++++++++++++++ kube-client/src/config/mod.rs | 44 ++++++++++++++++++---- 3 files changed, 80 insertions(+), 9 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 1f9bfb296..a758279f6 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -8,7 +8,7 @@ // These extensions are loaded for all users by default. "extensions": [ - "matklad.rust-analyzer", + "rust-lang.rust-analyzer", "NathanRidley.autotrim", "samverschueren.final-newline", "tamasfe.even-better-toml", diff --git a/kube-client/src/config/incluster_config.rs b/kube-client/src/config/incluster_config.rs index cadbd91ef..8960a9371 100644 --- a/kube-client/src/config/incluster_config.rs +++ b/kube-client/src/config/incluster_config.rs @@ -16,6 +16,10 @@ pub enum Error { #[error("failed to read a certificate bundle: {0}")] ReadCertificateBundle(#[source] std::io::Error), + /// Failed to parse cluster port value + #[error("failed to parse cluster port: {0}")] + ParseClusterPort(#[source] std::num::ParseIntError), + /// Failed to parse cluster url #[error("failed to parse cluster url: {0}")] ParseClusterUrl(#[source] http::uri::InvalidUri), @@ -29,6 +33,45 @@ pub fn kube_dns() -> http::Uri { http::Uri::from_static("https://kubernetes.default.svc/") } +pub fn try_kube_from_legacy_env_or_dns() -> Result { + // client-go requires that both environment variables are set, so we do too. + let host = match std::env::var("KUBERNETES_SERVICE_HOST") { + Ok(h) => h, + Err(_) => return Ok(kube_dns()), + }; + let port = match std::env::var("KUBERNETES_SERVICE_PORT") { + Ok(p) => p.parse::().map_err(Error::ParseClusterPort)?, + Err(_) => return Ok(kube_dns()), + }; + + // Format a host and, if not using 443, a port. + // + // Ensure that IPv6 addresses are properly bracketed. + let uri = match host.parse::() { + Ok(ip) => { + if port == 443 { + if ip.is_ipv6() { + format!("https://[{ip}]") + } else { + format!("https://{ip}") + } + } else { + let addr = std::net::SocketAddr::new(ip, port); + format!("https://{addr}") + } + } + Err(_) => { + if port == 443 { + format!("https://{host}") + } else { + format!("https://{host}:{port}") + } + } + }; + + uri.parse().map_err(Error::ParseClusterUrl) +} + pub fn token_file() -> String { SERVICE_TOKENFILE.to_owned() } diff --git a/kube-client/src/config/mod.rs b/kube-client/src/config/mod.rs index 7372a8a50..c4da92c3f 100644 --- a/kube-client/src/config/mod.rs +++ b/kube-client/src/config/mod.rs @@ -195,7 +195,7 @@ impl Config { "no local config found, falling back to local in-cluster config" ); - Self::from_cluster_env().map_err(|in_cluster_err| InferConfigError { + Self::load_in_cluster().map_err(|in_cluster_err| InferConfigError { in_cluster: in_cluster_err, kubeconfig: kubeconfig_err, })? @@ -206,13 +206,42 @@ impl Config { Ok(config) } - /// Create configuration from the cluster's environment variables - /// - /// This follows the standard [API Access from a Pod](https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/#accessing-the-api-from-a-pod) - /// and relies on you having the service account's token mounted, - /// as well as having given the service account rbac access to do what you need. + /// Replaced by [`Self::load_in_cluster`]. + #[deprecated(since = "0.75.0", note = "use Config::load_in_cluster")] pub fn from_cluster_env() -> Result { - let cluster_url = incluster_config::kube_dns(); + Self::load_in_cluster() + } + + /// Load the default in-cluster config. + /// + /// This follows the standard [API Access from a Pod][docs] policy of using + /// `https://kubernetes.default.svc` to connect to the Kubernetes API + /// server. A service account's token must be available in + /// `/var/run/secrets/kubernetes.io/serviceaccount/`. + /// + /// [docs]: https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/#accessing-the-api-from-a-pod + pub fn load_in_cluster() -> Result { + Self::load_in_cluster_with_uri(incluster_config::kube_dns()) + } + + /// Load an in-cluster config use the legacy `KUBERNETES_SERVICE_HOST` and + /// `KUBERNETES_SERVICE_PORT` environment variables. + /// + /// This matches the behavior of [client-go], but it will fallback to using + /// `kubernetes.default.svc` if the environment is not set. + /// + /// A service account's token must be available in + /// `/var/run/secrets/kubernetes.io/serviceaccount/`. + /// + /// This will be deprecated/removed when client-go's behavior changes. + /// + /// [client-go]: https://github.com/kubernetes/kubernetes/blob/67bde9a1023d1805e33d698b28aa6fad991dfb39/staging/src/k8s.io/client-go/rest/config.go#L507-L541 + pub fn load_in_cluster_from_legacy_env() -> Result { + let uri = incluster_config::try_kube_from_legacy_env_or_dns()?; + Self::load_in_cluster_with_uri(uri) + } + + fn load_in_cluster_with_uri(cluster_url: http::uri::Uri) -> Result { let default_namespace = incluster_config::load_default_ns()?; let root_cert = incluster_config::load_cert()?; @@ -378,7 +407,6 @@ pub use file_config::{ NamedContext, NamedExtension, Preferences, }; - #[cfg(test)] mod tests { #[cfg(not(feature = "client"))] // want to ensure this works without client features From 5ffdb5ef0b54932cbb199f2b43c052d2070c10c9 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Wed, 7 Sep 2022 23:03:28 +0000 Subject: [PATCH 2/9] constify "https" scheme to make accidental "http" harder Signed-off-by: Oliver Gould --- kube-client/src/config/incluster_config.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/kube-client/src/config/incluster_config.rs b/kube-client/src/config/incluster_config.rs index 8960a9371..5141faf68 100644 --- a/kube-client/src/config/incluster_config.rs +++ b/kube-client/src/config/incluster_config.rs @@ -47,24 +47,25 @@ pub fn try_kube_from_legacy_env_or_dns() -> Result { // Format a host and, if not using 443, a port. // // Ensure that IPv6 addresses are properly bracketed. + const HTTPS: &str = "https"; let uri = match host.parse::() { Ok(ip) => { if port == 443 { if ip.is_ipv6() { - format!("https://[{ip}]") + format!("{HTTPS}://[{ip}]") } else { - format!("https://{ip}") + format!("{HTTPS}://{ip}") } } else { let addr = std::net::SocketAddr::new(ip, port); - format!("https://{addr}") + format!("{HTTPS}://{addr}") } } Err(_) => { if port == 443 { - format!("https://{host}") + format!("{HTTPS}://{host}") } else { - format!("https://{host}:{port}") + format!("{HTTPS}://{host}:{port}") } } }; From dd00e12629bc07f16c07a91ff45ab73cea6b09a8 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 9 Sep 2022 00:46:04 +0000 Subject: [PATCH 3/9] Restore `Config::from_cluster_env` naming Add `Config::from_cluster_dns` to support the current behavior. Signed-off-by: Oliver Gould --- kube-client/src/config/incluster_config.rs | 21 +++++---- kube-client/src/config/mod.rs | 54 +++++++++------------- 2 files changed, 34 insertions(+), 41 deletions(-) diff --git a/kube-client/src/config/incluster_config.rs b/kube-client/src/config/incluster_config.rs index 5141faf68..41282c878 100644 --- a/kube-client/src/config/incluster_config.rs +++ b/kube-client/src/config/incluster_config.rs @@ -12,6 +12,10 @@ pub enum Error { #[error("failed to read the default namespace: {0}")] ReadDefaultNamespace(#[source] std::io::Error), + /// Failed to read the in-cluster environment variables + #[error("failed to read an incluster environment variable: {0}")] + ReadEnvironmentVariable(#[source] std::env::VarError), + /// Failed to read a certificate bundle #[error("failed to read a certificate bundle: {0}")] ReadCertificateBundle(#[source] std::io::Error), @@ -33,16 +37,13 @@ pub fn kube_dns() -> http::Uri { http::Uri::from_static("https://kubernetes.default.svc/") } -pub fn try_kube_from_legacy_env_or_dns() -> Result { - // client-go requires that both environment variables are set, so we do too. - let host = match std::env::var("KUBERNETES_SERVICE_HOST") { - Ok(h) => h, - Err(_) => return Ok(kube_dns()), - }; - let port = match std::env::var("KUBERNETES_SERVICE_PORT") { - Ok(p) => p.parse::().map_err(Error::ParseClusterPort)?, - Err(_) => return Ok(kube_dns()), - }; +pub fn try_kube_from_env() -> Result { + // client-go requires that both environment variables are set. + let host = std::env::var("KUBERNETES_SERVICE_HOST").map_err(Error::ReadEnvironmentVariable)?; + let port = std::env::var("KUBERNETES_SERVICE_PORT") + .map_err(Error::ReadEnvironmentVariable)? + .parse::() + .map_err(Error::ParseClusterPort)?; // Format a host and, if not using 443, a port. // diff --git a/kube-client/src/config/mod.rs b/kube-client/src/config/mod.rs index c4da92c3f..ecdbaae24 100644 --- a/kube-client/src/config/mod.rs +++ b/kube-client/src/config/mod.rs @@ -181,12 +181,14 @@ impl Config { /// Infer the configuration from the environment /// - /// Done by attempting to load the local kubec-config first, and - /// then if that fails, trying the in-cluster environment variables . + /// Done by attempting to load the local kubec-config first, and then if + /// that fails, trying the in-cluster environment variables via + /// [`Config::from_cluster_env`]. /// /// Fails if inference from both sources fails /// - /// Applies debug overrides, see [`Config::apply_debug_overrides`] for more details + /// Applies debug overrides, see [`Config::apply_debug_overrides`] for more + /// details pub async fn infer() -> Result { let mut config = match Self::from_kubeconfig(&KubeConfigOptions::default()).await { Err(kubeconfig_err) => { @@ -195,8 +197,8 @@ impl Config { "no local config found, falling back to local in-cluster config" ); - Self::load_in_cluster().map_err(|in_cluster_err| InferConfigError { - in_cluster: in_cluster_err, + Self::from_cluster_env().map_err(|in_cluster| InferConfigError { + in_cluster, kubeconfig: kubeconfig_err, })? } @@ -206,42 +208,32 @@ impl Config { Ok(config) } - /// Replaced by [`Self::load_in_cluster`]. - #[deprecated(since = "0.75.0", note = "use Config::load_in_cluster")] - pub fn from_cluster_env() -> Result { - Self::load_in_cluster() - } - - /// Load the default in-cluster config. + /// Load an in-cluster config using the `KUBERNETES_SERVICE_HOST` and + /// `KUBERNETES_SERVICE_PORT` environment variables. /// - /// This follows the standard [API Access from a Pod][docs] policy of using - /// `https://kubernetes.default.svc` to connect to the Kubernetes API - /// server. A service account's token must be available in - /// `/var/run/secrets/kubernetes.io/serviceaccount/`. + /// This matches the behavior of the official Kubernetes client libraries, + /// but it may not be compatible with `rustls`. /// - /// [docs]: https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/#accessing-the-api-from-a-pod - pub fn load_in_cluster() -> Result { - Self::load_in_cluster_with_uri(incluster_config::kube_dns()) + /// A service account's token must be available in + /// `/var/run/secrets/kubernetes.io/serviceaccount/`. + pub fn from_cluster_env() -> Result { + let uri = incluster_config::try_kube_from_env()?; + Self::load_inluster_with_uri(uri) } - /// Load an in-cluster config use the legacy `KUBERNETES_SERVICE_HOST` and - /// `KUBERNETES_SERVICE_PORT` environment variables. + /// Load an in-cluster config using the API server at + /// `https://kubernetes.default.svc`. /// - /// This matches the behavior of [client-go], but it will fallback to using - /// `kubernetes.default.svc` if the environment is not set. + /// This behavior does not match that of the official Kubernetes clients, + /// but this approach is compatible with `rustls`. /// /// A service account's token must be available in /// `/var/run/secrets/kubernetes.io/serviceaccount/`. - /// - /// This will be deprecated/removed when client-go's behavior changes. - /// - /// [client-go]: https://github.com/kubernetes/kubernetes/blob/67bde9a1023d1805e33d698b28aa6fad991dfb39/staging/src/k8s.io/client-go/rest/config.go#L507-L541 - pub fn load_in_cluster_from_legacy_env() -> Result { - let uri = incluster_config::try_kube_from_legacy_env_or_dns()?; - Self::load_in_cluster_with_uri(uri) + pub fn from_cluster_dns() -> Result { + Self::load_inluster_with_uri(incluster_config::kube_dns()) } - fn load_in_cluster_with_uri(cluster_url: http::uri::Uri) -> Result { + fn load_inluster_with_uri(cluster_url: http::uri::Uri) -> Result { let default_namespace = incluster_config::load_default_ns()?; let root_cert = incluster_config::load_cert()?; From 4a6aaf364f9be75a99c6aaacd326a094e4d57aaa Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 9 Sep 2022 02:46:57 +0000 Subject: [PATCH 4/9] Disable the in-cluster rustls test Signed-off-by: Oliver Gould --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6346e0549..e0045112e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -234,7 +234,8 @@ jobs: # Prevent GitHub from cancelling all in-progress jobs when a matrix job fails. fail-fast: false matrix: - tls: [openssl, rustls] + # TODO: Enable rustls https://github.com/kube-rs/kube-rs/issues/1003 + tls: [openssl] steps: - uses: actions/checkout@v2 - uses: actions/cache@v2 From d272478ecfee1dc4a00954fa7aab5f0b1930a8fe Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 9 Sep 2022 02:48:52 +0000 Subject: [PATCH 5/9] fix typo Signed-off-by: Oliver Gould --- kube-client/src/config/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kube-client/src/config/mod.rs b/kube-client/src/config/mod.rs index ecdbaae24..ea010d87b 100644 --- a/kube-client/src/config/mod.rs +++ b/kube-client/src/config/mod.rs @@ -218,7 +218,7 @@ impl Config { /// `/var/run/secrets/kubernetes.io/serviceaccount/`. pub fn from_cluster_env() -> Result { let uri = incluster_config::try_kube_from_env()?; - Self::load_inluster_with_uri(uri) + Self::load_incluster_with_uri(uri) } /// Load an in-cluster config using the API server at @@ -230,10 +230,10 @@ impl Config { /// A service account's token must be available in /// `/var/run/secrets/kubernetes.io/serviceaccount/`. pub fn from_cluster_dns() -> Result { - Self::load_inluster_with_uri(incluster_config::kube_dns()) + Self::load_incluster_with_uri(incluster_config::kube_dns()) } - fn load_inluster_with_uri(cluster_url: http::uri::Uri) -> Result { + fn load_incluster_with_uri(cluster_url: http::uri::Uri) -> Result { let default_namespace = incluster_config::load_default_ns()?; let root_cert = incluster_config::load_cert()?; From bf4d07ce9a15d8676301bd5e4fa6ee99e002eb18 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 9 Sep 2022 03:26:40 +0000 Subject: [PATCH 6/9] client: Make discovery conditional on the TLS impl When `rustls-tls` is enabled, the `kubernetes.default.svc` DNS name is used. Otherwise, the `KUBERNETES_SERVICE_{HOST,PORT}` environment variables are used. Signed-off-by: Oliver Gould --- .github/workflows/ci.yml | 3 +-- kube-client/src/config/mod.rs | 36 +++++++++++++++++++++-------------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0045112e..6346e0549 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -234,8 +234,7 @@ jobs: # Prevent GitHub from cancelling all in-progress jobs when a matrix job fails. fail-fast: false matrix: - # TODO: Enable rustls https://github.com/kube-rs/kube-rs/issues/1003 - tls: [openssl] + tls: [openssl, rustls] steps: - uses: actions/checkout@v2 - uses: actions/cache@v2 diff --git a/kube-client/src/config/mod.rs b/kube-client/src/config/mod.rs index ea010d87b..75c1703d2 100644 --- a/kube-client/src/config/mod.rs +++ b/kube-client/src/config/mod.rs @@ -179,16 +179,22 @@ impl Config { } } - /// Infer the configuration from the environment + /// Infer a Kubernetes client configuration. /// - /// Done by attempting to load the local kubec-config first, and then if - /// that fails, trying the in-cluster environment variables via - /// [`Config::from_cluster_env`]. + /// First, a user's kubeconfig is loaded from `KUBECONFIG` or + /// `~/.kube/config`. If that fails, an in-cluster config is loaded. If + /// inference from both sources fails, then an error is returned. /// - /// Fails if inference from both sources fails + /// When an [`Config::incluster`] configuration is loaded, the behavior is + /// dependent on the features that are enabled. If `rustls-tls` is enabled, + /// the `kubernetes.default.svc` DNS name is used to connect to a Kubernetes + /// cluster. Otherwise, if another TLS implementation is enabled, the + /// default `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` + /// environment variables are used to reference the Kubernetes API server. + /// See . /// - /// Applies debug overrides, see [`Config::apply_debug_overrides`] for more - /// details + /// [`Config::apply_debug_overrides`] is used to augment the loaded + /// configuration based on the environment. pub async fn infer() -> Result { let mut config = match Self::from_kubeconfig(&KubeConfigOptions::default()).await { Err(kubeconfig_err) => { @@ -197,7 +203,7 @@ impl Config { "no local config found, falling back to local in-cluster config" ); - Self::from_cluster_env().map_err(|in_cluster| InferConfigError { + Self::incluster().map_err(|in_cluster| InferConfigError { in_cluster, kubeconfig: kubeconfig_err, })? @@ -212,13 +218,14 @@ impl Config { /// `KUBERNETES_SERVICE_PORT` environment variables. /// /// This matches the behavior of the official Kubernetes client libraries, - /// but it may not be compatible with `rustls`. + /// but it is not compatible with `rustls`. /// /// A service account's token must be available in /// `/var/run/secrets/kubernetes.io/serviceaccount/`. - pub fn from_cluster_env() -> Result { + #[cfg(not(feature = "rustls-tls"))] + pub fn incluster() -> Result { let uri = incluster_config::try_kube_from_env()?; - Self::load_incluster_with_uri(uri) + Self::incluster_with_uri(uri) } /// Load an in-cluster config using the API server at @@ -229,11 +236,12 @@ impl Config { /// /// A service account's token must be available in /// `/var/run/secrets/kubernetes.io/serviceaccount/`. - pub fn from_cluster_dns() -> Result { - Self::load_incluster_with_uri(incluster_config::kube_dns()) + #[cfg(feature = "rustls-tls")] + pub fn incluster() -> Result { + Self::incluster_with_uri(incluster_config::kube_dns()) } - fn load_incluster_with_uri(cluster_url: http::uri::Uri) -> Result { + fn incluster_with_uri(cluster_url: http::uri::Uri) -> Result { let default_namespace = incluster_config::load_default_ns()?; let root_cert = incluster_config::load_cert()?; From be9da2b2cdf3ddb06adb0d73391929ecdb08203b Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 9 Sep 2022 17:50:13 +0000 Subject: [PATCH 7/9] Review feedback * Make `Config::incluster_env` and `Config::incluster_dns` public regardless of what features are enabled. * Restrict visibility for `pub` helpers that are not actually publicly exported. Signed-off-by: Oliver Gould --- kube-client/src/config/incluster_config.rs | 9 +++- kube-client/src/config/mod.rs | 50 +++++++++++++--------- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/kube-client/src/config/incluster_config.rs b/kube-client/src/config/incluster_config.rs index 41282c878..beafd2e5a 100644 --- a/kube-client/src/config/incluster_config.rs +++ b/kube-client/src/config/incluster_config.rs @@ -33,11 +33,16 @@ pub enum Error { ParseCertificates(#[source] pem::PemError), } -pub fn kube_dns() -> http::Uri { +/// Returns the URI of the Kubernetes API server using the in-cluster DNS name +/// `kubernetes.default.svc`. +pub(super) fn kube_dns() -> http::Uri { http::Uri::from_static("https://kubernetes.default.svc/") } -pub fn try_kube_from_env() -> Result { +/// Returns the URI of the Kubernetes API server by reading the +/// `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` environment +/// variables. +pub(super) fn try_kube_from_env() -> Result { // client-go requires that both environment variables are set. let host = std::env::var("KUBERNETES_SERVICE_HOST").map_err(Error::ReadEnvironmentVariable)?; let port = std::env::var("KUBERNETES_SERVICE_PORT") diff --git a/kube-client/src/config/mod.rs b/kube-client/src/config/mod.rs index 75c1703d2..91222e6f4 100644 --- a/kube-client/src/config/mod.rs +++ b/kube-client/src/config/mod.rs @@ -182,16 +182,9 @@ impl Config { /// Infer a Kubernetes client configuration. /// /// First, a user's kubeconfig is loaded from `KUBECONFIG` or - /// `~/.kube/config`. If that fails, an in-cluster config is loaded. If - /// inference from both sources fails, then an error is returned. - /// - /// When an [`Config::incluster`] configuration is loaded, the behavior is - /// dependent on the features that are enabled. If `rustls-tls` is enabled, - /// the `kubernetes.default.svc` DNS name is used to connect to a Kubernetes - /// cluster. Otherwise, if another TLS implementation is enabled, the - /// default `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` - /// environment variables are used to reference the Kubernetes API server. - /// See . + /// `~/.kube/config`. If that fails, an in-cluster config is loaded via + /// [`Config::incluster`]. If inference from both sources fails, then an + /// error is returned. /// /// [`Config::apply_debug_overrides`] is used to augment the loaded /// configuration based on the environment. @@ -214,16 +207,34 @@ impl Config { Ok(config) } + /// Load an in-cluster Kubernetes client configuration using + /// [`Config::incluster_env`]. + #[cfg(not(feature = "rustls-tls"))] + pub fn incluster() -> Result { + Self::incluster_env() + } + + /// Load an in-cluster Kubernetes client configuration using + /// [`Config::incluster_dns`]. + /// + /// The `rustls-tls` feature is currently incompatible with + /// [`Config::incluster_env`]. See + /// . + #[cfg(feature = "rustls-tls")] + pub fn incluster() -> Result { + Self::incluster_dns(incluster_config::kube_dns()) + } /// Load an in-cluster config using the `KUBERNETES_SERVICE_HOST` and /// `KUBERNETES_SERVICE_PORT` environment variables. /// - /// This matches the behavior of the official Kubernetes client libraries, - /// but it is not compatible with `rustls`. - /// /// A service account's token must be available in /// `/var/run/secrets/kubernetes.io/serviceaccount/`. - #[cfg(not(feature = "rustls-tls"))] - pub fn incluster() -> Result { + /// + /// This method matches the behavior of the official Kubernetes client + /// libraries, but it is not compatible with the `rustls-tls` feature . When + /// this feature is enabled, [`Config::incluster_dns`] should be used + /// instead. See . + pub fn incluster_env() -> Result { let uri = incluster_config::try_kube_from_env()?; Self::incluster_with_uri(uri) } @@ -231,13 +242,12 @@ impl Config { /// Load an in-cluster config using the API server at /// `https://kubernetes.default.svc`. /// - /// This behavior does not match that of the official Kubernetes clients, - /// but this approach is compatible with `rustls`. - /// /// A service account's token must be available in /// `/var/run/secrets/kubernetes.io/serviceaccount/`. - #[cfg(feature = "rustls-tls")] - pub fn incluster() -> Result { + /// + /// This behavior does not match that of the official Kubernetes clients, + /// but this approach is compatible with the `rustls-tls` feature. + pub fn incluster_dns() -> Result { Self::incluster_with_uri(incluster_config::kube_dns()) } From 7c9645c02b82ec71aae7b84339b7e74c22b580c6 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 9 Sep 2022 18:18:24 +0000 Subject: [PATCH 8/9] Add URI-formatting tests Signed-off-by: Oliver Gould --- kube-client/src/config/incluster_config.rs | 63 ++++++++++++++++++++-- kube-client/src/config/mod.rs | 2 +- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/kube-client/src/config/incluster_config.rs b/kube-client/src/config/incluster_config.rs index beafd2e5a..e450e2f16 100644 --- a/kube-client/src/config/incluster_config.rs +++ b/kube-client/src/config/incluster_config.rs @@ -1,5 +1,9 @@ +use std::env; use thiserror::Error; +const SERVICE_HOSTENV: &str = "KUBERNETES_SERVICE_HOST"; +const SERVICE_PORTENV: &str = "KUBERNETES_SERVICE_PORT"; + // Mounted credential files const SERVICE_TOKENFILE: &str = "/var/run/secrets/kubernetes.io/serviceaccount/token"; const SERVICE_CERTFILE: &str = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"; @@ -14,7 +18,7 @@ pub enum Error { /// Failed to read the in-cluster environment variables #[error("failed to read an incluster environment variable: {0}")] - ReadEnvironmentVariable(#[source] std::env::VarError), + ReadEnvironmentVariable(#[source] env::VarError), /// Failed to read a certificate bundle #[error("failed to read a certificate bundle: {0}")] @@ -44,12 +48,16 @@ pub(super) fn kube_dns() -> http::Uri { /// variables. pub(super) fn try_kube_from_env() -> Result { // client-go requires that both environment variables are set. - let host = std::env::var("KUBERNETES_SERVICE_HOST").map_err(Error::ReadEnvironmentVariable)?; - let port = std::env::var("KUBERNETES_SERVICE_PORT") + let host = env::var(SERVICE_HOSTENV).map_err(Error::ReadEnvironmentVariable)?; + let port = env::var(SERVICE_PORTENV) .map_err(Error::ReadEnvironmentVariable)? .parse::() .map_err(Error::ParseClusterPort)?; + try_uri(&host, port) +} + +fn try_uri(host: &str, port: u16) -> Result { // Format a host and, if not using 443, a port. // // Ensure that IPv6 addresses are properly bracketed. @@ -93,3 +101,52 @@ pub fn load_cert() -> Result>, Error> { pub fn load_default_ns() -> Result { std::fs::read_to_string(SERVICE_DEFAULT_NS).map_err(Error::ReadDefaultNamespace) } + +#[test] +fn test_kube_name() { + assert_eq!( + try_uri("fake.io", 8080).unwrap().to_string(), + "https://fake.io:8080/" + ); +} + +#[test] +fn test_kube_name_default_port() { + assert_eq!(try_uri("kubernetes.default.svc", 443).unwrap(), kube_dns()) +} + +#[test] +fn test_kube_ipv4() { + assert_eq!( + try_uri("10.11.12.13", 6443).unwrap().to_string(), + "https://10.11.12.13:6443/" + ); +} + +#[test] +fn test_kube_ipv4_default_port() { + assert_eq!( + try_uri("10.11.12.13", 443).unwrap().to_string(), + "https://10.11.12.13/" + ); +} + +#[test] +fn test_kube_ipv6() { + assert_eq!( + try_uri("2001:0db8:85a3:0000:0000:8a2e:0370:7334", 6443) + .unwrap() + .to_string(), + "https://[2001:db8:85a3::8a2e:370:7334]:6443/" + ); +} + +#[test] +fn test_kube_ipv6_default_port() { + assert_eq!( + try_uri("2001:0db8:85a3:0000:0000:8a2e:0370:7334", 443) + .unwrap() + .to_string(), + "https://[2001:db8:85a3::8a2e:370:7334]/" + ); +} diff --git a/kube-client/src/config/mod.rs b/kube-client/src/config/mod.rs index 91222e6f4..38eee7905 100644 --- a/kube-client/src/config/mod.rs +++ b/kube-client/src/config/mod.rs @@ -222,7 +222,7 @@ impl Config { /// . #[cfg(feature = "rustls-tls")] pub fn incluster() -> Result { - Self::incluster_dns(incluster_config::kube_dns()) + Self::incluster_dns() } /// Load an in-cluster config using the `KUBERNETES_SERVICE_HOST` and /// `KUBERNETES_SERVICE_PORT` environment variables. From 727d809e4daec7fba3d4e26fbd750cd2df6eefc4 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 9 Sep 2022 18:43:20 +0000 Subject: [PATCH 9/9] fmt Signed-off-by: Oliver Gould --- kube-client/src/config/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/kube-client/src/config/mod.rs b/kube-client/src/config/mod.rs index 38eee7905..ae63737e1 100644 --- a/kube-client/src/config/mod.rs +++ b/kube-client/src/config/mod.rs @@ -224,6 +224,7 @@ impl Config { pub fn incluster() -> Result { Self::incluster_dns() } + /// Load an in-cluster config using the `KUBERNETES_SERVICE_HOST` and /// `KUBERNETES_SERVICE_PORT` environment variables. ///