mirror of
https://github.com/bartvdbraak/keyweave.git
synced 2026-09-14 00:36:37 +00:00
Add --set flag to update Key Vault from .env file
Adds a boolean --set flag for updating Key Vault values from .env, implements the corresponding logic, and documents required setup and Access Policies.
This commit is contained in:
parent
9a47a2ae4f
commit
e2891fbf85
2 changed files with 82 additions and 3 deletions
10
README.md
10
README.md
|
|
@ -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).
|
||||||
|
|
|
||||||
75
src/main.rs
75
src/main.rs
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue