Skip to content

feat(system): emit playbook config access#2202

Open
adityathebe wants to merge 1 commit into
mainfrom
playbook-config-access-rbac
Open

feat(system): emit playbook config access#2202
adityathebe wants to merge 1 commit into
mainfrom
playbook-config-access-rbac

Conversation

@adityathebe
Copy link
Copy Markdown
Member

@adityathebe adityathebe commented May 7, 2026

Generate playbook config access from the config-db system scraper.

The scraper uses duty RBAC subject access search for each active user and playbook action, and emits config_access only for allowed playbooks.

It also creates external roles for mcp:run, playbook:run, playbook:approve and playbook:cancel.

resolves: flanksource/flanksource-ui#3025

Summary by CodeRabbit

  • New Features

    • System access detection now includes external user playbook access metadata, discovering permissions across multiple action types.
  • Refactor

    • Playbook role generation improved to support expanded action type coverage.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack

Walkthrough

The PR extends the system scraper to enrich access entities with computed external playbook access metadata. A new scrapePlaybookAccess function queries RBAC for each external user's playbook permissions and builds config access entries. Helper utilities extract person IDs from aliases, and role generation is refactored to iterate over a consolidated actions list.

Changes

Playbook Access Scraping

Layer / File(s) Summary
Dependencies
scrapers/system/system.go
RBAC package import added for access metadata queries.
Helper Functions
scrapers/system/system.go
personIDFromAliases extracts person UUIDs from user aliases with the people: prefix.
Core Access Scraping
scrapers/system/system.go
scrapePlaybookAccess iterates over external users and actions, calls RBAC subject access search for playbook resources, parses playbook UUIDs from results, and builds v1.ExternalConfigAccess entries with user/role aliases, config IDs, and scraper metadata.
Role Generation Refactor
scrapers/system/system.go
scrapePlaybookRoles is refactored to generate roles by iterating over an actions slice instead of a hardcoded two-role set, adding support for ActionMCPRun, ActionPlaybookRun, ActionPlaybookApprove, and ActionPlaybookCancel.
Integration
scrapers/system/system.go
scrapeAccessEntities is extended to call scrapePlaybookAccess and store results in result.ConfigAccess, with error handling via SetError.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(system): emit playbook config access' clearly and concisely summarizes the main change: adding playbook configuration access metadata emission to the system scraper.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch playbook-config-access-rbac
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch playbook-config-access-rbac

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 and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

Benchstat

Base: ac92ce21099592f3abab510ec61fe6eed7503b7f
Head: 3f9112bc3a60225fd0c2af4669b22caaf8c8009d

✅ No significant performance changes detected

Full benchstat output
goos: linux
goarch: amd64
pkg: github.com/flanksource/config-db/bench
cpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz
                                         │ bench-base.txt │           bench-head.txt           │
                                         │     sec/op     │    sec/op     vs base              │
BenchSaveResultsSeed/N=1000-4                520.2m ± 12%   514.6m ±  5%       ~ (p=0.699 n=6)
BenchSaveResultsUpdateUnchanged/N=1000-4     77.09m ±  8%   76.33m ± 10%       ~ (p=0.485 n=6)
BenchSaveResultsUpdateChanged/N=1000-4       910.9m ±  1%   914.3m ±  6%       ~ (p=0.093 n=6)
geomean                                      331.8m         329.9m        -0.56%

                                       │ bench-base.txt │           bench-head.txt           │
                                       │      MB/s      │    MB/s     vs base                │
BenchSaveResultsSeed/N=1000-4              0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
BenchSaveResultsUpdateChanged/N=1000-4     0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                               ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                         │ bench-base.txt │           bench-head.txt           │
                                         │      B/op      │     B/op      vs base              │
