This commit is contained in:
WebBrain 2026-08-15 14:30:28 +03:00 committed by GitHub
commit 59441ab183
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 82 additions and 3 deletions

View file

@ -16,6 +16,7 @@ Keyweave is an open-source tool crafted to seamlessly fetch secrets from Azure K
- **Filtering**: Optionally filter the secrets to be retrieved by name. - **Filtering**: Optionally filter the secrets to be retrieved by name.
- **Output Customization**: Choose the name of the output file, defaulting to `.env`. - **Output Customization**: Choose the name of the output file, defaulting to `.env`.
- **Azure Default Credentials**: Utilizes Azure default credentials for authentication. - **Azure Default Credentials**: Utilizes Azure default credentials for authentication.
- **Set Secrets**: Push secrets from a `.env` file to Azure Key Vault with `--set`.
## Prerequisites ## Prerequisites
@ -69,7 +70,7 @@ cargo build --release
Once built, run Keyweave using Cargo: Once built, run Keyweave using Cargo:
```sh ```sh
cargo run -- --vault-name <VAULT_NAME> [--output <FILE>] [--filter <FILTER>] cargo run -- --vault-name <VAULT_NAME> [--output <FILE>] [--filter <FILTER>] [--set]
``` ```
## Usage ## Usage
@ -77,12 +78,13 @@ cargo run -- --vault-name <VAULT_NAME> [--output <FILE>] [--filter <FILTER>]
With the binary on your `PATH`, run Keyweave as follows: With the binary on your `PATH`, run Keyweave as follows:
```sh ```sh
keyweave --vault-name <VAULT_NAME> [--output <FILE>] [--filter <FILTER>] keyweave --vault-name <VAULT_NAME> [--output <FILE>] [--filter <FILTER>] [--set]
``` ```
- `--vault-name <VAULT_NAME>`: Sets the name of the Azure Key Vault. - `--vault-name <VAULT_NAME>`: Sets the name of the Azure Key Vault.
- `--output <FILE>`: (Optional) Sets the name of the output file (default: `.env`). - `--output <FILE>`: (Optional) Sets the name of the output file (default: `.env`).
- `--filter <FILTER>`: (Optional) Filters the secrets to be retrieved by name. - `--filter <FILTER>`: (Optional) Filters the secrets to be retrieved by name.
- `-s, --set`: (Optional) Sets secrets in the Key Vault from the output file (default: `.env`). Requires `Set` Secret Permission in the Key Vault access policy.
### Example ### Example
@ -90,6 +92,10 @@ keyweave --vault-name <VAULT_NAME> [--output <FILE>] [--filter <FILTER>]
keyweave --vault-name my-key-vault --output my-env-file.env --filter my-secret keyweave --vault-name my-key-vault --output my-env-file.env --filter my-secret
``` ```
```sh
keyweave --vault-name my-key-vault --set
```
## Documentation ## Documentation
Additional documentation for this package can be found on [docs.rs](https://docs.rs/keyweave). Additional documentation for this package can be found on [docs.rs](https://docs.rs/keyweave).

View file

@ -2,7 +2,7 @@ use anyhow::Result;
use azure_identity::{DefaultAzureCredential, TokenCredentialOptions}; use azure_identity::{DefaultAzureCredential, TokenCredentialOptions};
use azure_security_keyvault::prelude::KeyVaultGetSecretsResponse; use azure_security_keyvault::prelude::KeyVaultGetSecretsResponse;
use azure_security_keyvault::KeyvaultClient; use azure_security_keyvault::KeyvaultClient;
use clap::Parser; use clap::{ArgAction, Parser};
use futures::stream::StreamExt; use futures::stream::StreamExt;
use paris::{error, Logger}; use paris::{error, Logger};
use std::error::Error; use std::error::Error;
@ -40,6 +40,10 @@ struct Opts {
/// Filters the secrets to be retrieved by name /// Filters the secrets to be retrieved by name
#[clap(short, long, value_name = "FILTER")] #[clap(short, long, value_name = "FILTER")]
filter: Option<String>, filter: Option<String>,
/// Sets secrets in the Key Vault from the output file
#[clap(short, long, action = ArgAction::SetTrue)]
set: bool,
} }
async fn check_vault_dns(vault_name: &str) -> Result<()> { async fn check_vault_dns(vault_name: &str) -> Result<()> {
@ -199,6 +203,61 @@ fn create_env_file(secrets: Vec<(String, String)>, output_file: &str) -> Result<
Ok(()) Ok(())
} }
async fn set_secrets_from_env_file(client: &KeyvaultClient, input_file: &str) -> Result<u32> {
let content = match std::fs::read_to_string(input_file) {
Ok(c) => c,
Err(err) => {
error!("Failed to read input file: {}", err);
return Err(CustomError {
message: "An error occurred while reading the .env file".to_string(),
}
.into());
}
};
let mut set_count = 0u32;
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let (key, value) = match line.split_once('=') {
Some((k, v)) => (k.trim(), v.trim()),
None => {
error!("Invalid line in input file: {}", line);
return Err(CustomError {
message: "An error occurred while parsing the .env file".to_string(),
}
.into());
}
};
if key.is_empty() {
continue;
}
match client.secret_client().set(key, value).await {
Ok(_) => {
set_count += 1;
}
Err(err) => {
error!(
"Failed to set secret: {}. Make sure you have Set permissions on the Key Vault.",
key
);
error!("Error: {}", err);
return Err(CustomError {
message: "An error occurred while setting secrets".to_string(),
}
.into());
}
}
}
Ok(set_count)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -254,6 +313,20 @@ async fn main() -> Result<()> {
check_vault_dns(&opts.vault_name).await?; check_vault_dns(&opts.vault_name).await?;
if opts.set {
log.loading(format!(
"Setting secrets from file: <blue>{}</>",
opts.output
));
let count = set_secrets_from_env_file(&client, &opts.output).await?;
log.success(format!(
"Set {} secret(s) in Key Vault: <blue>{}</>",
count, opts.vault_name
));
log.success("Done.");
return Ok(());
}
log.loading(format!( log.loading(format!(
"Fetching secrets from Key Vault: <blue>{}</>", "Fetching secrets from Key Vault: <blue>{}</>",
opts.vault_name opts.vault_name