use rand::RngExt as _;
use std::fs::{self, OpenOptions};
use std::io::{ErrorKind, Write};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use crate::infra::cluster::config::ClusterRole;
use crate::infra::cluster::security::controller_key::ControllerSigningKey;
use crate::infra::cluster::security::join_token::{
self, JoinToken, JoinTokenError, OfficeVouch, OfficeVouchError, VOUCH_TTL_SECS,
controller_call_header, digests_match, registration_proof, secret_digest, token_fingerprint,
verify_controller_call_header, verify_registration_proof, vouch_nonce,
};
use crate::platform::adapters::data::DataAdapter;
use crate::platform::error::PlatformError;
use crate::platform::model::{
AcceptedOfficeVouch, ClusterJoinTokenMintRequest, ClusterMintedJoinToken, ClusterOfficeVouch,
JOIN_TOKEN_STATUS_ACTIVE, JOIN_TOKEN_STATUS_REVOKED, PlatformOffice,
PlatformOfficeIdentityWrite, PlatformOfficeJoinToken, PlatformOfficeVouchRedemption, now_ts,
slug_segment,
};
const OFFICE_TOKEN_REL: &str = "platform/office-join-token";
pub const OFFICE_VOUCH_REDEEM_PATH: &str = "/office/vouch";
const CONTROLLER_KEY_REL: &str = "platform/cluster-signing-key";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OfficeJoinIdentity {
pub office_id: String,
pub token: String,
pub secret_digest: String,
pub controller_verify_key: String,
pub fingerprint: String,
}
impl OfficeJoinIdentity {
fn from_token(token: JoinToken) -> Self {
let secret_digest = token.secret_digest();
Self {
office_id: token.office_id.clone(),
fingerprint: token_fingerprint(&secret_digest),
secret_digest,
controller_verify_key: token.controller_verify_key.clone(),
token: token.render(),
}
}
pub fn verify_registration_proof(&self, nonce: &str, proof: &str) -> bool {
verify_registration_proof(
&self.controller_verify_key,
&self.office_id,
&self.fingerprint,
nonce,
proof,
)
}
pub fn verify_controller_header(&self, raw: &str) -> bool {
verify_controller_call_header(
&self.controller_verify_key,
&self.office_id,
&self.fingerprint,
raw,
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VerifiedOffice {
pub office_id: String,
pub record: PlatformOfficeJoinToken,
}
#[derive(Clone)]
pub struct ClusterJoinTokenService {
data: Arc<dyn DataAdapter>,
role: ClusterRole,
identity: Option<OfficeJoinIdentity>,
signing: Option<Arc<ControllerSigningKey>>,
}
impl ClusterJoinTokenService {
pub fn new(
data: Arc<dyn DataAdapter>,
role: ClusterRole,
identity: Option<OfficeJoinIdentity>,
signing: Option<Arc<ControllerSigningKey>>,
) -> Self {
Self {
data,
role,
identity,
signing: if role == ClusterRole::Worker {
None
} else {
signing
},
}
}
pub fn office_identity(&self) -> Option<&OfficeJoinIdentity> {
self.identity.as_ref()
}
pub fn controller_verify_key(&self) -> Option<&str> {
self.signing
.as_deref()
.map(ControllerSigningKey::verify_key)
}
fn signing_key(&self) -> Result<&ControllerSigningKey, PlatformError> {
self.signing.as_deref().ok_or_else(|| {
PlatformError::new(
"CLUSTER_CONTROLLER_KEY_MISSING",
format!(
"this instance holds no cluster signing key, so it cannot prove itself to \
any office. The key lives at '{CONTROLLER_KEY_REL}' in the data root and \
is created on the first start of a controller; an office never has one."
),
)
})
}
pub fn mint(
&self,
request: &ClusterJoinTokenMintRequest,
) -> Result<ClusterMintedJoinToken, PlatformError> {
let office_id = normalize_office_id(&request.office_id)?;
let now = now_ts();
let base_url = super::normalize_office_base_url(&request.base_url)?;
if let Some(existing) = self.data.get_office_join_token(&office_id)?
&& !request.rotate
{
return Err(PlatformError::new(
"CLUSTER_JOIN_TOKEN_EXISTS",
format!(
"office '{office_id}' already holds a join token (status {}, minted at {}). \
Re-mint with \"rotate\": true to replace it, which invalidates the token \
that office is using now.",
existing.status, existing.created_at
),
));
}
let office = match self.data.get_platform_office(&office_id)? {
Some(mut existing) => {
if !request.label.trim().is_empty() {
existing.label = request.label.trim().to_string();
}
existing.base_url = base_url.clone();
existing.updated_at = now;
existing
}
None => PlatformOffice {
office_id: office_id.clone(),
office_slug: office_id.clone(),
label: if request.label.trim().is_empty() {
office_id.clone()
} else {
request.label.trim().to_string()
},
office_kind: "office".to_string(),
base_url: base_url.clone(),
status: "planned".to_string(),
created_at: now,
updated_at: now,
},
};
self.data.put_platform_office(&office)?;
let token = JoinToken::mint(office_id.clone(), self.signing_key()?.verify_key());
let record = PlatformOfficeJoinToken {
office_id: office_id.clone(),
secret_digest: token.secret_digest(),
status: JOIN_TOKEN_STATUS_ACTIVE.to_string(),
note: request.note.trim().to_string(),
created_at: now,
last_used_at: 0,
};
self.data.put_office_join_token(&record)?;
Ok(ClusterMintedJoinToken {
record,
office,
token: token.render(),
})
}
pub fn revoke(&self, office_id: &str) -> Result<PlatformOfficeJoinToken, PlatformError> {
let office_id = normalize_office_id(office_id)?;
let mut record = self
.data
.get_office_join_token(&office_id)?
.ok_or_else(|| {
PlatformError::new(
"CLUSTER_JOIN_TOKEN_UNKNOWN",
format!("no join token has been minted for office '{office_id}'"),
)
})?;
record.status = JOIN_TOKEN_STATUS_REVOKED.to_string();
self.data.put_office_join_token(&record)?;
Ok(record)
}
pub fn list(&self) -> Result<Vec<PlatformOfficeJoinToken>, PlatformError> {
self.data.list_office_join_tokens()
}
pub fn verify_office_token(&self, raw: &str) -> Result<VerifiedOffice, PlatformError> {
if self.role != ClusterRole::Master {
return Err(PlatformError::new(
"CLUSTER_JOIN_TOKEN_NOT_A_CONTROLLER",
"only a controller verifies an office's join token; this instance is not one. \
An office proves a peer with the controller's signature, never with a token \
it was issued (`offices.md` §8).",
));
}
let presented = JoinToken::parse(raw).map_err(join_token_error)?;
let office_id = presented.office_id.clone();
let mut record = self
.data
.get_office_join_token(&office_id)?
.ok_or_else(|| {
PlatformError::new(
"CLUSTER_JOIN_TOKEN_UNKNOWN",
format!(
"no join token has been minted for office '{office_id}'. \
Mint one on the controller (POST /api/platform/cluster/join-tokens)."
),
)
})?;
let presented_digest = presented.secret_digest();
if !digests_match(&record.secret_digest, &presented_digest) {
return Err(PlatformError::new(
"CLUSTER_JOIN_TOKEN_INVALID",
format!(
"the join token presented for office '{office_id}' does not match the one \
issued to it. Mint a replacement on the controller \
(POST /api/platform/cluster/join-tokens with \"rotate\": true)."
),
));
}
if !record.is_active() {
return Err(PlatformError::new(
"CLUSTER_JOIN_TOKEN_REVOKED",
format!(
"the join token for office '{office_id}' is revoked. \
Mint a replacement on the controller \
(POST /api/platform/cluster/join-tokens with \"rotate\": true)."
),
));
}
let used_at = now_ts();
if !self
.data
.touch_office_join_token(&office_id, &presented_digest, used_at)?
{
return Err(PlatformError::new(
"CLUSTER_JOIN_TOKEN_STALE",
format!(
"the join token for office '{office_id}' was revoked or rotated while this \
request was being verified, so it is not the token in force. Present the \
current token, or mint a replacement on the controller \
(POST /api/platform/cluster/join-tokens with \"rotate\": true)."
),
));
}
record.last_used_at = used_at;
Ok(VerifiedOffice { office_id, record })
}
pub fn registration_proof_for(&self, office_id: &str, nonce: &str) -> Option<String> {
if nonce.trim().is_empty() {
return None;
}
let record = self.data.get_office_join_token(office_id).ok().flatten()?;
Some(registration_proof(
self.signing.as_deref()?,
office_id,
&token_fingerprint(&record.secret_digest),
nonce,
))
}
pub fn controller_call_header_for(&self, office_id: &str) -> Result<String, PlatformError> {
let office_id = normalize_office_id(office_id)?;
let record = self.data.get_office_join_token(&office_id)?.ok_or_else(|| {
PlatformError::new(
"CLUSTER_JOIN_TOKEN_UNKNOWN",
format!(
"no join token has been minted for office '{office_id}', so this controller \
cannot prove itself to it. Mint one (POST /api/platform/cluster/join-tokens)."
),
)
})?;
if !record.is_active() {
return Err(PlatformError::new(
"CLUSTER_JOIN_TOKEN_REVOKED",
format!("the join token for office '{office_id}' is revoked"),
));
}
Ok(controller_call_header(
self.signing_key()?,
&office_id,
&token_fingerprint(&record.secret_digest),
))
}
pub fn mint_vouch(
&self,
office_id: &str,
identity: &str,
now: i64,
) -> Result<ClusterOfficeVouch, PlatformError> {
if self.role != ClusterRole::Master {
return Err(PlatformError::new(
"CLUSTER_VOUCH_NOT_A_CONTROLLER",
"only a controller vouches for an identity at an office (`offices.md` §2 gives the three verbs to the controller alone)",
));
}
let office_id = normalize_office_id(office_id)?;
let identity = slug_segment(identity);
if identity.is_empty() {
return Err(PlatformError::new(
"CLUSTER_VOUCH_IDENTITY_INVALID",
"a vouch must name the identity it vouches for",
));
}
let record = self.data.get_office_join_token(&office_id)?.ok_or_else(|| {
PlatformError::new(
"CLUSTER_JOIN_TOKEN_UNKNOWN",
format!(
"no join token has been minted for office '{office_id}', so this controller \
holds nothing that office would accept a vouch under. Mint one \
(POST /api/platform/cluster/join-tokens)."
),
)
})?;
if !record.is_active() {
return Err(PlatformError::new(
"CLUSTER_JOIN_TOKEN_REVOKED",
format!(
"the join token for office '{office_id}' is revoked, so this controller no \
longer vouches for anybody there. Re-mint with \"rotate\": true to restore \
membership, which also invalidates every vouch issued under the old token."
),
));
}
let expires_at = now + VOUCH_TTL_SECS;
let vouch = OfficeVouch::mint(
self.signing_key()?,
&office_id,
&identity,
expires_at,
&vouch_nonce(),
&token_fingerprint(&record.secret_digest),
)
.render();
let base_url = self
.data
.get_platform_office(&office_id)?
.map(|office| office.base_url.trim_end_matches('/').to_string())
.unwrap_or_default();
let redeem_url = if base_url.is_empty() {
String::new()
} else {
format!(
"{base_url}{OFFICE_VOUCH_REDEEM_PATH}?v={}",
query_encode(&vouch)
)
};
Ok(ClusterOfficeVouch {
office_id,
identity,
expires_at,
ttl_seconds: VOUCH_TTL_SECS,
vouch,
redeem_url,
})
}
pub fn redeem_vouch(&self, raw: &str, now: i64) -> Result<AcceptedOfficeVouch, PlatformError> {
let Some(identity) = self.identity.as_ref() else {
return Err(PlatformError::new(
"CLUSTER_VOUCH_NOT_AN_OFFICE",
"this instance has joined no controller, so nobody vouches for anybody here. \
A vouch is redeemable only on an office that holds a join token \
(`offices.md` §4).",
));
};
let vouch = OfficeVouch::parse(raw).map_err(vouch_error)?;
vouch
.verify(
&identity.controller_verify_key,
&identity.office_id,
&identity.fingerprint,
now,
)
.map_err(vouch_error)?;
let claimed = self
.data
.claim_office_vouch_nonce(&PlatformOfficeVouchRedemption {
nonce: vouch.nonce.clone(),
office_id: vouch.office_id.clone(),
identity: vouch.identity.clone(),
expires_at: vouch.expires_at,
redeemed_at: now,
})?;
if !claimed {
return Err(PlatformError::new(
"CLUSTER_VOUCH_ALREADY_REDEEMED",
"this vouch has already been spent. A vouch is a hand-off and opens one \
session; ask the controller for a fresh one.",
));
}
Ok(AcceptedOfficeVouch {
office_id: vouch.office_id,
identity: vouch.identity,
expires_at: vouch.expires_at,
})
}
pub fn record_identity_write(
&self,
owner: &str,
action: &str,
role: &str,
source: &str,
detail: &str,
) -> Result<PlatformOfficeIdentityWrite, PlatformError> {
let mut bytes = [0u8; 16];
rand::rng().fill(&mut bytes);
let entry = PlatformOfficeIdentityWrite {
write_id: hex::encode(bytes),
office_id: self
.identity
.as_ref()
.map(|identity| identity.office_id.clone())
.unwrap_or_default(),
owner: owner.to_string(),
action: action.to_string(),
role: role.to_string(),
source: source.to_string(),
detail: detail.to_string(),
written_at: now_ts(),
};
self.data.put_office_identity_write(&entry)?;
Ok(entry)
}
pub fn list_identity_writes(
&self,
limit: usize,
) -> Result<Vec<PlatformOfficeIdentityWrite>, PlatformError> {
self.data.list_office_identity_writes(limit.clamp(1, 500))
}
pub fn controller_call_authenticates(&self, raw: &str) -> bool {
if self.role != ClusterRole::Worker {
return false;
}
self.identity
.as_ref()
.is_some_and(|identity| identity.verify_controller_header(raw))
}
}
fn vouch_error(err: OfficeVouchError) -> PlatformError {
let code = match err {
OfficeVouchError::OfficeMismatch { .. } => "CLUSTER_VOUCH_OFFICE_MISMATCH",
OfficeVouchError::ProofInvalid => "CLUSTER_VOUCH_INVALID",
OfficeVouchError::Expired { .. } => "CLUSTER_VOUCH_EXPIRED",
_ => "CLUSTER_VOUCH_MALFORMED",
};
PlatformError::new(code, err.to_string())
}
fn query_encode(value: &str) -> String {
let mut out = String::with_capacity(value.len());
for byte in value.as_bytes() {
match *byte {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
out.push(*byte as char);
}
byte => out.push_str(&format!("%{byte:02X}")),
}
}
out
}
fn normalize_office_id(raw: &str) -> Result<String, PlatformError> {
let value = slug_segment(raw);
if value.is_empty() {
return Err(PlatformError::new(
"CLUSTER_JOIN_TOKEN_OFFICE_INVALID",
format!("'{raw}' is not a usable office id"),
));
}
Ok(value)
}
fn join_token_error(err: JoinTokenError) -> PlatformError {
PlatformError::new("CLUSTER_JOIN_TOKEN_MALFORMED", err.to_string())
}
pub fn office_token_path(data_root: &Path) -> PathBuf {
data_root.join(OFFICE_TOKEN_REL)
}
pub fn resolve_office_identity(
data_root: &Path,
configured: Option<&str>,
) -> Result<Option<OfficeJoinIdentity>, PlatformError> {
let path = office_token_path(data_root);
let stored = read_stored_token(&path)?;
let configured = configured.map(str::trim).filter(|value| !value.is_empty());
match (stored, configured) {
(None, None) => Ok(None),
(Some(stored), None) => Ok(Some(OfficeJoinIdentity::from_token(
JoinToken::parse(&stored).map_err(|err| stored_token_error(&path, err))?,
))),
(None, Some(configured)) => {
let token = JoinToken::parse(configured).map_err(join_token_error)?;
write_stored_token(&path, &token.render())?;
Ok(Some(OfficeJoinIdentity::from_token(token)))
}
(Some(stored), Some(configured)) => {
let stored_token =
JoinToken::parse(&stored).map_err(|err| stored_token_error(&path, err))?;
let configured_token = JoinToken::parse(configured).map_err(join_token_error)?;
if stored_token != configured_token {
return Err(PlatformError::new(
"CLUSTER_JOIN_TOKEN_MISMATCH",
format!(
"ZEBFLOW_CLUSTER_JOIN_TOKEN names office '{}' but this data root already \
holds a token for office '{}' at '{}'. Refusing to start rather than \
overwrite either. Keep the stored token and unset the variable, or \
delete the file if this office is being re-issued.",
configured_token.office_id,
stored_token.office_id,
path.display()
),
));
}
Ok(Some(OfficeJoinIdentity::from_token(stored_token)))
}
}
}
fn stored_token_error(path: &Path, err: JoinTokenError) -> PlatformError {
PlatformError::new(
"CLUSTER_JOIN_TOKEN_STORED_INVALID",
format!(
"the token stored at '{}' is unusable: {err}",
path.display()
),
)
}
fn read_stored_token(path: &Path) -> Result<Option<String>, PlatformError> {
match fs::read_to_string(path) {
Ok(value) => {
let value = value.trim().to_string();
if value.is_empty() {
Ok(None)
} else {
Ok(Some(value))
}
}
Err(err) if err.kind() == ErrorKind::NotFound => Ok(None),
Err(err) => Err(PlatformError::new(
"CLUSTER_JOIN_TOKEN_STORED_UNREADABLE",
format!("failed reading '{}': {err}", path.display()),
)),
}
}
fn write_stored_token(path: &Path, value: &str) -> Result<(), PlatformError> {
let mut bytes = value.as_bytes().to_vec();
bytes.push(b'\n');
write_private_file(path, &bytes, "CLUSTER_JOIN_TOKEN_STORE_WRITE")
}
fn write_private_file(path: &Path, bytes: &[u8], code: &'static str) -> Result<(), PlatformError> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).map_err(|err| {
PlatformError::new(
code,
format!("failed creating '{}': {err}", parent.display()),
)
})?;
}
let mut options = OpenOptions::new();
options.write(true).create(true).truncate(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
options.mode(0o600);
}
let mut file = options.open(path).map_err(|err| {
PlatformError::new(code, format!("failed creating '{}': {err}", path.display()))
})?;
file.write_all(bytes)
.and_then(|()| file.sync_all())
.map_err(|err| {
PlatformError::new(code, format!("failed writing '{}': {err}", path.display()))
})?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(path, fs::Permissions::from_mode(0o600)).map_err(|err| {
PlatformError::new(code, format!("failed securing '{}': {err}", path.display()))
})?;
}
Ok(())
}
pub fn controller_signing_key_path(data_root: &Path) -> PathBuf {
data_root.join(CONTROLLER_KEY_REL)
}
pub fn resolve_controller_signing_key(
data_root: &Path,
) -> Result<ControllerSigningKey, PlatformError> {
let path = controller_signing_key_path(data_root);
match fs::read(&path) {
Ok(document) => ControllerSigningKey::from_pkcs8(&document).map_err(|err| {
PlatformError::new(
"CLUSTER_CONTROLLER_KEY_UNUSABLE",
format!("'{}': {err}", path.display()),
)
}),
Err(err) if err.kind() == ErrorKind::NotFound => {
let (document, key) = ControllerSigningKey::generate().map_err(|err| {
PlatformError::new("CLUSTER_CONTROLLER_KEY_UNUSABLE", err.to_string())
})?;
write_private_file(&path, &document, "CLUSTER_CONTROLLER_KEY_WRITE")?;
Ok(key)
}
Err(err) => Err(PlatformError::new(
"CLUSTER_CONTROLLER_KEY_UNUSABLE",
format!("failed reading '{}': {err}", path.display()),
)),
}
}
pub fn digest_of(secret: &str) -> String {
secret_digest(secret)
}
pub fn nonce() -> String {
join_token::registration_nonce()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::infra::cluster::registry::WorkerRegistryRecord;
use crate::infra::execution::placement::ProjectRuntimePlacement;
use crate::platform::model::{
McpSession, PipelineMeta, PlatformProject, PlatformUser, ProjectCredential,
ProjectDbConnection, ProjectInvite, ProjectMember, ProjectPolicy, ProjectPolicyBinding,
StoredUser,
};
fn temp_root(name: &str) -> (tempfile::TempDir, PathBuf) {
let tmp = tempfile::Builder::new()
.prefix(&format!("zebflow-join-token-{name}-"))
.tempdir()
.expect("tempdir");
let root = tmp.path().to_path_buf();
(tmp, root)
}
fn controller() -> ControllerSigningKey {
ControllerSigningKey::generate().expect("generate").1
}
fn mint_for(office_id: &str, key: &ControllerSigningKey) -> JoinToken {
JoinToken::mint(office_id, key.verify_key())
}
#[test]
fn a_first_join_stores_the_supplied_token_privately() {
let (_tmp, root) = temp_root("first-join");
let key = controller();
let minted = mint_for("office-a", &key);
let identity = resolve_office_identity(&root, Some(&minted.render()))
.expect("first join")
.expect("identity");
assert_eq!(identity.office_id, "office-a");
assert_eq!(identity.secret_digest, minted.secret_digest());
assert_eq!(identity.controller_verify_key, key.verify_key());
assert_eq!(identity.fingerprint, minted.fingerprint());
let path = office_token_path(&root);
assert_eq!(
fs::read_to_string(&path).expect("stored token").trim(),
minted.render()
);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mode = fs::metadata(&path).expect("meta").permissions().mode() & 0o777;
assert_eq!(mode, 0o600);
}
let reloaded = resolve_office_identity(&root, None)
.expect("second start")
.expect("identity");
assert_eq!(reloaded, identity);
let _ = fs::remove_dir_all(&root);
}
#[test]
fn a_disagreeing_environment_variable_refuses_to_start() {
let (_tmp, root) = temp_root("mismatch");
let key = controller();
let stored = mint_for("office-a", &key);
resolve_office_identity(&root, Some(&stored.render())).expect("first join");
let other = mint_for("office-b", &key);
let err = resolve_office_identity(&root, Some(&other.render()))
.expect_err("mismatch must refuse");
assert_eq!(err.code, "CLUSTER_JOIN_TOKEN_MISMATCH");
assert!(err.message.contains("office-a") && err.message.contains("office-b"));
assert_eq!(
fs::read_to_string(office_token_path(&root))
.expect("stored")
.trim(),
stored.render()
);
let _ = fs::remove_dir_all(&root);
}
#[test]
fn the_same_token_supplied_again_is_not_a_mismatch() {
let (_tmp, root) = temp_root("same-token");
let key = controller();
let stored = mint_for("office-a", &key);
resolve_office_identity(&root, Some(&stored.render())).expect("first join");
let again = resolve_office_identity(&root, Some(&stored.render()))
.expect("same token")
.expect("identity");
assert_eq!(again.office_id, "office-a");
let _ = fs::remove_dir_all(&root);
}
#[test]
fn an_office_refuses_a_response_that_does_not_prove_the_controller() {
let key = controller();
let issued = mint_for("office-a", &key);
let identity = OfficeJoinIdentity::from_token(issued.clone());
let sent = nonce();
let honest = registration_proof(&key, "office-a", &issued.fingerprint(), &sent);
assert!(identity.verify_registration_proof(&sent, &honest));
let impostor = controller();
assert!(!identity.verify_registration_proof(
&sent,
®istration_proof(&impostor, "office-a", &issued.fingerprint(), &sent)
));
assert!(!identity.verify_registration_proof(&sent, ""));
assert!(!identity.verify_registration_proof(&sent, "not-a-proof"));
assert!(!identity.verify_registration_proof(
&sent,
®istration_proof(&key, "office-a", &issued.fingerprint(), &nonce())
));
assert!(!identity.verify_registration_proof(&sent, &issued.secret_digest()));
}
#[test]
fn an_office_accepts_only_its_own_controller_call_header() {
let key = controller();
let issued = mint_for("office-a", &key);
let identity = OfficeJoinIdentity::from_token(issued.clone());
assert!(identity.verify_controller_header(&controller_call_header(
&key,
"office-a",
&issued.fingerprint()
)));
assert!(!identity.verify_controller_header(&controller_call_header(
&key,
"office-b",
&issued.fingerprint()
)));
assert!(!identity.verify_controller_header(&controller_call_header(
&controller(),
"office-a",
&issued.fingerprint()
)));
assert!(!identity.verify_controller_header(&controller_call_header(
&key,
"office-a",
&mint_for("office-a", &key).fingerprint()
)));
assert!(!identity.verify_controller_header(&issued.render()));
assert!(!identity.verify_controller_header(""));
}
#[test]
fn the_old_shared_secret_shape_refuses_and_names_the_fix() {
let (_tmp, root) = temp_root("legacy-secret");
let err =
resolve_office_identity(&root, Some("join-token")).expect_err("legacy shape refuses");
assert_eq!(err.code, "CLUSTER_JOIN_TOKEN_MALFORMED");
assert!(
err.message.contains("Mint one on the controller"),
"{}",
err.message
);
assert!(
!office_token_path(&root).exists(),
"a refused token must not be stored"
);
let _ = fs::remove_dir_all(&root);
}
#[test]
fn a_controller_key_is_created_once_and_reused_for_the_life_of_the_instance() {
let (_tmp, root) = temp_root("controller-key");
let first = resolve_controller_signing_key(&root).expect("first start");
let path = controller_signing_key_path(&root);
assert!(path.is_file());
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mode = fs::metadata(&path).expect("meta").permissions().mode() & 0o777;
assert_eq!(mode, 0o600, "the signing key is not a world-readable file");
}
let second = resolve_controller_signing_key(&root).expect("second start");
assert_eq!(
first.verify_key(),
second.verify_key(),
"regenerating would invalidate every token this controller ever issued"
);
fs::write(&path, b"not a key").expect("damage");
let err = resolve_controller_signing_key(&root).expect_err("damaged key");
assert_eq!(err.code, "CLUSTER_CONTROLLER_KEY_UNUSABLE");
assert!(
err.message.contains("re-mint every office's token"),
"{err:?}"
);
let _ = fs::remove_dir_all(&root);
}
struct InterleavingAdapter {
inner: Arc<dyn DataAdapter>,
on_first_read: Box<dyn Fn(&dyn DataAdapter) + Send + Sync>,
fired: std::sync::atomic::AtomicBool,
}
impl DataAdapter for InterleavingAdapter {
fn get_office_join_token(
&self,
office_id: &str,
) -> Result<Option<PlatformOfficeJoinToken>, PlatformError> {
let record = self.inner.get_office_join_token(office_id)?;
if record.is_some() && !self.fired.swap(true, std::sync::atomic::Ordering::SeqCst) {
(self.on_first_read)(self.inner.as_ref());
}
Ok(record)
}
fn put_office_join_token(
&self,
token: &PlatformOfficeJoinToken,
) -> Result<(), PlatformError> {
self.inner.put_office_join_token(token)
}
fn touch_office_join_token(
&self,
office_id: &str,
secret_digest: &str,
last_used_at: i64,
) -> Result<bool, PlatformError> {
self.inner
.touch_office_join_token(office_id, secret_digest, last_used_at)
}
fn list_office_join_tokens(&self) -> Result<Vec<PlatformOfficeJoinToken>, PlatformError> {
self.inner.list_office_join_tokens()
}
fn get_platform_office(
&self,
office_id: &str,
) -> Result<Option<PlatformOffice>, PlatformError> {
self.inner.get_platform_office(office_id)
}
fn put_platform_office(&self, office: &PlatformOffice) -> Result<(), PlatformError> {
self.inner.put_platform_office(office)
}
fn id(&self) -> &'static str {
self.inner.id()
}
fn get_user_auth(&self, owner: &str) -> Result<Option<StoredUser>, PlatformError> {
self.inner.get_user_auth(owner)
}
fn put_user(&self, user: &StoredUser) -> Result<(), PlatformError> {
self.inner.put_user(user)
}
fn list_users(&self) -> Result<Vec<PlatformUser>, PlatformError> {
self.inner.list_users()
}
fn get_project(
&self,
owner: &str,
project: &str,
) -> Result<Option<PlatformProject>, PlatformError> {
self.inner.get_project(owner, project)
}
fn put_project(&self, project: &PlatformProject) -> Result<(), PlatformError> {
self.inner.put_project(project)
}
fn list_projects(&self, owner: &str) -> Result<Vec<PlatformProject>, PlatformError> {
self.inner.list_projects(owner)
}
fn delete_project(&self, owner: &str, project: &str) -> Result<(), PlatformError> {
self.inner.delete_project(owner, project)
}
fn get_project_credential(
&self,
owner: &str,
project: &str,
credential_id: &str,
) -> Result<Option<ProjectCredential>, PlatformError> {
self.inner
.get_project_credential(owner, project, credential_id)
}
fn put_project_credential(
&self,
credential: &ProjectCredential,
) -> Result<(), PlatformError> {
self.inner.put_project_credential(credential)
}
fn list_project_credentials(
&self,
owner: &str,
project: &str,
) -> Result<Vec<ProjectCredential>, PlatformError> {
self.inner.list_project_credentials(owner, project)
}
fn delete_project_credential(
&self,
owner: &str,
project: &str,
credential_id: &str,
) -> Result<(), PlatformError> {
self.inner
.delete_project_credential(owner, project, credential_id)
}
fn get_project_db_connection(
&self,
owner: &str,
project: &str,
connection_slug: &str,
) -> Result<Option<ProjectDbConnection>, PlatformError> {
self.inner
.get_project_db_connection(owner, project, connection_slug)
}
fn put_project_db_connection(
&self,
connection: &ProjectDbConnection,
) -> Result<(), PlatformError> {
self.inner.put_project_db_connection(connection)
}
fn list_project_db_connections(
&self,
owner: &str,
project: &str,
) -> Result<Vec<ProjectDbConnection>, PlatformError> {
self.inner.list_project_db_connections(owner, project)
}
fn delete_project_db_connection(
&self,
owner: &str,
project: &str,
connection_slug: &str,
) -> Result<(), PlatformError> {
self.inner
.delete_project_db_connection(owner, project, connection_slug)
}
fn put_pipeline_meta(&self, meta: &PipelineMeta) -> Result<(), PlatformError> {
self.inner.put_pipeline_meta(meta)
}
fn delete_pipeline_meta(
&self,
owner: &str,
project: &str,
file_rel_path: &str,
) -> Result<(), PlatformError> {
self.inner
.delete_pipeline_meta(owner, project, file_rel_path)
}
fn list_pipeline_meta(
&self,
owner: &str,
project: &str,
) -> Result<Vec<PipelineMeta>, PlatformError> {
self.inner.list_pipeline_meta(owner, project)
}
fn put_project_policy(&self, policy: &ProjectPolicy) -> Result<(), PlatformError> {
self.inner.put_project_policy(policy)
}
fn list_project_policies(
&self,
owner: &str,
project: &str,
) -> Result<Vec<ProjectPolicy>, PlatformError> {
self.inner.list_project_policies(owner, project)
}
fn put_project_policy_binding(
&self,
binding: &ProjectPolicyBinding,
) -> Result<(), PlatformError> {
self.inner.put_project_policy_binding(binding)
}
fn list_project_policy_bindings(
&self,
owner: &str,
project: &str,
) -> Result<Vec<ProjectPolicyBinding>, PlatformError> {
self.inner.list_project_policy_bindings(owner, project)
}
fn delete_project_policy(
&self,
owner: &str,
project: &str,
policy_id: &str,
) -> Result<(), PlatformError> {
self.inner.delete_project_policy(owner, project, policy_id)
}
fn delete_project_policy_binding(
&self,
owner: &str,
project: &str,
subject_id: &str,
) -> Result<(), PlatformError> {
self.inner
.delete_project_policy_binding(owner, project, subject_id)
}
fn get_project_member(
&self,
owner: &str,
project: &str,
user_id: &str,
) -> Result<Option<ProjectMember>, PlatformError> {
self.inner.get_project_member(owner, project, user_id)
}
fn put_project_member(&self, member: &ProjectMember) -> Result<(), PlatformError> {
self.inner.put_project_member(member)
}
fn list_project_members(
&self,
owner: &str,
project: &str,
) -> Result<Vec<ProjectMember>, PlatformError> {
self.inner.list_project_members(owner, project)
}
fn delete_project_member(
&self,
owner: &str,
project: &str,
user_id: &str,
) -> Result<(), PlatformError> {
self.inner.delete_project_member(owner, project, user_id)
}
fn get_project_invite(
&self,
owner: &str,
project: &str,
invite_id: &str,
) -> Result<Option<ProjectInvite>, PlatformError> {
self.inner.get_project_invite(owner, project, invite_id)
}
fn put_project_invite(&self, invite: &ProjectInvite) -> Result<(), PlatformError> {
self.inner.put_project_invite(invite)
}
fn list_project_invites(
&self,
owner: &str,
project: &str,
) -> Result<Vec<ProjectInvite>, PlatformError> {
self.inner.list_project_invites(owner, project)
}
fn delete_project_invite(
&self,
owner: &str,
project: &str,
invite_id: &str,
) -> Result<(), PlatformError> {
self.inner.delete_project_invite(owner, project, invite_id)
}
fn get_worker_registry_record(
&self,
node_id: &str,
) -> Result<Option<WorkerRegistryRecord>, PlatformError> {
self.inner.get_worker_registry_record(node_id)
}
fn put_worker_registry_record(
&self,
record: &WorkerRegistryRecord,
) -> Result<(), PlatformError> {
self.inner.put_worker_registry_record(record)
}
fn list_worker_registry_records(&self) -> Result<Vec<WorkerRegistryRecord>, PlatformError> {
self.inner.list_worker_registry_records()
}
fn delete_worker_registry_record(&self, node_id: &str) -> Result<(), PlatformError> {
self.inner.delete_worker_registry_record(node_id)
}
fn get_project_runtime_placement(
&self,
owner: &str,
project: &str,
) -> Result<Option<ProjectRuntimePlacement>, PlatformError> {
self.inner.get_project_runtime_placement(owner, project)
}
fn put_project_runtime_placement(
&self,
placement: &ProjectRuntimePlacement,
) -> Result<(), PlatformError> {
self.inner.put_project_runtime_placement(placement)
}
fn list_project_runtime_placements(
&self,
) -> Result<Vec<ProjectRuntimePlacement>, PlatformError> {
self.inner.list_project_runtime_placements()
}
fn delete_project_runtime_placement(
&self,
owner: &str,
project: &str,
) -> Result<(), PlatformError> {
self.inner.delete_project_runtime_placement(owner, project)
}
fn list_all_mcp_sessions(&self) -> Result<Vec<McpSession>, PlatformError> {
self.inner.list_all_mcp_sessions()
}
fn put_mcp_session(&self, session: &McpSession) -> Result<(), PlatformError> {
self.inner.put_mcp_session(session)
}
fn delete_mcp_session(&self, token: &str) -> Result<(), PlatformError> {
self.inner.delete_mcp_session(token)
}
}
fn interleaved(
inner: Arc<dyn DataAdapter>,
on_first_read: impl Fn(&dyn DataAdapter) + Send + Sync + 'static,
) -> Arc<dyn DataAdapter> {
Arc::new(InterleavingAdapter {
inner,
on_first_read: Box::new(on_first_read),
fired: std::sync::atomic::AtomicBool::new(false),
})
}
fn seed_token(data: &dyn DataAdapter, token: &JoinToken) {
let now = now_ts();
data.put_platform_office(&PlatformOffice {
office_id: token.office_id.clone(),
office_slug: token.office_id.clone(),
label: token.office_id.clone(),
office_kind: "office".to_string(),
base_url: String::new(),
status: "planned".to_string(),
created_at: now,
updated_at: now,
})
.expect("seed office");
data.put_office_join_token(&PlatformOfficeJoinToken {
office_id: token.office_id.clone(),
secret_digest: token.secret_digest(),
status: JOIN_TOKEN_STATUS_ACTIVE.to_string(),
note: String::new(),
created_at: now_ts(),
last_used_at: 0,
})
.expect("seed");
}
fn sqlite_at(root: &Path) -> Arc<dyn DataAdapter> {
crate::platform::adapters::data::build_data_adapter(
crate::platform::model::DataAdapterKind::Sqlite,
root,
)
.expect("adapter")
}
#[test]
fn a_revoke_landing_mid_verification_is_not_undone_by_the_use_record() {
let (_tmp, root) = temp_root("lost-update-revoke");
let inner = sqlite_at(&root);
let token = mint_for("office-a", &controller());
seed_token(inner.as_ref(), &token);
let office_id = token.office_id.clone();
let data = interleaved(inner.clone(), move |db| {
let mut record = db
.get_office_join_token(&office_id)
.expect("read")
.expect("row");
record.status = JOIN_TOKEN_STATUS_REVOKED.to_string();
db.put_office_join_token(&record).expect("revoke");
});
let service = ClusterJoinTokenService::new(
data,
ClusterRole::Master,
None,
Some(Arc::new(controller())),
);
let err = service
.verify_office_token(&token.render())
.expect_err("a token revoked mid-verification must not verify");
assert_eq!(err.code, "CLUSTER_JOIN_TOKEN_STALE");
assert_eq!(
inner
.get_office_join_token("office-a")
.expect("read")
.expect("row")
.status,
JOIN_TOKEN_STATUS_REVOKED,
"recording a use must never write `status` back"
);
let _ = fs::remove_dir_all(&root);
}
#[test]
fn a_rotation_landing_mid_verification_is_not_undone_by_the_use_record() {
let (_tmp, root) = temp_root("lost-update-rotate");
let inner = sqlite_at(&root);
let key = controller();
let old = mint_for("office-a", &key);
let fresh = mint_for("office-a", &key);
seed_token(inner.as_ref(), &old);
let replacement = fresh.secret_digest();
let office_id = old.office_id.clone();
let data = interleaved(inner.clone(), move |db| {
let mut record = db
.get_office_join_token(&office_id)
.expect("read")
.expect("row");
record.secret_digest = replacement.clone();
db.put_office_join_token(&record).expect("rotate");
});
let service =
ClusterJoinTokenService::new(data, ClusterRole::Master, None, Some(Arc::new(key)));
let err = service
.verify_office_token(&old.render())
.expect_err("the superseded token must not verify");
assert_eq!(err.code, "CLUSTER_JOIN_TOKEN_STALE");
assert_eq!(
inner
.get_office_join_token("office-a")
.expect("read")
.expect("row")
.secret_digest,
fresh.secret_digest(),
"recording a use must never write `secret_digest` back"
);
let _ = fs::remove_dir_all(&root);
}
#[test]
fn minting_refuses_an_office_with_no_base_url() {
let (_tmp, root) = temp_root("mint-no-base-url");
let data = sqlite_at(&root);
let service = ClusterJoinTokenService::new(
data.clone(),
ClusterRole::Master,
None,
Some(Arc::new(controller())),
);
for base_url in ["", " ", "///"] {
let err = service
.mint(&ClusterJoinTokenMintRequest {
office_id: "office-a".to_string(),
base_url: base_url.to_string(),
..Default::default()
})
.expect_err("an unreachable office must not be minted");
assert_eq!(err.code, "CLUSTER_OFFICE_BASE_URL_REQUIRED");
}
assert!(
data.get_platform_office("office-a")
.expect("read")
.is_none()
);
assert!(
data.get_office_join_token("office-a")
.expect("read")
.is_none()
);
let minted = service
.mint(&ClusterJoinTokenMintRequest {
office_id: "office-a".to_string(),
base_url: "https://office-a.example.com/".to_string(),
..Default::default()
})
.expect("mint");
assert_eq!(minted.office.base_url, "https://office-a.example.com");
let _ = fs::remove_dir_all(&root);
}
#[test]
fn an_uncontested_verification_records_the_use() {
let (_tmp, root) = temp_root("touch");
let data = sqlite_at(&root);
let key = controller();
let token = mint_for("office-a", &key);
seed_token(data.as_ref(), &token);
let service = ClusterJoinTokenService::new(
data.clone(),
ClusterRole::Master,
None,
Some(Arc::new(key)),
);
let verified = service
.verify_office_token(&token.render())
.expect("verify");
assert!(verified.record.last_used_at > 0);
assert_eq!(
data.get_office_join_token("office-a")
.expect("read")
.expect("row")
.last_used_at,
verified.record.last_used_at
);
let _ = fs::remove_dir_all(&root);
}
#[test]
fn each_half_of_the_credential_authenticates_one_direction_only() {
let (_tmp, root) = temp_root("directions");
let key = Arc::new(controller());
let data = sqlite_at(&root);
let token = mint_for("office-a", &key);
seed_token(data.as_ref(), &token);
let controller_side = ClusterJoinTokenService::new(
data.clone(),
ClusterRole::Master,
None,
Some(key.clone()),
);
let office_side = ClusterJoinTokenService::new(
data.clone(),
ClusterRole::Worker,
Some(OfficeJoinIdentity::from_token(token.clone())),
Some(key.clone()),
);
assert!(
office_side.controller_verify_key().is_none(),
"an office must not hold a signing key even if one is handed to it"
);
let call_header = controller_side
.controller_call_header_for("office-a")
.expect("header");
assert!(office_side.controller_call_authenticates(&call_header));
assert!(!office_side.controller_call_authenticates(&token.render()));
assert!(!controller_side.controller_call_authenticates(&call_header));
assert!(!controller_side.controller_call_authenticates(&token.render()));
assert!(controller_side.verify_office_token(&token.render()).is_ok());
let err = office_side
.verify_office_token(&token.render())
.expect_err("an office is not a controller");
assert_eq!(err.code, "CLUSTER_JOIN_TOKEN_NOT_A_CONTROLLER");
let _ = fs::remove_dir_all(&root);
}
}