feat(system): emit playbook config access#2202
Conversation
WalkthroughThe PR extends the system scraper to enrich access entities with computed external playbook access metadata. A new ChangesPlaybook Access Scraping
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
✨ Simplify code
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. Comment |
BenchstatBase: ✅ No significant performance changes detectedFull benchstat output |
8e8132a to
de28ebd
Compare
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.
de28ebd to
3f9112b
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (2)
scrapers/system/system.go (2)
233-238: ⚡ Quick winCentralize 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 winDeduplicate emitted access entries per user/action/playbook.
If RBAC returns overlapping matches, this currently appends duplicate
ExternalConfigAccessrows. 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
📒 Files selected for processing (1)
scrapers/system/system.go
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
Refactor