Rust implementation of OCI Distribution Spec with granular access control
1use axum::{extract::Request, middleware::Next, response::Response};
2use std::time::Instant;
3
4use crate::metrics;
5
6pub async fn track_metrics(req: Request, next: Next) -> Response {
7 let start = Instant::now();
8 let method = req.method().to_string();
9 let path = req.uri().path().to_string();
10
11 // Process request
12 let response = next.run(req).await;
13
14 // Record metrics
15 let duration = start.elapsed().as_secs_f64();
16 let status = response.status().as_u16().to_string();
17
18 // Normalize endpoint for metrics (avoid cardinality explosion)
19 let endpoint = normalize_endpoint(&path);
20
21 metrics::HTTP_REQUESTS_TOTAL
22 .with_label_values(&[&method, &endpoint, &status])
23 .inc();
24
25 metrics::REQUEST_DURATION
26 .with_label_values(&[&method, &endpoint])
27 .observe(duration);
28
29 response
30}
31
32fn normalize_endpoint(path: &str) -> String {
33 // Replace dynamic segments with placeholders
34 if path == "/v2/" {
35 return "/v2/".to_string();
36 }
37 if path.starts_with("/v2/") {
38 if path.contains("/blobs/") {
39 if path.contains("/uploads/") {
40 return "/v2/{name}/blobs/uploads/{reference}".to_string();
41 }
42 return "/v2/{name}/blobs/{digest}".to_string();
43 } else if path.contains("/manifests/") {
44 return "/v2/{name}/manifests/{reference}".to_string();
45 } else if path.contains("/tags/") {
46 return "/v2/{name}/tags/list".to_string();
47 }
48 }
49 if path.starts_with("/admin/") {
50 if path.contains("/users/") && path.split('/').count() > 3 {
51 if path.contains("/permissions") {
52 return "/admin/users/{username}/permissions".to_string();
53 }
54 return "/admin/users/{username}".to_string();
55 }
56 return path.to_string();
57 }
58 path.to_string()
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn test_normalize_endpoint() {
67 assert_eq!(
68 normalize_endpoint("/v2/myorg/myrepo/blobs/sha256:abc123"),
69 "/v2/{name}/blobs/{digest}"
70 );
71 assert_eq!(
72 normalize_endpoint("/v2/myorg/myrepo/manifests/latest"),
73 "/v2/{name}/manifests/{reference}"
74 );
75 assert_eq!(
76 normalize_endpoint("/v2/myorg/myrepo/tags/list"),
77 "/v2/{name}/tags/list"
78 );
79 assert_eq!(normalize_endpoint("/health"), "/health");
80 assert_eq!(normalize_endpoint("/metrics"), "/metrics");
81 }
82}