Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f63ee8265 | ||
|
6cba3d6d0b
|
|||
|
b2a51dc1b1
|
|||
|
|
cc44fca54e | ||
|
9a678ae67d
|
|||
|
66b950991c
|
|||
| e8e0bd02e9 | |||
|
ed5a7308be
|
|||
|
044d5960eb
|
@@ -0,0 +1,11 @@
|
||||
### AI assistance (if any):
|
||||
- List tools here and files touched by them
|
||||
|
||||
### Authorship & Understanding
|
||||
|
||||
- [ ] I wrote or heavily modified this code myself
|
||||
- [ ] I understand how it works end-to-end
|
||||
- [ ] I can maintain this code in the future
|
||||
- [ ] No undisclosed AI-generated code was used
|
||||
- [ ] If AI assistance was used, it is documented below
|
||||
|
||||
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## v0.4.1 (2026-03-20)
|
||||
|
||||
### Feat
|
||||
|
||||
- Upgraded aws-lc-sys version to address high severity CWE-295
|
||||
|
||||
## v0.4.0 (2026-03-09)
|
||||
|
||||
### Feat
|
||||
|
||||
- Added 1password support
|
||||
- sort local keys alphabetically when listing them
|
||||
|
||||
## v0.3.0 (2026-02-02)
|
||||
|
||||
### Fix
|
||||
|
||||
+9
-1
@@ -48,7 +48,8 @@ cz commit
|
||||
1. Clone this repo
|
||||
2. Run `cargo test` to set up hooks
|
||||
3. Make changes
|
||||
4. Run the application using `make run` or `cargo run`
|
||||
4. Run the application using `just run` or `just run`
|
||||
- Install `just` (`cargo install just`) if you haven't already to use the [justfile](./justfile) in this project.
|
||||
5. Commit changes. This will trigger pre-commit hooks that will run format, test and lint. If there are errors or
|
||||
warnings from Clippy, please fix them.
|
||||
6. Push your code to a new branch named after the feature/bug/etc. you're adding. This will trigger pre-push hooks that
|
||||
@@ -75,6 +76,13 @@ Then, you can run workflows locally without having to commit and see if the GitH
|
||||
act -W .github/workflows/release.yml --input_type bump=minor
|
||||
```
|
||||
|
||||
## Authorship Policy
|
||||
|
||||
All code in this repository is written and reviewed by humans. AI-generated code (e.g., Copilot, ChatGPT,
|
||||
Claude, etc.) is not permitted unless explicitly disclosed and approved.
|
||||
|
||||
Submissions must certify that the contributor understands and can maintain the code they submit.
|
||||
|
||||
## Questions? Reach out to me!
|
||||
If you encounter any questions while developing G-Man, please don't hesitate to reach out to me at
|
||||
alex.j.tusa@gmail.com. I'm happy to help contributors in any way I can, regardless of if they're new or experienced!
|
||||
|
||||
Generated
+457
-319
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "gman"
|
||||
version = "0.3.0"
|
||||
version = "0.4.1"
|
||||
edition = "2024"
|
||||
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
||||
description = "Universal command line secret management and injection tool"
|
||||
@@ -58,14 +58,14 @@ tokio = { version = "1.47.1", features = ["full"] }
|
||||
aws-config = { version = "1.8.12", features = ["behavior-version-latest"] }
|
||||
async-trait = "0.1.89"
|
||||
futures = "0.3.31"
|
||||
gcloud-sdk = { version = "0.28.1", features = [
|
||||
gcloud-sdk = { version = "0.28.5", features = [
|
||||
"google-cloud-secretmanager-v1",
|
||||
] }
|
||||
crc32c = "0.6.8"
|
||||
azure_core = "0.31.0"
|
||||
azure_identity = "0.31.0"
|
||||
azure_security_keyvault_secrets = "0.10.0"
|
||||
aws-lc-sys = { version = "0.37.0", features = ["bindgen"] }
|
||||
aws-lc-sys = { version = "0.39.0", features = ["bindgen"] }
|
||||
which = "8.0.0"
|
||||
once_cell = "1.21.3"
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
#!make
|
||||
default: run
|
||||
|
||||
.PHONY: test test-cov build run lint lint-fix fmt minimal-versions analyze release delete-tag
|
||||
|
||||
test:
|
||||
@cargo test --all
|
||||
|
||||
## Run all tests with coverage - `cargo install cargo-tarpaulin`
|
||||
test-cov:
|
||||
@cargo tarpaulin
|
||||
|
||||
build: test
|
||||
@cargo build --release
|
||||
|
||||
run:
|
||||
@CARGO_INCREMENTAL=1 cargo fmt && make lint && cargo run
|
||||
|
||||
lint:
|
||||
@find . | grep '\.\/src\/.*\.rs$$' | xargs touch && CARGO_INCREMENTAL=0 cargo clippy --all-targets --workspace
|
||||
|
||||
lint-fix:
|
||||
@cargo fix
|
||||
|
||||
fmt:
|
||||
@cargo fmt
|
||||
|
||||
minimal-versions:
|
||||
@cargo +nightly update -Zdirect-minimal-versions
|
||||
|
||||
## Analyze for unsafe usage - `cargo install cargo-geiger`
|
||||
analyze:
|
||||
@cargo geiger
|
||||
|
||||
release:
|
||||
@git tag -a ${V} -m "Release ${V}" && git push origin ${V}
|
||||
|
||||
delete-tag:
|
||||
@git tag -d ${V} && git push --delete origin ${V}
|
||||
|
||||
@@ -14,8 +14,8 @@ files or sprinkling environment variables everywhere.
|
||||
|
||||
## Overview
|
||||
|
||||
`gman` acts as a universal wrapper for any command that needs credentials. Store your secrets—API tokens, passwords,
|
||||
certs—with a provider, then either fetch them directly or run your command through `gman` to inject what it needs as
|
||||
`gman` acts as a universal wrapper for any command that needs credentials. Store your secrets (e.g. API tokens, passwords,
|
||||
certs, etc.) with a provider, then either fetch them directly or run your command through `gman` to inject what it needs as
|
||||
environment variables, flags, or file content.
|
||||
|
||||
## Quick Examples: Before vs After
|
||||
@@ -96,6 +96,7 @@ gman aws sts get-caller-identity
|
||||
- [GCP Secret Manager](#provider-gcp_secret_manager)
|
||||
- [Azure Key Vault](#provider-azure_key_vault)
|
||||
- [Gopass](#provider-gopass)
|
||||
- [1Password](#provider-one_password)
|
||||
- [Run Configurations](#run-configurations)
|
||||
- [Specifying a Default Provider per Run Config](#specifying-a-default-provider-per-run-config)
|
||||
- [Environment Variable Secret Injection](#environment-variable-secret-injection)
|
||||
@@ -287,7 +288,7 @@ documented and added without breaking existing setups. The following table shows
|
||||
| [`azure_key_vault`](https://azure.microsoft.com/en-us/products/key-vault/) | ✅ | [Azure Key Vault](#provider-azure_key_vault) | |
|
||||
| [`gcp_secret_manager`](https://cloud.google.com/security/products/secret-manager?hl=en) | ✅ | [GCP Secret Manager](#provider-gcp_secret_manager) | |
|
||||
| [`gopass`](https://www.gopass.pw/) | ✅ | | |
|
||||
| [`1password`](https://1password.com/) | 🕒 | | |
|
||||
| [`1password`](https://1password.com/) | ✅ | [1Password](#provider-one_password) | |
|
||||
| [`bitwarden`](https://bitwarden.com/) | 🕒 | | |
|
||||
| [`dashlane`](https://www.dashlane.com/) | 🕒 | | Waiting for CLI support for adding secrets |
|
||||
| [`lastpass`](https://www.lastpass.com/) | 🕒 | | |
|
||||
@@ -450,6 +451,42 @@ Important notes:
|
||||
- Secrets are managed using gopass's native commands; `gman` acts as a wrapper to interface with gopass.
|
||||
- Updates overwrite existing secrets
|
||||
- If no store is specified, the default gopass store is used and `gman sync` will sync with all configured stores.
|
||||
|
||||
### Provider: `one_password`
|
||||
The `one_password` provider uses the [1Password CLI (`op`)](https://developer.1password.com/docs/cli/) as the backing
|
||||
storage location for secrets.
|
||||
|
||||
- Optional: `vault` (string) to specify which 1Password vault to use. If omitted, the default vault is used.
|
||||
- Optional: `account` (string) to specify which 1Password account to use. Useful if you have multiple accounts. If
|
||||
omitted, the default signed-in account is used.
|
||||
|
||||
Configuration example:
|
||||
|
||||
```yaml
|
||||
default_provider: op
|
||||
providers:
|
||||
- name: op
|
||||
type: one_password
|
||||
vault: Production # Optional; if omitted, uses the default vault
|
||||
account: my.1password.com # Optional; if omitted, uses the default account
|
||||
```
|
||||
|
||||
Authentication:
|
||||
- **Interactive**: Run `op signin` to sign in interactively.
|
||||
- **Service Account**: Set the `OP_SERVICE_ACCOUNT_TOKEN` environment variable for non-interactive/CI usage.
|
||||
- **Desktop App Integration**: If the 1Password desktop app is installed and configured, the CLI can use biometric
|
||||
authentication (Touch ID, Windows Hello, etc.).
|
||||
|
||||
Important notes:
|
||||
- Ensure the 1Password CLI (`op`) is installed on your system. Install instructions are at
|
||||
https://developer.1password.com/docs/cli/get-started/.
|
||||
- Secrets are stored as 1Password Password items. The item title is the secret name and the `password` field holds the
|
||||
secret value.
|
||||
- **Deletions are permanent. Deleted items are not archived.**
|
||||
- `add` creates a new Password item. If an item with the same title already exists in the vault, `op` will create a
|
||||
duplicate. Use `update` to change an existing secret value.
|
||||
- `list` returns the titles of all items in the configured vault.
|
||||
|
||||
## Run Configurations
|
||||
|
||||
Run configurations (or "profiles") tell `gman` how to inject secrets into a command. Three modes of secret injection are
|
||||
@@ -657,7 +694,7 @@ gman managarr
|
||||
|
||||
### Multiple Providers and Switching
|
||||
|
||||
You can define multiple providers—even multiple of the same type—and switch between them per command.
|
||||
You can define multiple providers (even multiple of the same type) and switch between them per command.
|
||||
|
||||
Example: two AWS Secrets Manager providers named `lab` and `prod`.
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# List all recipes
|
||||
default:
|
||||
@just --list
|
||||
|
||||
# Format all files
|
||||
[group: 'style']
|
||||
fmt:
|
||||
@cargo fmt --all
|
||||
|
||||
alias clippy := lint
|
||||
# Run Clippy to inspect all files
|
||||
[group: 'style']
|
||||
lint:
|
||||
@cargo clippy --all
|
||||
|
||||
alias clippy-fix := lint-fix
|
||||
# Automatically fix clippy issues where possible
|
||||
[group: 'style']
|
||||
lint-fix:
|
||||
@cargo fix
|
||||
|
||||
# Run all tests
|
||||
[group: 'test']
|
||||
test:
|
||||
@cargo test --all
|
||||
|
||||
# Build and run the binary for the current system
|
||||
run:
|
||||
@cargo run
|
||||
|
||||
# Build the project for the current system architecture
|
||||
[group: 'build']
|
||||
[arg('build_type', pattern="debug|release")]
|
||||
build build_type='debug':
|
||||
@cargo build {{ if build_type == "release" { "--release" } else { "" } }}
|
||||
@@ -169,6 +169,10 @@ impl ProviderConfig {
|
||||
debug!("Using Gopass provider");
|
||||
provider_def
|
||||
}
|
||||
SupportedProvider::OnePassword { provider_def } => {
|
||||
debug!("Using 1Password provider");
|
||||
provider_def
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +158,8 @@ impl SecretProvider for LocalProvider {
|
||||
async fn list_secrets(&self) -> Result<Vec<String>> {
|
||||
let vault_path = self.active_vault_path()?;
|
||||
let vault: HashMap<String, String> = load_vault(&vault_path).unwrap_or_default();
|
||||
let keys: Vec<String> = vault.keys().cloned().collect();
|
||||
let mut keys: Vec<String> = vault.keys().cloned().collect();
|
||||
keys.sort();
|
||||
|
||||
Ok(keys)
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ pub mod gcp_secret_manager;
|
||||
mod git_sync;
|
||||
pub mod gopass;
|
||||
pub mod local;
|
||||
pub mod one_password;
|
||||
|
||||
use crate::providers::gopass::GopassProvider;
|
||||
use crate::providers::local::LocalProvider;
|
||||
use crate::providers::one_password::OnePasswordProvider;
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use aws_secrets_manager::AwsSecretsManagerProvider;
|
||||
use azure_key_vault::AzureKeyVaultProvider;
|
||||
@@ -76,6 +78,10 @@ pub enum SupportedProvider {
|
||||
#[serde(flatten)]
|
||||
provider_def: GopassProvider,
|
||||
},
|
||||
OnePassword {
|
||||
#[serde(flatten)]
|
||||
provider_def: OnePasswordProvider,
|
||||
},
|
||||
}
|
||||
|
||||
impl Validate for SupportedProvider {
|
||||
@@ -86,6 +92,7 @@ impl Validate for SupportedProvider {
|
||||
SupportedProvider::GcpSecretManager { provider_def } => provider_def.validate(),
|
||||
SupportedProvider::AzureKeyVault { provider_def } => provider_def.validate(),
|
||||
SupportedProvider::Gopass { provider_def } => provider_def.validate(),
|
||||
SupportedProvider::OnePassword { provider_def } => provider_def.validate(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,6 +113,7 @@ impl Display for SupportedProvider {
|
||||
SupportedProvider::GcpSecretManager { .. } => write!(f, "gcp_secret_manager"),
|
||||
SupportedProvider::AzureKeyVault { .. } => write!(f, "azure_key_vault"),
|
||||
SupportedProvider::Gopass { .. } => write!(f, "gopass"),
|
||||
SupportedProvider::OnePassword { .. } => write!(f, "one_password"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
use crate::providers::{ENV_PATH, SecretProvider};
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
use std::io::Read;
|
||||
use std::process::{Command, Stdio};
|
||||
use validator::Validate;
|
||||
|
||||
#[skip_serializing_none]
|
||||
/// 1Password-based secret provider.
|
||||
/// See [1Password CLI](https://developer.1password.com/docs/cli/) for more
|
||||
/// information.
|
||||
///
|
||||
/// You must already have the 1Password CLI (`op`) installed and configured
|
||||
/// on your system.
|
||||
///
|
||||
/// This provider stores secrets as 1Password Password items. It requires
|
||||
/// an optional vault name and an optional account identifier to be specified.
|
||||
/// If no vault is specified, the user's default vault is used. If no account
|
||||
/// is specified, the default signed-in account is used.
|
||||
///
|
||||
/// Example
|
||||
/// ```no_run
|
||||
/// use gman::providers::one_password::OnePasswordProvider;
|
||||
/// use gman::providers::{SecretProvider, SupportedProvider};
|
||||
/// use gman::config::Config;
|
||||
///
|
||||
/// let provider = OnePasswordProvider::default();
|
||||
/// let _ = provider.set_secret("MY_SECRET", "value");
|
||||
/// ```
|
||||
#[derive(Debug, Default, Clone, Validate, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct OnePasswordProvider {
|
||||
pub vault: Option<String>,
|
||||
pub account: Option<String>,
|
||||
}
|
||||
|
||||
impl OnePasswordProvider {
|
||||
fn base_command(&self) -> Command {
|
||||
let mut cmd = Command::new("op");
|
||||
cmd.env("PATH", ENV_PATH.as_ref().expect("No ENV_PATH set"));
|
||||
if let Some(account) = &self.account {
|
||||
cmd.args(["--account", account]);
|
||||
}
|
||||
cmd
|
||||
}
|
||||
|
||||
fn vault_args(&self) -> Vec<&str> {
|
||||
match &self.vault {
|
||||
Some(vault) => vec!["--vault", vault],
|
||||
None => vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SecretProvider for OnePasswordProvider {
|
||||
fn name(&self) -> &'static str {
|
||||
"OnePasswordProvider"
|
||||
}
|
||||
|
||||
async fn get_secret(&self, key: &str) -> Result<String> {
|
||||
ensure_op_installed()?;
|
||||
|
||||
let mut cmd = self.base_command();
|
||||
cmd.args(["item", "get", key, "--fields", "password", "--reveal"]);
|
||||
cmd.args(self.vault_args());
|
||||
cmd.stdin(Stdio::inherit())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::inherit());
|
||||
|
||||
let mut child = cmd.spawn().context("Failed to spawn op command")?;
|
||||
|
||||
let mut output = String::new();
|
||||
child
|
||||
.stdout
|
||||
.as_mut()
|
||||
.expect("Failed to open op stdout")
|
||||
.read_to_string(&mut output)
|
||||
.context("Failed to read op output")?;
|
||||
|
||||
let status = child.wait().context("Failed to wait on op process")?;
|
||||
if !status.success() {
|
||||
return Err(anyhow!("op command failed with status: {}", status));
|
||||
}
|
||||
|
||||
Ok(output.trim_end_matches(&['\r', '\n'][..]).to_string())
|
||||
}
|
||||
|
||||
async fn set_secret(&self, key: &str, value: &str) -> Result<()> {
|
||||
ensure_op_installed()?;
|
||||
|
||||
let mut cmd = self.base_command();
|
||||
cmd.args(["item", "create", "--category", "password", "--title", key]);
|
||||
cmd.args(self.vault_args());
|
||||
cmd.arg(format!("password={}", value));
|
||||
cmd.stdin(Stdio::inherit())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::inherit());
|
||||
|
||||
let mut child = cmd.spawn().context("Failed to spawn op command")?;
|
||||
|
||||
let status = child.wait().context("Failed to wait on op process")?;
|
||||
if !status.success() {
|
||||
return Err(anyhow!("op command failed with status: {}", status));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_secret(&self, key: &str, value: &str) -> Result<()> {
|
||||
ensure_op_installed()?;
|
||||
|
||||
let mut cmd = self.base_command();
|
||||
cmd.args(["item", "edit", key]);
|
||||
cmd.args(self.vault_args());
|
||||
cmd.arg(format!("password={}", value));
|
||||
cmd.stdin(Stdio::inherit())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::inherit());
|
||||
|
||||
let mut child = cmd.spawn().context("Failed to spawn op command")?;
|
||||
|
||||
let status = child.wait().context("Failed to wait on op process")?;
|
||||
if !status.success() {
|
||||
return Err(anyhow!("op command failed with status: {}", status));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_secret(&self, key: &str) -> Result<()> {
|
||||
ensure_op_installed()?;
|
||||
|
||||
let mut cmd = self.base_command();
|
||||
cmd.args(["item", "delete", key]);
|
||||
cmd.args(self.vault_args());
|
||||
cmd.stdin(Stdio::inherit())
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit());
|
||||
|
||||
let mut child = cmd.spawn().context("Failed to spawn op command")?;
|
||||
|
||||
let status = child.wait().context("Failed to wait on op process")?;
|
||||
if !status.success() {
|
||||
return Err(anyhow!("op command failed with status: {}", status));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn list_secrets(&self) -> Result<Vec<String>> {
|
||||
ensure_op_installed()?;
|
||||
|
||||
let mut cmd = self.base_command();
|
||||
cmd.args(["item", "list", "--format", "json"]);
|
||||
cmd.args(self.vault_args());
|
||||
cmd.stdin(Stdio::inherit())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::inherit());
|
||||
|
||||
let mut child = cmd.spawn().context("Failed to spawn op command")?;
|
||||
|
||||
let mut output = String::new();
|
||||
child
|
||||
.stdout
|
||||
.as_mut()
|
||||
.expect("Failed to open op stdout")
|
||||
.read_to_string(&mut output)
|
||||
.context("Failed to read op output")?;
|
||||
|
||||
let status = child.wait().context("Failed to wait on op process")?;
|
||||
if !status.success() {
|
||||
return Err(anyhow!("op command failed with status: {}", status));
|
||||
}
|
||||
|
||||
let items: Vec<serde_json::Value> =
|
||||
serde_json::from_str(&output).context("Failed to parse op item list JSON output")?;
|
||||
|
||||
let secrets: Vec<String> = items
|
||||
.iter()
|
||||
.filter_map(|item| item.get("title").and_then(|t| t.as_str()))
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
|
||||
Ok(secrets)
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_op_installed() -> Result<()> {
|
||||
if which::which("op").is_err() {
|
||||
Err(anyhow!(
|
||||
"1Password CLI (op) is not installed or not found in PATH. \
|
||||
Please install it from https://developer.1password.com/docs/cli/get-started/"
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,5 @@ mod azure_key_vault_tests;
|
||||
mod gcp_secret_manager_tests;
|
||||
mod gopass_tests;
|
||||
mod local_tests;
|
||||
mod one_password_tests;
|
||||
mod provider_tests;
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
use gman::config::{Config, ProviderConfig};
|
||||
use gman::providers::{SecretProvider, SupportedProvider};
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use validator::Validate;
|
||||
|
||||
#[test]
|
||||
fn test_one_password_supported_provider_display_and_validate_from_yaml() {
|
||||
let yaml = r#"---
|
||||
type: one_password
|
||||
vault: Production
|
||||
account: my.1password.com
|
||||
"#;
|
||||
|
||||
let sp: SupportedProvider = serde_yaml::from_str(yaml).expect("valid supported provider yaml");
|
||||
assert!(sp.validate().is_ok());
|
||||
assert_eq!(sp.to_string(), "one_password");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_one_password_supported_provider_minimal_yaml() {
|
||||
let yaml = r#"---
|
||||
type: one_password
|
||||
"#;
|
||||
|
||||
let sp: SupportedProvider = serde_yaml::from_str(yaml).expect("valid supported provider yaml");
|
||||
assert!(sp.validate().is_ok());
|
||||
assert_eq!(sp.to_string(), "one_password");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_one_password_supported_provider_vault_only() {
|
||||
let yaml = r#"---
|
||||
type: one_password
|
||||
vault: Personal
|
||||
"#;
|
||||
|
||||
let sp: SupportedProvider = serde_yaml::from_str(yaml).expect("valid supported provider yaml");
|
||||
assert!(sp.validate().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_one_password_supported_provider_account_only() {
|
||||
let yaml = r#"---
|
||||
type: one_password
|
||||
account: team.1password.com
|
||||
"#;
|
||||
|
||||
let sp: SupportedProvider = serde_yaml::from_str(yaml).expect("valid supported provider yaml");
|
||||
assert!(sp.validate().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_one_password_supported_provider_rejects_unknown_fields() {
|
||||
let yaml = r#"---
|
||||
type: one_password
|
||||
vault: Production
|
||||
unknown_field: bad
|
||||
"#;
|
||||
|
||||
let result: Result<SupportedProvider, _> = serde_yaml::from_str(yaml);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_provider_config_with_one_password_deserialize_and_extract() {
|
||||
let yaml = r#"---
|
||||
name: op
|
||||
type: one_password
|
||||
"#;
|
||||
|
||||
let pc: ProviderConfig = serde_yaml::from_str(yaml).expect("valid provider config yaml");
|
||||
assert!(pc.validate().is_ok());
|
||||
|
||||
let mut pc_owned = pc.clone();
|
||||
let provider: &mut dyn SecretProvider = pc_owned.extract_provider();
|
||||
assert_str_eq!(provider.name(), "OnePasswordProvider");
|
||||
|
||||
let cfg_yaml = r#"---
|
||||
default_provider: op
|
||||
providers:
|
||||
- name: op
|
||||
type: one_password
|
||||
vault: Production
|
||||
account: my.1password.com
|
||||
"#;
|
||||
let cfg: Config = serde_yaml::from_str(cfg_yaml).expect("valid config yaml");
|
||||
assert!(cfg.validate().is_ok());
|
||||
|
||||
let extracted = cfg
|
||||
.extract_provider_config(None)
|
||||
.expect("should find default provider");
|
||||
assert_eq!(extracted.name.as_deref(), Some("op"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_one_password_config_with_multiple_providers() {
|
||||
let cfg_yaml = r#"---
|
||||
default_provider: local
|
||||
providers:
|
||||
- name: local
|
||||
type: local
|
||||
- name: op
|
||||
type: one_password
|
||||
vault: Production
|
||||
"#;
|
||||
let cfg: Config = serde_yaml::from_str(cfg_yaml).expect("valid config yaml");
|
||||
assert!(cfg.validate().is_ok());
|
||||
|
||||
let extracted = cfg
|
||||
.extract_provider_config(Some("op".into()))
|
||||
.expect("should find op provider");
|
||||
assert_eq!(extracted.name.as_deref(), Some("op"));
|
||||
}
|
||||
Reference in New Issue
Block a user