Skip to content

feat: share a resource in named namespaces alone - #47

Merged
sunib merged 2 commits into
mainfrom
feat/namespaced-shared-watches
Oct 29, 2026
Merged

sunib merged 2 commits into
mainfrom
feat/namespaced-shared-watches

Conversation

@sunib

@sunib sunib commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Lets a resource be shared in named namespaces only, for an identity granted it there with a Role, never a ClusterRole. The motivating case is RoleBindings: Voter wants to watch the grants in its own namespace (voter), so a phone asks "what may I do?" again only when a grant changes, instead of polling a rules review every 4 s. Today, sharing rolebindings would give the shared identity a ClusterRole that reads every grant in the cluster. docs/watches.md already names that as the reason krm-foyer doesn't watch RBAC.

1. -shared-watch-namespaced-resources

  • New flag, e.g. voter/rolebindings.rbac.authorization.k8s.io. Comma-separated namespace/resource; the namespaces of one resource are gathered.
  • serves(scope) sends a stream of such a resource to the shared watch only in its namespaces. In any other namespace, and across every namespace at once (no namespace), the stream is the user's own, opened with their token, as for a resource not shared. The shared identity has no grant there, so routing it there would have been a terminal INTERNAL.
  • Its GroupResource never has AllowAllNamespaces. The parser and newShared both force that off.
  • Refused at startup:
    • a resource named in both flags;
    • a namespaced resource with no namespace;
    • a namespace that is not a DNS label;
    • either resource flag without -shared-watch-token-file, or without the sign-in flags.
  • Either flag alone now turns shared watches on. The "names no resource" error now names both flags.

2. Chart: sharedWatches.namespacedResources

sharedWatches:
  namespacedResources:
    voter: [rolebindings.rbac.authorization.k8s.io]
  • Renders a Role and RoleBinding named <shared>-watches in each namespace, with list/watch on exactly those resources, plus the flag.
  • The -watches ClusterRole and ClusterRoleBinding render only when sharedWatches.resources is non-empty. A namespaced-only install has no cluster-wide watch grant at all, just the reviews ClusterRole.
  • The shared account, its token Secret and the reviews grant render when either list is set.
  • Schema: keys must be DNS labels, each list needs at least one item, and items follow the existing resource pattern.

3. Tests

  • TestANamespacedResourceIsSharedInItsNamespacesAlone opens the resource in its namespace (twice), in another namespace and across all namespaces. It checks that only the first two use the shared token and the shared watch, that the others go with the user's token, and that no SubjectAccessReview is sent about any namespace but the shared one. Mutation-checked: with serves ignoring the namespace, it fails.
  • TestParseNamespacedResources: gathering, refusals, and New refusing both-lists and no-namespace configs.
  • TestParseConfigSharedWatches / TestParseConfigRefuses: the flag on its own, plus the new refusals.
  • TestChartGrantsANamespacedResourceInItsNamespacesAlone: covers the Roles and RoleBindings per namespace, the absence of any ClusterRole rule for those resources, and krm-foyer being told the same namespaces (round-tripped through parseRendered).
  • The pod's-own-account test used to refuse any RoleBinding, and asked to be relaxed once one was checked. It now checks RoleBinding subjects as it does ClusterRoleBinding subjects, with a namespaced case added.
  • e2e, against the real API server (shares a namespaced resource in its namespace alone, and shows nothing of any other). The fixture now shares rolebindings in a fixed foyer-shared namespace. Each namespace holds a RoleBinding with a unique marker name. One spec checks:
    • Alice, allowed in foyer-shared only: her stream there is served from the shared watch (shared-watch gauge +1) and contains that namespace's marker, never the other one.
    • Alice, another namespace: terminal FORBIDDEN, with neither the marker nor the shared account's name in the stream. In the audit log, every request it led to was sent as alice, including the watch of that namespace's RoleBindings.
    • Alice, every namespace at once: the same, with neither marker.
    • Bob, allowed in the other namespace: his own per-user watch (user gauge +1, shared gauge unchanged) shows that namespace's marker only. His requests are audited as bob.
    • The API server: SubjectAccessReviews say the shared identity may not list or watch RoleBindings in the other namespace or cluster-wide, and may watch them in foyer-shared.
    • The audit log: every RoleBinding request the shared identity ever sent is under /namespaces/foyer-shared/rolebindings.
    • Mutation-checked: a build whose serves ignores the namespace fails this spec. The routing of alice's request into another namespace goes through the shared path.

4. Docs

  • docs/design.md (Shared watches) states the contract.
  • docs/watches.md has a new section, "Shared in some namespaces only", plus updates to the identity's needs and to "Why not check only when something changes?".
  • The chart README and the values.yaml comments are updated.

Validation

  • go test -race ./..., fuzz runs, node --test, helm lint, tidy and image smoke are green at eb2dc9d.
  • e2e: the full suite passed at eb2dc9d (comment below). The new spec passed at 5e4fef4, and the full suite with it is running.
  • golangci-lint locally reports errcheck at internal/stream/stream_test.go:299 (<-done). That line isn't touched here, and it fails the same way on main: the devcontainer has golangci-lint 2.15.0, while CI pins 2.13.2. Left for a separate change.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Shared watches can now be limited to selected resources in specified namespaces. Streams outside those namespaces continue to use the user’s own identity.
    • The chart can grant watch access within each configured namespace, without granting cluster-wide access for those resources.
  • Documentation
    • Updated configuration guidance to explain namespace-scoped sharing, permissions, and how it works alongside cluster-wide shared watches.

