ownerlens 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/LICENSE +183 -0
  2. package/README.md +209 -0
  3. package/bin/ownerlens.js +92 -0
  4. package/dist/assets/index-B9aAYpVl.css +1 -0
  5. package/dist/assets/index-BcwLk2bx.js +10 -0
  6. package/dist/index.html +13 -0
  7. package/package.json +73 -0
  8. package/src/App.tsx +18 -0
  9. package/src/components/azure/AzureComponent.test.tsx +625 -0
  10. package/src/components/azure/AzureComponent.tsx +189 -0
  11. package/src/components/azure/AzureRbacComponent.tsx +104 -0
  12. package/src/components/azure/ClosableAzureTab.tsx +42 -0
  13. package/src/components/azure/EntraPermissionsComponent.tsx +194 -0
  14. package/src/components/azure/ManagedIdentityComponent.test.tsx +324 -0
  15. package/src/components/azure/ManagedIdentityComponent.tsx +141 -0
  16. package/src/components/azure/ResourceGroupComponent.tsx +157 -0
  17. package/src/components/azure/ServicePrincipalComponent.test.tsx +457 -0
  18. package/src/components/azure/ServicePrincipalComponent.tsx +155 -0
  19. package/src/components/azure/ServicePrincipalFieldRenderers.tsx +140 -0
  20. package/src/components/azure/ZtaComponent.test.tsx +267 -0
  21. package/src/components/azure/ZtaComponent.tsx +276 -0
  22. package/src/components/azure/ZtaRemediationBadge.tsx +70 -0
  23. package/src/components/azure/api.ts +216 -0
  24. package/src/components/azure/azureReportConfig.ts +247 -0
  25. package/src/core/azure/azureRbac.ts +70 -0
  26. package/src/core/azure/entra/index.ts +1 -0
  27. package/src/core/azure/entra/managedIdentity.ts +21 -0
  28. package/src/core/azure/entra/servicePrincipal.ts +34 -0
  29. package/src/core/azure/entra/types.ts +56 -0
  30. package/src/core/azure/identityEnrichment.ts +65 -0
  31. package/src/core/azure/resources.ts +141 -0
  32. package/src/core/azure/ztaReport.ts +58 -0
  33. package/src/core/config.ts +39 -0
  34. package/src/core/ownership/OwnershipTarget.ts +32 -0
  35. package/src/core/ownership/resolveOwner.ts +5 -0
  36. package/src/core/ownership/types.ts +14 -0
  37. package/src/core/risk/types.ts +1 -0
  38. package/src/core/runtime/index.ts +1 -0
  39. package/src/core/runtime/localSnapshotFiles.ts +74 -0
  40. package/src/core/runtime/rest.ts +61 -0
  41. package/src/lib/searchFilterUtils.ts +17 -0
  42. package/src/lib/utils.ts +48 -0
  43. package/src/main.tsx +10 -0
  44. package/src/providers/azure/identities/azureIdentityTypes.ts +1 -0
  45. package/src/providers/azure/identities/buildAzureManagedIdentityAssignmentIndex.test.ts +32 -0
  46. package/src/providers/azure/identities/buildAzureManagedIdentityAssignmentIndex.ts +35 -0
  47. package/src/providers/azure/identities/userAssignedIdentityAssignments.ts +52 -0
  48. package/src/providers/azure/inputTransferObject/entra/EntraAppRoleAssignment.ts +10 -0
  49. package/src/providers/azure/inputTransferObject/entra/EntraApplication.ts +27 -0
  50. package/src/providers/azure/inputTransferObject/entra/EntraOAuth2PermissionGrant.ts +8 -0
  51. package/src/providers/azure/inputTransferObject/entra/EntraServicePrincipal.ts +43 -0
  52. package/src/providers/azure/inputTransferObject/entra/EntraSnapshot.ts +13 -0
  53. package/src/providers/azure/inputTransferObject/entra/EntraSnapshotMeta.ts +12 -0
  54. package/src/providers/azure/inputTransferObject/resources/AzureActivityLog.ts +1 -0
  55. package/src/providers/azure/inputTransferObject/resources/AzureResource.ts +1 -0
  56. package/src/providers/azure/inputTransferObject/resources/AzureResourceGroup.ts +1 -0
  57. package/src/providers/azure/inputTransferObject/resources/AzureRoleAssignment.ts +1 -0
  58. package/src/providers/azure/inputTransferObject/resources/AzureSnapshot.ts +1 -0
  59. package/src/providers/azure/inputTransferObject/resources/AzureSnapshotMeta.ts +1 -0
  60. package/src/providers/azure/inputTransferObject/resources/AzureSubscription.ts +1 -0
  61. package/src/providers/azure/inputTransferObject/resources/AzureUserAssignedManagedIdentity.ts +1 -0
  62. package/src/providers/azure/ownership/azureActivityOwnershipEvidence.ts +60 -0
  63. package/src/providers/azure/ownership/azureOwnerReportTypes.ts +13 -0
  64. package/src/providers/azure/ownership/azureOwnershipConfig.ts +21 -0
  65. package/src/providers/azure/ownership/azureOwnershipTypes.ts +46 -0
  66. package/src/providers/azure/ownership/buildAzureOwnershipReport.test.ts +99 -0
  67. package/src/providers/azure/ownership/buildAzureOwnershipReport.ts +90 -0
  68. package/src/providers/azure/ownership/buildAzureOwnershipTargets.test.ts +87 -0
  69. package/src/providers/azure/ownership/buildAzureOwnershipTargets.ts +42 -0
  70. package/src/providers/azure/ownership/resolveAzureOwner.ts +146 -0
  71. package/src/providers/azure/runtime/DisabledEvidenceStore.ts +34 -0
  72. package/src/providers/azure/runtime/EnrichmentService.ts +35 -0
  73. package/src/providers/azure/runtime/LocalReportRuntime.test.ts +2318 -0
  74. package/src/providers/azure/runtime/LocalReportRuntime.ts +302 -0
  75. package/src/providers/azure/runtime/RuntimeHost.ts +60 -0
  76. package/src/providers/azure/runtime/SnapshotImporter.ts +44 -0
  77. package/src/providers/azure/runtime/enrichment/azureIdentityEnrichment.ts +523 -0
  78. package/src/providers/azure/runtime/enrichment/azureScopeClassifier.ts +30 -0
  79. package/src/providers/azure/runtime/enrichment/evaluateAzureRoleAssignmentRisk.ts +88 -0
  80. package/src/providers/azure/runtime/entra/EntraCollectionQueryService.ts +307 -0
  81. package/src/providers/azure/runtime/entra/LocalEntraReportRuntime.ts +227 -0
  82. package/src/providers/azure/runtime/entra/appRoleAssignmentsTable.ts +52 -0
  83. package/src/providers/azure/runtime/entra/applicationsTable.ts +175 -0
  84. package/src/providers/azure/runtime/entra/entraServicePrincipalMapper.ts +63 -0
  85. package/src/providers/azure/runtime/entra/localReportRuntimeRest.ts +41 -0
  86. package/src/providers/azure/runtime/entra/oauth2PermissionGrantsTable.ts +48 -0
  87. package/src/providers/azure/runtime/entra/principalProjection.ts +173 -0
  88. package/src/providers/azure/runtime/entra/servicePrincipalsTable.ts +149 -0
  89. package/src/providers/azure/runtime/entra/snapshotMetadataTable.ts +18 -0
  90. package/src/providers/azure/runtime/entra/snapshotStore.ts +102 -0
  91. package/src/providers/azure/runtime/localReportCollections.ts +101 -0
  92. package/src/providers/azure/runtime/localReportRuntimeRest.ts +71 -0
  93. package/src/providers/azure/runtime/resources/AzureResourcesCollectionQueryService.ts +145 -0
  94. package/src/providers/azure/runtime/resources/LocalAzureResourcesReportRuntime.ts +114 -0
  95. package/src/providers/azure/runtime/resources/disabledOwnerEvidenceTable.ts +60 -0
  96. package/src/providers/azure/runtime/resources/localReportRuntimeRest.ts +81 -0
  97. package/src/providers/azure/runtime/resources/resourceGroupOwnership.ts +90 -0
  98. package/src/providers/azure/runtime/resources/snapshotMetadataTable.ts +19 -0
  99. package/src/providers/azure/runtime/resources/snapshotStore.ts +128 -0
  100. package/src/providers/azure/runtime/resources/tables.ts +441 -0
  101. package/src/providers/azure/runtime/runtimeRestQuery.ts +46 -0
  102. package/src/providers/azure/runtime/runtimeSqlSchema.ts +357 -0
  103. package/src/providers/azure/runtime/zta/Discovery.ts +141 -0
  104. package/src/providers/azure/runtime/zta/LocalZeroTrustAssessmentReportRuntime.ts +86 -0
  105. package/src/providers/azure/runtime/zta/ZeroTrustAssessmentQueryService.ts +124 -0
  106. package/src/providers/azure/runtime/zta/localReportRuntimeRest.ts +15 -0
  107. package/src/providers/azure/runtime/zta/snapshotMetadataTable.ts +77 -0
  108. package/src/providers/azure/runtime/zta/snapshotStore.ts +112 -0
  109. package/src/providers/azure/runtime/zta/tables.ts +361 -0
  110. package/src/providers/azure/runtime/zta/types.ts +7 -0
  111. package/src/providers/azure/runtime/zta/ztaReportMapper.ts +12 -0
  112. package/src/report/applyCollectionControls.ts +289 -0
  113. package/src/report/buildCollectionColumns.tsx +38 -0
  114. package/src/report/components/ConfidenceBadge.tsx +10 -0
  115. package/src/report/components/EvidenceList.test.ts +25 -0
  116. package/src/report/components/EvidenceList.tsx +52 -0
  117. package/src/report/components/GenericTable.tsx +373 -0
  118. package/src/report/components/PermissionRiskBadge.tsx +19 -0
  119. package/src/report/components/reportTableControls.test.ts +175 -0
  120. package/src/report/components/reportTableControls.tsx +483 -0
  121. package/src/report/components/ui/badge.tsx +35 -0
  122. package/src/report/components/ui/button.tsx +38 -0
  123. package/src/report/components/ui/card.tsx +23 -0
  124. package/src/report/components/ui/input.tsx +15 -0
  125. package/src/report/components/ui/table.tsx +44 -0
  126. package/src/report/components/ui/tabs.tsx +29 -0
  127. package/src/report/export/csv.ts +34 -0
  128. package/src/report/ownerManualPrecheck.test.ts +137 -0
  129. package/src/report/ownerManualPrecheck.ts +132 -0
  130. package/src/report/reportArchitecture.test.ts +125 -0
  131. package/src/report/reportTypes.ts +54 -0
  132. package/src/report/reportValueRenderers.tsx +54 -0
  133. package/src/report/runtimeCollectionQuery.ts +23 -0
  134. package/src/report/types.ts +14 -0
  135. package/src/styles.css +43 -0
  136. package/tools/README.md +108 -0
  137. package/tools/azure-activity-check.ps1 +164 -0
  138. package/tools/collect-azure.ps1 +54 -0
  139. package/tools/collect-entra.ps1 +47 -0
  140. package/tools/collect-scripts.test.ts +22 -0
  141. package/tools/prepare-entra-snapshot.ps1 +403 -0
  142. package/tools/prepare-entra-snapshot.test.ts +14 -0
  143. package/tools/prepare-resource-snapshot.ps1 +345 -0
  144. package/vite.config.ts +23 -0
