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
Add support for user impersonation #797
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
use std::sync::Arc; | ||
|
||
use http::{header::HeaderName, request::Request, HeaderValue}; | ||
use tower::{Layer, Service}; | ||
|
||
#[derive(Clone)] | ||
/// Layer that adds a static set of extra headers to each request | ||
pub struct ExtraHeadersLayer { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought we can use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah.. SRHL also means that we need to encode the exact number of headers into the type signature (or do a bunch of newtyping to hide it). |
||
pub(crate) headers: Arc<Vec<(HeaderName, HeaderValue)>>, | ||
} | ||
|
||
impl<S> Layer<S> for ExtraHeadersLayer { | ||
type Service = ExtraHeaders<S>; | ||
|
||
fn layer(&self, inner: S) -> Self::Service { | ||
ExtraHeaders { | ||
inner, | ||
headers: self.headers.clone(), | ||
} | ||
} | ||
} | ||
|
||
#[derive(Clone)] | ||
/// Service that adds a static set of extra headers to each request | ||
pub struct ExtraHeaders<S> { | ||
inner: S, | ||
headers: Arc<Vec<(HeaderName, HeaderValue)>>, | ||
} | ||
|
||
impl<S, ReqBody> Service<Request<ReqBody>> for ExtraHeaders<S> | ||
where | ||
S: Service<Request<ReqBody>>, | ||
{ | ||
type Error = S::Error; | ||
type Future = S::Future; | ||
type Response = S::Response; | ||
|
||
fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> { | ||
self.inner.poll_ready(cx) | ||
} | ||
|
||
fn call(&mut self, mut req: Request<ReqBody>) -> Self::Future { | ||
req.headers_mut().extend(self.headers.iter().cloned()); | ||
self.inner.call(req) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -166,22 +166,24 @@ impl Config { | |
/// then if that fails, trying the local kubeconfig. | ||
/// | ||
/// Fails if inference from both sources fails | ||
/// | ||
/// Applies debug overrides, see [`Config::apply_debug_overrides`] for more details | ||
pub async fn infer() -> Result<Self, InferConfigError> { | ||
match Self::from_cluster_env() { | ||
let mut config = match Self::from_cluster_env() { | ||
Err(in_cluster_err) => { | ||
tracing::trace!("No in-cluster config found: {}", in_cluster_err); | ||
tracing::trace!("Falling back to local kubeconfig"); | ||
let config = Self::from_kubeconfig(&KubeConfigOptions::default()) | ||
Self::from_kubeconfig(&KubeConfigOptions::default()) | ||
.await | ||
.map_err(|kubeconfig_err| InferConfigError { | ||
in_cluster: in_cluster_err, | ||
kubeconfig: kubeconfig_err, | ||
})?; | ||
|
||
Ok(config) | ||
})? | ||
} | ||
Ok(success) => Ok(success), | ||
} | ||
Ok(success) => success, | ||
}; | ||
config.apply_debug_overrides(); | ||
Ok(config) | ||
} | ||
|
||
/// Create configuration from the cluster's environment variables | ||
|
@@ -271,6 +273,45 @@ impl Config { | |
}) | ||
} | ||
|
||
/// Override configuration based on environment variables | ||
/// | ||
/// This is only intended for use as a debugging aid, and the specific variables and their behaviour | ||
/// should **not** be considered stable across releases. | ||
/// | ||
/// Currently, the following overrides are supported: | ||
/// | ||
/// - `KUBE_RS_DEBUG_IMPERSONATE_USER`: A Kubernetes user to impersonate, for example: `system:serviceaccount:default:foo` will impersonate the `ServiceAccount` `foo` in the `Namespace` `default` | ||
clux marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// - `KUBE_RS_DEBUG_IMPERSONATE_GROUP`: A Kubernetes group to impersonate, multiple groups may be specified by separating them with commas | ||
nightkr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// - `KUBE_RS_DEBUG_OVERRIDE_URL`: A Kubernetes cluster URL to use rather than the one specified in the config, useful for proxying traffic through `kubectl proxy` | ||
#[tracing::instrument(level = "warn")] | ||
pub fn apply_debug_overrides(&mut self) { | ||
clux marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Log these overrides loudly, to emphasize that this is only a debugging aid, and should not be relied upon in production | ||
clux marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if let Ok(impersonate_user) = std::env::var("KUBE_RS_DEBUG_IMPERSONATE_USER") { | ||
tracing::warn!(?impersonate_user, "impersonating user"); | ||
self.auth_info.impersonate = Some(impersonate_user); | ||
} | ||
if let Ok(impersonate_groups) = std::env::var("KUBE_RS_DEBUG_IMPERSONATE_GROUP") { | ||
let impersonate_groups = impersonate_groups.split(',').map(str::to_string).collect(); | ||
tracing::warn!(?impersonate_groups, "impersonating groups"); | ||
self.auth_info.impersonate_groups = Some(impersonate_groups); | ||
} | ||
if let Ok(url) = std::env::var("KUBE_RS_DEBUG_OVERRIDE_URL") { | ||
tracing::warn!(?url, "overriding cluster URL"); | ||
match url.parse() { | ||
Ok(uri) => { | ||
self.cluster_url = uri; | ||
} | ||
Err(err) => { | ||
tracing::warn!( | ||
?url, | ||
error = &err as &dyn std::error::Error, | ||
"failed to parse override cluster URL, ignoring" | ||
); | ||
} | ||
} | ||
} | ||
Comment on lines
+298
to
+312
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can put the secondary There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nevermind i see you want this as a method to be used with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, the latter. There have also been cases where I have wanted to access the cluster in weird ways involving TCP reverse proxies, so this would have been helpful there as well. |
||
} | ||
|
||
/// Client certificate and private key in PEM. | ||
pub(crate) fn identity_pem(&self) -> Option<Vec<u8>> { | ||
self.auth_info.identity_pem().ok() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be better to add an explicit error for invalid header value with the name.
Error::HttpError
is only used by thesehttps://github.com/kube-rs/kube-rs/blob/18b5316b3a644a22ce64806b6cd2ed75d5e80b03/kube-client/src/client/mod.rs#L340-L345
and I think it should be removed eventually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, but that also feels like a part of the larger error refactoring.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we can do it later.