Skip to content

Commit

Permalink
Merge pull request #136 from zzztimbo/featgcloud-Add-support-for-exte…
Browse files Browse the repository at this point in the history
…rnal-account-authentication

feat(gcloud): Add support for external account authentication
  • Loading branch information
PierreBeucher authored Dec 7, 2024
2 parents e278c99 + 566cf5f commit 566fc7f
Showing 1 changed file with 51 additions and 19 deletions.
70 changes: 51 additions & 19 deletions src/modules/gcloud/client.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@

use anyhow::Context;
use google_secretmanager1::{
SecretManager,
SecretManager,
oauth2::{
self,
self,
hyper_rustls::HttpsConnector,
authenticator::{Authenticator, ApplicationDefaultCredentialsTypes},
authenticator::{Authenticator, ApplicationDefaultCredentialsTypes},
ApplicationDefaultCredentialsAuthenticator,
ApplicationDefaultCredentialsFlowOpts
ApplicationDefaultCredentialsFlowOpts,
read_external_account_secret
},
hyper::{self, client::HttpConnector},
hyper::{self, client::HttpConnector},
hyper_rustls,
api::SecretPayload
};
use log::debug;
use async_trait::async_trait;
use home;
use std::env;

use crate::core::NovopsContext;

Expand All @@ -36,24 +38,24 @@ impl GCloudClient for DefaultGCloudClient{

let authenticator = get_authenticator()
.await.with_context(|| "Couldn't get Google client authenticator")?;

let hub = SecretManager::new(
hyper::Client::builder().build(
hyper_rustls::HttpsConnectorBuilder::new().with_native_roots()?.https_or_http().enable_http1().build()
),
),
authenticator
);

let (_, secret) = hub.projects()
.secrets_versions_access(name)
.doit()
.await.with_context(|| format!("Couldn't get secret {:?}. Did you setup credentials compatible with Application Default Credentials?", name))?;

let result = secret.payload
.ok_or(anyhow::anyhow!("No secret value found for '{}'", name))?;

Ok(result)

}
}

Expand All @@ -63,6 +65,7 @@ impl GCloudClient for DefaultGCloudClient{
/// 1. GOOGLE_APPLICATION_CREDENTIALS environment variable
/// 2. User credentials set up by using the Google Cloud CLI
/// 3. The attached service account, returned by the metadata server
/// 4. External credentials (Workload Identity Federation)
///
/// But google_secretmanager1 only performs 1 and 3
/// Workaround by implementing our own way for 2.
Expand All @@ -85,7 +88,7 @@ async fn get_authenticator() -> Result<Authenticator<HttpsConnector<HttpConnecto
Ok(auth) => Ok(auth),
Err(e) => {
debug!("Couldn't generate Authorized User Credentials authenticator: ${:?}", e);

// 3. Use google_secretmanager1 again to retrieve Metadata instance
debug!("Trying to get Instance Metadata authenticator");
let metadata_authentiactor = try_metadata_authenticator().await;
Expand All @@ -95,16 +98,26 @@ async fn get_authenticator() -> Result<Authenticator<HttpsConnector<HttpConnecto
Err(e) => {
debug!("Couldn't generate Instance Metadata authenticator: ${:?}", e);

Err(anyhow::anyhow!("Couldn't generate Authenticator for Google client. Did you setup credentials compatible with Application Default Credentials? "))
// 4. Try external account authneticator (workload identity federation)
debug!("Trying to get external account authenticator");
let external_account_authenticator = try_external_account_authenticator().await;

match external_account_authenticator {
Ok(auth) => Ok(auth),
Err(e) => {
debug!("Couldn't generate external account authenticator: ${:?}", e);
Err(anyhow::anyhow!("Couldn't generate Authenticator for Google client. Did you setup credentials compatible with Application Default Credentials? "))
}
}
},
}
},
}
},
}



}

async fn try_metadata_authenticator() -> Result<Authenticator<HttpsConnector<HttpConnector>>, anyhow::Error>{
Expand All @@ -118,13 +131,13 @@ async fn try_metadata_authenticator() -> Result<Authenticator<HttpsConnector<Htt
.build()
.await
.with_context(|| "Unable to create Instance Metadata authenticator")?,
ApplicationDefaultCredentialsTypes::ServiceAccount(_) =>
ApplicationDefaultCredentialsTypes::ServiceAccount(_) =>
return Err(anyhow::anyhow!("Expected ServiceAccount authenticator."))
};
Ok(result)
}

// Try to generate a Service Account Authenticator using GOOGLE_APPLICATION_CREDENTIALS env var
// Try to generate a Service Account Authenticator using GOOGLE_APPLICATION_CREDENTIALS env var
async fn try_service_account_authenticator() -> Result<Authenticator<HttpsConnector<HttpConnector>>, anyhow::Error> {

let opts = ApplicationDefaultCredentialsFlowOpts::default();
Expand All @@ -137,7 +150,7 @@ async fn try_service_account_authenticator() -> Result<Authenticator<HttpsConnec
.build()
.await
.with_context(|| "Unable to create service account authenticator")?,
ApplicationDefaultCredentialsTypes::InstanceMetadata(_) =>
ApplicationDefaultCredentialsTypes::InstanceMetadata(_) =>
return Err(anyhow::anyhow!("Expected ServiceAccount authenticator."))
};
Ok(result)
Expand All @@ -162,6 +175,25 @@ async fn try_user_authenticator() -> Result<Authenticator<HttpsConnector<HttpCon
Ok(user_authenticator)
}


// Try to generate a user authenticator using external account workflow
async fn try_external_account_authenticator() -> Result<Authenticator<HttpsConnector<HttpConnector>>, anyhow::Error> {
let credentials_path = env::var("GOOGLE_APPLICATION_CREDENTIALS")
.map_err(|_| anyhow::anyhow!("GOOGLE_APPLICATION_CREDENTIALS environment variable not set"))?;

let external_account_secret = read_external_account_secret(credentials_path)
.await
.with_context(|| "Couldn't read Google client external account secret")?;

let authenticator = oauth2::ExternalAccountAuthenticator::builder(external_account_secret)
.build()
.await
.with_context(|| "Couldn't build ExternalAccountAuthenticator for Google client")?;

Ok(authenticator)
}


#[async_trait]
impl GCloudClient for DryRunGCloudClient{

Expand All @@ -174,7 +206,7 @@ impl GCloudClient for DryRunGCloudClient{
data_crc32c: Some(i64::from(crc32c::crc32c(result.as_bytes())))
})


}
}

Expand All @@ -184,4 +216,4 @@ pub async fn get_client(ctx: &NovopsContext) -> Box<dyn GCloudClient + Send + Sy
} else {
Box::new(DefaultGCloudClient{})
}
}
}

0 comments on commit 566fc7f

Please sign in to comment.