-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathauth.rs
More file actions
69 lines (60 loc) · 2.33 KB
/
Copy pathauth.rs
File metadata and controls
69 lines (60 loc) · 2.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//! Bearer-token auth for the one privileged route (`POST /v1/execute`).
//!
//! Unlike the gateway's optional admin key (which treats a loopback bind as
//! evidence of safe operator intent), this binary's entire surface *is* the
//! privileged action it guards — there is no "safe to leave open" mode, so
//! the token is a required CLI/env field (`Cli::api_token` has no
//! `default_value`) and every `/v1/execute` call is checked unconditionally.
use axum::extract::State;
use axum::http::{header, Request, StatusCode};
use axum::middleware::Next;
use axum::response::{IntoResponse, Response};
use std::sync::Arc;
/// SHA-256 digest equality rather than a raw `==` — mirrors the gateway's
/// `admin_decision` rationale: a timing side-channel on a raw string
/// comparison can leak the configured token one byte at a time.
pub fn tokens_match(configured: &str, provided: &str) -> bool {
fn hash(token: &str) -> String {
use sha2::{Digest, Sha256};
hex::encode(Sha256::digest(token.as_bytes()))
}
hash(configured) == hash(provided)
}
pub async fn require_bearer_token(
State(configured_token): State<Arc<String>>,
request: Request<axum::body::Body>,
next: Next,
) -> Response {
let provided = request
.headers()
.get(header::AUTHORIZATION)
.and_then(|h| h.to_str().ok())
.and_then(|v| v.strip_prefix("Bearer "));
match provided {
Some(token) if tokens_match(&configured_token, token) => next.run(request).await,
_ => (StatusCode::UNAUTHORIZED, "invalid or missing bearer token").into_response(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn matching_tokens_are_equal() {
assert!(tokens_match("secret-token", "secret-token"));
}
#[test]
fn mismatched_tokens_are_not_equal() {
assert!(!tokens_match("secret-token", "wrong-token"));
}
#[test]
fn empty_provided_token_never_matches_a_configured_one() {
assert!(!tokens_match("secret-token", ""));
}
#[test]
fn two_empty_tokens_are_equal_but_the_server_never_configures_an_empty_one() {
// Documents the invariant this relies on: Cli::api_token has no
// default_value, so an empty configured token is unreachable in
// production -- clap itself refuses to start the process.
assert!(tokens_match("", ""));
}
}