add more quotas to prevent abuse on cloud

This commit is contained in:
Ruben Fiszel
2025-06-24 06:42:42 +02:00
parent ec5737da4e
commit b92f1e26db
16 changed files with 323 additions and 3 deletions

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM app WHERE workspace_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "08c827d9b2de0b77ce0ea2653760751615112c501b35e931ed817dbefd7c6bdb"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM token WHERE email = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "2f30274b0fe89aa1579b252b990876e5035ca5b31a68fcf08701102a6457e5c4"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM raw_app WHERE workspace_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "3b5295a7c4b99aefa52c9a8ae1e0dd12bf4a0be1bf755caf7a1fa863e7950562"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM flow WHERE workspace_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "52032730f2eeaaeab55305f72bea5481d1c50c2eaa92a97a078239430f0d6c13"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM variable WHERE workspace_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "5a31b32659a0ac6a6ad0e122a4d475787240d6714ddadf16296d2b7bd5fdcb52"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM resource WHERE workspace_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "7c765f50c67b0ef751bafc1bf9279c4cb8a851dfab406ba7611f77773663e9f3"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM workspace WHERE owner = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "9d488c5ba4b9f5203692721d76ec831f5954861a5576e0d8c1c42a9eca90927f"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM script WHERE workspace_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "cb8bde4d92a020278cbae79c5c01a766c198392aceb38fb27e57b73de8f7f279"
}

View File