-shared-watch-namespaced-resources (chart: sharedWatches.namespacedResources)
shares a resource in the namespaces named for it, for an identity granted it
there with a Role, never a ClusterRole. A stream of it in any other namespace,
or across every namespace, is the user's own and never reaches the shared
identity. For RoleBindings: an application can watch the grants in its own
namespace without the shared identity reading every grant in the cluster.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 52 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 62d29ef3-7035-4f69-9208-9fa1c0d8becd
Commits

Reviewing files that changed from the base of the PR and between eb2dc9d and 5e4fef4.

Files selected for processing: 4
  • test/e2e/cluster/deploy-foyer.sh
  • test/e2e/cluster/foyer-values.yaml
  • test/e2e/fixture_test.go
  • test/e2e/foyer_shared_test.go

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ab34c03a-7409-450c-b577-aefc69e7d7c9
Commits

Reviewing files that changed from the base of the PR and between 1764467 and eb2dc9d.

Files selected for processing: 12
  • charts/krm-foyer/README.md
  • charts/krm-foyer/templates/deployment.yaml
  • charts/krm-foyer/templates/shared-watches.yaml
  • charts/krm-foyer/values.schema.json
  • charts/krm-foyer/values.yaml
  • cmd/krm-foyer/chart_test.go
  • cmd/krm-foyer/main.go
  • cmd/krm-foyer/main_test.go
  • docs/design.md
  • docs/watches.md
  • internal/stream/shared.go
  • internal/stream/shared_test.go

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Walkthrough

Shared watches can now be limited to selected namespaces. The CLI validates and passes namespace/resource entries to the stream backend, which uses the shared identity only for configured namespaces. The Helm chart renders namespace-scoped Roles and RoleBindings for those resources.

Changes: Namespace-scoped shared watches

Layer / File(s) Summary
Parse configuration and select streams
internal/stream/shared.go, internal/stream/shared_test.go
The stream configuration accepts comma-separated namespace/resource entries and groups repeated resources by namespace. Namespaced resources use the shared stream only in configured namespaces; other scopes use the user stream. Tests cover parsing, validation, stream selection, and authorization scope.
Configure namespaced watches in the CLI
cmd/krm-foyer/main.go, cmd/krm-foyer/main_test.go, docs/design.md, docs/watches.md
The CLI adds -shared-watch-namespaced-resources, validates it with the other shared-watch options, and includes the parsed entries in SharedConfig. Tests cover valid and rejected configurations. Documentation describes scope selection and authorization behavior.
Render namespace-scoped chart permissions
charts/krm-foyer/values.schema.json, charts/krm-foyer/values.yaml, charts/krm-foyer/templates/*, charts/krm-foyer/README.md, cmd/krm-foyer/chart_test.go
The chart accepts namespace-to-resource mappings and passes them to the application. It renders a Role and RoleBinding in each configured namespace, while cluster-scoped RBAC is conditional on cluster-wide resources. Chart tests check validation, rendered permissions, and application configuration.

Priority: Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant RequestScope
  participant serves
  participant SharedWatch
  participant UserStream
  RequestScope->>serves: Match resource and namespace
  alt Namespace is configured
    serves->>SharedWatch: Select shared identity
  else Namespace is not configured or scope is all namespaces
    serves->>UserStream: Select user identity
  end
Loading
Merge Risk: Minimal · previous review eb2dc

The namespace-scoped watch change is mergeable after normal checks; no concrete blocking issue was identified.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 72.73% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: sharing a resource only in specified namespaces.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 72.73% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 11 functions across 5 files. (7 skipped: 7 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@sunib

sunib commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

e2e is green on this code (eb2dc9d): go test -tags e2e ./test/e2e/ passed in 358 s against a freshly deployed fixture, the same run as the tidy, Helm lint, race tests, fuzzing and image smoke listed above. The only local gate failure is still the existing errcheck at internal/stream/stream_test.go:299, which comes from golangci-lint 2.15.0 here versus the 2.13.2 CI pins.

…paces

The e2e fixture shares RoleBindings in foyer-shared alone. One spec opens them
there, in another namespace, and across every namespace, as a user allowed in
the shared namespace only and as one allowed in the other. It checks the streams'
contents, the watch metrics, SubjectAccessReviews of the shared identity, and
the audit log: the identity read RoleBindings in foyer-shared and nowhere else.
Mutation-checked: with serves() ignoring the namespace, it fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sunib

sunib commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

Full e2e suite green at 5e4fef4, including the new spec shares a namespaced resource in its namespace alone, and shows nothing of any other: ok github.com/ConfigButler/krm-foyer/test/e2e 361.810s. The fixture was redeployed from this branch first, after the mutation check.

@sunib
sunib merged commit 79f188e into main Oct 29, 2026
16 of 17 checks passed
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