Skip to content

feat(rbac): wire RBAC into RESP and binary servers - #23

Merged
Saxy merged 5 commits into
feat/rbacfrom
feat/phase-2-role-commands
Jul 31, 2026
Merged

feat(rbac): wire RBAC into RESP and binary servers#23
Saxy merged 5 commits into
feat/rbacfrom
feat/phase-2-role-commands

Conversation

@Saxy

@Saxy Saxy commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Description

Adds the ROLE command family and per-user bcrypt auth over both protocols, replacing --require-pass when an RBAC policy is loaded:

  • internal/rbac: Role/User policy store, rule parser (+cmd, +@cat, ~prefix), atomic hot-swap holder, and zero-alloc session gating
  • RESP: AUTH , ROLE CREATE/SETUSER/DELUSER/DELETE/LIST/GETUSER, -NOAUTH / -NOPERM replies; nopass default-user semantics
  • binary: OpRoleCreate/SetUser/DelUser/Delete/List/GetUser wire codecs, MsgAuth username+password frame, ERR NOT_AUTHORIZED gating on both plaintext and TLS loops
  • server: --rbac-config / TELLSTONE_RBAC_CONFIG, SIGHUP hot-reload
  • client: AuthUser + Role* methods in the network and public client packages
  • example: cmd/example/role drives the full flow over the binary protocol

Component: Networking/RESP, CLI

Type of Change:

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Performance optimization (no change in behavior, improved speed/memory)
  • Refactoring (no functional changes, code cleanup)
  • Build / CI / Documentation

Related Issue

#16


Technical Deep Dive & Context

The policy store is an immutable snapshot behind a single atomic.Pointer swap: readers Load() without blocking or allocating, and writers clone → mutate → republish in one operation. A SessionContext is pinned to a connection at AUTH time and references an immutable *Role, so a SIGHUP hot-reload (which swaps the whole snapshot) never invalidates in-flight sessions; only future auth and fresh lookups observe the new policy.

Authorization on the hot path is one bit test (command permission in a Bitset) plus a key-prefix whitelist scan over raw bytes — no allocations, no locks. Fail-closed: a session with a nil role denies everything. bcrypt verification runs on a bounded worker pool off the gnet event loop so hash work never blocks request processing; a nopass user (empty hash) accepts any password, matching Redis ACL semantics.

RESP and binary paths behave identically: unauthenticated data ops get -NOAUTH / ERR INVALID_AUTH, role-denied ops get -NOPERM / ERR NOT_AUTHORIZED. --rbac-config supersedes --require-pass when both are set. RBAC is disabled by default, so existing deployments see zero change.


Performance & Benchmarks (If Applicable)

No end-to-end server benchmark was run; RBAC is opt-in and disabled by default (zero overhead when off). The added per-op cost when enabled is one bit test + prefix scan. Gating micro-benchmark (go test -bench=. -benchmem ./internal/rbac/):

BenchmarkIsAllowedAllowed-32             462155184   2.539 ns/op   0 B/op   0 allocs/op
BenchmarkIsAllowedDeniedByPrefix-32      449820994   2.638 ns/op   0 B/op   0 allocs/op
Metric Before After Delta
IsAllowed (allowed) n/a 2.54 ns/op, 0 allocs
IsAllowed (denied by prefix) n/a 2.64 ns/op, 0 allocs

How Has This Been Tested?

  • go vet ./... — clean
  • go test ./... — all pass
  • go test -race ./internal/rbac/ ./internal/resp/ ./internal/network/ — pass
  • Unit/integration tests added in internal/rbac, internal/resp, internal/network (auth, nopass, gating, ROLE codecs, client methods, hot-reload)
  • Manual RESP proof over redis-cli: NOAUTH gating, AUTH admin adminsecret, ROLE CREATE/SETUSER/GETUSER/LIST, and per-role denial (alice read-only, bob write-only, carol ~users:*-scoped) — all returned expected OK / NOPERM replies
  • Manual binary-protocol proof via cmd/example/role: admin creates a role + user, the user's GET passes and SET / out-of-namespace GET are denied

