google_cloud_auth/
credentials.rs

1// Copyright 2024 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15mod api_key_credentials;
16// Export API Key factory function and options
17pub use api_key_credentials::{ApiKeyOptions, create_api_key_credentials};
18
19pub mod mds;
20pub mod service_account;
21pub mod user_account;
22
23use crate::Result;
24use crate::errors::{self, CredentialsError};
25use http::header::{HeaderName, HeaderValue};
26use std::future::Future;
27use std::sync::Arc;
28
29pub(crate) const QUOTA_PROJECT_KEY: &str = "x-goog-user-project";
30pub(crate) const DEFAULT_UNIVERSE_DOMAIN: &str = "googleapis.com";
31
32/// An implementation of [crate::credentials::CredentialsTrait].
33///
34/// Represents a [Credentials] used to obtain auth [Token][crate::token::Token]s
35/// and the corresponding request headers.
36///
37/// In general, [Credentials][credentials-link] are "digital object that provide
38/// proof of identity", the archetype may be a username and password
39/// combination, but a private RSA key may be a better example.
40///
41/// Modern authentication protocols do not send the credentials to authenticate
42/// with a service. Even when sent over encrypted transports, the credentials
43/// may be accidentally exposed via logging or may be captured if there are
44/// errors in the transport encryption. Because the credentials are often
45/// long-lived, that risk of exposure is also long-lived.
46///
47/// Instead, modern authentication protocols exchange the credentials for a
48/// time-limited [Token][token-link], a digital object that shows the caller was
49/// in possession of the credentials. Because tokens are time limited, risk of
50/// misuse is also time limited. Tokens may be further restricted to only a
51/// certain subset of the RPCs in the service, or even to specific resources, or
52/// only when used from a given machine (virtual or not). Further limiting the
53/// risks associated with any leaks of these tokens.
54///
55/// This struct also abstracts token sources that are not backed by a specific
56/// digital object. The canonical example is the [Metadata Service]. This
57/// service is available in many Google Cloud environments, including
58/// [Google Compute Engine], and [Google Kubernetes Engine].
59///
60/// [credentials-link]: https://cloud.google.com/docs/authentication#credentials
61/// [token-link]: https://cloud.google.com/docs/authentication#token
62/// [Metadata Service]: https://cloud.google.com/compute/docs/metadata/overview
63/// [Google Compute Engine]: https://cloud.google.com/products/compute
64/// [Google Kubernetes Engine]: https://cloud.google.com/kubernetes-engine
65#[derive(Clone, Debug)]
66pub struct Credentials {
67    // We use an `Arc` to hold the inner implementation.
68    //
69    // Credentials may be shared across threads (`Send + Sync`), so an `Rc`
70    // will not do.
71    //
72    // They also need to derive `Clone`, as the
73    // `gax::http_client::ReqwestClient`s which hold them derive `Clone`. So a
74    // `Box` will not do.
75    inner: Arc<dyn dynamic::CredentialsTrait>,
76}
77
78impl<T> std::convert::From<T> for Credentials
79where
80    T: crate::credentials::CredentialsTrait + Send + Sync + 'static,
81{
82    fn from(value: T) -> Self {
83        Self {
84            inner: Arc::new(value),
85        }
86    }
87}
88
89impl Credentials {
90    pub async fn token(&self) -> Result<crate::token::Token> {
91        self.inner.token().await
92    }
93
94    pub async fn headers(&self) -> Result<Vec<(HeaderName, HeaderValue)>> {
95        self.inner.headers().await
96    }
97
98    pub async fn universe_domain(&self) -> Option<String> {
99        self.inner.universe_domain().await
100    }
101}
102
103/// Represents a [Credentials] used to obtain auth
104/// [Token][crate::token::Token]s and the corresponding request headers.
105///
106/// In general, [Credentials][credentials-link] are "digital object that
107/// provide proof of identity", the archetype may be a username and password
108/// combination, but a private RSA key may be a better example.
109///
110/// Modern authentication protocols do not send the credentials to
111/// authenticate with a service. Even when sent over encrypted transports,
112/// the credentials may be accidentally exposed via logging or may be
113/// captured if there are errors in the transport encryption. Because the
114/// credentials are often long-lived, that risk of exposure is also
115/// long-lived.
116///
117/// Instead, modern authentication protocols exchange the credentials for a
118/// time-limited [Token][token-link], a digital object that shows the caller
119/// was in possession of the credentials. Because tokens are time limited,
120/// risk of misuse is also time limited. Tokens may be further restricted to
121/// only a certain subset of the RPCs in the service, or even to specific
122/// resources, or only when used from a given machine (virtual or not).
123/// Further limiting the risks associated with any leaks of these tokens.
124///
125/// This struct also abstracts token sources that are not backed by a
126/// specific digital object. The canonical example is the
127/// [Metadata Service]. This service is available in many Google Cloud
128/// environments, including [Google Compute Engine], and
129/// [Google Kubernetes Engine].
130///
131/// # Notes
132///
133/// Application developers who directly use the Auth SDK can use this trait,
134/// along with [crate::credentials::Credentials::from()] to mock the credentials.
135/// Application developers who use the Google Cloud Rust SDK directly should not
136/// need this functionality.
137///
138/// [credentials-link]: https://cloud.google.com/docs/authentication#credentials
139/// [token-link]: https://cloud.google.com/docs/authentication#token
140/// [Metadata Service]: https://cloud.google.com/compute/docs/metadata/overview
141/// [Google Compute Engine]: https://cloud.google.com/products/compute
142/// [Google Kubernetes Engine]: https://cloud.google.com/kubernetes-engine
143pub trait CredentialsTrait: std::fmt::Debug {
144    /// Asynchronously retrieves a token.
145    ///
146    /// Returns a [Token][crate::token::Token] for the current credentials.
147    /// The underlying implementation refreshes the token as needed.
148    fn token(&self) -> impl Future<Output = Result<crate::token::Token>> + Send;
149
150    /// Asynchronously constructs the auth headers.
151    ///
152    /// Different auth tokens are sent via different headers. The
153    /// [Credentials] constructs the headers (and header values) that should be
154    /// sent with a request.
155    ///
156    /// The underlying implementation refreshes the token as needed.
157    fn headers(&self) -> impl Future<Output = Result<Vec<(HeaderName, HeaderValue)>>> + Send;
158
159    /// Retrieves the universe domain associated with the credentials, if any.
160    fn universe_domain(&self) -> impl Future<Output = Option<String>> + Send;
161}
162
163pub(crate) mod dynamic {
164    use super::Result;
165    use super::{HeaderName, HeaderValue};
166
167    /// A dyn-compatible, crate-private version of `CredentialsTrait`.
168    #[async_trait::async_trait]
169    pub trait CredentialsTrait: Send + Sync + std::fmt::Debug {
170        /// Asynchronously retrieves a token.
171        ///
172        /// Returns a [Token][crate::token::Token] for the current credentials.
173        /// The underlying implementation refreshes the token as needed.
174        async fn token(&self) -> Result<crate::token::Token>;
175
176        /// Asynchronously constructs the auth headers.
177        ///
178        /// Different auth tokens are sent via different headers. The
179        /// [Credentials] constructs the headers (and header values) that should be
180        /// sent with a request.
181        ///
182        /// The underlying implementation refreshes the token as needed.
183        async fn headers(&self) -> Result<Vec<(HeaderName, HeaderValue)>>;
184
185        /// Retrieves the universe domain associated with the credentials, if any.
186        async fn universe_domain(&self) -> Option<String> {
187            Some("googleapis.com".to_string())
188        }
189    }
190
191    /// The public CredentialsTrait implements the dyn-compatible CredentialsTrait.
192    #[async_trait::async_trait]
193    impl<T> CredentialsTrait for T
194    where
195        T: super::CredentialsTrait + Send + Sync,
196    {
197        async fn token(&self) -> Result<crate::token::Token> {
198            T::token(self).await
199        }
200        async fn headers(&self) -> Result<Vec<(HeaderName, HeaderValue)>> {
201            T::headers(self).await
202        }
203        async fn universe_domain(&self) -> Option<String> {
204            T::universe_domain(self).await
205        }
206    }
207}
208
209/// Create access token credentials.
210///
211/// Returns [Application Default Credentials (ADC)][ADC-link]. These are the
212/// most commonly used credentials, and are expected to meet the needs of most
213/// applications. They conform to [AIP-4110].
214///
215/// The access tokens returned by these credentials are to be used in the
216/// `Authorization` HTTP header.
217///
218/// Consider using these credentials when:
219///
220/// - Your application is deployed to a Google Cloud environment such as
221///   [Google Compute Engine (GCE)][gce-link],
222///   [Google Kubernetes Engine (GKE)][gke-link], or [Cloud Run]. Each of these
223///   deployment environments provides a default service account to the
224///   application, and offers mechanisms to change this default service account
225///   without any code changes to your application.
226/// - You are testing or developing the application on a workstation (physical or
227///   virtual). These credentials will use your preferences as set with
228///   [gcloud auth application-default]. These preferences can be your own GCP
229///   user credentials, or some service account.
230/// - Regardless of where your application is running, you can use the
231///   `GOOGLE_APPLICATION_CREDENTIALS` environment variable to override the
232///   defaults. This environment variable should point to a file containing a
233///   service account key file, or a JSON object describing your user
234///   credentials.
235///
236/// Example usage:
237///
238/// ```
239/// # use google_cloud_auth::credentials::create_access_token_credentials;
240/// # use google_cloud_auth::errors::CredentialsError;
241/// # tokio_test::block_on(async {
242/// let mut creds = create_access_token_credentials().await?;
243/// let token = creds.token().await?;
244/// println!("Token: {}", token.token);
245/// # Ok::<(), CredentialsError>(())
246/// # });
247/// ```
248///
249/// [ADC-link]: https://cloud.google.com/docs/authentication/application-default-credentials
250/// [AIP-4110]: https://google.aip.dev/auth/4110
251/// [Cloud Run]: https://cloud.google.com/run
252/// [gce-link]: https://cloud.google.com/products/compute
253/// [gcloud auth application-default]: https://cloud.google.com/sdk/gcloud/reference/auth/application-default
254/// [gke-link]: https://cloud.google.com/kubernetes-engine
255pub async fn create_access_token_credentials() -> Result<Credentials> {
256    let contents = match load_adc()? {
257        AdcContents::Contents(contents) => contents,
258        AdcContents::FallbackToMds => return Ok(mds::new()),
259    };
260    let js: serde_json::Value = serde_json::from_str(&contents).map_err(errors::non_retryable)?;
261    let cred_type = js
262        .get("type")
263        .ok_or_else(|| errors::non_retryable_from_str("Failed to parse Application Default Credentials (ADC). No `type` field found."))?
264        .as_str()
265        .ok_or_else(|| errors::non_retryable_from_str("Failed to parse Application Default Credentials (ADC). `type` field is not a string.")
266        )?;
267    match cred_type {
268        "authorized_user" => user_account::creds_from(js),
269        "service_account" => service_account::creds_from(js),
270        _ => Err(errors::non_retryable_from_str(format!(
271            "Unimplemented credentials type: {cred_type}"
272        ))),
273    }
274}
275
276#[derive(Debug, PartialEq)]
277enum AdcPath {
278    FromEnv(String),
279    WellKnown(String),
280}
281
282#[derive(Debug, PartialEq)]
283enum AdcContents {
284    Contents(String),
285    FallbackToMds,
286}
287
288fn path_not_found(path: String) -> CredentialsError {
289    errors::non_retryable_from_str(format!(
290        "Failed to load Application Default Credentials (ADC) from {path}. Check that the `GOOGLE_APPLICATION_CREDENTIALS` environment variable points to a valid file."
291    ))
292}
293
294fn load_adc() -> Result<AdcContents> {
295    match adc_path() {
296        None => Ok(AdcContents::FallbackToMds),
297        Some(AdcPath::FromEnv(path)) => match std::fs::read_to_string(&path) {
298            Ok(contents) => Ok(AdcContents::Contents(contents)),
299            Err(e) if e.kind() == std::io::ErrorKind::NotFound => Err(path_not_found(path)),
300            Err(e) => Err(errors::non_retryable(e)),
301        },
302        Some(AdcPath::WellKnown(path)) => match std::fs::read_to_string(path) {
303            Ok(contents) => Ok(AdcContents::Contents(contents)),
304            Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(AdcContents::FallbackToMds),
305            Err(e) => Err(errors::non_retryable(e)),
306        },
307    }
308}
309
310/// The path to Application Default Credentials (ADC), as specified in [AIP-4110].
311///
312/// [AIP-4110]: https://google.aip.dev/auth/4110
313fn adc_path() -> Option<AdcPath> {
314    if let Ok(path) = std::env::var("GOOGLE_APPLICATION_CREDENTIALS") {
315        return Some(AdcPath::FromEnv(path));
316    }
317    Some(AdcPath::WellKnown(adc_well_known_path()?))
318}
319
320/// The well-known path to ADC on Windows, as specified in [AIP-4113].
321///
322/// [AIP-4113]: https://google.aip.dev/auth/4113
323#[cfg(target_os = "windows")]
324fn adc_well_known_path() -> Option<String> {
325    std::env::var("APPDATA")
326        .ok()
327        .map(|root| root + "/gcloud/application_default_credentials.json")
328}
329
330/// The well-known path to ADC on Linux and Mac, as specified in [AIP-4113].
331///
332/// [AIP-4113]: https://google.aip.dev/auth/4113
333#[cfg(not(target_os = "windows"))]
334fn adc_well_known_path() -> Option<String> {
335    std::env::var("HOME")
336        .ok()
337        .map(|root| root + "/.config/gcloud/application_default_credentials.json")
338}
339
340/// A module providing invalid credentials where authentication does not matter.
341///
342/// These credentials are a convenient way to avoid errors from loading
343/// Application Default Credentials in tests.
344///
345/// This module is mainly relevant to other `google-cloud-*` crates, but some
346/// external developers (i.e. consumers, not developers of `google-cloud-rust`)
347/// may find it useful.
348pub mod testing {
349    use crate::Result;
350    use crate::credentials::Credentials;
351    use crate::credentials::dynamic::CredentialsTrait;
352    use crate::token::Token;
353    use http::header::{HeaderName, HeaderValue};
354    use std::sync::Arc;
355
356    /// A simple credentials implementation to use in tests where authentication does not matter.
357    ///
358    /// Always returns a "Bearer" token, with "test-only-token" as the value.
359    pub fn test_credentials() -> Credentials {
360        Credentials {
361            inner: Arc::from(TestCredentials {}),
362        }
363    }
364
365    #[derive(Debug)]
366    struct TestCredentials;
367
368    #[async_trait::async_trait]
369    impl CredentialsTrait for TestCredentials {
370        async fn token(&self) -> Result<Token> {
371            Ok(Token {
372                token: "test-only-token".to_string(),
373                token_type: "Bearer".to_string(),
374                expires_at: None,
375                metadata: None,
376            })
377        }
378
379        async fn headers(&self) -> Result<Vec<(HeaderName, HeaderValue)>> {
380            Ok(Vec::new())
381        }
382
383        async fn universe_domain(&self) -> Option<String> {
384            None
385        }
386    }
387
388    /// A simple credentials implementation to use in tests.
389    ///
390    /// Always return an error in `token()` and `headers()`.
391    pub fn error_credentials(retryable: bool) -> Credentials {
392        Credentials {
393            inner: Arc::from(ErrorCredentials(retryable)),
394        }
395    }
396
397    #[derive(Debug, Default)]
398    struct ErrorCredentials(bool);
399
400    #[async_trait::async_trait]
401    impl CredentialsTrait for ErrorCredentials {
402        async fn token(&self) -> Result<Token> {
403            Err(super::CredentialsError::from_str(self.0, "test-only"))
404        }
405
406        async fn headers(&self) -> Result<Vec<(HeaderName, HeaderValue)>> {
407            Err(super::CredentialsError::from_str(self.0, "test-only"))
408        }
409
410        async fn universe_domain(&self) -> Option<String> {
411            None
412        }
413    }
414}
415
416#[cfg(test)]
417mod test {
418    use super::*;
419    use scoped_env::ScopedEnv;
420    use std::error::Error;
421    use test_case::test_case;
422
423    // Convenience struct for verifying (HeaderName, HeaderValue) pairs.
424    #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
425    pub struct HV {
426        pub header: String,
427        pub value: String,
428        pub is_sensitive: bool,
429    }
430
431    impl HV {
432        pub fn from(headers: Vec<(HeaderName, HeaderValue)>) -> Vec<HV> {
433            let mut hvs: Vec<HV> = headers
434                .into_iter()
435                .map(|(h, v)| HV {
436                    header: h.to_string(),
437                    value: v.to_str().unwrap().to_string(),
438                    is_sensitive: v.is_sensitive(),
439                })
440                .collect();
441
442            // We want to verify the contents of the headers. We do not care
443            // what order they are in.
444            hvs.sort();
445            hvs
446        }
447    }
448
449    #[cfg(target_os = "windows")]
450    #[test]
451    #[serial_test::serial]
452    fn adc_well_known_path_windows() {
453        let _creds = ScopedEnv::remove("GOOGLE_APPLICATION_CREDENTIALS");
454        let _appdata = ScopedEnv::set("APPDATA", "C:/Users/foo");
455        assert_eq!(
456            adc_well_known_path(),
457            Some("C:/Users/foo/gcloud/application_default_credentials.json".to_string())
458        );
459        assert_eq!(
460            adc_path(),
461            Some(AdcPath::WellKnown(
462                "C:/Users/foo/gcloud/application_default_credentials.json".to_string()
463            ))
464        );
465    }
466
467    #[cfg(target_os = "windows")]
468    #[test]
469    #[serial_test::serial]
470    fn adc_well_known_path_windows_no_appdata() {
471        let _creds = ScopedEnv::remove("GOOGLE_APPLICATION_CREDENTIALS");
472        let _appdata = ScopedEnv::remove("APPDATA");
473        assert_eq!(adc_well_known_path(), None);
474        assert_eq!(adc_path(), None);
475    }
476
477    #[cfg(not(target_os = "windows"))]
478    #[test]
479    #[serial_test::serial]
480    fn adc_well_known_path_posix() {
481        let _creds = ScopedEnv::remove("GOOGLE_APPLICATION_CREDENTIALS");
482        let _home = ScopedEnv::set("HOME", "/home/foo");
483        assert_eq!(
484            adc_well_known_path(),
485            Some("/home/foo/.config/gcloud/application_default_credentials.json".to_string())
486        );
487        assert_eq!(
488            adc_path(),
489            Some(AdcPath::WellKnown(
490                "/home/foo/.config/gcloud/application_default_credentials.json".to_string()
491            ))
492        );
493    }
494
495    #[cfg(not(target_os = "windows"))]
496    #[test]
497    #[serial_test::serial]
498    fn adc_well_known_path_posix_no_home() {
499        let _creds = ScopedEnv::remove("GOOGLE_APPLICATION_CREDENTIALS");
500        let _appdata = ScopedEnv::remove("HOME");
501        assert_eq!(adc_well_known_path(), None);
502        assert_eq!(adc_path(), None);
503    }
504
505    #[test]
506    #[serial_test::serial]
507    fn adc_path_from_env() {
508        let _creds = ScopedEnv::set(
509            "GOOGLE_APPLICATION_CREDENTIALS",
510            "/usr/bar/application_default_credentials.json",
511        );
512        assert_eq!(
513            adc_path(),
514            Some(AdcPath::FromEnv(
515                "/usr/bar/application_default_credentials.json".to_string()
516            ))
517        );
518    }
519
520    #[test]
521    #[serial_test::serial]
522    fn load_adc_no_well_known_path_fallback_to_mds() {
523        let _e1 = ScopedEnv::remove("GOOGLE_APPLICATION_CREDENTIALS");
524        let _e2 = ScopedEnv::remove("HOME"); // For posix
525        let _e3 = ScopedEnv::remove("APPDATA"); // For windows
526        assert_eq!(load_adc().unwrap(), AdcContents::FallbackToMds);
527    }
528
529    #[test]
530    #[serial_test::serial]
531    fn load_adc_no_file_at_well_known_path_fallback_to_mds() {
532        // Create a new temp directory. There is not an ADC file in here.
533        let dir = tempfile::TempDir::new().unwrap();
534        let path = dir.path().to_str().unwrap();
535        let _e1 = ScopedEnv::remove("GOOGLE_APPLICATION_CREDENTIALS");
536        let _e2 = ScopedEnv::set("HOME", path); // For posix
537        let _e3 = ScopedEnv::set("APPDATA", path); // For windows
538        assert_eq!(load_adc().unwrap(), AdcContents::FallbackToMds);
539    }
540
541    #[test]
542    #[serial_test::serial]
543    fn load_adc_no_file_at_env_is_error() {
544        let _e = ScopedEnv::set("GOOGLE_APPLICATION_CREDENTIALS", "file-does-not-exist.json");
545        let err = load_adc().err().unwrap();
546        let msg = err.source().unwrap().to_string();
547        assert!(msg.contains("Failed to load Application Default Credentials"));
548        assert!(msg.contains("file-does-not-exist.json"));
549        assert!(msg.contains("GOOGLE_APPLICATION_CREDENTIALS"));
550    }
551
552    #[test]
553    #[serial_test::serial]
554    fn load_adc_success() {
555        let file = tempfile::NamedTempFile::new().unwrap();
556        let path = file.into_temp_path();
557        std::fs::write(&path, "contents").expect("Unable to write to temporary file.");
558        let _e = ScopedEnv::set("GOOGLE_APPLICATION_CREDENTIALS", path.to_str().unwrap());
559
560        assert_eq!(
561            load_adc().unwrap(),
562            AdcContents::Contents("contents".to_string())
563        );
564    }
565
566    #[test_case(true; "retryable")]
567    #[test_case(false; "non-retryable")]
568    #[tokio::test]
569    async fn error_credentials(retryable: bool) {
570        let credentials = super::testing::error_credentials(retryable);
571        assert!(
572            credentials.universe_domain().await.is_none(),
573            "{credentials:?}"
574        );
575        let err = credentials.token().await.err().unwrap();
576        assert_eq!(err.is_retryable(), retryable, "{err:?}");
577        let err = credentials.headers().await.err().unwrap();
578        assert_eq!(err.is_retryable(), retryable, "{err:?}");
579    }
580}