Skip to content

Commit

Permalink
Added refreshing OIDC ID token as an optional feature (#1229)
Browse files Browse the repository at this point in the history
* Added refreshing OIDC ID token as an optional feature

Signed-off-by: Razz4780 <msmolarekg@gmail.com>

* Feature rename and propagation to 'kube' crate

Signed-off-by: Razz4780 <msmolarekg@gmail.com>

* token_valid test

Signed-off-by: Razz4780 <msmolarekg@gmail.com>

* from_config tests

Signed-off-by: Razz4780 <msmolarekg@gmail.com>

* Exposed errors in docs

Signed-off-by: Razz4780 <msmolarekg@gmail.com>

---------

Signed-off-by: Razz4780 <msmolarekg@gmail.com>
Co-authored-by: Eirik A <sszynrae@gmail.com>
  • Loading branch information
Razz4780 and clux authored Jun 30, 2023
1 parent 56b51f7 commit a6a627c
Show file tree
Hide file tree
Showing 7 changed files with 587 additions and 3 deletions.
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

0 comments on commit a6a627c

Please sign in to comment.