Skip to content

Commit

Permalink
Merge pull request #35 from Infisical/daniel/azure-auth
Browse files Browse the repository at this point in the history
Feat: Azure Auth
  • Loading branch information
DanielHougaard authored May 27, 2024
2 parents f1592d3 + 772bb8b commit de75e04
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 23 deletions.
3 changes: 2 additions & 1 deletion crates/infisical-py/infisical_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@
from .schemas import AWSIamAuthMethod as AWSIamAuthMethod
from .schemas import GCPIamAuthMethod as GCPIamAuthMethod
from .schemas import GCPIDTokenAuthMethod as GCPIDTokenAuthMethod
from .schemas import UniversalAuthMethod as UniversalAuthMethod
from .schemas import UniversalAuthMethod as UniversalAuthMethod
from .schemas import AzureAuthMethod as AzureAuthMethod
56 changes: 56 additions & 0 deletions crates/infisical/src/api/auth/azure_login.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use serde::{Deserialize, Serialize};

use crate::{
constants::AZURE_METADATA_SERVICE_URL,
error::{api_error_handler, Error, Result},
Client,
};

use super::{auth_infisical_azure, AccessTokenSuccessResponse};

#[derive(Serialize, Deserialize, Debug)]
struct AzureSuccessResponse {
access_token: String,
}

pub async fn azure_login(client: &mut Client) -> Result<AccessTokenSuccessResponse> {
let identity_id;

if let Some(azure_auth) = &client.auth.azure {
identity_id = azure_auth.identity_id.clone();
} else {
return Err(Error::MissingParametersAuthError {
message: "Attempt to authenticate with Azure. Identity ID is missing.".to_string(),
});
}

let request_client = reqwest::Client::builder()
.use_preconfigured_tls(rustls_platform_verifier::tls_config())
.build()
.unwrap();

let metadata_request = request_client
.get(AZURE_METADATA_SERVICE_URL)
.header("Metadata", "true")
.header(reqwest::header::ACCEPT, "application/json");

let azure_response = metadata_request.send().await?;

if !azure_response.status().is_success() {
let err = api_error_handler(azure_response.status(), azure_response, None, false).await?;
return Err(err);
}

let azure_metadata = azure_response.json::<AzureSuccessResponse>().await?;

let response =
auth_infisical_azure(client, Some(identity_id), Some(azure_metadata.access_token)).await?;

if !response.status().is_success() {
let err = api_error_handler(response.status(), response, None, false).await?;
return Err(err);
}

let response_json = response.json::<AccessTokenSuccessResponse>().await?;
return Ok(response_json);
}
27 changes: 27 additions & 0 deletions crates/infisical/src/api/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{
};

pub mod aws_iam_login;
pub mod azure_login;
pub mod gcp_iam_login;
pub mod gcp_id_token_login;
pub mod kubernetes_login;
Expand Down Expand Up @@ -62,6 +63,32 @@ pub(self) async fn auth_infisical_google(
return Ok(response);
}

pub(self) async fn auth_infisical_azure(
client: &mut Client,
identity_id: Option<String>,
jwt: Option<String>,
) -> Result<reqwest::Response> {
let request_client = reqwest::Client::builder()
.use_preconfigured_tls(rustls_platform_verifier::tls_config())
.build()?;

let request = request_client
.post(format!(
"{}/api/v1/auth/azure-auth/login",
client.site_url.clone()
))
.header(reqwest::header::ACCEPT, "application/json")
.header(reqwest::header::USER_AGENT, client.user_agent.clone());

let mut body = HashMap::new();
body.insert("identityId", identity_id);
body.insert("jwt", jwt);

let response = request.form(&body).send().await?;

return Ok(response);
}

