use std::borrow::Cow;
use std::collections::HashMap;
use std::collections::HashSet;
use super::Address;
use super::ProviderCredentials;
use crate::MonosecretError;
use crate::Result;
use crate::SecretBytes;
use crate::config::NativeAddress;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct DiscoveryContext<'a> {
pub project: &'a str,
pub profile: &'a str,
}
impl<'a> DiscoveryContext<'a> {
pub const fn new(project: &'a str, profile: &'a str) -> Self {
Self { project, profile }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProducedValuePersistence {
Persist,
Ephemeral,
}
#[derive(Clone)]
pub struct ProviderValue {
pub value: SecretBytes,
pub expires_at_unix_ms: Option<u64>,
pub revision: Option<monosecret_rpc::Revision>,
}
impl ProviderValue {
pub fn new(value: SecretBytes, expires_at_unix_ms: Option<u64>) -> Self {
Self {
value,
expires_at_unix_ms,
revision: None,
}
}
#[must_use]
pub fn with_revision(mut self, revision: Option<monosecret_rpc::Revision>) -> Self {
self.revision = revision;
self
}
}
pub trait Provider: Send + Sync {
fn convention_address(&self, project: &str, profile: &str, key: &str) -> Result<NativeAddress>;
fn supported_coords(&self) -> &'static [&'static str] {
&[]
}
fn supports_coord(&self, name: &str) -> bool {
self.supported_coords().contains(&name)
}
fn resolve_coords<'a>(&self, addr: Address<'a>) -> Result<Cow<'a, NativeAddress>> {
let coords = match addr {
Address::Native(native) => Cow::Borrowed(native),
Address::Convention {
project,
profile,
key,
} => Cow::Owned(self.convention_address(project, profile, key)?),
};
for (name, value) in coords.coordinates() {
if name == "item" || value.is_none() {
continue;
}
if !self.supports_coord(name) {
return Err(MonosecretError::ProviderOperationFailed(format!(
"the {} provider does not support the `{name}` coordinate. \
Drop `{name}` from the ref for `{item}`, or give this provider its \
own address with `refs.<alias>` or an alias `ref` template (0.19+): \
https://monosecret.dev/concepts/references/#different-coordinates-per-provider-019",
self.name(),
item = coords.item
)));
}
}
Ok(coords)
}
fn configured_entry_coordinates<'a>(
&self,
addr: Address<'a>,
) -> Result<Cow<'a, NativeAddress>> {
self.resolve_coords(addr)
}
fn entry_coordinates<'a>(&self, addr: Address<'a>) -> Result<Cow<'a, NativeAddress>> {
self.configured_entry_coordinates(addr)
}
fn entry_coordinates_many(&self, addrs: &[Address<'_>]) -> Result<Vec<NativeAddress>> {
addrs
.iter()
.map(|addr| Ok(self.entry_coordinates(*addr)?.into_owned()))
.collect()
}
fn get(&self, addr: Address<'_>) -> Result<Option<SecretBytes>>;
fn get_with_metadata(&self, addr: Address<'_>) -> Result<Option<ProviderValue>> {
self.get(addr)
.map(|value| value.map(|value| ProviderValue::new(value, None)))
}
fn supports_read(&self) -> bool {
true
}
fn exists(&self, addr: Address<'_>) -> Result<bool> {
Ok(self.get(addr)?.is_some())
}
fn configure_dependency_secrets(&self, _dependencies: &[(String, SecretBytes)]) -> Result<()> {
Ok(())
}
fn set(&self, addr: Address<'_>, value: &SecretBytes) -> Result<()>;
fn set_expiring(
&self,
addr: Address<'_>,
value: &SecretBytes,
max_age: std::time::Duration,
) -> Result<()> {
let _ = max_age;
self.set(addr, value)
}
fn delete(&self, addr: Address<'_>) -> Result<bool> {
let _ = addr;
Err(self.deletion_unsupported())
}
fn supports_delete(&self) -> bool {
false
}
fn deletion_unsupported(&self) -> MonosecretError {
MonosecretError::ProviderOperationFailed(format!(
"provider '{}' does not support deleting secrets",
self.name()
))
}
fn check_deletable(&self, addr: Address<'_>) -> Result<()> {
if !self.supports_delete() {
return Err(self.deletion_unsupported());
}
self.resolve_coords(addr).map(|_| ())
}
fn check_writable(&self, addr: Address<'_>) -> Result<()> {
let _ = addr;
Ok(())
}
fn generated_value_persistence(&self) -> ProducedValuePersistence {
ProducedValuePersistence::Persist
}
fn prompted_value_persistence(&self) -> ProducedValuePersistence {
ProducedValuePersistence::Persist
}
fn describe_write_target(&self, addr: Address<'_>) -> Result<String> {
Ok(self.resolve_coords(addr)?.render())
}
fn auth_scope_key(&self) -> Option<String> {
None
}
fn name(&self) -> &str;
fn uri(&self) -> String;
fn storage_identity(&self) -> String {
self.uri()
}
fn entry_container_identity(&self) -> String {
self.storage_identity()
}
fn same_entry(&self, other: &dyn Provider, addr: Address<'_>) -> Result<bool> {
self.same_entries(addr, other, addr)
}
fn same_entries(
&self,
self_addr: Address<'_>,
other: &dyn Provider,
other_addr: Address<'_>,
) -> Result<bool> {
if !same_storage_container(self, other) {
return Ok(false);
}
Ok(self.entry_coordinates(self_addr)? == other.entry_coordinates(other_addr)?)
}
fn physical_store_path(&self) -> Option<&std::path::Path> {
None
}
fn configured_physical_store_path(&self) -> Option<&std::path::Path> {
self.physical_store_path()
}
fn set_reason(&self, _reason: Option<String>) {}
fn set_requested_authorization_duration(&self, _duration: Option<std::time::Duration>) {}
fn set_caller(&self, _caller: Option<crate::CallerContext>) {}
fn set_project(&self, _project: &str) {}
fn set_profile(&self, _profile: &str) {}
fn with_base_dir(&mut self, _base_dir: &std::path::Path) {}
fn with_credentials(&mut self, _credentials: ProviderCredentials) {}
fn reflect(&self, _context: DiscoveryContext<'_>) -> Result<HashMap<String, crate::Secret>> {
Err(MonosecretError::ProviderOperationFailed(format!(
"Provider '{}' does not support reflection",
self.name()
)))
}
fn get_many(&self, requests: &[(&str, Address<'_>)]) -> Result<HashMap<String, SecretBytes>> {
get_each(self, requests)
}
fn get_many_with_metadata(
&self,
requests: &[(&str, Address<'_>)],
) -> Result<HashMap<String, ProviderValue>> {
self.get_many(requests).map(|values| {
values
.into_iter()
.map(|(name, value)| (name, ProviderValue::new(value, None)))
.collect()
})
}
fn exists_many(&self, requests: &[(&str, Address<'_>)]) -> Result<HashSet<String>> {
if self.supports_read() {
return self
.get_many(requests)
.map(|values| values.into_keys().collect());
}
exists_each(self, requests)
}
}
fn comparable_missing_file_path(path: &std::path::Path) -> std::path::PathBuf {
let absolute = std::path::absolute(path).unwrap_or_else(|_| path.to_path_buf());
let Some(parent) = absolute.parent() else {
return absolute;
};
let Some(file_name) = absolute.file_name() else {
return absolute;
};
std::fs::canonicalize(parent)
.map(|parent| parent.join(file_name))
.unwrap_or(absolute)
}
pub(crate) fn same_storage_container<L, R>(left: &L, right: &R) -> bool
where
L: Provider + ?Sized,
R: Provider + ?Sized,
{
same_container_at(
left,
left.physical_store_path(),
right,
right.physical_store_path(),
)
}
fn same_configured_storage_container(left: &dyn Provider, right: &dyn Provider) -> bool {
same_container_at(
left,
left.configured_physical_store_path(),
right,
right.configured_physical_store_path(),
)
}
fn same_container_at<L, R>(
left: &L,
left_path: Option<&std::path::Path>,
right: &R,
right_path: Option<&std::path::Path>,
) -> bool
where
L: Provider + ?Sized,
R: Provider + ?Sized,
{
match (left_path, right_path) {
(Some(left), Some(right)) => {
same_file::is_same_file(left, right).unwrap_or_else(|_| {
let left = comparable_missing_file_path(left);
let right = comparable_missing_file_path(right);
left == right
})
}
(None, None) => left.entry_container_identity() == right.entry_container_identity(),
_ => false,
}
}
pub(crate) fn same_configured_entries(
left: &dyn Provider,
left_addr: Address<'_>,
right: &dyn Provider,
right_addr: Address<'_>,
) -> Result<bool> {
if !same_configured_storage_container(left, right) {
return Ok(false);
}
Ok(left.configured_entry_coordinates(left_addr)?
== right.configured_entry_coordinates(right_addr)?)
}
const DEFAULT_GET_EACH_CONCURRENCY: usize = 8;
pub(crate) const GET_EACH_CONCURRENCY_ENV: &str = "MONOSECRET_PROVIDER_CONCURRENCY";
pub(crate) fn get_each_concurrency() -> usize {
std::env::var(GET_EACH_CONCURRENCY_ENV)
.ok()
.and_then(|value| value.parse::<usize>().ok())
.filter(|&n| n >= 1)
.unwrap_or(DEFAULT_GET_EACH_CONCURRENCY)
}
pub(crate) fn map_concurrently<T, R, F>(items: &[T], concurrency: usize, map: F) -> Vec<R>
where
T: Sync,
R: Send,
F: Fn(&T) -> R + Sync,
{
let concurrency = concurrency.max(1);
if items.len() <= 1 || concurrency == 1 {
return items.iter().map(map).collect();
}
let mut mapped = Vec::with_capacity(items.len());
for chunk in items.chunks(concurrency) {
std::thread::scope(|scope| {
let handles: Vec<_> = chunk.iter().map(|item| scope.spawn(|| map(item))).collect();
mapped.extend(
handles
.into_iter()
.map(|handle| handle.join().expect("concurrent map thread panicked")),
);
});
}
mapped
}
pub(crate) fn get_each<P: Provider + ?Sized>(
provider: &P,
requests: &[(&str, Address<'_>)],
) -> Result<HashMap<String, SecretBytes>> {
get_each_with(requests, |addr| provider.get(addr))
}
pub(crate) fn exists_each<P: Provider + ?Sized>(
provider: &P,
requests: &[(&str, Address<'_>)],
) -> Result<HashSet<String>> {
let mut groups: HashMap<Address<'_>, Vec<&str>> = HashMap::new();
for (name, addr) in requests {
groups.entry(*addr).or_default().push(name);
}
let groups: Vec<(Address<'_>, Vec<&str>)> = groups.into_iter().collect();
let checked: Vec<(Vec<&str>, Result<bool>)> =
map_concurrently(&groups, get_each_concurrency(), |(addr, names)| {
(names.clone(), provider.exists(*addr))
});
let mut present = HashSet::new();
for (names, result) in checked {
if result? {
present.extend(names.into_iter().map(str::to_string));
}
}
Ok(present)
}
pub(crate) fn get_each_with<'a, F, T>(
requests: &[(&str, Address<'a>)],
fetch: F,
) -> Result<HashMap<String, T>>
where
F: Fn(Address<'a>) -> Result<Option<T>> + Sync,
T: Clone + Send,
{
let mut groups: HashMap<Address<'_>, Vec<&str>> = HashMap::new();
for (name, addr) in requests {
groups.entry(*addr).or_default().push(name);
}
let groups: Vec<(Address<'_>, Vec<&str>)> = groups.into_iter().collect();
let fetched: Vec<(Vec<&str>, Result<Option<T>>)> =
map_concurrently(&groups, get_each_concurrency(), |(addr, names)| {
(names.clone(), fetch(*addr))
});
let mut results = HashMap::new();
for (names, result) in fetched {
if let Some(value) = result? {
for name in names {
results.insert(name.to_string(), value.clone());
}
}
}
Ok(results)
}
impl<T: Provider> Provider for std::sync::Arc<T> {
fn convention_address(&self, project: &str, profile: &str, key: &str) -> Result<NativeAddress> {
(**self).convention_address(project, profile, key)
}
fn supported_coords(&self) -> &'static [&'static str] {
(**self).supported_coords()
}
fn supports_coord(&self, name: &str) -> bool {
(**self).supports_coord(name)
}
fn resolve_coords<'a>(&self, addr: Address<'a>) -> Result<Cow<'a, NativeAddress>> {
(**self).resolve_coords(addr)
}
fn configured_entry_coordinates<'a>(
&self,
addr: Address<'a>,
) -> Result<Cow<'a, NativeAddress>> {
(**self).configured_entry_coordinates(addr)
}
fn entry_coordinates<'a>(&self, addr: Address<'a>) -> Result<Cow<'a, NativeAddress>> {
(**self).entry_coordinates(addr)
}
fn entry_coordinates_many(&self, addrs: &[Address<'_>]) -> Result<Vec<NativeAddress>> {
(**self).entry_coordinates_many(addrs)
}
fn get(&self, addr: Address<'_>) -> Result<Option<SecretBytes>> {
(**self).get(addr)
}
fn get_with_metadata(&self, addr: Address<'_>) -> Result<Option<ProviderValue>> {
(**self).get_with_metadata(addr)
}
fn supports_read(&self) -> bool {
(**self).supports_read()
}
fn exists(&self, addr: Address<'_>) -> Result<bool> {
(**self).exists(addr)
}
fn set(&self, addr: Address<'_>, value: &SecretBytes) -> Result<()> {
(**self).set(addr, value)
}
fn set_expiring(
&self,
addr: Address<'_>,
value: &SecretBytes,
max_age: std::time::Duration,
) -> Result<()> {
(**self).set_expiring(addr, value, max_age)
}
fn delete(&self, addr: Address<'_>) -> Result<bool> {
(**self).delete(addr)
}
fn supports_delete(&self) -> bool {
(**self).supports_delete()
}
fn check_deletable(&self, addr: Address<'_>) -> Result<()> {
(**self).check_deletable(addr)
}
fn check_writable(&self, addr: Address<'_>) -> Result<()> {
(**self).check_writable(addr)
}
fn generated_value_persistence(&self) -> ProducedValuePersistence {
(**self).generated_value_persistence()
}
fn prompted_value_persistence(&self) -> ProducedValuePersistence {
(**self).prompted_value_persistence()
}
fn describe_write_target(&self, addr: Address<'_>) -> Result<String> {
(**self).describe_write_target(addr)
}
fn auth_scope_key(&self) -> Option<String> {
(**self).auth_scope_key()
}
fn name(&self) -> &str {
(**self).name()
}
fn uri(&self) -> String {
(**self).uri()
}
fn same_entry(&self, other: &dyn Provider, addr: Address<'_>) -> Result<bool> {
(**self).same_entry(other, addr)
}
fn same_entries(
&self,
self_addr: Address<'_>,
other: &dyn Provider,
other_addr: Address<'_>,
) -> Result<bool> {
(**self).same_entries(self_addr, other, other_addr)
}
fn storage_identity(&self) -> String {
(**self).storage_identity()
}
fn entry_container_identity(&self) -> String {
(**self).entry_container_identity()
}
fn physical_store_path(&self) -> Option<&std::path::Path> {
(**self).physical_store_path()
}
fn configured_physical_store_path(&self) -> Option<&std::path::Path> {
(**self).configured_physical_store_path()
}
fn set_reason(&self, reason: Option<String>) {
(**self).set_reason(reason);
}
fn set_requested_authorization_duration(&self, duration: Option<std::time::Duration>) {
(**self).set_requested_authorization_duration(duration);
}
fn set_caller(&self, caller: Option<crate::CallerContext>) {
(**self).set_caller(caller);
}
fn set_project(&self, project: &str) {
(**self).set_project(project);
}
fn set_profile(&self, profile: &str) {
(**self).set_profile(profile);
}
fn configure_dependency_secrets(&self, dependencies: &[(String, SecretBytes)]) -> Result<()> {
(**self).configure_dependency_secrets(dependencies)
}
fn reflect(&self, context: DiscoveryContext<'_>) -> Result<HashMap<String, crate::Secret>> {
(**self).reflect(context)
}
fn get_many(&self, requests: &[(&str, Address<'_>)]) -> Result<HashMap<String, SecretBytes>> {
(**self).get_many(requests)
}
fn get_many_with_metadata(
&self,
requests: &[(&str, Address<'_>)],
) -> Result<HashMap<String, ProviderValue>> {
(**self).get_many_with_metadata(requests)
}
fn exists_many(&self, requests: &[(&str, Address<'_>)]) -> Result<HashSet<String>> {
(**self).exists_many(requests)
}
}