diff --git a/kube-client/src/client/auth/mod.rs b/kube-client/src/client/auth/mod.rs index 49337266a..cc8cd179b 100644 --- a/kube-client/src/client/auth/mod.rs +++ b/kube-client/src/client/auth/mod.rs @@ -90,6 +90,7 @@ pub(crate) enum Auth { Basic(String, SecretString), Bearer(SecretString), RefreshableToken(RefreshableToken), + Certificate(String, SecretString), } // Token file reference. Reloads at least once per minute. @@ -184,7 +185,7 @@ impl RefreshableToken { if Utc::now() + Duration::seconds(60) >= locked_data.1 { // TODO Improve refreshing exec to avoid `Auth::try_from` match Auth::try_from(&locked_data.2)? { - Auth::None | Auth::Basic(_, _) | Auth::Bearer(_) => { + Auth::None | Auth::Basic(_, _) | Auth::Bearer(_) | Auth::Certificate(_, _) => { return Err(Error::UnrefreshableTokenResponse); } @@ -291,6 +292,11 @@ impl TryFrom<&AuthInfo> for Auth { if let Some(exec) = &auth_info.exec { let creds = auth_exec(exec)?; let status = creds.status.ok_or(Error::ExecPluginFailed)?; + if let (Some(client_certificate_data), Some(client_key_data)) = + (status.client_certificate_data, status.client_key_data) + { + return Ok(Self::Certificate(client_certificate_data, client_key_data.into())); + } let expiration = status .expiration_timestamp .map(|ts| ts.parse()) diff --git a/kube-client/src/client/config_ext.rs b/kube-client/src/client/config_ext.rs index 35cac8c3d..7871b01dc 100644 --- a/kube-client/src/client/config_ext.rs +++ b/kube-client/src/client/config_ext.rs @@ -144,6 +144,7 @@ impl ConfigExt for Config { Auth::RefreshableToken(refreshable) => { Some(AuthLayer(Either::B(AsyncFilterLayer::new(refreshable)))) } + Auth::Certificate(_client_certificate_data, _client_key_data) => None, }) } @@ -174,8 +175,9 @@ impl ConfigExt for Config { #[cfg(feature = "rustls-tls")] fn rustls_client_config(&self) -> Result { + let identity = self.exec_identity_pem().or_else(|| self.identity_pem()); tls::rustls_tls::rustls_client_config( - self.identity_pem().as_deref(), + identity.as_deref(), self.root_cert.as_deref(), self.accept_invalid_certs, ) @@ -192,7 +194,8 @@ impl ConfigExt for Config { #[cfg(feature = "openssl-tls")] fn openssl_ssl_connector_builder(&self) -> Result { - tls::openssl_tls::ssl_connector_builder(self.identity_pem().as_ref(), self.root_cert.as_ref()) + let identity = self.exec_identity_pem().or_else(|| self.identity_pem()); + tls::openssl_tls::ssl_connector_builder(identity.as_ref(), self.root_cert.as_ref()) .map_err(|e| Error::OpensslTls(tls::openssl_tls::Error::CreateSslConnector(e))) } @@ -220,3 +223,20 @@ impl ConfigExt for Config { Ok(https) } } + +impl Config { + // This is necessary to retrieve an identity when an exec plugin + // returns a client certificate and key instead of a token. + // This has be to be checked on TLS configuration vs tokens + // which can be added in as an AuthLayer. + fn exec_identity_pem(&self) -> Option> { + match Auth::try_from(&self.auth_info) { + Ok(Auth::Certificate(client_certificate_data, client_key_data)) => { + let mut buffer = client_key_data.expose_secret().as_bytes().to_vec(); + buffer.extend_from_slice(client_certificate_data.as_bytes()); + Some(buffer) + } + _ => None, + } + } +}