feat(snapshot): bulk snapshot endpoints for OpenGRC (#7505) - #7535
feat(snapshot): bulk snapshot endpoints for OpenGRC (#7505)#7535Alexandre Gaspard (alexgaspard) wants to merge 1 commit into
Conversation
|
📖 Documentation check — ✅ Passed 44 functional file(s), 3 doc file(s) changed. Documentation-worthy changes detected and documentation was updated. 👏 Detected changes (covered by doc updates)
|
|
✅ Container vulnerability scan — Passed Previously reported findings are no longer present.
View workflow run · Standard JSON report · UBI9 JSON report Updated from CI run attempt 1. |
b8704d3 to
aabbd6a
Compare
c2cbaf2 to
88b7f02
Compare
|
Thank you for your contribution. This PR is but one step away from being ready for merging: all commits must be PGP-signed. To get started, please see https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits |
88b7f02 to
70e6053
Compare
There was a problem hiding this comment.
Pull request overview
Adds a new “bulk snapshot export” capability for OpenGRC-style integrations by introducing two new snapshot observation streams (attack + vulnerability) backed by dedicated engine indexes, plus cursor-paged read APIs that expose the current verified endpoint state with explicit window/horizon metadata. It also extends the indexing loop and engine abstraction to support keyset paging and cursor-paged search (Elastic + OpenSearch), and wires RBAC/capability gating and preview-flag gating end-to-end.
Changes:
- Introduces two new indexable snapshot models (attack/vulnerability observations) with keyset-paged indexing handlers and native-query grains.
- Adds cursor-paged search support in both ElasticService and OpenSearchService (keyset predicate + strict tenant/grant restrictions).
- Adds preview-flag gating (
BULK_SNAPSHOT_EXPORT), a new tenant capability (ACCESS_SNAPSHOT_OBSERVATION), API endpoints, tests, and documentation.
Reviewed changes
Copilot reviewed 65 out of 66 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| openaev-model/src/main/java/io/openaev/utils/OpenSearchUtils.java | Adds keyset “resume after (ts,id)” predicate builder for cursor paging. |
| openaev-model/src/main/java/io/openaev/utils/ElasticUtils.java | Adds Elastic equivalent keyset predicate builder. |
| openaev-model/src/main/java/io/openaev/service/OpenSearchService.java | Persists keyset cursor (timestamp + lastId) and adds cursor-paged search implementation (OpenSearch). |
| openaev-model/src/main/java/io/openaev/service/IndexingCursor.java | New record to represent persisted indexing cursor (timestamp + optional lastId). |
| openaev-model/src/main/java/io/openaev/service/EsIndexingUtils.java | Adds keyset cursor computation + grace-window capping logic for (ts,id). |
| openaev-model/src/main/java/io/openaev/service/ElasticService.java | Persists keyset cursor (timestamp + lastId) and adds cursor-paged search implementation (Elasticsearch). |
| openaev-model/src/main/java/io/openaev/engine/model/snapshotobservation/VulnerabilityObservationHandler.java | New keyset-paged handler to fetch vulnerability observation grains for indexing. |
| openaev-model/src/main/java/io/openaev/engine/model/snapshotobservation/EsVulnerabilityObservation.java | New indexable ES model for vulnerability observation snapshot docs. |
| openaev-model/src/main/java/io/openaev/engine/model/snapshotobservation/EsAttackObservation.java | New indexable ES model for attack observation snapshot docs. |
| openaev-model/src/main/java/io/openaev/engine/model/snapshotobservation/AttackObservationHandler.java | New keyset-paged handler to fetch attack observation grains for indexing. |
| openaev-model/src/main/java/io/openaev/engine/model/EsBase.java | Registers new snapshot observation discriminators for engine polymorphic schema. |
| openaev-model/src/main/java/io/openaev/engine/Handler.java | Extends handler contract with optional keyset-paged fetch(from, fromId, limit) + isKeysetPaged(). |
| openaev-model/src/main/java/io/openaev/engine/EngineService.java | Adds cursor-paged search API and max page size constant. |
| openaev-model/src/main/java/io/openaev/engine/BulkSnapshotExportCondition.java | New Spring condition to gate handler bean registration behind preview feature flag. |
| openaev-model/src/main/java/io/openaev/engine/api/CursorPageQuery.java | New record describing cursor-paged query bounds (since/after/windowEnd/size). |
| openaev-model/src/main/java/io/openaev/database/repository/VulnerabilityObservationRepository.java | Adds native keyset-grain query + existence probe for vulnerability observations. |
| openaev-model/src/main/java/io/openaev/database/repository/AttackObservationRepository.java | Adds native keyset-grain query + existence probe for attack observations. |
| openaev-model/src/main/java/io/openaev/database/raw/RawVulnerabilityObservationIndexing.java | New projection interface for vulnerability observation indexing query. |
| openaev-model/src/main/java/io/openaev/database/raw/RawAttackObservationIndexing.java | New projection interface for attack observation indexing query. |
| openaev-model/src/main/java/io/openaev/database/model/ResourceType.java | Adds SNAPSHOT_OBSERVATION resource type. |
| openaev-model/src/main/java/io/openaev/database/model/IndexingStatus.java | Adds persisted indexing_status_last_id field to support keyset cursor. |
| openaev-model/src/main/java/io/openaev/database/model/Capability.java | Adds ACCESS_SNAPSHOT_OBSERVATION capability and removes credential helper method. |
| openaev-front/src/utils/lang/zh.json | Adds translation for ACCESS_SNAPSHOT_OBSERVATION. |
| openaev-front/src/utils/lang/ru.json | Adds translation for ACCESS_SNAPSHOT_OBSERVATION. |
| openaev-front/src/utils/lang/ko.json | Adds translation for ACCESS_SNAPSHOT_OBSERVATION. |
| openaev-front/src/utils/lang/ja.json | Adds translation for ACCESS_SNAPSHOT_OBSERVATION. |
| openaev-front/src/utils/lang/it.json | Adds translation for ACCESS_SNAPSHOT_OBSERVATION. |
| openaev-front/src/utils/lang/fr.json | Adds translation for ACCESS_SNAPSHOT_OBSERVATION. |
| openaev-front/src/utils/lang/es.json | Adds translation for ACCESS_SNAPSHOT_OBSERVATION. |
| openaev-front/src/utils/lang/en.json | Adds translation for ACCESS_SNAPSHOT_OBSERVATION. |
| openaev-front/src/utils/lang/de.json | Adds translation for ACCESS_SNAPSHOT_OBSERVATION. |
| openaev-front/src/utils/api-types.d.ts | Updates generated API types for snapshot models/DTOs and preview feature/capability enums. |
| openaev-api/src/test/java/io/openaev/utilstest/OpenSearchUtilsTest.java | Adds unit test for OpenSearch keyset predicate shape. |
| openaev-api/src/test/java/io/openaev/utilstest/ElasticUtilsTest.java | Adds unit test for Elastic keyset predicate shape. |
| openaev-api/src/test/java/io/openaev/service/PermissionServiceTest.java | Adds permission regression tests for SNAPSHOT_OBSERVATION SEARCH gating. |
| openaev-api/src/test/java/io/openaev/service/EsIndexingUtilsTest.java | Adds unit tests for keyset cursor computation and grace-window behavior. |
| openaev-api/src/test/java/io/openaev/engine/VulnerabilityObservationIndexingFlagOffTest.java | Verifies handler/model not registered when preview flag is off. |
| openaev-api/src/test/java/io/openaev/engine/IndexingRegressionIntegrationTest.java | Regression coverage for keyset cursor additive slice (null lastId for existing models, etc.). |
| openaev-api/src/test/java/io/openaev/engine/CursorPagedSearchResultWindowTest.java | Proves cursor paging works past engine max-result-window using small cap. |
| openaev-api/src/test/java/io/openaev/engine/BulkSnapshotExportConditionTest.java | Unit tests for feature-flag parsing + anti-drift with PreviewFeature enum. |
| openaev-api/src/test/java/io/openaev/engine/AttackObservationIndexingFlagOffTest.java | Verifies handler/model not registered when preview flag is off. |
| openaev-api/src/test/java/io/openaev/architecture/TenantScopedEntrypointsTxCtxArchTest.java | Registers new snapshot API entrypoints as TxCtx-required. |
| openaev-api/src/test/java/io/openaev/api/snapshot/SnapshotObservationServiceTest.java | Unit tests for horizon/window computation, clamping, paging semantics. |
| openaev-api/src/test/java/io/openaev/api/snapshot/SnapshotObservationMapperTest.java | Verifies exact wire contract field mapping/serialization for both streams. |
| openaev-api/src/test/java/io/openaev/api/snapshot/SnapshotObservationFeatureFlagApiTest.java | Ensures endpoints return 404 when preview flag is off even with capability. |
| openaev-api/src/test/java/io/openaev/api/snapshot/SnapshotCursorCodecTest.java | Unit tests for cursor encoding/decoding, tenant binding, and rejections. |
| openaev-api/src/test/java/io/openaev/api/capabilities/CapabilityTreeBuilderTest.java | Ensures snapshot capability appears/vanishes based on preview gate; platform vs tenant scope. |
| openaev-api/src/main/java/io/openaev/rest/settings/PreviewFeature.java | Adds BULK_SNAPSHOT_EXPORT preview feature enum entry. |
| openaev-api/src/main/java/io/openaev/migration/V6_20260819100000000__Indexing_status_keyset_cursor.java | Adds nullable DB column indexing_status_last_id (Flyway). |
| openaev-api/src/main/java/io/openaev/api/snapshot/SnapshotObservationService.java | Implements window/horizon computation + delegates to cursor-paged engine search. |
| openaev-api/src/main/java/io/openaev/api/snapshot/SnapshotObservationMapper.java | Maps engine docs to output DTOs for both streams. |
| openaev-api/src/main/java/io/openaev/api/snapshot/SnapshotObservationApi.java | Adds two tenant-scoped snapshot search endpoints gated by preview flag + capability. |
| openaev-api/src/main/java/io/openaev/api/snapshot/SnapshotCursorCodec.java | Implements opaque, URL-safe base64 cursor codec with tenant binding and validation. |
| openaev-api/src/main/java/io/openaev/api/snapshot/form/VulnerabilityObservationOutput.java | New output DTO for vulnerability observation stream. |
| openaev-api/src/main/java/io/openaev/api/snapshot/form/SnapshotSearchOutput.java | New response envelope carrying page + window/horizon/readiness metadata. |
| openaev-api/src/main/java/io/openaev/api/snapshot/form/SnapshotSearchInput.java | New input DTO supporting since/cursor, clamped page size and safety lag. |
| openaev-api/src/main/java/io/openaev/api/snapshot/form/AttackObservationOutput.java | New output DTO for attack observation stream. |
| openaev-api/src/main/java/io/openaev/api/capabilities/CapabilityTreeBuilder.java | Generalizes capability preview-gating (credentials + snapshot) and caches “all gates enabled” trees. |
| openaev-api/src/main/java/io/openaev/api/capabilities/CapabilityApi.java | Builds capability tree based on enabled preview features set (not just credentials). |
| docs/mkdocs.yml | Adds Bulk snapshot export doc page to nav. |
| docs/docs/reference/apis/bulk-snapshot.md | New API reference describing cursor semantics, readiness/window, and client obligations. |
| docs/docs/administration/users-and-rbac.md | Documents the new “Access observation snapshots” capability. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
70e6053 to
6b308d4
Compare
Proposed changes
Two new engine indexes holding the current verified state of every endpoint:
snapshot-attack-observation(one document per tenant, endpoint, technique, expectation type and scenario) andsnapshot-vulnerability-observation(one document per tenant, endpoint, finding type and finding value). Both are built by keyset paged handlers.Keyset paging in the indexing loop, and cursor paged search in the engine abstraction, for both Elasticsearch and OpenSearch.
Two endpoints for OpenGRC to read those indexes differentially:
POST /api/tenants/{tenantId}/snapshot/attack-observations/searchPOST /api/tenants/{tenantId}/snapshot/vulnerability-observations/searchEach response carries the page plus
snapshot_window_end,indexed_throughandsnapshot_ready, so a client can tell whether the inventory it just read was complete.New read only capability
ACCESS_SNAPSHOT_OBSERVATION, with its nine translations.Everything is behind the
BULK_SNAPSHOT_EXPORTpreview flag, off by default. With the flag off the handlers are not registered, no index is created and both endpoints return 404.Documentation in
docs/reference/apis/bulk-snapshot.md, plus the capability row inusers-and-rbac.md.One additive migration: nullable
indexing_status_last_idcolumn onindexing_status, no backfill, no cursor reset. No new configuration key.Where to start
The diff is large but most of it is tests and generated types. The parts worth reading closely:
SnapshotObservationServicefor the window computation: safety lag, clamping, readiness.AttackObservationRepositoryandVulnerabilityObservationRepositoryfor the two native queries that define the grains.SnapshotObservationApiandSnapshotObservationApiTestfor RBAC, tenant isolation and cursor validation.Testing Instructions
cd openaev-dev && docker compose up -d openaev-dev-pgsql openaev-dev-minio openaev-dev-elasticsearch openaev-dev-rabbitmqEngineContext.getModels()):openaev.enabled-dev-features=BULK_SNAPSHOT_EXPORTand check that both indexes are created and that both endpoints answer for a non admin user holding the capability. Boot without the flag and check that neither the indexes nor the endpoints exist.Related issues
Checklist
Further comments
A few choices worth confirming:
ENDPOINTassets are exported, consistently withEsVulnerableEndpoint. Findings on other asset types are skipped.base_dependenciesholds the asset only. The simulation and the scenario are carried as ACL restrictions, so deleting a simulation does not orphan a document.last_verified_atis the latestfinding_updated_atof the grain rather than the inject execution date, because it is the only timestamp every producer sets.indexed_throughis stored per stream and not per tenant, sosnapshot_readyreflects the platform backlog: a quiet tenant can be held back by a noisy one. Documented on the page as a known limitation.last_scenario_id.