diff --git a/crates/infisical-py/infisical_client/__init__.py b/crates/infisical-py/infisical_client/__init__.py index 034101f..33c6294 100644 --- a/crates/infisical-py/infisical_client/__init__.py +++ b/crates/infisical-py/infisical_client/__init__.py @@ -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 \ No newline at end of file +from .schemas import UniversalAuthMethod as UniversalAuthMethod +from .schemas import AzureAuthMethod as AzureAuthMethod \ No newline at end of file diff --git a/crates/infisical/src/api/auth/azure_login.rs b/crates/infisical/src/api/auth/azure_login.rs new file mode 100644 index 0000000..74c8649 --- /dev/null +++ b/crates/infisical/src/api/auth/azure_login.rs @@ -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 { + 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::().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::().await?; + return Ok(response_json); +} diff --git a/crates/infisical/src/api/auth/mod.rs b/crates/infisical/src/api/auth/mod.rs index 29b63a1..af159c8 100644 --- a/crates/infisical/src/api/auth/mod.rs +++ b/crates/infisical/src/api/auth/mod.rs @@ -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; @@ -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, + jwt: Option, +) -> Result { + 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, diff --git a/crates/infisical/src/client/auth_method_settings.rs b/crates/infisical/src/client/auth_method_settings.rs index bdc05d1..3521196 100644 --- a/crates/infisical/src/client/auth_method_settings.rs +++ b/crates/infisical/src/client/auth_method_settings.rs @@ -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, @@ -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, } +#[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, pub universal_auth: Option, pub kubernetes: Option, + pub azure: Option, pub gcp_id_token: Option, pub gcp_iam: Option, pub aws_iam: Option, @@ -81,6 +92,7 @@ impl Default for AuthenticationOptions { gcp_iam: None, aws_iam: None, kubernetes: None, + azure: None, } } } @@ -89,6 +101,7 @@ impl Default for AuthenticationOptions { pub enum AuthMethod { UniversalAuth, Kubernetes, + Azure, GcpIdToken, GcpIam, AwsIam, @@ -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() @@ -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) @@ -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()); } } diff --git a/crates/infisical/src/constants.rs b/crates/infisical/src/constants.rs index 26b226e..a39b841 100644 --- a/crates/infisical/src/constants.rs +++ b/crates/infisical/src/constants.rs @@ -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"; diff --git a/crates/infisical/src/helper.rs b/crates/infisical/src/helper.rs index 59e4043..607df4d 100644 --- a/crates/infisical/src/helper.rs +++ b/crates/infisical/src/helper.rs @@ -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, }, @@ -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(()) } diff --git a/crates/infisical/tests/cryptography.rs b/crates/infisical/tests/cryptography.rs index eebb815..549f8a1 100644 --- a/crates/infisical/tests/cryptography.rs +++ b/crates/infisical/tests/cryptography.rs @@ -44,6 +44,7 @@ fn create_client() -> Client { auth: AuthenticationOptions { gcp_iam: None, + azure: None, gcp_id_token: None, kubernetes: None, aws_iam: None,