Skip to content

Commit

Permalink
Merge branch 'external-account-url-sourced' #222
Browse files Browse the repository at this point in the history
  • Loading branch information
dermesser committed Jun 8, 2024
2 parents aa0a330 + ca74c95 commit 81e76f7
Showing 1 changed file with 65 additions and 3 deletions.
68 changes: 65 additions & 3 deletions src/external_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use http::header;
use http_body_util::BodyExt;
use hyper_util::client::legacy::connect::Connect;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use url::form_urlencoded;

/// JSON schema of external account secret.
Expand Down Expand Up @@ -41,14 +42,42 @@ pub struct ExternalAccountSecret {
pub enum CredentialSource {
/// file-sourced credentials
File {
/// file
/// File name of a file containing a subject token.
file: String,
},
// TODO: Microsoft Azure and URL-sourced credentials

//// [Microsoft Azure and URL-sourced
///credentials](https://google.aip.dev/auth/4117#determining-the-subject-token-in-microsoft-azure-and-url-sourced-credentials)
Url {
/// This defines the local metadata server to retrieve the external credentials from. For
/// Azure, this should be the Azure Instance Metadata Service (IMDS) URL used to retrieve
/// the Azure AD access token.
url: String,
/// This defines the headers to append to the GET request to credential_source.url.
headers: Option<HashMap<String, String>>,
/// See struct documentation.
format: UrlCredentialSourceFormat,
},
// TODO: executable-sourced credentials
}

/// ExternalAccountFlow can fetch oauth tokens using an external account secret.
/// JSON schema of URL-sourced credentials' format.
/// This indicates the format of the URL response. This can be either "text" or "json". The default should be "text".
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type")]
pub enum UrlCredentialSourceFormat {
/// Response is text.
#[serde(rename = "text")]
Text,
/// Response is JSON.
#[serde(rename = "json")]
Json {
/// Required for JSON URL responses. This indicates the JSON field name where the subject_token should be stored.
subject_token_field_name: String,
},
}

/// An ExternalAccountFlow can fetch OAuth tokens using an external account secret.
pub struct ExternalAccountFlow {
pub(crate) secret: ExternalAccountSecret,
}
Expand All @@ -66,6 +95,39 @@ impl ExternalAccountFlow {
{
let subject_token = match &self.secret.credential_source {
CredentialSource::File { file } => tokio::fs::read_to_string(file).await?,
CredentialSource::Url {
url,
headers,
format,
} => {
let request = headers
.iter()
.flatten()
.fold(hyper::Request::get(url), |builder, (name, value)| {
builder.header(name, value)
})
.body(String::new())
.unwrap();

log::debug!("requesting credential from url: {:?}", request);
let (head, body) = hyper_client.request(request).await?.into_parts();
let body = body.collect().await?.to_bytes();
log::debug!("received response; head: {:?}, body: {:?}", head, body);

match format {
UrlCredentialSourceFormat::Text => {
String::from_utf8(body.to_vec()).map_err(anyhow::Error::from)?
}
UrlCredentialSourceFormat::Json {
subject_token_field_name,
} => serde_json::from_slice::<HashMap<String, serde_json::Value>>(&body)?
.remove(subject_token_field_name)
.ok_or_else(|| anyhow::format_err!("missing {subject_token_field_name}"))?
.as_str()
.ok_or_else(|| anyhow::format_err!("invalid type"))?
.to_string(),
}
}
};

let req = form_urlencoded::Serializer::new(String::new())
Expand Down

0 comments on commit 81e76f7

Please sign in to comment.