Checklist

  • My code follows the existing code style of this project
  • I have added tests that prove my fix/feature works
  • New and existing tests pass locally (go test ./... and go test -race ./...)
  • I have updated the documentation (README, comments, or any relevant docs)
  • My changes generate no new go vet warnings
  • Any breaking changes are documented and communicated

Summary by CodeRabbit

  • New Features

    • Added optional role-based access control with YAML/JSON policy configuration.
    • Added user authentication, passwordless users, command permissions, and namespace restrictions.
    • Added runtime role and user management, including role creation, assignment, listing, lookup, and deletion.
    • Added RBAC support to RESP and native clients, with hot-reload support via SIGHUP.
  • Bug Fixes

    • Improved server error handling so error responses are distinguished from stored data.
    • Unauthorized operations now return clear authorization errors.
  • Documentation

    • Expanded authentication, RBAC commands, policy examples, and client API documentation.

Add the ROLE command family and per-user bcrypt auth over both protocols,
replacing --require-pass when an RBAC policy is loaded:
- internal/rbac: Role/User policy store, rule parser (+cmd, +@cat, ~prefix),
  atomic hot-swap holder, and zero-alloc session gating
-  RESP: AUTH <user> <pass>, ROLE CREATE/SETUSER/DELUSER/DELETE/LIST/GETUSER,
  -NOAUTH / -NOPERM replies; nopass default-user semantics
- binary: OpRoleCreate/SetUser/DelUser/Delete/List/GetUser wire codecs,
  MsgAuth username+password frame, ERR NOT_AUTHORIZED gating on both
  plaintext and TLS loops
- server: --rbac-config / TELLSTONE_RBAC_CONFIG, SIGHUP hot-reload
- client: AuthUser + Role* methods in the network and public client packages
- example: cmd/example/role drives the full flow over the binary protocol

Signed-off-by: Maximilian Hagen <git@saxy.dev>
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4f94db8f-75dc-452d-890d-c1214e4d11eb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

🧹 Nitpick comments (7)
internal/rbac/config.go (1)

99-102: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider validating the bcrypt hash format at load time.

u.Password is stored as raw bytes assuming it is already a bcrypt hash, per the schema comment at Line 29. There is no check that the value looks like a valid bcrypt hash (for example, the $2a$/$2b$/$2y$ prefix). A malformed hash — for instance, an accidentally pasted plaintext password — will load successfully and fail silently only later, at authentication time, rather than at config load time when the operator can immediately correct it.

Validate the hash format in build() and reject the config early if it looks invalid.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/rbac/config.go` around lines 99 - 102, Update build() to validate
each non-Nopass u.Password as a bcrypt hash before assigning it to hash,
accepting supported bcrypt prefixes and rejecting malformed values with a
configuration-load error. Preserve the existing Nopass behavior and raw hash
assignment for valid passwords.
internal/network/role_test.go (1)

128-203: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Share the ROLE dispatch instead of duplicating it in the test.

The comment on Line 126 states that rbacTestHandler mirrors server.networkHandler. The two implementations can diverge, and the tests then verify a handler that production does not use. That risk matters here, because this is an authorization path.

Export the ROLE dispatch from the server package, or move it into a shared helper that both the server and this test call. Then the test exercises the real decoding, validation, and error mapping.

The nilerr reports on Lines 145, 155, 158, and 174 are false positives. The handler converts domain errors into "ERR ..." wire payloads and returns a nil transport error on purpose.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/network/role_test.go` around lines 128 - 203, Replace the duplicated
ROLE switch in rbacTestHandler with the server’s shared/exported ROLE dispatch
so tests exercise production decoding, validation, and error mapping. Update
server.networkHandler or extract its ROLE handling into a reusable helper, then
have both paths call it; preserve the intentional nil transport errors after
domain failures are encoded as ERR payloads.

Source: Linters/SAST tools

client/client_role.go (1)

17-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a typed password option instead of raw [][]byte tokens.

