Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provide rustls implementation to sign request to gke #123

Merged
merged 1 commit into from Feb 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 6 additions & 13 deletions src/config/apis.rs
Expand Up @@ -155,19 +155,12 @@ impl AuthInfo {
Some(provider) => {
if let Some(access_token) = provider.config.get("access-token") {
self.token = Some(access_token.clone());
#[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 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);
}
}
if let Some(id_token) = provider.config.get("id-token") {
Expand Down
136 changes: 104 additions & 32 deletions src/oauth2/mod.rs
Expand Up @@ -5,13 +5,6 @@ use std::path::PathBuf;
use chrono::Utc;
use crate::{Result, Error};

#[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 @@ -122,53 +115,132 @@ impl CredentialsClient {
})
}

#[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)))?;
let encoded = &self.jws_encode(
let encoded = jws_encode(
&Claim::new(&self.credentials, scopes),
&Header{
alg: "RS256".to_string(),
typ: "JWT".to_string(),
},
private_key)?;
&self.credentials.private_key)?;

let body = Serializer::new(String::new())
.extend_pairs(vec![
("grant_type".to_string(), DEFAULT_GRANT_TYPE.to_string()),
("assertion".to_string(), encoded.to_string()),
]).finish();
let token_response: TokenResponse = self.client
let token_response: TokenResponse = self.client
.post(&self.credentials.token_uri)
.body(body)
.header(CONTENT_TYPE, "application/x-www-form-urlencoded")
.send()
.await
.map_err(|e| Error::KubeConfig(format!("Unable to request token: {}", e)))?
.json::<TokenResponse>()
.map_err(|e| Error::KubeConfig(format!("Unable to request token: {}", e)))
.and_then(|response| {
if response.status() != reqwest::StatusCode::OK {
Err(Error::KubeConfig(format!("Fail to retrieve new credential {:#?}", response)))
} else {
Ok(response)
}
})?.json::<TokenResponse>()
.await
.map_err(|e| Error::KubeConfig(format!("Unable to parse request token: {}", e)))?;
Ok(token_response.into_token())
}
}

fn jws_encode(claim: &Claim, header: &Header, private_key: &str) -> Result<String> {
let encoded_header = base64_encode(serde_json::to_string(&header).unwrap().as_bytes());
let encoded_claims = base64_encode(serde_json::to_string(&claim).unwrap().as_bytes());
let signature_base = format!("{}.{}", encoded_header, encoded_claims);
let signature = sign(&signature_base, private_key)?;
let encoded_signature = base64_encode(&signature);
Ok(format!("{}.{}", signature_base, encoded_signature))
}

#[cfg(feature = "native-tls")]
fn sign(signature_base: &str, private_key: &str) -> Result<Vec<u8>> {
use openssl::{
pkey::PKey,
sign::Signer,
rsa::Padding,
hash::MessageDigest,
};
let key = PKey::private_key_from_pem(private_key.as_bytes())
.map_err(|e| Error::SslError(format!("{}", e)))?;
let mut signer = Signer::new(MessageDigest::sha256(), &key)
.map_err(|e| Error::SslError(format!("{}", e)))?;
signer.set_rsa_padding(Padding::PKCS1)
.map_err(|e| Error::SslError(format!("{}", e)))?;
signer.update(signature_base.as_bytes())
.map_err(|e| Error::SslError(format!("{}", e)))?;
signer.sign_to_vec()
.map_err(|e| Error::SslError(format!("{}", e)))
}

#[cfg(feature = "rustls-tls")]
fn sign(signature_base: &str, private_key: &str) -> Result<Vec<u8>> {
use rustls::internal::pemfile;
use rustls::sign::RSASigningKey;
use rustls::sign::SigningKey;

let keys = pemfile::pkcs8_private_keys(&mut private_key.as_bytes().clone())
.map_err(|_| Error::SslError("fail to parse private key".into()))?;
let key = keys.get(0)
.ok_or_else(|| Error::SslError("no usable private key found to sign with RS256".into()))?;
let signing_key = RSASigningKey::new(key)
.map_err(|_| Error::SslError("fail to make RSA signing key".into()))?;
let signer = signing_key.choose_scheme(&[rustls::SignatureScheme::RSA_PKCS1_SHA256])
.ok_or_else(|| Error::SslError("scheme RSA_PKCS1_SHA256 not found into private key".into()))?;
signer.sign(signature_base.as_bytes()).map_err(|e| Error::SslError(format!("{}", e)))
}

fn base64_encode(bytes: &[u8]) -> String {
base64::encode_config(bytes, base64::URL_SAFE)
}

#[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());
let signature_base = format!("{}.{}", encoded_header, encoded_claims);
let mut signer = Signer::new(MessageDigest::sha256(), &key)
.map_err(|e| Error::SslError(format!("{}", e)))?;
signer.set_rsa_padding(Padding::PKCS1)
.map_err(|e| Error::SslError(format!("{}", e)))?;
signer.update(signature_base.as_bytes())
.map_err(|e| Error::SslError(format!("{}", e)))?;
let signature = signer.sign_to_vec()
.map_err(|e| Error::SslError(format!("{}", e)))?;
Ok(format!("{}.{}", signature_base, self.base64_encode(&signature)))
}

