Public release release-a513a752ee52

Source commit: a513a752ee528b3c50400dde5d86e24c694e35b5

Public tree identity: sha256:ae10e20386250d9dac200a07ef69e1944402e8cb0f9c37a249587595638b3be0
This commit is contained in:
Clusterflux release 2026-07-19 17:50:25 +02:00
commit b9d3e7681a
221 changed files with 81138 additions and 0 deletions

View file

@ -0,0 +1,194 @@
use std::time::{SystemTime, UNIX_EPOCH};
use clusterflux_core::{Actor, AuthContext, CredentialKind, Digest, ProjectId, TenantId, UserId};
use crate::{CliSessionRecord, Coordinator, CoordinatorError, CredentialRecord};
impl Coordinator {
pub fn issue_cli_session(
&mut self,
tenant: TenantId,
project: ProjectId,
user: UserId,
session_secret: &str,
expires_at_epoch_seconds: Option<u64>,
) -> CliSessionRecord {
self.upsert_tenant(tenant.clone());
self.upsert_user(
tenant.clone(),
user.clone(),
CredentialKind::CliDeviceSession,
);
let session_digest = Digest::sha256(session_secret);
let record = CliSessionRecord {
session_digest: session_digest.clone(),
tenant: tenant.clone(),
project: project.clone(),
user: user.clone(),
credential_kind: CredentialKind::CliDeviceSession,
expires_at_epoch_seconds,
revoked: false,
};
self.durable
.cli_sessions
.insert(session_digest.clone(), record.clone());
let credential_subject = format!("cli-session:{}", session_digest.as_str());
self.durable.credentials.insert(
credential_subject.clone(),
CredentialRecord {
subject: credential_subject,
tenant,
project: Some(project),
kind: CredentialKind::CliDeviceSession,
public_key_fingerprint: None,
},
);
record
}
pub fn authenticate_cli_session(
&self,
session_secret: &str,
) -> Result<AuthContext, CoordinatorError> {
self.authenticate_cli_session_at_with_tenant_policy(
session_secret,
unix_timestamp_seconds(),
true,
)
}
pub fn authenticate_cli_session_for_status(
&self,
session_secret: &str,
) -> Result<AuthContext, CoordinatorError> {
self.authenticate_cli_session_at_with_tenant_policy(
session_secret,
unix_timestamp_seconds(),
false,
)
}
pub fn authenticate_cli_session_at(
&self,
session_secret: &str,
now_epoch_seconds: u64,
) -> Result<AuthContext, CoordinatorError> {
self.authenticate_cli_session_at_with_tenant_policy(session_secret, now_epoch_seconds, true)
}
fn authenticate_cli_session_at_with_tenant_policy(
&self,
session_secret: &str,
now_epoch_seconds: u64,
require_active_tenant: bool,
) -> Result<AuthContext, CoordinatorError> {
if session_secret.trim().is_empty() {
return Err(CoordinatorError::Unauthorized(
"CLI session credential is missing".to_owned(),
));
}
let session_digest = Digest::sha256(session_secret);
let record = self
.durable
.cli_sessions
.get(&session_digest)
.ok_or_else(|| {
CoordinatorError::Unauthorized(
"CLI session credential is not recognized".to_owned(),
)
})?;
if record.revoked {
return Err(CoordinatorError::Unauthorized(
"CLI session credential has been revoked".to_owned(),
));
}
if record
.expires_at_epoch_seconds
.is_some_and(|expires_at| expires_at <= now_epoch_seconds)
{
return Err(CoordinatorError::Unauthorized(
"CLI session credential has expired; run clusterflux login --browser again"
.to_owned(),
));
}
if record.credential_kind != CredentialKind::CliDeviceSession {
return Err(CoordinatorError::Unauthorized(
"credential is not a CLI session".to_owned(),
));
}
if require_active_tenant {
self.ensure_tenant_active(&record.tenant)?;
}
Ok(AuthContext {
tenant: record.tenant.clone(),
project: record.project.clone(),
actor: Actor::User(record.user.clone()),
})
}
pub fn revoke_cli_session(
&mut self,
session_secret: &str,
) -> Result<CliSessionRecord, CoordinatorError> {
self.authenticate_cli_session(session_secret)?;
let session_digest = Digest::sha256(session_secret);
let record = self
.durable
.cli_sessions
.get_mut(&session_digest)
.expect("authenticated session must exist");
record.revoked = true;
Ok(record.clone())
}
}
fn unix_timestamp_seconds() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_secs())
.unwrap_or(0)
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use crate::InMemoryDurableStore;
use super::*;
#[test]
fn repeated_logins_for_one_user_create_distinct_session_credentials() {
let store = InMemoryDurableStore::default();
let mut coordinator = Coordinator::boot(&store, 1);
let tenant = TenantId::from("tenant");
let user = UserId::from("user");
coordinator.issue_cli_session(
tenant.clone(),
ProjectId::from("project-one"),
user.clone(),
"session-one",
None,
);
coordinator.issue_cli_session(
tenant,
ProjectId::from("project-two"),
user,
"session-two",
None,
);
let subjects = coordinator
.durable
.credentials
.values()
.map(|credential| credential.subject.clone())
.collect::<BTreeSet<_>>();
assert_eq!(coordinator.durable.cli_sessions.len(), 2);
assert_eq!(subjects.len(), 2);
assert!(subjects
.iter()
.all(|subject| subject.starts_with("cli-session:sha256:")));
}
}