passOptions requires the caller to construct wire tokens such as []byte(">password") and []byte("nopass"). The caller must know the RESP prefix convention, and a typo produces a server-side error rather than a compile error.

Accept an explicit option instead, for example a Password string plus a NoPass bool field on a small options struct, and build the tokens inside the method. This is optional, and it is a breaking change to a new API, so it is cheapest to do before release.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@client/client_role.go` around lines 17 - 24, Update RoleSetUser to accept a
typed options struct containing Password and NoPass instead of raw passOptions
[][]byte, and construct the required wire tokens internally using the existing
password-option conventions. Preserve the documented last-option-wins behavior
and nopass password-clearing semantics while keeping validation and delegation
through c.valid and c.c.RoleSetUser.
config/config.go (1)

284-292: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Align the environment variable prefix with the other flags.

Every other option in LoadConfig reads a TSD_-prefixed variable (TSD_ADDR, TSD_REQUIRE_PASS, TSD_TLS_CERT). This flag reads TELLSTONE_RBAC_CONFIG. Operators must then remember two prefixes. Consider TSD_RBAC_CONFIG, optionally with TELLSTONE_RBAC_CONFIG kept as a fallback for compatibility.

♻️ Proposed change
 	fs.StringVar(
 		&cfg.rbacConfig,
 		"rbac-config",
-		getEnv("TELLSTONE_RBAC_CONFIG", ""),
+		getEnv("TSD_RBAC_CONFIG", ""),
 		"Path to YAML/JSON RBAC policy file (roles, users, default_role); empty disables RBAC (default: none)",
 	)

The doc comment on line 122 needs the same update.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@config/config.go` around lines 284 - 292, Update the rbacConfig environment
lookup in LoadConfig to use TSD_RBAC_CONFIG, while retaining
TELLSTONE_RBAC_CONFIG only as a compatibility fallback if appropriate. Also
update the related doc comment to document the TSD_ prefix and fallback behavior
consistently with the other flags.
internal/resp/role_test.go (1)

15-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for key-prefix denial.

Every role in rbacTestPolicy uses ~*, so role.AllowsKey always returns true and the namespace half of IsAllowed is never exercised. Add a role restricted to one prefix, for example +get ~app:, and assert that GET app:k succeeds while GET other:k returns NOPERM. That covers the key-scan path that the PR objectives describe.

Also applies to: 87-101

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/resp/role_test.go` around lines 15 - 41, Extend rbacTestPolicy with
a role/user restricted to the app: key prefix using GET permission, then add
IsAllowed/command test coverage asserting GET app:k succeeds and GET other:k
returns NOPERM. Ensure the assertions exercise the role’s key-prefix denial path
rather than the existing ~* roles.
internal/resp/server.go (1)

103-112: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Skip the --require-pass hash when a policy store is configured.

Lines 83-84 state that requirePassHash is ignored when a policy exists, and auth routes to authRBAC before reading it. internal/network/server.go line 122 already guards the hash computation with policy == nil. Apply the same guard here so the two servers stay symmetric and the dead hash is not built.

♻️ Proposed change
-	if requirePass != "" {
+	if requirePass != "" && policy == nil {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/resp/server.go` around lines 103 - 112, Guard the bcrypt hash
generation in the requirePass initialization block with the policy-store absence
condition, matching the existing policy == nil behavior in the other server.
Ensure requirePass is hashed only when no policy is configured, while preserving
the current invalid-password panic behavior when hashing is performed.
server/server.go (1)

146-155: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

The SIGHUP goroutine outlives Run.

defer signal.Stop(hup) stops signal delivery, but the channel is never closed, so the for range hup goroutine stays blocked after Run returns. The process normally exits at that point, so the impact is limited to tests that call Run repeatedly. Close the channel after signal.Stop, or select on the shutdown context.

♻️ Proposed change
 	hup := make(chan os.Signal, 1)
 	signal.Notify(hup, syscall.SIGHUP)
