swarm: split the deploy subject per hive
Per mara on the PR: *"split by hive. its not a security thing, just so hives dont get messages they dont care about."* She agreed with the finding and still wanted the split, which is the part worth recording. I measured that a per-hive subject gives no confidentiality — `sub` is unrestricted, so a hive that wanted another's messages could subscribe to them — and concluded it bought nothing. "Nothing" is a claim over every axis and I had checked one. The axis I never priced: every hive in the swarm being woken by every other hive's deploys. So `deploy_subject(hive)` replaces the single literal, and the payload drops `hive` to carry only the agent — the subject names the hive, and two places stating one fact are free to disagree. The hive subscribes to its own subject and no longer filters. The grant is a wildcard rather than a subject per hive because the responder has no roster: it cannot enumerate hives, and a grant that had to track one would be a second place to get the list wrong — the same argument `hive_name`'s doc makes about admission. The negative test gets stronger rather than merely adapted. Splitting the family makes "another hive's subject" and "its own" separate strings for the first time, so it now asserts a hive reaches neither, nor the wildcard.
This commit is contained in:
parent
7519d9b904
commit
b004ba3dc5
4 changed files with 73 additions and 71 deletions
|
|
@ -278,12 +278,14 @@ impl Policy {
|
|||
// about a change, with nothing in the controller's log to say a
|
||||
// permission was the reason.
|
||||
swarm_queue_client::KNOWLEDGE_SUBJECT.to_owned(),
|
||||
// The deploy event, same shape and the same failure mode as the
|
||||
// knowledge event above — one literal subject, one writer. Also
|
||||
// deliberately not a per-hive family: this responder scopes
|
||||
// publish only, so a per-hive subject would not stop a hive
|
||||
// reading another's. See the const's own doc.
|
||||
swarm_queue_client::DEPLOY_SUBJECT.to_owned(),
|
||||
// The deploy events: one subject per hive, so a hive is not woken
|
||||
// by a deploy meant for another. Granted as a wildcard because
|
||||
// this responder has no roster — it cannot enumerate hives, and a
|
||||
// grant that had to track one would be a second place to get the
|
||||
// list wrong (see `hive_name`'s doc for the same argument about
|
||||
// admission). Same failure mode as the knowledge event above: a
|
||||
// refused publish reaches the client as a timeout.
|
||||
swarm_queue_client::DEPLOY_SUBJECT_WILDCARD.to_owned(),
|
||||
]);
|
||||
subjects
|
||||
}
|
||||
|
|
@ -380,36 +382,40 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn a_reader_may_publish_the_deploy_event() {
|
||||
fn a_reader_may_publish_a_deploy_event_to_any_hive() {
|
||||
let p = policy().permissions("swarm-controller").expect("a reader");
|
||||
assert!(
|
||||
p.publish
|
||||
.contains(&swarm_queue_client::DEPLOY_SUBJECT.to_owned()),
|
||||
"the controller is the only publisher of this event; without the \
|
||||
.contains(&swarm_queue_client::DEPLOY_SUBJECT_WILDCARD.to_owned()),
|
||||
"the controller is the only publisher of these events; without the \
|
||||
grant its publish is refused, and a refusal arrives as a timeout"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_hive_may_not_publish_the_deploy_event_to_anyone_including_itself() {
|
||||
fn a_hive_may_not_publish_a_deploy_event_to_anyone_including_itself() {
|
||||
// Same arm as the knowledge event's, and it matters more here: a forged
|
||||
// knowledge event makes a hive re-read a repo, while a forged deploy
|
||||
// event makes it rebuild and restart a named agent. One shared subject
|
||||
// means a single forged message reaches every hive in the swarm.
|
||||
// event makes it rebuild and restart a named agent.
|
||||
//
|
||||
// Note this is the half `sub` scoping would not fix even once it lands:
|
||||
// reading another hive's deploy message is a confidentiality question,
|
||||
// *sending* one is this.
|
||||
// Both directions asserted, because splitting the subject per hive
|
||||
// makes them separate strings for the first time: a hive must reach
|
||||
// neither another hive's deploy subject nor its own. Nothing in the
|
||||
// grant should mention this family at all.
|
||||
let p = policy()
|
||||
.permissions("hive-alpha")
|
||||
.expect("a hive is admitted");
|
||||
assert!(
|
||||
!p.publish
|
||||
.iter()
|
||||
.any(|s| s == swarm_queue_client::DEPLOY_SUBJECT),
|
||||
"a hive must not publish the deploy event: {:?}",
|
||||
p.publish
|
||||
);
|
||||
for forbidden in [
|
||||
swarm_queue_client::deploy_subject("beta"),
|
||||
swarm_queue_client::deploy_subject("alpha"),
|
||||
swarm_queue_client::DEPLOY_SUBJECT_WILDCARD.to_owned(),
|
||||
] {
|
||||
assert!(
|
||||
!p.publish.contains(&forbidden),
|
||||
"a hive must not publish {forbidden}: {:?}",
|
||||
p.publish
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in a new issue