8000 feat(bed-5656): create AZRoleApprover edge for post processing by neumachen · Pull Request #1561 · SpecterOps/BloodHound · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat(bed-5656): create AZRoleApprover edge for post processing #1561

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jun 23, 2025
Merged

Conversation

neumachen
Copy link
Contributor
@neumachen neumachen commented Jun 6, 2025

Description

Describe your changes in detail

Motivation and Context

Resolves BED-5656

Why is this change required? What problem does it solve?

How Has This Been Tested?

This was tested locally by ingesting data from our SpecterOps Test environment and comparing the results before/and after post processing was added

With just ingest and no post processing:
image

After post processing was ran, we can see the new AZRoleApprover relationships created between the AZRole nodes
image

Screenshots (optional):

Types of changes

  • Chore (a change that does not modify the application functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality 8000 )
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Database Migrations

Checklist:

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • Introduced automated creation of approver relationships for Azure roles, supporting both explicit approvers and default admin roles.
    • Improved processing and error handling for Azure tenant roles requiring approval.

Copy link
Contributor
coderabbitai bot commented Jun 6, 2025

Warning

Rate limit exceeded

@neumachen has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 18 minutes and 11 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 594bc47 and 3cbaf21.

📒 Files selected for processing (1)
  • packages/go/analysis/azure/role_approver.go (1 hunks)

Walkthrough

This change introduces new functionality to the Azure analysis package, enabling the creation of "approver" edges in the graph database for Azure roles. It adds a function to iterate over all tenants and delegates the creation of approver edges for each role, handling explicit and default approvers.

Changes

File(s) Change Summary
.../analysis/azure/post.go Added CreateAZRoleApproverEdge function to initialize and coordinate post-processing for tenants.
.../analysis/azure/role_approver.go New file implementing CreateApproverEdge for creating approver edges per tenant and role.
.../graphschema/ad/ad.go Removed copyright and license header block only.
.../graphschema/azure/azure.go Removed copyright and license header block only.
.../graphschema/common/common.go Removed copyright and license header block only.
.../graphschema/graph.go Removed copyright and license header block only.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant Post as CreateAZRoleApproverEdge
    participant DB as Graph Database
    participant Edge as CreateApproverEdge

    Caller->>Post: Call CreateAZRoleApproverEdge(ctx, db)
    Post->>DB: Fetch all AZTenant nodes
    loop For each tenant
        Post->>Edge: CreateApproverEdge(ctx, db, tenantNode, operation)
        Edge->>DB: Query roles and approvers for tenant
        alt Explicit approvers exist
            Edge->>DB: Fetch approver entity nodes
            Edge->>DB: Create approver edges
        else No explicit approvers
            Edge->>DB: Fetch default admin roles
            Edge->>DB: Create approver edges from admin roles
        end
    end
    Post-->>Caller: Return stats and error (if any)
Loading

Possibly related PRs

Suggested labels

enhancement, api

Suggested reviewers

  • mvlipka
  • rvazarkar

Poem

In Azure fields where roles reside,
Approver edges now coincide.
Tenants gather, nodes connect,
Admins and users, all checked.
With every hop and every link,
The graph grows stronger—don’t you think?
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@neumachen neumachen force-pushed the bed-5656 branch 3 times, most recently from a1c5a8c to 02ba7d9 Compare June 6, 2025 16:57
@neumachen neumachen marked this pull request as ready for review June 12, 2025 20:26
Copy link
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.

Actionable comments posted: 2

♻️ Duplicate comments (2)
packages/go/analysis/azure/post.go (2)

988-997: ⚠️ Potential issue

Missing EndUserAssignmentUserApprovers handling

The code only reads EndUserAssignmentGroupApprovers but according to the past review comments and the query logic, you should also handle EndUserAssignmentUserApprovers.


1018-1023: ⚠️ Potential issue

Critical: Uninitialized channel will cause nil pointer panic

The outC channel is declared but never initialized, which will cause a panic when trying to submit. Additionally, the function doesn't follow the established post-processing pattern used by other functions in this file.

You need to follow the pattern shown in past review comments and other functions like AppRoleAssignments. Here's an example from the file:

operation := analysis.NewPostRelationshipOperation(ctx, db, "Azure Role Approver Post Processing")
// ... then use operation.Operation.SubmitReader to properly handle channels

Also applies to: 1063-1068

🧹 Nitpick comments (1)
packages/go/analysis/azure/post.go (1)

948-949: Improve error message with tenant context

The error message should include which tenant node had the missing property for better debugging.

