@oneuptime/common 13.0.3 → 13.0.5
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/Models/DatabaseModels/Index.ts +4 -0
- package/Models/DatabaseModels/RumApplication.ts +1 -1
- package/Models/DatabaseModels/SecurityEventConnection.ts +457 -0
- package/Models/DatabaseModels/SecurityEventConnectionRun.ts +263 -0
- package/Models/DatabaseModels/TelemetryIngestionKey.ts +2 -2
- package/Server/API/AIInvestigationAPI.ts +484 -24
- package/Server/API/GoogleSecOpsConnectionAPI.ts +249 -8
- package/Server/API/SecurityEventConnectionAPI.ts +250 -0
- package/Server/API/TelemetryAPI.ts +402 -265
- package/Server/Infrastructure/Postgres/SchemaMigrations/1792700000000-AddSecurityEventConnections.ts +99 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Middleware/TelemetryIngest.ts +101 -6
- package/Server/Services/ExceptionAggregationService.ts +7 -16
- package/Server/Services/GoogleSecOpsConnectionService.ts +15 -0
- package/Server/Services/Index.ts +4 -0
- package/Server/Services/InventoryItemService.ts +28 -0
- package/Server/Services/LogAggregationService.ts +17 -23
- package/Server/Services/MetricAggregationService.ts +4 -14
- package/Server/Services/SecurityEventConnectionRunService.ts +10 -0
- package/Server/Services/SecurityEventConnectionService.ts +538 -0
- package/Server/Services/TraceAggregationService.ts +77 -28
- package/Server/Types/AnalyticsDatabase/ModelPermission.ts +9 -0
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +13 -1
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +15 -1
- package/Server/Utils/AI/SRE/InvestigationEvidence.ts +658 -0
- package/Server/Utils/AI/SRE/InvestigationReferences.ts +324 -0
- package/Server/Utils/AI/Toolbox/Index.ts +38 -0
- package/Server/Utils/AI/Toolbox/TimelineTools.ts +9 -2
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +80 -0
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +5 -3
- package/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubClient.ts +1108 -0
- package/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubConnector.ts +732 -0
- package/Server/Utils/SecurityEvent/Connectors/ConnectorPlatformHealth.ts +495 -0
- package/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconClient.ts +689 -0
- package/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconConnector.ts +767 -0
- package/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityClient.ts +571 -0
- package/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityConnector.ts +827 -0
- package/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrClient.ts +610 -0
- package/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrConnector.ts +549 -0
- package/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelClient.ts +830 -0
- package/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelConnector.ts +684 -0
- package/Server/Utils/SecurityEvent/Connectors/Okta/OktaClient.ts +487 -0
- package/Server/Utils/SecurityEvent/Connectors/Okta/OktaConnector.ts +778 -0
- package/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionPoller.ts +1002 -0
- package/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionRunExecutor.ts +556 -0
- package/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionTester.ts +275 -0
- package/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectorRegistry.ts +53 -0
- package/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkClient.ts +709 -0
- package/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkConnector.ts +596 -0
- package/Server/Utils/SecurityEvent/Connectors/Types.ts +191 -0
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.ts +353 -65
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsConnectionTester.ts +777 -0
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +747 -148
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.ts +48 -2
- package/Server/Utils/SecurityEvent/SecurityEventDedupe.ts +81 -0
- package/Server/Utils/SessionReplay/SessionReplayReadService.ts +741 -40
- package/Server/Utils/StartServer.ts +10 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +8 -0
- package/Server/Utils/Telemetry/KubernetesResourceAttributes.ts +108 -0
- package/Server/Utils/Telemetry/ResourceEntityFilter.ts +174 -93
- package/Server/Utils/Telemetry/ResourceFacetPlanner.ts +251 -0
- package/Server/Utils/Telemetry/ResourceFacetResolver.ts +248 -413
- package/Server/Utils/Telemetry/ServiceDependencyDiscovery.ts +668 -0
- package/Server/Utils/Telemetry/TraceContextPropagation.ts +348 -0
- package/Server/Utils/Telemetry.ts +18 -0
- package/Tests/App/Dashboard/AIInvestigationHeaderStatus.test.tsx +413 -0
- package/Tests/App/Dashboard/AIRootCauseFeedItem.test.tsx +285 -0
- package/Tests/App/Dashboard/AffectedResourcesDisplay.test.tsx +242 -0
- package/Tests/App/Dashboard/AlertEpisodeViewFields.test.tsx +601 -15
- package/Tests/App/Dashboard/ChangeAlertStateAIHeader.test.tsx +835 -0
- package/Tests/App/Dashboard/CloudResourceTelemetryScope.test.ts +41 -0
- package/Tests/App/Dashboard/ConnectionTestModal.test.tsx +604 -0
- package/Tests/App/Dashboard/ConnectorTestReportView.test.tsx +738 -0
- package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +153 -6
- package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +165 -0
- package/Tests/App/Dashboard/EpisodeChangeState.test.tsx +1069 -0
- package/Tests/App/Dashboard/EpisodeMembersCard.test.tsx +772 -0
- package/Tests/App/Dashboard/EventOverviewPages.test.tsx +2354 -0
- package/Tests/App/Dashboard/EventOverviewSkeleton.test.tsx +210 -0
- package/Tests/App/Dashboard/EventStatBar.test.tsx +392 -0
- package/Tests/App/Dashboard/EventStatusPanel.test.tsx +247 -0
- package/Tests/App/Dashboard/ExceptionAIAssistance.test.tsx +606 -0
- package/Tests/App/Dashboard/ExceptionBreadcrumbTimeline.test.tsx +273 -0
- package/Tests/App/Dashboard/ExceptionDetailSideMenu.test.tsx +176 -0
- package/Tests/App/Dashboard/ExceptionOverviewCards.test.tsx +366 -0
- package/Tests/App/Dashboard/ExceptionSignalPages.test.tsx +371 -0
- package/Tests/App/Dashboard/ExceptionStackFrameViewer.test.tsx +440 -0
- package/Tests/App/Dashboard/ExceptionSummary.test.tsx +381 -0
- package/Tests/App/Dashboard/ExceptionTriage.test.tsx +306 -0
- package/Tests/App/Dashboard/ExceptionsResourceFacets.test.tsx +604 -0
- package/Tests/App/Dashboard/ExceptionsViewerEntityChips.test.tsx +706 -0
- package/Tests/App/Dashboard/FeedOrdering.test.ts +32 -0
- package/Tests/App/Dashboard/GoogleSecOpsConnectionForm.test.tsx +838 -0
- package/Tests/App/Dashboard/GoogleSecOpsConnectionsErrors.test.tsx +18 -0
- package/Tests/App/Dashboard/GoogleSecOpsDiagnostics.test.tsx +297 -20
- package/Tests/App/Dashboard/IncidentEpisodeViewFields.test.tsx +618 -13
- package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +254 -207
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +303 -135
- package/Tests/App/Dashboard/InvestigationEvidenceList.test.tsx +1251 -0
- package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +303 -0
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +876 -8
- package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +35 -0
- package/Tests/App/Dashboard/InvestigationReferenceLink.test.tsx +177 -0
- package/Tests/App/Dashboard/InvestigationReportView.test.tsx +1295 -0
- package/Tests/App/Dashboard/KubernetesImageReferenceView.test.tsx +114 -0
- package/Tests/App/Dashboard/KubernetesLogsTabChips.test.tsx +285 -0
- package/Tests/App/Dashboard/LogsEntityChipNames.test.tsx +316 -0
- package/Tests/App/Dashboard/LogsResourceFacetChips.test.tsx +361 -0
- package/Tests/App/Dashboard/ProfileTableEntityNames.test.tsx +432 -0
- package/Tests/App/Dashboard/ProfileViewEntityName.test.tsx +250 -0
- package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +10 -0
- package/Tests/App/Dashboard/ResourceLogsTabsBareLayout.test.tsx +700 -0
- package/Tests/App/Dashboard/ResourcePageEntityNameProps.test.tsx +803 -0
- package/Tests/App/Dashboard/RumSessionReplaySideMenu.test.tsx +11 -1
- package/Tests/App/Dashboard/ScheduledMaintenanceChangeState.test.tsx +1026 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceFeedRefresh.test.tsx +436 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +1195 -0
- package/Tests/App/Dashboard/SecurityEventConnectionFormModal.test.tsx +1022 -0
- package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +905 -0
- package/Tests/App/Dashboard/SecurityEventsSideMenu.test.tsx +247 -0
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +375 -274
- package/Tests/App/Dashboard/SessionReplayAuditTable.test.tsx +410 -0
- package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +774 -0
- package/Tests/App/Dashboard/TopErrorsPanelResourceNames.test.tsx +316 -0
- package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +30 -0
- package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +88 -24
- package/Tests/App/Dashboard/TraceExplorer.test.tsx +1590 -0
- package/Tests/App/Dashboard/TraceWaterfall.test.tsx +488 -0
- package/Tests/App/Dashboard/TracesAnalyticsSeriesLabels.test.tsx +367 -0
- package/Tests/App/Dashboard/TracesEntityNames.test.tsx +585 -0
- package/Tests/App/Dashboard/TracesResourceFacets.test.tsx +613 -0
- package/Tests/App/Dashboard/UseServiceNames.test.tsx +359 -0
- package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +7 -0
- package/Tests/Models/InventoryItemNaming.test.ts +132 -16
- package/Tests/Models/SecurityDomainAccessControl.test.ts +61 -17
- package/Tests/Server/API/AIInvestigationAPI.test.ts +611 -2
- package/Tests/Server/API/AIInvestigationEvidenceAPI.test.ts +1098 -0
- package/Tests/Server/API/GoogleSecOpsConnectionAPI.test.ts +409 -0
- package/Tests/Server/API/SecurityEventConnectionAPI.test.ts +856 -0
- package/Tests/Server/API/SessionReplayAPI.test.ts +528 -1
- package/Tests/Server/API/TelemetryFacetsAPI.test.ts +810 -0
- package/Tests/Server/Infrastructure/E2EClickHouseFixtureAccess.test.ts +240 -0
- package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +229 -0
- package/Tests/Server/Services/InventoryItemDisplayName.test.ts +66 -0
- package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +28 -1
- package/Tests/Server/Services/SecurityEventConnectionService.test.ts +1233 -0
- package/Tests/Server/Services/TelemetryIngestionKeyValidation.test.ts +53 -0
- package/Tests/Server/Services/TelemetryResourceFacetCounting.test.ts +218 -0
- package/Tests/Server/Services/TelemetryResourceFacetFilters.test.ts +155 -1
- package/Tests/Server/Services/TraceAggregationExceptionScope.test.ts +142 -0
- package/Tests/Server/Services/TraceAnalyticsResourceDisplayNames.test.ts +85 -24
- package/Tests/Server/Types/Database/Permissions/SecurityRoleAccess.test.ts +33 -0
- package/Tests/Server/Utils/AI/InvestigationEvidence.test.ts +1395 -0
- package/Tests/Server/Utils/AI/InvestigationReferences.test.ts +616 -0
- package/Tests/Server/Utils/AI/TimelineTools.test.ts +119 -0
- package/Tests/Server/Utils/AI/Toolbox/AIToolbox.test.ts +151 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +65 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGeneratorExceptionScope.test.ts +224 -0
- package/Tests/Server/Utils/LoggerCore.test.ts +597 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPerSeriesFanout.test.ts +55 -2
- package/Tests/Server/Utils/Monitor/MonitorTemplateUtil.test.ts +1109 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubClient.test.ts +1240 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubConnector.test.ts +1853 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/ConnectorPlatformHealth.test.ts +1129 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconClient.test.ts +1066 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconConnector.test.ts +1568 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityClient.test.ts +930 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityConnector.test.ts +1409 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrClient.test.ts +763 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrConnector.test.ts +1296 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelClient.test.ts +1102 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelConnector.test.ts +1174 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/Okta/OktaClient.test.ts +1032 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/Okta/OktaConnector.test.ts +1558 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionPoller.test.ts +3041 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionRunExecutor.test.ts +1168 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionTester.test.ts +906 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkClient.test.ts +1068 -0
- package/Tests/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkConnector.test.ts +1004 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsConnectionTester.test.ts +784 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.test.ts +113 -1
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClient.test.ts +5 -1
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsDiagnostics.test.ts +93 -13
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsHttpIntegration.test.ts +23 -1
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +25 -3
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +22 -8
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerHardening.test.ts +82 -16
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsSearchDetections.test.ts +730 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsThreePassPoller.test.ts +1577 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayOriginAllowlist.test.ts +41 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayReadServiceQueries.test.ts +1453 -18
- package/Tests/Server/Utils/Telemetry/EntityRegistry.test.ts +711 -0
- package/Tests/Server/Utils/Telemetry/KubernetesResourceAttributes.test.ts +162 -0
- package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +460 -13
- package/Tests/Server/Utils/Telemetry/ResourceFacetPlanner.test.ts +678 -0
- package/Tests/Server/Utils/Telemetry/ResourceFacetResolver.test.ts +868 -0
- package/Tests/Server/Utils/Telemetry/ServiceDependencyDiscovery.test.ts +525 -0
- package/Tests/Server/Utils/Telemetry/TraceContextPropagation.test.ts +420 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilCorePaths.test.ts +1014 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveRotation.test.ts +9 -6
- package/Tests/Types/Rules/RuleCriteriaFieldRegistry.test.ts +276 -0
- package/Tests/Types/Rum/SessionReplayApiContracts.test.ts +73 -0
- package/Tests/Types/Rum/SessionReplayTransportContracts.test.ts +95 -0
- package/Tests/Types/SecurityEvent/Connectors/ConnectorDiagnostics.test.ts +82 -0
- package/Tests/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.test.ts +314 -0
- package/Tests/Types/Telemetry/ExceptionSpanScope.test.ts +89 -0
- package/Tests/Types/Telemetry/LlmConventions.test.ts +495 -0
- package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +96 -0
- package/Tests/Types/Telemetry/ResourceFacetCatalog.test.ts +304 -0
- package/Tests/UI/Components/ActiveFilterChipsLockedFilters.test.tsx +357 -0
- package/Tests/UI/Components/Card.test.tsx +312 -1
- package/Tests/UI/Components/CardModelDetailHeaderLayout.test.tsx +253 -0
- package/Tests/UI/Components/CardModelDetailRefresher.test.tsx +342 -0
- package/Tests/UI/Components/Checkbox.test.tsx +79 -0
- package/Tests/UI/Components/CopyTextButton.test.tsx +142 -0
- package/Tests/UI/Components/DetailCompactStyle.test.tsx +621 -0
- package/Tests/UI/Components/ErrorBoundary.test.tsx +60 -0
- package/Tests/UI/Components/FacetSections.test.tsx +581 -0
- package/Tests/UI/Components/FacetSidebarControls.test.tsx +812 -0
- package/Tests/UI/Components/FacetVisibility.test.ts +429 -0
- package/Tests/UI/Components/LockedFilterActions.test.tsx +396 -0
- package/Tests/UI/Components/LockedFilterChip.test.tsx +651 -0
- package/Tests/UI/Components/LogsEntityNames.test.ts +1769 -0
- package/Tests/UI/Components/LogsEntityNamesComponents.test.tsx +696 -0
- package/Tests/UI/Components/LogsFacetSidebarHiddenFacets.test.tsx +600 -0
- package/Tests/UI/Components/LogsViewerEntityNames.test.tsx +713 -0
- package/Tests/UI/Components/MarkdownInlineReferences.test.tsx +2079 -0
- package/Tests/UI/Components/MemberRoleAssignment.test.tsx +446 -0
- package/Tests/UI/Components/ModelDetailRefetch.test.tsx +397 -0
- package/Tests/UI/Components/ModelListRefetch.test.tsx +233 -0
- package/Tests/UI/Components/ModelPageModelChange.test.tsx +409 -0
- package/Tests/UI/Components/PaginationIntegration.test.tsx +25 -0
- package/Tests/UI/Components/ResourceFacetConfigs.test.ts +172 -0
- package/Tests/UI/Components/SavedViewsSidebarIntegration.test.tsx +32 -14
- package/Tests/UI/Components/TableSortAndMobileColumns.test.tsx +44 -1
- package/Tests/UI/Components/TelemetryActiveFilterChipsLockedFilters.test.tsx +304 -0
- package/Tests/UI/Components/TelemetryFacetSidebarHiddenFacets.test.tsx +718 -0
- package/Tests/UI/Monitor/EvaluationLogList.test.tsx +303 -0
- package/Tests/UI/Monitor/MonitorSummarySnapshotRender.test.tsx +55 -1
- package/Tests/UI/Rum/FidelityNotices.test.ts +22 -0
- package/Tests/UI/Rum/RecordingHealthCard.test.tsx +120 -136
- package/Tests/UI/Rum/RecordingHealthDashboard.test.tsx +988 -0
- package/Tests/UI/Rum/ReplayCard.test.tsx +32 -0
- package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +709 -12
- package/Tests/UI/Rum/ReplayHeader.test.tsx +50 -1
- package/Tests/UI/Rum/ReplayPinControl.test.tsx +67 -10
- package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +41 -12
- package/Tests/UI/Rum/ReplayStage.test.tsx +663 -1
- package/Tests/UI/Rum/ReplayStageOverlays.test.tsx +173 -0
- package/Tests/UI/Rum/ReplayUi.test.tsx +189 -2
- package/Tests/UI/Rum/ReplayUserSessionsMenu.test.tsx +91 -0
- package/Tests/UI/Rum/SessionReplayEmptyState.test.tsx +1 -1
- package/Tests/UI/Rum/SessionReplaySettingsPolicyLoading.test.tsx +585 -0
- package/Tests/UI/Rum/SessionReplayTable.test.tsx +844 -51
- package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +29 -1
- package/Tests/UI/Telemetry/TelemetryDetailPanel.test.tsx +288 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +31 -1
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +6 -0
- package/Tests/UI/Utils/Clipboard.test.ts +154 -0
- package/Tests/UI/Utils/Navigation.test.ts +24 -0
- package/Tests/UI/Utils/Telemetry/TelemetryEntityNames.test.ts +2205 -0
- package/Tests/UI/Utils/Telemetry/UseTelemetryEntityNames.test.tsx +844 -0
- package/Tests/Utils/AI/InvestigationReport.test.ts +2430 -0
- package/Tests/Utils/APIOutgoingRequestTracer.test.ts +137 -0
- package/Tests/Utils/Rum/SessionReplayHealth.test.ts +884 -0
- package/Tests/Utils/Rum/SessionReplayHealthDiagnosis.test.ts +40 -0
- package/Tests/Utils/Rum/SessionReplayRecordingEnded.test.ts +619 -0
- package/Tests/Utils/SecurityEvent/Connectors/AwsSecurityHubNormalizer.test.ts +631 -0
- package/Tests/Utils/SecurityEvent/Connectors/CrowdStrikeFalconNormalizer.test.ts +479 -0
- package/Tests/Utils/SecurityEvent/Connectors/ElasticSecurityNormalizer.test.ts +507 -0
- package/Tests/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdrNormalizer.test.ts +686 -0
- package/Tests/Utils/SecurityEvent/Connectors/MicrosoftSentinelNormalizer.test.ts +474 -0
- package/Tests/Utils/SecurityEvent/Connectors/OktaNormalizer.test.ts +736 -0
- package/Tests/Utils/SecurityEvent/Connectors/SplunkNormalizer.test.ts +407 -0
- package/Tests/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.test.ts +129 -0
- package/Tests/Utils/Telemetry/CrossSignalScope.test.ts +57 -1
- package/Tests/Utils/Telemetry/EntityRelationship.test.ts +36 -0
- package/Tests/Utils/Telemetry/LockedFilterSearch.test.ts +600 -0
- package/Tests/Utils/Telemetry/NetworkHost.test.ts +48 -0
- package/Tests/Utils/Telemetry/OriginAllowList.test.ts +154 -0
- package/Tests/Utils/Traces/CriticalPath.test.ts +25 -0
- package/Types/AI/AIChatTypes.ts +8 -0
- package/Types/AI/InvestigationEvidence.ts +96 -0
- package/Types/Monitor/MonitorEvaluationSummary.ts +5 -0
- package/Types/OnCallDutyPolicy/Layer.ts +12 -0
- package/Types/Permission.ts +2 -2
- package/Types/Rules/RuleCriteriaFieldRegistry.ts +12 -0
- package/Types/Rum/SessionReplay.ts +109 -0
- package/Types/Rum/SessionReplayApi.ts +31 -0
- package/Types/SecurityEvent/Connectors/ConnectorDiagnostics.ts +111 -0
- package/Types/SecurityEvent/Connectors/SecurityEventConnectionDiagnostics.ts +87 -0
- package/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.ts +492 -0
- package/Types/SecurityEvent/Connectors/SecurityEventConnectorProvider.ts +41 -0
- package/Types/SecurityEvent/GoogleSecOpsDiagnostics.ts +56 -1
- package/Types/SecurityEvent/GoogleSecOpsRegion.ts +34 -0
- package/Types/Telemetry/EntityType.ts +14 -0
- package/Types/Telemetry/ExceptionSpanScope.ts +83 -0
- package/Types/Telemetry/LockedFilterDetail.ts +46 -0
- package/Types/Telemetry/ResourceEntityFacet.ts +17 -10
- package/Types/Telemetry/ResourceFacetCatalog.ts +171 -0
- package/UI/Components/Card/Card.tsx +118 -83
- package/UI/Components/Checkbox/Checkbox.tsx +8 -5
- package/UI/Components/CopyTextButton/CopyTextButton.tsx +7 -1
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +4 -1
- package/UI/Components/Detail/Detail.tsx +137 -22
- package/UI/Components/ErrorBoundary.tsx +38 -0
- package/UI/Components/LogsViewer/LogsEntityNames.ts +912 -0
- package/UI/Components/LogsViewer/LogsViewer.tsx +156 -77
- package/UI/Components/LogsViewer/components/ActiveFilterChips.tsx +28 -11
- package/UI/Components/LogsViewer/components/FacetSection.tsx +66 -79
- package/UI/Components/LogsViewer/components/FacetValueRow.tsx +17 -2
- package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +45 -5
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +100 -19
- package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +156 -39
- package/UI/Components/LogsViewer/components/LogsTable.tsx +30 -8
- package/UI/Components/LogsViewer/types.ts +6 -0
- package/UI/Components/Markdown.tsx/InlineReferences.tsx +628 -0
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +385 -312
- package/UI/Components/MemberRoleAssignment/MemberRoleAssignment.tsx +50 -22
- package/UI/Components/ModelDetail/CardModelDetail.tsx +15 -0
- package/UI/Components/ModelDetail/ModelDetail.tsx +64 -19
- package/UI/Components/ModelList/ModelList.tsx +33 -8
- package/UI/Components/Page/ModelPage.tsx +119 -54
- package/UI/Components/Table/Table.tsx +8 -0
- package/UI/Components/Table/TableRow.tsx +13 -5
- package/UI/Components/TelemetryViewer/FacetVisibility.ts +229 -0
- package/UI/Components/TelemetryViewer/ResourceFacetConfigs.ts +46 -0
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +11 -0
- package/UI/Components/TelemetryViewer/components/FacetSearchInput.tsx +79 -0
- package/UI/Components/TelemetryViewer/components/FacetSectionHeader.tsx +69 -0
- package/UI/Components/TelemetryViewer/components/FacetShowMoreButton.tsx +36 -0
- package/UI/Components/TelemetryViewer/components/HiddenFacetsFooter.tsx +88 -0
- package/UI/Components/TelemetryViewer/components/LockedFilterActions.tsx +205 -0
- package/UI/Components/TelemetryViewer/components/LockedFilterChip.tsx +496 -0
- package/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.tsx +30 -10
- package/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.tsx +203 -10
- package/UI/Components/TelemetryViewer/components/TelemetryFacetSection.tsx +60 -73
- package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +94 -7
- package/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.tsx +17 -2
- package/UI/Components/TelemetryViewer/types.ts +19 -0
- package/UI/Components/TelemetryViewer/useFacetSearchExemptions.ts +158 -0
- package/UI/Components/TelemetryViewer/useFacetSectionSearch.ts +102 -0
- package/UI/Utils/Clipboard.ts +68 -2
- package/UI/Utils/Navigation.ts +17 -2
- package/UI/Utils/Telemetry/TelemetryEntityNames.ts +620 -0
- package/UI/Utils/Telemetry/UseTelemetryEntityNames.ts +151 -0
- package/Utils/AI/InvestigationReport.ts +1789 -0
- package/Utils/API.ts +90 -1
- package/Utils/Rum/SessionReplayHealth.ts +11 -0
- package/Utils/Rum/SessionReplayRecordingEnded.ts +134 -0
- package/Utils/SecurityEvent/Connectors/AwsSecurityHubNormalizer.ts +427 -0
- package/Utils/SecurityEvent/Connectors/CrowdStrikeFalconNormalizer.ts +197 -0
- package/Utils/SecurityEvent/Connectors/ElasticSecurityNormalizer.ts +464 -0
- package/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdrNormalizer.ts +567 -0
- package/Utils/SecurityEvent/Connectors/MicrosoftSentinelNormalizer.ts +299 -0
- package/Utils/SecurityEvent/Connectors/OktaNormalizer.ts +467 -0
- package/Utils/SecurityEvent/Connectors/SplunkNormalizer.ts +439 -0
- package/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.ts +124 -12
- package/Utils/Telemetry/CrossSignalScope.ts +22 -8
- package/Utils/Telemetry/EntityRelationship.ts +8 -0
- package/Utils/Telemetry/LockedFilterSearch.ts +412 -0
- package/Utils/Telemetry/NetworkHost.ts +43 -0
- package/Utils/Telemetry/OriginAllowList.ts +139 -4
- package/Utils/Traces/CriticalPath.ts +10 -4
- package/build/dist/Models/DatabaseModels/Index.js +4 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
- package/build/dist/Models/DatabaseModels/SecurityEventConnection.js +490 -0
- package/build/dist/Models/DatabaseModels/SecurityEventConnection.js.map +1 -0
- package/build/dist/Models/DatabaseModels/SecurityEventConnectionRun.js +287 -0
- package/build/dist/Models/DatabaseModels/SecurityEventConnectionRun.js.map +1 -0
- package/build/dist/Models/DatabaseModels/TelemetryIngestionKey.js +2 -2
- package/build/dist/Models/DatabaseModels/TelemetryIngestionKey.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +336 -27
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/GoogleSecOpsConnectionAPI.js +182 -7
- package/build/dist/Server/API/GoogleSecOpsConnectionAPI.js.map +1 -1
- package/build/dist/Server/API/SecurityEventConnectionAPI.js +171 -0
- package/build/dist/Server/API/SecurityEventConnectionAPI.js.map +1 -0
- package/build/dist/Server/API/TelemetryAPI.js +248 -170
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792700000000-AddSecurityEventConnections.js +40 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792700000000-AddSecurityEventConnections.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +57 -4
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Services/ExceptionAggregationService.js +7 -16
- package/build/dist/Server/Services/ExceptionAggregationService.js.map +1 -1
- package/build/dist/Server/Services/GoogleSecOpsConnectionService.js +8 -0
- package/build/dist/Server/Services/GoogleSecOpsConnectionService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +4 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +22 -0
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +8 -17
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MetricAggregationService.js +4 -14
- package/build/dist/Server/Services/MetricAggregationService.js.map +1 -1
- package/build/dist/Server/Services/SecurityEventConnectionRunService.js +9 -0
- package/build/dist/Server/Services/SecurityEventConnectionRunService.js.map +1 -0
- package/build/dist/Server/Services/SecurityEventConnectionService.js +356 -0
- package/build/dist/Server/Services/SecurityEventConnectionService.js.map +1 -0
- package/build/dist/Server/Services/TraceAggregationService.js +51 -25
- package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +9 -0
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +3 -0
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +14 -7
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationEvidence.js +468 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationEvidence.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationReferences.js +230 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationReferences.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js +32 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js +6 -2
- package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +59 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +5 -3
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubClient.js +752 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubClient.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubConnector.js +493 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubConnector.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/ConnectorPlatformHealth.js +352 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/ConnectorPlatformHealth.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconClient.js +405 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconClient.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconConnector.js +466 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconConnector.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityClient.js +344 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityClient.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityConnector.js +511 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityConnector.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrClient.js +406 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrClient.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrConnector.js +336 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrConnector.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelClient.js +474 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelClient.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelConnector.js +417 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelConnector.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/Okta/OktaClient.js +314 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/Okta/OktaClient.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/Okta/OktaConnector.js +494 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/Okta/OktaConnector.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionPoller.js +719 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionPoller.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionRunExecutor.js +404 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionRunExecutor.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionTester.js +209 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionTester.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectorRegistry.js +40 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectorRegistry.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkClient.js +494 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkClient.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkConnector.js +379 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkConnector.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/Types.js +27 -0
- package/build/dist/Server/Utils/SecurityEvent/Connectors/Types.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js +234 -61
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsConnectionTester.js +537 -0
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsConnectionTester.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +572 -115
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.js +35 -1
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/SecurityEventDedupe.js +52 -0
- package/build/dist/Server/Utils/SecurityEvent/SecurityEventDedupe.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +521 -32
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +10 -0
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +8 -0
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/KubernetesResourceAttributes.js +80 -0
- package/build/dist/Server/Utils/Telemetry/KubernetesResourceAttributes.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +122 -67
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ResourceFacetPlanner.js +146 -0
- package/build/dist/Server/Utils/Telemetry/ResourceFacetPlanner.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js +174 -248
- package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js +492 -0
- package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TraceContextPropagation.js +259 -0
- package/build/dist/Server/Utils/Telemetry/TraceContextPropagation.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry.js +11 -5
- package/build/dist/Server/Utils/Telemetry.js.map +1 -1
- package/build/dist/Types/AI/InvestigationEvidence.js +2 -0
- package/build/dist/Types/AI/InvestigationEvidence.js.map +1 -0
- package/build/dist/Types/OnCallDutyPolicy/Layer.js +11 -0
- package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
- package/build/dist/Types/Permission.js +2 -2
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +9 -0
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplay.js +95 -0
- package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplayApi.js.map +1 -1
- package/build/dist/Types/SecurityEvent/Connectors/ConnectorDiagnostics.js +18 -0
- package/build/dist/Types/SecurityEvent/Connectors/ConnectorDiagnostics.js.map +1 -0
- package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectionDiagnostics.js +2 -0
- package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectionDiagnostics.js.map +1 -0
- package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.js +374 -0
- package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.js.map +1 -0
- package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorProvider.js +35 -0
- package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorProvider.js.map +1 -0
- package/build/dist/Types/SecurityEvent/GoogleSecOpsRegion.js +35 -0
- package/build/dist/Types/SecurityEvent/GoogleSecOpsRegion.js.map +1 -0
- package/build/dist/Types/Telemetry/EntityType.js +14 -0
- package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
- package/build/dist/Types/Telemetry/ExceptionSpanScope.js +59 -0
- package/build/dist/Types/Telemetry/ExceptionSpanScope.js.map +1 -0
- package/build/dist/Types/Telemetry/LockedFilterDetail.js +16 -0
- package/build/dist/Types/Telemetry/LockedFilterDetail.js.map +1 -0
- package/build/dist/Types/Telemetry/ResourceEntityFacet.js +16 -10
- package/build/dist/Types/Telemetry/ResourceEntityFacet.js.map +1 -1
- package/build/dist/Types/Telemetry/ResourceFacetCatalog.js +119 -0
- package/build/dist/Types/Telemetry/ResourceFacetCatalog.js.map +1 -0
- package/build/dist/UI/Components/Card/Card.js +34 -17
- package/build/dist/UI/Components/Card/Card.js.map +1 -1
- package/build/dist/UI/Components/Checkbox/Checkbox.js +6 -4
- package/build/dist/UI/Components/Checkbox/Checkbox.js.map +1 -1
- package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js +5 -1
- package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/Detail/Detail.js +63 -13
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- package/build/dist/UI/Components/ErrorBoundary.js +34 -0
- package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsEntityNames.js +535 -0
- package/build/dist/UI/Components/LogsViewer/LogsEntityNames.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +117 -57
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js +5 -7
- package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/FacetSection.js +31 -44
- package/build/dist/UI/Components/LogsViewer/components/FacetSection.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js +17 -3
- package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +19 -3
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +64 -18
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +100 -41
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsTable.js +13 -5
- package/build/dist/UI/Components/LogsViewer/components/LogsTable.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/types.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/InlineReferences.js +403 -0
- package/build/dist/UI/Components/Markdown.tsx/InlineReferences.js.map +1 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +197 -179
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/MemberRoleAssignment/MemberRoleAssignment.js +30 -21
- package/build/dist/UI/Components/MemberRoleAssignment/MemberRoleAssignment.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +12 -0
- package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
- package/build/dist/UI/Components/ModelDetail/ModelDetail.js +46 -12
- package/build/dist/UI/Components/ModelDetail/ModelDetail.js.map +1 -1
- package/build/dist/UI/Components/ModelList/ModelList.js +26 -7
- package/build/dist/UI/Components/ModelList/ModelList.js.map +1 -1
- package/build/dist/UI/Components/Page/ModelPage.js +62 -34
- package/build/dist/UI/Components/Page/ModelPage.js.map +1 -1
- package/build/dist/UI/Components/Table/Table.js +3 -1
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Components/Table/TableRow.js +8 -1
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js +111 -0
- package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/ResourceFacetConfigs.js +22 -0
- package/build/dist/UI/Components/TelemetryViewer/ResourceFacetConfigs.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/FacetSearchInput.js +36 -0
- package/build/dist/UI/Components/TelemetryViewer/components/FacetSearchInput.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/FacetSectionHeader.js +21 -0
- package/build/dist/UI/Components/TelemetryViewer/components/FacetSectionHeader.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/FacetShowMoreButton.js +14 -0
- package/build/dist/UI/Components/TelemetryViewer/components/FacetShowMoreButton.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/HiddenFacetsFooter.js +27 -0
- package/build/dist/UI/Components/TelemetryViewer/components/HiddenFacetsFooter.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterActions.js +88 -0
- package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterActions.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js +228 -0
- package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.js +6 -6
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js +111 -12
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSection.js +27 -40
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSection.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +70 -10
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js +17 -3
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/types.js +0 -4
- package/build/dist/UI/Components/TelemetryViewer/types.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/useFacetSearchExemptions.js +70 -0
- package/build/dist/UI/Components/TelemetryViewer/useFacetSearchExemptions.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/useFacetSectionSearch.js +52 -0
- package/build/dist/UI/Components/TelemetryViewer/useFacetSectionSearch.js.map +1 -0
- package/build/dist/UI/Utils/Clipboard.js +59 -2
- package/build/dist/UI/Utils/Clipboard.js.map +1 -1
- package/build/dist/UI/Utils/Navigation.js +11 -2
- package/build/dist/UI/Utils/Navigation.js.map +1 -1
- package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js +471 -0
- package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js.map +1 -0
- package/build/dist/UI/Utils/Telemetry/UseTelemetryEntityNames.js +113 -0
- package/build/dist/UI/Utils/Telemetry/UseTelemetryEntityNames.js.map +1 -0
- package/build/dist/Utils/AI/InvestigationReport.js +1241 -0
- package/build/dist/Utils/AI/InvestigationReport.js.map +1 -0
- package/build/dist/Utils/API.js +25 -0
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/Rum/SessionReplayHealth.js +10 -0
- package/build/dist/Utils/Rum/SessionReplayHealth.js.map +1 -1
- package/build/dist/Utils/Rum/SessionReplayRecordingEnded.js +54 -0
- package/build/dist/Utils/Rum/SessionReplayRecordingEnded.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/Connectors/AwsSecurityHubNormalizer.js +311 -0
- package/build/dist/Utils/SecurityEvent/Connectors/AwsSecurityHubNormalizer.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/Connectors/CrowdStrikeFalconNormalizer.js +150 -0
- package/build/dist/Utils/SecurityEvent/Connectors/CrowdStrikeFalconNormalizer.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/Connectors/ElasticSecurityNormalizer.js +328 -0
- package/build/dist/Utils/SecurityEvent/Connectors/ElasticSecurityNormalizer.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdrNormalizer.js +405 -0
- package/build/dist/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdrNormalizer.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/Connectors/MicrosoftSentinelNormalizer.js +212 -0
- package/build/dist/Utils/SecurityEvent/Connectors/MicrosoftSentinelNormalizer.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/Connectors/OktaNormalizer.js +351 -0
- package/build/dist/Utils/SecurityEvent/Connectors/OktaNormalizer.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/Connectors/SplunkNormalizer.js +348 -0
- package/build/dist/Utils/SecurityEvent/Connectors/SplunkNormalizer.js.map +1 -0
- package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js +103 -10
- package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js.map +1 -1
- package/build/dist/Utils/Telemetry/CrossSignalScope.js +16 -5
- package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
- package/build/dist/Utils/Telemetry/EntityRelationship.js +7 -0
- package/build/dist/Utils/Telemetry/EntityRelationship.js.map +1 -1
- package/build/dist/Utils/Telemetry/LockedFilterSearch.js +277 -0
- package/build/dist/Utils/Telemetry/LockedFilterSearch.js.map +1 -0
- package/build/dist/Utils/Telemetry/NetworkHost.js +39 -0
- package/build/dist/Utils/Telemetry/NetworkHost.js.map +1 -0
- package/build/dist/Utils/Telemetry/OriginAllowList.js +105 -4
- package/build/dist/Utils/Telemetry/OriginAllowList.js.map +1 -1
- package/build/dist/Utils/Traces/CriticalPath.js +9 -4
- package/build/dist/Utils/Traces/CriticalPath.js.map +1 -1
- package/package.json +1 -1
- package/Tests/UI/Rum/RecordingHealthStrip.test.tsx +0 -600
- package/UI/Components/GanttChart/Bar/BarLabel.tsx +0 -17
- package/UI/Components/GanttChart/Bar/Index.tsx +0 -149
- package/UI/Components/GanttChart/ChartContainer.tsx +0 -50
- package/UI/Components/GanttChart/Index.tsx +0 -90
- package/UI/Components/GanttChart/Row/Index.tsx +0 -22
- package/UI/Components/GanttChart/Row/Row.tsx +0 -231
- package/UI/Components/GanttChart/Row/RowLabel.tsx +0 -30
- package/UI/Components/GanttChart/Rows.tsx +0 -59
- package/UI/Components/GanttChart/Timeline/Index.tsx +0 -46
- package/UI/Components/GanttChart/Timeline/TimelineInterval.tsx +0 -33
- package/UI/Components/GanttChart/Timeline/TimelineIntervalMarks.tsx +0 -43
- package/build/dist/UI/Components/GanttChart/Bar/BarLabel.js +0 -8
- package/build/dist/UI/Components/GanttChart/Bar/BarLabel.js.map +0 -1
- package/build/dist/UI/Components/GanttChart/Bar/Index.js +0 -77
- package/build/dist/UI/Components/GanttChart/Bar/Index.js.map +0 -1
- package/build/dist/UI/Components/GanttChart/ChartContainer.js +0 -33
- package/build/dist/UI/Components/GanttChart/ChartContainer.js.map +0 -1
- package/build/dist/UI/Components/GanttChart/Index.js +0 -39
- package/build/dist/UI/Components/GanttChart/Index.js.map +0 -1
- package/build/dist/UI/Components/GanttChart/Row/Index.js +0 -7
- package/build/dist/UI/Components/GanttChart/Row/Index.js.map +0 -1
- package/build/dist/UI/Components/GanttChart/Row/Row.js +0 -106
- package/build/dist/UI/Components/GanttChart/Row/Row.js.map +0 -1
- package/build/dist/UI/Components/GanttChart/Row/RowLabel.js +0 -10
- package/build/dist/UI/Components/GanttChart/Row/RowLabel.js.map +0 -1
- package/build/dist/UI/Components/GanttChart/Rows.js +0 -19
- package/build/dist/UI/Components/GanttChart/Rows.js.map +0 -1
- package/build/dist/UI/Components/GanttChart/Timeline/Index.js +0 -16
- package/build/dist/UI/Components/GanttChart/Timeline/Index.js.map +0 -1
- package/build/dist/UI/Components/GanttChart/Timeline/TimelineInterval.js +0 -13
- package/build/dist/UI/Components/GanttChart/Timeline/TimelineInterval.js.map +0 -1
- package/build/dist/UI/Components/GanttChart/Timeline/TimelineIntervalMarks.js +0 -13
- package/build/dist/UI/Components/GanttChart/Timeline/TimelineIntervalMarks.js.map +0 -1
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
import { ResponseJSON, ResultSet } from "@clickhouse/client";
|
|
2
|
-
import TableColumnType from "../../../../Types/AnalyticsDatabase/TableColumnType";
|
|
3
|
-
import { SQL, Statement } from "../../AnalyticsDatabase/Statement";
|
|
4
|
-
import {
|
|
5
|
-
getClickhouseClusterName,
|
|
6
|
-
getClickhouseDatabaseName,
|
|
7
|
-
getStorageTableName,
|
|
8
|
-
} from "../../AnalyticsDatabase/ClusterConfig";
|
|
9
|
-
import { getQuerySettings } from "../../AnalyticsDatabase/QuerySettingsHelper";
|
|
10
1
|
import BadDataException from "../../../../Types/Exception/BadDataException";
|
|
11
2
|
import {
|
|
3
|
+
GoogleSecOpsCreationLag,
|
|
12
4
|
GoogleSecOpsDetectionSample,
|
|
5
|
+
GoogleSecOpsDiagnosticCheck,
|
|
13
6
|
GoogleSecOpsRunOptions,
|
|
14
7
|
GoogleSecOpsRunResult,
|
|
15
8
|
} from "../../../../Types/SecurityEvent/GoogleSecOpsDiagnostics";
|
|
@@ -19,7 +12,10 @@ import {
|
|
|
19
12
|
readString,
|
|
20
13
|
readValue,
|
|
21
14
|
} from "../../../../Utils/SecurityEvent/NormalizerHelpers";
|
|
22
|
-
import Semaphore, {
|
|
15
|
+
import Semaphore, {
|
|
16
|
+
SemaphoreLockTimeoutError,
|
|
17
|
+
SemaphoreMutex,
|
|
18
|
+
} from "../../../Infrastructure/Semaphore";
|
|
23
19
|
import GoogleSecOpsConnection from "../../../../Models/DatabaseModels/GoogleSecOpsConnection";
|
|
24
20
|
import LIMIT_MAX from "../../../../Types/Database/LimitMax";
|
|
25
21
|
import OneUptimeDate from "../../../../Types/Date";
|
|
@@ -37,20 +33,66 @@ import logger from "../../Logger";
|
|
|
37
33
|
import { redactLogString } from "../../LogRedaction";
|
|
38
34
|
import CaptureSpan from "../../Telemetry/CaptureSpan";
|
|
39
35
|
import ConnectorErrorMessage from "../ConnectorErrorMessage";
|
|
36
|
+
import SecurityEventDedupe from "../SecurityEventDedupe";
|
|
40
37
|
import { buildSecurityEventDbRow } from "../SecurityEventRow";
|
|
41
38
|
import ThreatIntelEnricher from "../ThreatIntel/ThreatIntelEnricher";
|
|
42
|
-
import GoogleSecOpsClient, {
|
|
39
|
+
import GoogleSecOpsClient, {
|
|
40
|
+
FetchAlertsResult,
|
|
41
|
+
GoogleSecOpsListBasis,
|
|
42
|
+
SearchDetectionsResult,
|
|
43
|
+
} from "./GoogleSecOpsClient";
|
|
43
44
|
|
|
44
45
|
const SECOPS_SERVICE_NAME: string = "Google SecOps";
|
|
46
|
+
const SECOPS_VENDOR_NAME: string = "Google";
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
/*
|
|
49
|
+
* The first poll of a new connection reads a full day of CREATED detections.
|
|
50
|
+
* Fifteen minutes used to be the default, and on a tenant whose rules run
|
|
51
|
+
* hourly or daily it reliably returned nothing, which read as "the
|
|
52
|
+
* connector is broken" on the very first look.
|
|
53
|
+
*/
|
|
54
|
+
const DEFAULT_LOOKBACK_IN_MINUTES: number = 24 * 60;
|
|
55
|
+
/*
|
|
56
|
+
* Adaptive catch-up. A scheduled poll reads at most MAX_CHUNK_MINUTES of
|
|
57
|
+
* created time past its cursor. A poll that cannot read its window in one
|
|
58
|
+
* run halves the next chunk and a complete poll doubles it back, because
|
|
59
|
+
* re-reading an identical window that never fits pinned the cursor forever
|
|
60
|
+
* and nothing new was ever imported. The chunk is measured from the cursor,
|
|
61
|
+
* not from the overlapped window start: a chunk no longer than the overlap
|
|
62
|
+
* would otherwise end at or before the cursor and never move it.
|
|
63
|
+
*/
|
|
64
|
+
const MAX_CHUNK_MINUTES: number = 24 * 60;
|
|
65
|
+
const MIN_CHUNK_MINUTES: number = 1;
|
|
48
66
|
const WINDOW_OVERLAP_IN_MINUTES: number = 1;
|
|
49
|
-
|
|
50
|
-
|
|
67
|
+
/*
|
|
68
|
+
* Separate request budgets per kind of read. The two created-time search
|
|
69
|
+
* passes are the authoritative read of rule detections and share one page
|
|
70
|
+
* budget; the alerts view splits truncated windows and gets its own, so a
|
|
71
|
+
* burst that the alerts view has to split can no longer starve the searches
|
|
72
|
+
* (or the other way round).
|
|
73
|
+
*/
|
|
74
|
+
const MAX_SEARCH_PAGES_PER_POLL: number = 20;
|
|
75
|
+
const MAX_ALERTS_VIEW_REQUESTS_PER_POLL: number = 16;
|
|
76
|
+
// The worker job times out at ten minutes; normalizing and importing follow.
|
|
77
|
+
const MAX_FETCH_DURATION_MS: number = 4 * 60 * 1000;
|
|
51
78
|
const MAX_ALERTS_PER_REQUEST: number = 1000;
|
|
79
|
+
const MAX_SEARCH_PAGE_SIZE: number = 1000;
|
|
52
80
|
const MAX_DIAGNOSTIC_SAMPLES: number = 25;
|
|
53
81
|
const MAX_RANGE_MS: number = 7 * 24 * 60 * 60 * 1000;
|
|
82
|
+
const DEFAULT_POLL_INTERVAL_IN_MINUTES: number = 5;
|
|
83
|
+
/*
|
|
84
|
+
* A detection created within one poll interval plus this grace of its
|
|
85
|
+
* detection time would still have been caught by a detection-time cursor;
|
|
86
|
+
* anything later is exactly the record the old basis lost.
|
|
87
|
+
*/
|
|
88
|
+
const CREATION_LAG_GRACE_IN_MINUTES: number = 1;
|
|
89
|
+
/*
|
|
90
|
+
* Curated (Google-authored) rule detections are optional: tenants without
|
|
91
|
+
* that entitlement answer 403 (and some 400/404) on the curated route. Those
|
|
92
|
+
* statuses degrade the pass to a warning; 401, 429, 5xx, timeouts and parse
|
|
93
|
+
* failures still fail the run like any other pass.
|
|
94
|
+
*/
|
|
95
|
+
const CURATED_OPTIONAL_STATUSES: Array<number> = [400, 403, 404];
|
|
54
96
|
export const GOOGLE_SECOPS_SOURCE_LOCK_NAMESPACE: string = "GoogleSecOpsSource";
|
|
55
97
|
|
|
56
98
|
interface PollWindow {
|
|
@@ -58,6 +100,43 @@ interface PollWindow {
|
|
|
58
100
|
endTime: Date;
|
|
59
101
|
warnings: Array<string>;
|
|
60
102
|
hasUsableCursor: boolean;
|
|
103
|
+
/*
|
|
104
|
+
* Scheduled polls only: the saved cursor this window reads past, or the
|
|
105
|
+
* window start when there is no usable cursor. chunkMinutes is the
|
|
106
|
+
* distance from there to endTime, rounded up; requestedChunkMinutes is
|
|
107
|
+
* the chunk the previous poll asked for, which is longer whenever this
|
|
108
|
+
* window was cut short by the present.
|
|
109
|
+
*/
|
|
110
|
+
chunkBase?: Date | undefined;
|
|
111
|
+
chunkMinutes?: number | undefined;
|
|
112
|
+
requestedChunkMinutes?: number | undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Why a read pass stopped before it had read everything it was asked for.
|
|
116
|
+
type PassStop = "requests" | "time" | null;
|
|
117
|
+
|
|
118
|
+
interface PassOutcome {
|
|
119
|
+
// Records Google returned across every request of the pass.
|
|
120
|
+
returned: number;
|
|
121
|
+
requests: number;
|
|
122
|
+
stoppedBy: PassStop;
|
|
123
|
+
// Google itself left part of the window unread (truncation, unfinished stream).
|
|
124
|
+
leftUnread: boolean;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// The per-poll budgets: request counts per kind of read and the wall clock.
|
|
128
|
+
interface PollBudget {
|
|
129
|
+
startedMs: number;
|
|
130
|
+
searchRequests: number;
|
|
131
|
+
alertsViewRequests: number;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// How a scheduled poll moves its cursor, decided once the passes are done.
|
|
135
|
+
interface CursorDecision {
|
|
136
|
+
cursor?: string | undefined;
|
|
137
|
+
nextChunkMinutes: number;
|
|
138
|
+
forcedAdvance: boolean;
|
|
139
|
+
warning?: string | undefined;
|
|
61
140
|
}
|
|
62
141
|
|
|
63
142
|
class RecordedPollFailure extends Error {}
|
|
@@ -80,6 +159,8 @@ export default class GoogleSecOpsPoller {
|
|
|
80
159
|
pollIntervalInMinutes: true,
|
|
81
160
|
lastPolledAt: true,
|
|
82
161
|
cursor: true,
|
|
162
|
+
// Carries nextChunkMinutes, the length of the next catch-up chunk.
|
|
163
|
+
lastPollResult: true,
|
|
83
164
|
includeNonAlertingDetections: true,
|
|
84
165
|
},
|
|
85
166
|
skip: 0,
|
|
@@ -197,13 +278,30 @@ export default class GoogleSecOpsPoller {
|
|
|
197
278
|
return this.executeUnlocked(connection, options, clientOverride);
|
|
198
279
|
}
|
|
199
280
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
281
|
+
let mutex: SemaphoreMutex;
|
|
282
|
+
try {
|
|
283
|
+
mutex = await Semaphore.lock({
|
|
284
|
+
namespace: GOOGLE_SECOPS_SOURCE_LOCK_NAMESPACE,
|
|
285
|
+
key: connection.projectId!.toString(),
|
|
286
|
+
lockTimeout: 30000,
|
|
287
|
+
acquireTimeout: 10000,
|
|
288
|
+
retryInterval: 100,
|
|
289
|
+
});
|
|
290
|
+
} catch (error) {
|
|
291
|
+
/*
|
|
292
|
+
* The library's timeout message names Redis keys and reads like an
|
|
293
|
+
* infrastructure fault. It is the expected steady state when a long
|
|
294
|
+
* import holds the source lock, so it is reworded for the person
|
|
295
|
+
* reading run history, and left as a BadDataException so the run
|
|
296
|
+
* executor records it without retrying.
|
|
297
|
+
*/
|
|
298
|
+
if (error instanceof SemaphoreLockTimeoutError) {
|
|
299
|
+
throw new BadDataException(
|
|
300
|
+
"Another poll or import for this source is still running in this project. This run was skipped; polling continues on the next scheduled tick.",
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
207
305
|
try {
|
|
208
306
|
/*
|
|
209
307
|
* An injected client also injects the connection snapshot for tests.
|
|
@@ -221,6 +319,12 @@ export default class GoogleSecOpsPoller {
|
|
|
221
319
|
instanceResourceName: true,
|
|
222
320
|
serviceAccountJson: true,
|
|
223
321
|
cursor: true,
|
|
322
|
+
/*
|
|
323
|
+
* Reloaded with the cursor: the previous poll's result decides
|
|
324
|
+
* this poll's chunk and whether it resumes past a forced advance.
|
|
325
|
+
*/
|
|
326
|
+
lastPollResult: true,
|
|
327
|
+
pollIntervalInMinutes: true,
|
|
224
328
|
includeNonAlertingDetections: true,
|
|
225
329
|
},
|
|
226
330
|
props: { isRoot: true },
|
|
@@ -286,35 +390,124 @@ export default class GoogleSecOpsPoller {
|
|
|
286
390
|
"Select a valid past time range of at most seven days.",
|
|
287
391
|
);
|
|
288
392
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
393
|
+
return { startTime, endTime, warnings, hasUsableCursor };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (options.type !== "poll") {
|
|
397
|
+
return { startTime, endTime, warnings, hasUsableCursor };
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const chunk: number = this.getChunkMinutes(connection);
|
|
401
|
+
let cursorTime: Date | null = null;
|
|
402
|
+
if (connection.cursor) {
|
|
403
|
+
const parsed: Date = new Date(connection.cursor);
|
|
404
|
+
if (!Number.isFinite(parsed.getTime())) {
|
|
292
405
|
warnings.push(
|
|
293
|
-
"The saved cursor is unreadable; polling the default
|
|
406
|
+
"The saved cursor is unreadable; polling the default 24 hour window.",
|
|
294
407
|
);
|
|
295
|
-
} else if (
|
|
408
|
+
} else if (parsed >= now) {
|
|
296
409
|
warnings.push(
|
|
297
|
-
"The saved cursor is in the future; polling the default
|
|
410
|
+
"The saved cursor is in the future; polling the default 24 hour window.",
|
|
298
411
|
);
|
|
299
412
|
} else {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
413
|
+
cursorTime = parsed;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (cursorTime) {
|
|
418
|
+
hasUsableCursor = true;
|
|
419
|
+
startTime = this.resumesFromForcedAdvance(connection, cursorTime)
|
|
420
|
+
? cursorTime
|
|
421
|
+
: OneUptimeDate.addRemoveMinutes(
|
|
422
|
+
cursorTime,
|
|
423
|
+
-WINDOW_OVERLAP_IN_MINUTES,
|
|
424
|
+
);
|
|
425
|
+
const chunkEnd: Date = OneUptimeDate.addRemoveMinutes(cursorTime, chunk);
|
|
426
|
+
if (chunkEnd < endTime) {
|
|
427
|
+
endTime = chunkEnd;
|
|
428
|
+
warnings.push(
|
|
429
|
+
`Catching up from the saved cursor in ${this.describeChunk(chunk)} windows. Later detections will be fetched by subsequent polls.`,
|
|
304
430
|
);
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
431
|
+
}
|
|
432
|
+
} else {
|
|
433
|
+
// The first window keeps its 24 hour lookback; a narrowed chunk only shortens its end.
|
|
434
|
+
const chunkEnd: Date = OneUptimeDate.addRemoveMinutes(startTime, chunk);
|
|
435
|
+
if (chunkEnd < endTime) {
|
|
436
|
+
endTime = chunkEnd;
|
|
437
|
+
warnings.push(
|
|
438
|
+
`Reading the first ${chunk} minutes of the 24 hour lookback. Later detections will be fetched by subsequent polls.`,
|
|
308
439
|
);
|
|
309
|
-
if (chunkEnd < endTime) {
|
|
310
|
-
endTime = chunkEnd;
|
|
311
|
-
warnings.push(
|
|
312
|
-
"Catching up from the saved cursor in 24 hour windows. Later detections will be fetched by subsequent polls.",
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
440
|
}
|
|
316
441
|
}
|
|
317
|
-
|
|
442
|
+
|
|
443
|
+
const chunkBase: Date = cursorTime || startTime;
|
|
444
|
+
return {
|
|
445
|
+
startTime,
|
|
446
|
+
endTime,
|
|
447
|
+
warnings,
|
|
448
|
+
hasUsableCursor,
|
|
449
|
+
chunkBase,
|
|
450
|
+
requestedChunkMinutes: chunk,
|
|
451
|
+
chunkMinutes: Math.max(
|
|
452
|
+
MIN_CHUNK_MINUTES,
|
|
453
|
+
Math.ceil((endTime.getTime() - chunkBase.getTime()) / 60000),
|
|
454
|
+
),
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/*
|
|
459
|
+
* lastPollResult.nextChunkMinutes when it is a whole number of minutes
|
|
460
|
+
* inside the allowed range. Anything else (no previous poll, a hand-edited
|
|
461
|
+
* row, a result from before adaptive catch-up) reads the full 24 hours.
|
|
462
|
+
*/
|
|
463
|
+
private static getChunkMinutes(connection: GoogleSecOpsConnection): number {
|
|
464
|
+
const stored: unknown = connection.lastPollResult
|
|
465
|
+
? connection.lastPollResult["nextChunkMinutes"]
|
|
466
|
+
: undefined;
|
|
467
|
+
if (
|
|
468
|
+
typeof stored === "number" &&
|
|
469
|
+
Number.isInteger(stored) &&
|
|
470
|
+
stored >= MIN_CHUNK_MINUTES &&
|
|
471
|
+
stored <= MAX_CHUNK_MINUTES
|
|
472
|
+
) {
|
|
473
|
+
return stored;
|
|
474
|
+
}
|
|
475
|
+
return MAX_CHUNK_MINUTES;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/*
|
|
479
|
+
* True when the saved cursor is the end of a minute that a forced advance
|
|
480
|
+
* skipped (or a poll that already started there and did not move the
|
|
481
|
+
* cursor). Such a poll starts at the cursor without the overlap: the
|
|
482
|
+
* overlap minute is exactly the minute that could not be read, and
|
|
483
|
+
* re-reading it would overflow again and force a second advance over a
|
|
484
|
+
* minute that was never the problem.
|
|
485
|
+
*/
|
|
486
|
+
private static resumesFromForcedAdvance(
|
|
487
|
+
connection: GoogleSecOpsConnection,
|
|
488
|
+
cursorTime: Date,
|
|
489
|
+
): boolean {
|
|
490
|
+
const stored: JSONObject | undefined = connection.lastPollResult;
|
|
491
|
+
if (!stored || stored["type"] !== "poll") {
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
const readTime: (key: string) => number = (key: string): number => {
|
|
495
|
+
const value: unknown = stored[key];
|
|
496
|
+
return typeof value === "string" ? Date.parse(value) : Number.NaN;
|
|
497
|
+
};
|
|
498
|
+
return (
|
|
499
|
+
(stored["forcedAdvance"] === true &&
|
|
500
|
+
readTime("windowEnd") === cursorTime.getTime()) ||
|
|
501
|
+
readTime("windowStart") === cursorTime.getTime()
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
private static describeChunk(minutes: number): string {
|
|
506
|
+
if (minutes % 60 === 0) {
|
|
507
|
+
const hours: number = minutes / 60;
|
|
508
|
+
return `${hours} hour`;
|
|
509
|
+
}
|
|
510
|
+
return `${minutes} minute`;
|
|
318
511
|
}
|
|
319
512
|
|
|
320
513
|
private static async executeUnlocked(
|
|
@@ -324,6 +517,21 @@ export default class GoogleSecOpsPoller {
|
|
|
324
517
|
): Promise<GoogleSecOpsRunResult> {
|
|
325
518
|
const window: PollWindow = this.getWindow(connection, options);
|
|
326
519
|
const startedMs: number = Date.now();
|
|
520
|
+
/*
|
|
521
|
+
* Preview and backfill take a detection-time range from the person (it
|
|
522
|
+
* is how the SecOps alerts view frames time), and read created time as
|
|
523
|
+
* well so an import of "yesterday" also carries detections created
|
|
524
|
+
* yesterday about older events. Polls read created time only; the
|
|
525
|
+
* alerts-view pass supplies detection time.
|
|
526
|
+
*/
|
|
527
|
+
const isHistorical: boolean =
|
|
528
|
+
options.type === "preview" || options.type === "backfill";
|
|
529
|
+
const searchBases: Array<GoogleSecOpsListBasis> = isHistorical
|
|
530
|
+
? ["CREATED_TIME", "DETECTION_TIME"]
|
|
531
|
+
: ["CREATED_TIME"];
|
|
532
|
+
const basisLabel: string = isHistorical
|
|
533
|
+
? "created and detection time"
|
|
534
|
+
: "created time";
|
|
327
535
|
const result: GoogleSecOpsRunResult = {
|
|
328
536
|
type: options.type,
|
|
329
537
|
...(options.runId ? { runId: options.runId } : {}),
|
|
@@ -335,6 +543,8 @@ export default class GoogleSecOpsPoller {
|
|
|
335
543
|
windowEnd: window.endTime.toISOString(),
|
|
336
544
|
includeNonAlertingDetections:
|
|
337
545
|
connection.includeNonAlertingDetections === true,
|
|
546
|
+
basis: isHistorical ? "detection-time" : "created-time",
|
|
547
|
+
sourceCounts: { ruleDetections: 0, curatedDetections: 0, alertsView: 0 },
|
|
338
548
|
fetchedCount: 0,
|
|
339
549
|
ingestedCount: 0,
|
|
340
550
|
duplicateCount: 0,
|
|
@@ -345,6 +555,14 @@ export default class GoogleSecOpsPoller {
|
|
|
345
555
|
warnings: window.warnings,
|
|
346
556
|
samples: [],
|
|
347
557
|
checks: [],
|
|
558
|
+
...(window.chunkMinutes !== undefined
|
|
559
|
+
? { chunkMinutes: window.chunkMinutes }
|
|
560
|
+
: {}),
|
|
561
|
+
};
|
|
562
|
+
const budget: PollBudget = {
|
|
563
|
+
startedMs,
|
|
564
|
+
searchRequests: 0,
|
|
565
|
+
alertsViewRequests: 0,
|
|
348
566
|
};
|
|
349
567
|
let phase: string = "Validate configuration";
|
|
350
568
|
let phaseStartedMs: number = Date.now();
|
|
@@ -374,23 +592,101 @@ export default class GoogleSecOpsPoller {
|
|
|
374
592
|
message: "Google accepted the service account credentials.",
|
|
375
593
|
});
|
|
376
594
|
}
|
|
377
|
-
|
|
595
|
+
|
|
596
|
+
/*
|
|
597
|
+
* Three passes over one window, unioned by Collection.id. The alerts
|
|
598
|
+
* view alone filters on DETECTION time, and a rule that runs hourly
|
|
599
|
+
* creates detections whose detection time is already behind a
|
|
600
|
+
* forward-only cursor, so it can never be the primary read.
|
|
601
|
+
*/
|
|
602
|
+
const seen: Map<string, JSONObject> = new Map();
|
|
603
|
+
|
|
604
|
+
phase = `Read rule detections by ${basisLabel}`;
|
|
378
605
|
phaseStartedMs = Date.now();
|
|
379
|
-
const
|
|
606
|
+
const rulePass: PassOutcome = await this.searchPass(
|
|
380
607
|
client,
|
|
381
608
|
window,
|
|
382
609
|
result,
|
|
383
|
-
|
|
610
|
+
budget,
|
|
611
|
+
seen,
|
|
612
|
+
{ curated: false, bases: searchBases },
|
|
384
613
|
);
|
|
385
|
-
result.
|
|
386
|
-
|
|
614
|
+
result.sourceCounts!.ruleDetections = rulePass.returned;
|
|
615
|
+
this.recordPass({
|
|
616
|
+
result,
|
|
387
617
|
name: phase,
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
618
|
+
startedMs: phaseStartedMs,
|
|
619
|
+
outcome: rulePass,
|
|
620
|
+
countMessage: `${rulePass.returned} rule detections returned for the window by ${basisLabel}.`,
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
phase = `Read curated rule detections by ${basisLabel}`;
|
|
624
|
+
phaseStartedMs = Date.now();
|
|
625
|
+
try {
|
|
626
|
+
const curatedPass: PassOutcome = await this.searchPass(
|
|
627
|
+
client,
|
|
628
|
+
window,
|
|
629
|
+
result,
|
|
630
|
+
budget,
|
|
631
|
+
seen,
|
|
632
|
+
{ curated: true, bases: searchBases },
|
|
633
|
+
);
|
|
634
|
+
result.sourceCounts!.curatedDetections = curatedPass.returned;
|
|
635
|
+
this.recordPass({
|
|
636
|
+
result,
|
|
637
|
+
name: phase,
|
|
638
|
+
startedMs: phaseStartedMs,
|
|
639
|
+
outcome: curatedPass,
|
|
640
|
+
countMessage: `${curatedPass.returned} curated rule detections returned for the window by ${basisLabel}.`,
|
|
641
|
+
});
|
|
642
|
+
} catch (curatedError) {
|
|
643
|
+
/*
|
|
644
|
+
* Named differently from the outer catch on purpose: the guidance
|
|
645
|
+
* accuracy test reads executeUnlocked's first catch clause, by its
|
|
646
|
+
* variable name, as the run-failure path.
|
|
647
|
+
*/
|
|
648
|
+
const status: number | null =
|
|
649
|
+
GoogleSecOpsClient.readHttpStatus(curatedError);
|
|
650
|
+
if (status === null || !CURATED_OPTIONAL_STATUSES.includes(status)) {
|
|
651
|
+
throw curatedError;
|
|
652
|
+
}
|
|
653
|
+
const message: string = redactLogString(
|
|
654
|
+
ConnectorErrorMessage.toMessage(curatedError),
|
|
655
|
+
);
|
|
656
|
+
result.warnings.push(
|
|
657
|
+
`Curated rule detections could not be read (HTTP ${status}); this tenant may not have curated rule access. Rule detections and the alerts view were still read.`,
|
|
658
|
+
);
|
|
659
|
+
result.checks.push({
|
|
660
|
+
name: phase,
|
|
661
|
+
status: "warn",
|
|
662
|
+
durationMs: Date.now() - phaseStartedMs,
|
|
663
|
+
message,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
phase = "Read alerts view by detection time";
|
|
668
|
+
phaseStartedMs = Date.now();
|
|
669
|
+
const alertsPass: PassOutcome = await this.fetchWindows(
|
|
670
|
+
client,
|
|
671
|
+
window,
|
|
672
|
+
result,
|
|
673
|
+
budget,
|
|
674
|
+
seen,
|
|
675
|
+
);
|
|
676
|
+
result.sourceCounts!.alertsView = alertsPass.returned;
|
|
677
|
+
this.recordPass({
|
|
678
|
+
result,
|
|
679
|
+
name: phase,
|
|
680
|
+
startedMs: phaseStartedMs,
|
|
681
|
+
outcome: alertsPass,
|
|
682
|
+
countMessage: `${alertsPass.returned} alerts returned by detection time.`,
|
|
683
|
+
successMessage: `${alertsPass.returned} alerts returned by detection time. The configured Google SecOps instance is reachable and allows reading detections.`,
|
|
392
684
|
});
|
|
393
685
|
|
|
686
|
+
const alerts: Array<JSONObject> = Array.from(seen.values());
|
|
687
|
+
result.fetchedCount = alerts.length;
|
|
688
|
+
result.creationLag = this.measureCreationLag(alerts, connection, result);
|
|
689
|
+
|
|
394
690
|
phase = "Normalize detections";
|
|
395
691
|
phaseStartedMs = Date.now();
|
|
396
692
|
const normalized: Array<NormalizedSecurityEvent> = [];
|
|
@@ -411,23 +707,29 @@ export default class GoogleSecOpsPoller {
|
|
|
411
707
|
}
|
|
412
708
|
}
|
|
413
709
|
if (result.rejectedCount) {
|
|
710
|
+
/*
|
|
711
|
+
* Counted and reported, never retried: an object Google returns
|
|
712
|
+
* that is not a Collection is permanently unrecognizable, so
|
|
713
|
+
* holding the cursor for it would pin the window until the 24 hour
|
|
714
|
+
* chunk could never reach the present.
|
|
715
|
+
*/
|
|
414
716
|
result.warnings.push(
|
|
415
|
-
`${result.rejectedCount} returned objects were discarded because they do not look like Google SecOps detections.`,
|
|
717
|
+
`${result.rejectedCount} returned objects were discarded because they do not look like Google SecOps detections. They are counted here and do not hold the poll cursor.`,
|
|
416
718
|
);
|
|
417
719
|
}
|
|
418
720
|
if (result.failedCount) {
|
|
419
721
|
result.warnings.push(
|
|
420
|
-
`${result.failedCount} detections could not be normalized
|
|
722
|
+
`${result.failedCount} detections could not be normalized, so this window is not fully imported.`,
|
|
421
723
|
);
|
|
422
724
|
}
|
|
423
|
-
result.complete =
|
|
424
|
-
result.complete &&
|
|
425
|
-
result.failedCount === 0 &&
|
|
426
|
-
result.rejectedCount === 0;
|
|
725
|
+
result.complete = result.complete && result.failedCount === 0;
|
|
427
726
|
result.checks.push({
|
|
428
727
|
name: phase,
|
|
429
|
-
status:
|
|
430
|
-
|
|
728
|
+
status: result.failedCount
|
|
729
|
+
? "failed"
|
|
730
|
+
: result.rejectedCount
|
|
731
|
+
? "warn"
|
|
732
|
+
: "success",
|
|
431
733
|
durationMs: Date.now() - phaseStartedMs,
|
|
432
734
|
message: `${normalized.length} detections recognized; ${result.rejectedCount} rejected; ${result.failedCount} failed.`,
|
|
433
735
|
});
|
|
@@ -461,6 +763,25 @@ export default class GoogleSecOpsPoller {
|
|
|
461
763
|
message: result.error,
|
|
462
764
|
});
|
|
463
765
|
}
|
|
766
|
+
let decision: CursorDecision | null = null;
|
|
767
|
+
if (options.type === "poll") {
|
|
768
|
+
decision = this.decideCursor(window, result);
|
|
769
|
+
result.nextChunkMinutes = decision.nextChunkMinutes;
|
|
770
|
+
if (decision.forcedAdvance) {
|
|
771
|
+
result.forcedAdvance = true;
|
|
772
|
+
}
|
|
773
|
+
if (decision.warning) {
|
|
774
|
+
/*
|
|
775
|
+
* A forced advance leaves a reported gap, so it leads the warnings
|
|
776
|
+
* (and therefore lastError); narrowing follows the reasons for it.
|
|
777
|
+
*/
|
|
778
|
+
if (decision.forcedAdvance) {
|
|
779
|
+
result.warnings.unshift(decision.warning);
|
|
780
|
+
} else {
|
|
781
|
+
result.warnings.push(decision.warning);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
464
785
|
result.completedAt = OneUptimeDate.getCurrentDate().toISOString();
|
|
465
786
|
result.durationMs = Math.max(0, Date.now() - startedMs);
|
|
466
787
|
for (const warning of result.warnings) {
|
|
@@ -472,19 +793,7 @@ export default class GoogleSecOpsPoller {
|
|
|
472
793
|
`GoogleSecOpsPoller: connection ${connection.id!.toString()} fetched ${result.fetchedCount} alerts and ingested ${result.ingestedCount} security events for ${result.windowStart} to ${result.windowEnd}.`,
|
|
473
794
|
);
|
|
474
795
|
|
|
475
|
-
if (options.type === "poll") {
|
|
476
|
-
/*
|
|
477
|
-
* A failed first poll must keep its original start. Otherwise the next
|
|
478
|
-
* default lookback would slide past records before we ever imported them.
|
|
479
|
-
*/
|
|
480
|
-
const cursor: string | undefined = result.complete
|
|
481
|
-
? result.windowEnd
|
|
482
|
-
: !window.hasUsableCursor
|
|
483
|
-
? OneUptimeDate.addRemoveMinutes(
|
|
484
|
-
window.startTime,
|
|
485
|
-
WINDOW_OVERLAP_IN_MINUTES,
|
|
486
|
-
).toISOString()
|
|
487
|
-
: undefined;
|
|
796
|
+
if (options.type === "poll" && decision) {
|
|
488
797
|
await GoogleSecOpsConnectionService.updateOneById({
|
|
489
798
|
id: connection.id!,
|
|
490
799
|
data: {
|
|
@@ -494,8 +803,8 @@ export default class GoogleSecOpsPoller {
|
|
|
494
803
|
(result.complete
|
|
495
804
|
? null
|
|
496
805
|
: result.warnings.join(" ") ||
|
|
497
|
-
"Poll incomplete
|
|
498
|
-
...(cursor ? { cursor } : {}),
|
|
806
|
+
"Poll incomplete.")) as unknown as string,
|
|
807
|
+
...(decision.cursor ? { cursor: decision.cursor } : {}),
|
|
499
808
|
...(result.complete
|
|
500
809
|
? { lastSuccessfulPollAt: new Date(result.completedAt) }
|
|
501
810
|
: {}),
|
|
@@ -515,44 +824,180 @@ export default class GoogleSecOpsPoller {
|
|
|
515
824
|
return result;
|
|
516
825
|
}
|
|
517
826
|
|
|
827
|
+
/*
|
|
828
|
+
* Where a scheduled poll leaves its cursor and how long the next chunk is.
|
|
829
|
+
* Google's search endpoints return newest first, so a poll that stops
|
|
830
|
+
* early has no "everything before here was read" point to resume from;
|
|
831
|
+
* the only ways forward are a shorter window and, at one minute, moving
|
|
832
|
+
* past the minute and reporting it.
|
|
833
|
+
*/
|
|
834
|
+
private static decideCursor(
|
|
835
|
+
window: PollWindow,
|
|
836
|
+
result: GoogleSecOpsRunResult,
|
|
837
|
+
): CursorDecision {
|
|
838
|
+
const chunkMinutes: number = window.chunkMinutes || MAX_CHUNK_MINUTES;
|
|
839
|
+
const requestedChunkMinutes: number =
|
|
840
|
+
window.requestedChunkMinutes || MAX_CHUNK_MINUTES;
|
|
841
|
+
/*
|
|
842
|
+
* A first poll that does not finish keeps its original start. Otherwise
|
|
843
|
+
* the next default lookback would slide past records before we ever
|
|
844
|
+
* imported them.
|
|
845
|
+
*/
|
|
846
|
+
const heldCursor: string | undefined = window.hasUsableCursor
|
|
847
|
+
? undefined
|
|
848
|
+
: OneUptimeDate.addRemoveMinutes(
|
|
849
|
+
window.startTime,
|
|
850
|
+
WINDOW_OVERLAP_IN_MINUTES,
|
|
851
|
+
).toISOString();
|
|
852
|
+
|
|
853
|
+
/*
|
|
854
|
+
* Failed and finished polls work from the requested chunk, not from the
|
|
855
|
+
* window read: a caught-up window is only as long as the gap to now, and
|
|
856
|
+
* keeping that as the next chunk would make polls after an outage or a
|
|
857
|
+
* failure fall behind the present for no reason.
|
|
858
|
+
*/
|
|
859
|
+
if (result.status === "failed") {
|
|
860
|
+
// A failure says nothing about volume, so the chunk is kept as it was.
|
|
861
|
+
return {
|
|
862
|
+
cursor: heldCursor,
|
|
863
|
+
nextChunkMinutes: requestedChunkMinutes,
|
|
864
|
+
forcedAdvance: false,
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
if (result.complete) {
|
|
869
|
+
// A finished poll never shrinks the chunk; it doubles back after narrowing.
|
|
870
|
+
return {
|
|
871
|
+
cursor: result.windowEnd,
|
|
872
|
+
nextChunkMinutes: Math.min(
|
|
873
|
+
MAX_CHUNK_MINUTES,
|
|
874
|
+
Math.max(requestedChunkMinutes, chunkMinutes * 2),
|
|
875
|
+
),
|
|
876
|
+
forcedAdvance: false,
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
if (chunkMinutes > MIN_CHUNK_MINUTES) {
|
|
881
|
+
const nextChunkMinutes: number = Math.max(
|
|
882
|
+
MIN_CHUNK_MINUTES,
|
|
883
|
+
Math.floor(chunkMinutes / 2),
|
|
884
|
+
);
|
|
885
|
+
return {
|
|
886
|
+
cursor: heldCursor,
|
|
887
|
+
nextChunkMinutes,
|
|
888
|
+
forcedAdvance: false,
|
|
889
|
+
warning: `This window holds more records than one poll can read; the next poll reads a ${nextChunkMinutes} minute window from the same starting point.`,
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/*
|
|
894
|
+
* One minute that still cannot be read. Holding here would stall polling
|
|
895
|
+
* for good, so the cursor moves past the minute and the gap is reported
|
|
896
|
+
* loudly with the way to recover it.
|
|
897
|
+
*/
|
|
898
|
+
const from: Date = window.chunkBase || window.startTime;
|
|
899
|
+
return {
|
|
900
|
+
cursor: result.windowEnd,
|
|
901
|
+
nextChunkMinutes: MIN_CHUNK_MINUTES,
|
|
902
|
+
forcedAdvance: true,
|
|
903
|
+
warning: `More records were created in the one minute from ${from.toISOString()} to ${result.windowEnd} than one poll can read. Polling moved past this minute so newer records keep arriving; use Import this time range in Diagnostics on this minute to recover what one run can read.`,
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/*
|
|
908
|
+
* One diagnostic check per read pass. A pass a budget stopped, or never
|
|
909
|
+
* let start, is a warning naming the budget, never a success: a green
|
|
910
|
+
* step that did not read its window hid which data went unread.
|
|
911
|
+
*/
|
|
912
|
+
private static recordPass(data: {
|
|
913
|
+
result: GoogleSecOpsRunResult;
|
|
914
|
+
name: string;
|
|
915
|
+
startedMs: number;
|
|
916
|
+
outcome: PassOutcome;
|
|
917
|
+
// What the pass returned; the whole message when it read everything.
|
|
918
|
+
countMessage: string;
|
|
919
|
+
// Replaces countMessage when the pass read everything, if given.
|
|
920
|
+
successMessage?: string | undefined;
|
|
921
|
+
}): void {
|
|
922
|
+
const outcome: PassOutcome = data.outcome;
|
|
923
|
+
let status: GoogleSecOpsDiagnosticCheck["status"] = "success";
|
|
924
|
+
let message: string = data.successMessage || data.countMessage;
|
|
925
|
+
if (outcome.stoppedBy === "time" && outcome.requests === 0) {
|
|
926
|
+
status = "warn";
|
|
927
|
+
message = "Not run: the poll time budget was spent.";
|
|
928
|
+
this.addWarning(
|
|
929
|
+
data.result,
|
|
930
|
+
`${data.name} was not run: the poll time budget was spent.`,
|
|
931
|
+
);
|
|
932
|
+
} else if (outcome.stoppedBy) {
|
|
933
|
+
status = "warn";
|
|
934
|
+
const budgetName: string =
|
|
935
|
+
outcome.stoppedBy === "time" ? "poll time budget" : "request budget";
|
|
936
|
+
const stop: string = `stopped by the ${budgetName} after ${outcome.requests} ${outcome.requests === 1 ? "request" : "requests"}`;
|
|
937
|
+
message =
|
|
938
|
+
outcome.requests === 0
|
|
939
|
+
? `Not run: ${stop}. The search passes share one page budget and it was spent before this pass started.`
|
|
940
|
+
: `${data.countMessage} The pass was ${stop}.`;
|
|
941
|
+
this.addWarning(data.result, `${data.name} was ${stop}.`);
|
|
942
|
+
} else if (outcome.leftUnread) {
|
|
943
|
+
status = "warn";
|
|
944
|
+
message = `${data.countMessage} Google did not return part of the window.`;
|
|
945
|
+
}
|
|
946
|
+
data.result.checks.push({
|
|
947
|
+
name: data.name,
|
|
948
|
+
status,
|
|
949
|
+
durationMs: Date.now() - data.startedMs,
|
|
950
|
+
message,
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/*
|
|
955
|
+
* The alerts-view pass (legacyFetchAlertsView, detection time). The
|
|
956
|
+
* endpoint has no pagination, so a truncated window is split in half and
|
|
957
|
+
* both halves are re-read until they fit or the alerts-view budget runs
|
|
958
|
+
* out.
|
|
959
|
+
*/
|
|
518
960
|
private static async fetchWindows(
|
|
519
961
|
client: GoogleSecOpsClient,
|
|
520
962
|
window: PollWindow,
|
|
521
963
|
result: GoogleSecOpsRunResult,
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
const
|
|
964
|
+
budget: PollBudget,
|
|
965
|
+
seen: Map<string, JSONObject>,
|
|
966
|
+
): Promise<PassOutcome> {
|
|
967
|
+
const outcome: PassOutcome = {
|
|
968
|
+
returned: 0,
|
|
969
|
+
requests: 0,
|
|
970
|
+
stoppedBy: null,
|
|
971
|
+
leftUnread: false,
|
|
972
|
+
};
|
|
973
|
+
const pending: Array<{ startTime: Date; endTime: Date }> = [
|
|
974
|
+
{ startTime: window.startTime, endTime: window.endTime },
|
|
975
|
+
];
|
|
976
|
+
let splits: number = 0;
|
|
526
977
|
while (pending.length > 0) {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
978
|
+
const stop: PassStop = this.budgetStop(
|
|
979
|
+
budget,
|
|
980
|
+
budget.alertsViewRequests,
|
|
981
|
+
MAX_ALERTS_VIEW_REQUESTS_PER_POLL,
|
|
982
|
+
);
|
|
983
|
+
if (stop) {
|
|
984
|
+
outcome.stoppedBy = stop;
|
|
531
985
|
result.complete = false;
|
|
532
|
-
result.warnings.push(
|
|
533
|
-
"The recovery request limit was reached. Narrow the time range; the poll cursor has not advanced.",
|
|
534
|
-
);
|
|
535
986
|
break;
|
|
536
987
|
}
|
|
537
988
|
const current: { startTime: Date; endTime: Date } = pending.shift()!;
|
|
989
|
+
budget.alertsViewRequests++;
|
|
990
|
+
outcome.requests++;
|
|
538
991
|
result.requestCount++;
|
|
539
992
|
const fetched: FetchAlertsResult = await client.fetchDetectionAlerts({
|
|
540
|
-
|
|
993
|
+
startTime: current.startTime,
|
|
994
|
+
endTime: current.endTime,
|
|
541
995
|
maxAlerts: result.type === "test" ? 1 : MAX_ALERTS_PER_REQUEST,
|
|
542
996
|
includeNonAlertingDetections: result.includeNonAlertingDetections,
|
|
543
997
|
});
|
|
998
|
+
outcome.returned += fetched.alerts.length;
|
|
544
999
|
for (const alert of fetched.alerts) {
|
|
545
|
-
|
|
546
|
-
try {
|
|
547
|
-
key = readString(alert, "id") || contentHashEventUid(alert);
|
|
548
|
-
} catch {
|
|
549
|
-
/*
|
|
550
|
-
* Keep an uninspectable object in the normalization failure count
|
|
551
|
-
* without preventing valid detections in this response from importing.
|
|
552
|
-
*/
|
|
553
|
-
key = `malformed:${result.requestCount}:${seen.size}`;
|
|
554
|
-
}
|
|
555
|
-
seen.set(key, alert);
|
|
1000
|
+
this.collect(seen, alert, result);
|
|
556
1001
|
}
|
|
557
1002
|
result.fetchedCount = seen.size;
|
|
558
1003
|
const truncated: boolean =
|
|
@@ -565,7 +1010,7 @@ export default class GoogleSecOpsPoller {
|
|
|
565
1010
|
* A one-record permission probe is deliberately limited; it makes no
|
|
566
1011
|
* assertion about whether the full detection window can be imported.
|
|
567
1012
|
*/
|
|
568
|
-
result.complete = fetched.complete;
|
|
1013
|
+
result.complete = result.complete && fetched.complete;
|
|
569
1014
|
if (truncated) {
|
|
570
1015
|
result.warnings.push(
|
|
571
1016
|
"Connection test reads at most one detection. Use Preview to inspect the full time range.",
|
|
@@ -585,23 +1030,211 @@ export default class GoogleSecOpsPoller {
|
|
|
585
1030
|
truncated &&
|
|
586
1031
|
current.endTime.getTime() - current.startTime.getTime() > 1000
|
|
587
1032
|
) {
|
|
588
|
-
|
|
589
|
-
`Google limited the response for ${current.startTime.toISOString()} to ${current.endTime.toISOString()}; retrying smaller windows.`,
|
|
590
|
-
);
|
|
1033
|
+
splits++;
|
|
591
1034
|
pending.unshift(
|
|
592
1035
|
{ startTime: current.startTime, endTime: new Date(midpoint) },
|
|
593
1036
|
{ startTime: new Date(midpoint), endTime: current.endTime },
|
|
594
1037
|
);
|
|
595
1038
|
} else if (!fetched.complete || truncated) {
|
|
596
1039
|
result.complete = false;
|
|
597
|
-
|
|
1040
|
+
outcome.leftUnread = true;
|
|
1041
|
+
this.addWarning(
|
|
1042
|
+
result,
|
|
598
1043
|
truncated
|
|
599
|
-
? "Google still truncated a one-second window
|
|
600
|
-
: "Google ended
|
|
1044
|
+
? "Google still truncated a one-second window of the alerts view, so some alerts in it were not read."
|
|
1045
|
+
: "Google ended an alerts view response without confirming it was complete.",
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
if (splits > 0) {
|
|
1050
|
+
// One line rather than one per split: lastError joins every warning.
|
|
1051
|
+
result.warnings.push(
|
|
1052
|
+
`Google limited the alerts view response for ${window.startTime.toISOString()} to ${window.endTime.toISOString()}, so it was split into smaller windows ${splits} ${splits === 1 ? "time" : "times"}.`,
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
return outcome;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
/*
|
|
1059
|
+
* A detections-search pass (legacySearchDetections or the curated
|
|
1060
|
+
* variant), one basis at a time, following nextPageToken under the page
|
|
1061
|
+
* budget the two search passes share.
|
|
1062
|
+
*/
|
|
1063
|
+
private static async searchPass(
|
|
1064
|
+
client: GoogleSecOpsClient,
|
|
1065
|
+
window: PollWindow,
|
|
1066
|
+
result: GoogleSecOpsRunResult,
|
|
1067
|
+
budget: PollBudget,
|
|
1068
|
+
seen: Map<string, JSONObject>,
|
|
1069
|
+
options: { curated: boolean; bases: Array<GoogleSecOpsListBasis> },
|
|
1070
|
+
): Promise<PassOutcome> {
|
|
1071
|
+
const label: string = options.curated
|
|
1072
|
+
? "curated rule detections"
|
|
1073
|
+
: "rule detections";
|
|
1074
|
+
const outcome: PassOutcome = {
|
|
1075
|
+
returned: 0,
|
|
1076
|
+
requests: 0,
|
|
1077
|
+
stoppedBy: null,
|
|
1078
|
+
leftUnread: false,
|
|
1079
|
+
};
|
|
1080
|
+
for (const listBasis of options.bases) {
|
|
1081
|
+
let pageToken: string | undefined = undefined;
|
|
1082
|
+
do {
|
|
1083
|
+
const stop: PassStop = this.budgetStop(
|
|
1084
|
+
budget,
|
|
1085
|
+
budget.searchRequests,
|
|
1086
|
+
MAX_SEARCH_PAGES_PER_POLL,
|
|
1087
|
+
);
|
|
1088
|
+
if (stop) {
|
|
1089
|
+
outcome.stoppedBy = stop;
|
|
1090
|
+
result.complete = false;
|
|
1091
|
+
return outcome;
|
|
1092
|
+
}
|
|
1093
|
+
budget.searchRequests++;
|
|
1094
|
+
outcome.requests++;
|
|
1095
|
+
result.requestCount++;
|
|
1096
|
+
const page: SearchDetectionsResult = await client.searchDetections({
|
|
1097
|
+
startTime: window.startTime,
|
|
1098
|
+
endTime: window.endTime,
|
|
1099
|
+
listBasis,
|
|
1100
|
+
alertingOnly: !result.includeNonAlertingDetections,
|
|
1101
|
+
pageSize: result.type === "test" ? 1 : MAX_SEARCH_PAGE_SIZE,
|
|
1102
|
+
pageToken,
|
|
1103
|
+
curated: options.curated,
|
|
1104
|
+
});
|
|
1105
|
+
outcome.returned += page.detections.length;
|
|
1106
|
+
for (const detection of page.detections) {
|
|
1107
|
+
this.collect(seen, detection, result);
|
|
1108
|
+
}
|
|
1109
|
+
result.fetchedCount = seen.size;
|
|
1110
|
+
if (page.truncated) {
|
|
1111
|
+
/*
|
|
1112
|
+
* respTooLargeDetectionsTruncated: Google cut the page by byte
|
|
1113
|
+
* size and the rest is not reachable through a page token, so the
|
|
1114
|
+
* window is not fully read.
|
|
1115
|
+
*/
|
|
1116
|
+
result.complete = false;
|
|
1117
|
+
outcome.leftUnread = true;
|
|
1118
|
+
this.addWarning(
|
|
1119
|
+
result,
|
|
1120
|
+
`Google truncated a page of ${label} by size for ${window.startTime.toISOString()} to ${window.endTime.toISOString()}, so part of the window was not read.`,
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
if (result.type === "test") {
|
|
1124
|
+
// One record per pass is all a permission probe needs.
|
|
1125
|
+
return outcome;
|
|
1126
|
+
}
|
|
1127
|
+
pageToken = page.nextPageToken || undefined;
|
|
1128
|
+
} while (pageToken);
|
|
1129
|
+
}
|
|
1130
|
+
return outcome;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
/*
|
|
1134
|
+
* Whether a pass may make another request: each kind of read has its own
|
|
1135
|
+
* request count, and all of them share the poll's wall-clock bound so a
|
|
1136
|
+
* slow endpoint cannot run the worker job into its timeout.
|
|
1137
|
+
*/
|
|
1138
|
+
private static budgetStop(
|
|
1139
|
+
budget: PollBudget,
|
|
1140
|
+
used: number,
|
|
1141
|
+
limit: number,
|
|
1142
|
+
): PassStop {
|
|
1143
|
+
if (used >= limit) {
|
|
1144
|
+
return "requests";
|
|
1145
|
+
}
|
|
1146
|
+
if (Date.now() - budget.startedMs >= MAX_FETCH_DURATION_MS) {
|
|
1147
|
+
return "time";
|
|
1148
|
+
}
|
|
1149
|
+
return null;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// Repeated conditions (every one-second window, every page) are said once.
|
|
1153
|
+
private static addWarning(
|
|
1154
|
+
result: GoogleSecOpsRunResult,
|
|
1155
|
+
warning: string,
|
|
1156
|
+
): void {
|
|
1157
|
+
if (!result.warnings.includes(warning)) {
|
|
1158
|
+
result.warnings.push(warning);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
/*
|
|
1163
|
+
* Union by Collection.id across passes and across the alerts view's
|
|
1164
|
+
* chunks; a record with no id falls back to a content hash, and an
|
|
1165
|
+
* object that cannot even be inspected is kept under a positional key so
|
|
1166
|
+
* it is counted as rejected without hiding the valid records beside it.
|
|
1167
|
+
*/
|
|
1168
|
+
private static collect(
|
|
1169
|
+
seen: Map<string, JSONObject>,
|
|
1170
|
+
record: JSONObject,
|
|
1171
|
+
result: GoogleSecOpsRunResult,
|
|
1172
|
+
): void {
|
|
1173
|
+
let key: string;
|
|
1174
|
+
try {
|
|
1175
|
+
key = readString(record, "id") || contentHashEventUid(record);
|
|
1176
|
+
} catch {
|
|
1177
|
+
key = `malformed:${result.requestCount}:${seen.size}`;
|
|
1178
|
+
}
|
|
1179
|
+
seen.set(key, record);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
/*
|
|
1183
|
+
* createdTime minus detectionTime over what was fetched. A lag beyond one
|
|
1184
|
+
* poll interval is the record a detection-time cursor skips, so the
|
|
1185
|
+
* warning names the reason polling reads created time.
|
|
1186
|
+
*/
|
|
1187
|
+
private static measureCreationLag(
|
|
1188
|
+
alerts: Array<JSONObject>,
|
|
1189
|
+
connection: GoogleSecOpsConnection,
|
|
1190
|
+
result: GoogleSecOpsRunResult,
|
|
1191
|
+
): GoogleSecOpsCreationLag {
|
|
1192
|
+
const intervalInMinutes: number = Math.max(
|
|
1193
|
+
1,
|
|
1194
|
+
connection.pollIntervalInMinutes || DEFAULT_POLL_INTERVAL_IN_MINUTES,
|
|
1195
|
+
);
|
|
1196
|
+
const thresholdInMinutes: number =
|
|
1197
|
+
intervalInMinutes + CREATION_LAG_GRACE_IN_MINUTES;
|
|
1198
|
+
let measured: number = 0;
|
|
1199
|
+
let lateCount: number = 0;
|
|
1200
|
+
let maxLagMs: number = 0;
|
|
1201
|
+
for (const alert of alerts) {
|
|
1202
|
+
let detectionTime: Date | null = null;
|
|
1203
|
+
let createdTime: Date | null = null;
|
|
1204
|
+
try {
|
|
1205
|
+
detectionTime = parseEventTime(
|
|
1206
|
+
readValue(alert, "detectionTime") ??
|
|
1207
|
+
readValue(alert, "detection_time"),
|
|
601
1208
|
);
|
|
1209
|
+
createdTime = parseEventTime(
|
|
1210
|
+
readValue(alert, "createdTime") ?? readValue(alert, "created_time"),
|
|
1211
|
+
);
|
|
1212
|
+
} catch {
|
|
1213
|
+
continue;
|
|
1214
|
+
}
|
|
1215
|
+
if (!detectionTime || !createdTime) {
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1218
|
+
measured++;
|
|
1219
|
+
const lagMs: number = createdTime.getTime() - detectionTime.getTime();
|
|
1220
|
+
if (lagMs > maxLagMs) {
|
|
1221
|
+
maxLagMs = lagMs;
|
|
602
1222
|
}
|
|
1223
|
+
if (lagMs > thresholdInMinutes * 60 * 1000) {
|
|
1224
|
+
lateCount++;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
const lag: GoogleSecOpsCreationLag = {
|
|
1228
|
+
measured,
|
|
1229
|
+
lateCount,
|
|
1230
|
+
maxLagMinutes: Math.round(maxLagMs / 60000),
|
|
1231
|
+
};
|
|
1232
|
+
if (lateCount > 0) {
|
|
1233
|
+
result.warnings.push(
|
|
1234
|
+
`${lateCount} of ${measured} detections were created more than ${thresholdInMinutes} minutes after their detection time (up to ${lag.maxLagMinutes} minutes). This is why the connector polls by created time: a cursor over detection time would already have moved past them.`,
|
|
1235
|
+
);
|
|
603
1236
|
}
|
|
604
|
-
return
|
|
1237
|
+
return lag;
|
|
605
1238
|
}
|
|
606
1239
|
|
|
607
1240
|
private static sample(
|
|
@@ -637,54 +1270,20 @@ export default class GoogleSecOpsPoller {
|
|
|
637
1270
|
}
|
|
638
1271
|
|
|
639
1272
|
/**
|
|
640
|
-
*
|
|
641
|
-
*
|
|
642
|
-
*
|
|
643
|
-
* source lock plus synchronous inserts make at least one replica contain
|
|
644
|
-
* every earlier acknowledged import before this query begins.
|
|
1273
|
+
* Thin delegate kept as a static on this class: existing tests spy on it,
|
|
1274
|
+
* and the query itself now lives in SecurityEventDedupe so the Google
|
|
1275
|
+
* poller and the Security Event Connections poller cannot drift.
|
|
645
1276
|
*/
|
|
646
1277
|
public static async findExistingEventUids(
|
|
647
1278
|
projectId: ObjectID,
|
|
648
1279
|
ids: Array<string>,
|
|
649
1280
|
): Promise<Set<string>> {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
) {
|
|
657
|
-
if (Date.now() - startedMs >= 2 * 60 * 1000) {
|
|
658
|
-
throw new Error(
|
|
659
|
-
"Duplicate lookup exceeded its time limit. Retry a smaller import window.",
|
|
660
|
-
);
|
|
661
|
-
}
|
|
662
|
-
const statement: Statement = SQL`SELECT DISTINCT eventUid FROM clusterAllReplicas(
|
|
663
|
-
${{ type: TableColumnType.Text, value: getClickhouseClusterName() }},
|
|
664
|
-
${{ type: TableColumnType.Text, value: getClickhouseDatabaseName() }},
|
|
665
|
-
${{ type: TableColumnType.Text, value: getStorageTableName(SecurityEventService.model.tableName) }}
|
|
666
|
-
) WHERE projectId = ${{ type: TableColumnType.ObjectID, value: projectId }}
|
|
667
|
-
AND vendorName = ${{ type: TableColumnType.Text, value: "Google" }}
|
|
668
|
-
AND productName = ${{ type: TableColumnType.Text, value: SECOPS_SERVICE_NAME }}
|
|
669
|
-
AND eventUid IN ${{ type: TableColumnType.ArrayText, value: ids.slice(offset, offset + MAX_ALERTS_PER_REQUEST) }}`;
|
|
670
|
-
statement.append(
|
|
671
|
-
getQuerySettings({
|
|
672
|
-
maxExecutionTimeInSeconds: 30,
|
|
673
|
-
timeoutOverflowMode: "throw",
|
|
674
|
-
boundScanMemory: true,
|
|
675
|
-
additionalSettings: { skip_unavailable_shards: 0 },
|
|
676
|
-
}),
|
|
677
|
-
);
|
|
678
|
-
const response: ResultSet<"JSON"> =
|
|
679
|
-
await SecurityEventService.executeQuery(statement);
|
|
680
|
-
const data: ResponseJSON<JSONObject> = await response.json<JSONObject>();
|
|
681
|
-
for (const row of data.data) {
|
|
682
|
-
if (typeof row["eventUid"] === "string") {
|
|
683
|
-
existing.add(row["eventUid"]);
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
return existing;
|
|
1281
|
+
return SecurityEventDedupe.findExistingEventUids({
|
|
1282
|
+
projectId,
|
|
1283
|
+
vendorName: SECOPS_VENDOR_NAME,
|
|
1284
|
+
productName: SECOPS_SERVICE_NAME,
|
|
1285
|
+
ids,
|
|
1286
|
+
});
|
|
688
1287
|
}
|
|
689
1288
|
|
|
690
1289
|
private static async ingest(
|