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
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Expand Up @@ -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
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
42 changes: 31 additions & 11 deletions kube-client/src/config/mod.rs
Expand Up @@ -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<Self, InferConfigError> {
let mut config = match Self::from_kubeconfig(&KubeConfigOptions::default()).await {
Err(kubeconfig_err) => {
Expand All @@ -195,8 +197,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::from_cluster_env().map_err(|in_cluster| InferConfigError {
in_cluster,
kubeconfig: kubeconfig_err,
})?
}
Expand All @@ -206,13 +208,32 @@ 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.
/// This matches the behavior of the official Kubernetes client libraries,
/// but it may not be compatible with `rustls`.
///
/// A service account's token must be available in
/// `/var/run/secrets/kubernetes.io/serviceaccount/`.
pub fn from_cluster_env() -> Result<Self, InClusterError> {
let cluster_url = incluster_config::kube_dns();
let uri = incluster_config::try_kube_from_env()?;
Self::load_inluster_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/`.
pub fn from_cluster_dns() -> Result<Self, InClusterError> {
olix0r marked this conversation as resolved.
Show resolved Hide resolved
Self::load_inluster_with_uri(incluster_config::kube_dns())
}

fn load_inluster_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 +399,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