fn base64_encode(&self, bytes: &[u8]) -> String {
base64::encode_config(bytes, base64::URL_SAFE)
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_true() {
// generated with
// ```
// openssl genpkey -out rsakey.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048
// ```
let private_key = r#"-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDjT1UyWwk/v2UG
BhTEB+8NIL4RW3+u7TSOVP0Qpxf22bhJH9+RqwZPlwzbhYQT1TNXT4fFnARaGWaG
EtPV/rlV6o9PcLCMj3y2sOiKBy0qS6/3nYHKlFNPGnesYLTIbk54Orp4OYnSqQ/G
zBZbS3IRDsTaOb4D+KaxdPm/I8qN1TEPIDEkDRYtRprbmTQaz3rl0ooKuDCHiWoW
I7rG6zGkcGwBZAkwh0XFeklJSwZbC0JK88wolHKWJba6KCO8A2LpskacPB/KP5mQ
bnTzIS5xiNMKf9qhLm/HgDzgCL9E8StnZygUmRFKYh4MTzrpfGaoT5Vm+tijlrDi
CDE33tuZAgMBAAECggEBANuVDnsngCbJsECCbVr1QxNOdu1zk0ObN3LrXM/Sao72
wVQ6axFfwifuhegl8XHrOb51QHY/geC7utN3qpWFjOoXPbuC47nU/qfI+8oippm+
Jc2wpOnaISRAMC0f+mPIUxtHuExdYOtUj7399vbYSed6eeVJdGqHsBerJXtkis44
uuzlQ6ISMPd3YhxN6S4QbPyw6aaoJG0qYpdHSL/n9r49hA3sKbAQVSOTzM1PMRje
6kB6BPrfmyVavHUXRZG1lU7gD41F8nG0wXOvsFu1XXPeEjw2/uBRanA8rWtAPv02
vBXcBMHpv7ySWCVOXMLWfZmo4GJIjfhtjTasUTSxVAECgYEA+Tvei02NBGWdjOzu
xoLvF71oT22Ic862StvmDJYSV/9bs1r8pxGCPs0tkHr/DQoBcmvAComrcEBltkaZ
yyKxDdSLrsy1nkL4hPMwJF0gNZAAaj4rMNfadKGOmlhQBcFCBph8kijcGsyYn1Vs
2cGCIZCALofDm4t8oIUpB8+UsYECgYEA6XsYh50+JkpuTknTbciOQJijl0a0oK6X
SO9mwoWEtg0+mrR3gL0kjghUObN5p0lhLLyqCBDVeFdaDcWbZXdF/KuSyI48Bube
c0EYkCFk1W/39yVb6LqQP6xoPrA/nLB4AuZYSqkZwx+bHH7OBgHaXRh2m2HawU5B
pQsM2PVwhhkCgYAonJfTzSw4VjKI/yadVEKPdL6liqycakeMBS8ESAPvMN4JaL8Y
niLCBv7wtwoOXt4DfglJ7krwPJ4WSITQ8/Mz1Ll6H0NM6Y7DYzkqA76226MlrMGu
8M1ZCeZJwjAv7+DJYFmUG3JaL5KDDBFznjONMpWgf2DhXKZPJcOc0TdigQKBgGHL
4NN1JsItLRT30WrLteISzXsg76naV54CQR27hYIn/BAbBW9USop/rJ/asFtE3kI5
6FKmknPsyti368ZNdnBGgZ4mDbiqXYUTQDGm+zB3zPqlmGDcPG2fTq7rbkm4lRxJ
1bO4LwVPKM5/wtY7UnbqN0wQaevMVqzF+ySpce+JAoGBAOLkdZrv7+JPfusIlj/H
CuNhikh1WMHm6Al0SYBURhUb52hHGEAnpaIxwQrN4PPD4Iboyp0kLsjMtKxlvnBm
WpsqFXdkj9GLZt1s1Q/5SW5Twb7gxdR7cXrXOcATivN1/GDdhIHS1NEb3MM7EBXc
9RSM375nLWCP0LDosgKSaq+u
-----END PRIVATE KEY-----
"#;
let msg = "foo bar";
let expected = "h0H_U6SO_i1F7JwzzTBHL1DNTw-YD6jdMul9Uwo_5xB_TmztP9c7T8e5CVY1o5_vMfQ3SZJXZ9liwd7FK8a7NjNumWIWq0_KZvDxMK6D2SSkA8WAz4KsdUU1CNVxM51UYQgYnHpaJvtNmowgzCnahNQQso4hKsYCe7nNKlTiCP1yPzM4MWJYh2cekH1SGqSaOtgvQZz4GrOPG-hhcyMZMk_u-sZ0F3PUFj0-kfbhZPNVpvv4-wI_XA84q85Wech4nsgLbxO9397-whsmGVNlqqo2PwwxASn7dEqtrrvD7mkabf32OqmgJ-xXT_n4m67kvgzC7ausezX7E0zcnBj3RQ==".to_string();
assert_eq!(base64_encode(&sign(&msg, private_key).unwrap()), expected);
}
}