1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
//! GCP auth provides authentication using service accounts Google Cloud Platform (GCP)
//!
//! GCP auth is a simple, minimal authentication library for Google Cloud Platform (GCP)
//! providing authentication using service accounts. Once authenticated, the service
//! account can be used to acquire bearer tokens for use in authenticating against GCP
//! services.
//!
//! The library supports the following methods of retrieving tokens:
//!
//! 1. Reading custom service account credentials from the path pointed to by the
//! `GOOGLE_APPLICATION_CREDENTIALS` environment variable. Alternatively, custom service
//! account credentials can be read from a JSON file or string.
//! 2. Look for credentials in `.config/gcloud/application_default_credentials.json`;
//! if found, use these credentials to request refresh tokens. This file can be created
//! by invoking `gcloud auth application-default login`.
//! 3. Use the default service account by retrieving a token from the metadata server.
//! 4. Retrieving a token from the `gcloud` CLI tool, if it is available on the `PATH`.
//!
//! For more details, see [`provider()`].
//!
//! A [`TokenProvider`] handles caching tokens for their lifetime; it will not make a request if
//! an appropriate token is already cached. Therefore, the caller should not cache tokens.
//!
//! ## Simple usage
//!
//! The default way to use this library is to select the appropriate token provider using
//! [`provider()`]. It will find the appropriate authentication method and use it to retrieve
//! tokens.
//!
//! ```rust,no_run
//! # async fn get_token() -> Result<(), gcp_auth::Error> {
//! let provider = gcp_auth::provider().await?;
//! let scopes = &["https://www.googleapis.com/auth/cloud-platform"];
//! let token = provider.token(scopes).await?;
//! # Ok(())
//! # }
//! ```
//!
//! ## Supplying service account credentials
//!
//! When running outside of GCP (for example, on a development machine), it can be useful to supply
//! service account credentials. The first method checked by [`provider()`] is to
//! read a path to a file containing JSON credentials in the `GOOGLE_APPLICATION_CREDENTIALS`
//! environment variable. However, you may also supply a custom path to read credentials from, or
//! a `&str` containing the credentials. In both of these cases, you should create a
//! [`CustomServiceAccount`] directly using one of its associated functions:
//!
//! ```rust,no_run
//! # use std::path::PathBuf;
//! #
//! # async fn get_token() -> Result<(), gcp_auth::Error> {
//! use gcp_auth::{CustomServiceAccount, TokenProvider};
//!
//! // `credentials_path` variable is the path for the credentials `.json` file.
//! let credentials_path = PathBuf::from("service-account.json");
//! let service_account = CustomServiceAccount::from_file(credentials_path)?;
//! let scopes = &["https://www.googleapis.com/auth/cloud-platform"];
//! let token = service_account.token(scopes).await?;
//! # Ok(())
//! # }
//! ```
//!
//! ## Getting tokens in multi-thread or async environments
//!
//! Using a `OnceCell` makes it easy to reuse the [`AuthenticationManager`] across different
//! threads or async tasks.
//!
//! ```rust,no_run
//! use std::sync::Arc;
//! use tokio::sync::OnceCell;
//! use gcp_auth::TokenProvider;
//!
//! static TOKEN_PROVIDER: OnceCell<Arc<dyn TokenProvider>> = OnceCell::const_new();
//!
//! async fn token_provider() -> &'static Arc<dyn TokenProvider> {
//! TOKEN_PROVIDER
//! .get_or_init(|| async {
//! gcp_auth::provider()
//! .await
//! .expect("unable to initialize token provider")
//! })
//! .await
//! }
//! ```
#![warn(unreachable_pub)]
use std::sync::Arc;
use async_trait::async_trait;
use thiserror::Error;
use tracing::{debug, instrument, Level};
mod custom_service_account;
pub use custom_service_account::CustomServiceAccount;
mod config_default_credentials;
pub use config_default_credentials::ConfigDefaultCredentials;
mod metadata_service_account;
pub use metadata_service_account::MetadataServiceAccount;
mod gcloud_authorized_user;
pub use gcloud_authorized_user::GCloudAuthorizedUser;
mod types;
use types::HttpClient;
pub use types::{Signer, Token};
/// Finds a service account provider to get authentication tokens from
///
/// Tries the following approaches, in order:
///
/// 1. Check if the `GOOGLE_APPLICATION_CREDENTIALS` environment variable if set;
/// if so, use a custom service account as the token source.
/// 2. Look for credentials in `.config/gcloud/application_default_credentials.json`;
/// if found, use these credentials to request refresh tokens.
/// 3. Send a HTTP request to the internal metadata server to retrieve a token;
/// if it succeeds, use the default service account as the token source.
/// 4. Check if the `gcloud` tool is available on the `PATH`; if so, use the
/// `gcloud auth print-access-token` command as the token source.
#[instrument(level = Level::DEBUG)]
pub async fn provider() -> Result<Arc<dyn TokenProvider>, Error> {
debug!("initializing gcp_auth");
if let Some(provider) = CustomServiceAccount::from_env()? {
return Ok(Arc::new(provider));
}
let client = HttpClient::new()?;
let default_user_error = match ConfigDefaultCredentials::with_client(&client).await {
Ok(provider) => {
debug!("using ConfigDefaultCredentials");
return Ok(Arc::new(provider));
}
Err(e) => e,
};
let default_service_error = match MetadataServiceAccount::with_client(&client).await {
Ok(provider) => {
debug!("using MetadataServiceAccount");
return Ok(Arc::new(provider));
}
Err(e) => e,
};
let gcloud_error = match GCloudAuthorizedUser::new().await {
Ok(provider) => {
debug!("using GCloudAuthorizedUser");
return Ok(Arc::new(provider));
}
Err(e) => e,
};
Err(Error::NoAuthMethod(
Box::new(gcloud_error),
Box::new(default_service_error),
Box::new(default_user_error),
))
}
/// A trait for an authentication context that can provide tokens
#[async_trait]
pub trait TokenProvider: Send + Sync {
/// Get a valid token for the given scopes
///
/// Tokens are cached until they expire, so this method will only fetch a fresh token once
/// the current token (for the given scopes) has expired.
async fn token(&self, scopes: &[&str]) -> Result<Arc<Token>, Error>;
/// Get the project ID for the authentication context
async fn project_id(&self) -> Result<Arc<str>, Error>;
}
/// Enumerates all possible errors returned by this library.
#[derive(Error, Debug)]
pub enum Error {
/// No available authentication method was discovered
///
/// Application can authenticate against GCP using:
///
/// - Default service account - available inside GCP platform using GCP Instance Metadata server
/// - GCloud authorized user - retrieved using `gcloud auth` command
///
/// All authentication methods have been tested and none succeeded.
/// Service account file can be downloaded from GCP in json format.
#[error("no available authentication method found")]
NoAuthMethod(Box<Error>, Box<Error>, Box<Error>),
/// Could not connect to server
#[error("{0}")]
Http(&'static str, #[source] hyper::Error),
#[error("{0}: {1}")]
Io(&'static str, #[source] std::io::Error),
#[error("{0}: {1}")]
Json(&'static str, #[source] serde_json::error::Error),
#[error("{0}: {1}")]
Other(
&'static str,
#[source] Box<dyn std::error::Error + Send + Sync>,
),
#[error("{0}")]
Str(&'static str),
}