Skip to content

Commit

Permalink
Support service_account in ApplicationDefaultCredentials
Browse files Browse the repository at this point in the history
  • Loading branch information
tustvold committed Oct 13, 2023
1 parent c6387c1 commit b79b1b6
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 73 deletions.
87 changes: 26 additions & 61 deletions object_store/src/gcp/credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,16 @@

use crate::client::retry::RetryExt;
use crate::client::token::TemporaryToken;
use crate::client::{TokenCredentialProvider, TokenProvider};
use crate::gcp::credential::Error::UnsupportedCredentialsType;
use crate::gcp::{GcpCredentialProvider, STORE};
use crate::ClientOptions;
use crate::client::TokenProvider;
use crate::gcp::STORE;
use crate::RetryConfig;
use async_trait::async_trait;
use base64::prelude::BASE64_URL_SAFE_NO_PAD;
use base64::Engine;
use futures::TryFutureExt;
use reqwest::{Client, Method};
use ring::signature::RsaKeyPair;
use serde::Deserialize;
use snafu::{ResultExt, Snafu};
use std::env;
use std::fs::File;
Expand All @@ -37,6 +36,10 @@ use std::sync::Arc;
use std::time::{Duration, Instant};
use tracing::info;

// TODO: https://cloud.google.com/storage/docs/authentication#oauth-scopes
pub const DEFAULT_SCOPE: &str = "https://www.googleapis.com/auth/devstorage.full_control";
pub const DEFAULT_AUDIENCE: &str = "https://www.googleapis.com/oauth2/v4/token";

