docs(#3297): move each subject set's rationale next to the list it constrains
The module doc had grown a 37-line preamble carrying three separate arguments, which the comment-block lint refuses. Splitting it is the better fix than raising the limit: the reader who is about to widen a subject list meets the reason not to at the list, not seven screens up. Also corrects one claim I had no measurement for. The CREATE note said creating an existing stream with a different config "is an error rather than a rewrite" — asserted, not observed. What is observed is narrower and enough: a hive holding this grant leaves the stream config untouched and never publishes $JS.API.STREAM.UPDATE at all.
This commit is contained in:
parent
61a13a63d4
commit
2e9ce53a32
1 changed files with 33 additions and 27 deletions
|
|
@ -12,31 +12,19 @@
|
|||
//! the failure would be silent. Denying costs a loud refusal the first time a
|
||||
//! new consumer appears, which is a config line to fix.
|
||||
//!
|
||||
//! # Subjects were measured, not reasoned about
|
||||
//!
|
||||
//! The defaults below are the *minimal* sets, established by granting a
|
||||
//! candidate set and then removing each subject in turn to confirm the client
|
||||
//! breaks without it (`state/attack-3297-js-api-door.sh`,
|
||||
//! `state/attack-3297-reader-scope.sh` in atlas's notes). Two things that a
|
||||
//! reader would otherwise reasonably assume, and that are false:
|
||||
//!
|
||||
//! - `$KV.<bucket>.<key>` alone does **not** let a client write that key. The
|
||||
//! client looks the bucket up first, so `$JS.API.STREAM.INFO.KV_<bucket>` is
|
||||
//! part of the minimum.
|
||||
//! - `$JS.API.>` is not "the `JetStream` permission". It also covers
|
||||
//! `$JS.API.STREAM.DELETE.KV_<bucket>`, with which any hive can destroy the
|
||||
//! whole bucket — every hive's data — while being correctly refused on a
|
||||
//! neighbour's individual key. Granting it makes per-key scoping decorative.
|
||||
//!
|
||||
//! # Only publish is scoped
|
||||
//!
|
||||
//! Subscription permissions are left unrestricted. The queue's confidentiality
|
||||
//! boundary is the account, and a hive reading another hive's *published*
|
||||
//! status is not the problem this solves — writing it is. Scoping `sub` is a
|
||||
//! separate change with its own measurement, and claiming it here without one
|
||||
//! would be the same mistake as `$JS.API.>` above.
|
||||
//! Each subject set below is *minimal by removal*, and carries its own note
|
||||
//! saying what breaks without it — the reasoning lives next to the list it
|
||||
//! constrains rather than in one block here, because that is where a reader
|
||||
//! about to edit the list will meet it.
|
||||
|
||||
/// The subjects an admitted client may publish to.
|
||||
///
|
||||
/// **Publish only — subscription is left unrestricted.** The queue's
|
||||
/// confidentiality boundary is the account, and a hive reading another hive's
|
||||
/// *published* status is not the problem this solves; writing it is. Scoping
|
||||
/// `sub` is a separate change with its own measurement, and claiming it here
|
||||
/// without one would repeat the `$JS.API.>` mistake described on
|
||||
/// `Policy::hive_subjects`.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Permissions {
|
||||
/// Subjects allowed for publish. Never empty: an empty allow-list is a
|
||||
|
|
@ -181,14 +169,32 @@ impl Policy {
|
|||
/// connects first therefore has to be able to create it.
|
||||
///
|
||||
/// Narrower than it looks: this is `CREATE` on one named stream, not
|
||||
/// `UPDATE` and not the `$JS.API.>` wildcard. Creating a stream that
|
||||
/// already exists with a different config is an error rather than a
|
||||
/// rewrite, so a second arrival cannot reshape the bucket the first one
|
||||
/// made.
|
||||
/// `UPDATE` and not the `$JS.API.>` wildcard. Measured rather than
|
||||
/// assumed — a hive holding this grant and running `stream edit` leaves
|
||||
/// the stream's config untouched, and no `$JS.API.STREAM.UPDATE` is ever
|
||||
/// published. Worth stating because the failure it would hide is quiet: a
|
||||
/// hive able to reshape the shared bucket could set `MaxMsgs: 1` and evict
|
||||
/// every other hive's status without ever touching `STREAM.DELETE`, and
|
||||
/// per-key scoping would still look intact.
|
||||
fn create(&self) -> String {
|
||||
format!("$JS.API.STREAM.CREATE.{}", self.stream())
|
||||
}
|
||||
|
||||
/// What one hive may publish: the account minimum, the bucket lookup,
|
||||
/// creation, and its **own** key.
|
||||
///
|
||||
/// Two things a reader would reasonably assume, both false and both
|
||||
/// measured (`state/attack-3297-js-api-door.sh` in atlas's notes):
|
||||
///
|
||||
/// - `$KV.<bucket>.<key>` alone does **not** let a client write that key.
|
||||
/// The client resolves the bucket first, so the `STREAM.INFO` subject is
|
||||
/// part of the minimum for a plain write.
|
||||
/// - `$JS.API.>` is not "the `JetStream` permission". It also covers
|
||||
/// `$JS.API.STREAM.DELETE`, with which a hive correctly refused on a
|
||||
/// neighbour's individual key can destroy the whole bucket — every
|
||||
/// hive's data. Granting it would make per-key scoping decorative, which
|
||||
/// is why these are named one at a time and a test asserts the wildcard
|
||||
/// never returns as a convenience.
|
||||
fn hive_subjects(&self, hive: &str) -> Vec<String> {
|
||||
let mut subjects = Self::jetstream_minimum().to_vec();
|
||||
subjects.extend([
|
||||
|
|
|
|||
Loading…
Reference in a new issue