Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
36 changes: 36 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[workspace]
members = [
"cloud/big-bin",
"cloud/core",
"cloud/core/cedar",
"cloud/core/db-types",
Expand Down
6 changes: 4 additions & 2 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ run bin *args:
bazel run //cloud/core:bin -- {{ args }}
elif [ {{ bin }} == "email" ]; then
bazel run //cloud/email:bin -- {{ args }}
elif [ {{ bin }} == "big-bin" ]; then
bazel run //cloud/big-bin -- {{ args }}
else
echo "Unknown binary: {{ bin }}"
exit 1
Expand All @@ -63,7 +65,7 @@ generate-mtls-certs:
openssl genpkey -out local/mtls/scufflecloud_core_key.pem -algorithm ED25519
openssl req -new -key local/mtls/scufflecloud_core_key.pem \
-subj "/CN=scufflecloud-core-mtls" \
-addext "subjectAltName=DNS:localhost" \
-addext "subjectAltName=DNS:localhost,DNS:127.0.0.1" \
-out local/mtls/scufflecloud_core_csr.pem

# Sign core cert with root CA
Expand All @@ -79,7 +81,7 @@ generate-mtls-certs:
openssl genpkey -out local/mtls/scufflecloud_email_key.pem -algorithm ED25519
openssl req -new -key local/mtls/scufflecloud_email_key.pem \
-subj "/CN=scufflecloud-email-mtls" \
-addext "subjectAltName=DNS:localhost" \
-addext "subjectAltName=DNS:localhost,DNS:127.0.0.1" \
-out local/mtls/scufflecloud_email_csr.pem

# Sign email cert with root CA
Expand Down
1 change: 1 addition & 0 deletions cargo_targets.bzl
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
_packages = [
"//cloud/big-bin",
"//cloud/ext-traits",
"//cloud/core",
"//cloud/core/cedar",
Expand Down
31 changes: 31 additions & 0 deletions cloud/big-bin/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
load("//misc/utils/rust:manifest.bzl", "cargo_toml")
load("//misc/utils/rust:package.bzl", "scuffle_package")

cargo_toml()

scuffle_package(
aliases = {
"//cloud/proto": "pb",
"//cloud/core/db-types": "core_db_types",
"//cloud/geo-ip": "geo_ip",
"//cloud/core/traits": "core_traits",
"//cloud/email": "email",
"//cloud/email/traits": "email_traits",
},
crate_name = "scufflecloud-big-bin",
crate_type = "bin",
deps = [
"//cloud/core",
"//cloud/core/db-types",
"//cloud/core/traits",
"//cloud/email",
"//cloud/email/traits",
"//cloud/geo-ip",
"//cloud/proto",
"//crates/batching",
"//crates/bootstrap",
"//crates/bootstrap-telemetry",
"//crates/settings",
"//crates/signal",
],
)
50 changes: 50 additions & 0 deletions cloud/big-bin/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
[package]
name = "scufflecloud-big-bin"
version = "0.1.0"
authors = ["Scuffle <[email protected]>"]
edition = "2024"
license = "AGPL-3.0"
publish = false
repository = "https://github.com/scufflecloud/scuffle"
description = "Big binary for scuffle.cloud"

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] }

[dependencies]
anyhow = "1.0.98"
core-db-types = { path = "../core/db-types", package = "scufflecloud-core-db-types" }
core-traits = { path = "../core/traits", package = "scufflecloud-core-traits" }
diesel = "2.2.10"
diesel-async = "0.6.1"
email = { path = "../email", package = "scufflecloud-email" }
email-traits = { path = "../email/traits", package = "scufflecloud-email-traits" }
fred = "10.1.0"
geo-ip = { path = "../geo-ip", package = "scufflecloud-geo-ip" }
ipnetwork = { features = ["serde"], version = "0.21.1" }
itertools = "0.14.0"
pb = { path = "../proto", package = "scufflecloud-proto" }
reqsign = { version = "0.17.0", default-features = false, features = ["aws"] }
reqwest = { features = ["rustls-tls-native-roots-no-provider"], version = "0.12.23", default-features = false }
rustls = { default-features = false, version = "0.23.31", features = ["aws_lc_rs"] }
scuffle-batching = { path = "../../crates/batching" }
scuffle-bootstrap = { path = "../../crates/bootstrap" }
scuffle-bootstrap-telemetry = { features = ["opentelemetry-logs", "opentelemetry-traces"], path = "../../crates/bootstrap-telemetry" }
scuffle-settings = { path = "../../crates/settings" }
scuffle-signal = { features = ["bootstrap"], path = "../../crates/signal" }
scufflecloud-core = { path = "../core", package = "scufflecloud-core" } # cannot be called core because it would override the rust core crate
serde = "1.0.219"
serde_derive = "1.0.219"
smart-default = "0.7.1"
tokio = { default-features = false, features = ["sync"], version = "1.47.1" }
tonic = "0.14.1"
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
url = "2.5.4"
webauthn-rs = "0.5.2"

