Skip to content

Commit

Permalink
Merge pull request #31 from thrykol/master
Browse files Browse the repository at this point in the history
Add option to use gcloud for getting user auth
  • Loading branch information
hrvolapeter authored Aug 4, 2021
2 parents 04eb69a + 107776a commit 3007b55
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 14 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ serde = {version = "1.0", features = ["derive"]}
serde_json = "1.0"
tokio = { version = "1.1", features = ["fs"] }
url = "2"
which = "4.2"
async-trait = "0.1"
thiserror = "1.0"
dirs-next = "2.0"
Expand Down
35 changes: 24 additions & 11 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,32 @@ pub enum Error {
///
/// Application can authenticate against GCP using:
///
/// - Defaul service account - available inside GCP platform using GCP Instance Metadata server
/// - Default service account - available inside GCP platform using GCP Instance Metadata server
/// - Service account file - provided using `GOOGLE_APPLICATION_CREDENTIALS` with path
/// - GCloud authorized user - retrieved using `gcloud auth` command
///
/// All authentication methods have been tested and none succeeded.
/// Service account file can be donwloaded from GCP in json format.
/// Service account file can be downloaded from GCP in json format.
#[error("No available authentication method was discovered")]
NoAuthMethod(Box<Error>, Box<Error>, Box<Error>),
NoAuthMethod(Box<Error>, Box<Error>, Box<Error>, Box<Error>),

/// Error in underlaying RustTLS library.
/// Might signal problem with establishin secure connection using trusted certificates
/// Error in underlying RustTLS library.
/// Might signal problem with establishing secure connection using trusted certificates
#[error("TLS error")]
TLSError(rustls::TLSError),

/// Error when establishing connection to OAuth server
#[error("Could not establish connection with OAuth server")]
OAuthConnectionError(hyper::Error),

/// Error when parsin response from OAuth server
#[error("Could not parse OAuth server reponse")]
/// Error when parsing response from OAuth server
#[error("Could not parse OAuth server response")]
OAuthParsingError(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 donwoloaded in GCP Console when creating service account.
/// 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,

Expand All @@ -43,7 +44,7 @@ pub enum Error {
/// Wrong format of custom application profile
///
/// Application profile is downloaded from GCP console and is stored in filesystem on the server.
/// Full path is passed to library by seeting `GOOGLE_APPLICATION_CREDENTIALS` variable with path as a value.
/// Full path is passed to library by setting `GOOGLE_APPLICATION_CREDENTIALS` variable with path as a value.
#[error("Application profile provided in `GOOGLE_APPLICATION_CREDENTIALS` was not parsable")]
ApplicationProfileFormat(serde_json::error::Error),

Expand All @@ -63,7 +64,7 @@ pub enum Error {
ConnectionError(hyper::Error),

/// Could not parse response from server
#[error("Could not parse server reponse")]
#[error("Could not parse server response")]
ParsingError(serde_json::error::Error),

/// Could not connect to server
Expand All @@ -74,7 +75,7 @@ pub enum Error {
#[error("Couldn't choose signing scheme")]
SignerSchemeError,

/// Could not initalize signer
/// Could not initialize signer
#[error("Couldn't initialize signer")]
SignerInit,

Expand All @@ -94,6 +95,18 @@ pub enum Error {
#[error("Project ID is invalid UTF-8")]
ProjectIdNonUtf8,

/// GCloud executable not found
#[error("GCloud executable not found in $PATH")]
GCloudNotFound,

/// GCloud returned an error status
#[error("GCloud returned a non OK status")]
GCloudError,

/// GCloud output couldn't be parsed
#[error("Failed to parse output of GCloud")]
GCloudParseError,

/// Represents all other cases of `std::io::Error`.
#[error(transparent)]
IOError(#[from] std::io::Error),
Expand Down
51 changes: 51 additions & 0 deletions src/gcloud_authorized_user.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use crate::authentication_manager::ServiceAccount;
use crate::error::Error;
use crate::error::Error::{
GCloudError, GCloudNotFound, GCloudParseError, NoProjectId, ParsingError,
};
use crate::types::HyperClient;
use crate::Token;
use async_trait::async_trait;
use serde_json::json;
use std::path::PathBuf;
use std::process::Command;
use which::which;

#[derive(Debug)]
pub(crate) struct GCloudAuthorizedUser {
gcloud: PathBuf,
}

impl GCloudAuthorizedUser {
pub(crate) async fn new() -> Result<Self, Error> {
which("gcloud")
.map_err(|_| GCloudNotFound)
.map(|path| Self { gcloud: path })
}
}

#[async_trait]
impl ServiceAccount for GCloudAuthorizedUser {
async fn project_id(&self, _: &HyperClient) -> Result<String, Error> {
Err(NoProjectId)
}

fn get_token(&self, _scopes: &[&str]) -> Option<Token> {
None
}

async fn refresh_token(&self, _client: &HyperClient, _scopes: &[&str]) -> Result<Token, Error> {
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::<Token>(json!({ "access_token": access_token.trim() }))
.map_err(ParsingError)
}),
_ => Err(GCloudError),
}
}
}
15 changes: 12 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//! The downloaded JSON file should be provided without any further modification.
//! 2. Invoking the library inside GCP environment fetches the default service account for the service and
//! the application is authenticated using that particular account
//! 3. Application default credentials. Local user authetincation for development purposes created using `gcloud auth` application.
//! 3. Application default credentials. Local user authentication for development purposes created using `gcloud auth` application.
//! 4. If none of the above can be used an error occurs
//!
//! The tokens are single-use and as such they shouldn't be cached and for each use a new token should be requested.
Expand All @@ -33,7 +33,7 @@
//! `GOOGLE_APPLICATION_CREDENTIALS` environment variable.
//!
//! ```async
//! // GOOGLE_APPLICATION_CREDENTIALS environtment variable is set-up
//! // GOOGLE_APPLICATION_CREDENTIALS environment variable is set-up
//! let authentication_manager = gcp_auth::init().await?;
//! let token = authentication_manager.get_token().await?;
//! ```
Expand All @@ -46,7 +46,7 @@
//! ```
//!
//! # Local user authentication
//! This authentication method allows developers to authenticate again GCP services when developign locally.
//! This authentication method allows developers to authenticate again GCP services when developing locally.
//! The method is intended only for development. Credentials can be set-up using `gcloud auth` utility.
//! Credentials are read from file `~/.config/gcloud/application_default_credentials.json`.
//!
Expand All @@ -66,6 +66,7 @@ mod custom_service_account;
mod default_authorized_user;
mod default_service_account;
mod error;
mod gcloud_authorized_user;
mod jwt;
mod types;
mod util;
Expand Down Expand Up @@ -114,6 +115,13 @@ async fn get_authentication_manager(
service_account: Box::new(service_account),
});
}
let gcloud = gcloud_authorized_user::GCloudAuthorizedUser::new().await;
if let Ok(service_account) = gcloud {
return Ok(AuthenticationManager {
client: client.clone(),
service_account: Box::new(service_account),
});
}
let default = default_service_account::DefaultServiceAccount::new(&client).await;
if let Ok(service_account) = default {
return Ok(AuthenticationManager {
Expand All @@ -130,6 +138,7 @@ async fn get_authentication_manager(
}
Err(Error::NoAuthMethod(
Box::new(custom.unwrap_err()),
Box::new(gcloud.unwrap_err()),
Box::new(default.unwrap_err()),
Box::new(user.unwrap_err()),
))
Expand Down
1 change: 1 addition & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize};
pub struct Token {
access_token: String,
#[serde(
default,
deserialize_with = "deserialize_time",
rename(deserialize = "expires_in")
)]
Expand Down

0 comments on commit 3007b55

Please sign in to comment.