#[derive(Debug, Snafu)]
pub enum Error {
#[snafu(display("Unable to open service account file from {}: {}", path.display(), source))]
Expand Down Expand Up @@ -68,9 +71,6 @@ pub enum Error {

#[snafu(display("Error getting token response body: {}", source))]
TokenResponseBody { source: reqwest::Error },

#[snafu(display("Unsupported ApplicationCredentials type: {}", type_))]
UnsupportedCredentialsType { type_: String },
}

impl From<Error> for crate::Error {
Expand Down Expand Up @@ -231,6 +231,8 @@ impl TokenProvider for OAuthProvider {
.await
.context(TokenResponseBodySnafu)?;

println!("{}", response.access_token);

Ok(TemporaryToken {
token: Arc::new(GcpCredential {
bearer: response.access_token,
Expand Down Expand Up @@ -291,16 +293,12 @@ impl ServiceAccountCredentials {
}

/// Create an [`OAuthProvider`] from this credentials struct.
pub fn oauth_provider(
self,
scope: &str,
audience: &str,
) -> crate::Result<OAuthProvider> {
pub fn oauth_provider(self) -> crate::Result<OAuthProvider> {
Ok(OAuthProvider::new(
self.client_email,
self.private_key,
scope.to_string(),
audience.to_string(),
DEFAULT_SCOPE.to_string(),
DEFAULT_AUDIENCE.to_string(),
)?)
}
}
Expand Down Expand Up @@ -404,62 +402,29 @@ impl TokenProvider for InstanceCredentialProvider {
}
}

/// ApplicationDefaultCredentials
/// <https://google.aip.dev/auth/4110>
pub fn application_default_credentials(
path: Option<&str>,
client: &ClientOptions,
retry: &RetryConfig,
) -> crate::Result<Option<GcpCredentialProvider>> {
let file = match ApplicationDefaultCredentialsFile::read(path)? {
Some(x) => x,
None => return Ok(None),
};

match file.type_.as_str() {
// <https://google.aip.dev/auth/4113>
"authorized_user" => {
let token = AuthorizedUserCredentials {
client_id: file.client_id,
client_secret: file.client_secret,
refresh_token: file.refresh_token,
};

Ok(Some(Arc::new(TokenCredentialProvider::new(
token,
client.client()?,
retry.clone(),
))))
}
type_ => Err(UnsupportedCredentialsType {
type_: type_.to_string(),
}
.into()),
}
}

/// A deserialized `application_default_credentials.json`-file.
///
/// <https://cloud.google.com/docs/authentication/application-default-credentials#personal>
/// <https://google.aip.dev/auth/4110>
#[derive(serde::Deserialize)]
struct ApplicationDefaultCredentialsFile {
#[serde(default)]
client_id: String,
#[serde(default)]
client_secret: String,
#[serde(default)]
refresh_token: String,
#[serde(rename = "type")]
type_: String,
#[serde(tag = "type")]
pub enum ApplicationDefaultCredentials {
/// <https://google.aip.dev/auth/4112>
#[serde(rename = "service_account")]
ServiceAccount(ServiceAccountCredentials),
/// <https://google.aip.dev/auth/4113>
#[serde(rename = "authorized_user")]
AuthorizedUser(AuthorizedUserCredentials),
}

impl ApplicationDefaultCredentialsFile {
impl ApplicationDefaultCredentials {
const CREDENTIALS_PATH: &'static str =
".config/gcloud/application_default_credentials.json";

// Create a new application default credential in the following situations:
// 1. a file is passed in and the type matches.
// 2. without argument if the well-known configuration file is present.
fn read(path: Option<&str>) -> Result<Option<Self>, Error> {
pub fn read(path: Option<&str>) -> Result<Option<Self>, Error> {
if let Some(path) = path {
return read_credentials_file::<Self>(path).map(Some);
}
Expand All @@ -478,8 +443,8 @@ impl ApplicationDefaultCredentialsFile {
const DEFAULT_TOKEN_GCP_URI: &str = "https://accounts.google.com/o/oauth2/token";

/// <https://google.aip.dev/auth/4113>
#[derive(Debug)]
struct AuthorizedUserCredentials {
#[derive(Debug, Deserialize)]
pub struct AuthorizedUserCredentials {
client_id: String,
client_secret: String,
refresh_token: String,
Expand Down
33 changes: 21 additions & 12 deletions object_store/src/gcp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ use crate::{
};

use credential::{
application_default_credentials, default_gcs_base_url, InstanceCredentialProvider,
ServiceAccountCredentials,
default_gcs_base_url, InstanceCredentialProvider, ServiceAccountCredentials,
};

mod credential;
Expand All @@ -68,6 +67,7 @@ const STORE: &str = "GCS";

/// [`CredentialProvider`] for [`GoogleCloudStorage`]
pub type GcpCredentialProvider = Arc<dyn CredentialProvider<Credential = GcpCredential>>;
use crate::gcp::credential::{ApplicationDefaultCredentials, DEFAULT_AUDIENCE};
pub use credential::GcpCredential;

#[derive(Debug, Snafu)]
Expand Down Expand Up @@ -1043,10 +1043,8 @@ impl GoogleCloudStorageBuilder {
};

// Then try to initialize from the application credentials file, or the environment.
let application_default_credentials = application_default_credentials(
let application_default_credentials = ApplicationDefaultCredentials::read(
self.application_credentials_path.as_deref(),
&self.client_options,
&self.retry_config,
)?;

let disable_oauth = service_account_credentials
Expand All @@ -1059,10 +1057,6 @@ impl GoogleCloudStorageBuilder {
.map(|c| c.gcs_base_url.clone())
.unwrap_or_else(default_gcs_base_url);

// TODO: https://cloud.google.com/storage/docs/authentication#oauth-scopes
let scope = "https://www.googleapis.com/auth/devstorage.full_control";
let audience = "https://www.googleapis.com/oauth2/v4/token";

let credentials = if let Some(credentials) = self.credentials {
credentials
} else if disable_oauth {
Expand All @@ -1071,15 +1065,30 @@ impl GoogleCloudStorageBuilder {
})) as _
} else if let Some(credentials) = service_account_credentials {
Arc::new(TokenCredentialProvider::new(
credentials.oauth_provider(scope, audience)?,
credentials.oauth_provider()?,
self.client_options.client()?,
self.retry_config.clone(),
)) as _
} else if let Some(credentials) = application_default_credentials {
credentials
match credentials {
ApplicationDefaultCredentials::AuthorizedUser(token) => {
Arc::new(TokenCredentialProvider::new(
token,
self.client_options.client()?,
self.retry_config.clone(),
)) as _
}
ApplicationDefaultCredentials::ServiceAccount(token) => {
Arc::new(TokenCredentialProvider::new(
token.oauth_provider()?,
self.client_options.client()?,
self.retry_config.clone(),
)) as _
}
}
} else {
Arc::new(TokenCredentialProvider::new(
InstanceCredentialProvider::new(audience),
InstanceCredentialProvider::new(DEFAULT_AUDIENCE),
self.client_options.clone().with_allow_http(true).client()?,
self.retry_config.clone(),
)) as _
Expand Down

0 comments on commit b79b1b6

Please sign in to comment.