Skip to content

Commit

Permalink
Merge pull request #114 from clux/rustls
Browse files Browse the repository at this point in the history
rustls support
  • Loading branch information
clux authored Feb 1, 2020
2 parents cf38a22 + faa5126 commit 5d56e59
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 63 deletions.
3 changes: 2 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ jobs:
- run: cargo build
- run: cargo test --lib
- run: cargo test --example crd_api crd_reflector
- run: cargo test --all-features -j4
- run: cargo test --features=openapi -j4
- run: cargo test --lib --no-default-features --features=rustls-tls
- save_cache:
paths:
- /usr/local/cargo/registry
Expand Down
31 changes: 17 additions & 14 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ authors = [
"clux <sszynrae@gmail.com>",
"ynqa <un.pensiero.vano@gmail.com>",
]
license-file = "LICENSE"
license = "Apache-2.0"
repository = "https://github.com/clux/kube-rs"
readme = "README.md"
keywords = ["kubernetes", "reflector", "informer", "client-go", "client-rust"]
Expand All @@ -21,7 +21,6 @@ serde = "1.0.104"
serde_derive = "1.0.104"
serde_json = "1.0.45"
serde_yaml = "0.8.11"
openssl = "0.10.26"
http = "0.2.0"
url = "2.1.1"
log = "0.4.8"
Expand All @@ -30,15 +29,25 @@ either = "1.5.3"
thiserror = "1.0.9"
futures-timer = "2.0.0"
futures = "0.3.1"

[features]
default = []
openapi = ["k8s-openapi"]
openssl = { version = "0.10.27", optional = true }
rustls = { version = "0.16.0", optional = true }

[dependencies.reqwest]
version = "0.10.1"
features = ["json", "gzip", "native-tls"]
# TODO: rustls
default-features = false
features = ["json", "gzip"]

[dependencies.k8s-openapi]
version = "0.7.1"
default-features = false
features = []
optional = true

[features]
default = ["native-tls"]
native-tls = ["openssl", "reqwest/native-tls"]
rustls-tls = ["rustls", "reqwest/rustls-tls"]
openapi = ["k8s-openapi"]

[dev-dependencies]
tempfile = "3.1.0"
Expand All @@ -50,9 +59,3 @@ anyhow = "1.0.26"
version = "0.7.1"
default-features = false
features = ["v1_15"]

