Skip to content
Closed
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
1 change: 0 additions & 1 deletion Cargo.lock

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

1 change: 0 additions & 1 deletion rivetkit-rust/packages/rivetkit-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ serde.workspace = true
serde_json.workspace = true
serde_bare.workspace = true
serde_bytes.workspace = true
subtle.workspace = true
tokio.workspace = true
tokio-util.workspace = true
tracing.workspace = true
Expand Down
40 changes: 23 additions & 17 deletions rivetkit-rust/packages/rivetkit-core/src/serverless.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ use rivetkit_shared_types::serverless_metadata::{
};
use serde::Serialize;
use serde_json::json;
use subtle::ConstantTimeEq;
use tokio::sync::{Mutex as TokioMutex, mpsc};
use tokio_util::sync::CancellationToken;

Expand Down Expand Up @@ -93,10 +92,6 @@ struct InvalidRequest {
reason: String,
}

#[derive(rivet_error::RivetError, Serialize)]
#[error("auth", "forbidden", "Forbidden.")]
struct Forbidden;

#[derive(rivet_error::RivetError, Serialize)]
#[error(
"config",
Expand Down Expand Up @@ -358,17 +353,27 @@ impl CoreServerlessRuntime {
}

fn validate_start_headers(&self, headers: &StartHeaders) -> Result<()> {
if let Some(expected_token) = &self.settings.configured_token {
let Some(received_token) = &headers.token else {
return Err(Forbidden.build());
};
if !constant_time_eq(expected_token, received_token) {
return Err(Forbidden.build());
}
}
// TODO: pegboard-outbound does not currently auth the /start endpoint,
// so the incoming `x-rivet-token` does not match `config.token`
// (which is the user's API token, not a shared pool secret). Re-enable
// once the envoy-era serverless pool carries a dedicated shared secret
// in its configured headers.
// if let Some(expected_token) = &self.settings.configured_token {
// let Some(received_token) = &headers.token else {
// return Err(Forbidden.build());
// };
// if !constant_time_eq(expected_token, received_token) {
// return Err(Forbidden.build());
// }
// }

if self.settings.validate_endpoint {
if !endpoints_match(&headers.endpoint, &self.settings.configured_endpoint) {
tracing::warn!(
configured_endpoint = %self.settings.configured_endpoint,
received_endpoint = %headers.endpoint,
"serverless start rejected: endpoint mismatch",
);
return Err(EndpointMismatch {
expected: self.settings.configured_endpoint.clone(),
received: headers.endpoint.clone(),
Expand All @@ -377,6 +382,11 @@ impl CoreServerlessRuntime {
}

if headers.namespace != self.settings.configured_namespace {
tracing::warn!(
configured_namespace = %self.settings.configured_namespace,
received_namespace = %headers.namespace,
"serverless start rejected: namespace mismatch",
);
return Err(NamespaceMismatch {
expected: self.settings.configured_namespace.clone(),
received: headers.namespace.clone(),
Expand Down Expand Up @@ -464,10 +474,6 @@ fn optional_header(headers: &HashMap<String, String>, name: &str) -> Option<Stri
headers.get(name).filter(|value| !value.is_empty()).cloned()
}

fn constant_time_eq(expected: &str, received: &str) -> bool {
bool::from(expected.as_bytes().ct_eq(received.as_bytes()))
}

fn cors_headers(req: &ServerlessRequest) -> HashMap<String, String> {
let origin = req
.headers
Expand Down
Loading