@@ -917,6 +917,23 @@ async fn create_app_internal<'a>(
raw_app: bool,
mut app: CreateApp,
) -> Result<(sqlx::Transaction<'a, sqlx::Postgres>, String, i64)> {
if *CLOUD_HOSTED {
let nb_apps =
sqlx::query_scalar!("SELECT COUNT(*) FROM app WHERE workspace_id = $1", &w_id)
.fetch_one(&db)
.await?;
if nb_apps.unwrap_or(0) >= 1000 {
return Err(Error::BadRequest(
"You have reached the maximum number of apps (1000) on cloud. Contact support@windmill.dev to increase the limit"
.to_string(),
));
}
if app.summary.len() > 300 {
return Err(Error::BadRequest(
"Summary must be less than 300 characters on cloud".to_string(),
));
}
}
let mut tx = user_db.clone().begin(&authed).await?;
app.policy.on_behalf_of = Some(username_to_permissioned_as(&authed.username));
app.policy.on_behalf_of_email = Some(authed.email.clone());

View File

@@ -34,7 +34,7 @@ use sqlx::{FromRow, Postgres, Transaction};
use windmill_audit::audit_oss::audit_log;
use windmill_audit::ActionKind;
use windmill_common::utils::query_elems_from_hub;
use windmill_common::worker::to_raw_value;
use windmill_common::worker::{to_raw_value, CLOUD_HOSTED};
use windmill_common::HUB_BASE_URL;
use windmill_common::{
db::UserDB,
@@ -358,6 +358,32 @@ async fn create_flow(
Path(w_id): Path<String>,
Json(nf): Json<NewFlow>,
) -> Result<(StatusCode, String)> {
if *CLOUD_HOSTED {
let nb_flows =
sqlx::query_scalar!("SELECT COUNT(*) FROM flow WHERE workspace_id = $1", &w_id)
.fetch_one(&db)
.await?;
if nb_flows.unwrap_or(0) >= 1000 {
return Err(Error::BadRequest(
"You have reached the maximum number of flows (1000) on cloud. Contact support@windmill.dev to increase the limit"
.to_string(),
));
}
if nf.summary.len() > 300 {
return Err(Error::BadRequest(
"Summary must be less than 300 characters on cloud".to_string(),
));
}
if nf
.description
.as_ref()
.is_some_and(|desc| desc.len() > 3000)
{
return Err(Error::BadRequest(
"Description must be less than 3000 characters on cloud".to_string(),
));
}
}
#[cfg(not(feature = "enterprise"))]
if nf
.value

View File

@@ -29,6 +29,7 @@ use windmill_common::{
db::UserDB,
error::{Error, JsonResult, Result},
utils::{not_found_if_none, paginate, Pagination, StripPath},
worker::CLOUD_HOSTED,
};
pub fn workspaced_service() -> Router {
@@ -149,9 +150,29 @@ async fn create_app(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Extension(webhook): Extension<WebhookShared>,
Extension(db): Extension<DB>,
Path(w_id): Path<String>,
Json(app): Json<CreateApp>,
) -> Result<(StatusCode, String)> {
if *CLOUD_HOSTED {
let nb_apps = sqlx::query_scalar!(
"SELECT COUNT(*) FROM raw_app WHERE workspace_id = $1",
&w_id
)
.fetch_one(&db)
.await?;
if nb_apps.unwrap_or(0) >= 1000 {
return Err(Error::BadRequest(
"You have reached the maximum number of apps (1000) on cloud. Contact support@windmill.dev to increase the limit"
.to_string(),
));
}
if app.summary.len() > 300 {
return Err(Error::BadRequest(
"Summary must be less than 300 characters on cloud".to_string(),
));
}
}
let mut tx = user_db.begin(&authed).await?;
if &app.path == "" {
return Err(Error::BadRequest("App path cannot be empty".to_string()));

View File

@@ -33,6 +33,7 @@ use windmill_common::{
error::{Error, JsonResult, Result},
utils::{not_found_if_none, paginate, require_admin, Pagination, StripPath},
variables,
worker::CLOUD_HOSTED,
};
pub fn workspaced_service() -> Router {
@@ -656,6 +657,20 @@ async fn create_resource(
Query(q): Query<CreateResourceQuery>,
Json(resource): Json<CreateResource>,
) -> Result<(StatusCode, String)> {
if *CLOUD_HOSTED {
let nb_resources = sqlx::query_scalar!(
"SELECT COUNT(*) FROM resource WHERE workspace_id = $1",
&w_id
)
.fetch_one(&db)
.await?;
if nb_resources.unwrap_or(0) >= 10000 {
return Err(Error::BadRequest(
"You have reached the maximum number of resources (10000) on cloud. Contact support@windmill.dev to increase the limit"
.to_string(),
));
}
}
let authed = maybe_refresh_folders(&resource.path, &w_id, authed, &db).await;
let mut tx = user_db.begin(&authed).await?;

View File

@@ -42,7 +42,7 @@ use windmill_audit::audit_oss::audit_log;
use windmill_audit::ActionKind;
use windmill_worker::process_relative_imports;
use windmill_common::error::to_anyhow;
use windmill_common::{error::to_anyhow, worker::CLOUD_HOSTED};
use windmill_common::{
db::UserDB,
@@ -520,6 +520,29 @@ async fn create_script_internal<'c>(
.to_string(),
));
}
if *CLOUD_HOSTED {
let nb_scripts =
sqlx::query_scalar!("SELECT COUNT(*) FROM script WHERE workspace_id = $1", &w_id)
.fetch_one(&db)
.await?;
if nb_scripts.unwrap_or(0) >= 5000 {
return Err(Error::BadRequest(
"You have reached the maximum number of scripts (5000) on cloud. Contact support@windmill.dev to increase the limit"
.to_string(),
));
}
if ns.summary.len() > 300 {
return Err(Error::BadRequest(
"Summary must be less than 300 characters on cloud".to_string(),
));
}
if ns.description.len() > 3000 {
return Err(Error::BadRequest(
"Description must be less than 3000 characters on cloud".to_string(),
));
}
}
let script_path = ns.path.clone();
let hash = ScriptHash(hash_script(&ns));
let authed = maybe_refresh_folders(&ns.path, &w_id, authed, &db).await;

View File

@@ -1853,6 +1853,18 @@ async fn create_token(
.fetch_optional(&mut *tx)
.await?
.unwrap_or(false);
if *CLOUD_HOSTED {
let nb_tokens =
sqlx::query_scalar!("SELECT COUNT(*) FROM token WHERE email = $1", &authed.email)
.fetch_one(&db)
.await?;
if nb_tokens.unwrap_or(0) >= 10000 {
return Err(Error::BadRequest(
"You have reached the maximum number of tokens (10000) on cloud. Contact support@windmill.dev to increase the limit"
.to_string(),
));
}
}
sqlx::query!(
"INSERT INTO token
(token, email, label, expiration, super_admin, scopes, workspace_id)

View File

@@ -29,6 +29,7 @@ use windmill_common::{
variables::{
build_crypt, get_reserved_variables, ContextualVariable, CreateVariable, ListableVariable,
},
worker::CLOUD_HOSTED,
};
use lazy_static::lazy_static;
@@ -313,6 +314,20 @@ async fn create_variable(
Query(AlreadyEncrypted { already_encrypted }): Query<AlreadyEncrypted>,
Json(variable): Json<CreateVariable>,
) -> Result<(StatusCode, String)> {
if *CLOUD_HOSTED {
let nb_variables = sqlx::query_scalar!(
"SELECT COUNT(*) FROM variable WHERE workspace_id = $1",
&w_id
)
.fetch_one(&db)
.await?;
if nb_variables.unwrap_or(0) >= 10000 {
return Err(Error::BadRequest(
"You have reached the maximum number of variables (10000) on cloud. Contact support@windmill.dev to increase the limit"
.to_string(),
));
}
}
let authed = maybe_refresh_folders(&variable.path, &w_id, authed, &db).await;
check_path_conflict(&db, &w_id, &variable.path).await?;

View File

@@ -36,7 +36,7 @@ use windmill_common::db::UserDB;
use windmill_common::s3_helpers::LargeFileStorage;
use windmill_common::users::username_to_permissioned_as;
use windmill_common::variables::{build_crypt, decrypt, encrypt};
use windmill_common::worker::to_raw_value;
use windmill_common::worker::{to_raw_value, CLOUD_HOSTED};
#[cfg(feature = "enterprise")]
use windmill_common::workspaces::WorkspaceDeploymentUISettings;
#[cfg(feature = "enterprise")]
@@ -1454,6 +1454,21 @@ async fn create_workspace(
#[cfg(not(feature = "enterprise"))]
_check_nb_of_workspaces(&db).await?;
if *CLOUD_HOSTED {
let nb_workspaces = sqlx::query_scalar!(
"SELECT COUNT(*) FROM workspace WHERE owner = $1",
authed.email
)
.fetch_one(&db)
.await?;
if nb_workspaces.unwrap_or(0) >= 10 {
return Err(Error::BadRequest(
"You have reached the maximum number of workspaces (10) on cloud. Contact support@windmill.dev to increase the limit"
.to_string(),
));
}
}
let mut tx: Transaction<'_, Postgres> = db.begin().await?;
check_w_id_conflict(&mut tx, &nw.id).await?;