[dependencies.k8s-openapi]
version = "0.7.1"
default-features = false
features = []
optional = true
19 changes: 13 additions & 6 deletions src/config/apis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,19 @@ impl AuthInfo {
Some(provider) => {
if let Some(access_token) = provider.config.get("access-token") {
self.token = Some(access_token.clone());
if utils::is_expired(&provider.config["expiry"]) {
let client = oauth2::CredentialsClient::new()?;
let token = client.request_token(&[
"https://www.googleapis.com/auth/cloud-platform".to_string(),
]).await?;
self.token = Some(token.access_token);
#[cfg(feature = "native-tls")]
{ // TODO: allow rusttls with this auth provider bs
if utils::is_expired(&provider.config["expiry"]) {
let client = oauth2::CredentialsClient::new()?;
let token = client.request_token(&[
"https://www.googleapis.com/auth/cloud-platform".to_string(),
]).await?;
self.token = Some(token.access_token);
}
}
#[cfg(feature = "rustls-tls")]
{
error!("kube-rs does not support auth_provider setup with rustls atm")
}
}
if let Some(id_token) = provider.config.get("id-token") {
Expand Down
6 changes: 3 additions & 3 deletions src/config/incluster_config.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::env;

use crate::{Result, Error};
use openssl::x509::X509;
use reqwest::Certificate;

use crate::config::utils;

Expand Down Expand Up @@ -31,9 +31,9 @@ pub fn load_token() -> Result<String> {
}

/// Returns certification from specified path in cluster.
pub fn load_cert() -> Result<X509> {
pub fn load_cert() -> Result<Certificate> {
let ca = utils::data_or_file_with_base64(&None, &Some(SERVICE_CERTFILE))?;
X509::from_pem(&ca).map_err(|e| Error::KubeConfig(format!("{}", e)))
Certificate::from_pem(&ca).map_err(|e| Error::KubeConfig(format!("{}", e)))
}

/// Returns the default namespace from specified path in cluster.
Expand Down
75 changes: 67 additions & 8 deletions src/config/kube_config.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
use std::path::Path;

#[cfg(feature = "native-tls")]
use openssl::{
pkcs12::Pkcs12,
pkey::PKey,
x509::X509,
};

use reqwest::{Identity, Certificate};

use crate::{Error, Result};
use crate::config::apis::{AuthInfo, Cluster, Config, Context};

/// Regardless of tls type, a Certificate Der is always a byte array
pub struct Der(pub Vec<u8>);

use std::convert::TryFrom;
impl TryFrom<Der> for Certificate {
type Error = Error;
fn try_from(val: Der) -> Result<Certificate> {
Certificate::from_der(&val.0)
.map_err(Error::ReqwestError)
}
}

/// KubeConfigLoader loads current context, cluster, and authentication information.
#[derive(Clone,Debug)]
pub struct KubeConfigLoader {
Expand Down Expand Up @@ -39,7 +56,6 @@ impl KubeConfigLoader {
.ok_or_else(|| Error::KubeConfig("Unable to load cluster of context".into()))?;
let user_name = user.as_ref().unwrap_or(&current_context.user);

// NB: can't have async closures yet
let mut user_opt = None;
for named_user in config.auth_infos {
if &named_user.name == user_name {
Expand All @@ -56,20 +72,63 @@ impl KubeConfigLoader {
})
}

pub fn p12(&self, password: &str) -> Result<Pkcs12> {
#[cfg(feature="native-tls")]
pub fn identity(&self, password: &str) -> Result<Identity> {
let client_cert = &self.user.load_client_certificate()?;
let client_key = &self.user.load_client_key()?;

let x509 = X509::from_pem(&client_cert).map_err(|e| Error::SslError(format!("{}", e)))?;
let pkey = PKey::private_key_from_pem(&client_key).map_err(|e| Error::SslError(format!("{}", e)))?;
let x509 = X509::from_pem(&client_cert)
.map_err(|e| Error::SslError(format!("{}", e)))?;
let pkey = PKey::private_key_from_pem(&client_key)
.map_err(|e| Error::SslError(format!("{}", e)))?;

Pkcs12::builder()
let p12 = Pkcs12::builder()
.build(password, "kubeconfig", &pkey, &x509)
.map_err(|e| Error::SslError(format!("{}", e)))?;

let der = p12.to_der()
.map_err(|e| Error::SslError(format!("{}", e)))?;
Ok(Identity::from_pkcs12_der(&der, password)?)
}

#[cfg(feature="rustls-tls")]
pub fn identity(&self, _password: &str) -> Result<Identity> {
let client_cert = &self.user.load_client_certificate()?;
let client_key = &self.user.load_client_key()?;

let mut buffer = client_key.clone();
buffer.extend_from_slice(client_cert);
Identity::from_pem(&buffer.as_slice())
.map_err(|e| Error::SslError(format!("{}", e)))
}

pub fn ca_bundle(&self) -> Option<Result<Vec<X509>>> {
let bundle = self.cluster.load_certificate_authority().ok()?;
Some(X509::stack_from_pem(&bundle).map_err(|e| Error::SslError(format!("{}", e))))
#[cfg(feature="native-tls")]
pub fn ca_bundle(&self) -> Result<Vec<Der>> {
let bundle = self.cluster.load_certificate_authority()
.map_err(|e| Error::SslError(format!("{}", e)))?;
let bundle = X509::stack_from_pem(&bundle)
.map_err(|e| Error::SslError(format!("{}", e)))?;

let mut stack = vec![];
for ca in bundle {
let der = ca.to_der().map_err(|e| Error::SslError(format!("{}", e)))?;
stack.push(Der(der))
}
Ok(stack)
}

#[cfg(feature = "rustls-tls")]
pub fn ca_bundle(&self) -> Result<Vec<Der>> {
use rustls::internal::pemfile;
use std::io::Cursor;
let bundle = self.cluster.load_certificate_authority()?;
let mut pem = Cursor::new(bundle);
pem.set_position(0);

let mut stack = vec![];
for ca in pemfile::certs(&mut pem).map_err(|e| Error::SslError(format!("{:?}", e)))? {
stack.push(Der(ca.0))
}
Ok(stack)
}
}
48 changes: 21 additions & 27 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ mod incluster_config;
mod kube_config;
mod utils;

use crate::config::kube_config::{Der};
use crate::{Error, Result};
use std::convert::TryInto;
use base64;
use openssl::x509::X509;
use reqwest::{header, Certificate, Client, ClientBuilder, Identity};
use reqwest::{header, Client, ClientBuilder};

use self::kube_config::KubeConfigLoader;

Expand Down Expand Up @@ -66,8 +67,11 @@ pub async fn load_kube_config_with(options: ConfigOptions) -> Result<Configurati
))
}

#[cfg(target_os = "macos")]
fn platform_cfg_client_builder(client_builder: ClientBuilder, ca: &X509) -> ClientBuilder {
// temporary catalina hack for openssl only
#[cfg(all(target_os = "macos", feature="native-tls"))]
fn hacky_cert_lifetime_for_macos(client_builder: ClientBuilder, ca_: &Der) -> ClientBuilder {
use openssl::x509::X509;
let ca = X509::from_der(&ca_.0).expect("valid der is a der");
if ca
.not_before()
.diff(ca.not_after())
Expand All @@ -80,8 +84,8 @@ fn platform_cfg_client_builder(client_builder: ClientBuilder, ca: &X509) -> Clie
}
}

#[cfg(not(target_os = "macos"))]
fn platform_cfg_client_builder(client_builder: ClientBuilder, _: &X509) -> ClientBuilder {
#[cfg(any(not(target_os = "macos"), not(feature="native-tls")))]
fn hacky_cert_lifetime_for_macos(client_builder: ClientBuilder, _: &Der) -> ClientBuilder {
client_builder
}

Expand Down Expand Up @@ -112,22 +116,17 @@ pub async fn create_client_builder(options: ConfigOptions) -> Result<(ClientBuil

let mut client_builder = Client::builder();

if let Some(bundle) = loader.ca_bundle() {
for ca in bundle? {
let cert = Certificate::from_der(&ca.to_der().map_err(|e| Error::SslError(format!("{}", e)))?)
.map_err(Error::ReqwestError)?;
client_builder = client_builder.add_root_certificate(cert);
client_builder = platform_cfg_client_builder(client_builder, &ca);
}
for ca in loader.ca_bundle()? {
client_builder = hacky_cert_lifetime_for_macos(client_builder, &ca);
client_builder = client_builder.add_root_certificate(ca.try_into()?);
}
match loader.p12(" ") {
Ok(p12) => {
let der = p12.to_der().map_err(|e| Error::SslError(format!("{}", e)))?;
let req_p12 = Identity::from_pkcs12_der(&der, " ")
.map_err(Error::ReqwestError)?;
client_builder = client_builder.identity(req_p12);

match loader.identity(" ") {
Ok(id) => {
client_builder = client_builder.identity(id);
}
Err(_) => {
Err(e) => {
warn!("failed to load client identity from kube config: {}", e);
// last resort only if configs ask for it, and no client certs
if let Some(true) = loader.cluster.insecure_skip_tls_verify {
client_builder = client_builder.danger_accept_invalid_certs(true);
Expand Down Expand Up @@ -160,7 +159,6 @@ pub async fn create_client_builder(options: ConfigOptions) -> Result<(ClientBuil
}

Ok((client_builder.default_headers(headers), loader))

}

/// Returns a config which is used by clients within pods on kubernetes.
Expand All @@ -174,11 +172,7 @@ pub fn incluster_config() -> Result<Configuration> {
incluster_config::SERVICE_PORTENV
)))?;

let ca = incluster_config::load_cert()
.map_err(|e| Error::SslError(format!("{}", e)))?;
let der = ca.to_der().map_err(|e| Error::SslError(format!("{}", e)))?;
let req_ca = Certificate::from_der(&der)
.map_err(|e| Error::SslError(format!("{}", e)))?;
let cert = incluster_config::load_cert()?;

let token = incluster_config::load_token()
.map_err(|e| Error::KubeConfig(format!("Unable to load in cluster token: {}", e)))?;
Expand All @@ -195,7 +189,7 @@ pub fn incluster_config() -> Result<Configuration> {
);

let client_builder = Client::builder()
.add_root_certificate(req_ca)
.add_root_certificate(cert)
.default_headers(headers);

Ok(Configuration::with_default_ns(
Expand Down
15 changes: 11 additions & 4 deletions src/oauth2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ use std::path::PathBuf;

use chrono::Utc;
use crate::{Result, Error};
use openssl::pkey::{PKey, Private};
use openssl::sign::Signer;
use openssl::rsa::Padding;
use openssl::hash::MessageDigest;

#[cfg(feature = "native-tls")]
use openssl::{
pkey::{PKey, Private},
sign::Signer,
rsa::Padding,
hash::MessageDigest,
};
use reqwest::Client;
use reqwest::header::CONTENT_TYPE;
//use time::Duration;
Expand Down Expand Up @@ -117,6 +121,8 @@ impl CredentialsClient {
client: Client::new(),
})
}

#[cfg(feature = "native-tls")]
pub async fn request_token(&self, scopes: &[String]) -> Result<Token> {
let private_key = PKey::private_key_from_pem(&self.credentials.private_key.as_bytes())
.map_err(|e| Error::SslError(format!("{}", e)))?;
Expand Down Expand Up @@ -146,6 +152,7 @@ impl CredentialsClient {
Ok(token_response.into_token())
}

#[cfg(feature = "native-tls")]
fn jws_encode(&self, claim: &Claim, header: &Header, key: PKey<Private>) -> Result<String> {
let encoded_header = self.base64_encode(serde_json::to_string(&header).unwrap().as_bytes());
let encoded_claims = self.base64_encode(serde_json::to_string(&claim).unwrap().as_bytes());
Expand Down

0 comments on commit 5d56e59

Please sign in to comment.