move to cert store

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
Marc 'risson' Schmitt
2026-05-07 16:54:25 +02:00
parent f8ef77d9bf
commit 8d1e024658
7 changed files with 116 additions and 37 deletions

2
Cargo.lock generated
View File

@@ -293,6 +293,7 @@ dependencies = [
"config",
"console-subscriber",
"eyre",
"futures",
"glob",
"ipnet",
"json-subscriber",
@@ -317,6 +318,7 @@ dependencies = [
"tracing-error",
"tracing-subscriber",
"url",
"uuid",
]
[[package]]

View File

@@ -298,7 +298,6 @@ serde_json.workspace = true
serde_repr.workspace = true
sqlx = { workspace = true, optional = true }
time.workspace = true
tokio-retry2.workspace = true
tokio-tungstenite.workspace = true
tokio.workspace = true
tower.workspace = true

View File

@@ -22,6 +22,7 @@ axum-server.workspace = true
config-rs.workspace = true
console-subscriber.workspace = true
eyre.workspace = true
futures.workspace = true
glob.workspace = true
ipnet.workspace = true
json-subscriber.workspace = true
@@ -44,6 +45,7 @@ tracing-error.workspace = true
tracing-subscriber.workspace = true
tracing.workspace = true
url.workspace = true
uuid.workspace = true
[dev-dependencies]
nix.workspace = true

View File

@@ -8,6 +8,7 @@ use tracing::trace;
use crate::config;
pub mod self_signed;
pub mod store;
/// Dummy resolver for FIPS compliance check.
#[derive(Debug)]

View File

@@ -0,0 +1,92 @@
use std::{collections::HashMap, sync::Arc};
use ak_client::apis::{
configuration::Configuration,
crypto_api::{
crypto_certificatekeypairs_retrieve, crypto_certificatekeypairs_view_certificate_retrieve,
crypto_certificatekeypairs_view_private_key_retrieve,
},
};
use eyre::{Report, Result};
use futures::FutureExt as _;
use rustls::{
crypto::CryptoProvider,
pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject as _},
sign::CertifiedKey,
};
use tokio::sync::Mutex;
use uuid::Uuid;
#[derive(Debug)]
pub struct Certificate {
pub fingerprint: String,
pub certificate: String,
pub key: String,
pub certified_key: Arc<CertifiedKey>,
}
#[derive(Clone, Debug, Default)]
pub struct CertificateStore {
certificates: Arc<Mutex<HashMap<Uuid, Arc<Certificate>>>>,
}
impl CertificateStore {
pub fn new() -> Self {
Self::default()
}
pub async fn ensure_keypair(
&self,
api_config: &Configuration,
kp_uuid: Uuid,
) -> Result<Arc<Certificate>> {
let kp_uuid_s = kp_uuid.to_string();
let fingerprint = crypto_certificatekeypairs_retrieve(api_config, &kp_uuid_s)
.await?
.fingerprint_sha256;
if let Some(certificate) = self.certificates.lock().await.get(&kp_uuid)
&& let Some(fingerprint) = &fingerprint
&& &certificate.fingerprint == fingerprint
{
return Ok(Arc::clone(certificate));
}
let (cert, key) = tokio::try_join!(
crypto_certificatekeypairs_view_certificate_retrieve(api_config, &kp_uuid_s, None,)
.map(|res| res.map_err(Report::from)),
crypto_certificatekeypairs_view_private_key_retrieve(api_config, &kp_uuid_s, None,)
.map(|res| res.map_err(Report::from)),
)?;
let certified_key = {
let cert_chain = CertificateDer::pem_reader_iter(cert.data.as_bytes())
.collect::<Result<Vec<_>, _>>()?;
let key_der = PrivateKeyDer::from_pem_reader(key.data.as_bytes())?;
let provider = CryptoProvider::get_default().expect("no rustls provider installed");
Arc::new(CertifiedKey::new(
cert_chain,
provider.key_provider.load_private_key(key_der)?,
))
};
let cert = Arc::new(Certificate {
fingerprint: fingerprint.unwrap_or_default(),
certificate: cert.data,
key: key.data,
certified_key,
});
if !cert.fingerprint.is_empty() {
self.certificates
.lock()
.await
.insert(kp_uuid, Arc::clone(&cert));
}
Ok(cert)
}
}

