Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add ability to track messages based on known VAPID keys #739

Merged
merged 17 commits into from
Sep 20, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: Add ability to track messages based on known VAPID keys
Closes: SYNC-4349
jrconlin committed Jul 22, 2024
commit 5c160cb8a3963578443f0d85c7eb4c18c744fc55
10 changes: 10 additions & 0 deletions autoendpoint/src/extractors/subscription.rs
Original file line number Diff line number Diff line change
@@ -35,6 +35,10 @@ pub struct Subscription {
pub user: User,
pub channel_id: Uuid,
pub vapid: Option<VapidHeaderWithKey>,
/// Should this subscription update be tracked internally?
/// (This should ONLY be applied for messages that match known
/// Mozilla provided VAPID public keys.)
pub trackable: bool,
}

impl FromRequest for Subscription {
@@ -69,6 +73,11 @@ impl FromRequest for Subscription {
.transpose()?;

trace!("raw vapid: {:?}", &vapid);
let trackable = if let Some(vapid) = &vapid {
app_state.settings.is_trackable(&vapid)
} else {
false
};

// Capturing the vapid sub right now will cause too much cardinality. Instead,
// let's just capture if we have a valid VAPID, as well as what sort of bad sub
@@ -127,6 +136,7 @@ impl FromRequest for Subscription {
user,
channel_id,
vapid,
trackable,
})
}
.boxed_local()
1 change: 1 addition & 0 deletions autoendpoint/src/routers/common.rs
Original file line number Diff line number Diff line change
@@ -238,6 +238,7 @@ pub mod tests {
},
channel_id: channel_id(),
vapid: None,
trackable: false,
},
headers: NotificationHeaders {
ttl: 0,
46 changes: 42 additions & 4 deletions autoendpoint/src/settings.rs
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ use fernet::{Fernet, MultiFernet};
use serde::Deserialize;
use url::Url;

use crate::headers::vapid::VapidHeaderWithKey;
use crate::routers::apns::settings::ApnsSettings;
use crate::routers::fcm::settings::FcmSettings;
#[cfg(feature = "stub")]
@@ -31,6 +32,12 @@ pub struct Settings {

pub vapid_aud: Vec<String>,

/// A stringified JSON list of VAPID public keys which should be tracked internally.
/// This should ONLY include Mozilla generated and consumed messages (e.g. "SendToTab", etc.)
pub tracking_keys: String,
/// Cached, parsed tracking keys.
pub tracking_vapid_pubs: Vec<String>,
Copy link
Member

@pjenvey pjenvey Sep 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this isn't an actual setting I think it should live in AppState instead. Then fn tracking_keys won't need to do caching.

(the existing fn auth_keys should probably similarly live in AppState too instead of it reparsing the setting every time but I don't think it's that heavily called).

It'll make it more annoying to test -- but can we just wrap it in a quick tuple struct with an is_trackable method to avoid needing an AppState for the test?

struct VapidTracker(Vec<String);
impl VapidTracker {
    pub fn is_trackable(...
}


pub max_data_bytes: usize,
pub crypto_keys: String,
pub auth_keys: String,
@@ -65,13 +72,15 @@ impl Default for Settings {
"https://push.services.mozilla.org".to_string(),
"http://127.0.0.1:9160".to_string(),
],
tracking_vapid_pubs: vec![],
// max data is a bit hard to figure out, due to encryption. Using something
// like pywebpush, if you encode a block of 4096 bytes, you'll get a
// 4216 byte data block. Since we're going to be receiving this, we have to
// presume base64 encoding, so we can bump things up to 5630 bytes max.
max_data_bytes: 5630,
crypto_keys: format!("[{}]", Fernet::generate_key()),
auth_keys: r#"["AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB="]"#.to_string(),
tracking_keys: r#"[]"#.to_string(),
human_logs: false,
connection_timeout_millis: 1000,
request_timeout_millis: 3000,
@@ -101,9 +110,7 @@ impl Settings {
// down to the sub structures.
config = config.add_source(Environment::with_prefix(ENV_PREFIX).separator("__"));

let built = config.build()?;

built.try_deserialize::<Self>().map_err(|error| {
let mut built: Self = config.build()?.try_deserialize::<Self>().map_err(|error| {
match error {
// Configuration errors are not very sysop friendly, Try to make them
// a bit more 3AM useful.
@@ -122,7 +129,12 @@ impl Settings {
error
}
}
})
})?;

// cache the tracking keys we've built.
built.tracking_vapid_pubs = built.tracking_keys();

Ok(built)
}

/// Convert a string like `[item1,item2]` into a iterator over `item1` and `item2`.
@@ -159,6 +171,22 @@ impl Settings {
.collect()
}

/// Get the list of tracking public keys
pub fn tracking_keys(&self) -> Vec<String> {
// return the cached version if present.
if !self.tracking_vapid_pubs.is_empty() {
return self.tracking_vapid_pubs.clone();
};
let keys = &self.tracking_keys.replace(['"', ' '], "");
Self::read_list_from_str(keys, "Invalid AUTOEND_TRACKING_KEYS")
.map(|v| v.to_owned())
.collect()
}

pub fn is_trackable(&self, vapid: &VapidHeaderWithKey) -> bool {
self.tracking_vapid_pubs.contains(&vapid.public_key)
}

/// Get the URL for this endpoint server
pub fn endpoint_url(&self) -> Url {
let endpoint = if self.endpoint_url.is_empty() {
@@ -199,6 +227,16 @@ mod tests {
Ok(())
}

#[test]
fn test_tracking_keys() -> ApiResult<()> {
let mut settings = Settings{
tracking_keys: r#"["AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB=", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC="]"#.to_owned(),
..Default::default()
};

let result = settings.tracking_keys();
}

#[test]
fn test_endpoint_url() -> ApiResult<()> {
let example = "https://example.org/";