-	defer signal.Stop(hup)
+	defer func() {
+		signal.Stop(hup)
+		close(hup)
+	}()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/server.go` around lines 146 - 155, Update the SIGHUP handling
goroutine around signal.Notify and reloadRBAC so it exits when Run returns,
using the existing shutdown context if available or otherwise coordinating
channel closure with signal.Stop. Ensure cleanup does not race with signal
delivery and preserve RBAC reload behavior while Run is active.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@client/client_role.go`:
- Around line 43-56: Move the response types used by RoleList and RoleGetUser
from internal/network into the client package, then update Client.RoleList and
Client.RoleGetUser to return the client-owned types and convert the underlying
c.RoleList and c.RoleGetUser results in the wrappers. Ensure the public API no
longer exposes any network package types.

In `@cmd/example/role/main.go`:
- Around line 90-103: Update the authorization checks around alice.Get and
alice.Set to fail closed: seed users:1 with the administrator client before
testing it if a missing key can produce an error, require the allowed GET to
succeed, require denied operations to return an error containing NOT_AUTHORIZED,
and call log.Fatalf for connection, authentication, protocol, or
unexpected-success results instead of printing them as valid outcomes.

In `@go.mod`:
- Line 40: Move gopkg.in/yaml.v3 from the indirect require section into the
direct require block, removing the stale // indirect marker. Run go mod tidy
afterward and retain its resulting go.mod changes.

In `@internal/network/client_role.go`:
- Around line 112-131: Document in Client.AuthUser that credentials are sent in
plaintext at the protocol layer and require a TLS-established connection via
DialTLS. Update RoleSetUser to reject password-bearing passOptions unless the
client connection was created with DialTLS, while preserving non-password role
updates and existing authentication behavior.

In `@internal/network/client.go`:
- Around line 98-106: Update the request/response handling around errReply and
MsgResponse so failures are identified by a dedicated MessageType rather than by
inspecting an "ERR " payload prefix; return data values unchanged, including
values beginning with "ERR ", and decode error details only from the dedicated
error frame. If the protocol change cannot be made here, document the
ERR-prefixed value collision in errReply’s doc comment.
- Around line 130-132: Update all callers of the network client’s Set, Get, and
Delete methods to handle their new error returns, including ResponseNotFound,
ResponseNotAuthorized, and other ERR payloads, rather than treating those
payloads as values. Preserve existing success handling, propagate or handle
errors appropriately at each caller, and document this breaking API change in
the release notes.

In `@internal/network/role_test.go`:
- Around line 205-225: Update startRBACNetworkServer to use
net.ListenConfig.Listen with an explicit context when reserving the address, and
capture the error returned by srv.ListenAndServe in a channel instead of
discarding it. Have the readiness wait path report the captured server error
when startup fails, while preserving the existing cleanup and successful startup
behavior.
- Around line 71-99: Update TestRoleListCodec to compare each decoded namespace
byte slice with the corresponding entries[i].Namespaces value using bytes.Equal,
in addition to the existing count checks. Extend the test inputs with a
namespace token longer than 65535 bytes so EncodeRoleArgs and
DecodeRoleListResponse exercise the length-prefix boundary while preserving the
existing round-trip assertions.

In `@internal/network/role.go`:
- Around line 133-189: Update DecodeRoleListResponse so each namespace stored in
e.Namespaces is copied into independent memory before appending, rather than
retaining a subslice of payload; leave the existing bounds checks and command
handling unchanged.
- Around line 23-38: Update EncodeRoleArgs to reject inputs that exceed the
uint16 wire limits instead of truncating lengths: validate each token against
the documented 64 KiB cap and validate the argument count before encoding.
Propagate the validation error through roleRequestPayload and the affected
exported Role* client methods, updating their signatures and callers as needed;
do not emit a partially encoded payload for invalid input.

In `@internal/network/server.go`:
- Around line 629-643: Update opAuthorized to call the keyless authorization
check for all ROLE opcodes, using the same authorizedCmd behavior as the RESP
server; retain msg.Key-based checks for non-ROLE commands.

