start on handlers

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
This commit is contained in:
Marc 'risson' Schmitt
2026-05-18 18:19:52 +02:00
parent f094fce1c4
commit bd42e24cd5
6 changed files with 230 additions and 64 deletions
-61
View File
@@ -1,61 +0,0 @@
use std::sync::Arc;
use ak_client::models::ProxyOutpostConfig;
use ak_common::tls::store::Certificate;
use axum::Router;
use eyre::{Result, eyre};
use tracing::instrument;
use url::Url;
use crate::outpost::proxy::ProxyOutpost;
const _REDIRECT_PARAM: &str = "rd";
const CALLBACK_SIGNATURE: &str = "X-authentik-auth-callback";
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<Certificate>>,
}
impl Application {
#[instrument(skip_all)]
pub(super) async fn new(outpost: &ProxyOutpost, provider: ProxyOutpostConfig) -> Result<Self> {
let external_url = Url::parse(&provider.external_host)?;
if !external_url.has_authority() {
return Err(eyre!("no host in external host"));
}
let external_host = external_url.authority();
let _old_app = outpost.apps.load().get(external_host);
let cert = if let Some(Some(kp_uuid)) = provider.certificate {
Some(
outpost
.certificate_store
.ensure_keypair(&outpost.controller.api_config, kp_uuid)
.await?,
)
} else {
None
};
let _redirect_url = {
let mut redirect_url = external_url.join("outpost.goauthentik.io/callback")?;
redirect_url.set_query(Some(&format!("{CALLBACK_SIGNATURE}=true")));
redirect_url
};
let router = Router::new();
Ok(Self {
host: external_host.to_owned(),
provider,
router,
cert,
})
}
}
@@ -0,0 +1,42 @@
use std::sync::Arc;
use ak_axum::error::Result;
use axum::{
extract::{Request, State},
response::Response,
};
use tracing::instrument;
use crate::outpost::proxy::application::Application;
#[instrument(skip_all)]
pub(crate) async fn handle_caddy(
State(_app): State<Arc<Application>>,
_request: Request,
) -> Result<Response> {
todo!()
}
#[instrument(skip_all)]
pub(crate) async fn handle_envoy(
State(_app): State<Arc<Application>>,
_request: Request,
) -> Result<Response> {
todo!()
}
#[instrument(skip_all)]
pub(crate) async fn handle_nginx(
State(_app): State<Arc<Application>>,
_request: Request,
) -> Result<Response> {
todo!()
}
#[instrument(skip_all)]
pub(crate) async fn handle_traefik(
State(_app): State<Arc<Application>>,
_request: Request,
) -> Result<Response> {
todo!()
}
@@ -0,0 +1,82 @@
use std::str::FromStr;
use std::{fmt, sync::Arc};
use ak_axum::error::Result;
use axum::{
extract::{Query, Request, State},
response::Response,
};
use serde::{Deserialize, Deserializer};
use tower::util::ServiceExt as _;
use tracing::{debug, instrument};
use crate::outpost::proxy::application::Application;
pub(super) mod forward;
pub(super) mod proxy;
// TODO: move this to ak-common
fn empty_string_as_none<'de, D, T>(de: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: FromStr,
T::Err: fmt::Display,
{
let opt = Option::<String>::deserialize(de)?;
match opt.as_deref() {
None | Some("") => Ok(None),
Some(s) => FromStr::from_str(s)
.map_err(serde::de::Error::custom)
.map(Some),
}
}
#[derive(Deserialize)]
struct Parameters {
// #[serde(rename = "rd", default, deserialize_with = "empty_string_as_none")]
// redirect: Option<String>,
#[serde(
rename = "X-authentik-auth-callback",
default,
deserialize_with = "empty_string_as_none"
)]
callback_signature: Option<bool>,
#[serde(
rename = "X-authentik-logout",
default,
deserialize_with = "empty_string_as_none"
)]
logout_signature: Option<bool>,
}
#[instrument(skip_all)]
pub(crate) async fn handle(app: Arc<Application>, request: Request) -> Result<Response> {
if let Ok(query) = Query::<Parameters>::try_from_uri(request.uri()) {
if query.callback_signature == Some(true) {
debug!("handling OAuth Callback from querystring signature");
return handle_auth_callback(State(app), request).await;
}
if query.logout_signature == Some(true) {
debug!("handling OAuth Logout from querystring signature");
return handle_sign_out(State(app), request).await;
}
}
Ok(app.router.clone().with_state(app).oneshot(request).await?)
}
#[instrument(skip_all)]
pub(super) async fn handle_auth_callback(
State(_app): State<Arc<Application>>,
_request: Request,
) -> Result<Response> {
todo!()
}
#[instrument(skip_all)]
pub(super) async fn handle_sign_out(
State(_app): State<Arc<Application>>,
_request: Request,
) -> Result<Response> {
todo!()
}
@@ -0,0 +1,18 @@
use std::sync::Arc;
use ak_axum::error::Result;
use axum::{
extract::{Request, State},
response::Response,
};
use tracing::instrument;
use crate::outpost::proxy::application::Application;
#[instrument(skip_all)]
pub(crate) async fn handle(
State(_app): State<Arc<Application>>,
_request: Request,
) -> Result<Response> {
todo!()
}
+84
View File
@@ -0,0 +1,84 @@
use std::sync::Arc;
use ak_client::models::{ProxyMode, ProxyOutpostConfig};
use ak_common::tls::store::Certificate;
use axum::{Router, routing::any};
use eyre::{Result, eyre};
use tracing::instrument;
use url::Url;
use crate::outpost::proxy::ProxyOutpost;
pub(super) mod handlers;
#[derive(Debug)]
pub(super) struct Application {
pub(super) host: String,
pub(super) provider: ProxyOutpostConfig,
pub(super) router: Router<Arc<Self>>,
pub(super) cert: Option<Arc<Certificate>>,
}
impl Application {
#[instrument(skip_all)]
pub(super) async fn new(outpost: &ProxyOutpost, provider: ProxyOutpostConfig) -> Result<Self> {
let external_url = Url::parse(&provider.external_host)?;
if !external_url.has_authority() {
return Err(eyre!("no host in external host"));
}
let external_host = external_url.authority();
let _old_app = outpost.apps.load().get(external_host);
let cert = if let Some(Some(kp_uuid)) = provider.certificate {
Some(
outpost
.certificate_store
.ensure_keypair(&outpost.controller.api_config, kp_uuid)
.await?,
)
} else {
None
};
let router = Router::new()
// TODO: /start
.route(
"/outpost.goauthentik.io/callback",
any(handlers::handle_auth_callback),
)
.route(
"/outpost.goauthentik.io/sign_out",
any(handlers::handle_sign_out),
);
let router = match provider.mode {
Some(ProxyMode::ForwardSingle | ProxyMode::ForwardDomain) => router
.route(
"/outpost.goauthentik.io/auth/caddy",
any(handlers::forward::handle_caddy),
)
.route(
"/outpost.goauthentik.io/auth/envoy",
any(handlers::forward::handle_envoy),
)
.route(
"/outpost.goauthentik.io/auth/nginx",
any(handlers::forward::handle_nginx),
)
.route(
"/outpost.goauthentik.io/auth/traefik",
any(handlers::forward::handle_traefik),
),
Some(ProxyMode::Proxy) => router.fallback(handlers::proxy::handle),
None => return Err(eyre!("no provider mode set")),
};
Ok(Self {
host: external_host.to_owned(),
provider,
router,
cert,
})
}
}
+4 -3
View File
@@ -9,10 +9,9 @@ use axum::{
use metrics::histogram; use metrics::histogram;
use serde_json::json; use serde_json::json;
use tokio::time::Instant; use tokio::time::Instant;
use tower::util::ServiceExt as _;
use tracing::{Instrument as _, debug, field, info_span, instrument, trace, warn}; use tracing::{Instrument as _, debug, field, info_span, instrument, trace, warn};
use crate::outpost::proxy::ProxyOutpost; use crate::outpost::proxy::{ProxyOutpost, application};
#[instrument(skip_all)] #[instrument(skip_all)]
pub(super) async fn handle_ping( pub(super) async fn handle_ping(
@@ -72,7 +71,9 @@ pub(super) async fn default(
}; };
trace!("passing to application"); trace!("passing to application");
let response = app.router.clone().oneshot(request).instrument(span).await?; let response = application::handlers::handle(app, request)
.instrument(span)
.await?;
histogram!( histogram!(
"authentik_outpost_proxy_request_duration_seconds", "authentik_outpost_proxy_request_duration_seconds",