This is an automated email from the ASF dual-hosted git repository.
xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal-reqsign.git
The following commit(s) were added to refs/heads/main by this push:
new 561a93a reqsign-google: support selecting service account in VM
metadata provider (#706)
561a93a is described below
commit 561a93a294a5b679133b1e7e3cd13f482cd9cb43
Author: Xuanwo <[email protected]>
AuthorDate: Wed Mar 18 17:38:03 2026 +0800
reqsign-google: support selecting service account in VM metadata provider
(#706)
This PR adds service account selection support to reqsign-google's VM
metadata provider so integrations running on GCE can request tokens for
a non-default service account without shipping a local custom provider.
The default ADC builder can now pass that option through
`configure_vm_metadata(...)`, and the existing behavior remains
unchanged when no service account is configured.
Closes #695.
---
services/google/src/provide_credential/default.rs | 54 +++++++++++++-
.../google/src/provide_credential/vm_metadata.rs | 82 +++++++++++++++++++++-
2 files changed, 132 insertions(+), 4 deletions(-)
diff --git a/services/google/src/provide_credential/default.rs
b/services/google/src/provide_credential/default.rs
index cf5d570..70c0b0a 100644
--- a/services/google/src/provide_credential/default.rs
+++ b/services/google/src/provide_credential/default.rs
@@ -264,8 +264,9 @@ impl DefaultCredentialProviderBuilder {
/// Configure the VM metadata provider.
///
- /// This allows setting a custom endpoint or other options for retrieving
- /// tokens when running on Google Compute Engine or compatible
environments.
+ /// This allows setting a custom endpoint, service account, or other
options
+ /// for retrieving tokens when running on Google Compute Engine or
+ /// compatible environments.
pub fn configure_vm_metadata<F>(mut self, f: F) -> Self
where
F: FnOnce(VmMetadataCredentialProvider) ->
VmMetadataCredentialProvider,
@@ -334,9 +335,32 @@ impl DefaultCredentialProviderBuilder {
#[cfg(test)]
mod tests {
use super::*;
+ use bytes::Bytes;
+ use reqsign_core::HttpSend;
use reqsign_core::{Context, StaticEnv};
use std::collections::HashMap;
use std::env;
+ use std::sync::{Arc, Mutex};
+
+ #[derive(Clone, Debug, Default)]
+ struct MockHttpSend {
+ uris: Arc<Mutex<Vec<String>>>,
+ }
+
+ impl HttpSend for MockHttpSend {
+ async fn http_send(&self, req: http::Request<Bytes>) ->
Result<http::Response<Bytes>> {
+ self.uris.lock().unwrap().push(req.uri().to_string());
+
+ Ok(http::Response::builder()
+ .status(http::StatusCode::OK)
+ .body(
+
br#"{"access_token":"test-access-token","expires_in":3600}"#
+ .as_slice()
+ .into(),
+ )
+ .expect("response must build"))
+ }
+ }
#[tokio::test]
async fn test_default_provider_env() {
@@ -381,4 +405,30 @@ mod tests {
.with_http_send(reqsign_http_send_reqwest::ReqwestHttpSend::default());
let _ = provider.provide_credential(&ctx).await;
}
+
+ #[tokio::test]
+ async fn test_default_provider_configures_vm_metadata_service_account() ->
Result<()> {
+ let http = MockHttpSend::default();
+ let ctx = Context::new().with_http_send(http.clone());
+
+ let provider = DefaultCredentialProvider::builder()
+ .configure_vm_metadata(|p| {
+ p.with_endpoint("127.0.0.1:8080")
+
.with_service_account("[email protected]")
+ })
+ .build();
+
+ let cred = provider
+ .provide_credential(&ctx)
+ .await?
+ .expect("credential must exist");
+
+ assert!(cred.has_token());
+ assert_eq!(
+ http.uris.lock().unwrap().as_slice(),
+
&["http://127.0.0.1:8080/computeMetadata/v1/instance/service-accounts/[email protected]/token?scopes=https://www.googleapis.com/auth/cloud-platform".to_string()]
+ );
+
+ Ok(())
+ }
}
diff --git a/services/google/src/provide_credential/vm_metadata.rs
b/services/google/src/provide_credential/vm_metadata.rs
index b850e2d..57af156 100644
--- a/services/google/src/provide_credential/vm_metadata.rs
+++ b/services/google/src/provide_credential/vm_metadata.rs
@@ -35,6 +35,7 @@ struct VmMetadataTokenResponse {
pub struct VmMetadataCredentialProvider {
scope: Option<String>,
endpoint: Option<String>,
+ service_account: Option<String>,
}
impl VmMetadataCredentialProvider {
@@ -54,6 +55,14 @@ impl VmMetadataCredentialProvider {
self.endpoint = Some(endpoint.into());
self
}
+
+ /// Set the service account used to retrieve a token from VM metadata
service.
+ ///
+ /// Defaults to `default` if not configured.
+ pub fn with_service_account(mut self, service_account: impl Into<String>)
-> Self {
+ self.service_account = Some(service_account.into());
+ self
+ }
}
impl ProvideCredential for VmMetadataCredentialProvider {
type Credential = Credential;
@@ -66,8 +75,7 @@ impl ProvideCredential for VmMetadataCredentialProvider {
.or_else(|| ctx.env_var(crate::constants::GOOGLE_SCOPE))
.unwrap_or_else(|| crate::constants::DEFAULT_SCOPE.to_string());
- // Use "default" service account if not specified
- let service_account = "default";
+ let service_account =
self.service_account.as_deref().unwrap_or("default");
debug!("loading token from VM metadata service for account:
{service_account}");
@@ -112,3 +120,73 @@ impl ProvideCredential for VmMetadataCredentialProvider {
})))
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use bytes::Bytes;
+ use reqsign_core::HttpSend;
+ use std::sync::{Arc, Mutex};
+
+ #[derive(Clone, Debug, Default)]
+ struct MockHttpSend {
+ uris: Arc<Mutex<Vec<String>>>,
+ }
+
+ impl HttpSend for MockHttpSend {
+ async fn http_send(&self, req: http::Request<Bytes>) ->
Result<http::Response<Bytes>> {
+ self.uris.lock().unwrap().push(req.uri().to_string());
+
+ Ok(http::Response::builder()
+ .status(http::StatusCode::OK)
+ .body(
+
br#"{"access_token":"test-access-token","expires_in":3600}"#
+ .as_slice()
+ .into(),
+ )
+ .expect("response must build"))
+ }
+ }
+
+ #[tokio::test]
+ async fn test_vm_metadata_uses_default_service_account() -> Result<()> {
+ let http = MockHttpSend::default();
+ let ctx = Context::new().with_http_send(http.clone());
+
+ let provider =
VmMetadataCredentialProvider::new().with_endpoint("127.0.0.1:8080");
+ let cred = provider
+ .provide_credential(&ctx)
+ .await?
+ .expect("credential must exist");
+
+ assert!(cred.has_token());
+ assert_eq!(
+ http.uris.lock().unwrap().as_slice(),
+
&["http://127.0.0.1:8080/computeMetadata/v1/instance/service-accounts/default/token?scopes=https://www.googleapis.com/auth/cloud-platform".to_string()]
+ );
+
+ Ok(())
+ }
+
+ #[tokio::test]
+ async fn test_vm_metadata_uses_configured_service_account() -> Result<()> {
+ let http = MockHttpSend::default();
+ let ctx = Context::new().with_http_send(http.clone());
+
+ let provider = VmMetadataCredentialProvider::new()
+ .with_endpoint("127.0.0.1:8080")
+
.with_service_account("[email protected]");
+ let cred = provider
+ .provide_credential(&ctx)
+ .await?
+ .expect("credential must exist");
+
+ assert!(cred.has_token());
+ assert_eq!(
+ http.uris.lock().unwrap().as_slice(),
+
&["http://127.0.0.1:8080/computeMetadata/v1/instance/service-accounts/[email protected]/token?scopes=https://www.googleapis.com/auth/cloud-platform".to_string()]
+ );
+
+ Ok(())
+ }
+}