In `@internal/rbac/config.go`:
- Around line 96-104: Update the user validation in Parse before constructing
the User value: reject any user with an empty Password unless Nopass is
explicitly true, while preserving the existing conflict check for Nopass with a
non-empty password. Ensure omitted or misspelled password fields therefore fail
configuration loading instead of creating a passwordless account.

In `@internal/rbac/manager.go`:
- Around line 21-38: Protect every Store mutation helper with a sync.Mutex held
across the complete Load, validation, clone/mutation, and Store sequence. Update
CreateRole (internal/rbac/manager.go:21-38), the helper at
internal/rbac/manager.go:43-56, the helper at internal/rbac/manager.go:60-68,
and DeleteRole (internal/rbac/manager.go:73-85); lock before each s.Load() and
unlock after s.Store(p), preserving existing validation and error paths.

In `@internal/resp/role.go`:
- Around line 68-82: Update roleSetUser to reject calls with exactly the
required user and role arguments unless an explicit password option is provided;
require either a >password or nopass option before calling
rbac.PasswordFromOpts. Apply the same validation rule to the binary ROLE SETUSER
handler so omitted password options cannot create passwordless users.

In `@internal/resp/server.go`:
- Around line 527-533: Update the authentication flow around UserFor to always
perform bcrypt verification, using the user’s PasswordHash for existing users
and a package-level dummyAuthHash generated once with the policy’s bcrypt cost
when the username is unknown. Preserve authFailed behavior while ensuring
missing-user and wrong-password paths incur the same bcrypt work.

In `@README.md`:
- Around line 192-195: Update the README authentication sample to remove the
passwordless default administrator: either delete the `default` user or assign
it a least-privileged role, and set `default_role` to the role intended for
unassigned users rather than `admin`. Preserve the explicitly password-protected
`admin` and `alice` examples.

In `@server/server.go`:
- Around line 417-427: Update the argument validation in Server.roleCreate so it
accepts a role name plus at least one rule, matching the RESP handler’s
contract; retain rejection for fewer than two total arguments and leave rule
construction and role creation unchanged.
- Around line 390-405: In the operation dispatch containing network.OpRoleCreate
through network.OpRoleGetUser, add a guard that checks whether s.policy is nil
before invoking any role handler; return the existing RESP-compatible
RBAC-disabled error response (the “ERR RBAC is not enabled” path) and only
dispatch to roleCreate, roleSetUser, roleDelUser, roleDelete, roleList, or
roleGetUser when the policy store is present.

---

Nitpick comments:
In `@client/client_role.go`:
- Around line 17-24: Update RoleSetUser to accept a typed options struct
containing Password and NoPass instead of raw passOptions [][]byte, and
construct the required wire tokens internally using the existing password-option
conventions. Preserve the documented last-option-wins behavior and nopass
password-clearing semantics while keeping validation and delegation through
c.valid and c.c.RoleSetUser.

In `@config/config.go`:
- Around line 284-292: Update the rbacConfig environment lookup in LoadConfig to
use TSD_RBAC_CONFIG, while retaining TELLSTONE_RBAC_CONFIG only as a
compatibility fallback if appropriate. Also update the related doc comment to
document the TSD_ prefix and fallback behavior consistently with the other
flags.

In `@internal/network/role_test.go`:
- Around line 128-203: Replace the duplicated ROLE switch in rbacTestHandler
with the server’s shared/exported ROLE dispatch so tests exercise production
decoding, validation, and error mapping. Update server.networkHandler or extract
its ROLE handling into a reusable helper, then have both paths call it; preserve
the intentional nil transport errors after domain failures are encoded as ERR
payloads.

In `@internal/rbac/config.go`:
- Around line 99-102: Update build() to validate each non-Nopass u.Password as a
bcrypt hash before assigning it to hash, accepting supported bcrypt prefixes and
rejecting malformed values with a configuration-load error. Preserve the
existing Nopass behavior and raw hash assignment for valid passwords.

In `@internal/resp/role_test.go`:
- Around line 15-41: Extend rbacTestPolicy with a role/user restricted to the
app: key prefix using GET permission, then add IsAllowed/command test coverage
asserting GET app:k succeeds and GET other:k returns NOPERM. Ensure the
assertions exercise the role’s key-prefix denial path rather than the existing
~* roles.

