From cf0ffba1286b015704112e18419b86b2bad53cfb Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 11:33:02 +0100 Subject: [PATCH 01/30] Clean up unintentionally added .DS_Store file --- .DS_Store | Bin 6148 -> 0 bytes .gitignore | 1 + 2 files changed, 1 insertion(+) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 64b92a61f26b8e8fac95db06cd6c1c1b1f8cdf4f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKJxc>Y5PfS-Jxr5AM6|hH5b_VskcfROEHxh_frNy7fS|>7HvR}}!OG4`vCz^| z@MmaY>CEn$?BHxFYDcAi@~9rf$obG;P| zNrgVW6LtTc*m}EuosPS;&p&hf_Py}da2d?Yad~WSyf6>3pH+a)Y_>3KP2#>rp!A}EDBvrQF^@Su|4Z}l|NbO-5(Pwof2Dv5isfQ~ zev&;~Q$LQ++KAdQH8##m4C*P=+2dFb_$c0_rj23|4+vw6i9zy^$sYkNgLI<6uPX2b D%)YIr diff --git a/.gitignore b/.gitignore index aa2ec5d..71e4408 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ target Cargo.lock +.DS_Store # Intellij project files *.iml From 148eb79447a3fde76484c2255e9c754749f00e83 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 11:38:12 +0100 Subject: [PATCH 02/30] Simplify parsing CustomServiceAccount setup --- src/custom_service_account.rs | 22 +++++----------------- src/error.rs | 17 +++++++++-------- src/lib.rs | 4 ++-- 3 files changed, 16 insertions(+), 27 deletions(-) diff --git a/src/custom_service_account.rs b/src/custom_service_account.rs index 36df77a..d6b7792 100644 --- a/src/custom_service_account.rs +++ b/src/custom_service_account.rs @@ -4,7 +4,6 @@ use std::sync::RwLock; use async_trait::async_trait; use serde::{Deserialize, Serialize}; -use tokio::fs; use crate::authentication_manager::ServiceAccount; use crate::error::Error; @@ -18,16 +17,18 @@ pub(crate) struct CustomServiceAccount { } impl CustomServiceAccount { - pub(crate) async fn from_file(path: &Path) -> Result { + pub(crate) fn from_file(path: &Path) -> Result { + let file = std::fs::File::open(path).map_err(Error::CustomServiceAccountPath)?; Ok(Self { - credentials: ApplicationCredentials::from_file(path).await?, + credentials: serde_json::from_reader(file) + .map_err(Error::CustomServiceAccountCredentials)?, tokens: RwLock::new(HashMap::new()), }) } pub(crate) fn from_json(s: &str) -> Result { Ok(Self { - credentials: ApplicationCredentials::from_json(s)?, + credentials: serde_json::from_str(s).map_err(Error::CustomServiceAccountCredentials)?, tokens: RwLock::new(HashMap::new()), }) } @@ -100,16 +101,3 @@ pub(crate) struct ApplicationCredentials { /// client_x509_cert_url pub(crate) client_x509_cert_url: Option, } - -impl ApplicationCredentials { - async fn from_file>(path: T) -> Result { - let content = fs::read_to_string(path) - .await - .map_err(Error::ApplicationProfilePath)?; - ApplicationCredentials::from_json(&content) - } - - fn from_json(s: &str) -> Result { - serde_json::from_str(s).map_err(Error::ApplicationProfileFormat) - } -} diff --git a/src/error.rs b/src/error.rs index 12f2b02..8db3f1f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -34,18 +34,19 @@ pub enum Error { #[error("Path to custom auth credentials was not provided in `GOOGLE_APPLICATION_CREDENTIALS` env variable")] ApplicationProfileMissing, - /// Wrong path to custom application profile credentials provided + /// Wrong path to custom service account credentials provided /// - /// Path has to be defined using `GOOGLE_APPLICATION_CREDENTIALS` environment variable - #[error("Environment variable `GOOGLE_APPLICATION_CREDENTIALS` contains invalid path to application profile file")] - ApplicationProfilePath(#[source] std::io::Error), + /// By default, the custom service account credentials are parsed from the path pointed to by the + /// `GOOGLE_APPLICATION_CREDENTIALS` environment variable. + #[error("Invalid path to custom service account")] + CustomServiceAccountPath(#[source] std::io::Error), - /// Wrong format of custom application profile + /// Failed to parse the application credentials provided /// - /// Application profile is downloaded from GCP console and is stored in filesystem on the server. - /// Full path is passed to library by setting `GOOGLE_APPLICATION_CREDENTIALS` variable with path as a value. + /// By default, the custom service account credentials are parsed from the path pointed to by the + /// `GOOGLE_APPLICATION_CREDENTIALS` environment variable. #[error("Application profile provided in `GOOGLE_APPLICATION_CREDENTIALS` was not parsable")] - ApplicationProfileFormat(#[source] serde_json::error::Error), + CustomServiceAccountCredentials(#[source] serde_json::error::Error), /// Default user profile not found /// diff --git a/src/lib.rs b/src/lib.rs index 86c86b3..cd88a83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,7 +88,7 @@ use hyper_rustls::HttpsConnectorBuilder; pub async fn from_credentials_file>( path: T, ) -> Result { - let custom = CustomServiceAccount::from_file(path.as_ref()).await?; + let custom = CustomServiceAccount::from_file(path.as_ref())?; get_authentication_manager(Some(custom)).await } @@ -159,7 +159,7 @@ pub async fn init() -> Result { // We know that GOOGLE_APPLICATION_CREDENTIALS exists, read the file and return an // error in case of failure. - Some(CustomServiceAccount::from_file(Path::new(&path)).await?) + Some(CustomServiceAccount::from_file(Path::new(&path))?) } Err(_) => None, }; From a0df627e9d304ac939c910e0de854db825ebbdd7 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 11:39:44 +0100 Subject: [PATCH 03/30] Remove unused Error variants --- src/error.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/error.rs b/src/error.rs index 8db3f1f..3088023 100644 --- a/src/error.rs +++ b/src/error.rs @@ -23,17 +23,6 @@ pub enum Error { #[error("Could not establish connection with OAuth server")] OAuthConnectionError(#[source] hyper::Error), - /// Error when parsing response from OAuth server - #[error("Could not parse OAuth server response")] - OAuthParsingError(#[source] serde_json::error::Error), - - /// Variable `GOOGLE_APPLICATION_CREDENTIALS` could not be found in the current environment - /// - /// GOOGLE_APPLICATION_CREDENTIALS is used for providing path to json file with applications credentials. - /// File can be downloaded in GCP Console when creating service account. - #[error("Path to custom auth credentials was not provided in `GOOGLE_APPLICATION_CREDENTIALS` env variable")] - ApplicationProfileMissing, - /// Wrong path to custom service account credentials provided /// /// By default, the custom service account credentials are parsed from the path pointed to by the From d5c543a22db5041f6146358d37676f521349522f Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 11:51:18 +0100 Subject: [PATCH 04/30] Move initialization of AuthenticationManager into associated function --- src/authentication_manager.rs | 51 ++++++++++++++++++++++++++++++-- src/lib.rs | 55 ++--------------------------------- 2 files changed, 52 insertions(+), 54 deletions(-) diff --git a/src/authentication_manager.rs b/src/authentication_manager.rs index d352e53..53d3ab7 100644 --- a/src/authentication_manager.rs +++ b/src/authentication_manager.rs @@ -1,7 +1,13 @@ use async_trait::async_trait; +use hyper::Client; +use hyper_rustls::HttpsConnectorBuilder; use tokio::sync::Mutex; +use crate::custom_service_account::CustomServiceAccount; +use crate::default_authorized_user::DefaultAuthorizedUser; +use crate::default_service_account::DefaultServiceAccount; use crate::error::Error; +use crate::gcloud_authorized_user::GCloudAuthorizedUser; use crate::types::{HyperClient, Token}; #[async_trait] @@ -21,10 +27,51 @@ pub struct AuthenticationManager { } impl AuthenticationManager { - pub(crate) fn new(client: HyperClient, service_account: Box) -> Self { + pub(crate) async fn select( + custom: Option, + ) -> Result { + #[cfg(feature = "webpki-roots")] + let https = HttpsConnectorBuilder::new().with_webpki_roots(); + #[cfg(not(feature = "webpki-roots"))] + let https = HttpsConnectorBuilder::new().with_native_roots(); + + let client = + Client::builder().build::<_, hyper::Body>(https.https_or_http().enable_http2().build()); + + if let Some(service_account) = custom { + log::debug!("Using CustomServiceAccount"); + return Ok(Self::new(client, service_account)); + } + + let gcloud = GCloudAuthorizedUser::new().await; + if let Ok(service_account) = gcloud { + log::debug!("Using GCloudAuthorizedUser"); + return Ok(Self::new(client.clone(), service_account)); + } + + let default = DefaultServiceAccount::new(&client).await; + if let Ok(service_account) = default { + log::debug!("Using DefaultServiceAccount"); + return Ok(Self::new(client.clone(), service_account)); + } + + let user = DefaultAuthorizedUser::new(&client).await; + if let Ok(user_account) = user { + log::debug!("Using DefaultAuthorizedUser"); + return Ok(Self::new(client, user_account)); + } + + Err(Error::NoAuthMethod( + Box::new(gcloud.unwrap_err()), + Box::new(default.unwrap_err()), + Box::new(user.unwrap_err()), + )) + } + + fn new(client: HyperClient, service_account: impl ServiceAccount + 'static) -> Self { Self { client, - service_account, + service_account: Box::new(service_account), refresh_mutex: Mutex::new(()), } } diff --git a/src/lib.rs b/src/lib.rs index cd88a83..5362c44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,16 +72,12 @@ mod types; mod util; use custom_service_account::CustomServiceAccount; - pub use authentication_manager::AuthenticationManager; pub use error::Error; pub use types::Token; use std::path::Path; -use hyper::Client; -use hyper_rustls::HttpsConnectorBuilder; - /// Initialize GCP authentication based on a credentials file path /// /// Returns `AuthenticationManager` which can be used to obtain tokens @@ -89,7 +85,7 @@ pub async fn from_credentials_file>( path: T, ) -> Result { let custom = CustomServiceAccount::from_file(path.as_ref())?; - get_authentication_manager(Some(custom)).await + AuthenticationManager::select(Some(custom)).await } /// Initialize GCP authentication based on a JSON string @@ -97,54 +93,9 @@ pub async fn from_credentials_file>( /// Returns `AuthenticationManager` which can be used to obtain tokens pub async fn from_credentials_json(s: &str) -> Result { let custom = CustomServiceAccount::from_json(s)?; - get_authentication_manager(Some(custom)).await + AuthenticationManager::select(Some(custom)).await } -async fn get_authentication_manager( - custom: Option, -) -> Result { - #[cfg(feature = "webpki-roots")] - let https = HttpsConnectorBuilder::new().with_webpki_roots(); - #[cfg(not(feature = "webpki-roots"))] - let https = HttpsConnectorBuilder::new().with_native_roots(); - - let client = - Client::builder().build::<_, hyper::Body>(https.https_or_http().enable_http2().build()); - - if let Some(service_account) = custom { - log::debug!("Using CustomServiceAccount"); - return Ok(AuthenticationManager::new( - client, - Box::new(service_account), - )); - } - let gcloud = gcloud_authorized_user::GCloudAuthorizedUser::new().await; - if let Ok(service_account) = gcloud { - log::debug!("Using GCloudAuthorizedUser"); - return Ok(AuthenticationManager::new( - client.clone(), - Box::new(service_account), - )); - } - let default = default_service_account::DefaultServiceAccount::new(&client).await; - if let Ok(service_account) = default { - log::debug!("Using DefaultServiceAccount"); - return Ok(AuthenticationManager::new( - client.clone(), - Box::new(service_account), - )); - } - let user = default_authorized_user::DefaultAuthorizedUser::new(&client).await; - if let Ok(user_account) = user { - log::debug!("Using DefaultAuthorizedUser"); - return Ok(AuthenticationManager::new(client, Box::new(user_account))); - } - Err(Error::NoAuthMethod( - Box::new(gcloud.unwrap_err()), - Box::new(default.unwrap_err()), - Box::new(user.unwrap_err()), - )) -} /// Initialize GCP authentication /// /// Returns `AuthenticationManager` which can be used to obtain tokens @@ -164,5 +115,5 @@ pub async fn init() -> Result { Err(_) => None, }; - get_authentication_manager(custom).await + AuthenticationManager::select(custom).await } From a73537c9cf3d14c99d07b84b12733286c36439d1 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 12:27:28 +0100 Subject: [PATCH 05/30] Make GCloudAuthorizedUser::new() synchronous --- src/authentication_manager.rs | 2 +- src/gcloud_authorized_user.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/authentication_manager.rs b/src/authentication_manager.rs index 53d3ab7..dd26d32 100644 --- a/src/authentication_manager.rs +++ b/src/authentication_manager.rs @@ -43,7 +43,7 @@ impl AuthenticationManager { return Ok(Self::new(client, service_account)); } - let gcloud = GCloudAuthorizedUser::new().await; + let gcloud = GCloudAuthorizedUser::new(); if let Ok(service_account) = gcloud { log::debug!("Using GCloudAuthorizedUser"); return Ok(Self::new(client.clone(), service_account)); diff --git a/src/gcloud_authorized_user.rs b/src/gcloud_authorized_user.rs index f82dfd6..05b2fb6 100644 --- a/src/gcloud_authorized_user.rs +++ b/src/gcloud_authorized_user.rs @@ -17,7 +17,7 @@ pub(crate) struct GCloudAuthorizedUser { } impl GCloudAuthorizedUser { - pub(crate) async fn new() -> Result { + pub(crate) fn new() -> Result { which("gcloud") .map_err(|_| GCloudNotFound) .map(|path| Self { gcloud: path }) From 0e6fd5ab72b2c61c94d8c1a69007b49bec355e41 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 12:29:59 +0100 Subject: [PATCH 06/30] Use matching to avoid unwrapping errors --- src/authentication_manager.rs | 46 ++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/authentication_manager.rs b/src/authentication_manager.rs index dd26d32..573bf7c 100644 --- a/src/authentication_manager.rs +++ b/src/authentication_manager.rs @@ -43,28 +43,34 @@ impl AuthenticationManager { return Ok(Self::new(client, service_account)); } - let gcloud = GCloudAuthorizedUser::new(); - if let Ok(service_account) = gcloud { - log::debug!("Using GCloudAuthorizedUser"); - return Ok(Self::new(client.clone(), service_account)); - } - - let default = DefaultServiceAccount::new(&client).await; - if let Ok(service_account) = default { - log::debug!("Using DefaultServiceAccount"); - return Ok(Self::new(client.clone(), service_account)); - } - - let user = DefaultAuthorizedUser::new(&client).await; - if let Ok(user_account) = user { - log::debug!("Using DefaultAuthorizedUser"); - return Ok(Self::new(client, user_account)); - } + let gcloud_error = match GCloudAuthorizedUser::new() { + Ok(service_account) => { + log::debug!("Using GCloudAuthorizedUser"); + return Ok(Self::new(client.clone(), service_account)); + } + Err(e) => e, + }; + + let default_service_error = match DefaultServiceAccount::new(&client).await { + Ok(service_account) => { + log::debug!("Using DefaultServiceAccount"); + return Ok(Self::new(client.clone(), service_account)); + } + Err(e) => e, + }; + + let default_user_error = match DefaultAuthorizedUser::new(&client).await { + Ok(service_account) => { + log::debug!("Using DefaultAuthorizedUser"); + return Ok(Self::new(client, service_account)); + } + Err(e) => e, + }; Err(Error::NoAuthMethod( - Box::new(gcloud.unwrap_err()), - Box::new(default.unwrap_err()), - Box::new(user.unwrap_err()), + Box::new(gcloud_error), + Box::new(default_service_error), + Box::new(default_user_error), )) } From ee892c3f4e482ea244883c89d95bc36f70b6454c Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 11:55:17 +0100 Subject: [PATCH 07/30] Allow non-UTF-8 paths in GOOGLE_APPLICATION_CREDENTIALS --- src/lib.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5362c44..6e61963 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,8 +71,8 @@ mod jwt; mod types; mod util; -use custom_service_account::CustomServiceAccount; pub use authentication_manager::AuthenticationManager; +use custom_service_account::CustomServiceAccount; pub use error::Error; pub use types::Token; @@ -104,16 +104,15 @@ pub async fn init() -> Result { // will return an error if the environment variable isn’t set, in which case custom is set to // none. - let custom = match std::env::var("GOOGLE_APPLICATION_CREDENTIALS") { - Ok(path) => { + let custom = std::env::var_os("GOOGLE_APPLICATION_CREDENTIALS") + .and_then(|path| { log::debug!("Reading credentials file from GOOGLE_APPLICATION_CREDENTIALS env var"); // We know that GOOGLE_APPLICATION_CREDENTIALS exists, read the file and return an // error in case of failure. - Some(CustomServiceAccount::from_file(Path::new(&path))?) - } - Err(_) => None, - }; + Some(CustomServiceAccount::from_file(Path::new(&path))) + }) + .transpose()?; AuthenticationManager::select(custom).await } From 8976520222dbf707ff0e3e5e98e033ab4b1cf529 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 12:05:20 +0100 Subject: [PATCH 08/30] Extract initialization of HyperClient into function --- src/authentication_manager.rs | 16 ++++------------ src/types.rs | 11 +++++++++++ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/authentication_manager.rs b/src/authentication_manager.rs index 573bf7c..3578427 100644 --- a/src/authentication_manager.rs +++ b/src/authentication_manager.rs @@ -1,6 +1,4 @@ use async_trait::async_trait; -use hyper::Client; -use hyper_rustls::HttpsConnectorBuilder; use tokio::sync::Mutex; use crate::custom_service_account::CustomServiceAccount; @@ -8,7 +6,7 @@ use crate::default_authorized_user::DefaultAuthorizedUser; use crate::default_service_account::DefaultServiceAccount; use crate::error::Error; use crate::gcloud_authorized_user::GCloudAuthorizedUser; -use crate::types::{HyperClient, Token}; +use crate::types::{self, HyperClient, Token}; #[async_trait] pub(crate) trait ServiceAccount: Send + Sync { @@ -30,13 +28,7 @@ impl AuthenticationManager { pub(crate) async fn select( custom: Option, ) -> Result { - #[cfg(feature = "webpki-roots")] - let https = HttpsConnectorBuilder::new().with_webpki_roots(); - #[cfg(not(feature = "webpki-roots"))] - let https = HttpsConnectorBuilder::new().with_native_roots(); - - let client = - Client::builder().build::<_, hyper::Body>(https.https_or_http().enable_http2().build()); + let client = types::client(); if let Some(service_account) = custom { log::debug!("Using CustomServiceAccount"); @@ -46,7 +38,7 @@ impl AuthenticationManager { let gcloud_error = match GCloudAuthorizedUser::new() { Ok(service_account) => { log::debug!("Using GCloudAuthorizedUser"); - return Ok(Self::new(client.clone(), service_account)); + return Ok(Self::new(client, service_account)); } Err(e) => e, }; @@ -54,7 +46,7 @@ impl AuthenticationManager { let default_service_error = match DefaultServiceAccount::new(&client).await { Ok(service_account) => { log::debug!("Using DefaultServiceAccount"); - return Ok(Self::new(client.clone(), service_account)); + return Ok(Self::new(client, service_account)); } Err(e) => e, }; diff --git a/src/types.rs b/src/types.rs index 0789bac..1660f9c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,6 +1,8 @@ use std::fmt; use std::sync::Arc; +use hyper::Client; +use hyper_rustls::HttpsConnectorBuilder; use serde::Deserializer; use serde::{Deserialize, Serialize}; use time::{Duration, OffsetDateTime}; @@ -78,6 +80,15 @@ where Ok(s) } +pub(crate) fn client() -> HyperClient { + #[cfg(feature = "webpki-roots")] + let https = HttpsConnectorBuilder::new().with_webpki_roots(); + #[cfg(not(feature = "webpki-roots"))] + let https = HttpsConnectorBuilder::new().with_native_roots(); + + Client::builder().build::<_, hyper::Body>(https.https_or_http().enable_http2().build()) +} + pub(crate) type HyperClient = hyper::Client>; From 42405589cb5fc2bb12c003750f6b0f3ef4ff1f63 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 12:12:02 +0100 Subject: [PATCH 09/30] Clarify internal API for turning CustomServiceAccount into AuthenticationManager --- src/authentication_manager.rs | 13 +++++-------- src/lib.rs | 35 +++++++++++++++++------------------ 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/authentication_manager.rs b/src/authentication_manager.rs index 3578427..0a5039d 100644 --- a/src/authentication_manager.rs +++ b/src/authentication_manager.rs @@ -25,15 +25,12 @@ pub struct AuthenticationManager { } impl AuthenticationManager { - pub(crate) async fn select( - custom: Option, - ) -> Result { - let client = types::client(); + pub(crate) fn from_custom_service_account(service_account: CustomServiceAccount) -> Self { + Self::new(types::client(), service_account) + } - if let Some(service_account) = custom { - log::debug!("Using CustomServiceAccount"); - return Ok(Self::new(client, service_account)); - } + pub(crate) async fn select() -> Result { + let client = types::client(); let gcloud_error = match GCloudAuthorizedUser::new() { Ok(service_account) => { diff --git a/src/lib.rs b/src/lib.rs index 6e61963..b5ce5df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,24 +76,24 @@ use custom_service_account::CustomServiceAccount; pub use error::Error; pub use types::Token; -use std::path::Path; +use std::path::{Path, PathBuf}; /// Initialize GCP authentication based on a credentials file path /// /// Returns `AuthenticationManager` which can be used to obtain tokens -pub async fn from_credentials_file>( - path: T, -) -> Result { - let custom = CustomServiceAccount::from_file(path.as_ref())?; - AuthenticationManager::select(Some(custom)).await +pub fn from_credentials_file>(path: T) -> Result { + Ok(AuthenticationManager::from_custom_service_account( + CustomServiceAccount::from_file(path.as_ref())?, + )) } /// Initialize GCP authentication based on a JSON string /// /// Returns `AuthenticationManager` which can be used to obtain tokens -pub async fn from_credentials_json(s: &str) -> Result { - let custom = CustomServiceAccount::from_json(s)?; - AuthenticationManager::select(Some(custom)).await +pub fn from_credentials_json(s: &str) -> Result { + Ok(AuthenticationManager::from_custom_service_account( + CustomServiceAccount::from_json(s)?, + )) } /// Initialize GCP authentication @@ -104,15 +104,14 @@ pub async fn init() -> Result { // will return an error if the environment variable isn’t set, in which case custom is set to // none. - let custom = std::env::var_os("GOOGLE_APPLICATION_CREDENTIALS") - .and_then(|path| { + match std::env::var_os("GOOGLE_APPLICATION_CREDENTIALS") { + Some(path) => { log::debug!("Reading credentials file from GOOGLE_APPLICATION_CREDENTIALS env var"); - // We know that GOOGLE_APPLICATION_CREDENTIALS exists, read the file and return an - // error in case of failure. - Some(CustomServiceAccount::from_file(Path::new(&path))) - }) - .transpose()?; - - AuthenticationManager::select(custom).await + Ok(AuthenticationManager::from_custom_service_account( + CustomServiceAccount::from_file(&PathBuf::from(path))?, + )) + } + None => AuthenticationManager::select().await, + } } From 9da6f8541ff99b9226620056ace4428f7e15a2c3 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 12:15:57 +0100 Subject: [PATCH 10/30] Move checking environment for credentials into AuthenticationManager::new() --- src/authentication_manager.rs | 6 +++++- src/custom_service_account.rs | 11 ++++++++++- src/lib.rs | 17 ++--------------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/authentication_manager.rs b/src/authentication_manager.rs index 0a5039d..27a33bc 100644 --- a/src/authentication_manager.rs +++ b/src/authentication_manager.rs @@ -30,8 +30,12 @@ impl AuthenticationManager { } pub(crate) async fn select() -> Result { - let client = types::client(); + log::debug!("Initializing gcp_auth"); + if let Some(service_account) = CustomServiceAccount::from_env()? { + return Ok(Self::from_custom_service_account(service_account)); + } + let client = types::client(); let gcloud_error = match GCloudAuthorizedUser::new() { Ok(service_account) => { log::debug!("Using GCloudAuthorizedUser"); diff --git a/src/custom_service_account.rs b/src/custom_service_account.rs index d6b7792..bffe587 100644 --- a/src/custom_service_account.rs +++ b/src/custom_service_account.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::sync::RwLock; use async_trait::async_trait; @@ -17,6 +17,15 @@ pub(crate) struct CustomServiceAccount { } impl CustomServiceAccount { + pub(crate) fn from_env() -> Result, Error> { + std::env::var_os("GOOGLE_APPLICATION_CREDENTIALS") + .map(|path| { + log::debug!("Reading credentials file from GOOGLE_APPLICATION_CREDENTIALS env var"); + Self::from_file(&PathBuf::from(path)) + }) + .transpose() + } + pub(crate) fn from_file(path: &Path) -> Result { let file = std::fs::File::open(path).map_err(Error::CustomServiceAccountPath)?; Ok(Self { diff --git a/src/lib.rs b/src/lib.rs index b5ce5df..600f119 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,7 +76,7 @@ use custom_service_account::CustomServiceAccount; pub use error::Error; pub use types::Token; -use std::path::{Path, PathBuf}; +use std::path::Path; /// Initialize GCP authentication based on a credentials file path /// @@ -100,18 +100,5 @@ pub fn from_credentials_json(s: &str) -> Result { /// /// Returns `AuthenticationManager` which can be used to obtain tokens pub async fn init() -> Result { - log::debug!("Initializing gcp_auth"); - - // will return an error if the environment variable isn’t set, in which case custom is set to - // none. - match std::env::var_os("GOOGLE_APPLICATION_CREDENTIALS") { - Some(path) => { - log::debug!("Reading credentials file from GOOGLE_APPLICATION_CREDENTIALS env var"); - - Ok(AuthenticationManager::from_custom_service_account( - CustomServiceAccount::from_file(&PathBuf::from(path))?, - )) - } - None => AuthenticationManager::select().await, - } + AuthenticationManager::select().await } From 8076fdb766df217b18526cb1fffcfec301c667ca Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 12:16:52 +0100 Subject: [PATCH 11/30] Rename AuthenticationManager::select() to new() --- src/authentication_manager.rs | 12 ++++++------ src/lib.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/authentication_manager.rs b/src/authentication_manager.rs index 27a33bc..d9d650d 100644 --- a/src/authentication_manager.rs +++ b/src/authentication_manager.rs @@ -26,10 +26,10 @@ pub struct AuthenticationManager { impl AuthenticationManager { pub(crate) fn from_custom_service_account(service_account: CustomServiceAccount) -> Self { - Self::new(types::client(), service_account) + Self::build(types::client(), service_account) } - pub(crate) async fn select() -> Result { + pub(crate) async fn new() -> Result { log::debug!("Initializing gcp_auth"); if let Some(service_account) = CustomServiceAccount::from_env()? { return Ok(Self::from_custom_service_account(service_account)); @@ -39,7 +39,7 @@ impl AuthenticationManager { let gcloud_error = match GCloudAuthorizedUser::new() { Ok(service_account) => { log::debug!("Using GCloudAuthorizedUser"); - return Ok(Self::new(client, service_account)); + return Ok(Self::build(client, service_account)); } Err(e) => e, }; @@ -47,7 +47,7 @@ impl AuthenticationManager { let default_service_error = match DefaultServiceAccount::new(&client).await { Ok(service_account) => { log::debug!("Using DefaultServiceAccount"); - return Ok(Self::new(client, service_account)); + return Ok(Self::build(client, service_account)); } Err(e) => e, }; @@ -55,7 +55,7 @@ impl AuthenticationManager { let default_user_error = match DefaultAuthorizedUser::new(&client).await { Ok(service_account) => { log::debug!("Using DefaultAuthorizedUser"); - return Ok(Self::new(client, service_account)); + return Ok(Self::build(client, service_account)); } Err(e) => e, }; @@ -67,7 +67,7 @@ impl AuthenticationManager { )) } - fn new(client: HyperClient, service_account: impl ServiceAccount + 'static) -> Self { + fn build(client: HyperClient, service_account: impl ServiceAccount + 'static) -> Self { Self { client, service_account: Box::new(service_account), diff --git a/src/lib.rs b/src/lib.rs index 600f119..4048aef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,5 +100,5 @@ pub fn from_credentials_json(s: &str) -> Result { /// /// Returns `AuthenticationManager` which can be used to obtain tokens pub async fn init() -> Result { - AuthenticationManager::select().await + AuthenticationManager::new().await } From 11c1d9e48a17fc87962d8e8db3fc47f7ef8be0aa Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 12:36:41 +0100 Subject: [PATCH 12/30] Make AuthenticationManager::new() public and document it --- src/authentication_manager.rs | 14 +++++++++++++- src/lib.rs | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/authentication_manager.rs b/src/authentication_manager.rs index d9d650d..4007094 100644 --- a/src/authentication_manager.rs +++ b/src/authentication_manager.rs @@ -29,7 +29,19 @@ impl AuthenticationManager { Self::build(types::client(), service_account) } - pub(crate) async fn new() -> Result { + /// Finds a service account provider to get authentication tokens from + /// + /// Tries the following approaches, in order: + /// + /// 1. Check if the `GOOGLE_APPLICATION_CREDENTIALS` environment variable if set; + /// if so, use a custom service account as the token source. + /// 2. Check if the `gcloud` tool is available on the `PATH`; if so, use the + /// `gcloud auth print-access-token` command as the token source. + /// 3. Send a HTTP request to the internal metadata server to retrieve a token; + /// if it succeeds, use the default service account as the token source. + /// 4. Look for credentials in `.config/gcloud/application_default_credentials.json`; + /// if found, use these credentials to request refresh tokens. + pub async fn new() -> Result { log::debug!("Initializing gcp_auth"); if let Some(service_account) = CustomServiceAccount::from_env()? { return Ok(Self::from_custom_service_account(service_account)); diff --git a/src/lib.rs b/src/lib.rs index 4048aef..9eb0028 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,7 +98,7 @@ pub fn from_credentials_json(s: &str) -> Result { /// Initialize GCP authentication /// -/// Returns `AuthenticationManager` which can be used to obtain tokens +/// This is a convenience wrapper around [`AuthenticationManager::new()`]. pub async fn init() -> Result { AuthenticationManager::new().await } From f95113ce9b77e7ca1260b11e383bb33e11b46b1f Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 12:41:42 +0100 Subject: [PATCH 13/30] Make CustomServiceAccount public --- src/authentication_manager.rs | 9 ++++++++- src/custom_service_account.rs | 14 +++++++++----- src/lib.rs | 22 +--------------------- 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/src/authentication_manager.rs b/src/authentication_manager.rs index 4007094..3a417c0 100644 --- a/src/authentication_manager.rs +++ b/src/authentication_manager.rs @@ -25,7 +25,8 @@ pub struct AuthenticationManager { } impl AuthenticationManager { - pub(crate) fn from_custom_service_account(service_account: CustomServiceAccount) -> Self { + /// Create an `AuthenticationManager` directly from a custom service account + pub fn from_custom_service_account(service_account: CustomServiceAccount) -> Self { Self::build(types::client(), service_account) } @@ -116,3 +117,9 @@ impl AuthenticationManager { self.service_account.project_id(&self.client).await } } + +impl From for AuthenticationManager { + fn from(service_account: CustomServiceAccount) -> Self { + Self::from_custom_service_account(service_account) + } +} diff --git a/src/custom_service_account.rs b/src/custom_service_account.rs index bffe587..55eaf38 100644 --- a/src/custom_service_account.rs +++ b/src/custom_service_account.rs @@ -10,14 +10,16 @@ use crate::error::Error; use crate::types::{HyperClient, Token}; use crate::util::HyperExt; +/// A custom service account containing credentials #[derive(Debug)] -pub(crate) struct CustomServiceAccount { +pub struct CustomServiceAccount { tokens: RwLock, Token>>, credentials: ApplicationCredentials, } impl CustomServiceAccount { - pub(crate) fn from_env() -> Result, Error> { + /// Check `GOOGLE_APPLICATION_CREDENTIALS` environment variable for a path to JSON credentials + pub fn from_env() -> Result, Error> { std::env::var_os("GOOGLE_APPLICATION_CREDENTIALS") .map(|path| { log::debug!("Reading credentials file from GOOGLE_APPLICATION_CREDENTIALS env var"); @@ -26,8 +28,9 @@ impl CustomServiceAccount { .transpose() } - pub(crate) fn from_file(path: &Path) -> Result { - let file = std::fs::File::open(path).map_err(Error::CustomServiceAccountPath)?; + /// Read service account credentials from the given JSON file + pub fn from_file>(path: T) -> Result { + let file = std::fs::File::open(path.as_ref()).map_err(Error::CustomServiceAccountPath)?; Ok(Self { credentials: serde_json::from_reader(file) .map_err(Error::CustomServiceAccountCredentials)?, @@ -35,7 +38,8 @@ impl CustomServiceAccount { }) } - pub(crate) fn from_json(s: &str) -> Result { + /// Read service account credentials from the given JSON string + pub fn from_json(s: &str) -> Result { Ok(Self { credentials: serde_json::from_str(s).map_err(Error::CustomServiceAccountCredentials)?, tokens: RwLock::new(HashMap::new()), diff --git a/src/lib.rs b/src/lib.rs index 9eb0028..a94cde8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,30 +72,10 @@ mod types; mod util; pub use authentication_manager::AuthenticationManager; -use custom_service_account::CustomServiceAccount; +pub use custom_service_account::CustomServiceAccount; pub use error::Error; pub use types::Token; -use std::path::Path; - -/// Initialize GCP authentication based on a credentials file path -/// -/// Returns `AuthenticationManager` which can be used to obtain tokens -pub fn from_credentials_file>(path: T) -> Result { - Ok(AuthenticationManager::from_custom_service_account( - CustomServiceAccount::from_file(path.as_ref())?, - )) -} - -/// Initialize GCP authentication based on a JSON string -/// -/// Returns `AuthenticationManager` which can be used to obtain tokens -pub fn from_credentials_json(s: &str) -> Result { - Ok(AuthenticationManager::from_custom_service_account( - CustomServiceAccount::from_json(s)?, - )) -} - /// Initialize GCP authentication /// /// This is a convenience wrapper around [`AuthenticationManager::new()`]. From ed6203b582d74168a3ac1e82a3d7518e102384e0 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 12:45:20 +0100 Subject: [PATCH 14/30] Add methods to access custom service account credentials --- src/custom_service_account.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/custom_service_account.rs b/src/custom_service_account.rs index 55eaf38..269a226 100644 --- a/src/custom_service_account.rs +++ b/src/custom_service_account.rs @@ -45,6 +45,16 @@ impl CustomServiceAccount { tokens: RwLock::new(HashMap::new()), }) } + + /// The project ID as found in the credentials + pub fn project_id(&self) -> Option<&str> { + self.credentials.project_id.as_deref() + } + + /// The private key as found in the credentials + pub fn private_key_pem(&self) -> &str { + &self.credentials.private_key + } } #[async_trait] From a220fad7e9198c7e37d421d8534fc7d7f2e71569 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 12:47:32 +0100 Subject: [PATCH 15/30] Rename TLSError variant to TlsError --- src/custom_service_account.rs | 2 +- src/error.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/custom_service_account.rs b/src/custom_service_account.rs index 269a226..fbc6e47 100644 --- a/src/custom_service_account.rs +++ b/src/custom_service_account.rs @@ -81,7 +81,7 @@ impl ServiceAccount for CustomServiceAccount { let signer = JwtSigner::new(&self.credentials.private_key)?; let claims = Claims::new(&self.credentials, scopes, None); - let signed = signer.sign_claims(&claims).map_err(Error::TLSError)?; + let signed = signer.sign_claims(&claims).map_err(Error::TlsError)?; let rqbody = form_urlencoded::Serializer::new(String::new()) .extend_pairs(&[("grant_type", GRANT_TYPE), ("assertion", signed.as_str())]) .finish(); diff --git a/src/error.rs b/src/error.rs index 3088023..0d965ac 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,7 +17,7 @@ pub enum Error { /// Error in underlying RustTLS library. /// Might signal problem with establishing secure connection using trusted certificates #[error("TLS error")] - TLSError(#[source] rustls::Error), + TlsError(#[source] rustls::Error), /// Error when establishing connection to OAuth server #[error("Could not establish connection with OAuth server")] From a4da35a36f2e7cfef6a068e2870ec72015a59a47 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 12:50:52 +0100 Subject: [PATCH 16/30] Deserialize user credentials directly from file --- src/default_authorized_user.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/default_authorized_user.rs b/src/default_authorized_user.rs index 51f916b..6af7c4a 100644 --- a/src/default_authorized_user.rs +++ b/src/default_authorized_user.rs @@ -1,11 +1,10 @@ -use std::path::Path; +use std::fs; use std::sync::RwLock; use async_trait::async_trait; use hyper::body::Body; use hyper::{Method, Request}; use serde::{Deserialize, Serialize}; -use tokio::fs; use crate::authentication_manager::ServiceAccount; use crate::error::Error; @@ -40,13 +39,18 @@ impl DefaultAuthorizedUser { log::debug!("Loading user credentials file"); let mut home = dirs_next::home_dir().ok_or(Error::NoHomeDir)?; home.push(Self::USER_CREDENTIALS_PATH); - let cred = UserCredentials::from_file(home.display().to_string()).await?; + + let file = fs::File::open(home).map_err(Error::UserProfilePath)?; + let cred = serde_json::from_reader::<_, UserCredentials>(file) + .map_err(Error::UserProfileFormat)?; + let req = Self::build_token_request(&RefreshRequest { client_id: cred.client_id, client_secret: cred.client_secret, grant_type: "refresh_token".to_string(), refresh_token: cred.refresh_token, }); + let token = client .request(req) .await @@ -93,12 +97,3 @@ struct UserCredentials { /// Type pub(crate) r#type: String, } - -impl UserCredentials { - async fn from_file>(path: T) -> Result { - let content = fs::read_to_string(path) - .await - .map_err(Error::UserProfilePath)?; - Ok(serde_json::from_str(&content).map_err(Error::UserProfileFormat)?) - } -} From 467c076f8fc43e12e48021b43677c0c1cfc47348 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 12:56:27 +0100 Subject: [PATCH 17/30] Add code to support project_id() in GCloud tokens --- src/gcloud_authorized_user.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/gcloud_authorized_user.rs b/src/gcloud_authorized_user.rs index 05b2fb6..d1d76d3 100644 --- a/src/gcloud_authorized_user.rs +++ b/src/gcloud_authorized_user.rs @@ -1,8 +1,6 @@ use crate::authentication_manager::ServiceAccount; use crate::error::Error; -use crate::error::Error::{ - GCloudError, GCloudNotFound, GCloudParseError, NoProjectId, ParsingError, -}; +use crate::error::Error::{GCloudError, GCloudNotFound, GCloudParseError, ParsingError}; use crate::types::HyperClient; use crate::Token; use async_trait::async_trait; @@ -27,7 +25,15 @@ impl GCloudAuthorizedUser { #[async_trait] impl ServiceAccount for GCloudAuthorizedUser { async fn project_id(&self, _: &HyperClient) -> Result { - Err(NoProjectId) + let mut command = Command::new(&self.gcloud); + command.args(&["config", "get-value", "project"]); + + match command.output() { + Ok(output) if output.status.success() => { + String::from_utf8(output.stdout).map_err(|_| GCloudParseError) + } + _ => Err(Error::ProjectIdNotFound), + } } fn get_token(&self, _scopes: &[&str]) -> Option { From b7c0fd57f46998548ac5a461c502e2490a458ac1 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 12:59:18 +0100 Subject: [PATCH 18/30] Rewrite code to be easier to follow --- src/gcloud_authorized_user.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/gcloud_authorized_user.rs b/src/gcloud_authorized_user.rs index d1d76d3..2c6fee1 100644 --- a/src/gcloud_authorized_user.rs +++ b/src/gcloud_authorized_user.rs @@ -44,14 +44,13 @@ impl ServiceAccount for GCloudAuthorizedUser { let mut command = Command::new(&self.gcloud); command.args(&["auth", "print-access-token", "--quiet"]); - match command.output() { - Ok(output) if output.status.success() => String::from_utf8(output.stdout) - .map_err(|_| GCloudParseError) - .and_then(|access_token| { - serde_json::from_value::(json!({ "access_token": access_token.trim() })) - .map_err(ParsingError) - }), - _ => Err(GCloudError), - } + let output = match command.output() { + Ok(output) if output.status.success() => output.stdout, + _ => return Err(GCloudError), + }; + + let access_token = String::from_utf8(output).map_err(|_| GCloudParseError)?; + serde_json::from_value::(json!({ "access_token": access_token.trim() })) + .map_err(ParsingError) } } From 8b8c07e4c4f9c44cafe4b2906328c17b6d3436c5 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 12:59:54 +0100 Subject: [PATCH 19/30] Bump version for API changes --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3360303..afda661 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gcp_auth" -version = "0.6.2" +version = "0.7.0" repository = "https://github.com/hrvolapeter/gcp_auth" description = "Google cloud platform (GCP) authentication using default and custom service accounts" documentation = "https://docs.rs/gcp_auth/" From 4c688cafa56bd806827fb492fea41742c64393c9 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 13:49:20 +0100 Subject: [PATCH 20/30] Inline decode_rsa_key() function into JwtSigner::new() --- src/jwt.rs | 54 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/src/jwt.rs b/src/jwt.rs index bb02572..6e348d5 100644 --- a/src/jwt.rs +++ b/src/jwt.rs @@ -20,26 +20,6 @@ fn append_base64 + ?Sized>(s: &T, out: &mut String) { base64::encode_config_buf(s, base64::URL_SAFE, out) } -/// Decode a PKCS8 formatted RSA key. -fn decode_rsa_key(pem_pkcs8: &str) -> Result { - let private_keys = rustls_pemfile::pkcs8_private_keys(&mut pem_pkcs8.as_bytes()); - - match private_keys { - Ok(mut keys) if !keys.is_empty() => { - keys.truncate(1); - Ok(PrivateKey(keys.remove(0))) - } - Ok(_) => Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Not enough private keys in PEM", - )), - Err(_) => Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Error reading key from PEM", - )), - } -} - /// Permissions requested for a JWT. /// See https://developers.google.com/identity/protocols/OAuth2ServiceAccount#authorizingrequests. #[derive(Serialize, Debug)] @@ -86,13 +66,35 @@ pub(crate) struct JwtSigner { } impl JwtSigner { - pub(crate) fn new(private_key: &str) -> Result { - let key = decode_rsa_key(private_key)?; + pub(crate) fn new(pem_pkcs8: &str) -> Result { + let private_keys = rustls_pemfile::pkcs8_private_keys(&mut pem_pkcs8.as_bytes()); + + let key = match private_keys { + Ok(mut keys) if !keys.is_empty() => { + keys.truncate(1); + PrivateKey(keys.remove(0)) + } + Ok(_) => { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Not enough private keys in PEM", + ) + .into()) + } + Err(_) => { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Error reading key from PEM", + ) + .into()) + } + }; + let signing_key = sign::RsaSigningKey::new(&key).map_err(|_| Error::SignerInit)?; - let signer = signing_key - .choose_scheme(&[rustls::SignatureScheme::RSA_PKCS1_SHA256]) - .ok_or(Error::SignerSchemeError)?; - Ok(JwtSigner { signer }) + match signing_key.choose_scheme(&[rustls::SignatureScheme::RSA_PKCS1_SHA256]) { + Some(signer) => Ok(JwtSigner { signer }), + None => return Err(Error::SignerSchemeError), + } } pub(crate) fn sign_claims(&self, claims: &Claims) -> Result { From c29a7df83011a19d2c2af91d284dde6eb8d417d3 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 13:53:05 +0100 Subject: [PATCH 21/30] Sort dependencies --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index afda661..3d6ed6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,8 +15,9 @@ default = ["hyper-rustls/rustls-native-certs"] webpki-roots = ["hyper-rustls/webpki-roots"] [dependencies] +async-trait = "0.1" base64 = "0.13" -time = { version = "0.3.5", features = ["serde"] } +dirs-next = "2.0" hyper = { version = "0.14.2", features = ["client", "runtime", "http2"] } hyper-rustls = { version = "0.23.0", default-features = false, features = ["native-tokio", "http1", "http2"] } log = "0.4" @@ -24,12 +25,11 @@ rustls = "0.20.2" rustls-pemfile = "0.2.1" serde = {version = "1.0", features = ["derive", "rc"]} serde_json = "1.0" +thiserror = "1.0" +time = { version = "0.3.5", features = ["serde"] } tokio = { version = "1.1", features = ["fs", "sync"] } url = "2" which = "4.2" -async-trait = "0.1" -thiserror = "1.0" -dirs-next = "2.0" [dev-dependencies] env_logger = "0.9" From 6c2c460e24e6177751e662a870718b8f05d9b47c Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 14:03:15 +0100 Subject: [PATCH 22/30] Use ring instead of rustls wrappers for signing --- Cargo.toml | 1 + src/custom_service_account.rs | 2 +- src/error.rs | 6 +++--- src/jwt.rs | 35 ++++++++++++++++++++++------------- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3d6ed6e..b7fbcfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ dirs-next = "2.0" hyper = { version = "0.14.2", features = ["client", "runtime", "http2"] } hyper-rustls = { version = "0.23.0", default-features = false, features = ["native-tokio", "http1", "http2"] } log = "0.4" +ring = "0.16.20" rustls = "0.20.2" rustls-pemfile = "0.2.1" serde = {version = "1.0", features = ["derive", "rc"]} diff --git a/src/custom_service_account.rs b/src/custom_service_account.rs index fbc6e47..67eb01b 100644 --- a/src/custom_service_account.rs +++ b/src/custom_service_account.rs @@ -81,7 +81,7 @@ impl ServiceAccount for CustomServiceAccount { let signer = JwtSigner::new(&self.credentials.private_key)?; let claims = Claims::new(&self.credentials, scopes, None); - let signed = signer.sign_claims(&claims).map_err(Error::TlsError)?; + let signed = signer.sign_claims(&claims)?; let rqbody = form_urlencoded::Serializer::new(String::new()) .extend_pairs(&[("grant_type", GRANT_TYPE), ("assertion", signed.as_str())]) .finish(); diff --git a/src/error.rs b/src/error.rs index 0d965ac..64d7b0f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -60,9 +60,9 @@ pub enum Error { #[error("Server unavailable: {0}")] ServerUnavailable(String), - /// Could not determine signer scheme - #[error("Couldn't choose signing scheme")] - SignerSchemeError, + /// Could not sign requested message + #[error("Could not sign")] + SignerFailed, /// Could not initialize signer #[error("Couldn't initialize signer")] diff --git a/src/jwt.rs b/src/jwt.rs index 6e348d5..ed00d6d 100644 --- a/src/jwt.rs +++ b/src/jwt.rs @@ -2,10 +2,9 @@ use std::io; -use rustls::{ - self, - sign::{self, SigningKey}, - PrivateKey, +use ring::{ + rand::SystemRandom, + signature::{RsaKeyPair, RSA_PKCS1_SHA256}, }; use serde::Serialize; @@ -62,7 +61,8 @@ impl<'a> Claims<'a> { /// A JSON Web Token ready for signing. pub(crate) struct JwtSigner { - signer: Box, + key: RsaKeyPair, + rng: SystemRandom, } impl JwtSigner { @@ -72,7 +72,7 @@ impl JwtSigner { let key = match private_keys { Ok(mut keys) if !keys.is_empty() => { keys.truncate(1); - PrivateKey(keys.remove(0)) + keys.remove(0) } Ok(_) => { return Err(io::Error::new( @@ -90,16 +90,25 @@ impl JwtSigner { } }; - let signing_key = sign::RsaSigningKey::new(&key).map_err(|_| Error::SignerInit)?; - match signing_key.choose_scheme(&[rustls::SignatureScheme::RSA_PKCS1_SHA256]) { - Some(signer) => Ok(JwtSigner { signer }), - None => return Err(Error::SignerSchemeError), - } + Ok(JwtSigner { + key: RsaKeyPair::from_pkcs8(&key).map_err(|_| Error::SignerInit)?, + rng: SystemRandom::new(), + }) } - pub(crate) fn sign_claims(&self, claims: &Claims) -> Result { + pub(crate) fn sign_claims(&self, claims: &Claims) -> Result { let mut jwt_head = Self::encode_claims(claims); - let signature = self.signer.sign(jwt_head.as_bytes())?; + + let mut signature = vec![0; self.key.public_modulus_len()]; + self.key + .sign( + &RSA_PKCS1_SHA256, + &self.rng, + jwt_head.as_bytes(), + &mut signature, + ) + .map_err(|_| Error::SignerFailed)?; + jwt_head.push('.'); append_base64(&signature, &mut jwt_head); Ok(jwt_head) From c0dc29686969209fae1981ceec2c1ec6bb43a95f Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 14:06:33 +0100 Subject: [PATCH 23/30] Move JWT-specific parts of signing into Claims method --- src/custom_service_account.rs | 13 ++++++----- src/jwt.rs | 44 ++++++++++++++--------------------- 2 files changed, 25 insertions(+), 32 deletions(-) diff --git a/src/custom_service_account.rs b/src/custom_service_account.rs index 67eb01b..9f6db62 100644 --- a/src/custom_service_account.rs +++ b/src/custom_service_account.rs @@ -73,22 +73,22 @@ impl ServiceAccount for CustomServiceAccount { async fn refresh_token(&self, client: &HyperClient, scopes: &[&str]) -> Result { use crate::jwt::Claims; - use crate::jwt::JwtSigner; + use crate::jwt::Signer; use crate::jwt::GRANT_TYPE; use hyper::header; use url::form_urlencoded; - let signer = JwtSigner::new(&self.credentials.private_key)?; - - let claims = Claims::new(&self.credentials, scopes, None); - let signed = signer.sign_claims(&claims)?; + let signer = Signer::new(&self.credentials.private_key)?; + let jwt = Claims::new(&self.credentials, scopes, None).to_jwt(&signer)?; let rqbody = form_urlencoded::Serializer::new(String::new()) - .extend_pairs(&[("grant_type", GRANT_TYPE), ("assertion", signed.as_str())]) + .extend_pairs(&[("grant_type", GRANT_TYPE), ("assertion", jwt.as_str())]) .finish(); + let request = hyper::Request::post(&self.credentials.token_uri) .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded") .body(hyper::Body::from(rqbody)) .unwrap(); + log::debug!("requesting token from service account: {:?}", request); let token = client .request(request) @@ -96,6 +96,7 @@ impl ServiceAccount for CustomServiceAccount { .map_err(Error::OAuthConnectionError)? .deserialize::() .await?; + let key = scopes.iter().map(|x| (*x).to_string()).collect(); self.tokens.write().unwrap().insert(key, token.clone()); Ok(token) diff --git a/src/jwt.rs b/src/jwt.rs index ed00d6d..f552ba5 100644 --- a/src/jwt.rs +++ b/src/jwt.rs @@ -57,15 +57,27 @@ impl<'a> Claims<'a> { scope, } } + + pub(crate) fn to_jwt(&self, signer: &Signer) -> Result { + let mut jwt = String::new(); + append_base64(GOOGLE_RS256_HEAD, &mut jwt); + jwt.push('.'); + append_base64(&serde_json::to_string(self).unwrap(), &mut jwt); + + let signature = signer.sign(jwt.as_bytes())?; + jwt.push('.'); + append_base64(&signature, &mut jwt); + Ok(jwt) + } } /// A JSON Web Token ready for signing. -pub(crate) struct JwtSigner { +pub(crate) struct Signer { key: RsaKeyPair, rng: SystemRandom, } -impl JwtSigner { +impl Signer { pub(crate) fn new(pem_pkcs8: &str) -> Result { let private_keys = rustls_pemfile::pkcs8_private_keys(&mut pem_pkcs8.as_bytes()); @@ -90,37 +102,17 @@ impl JwtSigner { } }; - Ok(JwtSigner { + Ok(Signer { key: RsaKeyPair::from_pkcs8(&key).map_err(|_| Error::SignerInit)?, rng: SystemRandom::new(), }) } - pub(crate) fn sign_claims(&self, claims: &Claims) -> Result { - let mut jwt_head = Self::encode_claims(claims); - + pub(crate) fn sign(&self, input: &[u8]) -> Result, Error> { let mut signature = vec![0; self.key.public_modulus_len()]; self.key - .sign( - &RSA_PKCS1_SHA256, - &self.rng, - jwt_head.as_bytes(), - &mut signature, - ) + .sign(&RSA_PKCS1_SHA256, &self.rng, input, &mut signature) .map_err(|_| Error::SignerFailed)?; - - jwt_head.push('.'); - append_base64(&signature, &mut jwt_head); - Ok(jwt_head) - } - - /// Encodes the first two parts (header and claims) to base64 and assembles them into a form - /// ready to be signed. - fn encode_claims(claims: &Claims) -> String { - let mut head = String::new(); - append_base64(GOOGLE_RS256_HEAD, &mut head); - head.push('.'); - append_base64(&serde_json::to_string(&claims).unwrap(), &mut head); - head + Ok(signature) } } From 562795daa37c4a95dee04853890bf65f3a451c39 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 14:19:28 +0100 Subject: [PATCH 24/30] Move Signer from jwt into types --- src/custom_service_account.rs | 2 +- src/jwt.rs | 53 +--------------------------------- src/types.rs | 54 ++++++++++++++++++++++++++++++++++- 3 files changed, 55 insertions(+), 54 deletions(-) diff --git a/src/custom_service_account.rs b/src/custom_service_account.rs index 9f6db62..65e03d0 100644 --- a/src/custom_service_account.rs +++ b/src/custom_service_account.rs @@ -73,8 +73,8 @@ impl ServiceAccount for CustomServiceAccount { async fn refresh_token(&self, client: &HyperClient, scopes: &[&str]) -> Result { use crate::jwt::Claims; - use crate::jwt::Signer; use crate::jwt::GRANT_TYPE; + use crate::types::Signer; use hyper::header; use url::form_urlencoded; diff --git a/src/jwt.rs b/src/jwt.rs index f552ba5..e11a280 100644 --- a/src/jwt.rs +++ b/src/jwt.rs @@ -1,15 +1,10 @@ //! Copyright (c) 2016 Google Inc (lewinb@google.com). -use std::io; - -use ring::{ - rand::SystemRandom, - signature::{RsaKeyPair, RSA_PKCS1_SHA256}, -}; 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"}"#; @@ -70,49 +65,3 @@ impl<'a> Claims<'a> { Ok(jwt) } } - -/// A JSON Web Token ready for signing. -pub(crate) struct Signer { - key: RsaKeyPair, - rng: SystemRandom, -} - -impl Signer { - pub(crate) fn new(pem_pkcs8: &str) -> Result { - let private_keys = rustls_pemfile::pkcs8_private_keys(&mut pem_pkcs8.as_bytes()); - - let key = match private_keys { - Ok(mut keys) if !keys.is_empty() => { - keys.truncate(1); - keys.remove(0) - } - Ok(_) => { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Not enough private keys in PEM", - ) - .into()) - } - Err(_) => { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "Error reading key from PEM", - ) - .into()) - } - }; - - Ok(Signer { - key: RsaKeyPair::from_pkcs8(&key).map_err(|_| Error::SignerInit)?, - rng: SystemRandom::new(), - }) - } - - pub(crate) fn sign(&self, input: &[u8]) -> Result, Error> { - let mut signature = vec![0; self.key.public_modulus_len()]; - self.key - .sign(&RSA_PKCS1_SHA256, &self.rng, input, &mut signature) - .map_err(|_| Error::SignerFailed)?; - Ok(signature) - } -} diff --git a/src/types.rs b/src/types.rs index 1660f9c..334da04 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,12 +1,18 @@ -use std::fmt; use std::sync::Arc; +use std::{fmt, io}; use hyper::Client; use hyper_rustls::HttpsConnectorBuilder; +use ring::{ + rand::SystemRandom, + signature::{RsaKeyPair, RSA_PKCS1_SHA256}, +}; use serde::Deserializer; use serde::{Deserialize, Serialize}; use time::{Duration, OffsetDateTime}; +use crate::Error; + /// Represents an access token. All access tokens are Bearer tokens. /// /// Tokens should not be cached, the [`AuthenticationManager`] handles the correct caching @@ -70,6 +76,52 @@ impl Token { } } +/// A JSON Web Token ready for signing. +pub(crate) struct Signer { + key: RsaKeyPair, + rng: SystemRandom, +} + +impl Signer { + pub(crate) fn new(pem_pkcs8: &str) -> Result { + let private_keys = rustls_pemfile::pkcs8_private_keys(&mut pem_pkcs8.as_bytes()); + + let key = match private_keys { + Ok(mut keys) if !keys.is_empty() => { + keys.truncate(1); + keys.remove(0) + } + Ok(_) => { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Not enough private keys in PEM", + ) + .into()) + } + Err(_) => { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Error reading key from PEM", + ) + .into()) + } + }; + + Ok(Signer { + key: RsaKeyPair::from_pkcs8(&key).map_err(|_| Error::SignerInit)?, + rng: SystemRandom::new(), + }) + } + + pub(crate) fn sign(&self, input: &[u8]) -> Result, Error> { + let mut signature = vec![0; self.key.public_modulus_len()]; + self.key + .sign(&RSA_PKCS1_SHA256, &self.rng, input, &mut signature) + .map_err(|_| Error::SignerFailed)?; + Ok(signature) + } +} + fn deserialize_time<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, From 88ae9573073778bc31e6d4bebceddb96bf6ca438 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 14:27:27 +0100 Subject: [PATCH 25/30] Reuse signer for the lifetime of the CustomServiceAccount --- src/custom_service_account.rs | 28 +++++++++++++++++----------- src/types.rs | 6 ++++++ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/custom_service_account.rs b/src/custom_service_account.rs index 65e03d0..5037d34 100644 --- a/src/custom_service_account.rs +++ b/src/custom_service_account.rs @@ -7,14 +7,15 @@ use serde::{Deserialize, Serialize}; use crate::authentication_manager::ServiceAccount; use crate::error::Error; -use crate::types::{HyperClient, Token}; +use crate::types::{HyperClient, Signer, Token}; use crate::util::HyperExt; /// A custom service account containing credentials #[derive(Debug)] pub struct CustomServiceAccount { - tokens: RwLock, Token>>, credentials: ApplicationCredentials, + signer: Signer, + tokens: RwLock, Token>>, } impl CustomServiceAccount { @@ -31,17 +32,24 @@ impl CustomServiceAccount { /// Read service account credentials from the given JSON file pub fn from_file>(path: T) -> Result { let file = std::fs::File::open(path.as_ref()).map_err(Error::CustomServiceAccountPath)?; - Ok(Self { - credentials: serde_json::from_reader(file) - .map_err(Error::CustomServiceAccountCredentials)?, - tokens: RwLock::new(HashMap::new()), - }) + match serde_json::from_reader::<_, ApplicationCredentials>(file) { + Ok(credentials) => Self::new(credentials), + Err(e) => Err(Error::CustomServiceAccountCredentials(e)), + } } /// Read service account credentials from the given JSON string pub fn from_json(s: &str) -> Result { + match serde_json::from_str::(s) { + Ok(credentials) => Self::new(credentials), + Err(e) => Err(Error::CustomServiceAccountCredentials(e)), + } + } + + fn new(credentials: ApplicationCredentials) -> Result { Ok(Self { - credentials: serde_json::from_str(s).map_err(Error::CustomServiceAccountCredentials)?, + signer: Signer::new(&credentials.private_key)?, + credentials, tokens: RwLock::new(HashMap::new()), }) } @@ -74,12 +82,10 @@ impl ServiceAccount for CustomServiceAccount { async fn refresh_token(&self, client: &HyperClient, scopes: &[&str]) -> Result { use crate::jwt::Claims; use crate::jwt::GRANT_TYPE; - use crate::types::Signer; use hyper::header; use url::form_urlencoded; - let signer = Signer::new(&self.credentials.private_key)?; - let jwt = Claims::new(&self.credentials, scopes, None).to_jwt(&signer)?; + let jwt = Claims::new(&self.credentials, scopes, None).to_jwt(&self.signer)?; let rqbody = form_urlencoded::Serializer::new(String::new()) .extend_pairs(&[("grant_type", GRANT_TYPE), ("assertion", jwt.as_str())]) .finish(); diff --git a/src/types.rs b/src/types.rs index 334da04..1faca6f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -122,6 +122,12 @@ impl Signer { } } +impl fmt::Debug for Signer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Signer").finish() + } +} + fn deserialize_time<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, From b62d18bc6e6d66604b4029c5149d2c80f7da6821 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 14:32:32 +0100 Subject: [PATCH 26/30] Expose API access to the CustomServiceAccount's Signer --- src/custom_service_account.rs | 5 +++++ src/lib.rs | 2 +- src/types.rs | 7 ++++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/custom_service_account.rs b/src/custom_service_account.rs index 5037d34..d9f5cf5 100644 --- a/src/custom_service_account.rs +++ b/src/custom_service_account.rs @@ -54,6 +54,11 @@ impl CustomServiceAccount { }) } + /// The RSA PKCS1 SHA256 [`Signer`] used to sign JWT tokens + pub fn signer(&self) -> &Signer { + &self.signer + } + /// The project ID as found in the credentials pub fn project_id(&self) -> Option<&str> { self.credentials.project_id.as_deref() diff --git a/src/lib.rs b/src/lib.rs index a94cde8..c44a6c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,7 +74,7 @@ mod util; pub use authentication_manager::AuthenticationManager; pub use custom_service_account::CustomServiceAccount; pub use error::Error; -pub use types::Token; +pub use types::{Signer, Token}; /// Initialize GCP authentication /// diff --git a/src/types.rs b/src/types.rs index 1faca6f..6e93b58 100644 --- a/src/types.rs +++ b/src/types.rs @@ -76,8 +76,8 @@ impl Token { } } -/// A JSON Web Token ready for signing. -pub(crate) struct Signer { +/// An RSA PKCS1 SHA256 signer +pub struct Signer { key: RsaKeyPair, rng: SystemRandom, } @@ -113,7 +113,8 @@ impl Signer { }) } - pub(crate) fn sign(&self, input: &[u8]) -> Result, Error> { + /// Sign the input message and return the signature + pub fn sign(&self, input: &[u8]) -> Result, Error> { let mut signature = vec![0; self.key.public_modulus_len()]; self.key .sign(&RSA_PKCS1_SHA256, &self.rng, input, &mut signature) From 3963a98e8c947a2f3c4129fc5b24f8ecbe16a619 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 14:35:53 +0100 Subject: [PATCH 27/30] Switch from log to tracing Given that this library makes use of async code, it's useful to use spans. --- Cargo.toml | 2 +- src/authentication_manager.rs | 8 ++++---- src/custom_service_account.rs | 6 ++++-- src/default_authorized_user.rs | 2 +- src/default_service_account.rs | 4 ++-- src/util.rs | 2 +- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b7fbcfc..7d5f2eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,6 @@ base64 = "0.13" dirs-next = "2.0" hyper = { version = "0.14.2", features = ["client", "runtime", "http2"] } hyper-rustls = { version = "0.23.0", default-features = false, features = ["native-tokio", "http1", "http2"] } -log = "0.4" ring = "0.16.20" rustls = "0.20.2" rustls-pemfile = "0.2.1" @@ -29,6 +28,7 @@ serde_json = "1.0" thiserror = "1.0" time = { version = "0.3.5", features = ["serde"] } tokio = { version = "1.1", features = ["fs", "sync"] } +tracing = "0.1.29" url = "2" which = "4.2" diff --git a/src/authentication_manager.rs b/src/authentication_manager.rs index 3a417c0..606620a 100644 --- a/src/authentication_manager.rs +++ b/src/authentication_manager.rs @@ -43,7 +43,7 @@ impl AuthenticationManager { /// 4. Look for credentials in `.config/gcloud/application_default_credentials.json`; /// if found, use these credentials to request refresh tokens. pub async fn new() -> Result { - log::debug!("Initializing gcp_auth"); + tracing::debug!("Initializing gcp_auth"); if let Some(service_account) = CustomServiceAccount::from_env()? { return Ok(Self::from_custom_service_account(service_account)); } @@ -51,7 +51,7 @@ impl AuthenticationManager { let client = types::client(); let gcloud_error = match GCloudAuthorizedUser::new() { Ok(service_account) => { - log::debug!("Using GCloudAuthorizedUser"); + tracing::debug!("Using GCloudAuthorizedUser"); return Ok(Self::build(client, service_account)); } Err(e) => e, @@ -59,7 +59,7 @@ impl AuthenticationManager { let default_service_error = match DefaultServiceAccount::new(&client).await { Ok(service_account) => { - log::debug!("Using DefaultServiceAccount"); + tracing::debug!("Using DefaultServiceAccount"); return Ok(Self::build(client, service_account)); } Err(e) => e, @@ -67,7 +67,7 @@ impl AuthenticationManager { let default_user_error = match DefaultAuthorizedUser::new(&client).await { Ok(service_account) => { - log::debug!("Using DefaultAuthorizedUser"); + tracing::debug!("Using DefaultAuthorizedUser"); return Ok(Self::build(client, service_account)); } Err(e) => e, diff --git a/src/custom_service_account.rs b/src/custom_service_account.rs index d9f5cf5..8953d34 100644 --- a/src/custom_service_account.rs +++ b/src/custom_service_account.rs @@ -23,7 +23,9 @@ impl CustomServiceAccount { pub fn from_env() -> Result, Error> { std::env::var_os("GOOGLE_APPLICATION_CREDENTIALS") .map(|path| { - log::debug!("Reading credentials file from GOOGLE_APPLICATION_CREDENTIALS env var"); + tracing::debug!( + "Reading credentials file from GOOGLE_APPLICATION_CREDENTIALS env var" + ); Self::from_file(&PathBuf::from(path)) }) .transpose() @@ -100,7 +102,7 @@ impl ServiceAccount for CustomServiceAccount { .body(hyper::Body::from(rqbody)) .unwrap(); - log::debug!("requesting token from service account: {:?}", request); + tracing::debug!("requesting token from service account: {:?}", request); let token = client .request(request) .await diff --git a/src/default_authorized_user.rs b/src/default_authorized_user.rs index 6af7c4a..7aa0233 100644 --- a/src/default_authorized_user.rs +++ b/src/default_authorized_user.rs @@ -36,7 +36,7 @@ impl DefaultAuthorizedUser { } async fn get_token(client: &HyperClient) -> Result { - log::debug!("Loading user credentials file"); + tracing::debug!("Loading user credentials file"); let mut home = dirs_next::home_dir().ok_or(Error::NoHomeDir)?; home.push(Self::USER_CREDENTIALS_PATH); diff --git a/src/default_service_account.rs b/src/default_service_account.rs index 863a9f4..08795bb 100644 --- a/src/default_service_account.rs +++ b/src/default_service_account.rs @@ -35,7 +35,7 @@ impl DefaultServiceAccount { } async fn get_token(client: &HyperClient) -> Result { - log::debug!("Getting token from GCP instance metadata server"); + tracing::debug!("Getting token from GCP instance metadata server"); let req = Self::build_token_request(Self::DEFAULT_TOKEN_GCP_URI); let token = client .request(req) @@ -50,7 +50,7 @@ impl DefaultServiceAccount { #[async_trait] impl ServiceAccount for DefaultServiceAccount { async fn project_id(&self, client: &HyperClient) -> Result { - log::debug!("Getting project ID from GCP instance metadata server"); + tracing::debug!("Getting project ID from GCP instance metadata server"); let req = Self::build_token_request(Self::DEFAULT_PROJECT_ID_GCP_URI); let rsp = client.request(req).await.map_err(Error::ConnectionError)?; diff --git a/src/util.rs b/src/util.rs index a2940f6..68e6a25 100644 --- a/src/util.rs +++ b/src/util.rs @@ -27,7 +27,7 @@ impl HyperExt for hyper::Response { parts.status, String::from_utf8_lossy(body.as_ref()) ); - log::error!("{}", error); + tracing::error!("{}", error); return Err(Error::ServerUnavailable(error)); } From b3122287ab32003a6678c01a764ddd96321b9327 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jan 2022 14:38:55 +0100 Subject: [PATCH 28/30] Instrument core code paths with tracing spans --- Cargo.toml | 1 + src/authentication_manager.rs | 1 + src/custom_service_account.rs | 1 + src/default_authorized_user.rs | 1 + src/default_service_account.rs | 1 + 5 files changed, 5 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 7d5f2eb..6dfb112 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ thiserror = "1.0" time = { version = "0.3.5", features = ["serde"] } tokio = { version = "1.1", features = ["fs", "sync"] } tracing = "0.1.29" +tracing-futures = "0.2.5" url = "2" which = "4.2" diff --git a/src/authentication_manager.rs b/src/authentication_manager.rs index 606620a..1cdb656 100644 --- a/src/authentication_manager.rs +++ b/src/authentication_manager.rs @@ -42,6 +42,7 @@ impl AuthenticationManager { /// if it succeeds, use the default service account as the token source. /// 4. Look for credentials in `.config/gcloud/application_default_credentials.json`; /// if found, use these credentials to request refresh tokens. + #[tracing::instrument] pub async fn new() -> Result { tracing::debug!("Initializing gcp_auth"); if let Some(service_account) = CustomServiceAccount::from_env()? { diff --git a/src/custom_service_account.rs b/src/custom_service_account.rs index 8953d34..e067c0c 100644 --- a/src/custom_service_account.rs +++ b/src/custom_service_account.rs @@ -86,6 +86,7 @@ impl ServiceAccount for CustomServiceAccount { self.tokens.read().unwrap().get(&key).cloned() } + #[tracing::instrument] async fn refresh_token(&self, client: &HyperClient, scopes: &[&str]) -> Result { use crate::jwt::Claims; use crate::jwt::GRANT_TYPE; diff --git a/src/default_authorized_user.rs b/src/default_authorized_user.rs index 7aa0233..a27e81d 100644 --- a/src/default_authorized_user.rs +++ b/src/default_authorized_user.rs @@ -35,6 +35,7 @@ impl DefaultAuthorizedUser { .unwrap() } + #[tracing::instrument] async fn get_token(client: &HyperClient) -> Result { tracing::debug!("Loading user credentials file"); let mut home = dirs_next::home_dir().ok_or(Error::NoHomeDir)?; diff --git a/src/default_service_account.rs b/src/default_service_account.rs index 08795bb..6d782e5 100644 --- a/src/default_service_account.rs +++ b/src/default_service_account.rs @@ -34,6 +34,7 @@ impl DefaultServiceAccount { .unwrap() } + #[tracing::instrument] async fn get_token(client: &HyperClient) -> Result { tracing::debug!("Getting token from GCP instance metadata server"); let req = Self::build_token_request(Self::DEFAULT_TOKEN_GCP_URI); From 7e202ee2bca12e73edeb1899e0dc8b3eeac6f7bc Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 1 Feb 2022 10:27:42 +0100 Subject: [PATCH 29/30] Remove init() wrapper --- examples/simple.rs | 2 +- src/lib.rs | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index 98af699..be7991f 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,7 +1,7 @@ #[tokio::main] async fn main() -> Result<(), Box> { env_logger::init(); - let authentication_manager = gcp_auth::init().await?; + let authentication_manager = gcp_auth::AuthenticationManager::new().await?; let _token = authentication_manager .get_token(&["https://www.googleapis.com/auth/cloud-platform"]) .await?; diff --git a/src/lib.rs b/src/lib.rs index c44a6c0..0e73757 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,10 +75,3 @@ pub use authentication_manager::AuthenticationManager; pub use custom_service_account::CustomServiceAccount; pub use error::Error; pub use types::{Signer, Token}; - -/// Initialize GCP authentication -/// -/// This is a convenience wrapper around [`AuthenticationManager::new()`]. -pub async fn init() -> Result { - AuthenticationManager::new().await -} From 3c6185dc3d65077d763333357aec59527f9eed5e Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 1 Feb 2022 10:34:08 +0100 Subject: [PATCH 30/30] Remove AuthenticationManager::from_custom_service_account() in favor of documentation --- src/authentication_manager.rs | 13 +++++-------- src/custom_service_account.rs | 3 +++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/authentication_manager.rs b/src/authentication_manager.rs index 1cdb656..647d55d 100644 --- a/src/authentication_manager.rs +++ b/src/authentication_manager.rs @@ -17,7 +17,9 @@ pub(crate) trait ServiceAccount: Send + Sync { /// Authentication manager is responsible for caching and obtaing credentials for the required scope /// -/// Cacheing for the full life time is ensured +/// Construct the authentication manager with [`AuthenticationManager::new()`] or by creating +/// a [`CustomServiceAccount`], then converting it into an `AuthenticationManager` using the `From` +/// impl. pub struct AuthenticationManager { pub(crate) client: HyperClient, pub(crate) service_account: Box, @@ -25,11 +27,6 @@ pub struct AuthenticationManager { } impl AuthenticationManager { - /// Create an `AuthenticationManager` directly from a custom service account - pub fn from_custom_service_account(service_account: CustomServiceAccount) -> Self { - Self::build(types::client(), service_account) - } - /// Finds a service account provider to get authentication tokens from /// /// Tries the following approaches, in order: @@ -46,7 +43,7 @@ impl AuthenticationManager { pub async fn new() -> Result { tracing::debug!("Initializing gcp_auth"); if let Some(service_account) = CustomServiceAccount::from_env()? { - return Ok(Self::from_custom_service_account(service_account)); + return Ok(service_account.into()); } let client = types::client(); @@ -121,6 +118,6 @@ impl AuthenticationManager { impl From for AuthenticationManager { fn from(service_account: CustomServiceAccount) -> Self { - Self::from_custom_service_account(service_account) + Self::build(types::client(), service_account) } } diff --git a/src/custom_service_account.rs b/src/custom_service_account.rs index e067c0c..0b8d22b 100644 --- a/src/custom_service_account.rs +++ b/src/custom_service_account.rs @@ -11,6 +11,9 @@ use crate::types::{HyperClient, Signer, Token}; use crate::util::HyperExt; /// A custom service account containing credentials +/// +/// Once initialized, a [`CustomServiceAccount`] can be converted into an [`AuthenticationManager`] +/// using the applicable `From` implementation. #[derive(Debug)] pub struct CustomServiceAccount { credentials: ApplicationCredentials,