87ef52cc80
The per-subject data-plane ACL existed since 0003e (membership.SubjectACLFor +
busauth.NewNkeyAuthenticatorACL, unit-tested in TestSubjectACLIsolation) but the
binary never used it: cmd/membershipd installed the plain NewNkeyAuthenticator, so
in production a registered NON-member could open a raw NATS connection,
Subscribe(">"), and harvest every room's subject plus JetStream stream/advisory
activity (payload stayed E2E ciphertext, metadata leaked) — the re-audit's H4
vector (report 0006).
Fix:
- New busauth.PermissionsFromSubjects adapts a subject-deriving function into the
PermissionsFunc the ACL authenticator expects (subjects granted as both the
publish and subscribe allow set; a derivation error fails closed). It lives in
busauth so membership stays free of the nats-server dependency.
- cmd/membershipd, under enforce, now installs
NewNkeyAuthenticatorACL(store.IsAuthorized,
PermissionsFromSubjects(membership.SubjectACLFor(store)))
so every connection is confined to the subjects of the rooms it belongs to plus
the client-infra subjects.
- pkg/membership/acl_test.go's helper now delegates to the production wiring
(PermissionsFromSubjects) instead of a test-only reimplementation, so the tests
exercise the real path.
Verification (pkg/membership/acl_test.go):
- TestReaudit_H4_WildcardMetadataLeak: a non-member's Subscribe(">") and any
foreign-subject subscribe raise permission violations; the member still pub/subs
her own room and the non-member captures nothing. With the plain authenticator
(the pre-0005e wiring) the test fails ("wildcard metadata leak still open"),
confirming the wiring is what closes it.
- TestSubjectACLIsolation / TestRefreshSessionGainsNewRoom still green.
- CGO_ENABLED=0 go build ./... && go vet ./... && go test -count=1 ./... green.
Residual (documented): the client-infra grant includes "$JS.API.>", shared by all
peers so per-connection JetStream works; a peer that subscribes specifically to
"$JS.API.>" can still observe stream-management requests whose subjects embed the
room-derived stream name. Fully closing that needs NATS accounts/permissions per
identity (deferred to the 0003 decentralization line). Operational note: NATS
freezes permissions at connect time, so clients must client.RefreshSession after a
membership change to gain a new room's subject; cmd/chat and cmd/worker do not yet
call it, a functional gap to close before an enforce+ACL deployment.
Refs: report 0006 H4, issue 0005e.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
27 lines
1.3 KiB
Go
27 lines
1.3 KiB
Go
package busauth
|
|
|
|
import server "github.com/nats-io/nats-server/v2/server"
|
|
|
|
// PermissionsFromSubjects adapts a subject-deriving function (e.g.
|
|
// membership.SubjectACLFor, which maps an identity to the subjects of the rooms
|
|
// it belongs to plus the client infrastructure subjects) into the PermissionsFunc
|
|
// the ACL authenticator expects. The derived subjects are granted as BOTH the
|
|
// publish and subscribe allow set, so a connection can only pub/sub on the
|
|
// subjects it is entitled to. A derivation error is propagated so the caller
|
|
// fails closed (denies the connection) rather than granting open access.
|
|
//
|
|
// This is the production wiring for the per-subject data-plane ACL (issue 0003e,
|
|
// audit H4): membershipd passes PermissionsFromSubjects(membership.SubjectACLFor(
|
|
// store)) to NewNkeyAuthenticatorACL. It lives in busauth (not membership) so the
|
|
// membership package stays free of the nats-server dependency.
|
|
func PermissionsFromSubjects(derive func(signPubHex string) ([]string, error)) PermissionsFunc {
|
|
return func(signPubHex string) (*server.Permissions, error) {
|
|
subjects, err := derive(signPubHex)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sp := &server.SubjectPermission{Allow: subjects}
|
|
return &server.Permissions{Publish: sp, Subscribe: sp}, nil
|
|
}
|
|
}
|