In `@internal/resp/server.go`:
- Around line 103-112: Guard the bcrypt hash generation in the requirePass
initialization block with the policy-store absence condition, matching the
existing policy == nil behavior in the other server. Ensure requirePass is
hashed only when no policy is configured, while preserving the current
invalid-password panic behavior when hashing is performed.

In `@server/server.go`:
- Around line 146-155: Update the SIGHUP handling goroutine around signal.Notify
and reloadRBAC so it exits when Run returns, using the existing shutdown context
if available or otherwise coordinating channel closure with signal.Stop. Ensure
cleanup does not race with signal delivery and preserve RBAC reload behavior
while Run is active.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 824e7f54-aad3-4563-9bbb-dbccc19adc8a

📥 Commits

Reviewing files that changed from the base of the PR and between a554a9f and 423d07f.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (33)
  • README.md
  • client/client_role.go
  • cmd/example/role/main.go
  • config/config.go
  • go.mod
  • internal/metrics/metrics_test.go
  • internal/network/benchmark_tls_test.go
  • internal/network/client.go
  • internal/network/client_role.go
  • internal/network/protocol.go
  • internal/network/protocol_test.go
  • internal/network/role.go
  • internal/network/role_test.go
  • internal/network/server.go
  • internal/network/server_test.go
  • internal/rbac/config.go
  • internal/rbac/config_test.go
  • internal/rbac/manager.go
  • internal/rbac/manager_test.go
  • internal/rbac/policy.go
  • internal/rbac/policy_test.go
  • internal/rbac/role.go
  • internal/rbac/session.go
  • internal/rbac/session_test.go
  • internal/rbac/user.go
  • internal/rbac/user_test.go
  • internal/resp/protocol.go
  • internal/resp/role.go
  • internal/resp/role_test.go
  • internal/resp/server.go
  • internal/resp/server_test.go
  • internal/shard/runner.go
  • server/server.go

Comment thread client/client_role.go Outdated
Comment thread cmd/example/role/main.go Outdated
Comment thread go.mod Outdated
Comment thread internal/network/client_role.go
Comment thread internal/network/client.go Outdated
Comment thread internal/resp/role.go
Comment thread internal/resp/server.go
Comment thread README.md Outdated
Comment thread server/server.go Outdated
Comment thread server/server.go
Signed-off-by: Maximilian Hagen <git@saxy.dev>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/network/role.go`:
- Around line 117-121: Update EncodeRoleListResponse to reject inputs whose
entry count exceeds math.MaxUint16 before appending the count to the buffer,
returning the existing failure result with no encoded payload. Preserve the
current encoding behavior for valid counts.

In `@server/server.go`:
- Around line 485-489: Sort the ROLE LIST entries by their Name field after
populating entries from p.Roles and before calling
network.EncodeRoleListResponse. Preserve the existing encoding and
error-handling flow while ensuring identical requests produce a stable,
name-ordered response.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 033c289f-7078-4e88-ad08-80644794e9e3

📥 Commits

Reviewing files that changed from the base of the PR and between 423d07f and 7a1a2bd.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (18)
  • README.md
  • client/client_role.go
  • cmd/example/role/main.go
  • config/config.go
  • config/config_test.go
  • go.mod
  • internal/network/client_role.go
  • internal/network/role.go
  • internal/network/role_test.go
  • internal/network/server.go
  • internal/rbac/config.go
  • internal/rbac/config_test.go
  • internal/rbac/manager.go
  • internal/rbac/policy.go
  • internal/resp/role.go
  • internal/resp/role_test.go
  • internal/resp/server.go
  • server/server.go
🚧 Files skipped from review as they are similar to previous changes (9)
  • go.mod
  • README.md
  • cmd/example/role/main.go
  • config/config.go
  • internal/resp/role.go
  • internal/rbac/config.go
  • internal/network/client_role.go
  • internal/resp/server.go
  • internal/network/server.go

Comment thread internal/network/role.go Outdated
Comment thread server/server.go
Saxy added 3 commits July 31, 2026 21:47
Signed-off-by: Maximilian Hagen <git@saxy.dev>
Signed-off-by: Maximilian Hagen <git@saxy.dev>
Signed-off-by: Maximilian Hagen <git@saxy.dev>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/network/README.md`:
- Around line 21-22: Update the payload documentation in the protocol README to
describe the actual binary framing for each request type: MsgRequest as the
operation byte, key length, TTL, key bytes, and value bytes; ROLE requests as
argument count followed by each token’s length and bytes in Value; and MsgAuth
as username length, password length, then credentials. Replace the raw-SQL
description while preserving the existing MsgError and MsgResponse behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ca17393-3a97-4009-b353-6f3e4d0fe5bd

