Skip to content
Open
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
3 changes: 3 additions & 0 deletions Cargo.lock

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

6 changes: 3 additions & 3 deletions configs/local.s3-based.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
service:
address: 0.0.0.0:8080
download_endpoint: 127.0.0.1:8080/downloads/{crate}/{version}
api_endpoint: 127.0.0.1:8080
download_endpoint: http://127.0.0.1:8080/downloads/{crate}/{version}
api_endpoint: http://127.0.0.1:8080
metrics_address: 0.0.0.0:8081
auth_required: false

Expand All @@ -19,4 +19,4 @@ store:
access_key_id: "1234567890"
access_key_secret: "valid-secret"

auth_allow_full_access_without_any_checks: true
auth_allow_full_access_without_any_checks: true
1 change: 1 addition & 0 deletions crates/freighter-auth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ parking_lot = { version = "0.12.3", optional = true }

[dev-dependencies]
tokio = { workspace = true, features = ["macros", "rt"] }
tracing-subscriber = { workspace = true }

[lints]
workspace = true
2 changes: 1 addition & 1 deletion crates/freighter-auth/src/cf_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ impl CfAccess {

#[cfg(test)]
#[tokio::test]
#[ignore]
#[ignore = "needs access token set up"]
async fn cf_access_token_test() {
// curl -sI -H "CF-Access-Client-Id: ….access" -H "CF-Access-Client-Secret: …" https://access.example.com | egrep -Eo 'CF_Authorization=[^;]+
let token = "…"; // Needs non-expired token ;(
Expand Down
3 changes: 3 additions & 0 deletions crates/freighter-auth/src/fs_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub struct FsAuthProvider {
}

impl FsAuthProvider {
#[allow(clippy::needless_pass_by_value)]
pub fn new(config: Config) -> AuthResult<Self> {
std::fs::create_dir_all(&config.auth_path)
.with_context(|| format!("Auth root at {}", config.auth_path.display()))
Expand Down Expand Up @@ -290,6 +291,8 @@ impl fmt::Debug for HashedToken {
#[cfg(test)]
#[tokio::test]
async fn test_fs_tokens() {
let _ = tracing_subscriber::fmt::fmt().with_test_writer().try_init();

let dir = tempfile::tempdir().unwrap();
let auth = FsAuthProvider::new(Config { auth_path: dir.path().to_path_buf(), auth_tokens_pepper: [123; 18] }).unwrap();
let user1 = auth.register("user1").await.unwrap();
Expand Down
1 change: 1 addition & 0 deletions crates/freighter-auth/src/yes_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub struct YesAuthProvider(());

impl YesAuthProvider {
#[track_caller]
#[allow(clippy::needless_pass_by_value)]
pub fn new(yes_config: Config) -> AuthResult<Self> {
if !yes_config.auth_allow_full_access_without_any_checks {
return Err(anyhow::anyhow!("enabled 'yes' auth without explicit opt-in").into());
Expand Down
3 changes: 2 additions & 1 deletion crates/freighter-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ freighter-api-types = { workspace = true, features = ["client"] }
async-trait = { workspace = true }
hyper = { workspace = true }
tower = { workspace = true }
tempfile.workspace = true
tempfile = { workspace = true }
tracing-subscriber = { workspace = true }

[lints]
workspace = true
8 changes: 8 additions & 0 deletions crates/freighter-server/tests/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use tower::ServiceExt;

#[tokio::test]
async fn publish_crate() {
let _ = tracing_subscriber::fmt::fmt().with_test_writer().try_init();

let router = api::api_router();

const TOKEN: &str = "12345";
Expand Down Expand Up @@ -44,6 +46,8 @@ async fn publish_crate() {

#[tokio::test]
async fn publish_crate_auth_denied() {
let _ = tracing_subscriber::fmt::fmt().with_test_writer().try_init();

let router = api::api_router();

let state = ServiceStateBuilder::default()
Expand Down Expand Up @@ -72,6 +76,8 @@ async fn publish_crate_auth_denied() {

#[tokio::test]
async fn index_auth() {
let _ = tracing_subscriber::fmt::fmt().with_test_writer().try_init();

let state = ServiceStateBuilder::default()
.auth_required(true)
.index_provider(MockIndexProvider {
Expand Down Expand Up @@ -114,6 +120,8 @@ async fn index_auth() {

#[tokio::test]
async fn list_all_crates() {
let _ = tracing_subscriber::fmt::fmt().with_test_writer().try_init();

let crates = BTreeMap::from([
(
"example-lib".to_owned(),
Expand Down
2 changes: 1 addition & 1 deletion crates/freighter-server/tests/common/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ pub fn generate_crate_payload(
.to_string();

// https://github.com/rust-lang/cargo/blob/20df9e40a4d41dd08478549915588395e55efb4c/crates/crates-io/lib.rs#L259

let mut payload = Vec::new();
payload.extend_from_slice(&(json.len() as u32).to_le_bytes());
payload.extend_from_slice(json.as_bytes());
Expand Down
2 changes: 2 additions & 0 deletions crates/freighter-server/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ struct TestServerConfig {

impl TestServerConfig {
fn from_env(default_port: u16) -> Self {
let _ = tracing_subscriber::fmt::fmt().with_test_writer().try_init();

Self {
db: Config {
user: Some(var("POSTGRES_USER").unwrap_or("freighter".to_owned())),
Expand Down
6 changes: 6 additions & 0 deletions crates/freighter-server/tests/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use tower::ServiceExt;

#[tokio::test]
async fn index_config_endpoint() {
let _ = tracing_subscriber::fmt::fmt().with_test_writer().try_init();

let router = index::index_router();

let state = ServiceStateBuilder::default().build();
Expand Down Expand Up @@ -42,6 +44,8 @@ async fn index_config_endpoint() {

#[tokio::test]
async fn missing_crate() {
let _ = tracing_subscriber::fmt::fmt().with_test_writer().try_init();

let router = index::index_router();

let state = ServiceStateBuilder::default().build();
Expand All @@ -62,6 +66,8 @@ async fn missing_crate() {

#[tokio::test]
async fn valid_crate() {
let _ = tracing_subscriber::fmt::fmt().with_test_writer().try_init();

let router = index::index_router();

let crates = BTreeMap::from([(
Expand Down
4 changes: 4 additions & 0 deletions crates/freighter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,7 @@ yes-auth-backend = ["freighter-auth/yes-backend"]

[lints]
workspace = true

[dev-dependencies]
rand.workspace = true
tempfile.workspace = true
130 changes: 130 additions & 0 deletions crates/freighter/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use anyhow::Context;

cfg_if::cfg_if! {
if #[cfg(feature = "filesystem-index-backend")] {
use freighter_fs_index::FsIndexProvider as SelectedIndexProvider;
} else if #[cfg(feature = "postgresql-index-backend")] {
use freighter_pg_index::PgIndexProvider as SelectedIndexProvider;
} else {
compile_error!("Use cargo features to select an index backend");
}
}
cfg_if::cfg_if! {
if #[cfg(feature = "filesystem-auth-backend")] {
use freighter_auth::fs_backend::FsAuthProvider as SelectedAuthProvider;
} else if #[cfg(feature = "cloudflare-auth-backend")] {
use freighter_auth::cf_backend::CfAuthProvider as SelectedAuthProvider;
} else if #[cfg(feature = "yes-auth-backend")] {
use freighter_auth::yes_backend::YesAuthProvider as SelectedAuthProvider;
} else {
use freighter_auth::no_backend::NoAuthProvider as SelectedAuthProvider;
}
}
use freighter_storage::s3_client::S3StorageProvider;
use metrics_exporter_prometheus::PrometheusBuilder;
use std::fs::read_to_string;
use tokio::net::TcpListener;

pub mod cli;
mod config;

pub async fn start_listening(
args: cli::FreighterArgs,
) -> anyhow::Result<impl Future<Output = anyhow::Result<()>>> {
let config: config::Config<SelectedIndexProvider, SelectedAuthProvider> =
serde_yaml::from_str(&read_to_string(&args.config).with_context(|| {
format!(
"Failed to read config file from disk, is {} present in {}?",
args.config.display(),
std::env::current_dir()
.as_deref()
.unwrap_or(".".as_ref())
.display()
)
})?)
.context("Failed to deserialize config file, please make sure its in the right format")?;

let config::Config {
service,
index_config,
auth_config,
store,
} = config;

PrometheusBuilder::new()
.with_http_listener(service.metrics_address)
.set_buckets(&[
100e-6, 500e-6, 1e-3, 5e-3, 1e-2, 5e-2, 1e-1, 2e-1, 3e-1, 4e-1, 5e-1, 6e-1, 7e-1, 8e-1,
9e-1, 1.0, 5.0, 10.0,
])
.context("Failed to set buckets for prometheus")?
.install()
.context("Failed to install prometheus exporter")?;

let addr = service.address;

let index_client =
SelectedIndexProvider::new(index_config).context("Failed to construct index client")?;

let storage_client = S3StorageProvider::new(
&store.name,
&store.endpoint_url,
&store.region,
&store.access_key_id.unwrap_or_else(|| {
std::env::var("FREIGHTER_STORE_BUCKET_KEY_ID")
.expect("Failed to find store bucket key id in environment variable or config")
}),
&store.access_key_secret.unwrap_or_else(|| {
std::env::var("FREIGHTER_STORE_BUCKET_KEY_SECRET")
.expect("Failed to find store bucket key secret in environment variable or config")
}),
);
let auth_client =
SelectedAuthProvider::new(auth_config).context("Failed to initialize auth client")?;

let router = freighter_server::router(
service,
Box::new(index_client),
Box::new(storage_client),
Box::new(auth_client),
);

tracing::info!(
?addr,
"Starting freighter instance with {} index and {} auth",
std::any::type_name::<SelectedIndexProvider>(),
std::any::type_name::<SelectedAuthProvider>()
);

let listener = TcpListener::bind(addr).await?;

Ok(async move {
axum::serve(listener, router.into_make_service())
.with_graceful_shutdown(shutdown_signal())
.await
.context("Freighter server exited with error")?;

tracing::info!("Completed graceful shutdown");
Ok(())
})
}

// Based on: https://github.com/tokio-rs/axum/blob/main/examples/graceful-shutdown/src/main.rs
async fn shutdown_signal() {
#[cfg(unix)]
let terminate = async {
use tokio::signal;

signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
};

#[cfg(not(unix))]
let terminate = std::future::pending::<()>();

terminate.await;

tracing::info!("SIGTERM received, beginning graceful shutdown");
}
Loading
Loading