BenchSaveResultsSeed/N=1000-4                36.25Mi ± 0%   36.27Mi ± 0%       ~ (p=0.394 n=6)
BenchSaveResultsUpdateUnchanged/N=1000-4     10.90Mi ± 2%   10.84Mi ± 3%       ~ (p=0.699 n=6)
BenchSaveResultsUpdateChanged/N=1000-4       74.96Mi ± 0%   75.01Mi ± 0%       ~ (p=0.240 n=6)
geomean                                      30.94Mi        30.90Mi       -0.16%

                                         │ bench-base.txt │          bench-head.txt           │
                                         │   allocs/op    │  allocs/op   vs base              │
BenchSaveResultsSeed/N=1000-4                 442.1k ± 0%   442.1k ± 0%       ~ (p=0.240 n=6)
BenchSaveResultsUpdateUnchanged/N=1000-4      122.2k ± 2%   121.4k ± 3%       ~ (p=0.983 n=6)
BenchSaveResultsUpdateChanged/N=1000-4        907.2k ± 0%   907.2k ± 0%       ~ (p=0.937 n=6)
geomean                                       366.0k        365.2k       -0.22%

                                         │ bench-base.txt │         bench-head.txt          │
                                         │      B/s       │     B/s       vs base           │
BenchSaveResultsUpdateUnchanged/N=1000-4     9.766Ki ± 0%   9.766Ki ± 0%  ~ (p=1.000 n=6) ¹
¹ all samples are equal

@adityathebe adityathebe force-pushed the playbook-config-access-rbac branch from 8e8132a to de28ebd Compare May 8, 2026 04:45
Use duty RBAC subject access search from the system scraper to derive config access for active users and playbook actions.\n\nThe scraper now emits roles for mcp:run, playbook:run, playbook:approve and playbook:cancel, and only creates config_access rows for playbooks a user is allowed to access.
@adityathebe adityathebe force-pushed the playbook-config-access-rbac branch from de28ebd to 3f9112b Compare May 8, 2026 10:27
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
scrapers/system/system.go (2)

233-238: ⚡ Quick win

Centralize the playbook action list to avoid drift.

The same action list is declared twice (roles + access). A future edit in one place can silently desync generated roles from emitted config access.

Proposed refactor
+var playbookAccessActions = []string{
+	policy.ActionMCPRun,
+	policy.ActionPlaybookRun,
+	policy.ActionPlaybookApprove,
+	policy.ActionPlaybookCancel,
+}
+
 func scrapePlaybookRoles(scraperID uuid.UUID) []models.ExternalRole {
-	actions := []string{
-		policy.ActionMCPRun,
-		policy.ActionPlaybookRun,
-		policy.ActionPlaybookApprove,
-		policy.ActionPlaybookCancel,
-	}
-
-	roles := make([]models.ExternalRole, 0, len(actions))
-	for _, action := range actions {
+	roles := make([]models.ExternalRole, 0, len(playbookAccessActions))
+	for _, action := range playbookAccessActions {
 		roles = append(roles, models.ExternalRole{
 			Name:      action,
 			Tenant:    "mission-control",
@@
 func scrapePlaybookAccess(ctx api.ScrapeContext, scraperID uuid.UUID, users []models.ExternalUser) ([]v1.ExternalConfigAccess, error) {
-	actions := []string{
-		policy.ActionMCPRun,
-		policy.ActionPlaybookRun,
-		policy.ActionPlaybookApprove,
-		policy.ActionPlaybookCancel,
-	}
 	source := "mission-control-rbac"
 	access := make([]v1.ExternalConfigAccess, 0)
@@
-		for _, action := range actions {
+		for _, action := range playbookAccessActions {

Also applies to: 256-261

🤖 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 `@scrapers/system/system.go` around lines 233 - 238, The playbook action list
is duplicated (the local variable actions with policy.ActionMCPRun,
policy.ActionPlaybookRun, policy.ActionPlaybookApprove,
policy.ActionPlaybookCancel appears in multiple places); centralize it by
extracting a single package-level variable or constant (e.g., PlaybookActions or
playbookActions) and replace both local declarations with references to that
symbol so roles generation and access generation use the same canonical list;
update all uses in system.go (where the local actions slice is currently
defined) to reference the new PlaybookActions symbol.

291-300: ⚡ Quick win

Deduplicate emitted access entries per user/action/playbook.

If RBAC returns overlapping matches, this currently appends duplicate ExternalConfigAccess rows. A small in-memory key set avoids duplicate writes/conflicts downstream.

Proposed refactor
 func scrapePlaybookAccess(ctx api.ScrapeContext, scraperID uuid.UUID, users []models.ExternalUser) ([]v1.ExternalConfigAccess, error) {
 	source := "mission-control-rbac"
 	access := make([]v1.ExternalConfigAccess, 0)
+	seen := make(map[string]struct{})
@@
 				playbookID, err := uuid.Parse(result.ID)
 				if err != nil {
 					return nil, fmt.Errorf("invalid playbook id from access search %q: %w", result.ID, err)
 				}
 
+				key := personID + "|" + action + "|" + playbookID.String()
+				if _, exists := seen[key]; exists {
+					continue
+				}
+				seen[key] = struct{}{}
+
 				access = append(access, v1.ExternalConfigAccess{
 					ConfigID:            playbookID,
 					ExternalUserAliases: []string{"people:" + personID},
🤖 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 `@scrapers/system/system.go` around lines 291 - 300, The current loop appends
duplicate v1.ExternalConfigAccess entries into the access slice; fix this by
deduplicating using an in-memory set keyed by the unique combination of
personID, action and playbookID before appending. Inside the code that builds
the access slice (where you create v1.ExternalConfigAccess with ConfigID:
playbookID, ExternalUserAliases: "people:"+personID, ExternalRoleAliases:
"role:"+action, ScraperID: &scraperID, Source: &source), create a
map[string]struct{} (or map[keyType]bool) and compute a stable key (e.g.
playbookID.String() + "|" + personID + "|" + action) for each candidate; check
the map and only append to access and mark the key when it’s not present to
avoid duplicate ExternalConfigAccess rows. Ensure the key uses the same
identifiers used when constructing ExternalConfigAccess so deduplication is
accurate.
🤖 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.

Nitpick comments:
In `@scrapers/system/system.go`:
- Around line 233-238: The playbook action list is duplicated (the local
variable actions with policy.ActionMCPRun, policy.ActionPlaybookRun,
policy.ActionPlaybookApprove, policy.ActionPlaybookCancel appears in multiple
places); centralize it by extracting a single package-level variable or constant
(e.g., PlaybookActions or playbookActions) and replace both local declarations
with references to that symbol so roles generation and access generation use the
same canonical list; update all uses in system.go (where the local actions slice
is currently defined) to reference the new PlaybookActions symbol.
- Around line 291-300: The current loop appends duplicate
v1.ExternalConfigAccess entries into the access slice; fix this by deduplicating
using an in-memory set keyed by the unique combination of personID, action and
playbookID before appending. Inside the code that builds the access slice (where
you create v1.ExternalConfigAccess with ConfigID: playbookID,
ExternalUserAliases: "people:"+personID, ExternalRoleAliases: "role:"+action,
ScraperID: &scraperID, Source: &source), create a map[string]struct{} (or
map[keyType]bool) and compute a stable key (e.g. playbookID.String() + "|" +
personID + "|" + action) for each candidate; check the map and only append to
access and mark the key when it’s not present to avoid duplicate
ExternalConfigAccess rows. Ensure the key uses the same identifiers used when
constructing ExternalConfigAccess so deduplication is accurate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4284e514-5021-43a8-aaea-ad1ab66429ac

📥 Commits

Reviewing files that changed from the base of the PR and between ac92ce2 and 3f9112b.

📒 Files selected for processing (1)
  • scrapers/system/system.go

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.

config access for playbook permissions

1 participant