Public release release-f70e47af061d
Source commit: f70e47af061d8f7ba27cd769c8cd2e2f8707dc72 Public tree identity: sha256:03a43db60d295811688bedaa5ad6460df0dbde27fbf37033997f4d8100f83ab2
This commit is contained in:
commit
b23e4e5bff
210 changed files with 78295 additions and 0 deletions
147
crates/clusterflux-coordinator/src/service/admin.rs
Normal file
147
crates/clusterflux-coordinator/src/service/admin.rs
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use clusterflux_core::{
|
||||
admin_request_proof_from_token_digest, CredentialKind, Digest, TenantId, UserId,
|
||||
};
|
||||
|
||||
use crate::CoordinatorError;
|
||||
|
||||
use super::{CoordinatorResponse, CoordinatorService, CoordinatorServiceError};
|
||||
|
||||
const ADMIN_REQUEST_MAX_CLOCK_SKEW_SECONDS: u64 = 300;
|
||||
|
||||
impl CoordinatorService {
|
||||
pub(super) fn handle_admin_status(
|
||||
&mut self,
|
||||
tenant: String,
|
||||
actor_user: String,
|
||||
admin_proof: Digest,
|
||||
admin_nonce: String,
|
||||
issued_at_epoch_seconds: u64,
|
||||
) -> Result<CoordinatorResponse, CoordinatorServiceError> {
|
||||
self.verify_admin_request(
|
||||
"admin_status",
|
||||
&tenant,
|
||||
&actor_user,
|
||||
&tenant,
|
||||
&admin_proof,
|
||||
&admin_nonce,
|
||||
issued_at_epoch_seconds,
|
||||
)?;
|
||||
let tenant = TenantId::new(tenant);
|
||||
let actor = UserId::new(actor_user);
|
||||
Ok(CoordinatorResponse::AdminStatus {
|
||||
suspended: self.coordinator.tenant_suspended(&tenant),
|
||||
tenant,
|
||||
actor,
|
||||
safe_default: "read_only".to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn handle_suspend_tenant(
|
||||
&mut self,
|
||||
tenant: String,
|
||||
actor_user: String,
|
||||
target_tenant: String,
|
||||
admin_proof: Digest,
|
||||
admin_nonce: String,
|
||||
issued_at_epoch_seconds: u64,
|
||||
) -> Result<CoordinatorResponse, CoordinatorServiceError> {
|
||||
self.verify_admin_request(
|
||||
"suspend_tenant",
|
||||
&tenant,
|
||||
&actor_user,
|
||||
&target_tenant,
|
||||
&admin_proof,
|
||||
&admin_nonce,
|
||||
issued_at_epoch_seconds,
|
||||
)?;
|
||||
let actor_tenant = TenantId::new(tenant);
|
||||
let actor = UserId::new(actor_user);
|
||||
let target_tenant = TenantId::new(target_tenant);
|
||||
self.coordinator.upsert_tenant(actor_tenant.clone());
|
||||
self.coordinator.upsert_user(
|
||||
actor_tenant,
|
||||
actor.clone(),
|
||||
CredentialKind::CliDeviceSession,
|
||||
);
|
||||
let policy = self
|
||||
.coordinator
|
||||
.suspend_tenant(target_tenant.clone(), actor.clone());
|
||||
self.persist_durable_state()?;
|
||||
Ok(CoordinatorResponse::TenantSuspended {
|
||||
tenant: target_tenant,
|
||||
actor,
|
||||
policy,
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn verify_admin_request(
|
||||
&mut self,
|
||||
operation: &str,
|
||||
tenant: &str,
|
||||
actor_user: &str,
|
||||
target_tenant: &str,
|
||||
admin_proof: &Digest,
|
||||
admin_nonce: &str,
|
||||
issued_at_epoch_seconds: u64,
|
||||
) -> Result<(), CoordinatorServiceError> {
|
||||
let expected = self.admin_token_digest.as_ref().ok_or_else(|| {
|
||||
CoordinatorError::Unauthorized(
|
||||
"self-hosted admin credential is not configured".to_owned(),
|
||||
)
|
||||
})?;
|
||||
if admin_nonce.trim().is_empty() || admin_nonce.len() > 256 {
|
||||
return Err(CoordinatorError::Unauthorized(
|
||||
"admin request nonce is missing or invalid".to_owned(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|duration| duration.as_secs())
|
||||
.unwrap_or_default();
|
||||
if now.abs_diff(issued_at_epoch_seconds) > ADMIN_REQUEST_MAX_CLOCK_SKEW_SECONDS {
|
||||
return Err(CoordinatorError::Unauthorized(
|
||||
"admin request timestamp is outside the allowed 300-second window".to_owned(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
let expected_proof = admin_request_proof_from_token_digest(
|
||||
expected,
|
||||
operation,
|
||||
tenant,
|
||||
actor_user,
|
||||
target_tenant,
|
||||
admin_nonce,
|
||||
issued_at_epoch_seconds,
|
||||
);
|
||||
if admin_proof != &expected_proof {
|
||||
return Err(CoordinatorError::Unauthorized(
|
||||
"admin request proof is invalid".to_owned(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
self.admin_replay_nonces.retain(|_, issued_at| {
|
||||
now <= issued_at.saturating_add(ADMIN_REQUEST_MAX_CLOCK_SKEW_SECONDS)
|
||||
});
|
||||
if self.admin_replay_nonces.contains_key(admin_nonce) {
|
||||
return Err(CoordinatorError::Unauthorized(
|
||||
"admin request nonce was already used".to_owned(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
if self.admin_replay_nonces.len() >= super::MAX_REPLAY_NONCES_PER_AUTHORITY {
|
||||
return Err(CoordinatorError::Unauthorized(
|
||||
"admin request replay window is full; retry after the bounded signature window advances"
|
||||
.to_owned(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
self.admin_replay_nonces
|
||||
.insert(admin_nonce.to_owned(), issued_at_epoch_seconds);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue