Skip to content

Commit

Permalink
Support external-account (#180)
Browse files Browse the repository at this point in the history
  • Loading branch information
yoshidan authored Jul 19, 2023
1 parent 09444a7 commit 2d6c3a7
Show file tree
Hide file tree
Showing 21 changed files with 800 additions and 56 deletions.
11 changes: 6 additions & 5 deletions bigquery/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "google-cloud-bigquery"
version = "0.2.0"
version = "0.3.0"
edition = "2021"
authors = ["yoshidan <[email protected]>"]
repository = "https://github.com/yoshidan/google-cloud-rust/tree/main/bigquery"
Expand All @@ -22,13 +22,13 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version="1.20", features=["macros"] }
time = { version = "0.3", features = ["std", "macros", "formatting", "parsing", "serde"] }
arrow = { version="42.0", default-features = false, features = ["ipc"] }
arrow = { version="44.0", default-features = false, features = ["ipc"] }
base64 = "0.21"
bigdecimal = { version="0.3", features=["serde"] }
num-bigint = "0.4"
backon = "0.4"

google-cloud-auth = { optional = true, version = "0.11", path="../foundation/auth", default-features=false }
google-cloud-auth = { optional = true, version = "0.12", path="../foundation/auth", default-features=false }

[dev-dependencies]
tokio = { version="1.20", features=["rt-multi-thread"] }
Expand All @@ -41,7 +41,8 @@ base64-serde = "0.7"

[features]
default = ["default-tls", "auth"]
default-tls = ["reqwest/default-tls"]
rustls-tls = ["reqwest/rustls-tls"]
default-tls = ["reqwest/default-tls","google-cloud-auth?/default-tls"]
rustls-tls = ["reqwest/rustls-tls","google-cloud-auth?/rustls-tls"]
trace = []
auth = ["google-cloud-auth"]
external-account = ["google-cloud-auth?/external-account"]
3 changes: 2 additions & 1 deletion deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,9 @@ skip = [
{ name = "syn", version = "=1.0.109" },
{ name = "regex-syntax", version = "=0.6.29" },
{ name = "webpki-roots", version = "=0.22.6" },
{ name = "rustls-webpki", version = "=0.100.1" },
{ name = "regex-automata", version = "=0.1.10" },
{ name = "hashbrown", version = "=0.14.0" },
{ name = "miniz_oxide", version = "=0.6.2" }
]
# Similarly to `skip` allows you to skip certain crates during duplicate
# detection. Unlike skip, it also includes the entire tree of transitive
Expand Down
10 changes: 9 additions & 1 deletion foundation/auth/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "google-cloud-auth"
version = "0.11.0"
version = "0.12.0"
authors = ["yoshidan <[email protected]>"]
edition = "2021"
repository = "https://github.com/yoshidan/google-cloud-rust/tree/main/foundation/auth"
Expand All @@ -25,6 +25,13 @@ google-cloud-token = { version = "0.1.1", path = "../token" }
base64 = "0.21"
time = "0.3"

url = { version="2.4", optional = true }
path-clean = { version="1.0", optional = true }
sha2 = {version = "0.10", optional = true}
percent-encoding = { version="2.3", optional = true }
hmac = { version = "0.12", optional = true }
hex = { version = "0.4", optional = true }

[dev-dependencies]
tokio = { version = "1.20", features = ["test-util", "rt-multi-thread", "macros"]}
tracing-subscriber = {version="0.3", features=["env-filter","std"]}
Expand All @@ -35,3 +42,4 @@ serial_test = "0.9"
default = ["default-tls"]
default-tls = ["reqwest/default-tls"]
rustls-tls = ["reqwest/rustls-tls"]
external-account = ["sha2", "path-clean", "url", "percent-encoding", "hmac", "hex"]
2 changes: 1 addition & 1 deletion foundation/auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ preferring the first location found:

https://cloud.google.com/iam/docs/workload-identity-federation

- [ ] AWS
- [x] AWS
- [ ] Azure Active Directory
- [ ] On-premises Active Directory
- [ ] Okta
Expand Down
59 changes: 36 additions & 23 deletions foundation/auth/src/credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,42 @@ use crate::error::Error;
const CREDENTIALS_FILE: &str = "application_default_credentials.json";

