Skip to main content

kube_client/client/
tls.rs

1#[cfg(feature = "rustls-tls")]
2pub mod rustls_tls {
3    use std::{
4        path::PathBuf,
5        sync::{Arc, RwLock},
6        time::{Duration, Instant},
7    };
8
9    use hyper_rustls::ConfigBuilderExt;
10    use rustls::{
11        self, ClientConfig, DigitallySignedStruct, RootCertStore,
12        client::{
13            WebPkiServerVerifier,
14            danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
15        },
16        pki_types::{CertificateDer, InvalidDnsNameError, PrivateKeyDer, ServerName},
17    };
18    use thiserror::Error;
19
20    /// Errors from Rustls
21    #[derive(Debug, Error)]
22    pub enum Error {
23        /// Identity PEM is invalid
24        #[error("identity PEM is invalid: {0}")]
25        InvalidIdentityPem(#[source] rustls::pki_types::pem::Error),
26
27        /// Identity PEM is missing a private key: the key must be PKCS8 or RSA/PKCS1
28        #[error("identity PEM is missing a private key: the key must be PKCS8 or RSA/PKCS1")]
29        MissingPrivateKey,
30
31        /// Identity PEM is missing certificate
32        #[error("identity PEM is missing certificate")]
33        MissingCertificate,
34
35        /// Invalid private key
36        #[error("invalid private key: {0}")]
37        InvalidPrivateKey(#[source] rustls::Error),
38
39        /// Unknown private key format
40        #[error("unknown private key format")]
41        UnknownPrivateKeyFormat,
42
43        // Using type-erased error to avoid depending on webpki
44        /// Failed to add a root certificate
45        #[error("failed to add a root certificate: {0}")]
46        AddRootCertificate(#[source] Box<dyn std::error::Error + Send + Sync>),
47
48        /// No valid native root CA certificates found
49        #[error("no valid native root CA certificates found")]
50        NoValidNativeRootCA(#[source] std::io::Error),
51
52        /// Invalid server name
53        #[error("invalid server name: {0}")]
54        InvalidServerName(#[source] InvalidDnsNameError),
55    }
56
57    /// Create `rustls::ClientConfig`.
58    pub fn rustls_client_config(
59        identity_pem: Option<&[u8]>,
60        root_certs: Option<&[Vec<u8>]>,
61        accept_invalid: bool,
62    ) -> Result<ClientConfig, Error> {
63        let config_builder = if let Some(certs) = root_certs {
64            ClientConfig::builder().with_root_certificates(root_store(certs)?)
65        } else {
66            #[cfg(feature = "webpki-roots")]
67            {
68                // Use WebPKI roots.
69                ClientConfig::builder().with_webpki_roots()
70            }
71            #[cfg(not(feature = "webpki-roots"))]
72            {
73                // Use native roots. This will panic on Android and iOS.
74                ClientConfig::builder()
75                    .with_native_roots()
76                    .map_err(Error::NoValidNativeRootCA)?
77            }
78        };
79
80        let mut client_config = if let Some((chain, pkey)) = identity_pem.map(client_auth).transpose()? {
81            config_builder
82                .with_client_auth_cert(chain, pkey)
83                .map_err(Error::InvalidPrivateKey)?
84        } else {
85            config_builder.with_no_client_auth()
86        };
87
88        if accept_invalid {
89            client_config
90                .dangerous()
91                .set_certificate_verifier(std::sync::Arc::new(NoCertificateVerification {}));
92        }
93        Ok(client_config)
94    }
95
96    fn root_store(root_certs: &[Vec<u8>]) -> Result<rustls::RootCertStore, Error> {
97        let mut root_store = rustls::RootCertStore::empty();
98        for der in root_certs {
99            root_store
100                .add(CertificateDer::from(der.to_owned()))
101                .map_err(|e| Error::AddRootCertificate(Box::new(e)))?;
102        }
103        Ok(root_store)
104    }
105
106    /// A [`ServerCertVerifier`] that re-reads the CA bundle file roughly once
107    /// per minute to pick up CA rotation.
108    ///
109    /// This mirrors the token-file reload behaviour in
110    /// [`crate::client::auth::TokenFile`]: the service-account `ca.crt` lives
111    /// in the same projected volume and rotates under the same mechanism.
112    /// Existing TLS connections are unaffected (they already handshook); new
113    /// connections use the freshly loaded roots.
114    ///
115    /// If a reload fails (file missing, parse error), the last successfully
116    /// loaded verifier is retained — same policy as `TokenFile`, per
117    /// <https://github.com/kubernetes/kubernetes/issues/68164>.
118    #[derive(Debug)]
119    pub(crate) struct ReloadingVerifier {
120        path: PathBuf,
121        inner: RwLock<(Arc<WebPkiServerVerifier>, Instant)>,
122    }
123
124    impl ReloadingVerifier {
125        const RELOAD_INTERVAL: Duration = Duration::from_secs(60);
126
127        pub(crate) fn new(path: PathBuf) -> Result<Self, Error> {
128            let verifier = Self::load(&path)?;
129            Ok(Self {
130                path,
131                inner: RwLock::new((verifier, Instant::now())),
132            })
133        }
134
135        fn load(path: &PathBuf) -> Result<Arc<WebPkiServerVerifier>, Error> {
136            let pem = std::fs::read(path).map_err(|e| Error::AddRootCertificate(Box::new(e)))?;
137            let ders = crate::config::certs(&pem).map_err(|e| Error::AddRootCertificate(Box::new(e)))?;
138            let mut store = RootCertStore::empty();
139            for der in ders {
140                store
141                    .add(CertificateDer::from(der))
142                    .map_err(|e| Error::AddRootCertificate(Box::new(e)))?;
143            }
144            WebPkiServerVerifier::builder(Arc::new(store))
145                .build()
146                .map_err(|e| Error::AddRootCertificate(Box::new(e)))
147        }
148
149        fn current(&self) -> Arc<WebPkiServerVerifier> {
150            {
151                let guard = self.inner.read().unwrap_or_else(|e| e.into_inner());
152                if guard.1.elapsed() < Self::RELOAD_INTERVAL {
153                    return guard.0.clone();
154                }
155            }
156            let mut guard = self.inner.write().unwrap_or_else(|e| e.into_inner());
157            if guard.1.elapsed() < Self::RELOAD_INTERVAL {
158                return guard.0.clone();
159            }
160            if let Ok(fresh) = Self::load(&self.path) {
161                guard.0 = fresh;
162            } else {
163                tracing::warn!(path = ?self.path, "failed to reload CA bundle; keeping stale roots");
164            }
165            guard.1 = Instant::now();
166            guard.0.clone()
167        }
168    }
169
170    impl ServerCertVerifier for ReloadingVerifier {
171        fn verify_server_cert(
172            &self,
173            end_entity: &CertificateDer,
174            intermediates: &[CertificateDer],
175            server_name: &ServerName,
176            ocsp_response: &[u8],
177            now: rustls::pki_types::UnixTime,
178        ) -> Result<ServerCertVerified, rustls::Error> {
179            self.current()
180                .verify_server_cert(end_entity, intermediates, server_name, ocsp_response, now)
181        }
182
183        fn verify_tls12_signature(
184            &self,
185            message: &[u8],
186            cert: &CertificateDer,
187            dss: &DigitallySignedStruct,
188        ) -> Result<HandshakeSignatureValid, rustls::Error> {
189            self.current().verify_tls12_signature(message, cert, dss)
190        }
191
192        fn verify_tls13_signature(
193            &self,
194            message: &[u8],
195            cert: &CertificateDer,
196            dss: &DigitallySignedStruct,
197        ) -> Result<HandshakeSignatureValid, rustls::Error> {
198            self.current().verify_tls13_signature(message, cert, dss)
199        }
200
201        fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
202            self.current().supported_verify_schemes()
203        }
204    }
205
206    #[cfg(test)]
207    mod tests {
208        use super::*;
209
210        // EC P-256 self-signed CAs, valid until 2126. Regenerate with:
211        //   openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
212        //     -keyout /dev/null -out ca.pem -days 36500 -subj "/CN=test-ca-N"
213        const CA1: &str = "-----BEGIN CERTIFICATE-----
214MIIBgDCCASWgAwIBAgIUVrQf5d//S01a0fbXxYRIx9wc0VQwCgYIKoZIzj0EAwIw
215FDESMBAGA1UEAwwJdGVzdC1jYS0xMCAXDTI2MDMwNDEzMDk1MFoYDzIxMjYwMjA4
216MTMwOTUwWjAUMRIwEAYDVQQDDAl0ZXN0LWNhLTEwWTATBgcqhkjOPQIBBggqhkjO
217PQMBBwNCAARg57mWJPDsAIEQAgXqMOOfjMQP+PE9HqcZobycO8z94r/uRuV0wKx/
2180SvMsKFtnreut0bjgFtmZaWY+6d87Is9o1MwUTAdBgNVHQ4EFgQUjtGuhkM7LtHB
219gMPCJIxMwbY69OQwHwYDVR0jBBgwFoAUjtGuhkM7LtHBgMPCJIxMwbY69OQwDwYD
220VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNJADBGAiEAj/WzNVJDg/cBtLqQVM77
221tkB+QyIXLG3Vi9Xj1YfW9QECIQDDFW8yFtgLeCg2Zhr4xQNq3/24r/01kI2rjFPO
222xBkDMw==
223-----END CERTIFICATE-----
224";
225        const CA2: &str = "-----BEGIN CERTIFICATE-----
226MIIBfjCCASWgAwIBAgIUZ7Qsiwan2joRz01p25/cy1XNNiwwCgYIKoZIzj0EAwIw
227FDESMBAGA1UEAwwJdGVzdC1jYS0yMCAXDTI2MDMwNDEzMDk1MFoYDzIxMjYwMjA4
228MTMwOTUwWjAUMRIwEAYDVQQDDAl0ZXN0LWNhLTIwWTATBgcqhkjOPQIBBggqhkjO
229PQMBBwNCAARJle2/yiOD5zp0UkjZg9Yy6ZHBItTLrqv/uzB2YMQg03frnqEUMzSV
230mFinosBcGpX/dPGfHNPhBMOpHmlocZu9o1MwUTAdBgNVHQ4EFgQUsqG0hSGDYsz2
231eGIsLIwJnCR5SFIwHwYDVR0jBBgwFoAUsqG0hSGDYsz2eGIsLIwJnCR5SFIwDwYD
232VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBEAiApvLu9DIC3/K/+G9ooOm75
233a72Cjw62aM8NfPe7ILs8SgIgL0VHe6ksTyB176RECCm3MJVnlhOop6b1tNvxjrru
234FRU=
235-----END CERTIFICATE-----
236";
237
238        fn expire(v: &ReloadingVerifier) {
239            // Can't move Instant backwards; instead, reach past the guard.
240            // The test pokes private state the same way auth::tests::token_file does.
241            v.inner.write().unwrap().1 = Instant::now().checked_sub(Duration::from_secs(120)).unwrap();
242        }
243
244        #[test]
245        fn reloading_verifier() {
246            #[cfg(feature = "aws-lc-rs")]
247            let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
248
249            let file = tempfile::NamedTempFile::new().unwrap();
250            std::fs::write(file.path(), CA1).unwrap();
251
252            let verifier = ReloadingVerifier::new(file.path().to_path_buf()).unwrap();
253            let first = verifier.current();
254
255            // File changed but we're still within the reload interval: no reload.
256            std::fs::write(file.path(), CA2).unwrap();
257            assert!(Arc::ptr_eq(&verifier.current(), &first));
258
259            // Force expiry: reload picks up CA2.
260            expire(&verifier);
261            let second = verifier.current();
262            assert!(!Arc::ptr_eq(&second, &first));
263
264            // File gone, expired again: keep stale verifier.
265            drop(file);
266            expire(&verifier);
267            assert!(Arc::ptr_eq(&verifier.current(), &second));
268        }
269    }
270
271    fn client_auth(data: &[u8]) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>), Error> {
272        use rustls::pki_types::pem::{self, SectionKind};
273
274        let mut cert_chain = Vec::new();
275        let mut pkcs8_key = None;
276        let mut pkcs1_key = None;
277        let mut sec1_key = None;
278        let mut reader = std::io::Cursor::new(data);
279        while let Some((kind, der)) = pem::from_buf(&mut reader).map_err(Error::InvalidIdentityPem)? {
280            match kind {
281                SectionKind::Certificate => cert_chain.push(der.into()),
282                SectionKind::PrivateKey => pkcs8_key = Some(PrivateKeyDer::Pkcs8(der.into())),
283                SectionKind::RsaPrivateKey => pkcs1_key = Some(PrivateKeyDer::Pkcs1(der.into())),
284                SectionKind::EcPrivateKey => sec1_key = Some(PrivateKeyDer::Sec1(der.into())),
285                _ => return Err(Error::UnknownPrivateKeyFormat),
286            }
287        }
288
289        let private_key = pkcs8_key
290            .or(pkcs1_key)
291            .or(sec1_key)
292            .ok_or(Error::MissingPrivateKey)?;
293        if cert_chain.is_empty() {
294            return Err(Error::MissingCertificate);
295        }
296        Ok((cert_chain, private_key))
297    }
298
299    #[derive(Debug)]
300    struct NoCertificateVerification {}
301
302    impl ServerCertVerifier for NoCertificateVerification {
303        fn verify_server_cert(
304            &self,
305            _end_entity: &CertificateDer,
306            _intermediates: &[CertificateDer],
307            _server_name: &ServerName,
308            _ocsp_response: &[u8],
309            _now: rustls::pki_types::UnixTime,
310        ) -> Result<ServerCertVerified, rustls::Error> {
311            tracing::warn!("Server cert bypassed");
312            Ok(ServerCertVerified::assertion())
313        }
314
315        fn verify_tls13_signature(
316            &self,
317            _message: &[u8],
318            _cert: &CertificateDer,
319            _dss: &DigitallySignedStruct,
320        ) -> Result<HandshakeSignatureValid, rustls::Error> {
321            Ok(HandshakeSignatureValid::assertion())
322        }
323
324        fn verify_tls12_signature(
325            &self,
326            _message: &[u8],
327            _cert: &CertificateDer,
328            _dss: &DigitallySignedStruct,
329        ) -> Result<HandshakeSignatureValid, rustls::Error> {
330            Ok(HandshakeSignatureValid::assertion())
331        }
332
333        fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
334            use rustls::SignatureScheme;
335            vec![
336                SignatureScheme::RSA_PKCS1_SHA1,
337                SignatureScheme::ECDSA_SHA1_Legacy,
338                SignatureScheme::RSA_PKCS1_SHA256,
339                SignatureScheme::ECDSA_NISTP256_SHA256,
340                SignatureScheme::RSA_PKCS1_SHA384,
341                SignatureScheme::ECDSA_NISTP384_SHA384,
342                SignatureScheme::RSA_PKCS1_SHA512,
343                SignatureScheme::ECDSA_NISTP521_SHA512,
344                SignatureScheme::RSA_PSS_SHA256,
345                SignatureScheme::RSA_PSS_SHA384,
346                SignatureScheme::RSA_PSS_SHA512,
347                SignatureScheme::ED25519,
348                SignatureScheme::ED448,
349            ]
350        }
351    }
352}
353
354#[cfg(feature = "openssl-tls")]
355pub mod openssl_tls {
356    use openssl::{
357        pkey::PKey,
358        ssl::{SslConnector, SslConnectorBuilder, SslMethod},
359        x509::X509,
360    };
361    use thiserror::Error;
362
363    /// Errors from OpenSSL TLS
364    #[derive(Debug, Error)]
365    pub enum Error {
366        /// Failed to create OpenSSL HTTPS connector
367        #[error("failed to create OpenSSL HTTPS connector: {0}")]
368        CreateHttpsConnector(#[source] openssl::error::ErrorStack),
369
370        /// Failed to create OpenSSL SSL connector
371        #[error("failed to create OpenSSL SSL connector: {0}")]
372        CreateSslConnector(#[source] SslConnectorError),
373    }
374
375    /// Errors from creating a `SslConnectorBuilder`
376    #[derive(Debug, Error)]
377    pub enum SslConnectorError {
378        /// Failed to build SslConnectorBuilder
379        #[error("failed to build SslConnectorBuilder: {0}")]
380        CreateBuilder(#[source] openssl::error::ErrorStack),
381
382        /// Failed to deserialize PEM-encoded chain of certificates
383        #[error("failed to deserialize PEM-encoded chain of certificates: {0}")]
384        DeserializeCertificateChain(#[source] openssl::error::ErrorStack),
385
386        /// Failed to deserialize PEM-encoded private key
387        #[error("failed to deserialize PEM-encoded private key: {0}")]
388        DeserializePrivateKey(#[source] openssl::error::ErrorStack),
389
390        /// Failed to set private key
391        #[error("failed to set private key: {0}")]
392        SetPrivateKey(#[source] openssl::error::ErrorStack),
393
394        /// Failed to get a leaf certificate, the certificate chain is empty
395        #[error("failed to get a leaf certificate, the certificate chain is empty")]
396        GetLeafCertificate,
397
398        /// Failed to set the leaf certificate
399        #[error("failed to set the leaf certificate: {0}")]
400        SetLeafCertificate(#[source] openssl::error::ErrorStack),
401
402        /// Failed to append a certificate to the chain
403        #[error("failed to append a certificate to the chain: {0}")]
404        AppendCertificate(#[source] openssl::error::ErrorStack),
405
406        /// Failed to deserialize DER-encoded root certificate
407        #[error("failed to deserialize DER-encoded root certificate: {0}")]
408        DeserializeRootCertificate(#[source] openssl::error::ErrorStack),
409
410        /// Failed to add a root certificate
411        #[error("failed to add a root certificate: {0}")]
412        AddRootCertificate(#[source] openssl::error::ErrorStack),
413    }
414
415    /// Create `openssl::ssl::SslConnectorBuilder` required for `hyper_openssl::HttpsConnector`.
416    pub fn ssl_connector_builder(
417        identity_pem: Option<&Vec<u8>>,
418        root_certs: Option<&Vec<Vec<u8>>>,
419    ) -> Result<SslConnectorBuilder, SslConnectorError> {
420        let mut builder =
421            SslConnector::builder(SslMethod::tls()).map_err(SslConnectorError::CreateBuilder)?;
422        if let Some(pem) = identity_pem {
423            let mut chain = X509::stack_from_pem(pem)
424                .map_err(SslConnectorError::DeserializeCertificateChain)?
425                .into_iter();
426            let leaf_cert = chain.next().ok_or(SslConnectorError::GetLeafCertificate)?;
427            builder
428                .set_certificate(&leaf_cert)
429                .map_err(SslConnectorError::SetLeafCertificate)?;
430            for cert in chain {
431                builder
432                    .add_extra_chain_cert(cert)
433                    .map_err(SslConnectorError::AppendCertificate)?;
434            }
435
436            let pkey = PKey::private_key_from_pem(pem).map_err(SslConnectorError::DeserializePrivateKey)?;
437            builder
438                .set_private_key(&pkey)
439                .map_err(SslConnectorError::SetPrivateKey)?;
440        }
441
442        if let Some(ders) = root_certs {
443            for der in ders {
444                let cert = X509::from_der(der).map_err(SslConnectorError::DeserializeRootCertificate)?;
445                builder
446                    .cert_store_mut()
447                    .add_cert(cert)
448                    .map_err(SslConnectorError::AddRootCertificate)?;
449            }
450        }
451
452        Ok(builder)
453    }
454}