Skip to content
This repository has been archived by the owner on Sep 13, 2023. It is now read-only.

Commit

Permalink
feat: Add metrics for validation (#42)
Browse files Browse the repository at this point in the history
This change creates a `Validate` trait and then uses it to wrap the existing validation logic.
We then wrap our `Validator` struct using `WithMetrics` to expose whether requests passed/failed/skipped validation.
  • Loading branch information
rikonor authored Jul 18, 2022
1 parent dcaa135 commit 02a0517
Show file tree
Hide file tree
Showing 4 changed files with 450 additions and 263 deletions.
137 changes: 137 additions & 0 deletions src/headers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use ic_utils::interfaces::http_request::HeaderField;
use lazy_regex::regex_captures;

const MAX_LOG_CERT_NAME_SIZE: usize = 100;
const MAX_LOG_CERT_B64_SIZE: usize = 2000;

#[derive(Debug, PartialEq)]
pub struct HeadersData {
pub certificate: Option<Result<Vec<u8>, ()>>,
pub tree: Option<Result<Vec<u8>, ()>>,
pub encoding: Option<String>,
}

pub fn extract_headers_data(headers: &[HeaderField], logger: &slog::Logger) -> HeadersData {
let mut headers_data = HeadersData {
certificate: None,
tree: None,
encoding: None,
};

for HeaderField(name, value) in headers {
if name.eq_ignore_ascii_case("Ic-Certificate") {
for field in value.split(',') {
if let Some((_, name, b64_value)) = regex_captures!("^(.*)=:(.*):$", field.trim()) {
slog::trace!(
logger,
">> certificate {:.l1$}: {:.l2$}",
name,
b64_value,
l1 = MAX_LOG_CERT_NAME_SIZE,
l2 = MAX_LOG_CERT_B64_SIZE
);
let bytes = decode_hash_tree(name, Some(b64_value.to_string()), logger);
if name == "certificate" {
headers_data.certificate = Some(match (headers_data.certificate, bytes) {
(None, bytes) => bytes,
(Some(Ok(certificate)), Ok(bytes)) => {
slog::warn!(logger, "duplicate certificate field: {:?}", bytes);
Ok(certificate)
}
(Some(Ok(certificate)), Err(_)) => {
slog::warn!(
logger,
"duplicate certificate field (failed to decode)"
);
Ok(certificate)
}
(Some(Err(_)), bytes) => {
slog::warn!(
logger,
"duplicate certificate field (failed to decode)"
);
bytes
}
});
} else if name == "tree" {
headers_data.tree = Some(match (headers_data.tree, bytes) {
(None, bytes) => bytes,
(Some(Ok(tree)), Ok(bytes)) => {
slog::warn!(logger, "duplicate tree field: {:?}", bytes);
Ok(tree)
}
(Some(Ok(tree)), Err(_)) => {
slog::warn!(logger, "duplicate tree field (failed to decode)");
Ok(tree)
}
(Some(Err(_)), bytes) => {
slog::warn!(logger, "duplicate tree field (failed to decode)");
bytes
}
});
}
}
}
} else if name.eq_ignore_ascii_case("Content-Encoding") {
let enc = value.trim().to_string();
headers_data.encoding = Some(enc);
}
}

headers_data
}

fn decode_hash_tree(
name: &str,
value: Option<String>,
logger: &slog::Logger,
) -> Result<Vec<u8>, ()> {
match value {
Some(tree) => base64::decode(tree).map_err(|e| {
slog::warn!(logger, "Unable to decode {} from base64: {}", name, e);
}),
_ => Err(()),
}
}

#[cfg(test)]
mod tests {
use ic_utils::interfaces::http_request::HeaderField;
use slog::o;

use super::{extract_headers_data, HeadersData};

#[test]
fn extract_headers_data_simple() {
let logger = slog::Logger::root(slog::Discard, o!());
let headers: Vec<HeaderField> = vec![];

let out = extract_headers_data(&headers, &logger);

assert_eq!(
out,
HeadersData {
certificate: None,
tree: None,
encoding: None,
}
);
}

#[test]
fn extract_headers_data_content_encoding() {
let logger = slog::Logger::root(slog::Discard, o!());
let headers: Vec<HeaderField> = vec![HeaderField("Content-Encoding".into(), "test".into())];

let out = extract_headers_data(&headers, &logger);

assert_eq!(
out,
HeadersData {
certificate: None,
tree: None,
encoding: Some(String::from("test")),
}
);
}
}
Loading

0 comments on commit 02a0517

Please sign in to comment.