-return nil, fmt.Errorf("read tenant ID property value is nil")
+return nil, fmt.Errorf("tenant node %d is missing %s property", tenantNode.ID, azure.TenantID)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf5b400 and c9dc6f3.

📒 Files selected for processing (1)
  • packages/go/analysis/azure/post.go (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/go/analysis/azure/post.go (9)
packages/go/dawgs/graph/node.go (1)
  • NodeSet (133-133)
packages/go/analysis/azure/tenant.go (1)
  • FetchTenants (86-102)
packages/go/graphschema/azure/azure.go (7)
  • TenantID (107-107)
  • EndUserAssignmentRequiresApproval (135-135)
  • EndUserAssignmentUserApprovers (137-137)
  • EndUserAssignmentGroupApprovers (138-138)
  • GlobalAdmin (65-65)
  • PrivilegedRoleAdmin (69-69)
  • AZRoleApprover (96-96)
packages/go/dawgs/graph/graph.go (2)
  • Transaction (340-378)
  • ID (113-113)
packages/go/dawgs/ops/ops.go (1)
  • FetchNodeSet (104-114)
packages/go/dawgs/query/model.go (3)
  • NodeProperty (159-161)
  • Or (201-205)
  • IsNotNull (553-555)
packages/go/analysis/post.go (1)
  • CreatePostRelationshipJob (151-157)
packages/go/graphschema/common/common.go (1)
  • ObjectID (52-52)
packages/go/dawgs/graph/properties.go (2)
  • AsProperties (492-511)
  • PropertyMap (480-480)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Build BloodHound Container Image / Build and Package Container
  • GitHub Check: run-analysis
  • GitHub Check: run-tests
  • GitHub Check: build-ui

Copy link
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.

Actionable comments posted: 4

♻️ Duplicate comments (3)
packages/go/analysis/azure/post.go (3)

1005-1012: Impossible AND of multiple Kind filters

A node cannot simultaneously be Role, GlobalAdmin, and PrivilegedRoleAdmin.
Previous review already pointed this out. The correct filter is by RoleTemplateID OR.

-    query.Kind(query.Node(), azure.Role),
-    query.Kind(query.Node(), azure.GlobalAdmin),
-    query.Kind(query.Node(), azure.PrivilegedRoleAdmin),
+    query.Kind(query.Node(), azure.Role),
+    query.Or(
+        query.Equals(query.NodeProperty(azure.RoleTemplateID.String()), azure.CompanyAdministratorRole),
+        query.Equals(query.NodeProperty(azure.RoleTemplateID.String()), azure.PrivilegedRoleAdministratorRole),
+    ),

1063-1069: Second occurrence of nil outC

Same issue as lines 1018-1025. Edge is never scheduled.


938-944: 🛠️ Refactor suggestion

⚠️ Potential issue

Missing analysis.NewPostRelationshipOperation – no jobs ever leave this function

Every other post-processing routine in this file creates an analysis.NewPostRelationshipOperation (see AppRoleAssignments, ExecuteCommand, etc.).
Without an operation, no SubmitReader pipe exists and the channels on which you attempt to enqueue jobs are nil (see lines 1019 and 1064). Consequently:

  • No edges are written.
  • processingStats is never mutated.
  • The caller will believe post-processing succeeded while the graph is unchanged.

Refactor to the established pattern:

-func CreateAZRoleApproverEdge(ctx context.Context, db graph.Database) (*analysis.AtomicPostProcessingStats, error) {
-    processingStats := &analysis.AtomicPostProcessingStats{}
+func CreateAZRoleApproverEdge(ctx context.Context, db graph.Database) (*analysis.AtomicPostProcessingStats, error) {
+    operation := analysis.NewPostRelationshipOperation(ctx, db, "Azure Role Approver Post Processing")

…and replace all manual db.ReadTransaction sections with operation.Operation.SubmitReader(...), then return &operation.Stats, operation.Done().

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c9dc6f3 and fcf017a.

📒 Files selected for processing (1)
  • packages/go/analysis/azure/post.go (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Build BloodHound Container Image / Build and Package Container
  • GitHub Check: run-analysis
  • GitHub Check: build-ui
  • GitHub Check: run-tests
🔇 Additional comments (1)
packages/go/analysis/azure/post.go (1)

957-978: ⚠️ Potential issue

Filter for approval-required roles ignores EndUserAssignmentUserApprovers

The spec requires “at least one of the user or group approver lists is non-null”.
The current criteria only check for GroupApprovers.

Add the missing predicate:

-    query.Or(
-        query.IsNotNull(query.NodeProperty(azure.EndUserAssignmentUserApprovers.String())),
-        query.IsNotNull(query.NodeProperty(azure.EndUserAssignmentGroupApprovers.String())),
-    ),
+    query.Or(
+        query.IsNotNull(query.NodeProperty(azure.EndUserAssignmentUserApprovers.String())),
+        query.IsNotNull(query.NodeProperty(azure.EndUserAssignmentGroupApprovers.String())),
+    ),

Later logic must iterate over both slices, not just the group one (see next comment).

Likely an incorrect or invalid review comment.

@neumachen neumachen requested a review from mvlipka June 13, 2025 20:02
Copy link
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.

Actionable comments posted: 1

♻️ Duplicate comments (5)
packages/go/analysis/azure/post.go (5)

1011-1018: Impossible role query – node cannot be Role and GlobalAdmin and PrivilegedRoleAdmin

Same issue raised in previous review – a node cannot hold three distinct kinds simultaneously.

Switch to a single azure.Role kind check and OR on RoleTemplateID values instead.


1025-1031: outC is nil – edge creation never reaches the worker

Declaring var outC chan<- analysis.CreatePostRelationshipJob without initialising it means every channels.Submit call is discarded (it immediately returns false).

This will be fixed automatically once the function is refactored to use operation.Operation.SubmitReader(...).


1043-1064: Node creation inside a read-only transaction violates the contract

db.ReadTransaction forbids writes. Calling tx.CreateNode here can panic or be silently ignored depending on backend.

Move the node-creation logic into a writer obtained inside operation.Operation.SubmitReader(...) or emit a CreateNodeJob.


1070-1075: Second occurrence of nil outC (see earlier comment)

Edge submission inside the approver loop also uses an uninitialised channel.


937-945: 🛠️ Refactor suggestion

Post-processing operation is never initialised – stats stay empty and edges cannot be emitted

processingStats is created manually but you never spin up an analysis.NewPostRelationshipOperation.
Consequences:

  1. processingStats is never mutated – callers see zero work done.
  2. All subsequent channels.Submit calls target a nil channel (see comments below), because outC is only handed out by the operation.

Refactor along the same pattern used by AppRoleAssignments or ExecuteCommand:

-func CreateAZRoleApproverEdge(ctx context.Context, db graph.Database) (*analysis.AtomicPostProcessingStats, error) {
-    processingStats := &analysis.AtomicPostProcessingStats{}
+func CreateAZRoleApproverEdge(ctx context.Context, db graph.Database) (*analysis.AtomicPostProcessingStats, error) {
+    operation := analysis.NewPostRelationshipOperation(ctx, db, "AZRoleApprover Post Processing")

…and return &operation.Stats, operation.Done() at the end.
All edge creation must then happen inside operation.Operation.SubmitReader(...) closures.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fcf017a and 891b319.

📒 Files selected for processing (1)
  • packages/go/analysis/azure/post.go (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Build BloodHound Container Image / Build and Package Container
  • GitHub Check: run-tests
  • GitHub Check: run-analysis
🔇 Additional comments (1)
packages/go/analysis/azure/post.go (1)

959-977: Possible tenant-ID comparison mismatch

query.Equals(query.NodeProperty(azure.TenantID.String()), tenantID) compares a property to a raw string.
If the query builder expects a graph.PropertyValue, the comparison will never match, silently returning zero roles.

Please confirm the expected type; if a PropertyValue is required, wrap the string:

query.Equals(query.NodeProperty(azure.TenantID.String()), graph.StringProperty(tenantID)),

Copy link
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.

Actionable comments posted: 2

🧹 Nitpick comments (3)
packages/go/analysis/azure/azrole/approver.go (3)

17-17: Add a meaningful package-level doc comment

// CreateApproverEdge ... does not describe behaviour or expectations. Replace with a GoDoc-style sentence so the exported symbol is discoverable in go doc.


93-93: Rename principalID parameter

The argument actually carries the tenant ID, not a principal ID. Misnaming
confuses readers and future maintainers.

-func handleDefaultAdminRoles(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob, principalID string, fetchedAZRole *graph.Node) error {
+func handleDefaultAdminRoles(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob, tenantID string, fetchedAZRole *graph.Node) error {

120-148: Batch principal lookup to cut N round-trips

handlePrincipalApprovers performs one DB query per principal ID. For large
approver lists this becomes O(n) round-trips. Consider fetching all at once:

nodeSet, err := ops.FetchNodeSet(tx.Nodes().Filterf(func() graph.Criteria {
    return query.And(
        query.Kind(query.Node(), azure.Entity),
        query.In(query.NodeProperty(common.ObjectID.String()), principalIDs...),
    )
}))

Then iterate the result map for edge creation and log missing IDs.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 891b319 and 3374eae.

📒 Files selected for processing (2)
  • packages/go/analysis/azure/azrole/approver.go (1 hunks)
  • packages/go/analysis/azure/post.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/go/analysis/azure/post.go
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Build BloodHound Container Image / Build and Package Container
  • GitHub Check: build-ui
  • GitHub Check: run-analysis
  • GitHub Check: run-tests

@neumachen neumachen requested a review from mvlipka June 17, 2025 20:43
Copy link
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.

Actionable comments posted: 0

♻️ Duplicate comments (2)
packages/go/analysis/azure/azrole/approver.go (2)

66-92: Loop variable captured - all edges may reference the last role

The closure passed to SubmitReader captures fetchedAZRole from the outer for loop. Because the loop variable is reused, every asynchronous reader may see the value from the final iteration.

Apply this fix to capture the loop variable correctly:

-for _, fetchedAZRole := range fetchedAZRoles {
+for _, role := range fetchedAZRoles {
+    role := role  // capture loop variable
     if err := operation.Operation.SubmitReader(func(ctx context.Context, tx graph.Transaction, outC chan<- analysis.CreatePostRelationshipJob) error {
-        userApproversID, err := fetchedAZRole.Properties.Get(
+        userApproversID, err := role.Properties.Get(
             azure.EndUserAssignmentUserApprovers.String(),
         ).StringSlice()
         if err != nil {
             return err
         }
-        groupApproversID, err := fetchedAZRole.Properties.Get(
+        groupApproversID, err := role.Properties.Get(
             azure.EndUserAssignmentGroupApprovers.String(),
         ).StringSlice()
         if err != nil {
             return err
         }
         principalIDs := append(userApproversID, groupApproversID...)
         if len(principalIDs) == 0 {
-            return handleDefaultAdminRoles(ctx, db, tx, outC, tenantID, fetchedAZRole)
+            return handleDefaultAdminRoles(ctx, db, tx, outC, tenantID, role)
         } else {
-            return handlePrincipalApprovers(ctx, db, tx, outC, principalIDs, fetchedAZRole)
+            return handlePrincipalApprovers(ctx, db, tx, outC, principalIDs, role)
         }
     }); err != nil {
         return err
     }
 }

101-108: Query can never match: a node cannot satisfy multiple Kind predicates

query.Kind(query.Node(), azure.Role) is combined with an OR that checks azure.GlobalAdmin and azure.PrivilegedRoleAdmin. A node can only have one kind, so the outer Kind(Role) makes the OR branch unreachable.

Remove the conflicting Kind(Role) predicate:

 return query.And(
     query.Equals(query.NodeProperty(azure.TenantID.String()), tenantID),
-    query.Kind(query.Node(), azure.Role),
     query.Or(
         query.Kind(query.Node(), azure.GlobalAdmin),
         query.Kind(query.Node(), azure.PrivilegedRoleAdmin),
     ),
 )
🧹 Nitpick comments (1)
packages/go/analysis/azure/azrole/approver.go (1)

155-156: Merge variable declaration with assignment

The variable declaration and assignment can be combined for cleaner code.

-var nodeID graph.ID
-nodeID = fetchedNode.ID
+nodeID := fetchedNode.ID
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7320e51 and 42d9eec.

📒 Files selected for processing (2)
  • packages/go/analysis/azure/azrole/approver.go (1 hunks)
  • packages/go/analysis/azure/post.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/go/analysis/azure/post.go
🧰 Additional context used
🪛 golangci-lint (1.64.8)
packages/go/analysis/azure/azrole/approver.go

[error] 155-155: S1021: should merge variable declaration with assignment on next line

(gosimple)

⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Build BloodHound Container Image / Build and Package Container
  • GitHub Check: run-analysis
  • GitHub Check: run-tests
  • GitHub Check: build-ui
🔇 Additional comments (2)
packages/go/analysis/azure/azrole/approver.go (2)

17-31: LGTM - Proper tenant property extraction

The function correctly extracts both tenantID and tenantObjectID from the tenant node properties with appropriate error handling.


131-167: LGTM - Robust principal approver handling

The function properly handles entity node lookup with appropriate error handling, including graceful handling of missing nodes with warning logs and context cancellation support.

Copy link
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.

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/go/analysis/azure/role_approver.go (2)

140-154: Performance concern: Multiple database transactions for principal lookup

Each principal ID requires a separate database transaction, which could be inefficient for roles with many approvers. Consider batching the lookups into a single transaction.

Optimize by fetching all principals in a single transaction:

+var fetchedNodes map[string]*graph.Node
+err := db.ReadTransaction(ctx, func(tx graph.Transaction) error {
+    nodes, err := ops.FetchNodeSet(tx.Nodes().Filterf(func() graph.Criteria {
+        return query.And(
+            query.Kind(query.Node(), azure.Entity),
+            query.In(query.NodeProperty(common.ObjectID.String()), principalIDs),
+        )
+    }))
+    if err != nil {
+        return err
+    }
+    
+    fetchedNodes = make(map[string]*graph.Node)
+    for _, node := range nodes {
+        if objID, err := node.Properties.Get(common.ObjectID.String()).String(); err == nil {
+            fetchedNodes[objID] = node
+        }
+    }
+    return nil
+})
+if err != nil {
+    return err
+}
+
 for _, principalID := range principalIDs {
-    var fetchedNode *graph.Node
-    err := db.ReadTransaction(ctx, func(tx graph.Transaction) error {
-        // ... transaction code
-    })
-    if err != nil {
-        if graph.IsErrNotFound(err) {
-            slog.WarnContext(...)
-            continue
-        } else {
-            return err
-        }
-    }
+    fetchedNode, exists := fetchedNodes[principalID]
+    if !exists {
+        slog.WarnContext(
+            ctx,
+            fmt.Sprintf(
+                "Entity node not found for principal ID: %s, skipping edge creation",
+                principalID,
+            ),
+        )
+        continue
+    }

170-176: Channel submission failure handling should be documented

The function returns nil when channels.Submit returns false, which suggests the channel is closed or full. This behavior should be documented as it exits the loop early, potentially leaving some edges uncreated.

Add a comment explaining the early return behavior:

 if !channels.Submit(ctx, outC, analysis.CreatePostRelationshipJob{
     FromID: fetchedNode.ID,
     ToID:   fetchedAZRole.ID,
     Kind:   azure.AZRoleApprover,
 }) {
+    // Channel is closed or context cancelled, stop processing
     return nil
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b40ea7 and a773bd7.

📒 Files selected for processing (2)
  • packages/go/analysis/azure/post.go (1 hunks)
  • packages/go/analysis/azure/role_approver.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/go/analysis/azure/post.go
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Build BloodHound Container Image / Build and Package Container
  • GitHub Check: run-tests
  • GitHub Check: run-analysis
  • GitHub Check: build-ui
🔇 Additional comments (3)
packages/go/analysis/azure/role_approver.go (3)

1-15: LGTM: Clean package structure and imports

The package declaration and imports are well-organized and appropriate for the Azure analysis functionality.


17-23: LGTM: Well-structured function signature

The function signature is well-designed with proper context handling, database interface, and operation tracking. The parameters are appropriately typed and named.


105-110: Verify TenantRoles function availability

The code calls TenantRoles function which is not defined in this file. Ensure this function is properly imported and available in the azure package.

#!/bin/bash
# Description: Verify TenantRoles function exists and is accessible
# Expected: Find the TenantRoles function definition in the azure package

ast-grep --pattern $'func TenantRoles($$$) ($$$) {
  $$$
}'

Copy link
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.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a773bd7 and 7f6423f.

📒 Files selected for processing (2)
  • packages/go/analysis/azure/post.go (2 hunks)
  • packages/go/analysis/azure/role_approver.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/go/analysis/azure/role_approver.go
🧰 Additional context used
🪛 golangci-lint (1.64.8)
packages/go/analysis/azure/post.go

[error] 26-26: other declaration of cardinality

(typecheck)


[error] 27-27: other declaration of graph

(typecheck)


[error] 28-28: other declaration of ops

(typecheck)


[error] 29-29: other declaration of query

(typecheck)


[error] 30-30: other declaration of channels

(typecheck)

⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Build BloodHound Container Image / Build and Package Container
  • GitHub Check: build-ui
  • GitHub Check: run-tests
  • GitHub Check: run-analysis
🔇 Additional comments (1)
packages/go/analysis/azure/post.go (1)

938-964: Well-structured post-processing function

The function correctly implements the post-processing pattern used throughout this file and addresses the key concerns from previous reviews:

  • ✅ Proper return type (*analysis.AtomicPostProcessingStats, error)
  • ✅ Uses analysis.NewPostRelationshipOperation framework
  • ✅ Follows established error handling patterns
  • ✅ Good separation of concerns by delegating to CreateApproverEdge

The implementation is consistent with other post-processing functions like AppRoleAssignments() and ExecuteCommand().

@neumachen neumachen merged commit 10dacff into main Jun 23, 2025
9 checks passed
@neumachen neumachen deleted the bed-5656 branch June 23, 2025 20:14
@github-actions github-actions bot locked and limited conversation to collaborators Jun 23, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants
0