prometheus: introduce a couple simple prometheus stats

This commit is contained in:
Vincent Breitmoser 2019-07-19 20:46:16 +02:00
parent 8a74b74e32
commit a9a6c51fac
No known key found for this signature in database
GPG Key ID: 7BD18320DEADFA11
10 changed files with 277 additions and 6 deletions

38
Cargo.lock generated
View File

@ -527,6 +527,11 @@ name = "fixedbitset"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fnv"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fs2"
version = "0.4.3"
@ -612,6 +617,7 @@ dependencies = [
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"hagrid-database 0.1.0",
"handlebars 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lettre 0.9.2 (git+https://github.com/lettre/lettre)",
"lettre_email 0.9.2 (git+https://github.com/lettre/lettre)",
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
@ -622,6 +628,7 @@ dependencies = [
"rocket 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rocket_codegen 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rocket_contrib 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rocket_prometheus 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"sequoia-openpgp 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.94 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1390,6 +1397,24 @@ dependencies = [
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "prometheus"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"protobuf 2.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "protobuf"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "quick-error"
version = "1.2.2"
@ -1686,6 +1711,15 @@ dependencies = [
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rocket_prometheus"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"prometheus 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rocket 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rustc-demangle"
version = "0.1.15"
@ -2326,6 +2360,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum fast_chemail 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)" = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4"
"checksum filetime 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "2f8c63033fcba1f51ef744505b3cad42510432b904c062afa67ad7ece008429d"
"checksum fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33"
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
"checksum fs2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
"checksum fsevent 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6"
"checksum fsevent-sys 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0"
@ -2415,6 +2450,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b"
"checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
"checksum prometheus 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "605a19be7e14fec3cd6ef79dbb6463790ebfda1bfeab55daba3293d99b407d24"
"checksum protobuf 2.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5f00e4a3cb64ecfeac2c0a73c74c68ae3439d7a6bead3870be56ad5dd2620a6f"
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
"checksum quickcheck 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9c35d9c36a562f37eca96e79f66d5fd56eefbc22560dacc4a864cabd2d277456"
"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
@ -2444,6 +2481,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum rocket_codegen 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "79aa1366f9b2eccddc05971e17c5de7bb75a5431eb12c2b5c66545fd348647f4"
"checksum rocket_contrib 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e0fa5c1392135adc0f96a02ba150ac4c765e27c58dbfd32aa40678e948f6e56f"
"checksum rocket_http 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b1391457ee4e80b40d4b57fa5765c0f2836b20d73bcbee4e3f35d93cf3b80817"
"checksum rocket_prometheus 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8648f031a8cafb1c8dce50899d2080f475fa58e6643bccd92cf87d0e9993df37"
"checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997"

View File

@ -32,6 +32,8 @@ num_cpus = "1.0"
ring = "0.13"
base64 = "0.10"
uuid = "0.7"
rocket_prometheus = "0.2"
lazy_static = "1.3.0"
[dependencies.lettre]
version = "0.9"

View File

@ -16,6 +16,7 @@ token_dir = "state/tokens"
tmp_dir = "state/tmp"
mail_rate_limit = 60
maintenance_file = "state/maintenance"
enable_prometheus = false
[staging]
base-URI = "https://keys.openpgp.org"
@ -31,6 +32,7 @@ token_dir = "tokens"
tmp_dir = "tmp"
mail_rate_limit = 60
maintenance_file = "maintenance"
enable_prometheus = false
[production]
base-URI = "https://keys.openpgp.org"
@ -47,3 +49,4 @@ token_dir = "tokens"
tmp_dir = "tmp"
mail_rate_limit = 3600
maintenance_file = "maintenance"
enable_prometheus = false

85
src/anonymize_utils.rs Normal file
View File

@ -0,0 +1,85 @@
use database::types::Email;
// from https://github.com/mailcheck/mailcheck/wiki/List-of-Popular-Domains
static POPULAR_DOMAINS: &[&str] = &[
/* Default domains included */
"aol.com", "att.net", "comcast.net", "facebook.com", "gmail.com", "gmx.com", "googlemail.com",
"google.com", "hotmail.com", "hotmail.co.uk", "mac.com", "me.com", "mail.com", "msn.com",
"live.com", "sbcglobal.net", "verizon.net", "yahoo.com", "yahoo.co.uk",
/* Other global domains */
"email.com", "fastmail.fm", "games.com" /* AOL */, "gmx.net", "hush.com", "hushmail.com", "icloud.com",
"iname.com", "inbox.com", "lavabit.com", "love.com" /* AOL */, "mailbox.org", "posteo.de", "outlook.com", "pobox.com", "protonmail.ch", "protonmail.com", "tutanota.de", "tutanota.com", "tutamail.com", "tuta.io",
"keemail.me", "rocketmail.com" /* Yahoo */, "safe-mail.net", "wow.com" /* AOL */, "ygm.com" /* AOL */,
"ymail.com" /* Yahoo */, "zoho.com", "yandex.com",
/* United States ISP domains */
"bellsouth.net", "charter.net", "cox.net", "earthlink.net", "juno.com",
/* British ISP domains */
"btinternet.com", "virginmedia.com", "blueyonder.co.uk", "freeserve.co.uk", "live.co.uk",
"ntlworld.com", "o2.co.uk", "orange.net", "sky.com", "talktalk.co.uk", "tiscali.co.uk",
"virgin.net", "wanadoo.co.uk", "bt.com",
/* Domains used in Asia */
"sina.com", "sina.cn", "qq.com", "naver.com", "hanmail.net", "daum.net", "nate.com", "yahoo.co.jp", "yahoo.co.kr", "yahoo.co.id", "yahoo.co.in", "yahoo.com.sg", "yahoo.com.ph", "163.com", "yeah.net", "126.com", "21cn.com", "aliyun.com", "foxmail.com",
/* French ISP domains */
"hotmail.fr", "live.fr", "laposte.net", "yahoo.fr", "wanadoo.fr", "orange.fr", "gmx.fr", "sfr.fr", "neuf.fr", "free.fr",
/* German ISP domains */
"gmx.de", "hotmail.de", "live.de", "online.de", "t-online.de" /* T-Mobile */, "web.de", "yahoo.de",
/* Italian ISP domains */
"libero.it", "virgilio.it", "hotmail.it", "aol.it", "tiscali.it", "alice.it", "live.it", "yahoo.it", "email.it", "tin.it", "poste.it", "teletu.it",
/* Russian ISP domains */
"mail.ru", "rambler.ru", "yandex.ru", "ya.ru", "list.ru",
/* Belgian ISP domains */
"hotmail.be", "live.be", "skynet.be", "voo.be", "tvcablenet.be", "telenet.be",
/* Argentinian ISP domains */
"hotmail.com.ar", "live.com.ar", "yahoo.com.ar", "fibertel.com.ar", "speedy.com.ar", "arnet.com.ar",
/* Domains used in Mexico */
"yahoo.com.mx", "live.com.mx", "hotmail.es", "hotmail.com.mx", "prodigy.net.mx",
/* Domains used in Brazil */
"yahoo.com.br", "hotmail.com.br", "outlook.com.br", "uol.com.br", "bol.com.br", "terra.com.br", "ig.com.br", "itelefonica.com.br", "r7.com", "zipmail.com.br", "globo.com", "globomail.com", "oi.com.br"
];
pub fn anonymize_address(email: &Email) -> Option<String> {
email.as_str()
.rsplit('@')
.next()
.map(|domain| domain.to_lowercase())
.and_then(|domain| {
if POPULAR_DOMAINS.contains(&domain.as_str()) {
Some(domain)
} else {
domain.rsplit('.').next().map(|tld| tld.to_owned())
}
})
}
pub fn anonymize_address_fallback(email: &Email) -> String {
anonymize_address(email).unwrap_or_else(|| "unknown".to_owned())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn known_domain() {
let email = "user@hotmail.be".parse::<Email>().unwrap();
assert_eq!("hotmail.be", anonymize_address(&email).unwrap());
}
#[test]
fn unknown_domain() {
let email = "user@example.org".parse::<Email>().unwrap();
assert_eq!("org", anonymize_address(&email).unwrap());
}
}

92
src/counters.rs Normal file
View File

@ -0,0 +1,92 @@
use lazy_static::lazy_static;
use rocket_prometheus::prometheus;
use anonymize_utils;
use database::types::Email;
lazy_static! {
pub static ref KEY_UPLOAD_NEW: Counter =
Counter::new("key_upload_new", "Uploaded keys (new)");
pub static ref KEY_UPLOAD_UPDATED: Counter =
Counter::new("key_upload_updated", "Uploaded keys (updated)");
pub static ref KEY_UPLOAD_UNCHANGED: Counter =
Counter::new("key_upload_unchanged", "Uploaded keys (unchanged)");
pub static ref KEY_UPLOAD_SECRET: Counter =
Counter::new("key_upload_secret", "Uploaded keys (secret)");
pub static ref KEY_UPLOAD_ERROR: Counter =
Counter::new("key_upload_error", "Uploaded keys (error)");
pub static ref MAIL_SEND_VERIFY: LabelCounter =
LabelCounter::new("mail_send_verify", "Sent verification mails", &["domain"]);
pub static ref MAIL_SEND_MANAGE: LabelCounter =
LabelCounter::new("mail_send_manage", "Sent manage mails", &["domain"]);
pub static ref MAIL_SEND_WELCOME: LabelCounter =
LabelCounter::new("mail_send_welcome", "Sent welcome mails", &["domain"]);
pub static ref KEY_ADDRESS_PUBLISHED: LabelCounter =
LabelCounter::new("key_address_published", "Verified email addresses", &["domain"]);
pub static ref KEY_ADDRESS_UNPUBLISHED: LabelCounter =
LabelCounter::new("key_address_unpublished", "Unpublished email addresses", &["domain"]);
}
pub fn register_counters(registry: &prometheus::Registry) {
MAIL_SEND_VERIFY.register(registry);
MAIL_SEND_MANAGE.register(registry);
MAIL_SEND_WELCOME.register(registry);
KEY_UPLOAD_NEW.register(registry);
KEY_UPLOAD_UPDATED.register(registry);
KEY_UPLOAD_UNCHANGED.register(registry);
KEY_UPLOAD_SECRET.register(registry);
KEY_UPLOAD_ERROR.register(registry);
KEY_ADDRESS_PUBLISHED.register(registry);
KEY_ADDRESS_UNPUBLISHED.register(registry);
}
pub struct LabelCounter {
prometheus_counter: prometheus::IntCounterVec,
}
impl LabelCounter {
fn new(name: &str, help: &str, labels: &[&str]) -> Self {
let opts = prometheus::Opts::new(name, help);
let prometheus_counter = prometheus::IntCounterVec::new(opts, labels).unwrap();
Self { prometheus_counter }
}
fn register(&self, registry: &prometheus::Registry) {
registry.register(Box::new(self.prometheus_counter.clone())).unwrap();
}
fn inc(&self, values: &[&str]) {
self.prometheus_counter.with_label_values(values).inc();
}
pub fn inc_email(&self, email: &Email) {
let anonymized_adddress = anonymize_utils::anonymize_address_fallback(email);
self.inc(&[&anonymized_adddress]);
}
}
pub struct Counter {
prometheus_counter: prometheus::Counter,
}
impl Counter {
fn new(name: &str, help: &str) -> Self {
let opts = prometheus::Opts::new(name, help);
let prometheus_counter = prometheus::Counter::with_opts(opts).unwrap();
Self { prometheus_counter }
}
pub fn inc(&self) {
self.prometheus_counter.inc();
}
fn register(&self, registry: &prometheus::Registry) {
registry.register(Box::new(self.prometheus_counter.clone())).unwrap();
}
}

View File

@ -7,6 +7,7 @@ use lettre_email::{Mailbox,EmailBuilder};
use url;
use serde::Serialize;
use uuid::Uuid;
use counters;
use database::types::Email;
use Result;
@ -90,6 +91,8 @@ impl Service {
domain: self.domain.clone(),
};
counters::MAIL_SEND_VERIFY.inc_email(userid);
self.send(
&vec![userid],
&format!("Verify {} for your key on {}", userid, self.domain),
@ -107,6 +110,8 @@ impl Service {
domain: self.domain.clone(),
};
counters::MAIL_SEND_MANAGE.inc_email(recipient);
self.send(
&[recipient],
&format!("Manage your key on {}", self.domain),
@ -125,6 +130,8 @@ impl Service {
domain: self.domain.clone(),
};
counters::MAIL_SEND_WELCOME.inc_email(userid);
self.send(
&vec![userid],
&format!("Your key upload on {}", self.domain),

View File

@ -20,8 +20,10 @@ extern crate rocket_contrib;
extern crate sequoia_openpgp;
extern crate handlebars;
extern crate lazy_static;
extern crate lettre;
extern crate lettre_email;
extern crate rocket_prometheus;
extern crate tempfile;
extern crate uuid;
@ -32,11 +34,13 @@ extern crate ring;
extern crate hagrid_database as database;
mod mail;
mod anonymize_utils;
mod web;
mod tokens;
mod sealed_state;
mod rate_limiter;
mod dump;
mod counters;
fn main() {
if let Err(e) = web::serve() {

View File

@ -8,6 +8,7 @@ use web::{RequestOrigin, MyResponse, templates::General};
use web::vks_web;
use database::{Database, KeyDatabase, types::Email, types::Fingerprint};
use mail;
use counters;
use rate_limiter::RateLimiter;
use tokens::{self, StatelessSerializable};
@ -186,6 +187,9 @@ pub fn vks_manage_unpublish_or_fail(
) -> Result<MyResponse> {
let verify_token = token_service.check::<StatelessVerifyToken>(&request.token)?;
let email = request.address.parse::<Email>()?;
db.set_email_unpublished(&verify_token.fpr, &email)?;
counters::KEY_ADDRESS_UNPUBLISHED.inc_email(&email);
Ok(vks_manage_key(request_origin, db, request.token.to_owned(), token_service))
}

View File

@ -9,6 +9,8 @@ use rocket::http::uri::Uri;
use rocket_contrib::json::JsonValue;
use rocket::response::status::Custom;
use rocket_prometheus::PrometheusMetrics;
use serde::Serialize;
use handlebars::Handlebars;
@ -16,6 +18,7 @@ use std::path::PathBuf;
use mail;
use tokens;
use counters;
use rate_limiter::RateLimiter;
use database::{Database, KeyDatabase, Query};
@ -332,7 +335,7 @@ pub fn serve() -> Result<()> {
Err(rocket_factory(rocket::ignite())?.launch().into())
}
fn rocket_factory(rocket: rocket::Rocket) -> Result<rocket::Rocket> {
fn rocket_factory(mut rocket: rocket::Rocket) -> Result<rocket::Rocket> {
let routes = routes![
// infra
root,
@ -389,7 +392,9 @@ fn rocket_factory(rocket: rocket::Rocket) -> Result<rocket::Rocket> {
let rate_limiter = configure_rate_limiter(rocket.config())?;
let maintenance_mode = configure_maintenance_mode(rocket.config())?;
Ok(rocket
let prometheus = configure_prometheus(rocket.config());
rocket = rocket
.attach(Template::fairing())
.attach(maintenance_mode)
.manage(hagrid_state)
@ -398,8 +403,24 @@ fn rocket_factory(rocket: rocket::Rocket) -> Result<rocket::Rocket> {
.manage(mail_service)
.manage(db_service)
.manage(rate_limiter)
.mount("/", routes)
)
.mount("/", routes);
if let Some(prometheus) = prometheus {
rocket = rocket
.attach(prometheus.clone())
.mount("/metrics", prometheus);
}
Ok(rocket)
}
fn configure_prometheus(config: &Config) -> Option<PrometheusMetrics> {
if !config.get_bool("enable_prometheus").unwrap_or(false) {
return None;
}
let prometheus = PrometheusMetrics::new();
counters::register_counters(&prometheus.registry());
return Some(prometheus);
}
fn configure_db_service(config: &Config) -> Result<KeyDatabase> {

View File

@ -3,6 +3,7 @@ use failure::Fallible as Result;
use database::{Database, KeyDatabase, StatefulTokens, EmailAddressStatus, TpkStatus, ImportResult};
use database::types::{Fingerprint,Email};
use mail;
use counters;
use tokens::{self, StatelessSerializable};
use rate_limiter::RateLimiter;
use web::RequestOrigin;
@ -111,6 +112,7 @@ pub fn process_key(
tpks.push(match tpk {
Ok(t) => {
if t.is_tsk() {
counters::KEY_UPLOAD_SECRET.inc();
return UploadResponse::err("Whoops, please don't upload secret keys!");
}
t
@ -126,6 +128,17 @@ pub fn process_key(
}
}
fn log_db_merge(import_result: Result<ImportResult>) -> Result<ImportResult> {
match import_result {
Ok(ImportResult::New(_)) => counters::KEY_UPLOAD_NEW.inc(),
Ok(ImportResult::Updated(_)) => counters::KEY_UPLOAD_UPDATED.inc(),
Ok(ImportResult::Unchanged(_)) => counters::KEY_UPLOAD_UNCHANGED.inc(),
Err(_) => counters::KEY_UPLOAD_ERROR.inc(),
};
import_result
}
fn process_key_multiple(
db: &KeyDatabase,
tpks: Vec<TPK>,
@ -134,7 +147,7 @@ fn process_key_multiple(
.into_iter()
.flat_map(|tpk| Fingerprint::try_from(tpk.fingerprint())
.map(|fpr| (fpr, tpk)))
.flat_map(|(fpr, tpk)| db.merge(tpk).map(|_| fpr.to_string()))
.flat_map(|(fpr, tpk)| log_db_merge(db.merge(tpk)).map(|_| fpr.to_string()))
.collect();
response::UploadResponse::OkMulti { key_fprs }
@ -148,7 +161,7 @@ fn process_key_single(
) -> response::UploadResponse {
let fp = Fingerprint::try_from(tpk.fingerprint()).unwrap();
let (tpk_status, is_new_key) = match db.merge(tpk) {
let (tpk_status, is_new_key) = match log_db_merge(db.merge(tpk)) {
Ok(ImportResult::New(tpk_status)) => (tpk_status, true),
Ok(ImportResult::Updated(tpk_status)) => (tpk_status, false),
Ok(ImportResult::Unchanged(tpk_status)) => (tpk_status, false),
@ -266,7 +279,9 @@ fn check_publish_token(
) -> Result<(Fingerprint,Email)> {
let payload = token_service.pop_token("verify", &token)?;
let (fingerprint, email) = serde_json::from_str(&payload)?;
db.set_email_published(&fingerprint, &email)?;
counters::KEY_ADDRESS_PUBLISHED.inc_email(&email);
Ok((fingerprint, email))
}