feat(notsecrets): add multi-provider secret resolution system
Some checks failed
Public Repo Readiness / Check Public Repo Readiness (push) Has been cancelled

Add a config-driven, strongly-typed secret/env resolution system to
notsecrets with 11 provider backends. The system supports explicit
key bindings with typed SecretRef enums and a priority chain fallback.

Tier 1 providers (implemented): Env, 1Password, dotenvx, SOPS, GSM
Tier 2 providers (stubbed): nuenv, direnv, mise, Bitwarden, Vault, dotenvy

New types: Provider, ProviderConfig, SecretRef, SecretsConfig,
SecretsError, SecretSource, EnumerableSecretSource, SecretResolver

Config lives in notsecrets.toml with typed serde deserialization.
ISP split: EnumerableSecretSource for backends that support bulk
enumeration, SecretSource for single-key lookup only.

Includes conformance test suite, 33 new tests (161 workspace total).
notstrap EnvInjector migration deferred to follow-up.
This commit is contained in:
2026-06-06 09:49:05 -04:00
parent 65d1a2dc2e
commit 1359def673
27 changed files with 3079 additions and 39 deletions

View File

@@ -0,0 +1,82 @@
use notsecrets::ports::{EnumerableSecretSource, SecretSource};
use notsecrets::sources::*;
fn assert_secret_source_contract(source: &dyn SecretSource) {
assert!(
!source.name().is_empty(),
"{:?}: name() must be non-empty",
source.provider()
);
// resolve for unknown key returns Ok(None)
let result = source.resolve("__CONFORMANCE_NONEXISTENT_KEY_99999__");
assert!(
result.is_ok(),
"{}: resolve(unknown) should be Ok, got {:?}",
source.name(),
result,
);
assert_eq!(
result.unwrap(),
None,
"{}: resolve(unknown) should be None",
source.name(),
);
}
fn assert_enumerable_contract(source: &dyn EnumerableSecretSource) {
assert_secret_source_contract(source);
let map = source.resolve_all();
assert!(
map.is_ok(),
"{}: resolve_all() should be Ok, got {:?}",
source.name(),
map,
);
}
#[test]
fn conformance_env_source() {
assert_enumerable_contract(&EnvSource);
}
#[test]
fn conformance_op_source() {
assert_secret_source_contract(&OpSource::new("test.1password.com".to_string()));
}
#[test]
fn conformance_gsm_source() {
assert_secret_source_contract(&GsmSource::new("test-project".to_string()));
}
#[test]
fn conformance_nuenv_stub() {
assert_secret_source_contract(&NuenvSource);
}
#[test]
fn conformance_direnv_stub() {
assert_secret_source_contract(&DirenvSource);
}
#[test]
fn conformance_mise_stub() {
assert_secret_source_contract(&MiseSource);
}
#[test]
fn conformance_vault_stub() {
assert_secret_source_contract(&VaultSource);
}
#[test]
fn conformance_dotenvy_stub() {
assert_secret_source_contract(&DotenvySource);
}
#[test]
fn conformance_bitwarden_stub() {
assert_secret_source_contract(&BitwardenSource::new("test-item"));
}