Skip to content

Normalize additional identifier casing BED-9100 - #204

Merged
ktstrader merged 3 commits into
mainfrom
BED-9100-normalize-more-properties
Aug 7, 2026
Merged

Normalize additional identifier casing BED-9100#204
ktstrader merged 3 commits into
mainfrom
BED-9100-normalize-more-properties

Conversation

@ktstrader

@ktstrader ktstrader commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Extends the identifier normalization introduced in BED-8944 (7092bc6) to cover the remaining node identifiers, nested identifiers, and human-readable names emitted by AzureHound models. The goal is to normalize (uppercase) every value at the source so that BloodHound / BHE does not need to normalize on ingest.

All normalization is done in each model's MarshalJSON using the non-mutating type Alias T; a := Alias(s) pattern — the source structs are never modified.

Motivation and Context

Resolves: BED-9100

What changed

Top-level node identifiers now uppercased

  • AZTenant.id (ARM path, e.g. /tenants/...) — tenant.go
  • subscriptionIdsubscription.go
  • deviceIddevice.go
  • appId on AppFICs and AppRoleAssignmentapp-fic.go, app-role-assignments.go
  • roleDefinitionId on unified role assignments — role-assignments.go

displayName normalized wherever it is a node property

  • app.go, device.go, group.go, role.go, service-principal.go,
    subscription.go, tenant.go, user.go
  • Nested: mgmt-group.go (properties.displayName),
    azure/descendant-info.go (properties.displayName)

Nested identifiers normalized

  • identity.tenantId (managed identity) — via UpperManagedIdentity in utils.go
  • properties.tenantIdkey-vault.go, mgmt-group.go
  • KeyVault access policiesproperties.accessPolicies[].objectId /
    .applicationId / .tenantId in key-vault.go
  • properties.nodeResourceGroup on managed clusters — managed-cluster.go
  • RoleManagementPolicyAssignment deep-nested approver groupId / userId inside raw policy.rulesrole-management-policy-assignment.go via new recursive UpperRawJSONKeys helper in utils.go

Human-readable names normalized (new scope, to remove all ingest-side work)

  • tenantNameapp-fic.go, app.go, device.go, group.go, role.go, service-principal.go, user.go
  • resourceGroupNameautomation-account.go, container-registry.go, function-app.go, logic-app.go, storage-account.go, storage-container.go, web-app.go

Missing MarshalJSON methods added

  • StorageAccount and StorageContainer had no MarshalJSON at all; added ones mirroring their siblings (uppercasing id, subscriptionId, resourceGroupId, resourceGroupName, storageAccountId, tenantId, and identity).

New utility helpers (models/utils.go)

  • UpperManagedIdentity now also uppercases TenantId (in addition to PrincipalId).
  • UpperRawJSONKeys(raw, keys...) — recursively uppercases string values under the named keys at any depth in a raw JSON blob (non-mutating). Used for the deeply nested RoleManagementPolicyAssignment approver IDs.
    • Backed by an unexported recursive helper upperDecodedKeys(value, targeted) and a small named targetKeys set type for clear membership checks.
    • Only invoked where identifiers are nested beyond the top level; the six owner/member cases continue to use OmitEmptyUpper (top-level id plus empty entry stripping).

Intentionally NOT normalized

  • AppRoleAssignment.id — a case-sensitive base64url token, not a GUID; uppercasing would corrupt it.
  • UnifiedRoleAssignment.id (directory role assignment id) — also a case-sensitive base64url Graph token. An earlier revision uppercased this by mistake; it has been reverted and a regression test now guards its original casing.
  • appRoleId on app role assignments — a permission discriminator matched against lowercase app-role-template GUID constants during ingest (the same category as roleDefinitionId). Uppercasing it would break edge creation, so it is deliberately left in its canonical lowercase uuid.UUID form.
  • PrincipalId (uuid.UUID) on unified role assignments — emitted uppercased via the raw path since the typed field cannot hold an uppercased string.