📥 Commits

Reviewing files that changed from the base of the PR and between 4c04600 and fbf5fdb.

📒 Files selected for processing (9)
  • cmd/example/role/main.go
  • internal/network/README.md
  • internal/network/client.go
  • internal/network/client_role.go
  • internal/network/protocol.go
  • internal/network/role_test.go
  • internal/network/server.go
  • internal/network/server_test.go
  • server/server.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • internal/network/protocol.go
  • cmd/example/role/main.go
  • internal/network/client_role.go
  • server/server.go
  • internal/network/server.go

Comment thread internal/network/README.md
@Saxy
Saxy merged commit cbbadef into feat/rbac Jul 31, 2026
5 checks passed
@Saxy
Saxy deleted the feat/phase-2-role-commands branch July 31, 2026 21:16
Saxy added a commit that referenced this pull request Aug 1, 2026
* feat(rbac): add zero-allocation RBAC core engine (#22)

* feat(rbac): add zero-allocation RBAC core engine
Implements Phase 1 of the RBAC design (issue #16): command ID
catalog with bulk categories, dynamic zero-allocation Bitset,
role whitelist rules, rule parser, SessionContext hot-path check,
and an atomically hot-swappable PolicyStore.

IsAllowed benchmarks at 0 allocs/op, ~2.8 ns/op.

Signed-off-by: Maximilian Hagen <git@saxy.dev>

fix(pr): resolve comments

chore(rbac): added test to ensure cmdList is always up2date

Signed-off-by: Maximilian Hagen <git@saxy.dev>

---------

Signed-off-by: Maximilian Hagen <git@saxy.dev>

* feat(rbac): wire RBAC into RESP and binary servers (#23)

* feat(rbac): wire RBAC into RESP and binary servers
Add the ROLE command family and per-user bcrypt auth over both protocols,
replacing --require-pass when an RBAC policy is loaded:
- internal/rbac: Role/User policy store, rule parser (+cmd, +@cat, ~prefix),
  atomic hot-swap holder, and zero-alloc session gating
-  RESP: AUTH <user> <pass>, ROLE CREATE/SETUSER/DELUSER/DELETE/LIST/GETUSER,
  -NOAUTH / -NOPERM replies; nopass default-user semantics
- binary: OpRoleCreate/SetUser/DelUser/Delete/List/GetUser wire codecs,
  MsgAuth username+password frame, ERR NOT_AUTHORIZED gating on both
  plaintext and TLS loops
- server: --rbac-config / TELLSTONE_RBAC_CONFIG, SIGHUP hot-reload
- client: AuthUser + Role* methods in the network and public client packages
- example: cmd/example/role drives the full flow over the binary protocol

Signed-off-by: Maximilian Hagen <git@saxy.dev>

* feat(rbac): add metrics auth and per-role command counters

Signed-off-by: Maximilian Hagen <git@saxy.dev>

* fix(pr): resolve comments

* fix(pr): resolve comments

* fix(startup): reordered inits for graceful shutdown on error

Signed-off-by: Maximilian Hagen <git@saxy.dev>

* fix(rbac): extend documentation and client logger

Signed-off-by: Maximilian Hagen <git@saxy.dev>

---------

Signed-off-by: Maximilian Hagen <git@saxy.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant