Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
8f201b3
Externalize Token Arc
djc May 23, 2024
166ae3e
Modularize logic for CustomServiceAccount::from_json()
djc May 23, 2024
d7b7402
Modularize logic for CustomServiceAccount::from_file()
djc May 23, 2024
57c3f3f
Modularize logic for CustomServiceAccount::from_env()
djc May 23, 2024
6add5ae
Keep HTTP clients in ServiceAccount implementations
djc May 23, 2024
571caae
Store project ID in an Arc
djc May 23, 2024
7d37728
Move HyperExt trait into types module
djc May 23, 2024
30f4530
Create newtype for HTTP client
djc May 23, 2024
adb6376
Rename HyperClient to HttpClient
djc May 23, 2024
5ee6de1
Use HttpClient constructor directly
djc May 23, 2024
462d0af
Fold HyperExt into HttpClient
djc May 23, 2024
b0fd7c0
Reorder items in types module
djc May 23, 2024
81443da
Deduplicate HTTP retry logic
djc May 23, 2024
d21fba3
Import more hyper types
djc May 23, 2024
bde8dfd
Move imports to the top of the file
djc May 23, 2024
4100ef2
Move consts to the bottom of modules
djc May 23, 2024
17bec15
Split fetch logic from storing for CustomServiceAccount
djc May 23, 2024
d3257b5
Make ServiceAccount::get_token() async
djc May 23, 2024
8630154
Rename and reorder ServiceAccount::token() method
djc May 23, 2024
e043188
Use inner RwLock to avoid thundering herd problems
djc May 23, 2024
e8a95b4
Inline refresh_token() logic
djc May 23, 2024
c6cde6e
Force fetching project ID once for MetadataServiceAccount
djc May 23, 2024
b1ffb7d
Rename ServiceAccount to TokenProvider
djc May 23, 2024
3f5ec79
Move TokenProvider crate into crate root
djc May 23, 2024
443b5d1
Make TokenProvider trait public
djc May 23, 2024
d98a0d2
Expose trait implementers directly
djc May 23, 2024
c0636b2
Move Error type into crate root
djc May 23, 2024
5ea9a32
Simplify lint configuration
djc May 23, 2024
f6ea22e
Replace env_logger with tracing-subscriber in example
djc May 25, 2024
aaa2bbd
Rename default_authorized_user module to config_default_credentials
djc May 25, 2024
ca495a5
Improve tracing events
djc May 25, 2024
9bbf5d4
Rename default_service_account to metadata_service_account
djc May 25, 2024
e051388
Use lowercase messages for tracing events
djc May 25, 2024
08134c0
Rename token retrieval methods to fetch_token()
djc May 25, 2024
88da567
Further improve clarity of tracing events
djc May 25, 2024
86f83c5
Deduplicate HTTP response handling
djc May 25, 2024
6b64eaa
Simplify Error type
djc May 25, 2024
a36cebe
Log some credential details (no secrets)
djc May 26, 2024
44e0154
Import tracing event macros
djc May 26, 2024
a7961ec
Make provider implementations public
djc May 26, 2024
cb4a1bf
Upgrade to hyper 1 and hyper-rustls 0.27
djc May 26, 2024
f53a157
Remove unused Serialize implementations for credentials types
djc May 26, 2024
a7380a4
Move credentials types to types module
djc May 26, 2024
7ada5d4
Clean up Debug implementations for credentials types
djc May 26, 2024
38e8123
Remove unnecessary Clone derives for credentials types
djc May 26, 2024
98082d7
Infer deserialized types
djc May 26, 2024
4deb466
Avoid temporary path allocation
djc May 26, 2024
e3e967b
Rename credential types
djc May 26, 2024
98b6364
Modularize retrieval of AuthorizedUserRefreshToken data
djc May 26, 2024
4004adf
Bump version to 0.12
djc May 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "gcp_auth"
version = "0.11.1"
version = "0.12.0"
edition = "2021"
rust-version = "1.70"
repository = "https://github.com/hrvolapeter/gcp_auth"
Expand All @@ -12,18 +12,21 @@ readme = "README.md"
license = "MIT"

[features]
default = ["hyper-rustls/rustls-native-certs"]
default = ["hyper-rustls/rustls-native-certs", "hyper-rustls/ring"]
webpki-roots = ["hyper-rustls/webpki-roots"]

