use std::sync::Arc;
use serde::Serialize;
use utoipa::ToSchema;
use crate::ApiKey;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, ToSchema)]
pub struct Limits {
pub max_sandboxes: u32,
pub max_sandboxes_global: u32,
pub max_ram_mib: u32,
pub max_vcpus: u8,
pub max_running_ram_mib: u32,
pub max_disk_bytes: u64,
pub max_pull_bytes: u64,
pub max_exec_output_bytes: u64,
pub pull_timeout_secs: u64,
pub default_ram_mib: u32,
pub default_vcpus: u8,
}
impl Default for Limits {
fn default() -> Self {
Self {
max_sandboxes: 32,
max_sandboxes_global: 32,
max_ram_mib: 2048,
max_vcpus: 4,
max_running_ram_mib: 8192,
max_disk_bytes: 32_u64 * 1024 * 1024 * 1024,
max_pull_bytes: 4_u64 * 1024 * 1024 * 1024,
max_exec_output_bytes: 1024 * 1024,
pull_timeout_secs: 300,
default_ram_mib: 512,
default_vcpus: 1,
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct AppState {
keys: Arc<[ApiKey]>,
pub(crate) runtime: Arc<bux::Runtime>,
pub(crate) limits: Limits,
pub(crate) allow_unrestricted_net: bool,
}
impl AppState {
pub(crate) fn new(keys: Vec<ApiKey>, runtime: bux::Runtime, limits: Limits) -> Self {
Self {
keys: keys.into(),
runtime: Arc::new(runtime),
limits,
allow_unrestricted_net: false,
}
}
#[must_use]
pub(crate) const fn with_unrestricted_net(mut self, allow: bool) -> Self {
self.allow_unrestricted_net = allow;
self
}
pub(crate) fn tenant_for_bearer(&self, token: &str) -> Option<&str> {
let token = token.as_bytes();
let mut found = None;
for key in self.keys.iter() {
if constant_time_eq(key.secret_bytes(), token) {
found = Some(key.id());
}
}
found
}
}
const CT_EQ_MIN_ITERS: usize = 256;
pub(crate) fn constant_time_eq(left: &[u8], right: &[u8]) -> bool {
let mut acc = u8::from(left.len() != right.len());
let n = left.len().max(right.len()).max(CT_EQ_MIN_ITERS);
for i in 0..n {
acc |= left.get(i).copied().unwrap_or(0) ^ right.get(i).copied().unwrap_or(0);
}
acc == 0
}
#[cfg(test)]
#[allow(clippy::unwrap_used, reason = "tests")]
mod tests {
use super::*;
#[test]
fn tenant_for_bearer_last_match_wins() {
let dir = tempfile::tempdir().unwrap();
let runtime = bux::Runtime::open(dir.path()).unwrap();
let state = AppState::new(
vec![
ApiKey::new("first", "shared").unwrap(),
ApiKey::new("second", "shared").unwrap(),
],
runtime,
Limits::default(),
);
assert_eq!(
state.tenant_for_bearer("shared"),
Some("second"),
"must walk every key"
);
}
#[test]
fn constant_time_eq_cases() {
assert!(constant_time_eq(b"abc", b"abc"), "eq");
assert!(!constant_time_eq(b"abc", b"abd"), "neq");
assert!(!constant_time_eq(b"abc", b"ab"), "len");
assert!(constant_time_eq(b"", b""), "empty");
}
#[test]
fn default_disk_cap_is_32_gib() {
assert_eq!(
Limits::default().max_disk_bytes,
32_u64 * 1024 * 1024 * 1024
);
}
#[test]
fn default_pull_and_exec_caps() {
let limits = Limits::default();
assert_eq!(limits.max_pull_bytes, 4_u64 * 1024 * 1024 * 1024);
assert_eq!(limits.max_exec_output_bytes, 1024 * 1024);
assert_eq!(limits.pull_timeout_secs, 300);
}
}