docs(audit): hash-chain tamper evidence shipped, default off (ScadaBridge#41)
Component-AuditLog Security & Tamper-Evidence describes the per-month chain, the chaining job and its lock, both purge paths (checkpoint anchor, tombstones), pre-existing rows, verification and what an unsigned chain does not prove. alog.md §11.4/§16, CLAUDE.md, HighLevelReqs AL-11/12, Component-CLI, Component-ManagementService, docs/components/CLI.md and the CLI README drop the "no-op placeholder" wording. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
@@ -181,7 +181,8 @@ spec for each is `docs/requirements/Component-<Name>.md`, and `README.md` carrie
|
||||
- Payload cap 8 KB by default / 64 KB on error rows; auth headers redacted by default; SQL parameter values captured by default; per-target redaction opt-in. Inbound API: full verbatim capture up to `InboundMaxBytes` (default 1 MiB); request headers stored in `Extra.requestHeaders` (post-redaction); per-method `SkipBodyCapture` flag suppresses bodies while still recording headers + metadata; `AuditInboundCeilingHits` counter surfaced on health snapshot. (M5.3 T7)
|
||||
- Audit-write failure NEVER aborts the user-facing action — audit is best-effort, the action's own success/failure path is authoritative.
|
||||
- 365-day central retention with monthly partition-switch purge; per-channel retention overrides (`AuditLog:PerChannelRetentionDays`) expire rows earlier than the global window via a bounded, batched row DELETE on the purge actor's maintenance path — values must be shorter than the global window (M5.5 T3); 7-day site SQLite retention with a hard `ForwardState` invariant (no row purged until forwarded or reconciled).
|
||||
- Append-only enforced via DB roles (writer role has INSERT only, no UPDATE/DELETE); hash-chain tamper evidence (T1) and Parquet archival (T2) are deferred to v1.x — not shipped in M5.
|
||||
- Append-only enforced via DB roles (writer role has INSERT only, no UPDATE/DELETE); Parquet archival (T2) is deferred to v1.x.
|
||||
- **Hash-chain tamper evidence (T1, ScadaBridge#41) — default OFF (`AuditLog:HashChain:Enabled`).** One SHA-256 chain per UTC calendar month: `ChainSeq` + `RowHash = SHA-256(prev || canonical(row))` on `AuditLog` (migration `AddAuditLogHashChain`), linked by the central `AuditLogHashChainService` (both nodes, serialised by `sp_getapplock`) — never by the ingest path. `AuditLogChainHead` catches tail deletion and carries the partition-switch checkpoint anchor; the per-channel DELETE writes an `AuditLogChainTombstone` per chained row in the same transaction. `audit verify-chain --month` = management command `VerifyAuditChain` (OperationalAudit, unscoped callers only), first break with partition/ChainSeq/EventId, CLI exit 3. A chained row's `SourceNode` is frozen (backfill skips it). Unsigned; signing/external anchoring are open.
|
||||
- Node-of-origin is captured alongside site-of-origin: `SourceNode` (`varchar(64)` NULL) on `AuditLog`, `Notifications`, and `SiteCalls` — `node-a`/`node-b` for site rows (qualified by `SourceSiteId`/`SourceSite`), `central-a`/`central-b` for central direct-write rows. Stamped at the writing node, carried verbatim through telemetry + reconciliation, and indexed via `IX_AuditLog_Node_Occurred (SourceNode, OccurredAtUtc)` on `AuditLog`.
|
||||
- Per-node stuck KPIs (M5.3 T6): Notification Outbox and Site Call Audit expose `PerNodeNotificationKpiRequest`/`PerNodeSiteCallKpiRequest` messages that group stuck/parked/delivered counts by `SourceNode`, surfacing per-node breakdowns on the Health dashboard.
|
||||
- `audit tree --execution-id <guid>` CLI command (M5.3 T8) + `GET /api/audit/tree` endpoint — resolves any node to its chain root and renders the full execution tree; backed by `IAuditLogRepository.GetExecutionTreeAsync`.
|
||||
|
||||
@@ -493,12 +493,17 @@ Audit Log page **Export** button streams CSV (current filter) server-side. Defau
|
||||
### 11.3 Payload redaction at write
|
||||
See §8.2. Contract: unredacted secrets never persist. Safety net over-redacts on misconfiguration.
|
||||
|
||||
### 11.4 Tamper-evidence hash chain (deferred, v1.x)
|
||||
- Each row gains a `RowHash` column.
|
||||
- `RowHash = SHA-256(prev.RowHash || canonical(row))` per partition.
|
||||
- Computed by a chaining job that runs after each monthly partition closes.
|
||||
- Verifiable offline via `scadabridge audit verify-chain --month YYYY-MM`.
|
||||
- Default **off** in v1 to avoid operational burden. Flag for v1.x.
|
||||
### 11.4 Tamper-evidence hash chain (shipped, ScadaBridge#41 — default off)
|
||||
- Each row gains a `RowHash` column, plus a `ChainSeq` (position in its partition's chain).
|
||||
- `RowHash = SHA-256(prev.RowHash || canonical(row))` per partition (UTC calendar month).
|
||||
- Computed by a chaining job — implemented incrementally (every minute, both central
|
||||
nodes, serialised by a SQL application lock) rather than only after the month closes.
|
||||
- Purges keep the chain verifiable: the partition switch records a checkpoint anchor on
|
||||
the month's chain head; the per-channel DELETE leaves a tombstone per chained row.
|
||||
- Verifiable via `scadabridge audit verify-chain --month YYYY-MM` (first break reported
|
||||
with partition, `ChainSeq`, `EventId`). Unsigned; signing / external anchoring are open.
|
||||
- Default **off** (`AuditLog:HashChain:Enabled`). Full design: Component-AuditLog.md,
|
||||
Security & Tamper-Evidence.
|
||||
|
||||
### 11.5 Site SQLite security
|
||||
- File permissions: read/write by the ScadaBridge service account only.
|
||||
@@ -607,7 +612,7 @@ A monthly job dumps the closing partition to Parquet on operator-configured obje
|
||||
|---|---|---|
|
||||
| 1 | Component number | **#23 Audit Log** (README matrix + HighLevelReqs). |
|
||||
| 2 | Nav placement | New top-level **Audit** nav group in Central UI. |
|
||||
| 3 | Hash-chain tamper evidence (§11.4) | Deferred to v1.x. v1 enforces append-only via DB grants only. |
|
||||
| 3 | Hash-chain tamper evidence (§11.4) | Deferred to v1.x; shipped by ScadaBridge#41 (unsigned SHA-256 chain, default off). |
|
||||
| 4 | Parquet archival to object storage (§15.2) | Deferred to v1.x. |
|
||||
| 5 | Per-channel retention overrides (§12.1) | Deferred to v1.x. v1 uses a single global `RetentionDays`. |
|
||||
| 6 | Default payload cap | **8 KB** for `RequestSummary` / `ResponseSummary`; **64 KB** on error rows (`Status IN ('Failed', 'Parked', 'Discarded')`). |
|
||||
|
||||
@@ -22,8 +22,8 @@ Direct-to-node access (`http://localhost:9001` for `central-a`, `http://localhos
|
||||
|
||||
Management commands use `POST /management`. Audit query, export, and tree use
|
||||
`GET /api/audit/query`, `/api/audit/export`, and `/api/audit/tree`; source-node
|
||||
backfill uses `POST /api/audit/backfill-source-node`. `audit verify-chain` is a
|
||||
local placeholder that performs no verification. `debug stream` uses a persistent
|
||||
backfill uses `POST /api/audit/backfill-source-node`. `audit verify-chain` sends the
|
||||
`VerifyAuditChain` management command (ScadaBridge#41). `debug stream` uses a persistent
|
||||
SignalR connection. Audit export streams the response without buffering it all.
|
||||
|
||||
### Authentication and roles
|
||||
@@ -120,7 +120,7 @@ scadabridge instance deploy --id 7
|
||||
| `db-connection` | `list`, `get`, `create`, `update`, `delete` | `Designer` |
|
||||
| `api-method` | `list`, `get`, `create`, `update`, `delete`, `validate`, `recompile` | `Designer` |
|
||||
| `bundle` | `export`, `preview`, `import` | Designer export; Administrator preview/import |
|
||||
| `audit` | `query`, `export`, `tree`, `backfill-source-node`, `verify-chain` | Audit roles; Administrator backfill; verify-chain is a local placeholder |
|
||||
| `audit` | `query`, `export`, `tree`, `backfill-source-node`, `verify-chain` | Audit roles; Administrator backfill; verify-chain needs an unscoped audit role |
|
||||
| `audit-config` | `query` (config-change audit trail; was `audit-log` pre-M8) | `Admin` |
|
||||
| `health` | `summary`, `pending-updates`, `site`, `event-log`, `parked-messages` | Authenticated reads with server-side scope checks |
|
||||
| `debug` | `snapshot`, `stream` | `Deployer` |
|
||||
|
||||
@@ -789,10 +789,11 @@ MS SQL for direct-write events). Unredacted secrets never persist.
|
||||
enforced by two application-level controls, not by database permissions:
|
||||
1. **CI grep guard** — `AuditLogAppendOnlyGuardTests` scans the ConfigurationDatabase
|
||||
source for any `UPDATE`/`DELETE` DML targeting `dbo.AuditLog` and fails the build
|
||||
on a hit. Exactly two maintenance-path mutations are allow-listed by an explicit
|
||||
`// AUDIT-PURGE-ALLOWED` marker (the per-channel retention `DELETE TOP` and the
|
||||
one-time `SourceNode` sentinel backfill `UPDATE`); every other UPDATE/DELETE trips
|
||||
the guard.
|
||||
on a hit. Exactly three mutations are allow-listed by an explicit
|
||||
`// AUDIT-PURGE-ALLOWED` marker (the per-channel retention `DELETE TOP`, the
|
||||
one-time `SourceNode` sentinel backfill `UPDATE`, and the hash-chain link `UPDATE`
|
||||
that sets only `ChainSeq`/`RowHash` while they are NULL — ScadaBridge#41); every
|
||||
other UPDATE/DELETE trips the guard.
|
||||
2. **Code review** — the marker is deliberately specific so an unrelated mutation
|
||||
cannot inherit the exemption without a reviewer noticing.
|
||||
- **Optional DBA hardening (two DB roles).** For a deployment that wants
|
||||
@@ -815,13 +816,61 @@ MS SQL for direct-write events). Unredacted secrets never persist.
|
||||
**AuditExport** permission.
|
||||
- **Payload redaction at write.** See Payload Capture Policy. Unredacted
|
||||
secrets never persist; the safety net over-redacts on misconfiguration.
|
||||
- **Hash-chain tamper evidence (T1) — deferred to v1.x.** A future `RowHash`
|
||||
column, computed per partition as `SHA-256(prev.RowHash || canonical(row))`, will
|
||||
be verifiable offline via `scadabridge audit verify-chain --month YYYY-MM`. The
|
||||
`verify-chain` CLI command is a no-op placeholder today. Off by default in v1.
|
||||
- **Hash-chain tamper evidence (T1) — shipped (ScadaBridge#41), default OFF.**
|
||||
`AuditLog:HashChain:Enabled` (default `false`, plus `IntervalSeconds` 60,
|
||||
`BatchSize` 1000, `MaxBatchesPerTick` 100) starts the central
|
||||
`AuditLogHashChainService` on both central nodes.
|
||||
- **Model.** One chain per **partition = UTC calendar month of `OccurredAtUtc`**
|
||||
(the `ps_AuditLog_Month` month). Each chained row carries `ChainSeq` (1, 2, 3 …,
|
||||
gap-free within its month) and `RowHash = SHA-256(prev.RowHash || canonical(row))`,
|
||||
`prev` being the row at `ChainSeq - 1` (32 zero bytes for the first). The canonical
|
||||
v1 form (`AuditChainHasher`) is a domain tag, the month and sequence number, then
|
||||
the ten writable columns in table order, each string length-prefixed with a NULL
|
||||
marker; the computed columns derive from `DetailsJson` and are covered by it. A
|
||||
known-answer test pins the encoding — changing it invalidates every stored chain.
|
||||
- **Who writes the chain.** Not the hot path: the two central nodes and the site
|
||||
ingest INSERT concurrently, so a write-time chain would need a global serialisation
|
||||
point on every audit write. A **chaining job** links unchained rows (oldest month
|
||||
first, in `OccurredAtUtc, EventId` order within a pass) under an exclusive SQL
|
||||
Server application lock (`sp_getapplock`, zero timeout — the peer node's pass just
|
||||
skips its tick), with an optimistic head check and a `ChainSeq IS NULL` row-count
|
||||
check that roll the pass back rather than leave a gap. This is alog.md §11.4's
|
||||
"chaining job", run every minute instead of only after a month closes, so a row is
|
||||
tamper-evident about a minute after it reaches central. Chain order is chaining
|
||||
order, so late-arriving site rows simply take the next sequence number.
|
||||
- **Purges.** `dbo.AuditLogChainHead` holds each month's `(LastSeq, LastHash)` — which
|
||||
is what exposes deletion of the newest rows — and the checkpoint anchor
|
||||
`(PurgedThroughSeq, PurgedThroughHash)` that the monthly partition switch sets
|
||||
inside its own transaction; verification resumes after it. The per-channel retention
|
||||
DELETE writes a `dbo.AuditLogChainTombstone` (month, `ChainSeq`, `RowHash`,
|
||||
`EventId`) for every chained row it removes, in the same transaction, via
|
||||
`OUTPUT … INTO`; verification bridges the gap with it. Both purge paths and the
|
||||
SourceNode backfill hold the same lock exclusively. The backfill skips chained rows
|
||||
(a chained row's `SourceNode` is frozen) — run it before enabling the chain.
|
||||
- **Pre-existing rows** start unchained (`ChainSeq`/`RowHash` NULL). Enabling the job
|
||||
chains the historical backlog oldest month first, so those rows are protected from
|
||||
the moment they are chained, not from when they were written. Verification reports
|
||||
still-unchained rows as `UnchainedRows`, never as a break.
|
||||
- **Verification.** `scadabridge audit verify-chain --month YYYY-MM` sends the
|
||||
`VerifyAuditChain` management command (OperationalAudit roles; refused for a
|
||||
site-scoped caller because a month's chain spans every site; long-running Ask). It
|
||||
takes the lock SHARED, recomputes every link in sequence order and reports `Intact`,
|
||||
`NotChained` or `Broken` with the **first break**: partition, `ChainSeq`, `EventId`
|
||||
and kind — `HashMismatch` (an edited row, or rows reordered — the sequence number is
|
||||
inside the hashed bytes), `MissingRow` (a deleted row with no tombstone),
|
||||
`DuplicateSequence`, `IncompleteLink`, or `HeadMismatch` (rows deleted from the end).
|
||||
CLI exit code 3 on `Broken`.
|
||||
- **What it does NOT prove.** The chain is unsigned and anchored only inside the same
|
||||
database: a principal that can rewrite rows AND recompute every later link AND the
|
||||
head can produce a consistent forgery. Signing the head (key custody) and anchoring
|
||||
it outside the database are open owner decisions (ScadaBridge#41).
|
||||
- **Roles (DBA hardening).** `AddAuditLogHashChain` adds `scadabridge_audit_chainer`
|
||||
(SELECT on `AuditLog`, column-level `UPDATE (ChainSeq, RowHash)`, read/write on the
|
||||
head), grants the purger the head/tombstone rights its purge paths need, and DENYs
|
||||
INSERT/UPDATE/DELETE on both chain tables to `scadabridge_audit_writer`.
|
||||
- **Parquet archival (T2) — deferred to v1.x.** Long-term cold storage of purged
|
||||
monthly partitions as Parquet files (suitable for offline analytics) will be
|
||||
added in a future milestone. T1 and T2 are not shipped as part of M5.
|
||||
added in a future milestone. (T1 shipped later — see above; T2 is still deferred.)
|
||||
- **Site SQLite security.** File permissions: read/write by the ScadaBridge
|
||||
service account only. Not backed up off-machine — site SQLite is a buffer,
|
||||
not a record.
|
||||
@@ -995,8 +1044,9 @@ orphaned entries) and in the CLI's `audit tree` output.
|
||||
- **[CLI (#19)](Component-CLI.md)** — `scadabridge audit query`,
|
||||
`scadabridge audit export`, `scadabridge audit tree --execution-id <guid>`,
|
||||
`scadabridge audit backfill-source-node --sentinel <s> --before <date>`, and
|
||||
`scadabridge audit verify-chain` (no-op placeholder for the deferred hash-chain
|
||||
feature); same permission requirements as the UI.
|
||||
`scadabridge audit verify-chain --month <YYYY-MM>` (hash-chain verification via the
|
||||
`VerifyAuditChain` management command, ScadaBridge#41); same permission requirements
|
||||
as the UI.
|
||||
- **[KPI History (#26)](Component-KpiHistory.md)** — emits `IKpiSampleSource`
|
||||
(`AuditLogKpiSampleSource`, Global) consumed by the KpiHistory recorder (#26),
|
||||
reusing the existing audit-KPI reads; the resulting `totalEventsLastHour` /
|
||||
|
||||
@@ -337,11 +337,15 @@ scadabridge audit verify-chain --month <YYYY-MM>
|
||||
rows where `SourceNode IS NULL` and `OccurredAtUtc < --before`, in batches
|
||||
(`--batch`, default 5000). Admin-only maintenance command. Idempotent.
|
||||
Backed by `POST /api/audit/backfill-source-node`.
|
||||
- `audit verify-chain` — hash-chain verification for the named month.
|
||||
**No-op in v1**: the command is defined so the command tree is stable, but
|
||||
verification only becomes meaningful once the hash-chain ships (see
|
||||
Component-AuditLog.md, Security & Tamper-Evidence). Until then, the server
|
||||
responds with a "verification not yet available" status and the CLI exits 0.
|
||||
- `audit verify-chain --month <YYYY-MM>` (ScadaBridge#41) — verifies the AuditLog
|
||||
hash chain for one UTC calendar month by sending the `VerifyAuditChain` management
|
||||
command over `POST /management` (the one `audit` leaf that is a management contract
|
||||
rather than a REST call). Reports `Intact` / `NotChained` / `Broken`, rows verified,
|
||||
tombstones applied, the partition-purge anchor, the head sequence and unchained rows;
|
||||
on `Broken`, the first break's partition, `ChainSeq`, `EventId` and kind. Exit code
|
||||
`0` intact or not chained, `3` broken, `1` error, `2` authorization failure. Requires
|
||||
`OperationalAudit` and a caller not scoped to specific sites. See
|
||||
Component-AuditLog.md, Security & Tamper-Evidence.
|
||||
|
||||
### Health Commands
|
||||
```
|
||||
@@ -520,7 +524,7 @@ Configuration is resolved in the following priority order (highest wins):
|
||||
- **System.CommandLine**: Command-line argument parsing.
|
||||
- **Microsoft.AspNetCore.SignalR.Client**: SignalR client for the `debug stream` command's WebSocket connection.
|
||||
- **Management Service (#18)**: The CLI hits the central cluster via the existing HTTP Management API (`POST /management`), which dispatches to the ManagementActor. The `scadabridge audit` command group rides a parallel REST surface on the same Host (`GET /api/audit/query` and `GET /api/audit/export`), sharing authentication (bearer token or HTTP Basic) with `/management` but bypassing the actor for read-only, keyset-paged / streaming workloads.
|
||||
- **Audit Log (#23)**: The `scadabridge audit query`, `audit export`, `audit tree`, and `audit backfill-source-node` subcommands target the centralized Audit Log component's REST endpoints (`GET /api/audit/query`, `GET /api/audit/export`, `GET /api/audit/tree`, `POST /api/audit/backfill-source-node`) on the Host's Management API surface; `audit verify-chain` is a client-side no-op today (hash-chain deferred to v1.x). Permission checks (`OperationalAudit`, `AuditExport`, `Admin`) are enforced server-side by `AuditEndpoints`.
|
||||
- **Audit Log (#23)**: The `scadabridge audit query`, `audit export`, `audit tree`, and `audit backfill-source-node` subcommands target the centralized Audit Log component's REST endpoints (`GET /api/audit/query`, `GET /api/audit/export`, `GET /api/audit/tree`, `POST /api/audit/backfill-source-node`) on the Host's Management API surface; `audit verify-chain` sends the `VerifyAuditChain` management command (`POST /management`, ScadaBridge#41). Permission checks (`OperationalAudit`, `AuditExport`, `Admin`) are enforced server-side by `AuditEndpoints`.
|
||||
|
||||
## Interactions
|
||||
|
||||
|
||||
@@ -199,6 +199,7 @@ The two-person authorization workflow for writes through the MxAccess Gateway. B
|
||||
### Audit Log
|
||||
|
||||
- **QueryAuditLog**: Legacy configuration-change audit query (filtered by entity type, user, date range, etc.) routed through `/management`. Gated to the `Admin` role; superseded for the centralized Audit Log component (#23) by the dedicated `/api/audit/*` REST endpoints described below.
|
||||
- **VerifyAuditChain** (ScadaBridge#41): verifies one UTC calendar month (`Month`, `YYYY-MM`) of the central AuditLog hash chain and returns an `AuditChainVerificationResult` (status, rows verified, tombstones applied, purge anchor, head sequence, unchained rows, first break with partition / `ChainSeq` / `EventId` / kind). Gated on the OperationalAudit roles (`Administrator`, `Viewer`); a site-scoped non-Administrator caller is refused (`ManagementUnauthorized`) because a month's chain spans every site. Long-running Ask timeout. Errors: `INVALID_ARGUMENT` (bad month), `CHAIN_BUSY` (a chaining/purge pass held the chain lock past 60 s). Backs `scadabridge audit verify-chain`.
|
||||
|
||||
### Transport (Bundle Import / Export)
|
||||
|
||||
@@ -266,6 +267,7 @@ Every incoming message carries the authenticated user's identity and roles. The
|
||||
- **Verifier** role required for: approving / rejecting a secured write (`ApproveSecuredWriteCommand` / `RejectSecuredWriteCommand`). The no-self-approval rule (`Operator ≠ Verifier`) is enforced in the handler, independent of the role check.
|
||||
- **Any of Operator / Verifier / Administrator** required for: listing / querying secured writes (`ListSecuredWritesCommand`). Read-only, but the history exposes process-sensitive tag values, so it is gated any-of rather than open to every authenticated user (arch-review UA1). Enforced as an any-of gate (`GetRequiredRoles` returns the permitted set; the caller must hold at least one).
|
||||
- **Admin** role additionally required for: server-certificate trust/remove/list (`TrustServerCertCommand` / `RemoveServerCertCommand` / `ListServerCertsCommand`).
|
||||
- **Any of Administrator / Viewer (OperationalAudit)** required for: AuditLog hash-chain verification (`VerifyAuditChainCommand`, ScadaBridge#41); the handler additionally refuses a site-scoped non-Administrator caller.
|
||||
- **Read-only access** (any authenticated role): health summary, health site, site event log queries, parked message queries.
|
||||
|
||||
Unauthorized commands receive an `Unauthorized` response message. Failed authorization attempts are not audit logged (consistent with existing behavior).
|
||||
|
||||
@@ -479,8 +479,8 @@ Sections 10.1–10.4 cover **configuration-database audit** (config-mutating use
|
||||
- **AL-8**: Central retention defaults to **365 days**, enforced by a **monthly partition switch-and-drop** purge — no row-by-row delete.
|
||||
- **AL-9**: The site SQLite Audit Log is purged only when `ForwardState ∈ {Forwarded, Reconciled}` — i.e., a row must be either confirmed-forwarded *or* confirmed-reconciled before it can be removed. A central outage therefore **cannot cause audit loss at sites**.
|
||||
- **AL-10**: The Central UI exposes an **Audit Log page** with a cross-channel filter (by site, target, status, time range, correlation ID), plus **drill-ins from existing operational pages** (Site Calls, Notification Outbox, Inbound API).
|
||||
- **AL-11**: Append-only semantics are **enforced via DB roles** (no UPDATE/DELETE granted on the `AuditLog` table to application accounts); a **tamper-evidence hash chain is deferred to v1.x**.
|
||||
- **AL-12**: The CLI provides a `scadabridge audit` command group for query, export, and hash-chain verification (verify-chain becomes operational once AL-11's hash chain ships) against the central Audit Log.
|
||||
- **AL-11**: Append-only semantics are **enforced via DB roles** (no UPDATE/DELETE granted on the `AuditLog` table to application accounts); a **tamper-evidence hash chain** (per-month SHA-256 chain, default off) shipped later by ScadaBridge#41.
|
||||
- **AL-12**: The CLI provides a `scadabridge audit` command group for query, export, and hash-chain verification (verify-chain reports the first break of a month's hash chain) against the central Audit Log.
|
||||
|
||||
## 11. Health Monitoring
|
||||
|
||||
|
||||
@@ -2050,13 +2050,24 @@ scadabridge --url <url> audit verify-chain --month <YYYY-MM>
|
||||
|
||||
| Option | Required | Default | Description |
|
||||
|--------|----------|---------|-------------|
|
||||
| `--month` | yes | — | Month to verify, `YYYY-MM` (e.g. `2026-05`) |
|
||||
| `--month` | yes | — | UTC calendar month to verify, `YYYY-MM` (e.g. `2026-05`) |
|
||||
|
||||
> **v1 no-op.** Hash-chain tamper-evidence is not enabled in this release (T1
|
||||
> deferred to v1.x). The subcommand validates the `--month` argument and prints a
|
||||
> notice pointing at the v1.x roadmap in `Component-AuditLog.md`; it exits `0`
|
||||
> without contacting the server. The command exists now so scripts and operator
|
||||
> habits do not need to change when tamper-evidence ships.
|
||||
Sends the `VerifyAuditChain` management command (ScadaBridge#41). The server
|
||||
recomputes every link of that month's AuditLog hash chain and reports the first
|
||||
break, if any: partition, `ChainSeq`, `EventId` and kind (`HashMismatch` = edited or
|
||||
reordered row, `MissingRow` = deleted row, `HeadMismatch` = rows deleted from the
|
||||
end, `DuplicateSequence`, `IncompleteLink`). `--format table` prints a summary;
|
||||
`json` prints the result object.
|
||||
|
||||
| Exit code | Meaning |
|
||||
|-----------|---------|
|
||||
| `0` | `Intact`, or `NotChained` (nothing chained for that month yet) |
|
||||
| `3` | `Broken` — tamper evidence; see `firstBreak` |
|
||||
| `1` | Request error (bad month, `CHAIN_BUSY`, server error) |
|
||||
| `2` | Authorization failure (needs `Administrator` or `Viewer`, not site-scoped) |
|
||||
|
||||
The chain only exists once `AuditLog:HashChain:Enabled` is on for the central nodes
|
||||
(default off); see `Component-AuditLog.md`, Security & Tamper-Evidence.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user