use std::fmt::{Debug, Formatter};
use async_trait::async_trait;
use cloud_detect::ProviderId;
use reqwest::Client;
use tracing::debug;
use url::Url;
use super::{ClientGrant, cloud_provider_id, jwt_bearer, request_token, token_endpoint};
use crate::http::{Accepted as _, shared_client};
use crate::{
CredentialResult, OAuth2ClientAuthentication, OAuth2Grant, OAuth2TokenRequest,
OAuth2TokenResponse, Token,
};
pub struct GcpAuthProvider {
client: Client,
token_endpoint: Url,
workload_id: String,
resource: Option<String>,
}
impl Debug for GcpAuthProvider {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GcpAuthProvider").finish()
}
}
impl GcpAuthProvider {
pub fn new(token_endpoint: Url, workload_id: String) -> Self {
GcpAuthProvider {
client: shared_client(),
token_endpoint,
workload_id,
resource: None,
}
}
pub fn with_resource(mut self, resource: impl Into<String>) -> Self {
self.resource = Some(resource.into());
self
}
}
impl GcpAuthProvider {
pub async fn token(&self) -> CredentialResult<Option<Token>> {
if !matches!(cloud_provider_id().await, Some(ProviderId::GCP)) {
return Ok(None);
}
let gcp_token = metadata_identity_token(&self.client, self.token_endpoint.as_str()).await?;
let token = jwt_bearer(
&self.client,
&self.token_endpoint,
self.workload_id.clone(),
gcp_token.expose_secret().to_string(),
self.resource.clone(),
)
.await?;
debug!("GcpAuthProvider obtained a workload token");
Ok(Some(token))
}
}
impl_token_source!(GcpAuthProvider);
pub struct GcpServiceAuthProvider {
client: Client,
token_endpoint: Url,
client_id: String,
resource: Option<String>,
}
impl GcpServiceAuthProvider {
pub fn new(
auth_url: &Url,
client_id: String,
resource: Option<String>,
) -> CredentialResult<Self> {
Ok(Self {
client: shared_client(),
token_endpoint: token_endpoint(auth_url)?,
client_id,
resource,
})
}
pub async fn token(&self) -> CredentialResult<Option<Token>> {
let assertion = metadata_identity_token(&self.client, self.token_endpoint.as_str()).await?;
let token = jwt_bearer(
&self.client,
&self.token_endpoint,
self.client_id.clone(),
assertion.expose_secret().to_string(),
self.resource.clone(),
)
.await?;
debug!(client_id = %self.client_id, "exchanged GCP host identity for service token");
Ok(Some(token))
}
}
impl Debug for GcpServiceAuthProvider {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GcpServiceAuthProvider")
.field("client_id", &self.client_id)
.finish_non_exhaustive()
}
}
impl_token_source!(GcpServiceAuthProvider);
#[async_trait]
impl ClientGrant for GcpServiceAuthProvider {
async fn grant(
&self,
resource: &str,
authorization_details: &serde_json::Value,
) -> CredentialResult<OAuth2TokenResponse> {
let assertion = metadata_identity_token(&self.client, self.token_endpoint.as_str()).await?;
let request = OAuth2TokenRequest {
client: OAuth2ClientAuthentication {
client_id: Some(self.client_id.clone()),
..Default::default()
},
grant: OAuth2Grant::JwtBearer {
assertion: assertion.expose_secret().to_owned(),
resource: Some(resource.to_owned()),
authorization_details: Some(authorization_details.to_string()),
},
};
request_token(&self.client, &self.token_endpoint, &request).await
}
}
async fn metadata_identity_token(client: &Client, audience: &str) -> CredentialResult<Token> {
let response = client
.get(format!(
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience={audience}"
))
.header("Metadata-Flavor", "Google")
.send()
.await?
.accepted()
.await?
.text()
.await?;
Ok(Token::from(response))
}