[dependencies]
async-trait = "0.1"
base64 = "0.22"
bytes = "1"
chrono = { version = "0.4.31", features = ["serde"] }
home = "0.5.5"
hyper = { version = "0.14.2", features = ["client", "runtime", "http2"] }
hyper-rustls = { version = "0.25", default-features = false, features = ["http1", "http2", "ring", "tokio-runtime"] }
http = "1"
http-body-util = "0.1"
hyper = { version = "1", default-features = false, features = ["client", "http1", "http2"] }
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2"] }
hyper-util = { version = "0.1.4", features = ["client-legacy"] }
ring = "0.17"
rustls = "0.22"
rustls-pemfile = "2"
serde = { version = "1.0", features = ["derive", "rc"] }
serde_json = "1.0"
Expand All @@ -35,5 +38,5 @@ url = "2"
which = "6.0"

[dev-dependencies]
env_logger = "0.11"
tokio = { version = "1.1", features = ["macros", "parking_lot", "rt-multi-thread"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
14 changes: 10 additions & 4 deletions examples/simple.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
let authentication_manager = gcp_auth::AuthenticationManager::new().await?;
let _token = authentication_manager
.get_token(&["https://www.googleapis.com/auth/cloud-platform"])
tracing::subscriber::set_global_default(
tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.finish(),
)
.unwrap();

let token_provider = gcp_auth::provider().await?;
let _token = token_provider
.token(&["https://www.googleapis.com/auth/cloud-platform"])
.await?;
Ok(())
}
127 changes: 0 additions & 127 deletions src/authentication_manager.rs

This file was deleted.

107 changes: 107 additions & 0 deletions src/config_default_credentials.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use std::sync::Arc;

use async_trait::async_trait;
use bytes::Bytes;
use http_body_util::Full;
use hyper::header::CONTENT_TYPE;
use hyper::{Method, Request};
use serde::Serialize;
use tokio::sync::RwLock;
use tracing::{debug, instrument, Level};

use crate::types::{AuthorizedUserRefreshToken, HttpClient, Token};
use crate::{Error, TokenProvider};

/// A token provider that uses the default user credentials
///
/// Reads credentials from `.config/gcloud/application_default_credentials.json`.
#[derive(Debug)]
pub struct ConfigDefaultCredentials {
client: HttpClient,
token: RwLock<Arc<Token>>,
credentials: AuthorizedUserRefreshToken,
}

impl ConfigDefaultCredentials {
/// Check for user credentials in the default location and try to deserialize them
pub async fn new() -> Result<Self, Error> {
let client = HttpClient::new()?;
Self::with_client(&client).await
}

pub(crate) async fn with_client(client: &HttpClient) -> Result<Self, Error> {
debug!("try to load credentials from {}", USER_CREDENTIALS_PATH);
let mut home = home::home_dir().ok_or(Error::Str("home directory not found"))?;
home.push(USER_CREDENTIALS_PATH);

let credentials = AuthorizedUserRefreshToken::from_file(&home)?;
debug!(project = ?credentials.quota_project_id, client = credentials.client_id, "found user credentials");

Ok(Self {
client: client.clone(),
token: RwLock::new(Self::fetch_token(&credentials, client).await?),
credentials,
})
}

#[instrument(level = Level::DEBUG, skip(cred, client))]
async fn fetch_token(
cred: &AuthorizedUserRefreshToken,
client: &HttpClient,
) -> Result<Arc<Token>, Error> {
client
.token(
&|| {
Request::builder()
.method(Method::POST)
.uri(DEFAULT_TOKEN_GCP_URI)
.header(CONTENT_TYPE, "application/json")
.body(Full::from(Bytes::from(
serde_json::to_vec(&RefreshRequest {
client_id: &cred.client_id,
client_secret: &cred.client_secret,
grant_type: "refresh_token",
refresh_token: &cred.refresh_token,
})
.unwrap(),
)))
.unwrap()
},
"ConfigDefaultCredentials",
)
.await
}
}

#[async_trait]
impl TokenProvider for ConfigDefaultCredentials {
async fn token(&self, _scopes: &[&str]) -> Result<Arc<Token>, Error> {
let token = self.token.read().await.clone();
if !token.has_expired() {
return Ok(token);
}

let mut locked = self.token.write().await;
let token = Self::fetch_token(&self.credentials, &self.client).await?;
*locked = token.clone();
Ok(token)
}

async fn project_id(&self) -> Result<Arc<str>, Error> {
self.credentials
.quota_project_id
.clone()
.ok_or(Error::Str("no project ID in user credentials"))
}
}

#[derive(Serialize, Debug)]
struct RefreshRequest<'a> {
client_id: &'a str,
client_secret: &'a str,
grant_type: &'a str,
refresh_token: &'a str,
}

const DEFAULT_TOKEN_GCP_URI: &str = "https://accounts.google.com/o/oauth2/token";
const USER_CREDENTIALS_PATH: &str = ".config/gcloud/application_default_credentials.json";
Loading