use std::convert::TryFrom;
use std::path::PathBuf;
use std::fmt::{Debug, self};
use anyhow::{Result, Context};
use tracing::info;
use serde::{Deserialize, Serialize};
use fluvio_future::net::{DomainConnector, DefaultDomainConnector};
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(tag = "tls_policy")]
pub enum TlsPolicy {
#[serde(rename = "disabled", alias = "disable")]
Disabled,
#[serde(rename = "anonymous")]
Anonymous,
#[serde(rename = "verified", alias = "verify")]
Verified(TlsConfig),
}
impl Default for TlsPolicy {
fn default() -> Self {
Self::Disabled
}
}
impl From<TlsConfig> for TlsPolicy {
fn from(tls: TlsConfig) -> Self {
Self::Verified(tls)
}
}
impl From<TlsCerts> for TlsPolicy {
fn from(certs: TlsCerts) -> Self {
Self::Verified(certs.into())
}
}
impl From<TlsPaths> for TlsPolicy {
fn from(paths: TlsPaths) -> Self {
Self::Verified(paths.into())
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(tag = "tls_source", content = "certs")]
pub enum TlsConfig {
#[serde(rename = "inline")]
Inline(TlsCerts),
#[serde(rename = "files", alias = "file")]
Files(TlsPaths),
}
impl TlsConfig {
pub fn domain(&self) -> &str {
match self {
TlsConfig::Files(TlsPaths { domain, .. }) => domain,
TlsConfig::Inline(TlsCerts { domain, .. }) => domain,
}
}
}
impl From<TlsCerts> for TlsConfig {
fn from(certs: TlsCerts) -> Self {
Self::Inline(certs)
}
}
impl From<TlsPaths> for TlsConfig {
fn from(paths: TlsPaths) -> Self {
Self::Files(paths)
}
}
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct TlsCerts {
pub domain: String,
pub key: String,
pub cert: String,
pub ca_cert: String,
}
impl Debug for TlsCerts {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "TlsCerts {{ domain: {} }}", self.domain)
}
}
impl TryFrom<TlsPaths> for TlsCerts {
type Error = anyhow::Error;
fn try_from(paths: TlsPaths) -> Result<Self, Self::Error> {
use std::fs::read;
Ok(Self {
domain: paths.domain,
key: String::from_utf8(read(paths.key)?).context("key should be UTF-8")?,
cert: String::from_utf8(read(paths.cert)?).context("cert should be UTF-8: {e}")?,
ca_cert: String::from_utf8(read(paths.ca_cert)?)
.context("CA cert should be UTF-8: {e}")?,
})
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct TlsPaths {
pub domain: String,
pub key: PathBuf,
pub cert: PathBuf,
pub ca_cert: PathBuf,
}
cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] {
impl TryFrom<TlsPolicy> for DomainConnector {
type Error = anyhow::Error;
fn try_from(_config: TlsPolicy) -> Result<Self, Self::Error> {
info!("Using Default Domain connector for wasm");
Ok(Box::new(DefaultDomainConnector::new()))
}
}
} else if #[cfg(feature = "openssl")] {
impl TryFrom<TlsPolicy> for DomainConnector {
type Error = anyhow::Error;
fn try_from(config: TlsPolicy) -> Result<Self, Self::Error> {
use fluvio_future::net::certs::CertBuilder;
use fluvio_future::openssl:: {TlsDomainConnector,TlsConnector,TlsAnonymousConnector};
use fluvio_future::openssl::certs::{IdentityBuilder,X509PemBuilder,PrivateKeyBuilder};
match config {
TlsPolicy::Disabled => Ok(Box::new(DefaultDomainConnector::new())),
TlsPolicy::Anonymous => {
info!("Using anonymous TLS");
let builder = TlsConnector::builder()?
.with_hostname_verification_disabled()?;
let connector: TlsAnonymousConnector = builder.build().into();
Ok(Box::new(connector))
}
TlsPolicy::Verified(TlsConfig::Files(tls)) => {
info!(
domain = &*tls.domain,
"Using verified TLS with certificates from paths"
);
let builder = TlsConnector::builder()?
.with_identity(
IdentityBuilder::from_x509(
X509PemBuilder::from_path(&tls.cert)?,
PrivateKeyBuilder::from_path(&tls.key)?
)?
)?
.add_root_certificate(
X509PemBuilder::from_path(&tls.ca_cert)?
.build()?
)?;
Ok(Box::new(TlsDomainConnector::new(
builder.build(),
tls.domain,
)))
}
TlsPolicy::Verified(TlsConfig::Inline(tls)) => {
info!(
domain = &*tls.domain,
"Using verified TLS with inline certificates"
);
let builder = TlsConnector::builder()?
.with_identity(
IdentityBuilder::from_x509(
X509PemBuilder::from_reader(&mut tls.cert.as_bytes())?,
PrivateKeyBuilder::from_reader(&mut tls.key.as_bytes())?
)?
)?
.add_root_certificate(
X509PemBuilder::from_reader(&mut tls.ca_cert.as_bytes())?
.build()?
)?;
Ok(Box::new(TlsDomainConnector::new(
builder.build(),
tls.domain,
)))
}
}
}
}
} else if #[cfg(feature = "rustls")] {
impl TryFrom<TlsPolicy> for DomainConnector {
type Error = anyhow::Error;
fn try_from(config: TlsPolicy) -> Result<Self, Self::Error> {
if rustls::crypto::CryptoProvider::get_default().is_none() {
cfg_if::cfg_if! {
if #[cfg(feature = "rustls-ring")] {
info!("Using rustls-ring as crypto provider");
let _ = rustls::crypto::ring::default_provider().install_default();
} else if #[cfg(feature = "rustls-aws")] {
info!("Using aws-lc-rs as crypto provider");
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
} else {
let err_msg = "No crypto provider selected for rustls, please enable either aws-lc-rs or ring feature";
error!("{error_msg}");
return Err(anyhow::anyhow!(err_msg));
}
}
}
use fluvio_future::rust_tls:: ConnectorBuilder;
use fluvio_future::rust_tls::TlsAnonymousConnector;
use fluvio_future::rust_tls::TlsDomainConnector;
match config {
TlsPolicy::Disabled => Ok(Box::new(DefaultDomainConnector::new())),
TlsPolicy::Anonymous => {
info!("Using anonymous TLS");
let rust_tls_connnector: TlsAnonymousConnector = ConnectorBuilder::with_safe_defaults()
.no_cert_verification()
.build().into();
Ok(Box::new(rust_tls_connnector))
}
TlsPolicy::Verified(TlsConfig::Files(tls)) => {
info!(
domain = &*tls.domain,
ca_cert_path = ?tls.ca_cert,
client.cert = ?tls.cert,
client.key = ?tls.key,
"Using verified TLS with rustls and certificates from paths"
);
let connector = ConnectorBuilder::with_safe_defaults()
.load_ca_cert(&tls.ca_cert)?
.load_client_certs(&tls.cert, &tls.key)?
.build();
Ok(Box::new(TlsDomainConnector::new(
connector,
tls.domain
)))
}
TlsPolicy::Verified(TlsConfig::Inline(tls)) => {
info!(
domain = &*tls.domain,
"Using verified TLS with rustls and inline certificates"
);
let connector = ConnectorBuilder::with_safe_defaults()
.load_ca_cert_from_bytes(tls.ca_cert.as_bytes())?
.load_client_certs_from_bytes(tls.cert.as_bytes(),tls.key.as_bytes())?
.build();
Ok(Box::new(TlsDomainConnector::new(
connector,
tls.domain
)))
}
}
}
}
} else {
impl TryFrom<TlsPolicy> for DomainConnector {
type Error = anyhow::Error;
fn try_from(_config: TlsPolicy) -> Result<Self, Self::Error> {
info!("Using Default Domain connector for wasm");
Ok(Box::new(DefaultDomainConnector::new()))
}
}
}
}