Testing

  • models/marshal_test.go — comprehensive unit tests added/updated for every new normalization (top-level, nested, raw-JSON, and the tenantName / resourceGroupName names), plus regression guards asserting the case-sensitive base64url ids (AppRoleAssignment.id, UnifiedRoleAssignment.id) and the lowercase-matched appRoleId are preserved as-is.
  • go build ./..., go vet ./..., go test ./... — all pass.
  • Verified end-to-end against a real ~86 MB collection output.json (52,289 records, 56 kinds): node splitting eliminated (0 ids appear in more than one casing), every targeted edge endpoint is 100% uppercased with zero lowercase stragglers, and the case-sensitive base64url ids / appRoleId remain in their original casing. A depth-agnostic sweep for tenantName / resourceGroupName also reports a clean pass. Re-run after the utils.go readability refactor produced byte-identical output, confirming that change is behavior-preserving.

Summary by CodeRabbit

  • Enhancements

    • Standardized JSON output by uppercasing resource identifiers, tenant details, display names, resource groups, and managed identity fields across Azure resources.
    • Extended normalization to nested access policies, role assignments, policy rules, descendants, storage accounts, and storage containers.
    • Added recursive normalization for selected fields within nested JSON structures.
    • Preserved original source objects and case-sensitive fields during serialization.
  • Tests

    • Expanded coverage for identifiers, names, nested resources, policies, identities, and serialization behavior.

@ktstrader ktstrader self-assigned this Aug 6, 2026
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: da1b5356-af6a-4608-978c-6bfd86129f0c

📥 Commits

Reviewing files that changed from the base of the PR and between f27f4b3 and 48849ba.

📒 Files selected for processing (1)
  • models/utils.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • models/utils.go

Walkthrough

The models now uppercase additional identifiers, names, tenant fields, managed identity values, and nested policy keys during JSON serialization. New custom marshaling and tests preserve source object values.

Changes

JSON normalization

Layer / File(s) Summary
Normalization helpers
models/utils.go
UpperManagedIdentity now normalizes tenant IDs. UpperRawJSONKeys recursively transforms selected JSON keys in objects and arrays.
Scalar model marshaling
models/app.go, models/device.go, models/group.go, models/role.go, models/service-principal.go, models/subscription.go, models/tenant.go, models/user.go, models/*-app.go, models/*-registry.go, models/mgmt-group.go
MarshalJSON methods now uppercase additional resource fields, identifiers, display names, and tenant fields.
Nested and custom marshaling
models/app-fic.go, models/azure/descendant-info.go, models/key-vault.go, models/managed-cluster.go, models/role-assignments.go, models/role-management-policy-assignment.go, models/storage-account.go, models/storage-container.go
Nested values and newly supported storage models are normalized during serialization.
Serialization validation
models/marshal_test.go
Tests cover serialized uppercase values, nested fields, raw policy keys, and source immutability.

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

Possibly related PRs

Poem

A rabbit checks each JSON line,
And makes each Azure value align.
IDs and tenants turn uppercase,
Nested policies keep their place.
Source fields remain unchanged—
Marshaled values are rearranged.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.86% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: extending identifier casing normalization across AzureHound models.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch BED-9100-normalize-more-properties

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@models/utils.go`:
- Around line 116-124: Update the JSON decoding flow before upperDecodedKeys to
use a json.Decoder configured with UseNumber instead of json.Unmarshal,
preserving numeric values under non-targeted keys when the normalized structure
is re-encoded. Keep the existing error propagation and targeted-key processing
unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5017089b-0b95-4aa6-8626-ba1ee895f8e8

📥 Commits

Reviewing files that changed from the base of the PR and between 7092bc6 and f27f4b3.

📒 Files selected for processing (25)
  • models/app-fic.go
  • models/app-role-assignments.go
  • models/app.go
  • models/automation-account.go
  • models/azure/descendant-info.go
  • models/container-registry.go
  • models/device.go
  • models/function-app.go
  • models/group.go
  • models/key-vault.go
  • models/logic-app.go
  • models/managed-cluster.go
  • models/marshal_test.go
  • models/mgmt-group.go
  • models/role-assignments.go
  • models/role-management-policy-assignment.go
  • models/role.go
  • models/service-principal.go
  • models/storage-account.go
  • models/storage-container.go
  • models/subscription.go
  • models/tenant.go
  • models/user.go
  • models/utils.go
  • models/web-app.go

Comment thread models/utils.go
@ktstrader
ktstrader merged commit c8fdff7 into main Aug 7, 2026
10 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 7, 2026
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