#[allow(dead_code)]
#[derive(Deserialize, Clone)]
pub(crate) struct Format {
tp: String,
subject_token_field_name: String,
#[derive(Deserialize, Clone, Debug)]
pub struct ServiceAccountImpersonationInfo {
pub(crate) token_lifetime_seconds: i32,
}

#[allow(dead_code)]
#[derive(Deserialize, Clone)]
#[derive(Deserialize, Clone, Debug)]
pub struct ExecutableConfig {
pub(crate) command: String,
pub(crate) timeout_millis: Option<i32>,
pub(crate) output_file: String,
}

#[allow(dead_code)]
#[derive(Deserialize, Clone, Debug)]
pub struct Format {
pub(crate) tp: String,
pub(crate) subject_token_field_name: String,
}

#[allow(dead_code)]
#[derive(Deserialize, Clone, Debug)]
pub struct CredentialSource {
file: String,
url: String,
headers: std::collections::HashMap<String, String>,
environment_id: String,
region_url: String,
regional_cred_verification_url: String,
cred_verification_url: String,
format: Format,
pub(crate) file: Option<String>,

pub(crate) url: Option<String>,
pub(crate) headers: Option<std::collections::HashMap<String, String>>,

pub(crate) executable: Option<ExecutableConfig>,

pub(crate) environment_id: Option<String>,
pub(crate) region_url: Option<String>,
pub(crate) regional_cred_verification_url: Option<String>,
pub(crate) cred_verification_url: Option<String>,
pub(crate) imdsv2_session_token_url: Option<String>,
pub(crate) format: Option<Format>,
}

#[allow(dead_code)]
Expand All @@ -49,21 +68,15 @@ pub struct CredentialsFile {
// External Account fields
pub audience: Option<String>,
pub subject_token_type: Option<String>,
#[serde(rename = "token_url")]
pub token_url_external: Option<String>,
pub token_info_url: Option<String>,
pub service_account_impersonation_url: Option<String>,
pub service_account_impersonation: Option<ServiceAccountImpersonationInfo>,
pub delegates: Option<Vec<String>>,
pub credential_source: Option<CredentialSource>,
pub quota_project_id: Option<String>,
}

#[allow(dead_code)]
#[derive(Deserialize, Clone)]
pub struct Credentials {
client_id: String,
client_secret: String,
redirect_urls: Vec<String>,
auth_uri: String,
token_uri: String,
pub workforce_pool_user_project: Option<String>,
}

impl CredentialsFile {
Expand Down
10 changes: 10 additions & 0 deletions foundation/auth/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,14 @@ pub enum Error {

#[error("invalid authentication token")]
InvalidToken,

#[error(transparent)]
TimeParse(#[from] time::error::Parse),

#[cfg(feature = "external-account")]
#[error("external account error : {0}")]
ExternalAccountSource(#[from] crate::token_source::external_account_source::error::Error),

#[error("unexpected impersonation token response : status={0}, detail={1}")]
UnexpectedImpersonateTokenResponse(u16, String),
}
38 changes: 33 additions & 5 deletions foundation/auth/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use crate::{credentials, error};

pub(crate) const SERVICE_ACCOUNT_KEY: &str = "service_account";
const USER_CREDENTIALS_KEY: &str = "authorized_user";
#[cfg(feature = "external-account")]
const EXTERNAL_ACCOUNT_KEY: &str = "external_account";

#[derive(Debug, Clone, Default)]
pub struct Config<'a> {
Expand Down Expand Up @@ -82,7 +84,7 @@ pub async fn create_token_source_from_credentials(
credentials: &CredentialsFile,
config: &Config<'_>,
) -> Result<Box<dyn TokenSource>, error::Error> {
let ts = credentials_from_json_with_params(credentials, config)?;
let ts = credentials_from_json_with_params(credentials, config).await?;
let token = ts.token().await?;
Ok(Box::new(ReuseTokenSource::new(ts, token)))
}
Expand All @@ -109,9 +111,9 @@ pub async fn create_token_source(config: Config<'_>) -> Result<Box<dyn TokenSour
create_token_source_from_project(&project, config).await
}

fn credentials_from_json_with_params(
async fn credentials_from_json_with_params(
credentials: &CredentialsFile,
config: &Config,
config: &Config<'_>,
) -> Result<Box<dyn TokenSource>, error::Error> {
match credentials.tp.as_str() {
SERVICE_ACCOUNT_KEY => {
Expand All @@ -137,8 +139,34 @@ fn credentials_from_json_with_params(
}
}
USER_CREDENTIALS_KEY => Ok(Box::new(UserAccountTokenSource::new(credentials)?)),
//TODO support GDC https://console.developers.google.com,
//TODO support external account
#[cfg(feature = "external-account")]
EXTERNAL_ACCOUNT_KEY => {
let ts = crate::token_source::external_account_source::ExternalAccountTokenSource::new(
config.scopes_to_string(" "),
credentials.clone(),
)
.await?;
if let Some(impersonation_url) = &credentials.service_account_impersonation_url {
let url = impersonation_url.clone();
let mut scopes = config.scopes.map(|v| v.to_vec()).unwrap_or(vec![]);
scopes.push("https://www.googleapis.com/auth/cloud-platform");
let scopes = scopes.iter().map(|e| e.to_string()).collect();
let lifetime = credentials
.service_account_impersonation
.clone()
.map(|v| v.token_lifetime_seconds);
let ts = crate::token_source::impersonate_token_source::ImpersonateTokenSource::new(
url,
vec![],
scopes,
lifetime,
Box::new(ts),
);
Ok(Box::new(ts))
} else {
Ok(Box::new(ts))
}
}
_ => Err(error::Error::UnsupportedAccountType(credentials.tp.to_string())),
}
}
Loading

0 comments on commit 2d6c3a7

Please sign in to comment.