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.
- package/LICENSE +183 -0
- package/README.md +209 -0
- package/bin/ownerlens.js +92 -0
- package/dist/assets/index-B9aAYpVl.css +1 -0
- package/dist/assets/index-BcwLk2bx.js +10 -0
- package/dist/index.html +13 -0
- package/package.json +73 -0
- package/src/App.tsx +18 -0
- package/src/components/azure/AzureComponent.test.tsx +625 -0
- package/src/components/azure/AzureComponent.tsx +189 -0
- package/src/components/azure/AzureRbacComponent.tsx +104 -0
- package/src/components/azure/ClosableAzureTab.tsx +42 -0
- package/src/components/azure/EntraPermissionsComponent.tsx +194 -0
- package/src/components/azure/ManagedIdentityComponent.test.tsx +324 -0
- package/src/components/azure/ManagedIdentityComponent.tsx +141 -0
- package/src/components/azure/ResourceGroupComponent.tsx +157 -0
- package/src/components/azure/ServicePrincipalComponent.test.tsx +457 -0
- package/src/components/azure/ServicePrincipalComponent.tsx +155 -0
- package/src/components/azure/ServicePrincipalFieldRenderers.tsx +140 -0
- package/src/components/azure/ZtaComponent.test.tsx +267 -0
- package/src/components/azure/ZtaComponent.tsx +276 -0
- package/src/components/azure/ZtaRemediationBadge.tsx +70 -0
- package/src/components/azure/api.ts +216 -0
- package/src/components/azure/azureReportConfig.ts +247 -0
- package/src/core/azure/azureRbac.ts +70 -0
- package/src/core/azure/entra/index.ts +1 -0
- package/src/core/azure/entra/managedIdentity.ts +21 -0
- package/src/core/azure/entra/servicePrincipal.ts +34 -0
- package/src/core/azure/entra/types.ts +56 -0
- package/src/core/azure/identityEnrichment.ts +65 -0
- package/src/core/azure/resources.ts +141 -0
- package/src/core/azure/ztaReport.ts +58 -0
- package/src/core/config.ts +39 -0
- package/src/core/ownership/OwnershipTarget.ts +32 -0
- package/src/core/ownership/resolveOwner.ts +5 -0
- package/src/core/ownership/types.ts +14 -0
- package/src/core/risk/types.ts +1 -0
- package/src/core/runtime/index.ts +1 -0
- package/src/core/runtime/localSnapshotFiles.ts +74 -0
- package/src/core/runtime/rest.ts +61 -0
- package/src/lib/searchFilterUtils.ts +17 -0
- package/src/lib/utils.ts +48 -0
- package/src/main.tsx +10 -0
- package/src/providers/azure/identities/azureIdentityTypes.ts +1 -0
- package/src/providers/azure/identities/buildAzureManagedIdentityAssignmentIndex.test.ts +32 -0
- package/src/providers/azure/identities/buildAzureManagedIdentityAssignmentIndex.ts +35 -0
- package/src/providers/azure/identities/userAssignedIdentityAssignments.ts +52 -0
- package/src/providers/azure/inputTransferObject/entra/EntraAppRoleAssignment.ts +10 -0
- package/src/providers/azure/inputTransferObject/entra/EntraApplication.ts +27 -0
- package/src/providers/azure/inputTransferObject/entra/EntraOAuth2PermissionGrant.ts +8 -0
- package/src/providers/azure/inputTransferObject/entra/EntraServicePrincipal.ts +43 -0
- package/src/providers/azure/inputTransferObject/entra/EntraSnapshot.ts +13 -0
- package/src/providers/azure/inputTransferObject/entra/EntraSnapshotMeta.ts +12 -0
- package/src/providers/azure/inputTransferObject/resources/AzureActivityLog.ts +1 -0
- package/src/providers/azure/inputTransferObject/resources/AzureResource.ts +1 -0
- package/src/providers/azure/inputTransferObject/resources/AzureResourceGroup.ts +1 -0
- package/src/providers/azure/inputTransferObject/resources/AzureRoleAssignment.ts +1 -0
- package/src/providers/azure/inputTransferObject/resources/AzureSnapshot.ts +1 -0
- package/src/providers/azure/inputTransferObject/resources/AzureSnapshotMeta.ts +1 -0
- package/src/providers/azure/inputTransferObject/resources/AzureSubscription.ts +1 -0
- package/src/providers/azure/inputTransferObject/resources/AzureUserAssignedManagedIdentity.ts +1 -0
- package/src/providers/azure/ownership/azureActivityOwnershipEvidence.ts +60 -0
- package/src/providers/azure/ownership/azureOwnerReportTypes.ts +13 -0
- package/src/providers/azure/ownership/azureOwnershipConfig.ts +21 -0
- package/src/providers/azure/ownership/azureOwnershipTypes.ts +46 -0
- package/src/providers/azure/ownership/buildAzureOwnershipReport.test.ts +99 -0
- package/src/providers/azure/ownership/buildAzureOwnershipReport.ts +90 -0
- package/src/providers/azure/ownership/buildAzureOwnershipTargets.test.ts +87 -0
- package/src/providers/azure/ownership/buildAzureOwnershipTargets.ts +42 -0
- package/src/providers/azure/ownership/resolveAzureOwner.ts +146 -0
- package/src/providers/azure/runtime/DisabledEvidenceStore.ts +34 -0
- package/src/providers/azure/runtime/EnrichmentService.ts +35 -0
- package/src/providers/azure/runtime/LocalReportRuntime.test.ts +2318 -0
- package/src/providers/azure/runtime/LocalReportRuntime.ts +302 -0
- package/src/providers/azure/runtime/RuntimeHost.ts +60 -0
- package/src/providers/azure/runtime/SnapshotImporter.ts +44 -0
- package/src/providers/azure/runtime/enrichment/azureIdentityEnrichment.ts +523 -0
- package/src/providers/azure/runtime/enrichment/azureScopeClassifier.ts +30 -0
- package/src/providers/azure/runtime/enrichment/evaluateAzureRoleAssignmentRisk.ts +88 -0
- package/src/providers/azure/runtime/entra/EntraCollectionQueryService.ts +307 -0
- package/src/providers/azure/runtime/entra/LocalEntraReportRuntime.ts +227 -0
- package/src/providers/azure/runtime/entra/appRoleAssignmentsTable.ts +52 -0
- package/src/providers/azure/runtime/entra/applicationsTable.ts +175 -0
- package/src/providers/azure/runtime/entra/entraServicePrincipalMapper.ts +63 -0
- package/src/providers/azure/runtime/entra/localReportRuntimeRest.ts +41 -0
- package/src/providers/azure/runtime/entra/oauth2PermissionGrantsTable.ts +48 -0
- package/src/providers/azure/runtime/entra/principalProjection.ts +173 -0
- package/src/providers/azure/runtime/entra/servicePrincipalsTable.ts +149 -0
- package/src/providers/azure/runtime/entra/snapshotMetadataTable.ts +18 -0
- package/src/providers/azure/runtime/entra/snapshotStore.ts +102 -0
- package/src/providers/azure/runtime/localReportCollections.ts +101 -0
- package/src/providers/azure/runtime/localReportRuntimeRest.ts +71 -0
- package/src/providers/azure/runtime/resources/AzureResourcesCollectionQueryService.ts +145 -0
- package/src/providers/azure/runtime/resources/LocalAzureResourcesReportRuntime.ts +114 -0
- package/src/providers/azure/runtime/resources/disabledOwnerEvidenceTable.ts +60 -0
- package/src/providers/azure/runtime/resources/localReportRuntimeRest.ts +81 -0
- package/src/providers/azure/runtime/resources/resourceGroupOwnership.ts +90 -0
- package/src/providers/azure/runtime/resources/snapshotMetadataTable.ts +19 -0
- package/src/providers/azure/runtime/resources/snapshotStore.ts +128 -0
- package/src/providers/azure/runtime/resources/tables.ts +441 -0
- package/src/providers/azure/runtime/runtimeRestQuery.ts +46 -0
- package/src/providers/azure/runtime/runtimeSqlSchema.ts +357 -0
- package/src/providers/azure/runtime/zta/Discovery.ts +141 -0
- package/src/providers/azure/runtime/zta/LocalZeroTrustAssessmentReportRuntime.ts +86 -0
- package/src/providers/azure/runtime/zta/ZeroTrustAssessmentQueryService.ts +124 -0
- package/src/providers/azure/runtime/zta/localReportRuntimeRest.ts +15 -0
- package/src/providers/azure/runtime/zta/snapshotMetadataTable.ts +77 -0
- package/src/providers/azure/runtime/zta/snapshotStore.ts +112 -0
- package/src/providers/azure/runtime/zta/tables.ts +361 -0
- package/src/providers/azure/runtime/zta/types.ts +7 -0
- package/src/providers/azure/runtime/zta/ztaReportMapper.ts +12 -0
- package/src/report/applyCollectionControls.ts +289 -0
- package/src/report/buildCollectionColumns.tsx +38 -0
- package/src/report/components/ConfidenceBadge.tsx +10 -0
- package/src/report/components/EvidenceList.test.ts +25 -0
- package/src/report/components/EvidenceList.tsx +52 -0
- package/src/report/components/GenericTable.tsx +373 -0
- package/src/report/components/PermissionRiskBadge.tsx +19 -0
- package/src/report/components/reportTableControls.test.ts +175 -0
- package/src/report/components/reportTableControls.tsx +483 -0
- package/src/report/components/ui/badge.tsx +35 -0
- package/src/report/components/ui/button.tsx +38 -0
- package/src/report/components/ui/card.tsx +23 -0
- package/src/report/components/ui/input.tsx +15 -0
- package/src/report/components/ui/table.tsx +44 -0
- package/src/report/components/ui/tabs.tsx +29 -0
- package/src/report/export/csv.ts +34 -0
- package/src/report/ownerManualPrecheck.test.ts +137 -0
- package/src/report/ownerManualPrecheck.ts +132 -0
- package/src/report/reportArchitecture.test.ts +125 -0
- package/src/report/reportTypes.ts +54 -0
- package/src/report/reportValueRenderers.tsx +54 -0
- package/src/report/runtimeCollectionQuery.ts +23 -0
- package/src/report/types.ts +14 -0
- package/src/styles.css +43 -0
- package/tools/README.md +108 -0
- package/tools/azure-activity-check.ps1 +164 -0
- package/tools/collect-azure.ps1 +54 -0
- package/tools/collect-entra.ps1 +47 -0
- package/tools/collect-scripts.test.ts +22 -0
- package/tools/prepare-entra-snapshot.ps1 +403 -0
- package/tools/prepare-entra-snapshot.test.ts +14 -0
- package/tools/prepare-resource-snapshot.ps1 +345 -0
- package/vite.config.ts +23 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { OwnerEvidence } from "../../../../core/ownership/types";
|
|
2
|
+
import type { AzureResourceGroup, ResourceGroupOwnershipRow } from "../../../../core/azure/resources";
|
|
3
|
+
import type { OwnerReportRow } from "../../ownership/azureOwnerReportTypes";
|
|
4
|
+
|
|
5
|
+
export function buildResourceGroupOwnershipRows(
|
|
6
|
+
resourceGroups: AzureResourceGroup[],
|
|
7
|
+
ownerRows: OwnerReportRow[]
|
|
8
|
+
): ResourceGroupOwnershipRow[] {
|
|
9
|
+
const ownerIndex = buildResourceGroupOwnerIndex(ownerRows);
|
|
10
|
+
|
|
11
|
+
return resourceGroups.map((group) => {
|
|
12
|
+
const ownerRow = ownerIndex.get(getResourceGroupOwnerIndexKey(group.subscriptionId, group.resourceGroup));
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
...group,
|
|
16
|
+
targetKey: ownerRow?.targetKey ?? getResourceGroupTargetKey(group.subscriptionId, group.resourceGroup),
|
|
17
|
+
owner: ownerRow?.owner ?? null,
|
|
18
|
+
confidence: ownerRow?.confidence ?? "none",
|
|
19
|
+
source: ownerRow?.source ?? "none",
|
|
20
|
+
evidence: ownerRow?.evidence ?? []
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function applyResourceGroupOwnerDisabledEvidence(
|
|
26
|
+
ownerRows: OwnerReportRow[],
|
|
27
|
+
disabledKeys: ReadonlySet<string>
|
|
28
|
+
): OwnerReportRow[] {
|
|
29
|
+
return ownerRows.map((row) => {
|
|
30
|
+
if (row.kind !== "resourceGroup" || !row.source.startsWith("activity.")) {
|
|
31
|
+
return row;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const evidence = row.evidence.map((entry) => ({
|
|
35
|
+
...entry,
|
|
36
|
+
disabled:
|
|
37
|
+
isDefaultDisabledOwnerEvidence(entry) ||
|
|
38
|
+
disabledKeys.has(getResourceGroupOwnerEvidenceKey(row, entry)) ||
|
|
39
|
+
undefined
|
|
40
|
+
}));
|
|
41
|
+
const activeEvidence = evidence.filter((entry) => !entry.disabled);
|
|
42
|
+
|
|
43
|
+
if (activeEvidence.length === 0) {
|
|
44
|
+
return {
|
|
45
|
+
...row,
|
|
46
|
+
owner: null,
|
|
47
|
+
confidence: "none",
|
|
48
|
+
evidence
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
...row,
|
|
54
|
+
owner: activeEvidence[0].user,
|
|
55
|
+
confidence: "low",
|
|
56
|
+
evidence
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function buildResourceGroupOwnerIndex(ownerRows: OwnerReportRow[]): Map<string, OwnerReportRow> {
|
|
62
|
+
const index = new Map<string, OwnerReportRow>();
|
|
63
|
+
|
|
64
|
+
for (const row of ownerRows) {
|
|
65
|
+
if (row.kind === "resourceGroup" && row.resourceGroup) {
|
|
66
|
+
index.set(getResourceGroupOwnerIndexKey(row.subscriptionId, row.resourceGroup), row);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return index;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getResourceGroupOwnerIndexKey(subscriptionId: string, resourceGroup: string): string {
|
|
74
|
+
return `${subscriptionId.toLowerCase()}:${resourceGroup.toLowerCase()}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getResourceGroupTargetKey(subscriptionId: string, resourceGroup: string): string {
|
|
78
|
+
return ["resourceGroup", subscriptionId.toLowerCase(), resourceGroup.toLowerCase()].join(":");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getResourceGroupOwnerEvidenceKey(
|
|
82
|
+
row: Pick<OwnerReportRow, "targetKey">,
|
|
83
|
+
evidence: Pick<OwnerEvidence, "user" | "date">
|
|
84
|
+
): string {
|
|
85
|
+
return [row.targetKey, evidence.user.trim().toLowerCase(), evidence.date ?? ""].join(":");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function isDefaultDisabledOwnerEvidence(evidence: Pick<OwnerEvidence, "user">): boolean {
|
|
89
|
+
return !evidence.user.includes("@");
|
|
90
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DuckDBConnection } from "@duckdb/node-api";
|
|
2
|
+
|
|
3
|
+
import type { LocalSnapshotData } from "../../../../core/runtime/localSnapshotFiles";
|
|
4
|
+
import type { AzureSnapshot as AzureSnapshotInput } from "../../inputTransferObject/resources/AzureSnapshot";
|
|
5
|
+
|
|
6
|
+
export async function importAzureResourcesSnapshotMetadata(
|
|
7
|
+
connection: DuckDBConnection,
|
|
8
|
+
snapshot: AzureSnapshotInput & LocalSnapshotData
|
|
9
|
+
): Promise<void> {
|
|
10
|
+
const { meta, subscriptions, resourceGroups, resources, userAssignedManagedIdentities, roleAssignments, activityLogs, ...extra } =
|
|
11
|
+
snapshot;
|
|
12
|
+
|
|
13
|
+
await connection.run("insert into azure_resources_snapshot_meta values ($meta::json)", {
|
|
14
|
+
meta: JSON.stringify(meta ?? {})
|
|
15
|
+
});
|
|
16
|
+
await connection.run("insert into azure_resources_snapshot_extra values ($extra::json)", {
|
|
17
|
+
extra: JSON.stringify(extra)
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { DuckDBConnection } from "@duckdb/node-api";
|
|
2
|
+
|
|
3
|
+
import type { AzureSnapshot } from "../../../../core/azure/resources";
|
|
4
|
+
import type { LocalSnapshotData } from "../../../../core/runtime/localSnapshotFiles";
|
|
5
|
+
import type { AzureSnapshot as AzureSnapshotInput } from "../../inputTransferObject/resources/AzureSnapshot";
|
|
6
|
+
import { importAzureResourcesSnapshotMetadata } from "./snapshotMetadataTable";
|
|
7
|
+
import {
|
|
8
|
+
insertAzureActivityLogRows,
|
|
9
|
+
insertAzureResourceGroupRows,
|
|
10
|
+
insertAzureResourceRows,
|
|
11
|
+
insertAzureRoleAssignmentRows,
|
|
12
|
+
insertAzureSubscriptionRows,
|
|
13
|
+
insertAzureUserAssignedManagedIdentityRows,
|
|
14
|
+
readAzureActivityLogRows,
|
|
15
|
+
readAzureResourceGroupRows,
|
|
16
|
+
readAzureResourceRows,
|
|
17
|
+
readAzureRoleAssignmentRows,
|
|
18
|
+
readAzureSubscriptionRows,
|
|
19
|
+
readAzureUserAssignedManagedIdentityRows
|
|
20
|
+
} from "./tables";
|
|
21
|
+
|
|
22
|
+
export const azureResourcesSnapshotFileName = "snapshot.json";
|
|
23
|
+
|
|
24
|
+
export type AzureResourcesDuckDbImportStatus = {
|
|
25
|
+
imported: boolean;
|
|
26
|
+
fileName: string;
|
|
27
|
+
subscriptionCount: number;
|
|
28
|
+
resourceGroupCount: number;
|
|
29
|
+
resourceCount: number;
|
|
30
|
+
userAssignedManagedIdentityCount: number;
|
|
31
|
+
roleAssignmentCount: number;
|
|
32
|
+
activityLogCount: number;
|
|
33
|
+
importedAt: string | null;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function createEmptyAzureResourcesImportStatus(): AzureResourcesDuckDbImportStatus {
|
|
37
|
+
return {
|
|
38
|
+
imported: false,
|
|
39
|
+
fileName: azureResourcesSnapshotFileName,
|
|
40
|
+
subscriptionCount: 0,
|
|
41
|
+
resourceGroupCount: 0,
|
|
42
|
+
resourceCount: 0,
|
|
43
|
+
userAssignedManagedIdentityCount: 0,
|
|
44
|
+
roleAssignmentCount: 0,
|
|
45
|
+
activityLogCount: 0,
|
|
46
|
+
importedAt: null
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function importAzureResourcesSnapshotToDuckDb(
|
|
51
|
+
connection: DuckDBConnection,
|
|
52
|
+
snapshot: AzureSnapshotInput & LocalSnapshotData
|
|
53
|
+
): Promise<AzureResourcesDuckDbImportStatus> {
|
|
54
|
+
await connection.run("begin transaction");
|
|
55
|
+
try {
|
|
56
|
+
await connection.run("delete from azure_resources_snapshot_meta");
|
|
57
|
+
await connection.run("delete from azure_resources_snapshot_extra");
|
|
58
|
+
await connection.run("delete from azure_subscriptions");
|
|
59
|
+
await connection.run("delete from azure_resource_groups");
|
|
60
|
+
await connection.run("delete from azure_resources");
|
|
61
|
+
await connection.run("delete from azure_user_assigned_managed_identities");
|
|
62
|
+
await connection.run("delete from azure_role_assignments");
|
|
63
|
+
await connection.run("delete from azure_activity_logs");
|
|
64
|
+
|
|
65
|
+
const {
|
|
66
|
+
subscriptions,
|
|
67
|
+
resourceGroups,
|
|
68
|
+
resources,
|
|
69
|
+
userAssignedManagedIdentities,
|
|
70
|
+
roleAssignments = [],
|
|
71
|
+
activityLogs
|
|
72
|
+
} = snapshot;
|
|
73
|
+
|
|
74
|
+
await importAzureResourcesSnapshotMetadata(connection, snapshot);
|
|
75
|
+
await insertAzureSubscriptionRows(connection, subscriptions);
|
|
76
|
+
await insertAzureResourceGroupRows(connection, resourceGroups);
|
|
77
|
+
await insertAzureResourceRows(connection, resources);
|
|
78
|
+
await insertAzureUserAssignedManagedIdentityRows(connection, userAssignedManagedIdentities);
|
|
79
|
+
await insertAzureRoleAssignmentRows(connection, roleAssignments);
|
|
80
|
+
await insertAzureActivityLogRows(connection, activityLogs);
|
|
81
|
+
|
|
82
|
+
await connection.run("commit");
|
|
83
|
+
return {
|
|
84
|
+
imported: true,
|
|
85
|
+
fileName: azureResourcesSnapshotFileName,
|
|
86
|
+
subscriptionCount: subscriptions.length,
|
|
87
|
+
resourceGroupCount: resourceGroups.length,
|
|
88
|
+
resourceCount: resources.length,
|
|
89
|
+
userAssignedManagedIdentityCount: userAssignedManagedIdentities.length,
|
|
90
|
+
roleAssignmentCount: roleAssignments.length,
|
|
91
|
+
activityLogCount: activityLogs.length,
|
|
92
|
+
importedAt: new Date().toISOString()
|
|
93
|
+
};
|
|
94
|
+
} catch (error) {
|
|
95
|
+
await connection.run("rollback");
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function readAzureResourcesSnapshotFromDuckDb(
|
|
101
|
+
connection: DuckDBConnection
|
|
102
|
+
): Promise<AzureSnapshot & LocalSnapshotData> {
|
|
103
|
+
const metaRows = await readRows<{ data: string }>(connection, "select data from azure_resources_snapshot_meta limit 1");
|
|
104
|
+
const extraRows = await readRows<{ data: string }>(connection, "select data from azure_resources_snapshot_extra limit 1");
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
...parseJsonObject(extraRows[0]?.data),
|
|
108
|
+
meta: parseJsonObject(metaRows[0]?.data) as AzureSnapshot["meta"],
|
|
109
|
+
subscriptions: await readAzureSubscriptionRows(connection),
|
|
110
|
+
resourceGroups: await readAzureResourceGroupRows(connection),
|
|
111
|
+
resources: await readAzureResourceRows(connection),
|
|
112
|
+
userAssignedManagedIdentities: await readAzureUserAssignedManagedIdentityRows(connection),
|
|
113
|
+
roleAssignments: await readAzureRoleAssignmentRows(connection),
|
|
114
|
+
activityLogs: await readAzureActivityLogRows(connection)
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function readRows<Row extends Record<string, unknown>>(
|
|
119
|
+
connection: DuckDBConnection,
|
|
120
|
+
sql: string
|
|
121
|
+
): Promise<Row[]> {
|
|
122
|
+
const reader = await connection.runAndReadAll(sql);
|
|
123
|
+
return reader.getRowObjectsJson() as Row[];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function parseJsonObject(value: string | null | undefined): Record<string, unknown> {
|
|
127
|
+
return value ? JSON.parse(value) : {};
|
|
128
|
+
}
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import type { DuckDBConnection } from "@duckdb/node-api";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
AzureActivityLog,
|
|
5
|
+
AzureResource,
|
|
6
|
+
AzureResourceGroup,
|
|
7
|
+
AzureRoleAssignment,
|
|
8
|
+
AzureSubscription,
|
|
9
|
+
AzureUserAssignedManagedIdentity
|
|
10
|
+
} from "../../../../core/azure/resources";
|
|
11
|
+
import type { AzureActivityLog as AzureActivityLogInput } from "../../inputTransferObject/resources/AzureActivityLog";
|
|
12
|
+
import type { AzureResource as AzureResourceInput } from "../../inputTransferObject/resources/AzureResource";
|
|
13
|
+
import type { AzureResourceGroup as AzureResourceGroupInput } from "../../inputTransferObject/resources/AzureResourceGroup";
|
|
14
|
+
import type { AzureRoleAssignment as AzureRoleAssignmentInput } from "../../inputTransferObject/resources/AzureRoleAssignment";
|
|
15
|
+
import type { AzureSubscription as AzureSubscriptionInput } from "../../inputTransferObject/resources/AzureSubscription";
|
|
16
|
+
import type { AzureUserAssignedManagedIdentity as AzureUserAssignedManagedIdentityInput } from "../../inputTransferObject/resources/AzureUserAssignedManagedIdentity";
|
|
17
|
+
|
|
18
|
+
export async function insertAzureSubscriptionRows(
|
|
19
|
+
connection: DuckDBConnection,
|
|
20
|
+
subscriptions: AzureSubscriptionInput[]
|
|
21
|
+
): Promise<void> {
|
|
22
|
+
for (const [ordinal, row] of subscriptions.entries()) {
|
|
23
|
+
await connection.run("insert into azure_subscriptions values ($ordinal, $subscriptionId, $subscriptionName, $tenantId, $state, $tags::json)", {
|
|
24
|
+
ordinal,
|
|
25
|
+
subscriptionId: row.subscriptionId,
|
|
26
|
+
subscriptionName: row.subscriptionName,
|
|
27
|
+
tenantId: row.tenantId,
|
|
28
|
+
state: row.state,
|
|
29
|
+
tags: JSON.stringify(row.tags ?? null)
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function insertAzureResourceGroupRows(
|
|
35
|
+
connection: DuckDBConnection,
|
|
36
|
+
resourceGroups: AzureResourceGroupInput[]
|
|
37
|
+
): Promise<void> {
|
|
38
|
+
for (const [ordinal, row] of resourceGroups.entries()) {
|
|
39
|
+
await connection.run(
|
|
40
|
+
"insert into azure_resource_groups values ($ordinal, $subscriptionId, $subscriptionName, $resourceGroup, $location, $tags::json)",
|
|
41
|
+
{
|
|
42
|
+
ordinal,
|
|
43
|
+
subscriptionId: row.subscriptionId,
|
|
44
|
+
subscriptionName: row.subscriptionName,
|
|
45
|
+
resourceGroup: row.resourceGroup,
|
|
46
|
+
location: row.location,
|
|
47
|
+
tags: JSON.stringify(row.tags ?? null)
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function insertAzureResourceRows(connection: DuckDBConnection, resources: AzureResourceInput[]): Promise<void> {
|
|
54
|
+
for (const [ordinal, row] of resources.entries()) {
|
|
55
|
+
await connection.run(
|
|
56
|
+
`insert into azure_resources values (
|
|
57
|
+
$ordinal, $subscriptionId, $subscriptionName, $resourceId, $resourceName, $resourceGroup, $resourceType,
|
|
58
|
+
$kind, $location, $tags::json, $identityType, $identityPrincipalId, $identityTenantId,
|
|
59
|
+
$userAssignedIdentityResourceIds::json, $userAssignedIdentities::json
|
|
60
|
+
)`,
|
|
61
|
+
{
|
|
62
|
+
ordinal,
|
|
63
|
+
subscriptionId: row.subscriptionId,
|
|
64
|
+
subscriptionName: row.subscriptionName,
|
|
65
|
+
resourceId: row.resourceId,
|
|
66
|
+
resourceName: row.resourceName,
|
|
67
|
+
resourceGroup: row.resourceGroup,
|
|
68
|
+
resourceType: row.resourceType,
|
|
69
|
+
kind: row.kind,
|
|
70
|
+
location: row.location,
|
|
71
|
+
tags: JSON.stringify(row.tags ?? null),
|
|
72
|
+
identityType: row.identityType,
|
|
73
|
+
identityPrincipalId: row.identityPrincipalId,
|
|
74
|
+
identityTenantId: row.identityTenantId,
|
|
75
|
+
userAssignedIdentityResourceIds: JSON.stringify(row.userAssignedIdentityResourceIds ?? []),
|
|
76
|
+
userAssignedIdentities: JSON.stringify(row.userAssignedIdentities ?? null)
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function insertAzureUserAssignedManagedIdentityRows(
|
|
83
|
+
connection: DuckDBConnection,
|
|
84
|
+
identities: AzureUserAssignedManagedIdentityInput[]
|
|
85
|
+
): Promise<void> {
|
|
86
|
+
for (const [ordinal, row] of identities.entries()) {
|
|
87
|
+
await connection.run(
|
|
88
|
+
`insert into azure_user_assigned_managed_identities values (
|
|
89
|
+
$ordinal, $subscriptionId, $subscriptionName, $resourceId, $name, $resourceGroup, $location,
|
|
90
|
+
$clientId, $principalId, $tenantId, $tags::json
|
|
91
|
+
)`,
|
|
92
|
+
{
|
|
93
|
+
ordinal,
|
|
94
|
+
subscriptionId: row.subscriptionId,
|
|
95
|
+
subscriptionName: row.subscriptionName,
|
|
96
|
+
resourceId: row.resourceId,
|
|
97
|
+
name: row.name,
|
|
98
|
+
resourceGroup: row.resourceGroup,
|
|
99
|
+
location: row.location,
|
|
100
|
+
clientId: row.clientId,
|
|
101
|
+
principalId: row.principalId,
|
|
102
|
+
tenantId: row.tenantId,
|
|
103
|
+
tags: JSON.stringify(row.tags ?? null)
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function insertAzureRoleAssignmentRows(
|
|
110
|
+
connection: DuckDBConnection,
|
|
111
|
+
assignments: AzureRoleAssignmentInput[]
|
|
112
|
+
): Promise<void> {
|
|
113
|
+
for (const [ordinal, row] of assignments.entries()) {
|
|
114
|
+
await connection.run(
|
|
115
|
+
`insert into azure_role_assignments values (
|
|
116
|
+
$ordinal, $subscriptionId, $subscriptionName, $roleAssignmentId, $scope, $scopeType, $scopeSubscriptionId,
|
|
117
|
+
$scopeResourceGroup, $scopeResourceProvider, $scopeResourceType, $scopeResourceName, $scopeManagementGroup,
|
|
118
|
+
$principalId, $principalType, $principalDisplayName, $signInName, $roleDefinitionId, $roleDefinitionName,
|
|
119
|
+
$canDelegate, $condition, $conditionVersion
|
|
120
|
+
)`,
|
|
121
|
+
{
|
|
122
|
+
ordinal,
|
|
123
|
+
subscriptionId: row.subscriptionId,
|
|
124
|
+
subscriptionName: row.subscriptionName,
|
|
125
|
+
roleAssignmentId: row.roleAssignmentId,
|
|
126
|
+
scope: row.scope,
|
|
127
|
+
scopeType: row.scopeType ?? null,
|
|
128
|
+
scopeSubscriptionId: row.scopeSubscriptionId ?? null,
|
|
129
|
+
scopeResourceGroup: row.scopeResourceGroup ?? null,
|
|
130
|
+
scopeResourceProvider: row.scopeResourceProvider ?? null,
|
|
131
|
+
scopeResourceType: row.scopeResourceType ?? null,
|
|
132
|
+
scopeResourceName: row.scopeResourceName ?? null,
|
|
133
|
+
scopeManagementGroup: row.scopeManagementGroup ?? null,
|
|
134
|
+
principalId: row.principalId,
|
|
135
|
+
principalType: row.principalType,
|
|
136
|
+
principalDisplayName: row.principalDisplayName,
|
|
137
|
+
signInName: row.signInName,
|
|
138
|
+
roleDefinitionId: row.roleDefinitionId,
|
|
139
|
+
roleDefinitionName: row.roleDefinitionName,
|
|
140
|
+
canDelegate: row.canDelegate,
|
|
141
|
+
condition: row.condition,
|
|
142
|
+
conditionVersion: row.conditionVersion
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function insertAzureActivityLogRows(connection: DuckDBConnection, logs: AzureActivityLogInput[]): Promise<void> {
|
|
149
|
+
for (const [ordinal, row] of logs.entries()) {
|
|
150
|
+
await connection.run(
|
|
151
|
+
`insert into azure_activity_logs values (
|
|
152
|
+
$ordinal, $subscriptionId, $subscriptionName, $eventTimestamp, $submissionTimestamp, $caller,
|
|
153
|
+
$callerUserPrincipalName, $callerName, $callerEmail, $callerObjectId, $callerIdentityType, $callerAppId,
|
|
154
|
+
$callerIpAddress, $callerTenantId, $operationName, $operationNameValue, $status, $subStatus, $category,
|
|
155
|
+
$resourceGroupName, $resourceId, $resourceProviderName, $resourceType, $authorizationAction, $authorizationScope
|
|
156
|
+
)`,
|
|
157
|
+
{
|
|
158
|
+
ordinal,
|
|
159
|
+
subscriptionId: row.subscriptionId,
|
|
160
|
+
subscriptionName: row.subscriptionName,
|
|
161
|
+
eventTimestamp: row.eventTimestamp,
|
|
162
|
+
submissionTimestamp: row.submissionTimestamp,
|
|
163
|
+
caller: row.caller,
|
|
164
|
+
callerUserPrincipalName: row.callerUserPrincipalName ?? null,
|
|
165
|
+
callerName: row.callerName ?? null,
|
|
166
|
+
callerEmail: row.callerEmail ?? null,
|
|
167
|
+
callerObjectId: row.callerObjectId ?? null,
|
|
168
|
+
callerIdentityType: row.callerIdentityType ?? null,
|
|
169
|
+
callerAppId: row.callerAppId ?? null,
|
|
170
|
+
callerIpAddress: row.callerIpAddress ?? null,
|
|
171
|
+
callerTenantId: row.callerTenantId ?? null,
|
|
172
|
+
operationName: row.operationName,
|
|
173
|
+
operationNameValue: row.operationNameValue,
|
|
174
|
+
status: row.status,
|
|
175
|
+
subStatus: row.subStatus,
|
|
176
|
+
category: row.category,
|
|
177
|
+
resourceGroupName: row.resourceGroupName,
|
|
178
|
+
resourceId: row.resourceId,
|
|
179
|
+
resourceProviderName: row.resourceProviderName,
|
|
180
|
+
resourceType: row.resourceType,
|
|
181
|
+
authorizationAction: row.authorizationAction,
|
|
182
|
+
authorizationScope: row.authorizationScope
|
|
183
|
+
}
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export async function readAzureSubscriptionRows(connection: DuckDBConnection): Promise<AzureSubscription[]> {
|
|
189
|
+
return (await readRows<AzureSubscriptionRow>(
|
|
190
|
+
connection,
|
|
191
|
+
"select subscription_id, subscription_name, tenant_id, state, tags from azure_subscriptions order by ordinal"
|
|
192
|
+
)).map((row) => ({
|
|
193
|
+
subscriptionId: row.subscription_id,
|
|
194
|
+
subscriptionName: row.subscription_name,
|
|
195
|
+
tenantId: row.tenant_id,
|
|
196
|
+
state: row.state,
|
|
197
|
+
tags: parseJsonObject(row.tags)
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export async function readAzureResourceGroupRows(connection: DuckDBConnection): Promise<AzureResourceGroup[]> {
|
|
202
|
+
return (await readRows<AzureResourceGroupRow>(
|
|
203
|
+
connection,
|
|
204
|
+
"select subscription_id, subscription_name, resource_group, location, tags from azure_resource_groups order by ordinal"
|
|
205
|
+
)).map((row) => ({
|
|
206
|
+
subscriptionId: row.subscription_id,
|
|
207
|
+
subscriptionName: row.subscription_name,
|
|
208
|
+
resourceGroup: row.resource_group,
|
|
209
|
+
location: row.location,
|
|
210
|
+
tags: parseJsonObject(row.tags)
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export async function readAzureResourceRows(connection: DuckDBConnection): Promise<AzureResource[]> {
|
|
215
|
+
return (await readRows<AzureResourceRow>(
|
|
216
|
+
connection,
|
|
217
|
+
`select subscription_id, subscription_name, resource_id, resource_name, resource_group, resource_type, kind, location,
|
|
218
|
+
tags, identity_type, identity_principal_id, identity_tenant_id, user_assigned_identity_resource_ids, user_assigned_identities
|
|
219
|
+
from azure_resources order by ordinal`
|
|
220
|
+
)).map((row) => ({
|
|
221
|
+
subscriptionId: row.subscription_id,
|
|
222
|
+
subscriptionName: row.subscription_name,
|
|
223
|
+
resourceId: row.resource_id,
|
|
224
|
+
resourceName: row.resource_name,
|
|
225
|
+
resourceGroup: row.resource_group,
|
|
226
|
+
resourceType: row.resource_type,
|
|
227
|
+
kind: row.kind,
|
|
228
|
+
location: row.location,
|
|
229
|
+
tags: parseJsonObject(row.tags),
|
|
230
|
+
identityType: row.identity_type,
|
|
231
|
+
identityPrincipalId: row.identity_principal_id,
|
|
232
|
+
identityTenantId: row.identity_tenant_id,
|
|
233
|
+
userAssignedIdentityResourceIds: parseJsonArray<string>(row.user_assigned_identity_resource_ids),
|
|
234
|
+
userAssignedIdentities: parseJsonValue(row.user_assigned_identities)
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export async function readAzureUserAssignedManagedIdentityRows(
|
|
239
|
+
connection: DuckDBConnection
|
|
240
|
+
): Promise<AzureUserAssignedManagedIdentity[]> {
|
|
241
|
+
return (await readRows<AzureUserAssignedManagedIdentityRow>(
|
|
242
|
+
connection,
|
|
243
|
+
`select subscription_id, subscription_name, resource_id, name, resource_group, location, client_id, principal_id, tenant_id, tags
|
|
244
|
+
from azure_user_assigned_managed_identities order by ordinal`
|
|
245
|
+
)).map((row) => ({
|
|
246
|
+
subscriptionId: row.subscription_id,
|
|
247
|
+
subscriptionName: row.subscription_name,
|
|
248
|
+
resourceId: row.resource_id,
|
|
249
|
+
name: row.name,
|
|
250
|
+
resourceGroup: row.resource_group,
|
|
251
|
+
location: row.location,
|
|
252
|
+
clientId: row.client_id,
|
|
253
|
+
principalId: row.principal_id,
|
|
254
|
+
tenantId: row.tenant_id,
|
|
255
|
+
tags: parseJsonObject(row.tags)
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export async function readAzureRoleAssignmentRows(connection: DuckDBConnection): Promise<AzureRoleAssignment[]> {
|
|
260
|
+
return (await readRows<AzureRoleAssignmentRow>(
|
|
261
|
+
connection,
|
|
262
|
+
`select subscription_id, subscription_name, role_assignment_id, scope, scope_type, scope_subscription_id,
|
|
263
|
+
scope_resource_group, scope_resource_provider, scope_resource_type, scope_resource_name, scope_management_group,
|
|
264
|
+
principal_id, principal_type, principal_display_name, sign_in_name, role_definition_id, role_definition_name,
|
|
265
|
+
can_delegate, condition, condition_version
|
|
266
|
+
from azure_role_assignments order by ordinal`
|
|
267
|
+
)).map((row) => ({
|
|
268
|
+
subscriptionId: row.subscription_id,
|
|
269
|
+
subscriptionName: row.subscription_name,
|
|
270
|
+
roleAssignmentId: row.role_assignment_id,
|
|
271
|
+
scope: row.scope,
|
|
272
|
+
scopeType: row.scope_type,
|
|
273
|
+
scopeSubscriptionId: row.scope_subscription_id,
|
|
274
|
+
scopeResourceGroup: row.scope_resource_group,
|
|
275
|
+
scopeResourceProvider: row.scope_resource_provider,
|
|
276
|
+
scopeResourceType: row.scope_resource_type,
|
|
277
|
+
scopeResourceName: row.scope_resource_name,
|
|
278
|
+
scopeManagementGroup: row.scope_management_group,
|
|
279
|
+
principalId: row.principal_id,
|
|
280
|
+
principalType: row.principal_type,
|
|
281
|
+
principalDisplayName: row.principal_display_name,
|
|
282
|
+
signInName: row.sign_in_name,
|
|
283
|
+
roleDefinitionId: row.role_definition_id,
|
|
284
|
+
roleDefinitionName: row.role_definition_name,
|
|
285
|
+
canDelegate: row.can_delegate,
|
|
286
|
+
condition: row.condition,
|
|
287
|
+
conditionVersion: row.condition_version
|
|
288
|
+
}));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export async function readAzureActivityLogRows(connection: DuckDBConnection): Promise<AzureActivityLog[]> {
|
|
292
|
+
return (await readRows<AzureActivityLogRow>(
|
|
293
|
+
connection,
|
|
294
|
+
`select subscription_id, subscription_name, event_timestamp, submission_timestamp, caller, caller_user_principal_name,
|
|
295
|
+
caller_name, caller_email, caller_object_id, caller_identity_type, caller_app_id, caller_ip_address, caller_tenant_id,
|
|
296
|
+
operation_name, operation_name_value, status, sub_status, category, resource_group_name, resource_id,
|
|
297
|
+
resource_provider_name, resource_type, authorization_action, authorization_scope
|
|
298
|
+
from azure_activity_logs order by ordinal`
|
|
299
|
+
)).map((row) => ({
|
|
300
|
+
subscriptionId: row.subscription_id,
|
|
301
|
+
subscriptionName: row.subscription_name,
|
|
302
|
+
eventTimestamp: row.event_timestamp,
|
|
303
|
+
submissionTimestamp: row.submission_timestamp,
|
|
304
|
+
caller: row.caller,
|
|
305
|
+
callerUserPrincipalName: row.caller_user_principal_name,
|
|
306
|
+
callerName: row.caller_name,
|
|
307
|
+
callerEmail: row.caller_email,
|
|
308
|
+
callerObjectId: row.caller_object_id,
|
|
309
|
+
callerIdentityType: row.caller_identity_type,
|
|
310
|
+
callerAppId: row.caller_app_id,
|
|
311
|
+
callerIpAddress: row.caller_ip_address,
|
|
312
|
+
callerTenantId: row.caller_tenant_id,
|
|
313
|
+
operationName: row.operation_name,
|
|
314
|
+
operationNameValue: row.operation_name_value,
|
|
315
|
+
status: row.status,
|
|
316
|
+
subStatus: row.sub_status,
|
|
317
|
+
category: row.category,
|
|
318
|
+
resourceGroupName: row.resource_group_name,
|
|
319
|
+
resourceId: row.resource_id,
|
|
320
|
+
resourceProviderName: row.resource_provider_name,
|
|
321
|
+
resourceType: row.resource_type,
|
|
322
|
+
authorizationAction: row.authorization_action,
|
|
323
|
+
authorizationScope: row.authorization_scope
|
|
324
|
+
}));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
type AzureSubscriptionRow = {
|
|
328
|
+
subscription_id: string;
|
|
329
|
+
subscription_name: string;
|
|
330
|
+
tenant_id: string;
|
|
331
|
+
state: AzureSubscription["state"];
|
|
332
|
+
tags: string | null;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
type AzureResourceGroupRow = {
|
|
336
|
+
subscription_id: string;
|
|
337
|
+
subscription_name: string;
|
|
338
|
+
resource_group: string;
|
|
339
|
+
location: string;
|
|
340
|
+
tags: string | null;
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
type AzureResourceRow = {
|
|
344
|
+
subscription_id: string;
|
|
345
|
+
subscription_name: string;
|
|
346
|
+
resource_id: string;
|
|
347
|
+
resource_name: string;
|
|
348
|
+
resource_group: string;
|
|
349
|
+
resource_type: string;
|
|
350
|
+
kind: string | null;
|
|
351
|
+
location: string;
|
|
352
|
+
tags: string | null;
|
|
353
|
+
identity_type: string | null;
|
|
354
|
+
identity_principal_id: string | null;
|
|
355
|
+
identity_tenant_id: string | null;
|
|
356
|
+
user_assigned_identity_resource_ids: string;
|
|
357
|
+
user_assigned_identities: string | null;
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
type AzureUserAssignedManagedIdentityRow = {
|
|
361
|
+
subscription_id: string;
|
|
362
|
+
subscription_name: string;
|
|
363
|
+
resource_id: string;
|
|
364
|
+
name: string;
|
|
365
|
+
resource_group: string;
|
|
366
|
+
location: string;
|
|
367
|
+
client_id: string;
|
|
368
|
+
principal_id: string;
|
|
369
|
+
tenant_id: string;
|
|
370
|
+
tags: string | null;
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
type AzureRoleAssignmentRow = {
|
|
374
|
+
subscription_id: string;
|
|
375
|
+
subscription_name: string;
|
|
376
|
+
role_assignment_id: string | null;
|
|
377
|
+
scope: string;
|
|
378
|
+
scope_type: AzureRoleAssignment["scopeType"];
|
|
379
|
+
scope_subscription_id: string | null;
|
|
380
|
+
scope_resource_group: string | null;
|
|
381
|
+
scope_resource_provider: string | null;
|
|
382
|
+
scope_resource_type: string | null;
|
|
383
|
+
scope_resource_name: string | null;
|
|
384
|
+
scope_management_group: string | null;
|
|
385
|
+
principal_id: string;
|
|
386
|
+
principal_type: string | null;
|
|
387
|
+
principal_display_name: string | null;
|
|
388
|
+
sign_in_name: string | null;
|
|
389
|
+
role_definition_id: string | null;
|
|
390
|
+
role_definition_name: string | null;
|
|
391
|
+
can_delegate: boolean | null;
|
|
392
|
+
condition: string | null;
|
|
393
|
+
condition_version: string | null;
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
type AzureActivityLogRow = {
|
|
397
|
+
subscription_id: string;
|
|
398
|
+
subscription_name: string;
|
|
399
|
+
event_timestamp: string;
|
|
400
|
+
submission_timestamp: string | null;
|
|
401
|
+
caller: string | null;
|
|
402
|
+
caller_user_principal_name: string | null;
|
|
403
|
+
caller_name: string | null;
|
|
404
|
+
caller_email: string | null;
|
|
405
|
+
caller_object_id: string | null;
|
|
406
|
+
caller_identity_type: string | null;
|
|
407
|
+
caller_app_id: string | null;
|
|
408
|
+
caller_ip_address: string | null;
|
|
409
|
+
caller_tenant_id: string | null;
|
|
410
|
+
operation_name: string | null;
|
|
411
|
+
operation_name_value: string | null;
|
|
412
|
+
status: string | null;
|
|
413
|
+
sub_status: string | null;
|
|
414
|
+
category: string | null;
|
|
415
|
+
resource_group_name: string | null;
|
|
416
|
+
resource_id: string | null;
|
|
417
|
+
resource_provider_name: string | null;
|
|
418
|
+
resource_type: string | null;
|
|
419
|
+
authorization_action: string | null;
|
|
420
|
+
authorization_scope: string | null;
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
async function readRows<Row extends Record<string, unknown>>(
|
|
424
|
+
connection: DuckDBConnection,
|
|
425
|
+
sql: string
|
|
426
|
+
): Promise<Row[]> {
|
|
427
|
+
const reader = await connection.runAndReadAll(sql);
|
|
428
|
+
return reader.getRowObjectsJson() as Row[];
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function parseJsonArray<T>(value: string | null | undefined): T[] {
|
|
432
|
+
return value ? JSON.parse(value) : [];
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function parseJsonObject<T extends Record<string, string>>(value: string | null | undefined): T | null {
|
|
436
|
+
return value ? JSON.parse(value) : null;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function parseJsonValue(value: string | null | undefined): unknown {
|
|
440
|
+
return value ? JSON.parse(value) : null;
|
|
441
|
+
}
|