Skip to content

Commit 44060f3

Browse files
committed
Auto merge of #1678 - sgrif:sg-monitor-spam, r=jtgeibel
Add monitoring for common spam patterns We've noticed some common patterns in recent spam attacks. While our response time on these has been ok, we can look for some of these common patterns and page whoever is on-call earlier than we'd otherwise notice. The exact patterns we look for is considered sensitive information, and thus not in the repo and should not be discussed publicly. Note that I've opted to look for crates that are likely spam, rather than volume. Volume is more likely to have false positives, and is better handled by more aggressive rate limiting. This assumes that we consider a spam attack to be something we always want to page for. Since we have better coverage of someone watching discord most hours, we could alternatively have this post in a private channel, and let whoever is awake determine if it's worth paging over. If someone does get paged, it's assumed that this will get resolved either by them taking action to remove the crates, or if the crate is legitimate, by updating the config vars to remove that pattern.
2 parents 991f53c + fb3da01 commit 44060f3

File tree

1 file changed

+66
-4
lines changed

1 file changed

+66
-4
lines changed

src/bin/monitor.rs

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,22 @@
88

99
mod on_call;
1010

11-
use cargo_registry::{db, util::CargoResult};
11+
use cargo_registry::{db, schema::*, util::CargoResult};
1212
use diesel::prelude::*;
1313

1414
fn main() -> CargoResult<()> {
1515
let conn = db::connect_now()?;
1616

1717
check_stalled_background_jobs(&conn)?;
18+
check_spam_attack(&conn)?;
1819
Ok(())
1920
}
2021

2122
fn check_stalled_background_jobs(conn: &PgConnection) -> CargoResult<()> {
2223
use cargo_registry::schema::background_jobs::dsl::*;
2324
use diesel::dsl::*;
2425

25-
const BACKGROUND_JOB_KEY: &str = "background_jobs";
26+
const EVENT_KEY: &str = "background_jobs";
2627

2728
println!("Checking for stalled background jobs");
2829

@@ -37,15 +38,15 @@ fn check_stalled_background_jobs(conn: &PgConnection) -> CargoResult<()> {
3738

3839
let event = if stalled_job_count > 0 {
3940
on_call::Event::Trigger {
40-
incident_key: Some(BACKGROUND_JOB_KEY.into()),
41+
incident_key: Some(EVENT_KEY.into()),
4142
description: format!(
4243
"{} jobs have been in the queue for more than {} minutes",
4344
stalled_job_count, max_job_time
4445
),
4546
}
4647
} else {
4748
on_call::Event::Resolve {
48-
incident_key: BACKGROUND_JOB_KEY.into(),
49+
incident_key: EVENT_KEY.into(),
4950
description: Some("No stalled background jobs".into()),
5051
}
5152
};
@@ -54,6 +55,67 @@ fn check_stalled_background_jobs(conn: &PgConnection) -> CargoResult<()> {
5455
Ok(())
5556
}
5657

58+
fn check_spam_attack(conn: &PgConnection) -> CargoResult<()> {
59+
use cargo_registry::models::krate::canon_crate_name;
60+
use diesel::dsl::*;
61+
use diesel::sql_types::Bool;
62+
63+
const EVENT_KEY: &str = "spam_attack";
64+
65+
println!("Checking for crates indicating someone is spamming us");
66+
67+
let bad_crate_names = dotenv::var("SPAM_CRATE_NAMES");
68+
let bad_crate_names: Vec<_> = bad_crate_names
69+
.as_ref()
70+
.map(|s| s.split(',').collect())
71+
.unwrap_or_default();
72+
let bad_author_patterns = dotenv::var("SPAM_AUTHOR_PATTERNS");
73+
let bad_author_patterns: Vec<_> = bad_author_patterns
74+
.as_ref()
75+
.map(|s| s.split(',').collect())
76+
.unwrap_or_default();
77+
78+
let mut event_description = None;
79+
80+
let bad_crate = crates::table
81+
.filter(canon_crate_name(crates::name).eq(any(bad_crate_names)))
82+
.select(crates::name)
83+
.first::<String>(conn)
84+
.optional()?;
85+
86+
if let Some(bad_crate) = bad_crate {
87+
event_description = Some(format!("Crate named {} published", bad_crate));
88+
}
89+
90+
let mut query = version_authors::table
91+
.select(version_authors::name)
92+
.filter(false.into_sql::<Bool>()) // Never return anything if we have no patterns
93+
.into_boxed();
94+
for author_pattern in bad_author_patterns {
95+
query = query.or_filter(version_authors::name.like(author_pattern));
96+
}
97+
let bad_author = query.first::<String>(conn).optional()?;
98+
99+
if let Some(bad_author) = bad_author {
100+
event_description = Some(format!("Crate with author {} published", bad_author));
101+
}
102+
103+
let event = if let Some(event_description) = event_description {
104+
on_call::Event::Trigger {
105+
incident_key: Some(EVENT_KEY.into()),
106+
description: format!("{}, possible spam attack underway", event_description,),
107+
}
108+
} else {
109+
on_call::Event::Resolve {
110+
incident_key: EVENT_KEY.into(),
111+
description: Some("No spam crates detected".into()),
112+
}
113+
};
114+
115+
log_and_trigger_event(event)?;
116+
Ok(())
117+
}
118+
57119
fn log_and_trigger_event(event: on_call::Event) -> CargoResult<()> {
58120
match event {
59121
on_call::Event::Trigger {

0 commit comments

Comments
 (0)