gcloud_auth/token_source/
compute_identity_source.rs

1use async_trait::async_trait;
2use serde::Deserialize;
3use time::OffsetDateTime;
4use urlencoding::encode;
5
6use google_cloud_metadata::{METADATA_FLAVOR_KEY, METADATA_GOOGLE, METADATA_HOST_ENV, METADATA_IP};
7
8use crate::error::Error;
9use crate::token::Token;
10use crate::token_source::{default_http_client, TokenSource};
11
12/// Fetches a JWT token from the metadata server.
13/// using the `identity` endpoint.
14///
15/// This token source is useful for service-to-service authentication, notably on Cloud Run.
16///
17/// See <https://cloud.google.com/run/docs/authenticating/service-to-service#use_the_metadata_server>
18#[derive(Clone)]
19pub struct ComputeIdentitySource {
20    token_url: String,
21    client: reqwest::Client,
22}
23
24impl std::fmt::Debug for ComputeIdentitySource {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        f.debug_struct("ComputeIdentitySource")
27            .field("token_url", &self.token_url)
28            .finish_non_exhaustive()
29    }
30}
31
32impl ComputeIdentitySource {
33    pub(crate) fn new(audience: &str) -> Result<ComputeIdentitySource, Error> {
34        let host = match std::env::var(METADATA_HOST_ENV) {
35            Ok(s) => s,
36            Err(_e) => METADATA_IP.to_string(),
37        };
38
39        Ok(ComputeIdentitySource {
40            token_url: format!(
41                "http://{}/computeMetadata/v1/instance/service-accounts/default/identity?audience={}&format=full",
42                host,
43                encode(audience)
44            ),
45            client: default_http_client(),
46        })
47    }
48}
49
50#[derive(Deserialize)]
51struct ExpClaim {
52    exp: i64,
53}
54
55#[async_trait]
56impl TokenSource for ComputeIdentitySource {
57    async fn token(&self) -> Result<Token, Error> {
58        let jwt = self
59            .client
60            .get(self.token_url.to_string())
61            .header(METADATA_FLAVOR_KEY, METADATA_GOOGLE)
62            .send()
63            .await?
64            .text()
65            .await?;
66
67        // Only used to extract the expiry without checking the signature.
68        let token = jsonwebtoken::dangerous::insecure_decode::<ExpClaim>(jwt.as_bytes())?;
69        Ok(Token {
70            access_token: jwt,
71            token_type: "Bearer".into(),
72            expiry: OffsetDateTime::from_unix_timestamp(token.claims.exp).ok(),
73        })
74    }
75}