Skip to content

Commit d015578

Browse files
sarroutbiclaude
andauthored
Add background attestation observation recording (#10)
The SRS and SDD lacked specification for background attestation recording — the mechanism that persists pass/fail observations into the attestation repository independently of frontend API requests. Without this, timeline charts show gaps when no user is viewing the dashboard. SRS: Add FR-087 with three Gherkin scenarios covering headless recording, dedup interval enforcement, and graceful degradation. SDD: Add section 3.5.4 documenting the tokio::spawn background task, its relationship to NFR-007/NFR-020, dedup tracker, configurable interval, and graceful shutdown. Also adds observation_interval_secs to AppConfig, a design rationale entry, and traceability matrix row. Signed-off-by: Sergio Arroutbi <sarroutb@redhat.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5170d44 commit d015578

2 files changed

Lines changed: 90 additions & 2 deletions

File tree

SDD-Keylime-Monitoring-Tool.md

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,53 @@ Re-render with new data
941941

942942
**Trace:** SRS SR-001, SR-002, SR-010, SR-011
943943

944+
#### 3.5.4 Data Flow: Background Attestation Recording
945+
946+
The backend spawns a long-lived Tokio task at startup that records attestation observations independently of frontend API requests, ensuring the time-series attestation history (FR-024) is complete regardless of dashboard usage.
947+
948+
```text
949+
tokio::spawn(background_observation_loop)
950+
|
951+
+-- tokio::time::interval(observation_interval) // Default: 30s (FR-087)
952+
|
953+
+-- Every tick:
954+
| +-- Acquire KeylimeClient via AppState::keylime()
955+
| +-- Fetch agent list from Verifier API
956+
| +-- For each agent:
957+
| | +-- Check dedup tracker (HashMap<Uuid, (Instant, AttestationResult)>)
958+
| | | |
959+
| | | +-- Same result within 30s: SKIP (dedup)
960+
| | | +-- State changed OR interval elapsed: RECORD
961+
| | |
962+
| | +-- Store observation via AttestationRepository::store_result()
963+
| | +-- Update dedup tracker entry
964+
| |
965+
| +-- Every 10th tick (5 min / 30s = 10):
966+
| +-- Full fleet reconciliation sweep (NFR-020)
967+
| +-- Compare cached state against Verifier, log corrections
968+
|
969+
+-- tokio::select! { _ = interval.tick() => ..., _ = shutdown_rx => break }
970+
```
971+
972+
**Relationship to existing requirements:**
973+
974+
| Requirement | Relationship |
975+
|-------------|-------------|
976+
| NFR-007 (Polling Fallback) | Background task reuses the same 30s polling cadence; both operate in polling mode against the Verifier API |
977+
| NFR-020 (Reconciliation) | The 5-minute reconciliation sweep is performed by the background task as a superset of the per-tick observation — every 10th tick performs a full fleet comparison |
978+
| FR-024 (Attestation Analytics) | The background task is the primary data producer for `AttestationRepository`, ensuring timeline charts have continuous data |
979+
| NFR-006 (Event-Driven Ingestion) | When ZeroMQ event-driven ingestion is available, the background task serves as the fallback/reconciliation mechanism rather than the primary data path |
980+
981+
**Reuse of `record_agent_observations()`:** The background task calls the same `pub(crate) async fn record_agent_observations(state: &AppState)` used by the attestation API handlers. This function iterates agents, consults the dedup tracker on `AppState`, and stores results via `AttestationRepository::store_result()`. Sharing this function guarantees identical observation logic whether triggered by an API request or the background task.
982+
983+
**Dedup Tracker:** `AppState` holds an `attestation_tracker: Arc<Mutex<HashMap<Uuid, (Instant, String)>>>` that maps each agent UUID to its last recorded observation timestamp and result. The tracker prevents redundant writes when an agent's state has not changed and fewer than 30 seconds have elapsed since the last recording. State changes (e.g., `pass``fail`) bypass the interval check and record immediately.
984+
985+
**Graceful Shutdown:** The task uses `tokio::select!` to race the interval tick against a shutdown signal (`tokio::sync::watch` or `broadcast` channel). On receiving the shutdown signal, the task breaks out of the loop, logs the number of observations recorded during its lifetime, and drops cleanly. No in-flight Verifier API calls are aborted — the current tick completes before shutdown.
986+
987+
**Configurable Interval:** The observation interval is read from `AppConfig::keylime::observation_interval_secs` (default: 30). Changing this value requires a restart; runtime hot-reload is not supported for the background task interval.
988+
989+
**Trace:** SRS FR-087, FR-024, NFR-006, NFR-007, NFR-020; Implementation -- `keylime-webtool-backend/src/api/handlers/attestations.rs`
990+
944991
### 3.6 State Dynamics View
945992

946993
#### 3.6.1 Agent State Machine
@@ -1087,6 +1134,7 @@ AppConfig
10871134
| | +-- key: String // HSM/Vault URI (SR-005, SR-006) or file path
10881135
| | +-- ca_cert: PathBuf
10891136
| +-- timeout_secs: u64 // Default: 30
1137+
| +-- observation_interval_secs: u64 // Default: 30 (FR-087, aligned with NFR-007)
10901138
| +-- circuit_breaker
10911139
| +-- failure_threshold: u32 // Default: 5
10921140
| +-- reset_timeout_secs: u64 // Default: 60
@@ -1204,6 +1252,7 @@ Maximum 5 parallel concurrent log fetches to the Verifier API, enforced via Toki
12041252
| `AuditRepository` is insert-only (no update/delete) | Enforces audit immutability at the trait API level; implementations cannot accidentally expose mutation; hash-chain integrity (SR-015) depends on append-only semantics | FR-061, SR-015, SR-026 |
12051253
| Repository injection via `AppState` (compile-time DI) | No runtime DI framework needed; `main.rs` constructs concrete implementations based on config and injects `Arc<dyn Trait>` into `AppState`; consistent with existing `KeylimeClient` and `SettingsStore` injection pattern | -- |
12061254
| `FallbackAttestationRepository` preserves current behavior | Timeline distribution algorithm (3.7.1) runs inside the fallback repository implementation, not in the handler; isolates the pre-DB algorithm behind the trait so the same handler code works with real history data once `SqlAttestationRepository` is implemented | FR-024 |
1255+
| Background `tokio::spawn` task for observations | Decouples attestation recording from frontend requests; ensures timeline data is continuous even when no user is viewing the dashboard; reuses `record_agent_observations()` to guarantee identical logic in both paths; 30s interval aligns with NFR-007 polling cadence and dedup tracker window | FR-087, NFR-007, NFR-020 |
12071256
| No `AgentRepository` — agents excluded from repository pattern | Agents are Keylime-owned data observed via pass-through proxy; all operations (list, detail, actions, bulk) forward to Verifier/Registrar APIs and cache responses (10s TTL). Keeping agents out of the repository layer preserves graceful degradation (NFR-016): agent listings work even when the webtool DB is down. `agent_id` in attestation/alert records is a bare UUID reference, not a foreign key requiring local agent persistence | FR-012, NFR-016 |
12081257

12091258
<!-- CHANGED: Added 7 repository abstraction design rationale entries -->
@@ -1246,7 +1295,7 @@ Maximum 5 parallel concurrent log fetches to the Verifier API, enforced via Toki
12461295
| Storage fallback | In-memory repository implementations when DB unavailable (3.3.11) | NFR-016 |
12471296
| Fault tolerance | Circuit breaker on Verifier API (threshold: 5, reset: 60s) | NFR-017 |
12481297
| Log fetch limit | Max 5 parallel concurrent Verifier log fetches | NFR-023 |
1249-
| Reconciliation | Periodic sweep every 5 minutes | NFR-020 |
1298+
| Reconciliation | Periodic sweep every 5 minutes via background observation task (3.5.4) | NFR-020, FR-087 |
12501299
| Frontend query cache | TanStack Query: 30s stale time, 1 retry | NFR-001 |
12511300

12521301
---
@@ -1314,6 +1363,7 @@ Maximum 5 parallel concurrent log fetches to the Verifier API, enforced via Toki
13141363
| FR-083 | 3.4.3 | Raw Data tab: compact copy icon button to the right of source selector group; Clipboard API with 2s checkmark feedback |
13151364
| FR-084 | 3.2.2 | `KpiCard.tsx`: optional `linkTo` prop wraps card in React Router `<Link>`; Dashboard page maps each KPI to its target route (e.g., Failed Agents → `/agents?state=failed,invalid_quote,tenant_failed`) |
13161365
| FR-085 | 3.2.2 | `Alerts.tsx`: three Recharts donut `PieChart` components below alert table — By Severity, By Type, By State; clickable segments navigate to `/alerts?{dimension}={value}` with filter pre-applied; color maps match Dashboard alert chart (FR-047) |
1366+
| FR-087 | 3.5.4, 3.8.1 | Background `tokio::spawn` observation task, `record_agent_observations()` reuse, dedup tracker, configurable interval |
13171367

13181368
### 6.2 Non-Functional Requirements
13191369

SRS-Keylime-Monitoring-Tool.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ The System transforms Keylime from a CLI-driven security tool into a visual oper
120120
| FR-084 | Fleet Overview KPI card drill-down navigation | SHOULD | Dashboard - Key Performance Indicators |
121121
| FR-085 | Alert Center distribution pie charts (by severity, type, state) | MUST | Revocation - Alert Workflow |
122122
| FR-086 | Integrations topology view with SSH connect | SHOULD | Integration Status - Backend Connectivity |
123+
| FR-087 | Background attestation observation recording independent of frontend | MUST | Attestation Analytics - Overview Dashboard |
123124

124125
### 2.2 Non-Functional Requirements
125126

@@ -2760,6 +2761,42 @@ Feature: Integrations Topology View
27602761
And the node statuses MUST match those displayed in List View
27612762
```
27622763

2764+
### FR-087: Background Attestation Observation Recording
2765+
2766+
**Description:** The System MUST continuously record attestation observations (pass/fail per agent) at the polling interval (default 30 seconds, aligned with NFR-007) via a background task, independent of frontend API requests. The background task MUST iterate over all known agents at each interval, query the Keylime Verifier for current attestation state, and store the result through the `AttestationRepository` (FR-024). Duplicate observations within the dedup interval (same agent, same result, within 30 seconds of last recorded observation) MUST be suppressed. Every 5 minutes (aligned with NFR-020), the background task MUST perform a full fleet reconciliation sweep to detect state drift. The observation interval MUST be configurable via `AppConfig`. If the Keylime Verifier API is unreachable, the background task MUST log a warning and retry on the next interval without crashing. The background task MUST shut down gracefully when the application receives a termination signal.
2767+
2768+
**Trace:** Attestation Analytics - Overview Dashboard; FR-024, NFR-006, NFR-007, NFR-020
2769+
2770+
```gherkin
2771+
Feature: Background Attestation Observation Recording
2772+
2773+
Scenario: Observations recorded when no user is viewing the dashboard
2774+
Given the backend is running with the background observation task active
2775+
And no frontend clients are connected
2776+
And the Keylime Verifier reports 50 agents in GET_QUOTE state and 2 in FAILED state
2777+
When 5 minutes elapse
2778+
Then the AttestationRepository MUST contain at least 10 observation records
2779+
And the hourly attestation timeline (FR-024) MUST show non-zero bars for the elapsed period
2780+
2781+
Scenario: Dedup interval prevents duplicate observations
2782+
Given agent "agent-042" was last recorded as "pass" 15 seconds ago
2783+
And agent "agent-042" is still in GET_QUOTE state (pass)
2784+
When the background observation task runs its next cycle
2785+
Then a new observation for agent "agent-042" MUST NOT be stored
2786+
And the dedup tracker MUST retain the existing timestamp
2787+
But if agent "agent-042" transitions to FAILED state before the next cycle
2788+
Then a new observation with result "fail" MUST be stored immediately
2789+
2790+
Scenario: Graceful degradation when Keylime API is unreachable
2791+
Given the background observation task is running
2792+
And the Keylime Verifier API becomes unreachable
2793+
When the next observation cycle executes
2794+
Then the task MUST log a warning indicating the Verifier is unreachable
2795+
And the task MUST NOT crash or panic
2796+
And the task MUST retry on the subsequent interval
2797+
And previously recorded observations MUST remain intact in the repository
2798+
```
2799+
27632800
---
27642801

27652802
## 4. Non-Functional Requirements Detail
@@ -4050,7 +4087,8 @@ The design details that realize these requirements -- including component decomp
40504087
| IR-017: Sidebar Visibility Toggle | 3.2.2 | Composition View |
40514088
| IR-018: Backend Health Probes | 3.7.3 | Algorithm View |
40524089
| IR-019: Repository Abstraction Layer | 3.3.11 | Logical View |
4090+
| IR-020: Background Attestation Recording | 3.5.4 | Interaction View |
40534091

4054-
<!-- CHANGED: Added IR-019 for repository abstraction layer -->
4092+
<!-- CHANGED: Added IR-020 for background attestation recording -->
40554093

40564094
The SDD also includes a full SRS traceability matrix (Section 6) mapping every implemented requirement to its corresponding design element.

0 commit comments

Comments
 (0)