[package.metadata.sync-readme.badges]
docs-rs = false
crates-io = false
license = true
codecov = true
1 change: 1 addition & 0 deletions cloud/big-bin/LICENSE.AGPL-3.0
15 changes: 15 additions & 0 deletions cloud/big-bin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!-- dprint-ignore-file -->
<!-- sync-readme title [[ -->
# scufflecloud-big-bin
<!-- sync-readme ]] -->

<!-- sync-readme badge [[ -->
![License: AGPL-3.0](https://img.shields.io/badge/license-AGPL--3.0-purple.svg?style=flat-square)
[![Codecov](https://img.shields.io/codecov/c/github/scufflecloud/scuffle.svg?label=codecov&logo=codecov&style=flat-square)](https://app.codecov.io/gh/scufflecloud/scuffle)
<!-- sync-readme ]] -->

---

<!-- sync-readme rustdoc [[ -->
Big binary for scuffle.cloud that contains all services.
<!-- sync-readme ]] -->
180 changes: 180 additions & 0 deletions cloud/big-bin/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
use std::net::SocketAddr;
use std::path::PathBuf;
use std::str::FromStr;

use anyhow::Context;
use fred::prelude::ClientLike;

#[derive(serde_derive::Deserialize, smart_default::SmartDefault, Debug, Clone)]
#[serde(default)]
pub(crate) struct Config {
#[default(env!("CARGO_PKG_NAME").to_string())]
pub service_name: String,
#[default(SocketAddr::from(([127, 0, 0, 1], 3001)))]
pub core_bind: SocketAddr,
#[default(SocketAddr::from(([127, 0, 0, 1], 3003)))]
pub email_bind: SocketAddr,
#[default = "info"]
pub level: String,
#[default(None)]
pub db_url: Option<String>,
#[default(false)]
pub swagger_ui: bool,
#[default = "scuffle.cloud"]
pub rp_id: String,
#[default(url::Url::from_str("https://dashboard.scuffle.cloud").unwrap())]
pub dashboard_origin: url::Url,
#[default = "1x0000000000000000000000000000000AA"]
pub turnstile_secret_key: String,
pub timeouts: TimeoutConfig,
pub google_oauth2: GoogleOAuth2Config,
pub telemetry: Option<TelemetryConfig>,
pub redis: RedisConfig,
#[default = "Scuffle"]
pub email_from_name: String,
#[default = "[email protected]"]
pub email_from_address: String,
pub reverse_proxy: Option<ReverseProxyConfig>,
#[default("./GeoLite2-City.mmdb".parse().unwrap())]
pub maxminddb_path: PathBuf,
pub aws: AwsConfig,
pub mtls: MtlsConfig,
}

scuffle_settings::bootstrap!(Config);

const fn days(days: u64) -> std::time::Duration {
hours(days * 24)
}

const fn hours(hours: u64) -> std::time::Duration {
minutes(hours * 60)
}

const fn minutes(mins: u64) -> std::time::Duration {
std::time::Duration::from_secs(mins * 60)
}

#[derive(serde_derive::Deserialize, smart_default::SmartDefault, Debug, Clone)]
#[serde(default)]
pub(crate) struct TimeoutConfig {
#[default(minutes(2))]
pub max_request_lifetime: std::time::Duration,
#[default(days(30))]
pub user_session: std::time::Duration,
#[default(minutes(5))]
pub mfa: std::time::Duration,
#[default(hours(4))]
pub user_session_token: std::time::Duration,
#[default(hours(1))]
pub new_user_email_request: std::time::Duration,
#[default(minutes(5))]
pub user_session_request: std::time::Duration,
#[default(minutes(15))]
pub magic_link_request: std::time::Duration,
}

#[derive(serde_derive::Deserialize, smart_default::SmartDefault, Debug, Clone)]
pub(crate) struct GoogleOAuth2Config {
pub client_id: String,
pub client_secret: String,
}

#[derive(serde_derive::Deserialize, smart_default::SmartDefault, Debug, Clone)]
pub(crate) struct TelemetryConfig {
#[default("[::1]:4317".parse().unwrap())]
pub bind: SocketAddr,
}

#[derive(serde_derive::Deserialize, smart_default::SmartDefault, Debug, Clone)]
pub(crate) struct RedisConfig {
#[default(vec!["localhost:6379".to_string()])]
pub servers: Vec<String>,
#[default(None)]
pub username: Option<String>,
#[default(None)]
pub password: Option<String>,
#[default(0)]
pub database: u8,
#[default(10)]
pub pool_size: usize,
}

fn parse_server(server: &str) -> anyhow::Result<fred::types::config::Server> {
let port_ip = server.split(':').collect::<Vec<_>>();

if port_ip.len() == 1 {
Ok(fred::types::config::Server::new(port_ip[0], 6379))
} else {
Ok(fred::types::config::Server::new(
port_ip[0],
port_ip[1].parse::<u16>().context("invalid port")?,
))
}
}

impl RedisConfig {
pub(crate) async fn setup(&self) -> anyhow::Result<fred::clients::Pool> {
let redis_server_config = if self.servers.len() == 1 {
fred::types::config::ServerConfig::Centralized {
server: parse_server(&self.servers[0])?,
}
} else {
fred::types::config::ServerConfig::Clustered {
hosts: self
.servers
.iter()
.map(|s| parse_server(s))
.collect::<anyhow::Result<Vec<_>>>()?,
policy: Default::default(),
}
};

tracing::info!(config = ?redis_server_config, "connecting to redis");

let config = fred::types::config::Config {
server: redis_server_config,
database: Some(self.database),
fail_fast: true,
password: self.password.clone(),
username: self.username.clone(),
..Default::default()
};

let client = fred::clients::Pool::new(config, None, None, None, self.pool_size).context("redis pool")?;
client.init().await?;

Ok(client)
}
}

#[derive(serde_derive::Deserialize, smart_default::SmartDefault, Debug, Clone)]
pub(crate) struct ReverseProxyConfig {
/// List of networks that bypass the IP address extraction from the configured IP header.
/// These are typically internal networks and other services that directly connect to the server without going
/// through the reverse proxy.
pub internal_networks: Vec<ipnetwork::IpNetwork>,
#[default("x-forwarded-for".to_string())]
pub ip_header: String,
/// List of trusted proxy networks that the server accepts connections from.
/// These are typically the networks of the reverse proxies in front of the server, e.g. Cloudflare, etc.
pub trusted_proxies: Vec<ipnetwork::IpNetwork>,
}

#[derive(serde_derive::Deserialize, smart_default::SmartDefault, Debug, Clone)]
pub(crate) struct AwsConfig {
#[default = "us-east-1"]
pub region: String,
pub access_key_id: String,
pub secret_access_key: String,
}

// TODO: Remove mTLS from this binary once we don't use a real connection anymore.
#[derive(serde_derive::Deserialize, smart_default::SmartDefault, Debug, Clone)]
pub(crate) struct MtlsConfig {
pub root_cert_path: PathBuf,
pub core_cert_path: PathBuf,
pub core_key_path: PathBuf,
pub email_cert_path: PathBuf,
pub email_key_path: PathBuf,
}
5 changes: 5 additions & 0 deletions cloud/big-bin/src/dataloaders.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod users;
pub(crate) use users::*;

mod organizations;
pub(crate) use organizations::*;
Loading