use crate::zerokms::{
key_provider::{KeyProvider, KeyProviderError},
vitur_client::{connection::HttpConnectionOpts, DEFAULT_CONCURRENT_REQS, DEFAULT_KEYS_PER_REQ},
ClientKey, ZeroKMS, ZeroKMSClientOpts, ZeroKMSWithClientKey,
};
use stack_auth::AuthStrategy;
use thiserror::Error;
use url::Url;
#[derive(Debug, Error, miette::Diagnostic)]
pub enum ZeroKMSBuilderError {
#[error("Failed to initialize client: {0}")]
ClientInit(#[from] crate::zerokms::Error),
#[error("Auth strategy error: {0}")]
Auth(#[from] stack_auth::AuthError),
#[error("Key provider error: {0}")]
KeyProvider(#[from] KeyProviderError),
}
pub struct ZeroKMSBuilder<C, ClientKeyState = ()> {
credentials: C,
request_timeout: Option<u64>,
max_keys_per_req: usize,
max_concurrent_reqs: usize,
client_key: ClientKeyState,
base_url_override: Option<Url>,
}
impl ZeroKMSBuilder<stack_auth::AutoStrategy, ()> {
pub fn auto() -> Result<Self, ZeroKMSBuilderError> {
let strategy = stack_auth::AutoStrategy::detect()?;
Ok(Self {
credentials: strategy,
request_timeout: None,
max_keys_per_req: DEFAULT_KEYS_PER_REQ,
max_concurrent_reqs: DEFAULT_CONCURRENT_REQS,
client_key: (),
base_url_override: None,
})
}
}
impl<C> ZeroKMSBuilder<C, ()>
where
C: Send + Sync + 'static,
for<'a> &'a C: AuthStrategy,
{
pub fn new(credentials: C) -> Self {
Self {
credentials,
request_timeout: None,
max_keys_per_req: DEFAULT_KEYS_PER_REQ,
max_concurrent_reqs: DEFAULT_CONCURRENT_REQS,
client_key: (),
base_url_override: None,
}
}
pub fn with_request_timeout(mut self, timeout_secs: u64) -> Self {
self.request_timeout = Some(timeout_secs);
self
}
pub fn with_max_keys_per_req(mut self, max_keys: usize) -> Self {
self.max_keys_per_req = max_keys;
self
}
pub fn with_max_concurrent_reqs(mut self, max_concurrent: usize) -> Self {
self.max_concurrent_reqs = max_concurrent;
self
}
pub fn with_base_url(mut self, base_url: Url) -> Self {
self.base_url_override = Some(base_url);
self
}
pub fn with_key_provider<K: KeyProvider>(
self,
provider: K,
) -> ZeroKMSBuilder<C, WithKeyProvider<K>> {
ZeroKMSBuilder {
credentials: self.credentials,
request_timeout: self.request_timeout,
max_keys_per_req: self.max_keys_per_req,
max_concurrent_reqs: self.max_concurrent_reqs,
client_key: WithKeyProvider(provider),
base_url_override: self.base_url_override,
}
}
pub fn with_client_key(self, client_key: ClientKey) -> ZeroKMSBuilder<C, ClientKey> {
ZeroKMSBuilder {
credentials: self.credentials,
request_timeout: self.request_timeout,
max_keys_per_req: self.max_keys_per_req,
max_concurrent_reqs: self.max_concurrent_reqs,
client_key,
base_url_override: self.base_url_override,
}
}
pub fn build(self) -> Result<ZeroKMS<C>, ZeroKMSBuilderError> {
let (opts, _) = self.build_opts();
Ok(ZeroKMS::connect(opts)?)
}
}
impl<C, S> ZeroKMSBuilder<C, S> {
fn build_opts(self) -> (ZeroKMSClientOpts<C>, S) {
let base_url = self.base_url_override.or_else(Self::base_url_from_env);
let mut connection_opts = HttpConnectionOpts::new(base_url);
if let Some(timeout) = self.request_timeout {
connection_opts = connection_opts.with_request_timeout(timeout);
}
let opts = ZeroKMSClientOpts {
credentials: self.credentials,
connection_opts,
max_keys_per_req: self.max_keys_per_req,
max_concurrent_reqs: self.max_concurrent_reqs,
};
(opts, self.client_key)
}
fn base_url_from_env() -> Option<Url> {
use crate::config::vars::CS_ZEROKMS_HOST;
for name in CS_ZEROKMS_HOST {
if let Ok(value) = std::env::var(name) {
match value.parse() {
Ok(url) => return Some(url),
Err(err) => {
tracing::warn!(
target: "zerokms",
%err,
env_var = name,
"Ignoring invalid URL in environment variable"
);
}
}
}
}
None
}
}
impl<C> ZeroKMSBuilder<C, ClientKey>
where
C: Send + Sync + 'static,
for<'a> &'a C: AuthStrategy,
{
pub fn build(self) -> Result<ZeroKMSWithClientKey<C>, ZeroKMSBuilderError> {
let (opts, client_key) = self.build_opts();
Ok(ZeroKMS::connect_with_client_key(opts, client_key)?)
}
}
pub struct WithKeyProvider<K: KeyProvider>(K);
impl<C, K> ZeroKMSBuilder<C, WithKeyProvider<K>>
where
C: Send + Sync + 'static,
for<'a> &'a C: AuthStrategy,
K: KeyProvider,
{
pub async fn build(self) -> Result<ZeroKMSWithClientKey<C>, ZeroKMSBuilderError> {
let (opts, provider) = self.build_opts();
let client_key = provider.0.client_key().await?;
Ok(ZeroKMS::connect_with_client_key(opts, client_key)?)
}
}