mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-19 13:08:15 +02:00
Façade Rust, lot 2 : interface, authentification et /api/ping
L'interface (index, marked, service worker, manifeste, polices) est désormais servie par loki-rs, embarquée par build.rs depuis la même source que le go:embed — les deux binaires d'un build servent la même UI. Mêmes en-têtes de cache que le Go, gzip à la première demande puis gardé en mémoire, jamais sur les polices ni en HEAD. L'authentification de pilotage est portée à l'identique (requireWebAuth, crossSiteReject, localHostName, Bearer en temps constant, mêmes messages, mêmes codes) ; les cas de TestCrossSiteReject sont repris un pour un. La clé est demandée à l'API interne du Go à chaque requête ; si elle ne répond pas proprement, la façade ferme (503). /api/ping est la première route authentifiée servie par Rust. Le jeton de l'API interne est tiré au hasard à chaque démarrage et passé au Go par l'environnement. La façade rend 404 sur /internal/* venu de l'extérieur et efface X-Loki-Internal entrant avant tout relais. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a99d988e70
commit
d4fa4488cc
13 files changed
+1023
-18
No files matched your search
@@ -1,6 +1,6 @@
|
||||
//! Façade HTTP : reçoit tout le trafic public et le relaie au serveur Go
|
||||
//! (`loki web` sur 127.0.0.1). Les lots suivants de la migration y brancheront
|
||||
//! les routes réécrites en Rust ; au lot 1, tout est relayé.
|
||||
//! Façade HTTP : reçoit tout le trafic public. Les routes déjà réécrites en
|
||||
//! Rust sont servies ici (lot 2 : UI statique, `/api/ping`) ; tout le reste
|
||||
//! est relayé au serveur Go (`loki web` sur 127.0.0.1).
|
||||
//!
|
||||
//! Règles, héritées de `web_server.go` :
|
||||
//! - aucun tampon : un flux SSE (chat, `/v1` en streaming) part trame par trame ;
|
||||
@@ -27,6 +27,9 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use crate::auth::{self, HostEnv, INTERNAL_HEADER, InternalClient};
|
||||
use crate::ui;
|
||||
|
||||
pub type Body = BoxBody<Bytes, hyper::Error>;
|
||||
|
||||
/// En-tête posé par le tunnel du relais côté Go (`viaTunnelHeader`).
|
||||
@@ -43,19 +46,35 @@ const HOP_BY_HOP: [&str; 8] = [
|
||||
"upgrade",
|
||||
];
|
||||
|
||||
/// Réglages de la façade.
|
||||
pub struct Config {
|
||||
/// Serveur Go (`loki web`), en écoute locale.
|
||||
pub backend: SocketAddr,
|
||||
/// Jeton de l'API interne du Go (`LOKI_INTERNAL_TOKEN`).
|
||||
pub internal_token: String,
|
||||
pub host_env: HostEnv,
|
||||
}
|
||||
|
||||
struct Proxy {
|
||||
client: Client<HttpConnector, Incoming>,
|
||||
backend: SocketAddr,
|
||||
internal: InternalClient,
|
||||
host_env: HostEnv,
|
||||
}
|
||||
|
||||
/// Sert la façade sur `listener` jusqu'à ce que `shutdown` se termine.
|
||||
pub async fn serve(
|
||||
listener: TcpListener,
|
||||
backend: SocketAddr,
|
||||
cfg: Config,
|
||||
shutdown: impl Future<Output = ()>,
|
||||
) -> std::io::Result<()> {
|
||||
let client = Client::builder(TokioExecutor::new()).build_http();
|
||||
let proxy = Arc::new(Proxy { client, backend });
|
||||
let proxy = Arc::new(Proxy {
|
||||
client,
|
||||
backend: cfg.backend,
|
||||
internal: InternalClient::new(cfg.backend, cfg.internal_token),
|
||||
host_env: cfg.host_env,
|
||||
});
|
||||
tokio::pin!(shutdown);
|
||||
loop {
|
||||
tokio::select! {
|
||||
@@ -91,9 +110,43 @@ pub async fn serve(
|
||||
|
||||
impl Proxy {
|
||||
async fn handle(&self, mut req: Request<Incoming>) -> Response<Body> {
|
||||
// Jamais de confiance accordée sur la foi d'un en-tête du client :
|
||||
// ni « je viens du tunnel », ni « je suis la façade ».
|
||||
req.headers_mut().remove(VIA_TUNNEL);
|
||||
req.headers_mut().remove(INTERNAL_HEADER);
|
||||
|
||||
let path = req.uri().path();
|
||||
// L'API interne du Go n'est jamais joignable de l'extérieur.
|
||||
if path.starts_with("/internal/") {
|
||||
return plain(StatusCode::NOT_FOUND, "404 page not found");
|
||||
}
|
||||
if let Some(resp) = ui::serve(req.method(), path, req.headers()) {
|
||||
return resp;
|
||||
}
|
||||
if path == "/api/ping" {
|
||||
return self.ping(req.headers()).await;
|
||||
}
|
||||
self.forward(req).await
|
||||
}
|
||||
|
||||
/// `/api/ping` : vérifie la connectivité ET la clé (200 = bonne clé,
|
||||
/// 401 = mauvaise clé), comme `handlePing`.
|
||||
async fn ping(&self, headers: &HeaderMap) -> Response<Body> {
|
||||
let info = self.internal.auth().await;
|
||||
if let Some(refus) = auth::authorize(headers, &info, &self.host_env) {
|
||||
return refus;
|
||||
}
|
||||
let version = info.map(|i| i.version).unwrap_or_default();
|
||||
auth::json(
|
||||
StatusCode::OK,
|
||||
&serde_json::json!({ "ok": true, "service": "loki", "version": version }),
|
||||
)
|
||||
}
|
||||
|
||||
/// Relais vers le Go (HTTP, SSE, WebSocket).
|
||||
async fn forward(&self, mut req: Request<Incoming>) -> Response<Body> {
|
||||
let upgrade = upgrade_protocol(req.headers());
|
||||
strip_hop_by_hop(req.headers_mut());
|
||||
req.headers_mut().remove(VIA_TUNNEL);
|
||||
let client_upgrade = upgrade.as_ref().map(|proto| {
|
||||
let h = req.headers_mut();
|
||||
h.insert(header::CONNECTION, HeaderValue::from_static("upgrade"));
|
||||
|
||||
Reference in new issue
Block a user