gcloud_auth/token_source/
compute_identity_source.rs

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