Skip to content

Commit cc856e0

Browse files
authored
Merge pull request #12 from njasm/feature_authorization
MONITORBOT_SECRET authorization
2 parents dd244c8 + ae5d0b4 commit cc856e0

File tree

2 files changed

+67
-3
lines changed

2 files changed

+67
-3
lines changed

src/config.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ const ENVIRONMENT_VARIABLE_PREFIX: &str = "MONITORBOT_";
66

77
#[derive(Clone, Debug)]
88
pub struct Config {
9+
// authorization secret (token) to be able to scrape the metrics endpoint
10+
pub secret: String,
911
// http server port to bind to
1012
pub port: u16,
1113
// github api tokens to collect rate limit statistics
@@ -17,6 +19,7 @@ pub struct Config {
1719
impl Config {
1820
pub fn from_env() -> Result<Self, Error> {
1921
Ok(Self {
22+
secret: require_env("SECRET")?,
2023
port: default_env("PORT", 3001)?,
2124
gh_rate_limit_tokens: require_env("RATE_LIMIT_TOKENS")?,
2225
gh_rate_limit_stats_cache_refresh: default_env("GH_RATE_LIMIT_STATS_REFRESH", 120)?,

src/lib.rs

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ use prometheus::{Encoder, Registry};
1111
use anyhow::{Error, Result};
1212
use futures::future;
1313
use futures::task::{Context, Poll};
14+
use hyper::header::AUTHORIZATION;
15+
use hyper::http::HeaderValue;
1416
use hyper::service::Service;
15-
use hyper::{Body, Method, Request, Response, StatusCode};
17+
use hyper::{Body, HeaderMap, Method, Request, Response, StatusCode};
1618
use log::{debug, error};
1719

1820
#[derive(Clone, Debug)]
@@ -59,9 +61,10 @@ impl Service<Request<Body>> for MetricProvider {
5961
fn call(&mut self, req: Request<Body>) -> Self::Future {
6062
debug!("New Request to endpoint {}", req.uri().path());
6163

62-
let output = match (req.method(), req.uri().path()) {
64+
let authorized = is_auth_token_valid(&self.config.secret, req.headers());
65+
let output = match (req.method(), req.uri().path(), authorized) {
6366
// Metrics handler
64-
(&Method::GET, "/metrics") => {
67+
(&Method::GET, "/metrics", true) => {
6568
let encoder = prometheus::TextEncoder::new();
6669
let mut buffer = Vec::<u8>::new();
6770
match self.gather_with_encoder(encoder, &mut buffer) {
@@ -78,6 +81,11 @@ impl Service<Request<Body>> for MetricProvider {
7881
}
7982
}
8083
}
84+
// Unauthorized request
85+
(&Method::GET, "/metrics", false) => Response::builder()
86+
.status(StatusCode::UNAUTHORIZED)
87+
.body(Body::empty())
88+
.unwrap(),
8189
// All other paths and methods
8290
_ => Response::builder()
8391
.status(StatusCode::OK)
@@ -89,6 +97,22 @@ impl Service<Request<Body>> for MetricProvider {
8997
}
9098
}
9199

100+
fn is_auth_token_valid(secret: &str, headers: &HeaderMap<HeaderValue>) -> bool {
101+
match headers.get(AUTHORIZATION) {
102+
Some(value) => value.to_str().map_or_else(
103+
|_| false,
104+
|t| {
105+
if let Some(t) = t.strip_prefix("Bearer ") {
106+
t == secret
107+
} else {
108+
false
109+
}
110+
},
111+
),
112+
None => false,
113+
}
114+
}
115+
92116
pub struct MetricProviderFactory(pub MetricProvider);
93117

94118
impl<T> Service<T> for MetricProviderFactory {
@@ -104,3 +128,40 @@ impl<T> Service<T> for MetricProviderFactory {
104128
future::ok(self.0.clone())
105129
}
106130
}
131+
132+
#[cfg(test)]
133+
mod tests {
134+
use crate::is_auth_token_valid;
135+
use hyper::http::HeaderValue;
136+
use hyper::HeaderMap;
137+
138+
#[test]
139+
fn auth_token_strip_bearer() {
140+
use hyper::header::AUTHORIZATION;
141+
142+
let secret = "aASgwyfbFAKETOKEN44562uj36";
143+
let token = "Bearer aASgwyfbFAKETOKEN44562uj36";
144+
let v = HeaderValue::from_static(token);
145+
let mut hv = HeaderMap::new();
146+
hv.insert(AUTHORIZATION, v);
147+
148+
// should be true
149+
let result = is_auth_token_valid(secret, &hv);
150+
assert!(result);
151+
}
152+
153+
#[test]
154+
fn auth_token_strip_bearer_fail() {
155+
use hyper::header::AUTHORIZATION;
156+
157+
let secret = "aASgwyfbFAKETOKEN44562uj36";
158+
let token = "Bearer aASgwyfbFAKETOKEN44562uj36 "; // notice the whitespace in the end
159+
let v = HeaderValue::from_static(token);
160+
let mut hv = HeaderMap::new();
161+
hv.insert(AUTHORIZATION, v);
162+
163+
// should be true
164+
let result = is_auth_token_valid(secret, &hv);
165+
assert_eq!(false, result);
166+
}
167+
}

0 commit comments

Comments
 (0)