Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Config: New incluster and incluster_dns constructors #1001

Merged
merged 10 commits into from Sep 9, 2022
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Expand Up @@ -8,7 +8,7 @@

// These extensions are loaded for all users by default.
"extensions": [
"matklad.rust-analyzer",
"rust-lang.rust-analyzer",
clux marked this conversation as resolved.
Show resolved Hide resolved
"NathanRidley.autotrim",
"samverschueren.final-newline",
"tamasfe.even-better-toml",
Expand Down
45 changes: 45 additions & 0 deletions kube-client/src/config/incluster_config.rs
Expand Up @@ -12,10 +12,18 @@ 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),

/// 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),
Expand All @@ -29,6 +37,43 @@ pub fn kube_dns() -> http::Uri {
http::Uri::from_static("https://kubernetes.default.svc/")
}

pub fn try_kube_from_env() -> Result<http::Uri, Error> {
clux marked this conversation as resolved.
Show resolved Hide resolved
// 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::<u16>()
.map_err(Error::ParseClusterPort)?;

// 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::<std::net::IpAddr>() {
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}")
}
}
};
clux marked this conversation as resolved.
Show resolved Hide resolved

uri.parse().map_err(Error::ParseClusterUrl)
olix0r marked this conversation as resolved.
Show resolved Hide resolved
}

pub fn token_file() -> String {
SERVICE_TOKENFILE.to_owned()
}
Expand Down
56 changes: 42 additions & 14 deletions kube-client/src/config/mod.rs
Expand Up @@ -179,14 +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 .
/// 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 <https://github.com/kube-rs/kube-rs/issues/1003>.
///
/// 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<Self, InferConfigError> {
let mut config = match Self::from_kubeconfig(&KubeConfigOptions::default()).await {
Err(kubeconfig_err) => {
Expand All @@ -195,8 +203,8 @@ impl Config {
"no local config found, falling back to local in-cluster config"
);

Self::from_cluster_env().map_err(|in_cluster_err| InferConfigError {
in_cluster: in_cluster_err,
Self::incluster().map_err(|in_cluster| InferConfigError {
in_cluster,
kubeconfig: kubeconfig_err,
})?
}
Expand All @@ -206,13 +214,34 @@ impl Config {
Ok(config)
}

/// Create configuration from the cluster's environment variables
/// 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](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.
pub fn from_cluster_env() -> Result<Self, InClusterError> {
let cluster_url = incluster_config::kube_dns();
/// 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<Self, InClusterError> {
let uri = incluster_config::try_kube_from_env()?;
Self::incluster_with_uri(uri)
}

/// 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<Self, InClusterError> {
Self::incluster_with_uri(incluster_config::kube_dns())
}
clux marked this conversation as resolved.
Show resolved Hide resolved

fn incluster_with_uri(cluster_url: http::uri::Uri) -> Result<Self, InClusterError> {
let default_namespace = incluster_config::load_default_ns()?;
let root_cert = incluster_config::load_cert()?;

Expand Down Expand Up @@ -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
Expand Down