gcp_auth/
metadata_service_account.rs1use std::str;
2use std::sync::Arc;
3
4use async_trait::async_trait;
5use bytes::Bytes;
6use http_body_util::Full;
7use hyper::{Method, Request};
8use tokio::sync::RwLock;
9use tracing::{debug, instrument, Level};
10
11use crate::types::{HttpClient, Token};
12use crate::{Error, TokenProvider};
13
14#[derive(Debug)]
18pub struct MetadataServiceAccount {
19 client: HttpClient,
20 project_id: Arc<str>,
21 token: RwLock<Arc<Token>>,
22}
23
24impl MetadataServiceAccount {
25 pub async fn new() -> Result<Self, Error> {
27 let client = HttpClient::new()?;
28 Self::with_client(&client).await
29 }
30
31 pub(crate) async fn with_client(client: &HttpClient) -> Result<Self, Error> {
32 debug!("try to fetch token from GCP instance metadata server");
33 let token = RwLock::new(Self::fetch_token(client).await?);
34
35 debug!("getting project ID from GCP instance metadata server");
36 let req = metadata_request(DEFAULT_PROJECT_ID_GCP_URI);
37 let body = client.request(req, "MetadataServiceAccount").await?;
38 let project_id = match str::from_utf8(&body) {
39 Ok(s) if !s.is_empty() => Arc::from(s),
40 Ok(_) => {
41 return Err(Error::Str(
42 "empty project ID from GCP instance metadata server",
43 ))
44 }
45 Err(_) => {
46 return Err(Error::Str(
47 "received invalid UTF-8 project ID from GCP instance metadata server",
48 ))
49 }
50 };
51
52 Ok(Self {
53 client: client.clone(),
54 project_id,
55 token,
56 })
57 }
58
59 #[instrument(level = Level::DEBUG, skip(client))]
60 async fn fetch_token(client: &HttpClient) -> Result<Arc<Token>, Error> {
61 client
62 .token(
63 &|| metadata_request(DEFAULT_TOKEN_GCP_URI),
64 "MetadataServiceAccount",
65 )
66 .await
67 }
68}
69
70#[async_trait]
71impl TokenProvider for MetadataServiceAccount {
72 async fn token(&self, _scopes: &[&str]) -> Result<Arc<Token>, Error> {
73 let token = self.token.read().await.clone();
74 if !token.has_expired() {
75 return Ok(token);
76 }
77
78 let mut locked = self.token.write().await;
79 let token = Self::fetch_token(&self.client).await?;
80 *locked = token.clone();
81 Ok(token)
82 }
83
84 async fn project_id(&self) -> Result<Arc<str>, Error> {
85 Ok(self.project_id.clone())
86 }
87}
88
89fn metadata_request(uri: &str) -> Request<Full<Bytes>> {
90 Request::builder()
91 .method(Method::GET)
92 .uri(uri)
93 .header("Metadata-Flavor", "Google")
94 .body(Full::from(Bytes::new()))
95 .unwrap()
96}
97
98const DEFAULT_PROJECT_ID_GCP_URI: &str =
100 "http://metadata.google.internal/computeMetadata/v1/project/project-id";
101const DEFAULT_TOKEN_GCP_URI: &str =
102 "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token";