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
379 changes: 117 additions & 262 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@ repository = "https://github.com/worldcoin/iris-mpc"
[workspace.dependencies]
async-stream = "0.3"
aws-config = { version = "1.5.10", features = ["behavior-version-latest"] }
aws-credential-types = "1.2.10"
aws-sdk-kms = { version = "1.44.0" }
aws-sdk-rds = { version = "1.44.0" }
aws-sdk-sns = { version = "1.44.0" }
aws-sdk-sqs = { version = "1.89.0" }
aws-sdk-s3 = { version = "1.65.0" }
aws-sdk-secretsmanager = { version = "1.47.0" }
object_store = { version = "0.13.1", features = ["aws", "azure", "gcp", "http"] }
async-trait = "0.1.83"
axum = "0.7"
blake3 = "1.8.2"
Expand Down
1 change: 0 additions & 1 deletion deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ ignore = [
{ id = "RUSTSEC-2026-0098", reason = "rustls-webpki 0.101.7 pulled transitively through aws-config 1.5.x via the legacy rustls 0.21 chain; bug silently accepts URI name constraints instead of rejecting them — only exploitable via CA misissuance of URI-constrained certs, and our TLS usage targets AWS endpoints where this isn't used. The 0.101.x line has no fix; drop once aws-config is bumped to drop the legacy rustls 0.21 chain." },
{ id = "RUSTSEC-2026-0099", reason = "same rustls-webpki 0.101.7 chain as RUSTSEC-2026-0098; sibling bug accepts DNS name constraints for wildcard-asserting certs. Same misissuance-required exploit model, same AWS-TLS-only attack surface, same fix path (bump aws-config off the legacy rustls 0.21 chain)." },
{ id = "RUSTSEC-2026-0104", reason = "rustls-webpki 0.101.7 pulled transitively through aws-config 1.5.x via the legacy rustls 0.21 chain; reachable panic in certificate revocation list parsing — only triggered when parsing CRLs, which iris-mpc does not do (TLS to AWS endpoints only). The 0.101.x line has no fix; drop once aws-config is bumped to drop the legacy rustls 0.21 chain." },
{ id = "RUSTSEC-2026-0002", reason = "lru 0.12.5 pulled transitively through aws-sdk-s3 1.65.0; Stacked Borrows soundness issue in IterMut — iris-mpc does not use lru directly and aws-sdk-s3 uses it for internal endpoint/credential caching that does not iterate mutably. Patched in lru >=0.16.3; drop once aws-sdk-s3 is bumped to a release pulling the patched lru. Note: cargo-deny 0.19+ demotes unsound informational advisories to notes locally, so this is only surfaced by the CI action (currently pinned to cargo-deny 0.18.6)." },
{ id = "RUSTSEC-2026-0176", reason = "pyo3 0.24.2 out-of-bounds read in PyList/PyTuple iterators; fixed in >=0.29.0. iris-mpc-py is a test-only tool with no production exposure; drop once iris-mpc-py updates to pyo3 >=0.29.0" },
{ id = "RUSTSEC-2026-0177", reason = "pyo3 0.24.2 missing Sync bound on PyCFunction::new_closure; fixed in >=0.29.0. iris-mpc-py is a test-only tool with no production exposure; drop once iris-mpc-py updates to pyo3 >=0.29.0" },
{ id = "RUSTSEC-2026-0194", reason = "quick-xml 0.26.0 quadratic-attribute-check DoS; reached only via pprof 0.15 -> inferno ^0.11 (flamegraph feature, transitively through ampc-server-utils), which is unbumpable (every pprof release pins inferno ^0.11 -> quick-xml ^0.26; patched only in quick-xml >=0.41.0). Not exploitable: inferno uses quick-xml solely to GENERATE flamegraph SVGs from our own profiling data, never to parse untrusted XML via the vulnerable Attributes/NsReader read paths. Matches the ampc-common ignore; drop once pprof widens its inferno bound." },
Expand Down
3 changes: 1 addition & 2 deletions iris-mpc-bins/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ aws-config = { workspace = true }
aws-sdk-kms = { workspace = true, optional = true }
aws-sdk-sns = { workspace = true, optional = true }
aws-sdk-sqs = { workspace = true, optional = true }
aws-sdk-s3 = { workspace = true }
aws-sdk-secretsmanager = { workspace = true, optional = true }
object_store.workspace = true
base64.workspace = true
cudarc.workspace = true
chrono.workspace = true
Expand Down Expand Up @@ -83,7 +83,6 @@ iris-mpc-store = { path = "../iris-mpc-store" }
iris-mpc-upgrade-hawk = { path = "../iris-mpc-upgrade-hawk" }
iris-mpc-utils = { path = "../iris-mpc-utils" }
blake3.workspace = true
aws-smithy-types = "1.2.9"
clap_builder = "4.5.51"

# ---------------------
Expand Down
31 changes: 13 additions & 18 deletions iris-mpc-bins/bin/iris-mpc-anon-stats-server/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@ use ampc_server_utils::{
init_heartbeat_task, shutdown_handler::ShutdownHandler, wait_for_others_ready,
wait_for_others_unready, TaskMonitor,
};
use aws_sdk_s3::{config::Builder as S3ConfigBuilder, Client as S3Client};
use aws_sdk_sns::{config::Region, types::MessageAttributeValue, Client as SNSClient};
use aws_smithy_types::retry::RetryConfig;
use chrono::{DateTime, Utc};
use clap_builder::Parser;
use eyre::{bail, eyre, Context, Result};
use iris_mpc_common::config::{ENV_PROD, ENV_STAGE};
use iris_mpc_common::helpers::sqs_s3_helper::upload_file_to_s3;
use iris_mpc_common::object_store::ObjectStoreClient;
use iris_mpc_common::{
helpers::{
smpc_request::{ANONYMIZED_STATISTICS_2D_MESSAGE_TYPE, ANONYMIZED_STATISTICS_MESSAGE_TYPE},
Expand Down Expand Up @@ -187,7 +186,7 @@ struct AnonStatsProcessor {
config: Arc<AnonStatsServerConfig>,
store: AnonStatsStore,
sns_client: SNSClient,
s3_client: S3Client,
s3_client: ObjectStoreClient,
publish: PublishTargets,
sync_failures: HashMap<(AnonStatsOrigin, AnonStatsOperation), usize>,
last_report_times: HashMap<(AnonStatsOrigin, AnonStatsOperation), DateTime<Utc>>,
Expand All @@ -198,7 +197,7 @@ impl AnonStatsProcessor {
config: Arc<AnonStatsServerConfig>,
store: AnonStatsStore,
sns_client: SNSClient,
s3_client: S3Client,
s3_client: ObjectStoreClient,
) -> Self {
let publish = PublishTargets {
topic_arn: config.results_topic_arn.clone(),
Expand Down Expand Up @@ -1089,26 +1088,22 @@ async fn build_sns_client(config: &AnonStatsServerConfig) -> Result<SNSClient> {
Ok(SNSClient::from_conf(sns_config_builder.build()))
}

async fn build_s3_client(config: &AnonStatsServerConfig) -> Result<S3Client> {
async fn build_s3_client(config: &AnonStatsServerConfig) -> Result<ObjectStoreClient> {
let force_path_style = config.environment != ENV_PROD && config.environment != ENV_STAGE;
let retry_config = RetryConfig::standard().with_max_attempts(5);

let region = config.aws.as_ref().and_then(|aws| aws.region.clone());
let mut loader = aws_config::from_env();
if let Some(aws) = &config.aws {
if let Some(region) = &aws.region {
loader = loader.region(Region::new(region.clone()));
}
if let Some(region) = &region {
loader = loader.region(Region::new(region.clone()));
}

let shared_config = loader.load().await;
let mut s3_config = S3ConfigBuilder::from(&shared_config).retry_config(retry_config.clone());
let mut client =
ObjectStoreClient::new(region, force_path_style).with_aws_sdk_config(&shared_config);
if let Some(aws) = &config.aws {
if let Some(endpoint) = &aws.endpoint {
s3_config = s3_config.endpoint_url(endpoint);
}
if force_path_style {
s3_config = s3_config.force_path_style(force_path_style);
client = client
.with_option("aws_endpoint", endpoint)
.with_option("aws_allow_http", endpoint.starts_with("http://"));
}
}
Ok(S3Client::from_conf(s3_config.build()))
Ok(client)
}
35 changes: 18 additions & 17 deletions iris-mpc-bins/bin/iris-mpc-common/key_manager.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
use aws_config::SdkConfig;
use aws_sdk_s3::{
config::Region as S3Region, operation::put_object::PutObjectOutput, Client as S3Client,
Error as S3Error,
};
use aws_sdk_secretsmanager::{
config::Region as S3Region,
operation::{get_secret_value::GetSecretValueOutput, put_secret_value::PutSecretValueOutput},
Client as SecretsManagerClient, Error as SecretsManagerError,
};
use base64::{engine::general_purpose::STANDARD, Engine};
use clap::{Parser, Subcommand};
use eyre::Result;
use iris_mpc_common::object_store::{path, ObjectStoreClient, ObjectStoreExt};
use object_store::PutResult;
use rand::{thread_rng, Rng};
use reqwest::Client;
use sodiumoxide::crypto::box_::{curve25519xsalsa20poly1305, PublicKey, SecretKey, Seed};
Expand Down Expand Up @@ -188,16 +187,21 @@ async fn rotate_keys(
rng.fill(&mut seedbuf);
let pk_seed = Seed(seedbuf);

let mut s3_config_builder = aws_sdk_s3::config::Builder::from(sdk_config);
let mut sm_config_builder = aws_sdk_secretsmanager::config::Builder::from(sdk_config);
let region = sdk_config
.region()
.map(ToString::to_string)
.unwrap_or_else(|| "us-east-1".to_owned());
let mut s3_client = ObjectStoreClient::new(Some(region), endpoint_url.is_some())
.with_aws_sdk_config(sdk_config);

if let Some(endpoint_url) = endpoint_url.as_ref() {
s3_config_builder = s3_config_builder.endpoint_url(endpoint_url);
s3_config_builder = s3_config_builder.force_path_style(true);
s3_client = s3_client
.with_option("aws_endpoint", endpoint_url)
.with_option("aws_allow_http", endpoint_url.starts_with("http://"));
sm_config_builder = sm_config_builder.endpoint_url(endpoint_url);
}

let s3_client = S3Client::from_conf(s3_config_builder.build());
let sm_client = SecretsManagerClient::from_conf(sm_config_builder.build());

let (public_key, private_key) = generate_key_pairs(pk_seed);
Expand Down Expand Up @@ -298,18 +302,15 @@ async fn upload_private_key_to_asm(
}

async fn upload_public_key_to_s3(
client: &S3Client,
client: &ObjectStoreClient,
bucket: &str,
key: &str,
content: &str,
) -> Result<PutObjectOutput, S3Error> {
Ok(client
.put_object()
.bucket(bucket)
.key(key)
.body(content.to_string().into_bytes().into())
.send()
.await?)
) -> object_store::Result<PutResult> {
client
.store(bucket)?
.put(&path(key)?, content.as_bytes().to_vec().into())
.await
}

fn generate_key_pairs(seed: Seed) -> (PublicKey, SecretKey) {
Expand Down
66 changes: 30 additions & 36 deletions iris-mpc-bins/bin/iris-mpc-cpu/db_sanity_check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use clap::Parser;
use eyre::Result;
use iris_mpc_common::object_store::{path as object_path, ObjectStoreClient, ObjectStoreExt};
use iris_mpc_common::{
config::{ENV_PROD, ENV_STAGE},
helpers::smpc_request::{
Expand Down Expand Up @@ -1094,7 +1095,7 @@ async fn main() -> Result<()> {
None => None,
};

// --- Load graph from S3 checkpoint, then replay any mutations recorded after it ---
// --- Load graph from an object-store checkpoint, then replay later mutations ---
let bucket = config.graph_checkpoint_bucket_name.as_str();
rpt!(rpt, "--- Loading graph from S3 checkpoint ---");
let checkpoint_state = load_checkpoint_state(
Expand All @@ -1105,8 +1106,8 @@ async fn main() -> Result<()> {
)
.await?;

// Build the checkpoint S3 client (mirrors AwsClients::checkpoint_s3_client,
// which may target a different region from the general S3 client).
// Build the checkpoint object-store client, which may target a different
// region from the general object-store client.
let checkpoint_s3_client = build_checkpoint_s3_client(
&config.graph_checkpoint_bucket_region,
config.force_path_style(),
Expand Down Expand Up @@ -2370,22 +2371,23 @@ fn parse_s3_uri(uri: &str) -> Result<(String, String)> {
Ok((bucket.to_string(), prefix.to_string()))
}

fn build_s3_client(config: &aws_config::SdkConfig, force_path_style: bool) -> aws_sdk_s3::Client {
let retry_config = aws_config::retry::RetryConfig::standard().with_max_attempts(5);
let s3_config = aws_sdk_s3::config::Builder::from(config)
.force_path_style(force_path_style)
.retry_config(retry_config)
.build();
aws_sdk_s3::Client::from_conf(s3_config)
fn build_s3_client(
sdk_config: &aws_config::SdkConfig,
force_path_style: bool,
) -> ObjectStoreClient {
ObjectStoreClient::new(None, force_path_style).with_aws_sdk_config(sdk_config)
}

/// Build an S3 client for the graph-checkpoint bucket, allowing the region to
/// differ from the ambient default (genesis writes checkpoints into a bucket
/// that may live in a different region from the iris/exclusions buckets).
async fn build_checkpoint_s3_client(region: &str, force_path_style: bool) -> aws_sdk_s3::Client {
let loader = aws_config::from_env().region(aws_sdk_s3::config::Region::new(region.to_owned()));
let config = loader.load().await;
build_s3_client(&config, force_path_style)
async fn build_checkpoint_s3_client(region: &str, force_path_style: bool) -> ObjectStoreClient {
let sdk_config = aws_config::from_env()
.region(aws_config::Region::new(region.to_owned()))
.load()
.await;
ObjectStoreClient::new(Some(region.to_owned()), force_path_style)
.with_aws_sdk_config(&sdk_config)
}

async fn download_exclusions_from_s3(
Expand All @@ -2396,12 +2398,15 @@ async fn download_exclusions_from_s3(
eyre::ensure!(!key.is_empty(), "S3 URI must include an object key");
println!("Downloading exclusions from s3://{bucket}/{key}");

let config = aws_config::from_env().load().await;
let client = build_s3_client(&config, force_path_style);

let response = client.get_object().bucket(&bucket).key(&key).send().await?;
let body = response.body.collect().await?;
let exclusions: ExclusionsFile = serde_json::from_slice(&body.into_bytes())?;
let sdk_config = aws_config::from_env().load().await;
let client = build_s3_client(&sdk_config, force_path_style);
let body = client
.store(&bucket)?
.get(&object_path(&key)?)
.await?
.bytes()
.await?;
let exclusions: ExclusionsFile = serde_json::from_slice(&body)?;

println!(
" Loaded {} excluded serial IDs",
Expand All @@ -2414,8 +2419,9 @@ async fn upload_to_s3(s3_uri: &str, files: &[PathBuf], force_path_style: bool) -
let (bucket, prefix) = parse_s3_uri(s3_uri)?;
println!("--- Uploading to S3: s3://{bucket}/{prefix} ---");

let config = aws_config::from_env().load().await;
let client = build_s3_client(&config, force_path_style);
let sdk_config = aws_config::from_env().load().await;
let client = build_s3_client(&sdk_config, force_path_style);
let store = client.store(&bucket)?;

for path in files {
let file_name = path
Expand All @@ -2429,20 +2435,8 @@ async fn upload_to_s3(s3_uri: &str, files: &[PathBuf], force_path_style: bool) -
format!("{trimmed}/{file_name}")
};

let content_type = if file_name.ends_with(".json") {
"application/json"
} else {
"text/plain"
};
let body = aws_sdk_s3::primitives::ByteStream::from_path(path).await?;
client
.put_object()
.bucket(&bucket)
.key(&key)
.body(body)
.content_type(content_type)
.send()
.await?;
let body = tokio::fs::read(path).await?;
store.put(&object_path(&key)?, body.into()).await?;
println!(" Uploaded s3://{bucket}/{key}");
}

Expand Down
13 changes: 6 additions & 7 deletions iris-mpc-bins/bin/iris-mpc-cpu/graph_mem_cli.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
#![recursion_limit = "256"]

use aws_sdk_s3::config::Region as S3Region;
use aws_sdk_s3::Client as S3Client;
use clap::{Parser, Subcommand};
use eyre::Result;
use iris_mpc_common::object_store::ObjectStoreClient;
use iris_mpc_cpu::hnsw::graph::test_utils::{DbContext, DiffMethod};
use iris_mpc_utils::misc::write_bin;
use std::path::PathBuf;
Expand Down Expand Up @@ -68,12 +67,12 @@ async fn main() -> Result<()> {
command,
} = Cli::parse();

let mut builder = aws_config::from_env();
if let Some(aws_region) = aws_region {
builder = builder.region(S3Region::new(aws_region));
let mut sdk_config_loader = aws_config::from_env();
if let Some(region) = &aws_region {
sdk_config_loader = sdk_config_loader.region(aws_config::Region::new(region.clone()));
}
let shared_config = builder.load().await;
let s3_client = S3Client::new(&shared_config);
let sdk_config = sdk_config_loader.load().await;
let s3_client = ObjectStoreClient::new(aws_region, false).with_aws_sdk_config(&sdk_config);

let db_context = DbContext::new(&db_url, &schema, s3_client, s3_bucket, party_id).await;

Expand Down
14 changes: 7 additions & 7 deletions iris-mpc-bins/bin/iris-mpc-cpu/init_test_dbs.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
#![recursion_limit = "256"]

use aws_sdk_s3::config::Region as S3Region;
use aws_sdk_s3::Client as S3Client;
use clap::Parser;
use eyre::Result;
use iris_mpc_common::object_store::ObjectStoreClient;
use iris_mpc_common::{iris_db::iris::IrisCode, SerialId, VectorId};
use iris_mpc_cpu::{
execution::hawk_main::STORE_IDS,
Expand Down Expand Up @@ -450,12 +449,13 @@ async fn main() -> Result<()> {
}

async fn init_dbs(args: &Args) -> Vec<DbContext> {
let mut builder = aws_config::from_env();
if let Some(aws_region) = args.aws_region.clone() {
builder = builder.region(S3Region::new(aws_region));
let mut sdk_config_loader = aws_config::from_env();
if let Some(region) = &args.aws_region {
sdk_config_loader = sdk_config_loader.region(aws_config::Region::new(region.clone()));
}
let shared_config = builder.load().await;
let s3_client = S3Client::new(&shared_config);
let sdk_config = sdk_config_loader.load().await;
let s3_client =
ObjectStoreClient::new(args.aws_region.clone(), false).with_aws_sdk_config(&sdk_config);

let mut dbs = Vec::new();
for (party_id, (url, schema)) in izip!(args.db_urls().iter(), args.db_schemas().iter())
Expand Down
8 changes: 6 additions & 2 deletions iris-mpc-bins/bin/iris-mpc-cpu/sidecar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,12 @@ async fn main() -> Result<()> {
let graph_store: GraphPg<iris_mpc_cpu::hawkers::aby3::aby3_store::Aby3Store> =
GraphPg::new(&postgres).await?;

let aws_cfg = aws_config::from_env().load().await;
let s3_client = aws_sdk_s3::Client::new(&aws_cfg);
let aws_config = aws_config::from_env().load().await;
let s3_client = iris_mpc_common::object_store::ObjectStoreClient::new(
None,
std::env::var("AWS_ENDPOINT_URL").is_ok(),
)
.with_aws_sdk_config(&aws_config);

// TLS is supplied via SMPC__TLS__* env vars (same path as Hawk Main), not
// CLI flags; load it from the SMPC-prefixed config and feed it through.
Expand Down
Loading
Loading