Skip to content

Commit 905ed7a

Browse files
committed
metrics-cache: Middleware to wire up Kube auth
Lacking tests for now, which it desperately needs, since it is auth code. But basic local testing shows it working as expected.
1 parent 4a3bec0 commit 905ed7a

2 files changed

Lines changed: 73 additions & 7 deletions

File tree

metrics-cache/src/kube_auth.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use anyhow::Result;
22
use k8s_openapi::api::authentication::v1::{TokenReview, TokenReviewSpec};
3-
use kube::{Api, Client};
43
use kube::api::PostParams;
4+
use kube::{Api, Client};
55
use std::time::Duration;
66

77
/// Generate a Kubernetes client config with overrides from CLI arguments for

metrics-cache/src/main.rs

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,83 @@
11
mod cli_args;
2+
mod kube_auth;
23

3-
use axum::{Router, routing::get};
4+
use anyhow::Result;
5+
use axum::{
6+
Router,
7+
extract::{Request, State},
8+
http::StatusCode,
9+
middleware::{self, Next},
10+
response::Response,
11+
routing::get,
12+
};
413
use clap::Parser;
514

15+
use crate::kube_auth::validate_token_against_kube;
16+
617
// use cmk_kube_types;
718

8-
#[tokio::main]
9-
async fn main() {
10-
let app = Router::new().route("/", get(|| async { "foo" }));
19+
#[derive(Clone)]
20+
struct AppState {
21+
/// A configured Kubernetes client, ready to validate authentication tokens.
22+
kube_client: kube::Client,
23+
}
1124

25+
#[tokio::main]
26+
async fn main() -> Result<()> {
1227
let args = cli_args::Args::parse();
13-
let listener = tokio::net::TcpListener::bind((args.address, args.port))
28+
let kube_client = kube_auth::kube_client(args.connect_timeout, args.read_timeout).await?;
29+
let state = AppState { kube_client };
30+
let app = Router::new()
31+
.route("/", get(|| async { "foo" }))
32+
// vvv Routes below this will REQUIRE AUTHENTICATION vvv
33+
.route_layer(middleware::from_fn_with_state(
34+
state.clone(),
35+
authenticate_against_kube,
36+
))
37+
// ^^^ Routes below this will be PUBLIC ^^^
38+
.route("/health", get(|| async { "Stayin' alive" }))
39+
.with_state(state);
40+
let listener = tokio::net::TcpListener::bind((args.address.as_str(), args.port))
1441
.await
1542
.unwrap();
16-
axum::serve(listener, app).await.unwrap();
43+
axum::serve(listener, app).await?;
44+
Ok(())
45+
}
46+
47+
/// Middleware function that handles authentication.
48+
///
49+
/// Every endpoint affected by this middleware will trigger an authentication
50+
/// request to Kubernetes and must be successful. Otherwise, the request is
51+
/// aborted.
52+
async fn authenticate_against_kube(
53+
state: State<AppState>,
54+
request: Request,
55+
next: Next,
56+
) -> std::result::Result<Response, StatusCode> {
57+
let token = request
58+
.headers()
59+
.get("Authorization")
60+
.and_then(|h| h.to_str().ok())
61+
.and_then(|s| s.strip_prefix("Bearer "))
62+
.ok_or(StatusCode::UNAUTHORIZED)?;
63+
64+
let Ok(validation_response) =
65+
validate_token_against_kube(state.kube_client.clone(), token).await
66+
else {
67+
// TODO: Log something (otherwise change this to .unwrap_or(...)?;)
68+
return Err(StatusCode::NOT_IMPLEMENTED); // Compat with Python cluster-collector
69+
};
70+
71+
let Some(status) = validation_response.status else {
72+
// Should never happen...?
73+
// TODO: Log something here, too
74+
return Err(StatusCode::INTERNAL_SERVER_ERROR);
75+
};
76+
77+
if !status.authenticated.unwrap_or(false) {
78+
// TODO: And here.
79+
return Err(StatusCode::UNAUTHORIZED);
80+
}
81+
82+
Ok(next.run(request).await)
1783
}

0 commit comments

Comments
 (0)