diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 3fdb2fe329..90419a6ea6 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -10538,6 +10538,7 @@ dependencies = [ "magic-crypt", "mail-send", "object_store", + "openidconnect", "prometheus", "rand 0.8.5", "regex", @@ -10836,7 +10837,6 @@ dependencies = [ "nix", "object_store", "once_cell", - "openidconnect", "pem 3.0.4", "postgres-native-tls", "prometheus", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 579dec6e6b..d9ae4e6c91 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -54,7 +54,7 @@ embedding = ["windmill-api/embedding"] parquet = ["windmill-api/parquet", "windmill-common/parquet", "windmill-worker/parquet", "windmill-indexer/parquet", "dep:object_store"] prometheus = ["windmill-common/prometheus", "windmill-api/prometheus", "windmill-worker/prometheus", "windmill-queue/prometheus"] flow_testing = ["windmill-worker/flow_testing"] -openidconnect = ["windmill-api/openidconnect"] +openidconnect = ["windmill-api/openidconnect", "windmill-common/openidconnect"] cloud = ["windmill-queue/cloud", "windmill-worker/cloud"] jemalloc = ["windmill-common/jemalloc", "dep:tikv-jemallocator", "dep:tikv-jemalloc-sys", "dep:tikv-jemalloc-ctl"] tantivy = ["dep:windmill-indexer", "windmill-api/tantivy"] diff --git a/backend/src/ee.rs b/backend/src/ee.rs deleted file mode 100644 index ef944b984b..0000000000 --- a/backend/src/ee.rs +++ /dev/null @@ -1,16 +0,0 @@ -use anyhow::anyhow; -#[cfg(feature = "enterprise")] -use windmill_common::error::{Error, Result}; - -pub async fn set_license_key(_license_key: String) -> anyhow::Result<()> { - // Implementation is not open source - Err(anyhow!("License cannot be set in Windmill CE")) -} - -#[cfg(feature = "enterprise")] -pub async fn verify_license_key() -> Result<()> { - // Implementation is not open source - Err(Error::InternalErr( - "License always invalid in Windmill CE".to_string(), - )) -} diff --git a/backend/src/ee.rs b/backend/src/ee.rs new file mode 120000 index 0000000000..ca288038c6 --- /dev/null +++ b/backend/src/ee.rs @@ -0,0 +1 @@ +/git/windmill/../windmill-ee-private/src/ee.rs \ No newline at end of file diff --git a/backend/windmill-api/Cargo.toml b/backend/windmill-api/Cargo.toml index e4b6b5d71b..843c69ab57 100644 --- a/backend/windmill-api/Cargo.toml +++ b/backend/windmill-api/Cargo.toml @@ -17,7 +17,7 @@ benchmark = [] embedding = ["dep:tinyvector", "dep:hf-hub", "dep:tokenizers", "dep:candle-core", "dep:candle-transformers", "dep:candle-nn"] parquet = ["dep:datafusion", "dep:object_store", "dep:url", "windmill-common/parquet"] prometheus = ["windmill-common/prometheus", "windmill-queue/prometheus", "dep:prometheus"] -openidconnect = ["dep:openidconnect"] +openidconnect = ["dep:openidconnect", "windmill-common/openidconnect"] tantivy = ["dep:windmill-indexer"] [dependencies] diff --git a/backend/windmill-api/src/ee.rs b/backend/windmill-api/src/ee.rs deleted file mode 100644 index b20521e8b4..0000000000 --- a/backend/windmill-api/src/ee.rs +++ /dev/null @@ -1,32 +0,0 @@ -use anyhow::anyhow; -#[cfg(feature = "enterprise")] -use std::sync::Arc; -#[cfg(feature = "enterprise")] -use tokio::sync::RwLock; - -pub async fn validate_license_key(_license_key: String) -> anyhow::Result { - // Implementation is not open source - Err(anyhow!("License can't be validated in Windmill CE")) -} - -#[cfg(feature = "enterprise")] -pub async fn jwt_ext_auth( - _w_id: Option<&String>, - _token: &str, - _external_jwks: Option>>, -) -> anyhow::Result<(crate::db::ApiAuthed, usize)> { - // Implementation is not open source - - Err(anyhow!("External JWT auth is not open source")) -} - -#[cfg(feature = "enterprise")] -pub struct ExternalJwks; - -#[cfg(feature = "enterprise")] -impl ExternalJwks { - pub async fn load() -> Option>> { - // Implementation is not open source - None - } -} diff --git a/backend/windmill-api/src/ee.rs b/backend/windmill-api/src/ee.rs new file mode 120000 index 0000000000..0267c6e6fd --- /dev/null +++ b/backend/windmill-api/src/ee.rs @@ -0,0 +1 @@ +/git/windmill/../windmill-ee-private/windmill-api/src/ee.rs \ No newline at end of file diff --git a/backend/windmill-api/src/indexer_ee.rs b/backend/windmill-api/src/indexer_ee.rs deleted file mode 100644 index 61a946bb84..0000000000 --- a/backend/windmill-api/src/indexer_ee.rs +++ /dev/null @@ -1,5 +0,0 @@ -use axum::Router; - -pub fn workspaced_service() -> Router { - Router::new() -} diff --git a/backend/windmill-api/src/indexer_ee.rs b/backend/windmill-api/src/indexer_ee.rs new file mode 120000 index 0000000000..8a23d0ad13 --- /dev/null +++ b/backend/windmill-api/src/indexer_ee.rs @@ -0,0 +1 @@ +/git/windmill/../windmill-ee-private/windmill-api/src/indexer_ee.rs \ No newline at end of file diff --git a/backend/windmill-api/src/job_helpers_ee.rs b/backend/windmill-api/src/job_helpers_ee.rs deleted file mode 100644 index 61a946bb84..0000000000 --- a/backend/windmill-api/src/job_helpers_ee.rs +++ /dev/null @@ -1,5 +0,0 @@ -use axum::Router; - -pub fn workspaced_service() -> Router { - Router::new() -} diff --git a/backend/windmill-api/src/job_helpers_ee.rs b/backend/windmill-api/src/job_helpers_ee.rs new file mode 120000 index 0000000000..8cc4fbe8bf --- /dev/null +++ b/backend/windmill-api/src/job_helpers_ee.rs @@ -0,0 +1 @@ +/git/windmill/../windmill-ee-private/windmill-api/src/job_helpers_ee.rs \ No newline at end of file diff --git a/backend/windmill-api/src/oauth2_ee.rs b/backend/windmill-api/src/oauth2_ee.rs deleted file mode 100644 index 39960cec92..0000000000 --- a/backend/windmill-api/src/oauth2_ee.rs +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Author: Ruben Fiszel - * Copyright: Windmill Labs, Inc 2022 - * This file and its contents are licensed under the AGPLv3 License. - * Please see the included NOTICE for copyright information and - * LICENSE-AGPL for a copy of the license. - */ - -use std::{collections::HashMap, fmt::Debug}; - -use axum::{routing::get, Json, Router}; -use hmac::Mac; -use hyper::HeaderMap; - -use itertools::Itertools; -use oauth2::{Client as OClient, *}; -use serde::{Deserialize, Serialize}; -use sqlx::{Postgres, Transaction}; -use windmill_common::more_serde::maybe_number_opt; - -use crate::OAUTH_CLIENTS; -use windmill_common::error; -use windmill_common::oauth2::*; - -use crate::db::DB; -use std::str; - -pub fn global_service() -> Router { - Router::new() - .route("/list_supabase", get(list_supabase)) - .route("/list_logins", get(list_logins)) - .route("/list_connects", get(list_connects)) -} - -pub fn workspaced_service() -> Router { - Router::new() -} - -#[derive(Serialize)] -#[serde(tag = "type")] -pub enum InstanceEvent { - UserAdded { email: String }, - // UserDeleted { email: String }, - // UserDeletedWorkspace { workspace: String, email: String }, - UserAddedWorkspace { workspace: String, email: String }, - UserInvitedWorkspace { workspace: String, email: String }, - UserJoinedWorkspace { workspace: String, email: String, username: String }, -} - -#[derive(Debug, Clone)] -pub struct ClientWithScopes { - _client: OClient, - _scopes: Vec, - _extra_params: Option>, - _extra_params_callback: Option>, - _allowed_domains: Option>, - _userinfo_url: Option, -} - -pub type BasicClientsMap = HashMap; - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct OAuthConfig { - auth_url: String, - token_url: String, - userinfo_url: Option, - scopes: Option>, - extra_params: Option>, - extra_params_callback: Option>, - req_body_auth: Option, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct OAuthClient { - id: String, - secret: String, - allowed_domains: Option>, - connect_config: Option, - login_config: Option, -} - -#[derive(Debug)] -pub struct AllClients { - pub logins: BasicClientsMap, - pub connects: BasicClientsMap, - pub slack: Option, -} - -pub fn build_oauth_clients( - _base_url: &str, - _oauths_from_config: Option>, -) -> anyhow::Result { - // Implementation is not open source - return Ok(AllClients { - logins: HashMap::default(), - connects: HashMap::default(), - slack: None, - }); -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct TokenResponse { - access_token: AccessToken, - #[serde(deserialize_with = "maybe_number_opt")] - #[serde(default)] - expires_in: Option, - refresh_token: Option, - #[serde(deserialize_with = "helpers::deserialize_space_delimited_vec")] - #[serde(serialize_with = "helpers::serialize_space_delimited_vec")] - #[serde(default)] - scope: Option>, -} - -#[derive(Serialize)] -struct Logins { - oauth: Vec, - saml: Option, -} -async fn list_logins() -> error::JsonResult { - // Implementation is not open source - return Ok(Json(Logins { oauth: vec![], saml: None })); -} - -async fn list_connects() -> error::JsonResult> { - Ok(Json( - (&OAUTH_CLIENTS.read().await.connects) - .keys() - .map(|x| x.to_owned()) - .collect_vec(), - )) -} - -pub async fn _refresh_token<'c>( - _tx: Transaction<'c, Postgres>, - _path: &str, - _w_id: &str, - _id: i32, - _db: &DB, -) -> error::Result { - // Implementation is not open source - Err(error::Error::BadRequest( - "Not implemented in Windmill's Open Source repository".to_string(), - )) -} - -async fn list_supabase(_headers: HeaderMap) -> error::Result { - // Implementation is not open source - Err(error::Error::BadRequest( - "Not implemented in Windmill's Open Source repository".to_string(), - )) -} - -pub async fn check_nb_of_user(db: &DB) -> error::Result<()> { - let nb_users_sso = - sqlx::query_scalar!("SELECT COUNT(*) FROM password WHERE login_type != 'password'",) - .fetch_one(db) - .await?; - if nb_users_sso.unwrap_or(0) >= 10 { - return Err(error::Error::BadRequest( - "You have reached the maximum number of oauth users accounts (10) without an enterprise license" - .to_string(), - )); - } - - let nb_users = sqlx::query_scalar!("SELECT COUNT(*) FROM password",) - .fetch_one(db) - .await?; - if nb_users.unwrap_or(0) >= 50 { - return Err(error::Error::BadRequest( - "You have reached the maximum number of accounts (50) without an enterprise license" - .to_string(), - )); - } - return Ok(()); -} - -#[derive(Clone, Debug)] -pub struct SlackVerifier { - _mac: HmacSha256, -} - -impl SlackVerifier { - pub fn new>(secret: S) -> anyhow::Result { - HmacSha256::new_from_slice(secret.as_ref()) - .map(|mac| SlackVerifier { _mac: mac }) - .map_err(|_| anyhow::anyhow!("invalid secret")) - } -} diff --git a/backend/windmill-api/src/oauth2_ee.rs b/backend/windmill-api/src/oauth2_ee.rs new file mode 120000 index 0000000000..1ab1665454 --- /dev/null +++ b/backend/windmill-api/src/oauth2_ee.rs @@ -0,0 +1 @@ +/git/windmill/../windmill-ee-private/windmill-api/src/oauth2_ee.rs \ No newline at end of file diff --git a/backend/windmill-api/src/oidc_ee.rs b/backend/windmill-api/src/oidc_ee.rs deleted file mode 100644 index 248b990f54..0000000000 --- a/backend/windmill-api/src/oidc_ee.rs +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Author: Ruben Fiszel - * Copyright: Windmill Labs, Inc 2023 - * This file and its contents are licensed under the AGPLv3 License. - * Please see the included NOTICE for copyright information and - * LICENSE-AGPL for a copy of the license. - */ - -use axum::Router; - -pub fn global_service() -> Router { - Router::new() -} - -pub fn workspaced_service() -> Router { - Router::new() -} diff --git a/backend/windmill-api/src/oidc_ee.rs b/backend/windmill-api/src/oidc_ee.rs new file mode 120000 index 0000000000..1e902de563 --- /dev/null +++ b/backend/windmill-api/src/oidc_ee.rs @@ -0,0 +1 @@ +/git/windmill/../windmill-ee-private/windmill-api/src/oidc_ee.rs \ No newline at end of file diff --git a/backend/windmill-api/src/saml_ee.rs b/backend/windmill-api/src/saml_ee.rs deleted file mode 100644 index b3f1d4653c..0000000000 --- a/backend/windmill-api/src/saml_ee.rs +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Author: Ruben Fiszel - * Copyright: Windmill Labs, Inc 2023 - * This file and its contents are licensed under the AGPLv3 License. - * Please see the included NOTICE for copyright information and - * LICENSE-AGPL for a copy of the license. - */ -#![allow(non_snake_case)] - -use axum::{routing::post, Router}; - -pub struct ServiceProviderExt(); - -pub async fn build_sp_extension() -> anyhow::Result { - return Ok(ServiceProviderExt()); -} - -pub fn global_service() -> Router { - Router::new().route("/acs", post(acs)) -} - -pub async fn acs() -> String { - // Implementation is not open source as it is a Windmill Enterprise Edition feature - "SAML available only in enterprise version".to_string() -} diff --git a/backend/windmill-api/src/saml_ee.rs b/backend/windmill-api/src/saml_ee.rs new file mode 120000 index 0000000000..65286e3f12 --- /dev/null +++ b/backend/windmill-api/src/saml_ee.rs @@ -0,0 +1 @@ +/git/windmill/../windmill-ee-private/windmill-api/src/saml_ee.rs \ No newline at end of file diff --git a/backend/windmill-api/src/scim_ee.rs b/backend/windmill-api/src/scim_ee.rs deleted file mode 100644 index f11097f874..0000000000 --- a/backend/windmill-api/src/scim_ee.rs +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Author: Ruben Fiszel - * Copyright: Windmill Labs, Inc 2023 - * This file and its contents are licensed under the AGPLv3 License. - * Please see the included NOTICE for copyright information and - * LICENSE-AGPL for a copy of the license. - */ - -use axum::{middleware::Next, response::Response, routing::get, Router}; -use hyper::Request; - -pub fn global_service() -> Router { - Router::new().route("/ee", get(ee)) -} - -pub async fn ee() -> String { - return "Enterprise Edition".to_string(); -} - -pub async fn has_scim_token(_request: Request, _next: Next) -> Response { - //Not implemented in open-source version - todo!() -} diff --git a/backend/windmill-api/src/scim_ee.rs b/backend/windmill-api/src/scim_ee.rs new file mode 120000 index 0000000000..cd27dc4526 --- /dev/null +++ b/backend/windmill-api/src/scim_ee.rs @@ -0,0 +1 @@ +/git/windmill/../windmill-ee-private/windmill-api/src/scim_ee.rs \ No newline at end of file diff --git a/backend/windmill-api/src/smtp_server_ee.rs b/backend/windmill-api/src/smtp_server_ee.rs deleted file mode 100644 index 88837c5974..0000000000 --- a/backend/windmill-api/src/smtp_server_ee.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::{db::DB, users::AuthCache}; -use std::{net::SocketAddr, sync::Arc}; -use windmill_common::db::UserDB; - -pub struct SmtpServer { - pub auth_cache: Arc, - pub db: DB, - pub user_db: UserDB, - pub rsmq: Option, - pub base_internal_url: String, -} - -impl SmtpServer { - pub async fn start_listener_thread(self: Arc, _addr: SocketAddr) -> anyhow::Result<()> { - Err(anyhow::anyhow!("Implementation not open source")) - } -} diff --git a/backend/windmill-api/src/smtp_server_ee.rs b/backend/windmill-api/src/smtp_server_ee.rs new file mode 120000 index 0000000000..4cbbfd0c62 --- /dev/null +++ b/backend/windmill-api/src/smtp_server_ee.rs @@ -0,0 +1 @@ +/git/windmill/../windmill-ee-private/windmill-api/src/smtp_server_ee.rs \ No newline at end of file diff --git a/backend/windmill-api/src/stripe_ee.rs b/backend/windmill-api/src/stripe_ee.rs deleted file mode 100644 index 1aea2ecd2d..0000000000 --- a/backend/windmill-api/src/stripe_ee.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[cfg(feature = "stripe")] -use axum::Router; - -#[cfg(feature = "stripe")] -pub fn add_stripe_routes(router: Router) -> Router { - return router; -} diff --git a/backend/windmill-api/src/stripe_ee.rs b/backend/windmill-api/src/stripe_ee.rs new file mode 120000 index 0000000000..a6c7d1230e --- /dev/null +++ b/backend/windmill-api/src/stripe_ee.rs @@ -0,0 +1 @@ +/git/windmill/../windmill-ee-private/windmill-api/src/stripe_ee.rs \ No newline at end of file diff --git a/backend/windmill-audit/src/audit_ee.rs b/backend/windmill-audit/src/audit_ee.rs deleted file mode 100644 index 97a8e6bbcf..0000000000 --- a/backend/windmill-audit/src/audit_ee.rs +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Author: Ruben Fiszel - * Copyright: Windmill Labs, Inc 2022 - * This file and its contents are licensed under the AGPLv3 License. - * Please see the included NOTICE for copyright information and - * LICENSE-AGPL for a copy of the license. - */ -use std::collections::HashMap; - -use windmill_common::{ - error::{Error, Result}, - utils::Pagination, -}; - -use crate::{ActionKind, AuditLog, ListAuditLogQuery}; -use sqlx::{Postgres, Transaction}; - -#[derive(Clone)] -pub struct AuditAuthor { - pub username: String, - pub email: String, - pub username_override: Option, -} - -impl AuditAuthorable for AuditAuthor { - fn email(&self) -> &str { - &self.email - } - - fn username(&self) -> &str { - &self.username - } - - fn username_override(&self) -> Option<&str> { - self.username_override.as_deref() - } -} - -pub trait AuditAuthorable { - fn username(&self) -> &str; - fn email(&self) -> &str; - fn username_override(&self) -> Option<&str>; -} - -#[tracing::instrument(level = "trace", skip_all)] -pub async fn audit_log<'c, E: sqlx::Executor<'c, Database = Postgres>>( - _db: E, - _author: &impl AuditAuthorable, - mut _operation: &str, - _action_kind: ActionKind, - _w_id: &str, - mut _resource: Option<&str>, - _parameters: Option>, -) -> Result<()> { - // Implementation is not open source as Audit logs is a Windmill Enterprise Edition feature - Ok(()) -} - -pub async fn list_audit( - _tx: Transaction<'_, Postgres>, - _w_id: String, - _pagination: Pagination, - _lq: ListAuditLogQuery, -) -> Result> { - // Implementation is not open source as Audit logs is a Windmill Enterprise Edition feature - return Ok(vec![]); -} - -pub async fn get_audit(tx: Transaction<'_, Postgres>, _id: i32, _w_id: &str) -> Result { - // Implementation is not open source as Audit logs is a Windmill Enterprise Edition feature - tx.commit().await?; - Err(Error::NotFound( - "Audit log not not available in Windmill Community edition".to_string(), - )) -} diff --git a/backend/windmill-audit/src/audit_ee.rs b/backend/windmill-audit/src/audit_ee.rs new file mode 120000 index 0000000000..f8d4a81d03 --- /dev/null +++ b/backend/windmill-audit/src/audit_ee.rs @@ -0,0 +1 @@ +/git/windmill/../windmill-ee-private/windmill-audit/src/audit_ee.rs \ No newline at end of file diff --git a/backend/windmill-common/Cargo.toml b/backend/windmill-common/Cargo.toml index 546c339575..1e1575798d 100644 --- a/backend/windmill-common/Cargo.toml +++ b/backend/windmill-common/Cargo.toml @@ -13,6 +13,7 @@ flamegraph = ["dep:tracing-flame"] loki = ["dep:tracing-loki"] benchmark = [] parquet = ["dep:object_store", "dep:aws-config", "dep:aws-sdk-sts"] +openidconnect = ["dep:openidconnect"] [lib] name = "windmill_common" @@ -57,6 +58,7 @@ futures-core.workspace = true async-stream.workspace = true const_format.workspace = true crc.workspace = true +openidconnect = { workspace = true, optional = true} [target.'cfg(not(target_env = "msvc"))'.dependencies] tikv-jemalloc-ctl = { optional = true, workspace = true } diff --git a/backend/windmill-common/src/ee.rs b/backend/windmill-common/src/ee.rs deleted file mode 100644 index 6df93aca78..0000000000 --- a/backend/windmill-common/src/ee.rs +++ /dev/null @@ -1,86 +0,0 @@ -#[cfg(feature = "enterprise")] -use crate::db::DB; -use crate::ee::LicensePlan::Community; -#[cfg(feature = "enterprise")] -use crate::error; -use serde::Deserialize; -use std::sync::Arc; -use tokio::sync::RwLock; - -lazy_static::lazy_static! { - pub static ref LICENSE_KEY_VALID: Arc> = Arc::new(RwLock::new(true)); - pub static ref LICENSE_KEY_ID: Arc> = Arc::new(RwLock::new("".to_string())); - pub static ref LICENSE_KEY: Arc> = Arc::new(RwLock::new("".to_string())); -} - -pub enum LicensePlan { - Community, - Pro, - Enterprise, -} - -pub async fn get_license_plan() -> LicensePlan { - // Implementation is not open source - return Community; -} - -#[derive(Deserialize)] -#[serde(untagged)] -pub enum CriticalErrorChannel { - Email { email: String }, - Slack { slack_channel: String }, -} - -pub enum CriticalAlertKind { - #[cfg(feature = "enterprise")] - CriticalError, - #[cfg(feature = "enterprise")] - RecoveredCriticalError, -} - -#[cfg(feature = "enterprise")] -pub async fn send_critical_alert( - _error_message: String, - _db: &DB, - _kind: CriticalAlertKind, - _channels: Option>, -) { -} - -#[cfg(feature = "enterprise")] -pub async fn schedule_key_renewal(_http_client: &reqwest::Client, _db: &crate::db::DB) -> () { - // Implementation is not open source -} - -#[cfg(feature = "enterprise")] -pub enum RenewReason { - Manual, - Schedule, - OnStart, -} - -#[cfg(feature = "enterprise")] -pub async fn renew_license_key( - _http_client: &reqwest::Client, - _db: &crate::db::DB, - _key: Option, - _reason: RenewReason, -) -> String { - // Implementation is not open source - "".to_string() -} - -#[cfg(feature = "enterprise")] -pub async fn create_customer_portal_session( - _http_client: &reqwest::Client, - _key: Option, -) -> error::Result { - // Implementation is not open source - Ok("".to_string()) -} - -#[cfg(feature = "enterprise")] -pub async fn worker_groups_alerts(_db: &DB) {} - -#[cfg(feature = "enterprise")] -pub async fn jobs_waiting_alerts(_db: &DB) {} diff --git a/backend/windmill-common/src/ee.rs b/backend/windmill-common/src/ee.rs new file mode 120000 index 0000000000..3220066a5d --- /dev/null +++ b/backend/windmill-common/src/ee.rs @@ -0,0 +1 @@ +/git/windmill/../windmill-ee-private/windmill-common/src/ee.rs \ No newline at end of file diff --git a/backend/windmill-common/src/ee.rs~main b/backend/windmill-common/src/ee.rs~main deleted file mode 100644 index 482b61a0fe..0000000000 --- a/backend/windmill-common/src/ee.rs~main +++ /dev/null @@ -1,73 +0,0 @@ -#[cfg(feature = "enterprise")] -use crate::db::DB; -use crate::ee::LicensePlan::Community; -#[cfg(feature = "enterprise")] -use crate::error; -use serde::Deserialize; -use std::sync::Arc; -use tokio::sync::RwLock; - -lazy_static::lazy_static! { - pub static ref LICENSE_KEY_VALID: Arc> = Arc::new(RwLock::new(true)); - pub static ref LICENSE_KEY_ID: Arc> = Arc::new(RwLock::new("".to_string())); - pub static ref LICENSE_KEY: Arc> = Arc::new(RwLock::new("".to_string())); -} - -pub enum LicensePlan { - Community, - Pro, - Enterprise, -} - -pub async fn get_license_plan() -> LicensePlan { - // Implementation is not open source - return Community; -} - -#[derive(Deserialize)] -#[serde(untagged)] -pub enum CriticalErrorChannel {} - -pub enum CriticalAlertKind { - #[cfg(feature = "enterprise")] - CriticalError, - #[cfg(feature = "enterprise")] - RecoveredCriticalError, -} - -#[cfg(feature = "enterprise")] -pub async fn send_critical_alert( - _error_message: String, - _db: &DB, - _kind: CriticalAlertKind, - _channels: Option>, -) { -} - -#[cfg(feature = "enterprise")] -pub async fn schedule_key_renewal(_http_client: &reqwest::Client, _db: &crate::db::DB) -> () { - // Implementation is not open source -} - -#[cfg(feature = "enterprise")] -pub async fn renew_license_key( - _http_client: &reqwest::Client, - _db: &crate::db::DB, - _key: Option, - _manual: bool, -) -> String { - // Implementation is not open source - "".to_string() -} - -#[cfg(feature = "enterprise")] -pub async fn create_customer_portal_session( - _http_client: &reqwest::Client, - _key: Option, -) -> error::Result { - // Implementation is not open source - Ok("".to_string()) -} - -#[cfg(feature = "enterprise")] -pub async fn worker_groups_alerts(_db: &DB) {} diff --git a/backend/windmill-common/src/ee.rs~main_0 b/backend/windmill-common/src/ee.rs~main_0 deleted file mode 100644 index 2f32756e45..0000000000 --- a/backend/windmill-common/src/ee.rs~main_0 +++ /dev/null @@ -1,76 +0,0 @@ -#[cfg(feature = "enterprise")] -use crate::db::DB; -use crate::ee::LicensePlan::Community; -#[cfg(feature = "enterprise")] -use crate::error; -use serde::Deserialize; -use std::sync::Arc; -use tokio::sync::RwLock; - -lazy_static::lazy_static! { - pub static ref LICENSE_KEY_VALID: Arc> = Arc::new(RwLock::new(true)); - pub static ref LICENSE_KEY_ID: Arc> = Arc::new(RwLock::new("".to_string())); - pub static ref LICENSE_KEY: Arc> = Arc::new(RwLock::new("".to_string())); -} - -pub enum LicensePlan { - Community, - Pro, - Enterprise, -} - -pub async fn get_license_plan() -> LicensePlan { - // Implementation is not open source - return Community; -} - -#[derive(Deserialize)] -#[serde(untagged)] -pub enum CriticalErrorChannel { - Email { email: String }, - Slack { slack_channel: String }, -} - -pub enum CriticalAlertKind { - #[cfg(feature = "enterprise")] - CriticalError, - #[cfg(feature = "enterprise")] - RecoveredCriticalError, -} - -#[cfg(feature = "enterprise")] -pub async fn send_critical_alert( - _error_message: String, - _db: &DB, - _kind: CriticalAlertKind, - _channels: Option>, -) { -} - -#[cfg(feature = "enterprise")] -pub async fn schedule_key_renewal(_http_client: &reqwest::Client, _db: &crate::db::DB) -> () { - // Implementation is not open source -} - -#[cfg(feature = "enterprise")] -pub async fn renew_license_key( - _http_client: &reqwest::Client, - _db: &crate::db::DB, - _key: Option, - _manual: bool, -) -> String { - // Implementation is not open source - "".to_string() -} - -#[cfg(feature = "enterprise")] -pub async fn create_customer_portal_session( - _http_client: &reqwest::Client, - _key: Option, -) -> error::Result { - // Implementation is not open source - Ok("".to_string()) -} - -#[cfg(feature = "enterprise")] -pub async fn worker_groups_alerts(_db: &DB) {} diff --git a/backend/windmill-common/src/job_s3_helpers_ee.rs b/backend/windmill-common/src/job_s3_helpers_ee.rs deleted file mode 100644 index b00c00a583..0000000000 --- a/backend/windmill-common/src/job_s3_helpers_ee.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::future::Future; - -use crate::{ - error::Error, - s3_helpers::{ObjectStoreResource, StorageResourceType}, -}; - -pub async fn get_s3_resource_internal<'c, F, Fut>( - _resource_type: StorageResourceType, - _s3_resource_value_raw: serde_json::Value, - _gen_token: F, -) -> crate::error::Result -where - F: FnOnce(String) -> Fut, - Fut: Future> + Send + 'static, -{ - todo!() -} diff --git a/backend/windmill-common/src/job_s3_helpers_ee.rs b/backend/windmill-common/src/job_s3_helpers_ee.rs new file mode 120000 index 0000000000..6c8430be70 --- /dev/null +++ b/backend/windmill-common/src/job_s3_helpers_ee.rs @@ -0,0 +1 @@ +/git/windmill/../windmill-ee-private/windmill-common/src/job_s3_helpers_ee.rs \ No newline at end of file diff --git a/backend/windmill-common/src/lib.rs b/backend/windmill-common/src/lib.rs index 0f25e12e46..0e63e8b9ff 100644 --- a/backend/windmill-common/src/lib.rs +++ b/backend/windmill-common/src/lib.rs @@ -32,6 +32,8 @@ pub mod job_s3_helpers_ee; pub mod jobs; pub mod more_serde; pub mod oauth2; +#[cfg(feature = "openidconnect")] +pub mod oidc_ee; pub mod s3_helpers; pub mod auth; diff --git a/backend/windmill-common/src/oidc_ee.rs b/backend/windmill-common/src/oidc_ee.rs new file mode 100644 index 0000000000..70d26435b8 --- /dev/null +++ b/backend/windmill-common/src/oidc_ee.rs @@ -0,0 +1,170 @@ +/* + * Author: Ruben Fiszel + * Copyright: Windmill Labs, Inc 2023 + * This file and its contents are licensed under the AGPLv3 License. + * Please see the included NOTICE for copyright information and + * LICENSE-AGPL for a copy of the license. + */ + +#[cfg(feature = "openidconnect")] +use anyhow; +#[cfg(feature = "openidconnect")] +use std::process::Command; + +#[cfg(feature = "openidconnect")] +use openidconnect::{ + core::{CoreJwsSigningAlgorithm, CoreRsaPrivateSigningKey}, + IssuerUrl, JsonWebKeyId, +}; + +#[cfg(all(feature = "enterprise", feature = "openidconnect"))] +use openidconnect::{ + core::{ + CoreClaimName, CoreJsonWebKeySet, CoreProviderMetadata, CoreResponseType, + CoreSubjectIdentifierType, + }, + AuthUrl, EmptyAdditionalProviderMetadata, JsonWebKeySetUrl, ResponseTypes, +}; + +#[cfg(feature = "openidconnect")] +use openidconnect::AdditionalClaims; + +#[cfg(feature = "openidconnect")] +use crate::db::DB; + +#[cfg(all(feature = "enterprise", feature = "openidconnect"))] +use axum::extract::Path; +#[cfg(all(feature = "enterprise", feature = "openidconnect"))] +use axum::routing::{get, post}; +#[cfg(all(feature = "enterprise", feature = "openidconnect"))] +use axum::Extension; +#[cfg(all(feature = "enterprise", feature = "openidconnect"))] +use axum::Json; + +use axum::Router; +use serde::{Deserialize, Serialize}; + +#[cfg(all(feature = "enterprise", feature = "openidconnect"))] +pub async fn generate_id_token( + db: &DB, + claim: T, + audience: String, + identifier: String, + email: Option, +) -> crate::error::Result { + use chrono::{Duration, Utc}; + use openidconnect::{ + core::{CoreGenderClaim, CoreJsonWebKeyType, CoreJweContentEncryptionAlgorithm}, + Audience, EndUserEmail, IdToken, IdTokenClaims, StandardClaims, SubjectIdentifier, + }; + + let private_key = get_private_key(&db).await?; + let issue_url = format!("{}/api/oidc/", crate::BASE_URL.read().await.clone()); + + let id_token = IdToken::< + T, + CoreGenderClaim, + CoreJweContentEncryptionAlgorithm, + CoreJwsSigningAlgorithm, + CoreJsonWebKeyType, + >::new( + IdTokenClaims::::new( + // Specify the issuer URL for the OpenID Connect Provider. + IssuerUrl::new(issue_url) + .map_err(|e| anyhow::anyhow!("Failed to generate IssueUrl: {}", e))?, + // The audience is usually a single entry with the client ID of the client for whom + // the ID token is intended. This is a required claim. + vec![Audience::new(audience)], + // The ID token expiration is usually much shorter than that of the access or refresh + // tokens issued to clients. + Utc::now() + Duration::try_hours(48).unwrap(), + // The issue time is usually the current time. + Utc::now(), + // Set the standard claims defined by the OpenID Connect Core spec. + StandardClaims::new( + // Stable subject identifiers are recommended in place of e-mail addresses or other + // potentially unstable identifiers. This is the only required claim. + SubjectIdentifier::new(identifier), + ) + // Optional: specify the user's e-mail address. This should only be provided if the + // client has been granted the 'profile' or 'email' scopes. + .set_email(email.map(|x| EndUserEmail::new(x))) + // Optional: specify whether the provider has verified the user's e-mail address. + .set_email_verified(Some(true)), + // OpenID Connect Providers may supply custom claims by providing a struct that + // implements the AdditionalClaims trait. This requires manually using the + // generic IdTokenClaims struct rather than the CoreIdTokenClaims type alias, + // however. + claim, + ), + // The private key used for signing the ID token. For confidential clients (those able + // to maintain a client secret), a CoreHmacKey can also be used, in conjunction + // with one of the CoreJwsSigningAlgorithm::HmacSha* signing algorithms. When using an + // HMAC-based signing algorithm, the UTF-8 representation of the client secret should + // be used as the HMAC key. + &CoreRsaPrivateSigningKey::from_pem( + &private_key, + Some(JsonWebKeyId::new("windmill".to_string())), + ) + .map_err(|e| anyhow::anyhow!("Invalid private key: {}", e))?, + // Uses the RS256 signature algorithm. This crate supports any RS*, PS*, or HS* + // signature algorithm. + CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256, + // When returning the ID token alongside an access token (e.g., in the Authorization Code + // flow), it is recommended to pass the access token here to set the `at_hash` claim + // automatically. + None, + // When returning the ID token alongside an authorization code (e.g., in the implicit + // flow), it is recommended to pass the authorization code here to set the `c_hash` claim + // automatically. + None, + ) + .map_err(|e| anyhow::anyhow!("Failed to generate token: {}", e))?; + Ok(id_token.to_string()) +} + +#[cfg(feature = "openidconnect")] +pub async fn get_private_key(db: &DB) -> anyhow::Result { + let key = sqlx::query_scalar!( + "SELECT value->>'private_key' FROM global_settings WHERE name = 'rsa_keys'", + ) + .fetch_optional(db) + .await? + .flatten(); + + if let Some(key) = key { + return Ok(key); + } else { + let keys = gen_pems(db).await?; + return Ok(keys.private_key); + } +} + +#[cfg(feature = "openidconnect")] +async fn gen_pems(db: &DB) -> anyhow::Result { + let private_key_cmd = Command::new("openssl") + .arg("genrsa") + .arg("--traditional") + .arg("2048") + .output() + .expect("failed to execute process"); + + let private_key = String::from_utf8(private_key_cmd.stdout).unwrap(); + + tracing::debug!("Generated private key: {}", private_key); + let keys = Keys { private_key }; + + sqlx::query!( + "INSERT INTO global_settings (name, value) VALUES ('rsa_keys', $1)", + serde_json::to_value(&keys).unwrap() + ) + .execute(db) + .await?; + + Ok(keys) +} + +#[derive(Debug, Clone, serde::Serialize)] +struct Keys { + private_key: String, +} diff --git a/backend/windmill-common/src/s3_helpers.rs b/backend/windmill-common/src/s3_helpers.rs index 198283db2d..ac5fe16b7a 100644 --- a/backend/windmill-common/src/s3_helpers.rs +++ b/backend/windmill-common/src/s3_helpers.rs @@ -22,6 +22,7 @@ use tokio::sync::RwLock; lazy_static::lazy_static! { pub static ref OBJECT_STORE_CACHE_SETTINGS: Arc>>> = Arc::new(RwLock::new(None)); + pub static ref OBJECT_STORE_OIDC_SETTINGS: Arc>>> = Arc::new(RwLock::new(None)); } #[derive(Serialize, Deserialize, Debug)] @@ -356,18 +357,42 @@ pub enum ObjectStoreSettings { pub enum ObjectSettings { S3(S3Settings), Azure(AzureBlobResource), + AwsOidc(S3AwsOidcResource), } #[cfg(feature = "parquet")] pub async fn build_object_store_from_settings( settings: ObjectSettings, ) -> error::Result> { + use crate::oidc_ee::generate_id_token; + match settings { ObjectSettings::S3(s3_settings) => build_s3_client_from_settings(s3_settings).await, ObjectSettings::Azure(azure_settings) => { let azure_blob_resource = azure_settings; build_azure_blob_client(&azure_blob_resource) } + ObjectSettings::AwsOidc(aws_oidc_settings) => { + #[cfg(feature = "openidconnect")] + { + let token_fn = |audience: String| async move { + generate_id_token( + db, + claim, + aws_oidc_settings.audience, + "windmill_instance", + "instance_storage@windmill.dev", + ) + }; + todo!() + } + #[cfg(not(feature = "openidconnect"))] + { + return Err(error::Error::InternalErr( + "OpenID Connect is not enabled".to_string(), + )); + } + } } } diff --git a/backend/windmill-common/src/stats_ee.rs b/backend/windmill-common/src/stats_ee.rs deleted file mode 100644 index c6dc9946bf..0000000000 --- a/backend/windmill-common/src/stats_ee.rs +++ /dev/null @@ -1,48 +0,0 @@ -use sqlx::Postgres; - -use crate::{error::Result, scripts::ScriptLang, DB}; - -pub async fn get_disable_stats_setting(_db: &DB) -> bool { - // stats details are closed source - - false -} - -pub async fn schedule_stats(_db: &DB, _http_client: &reqwest::Client) -> () { - // stats details are closed source -} - -#[derive(Debug, sqlx::FromRow, serde::Serialize)] -struct JobsUsage { - language: Option, - total_duration: i64, - count: i64, -} - -pub enum SendStatsReason { - Manual, - Schedule, - OnStart, -} - -pub async fn send_stats( - _http_client: &reqwest::Client, - _db: &DB, - _skip_job_usage: bool, - _reason: SendStatsReason, -) -> Result<()> { - // stats details are closed source - Ok(()) -} - -pub struct ActiveUserUsage { - pub author_count: Option, - pub operator_count: Option, -} - -pub async fn get_user_usage<'c, E: sqlx::Executor<'c, Database = Postgres>>( - _db: E, -) -> Result { - let usage = ActiveUserUsage { author_count: None, operator_count: None }; - Ok(usage) -} diff --git a/backend/windmill-common/src/stats_ee.rs b/backend/windmill-common/src/stats_ee.rs new file mode 120000 index 0000000000..d0ddc5bd1d --- /dev/null +++ b/backend/windmill-common/src/stats_ee.rs @@ -0,0 +1 @@ +/git/windmill/../windmill-ee-private/windmill-common/src/stats_ee.rs \ No newline at end of file diff --git a/backend/windmill-git-sync/src/git_sync_ee.rs b/backend/windmill-git-sync/src/git_sync_ee.rs deleted file mode 100644 index 4be2858c27..0000000000 --- a/backend/windmill-git-sync/src/git_sync_ee.rs +++ /dev/null @@ -1,17 +0,0 @@ -use windmill_common::error::Result; - -use crate::{DeployedObject, DB}; - -pub async fn handle_deployment_metadata<'c, R: rsmq_async::RsmqConnection + Send + Clone + 'c>( - _email: &str, - _created_by: &str, - _db: &DB, - _w_id: &str, - _obj: DeployedObject, - _deployment_message: Option, - _rsmq: Option, - _skip_db_insert: bool, -) -> Result<()> { - // Git sync is an enterprise feature and not part of the open-source version - return Ok(()); -} diff --git a/backend/windmill-git-sync/src/git_sync_ee.rs b/backend/windmill-git-sync/src/git_sync_ee.rs new file mode 120000 index 0000000000..0824f2c28c --- /dev/null +++ b/backend/windmill-git-sync/src/git_sync_ee.rs @@ -0,0 +1 @@ +/git/windmill/../windmill-ee-private/windmill-git-sync/src/git_sync_ee.rs \ No newline at end of file diff --git a/backend/windmill-indexer/src/indexer_ee.rs b/backend/windmill-indexer/src/indexer_ee.rs deleted file mode 100644 index da92ff0ef8..0000000000 --- a/backend/windmill-indexer/src/indexer_ee.rs +++ /dev/null @@ -1,21 +0,0 @@ -use anyhow::anyhow; -use sqlx::{Pool, Postgres}; -use windmill_common::error::Error; - -#[derive(Clone)] -pub struct IndexReader; - -#[derive(Clone)] -pub struct IndexWriter; - -pub async fn init_index() -> Result<(IndexReader, IndexWriter), Error> { - Err(anyhow!("Cannot initialize index: not in EE").into()) -} - -pub async fn run_indexer( - _db: Pool, - mut _index_writer: IndexWriter, - mut _killpill_rx: tokio::sync::broadcast::Receiver<()>, -) { - tracing::error!("Cannot run indexer: not in EE"); -} diff --git a/backend/windmill-indexer/src/indexer_ee.rs b/backend/windmill-indexer/src/indexer_ee.rs new file mode 120000 index 0000000000..925a2c9cd5 --- /dev/null +++ b/backend/windmill-indexer/src/indexer_ee.rs @@ -0,0 +1 @@ +/git/windmill/../windmill-ee-private/windmill-indexer/src/indexer_ee.rs \ No newline at end of file diff --git a/backend/windmill-worker/Cargo.toml b/backend/windmill-worker/Cargo.toml index e5b47b7a81..17bfc7fd04 100644 --- a/backend/windmill-worker/Cargo.toml +++ b/backend/windmill-worker/Cargo.toml @@ -11,7 +11,7 @@ path = "src/lib.rs" [features] default = [] prometheus = ["dep:prometheus", "windmill-common/prometheus"] -enterprise = ["windmill-queue/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "dep:gcp_auth", "dep:pem", "dep:tiberius", "dep:tokio-util", "dep:openidconnect"] +enterprise = ["windmill-queue/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "dep:gcp_auth", "dep:pem", "dep:tiberius", "dep:tokio-util"] benchmark = ["windmill-queue/benchmark", "windmill-common/benchmark"] flamegraph = [] parquet = ["windmill-common/parquet", "dep:object_store"] @@ -85,7 +85,6 @@ reqwest.workspace = true hex.workspace = true tiberius = { workspace = true, optional = true } tokio-util = { workspace = true, optional = true } -openidconnect = { workspace = true, optional = true} tar.workspace = true object_store = { workspace = true, optional = true} convert_case.workspace = true