diff --git a/.github/workflows/generate-schemas.yml b/.github/workflows/generate-schemas.yml index 70f549f..19d93c7 100644 --- a/.github/workflows/generate-schemas.yml +++ b/.github/workflows/generate-schemas.yml @@ -77,3 +77,10 @@ jobs: name: sdk-schemas-java path: ${{ github.workspace }}/languages/java/src/main/java/com/infisical/sdk/schema/* if-no-files-found: error + + - name: Upload Ruby schemas artifact + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + with: + name: sdk-schemas-ruby + path: ${{ github.workspace }}/languages/ruby/infisical-sdk/lib/schemas.rb + if-no-files-found: error diff --git a/.github/workflows/release-ruby.yml b/.github/workflows/release-ruby.yml new file mode 100644 index 0000000..2434d66 --- /dev/null +++ b/.github/workflows/release-ruby.yml @@ -0,0 +1,110 @@ +name: Release Ruby SDK +run-name: Release Ruby SDK + +on: + workflow_dispatch: + + push: + tags: + - "*.*.*" # version, e.g. 1.0.0 + +permissions: + contents: read + id-token: write + +jobs: + generate_schemas: + uses: ./.github/workflows/generate-schemas.yml + + build_rust: + uses: ./.github/workflows/build-c-bindings.yml + + release_ruby: + name: Release Ruby + runs-on: ubuntu-22.04 + needs: + - generate_schemas + - build_rust + steps: + - name: Checkout Repository + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + + - name: Set up Ruby + uses: ruby/setup-ruby@cacc9f1c0b3f4eb8a16a6bb0ed10897b43b9de49 # v1.176.0 + with: + ruby-version: 3.2 + + - name: Download Ruby schemas artifact + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: sdk-schemas-ruby + path: languages/ruby/infisical-sdk/lib/ + + # x64 Apple Darwin + - name: Download x86_64-apple-darwin files + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: libinfisical_c_files-x86_64-apple-darwin + path: temp/macos-x64 + + # ARM64 Apple Darwin + - name: Download aarch64-apple-darwin files + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: libinfisical_c_files-aarch64-apple-darwin + path: temp/macos-arm64 + + # x64 Linux GNU + - name: Download x86_64-unknown-linux-gnu files + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: libinfisical_c_files-x86_64-unknown-linux-gnu + path: temp/linux-x64 + + # ARM64 Linux GNU + - name: Download aarch64-unknown-linux-gnu files + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: libinfisical_c_files-aarch64-unknown-linux-gnu + path: temp/linux-arm64 + + # MSVC x86/x64 Windows + - name: Download x86_64-pc-windows-msvc files + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: libinfisical_c_files-x86_64-pc-windows-msvc + path: temp/windows-x64 + + - name: Copy lib files + run: | + mkdir -p languages/ruby/infisical-sdk/lib/macos-arm64 + mkdir -p languages/ruby/infisical-sdk/lib/linux-x64 + mkdir -p languages/ruby/infisical-sdk/lib/linux-arm64 + mkdir -p languages/ruby/infisical-sdk/lib/macos-x64 + mkdir -p languages/ruby/infisical-sdk/lib/windows-x64 + + platforms=("macos-arm64" "linux-x64" "linux-arm64" "macos-x64" "windows-x64") + files=("libinfisical_c.dylib" "libinfisical_c.so" "libinfisical_c.so" "libinfisical_c.dylib" "libinfisical_c.dll") + + for ((i=0; i<${#platforms[@]}; i++)); do + cp "temp/${platforms[$i]}/${files[$i]}" "languages/ruby/infisical-sdk/lib/${platforms[$i]}/${files[$i]}" + done + + - name: bundle install + run: bundle install + working-directory: languages/ruby/infisical-sdk + + - name: Build gem + run: gem build infisical-sdk.gemspec + working-directory: languages/ruby/infisical-sdk + + - name: Push gem to Rubygems + run: | + mkdir -p $HOME/.gem + touch $HOME/.gem/credentials + chmod 0600 $HOME/.gem/credentials + printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials + gem push *.gem + env: + GEM_HOST_API_KEY: ${{ secrets.GEM_HOST_API_KEY }} + working-directory: languages/ruby/infisical-sdk diff --git a/.gitignore b/.gitignore index 880f445..8292893 100644 --- a/.gitignore +++ b/.gitignore @@ -198,4 +198,8 @@ languages/node/npm/**/*.node languages/node/artifacts languages/node/package/**/* -languages/java/gradle.properties \ No newline at end of file + +languages/ruby/infisical-sdk/Gemfile.lock +languages/ruby/infisical-sdk/pkg + +languages/java/gradle.properties diff --git a/crates/infisical-json/src/client.rs b/crates/infisical-json/src/client.rs index 06f0b33..866772f 100644 --- a/crates/infisical-json/src/client.rs +++ b/crates/infisical-json/src/client.rs @@ -46,6 +46,20 @@ impl Client { Command::CreateSymmetricKey(_) => { self.0.cryptography().create_symmetric_key().into_string() } + + // Authentication + Command::UniversalAuthLogin(req) => { + self.0.auth().universal_login(&req).await.into_string() + } + Command::KubernetesAuthLogin(req) => { + self.0.auth().kubernetes_login(&req).await.into_string() + } + Command::AzureAuthLogin(req) => self.0.auth().azure_login(&req).await.into_string(), + Command::GcpIdTokenAuthLogin(req) => { + self.0.auth().gcp_id_token_login(&req).await.into_string() + } + Command::GcpIamAuthLogin(req) => self.0.auth().gcp_iam_login(&req).await.into_string(), + Command::AwsIamAuthLogin(req) => self.0.auth().aws_iam_login(&req).await.into_string(), } } diff --git a/crates/infisical-json/src/command.rs b/crates/infisical-json/src/command.rs index e87e991..e7eb313 100644 --- a/crates/infisical-json/src/command.rs +++ b/crates/infisical-json/src/command.rs @@ -5,6 +5,11 @@ use infisical::manager::secrets::{ use infisical::manager::cryptography::{DecryptSymmetricOptions, EncryptSymmetricOptions}; +use infisical::client::auth_method_settings::{ + AWSIamAuthMethod, AzureAuthMethod, GCPIamAuthMethod, GCPIdTokenAuthMethod, + KubernetesAuthMethod, UniversalAuthMethod, +}; + use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -28,4 +33,11 @@ pub enum Command { CreateSymmetricKey(ArbitraryOptions), EncryptSymmetric(EncryptSymmetricOptions), DecryptSymmetric(DecryptSymmetricOptions), + + UniversalAuthLogin(UniversalAuthMethod), + KubernetesAuthLogin(KubernetesAuthMethod), + AzureAuthLogin(AzureAuthMethod), + GcpIdTokenAuthLogin(GCPIdTokenAuthMethod), + GcpIamAuthLogin(GCPIamAuthMethod), + AwsIamAuthLogin(AWSIamAuthMethod), } diff --git a/crates/infisical/src/api/auth/aws_iam_login.rs b/crates/infisical/src/api/auth/aws_iam_login.rs index 9862225..82ebc35 100644 --- a/crates/infisical/src/api/auth/aws_iam_login.rs +++ b/crates/infisical/src/api/auth/aws_iam_login.rs @@ -15,18 +15,10 @@ use aws_sigv4::{ use crate::helper::get_aws_region; use log::debug; -pub async fn aws_iam_login(client: &mut Client) -> Result { - let identity_id; - - if let Some(aws_iam_auth) = &client.auth.aws_iam { - identity_id = aws_iam_auth.identity_id.clone(); - } else { - return Err(Error::MissingParametersAuthError { - message: "Attempt to authenticate with AWS IAM failed. Identity ID is missing." - .to_string(), - }); - } - +pub async fn aws_iam_login( + client: &mut Client, + identity_id: String, +) -> Result { let aws_region = get_aws_region().await?.into_owned(); let aws_region_str: &'static str = Box::leak(aws_region.into_boxed_str()); diff --git a/crates/infisical/src/api/auth/azure_login.rs b/crates/infisical/src/api/auth/azure_login.rs index 74c8649..0800f6e 100644 --- a/crates/infisical/src/api/auth/azure_login.rs +++ b/crates/infisical/src/api/auth/azure_login.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use crate::{ constants::AZURE_METADATA_SERVICE_URL, - error::{api_error_handler, Error, Result}, + error::{api_error_handler, Result}, Client, }; @@ -13,17 +13,10 @@ 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(), - }); - } - +pub async fn azure_login( + client: &mut Client, + identity_id: String, +) -> Result { let request_client = reqwest::Client::builder() .use_preconfigured_tls(rustls_platform_verifier::tls_config()) .build() diff --git a/crates/infisical/src/api/auth/gcp_iam_login.rs b/crates/infisical/src/api/auth/gcp_iam_login.rs index ba9e137..6c8d279 100644 --- a/crates/infisical/src/api/auth/gcp_iam_login.rs +++ b/crates/infisical/src/api/auth/gcp_iam_login.rs @@ -17,19 +17,11 @@ struct JwtPayload { use super::AccessTokenSuccessResponse; -pub async fn gcp_iam_login(client: &mut Client) -> Result { - let service_account_key_path; - let identity_id; - - if let Some(gcp_iam_auth) = &client.auth.gcp_iam { - service_account_key_path = gcp_iam_auth.service_account_key_file_path.clone(); - identity_id = gcp_iam_auth.identity_id.clone(); - } else { - return Err(Error::MissingParametersAuthError { - message: "Attempt to authenticate with GCP IAM failed. Identity ID or service account key path is missing.".to_string(), - }); - } - +pub async fn gcp_iam_login( + client: &mut Client, + identity_id: String, + service_account_key_path: String, +) -> Result { let service_account_key = &read_service_account_key(service_account_key_path).await?; // Create an authenticator diff --git a/crates/infisical/src/api/auth/gcp_id_token_login.rs b/crates/infisical/src/api/auth/gcp_id_token_login.rs index 8a16a40..b73f969 100644 --- a/crates/infisical/src/api/auth/gcp_id_token_login.rs +++ b/crates/infisical/src/api/auth/gcp_id_token_login.rs @@ -6,18 +6,10 @@ use crate::{ use super::AccessTokenSuccessResponse; -pub async fn gcp_id_token_login(client: &mut Client) -> Result { - let identity_id; - - if let Some(gcp_id_token_auth) = &client.auth.gcp_id_token { - identity_id = gcp_id_token_auth.identity_id.clone(); - } else { - return Err(Error::MissingParametersAuthError { - message: "Attempt to authenticate with GCP ID Token failed. Identity ID is missing." - .to_string(), - }); - } - +pub async fn gcp_id_token_login( + client: &mut Client, + identity_id: String, +) -> Result { let request_client = reqwest::Client::builder() .use_preconfigured_tls(rustls_platform_verifier::tls_config()) .build() diff --git a/crates/infisical/src/api/auth/kubernetes_login.rs b/crates/infisical/src/api/auth/kubernetes_login.rs index 6bb1326..17d5315 100644 --- a/crates/infisical/src/api/auth/kubernetes_login.rs +++ b/crates/infisical/src/api/auth/kubernetes_login.rs @@ -7,24 +7,11 @@ use crate::{ use super::{auth_infisical_kubernetes, AccessTokenSuccessResponse}; -pub async fn kubernetes_login(client: &mut Client) -> Result { - let identity_id; - let service_account_token_path; - - if let Some(kubernetes_auth) = &client.auth.kubernetes { - identity_id = kubernetes_auth.identity_id.clone(); - if kubernetes_auth.service_account_token_path.is_none() { - return Err(Error::MissingParametersAuthError { - message: "Attempt to authenticate with Kubernetes. Service account token path is missing.".to_string(), - }); - } - service_account_token_path = kubernetes_auth.service_account_token_path.clone().unwrap(); - } else { - return Err(Error::MissingParametersAuthError { - message: "Attempt to authenticate with Kubernetes. Identity ID and service account token path is missing.".to_string(), - }); - } - +pub async fn kubernetes_login( + client: &mut Client, + identity_id: String, + service_account_token_path: String, +) -> Result { debug!( "Reading service account token from path: {}", service_account_token_path diff --git a/crates/infisical/src/api/auth/mod.rs b/crates/infisical/src/api/auth/mod.rs index af159c8..910cc89 100644 --- a/crates/infisical/src/api/auth/mod.rs +++ b/crates/infisical/src/api/auth/mod.rs @@ -20,11 +20,15 @@ fn base64_encode(plain: String) -> String { return base64::engine::general_purpose::STANDARD.encode(plain); } -#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] #[serde(rename_all = "camelCase")] pub struct AccessTokenSuccessResponse { pub access_token: String, + pub expires_in: i64, + #[serde(rename = "accessTokenMaxTTL")] + pub access_token_max_ttl: i64, + pub token_type: String, } #[derive(Serialize)] diff --git a/crates/infisical/src/api/auth/universal_auth_login.rs b/crates/infisical/src/api/auth/universal_auth_login.rs index e0a4823..4319322 100644 --- a/crates/infisical/src/api/auth/universal_auth_login.rs +++ b/crates/infisical/src/api/auth/universal_auth_login.rs @@ -1,5 +1,5 @@ use crate::{ - error::{api_error_handler, Error, Result}, + error::{api_error_handler, Result}, Client, }; use log::debug; @@ -8,19 +8,11 @@ use std::collections::HashMap; use super::AccessTokenSuccessResponse; -pub async fn universal_auth_login(client: &mut Client) -> Result { - let client_id; - let client_secret; - - if let Some(universal_auth) = &client.auth.universal_auth { - client_id = universal_auth.client_id.clone(); - client_secret = universal_auth.client_secret.clone(); - } else { - return Err(Error::MissingParametersAuthError { - message: "Attempt to authenticate with Universal Auth failed. Universal auth credentials are missing.".to_string(), - }); - } - +pub async fn universal_auth_login( + client: &mut Client, + client_id: String, + client_secret: String, +) -> Result { let mut body = HashMap::new(); body.insert("clientId", Some(client_id)); body.insert("clientSecret", Some(client_secret)); diff --git a/crates/infisical/src/auth/authenticate.rs b/crates/infisical/src/auth/authenticate.rs deleted file mode 100644 index 241c19c..0000000 --- a/crates/infisical/src/auth/authenticate.rs +++ /dev/null @@ -1,24 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use crate::{ - api::auth::universal_auth_login::universal_auth_login, api::auth::AccessTokenSuccessResponse, - error::Result, Client, -}; - -#[derive(Serialize, Deserialize, Debug, JsonSchema)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct UpdateAccessTokenRequest; // No input. But we have to do this to get a schema for the request. - -pub async fn update_access_token(client: &mut Client) -> Result { - let res = universal_auth_login(client).await; - - // If the response is ok, then we set the client access token, otherwise we throw an error - match res { - Ok(res) => { - client.set_access_token(res.access_token.clone()); - Ok(res) - } - Err(res) => Err(res), - } -} diff --git a/crates/infisical/src/auth/mod.rs b/crates/infisical/src/auth/mod.rs index 3459c4b..e9f5ff9 100644 --- a/crates/infisical/src/auth/mod.rs +++ b/crates/infisical/src/auth/mod.rs @@ -1,4 +1 @@ -pub mod authenticate; - pub use crate::api::auth::AccessTokenSuccessResponse; -pub use authenticate::UpdateAccessTokenRequest; diff --git a/crates/infisical/src/helper.rs b/crates/infisical/src/helper.rs index f38f1c9..f018917 100644 --- a/crates/infisical/src/helper.rs +++ b/crates/infisical/src/helper.rs @@ -41,30 +41,109 @@ pub async fn handle_authentication(client: &mut Client) -> Result<()> { match auth_method { AuthMethod::UniversalAuth => { debug!("Auth method is Universal Auth"); - result = universal_auth_login(client).await?; + + let client_id; + let client_secret; + + if let Some(universal_auth) = &client.auth.universal_auth { + client_id = universal_auth.client_id.clone(); + client_secret = universal_auth.client_secret.clone(); + } else { + return Err(Error::MissingParametersAuthError { + message: "Attempt to authenticate with Universal Auth failed. Universal auth credentials are missing.".to_string(), + }); + } + + result = universal_auth_login(client, client_id, client_secret).await?; } AuthMethod::GcpIdToken => { debug!("Auth method is GCP ID Token"); - result = gcp_id_token_login(client).await?; + + let identity_id; + if let Some(gcp_id_token_auth) = &client.auth.gcp_id_token { + identity_id = gcp_id_token_auth.identity_id.clone(); + } else { + return Err(Error::MissingParametersAuthError { + message: + "Attempt to authenticate with GCP ID Token failed. Identity ID is missing." + .to_string(), + }); + } + + result = gcp_id_token_login(client, identity_id).await?; } AuthMethod::GcpIam => { debug!("Auth method is GCP IAM"); - result = gcp_iam_login(client).await?; + + let service_account_key_path; + let identity_id; + + if let Some(gcp_iam_auth) = &client.auth.gcp_iam { + service_account_key_path = gcp_iam_auth.service_account_key_file_path.clone(); + identity_id = gcp_iam_auth.identity_id.clone(); + } else { + return Err(Error::MissingParametersAuthError { + message: "Attempt to authenticate with GCP IAM failed. Identity ID or service account key path is missing.".to_string(), + }); + } + + result = gcp_iam_login(client, identity_id, service_account_key_path).await?; } AuthMethod::AwsIam => { debug!("Auth method is AWS IAM"); - result = aws_iam_login(client).await?; + + let identity_id; + + if let Some(aws_iam_auth) = &client.auth.aws_iam { + identity_id = aws_iam_auth.identity_id.clone(); + } else { + return Err(Error::MissingParametersAuthError { + message: "Attempt to authenticate with AWS IAM failed. Identity ID is missing." + .to_string(), + }); + } + + result = aws_iam_login(client, identity_id).await?; } AuthMethod::Kubernetes => { debug!("Auth method is Kubernetes"); - result = kubernetes_login(client).await?; + + let identity_id; + let service_account_token_path; + + if let Some(kubernetes_auth) = &client.auth.kubernetes { + identity_id = kubernetes_auth.identity_id.clone(); + if kubernetes_auth.service_account_token_path.is_none() { + return Err(Error::MissingParametersAuthError { + message: "Attempt to authenticate with Kubernetes. Service account token path is missing.".to_string(), + }); + } + service_account_token_path = + kubernetes_auth.service_account_token_path.clone().unwrap(); + } else { + return Err(Error::MissingParametersAuthError { + message: "Attempt to authenticate with Kubernetes. Identity ID and service account token path is missing.".to_string(), + }); + } + + result = kubernetes_login(client, identity_id, service_account_token_path).await?; } AuthMethod::Azure => { debug!("Auth method is Azure"); - result = azure_login(client).await?; + + 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(), + }); + } + result = azure_login(client, identity_id).await?; } } diff --git a/crates/infisical/src/manager/auth/aws_iam.rs b/crates/infisical/src/manager/auth/aws_iam.rs new file mode 100644 index 0000000..1f8d1ce --- /dev/null +++ b/crates/infisical/src/manager/auth/aws_iam.rs @@ -0,0 +1,11 @@ +use crate::{ + api::auth::aws_iam_login::aws_iam_login, auth::AccessTokenSuccessResponse, + client::auth_method_settings::AWSIamAuthMethod, error::Result, Client, +}; + +pub async fn aws_iam( + client: &mut Client, + input: &AWSIamAuthMethod, +) -> Result { + return aws_iam_login(client, input.identity_id.clone()).await; +} diff --git a/crates/infisical/src/manager/auth/azure.rs b/crates/infisical/src/manager/auth/azure.rs new file mode 100644 index 0000000..d8da56a --- /dev/null +++ b/crates/infisical/src/manager/auth/azure.rs @@ -0,0 +1,11 @@ +use crate::{ + api::auth::azure_login::azure_login, auth::AccessTokenSuccessResponse, + client::auth_method_settings::AzureAuthMethod, error::Result, Client, +}; + +pub async fn azure_auth( + client: &mut Client, + input: &AzureAuthMethod, +) -> Result { + return azure_login(client, input.identity_id.clone()).await; +} diff --git a/crates/infisical/src/manager/auth/gcp_iam.rs b/crates/infisical/src/manager/auth/gcp_iam.rs new file mode 100644 index 0000000..21d34e2 --- /dev/null +++ b/crates/infisical/src/manager/auth/gcp_iam.rs @@ -0,0 +1,16 @@ +use crate::{ + api::auth::gcp_iam_login::gcp_iam_login, auth::AccessTokenSuccessResponse, + client::auth_method_settings::GCPIamAuthMethod, error::Result, Client, +}; + +pub async fn gcp_iam( + client: &mut Client, + input: &GCPIamAuthMethod, +) -> Result { + return gcp_iam_login( + client, + input.identity_id.clone(), + input.service_account_key_file_path.clone(), + ) + .await; +} diff --git a/crates/infisical/src/manager/auth/gcp_id_token.rs b/crates/infisical/src/manager/auth/gcp_id_token.rs new file mode 100644 index 0000000..ae67d87 --- /dev/null +++ b/crates/infisical/src/manager/auth/gcp_id_token.rs @@ -0,0 +1,11 @@ +use crate::{ + api::auth::gcp_id_token_login::gcp_id_token_login, auth::AccessTokenSuccessResponse, + client::auth_method_settings::GCPIdTokenAuthMethod, error::Result, Client, +}; + +pub async fn gcp_id_token( + client: &mut Client, + input: &GCPIdTokenAuthMethod, +) -> Result { + return gcp_id_token_login(client, input.identity_id.clone()).await; +} diff --git a/crates/infisical/src/manager/auth/kubernetes.rs b/crates/infisical/src/manager/auth/kubernetes.rs new file mode 100644 index 0000000..dfbd4d9 --- /dev/null +++ b/crates/infisical/src/manager/auth/kubernetes.rs @@ -0,0 +1,19 @@ +use crate::{ + api::auth::kubernetes_login::kubernetes_login, auth::AccessTokenSuccessResponse, + client::auth_method_settings::KubernetesAuthMethod, error::Result, Client, +}; + +pub async fn kubernetes_auth( + client: &mut Client, + input: &KubernetesAuthMethod, +) -> Result { + return kubernetes_login( + client, + input.identity_id.clone(), + input + .service_account_token_path + .clone() + .unwrap_or("/var/run/secrets/kubernetes.io/serviceaccount/token".to_string()), + ) + .await; +} diff --git a/crates/infisical/src/manager/auth/mod.rs b/crates/infisical/src/manager/auth/mod.rs new file mode 100644 index 0000000..078276b --- /dev/null +++ b/crates/infisical/src/manager/auth/mod.rs @@ -0,0 +1,6 @@ +pub mod aws_iam; +pub mod azure; +pub mod gcp_iam; +pub mod gcp_id_token; +pub mod kubernetes; +pub mod universal_auth; diff --git a/crates/infisical/src/manager/auth/universal_auth.rs b/crates/infisical/src/manager/auth/universal_auth.rs new file mode 100644 index 0000000..05ca573 --- /dev/null +++ b/crates/infisical/src/manager/auth/universal_auth.rs @@ -0,0 +1,12 @@ +use crate::{ + api::auth::universal_auth_login::universal_auth_login, auth::AccessTokenSuccessResponse, + client::auth_method_settings::UniversalAuthMethod, error::Result, Client, +}; + +pub async fn universal_auth( + client: &mut Client, + input: &UniversalAuthMethod, +) -> Result { + return universal_auth_login(client, input.client_id.clone(), input.client_secret.clone()) + .await; +} diff --git a/crates/infisical/src/manager/client_auth.rs b/crates/infisical/src/manager/client_auth.rs new file mode 100644 index 0000000..6e6ace9 --- /dev/null +++ b/crates/infisical/src/manager/client_auth.rs @@ -0,0 +1,94 @@ +use crate::{ + auth::AccessTokenSuccessResponse, + client::auth_method_settings::{ + AWSIamAuthMethod, AzureAuthMethod, GCPIamAuthMethod, GCPIdTokenAuthMethod, + KubernetesAuthMethod, UniversalAuthMethod, + }, + error::Result, + Client, +}; + +use super::auth::{ + aws_iam::aws_iam, azure::azure_auth, gcp_iam::gcp_iam, gcp_id_token::gcp_id_token, + kubernetes::kubernetes_auth, universal_auth::universal_auth, +}; + +#[allow(dead_code)] +pub struct ClientAuth<'a> { + pub(crate) client: &'a mut crate::Client, +} + +impl<'a> ClientAuth<'a> { + fn handle_auth_response(&'a mut self, response: &Result) { + if response.is_ok() { + let response = response.as_ref().unwrap(); + self.client.set_access_token(response.access_token.clone()); + } + } + + pub async fn universal_login( + &'a mut self, + input: &UniversalAuthMethod, + ) -> Result { + let response = universal_auth(self.client, input).await; + + self.handle_auth_response(&response); + return response; + } + + pub async fn kubernetes_login( + &'a mut self, + input: &KubernetesAuthMethod, + ) -> Result { + let response = kubernetes_auth(self.client, input).await; + + self.handle_auth_response(&response); + return response; + } + + pub async fn azure_login( + &'a mut self, + input: &AzureAuthMethod, + ) -> Result { + let response = azure_auth(self.client, input).await; + + self.handle_auth_response(&response); + return response; + } + + pub async fn gcp_id_token_login( + &'a mut self, + input: &GCPIdTokenAuthMethod, + ) -> Result { + let response = gcp_id_token(self.client, input).await; + + self.handle_auth_response(&response); + return response; + } + + pub async fn gcp_iam_login( + &'a mut self, + input: &GCPIamAuthMethod, + ) -> Result { + let response = gcp_iam(self.client, input).await; + + self.handle_auth_response(&response); + return response; + } + + pub async fn aws_iam_login( + &'a mut self, + input: &AWSIamAuthMethod, + ) -> Result { + let response = aws_iam(self.client, input).await; + + self.handle_auth_response(&response); + return response; + } +} + +impl<'a> Client { + pub fn auth(&'a mut self) -> ClientAuth<'a> { + ClientAuth { client: self } + } +} diff --git a/crates/infisical/src/manager/mod.rs b/crates/infisical/src/manager/mod.rs index 232eb78..f93b474 100644 --- a/crates/infisical/src/manager/mod.rs +++ b/crates/infisical/src/manager/mod.rs @@ -1,5 +1,7 @@ +pub mod client_auth; pub mod client_cryptography; pub mod client_secrets; +pub mod auth; pub mod cryptography; pub mod secrets; diff --git a/languages/ruby/examples/basic.rb b/languages/ruby/examples/basic.rb new file mode 100644 index 0000000..8f72f00 --- /dev/null +++ b/languages/ruby/examples/basic.rb @@ -0,0 +1,91 @@ + +# Note, this for for testing purposes. This should be changed to [require 'infisical-sdk'] in your code. +require_relative '../infisical-sdk/lib/infisical-sdk' + +# Update these to your own values +project_id = 'f1617cbc-be46-4466-89de-ec8767afeaab' +env_slug = 'dev' + +# Update these to your own values +universal_auth_client_id = 'YOUR_IDENTITY_UNIVERSAL_AUTH_CLIENT_ID' +universal_auth_client_secret = 'YOUR_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET' + + +test_secret_name = 'TEST_SECRET' # Note, this secret needs to already exist in the project/environment that you're testing against. + +test_create_secret_name = 'A_NEW_SECRET' # This secret will be created +test_create_secret_value = 'SOME-API-KEY' # The value of the new secret that will be created + +test_update_secret_new_value = 'NEW VALUE' # The secret created above will be updated to this value + +# 1. Create the Infisical client +infisical = InfisicalSDK::InfisicalClient.new + +# 2. Authenticate the Infisical Client +infisical.auth.universal_auth(client_id: universal_auth_client_id, client_secret: universal_auth_client_secret) + +# 3. Use the Infisical client after authentication +secrets = infisical.secrets.list(project_id: project_id, environment: env_slug) # ... Takes more parameters, but only project ID and env slug is required. +secrets.each_with_index { |secret, index| puts "#{index}: #{secret['secretKey']}: #{secret['secretValue']}" } + +# 4. Get a single secret +puts "Getting secret with name '#{test_secret_name}'" +single_test_secret = infisical.secrets.get( + secret_name: test_secret_name, + project_id: project_id, + environment: env_slug +) +puts "Test Secret: #{single_test_secret}\n\n" + + +# 5. Create a secret +puts 'Creating new secret...' +new_secret = infisical.secrets.create( + secret_name: test_create_secret_name, + secret_value: test_create_secret_value, + project_id: project_id, + environment: env_slug +) +puts "New Secret: #{new_secret}\n\n" + + +# 6. Update the newly created secret's value to be "NEW VALUE" +puts 'Updating secret...' +updated_secret = infisical.secrets.update( + secret_name: test_create_secret_name, + secret_value: test_update_secret_new_value, + project_id: project_id, + environment: env_slug +) +puts "Updated Secret: #{updated_secret}\n\n" + + +# 7. Finally, delete the secret entirely +puts 'Deleting newly created secret...' +deleted_secret = infisical.secrets.delete( + secret_name: test_create_secret_name, + project_id: project_id, + environment: env_slug +) +puts "Deleted Secret: #{deleted_secret}\n\n" + + +### Encryption tests: +plaintext_data = 'Hello World' +key = infisical.encryption.create_symmetric_key + +encrypted_data = infisical.encryption.encrypt_symmetric(data: plaintext_data, key: key) + +decrypted_data = infisical.encryption.decrypt_symmetric( + ciphertext: encrypted_data['ciphertext'], + iv: encrypted_data['iv'], + tag: encrypted_data['tag'], + key: key +) + +puts "Plaintext: #{plaintext_data}\n" +puts "Encryption key: #{key}\n" +puts "Encrypted ciphertext: #{encrypted_data['ciphertext']}\n" +puts "Decrypted text: #{decrypted_data}\n" + +infisical.free_mem diff --git a/languages/ruby/infisical-sdk/Gemfile b/languages/ruby/infisical-sdk/Gemfile new file mode 100644 index 0000000..ecb3ae8 --- /dev/null +++ b/languages/ruby/infisical-sdk/Gemfile @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem 'dry-struct', '~> 1.6' +gem 'dry-types', '~> 1.7' +gem 'ffi', '~> 1.9', '>= 1.9.10' +gem 'rake', '~> 13.0' +gem 'rspec', '~> 3.0' +gem 'rubocop', '~> 1.21' +gem 'rbs', '>= 3.5.2' +gem 'steep', '>= 1.7.1' diff --git a/languages/ruby/infisical-sdk/Rakefile b/languages/ruby/infisical-sdk/Rakefile new file mode 100644 index 0000000..fc4fed2 --- /dev/null +++ b/languages/ruby/infisical-sdk/Rakefile @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'bundler/gem_tasks' +require 'rubocop/rake_task' +require 'rspec/core/rake_task' + +RSpec::Core::RakeTask.new + +RuboCop::RakeTask.new + +task default: :rubocop diff --git a/languages/ruby/infisical-sdk/Steepfile b/languages/ruby/infisical-sdk/Steepfile new file mode 100644 index 0000000..9e8f098 --- /dev/null +++ b/languages/ruby/infisical-sdk/Steepfile @@ -0,0 +1,46 @@ +D = Steep::Diagnostic +# +# target :lib do +# signature "sig" +# +# check "lib" # Directory name +# check "Gemfile" # File name +# check "app/models/**/*.rb" # Glob +# # ignore "lib/templates/*.rb" +# +# # library "pathname" # Standard libraries +# # library "strong_json" # Gems +# +# # configure_code_diagnostics(D::Ruby.default) # `default` diagnostics setting (applies by default) +# # configure_code_diagnostics(D::Ruby.strict) # `strict` diagnostics setting +# # configure_code_diagnostics(D::Ruby.lenient) # `lenient` diagnostics setting +# # configure_code_diagnostics(D::Ruby.silent) # `silent` diagnostics setting +# # configure_code_diagnostics do |hash| # You can setup everything yourself +# # hash[D::Ruby::NoMethod] = :information +# # end +# end + +# target :test do +# signature "sig", "sig-private" +# +# check "test" +# +# # library "pathname" # Standard libraries +# end + +target :app do + check 'lib' + signature 'sig' + + library 'set', 'pathname' + ignore( + 'lib/schemas.rb', + 'lib/infisical_lib.rb', + 'lib/extended_schemas/schemas.rb' + ) + + configure_code_diagnostics do |hash| # You can setup everything yourself + + hash[D::Ruby::UnknownConstant] = :information + end +end \ No newline at end of file diff --git a/languages/ruby/infisical-sdk/infisical-sdk.gemspec b/languages/ruby/infisical-sdk/infisical-sdk.gemspec new file mode 100644 index 0000000..b14a803 --- /dev/null +++ b/languages/ruby/infisical-sdk/infisical-sdk.gemspec @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require_relative 'lib/version' + +Gem::Specification.new do |spec| + spec.name = 'infisical-sdk' + spec.version = InfisicalSDK::VERSION + spec.authors = ['Infisical Inc.'] + spec.email = ['team@infisical.com'] + + spec.summary = 'Ruby SDK for interacting with the Infisical platform.' + spec.description = 'The official Infisical Ruby SDK.' + spec.homepage = 'https://infisical.com' + spec.required_ruby_version = '>= 2.7' + + spec.metadata['homepage_uri'] = spec.homepage + spec.metadata['source_code_uri'] = 'https://github.com/infisical/sdk' + spec.metadata['changelog_uri'] = 'https://infisical.com/docs/changelog/overview' + + # Specify which files should be added to the gem when it is released. + # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + spec.files = Dir.chdir(__dir__) do + `git ls-files -z`.split("\x0").reject do |f| + (File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git Gemfile]) + end + end + + spec.files += Dir.glob('lib/linux-x64/**/*') + spec.files += Dir.glob('lib/linux-arm64/**/*') + spec.files += Dir.glob('lib/macos-x64/**/*') + spec.files += Dir.glob('lib/windows-x64/**/*') + spec.files += Dir.glob('lib/macos-arm64/**/*') + spec.files += Dir.glob('lib/schemas.rb') + + spec.bindir = 'exe' + spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } + spec.require_paths = ['lib'] + + # Uncomment to register a new dependency of your gem + # spec.add_dependency "example-gem", "~> 1.0" + spec.add_dependency 'dry-struct', '~> 1.6' + spec.add_dependency 'dry-types', '~> 1.7' + spec.add_dependency 'ffi', '~> 1.15' + spec.add_dependency 'json', '~> 2.6' + spec.add_dependency 'rake', '~> 13.0' + spec.add_dependency 'rubocop', '~> 1.21' + +end diff --git a/languages/ruby/infisical-sdk/lib/clients/auth.rb b/languages/ruby/infisical-sdk/lib/clients/auth.rb new file mode 100644 index 0000000..e62bc50 --- /dev/null +++ b/languages/ruby/infisical-sdk/lib/clients/auth.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +require 'json' +require_relative '../extended_schemas/schemas' + +module InfisicalSDK + # Manage Infisical secrets. + class AuthClient + def initialize(command_runner) + @command_runner = command_runner + end + + def universal_auth(client_id:, client_secret:) + response = run_command(universal_auth_login: UniversalAuthMethod.new( + client_id: client_id, + client_secret: client_secret + )) + + handle_auth_response(response) + end + + def kubernetes_auth(identity_id:, service_account_token_path:) + + response = run_command(kubernetes_auth_login: KubernetesAuthMethod.new( + identity_id: identity_id, + service_account_token_path: service_account_token_path + )) + + handle_auth_response(response) + end + + def azure_auth(identity_id:) + + response = run_command(azure_auth_login: AzureAuthMethod.new( + identity_id: identity_id + )) + + handle_auth_response(response) + end + + def gcp_id_token_auth(identity_id:) + + response = run_command(gcp_id_token_auth_login: GCPIDTokenAuthMethod.new( + identity_id: identity_id + )) + + handle_auth_response(response) + end + + def gcp_iam_auth(identity_id:, service_account_key_file_path:) + + response = run_command(gcp_iam_auth_login: GCPIamAuthMethod.new( + identity_id: identity_id, + service_account_key_file_path: service_account_key_file_path + )) + + handle_auth_response(response) + end + + def aws_iam_auth(identity_id:) + + response = run_command(aws_iam_auth_login: AWSIamAuthMethod.new( + identity_id: identity_id + )) + + handle_auth_response(response) + end + + private + + def error_handler(response) + + # If the response is successful, we return without raising errors. + if response.key?('success') && response['success'] == true && response.key?('data') + return + end + + if response['errorMessage'] + raise InfisicalError, response['errorMessage'] if response.key?('errorMessage') + else + raise InfisicalError, 'Error while getting response' + end + end + + def handle_auth_response(response) + auth_response = ResponseForAccessTokenSuccessResponse.from_json!(response).to_dynamic + error_handler(auth_response) + + auth_response['data'] + end + + def run_command(command) + response = @command_runner.run(InfisicalCommands.new(command)) + raise InfisicalError, 'Error getting response' if response.nil? + + response + end + end +end + diff --git a/languages/ruby/infisical-sdk/lib/clients/encryption.rb b/languages/ruby/infisical-sdk/lib/clients/encryption.rb new file mode 100644 index 0000000..50b33e5 --- /dev/null +++ b/languages/ruby/infisical-sdk/lib/clients/encryption.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +# rubocop:disable Metrics/MethodLength +# rubocop:disable Naming/MethodParameterName + + + +require 'json' +require_relative '../extended_schemas/schemas' + + +module InfisicalSDK + # Perform encryption + class EncryptionClient + def initialize(command_runner) + @command_runner = command_runner + end + + def encrypt_symmetric(data:, key:) + response = run_command(encrypt_symmetric: EncryptSymmetricOptions.new( + key: key, + plaintext: data, + )) + + encrypt_response = ResponseForEncryptSymmetricResponse.from_json!(response).to_dynamic + error_handler(encrypt_response) + + + encrypt_response['data'] + end + + def decrypt_symmetric( + ciphertext:, + iv:, + tag:, + key: + ) + + response = run_command(decrypt_symmetric: DecryptSymmetricOptions.new( + ciphertext: ciphertext, + iv: iv, + tag: tag, + key: key + )) + + decrypt_response = ResponseForDecryptSymmetricResponse.from_json!(response).to_dynamic + error_handler(decrypt_response) + + decrypt_response['data']['decrypted'] + end + + def create_symmetric_key + response = run_command(create_symmetric_key: ArbitraryOptions.new( + data: '' + )) + + key_response = ResponseForCreateSymmetricKeyResponse.from_json!(response).to_dynamic + error_handler(key_response) + + key_response['data']['key'] + end + + private + + def error_handler(response) + + # If the response is successful, we return without raising errors. + if response.key?('success') && response['success'] == true && response.key?('data') + return + end + + if response['errorMessage'] + raise InfisicalError, response['errorMessage'] if response.key?('errorMessage') + else + raise InfisicalError, 'Error while getting response' + end + end + + def run_command(command) + response = @command_runner.run(InfisicalCommands.new(command)) + raise InfisicalError, 'Error getting response' if response.nil? + + response + end + end +end + +# rubocop:enable Metrics/MethodLength +# rubocop:enable Naming/MethodParameterName + diff --git a/languages/ruby/infisical-sdk/lib/clients/secrets.rb b/languages/ruby/infisical-sdk/lib/clients/secrets.rb new file mode 100644 index 0000000..a07a565 --- /dev/null +++ b/languages/ruby/infisical-sdk/lib/clients/secrets.rb @@ -0,0 +1,169 @@ +# frozen_string_literal: true + +require 'json' + +require_relative '../extended_schemas/schemas' + +module InfisicalSDK + # Manage Infisical secrets. + class SecretsClient + def initialize(command_runner) + @command_runner = command_runner + end + + # rubocop:disable Metrics/ParameterLists + # rubocop:disable Metrics/MethodLength + def get( + secret_name:, + project_id:, + environment:, + path: nil, + include_imports: nil, + type: nil + ) + + response = run_command(get_secret: GetSecretOptions.new( + secret_name: secret_name, + project_id: project_id, + environment: environment, + path: path, + include_imports: include_imports, + get_secret_options_type: type + )) + + secrets_response = ResponseForGetSecretResponse.from_json!(response).to_dynamic + error_handler(secrets_response) + + + secrets_response['data']['secret'] + end + + def list( + project_id:, + environment:, + path: nil , + attach_to_process_env: nil, + expand_secret_references: nil, + recursive: nil, + include_imports: nil + ) + response = run_command(list_secrets: ListSecretsOptions.new( + project_id: project_id, + environment: environment, + path: path, + include_imports: include_imports, + recursive: recursive, + attach_to_process_env: attach_to_process_env, + expand_secret_references: expand_secret_references, + )) + + secrets_response = ResponseForListSecretsResponse.from_json!(response).to_dynamic + error_handler(secrets_response) + + secrets_response['data']['secrets'] + end + + def update( + secret_name:, + secret_value:, + project_id:, + environment:, + path: nil, + skip_multiline_encoding: nil, + type: nil + ) + response = run_command(update_secret: UpdateSecretOptions.new( + secret_name: secret_name, + secret_value: secret_value, + project_id: project_id, + environment: environment, + path: path, + skip_multiline_encoding: skip_multiline_encoding, + update_secret_options_type: type + )) + + secrets_response = ResponseForUpdateSecretResponse.from_json!(response).to_dynamic + error_handler(secrets_response) + + + secrets_response['data']['secret'] + end + + def create( + secret_name:, + secret_value:, + project_id:, + environment:, + secret_comment: nil, + path: nil, + skip_multiline_encoding: nil, + type: nil + + ) + + response = run_command(create_secret: CreateSecretOptions.new( + secret_name: secret_name, + secret_value: secret_value, + secret_comment: secret_comment, + project_id: project_id, + environment: environment, + skip_multiline_encoding: skip_multiline_encoding, + path: path, + create_secret_options_type: type + )) + + secrets_response = ResponseForCreateSecretResponse.from_json!(response).to_dynamic + error_handler(secrets_response) + + + secrets_response['data']['secret'] + end + + def delete( + secret_name:, + project_id:, + environment:, + path: nil, + type: nil + ) + response = run_command(delete_secret: DeleteSecretOptions.new( + secret_name: secret_name, + project_id: project_id, + environment: environment, + path: path, + delete_secret_options_type: type + )) + + secrets_response = ResponseForDeleteSecretResponse.from_json!(response).to_dynamic + error_handler(secrets_response) + + secrets_response['data']['secret'] + end + + private + + def error_handler(response) + + # If the response is successful, we return without raising errors. + if response.key?('success') && response['success'] == true && response.key?('data') + return + end + + if response['errorMessage'] + raise InfisicalError, response['errorMessage'] if response.key?('errorMessage') + else + raise InfisicalError, 'Error while getting response' + end + end + + def run_command(command) + response = @command_runner.run(InfisicalCommands.new(command)) + raise InfisicalError, 'Error getting response' if response.nil? + + response + end + end +end + +# rubocop:enable Metrics/ParameterLists +# rubocop:enable Metrics/MethodLength diff --git a/languages/ruby/infisical-sdk/lib/command_runner.rb b/languages/ruby/infisical-sdk/lib/command_runner.rb new file mode 100644 index 0000000..3f43a32 --- /dev/null +++ b/languages/ruby/infisical-sdk/lib/command_runner.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module InfisicalSDK + # Run base SDK commands + class CommandRunner + def initialize(infisical_sdk, handle) + @infisical_sdk = infisical_sdk + @handle = handle + end + + # @param [Dry-Struct] cmd + def run(cmd) + @infisical_sdk.run_command(cmd.to_json, @handle) + end + end +end diff --git a/languages/ruby/infisical-sdk/lib/infisical-sdk.rb b/languages/ruby/infisical-sdk/lib/infisical-sdk.rb new file mode 100644 index 0000000..bd08f3c --- /dev/null +++ b/languages/ruby/infisical-sdk/lib/infisical-sdk.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'json' +require 'dry-types' + +require_relative 'schemas' +require_relative 'extended_schemas/schemas' +require_relative 'infisical_lib' +require_relative 'infisical_error' +require_relative 'command_runner' +require_relative 'clients/secrets' +require_relative 'clients/auth' +require_relative 'clients/encryption' + +module InfisicalSDK + class InfisicalClient + attr_reader :infisical, :command_runner, :secrets, :auth, :encryption + + def initialize(site_url = nil, cache_ttl = 300) + settings = ClientSettings.new( + # We preset these values or we'll get type validation errors (thanks Quicktype!) + access_token: nil, + client_secret: nil, + client_id: nil, + auth: nil, + user_agent: 'infisical-ruby-sdk', + cache_ttl: cache_ttl, + site_url: site_url + ) + + + @infisical = InfisicalLib + @handle = @infisical.init(settings.to_dynamic.compact.to_json) + @command_runner = CommandRunner.new(@infisical, @handle) + @secrets = SecretsClient.new(@command_runner) + @auth = AuthClient.new(@command_runner) + @encryption = EncryptionClient.new(@command_runner) + end + + def free_mem + @infisical.free_mem(@handle) + end + end +end diff --git a/languages/ruby/infisical-sdk/lib/infisical_error.rb b/languages/ruby/infisical-sdk/lib/infisical_error.rb new file mode 100644 index 0000000..9b1e7cb --- /dev/null +++ b/languages/ruby/infisical-sdk/lib/infisical_error.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module InfisicalSDK + # Infisical Error + class InfisicalError < StandardError + def initialize(message = 'Failed to get get response') + super(message) + end + end +end \ No newline at end of file diff --git a/languages/ruby/infisical-sdk/lib/infisical_lib.rb b/languages/ruby/infisical-sdk/lib/infisical_lib.rb new file mode 100644 index 0000000..321c49b --- /dev/null +++ b/languages/ruby/infisical-sdk/lib/infisical_lib.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'ffi' + +module InfisicalSDK + # C wrapper for Ruby SDK + module InfisicalLib + extend FFI::Library + + def self.mac_with_intel? + `uname -m`.strip == 'x86_64' + end + + def self.linux_arm64? + RUBY_PLATFORM.include?('linux') && %w[aarch64 arm64].any? { |arch| RUBY_PLATFORM.include?(arch) } + end + + ffi_lib case RUBY_PLATFORM + when /darwin/ + local_file = if mac_with_intel? + File.expand_path('macos-x64/libinfisical_c.dylib', __dir__) + else + File.expand_path('macos-arm64/libinfisical_c.dylib', __dir__) + end + File.exist?(local_file) ? local_file : File.expand_path('../../../../target/debug/libinfisical_c.dylib', __dir__) + when /linux/ + local_file = if linux_arm64? + File.expand_path('linux-arm64/libinfisical_c.so', __dir__) + else + File.expand_path('linux-x64/libinfisical_c.so', __dir__) + end + File.exist?(local_file) ? local_file : File.expand_path('../../../../target/debug/libinfisical_c.so', __dir__) + when /mswin|mingw/ + local_file = File.expand_path('windows-x64/infisical_c.dll', __dir__) + File.exist?(local_file) ? local_file : File.expand_path('../../../../target/debug/infisical_c.dll', __dir__) + else + raise "Unsupported platform: #{RUBY_PLATFORM}" + end + + attach_function :init, [:string], :pointer + attach_function :run_command, %i[string pointer], :string + attach_function :free_mem, [:pointer], :void + end +end \ No newline at end of file diff --git a/languages/ruby/infisical-sdk/lib/version.rb b/languages/ruby/infisical-sdk/lib/version.rb new file mode 100644 index 0000000..8d71b48 --- /dev/null +++ b/languages/ruby/infisical-sdk/lib/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module InfisicalSDK + VERSION = '0.0.1' +end diff --git a/languages/ruby/infisical-sdk/sig/infisical_sdk/auth_client.rbs b/languages/ruby/infisical-sdk/sig/infisical_sdk/auth_client.rbs new file mode 100644 index 0000000..7080dfd --- /dev/null +++ b/languages/ruby/infisical-sdk/sig/infisical_sdk/auth_client.rbs @@ -0,0 +1,20 @@ +module InfisicalSDK + class AuthClient + @command_runner: untyped + + def initialize: (CommandRunner) -> void + + def universal_auth: (client_id: String, client_secret: String) -> MachineIdentityLogin + def kubernetes_auth: (identity_id: String, service_account_token_path: String?) -> MachineIdentityLogin + def azure_auth: (identity_id: String) -> MachineIdentityLogin + def gcp_id_token_auth: (identity_id: String) -> MachineIdentityLogin + def gcp_iam_auth: (identity_id: String, service_account_key_file_path: String) -> MachineIdentityLogin + def aws_iam_auth: (identity_id: String) -> MachineIdentityLogin + + private + + def error_handler: (untyped) -> void + def handle_auth_response: (untyped) -> MachineIdentityLogin + def run_command: (untyped) -> untyped + end +end diff --git a/languages/ruby/infisical-sdk/sig/infisical_sdk/command_runner.rbs b/languages/ruby/infisical-sdk/sig/infisical_sdk/command_runner.rbs new file mode 100644 index 0000000..3e14722 --- /dev/null +++ b/languages/ruby/infisical-sdk/sig/infisical_sdk/command_runner.rbs @@ -0,0 +1,10 @@ +module InfisicalSDK + class CommandRunner + @handle: untyped + @infisical_sdk: InfisicalModule + + + def initialize: (untyped, untyped) -> String + def run: (untyped) -> String + end +end diff --git a/languages/ruby/infisical-sdk/sig/infisical_sdk/encryption_client.rbs b/languages/ruby/infisical-sdk/sig/infisical_sdk/encryption_client.rbs new file mode 100644 index 0000000..8a10592 --- /dev/null +++ b/languages/ruby/infisical-sdk/sig/infisical_sdk/encryption_client.rbs @@ -0,0 +1,15 @@ +module InfisicalSDK + class EncryptionClient + @command_runner: CommandRunner + def initialize: (CommandRunner) -> void + + def create_symmetric_key: -> String + def decrypt_symmetric: (ciphertext: String, iv: String, tag: String, key: String) -> String + def encrypt_symmetric: (data: String, key: String) -> EncryptedData + + private + def error_handler: (untyped) -> void + def run_command: (untyped) -> untyped + + end +end diff --git a/languages/ruby/infisical-sdk/sig/infisical_sdk/infisical_client.rbs b/languages/ruby/infisical-sdk/sig/infisical_sdk/infisical_client.rbs new file mode 100644 index 0000000..cd06569 --- /dev/null +++ b/languages/ruby/infisical-sdk/sig/infisical_sdk/infisical_client.rbs @@ -0,0 +1,30 @@ +module InfisicalSDK + class InfisicalClient + @handle: untyped + @infisical: InfisicalModule + @auth: AuthClient + @secrets: SecretsClient + @command_runner: CommandRunner + + attr_reader command_runner: CommandRunner + attr_reader infisical: InfisicalModule + + attr_reader auth: AuthClient + attr_reader encryption: EncryptionClient + attr_reader secrets: SecretsClient + + def initialize: (String?, Integer?) -> untyped + + def free_mem: -> void + end +end + +module InfisicalSDK + class InfisicalModule < Module + + def free_mem: (untyped) -> void + def run_command: (untyped, untyped) -> String + def init: (untyped) -> untyped + + end +end \ No newline at end of file diff --git a/languages/ruby/infisical-sdk/sig/infisical_sdk/infisical_lib.rbs b/languages/ruby/infisical-sdk/sig/infisical_sdk/infisical_lib.rbs new file mode 100644 index 0000000..1e582e4 --- /dev/null +++ b/languages/ruby/infisical-sdk/sig/infisical_sdk/infisical_lib.rbs @@ -0,0 +1,7 @@ +module InfisicalSDK + module InfisicalLib + + def self.linux_arm64?: -> bool + def self.mac_with_intel?: -> bool + end +end diff --git a/languages/ruby/infisical-sdk/sig/infisical_sdk/sdk.rbs b/languages/ruby/infisical-sdk/sig/infisical_sdk/sdk.rbs new file mode 100644 index 0000000..48f1e16 --- /dev/null +++ b/languages/ruby/infisical-sdk/sig/infisical_sdk/sdk.rbs @@ -0,0 +1,3 @@ +module InfisicalSDK + VERSION: String +end \ No newline at end of file diff --git a/languages/ruby/infisical-sdk/sig/infisical_sdk/secrets_client.rbs b/languages/ruby/infisical-sdk/sig/infisical_sdk/secrets_client.rbs new file mode 100644 index 0000000..e7f282d --- /dev/null +++ b/languages/ruby/infisical-sdk/sig/infisical_sdk/secrets_client.rbs @@ -0,0 +1,55 @@ +module InfisicalSDK + class SecretsClient + @command_runner: CommandRunner + def initialize: (CommandRunner) -> void + + def get: ( + secret_name: String, + project_id: String, + environment: String, + path: String?, + include_imports: bool?, + type: String? + ) -> InfisicalSecret + + def list: ( + project_id: String, + environment: String, + path: String?, + attach_to_process_env: bool?, + expand_secret_references: bool?, + recursive: bool?, + include_imports: bool?, + ) -> Array[InfisicalSecret] + def update: ( + secret_name: String, + secret_value: String, + project_id: String, + environment: String, + path: String?, + skip_multiline_encoding: bool?, + type: String? + ) -> InfisicalSecret + def create: ( + secret_name: String, + secret_value: String, + project_id: String, + environment: String, + secret_comment: String?, + skip_multiline_encoding: bool?, + type: String?, + path: String? + ) -> InfisicalSecret + def delete: ( + secret_name: String, + project_id: String, + environment: String, + path: String?, + type: String?, + ) -> InfisicalSecret + + private + def error_handler: (untyped) -> void + def run_command: (untyped) -> untyped + end +end diff --git a/languages/ruby/infisical-sdk/sig/infisical_sdk/structs.rbs b/languages/ruby/infisical-sdk/sig/infisical_sdk/structs.rbs new file mode 100644 index 0000000..7ba0b8a --- /dev/null +++ b/languages/ruby/infisical-sdk/sig/infisical_sdk/structs.rbs @@ -0,0 +1,37 @@ + +class InfisicalSecret + attr_reader environment: String? + attr_reader isFallback: bool? + attr_reader secretComment: String + attr_reader secretKey: String + attr_reader secretPath: String? + attr_reader secretValue: String + attr_reader type: String + attr_reader version: Integer + attr_reader workspace: String + + def initialize: ( + environment: String, + secretComment: String, + secretKey: String, + secretValue: String, + type: String, + version: Integer, + workspace: String, + ?isFallback: bool?, + ?secretPath: String? + ) -> void +end + +class MachineIdentityLogin + attr_reader accessToken: String + attr_reader accessTokenMaxTTL: Integer + attr_reader expiresIn: Integer + attr_reader tokenType: String +end + +class EncryptedData + attr_reader ciphertext: String + attr_reader iv: String + attr_reader tag: String +end \ No newline at end of file diff --git a/support/scripts/schemas.ts b/support/scripts/schemas.ts index d11e8ce..ae41242 100644 --- a/support/scripts/schemas.ts +++ b/support/scripts/schemas.ts @@ -4,87 +4,94 @@ import fs, { ensureDir } from "fs-extra"; import path from "path"; async function* walk(dir: string): AsyncIterable { - for await (const d of await fs.promises.opendir(dir)) { - const entry = path.join(dir, d.name); - if (d.isDirectory()) { - yield* walk(entry); - } else if (d.isFile()) { - yield entry; - } - } + for await (const d of await fs.promises.opendir(dir)) { + const entry = path.join(dir, d.name); + if (d.isDirectory()) { + yield* walk(entry); + } else if (d.isFile()) { + yield entry; + } + } } async function main() { - const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore()); + const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore()); - const filenames: string[] = []; - for await (const p of walk("./support/schemas")) { - filenames.push(p); - } + const filenames: string[] = []; + for await (const p of walk("./support/schemas")) { + filenames.push(p); + } - filenames.sort(); + filenames.sort(); - for (const f of filenames) { - const buffer = fs.readFileSync(f); - const relative = path.relative(path.join(process.cwd(), "support/schemas"), f); - await schemaInput.addSource({ name: relative, schema: buffer.toString() }); - } + for (const f of filenames) { + const buffer = fs.readFileSync(f); + const relative = path.relative(path.join(process.cwd(), "support/schemas"), f); + await schemaInput.addSource({ name: relative, schema: buffer.toString() }); + } - const inputData = new InputData(); - inputData.addInput(schemaInput); + const inputData = new InputData(); + inputData.addInput(schemaInput); - const ts = await quicktype({ - inputData, - lang: "typescript", - rendererOptions: {} - }); - await ensureDir("./languages/node/src/infisical_client"); - writeToFile("./languages/node/src/infisical_client/schemas.ts", ts.lines); + const ts = await quicktype({ + inputData, + lang: "typescript", + rendererOptions: {} + }); + await ensureDir("./languages/node/src/infisical_client"); + writeToFile("./languages/node/src/infisical_client/schemas.ts", ts.lines); - const python = await quicktype({ - inputData, - lang: "python", - rendererOptions: { - "python-version": "3.7" - } - }); - await ensureDir("./crates/infisical-py/infisical_client"); - writeToFile("./crates/infisical-py/infisical_client/schemas.py", python.lines); + const python = await quicktype({ + inputData, + lang: "python", + rendererOptions: { + "python-version": "3.7" + } + }); + await ensureDir("./crates/infisical-py/infisical_client"); + writeToFile("./crates/infisical-py/infisical_client/schemas.py", python.lines); - - const csharp = await quicktype({ - inputData, - lang: "csharp", - rendererOptions: { - namespace: "Infisical.Sdk", - framework: "SystemTextJson", - "csharp-version": "6" - } - }); - await ensureDir("./languages/csharp/Infisical.Sdk"); - writeToFile("./languages/csharp/Infisical.Sdk/schemas.cs", csharp.lines); - + const csharp = await quicktype({ + inputData, + lang: "csharp", + rendererOptions: { + namespace: "Infisical.Sdk", + framework: "SystemTextJson", + "csharp-version": "6" + } + }); + await ensureDir("./languages/csharp/Infisical.Sdk"); + writeToFile("./languages/csharp/Infisical.Sdk/schemas.cs", csharp.lines); - const java = await quicktypeMultiFile({ - inputData, - lang: "java", - rendererOptions: { - package: "com.infisical.sdk.schema", - "java-version": "8" - } - }); + const ruby = await quicktype({ + inputData, + lang: "ruby", + rendererOptions: { + "ruby-version": "3.0" + } + }); + writeToFile("./languages/ruby/infisical-sdk/lib/schemas.rb", ruby.lines); // { regexp: /d = Types::Hash\[d\]/g, replace: "d = Types::Hash[d].transform_keys(&:to_s)" } - const javaDir = "./languages/java/src/main/java/com/infisical/sdk/schema/"; - await ensureDir(javaDir); + const java = await quicktypeMultiFile({ + inputData, + lang: "java", + rendererOptions: { + package: "com.infisical.sdk.schema", + "java-version": "8" + } + }); - java.forEach((file, path) => writeToFile(javaDir + path, file.lines)); + const javaDir = "./languages/java/src/main/java/com/infisical/sdk/schema/"; + await ensureDir(javaDir); + + java.forEach((file, path) => writeToFile(javaDir + path, file.lines)); } main(); function writeToFile(filename: string, lines: string[]) { - const output = fs.createWriteStream(filename); - lines.forEach(line => { - output.write(line + "\n"); - }); - output.close(); + const output = fs.createWriteStream(filename); + lines.forEach(line => { + output.write(line + "\n"); + }); + output.close(); }