Public dry run dryrun-1bb23a07ddd0
Source commit: 1bb23a07ddd082b491fd20b9be7bd7a6f9c46916 Public tree identity: sha256:098417c1f41d9acc1149ce03c1b86e63bb5a13a9932a15beaedef5c21078f140
This commit is contained in:
parent
9f2d6ec010
commit
a40778221a
6 changed files with 294 additions and 3 deletions
|
|
@ -516,6 +516,35 @@ impl Coordinator {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn revoke_node_credential(
|
||||
&mut self,
|
||||
context: &AuthContext,
|
||||
node: &NodeId,
|
||||
) -> Result<NodeIdentityRecord, CoordinatorError> {
|
||||
let identity = self
|
||||
.durable
|
||||
.node_identities
|
||||
.get(node)
|
||||
.ok_or(CoordinatorError::UnknownNode)?
|
||||
.clone();
|
||||
if identity.tenant != context.tenant || identity.project != context.project {
|
||||
return Err(CoordinatorError::Unauthorized(
|
||||
"node credential is outside the signed-in tenant/project scope".to_owned(),
|
||||
));
|
||||
}
|
||||
if !matches!(context.actor, Actor::User(_)) {
|
||||
return Err(CoordinatorError::Unauthorized(
|
||||
"node credential revocation requires a user identity".to_owned(),
|
||||
));
|
||||
}
|
||||
self.durable.node_identities.remove(node);
|
||||
self.durable.credentials.remove(&format!("node:{node}"));
|
||||
for active in self.active_processes.values_mut() {
|
||||
active.connected_nodes.remove(node);
|
||||
}
|
||||
Ok(identity)
|
||||
}
|
||||
|
||||
pub fn list_projects(&self, context: &AuthContext) -> Vec<ProjectRecord> {
|
||||
self.durable
|
||||
.projects
|
||||
|
|
@ -751,6 +780,71 @@ mod tests {
|
|||
assert!(coordinator.node_identity(&NodeId::from("node")).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn node_credential_revocation_is_project_scoped_and_removes_identity() {
|
||||
let store = InMemoryDurableStore::default();
|
||||
let mut coordinator = Coordinator::boot(&store, 1);
|
||||
coordinator.enroll_node(
|
||||
TenantId::from("tenant"),
|
||||
ProjectId::from("project"),
|
||||
NodeId::from("node"),
|
||||
"public-key",
|
||||
"node:attach",
|
||||
);
|
||||
coordinator.durable.credentials.insert(
|
||||
"node:node".to_owned(),
|
||||
CredentialRecord {
|
||||
subject: "node:node".to_owned(),
|
||||
tenant: TenantId::from("tenant"),
|
||||
project: Some(ProjectId::from("project")),
|
||||
kind: CredentialKind::NodeCredential,
|
||||
public_key_fingerprint: Some(Digest::sha256("public-key")),
|
||||
},
|
||||
);
|
||||
coordinator.start_process(
|
||||
TenantId::from("tenant"),
|
||||
ProjectId::from("project"),
|
||||
ProcessId::from("process"),
|
||||
);
|
||||
coordinator
|
||||
.reconnect_node(
|
||||
&NodeId::from("node"),
|
||||
Some((&ProcessId::from("process"), 1)),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let foreign = coordinator
|
||||
.revoke_node_credential(
|
||||
&AuthContext {
|
||||
tenant: TenantId::from("other"),
|
||||
project: ProjectId::from("project"),
|
||||
actor: Actor::User(UserId::from("user")),
|
||||
},
|
||||
&NodeId::from("node"),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(foreign, CoordinatorError::Unauthorized(_)));
|
||||
|
||||
let revoked = coordinator
|
||||
.revoke_node_credential(
|
||||
&AuthContext {
|
||||
tenant: TenantId::from("tenant"),
|
||||
project: ProjectId::from("project"),
|
||||
actor: Actor::User(UserId::from("user")),
|
||||
},
|
||||
&NodeId::from("node"),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(revoked.id, NodeId::from("node"));
|
||||
assert!(coordinator.node_identity(&NodeId::from("node")).is_none());
|
||||
assert!(!coordinator.durable.credentials.contains_key("node:node"));
|
||||
assert!(!coordinator
|
||||
.active_process(&ProcessId::from("process"))
|
||||
.unwrap()
|
||||
.connected_nodes
|
||||
.contains(&NodeId::from("node")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_listing_is_filtered_by_tenant() {
|
||||
let store = InMemoryDurableStore::default();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue