Skip to content

[RBAC PR 5.1] Auto-assign owners for approved namespaces - #2428

Open
philipfweiss wants to merge 5 commits into
mainfrom
rbac-creator-auto-ownership
Open

[RBAC PR 5.1] Auto-assign owners for approved namespaces#2428
philipfweiss wants to merge 5 commits into
mainfrom
rbac-creator-auto-ownership

Conversation

@philipfweiss

@philipfweiss philipfweiss commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Tracking: #2234 (step 5.1).

Today, if Alice creates personal.alice while DJ is permissive, DJ records the namespace without giving Alice an RBAC grant. When write restrictions are later enabled for that namespace, Alice cannot manage what she created, so an administrator has to repair ownership.

This PR adds opt-in first-creator ownership for configured personal and ad hoc namespaces:

  • CREATOR_OWNED_NAMESPACE_PATTERNS accepts exact names and trailing .* subtrees. It is empty by default.
  • The first human creator receives namespace:<name>:owners, with MANAGE on the namespace, descendant namespaces, and contained nodes.
  • The namespace, role, scopes, assignment, and namespace/RBAC history entries commit together.
  • It uses the durable boundary marker and PostgreSQL transaction lock from #2408, so role renames and concurrent claims cannot create nested boundaries.
  • Service accounts cannot claim a new boundary. Creation under an existing governed boundary does not create a nested owner role.

Patterns are first-claim pools. With personal.*, any user already authorized to create a namespace can claim an unused personal.<name>, so operators should only configure prefixes intended for self-service.

CREATOR_OWNED_NAMESPACE_PATTERNS='["personal.*", "scratch"]'

Nonmatching and existing namespaces keep their current behavior. This adds no migration or backfill. #2408 is merged, and this draft is rebased on main.


Verification:

Started a local FastAPI harness on :18082 with a fresh SQLite database, the production namespace router and models, permissive RBAC, two principals (alice as USER, deploy-bot as SERVICE_ACCOUNT), and:

CREATOR_OWNED_NAMESPACE_PATTERNS='["personal.*"]'
  1. A nonmatching namespace kept the existing behavior.
curl -X POST -H 'X-User: alice' \
  http://127.0.0.1:18082/namespaces/team.finance/
# HTTP 201; owner roles = 0
  1. A matching namespace assigned its creator and all boundary scopes.
curl -X POST -H 'X-User: alice' \
  http://127.0.0.1:18082/namespaces/personal.alice/
# HTTP 201
# namespace:personal.alice:owners -> alice
# MANAGE namespace personal.alice
# MANAGE namespace personal.alice.*
# MANAGE node personal.alice.*
  1. A service account could not claim the first boundary, and no partial rows remained.
curl -X POST -H 'X-User: deploy-bot' \
  http://127.0.0.1:18082/namespaces/personal.bot/
# HTTP 422; namespaces = 0; owner roles = 0
  1. Creation below Alice's boundary did not generate a nested owner role.
curl -X POST -H 'X-User: deploy-bot' \
  http://127.0.0.1:18082/namespaces/personal.alice.project/
# HTTP 201; namespace:personal.alice.project:owners roles = 0
  1. Repeated the matching path in a fresh SQLite metadata database, inspected the marker and history, renamed Alice's generated role, and created a child as the service account.
created=created,boundary_marker=True
history=role:1,assignment:1
renamed_role_child_owner=None

@philipfweiss
philipfweiss force-pushed the rbac-auto-ownership-clean branch from d383991 to e89ed8b Compare August 18, 2026 20:29
@philipfweiss
philipfweiss force-pushed the rbac-creator-auto-ownership branch from a92a340 to ab0c9b0 Compare August 18, 2026 20:46
Base automatically changed from rbac-auto-ownership-clean to main August 18, 2026 22:24
Philip Weiss added 4 commits August 18, 2026 15:26
Assign personal and ad hoc namespace creators a scoped owner role only within configured patterns, with atomic and serialized boundary creation.
Keep the ownership path focused on configuration, atomic role assignment, and overlap safety while removing parser relocation and repetitive tests.
Use the base boundary marker and RBAC history so role renames and audit behavior stay consistent across both provisioning paths.
Exercise pattern matching through namespace behavior and remove incidental code churn after the base PR merged.
@philipfweiss
philipfweiss force-pushed the rbac-creator-auto-ownership branch from ab0c9b0 to f38206b Compare August 18, 2026 22:41
@netlify

netlify Bot commented Aug 18, 2026

Copy link
Copy Markdown

Deploy Preview for thriving-cassata-78ae72 canceled.

Name Link
🔨 Latest commit e56e91a
🔍 Latest deploy log https://app.netlify.com/projects/thriving-cassata-78ae72/deploys/6a863514fb3a43000831e9ca

@philipfweiss
philipfweiss marked this pull request as ready for review August 18, 2026 22:46

# Exact namespaces or subtrees where a first human creator becomes the owner.
# CREATOR_OWNED_NAMESPACE_PATTERNS uses JSON list syntax, for example
# ["personal.*", "scratch"].

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A pattern like personal.* only matches child namespaces; it does not match the root namespace personal

If both are wanted, would personal* be sufficient, or would ["personal", "personal.*"] be preferred?

This could be a common use case, and it is worth having comments and examples here.

await access_checker.check(on_denied=AccessDenialMode.RAISE)

# Create the namespace if required (idempotent)
await create_or_reactivate_namespace(

@ruizhang0519 ruizhang0519 Aug 21, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

create_or_reactivate_namespace is a helper shared by three callers: POST /namespaces/{namespace}/, register_table and register_view

That means whoever first creates a matching namespace becomes its owner, and “create” here is not only the namespace API. Registering a table or view can do it too -- is this intentional?

owner_role_name = f"namespace:{namespace}:owners"
if await Role.get_by_name(session, owner_role_name, include_deleted=True):
raise DJAlreadyExistsException(
message=f"Role `{owner_role_name}` already exists",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

With Role.get_by_name(..., include_deleted=True) doesn't that mean that a soft-deleted namespace:<ns>:owners permanently blocks recreating that namespace?

Is there a path back to recreation that doesn't need an admin, maybe by reusing the existing role, or undeleting it when the same principal reclaims the namespace?

namespace,
creator_owned_namespace_patterns,
):
await lock_namespace_boundary_lifecycle(session)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Minor: Doesn't this serialize every creator-owned namespace creation against every other, including unrelated ones? So for example personal.alice and personal.bob would contend on the same lock.

Although it's probably fine at self-service volume, but just noting that it could become a bottleneck if the pattern is ever configured over something high-throughput.

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.

3 participants