kube_client/client/auth/
oauth.rs1use http_body_util::BodyExt;
2use hyper_util::rt::TokioExecutor;
3use tame_oauth::{
4 gcp::{TokenOrRequest, TokenProvider, TokenProviderWrapper},
5 Token,
6};
7use thiserror::Error;
8
9use crate::client::Body;
10
11#[derive(Error, Debug)]
12pub enum Error {
14 #[error("default provider is configured but invalid: {0}")]
16 InvalidDefaultProviderConfig(#[source] tame_oauth::Error),
17
18 #[error("no provider was found")]
20 NoDefaultProvider,
21
22 #[error("failed to load OAuth credentials file: {0}")]
24 LoadCredentials(#[source] std::io::Error),
25
26 #[error("failed to parse OAuth credentials file: {0}")]
28 ParseCredentials(#[source] serde_json::Error),
29
30 #[error("credentials file had invalid key format: {0}")]
32 InvalidKeyFormat(#[source] tame_oauth::Error),
33
34 #[error("credentials file had invalid RSA key: {0}")]
36 InvalidRsaKey(#[source] tame_oauth::Error),
37
38 #[error("failed to request token: {0}")]
40 RequestToken(#[source] hyper_util::client::legacy::Error),
41
42 #[error("failed to retrieve new credential {0:?}")]
44 RetrieveCredentials(#[source] tame_oauth::Error),
45
46 #[error("failed to parse token: {0}")]
48 ParseToken(#[source] serde_json::Error),
49
50 #[error("failed to concatenate the buffers from response body: {0}")]
52 ConcatBuffers(#[source] hyper::Error),
53
54 #[error("failed to build request: {0}")]
56 BuildRequest(#[source] http::Error),
57
58 #[error("No valid native root CA certificates found")]
60 NoValidNativeRootCA(#[source] std::io::Error),
61
62 #[error("unknown OAuth error: {0}")]
64 Unknown(String),
65
66 #[cfg(feature = "openssl-tls")]
68 #[cfg_attr(docsrs, doc(cfg(feature = "openssl-tls")))]
69 #[error("failed to create OpenSSL HTTPS connector: {0}")]
70 CreateOpensslHttpsConnector(#[source] openssl::error::ErrorStack),
71}
72
73pub struct Gcp {
74 provider: TokenProviderWrapper,
75 scopes: Vec<String>,
76}
77
78impl std::fmt::Debug for Gcp {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 f.debug_struct("Gcp")
81 .field("provider", &"{}".to_owned())
82 .field("scopes", &self.scopes)
83 .finish()
84 }
85}
86
87impl Gcp {
88 pub(crate) fn default_credentials_with_scopes(scopes: Option<&String>) -> Result<Self, Error> {
94 const DEFAULT_SCOPES: &str =
95 "https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/userinfo.email";
96
97 let provider = TokenProviderWrapper::get_default_provider()
98 .map_err(Error::InvalidDefaultProviderConfig)?
99 .ok_or(Error::NoDefaultProvider)?;
100 let scopes = scopes
101 .map(String::to_owned)
102 .unwrap_or_else(|| DEFAULT_SCOPES.to_owned())
103 .split(',')
104 .map(str::to_owned)
105 .collect::<Vec<_>>();
106 Ok(Self { provider, scopes })
107 }
108
109 pub async fn token(&self) -> Result<Token, Error> {
110 match self.provider.get_token(&self.scopes) {
111 Ok(TokenOrRequest::Request {
112 request, scope_hash, ..
113 }) => {
114 #[cfg(not(any(feature = "rustls-tls", feature = "openssl-tls")))]
115 compile_error!(
116 "At least one of rustls-tls or openssl-tls feature must be enabled to use oauth feature"
117 );
118 #[cfg(all(feature = "rustls-tls", not(feature = "webpki-roots")))]
122 let https = hyper_rustls::HttpsConnectorBuilder::new()
123 .with_native_roots()
124 .map_err(Error::NoValidNativeRootCA)?
125 .https_only()
126 .enable_http1()
127 .build();
128 #[cfg(all(feature = "rustls-tls", feature = "webpki-roots"))]
129 let https = hyper_rustls::HttpsConnectorBuilder::new()
130 .with_webpki_roots()
131 .https_only()
132 .enable_http1()
133 .build();
134 #[cfg(all(not(feature = "rustls-tls"), feature = "openssl-tls"))]
135 let https =
136 hyper_openssl::HttpsConnector::new().map_err(Error::CreateOpensslHttpsConnector)?;
137
138 let client = hyper_util::client::legacy::Client::builder(TokioExecutor::new()).build(https);
139
140 let res = client
141 .request(request.map(Body::from))
142 .await
143 .map_err(Error::RequestToken)?;
144 let (parts, body) = res.into_parts();
146 let bytes = body.collect().await.map_err(Error::ConcatBuffers)?.to_bytes();
147 let response = http::Response::from_parts(parts, bytes.to_vec());
148 match self.provider.parse_token_response(scope_hash, response) {
149 Ok(token) => Ok(token),
150
151 Err(err) => Err(match err {
152 tame_oauth::Error::Auth(_) | tame_oauth::Error::HttpStatus(_) => {
153 Error::RetrieveCredentials(err)
154 }
155 tame_oauth::Error::Json(e) => Error::ParseToken(e),
156 err => Error::Unknown(err.to_string()),
157 }),
158 }
159 }
160
161 Ok(TokenOrRequest::Token(token)) => Ok(token),
162
163 Err(err) => match err {
164 tame_oauth::Error::Http(e) => Err(Error::BuildRequest(e)),
165 tame_oauth::Error::InvalidRsaKey(_) => Err(Error::InvalidRsaKey(err)),
166 tame_oauth::Error::InvalidKeyFormat => Err(Error::InvalidKeyFormat(err)),
167 e => Err(Error::Unknown(e.to_string())),
168 },
169 }
170 }
171}