use crate::storage::DeployProfile;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthBootstrapInput {
None,
Env,
Manifest,
}
impl AuthBootstrapInput {
const fn describe(self) -> &'static str {
match self {
Self::None => "no explicit auth bootstrap input",
Self::Env => "auth bootstrap env/preset input",
Self::Manifest => "auth bootstrap manifest input",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BootstrapDisposition {
ProceedLocal,
SkipDevBypass,
AlreadyComplete,
}
pub const fn is_cluster_shaped(deploy_profile: DeployProfile) -> bool {
matches!(deploy_profile, DeployProfile::Cluster)
}
pub fn authorize(
deploy_profile: DeployProfile,
no_auth: bool,
input: AuthBootstrapInput,
already_completed: bool,
) -> Result<BootstrapDisposition, String> {
if already_completed {
return Ok(BootstrapDisposition::AlreadyComplete);
}
if no_auth {
return Ok(BootstrapDisposition::SkipDevBypass);
}
if !is_cluster_shaped(deploy_profile) {
return Ok(BootstrapDisposition::ProceedLocal);
}
Err(format!(
"cluster bootstrap authority: refusing to run auth bootstrap on a \
cluster-shaped boot ({}) — no concrete authority owner is available. \
The reserved global system range owner (ADR 0058) is not yet \
implemented, so no member can prove it is the single writer of \
global auth/vault/config/policy state. Use --no-auth / --dev for a \
development cluster, or run auth bootstrap on a non-cluster topology.",
input.describe(),
))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VaultBootstrapPlan {
OpenClusterGlobalStore { consume_secret_inputs: bool },
SkipNoVault,
}
pub const fn plan_vault_bootstrap(disposition: BootstrapDisposition) -> VaultBootstrapPlan {
match disposition {
BootstrapDisposition::ProceedLocal => VaultBootstrapPlan::OpenClusterGlobalStore {
consume_secret_inputs: true,
},
BootstrapDisposition::AlreadyComplete => VaultBootstrapPlan::OpenClusterGlobalStore {
consume_secret_inputs: false,
},
BootstrapDisposition::SkipDevBypass => VaultBootstrapPlan::SkipNoVault,
}
}
pub fn authorize_vault_bootstrap(
deploy_profile: DeployProfile,
no_auth: bool,
input: AuthBootstrapInput,
already_completed: bool,
) -> Result<VaultBootstrapPlan, String> {
authorize(deploy_profile, no_auth, input, already_completed).map(plan_vault_bootstrap)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn non_cluster_profiles_proceed_locally() {
for profile in [
DeployProfile::Embedded,
DeployProfile::Serverless,
DeployProfile::PrimaryReplica,
] {
assert!(!is_cluster_shaped(profile), "{profile:?} is not cluster");
assert_eq!(
authorize(profile, false, AuthBootstrapInput::Env, false).unwrap(),
BootstrapDisposition::ProceedLocal,
"{profile:?} should proceed with local bootstrap"
);
}
}
#[test]
fn cluster_no_auth_is_the_dev_bypass_carveout() {
let disposition = authorize(
DeployProfile::Cluster,
true,
AuthBootstrapInput::None,
false,
)
.unwrap();
assert_eq!(disposition, BootstrapDisposition::SkipDevBypass);
}
#[test]
fn non_cluster_no_auth_also_skips() {
let disposition = authorize(
DeployProfile::Embedded,
true,
AuthBootstrapInput::Env,
false,
)
.unwrap();
assert_eq!(disposition, BootstrapDisposition::SkipDevBypass);
}
#[test]
fn cluster_env_bootstrap_fails_closed() {
let err = authorize(
DeployProfile::Cluster,
false,
AuthBootstrapInput::Env,
false,
)
.unwrap_err();
assert!(err.contains("no concrete authority owner"), "got: {err}");
assert!(err.contains("env/preset"), "got: {err}");
}
#[test]
fn cluster_manifest_bootstrap_fails_closed() {
let err = authorize(
DeployProfile::Cluster,
false,
AuthBootstrapInput::Manifest,
false,
)
.unwrap_err();
assert!(err.contains("no concrete authority owner"), "got: {err}");
assert!(err.contains("manifest"), "got: {err}");
}
#[test]
fn cluster_without_explicit_input_still_fails_closed() {
let err = authorize(
DeployProfile::Cluster,
false,
AuthBootstrapInput::None,
false,
)
.unwrap_err();
assert!(err.contains("no concrete authority owner"), "got: {err}");
}
#[test]
fn completion_marker_makes_local_restart_idempotent() {
for profile in [
DeployProfile::Embedded,
DeployProfile::Serverless,
DeployProfile::PrimaryReplica,
] {
assert_eq!(
authorize(profile, false, AuthBootstrapInput::Env, true).unwrap(),
BootstrapDisposition::AlreadyComplete,
"{profile:?} restart should be idempotent once completed"
);
}
}
#[test]
fn completion_marker_short_circuits_cluster_fail_closed() {
let disposition = authorize(
DeployProfile::Cluster,
false,
AuthBootstrapInput::Manifest,
true,
)
.unwrap();
assert_eq!(disposition, BootstrapDisposition::AlreadyComplete);
}
#[test]
fn duplicate_bootstrap_after_completion_is_idempotent_for_every_input() {
for input in [
AuthBootstrapInput::None,
AuthBootstrapInput::Env,
AuthBootstrapInput::Manifest,
] {
assert_eq!(
authorize(DeployProfile::Cluster, false, input, true).unwrap(),
BootstrapDisposition::AlreadyComplete,
"{input:?} duplicate after completion should be idempotent"
);
}
}
#[test]
fn completion_marker_wins_over_dev_bypass() {
let disposition =
authorize(DeployProfile::Cluster, true, AuthBootstrapInput::None, true).unwrap();
assert_eq!(disposition, BootstrapDisposition::AlreadyComplete);
}
#[test]
fn owner_first_boot_plan_opens_real_store_and_consumes_secrets() {
assert_eq!(
plan_vault_bootstrap(BootstrapDisposition::ProceedLocal),
VaultBootstrapPlan::OpenClusterGlobalStore {
consume_secret_inputs: true,
}
);
}
#[test]
fn restart_after_completion_unseals_real_store_without_consuming_secrets() {
assert_eq!(
plan_vault_bootstrap(BootstrapDisposition::AlreadyComplete),
VaultBootstrapPlan::OpenClusterGlobalStore {
consume_secret_inputs: false,
}
);
}
#[test]
fn dev_bypass_plan_skips_the_vault() {
assert_eq!(
plan_vault_bootstrap(BootstrapDisposition::SkipDevBypass),
VaultBootstrapPlan::SkipNoVault
);
}
#[test]
fn cluster_first_boot_mints_no_vault_and_consumes_no_secret() {
for input in [
AuthBootstrapInput::None,
AuthBootstrapInput::Env,
AuthBootstrapInput::Manifest,
] {
let err =
authorize_vault_bootstrap(DeployProfile::Cluster, false, input, false).unwrap_err();
assert!(err.contains("no concrete authority owner"), "got: {err}");
}
}
#[test]
fn non_cluster_owner_gets_real_store_vault_plan() {
for profile in [
DeployProfile::Embedded,
DeployProfile::Serverless,
DeployProfile::PrimaryReplica,
] {
assert_eq!(
authorize_vault_bootstrap(profile, false, AuthBootstrapInput::Env, false).unwrap(),
VaultBootstrapPlan::OpenClusterGlobalStore {
consume_secret_inputs: true,
},
"{profile:?} owner should open the real store and consume secrets"
);
}
}
#[test]
fn completed_cluster_restart_gets_unseal_only_plan() {
assert_eq!(
authorize_vault_bootstrap(
DeployProfile::Cluster,
false,
AuthBootstrapInput::Manifest,
true,
)
.unwrap(),
VaultBootstrapPlan::OpenClusterGlobalStore {
consume_secret_inputs: false,
}
);
}
fn vault_test_pager() -> (crate::storage::engine::pager::Pager, std::path::PathBuf) {
use crate::storage::engine::pager::{Pager, PagerConfig};
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let id = COUNTER.fetch_add(1, Ordering::Relaxed);
let tmp_dir =
std::env::temp_dir().join(format!("reddb_cluster_vault_{}_{}", std::process::id(), id));
std::fs::create_dir_all(&tmp_dir).unwrap();
let pager = Pager::open(&tmp_dir.join("cluster.rdb"), PagerConfig::default()).unwrap();
(pager, tmp_dir)
}
#[test]
fn owner_certificate_unseals_the_same_store_on_restart() {
use crate::auth::vault::{KeyPair, Vault, VaultState};
let plan = authorize_vault_bootstrap(
DeployProfile::PrimaryReplica,
false,
AuthBootstrapInput::Env,
false,
)
.unwrap();
assert!(matches!(
plan,
VaultBootstrapPlan::OpenClusterGlobalStore {
consume_secret_inputs: true
}
));
let (pager, tmp_dir) = vault_test_pager();
let kp = KeyPair::generate();
let vault = Vault::with_certificate_bytes(&pager, &kp.certificate).unwrap();
let state = VaultState {
users: vec![],
api_keys: vec![],
bootstrapped: true,
master_secret: Some(kp.master_secret.clone()),
kv: std::collections::HashMap::new(),
};
vault.save(&pager, &state).unwrap();
let restart_plan = plan_vault_bootstrap(BootstrapDisposition::AlreadyComplete);
assert_eq!(
restart_plan,
VaultBootstrapPlan::OpenClusterGlobalStore {
consume_secret_inputs: false
}
);
let reopened = Vault::with_certificate(&pager, &kp.certificate_hex()).unwrap();
let loaded = reopened.load(&pager).unwrap().unwrap();
assert!(loaded.bootstrapped);
assert_eq!(loaded.master_secret, Some(kp.master_secret));
drop(pager);
let _ = std::fs::remove_dir_all(&tmp_dir);
}
#[test]
fn scratch_certificate_cannot_unseal_the_real_store() {
use crate::auth::vault::{KeyPair, Vault, VaultState};
let (pager, tmp_dir) = vault_test_pager();
let owner = KeyPair::generate();
let vault = Vault::with_certificate_bytes(&pager, &owner.certificate).unwrap();
vault
.save(
&pager,
&VaultState {
users: vec![],
api_keys: vec![],
bootstrapped: true,
master_secret: Some(owner.master_secret.clone()),
kv: std::collections::HashMap::new(),
},
)
.unwrap();
let scratch = KeyPair::generate();
let scratch_vault = Vault::with_certificate_bytes(&pager, &scratch.certificate).unwrap();
assert!(scratch_vault.load(&pager).is_err());
drop(pager);
let _ = std::fs::remove_dir_all(&tmp_dir);
}
}