Verify Cloudflare Access's signed token before trusting the proxy
The proxy sign-in believed Cf-Access-Authenticated-User-Email from any address in trusted_proxies. On Tower that address is the Docker gateway, so any container there could name itself anyone (docs/sso.md said as much, and CLAUDE.md listed it as a known gap). With [web] access_team and access_aud set, a proxied request must also carry a Cf-Access-Jwt-Assertion that verifies against Cloudflare's keys (RS256 only, this application's audience, the team's issuer, not expired), and the name comes from its email claim. The keys are fetched at start and again when a token names an unseen key, at most once a minute, so made-up key ids cannot make every request a request to Cloudflare. While the keys cannot be had, proxied sign-in is refused; password and token sign-in are unaffected. Both settings empty, nothing changes. jsonwebtoken does the checking, on the aws-lc-rs backend already in the tree through rustls. Tests sign with throwaway keys in tests/data: a valid token, another app's audience, expired, a forged signature, HS256, alg none, the refetch limit, and keys that cannot be fetched. Checked live on a scratch daemon: the header alone and a forged token got 401, the admin token still signed in. vouched_name takes the peer and headers rather than the request: a &Request held across the new await made the auth middleware's future unsendable, as a body is not Sync. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
mod access;
|
||||
mod auth;
|
||||
mod config;
|
||||
mod db;
|
||||
@@ -567,10 +568,16 @@ async fn start_web(
|
||||
tracing::warn!(bind, "web ui is reachable off this machine; the token is all that guards it");
|
||||
}
|
||||
|
||||
let access = Arc::new(access::Keys::default());
|
||||
if let Some((team, _)) = ctx.cfg().web.access() {
|
||||
let (access, ctx, team) = (access.clone(), ctx.clone(), team.to_owned());
|
||||
tokio::spawn(async move { access.prefetch(&ctx.client, &team).await });
|
||||
}
|
||||
let state = web::WebState {
|
||||
ctx: ctx.clone(),
|
||||
cmds: cmds.clone(),
|
||||
events: events.clone(),
|
||||
access,
|
||||
};
|
||||
Ok(Some(tokio::spawn(async move {
|
||||
if let Err(e) = web::serve(state, &bind).await {
|
||||
|
||||
Reference in New Issue
Block a user