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

Added refreshing OIDC ID token as an optional feature #1229

Merged
merged 7 commits into from
Jun 30, 2023
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
3 changes: 2 additions & 1 deletion examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ license = "Apache-2.0"
release = false

[features]
default = ["openssl-tls", "kubederive", "ws", "latest", "runtime"]
default = ["openssl-tls", "kubederive", "ws", "latest", "runtime", "refresh"]
kubederive = ["kube/derive"]
openssl-tls = ["kube/client", "kube/openssl-tls"]
rustls-tls = ["kube/client", "kube/rustls-tls"]
runtime = ["kube/runtime", "kube/unstable-runtime"]
refresh = ["kube/oauth", "kube/oidc"]
ws = ["kube/ws"]
latest = ["k8s-openapi/v1_26"]

Expand Down
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fmt:
rustfmt +nightly --edition 2021 $(find . -type f -iname *.rs)

doc:
RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --lib --workspace --features=derive,ws,oauth,jsonpatch,client,derive,runtime,admission,k8s-openapi/v1_26,unstable-runtime --open
RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --lib --workspace --features=derive,ws,oauth,oidc,jsonpatch,client,derive,runtime,admission,k8s-openapi/v1_26,unstable-runtime --open

deny:
# might require rm Cargo.lock first to match CI
Expand Down
4 changes: 3 additions & 1 deletion kube-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ rustls-tls = ["rustls", "rustls-pemfile", "hyper-rustls"]
openssl-tls = ["openssl", "hyper-openssl"]
ws = ["client", "tokio-tungstenite", "rand", "kube-core/ws", "tokio/macros"]
oauth = ["client", "tame-oauth"]
oidc = ["client", "form_urlencoded"]
gzip = ["client", "tower-http/decompression-gzip"]
client = ["config", "__non_core", "hyper", "http-body", "tower", "tower-http", "hyper-timeout", "pin-project", "chrono", "jsonpath_lib", "bytes", "futures", "tokio", "tokio-util", "either"]
jsonpatch = ["kube-core/jsonpatch"]
Expand All @@ -31,7 +32,7 @@ config = ["__non_core", "pem", "dirs"]
__non_core = ["tracing", "serde_yaml", "base64"]

[package.metadata.docs.rs]
features = ["client", "rustls-tls", "openssl-tls", "ws", "oauth", "jsonpatch", "admission", "k8s-openapi/v1_26"]
features = ["client", "rustls-tls", "openssl-tls", "ws", "oauth", "oidc", "jsonpatch", "admission", "k8s-openapi/v1_26"]
# Define the configuration attribute `docsrs`. Used to enable `doc_cfg` feature.
rustdoc-args = ["--cfg", "docsrs"]

Expand Down Expand Up @@ -68,6 +69,7 @@ rand = { version = "0.8.3", optional = true }
secrecy = { version = "0.8.0", features = ["alloc", "serde"] }
tracing = { version = "0.1.36", features = ["log"], optional = true }
hyper-openssl = { version = "0.9.2", optional = true }
form_urlencoded = { version = "1.2.0", optional = true }

[dependencies.k8s-openapi]
version = "0.18.0"
Expand Down
37 changes: 37 additions & 0 deletions kube-client/src/client/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ use crate::config::{AuthInfo, AuthProviderConfig, ExecConfig, ExecInteractiveMod

#[cfg(feature = "oauth")] mod oauth;
#[cfg(feature = "oauth")] pub use oauth::Error as OAuthError;
#[cfg(feature = "oidc")] mod oidc;
#[cfg(feature = "oidc")] pub use oidc::errors as oidc_errors;
#[cfg(target_os = "windows")] use std::os::windows::process::CommandExt;

#[derive(Error, Debug)]
Expand Down Expand Up @@ -91,6 +93,12 @@ pub enum Error {
#[cfg_attr(docsrs, doc(cfg(feature = "oauth")))]
#[error("failed OAuth: {0}")]
OAuth(#[source] OAuthError),

/// OIDC error
#[cfg(feature = "oidc")]
#[cfg_attr(docsrs, doc(cfg(feature = "oidc")))]
#[error("failed OIDC: {0}")]
Oidc(#[source] oidc_errors::Error),
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -164,6 +172,8 @@ pub enum RefreshableToken {
File(Arc<RwLock<TokenFile>>),
#[cfg(feature = "oauth")]
GcpOauth(Arc<Mutex<oauth::Gcp>>),
#[cfg(feature = "oidc")]
Oidc(Arc<Mutex<oidc::Oidc>>),
}

// For use with `AsyncFilterLayer` to add `Authorization` header with a refreshed token.
Expand Down Expand Up @@ -212,6 +222,8 @@ impl RefreshableToken {
Auth::RefreshableToken(RefreshableToken::File(_)) => unreachable!(),
#[cfg(feature = "oauth")]
Auth::RefreshableToken(RefreshableToken::GcpOauth(_)) => unreachable!(),
#[cfg(feature = "oidc")]
Auth::RefreshableToken(RefreshableToken::Oidc(_)) => unreachable!(),
}
}

Expand All @@ -236,6 +248,12 @@ impl RefreshableToken {
let token = (*gcp_oauth).token().await.map_err(Error::OAuth)?;
bearer_header(&token.access_token)
}

#[cfg(feature = "oidc")]
RefreshableToken::Oidc(oidc) => {
let token = oidc.lock().await.id_token().await.map_err(Error::Oidc)?;
bearer_header(&token)
}
}
}
}
Expand All @@ -255,6 +273,14 @@ impl TryFrom<&AuthInfo> for Auth {
fn try_from(auth_info: &AuthInfo) -> Result<Self, Self::Error> {
if let Some(provider) = &auth_info.auth_provider {
match token_from_provider(provider)? {
#[cfg(feature = "oidc")]
ProviderToken::Oidc(oidc) => {
return Ok(Self::RefreshableToken(RefreshableToken::Oidc(Arc::new(
Mutex::new(oidc),
))));
}

#[cfg(not(feature = "oidc"))]
ProviderToken::Oidc(token) => {
return Ok(Self::Bearer(SecretString::from(token)));
}
Expand Down Expand Up @@ -327,6 +353,9 @@ impl TryFrom<&AuthInfo> for Auth {

// We need to differentiate providers because the keys/formats to store token expiration differs.
enum ProviderToken {
#[cfg(feature = "oidc")]
Oidc(oidc::Oidc),
#[cfg(not(feature = "oidc"))]
Oidc(String),
// "access-token", "expiry" (RFC3339)
GcpCommand(String, Option<DateTime<Utc>>),
Expand All @@ -350,6 +379,14 @@ fn token_from_provider(provider: &AuthProviderConfig) -> Result<ProviderToken, E
}
}

#[cfg(feature = "oidc")]
fn token_from_oidc_provider(provider: &AuthProviderConfig) -> Result<ProviderToken, Error> {
oidc::Oidc::from_config(&provider.config)
.map_err(Error::Oidc)
.map(ProviderToken::Oidc)
}

#[cfg(not(feature = "oidc"))]
fn token_from_oidc_provider(provider: &AuthProviderConfig) -> Result<ProviderToken, Error> {
match provider.config.get("id-token") {
Some(id_token) => Ok(ProviderToken::Oidc(id_token.clone())),
Expand Down
Loading