From e5d6a11fc37679bde6faaaf1b84e1805222e0b48 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 9 Apr 2024 12:10:10 +0200 Subject: [PATCH 1/7] Avoid redundant import --- src/gcloud_authorized_user.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gcloud_authorized_user.rs b/src/gcloud_authorized_user.rs index e2c58cb..b1de97d 100644 --- a/src/gcloud_authorized_user.rs +++ b/src/gcloud_authorized_user.rs @@ -80,7 +80,6 @@ fn run(gcloud: &Path, cmd: &[&str]) -> Result { #[cfg(test)] mod tests { use chrono::Utc; - use std::time::Duration; use super::*; From e8e3be3771fe62d15e76106b150c6d44c10fb30d Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 9 Apr 2024 11:47:59 +0200 Subject: [PATCH 2/7] Rename subject field to sub So far this field was not used. --- src/jwt.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/jwt.rs b/src/jwt.rs index 2cc7f41..3baa29b 100644 --- a/src/jwt.rs +++ b/src/jwt.rs @@ -19,7 +19,7 @@ pub(crate) struct Claims<'a> { aud: &'a str, exp: i64, iat: i64, - subject: Option<&'a str>, + sub: Option<&'a str>, scope: String, } @@ -27,7 +27,7 @@ impl<'a> Claims<'a> { pub(crate) fn new( key: &'a ApplicationCredentials, scopes: &[T], - subject: Option<&'a str>, + sub: Option<&'a str>, ) -> Self where T: std::string::ToString, @@ -45,7 +45,7 @@ impl<'a> Claims<'a> { aud: &key.token_uri, exp: expiry, iat, - subject, + sub, scope, } } From 95da892180ad3f10b49dc40aa7221faff43a256b Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 9 Apr 2024 11:51:13 +0200 Subject: [PATCH 3/7] Simplify encoding of JWT scopes --- src/jwt.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/jwt.rs b/src/jwt.rs index 3baa29b..60fd80c 100644 --- a/src/jwt.rs +++ b/src/jwt.rs @@ -24,22 +24,23 @@ pub(crate) struct Claims<'a> { } impl<'a> Claims<'a> { - pub(crate) fn new( + pub(crate) fn new( key: &'a ApplicationCredentials, - scopes: &[T], + scopes: &[&str], sub: Option<&'a str>, - ) -> Self - where - T: std::string::ToString, - { + ) -> Self { let iat = Utc::now().timestamp(); let expiry = iat + 3600 - 5; // Max validity is 1h. - let scope: String = scopes - .iter() - .map(|x| x.to_string()) - .collect::>() - .join(" "); + let mut scope = String::with_capacity(16); + for (i, s) in scopes.iter().enumerate() { + if i != 0 { + scope.push(' '); + } + + scope.push_str(s); + } + Claims { iss: &key.client_email, aud: &key.token_uri, From 0450491d8f552f8ca9c39432a7ba7956be1453a4 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 9 Apr 2024 11:52:20 +0200 Subject: [PATCH 4/7] Simplify JWT creation some more --- src/jwt.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/jwt.rs b/src/jwt.rs index 60fd80c..87129d4 100644 --- a/src/jwt.rs +++ b/src/jwt.rs @@ -29,9 +29,6 @@ impl<'a> Claims<'a> { scopes: &[&str], sub: Option<&'a str>, ) -> Self { - let iat = Utc::now().timestamp(); - let expiry = iat + 3600 - 5; // Max validity is 1h. - let mut scope = String::with_capacity(16); for (i, s) in scopes.iter().enumerate() { if i != 0 { @@ -41,10 +38,11 @@ impl<'a> Claims<'a> { scope.push_str(s); } + let iat = Utc::now().timestamp(); Claims { iss: &key.client_email, aud: &key.token_uri, - exp: expiry, + exp: iat + 3600 - 5, // Max validity is 1h iat, sub, scope, From ca5577a23e8bc89f2ffea2af7b6748ddef9a589b Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 9 Apr 2024 11:57:48 +0200 Subject: [PATCH 5/7] Move Claims type into custom_service_account --- src/custom_service_account.rs | 58 ++++++++++++++++++++++++++++++-- src/jwt.rs | 63 ----------------------------------- src/lib.rs | 1 - 3 files changed, 56 insertions(+), 66 deletions(-) delete mode 100644 src/jwt.rs diff --git a/src/custom_service_account.rs b/src/custom_service_account.rs index 5f5f6d3..77c4c2b 100644 --- a/src/custom_service_account.rs +++ b/src/custom_service_account.rs @@ -4,6 +4,8 @@ use std::path::{Path, PathBuf}; use std::sync::RwLock; use async_trait::async_trait; +use base64::{engine::general_purpose::URL_SAFE, Engine}; +use chrono::Utc; use serde::{Deserialize, Serialize}; use tracing::{instrument, Level}; @@ -95,8 +97,6 @@ impl ServiceAccount for CustomServiceAccount { #[instrument(level = Level::DEBUG)] async fn refresh_token(&self, client: &HyperClient, scopes: &[&str]) -> Result { - use crate::jwt::Claims; - use crate::jwt::GRANT_TYPE; use hyper::header; use url::form_urlencoded; @@ -136,6 +136,57 @@ impl ServiceAccount for CustomServiceAccount { } } +/// Permissions requested for a JWT. +/// See https://developers.google.com/identity/protocols/OAuth2ServiceAccount#authorizingrequests. +#[derive(Serialize, Debug)] +pub(crate) struct Claims<'a> { + iss: &'a str, + aud: &'a str, + exp: i64, + iat: i64, + sub: Option<&'a str>, + scope: String, +} + +impl<'a> Claims<'a> { + pub(crate) fn new( + key: &'a ApplicationCredentials, + scopes: &[&str], + sub: Option<&'a str>, + ) -> Self { + let mut scope = String::with_capacity(16); + for (i, s) in scopes.iter().enumerate() { + if i != 0 { + scope.push(' '); + } + + scope.push_str(s); + } + + let iat = Utc::now().timestamp(); + Claims { + iss: &key.client_email, + aud: &key.token_uri, + exp: iat + 3600 - 5, // Max validity is 1h + iat, + sub, + scope, + } + } + + pub(crate) fn to_jwt(&self, signer: &Signer) -> Result { + let mut jwt = String::new(); + URL_SAFE.encode_string(GOOGLE_RS256_HEAD, &mut jwt); + jwt.push('.'); + URL_SAFE.encode_string(&serde_json::to_string(self).unwrap(), &mut jwt); + + let signature = signer.sign(jwt.as_bytes())?; + jwt.push('.'); + URL_SAFE.encode_string(&signature, &mut jwt); + Ok(jwt) + } +} + #[derive(Serialize, Deserialize, Clone)] pub(crate) struct ApplicationCredentials { pub(crate) r#type: Option, @@ -168,5 +219,8 @@ impl fmt::Debug for ApplicationCredentials { } } +pub(crate) const GRANT_TYPE: &str = "urn:ietf:params:oauth:grant-type:jwt-bearer"; +const GOOGLE_RS256_HEAD: &str = r#"{"alg":"RS256","typ":"JWT"}"#; + /// How many times to attempt to fetch a token from the set credentials token endpoint. const RETRY_COUNT: u8 = 5; diff --git a/src/jwt.rs b/src/jwt.rs deleted file mode 100644 index 87129d4..0000000 --- a/src/jwt.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! Copyright (c) 2016 Google Inc (lewinb@google.com). - -use base64::{engine::general_purpose::URL_SAFE, Engine}; -use chrono::Utc; -use serde::Serialize; - -use crate::custom_service_account::ApplicationCredentials; -use crate::error::Error; -use crate::types::Signer; - -pub(crate) const GRANT_TYPE: &str = "urn:ietf:params:oauth:grant-type:jwt-bearer"; -const GOOGLE_RS256_HEAD: &str = r#"{"alg":"RS256","typ":"JWT"}"#; - -/// Permissions requested for a JWT. -/// See https://developers.google.com/identity/protocols/OAuth2ServiceAccount#authorizingrequests. -#[derive(Serialize, Debug)] -pub(crate) struct Claims<'a> { - iss: &'a str, - aud: &'a str, - exp: i64, - iat: i64, - sub: Option<&'a str>, - scope: String, -} - -impl<'a> Claims<'a> { - pub(crate) fn new( - key: &'a ApplicationCredentials, - scopes: &[&str], - sub: Option<&'a str>, - ) -> Self { - let mut scope = String::with_capacity(16); - for (i, s) in scopes.iter().enumerate() { - if i != 0 { - scope.push(' '); - } - - scope.push_str(s); - } - - let iat = Utc::now().timestamp(); - Claims { - iss: &key.client_email, - aud: &key.token_uri, - exp: iat + 3600 - 5, // Max validity is 1h - iat, - sub, - scope, - } - } - - pub(crate) fn to_jwt(&self, signer: &Signer) -> Result { - let mut jwt = String::new(); - URL_SAFE.encode_string(GOOGLE_RS256_HEAD, &mut jwt); - jwt.push('.'); - URL_SAFE.encode_string(&serde_json::to_string(self).unwrap(), &mut jwt); - - let signature = signer.sign(jwt.as_bytes())?; - jwt.push('.'); - URL_SAFE.encode_string(&signature, &mut jwt); - Ok(jwt) - } -} diff --git a/src/lib.rs b/src/lib.rs index 61ff25f..eeb9ed7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -95,7 +95,6 @@ mod default_authorized_user; mod default_service_account; mod error; mod gcloud_authorized_user; -mod jwt; mod types; mod util; From bbe0f2dabc1bd81efb6fc9f380f50f57cf100a55 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 9 Apr 2024 12:06:20 +0200 Subject: [PATCH 6/7] Add CustomServiceAccount::with_subject() method --- src/custom_service_account.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/custom_service_account.rs b/src/custom_service_account.rs index 77c4c2b..90dc1fd 100644 --- a/src/custom_service_account.rs +++ b/src/custom_service_account.rs @@ -25,6 +25,7 @@ pub struct CustomServiceAccount { credentials: ApplicationCredentials, signer: Signer, tokens: RwLock, Token>>, + subject: Option, } impl CustomServiceAccount { @@ -57,11 +58,18 @@ impl CustomServiceAccount { } } + /// Set the `subject` to impersonate a user + pub fn with_subject(mut self, subject: String) -> Self { + self.subject = Some(subject); + self + } + fn new(credentials: ApplicationCredentials) -> Result { Ok(Self { signer: Signer::new(&credentials.private_key)?, credentials, tokens: RwLock::new(HashMap::new()), + subject: None, }) } @@ -100,7 +108,8 @@ impl ServiceAccount for CustomServiceAccount { use hyper::header; use url::form_urlencoded; - let jwt = Claims::new(&self.credentials, scopes, None).to_jwt(&self.signer)?; + let jwt = + Claims::new(&self.credentials, scopes, self.subject.as_deref()).to_jwt(&self.signer)?; let rqbody = form_urlencoded::Serializer::new(String::new()) .extend_pairs(&[("grant_type", GRANT_TYPE), ("assertion", jwt.as_str())]) .finish(); From f5331b3e3ddafa7a11e41ad2d081c3c8378f558b Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 9 Apr 2024 12:11:18 +0200 Subject: [PATCH 7/7] Bump version to 0.11.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b74060f..7e1eaf0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gcp_auth" -version = "0.11.0" +version = "0.11.1" edition = "2021" rust-version = "1.70" repository = "https://github.com/hrvolapeter/gcp_auth"