View File

@@ -1,34 +1,24 @@
use std::sync::Arc;
use ak_client::{
apis::crypto_api::{
crypto_certificatekeypairs_view_certificate_retrieve,
crypto_certificatekeypairs_view_private_key_retrieve,
},
models::ProxyOutpostConfig,
};
use ak_client::models::ProxyOutpostConfig;
use ak_common::tls::store::Certificate;
use axum::Router;
use eyre::{Result, eyre};
use rustls::{
crypto::CryptoProvider,
pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject as _},
sign::CertifiedKey,
};
use tracing::instrument;
use url::Url;
use crate::outpost::proxy::ProxyOutpost;
const REDIRECT_PARAM: &str = "rd";
const _REDIRECT_PARAM: &str = "rd";
const CALLBACK_SIGNATURE: &str = "X-authentik-auth-callback";
const LOGOUT_SIGNATURE: &str = "X-authentik-logout";
const _LOGOUT_SIGNATURE: &str = "X-authentik-logout";
#[derive(Debug)]
pub(super) struct Application {
pub(super) host: String,
pub(super) provider: ProxyOutpostConfig,
pub(super) router: Router,
pub(super) cert: Option<Arc<CertifiedKey>>,
pub(super) cert: Option<Arc<Certificate>>,
}
impl Application {
@@ -42,26 +32,12 @@ impl Application {
// TODO: extract this to a certificate store to avoid re-fetching the certificate every time
let cert = if let Some(Some(kp_uuid)) = provider.certificate {
let cert = crypto_certificatekeypairs_view_certificate_retrieve(
&outpost.controller.api_config,
&kp_uuid.to_string(),
None,
Some(
outpost
.certificate_store
.ensure_keypair(&outpost.controller.api_config, kp_uuid)
.await?,
)
.await?;
let key = crypto_certificatekeypairs_view_private_key_retrieve(
&outpost.controller.api_config,
&kp_uuid.to_string(),
None,
)
.await?;
let cert_chain = CertificateDer::pem_reader_iter(cert.data.as_bytes())
.collect::<Result<Vec<_>, _>>()?;
let key_der = PrivateKeyDer::from_pem_reader(key.data.as_bytes())?;
let provider = CryptoProvider::get_default().expect("no rustls provider installed");
Some(Arc::new(CertifiedKey::new(
cert_chain,
provider.key_provider.load_private_key(key_der)?,
)))
} else {
None
};

View File

@@ -2,7 +2,12 @@ use std::{collections::HashMap, sync::Arc};
use ak_axum::router::wrap_router;
use ak_client::{apis::outposts_api::outposts_proxy_list, models::ProxyMode};
use ak_common::{Tasks, api::fetch_all, config, tls};
use ak_common::{
Tasks,
api::fetch_all,
config,
tls::{self, store::CertificateStore},
};
use arc_swap::ArcSwap;
use argh::FromArgs;
use axum::Router;
@@ -33,6 +38,7 @@ pub(crate) struct Cli {}
pub(crate) struct ProxyOutpost {
controller: Arc<OutpostController>,
apps: ArcSwap<HashMap<String, Arc<Application>>>,
certificate_store: CertificateStore,
default_cert: Arc<CertifiedKey>,
}
@@ -46,6 +52,7 @@ impl Outpost for ProxyOutpost {
Ok(Self {
controller,
apps: ArcSwap::from_pointee(HashMap::with_capacity(0)),
certificate_store: CertificateStore::new(),
default_cert: Arc::new(tls::self_signed::generate_certifiedkey()?),
})
}
@@ -149,7 +156,7 @@ impl ResolvesServerCert for ProxyOutpost {
&& let Some(app) = self.apps.load().get(server_name)
&& let Some(cert) = &app.cert
{
return Some(Arc::clone(cert));
return Some(Arc::clone(&cert.certified_key));
}
Some(Arc::clone(&self.default_cert))
}