@@ -0,0 +1,56 @@
1
+ import type { PermissionRiskLevel } from "../../risk/types";
2
+
3
+ export type ServicePrincipalType = "Application" | "ManagedIdentity" | "SocialIdp" | "Legacy";
4
+
5
+ export type ServicePrincipalAppRole = {
6
+ id: string;
7
+ value: string | null;
8
+ displayName: string | null;
9
+ description: string | null;
10
+ isEnabled: boolean | null;
11
+ allowedMemberTypes: string[];
12
+ };
13
+
14
+ export type ServicePrincipalOwner = {
15
+ id?: string | null;
16
+ displayName?: string | null;
17
+ userPrincipalName?: string | null;
18
+ mail?: string | null;
19
+ ownerType?: string | null;
20
+ };
21
+
22
+ export type EntraServicePrincipal = {
23
+ id: string;
24
+ appId: string;
25
+ displayName: string;
26
+ appDisplayName: string | null;
27
+ servicePrincipalType: ServicePrincipalType;
28
+ publisherName: string | null;
29
+ accountEnabled: boolean;
30
+ appOwnerOrganizationId: string | null;
31
+ homepage: string | null;
32
+ loginUrl: string | null;
33
+ replyUrls: string[];
34
+ servicePrincipalNames: string[];
35
+ tags: string[];
36
+ appRoles?: ServicePrincipalAppRole[];
37
+ owners?: ServicePrincipalOwner[];
38
+ appOwners?: ServicePrincipalOwner[];
39
+ servicePrincipalOwners?: ServicePrincipalOwner[];
40
+ applicationOwners?: ServicePrincipalOwner[];
41
+ metadata?: Record<string, unknown> | null;
42
+ };
43
+
44
+ export type EntraOAuth2PermissionGrant = {
45
+ id: string;
46
+ clientId: string;
47
+ consentType: "Principal" | "AllPrincipals" | string;
48
+ principalId: string | null;
49
+ resourceId: string;
50
+ risk: PermissionRiskLevel;
51
+ scope: string;
52
+ };
53
+
54
+ export type EntraAppRole = ServicePrincipalAppRole;
55
+ export type EntraServicePrincipalType = ServicePrincipalType;
56
+ export type EntraOwner = ServicePrincipalOwner;
@@ -0,0 +1,65 @@
1
+ import type { PermissionRiskLevel } from "../risk/types";
2
+ import type { AzureRoleAssignment, AzureUserAssignedIdentityAssignment } from "./resources";
3
+
4
+ export type AzureManagedIdentityResourceAssignment = AzureUserAssignedIdentityAssignment & {
5
+ assignedResourceId: string;
6
+ assignedResourceName: string;
7
+ assignedResourceType: string;
8
+ assignedResourceGroup: string;
9
+ subscriptionId: string;
10
+ subscriptionName: string;
11
+ };
12
+
13
+ export type ManagedIdentityPermissionRiskLevel = PermissionRiskLevel;
14
+
15
+ export type ManagedIdentityPermissionRiskAssignment = AzureRoleAssignment & {
16
+ riskLevel: ManagedIdentityPermissionRiskLevel;
17
+ reasons: string[];
18
+ };
19
+
20
+ export type ManagedIdentityPermissionRiskSummary = {
21
+ principalId: string;
22
+ riskLevel: ManagedIdentityPermissionRiskLevel;
23
+ assignmentCount: number;
24
+ highRiskAssignmentCount: number;
25
+ broadScopeAssignmentCount: number;
26
+ roleAssignments: ManagedIdentityPermissionRiskAssignment[];
27
+ };
28
+
29
+ export const AZURE_ACCESS_RISK_RANK: Record<ManagedIdentityPermissionRiskLevel, number> = {
30
+ none: 0,
31
+ low: 1,
32
+ medium: 2,
33
+ high: 3
34
+ };
35
+
36
+ export type AzureIdentityEnrichmentStatus = {
37
+ calculated: boolean;
38
+ latestRunId: string | null;
39
+ identityRoleAssignmentCount: number;
40
+ accessRiskIdentityCount: number;
41
+ managedIdentityAssignmentCount: number;
42
+ calculatedAt: string | null;
43
+ };
44
+
45
+ export type AzureRoleAssignmentEnrichment = {
46
+ principalId: string;
47
+ roleAssignments: AzureRoleAssignment[];
48
+ assignmentCount: number;
49
+ };
50
+
51
+ export type AzureManagedIdentityAssignmentEnrichment = {
52
+ servicePrincipalId: string;
53
+ principalId: string;
54
+ clientId: string;
55
+ managedIdentityAssignments: AzureManagedIdentityResourceAssignment[];
56
+ assignedResourceGroups: string[];
57
+ assignmentCount: number;
58
+ };
59
+
60
+ export type LatestAzureIdentityEnrichment = {
61
+ status: AzureIdentityEnrichmentStatus;
62
+ roleAssignmentsByPrincipalId: Map<string, AzureRoleAssignmentEnrichment>;
63
+ accessRiskByPrincipalId: Map<string, ManagedIdentityPermissionRiskSummary>;
64
+ managedIdentityAssignmentsByServicePrincipalId: Map<string, AzureManagedIdentityAssignmentEnrichment>;
65
+ };
@@ -0,0 +1,141 @@
1
+ import type { OwnerConfidence, OwnerEvidence } from "../ownership/types";
2
+
3
+ export type AzureResourceTags = Record<string, string>;
4
+
5
+ export type AzureResourceGroup = {
6
+ subscriptionId: string;
7
+ subscriptionName: string;
8
+ resourceGroup: string;
9
+ location: string;
10
+ tags: AzureResourceTags | null;
11
+ };
12
+
13
+ export type AzureResource = {
14
+ subscriptionId: string;
15
+ subscriptionName: string;
16
+ resourceId: string;
17
+ resourceName: string;
18
+ resourceGroup: string;
19
+ resourceType: string;
20
+ kind: string | null;
21
+ location: string;
22
+ tags: AzureResourceTags | null;
23
+ identityType: string | null;
24
+ identityPrincipalId: string | null;
25
+ identityTenantId: string | null;
26
+ userAssignedIdentityResourceIds: string[];
27
+ userAssignedIdentities: unknown;
28
+ };
29
+
30
+ export type AzureSubscriptionState = "Enabled" | "Disabled" | "Warned" | "PastDue" | "Deleted";
31
+
32
+ export type AzureSubscription = {
33
+ subscriptionId: string;
34
+ subscriptionName: string;
35
+ tenantId: string;
36
+ state: AzureSubscriptionState;
37
+ tags: AzureResourceTags | null;
38
+ };
39
+
40
+ export type AzureUserAssignedManagedIdentity = {
41
+ subscriptionId: string;
42
+ subscriptionName: string;
43
+ resourceId: string;
44
+ name: string;
45
+ resourceGroup: string;
46
+ location: string;
47
+ clientId: string;
48
+ principalId: string;
49
+ tenantId: string;
50
+ tags: AzureResourceTags | null;
51
+ };
52
+
53
+ export type AzureActivityLog = {
54
+ subscriptionId: string;
55
+ subscriptionName: string;
56
+ eventTimestamp: string;
57
+ submissionTimestamp: string | null;
58
+ caller: string | null;
59
+ callerUserPrincipalName?: string | null;
60
+ callerName?: string | null;
61
+ callerEmail?: string | null;
62
+ callerObjectId?: string | null;
63
+ callerIdentityType?: string | null;
64
+ callerAppId?: string | null;
65
+ callerIpAddress?: string | null;
66
+ callerTenantId?: string | null;
67
+ operationName: string | null;
68
+ operationNameValue: string | null;
69
+ status: string | null;
70
+ subStatus: string | null;
71
+ category: string | null;
72
+ resourceGroupName: string | null;
73
+ resourceId: string | null;
74
+ resourceProviderName: string | null;
75
+ resourceType: string | null;
76
+ authorizationAction: string | null;
77
+ authorizationScope: string | null;
78
+ };
79
+
80
+ export type AzureSnapshotMeta = {
81
+ provider: "azure";
82
+ snapshotVersion: string;
83
+ createdAt: string;
84
+ activityDays: number;
85
+ activityStartTime: string;
86
+ maxActivityRecords: number;
87
+ requestedSubscriptions: string[];
88
+ subscriptionCount: number;
89
+ resourceGroupCount: number;
90
+ resourceCount: number;
91
+ userAssignedManagedIdentityCount: number;
92
+ roleAssignmentCount?: number;
93
+ activityLogCount: number;
94
+ };
95
+
96
+ export type AzureSnapshot = {
97
+ meta: AzureSnapshotMeta;
98
+ subscriptions: AzureSubscription[];
99
+ resourceGroups: AzureResourceGroup[];
100
+ resources: AzureResource[];
101
+ userAssignedManagedIdentities: AzureUserAssignedManagedIdentity[];
102
+ roleAssignments?: AzureRoleAssignment[];
103
+ activityLogs: AzureActivityLog[];
104
+ };
105
+
106
+ export type ResourceGroupOwnershipRow = AzureResourceGroup & {
107
+ targetKey: string;
108
+ owner: string | null;
109
+ confidence: OwnerConfidence;
110
+ source: string;
111
+ evidence: OwnerEvidence[];
112
+ };
113
+
114
+ export type AzureUserAssignedIdentityAssignment = {
115
+ resourceId: string;
116
+ clientId: string | null;
117
+ principalId: string | null;
118
+ };
119
+
120
+ export type AzureRoleAssignment = {
121
+ subscriptionId: string;
122
+ subscriptionName: string;
123
+ roleAssignmentId: string | null;
124
+ scope: string;
125
+ scopeType?: "ManagementGroup" | "Subscription" | "ResourceGroup" | "Resource" | "Unknown" | null;
126
+ scopeSubscriptionId?: string | null;
127
+ scopeResourceGroup?: string | null;
128
+ scopeResourceProvider?: string | null;
129
+ scopeResourceType?: string | null;
130
+ scopeResourceName?: string | null;
131
+ scopeManagementGroup?: string | null;
132
+ principalId: string;
133
+ principalType: string | null;
134
+ principalDisplayName: string | null;
135
+ signInName: string | null;
136
+ roleDefinitionId: string | null;
137
+ roleDefinitionName: string | null;
138
+ canDelegate: boolean | null;
139
+ condition: string | null;
140
+ conditionVersion: string | null;
141
+ };
@@ -0,0 +1,58 @@
1
+ import type { PermissionRiskLevel } from "../risk/types";
2
+
3
+ export type ZtaReport = {
4
+ Meta: ZtaReportMeta;
5
+ Tests: ZtaReportTest[];
6
+ };
7
+
8
+ export type ZtaRemediationSummary = {
9
+ ztaRemediationCountAll: number;
10
+ ztaRemediationFailedCount: number;
11
+ ztaMaxRisk: PermissionRiskLevel;
12
+ };
13
+
14
+ export type ZtaReportMeta = {
15
+ Account?: string | null;
16
+ CurrentVersion?: string | null;
17
+ Domain?: string | null;
18
+ EndOfJson?: boolean | null;
19
+ ExecutedAt?: string | null;
20
+ LatestVersion?: string | null;
21
+ TenantId?: string | null;
22
+ TenantInfo?: Record<string, unknown> | null;
23
+ TenantName?: string | null;
24
+ TestResultSummary?: Record<string, unknown> | null;
25
+ [key: string]: unknown;
26
+ };
27
+
28
+ export type ZtaRelatedObject = {
29
+ id?: string | null;
30
+ object_id?: string | null;
31
+ servicePrincipalId?: string | null;
32
+ tags?: string[] | null;
33
+ applicationId?: string | null;
34
+ displayName?: string | null;
35
+ servicePrincipalType?: string | null;
36
+ userPrincipalName?: string | null;
37
+ };
38
+
39
+ export type ZtaReportTest = {
40
+ TestId?: string | number | null;
41
+ TestTitle?: string | null;
42
+ TestPillar?: string | null;
43
+ SkippedReason?: string | null;
44
+ TestImpact?: string | null;
45
+ TestImplementationCost?: string | null;
46
+ TestMinimumLicense?: string | string[] | null;
47
+ TestStatus?: string | null;
48
+ TestResult?: string | null;
49
+ TestTags?: string[] | null;
50
+ TestSkipped?: string | null;
51
+ TestDescription?: string | null;
52
+ TestCategory?: string | null;
53
+ TestRisk?: string | null;
54
+ TestSfiPillar?: string | null;
55
+ TestAppliesTo?: string[] | null;
56
+ RelatedObjects?: ZtaRelatedObject[] | null;
57
+ [key: string]: unknown;
58
+ };
@@ -0,0 +1,39 @@
1
+ import type { OwnerConfidence } from "./ownership/types";
2
+
3
+ export type OwnerTagConfig = {
4
+ name: string;
5
+ confidence: Exclude<OwnerConfidence, "none">;
6
+ };
7
+
8
+ export type AppConfig = {
9
+ azure: {
10
+ ownership: {
11
+ /**
12
+ * Ordered by priority. The tag value is treated as the owner identity,
13
+ * so it can be a group name, security group alias, or user email.
14
+ */
15
+ ownerTags: OwnerTagConfig[];
16
+ };
17
+ };
18
+ };
19
+
20
+ export const appConfig: AppConfig = {
21
+ azure: {
22
+ ownership: {
23
+ ownerTags: [
24
+ {
25
+ name: "ownerGroup",
26
+ confidence: "high"
27
+ },
28
+ {
29
+ name: "costCenter",
30
+ confidence: "high"
31
+ },
32
+ {
33
+ name: "owner",
34
+ confidence: "medium"
35
+ }
36
+ ]
37
+ }
38
+ }
39
+ };
@@ -0,0 +1,32 @@
1
+ import type { OwnerResolution } from "./types";
2
+
3
+ export type OwnershipSourceProvider = "azure" | "entra" | "zeroTrustAssessment";
4
+
5
+ export type OwnershipTargetRiskLevel = "none" | "low" | "medium" | "high" | "critical";
6
+
7
+ export type OwnershipTargetRef = {
8
+ type: string;
9
+ id: string;
10
+ label?: string;
11
+ };
12
+
13
+ export type OwnershipTarget = {
14
+ id: string;
15
+ kind: string;
16
+ displayName: string;
17
+ sourceProvider: OwnershipSourceProvider;
18
+ technicalId?: string | null;
19
+ ownership?: OwnerResolution;
20
+ riskLevel?: OwnershipTargetRiskLevel;
21
+ refs?: OwnershipTargetRef[];
22
+ };
23
+
24
+ export function buildZeroTrustAssessmentAuditFindingTarget(
25
+ target: Omit<OwnershipTarget, "kind" | "sourceProvider">
26
+ ): OwnershipTarget {
27
+ return {
28
+ ...target,
29
+ kind: "zta.auditFinding",
30
+ sourceProvider: "zeroTrustAssessment"
31
+ };
32
+ }
@@ -0,0 +1,5 @@
1
+ import type { OwnerResolution } from "./types";
2
+
3
+ export type OwnerResolver<TTarget, TContext> = {
4
+ resolveOwner(target: TTarget, context: TContext): OwnerResolution;
5
+ };
@@ -0,0 +1,14 @@
1
+ export type OwnerConfidence = "high" | "medium" | "low" | "none";
2
+
3
+ export type OwnerEvidence = {
4
+ user: string;
5
+ date: string | null;
6
+ disabled?: boolean;
7
+ };
8
+
9
+ export type OwnerResolution = {
10
+ owner: string | null;
11
+ confidence: OwnerConfidence;
12
+ source: string;
13
+ evidence: OwnerEvidence[];
14
+ };
@@ -0,0 +1 @@
1
+ export type PermissionRiskLevel = "high" | "medium" | "low" | "none";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,74 @@
1
+ import { constants as fsConstants } from "node:fs";
2
+ import { access, readdir, readFile, stat } from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ export type LocalSnapshotFile = {
6
+ name: string;
7
+ size: number;
8
+ updatedAt: string;
9
+ };
10
+
11
+ export type LocalSnapshotData = {
12
+ meta?: {
13
+ provider?: string;
14
+ createdAt?: string;
15
+ [key: string]: unknown;
16
+ };
17
+ [key: string]: unknown;
18
+ };
19
+
20
+ export const snapshotNamePattern = /^[\w.-]*snapshot\.json$/i;
21
+
22
+ export async function listLocalSnapshotFiles(dataDir: string): Promise<LocalSnapshotFile[]> {
23
+ const entries = await readdir(dataDir);
24
+ const files = await Promise.all(
25
+ entries
26
+ .filter((name) => snapshotNamePattern.test(name))
27
+ .map(async (name) => {
28
+ const filePath = path.join(dataDir, name);
29
+ const details = await stat(filePath);
30
+ return {
31
+ name,
32
+ size: details.size,
33
+ updatedAt: details.mtime.toISOString()
34
+ };
35
+ })
36
+ );
37
+
38
+ return files.sort((a, b) => a.name.localeCompare(b.name));
39
+ }
40
+
41
+ export async function readLocalSnapshotFile(dataDir: string, name: string): Promise<LocalSnapshotData> {
42
+ validateSnapshotFileName(name);
43
+
44
+ const filePath = path.join(dataDir, name);
45
+ if (!(await pathExists(filePath))) {
46
+ throw new RuntimeHttpError(`Snapshot file ./data/${name} was not found.`, 404);
47
+ }
48
+
49
+ return JSON.parse(await readFile(filePath, "utf8")) as LocalSnapshotData;
50
+ }
51
+
52
+ export function validateSnapshotFileName(name: string): void {
53
+ if (!snapshotNamePattern.test(name) || path.basename(name) !== name) {
54
+ throw new RuntimeHttpError("Invalid snapshot file name.", 400);
55
+ }
56
+ }
57
+
58
+ export async function pathExists(filePath: string): Promise<boolean> {
59
+ try {
60
+ await access(filePath, fsConstants.F_OK);
61
+ return true;
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+
67
+ export class RuntimeHttpError extends Error {
68
+ readonly statusCode: number;
69
+
70
+ constructor(message: string, statusCode: number) {
71
+ super(message);
72
+ this.statusCode = statusCode;
73
+ }
74
+ }
@@ -0,0 +1,61 @@
1
+ export type RuntimeRequest = {
2
+ method?: string;
3
+ url?: string;
4
+ };
5
+
6
+ export type RuntimeResponse = {
7
+ statusCode: number;
8
+ setHeader(name: string, value: string): void;
9
+ end(body: string): void;
10
+ };
11
+
12
+ export type RuntimeNext = () => void;
13
+
14
+ export type RuntimeRestEndpoint = {
15
+ method?: string;
16
+ path: string;
17
+ handle(input: { req: RuntimeRequest; url: URL }): Promise<unknown> | unknown;
18
+ };
19
+
20
+ export type RuntimeRestMiddlewareOptions = {
21
+ basePath: string;
22
+ endpoints: RuntimeRestEndpoint[];
23
+ getErrorStatusCode(error: unknown): number;
24
+ };
25
+
26
+ export function createRuntimeRestMiddleware(options: RuntimeRestMiddlewareOptions) {
27
+ return async (req: RuntimeRequest, res: RuntimeResponse, next: RuntimeNext): Promise<void> => {
28
+ if (!req.url?.startsWith(options.basePath)) {
29
+ next();
30
+ return;
31
+ }
32
+
33
+ try {
34
+ const url = new URL(req.url, "http://localhost");
35
+ const endpoint = options.endpoints.find(
36
+ (candidate) =>
37
+ candidate.path === url.pathname &&
38
+ (!candidate.method || candidate.method.toUpperCase() === (req.method ?? "GET").toUpperCase())
39
+ );
40
+
41
+ if (!endpoint) {
42
+ next();
43
+ return;
44
+ }
45
+
46
+ sendJson(res, await endpoint.handle({ req, url }));
47
+ } catch (error) {
48
+ sendJson(
49
+ res,
50
+ { error: error instanceof Error ? error.message : "Unknown error" },
51
+ options.getErrorStatusCode(error)
52
+ );
53
+ }
54
+ };
55
+ }
56
+
57
+ export function sendJson(res: RuntimeResponse, value: unknown, statusCode = 200): void {
58
+ res.statusCode = statusCode;
59
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
60
+ res.end(JSON.stringify(value));
61
+ }
@@ -0,0 +1,17 @@
1
+ export function hasSearchExpression(query: string): boolean {
2
+ return query.trim().length > 0;
3
+ }
4
+
5
+ export function matchesSearchExpression(searchText: string, query: string): boolean {
6
+ const expression = query.trim();
7
+
8
+ if (expression.length === 0) {
9
+ return true;
10
+ }
11
+
12
+ try {
13
+ return new RegExp(expression, "im").test(searchText);
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
@@ -0,0 +1,48 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+
8
+ export function formatValue(value: unknown): string {
9
+ if (value === null || value === undefined || value === "") {
10
+ return "-";
11
+ }
12
+
13
+ if (Array.isArray(value)) {
14
+ return value.length > 0 ? value.map(formatValue).join(", ") : "[]";
15
+ }
16
+
17
+ if (typeof value === "object") {
18
+ return JSON.stringify(value);
19
+ }
20
+
21
+ return String(value);
22
+ }
23
+
24
+ export function formatDate(value: unknown): string {
25
+ if (typeof value !== "string") {
26
+ return "-";
27
+ }
28
+
29
+ const parsed = new Date(value);
30
+ return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleString();
31
+ }
32
+
33
+ export function formatBytes(bytes: number): string {
34
+ if (!Number.isFinite(bytes)) {
35
+ return "-";
36
+ }
37
+
38
+ const units = ["B", "KB", "MB", "GB"];
39
+ let value = bytes;
40
+ let unitIndex = 0;
41
+
42
+ while (value >= 1024 && unitIndex < units.length - 1) {
43
+ value /= 1024;
44
+ unitIndex += 1;
45
+ }
46
+
47
+ return `${value.toFixed(unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`;
48
+ }
package/src/main.tsx ADDED
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import App from "./App";
4
+ import "./styles.css";
5
+
6
+ createRoot(document.getElementById("root")!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>
10
+ );
@@ -0,0 +1 @@
1
+ export type { AzureManagedIdentityResourceAssignment } from "../../../core/azure/identityEnrichment";
@@ -0,0 +1,32 @@
1
+ import { normalizeUserAssignedIdentityAssignments } from "./userAssignedIdentityAssignments.ts";
2
+
3
+ const firstIdentityResourceId =
4
+ "/subscriptions/sub-1/resourceGroups/rg-a/providers/Microsoft.ManagedIdentity/userAssignedIdentities/identity-a";
5
+ const secondIdentityResourceId =
6
+ "/subscriptions/sub-1/resourceGroups/rg-b/providers/Microsoft.ManagedIdentity/userAssignedIdentities/identity-b";
7
+
8
+ test("normalizes user-assigned identity assignments", () => {
9
+ const assignments = normalizeUserAssignedIdentityAssignments({
10
+ [firstIdentityResourceId]: {
11
+ clientId: "client-a",
12
+ principalId: "principal-a"
13
+ },
14
+ [secondIdentityResourceId]: {
15
+ ClientId: "client-b",
16
+ PrincipalId: "principal-b"
17
+ }
18
+ });
19
+
20
+ expect(assignments).toEqual([
21
+ {
22
+ resourceId: firstIdentityResourceId,
23
+ clientId: "client-a",
24
+ principalId: "principal-a"
25
+ },
26
+ {
27
+ resourceId: secondIdentityResourceId,
28
+ clientId: "client-b",
29
+ principalId: "principal-b"
30
+ }
31
+ ]);
32
+ });