Skip to content

Commit ab9b15c

Browse files
committed
Auto merge of #3858 - rust-lang:revert-3790-pa-rate-limit-updates, r=pietroalbini
Revert "Move new versions rate limits to the application" Reverts #3790 This has been causing issues in production 😢 see https://sentry.io/organizations/rust-lang/issues/2594868566/
2 parents 1d3266a + bf3f653 commit ab9b15c

25 files changed

+531
-1212
lines changed

config/nginx.conf.erb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ http {
176176
client_body_timeout 30;
177177
client_max_body_size 50m;
178178

179+
limit_req_zone $remote_addr zone=publish:10m rate=1r/m;
180+
179181
upstream app_server {
180182
server localhost:8888 fail_timeout=0;
181183
}
@@ -259,5 +261,12 @@ http {
259261
proxy_pass http://app_server;
260262
}
261263
<% end %>
264+
265+
location ~ ^/api/v./crates/new$ {
266+
proxy_pass http://app_server;
267+
268+
limit_req zone=publish burst=30 nodelay;
269+
limit_req_status 429;
270+
}
262271
}
263272
}

migrations/2021-07-18-125813_add_rate_limit_action/down.sql

Lines changed: 0 additions & 9 deletions
This file was deleted.

migrations/2021-07-18-125813_add_rate_limit_action/up.sql

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/app.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use crate::downloads_counter::DownloadsCounter;
88
use crate::email::Emails;
99
use crate::github::GitHubClient;
1010
use crate::metrics::{InstanceMetrics, ServiceMetrics};
11-
use crate::rate_limiter::RateLimiter;
1211
use diesel::r2d2;
1312
use oauth2::basic::BasicClient;
1413
use reqwest::blocking::Client;
@@ -44,8 +43,6 @@ pub struct App {
4443
/// Metrics related to this specific instance of the service
4544
pub instance_metrics: InstanceMetrics,
4645

47-
pub rate_limiter: RateLimiter,
48-
4946
/// A configured client for outgoing HTTP requests
5047
///
5148
/// In production this shares a single connection pool across requests. In tests
@@ -168,7 +165,6 @@ impl App {
168165
read_only_replica_database: replica_database,
169166
github,
170167
github_oauth,
171-
rate_limiter: RateLimiter::new(config.rate_limiter.clone()),
172168
config,
173169
downloads_counter: DownloadsCounter::new(),
174170
emails: Emails::from_environment(),

src/config.rs

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
use crate::rate_limiter::{LimitedAction, RateLimiterConfig};
1+
use crate::publish_rate_limit::PublishRateLimit;
22
use crate::{env, env_optional, uploaders::Uploader, Env};
3-
use std::collections::HashMap;
4-
use std::time::Duration;
53

64
mod base;
75
mod database_pools;
@@ -18,6 +16,7 @@ pub struct Server {
1816
pub gh_base_url: String,
1917
pub max_upload_size: u64,
2018
pub max_unpack_size: u64,
19+
pub publish_rate_limit: PublishRateLimit,
2120
pub blocked_traffic: Vec<(String, Vec<String>)>,
2221
pub max_allowed_page_offset: u32,
2322
pub page_offset_ua_blocklist: Vec<String>,
@@ -28,7 +27,6 @@ pub struct Server {
2827
pub metrics_authorization_token: Option<String>,
2928
pub use_test_database_pool: bool,
3029
pub instance_metrics_log_every_seconds: Option<u64>,
31-
pub rate_limiter: HashMap<LimitedAction, RateLimiterConfig>,
3230
}
3331

3432
impl Default for Server {
@@ -66,34 +64,12 @@ impl Default for Server {
6664
.split(',')
6765
.map(ToString::to_string)
6866
.collect();
69-
7067
let page_offset_ua_blocklist = match env_optional::<String>("WEB_PAGE_OFFSET_UA_BLOCKLIST")
7168
{
7269
None => vec![],
7370
Some(s) if s.is_empty() => vec![],
7471
Some(s) => s.split(',').map(String::from).collect(),
7572
};
76-
77-
// Dynamically load the configuration for all the rate limiting actions. See
78-
// `src/rate_limiter.rs` for their definition.
79-
let mut rate_limiter = HashMap::new();
80-
for action in LimitedAction::VARIANTS {
81-
rate_limiter.insert(
82-
*action,
83-
RateLimiterConfig {
84-
rate: Duration::from_secs(
85-
env_optional(&format!(
86-
"RATE_LIMITER_{}_RATE_SECONDS",
87-
action.env_var_key()
88-
))
89-
.unwrap_or_else(|| action.default_rate_seconds()),
90-
),
91-
burst: env_optional(&format!("RATE_LIMITER_{}_BURST", action.env_var_key()))
92-
.unwrap_or_else(|| action.default_burst()),
93-
},
94-
);
95-
}
96-
9773
Server {
9874
db: DatabasePools::full_from_environment(),
9975
base: Base::from_environment(),
@@ -103,6 +79,7 @@ impl Default for Server {
10379
gh_base_url: "https://api.github.com".to_string(),
10480
max_upload_size: 10 * 1024 * 1024, // 10 MB default file upload size limit
10581
max_unpack_size: 512 * 1024 * 1024, // 512 MB max when decompressed
82+
publish_rate_limit: Default::default(),
10683
blocked_traffic: blocked_traffic(),
10784
max_allowed_page_offset: env_optional("WEB_MAX_ALLOWED_PAGE_OFFSET").unwrap_or(200),
10885
page_offset_ua_blocklist,
@@ -119,7 +96,6 @@ impl Default for Server {
11996
metrics_authorization_token: dotenv::var("METRICS_AUTHORIZATION_TOKEN").ok(),
12097
use_test_database_pool: false,
12198
instance_metrics_log_every_seconds: env_optional("INSTANCE_METRICS_LOG_EVERY_SECONDS"),
122-
rate_limiter,
12399
}
124100
}
125101
}

src/controllers/krate/publish.rs

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ use crate::models::{
1111
NewVersion, Rights, VersionAction,
1212
};
1313

14-
use crate::rate_limiter::LimitedAction;
1514
use crate::render;
1615
use crate::schema::*;
1716
use crate::util::errors::{cargo_err, AppResult};
@@ -73,16 +72,6 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult {
7372
))
7473
})?;
7574

76-
// Check the rate limits on the endpoint. Publishing a new crate uses a different (stricter)
77-
// limiting pool than publishing a new version of an existing crate to prevent mass squatting.
78-
let limited_action = if Crate::by_name(&new_crate.name).execute(&*conn)? == 0 {
79-
LimitedAction::PublishNew
80-
} else {
81-
LimitedAction::PublishExisting
82-
};
83-
app.rate_limiter
84-
.check_rate_limit(user.id, limited_action, &conn)?;
85-
8675
// Create a transaction on the database, if there are no errors,
8776
// commit the transactions to record a new or updated crate.
8877
conn.transaction(|| {
@@ -118,7 +107,8 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult {
118107
};
119108

120109
let license_file = new_crate.license_file.as_deref();
121-
let krate = persist.create_or_update(&conn, user.id)?;
110+
let krate =
111+
persist.create_or_update(&conn, user.id, Some(&app.config.publish_rate_limit))?;
122112

123113
let owners = krate.owners(&conn)?;
124114
if user.rights(req.app(), &owners)? < Rights::Publish {

src/downloads_counter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ mod tests {
438438
name: "foo",
439439
..NewCrate::default()
440440
}
441-
.create_or_update(conn, user.id)
441+
.create_or_update(conn, user.id, None)
442442
.expect("failed to create crate");
443443

444444
Self {

src/lib.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,12 @@ pub mod git;
4444
pub mod github;
4545
pub mod metrics;
4646
pub mod middleware;
47-
pub mod rate_limiter;
47+
mod publish_rate_limit;
4848
pub mod render;
4949
pub mod schema;
5050
pub mod tasks;
5151
mod test_util;
5252
pub mod uploaders;
53-
#[macro_use]
5453
pub mod util;
5554

5655
pub mod controllers;

src/models/action.rs

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
1-
use crate::models::{ApiToken, User, Version};
2-
use crate::schema::*;
31
use chrono::NaiveDateTime;
42
use diesel::prelude::*;
3+
use diesel::{
4+
deserialize::{self, FromSql},
5+
pg::Pg,
6+
serialize::{self, Output, ToSql},
7+
sql_types::Integer,
8+
};
9+
use std::io::Write;
510

6-
pg_enum! {
7-
pub enum VersionAction {
8-
Publish = 0,
9-
Yank = 1,
10-
Unyank = 2,
11-
}
11+
use crate::models::{ApiToken, User, Version};
12+
use crate::schema::*;
13+
14+
#[derive(Debug, Clone, Copy, PartialEq, FromSqlRow, AsExpression)]
15+
#[repr(i32)]
16+
#[sql_type = "Integer"]
17+
pub enum VersionAction {
18+
Publish = 0,
19+
Yank = 1,
20+
Unyank = 2,
1221
}
1322

1423
impl From<VersionAction> for &'static str {
@@ -29,6 +38,23 @@ impl From<VersionAction> for String {
2938
}
3039
}
3140

41+
impl FromSql<Integer, Pg> for VersionAction {
42+
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
43+
match <i32 as FromSql<Integer, Pg>>::from_sql(bytes)? {
44+
0 => Ok(VersionAction::Publish),
45+
1 => Ok(VersionAction::Yank),
46+
2 => Ok(VersionAction::Unyank),
47+
n => Err(format!("unknown version action: {}", n).into()),
48+
}
49+
}
50+
}
51+
52+
impl ToSql<Integer, Pg> for VersionAction {
53+
fn to_sql<W: Write>(&self, out: &mut Output<'_, W, Pg>) -> serialize::Result {
54+
ToSql::<Integer, Pg>::to_sql(&(*self as i32), out)
55+
}
56+
}
57+
3258
#[derive(Debug, Clone, Copy, Queryable, Identifiable, Associations)]
3359
#[belongs_to(Version)]
3460
#[belongs_to(User, foreign_key = "user_id")]

src/models/dependency.rs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
use diesel::deserialize::{self, FromSql};
2+
use diesel::pg::Pg;
3+
use diesel::sql_types::Integer;
4+
15
use crate::models::{Crate, Version};
26
use crate::schema::*;
37

@@ -28,10 +32,23 @@ pub struct ReverseDependency {
2832
pub name: String,
2933
}
3034

31-
pg_enum! {
32-
pub enum DependencyKind {
33-
Normal = 0,
34-
Build = 1,
35-
Dev = 2,
35+
#[derive(Copy, Clone, Serialize, Deserialize, Debug, FromSqlRow)]
36+
#[serde(rename_all = "lowercase")]
37+
#[repr(u32)]
38+
pub enum DependencyKind {
39+
Normal = 0,
40+
Build = 1,
41+
Dev = 2,
42+
// if you add a kind here, be sure to update `from_row` below.
43+
}
44+
45+
impl FromSql<Integer, Pg> for DependencyKind {
46+
fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result<Self> {
47+
match <i32 as FromSql<Integer, Pg>>::from_sql(bytes)? {
48+
0 => Ok(DependencyKind::Normal),
49+
1 => Ok(DependencyKind::Build),
50+
2 => Ok(DependencyKind::Dev),
51+
n => Err(format!("unknown kind: {}", n).into()),
52+
}
3653
}
3754
}

src/models/krate.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::models::{
1515
use crate::util::errors::{cargo_err, AppResult};
1616

1717
use crate::models::helpers::with_count::*;
18+
use crate::publish_rate_limit::PublishRateLimit;
1819
use crate::schema::*;
1920

2021
#[derive(Debug, Queryable, Identifiable, Associations, Clone, Copy)]
@@ -92,7 +93,12 @@ pub struct NewCrate<'a> {
9293
}
9394

9495
impl<'a> NewCrate<'a> {
95-
pub fn create_or_update(self, conn: &PgConnection, uploader: i32) -> AppResult<Crate> {
96+
pub fn create_or_update(
97+
self,
98+
conn: &PgConnection,
99+
uploader: i32,
100+
rate_limit: Option<&PublishRateLimit>,
101+
) -> AppResult<Crate> {
96102
use diesel::update;
97103

98104
self.validate()?;
@@ -102,6 +108,9 @@ impl<'a> NewCrate<'a> {
102108
// To avoid race conditions, we try to insert
103109
// first so we know whether to add an owner
104110
if let Some(krate) = self.save_new_crate(conn, uploader)? {
111+
if let Some(rate_limit) = rate_limit {
112+
rate_limit.check_rate_limit(uploader, conn)?;
113+
}
105114
return Ok(krate);
106115
}
107116

0 commit comments

Comments
 (0)