kube_client/client/auth/
oauth.rsuse http_body_util::BodyExt;
use hyper_util::rt::TokioExecutor;
use tame_oauth::{
gcp::{TokenOrRequest, TokenProvider, TokenProviderWrapper},
Token,
};
use thiserror::Error;
use crate::client::Body;
#[derive(Error, Debug)]
pub enum Error {
#[error("default provider is configured but invalid: {0}")]
InvalidDefaultProviderConfig(#[source] tame_oauth::Error),
#[error("no provider was found")]
NoDefaultProvider,
#[error("failed to load OAuth credentials file: {0}")]
LoadCredentials(#[source] std::io::Error),
#[error("failed to parse OAuth credentials file: {0}")]
ParseCredentials(#[source] serde_json::Error),
#[error("credentials file had invalid key format: {0}")]
InvalidKeyFormat(#[source] tame_oauth::Error),
#[error("credentials file had invalid RSA key: {0}")]
InvalidRsaKey(#[source] tame_oauth::Error),
#[error("failed to request token: {0}")]
RequestToken(#[source] hyper_util::client::legacy::Error),
#[error("failed to retrieve new credential {0:?}")]
RetrieveCredentials(#[source] tame_oauth::Error),
#[error("failed to parse token: {0}")]
ParseToken(#[source] serde_json::Error),
#[error("failed to concatenate the buffers from response body: {0}")]
ConcatBuffers(#[source] hyper::Error),
#[error("failed to build request: {0}")]
BuildRequest(#[source] http::Error),
#[error("No valid native root CA certificates found")]
NoValidNativeRootCA(#[source] std::io::Error),
#[error("unknown OAuth error: {0}")]
Unknown(String),
#[cfg(feature = "openssl-tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "openssl-tls")))]
#[error("failed to create OpenSSL HTTPS connector: {0}")]
CreateOpensslHttpsConnector(#[source] openssl::error::ErrorStack),
}
pub struct Gcp {
provider: TokenProviderWrapper,
scopes: Vec<String>,
}
impl std::fmt::Debug for Gcp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Gcp")
.field("provider", &"{}".to_owned())
.field("scopes", &self.scopes)
.finish()
}
}
impl Gcp {
pub(crate) fn default_credentials_with_scopes(scopes: Option<&String>) -> Result<Self, Error> {
const DEFAULT_SCOPES: &str =
"https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/userinfo.email";
let provider = TokenProviderWrapper::get_default_provider()
.map_err(Error::InvalidDefaultProviderConfig)?
.ok_or(Error::NoDefaultProvider)?;
let scopes = scopes
.map(String::to_owned)
.unwrap_or_else(|| DEFAULT_SCOPES.to_owned())
.split(',')
.map(str::to_owned)
.collect::<Vec<_>>();
Ok(Self { provider, scopes })
}
pub async fn token(&self) -> Result<Token, Error> {
match self.provider.get_token(&self.scopes) {
Ok(TokenOrRequest::Request {
request, scope_hash, ..
}) => {
#[cfg(not(any(feature = "rustls-tls", feature = "openssl-tls")))]
compile_error!(
"At least one of rustls-tls or openssl-tls feature must be enabled to use oauth feature"
);
#[cfg(all(feature = "rustls-tls", not(feature = "webpki-roots")))]
let https = hyper_rustls::HttpsConnectorBuilder::new()
.with_native_roots()
.map_err(Error::NoValidNativeRootCA)?
.https_only()
.enable_http1()
.build();
#[cfg(all(feature = "rustls-tls", feature = "webpki-roots"))]
let https = hyper_rustls::HttpsConnectorBuilder::new()
.with_webpki_roots()
.https_only()
.enable_http1()
.build();
#[cfg(all(not(feature = "rustls-tls"), feature = "openssl-tls"))]
let https =
hyper_openssl::HttpsConnector::new().map_err(Error::CreateOpensslHttpsConnector)?;
let client = hyper_util::client::legacy::Client::builder(TokioExecutor::new()).build(https);
let res = client
.request(request.map(Body::from))
.await
.map_err(Error::RequestToken)?;
let (parts, body) = res.into_parts();
let bytes = body.collect().await.map_err(Error::ConcatBuffers)?.to_bytes();
let response = http::Response::from_parts(parts, bytes.to_vec());
match self.provider.parse_token_response(scope_hash, response) {
Ok(token) => Ok(token),
Err(err) => Err(match err {
tame_oauth::Error::Auth(_) | tame_oauth::Error::HttpStatus(_) => {
Error::RetrieveCredentials(err)
}
tame_oauth::Error::Json(e) => Error::ParseToken(e),
err => Error::Unknown(err.to_string()),
}),
}
}
Ok(TokenOrRequest::Token(token)) => Ok(token),
Err(err) => match err {
tame_oauth::Error::Http(e) => Err(Error::BuildRequest(e)),
tame_oauth::Error::InvalidRsaKey(_) => Err(Error::InvalidRsaKey(err)),
tame_oauth::Error::InvalidKeyFormat => Err(Error::InvalidKeyFormat(err)),
e => Err(Error::Unknown(e.to_string())),
},
}
}
}