use huskarl_core::platform::{MaybeSend, MaybeSendSync};
#[cfg(all(not(target_arch = "wasm32"), feature = "native-tls"))]
use huskarl_core::secrets::SecretBytes;
#[cfg(all(
not(target_arch = "wasm32"),
any(feature = "rustls-tls", feature = "native-tls")
))]
use huskarl_core::secrets::{Secret, SecretString};
#[cfg(all(
not(target_arch = "wasm32"),
any(feature = "rustls-tls", feature = "native-tls")
))]
use snafu::Snafu;
pub struct MtlsApplyOutput {
pub builder: reqwest::ClientBuilder,
#[cfg(all(
not(target_arch = "wasm32"),
any(feature = "rustls-tls", feature = "native-tls")
))]
pub identity: Option<reqwest::Identity>,
}
pub trait MtlsProvider: MaybeSendSync {
type Error: huskarl_core::Error + 'static;
fn apply(
&self,
builder: reqwest::ClientBuilder,
) -> impl Future<Output = Result<MtlsApplyOutput, Self::Error>> + MaybeSend;
fn uses_mtls(&self) -> bool;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NoMtls;
impl MtlsProvider for NoMtls {
type Error = std::convert::Infallible;
async fn apply(&self, builder: reqwest::ClientBuilder) -> Result<MtlsApplyOutput, Self::Error> {
Ok(MtlsApplyOutput {
builder,
#[cfg(all(
not(target_arch = "wasm32"),
any(feature = "rustls-tls", feature = "native-tls")
))]
identity: None,
})
}
fn uses_mtls(&self) -> bool {
false
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "rustls-tls"))]
pub struct MtlsPem<S: Secret<Output = SecretString>> {
secret: S,
}
#[cfg(all(not(target_arch = "wasm32"), feature = "rustls-tls"))]
impl<S: Secret<Output = SecretString>> MtlsPem<S> {
pub fn new(secret: S) -> Self {
Self { secret }
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "rustls-tls"))]
#[derive(Debug, Snafu)]
pub enum MtlsPemError<E: huskarl_core::Error + 'static> {
#[snafu(display("Failed to fetch mTLS secret"))]
FetchSecret {
source: E,
},
#[snafu(display("Failed to parse mTLS identity"))]
ParseIdentity {
source: reqwest::Error,
},
}
#[cfg(all(not(target_arch = "wasm32"), feature = "rustls-tls"))]
impl<E: huskarl_core::Error + 'static> huskarl_core::Error for MtlsPemError<E> {
fn is_retryable(&self) -> bool {
match self {
Self::FetchSecret { source } => source.is_retryable(),
Self::ParseIdentity { .. } => false,
}
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "rustls-tls"))]
impl<S: Secret<Output = SecretString>> MtlsProvider for MtlsPem<S> {
type Error = MtlsPemError<S::Error>;
async fn apply(&self, builder: reqwest::ClientBuilder) -> Result<MtlsApplyOutput, Self::Error> {
use snafu::ResultExt;
let secret_output = self
.secret
.get_secret_value()
.await
.context(FetchSecretSnafu)?;
let identity = reqwest::Identity::from_pem(secret_output.value.expose_secret().as_bytes())
.context(ParseIdentitySnafu)?;
Ok(MtlsApplyOutput {
builder: builder.identity(identity.clone()),
identity: Some(identity),
})
}
fn uses_mtls(&self) -> bool {
true
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "native-tls"))]
pub struct MtlsPkcs12<D: Secret<Output = SecretBytes>, P: Secret<Output = SecretString>> {
der: D,
password: P,
}
#[cfg(all(not(target_arch = "wasm32"), feature = "native-tls"))]
impl<D: Secret<Output = SecretBytes>, P: Secret<Output = SecretString>> MtlsPkcs12<D, P> {
pub fn new(der: D, password: P) -> Self {
Self { der, password }
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "native-tls"))]
#[derive(Debug, Snafu)]
#[snafu(module)]
pub enum MtlsPkcs12Error<DE: huskarl_core::Error + 'static, PE: huskarl_core::Error + 'static> {
#[snafu(display("Failed to fetch mTLS DER secret"))]
FetchDer {
source: DE,
},
#[snafu(display("Failed to fetch mTLS password secret"))]
FetchPassword {
source: PE,
},
#[snafu(display("Failed to parse mTLS identity"))]
ParseIdentity {
source: reqwest::Error,
},
}
#[cfg(all(not(target_arch = "wasm32"), feature = "native-tls"))]
impl<DE: huskarl_core::Error + 'static, PE: huskarl_core::Error + 'static> huskarl_core::Error
for MtlsPkcs12Error<DE, PE>
{
fn is_retryable(&self) -> bool {
match self {
Self::FetchDer { source } => source.is_retryable(),
Self::FetchPassword { source } => source.is_retryable(),
Self::ParseIdentity { .. } => false,
}
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "native-tls"))]
impl<D: Secret<Output = SecretBytes>, P: Secret<Output = SecretString>> MtlsProvider
for MtlsPkcs12<D, P>
{
type Error = MtlsPkcs12Error<D::Error, P::Error>;
async fn apply(&self, builder: reqwest::ClientBuilder) -> Result<MtlsApplyOutput, Self::Error> {
use mtls_pkcs12_error::*;
use snafu::ResultExt;
let der = self.der.get_secret_value().await.context(FetchDerSnafu)?;
let password = self
.password
.get_secret_value()
.await
.context(FetchPasswordSnafu)?;
let identity = reqwest::Identity::from_pkcs12_der(
der.value.expose_secret(),
password.value.expose_secret(),
)
.context(ParseIdentitySnafu)?;
Ok(MtlsApplyOutput {
builder: builder.identity(identity.clone()),
identity: Some(identity),
})
}
fn uses_mtls(&self) -> bool {
true
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "native-tls"))]
pub struct MtlsPkcs8Pem<K: Secret<Output = SecretString>> {
cert_chain: String,
key: K,
}
#[cfg(all(not(target_arch = "wasm32"), feature = "native-tls"))]
impl<K: Secret<Output = SecretString>> MtlsPkcs8Pem<K> {
pub fn new(cert_chain: impl Into<String>, key: K) -> Self {
Self {
cert_chain: cert_chain.into(),
key,
}
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "native-tls"))]
#[derive(Debug, Snafu)]
#[snafu(module)]
pub enum MtlsPkcs8PemError<KE: huskarl_core::Error + 'static> {
#[snafu(display("Failed to fetch mTLS private key secret"))]
FetchKey {
source: KE,
},
#[snafu(display("Failed to parse mTLS identity"))]
ParseIdentity {
source: reqwest::Error,
},
}
#[cfg(all(not(target_arch = "wasm32"), feature = "native-tls"))]
impl<KE: huskarl_core::Error + 'static> huskarl_core::Error for MtlsPkcs8PemError<KE> {
fn is_retryable(&self) -> bool {
match self {
Self::FetchKey { source } => source.is_retryable(),
Self::ParseIdentity { .. } => false,
}
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "native-tls"))]
impl<K: Secret<Output = SecretString>> MtlsProvider for MtlsPkcs8Pem<K> {
type Error = MtlsPkcs8PemError<K::Error>;
async fn apply(&self, builder: reqwest::ClientBuilder) -> Result<MtlsApplyOutput, Self::Error> {
use mtls_pkcs8_pem_error::*;
use snafu::ResultExt;
let key = self.key.get_secret_value().await.context(FetchKeySnafu)?;
let identity = reqwest::Identity::from_pkcs8_pem(
self.cert_chain.as_bytes(),
key.value.expose_secret().as_bytes(),
)
.context(ParseIdentitySnafu)?;
Ok(MtlsApplyOutput {
builder: builder.identity(identity.clone()),
identity: Some(identity),
})
}
fn uses_mtls(&self) -> bool {
true
}
}