Skip to content

Commit 55e865c

Browse files
authored
add valid_until to Client (#1707)
* add `valid_until` to Client Signed-off-by: goenning <[email protected]> * fix unit tests Signed-off-by: goenning <[email protected]> --------- Signed-off-by: goenning <[email protected]>
1 parent b27cbcd commit 55e865c

File tree

4 files changed

+58
-17
lines changed

4 files changed

+58
-17
lines changed

kube-client/src/client/auth/mod.rs

+14-7
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ pub(crate) enum Auth {
115115
Basic(String, SecretString),
116116
Bearer(SecretString),
117117
RefreshableToken(RefreshableToken),
118-
Certificate(String, SecretString),
118+
Certificate(String, SecretString, Option<DateTime<Utc>>),
119119
}
120120

121121
// Token file reference. Reloads at least once per minute.
@@ -227,7 +227,7 @@ impl RefreshableToken {
227227
if Utc::now() + SIXTY_SEC >= locked_data.1 {
228228
// TODO Improve refreshing exec to avoid `Auth::try_from`
229229
match Auth::try_from(&locked_data.2)? {
230-
Auth::None | Auth::Basic(_, _) | Auth::Bearer(_) | Auth::Certificate(_, _) => {
230+
Auth::None | Auth::Basic(_, _) | Auth::Bearer(_) | Auth::Certificate(_, _, _) => {
231231
return Err(Error::UnrefreshableTokenResponse);
232232
}
233233

@@ -350,16 +350,23 @@ impl TryFrom<&AuthInfo> for Auth {
350350
if let Some(exec) = &auth_info.exec {
351351
let creds = auth_exec(exec)?;
352352
let status = creds.status.ok_or(Error::ExecPluginFailed)?;
353-
if let (Some(client_certificate_data), Some(client_key_data)) =
354-
(status.client_certificate_data, status.client_key_data)
355-
{
356-
return Ok(Self::Certificate(client_certificate_data, client_key_data.into()));
357-
}
358353
let expiration = status
359354
.expiration_timestamp
360355
.map(|ts| ts.parse())
361356
.transpose()
362357
.map_err(Error::MalformedTokenExpirationDate)?;
358+
359+
360+
if let (Some(client_certificate_data), Some(client_key_data)) =
361+
(status.client_certificate_data, status.client_key_data)
362+
{
363+
return Ok(Self::Certificate(
364+
client_certificate_data,
365+
client_key_data.into(),
366+
expiration,
367+
));
368+
}
369+
363370
match (status.token.map(SecretString::from), expiration) {
364371
(Some(token), Some(expire)) => Ok(Self::RefreshableToken(RefreshableToken::Exec(Arc::new(
365372
Mutex::new((token, expire, auth_info.clone())),

kube-client/src/client/builder.rs

+23-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use bytes::Bytes;
2+
use chrono::{DateTime, Utc};
23
use http::{header::HeaderMap, Request, Response};
34
use hyper::{
45
body::Incoming,
@@ -30,6 +31,7 @@ pub type DynBody = dyn http_body::Body<Data = Bytes, Error = BoxError> + Send +
3031
pub struct ClientBuilder<Svc> {
3132
service: Svc,
3233
default_ns: String,
34+
valid_until: Option<DateTime<Utc>>,
3335
}
3436

3537
impl<Svc> ClientBuilder<Svc> {
@@ -44,6 +46,7 @@ impl<Svc> ClientBuilder<Svc> {
4446
Self {
4547
service,
4648
default_ns: default_namespace.into(),
49+
valid_until: None,
4750
}
4851
}
4952

@@ -52,10 +55,21 @@ impl<Svc> ClientBuilder<Svc> {
5255
let Self {
5356
service: stack,
5457
default_ns,
58+
valid_until,
5559
} = self;
5660
ClientBuilder {
5761
service: layer.layer(stack),
5862
default_ns,
63+
valid_until,
64+
}
65+
}
66+
67+
/// Sets an expiration timestamp for the client.
68+
pub fn with_valid_until(self, valid_until: Option<DateTime<Utc>>) -> Self {
69+
ClientBuilder {
70+
service: self.service,
71+
default_ns: self.default_ns,
72+
valid_until,
5973
}
6074
}
6175

@@ -68,7 +82,7 @@ impl<Svc> ClientBuilder<Svc> {
6882
B: http_body::Body<Data = bytes::Bytes> + Send + 'static,
6983
B::Error: Into<BoxError>,
7084
{
71-
Client::new(self.service, self.default_ns)
85+
Client::new(self.service, self.default_ns).with_valid_until(self.valid_until)
7286
}
7387
}
7488

@@ -242,15 +256,21 @@ where
242256
.map_err(BoxError::from)
243257
.service(client);
244258

245-
Ok(ClientBuilder::new(
259+
260+
let (_, expiration) = config.exec_identity_pem();
261+
262+
let client = ClientBuilder::new(
246263
BoxService::new(
247264
MapResponseBodyLayer::new(|body| {
248265
Box::new(http_body_util::BodyExt::map_err(body, BoxError::from)) as Box<DynBody>
249266
})
250267
.layer(service),
251268
),
252269
default_ns,
253-
))
270+
)
271+
.with_valid_until(expiration);
272+
273+
Ok(client)
254274
}
255275

256276
#[cfg(test)]

kube-client/src/client/config_ext.rs

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::sync::Arc;
22

3+
use chrono::{DateTime, Utc};
34
use http::{header::HeaderName, HeaderValue};
45
#[cfg(feature = "openssl-tls")] use hyper::rt::{Read, Write};
56
use hyper_util::client::legacy::connect::HttpConnector;
@@ -176,7 +177,7 @@ impl ConfigExt for Config {
176177
Auth::RefreshableToken(refreshable) => {
177178
Some(AuthLayer(Either::Right(AsyncFilterLayer::new(refreshable))))
178179
}
179-
Auth::Certificate(_client_certificate_data, _client_key_data) => None,
180+
Auth::Certificate(_client_certificate_data, _client_key_data, _) => None,
180181
})
181182
}
182183

@@ -207,7 +208,7 @@ impl ConfigExt for Config {
207208

208209
#[cfg(feature = "rustls-tls")]
209210
fn rustls_client_config(&self) -> Result<rustls::ClientConfig> {
210-
let identity = self.exec_identity_pem().or_else(|| self.identity_pem());
211+
let identity = self.exec_identity_pem().0.or_else(|| self.identity_pem());
211212
tls::rustls_tls::rustls_client_config(
212213
identity.as_deref(),
213214
self.root_cert.as_deref(),
@@ -249,7 +250,7 @@ impl ConfigExt for Config {
249250

250251
#[cfg(feature = "openssl-tls")]
251252
fn openssl_ssl_connector_builder(&self) -> Result<openssl::ssl::SslConnectorBuilder> {
252-
let identity = self.exec_identity_pem().or_else(|| self.identity_pem());
253+
let identity = self.exec_identity_pem().0.or_else(|| self.identity_pem());
253254
// TODO: pass self.tls_server_name for openssl
254255
tls::openssl_tls::ssl_connector_builder(identity.as_ref(), self.root_cert.as_ref())
255256
.map_err(|e| Error::OpensslTls(tls::openssl_tls::Error::CreateSslConnector(e)))
@@ -295,18 +296,18 @@ impl Config {
295296
// returns a client certificate and key instead of a token.
296297
// This has be to be checked on TLS configuration vs tokens
297298
// which can be added in as an AuthLayer.
298-
fn exec_identity_pem(&self) -> Option<Vec<u8>> {
299+
pub(crate) fn exec_identity_pem(&self) -> (Option<Vec<u8>>, Option<DateTime<Utc>>) {
299300
match Auth::try_from(&self.auth_info) {
300-
Ok(Auth::Certificate(client_certificate_data, client_key_data)) => {
301+
Ok(Auth::Certificate(client_certificate_data, client_key_data, expiratiom)) => {
301302
const NEW_LINE: u8 = b'\n';
302303

303304
let mut buffer = client_key_data.expose_secret().as_bytes().to_vec();
304305
buffer.push(NEW_LINE);
305306
buffer.extend_from_slice(client_certificate_data.as_bytes());
306307
buffer.push(NEW_LINE);
307-
Some(buffer)
308+
(Some(buffer), expiratiom)
308309
}
309-
_ => None,
310+
_ => (None, None),
310311
}
311312
}
312313
}

kube-client/src/client/mod.rs

+13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//!
88
//! The [`Client`] can also be used with [`Discovery`](crate::Discovery) to dynamically
99
//! retrieve the resources served by the kubernetes API.
10+
use chrono::{DateTime, Utc};
1011
use either::{Either, Left, Right};
1112
use futures::{future::BoxFuture, AsyncBufRead, StreamExt, TryStream, TryStreamExt};
1213
use http::{self, Request, Response};
@@ -78,6 +79,7 @@ pub struct Client {
7879
// - `BoxFuture` for dynamic response future type
7980
inner: Buffer<Request<Body>, BoxFuture<'static, Result<Response<Body>, BoxError>>>,
8081
default_ns: String,
82+
valid_until: Option<DateTime<Utc>>,
8183
}
8284

8385
/// Represents a WebSocket connection.
@@ -154,9 +156,20 @@ impl Client {
154156
Self {
155157
inner: Buffer::new(BoxService::new(service), 1024),
156158
default_ns: default_namespace.into(),
159+
valid_until: None,
157160
}
158161
}
159162

163+
/// Sets an expiration timestamp to the client, which has to be checked by the user using [`Client::valid_until`] function.
164+
pub fn with_valid_until(self, valid_until: Option<DateTime<Utc>>) -> Self {
165+
Client { valid_until, ..self }
166+
}
167+
168+
/// Get the expiration timestamp of the client, if it has been set.
169+
pub fn valid_until(&self) -> &Option<DateTime<Utc>> {
170+
&self.valid_until
171+
}
172+
160173
/// Create and initialize a [`Client`] using the inferred configuration.
161174
///
162175
/// Will use [`Config::infer`] which attempts to load the local kubeconfig first,

0 commit comments

Comments
 (0)