pub(self) async fn auth_infisical_kubernetes(
client: &mut Client,
identity_id: Option<String>,
Expand Down
48 changes: 41 additions & 7 deletions crates/infisical/src/client/auth_method_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::constants::{
INFISICAL_AWS_IAM_IDENTITY_ID_ENV_NAME, INFISICAL_GCP_AUTH_IDENTITY_ID_ENV_NAME,
INFISICAL_AWS_IAM_AUTH_IDENTITY_ID_ENV_NAME, INFISICAL_AZURE_AUTH_IDENTITY_ID_ENV_NAME,
INFISICAL_GCP_AUTH_IDENTITY_ID_ENV_NAME,
INFISICAL_GCP_IAM_SERVICE_ACCOUNT_KEY_FILE_PATH_ENV_NAME,
INFISICAL_KUBERNETES_IDENTITY_ID_ENV_NAME,
INFISICAL_KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH_ENV_NAME,
Expand Down Expand Up @@ -55,18 +56,28 @@ pub struct KubernetesAuthMethod {
pub identity_id: String,

#[schemars(
description = "The path to the Kubernetes Service Account token file.\n\n This is usually located at /var/run/secrets/kubernetes.io/serviceaccount/token."
description = "The path to the Kubernetes Service Account token file.\n\nIf no path is provided, it will default to /var/run/secrets/kubernetes.io/serviceaccount/token."
)]
#[serde(default = "default_kubernetes_service_account_token_path")]
pub service_account_token_path: Option<String>,
}

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct AzureAuthMethod {
#[schemars(
description = "The Infisical Identity ID that you want to authenticate to Infisical with."
)]
pub identity_id: String,
}

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(default, rename_all = "camelCase")]
pub struct AuthenticationOptions {
pub access_token: Option<String>,
pub universal_auth: Option<UniversalAuthMethod>,
pub kubernetes: Option<KubernetesAuthMethod>,
pub azure: Option<AzureAuthMethod>,
pub gcp_id_token: Option<GCPIdTokenAuthMethod>,
pub gcp_iam: Option<GCPIamAuthMethod>,
pub aws_iam: Option<AWSIamAuthMethod>,
Expand All @@ -81,6 +92,7 @@ impl Default for AuthenticationOptions {
gcp_iam: None,
aws_iam: None,
kubernetes: None,
azure: None,
}
}
}
Expand All @@ -89,6 +101,7 @@ impl Default for AuthenticationOptions {
pub enum AuthMethod {
UniversalAuth,
Kubernetes,
Azure,
GcpIdToken,
GcpIam,
AwsIam,
Expand Down Expand Up @@ -132,30 +145,44 @@ impl AuthenticationOptions {
return Ok(AuthMethod::Kubernetes);
}
return Err("kubernetes auth is present but identity_id is empty".into());

// AZURE AUTH:
} else if let Some(ref auth) = self.azure {
if !auth.identity_id.is_empty() {
return Ok(AuthMethod::Azure);
}
return Err("azure auth is present but identity_id is empty".into());
} else {
debug!("No authentication method is set. Checking environment variables.");

// universal auth env's
let universal_auth_client_id_env =
std::env::var(INFISICAL_UNIVERSAL_AUTH_CLIENT_ID_ENV_NAME).unwrap_or_default();
let universal_auth_client_secret_env =
std::env::var(INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET_ENV_NAME).unwrap_or_default();

// gcp auth env's
let gcp_auth_identity_id_env =
std::env::var(INFISICAL_GCP_AUTH_IDENTITY_ID_ENV_NAME).unwrap_or_default();

let gcp_iam_service_account_key_file_path_env =
std::env::var(INFISICAL_GCP_IAM_SERVICE_ACCOUNT_KEY_FILE_PATH_ENV_NAME)
.unwrap_or_default();

// aws iam auth env's
let aws_iam_identity_id_env =
std::env::var(INFISICAL_AWS_IAM_IDENTITY_ID_ENV_NAME).unwrap_or_default();
std::env::var(INFISICAL_AWS_IAM_AUTH_IDENTITY_ID_ENV_NAME).unwrap_or_default();

// kubernetes auth env's
let kubernetes_identity_id_env =
std::env::var(INFISICAL_KUBERNETES_IDENTITY_ID_ENV_NAME).unwrap_or_default();
let kubernetes_service_account_token_path_env =
std::env::var(INFISICAL_KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH_ENV_NAME)
.unwrap_or_default();

// azure auth env's
let azure_auth_identity_id_env =
std::env::var(INFISICAL_AZURE_AUTH_IDENTITY_ID_ENV_NAME).unwrap_or_default();

// universal auth env check
if !universal_auth_client_id_env.is_empty()
&& !universal_auth_client_secret_env.is_empty()
Expand Down Expand Up @@ -198,9 +225,7 @@ impl AuthenticationOptions {
}

// kubernetes auth env check
if !kubernetes_identity_id_env.is_empty()
// && !kubernetes_service_account_token_path_env.is_empty()
{
if !kubernetes_identity_id_env.is_empty() {
self.kubernetes = Some(KubernetesAuthMethod {
identity_id: kubernetes_identity_id_env,
service_account_token_path: Some(kubernetes_service_account_token_path_env)
Expand All @@ -210,6 +235,15 @@ impl AuthenticationOptions {
return Ok(AuthMethod::Kubernetes);
}

// azure auth env check
if !azure_auth_identity_id_env.is_empty() {
self.azure = Some(AzureAuthMethod {
identity_id: azure_auth_identity_id_env,
});

return Ok(AuthMethod::Azure);
}

return Err("No authentication method is set.".into());
}
}
Expand Down
9 changes: 8 additions & 1 deletion crates/infisical/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,23 @@ pub const INFISICAL_GCP_IAM_SERVICE_ACCOUNT_KEY_FILE_PATH_ENV_NAME: &str =
"INFISICAL_GCP_IAM_SERVICE_ACCOUNT_KEY_FILE_PATH";

// AWS IAM Auth:
pub const INFISICAL_AWS_IAM_IDENTITY_ID_ENV_NAME: &str = "INFISICAL_AWS_IAM_AUTH_IDENTITY_ID";
pub const INFISICAL_AWS_IAM_AUTH_IDENTITY_ID_ENV_NAME: &str = "INFISICAL_AWS_IAM_AUTH_IDENTITY_ID";

// Kubernetes Auth:
pub const INFISICAL_KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH_ENV_NAME: &str =
// /var/run/secrets/kubernetes.io/serviceaccount/token
"INFISICAL_KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH";

// Azure Auth:
pub const INFISICAL_AZURE_AUTH_IDENTITY_ID_ENV_NAME: &str = "INFISICAL_AZURE_AUTH_IDENTITY_ID";

pub const INFISICAL_KUBERNETES_IDENTITY_ID_ENV_NAME: &str = "INFISICAL_KUBERNETES_IDENTITY_ID";

// AWS EC2 Metadata Service:
pub const AWS_EC2_METADATA_TOKEN_URL: &str = "http://169.254.169.254/latest/api/token";
pub const AWS_EC2_INSTANCE_IDENTITY_DOCUMENT_URL: &str =
"http://169.254.169.254/latest/dynamic/instance-identity/document";

// Azure Metadata Service:
pub const AZURE_METADATA_SERVICE_URL: &str =
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F";
29 changes: 15 additions & 14 deletions crates/infisical/src/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::borrow::Cow;

use crate::{
api::auth::{
aws_iam_login::aws_iam_login, gcp_iam_login::gcp_iam_login,
aws_iam_login::aws_iam_login, azure_login::azure_login, gcp_iam_login::gcp_iam_login,
gcp_id_token_login::gcp_id_token_login, kubernetes_login::kubernetes_login,
universal_auth_login::universal_auth_login,
},
Expand Down Expand Up @@ -35,45 +35,46 @@ pub async fn handle_authentication(client: &mut Client) -> Result<()> {
debug!("Auth validation passed");

let auth_method = validation_result.unwrap_or(AuthMethod::UniversalAuth);
let access_token;

let result;

match auth_method {
AuthMethod::UniversalAuth => {
debug!("Auth method is Universal Auth");
let result = universal_auth_login(client).await?;
access_token = result.access_token;
result = universal_auth_login(client).await?;
}
AuthMethod::GcpIdToken => {
debug!("Auth method is GCP ID Token");
let result = gcp_id_token_login(client).await?;
access_token = result.access_token;
result = gcp_id_token_login(client).await?;
}
AuthMethod::GcpIam => {
debug!("Auth method is GCP IAM");
let result = gcp_iam_login(client).await?;
access_token = result.access_token;
result = gcp_iam_login(client).await?;
}

AuthMethod::AwsIam => {
debug!("Auth method is AWS IAM");
let result = aws_iam_login(client).await?;
access_token = result.access_token;
result = aws_iam_login(client).await?;
}

AuthMethod::Kubernetes => {
debug!("Auth method is Kubernetes");
let result = kubernetes_login(client).await?;
access_token = result.access_token;
result = kubernetes_login(client).await?;
}

AuthMethod::Azure => {
debug!("Auth method is Azure");
result = azure_login(client).await?;
}
}

if access_token.is_empty() {
if result.access_token.is_empty() {
debug!("No access token obtained");
return Err(Error::NoAccessTokenObtained);
}

debug!("Setting access token");
client.set_access_token(access_token);
client.set_access_token(result.access_token);
Ok(())
}

Expand Down
1 change: 1 addition & 0 deletions crates/infisical/tests/cryptography.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ fn create_client() -> Client {

auth: AuthenticationOptions {
gcp_iam: None,
azure: None,
gcp_id_token: None,
kubernetes: None,
aws_iam: None,
Expand Down

0 comments on commit de75e04

Please sign in to comment.