swarm-nats-auth: grant every hive the shared hive-notices stream subjects
This commit is contained in:
parent
044e4020fa
commit
81adf2b6b4
3 changed files with 111 additions and 11 deletions
|
|
@ -180,6 +180,36 @@ impl Policy {
|
|||
format!("$JS.API.STREAM.CREATE.{}", self.stream())
|
||||
}
|
||||
|
||||
/// What every hive needs to open the shared lifecycle-notices stream:
|
||||
/// look it up, and create it if this hive is the first to arrive.
|
||||
///
|
||||
/// **Not per-hive templated, unlike `extra_hive_subjects`.** The KV
|
||||
/// bucket case above namespaces a shared resource *within* itself
|
||||
/// (`$KV.<bucket>.<hive>`, one key per hive); the notices stream has
|
||||
/// no such per-hive split at the `STREAM.INFO`/`STREAM.CREATE` layer
|
||||
/// — the stream itself, not a slice of it, is what every hive's
|
||||
/// `open_or_create` needs to reach before it can publish to its own
|
||||
/// `hive-notices.<hive>` subject (which `extra_hive_subjects` already
|
||||
/// covers, since that one *is* per-hive). Granting these two subjects
|
||||
/// to every hive is therefore correct, not a widening: it is
|
||||
/// `CREATE`/`INFO` on one named stream, the same shape already
|
||||
/// measured safe for the hive-status bucket in [`Self::create`] —
|
||||
/// create-if-absent, never `STREAM.UPDATE`, so no hive can reshape a
|
||||
/// stream another hive (or the swarm-controller, once it reads from
|
||||
/// this stream) already created.
|
||||
fn notices_subjects() -> [String; 2] {
|
||||
// Unlike `Self::stream()` above, no `KV_` prefix: the notices
|
||||
// stream is a plain `JetStream` stream, not a KV bucket, so its
|
||||
// NATS stream name *is* `swarm_queue_client::notices::STREAM`
|
||||
// verbatim — the `KV_` prefix is `create_key_value`'s own
|
||||
// convention, not something every stream carries.
|
||||
let stream = swarm_queue_client::notices::STREAM;
|
||||
[
|
||||
format!("$JS.API.STREAM.INFO.{stream}"),
|
||||
format!("$JS.API.STREAM.CREATE.{stream}"),
|
||||
]
|
||||
}
|
||||
|
||||
/// What one hive may publish: the account minimum, the bucket lookup,
|
||||
/// creation, and its **own** key.
|
||||
///
|
||||
|
|
@ -202,6 +232,7 @@ impl Policy {
|
|||
self.create(),
|
||||
format!("$KV.{}.{hive}", self.bucket),
|
||||
]);
|
||||
subjects.extend(Self::notices_subjects());
|
||||
subjects.extend(
|
||||
self.extra_hive_subjects
|
||||
.iter()
|
||||
|
|
@ -449,6 +480,67 @@ mod tests {
|
|||
assert_eq!(policy().permissions("hive-"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_hive_may_open_the_shared_notices_stream() {
|
||||
// 🩸 The actual defect this closes: `open_or_create`'s `get_stream`
|
||||
// call needs these two before a hive ever reaches its own
|
||||
// `hive-notices.<hive>` publish subject, and neither has a
|
||||
// `{hive}` to go through `extra_hive_subjects`.
|
||||
let p = policy()
|
||||
.permissions("hive-alpha")
|
||||
.expect("a hive is admitted");
|
||||
assert!(
|
||||
p.publish
|
||||
.contains(&"$JS.API.STREAM.INFO.hive-notices".to_owned())
|
||||
);
|
||||
assert!(
|
||||
p.publish
|
||||
.contains(&"$JS.API.STREAM.CREATE.hive-notices".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_notices_stream_grant_is_identical_across_hives() {
|
||||
// Unlike `$KV.<bucket>.<hive>` or an `extra_hive_subjects`
|
||||
// template, these two subjects name the stream itself, not a
|
||||
// per-hive slice of it — every hive gets the exact same two
|
||||
// strings, and that is the correct shape, not an oversight.
|
||||
let alpha = policy()
|
||||
.permissions("hive-alpha")
|
||||
.expect("a hive is admitted");
|
||||
let beta = policy()
|
||||
.permissions("hive-beta")
|
||||
.expect("a hive is admitted");
|
||||
for subject in [
|
||||
"$JS.API.STREAM.INFO.hive-notices",
|
||||
"$JS.API.STREAM.CREATE.hive-notices",
|
||||
] {
|
||||
assert!(alpha.publish.contains(&subject.to_owned()));
|
||||
assert!(beta.publish.contains(&subject.to_owned()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_notices_grant_never_includes_stream_update_or_delete() {
|
||||
// Same invariant `a_hive_grant_never_includes_the_jetstream_wildcard`
|
||||
// holds for the status bucket, restated for the stream this fold
|
||||
// grants CREATE/INFO on: create-if-absent must not become
|
||||
// reshape-or-destroy.
|
||||
let p = policy()
|
||||
.permissions("hive-alpha")
|
||||
.expect("a hive is admitted");
|
||||
assert!(
|
||||
!p.publish
|
||||
.iter()
|
||||
.any(|s| s.contains("hive-notices") && s.contains("STREAM.UPDATE"))
|
||||
);
|
||||
assert!(
|
||||
!p.publish
|
||||
.iter()
|
||||
.any(|s| s.contains("hive-notices") && s.contains("STREAM.DELETE"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extra_subjects_are_scoped_to_the_hive_that_publishes_them() {
|
||||
// The extension point: a second stream (lifecycle notices, say) is
|
||||
|
|
|
|||
Loading…
Reference in a new issue