Make the metrics page configurable
Make the metrics page configurable with an `ip_filter`. This closes #10.
This commit is contained in:
parent
f0f1d3239d
commit
f24a786ec6
3 changed files with 222 additions and 43 deletions
14
README.md
14
README.md
|
@ -59,6 +59,20 @@ Whereas `<config_dir>` depends on the platform:
|
|||
|
||||
### Configuration parameters
|
||||
|
||||
#### Metrics
|
||||
A metrics page can optionally enabled to query stats of the currently
|
||||
running webhookey instance. Note that stats are lost between restarts
|
||||
of webhookey as those are not stored persistently.
|
||||
|
||||
Example:
|
||||
```yaml
|
||||
metrics:
|
||||
enabled: true
|
||||
ip_filter:
|
||||
allow:
|
||||
- 127.0.0.1/31
|
||||
```
|
||||
|
||||
#### Hooks
|
||||
With `hooks` you can configure a sequence of hooks. A single hook
|
||||
consists of the following fields:
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
---
|
||||
metrics:
|
||||
enabled: true
|
||||
ip_filter:
|
||||
deny:
|
||||
- 127.0.0.1
|
||||
hooks:
|
||||
hook1:
|
||||
command: /usr/bin/local/script_xy.sh {{ /field2/foo }} asdfasdf
|
||||
|
|
246
src/main.rs
246
src/main.rs
|
@ -119,9 +119,17 @@ impl IpFilter {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct MetricsConfig {
|
||||
enabled: bool,
|
||||
ip_filter: Option<IpFilter>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct Config {
|
||||
metrics: Option<MetricsConfig>,
|
||||
hooks: BTreeMap<String, Hook>,
|
||||
}
|
||||
|
||||
|
@ -432,6 +440,48 @@ fn get_config() -> Result<File> {
|
|||
bail!("No configuration file found.");
|
||||
}
|
||||
|
||||
fn get_metrics(metrics: &WebhookeyMetrics) -> String {
|
||||
format!(
|
||||
r"# HELP webhookey_requests_received Number of requests received
|
||||
# TYPE webhookey_requests_received gauge
|
||||
webhookey_requests_received {}
|
||||
# HELP webhookey_requests_invalid Number of invalid requests received
|
||||
# TYPE webhookey_requests_invalid gauge
|
||||
webhookey_requests_invalid {}
|
||||
# HELP webhookey_hooks_successful Number of successfully executed hooks
|
||||
# TYPE webhookey_hooks_successful gauge
|
||||
webhookey_hooks_sucessful {}
|
||||
# HELP webhookey_hooks_forbidden Number of forbidden requests
|
||||
# TYPE webhookey_hooks_forbidden gauge
|
||||
webhookey_hooks_forbidden {}
|
||||
# HELP webhookey_hooks_unmatched Number of unmatched requests
|
||||
# TYPE webhookey_hooks_unmatched gauge
|
||||
webhookey_hooks_unmatched {}
|
||||
# HELP webhookey_commands_executed Number of commands executed
|
||||
# TYPE webhookey_commands_executed gauge
|
||||
webhookey_commands_executed {}
|
||||
# HELP webhookey_commands_execution_failed Number of commands failed to execute
|
||||
# TYPE webhookey_commands_execution_failed gauge
|
||||
webhookey_commands_execution_failed {}
|
||||
# HELP webhookey_commands_successful Number of executed commands returning return code 0
|
||||
# TYPE webhookey_commands_successful gauge
|
||||
webhookey_commands_successful {}
|
||||
# HELP webhookey_commands_failed Number of executed commands returning different return code than 0
|
||||
# TYPE webhookey_commands_failed gauge
|
||||
webhookey_commands_failed {}
|
||||
",
|
||||
metrics.requests_received.lock().unwrap(), // TODO: Check if unwrap need to be fixed
|
||||
metrics.requests_invalid.lock().unwrap(), // TODO: Check if unwrap need to be fixed
|
||||
metrics.hooks_successful.lock().unwrap(), // TODO: Check if unwrap need to be fixed
|
||||
metrics.hooks_forbidden.lock().unwrap(), // TODO: Check if unwrap need to be fixed
|
||||
metrics.hooks_unmatched.lock().unwrap(), // TODO: Check if unwrap need to be fixed
|
||||
metrics.commands_executed.lock().unwrap(), // TODO: Check if unwrap need to be fixed
|
||||
metrics.commands_execution_failed.lock().unwrap(), // TODO: Check if unwrap need to be fixed
|
||||
metrics.commands_successful.lock().unwrap(), // TODO: Check if unwrap need to be fixed
|
||||
metrics.commands_failed.lock().unwrap() // TODO: Check if unwrap need to be fixed
|
||||
)
|
||||
}
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'r> FromData<'r> for Hooks {
|
||||
type Error = WebhookeyError;
|
||||
|
@ -554,46 +604,24 @@ async fn receive_hook<'a>(
|
|||
}
|
||||
|
||||
#[get("/metrics")]
|
||||
async fn metrics(metrics: &State<WebhookeyMetrics>) -> String {
|
||||
format!(
|
||||
r"# HELP webhookey_requests_received Number of requests received
|
||||
# TYPE webhookey_requests_received gauge
|
||||
webhookey_requests_received {:?}
|
||||
# HELP webhookey_requests_invalid Number of invalid requests received
|
||||
# TYPE webhookey_requests_invalid gauge
|
||||
webhookey_requests_invalid {:?}
|
||||
# HELP webhookey_hooks_successful Number of successfully executed hooks
|
||||
# TYPE webhookey_hooks_successful gauge
|
||||
webhookey_hooks_sucessful {:?}
|
||||
# HELP webhookey_hooks_forbidden Number of forbidden requests
|
||||
# TYPE webhookey_hooks_forbidden gauge
|
||||
webhookey_hooks_forbidden {:?}
|
||||
# HELP webhookey_hooks_unmatched Number of unmatched requests
|
||||
# TYPE webhookey_hooks_unmatched gauge
|
||||
webhookey_hooks_unmatched {:?}
|
||||
# HELP webhookey_commands_executed Number of commands executed
|
||||
# TYPE webhookey_commands_executed gauge
|
||||
webhookey_commands_executed {:?}
|
||||
# HELP webhookey_commands_execution_failed Number of commands failed to execute
|
||||
# TYPE webhookey_commands_execution_failed gauge
|
||||
webhookey_commands_execution_failed {:?}
|
||||
# HELP webhookey_commands_successful Number of executed commands returning return code 0
|
||||
# TYPE webhookey_commands_successful gauge
|
||||
webhookey_commands_successful {:?}
|
||||
# HELP webhookey_commands_failed Number of executed commands returning different return code than 0
|
||||
# TYPE webhookey_commands_failed gauge
|
||||
webhookey_commands_failed {:?}
|
||||
",
|
||||
metrics.requests_received.lock().unwrap(), // TODO: Check if unwrap need to be fixed
|
||||
metrics.requests_invalid.lock().unwrap(), // TODO: Check if unwrap need to be fixed
|
||||
metrics.hooks_successful.lock().unwrap(), // TODO: Check if unwrap need to be fixed
|
||||
metrics.hooks_forbidden.lock().unwrap(), // TODO: Check if unwrap need to be fixed
|
||||
metrics.hooks_unmatched.lock().unwrap(), // TODO: Check if unwrap need to be fixed
|
||||
metrics.commands_executed.lock().unwrap(), // TODO: Check if unwrap need to be fixed
|
||||
metrics.commands_execution_failed.lock().unwrap(), // TODO: Check if unwrap need to be fixed
|
||||
metrics.commands_successful.lock().unwrap(), // TODO: Check if unwrap need to be fixed
|
||||
metrics.commands_failed.lock().unwrap() // TODO: Check if unwrap need to be fixed
|
||||
)
|
||||
async fn metrics(
|
||||
address: SocketAddr,
|
||||
metrics: &State<WebhookeyMetrics>,
|
||||
config: &State<Config>,
|
||||
) -> Option<String> {
|
||||
if let Some(metrics_config) = &config.metrics {
|
||||
if let Some(filter) = &metrics_config.ip_filter {
|
||||
if filter.validate(&address.ip()) {
|
||||
return Some(get_metrics(&metrics));
|
||||
}
|
||||
} else {
|
||||
return Some(get_metrics(&metrics));
|
||||
}
|
||||
}
|
||||
|
||||
warn!("Forbidden request for metrics: {:?}", address);
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[rocket::main]
|
||||
|
@ -652,7 +680,10 @@ mod tests {
|
|||
},
|
||||
);
|
||||
|
||||
let config = Config { hooks: hooks };
|
||||
let config = Config {
|
||||
metrics: None,
|
||||
hooks: hooks,
|
||||
};
|
||||
|
||||
let rocket = rocket::build()
|
||||
.mount("/", routes![receive_hook])
|
||||
|
@ -838,7 +869,7 @@ mod tests {
|
|||
);
|
||||
|
||||
let config = Config {
|
||||
// default: None,
|
||||
metrics: None,
|
||||
hooks: hooks,
|
||||
};
|
||||
|
||||
|
@ -896,7 +927,7 @@ mod tests {
|
|||
);
|
||||
|
||||
let config = Config {
|
||||
// default: None,
|
||||
metrics: None,
|
||||
hooks: hooks,
|
||||
};
|
||||
|
||||
|
@ -933,4 +964,133 @@ mod tests {
|
|||
|
||||
assert_eq!(response.await.status(), Status::NotFound);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_config() {
|
||||
let config: Config = serde_yaml::from_str(
|
||||
r#"---
|
||||
hooks:
|
||||
hook1:
|
||||
command: /usr/bin/local/script_xy.sh {{ /field2/foo }} asdfasdf
|
||||
signature: X-Gitea-Signature
|
||||
ip_filter:
|
||||
allow:
|
||||
- 127.0.0.1/31
|
||||
secrets:
|
||||
- secret_key_01
|
||||
- secret_key_02
|
||||
filter:
|
||||
json:
|
||||
pointer: /ref
|
||||
regex: refs/heads/master
|
||||
hook2:
|
||||
command: /usr/bin/local/script_xy.sh asdfasdf
|
||||
signature: X-Gitea-Signature
|
||||
secrets:
|
||||
- secret_key_01
|
||||
- secret_key_02
|
||||
filter:
|
||||
and:
|
||||
- json:
|
||||
pointer: /ref
|
||||
regex: refs/heads/master
|
||||
- json:
|
||||
pointer: /after
|
||||
regex: f6e5fe4fe37df76629112d55cc210718b6a55e7e"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
serde_yaml::to_string(&config).unwrap(),
|
||||
serde_yaml::to_string(&Config {
|
||||
metrics: None,
|
||||
hooks: BTreeMap::from([
|
||||
(
|
||||
"hook1".to_string(),
|
||||
Hook {
|
||||
command: "/usr/bin/local/script_xy.sh {{ /field2/foo }} asdfasdf"
|
||||
.to_string(),
|
||||
signature: "X-Gitea-Signature".to_string(),
|
||||
ip_filter: Some(IpFilter::Allow(vec![AddrType::IpNet(
|
||||
"127.0.0.1/31".parse().unwrap()
|
||||
)])),
|
||||
secrets: vec!["secret_key_01".to_string(), "secret_key_02".to_string()],
|
||||
filter: FilterType::JsonFilter(JsonFilter {
|
||||
pointer: "/ref".to_string(),
|
||||
regex: "refs/heads/master".to_string(),
|
||||
}),
|
||||
}
|
||||
),
|
||||
(
|
||||
"hook2".to_string(),
|
||||
Hook {
|
||||
command: "/usr/bin/local/script_xy.sh asdfasdf".to_string(),
|
||||
signature: "X-Gitea-Signature".to_string(),
|
||||
ip_filter: None,
|
||||
secrets: vec!["secret_key_01".to_string(), "secret_key_02".to_string()],
|
||||
filter: FilterType::And(vec![
|
||||
FilterType::JsonFilter(JsonFilter {
|
||||
pointer: "/ref".to_string(),
|
||||
regex: "refs/heads/master".to_string(),
|
||||
}),
|
||||
FilterType::JsonFilter(JsonFilter {
|
||||
pointer: "/after".to_string(),
|
||||
regex: "f6e5fe4fe37df76629112d55cc210718b6a55e7e".to_string(),
|
||||
}),
|
||||
]),
|
||||
}
|
||||
)
|
||||
])
|
||||
})
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
let config: Config = serde_yaml::from_str(
|
||||
r#"---
|
||||
metrics:
|
||||
enabled: true
|
||||
hooks:
|
||||
hook1:
|
||||
command: /usr/bin/local/script_xy.sh {{ /field2/foo }} asdfasdf
|
||||
signature: X-Gitea-Signature
|
||||
ip_filter:
|
||||
allow:
|
||||
- 127.0.0.1/31
|
||||
secrets:
|
||||
- secret_key_01
|
||||
- secret_key_02
|
||||
filter:
|
||||
json:
|
||||
pointer: /ref
|
||||
regex: refs/heads/master"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
serde_yaml::to_string(&config).unwrap(),
|
||||
serde_yaml::to_string(&Config {
|
||||
metrics: Some(MetricsConfig {
|
||||
enabled: true,
|
||||
ip_filter: None
|
||||
}),
|
||||
hooks: BTreeMap::from([(
|
||||
"hook1".to_string(),
|
||||
Hook {
|
||||
command: "/usr/bin/local/script_xy.sh {{ /field2/foo }} asdfasdf"
|
||||
.to_string(),
|
||||
signature: "X-Gitea-Signature".to_string(),
|
||||
ip_filter: Some(IpFilter::Allow(vec![AddrType::IpNet(
|
||||
"127.0.0.1/31".parse().unwrap()
|
||||
)])),
|
||||
secrets: vec!["secret_key_01".to_string(), "secret_key_02".to_string()],
|
||||
filter: FilterType::JsonFilter(JsonFilter {
|
||||
pointer: "/ref".to_string(),
|
||||
regex: "refs/heads/master".to_string(),
|
||||
}),
|
||||
}
|
||||
),])
|
||||
})
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue