hasp_core/lib.rs
1//! Core contracts for the hasp secrets library.
2//!
3//! This crate defines the `Backend` trait, the error taxonomy,
4//! and the `Entry` type shared by every backend implementation.
5//! It intentionally has no profile, TTY, or config dependencies —
6//! those live in `hasp-cli`.
7
8pub mod audit;
9pub mod cache;
10pub mod error;
11pub mod field;
12pub mod hardening;
13pub mod proxy;
14pub mod retry;
15pub mod secret_mem;
16
17#[cfg(any(test, feature = "test-utils"))]
18pub mod test_utils;
19
20#[cfg(unix)]
21pub use audit::SyslogSink;
22pub use audit::{AuditEvent, AuditSink, CacheEvent, FileSink, NoopSink, StderrSink, Verb};
23pub use cache::{CacheKey, CachePolicy, ProcessCache};
24pub use error::{BackendFailureKind, Error};
25pub use field::{extract_field, extract_field_from_str};
26#[cfg(feature = "memory-lock")]
27pub use hardening::lock_secret_pages;
28pub use hardening::{
29 apply_mitigations, check_refusal_conditions, harden_process, install, HardenRefusal,
30 HardeningToken, MitigationOutcome,
31};
32pub use proxy::{is_no_proxy, resolve_proxy_from_env, ProxyConfig};
33pub use retry::RetryBackend;
34pub use secrecy::{ExposeSecret, SecretString};
35pub use subtle;
36
37use url::Url;
38
39/// Unified backend trait for secret stores.
40///
41/// Each backend owns its URL grammar and maps native errors into
42/// `hasp_core::Error`. Secrets are wrapped in `SecretString` at the
43/// earliest possible boundary — before returning from `get`.
44///
45/// Implementors must ensure that secret values never appear in
46/// `Debug` output or error messages.
47pub trait Backend: Send + Sync {
48 /// Returns the URL scheme this backend handles (e.g., `"env"`).
49 fn scheme(&self) -> &'static str;
50
51 /// Fetch the secret at the given URL.
52 ///
53 /// # Errors
54 ///
55 /// Returns `Error::NotFound` if the secret does not exist.
56 /// Returns `Error::Backend { kind: Transient, .. }` for retryable
57 /// platform failures.
58 fn get(&self, url: &Url) -> Result<SecretString, Error>;
59
60 /// Store a secret at the given URL.
61 ///
62 /// # Errors
63 ///
64 /// Returns `Error::UnsupportedOperation` if the backend is
65 /// read-only (e.g., `env://`).
66 fn put(&self, url: &Url, value: &SecretString) -> Result<(), Error>;
67
68 /// List entries matching the URL prefix or pattern.
69 ///
70 /// # Errors
71 ///
72 /// Returns `Error::UnsupportedOperation` if listing is not
73 /// supported by the backend.
74 fn list(&self, url: &Url) -> Result<Vec<Entry>, Error>;
75
76 /// Delete the secret at the given URL.
77 ///
78 /// # Errors
79 ///
80 /// Returns `Error::UnsupportedOperation` if deletion is not
81 /// supported by the backend.
82 fn delete(&self, url: &Url) -> Result<(), Error>;
83
84 /// Returns `true` if a secret exists at the given URL.
85 fn exists(&self, url: &Url) -> Result<bool, Error>;
86
87 /// Validate URL grammar without performing I/O.
88 ///
89 /// Backends override by delegating to their existing URL `TryFrom`.
90 /// Used by `Store::resolve` so `--explain` rejects the same URLs
91 /// `get` would — keeps the dry-run path honest about what an actual
92 /// operation would do. Default impl is a no-op for backends that
93 /// have no grammar to validate beyond the scheme.
94 fn validate(&self, _url: &Url) -> Result<(), Error> {
95 Ok(())
96 }
97}
98
99/// A named entry returned by `Backend::list`.
100///
101/// `name` is the human-readable identifier; `url` is the canonical
102/// address that can be passed back to `Store::get`.
103#[derive(Debug, Clone)]
104pub struct Entry {
105 pub name: String,
106 pub url: Url,
107}
108
109/// Extract the scheme prefix from a URL string.
110///
111/// Returns the substring before `://`, or an error if the separator
112/// is absent. This is the only central URL knowledge in `hasp-core`;
113/// all grammar validation lives in backend crates.
114pub fn scheme_from_url(url: &str) -> Result<&str, Error> {
115 url.split_once("://")
116 .map(|(scheme, _)| scheme)
117 .ok_or_else(|| Error::InvalidUrl("missing scheme separator".into()))
118}