Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/package-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,54 @@ jobs:
bash packaging/tests/run-upgrade-from-ga-test.sh \
"${{ matrix.distro }}" "${{ matrix.kind }}" v0.6.0

# The package manager enforces the engine/corpus pairing (spec
# release-upgrade C-06). AC-09 is verified in Go CI against the resolvers;
# this runs the same scenarios in real containers, scriptlets included. A Kensa engine older than its corpus
# cannot load it and the service starts anyway with every scan failing, so
# a rules-only upgrade beside an older openwatch must be refused while the
# coordinated upgrade, an openwatch-only upgrade and a fresh install
# succeed. The previous GA predates the engine provide, which is the state
# the refusal has to hold against.
kensa-rules-compat:
name: kensa-rules compat ${{ matrix.distro }}
needs: build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- { distro: 'rockylinux:9', kind: rpm }
- { distro: 'ubuntu:24.04', kind: deb }
steps:
- uses: actions/checkout@v7
- uses: actions/download-artifact@v4
with:
name: packages
path: dist
- name: fetch the previous GA
env:
GH_TOKEN: ${{ github.token }}
run: |
mkdir -p old new
if [ "${{ matrix.kind }}" = rpm ]; then
gh release download v0.7.1 -D old -p 'openwatch-*.x86_64.rpm' -p 'kensa-rules-*.noarch.rpm'
cp dist/openwatch-*.x86_64.rpm dist/kensa-rules-*.noarch.rpm new/
else
gh release download v0.7.1 -D old -p 'openwatch_*_amd64.deb' -p 'kensa-rules_*_all.deb'
cp dist/openwatch_*_amd64.deb dist/kensa-rules_*_all.deb new/
fi
ls old new
- uses: actions/setup-go@v6
with:
go-version: '1.26.6'
- name: engine and corpus pairing is enforced
env:
OPENWATCH_KENSA_COMPAT_IMAGE: ${{ matrix.distro }}
OPENWATCH_KENSA_COMPAT_KIND: ${{ matrix.kind }}
run: |
OPENWATCH_KENSA_COMPAT_OLD_DIR="$PWD/old" OPENWATCH_KENSA_COMPAT_NEW_DIR="$PWD/new" \
go test -count=1 -v -run 'TestUpgrade_EngineCorpusPairingInContainers' ./packaging/tests/

upgrade:
name: Package upgrade (rpm -U auto-migrate)
runs-on: ubuntu-latest
Expand Down
12 changes: 6 additions & 6 deletions .secrets.baseline

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

87 changes: 74 additions & 13 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Upgrade notes
**Upgrade notes.** Read these before upgrading.

- **Upgrading signs everyone out.** Migration 0065 revokes every live session
and refresh token. Access tokens issued before it carry no session binding
Expand All @@ -29,6 +29,26 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- **API tokens with no owner stop working.** List and replace them before
upgrading; the query is under Security below. (#881)

**Known limitations.** These ship in this release.

- **Changing your own password does not sign out your other sessions.** They
stay valid until their absolute limit, 12 hours by default. To end them, ask
an administrator to reset your password. (CP `bugs/OW-072`)
- **Settings shows only the current session.** It cannot list or revoke other
sessions.
- **A Bearer-only logout revokes nothing.** An access token presented alone
stays valid until it expires, 30 minutes after issue, and a refresh token
returned in the login body has no revoke route. (CP `bugs/OW-062`)
- **The Kensa-published `kensa-rules` package is not checked.** Kensa
publishes a package of the same name and install path that does not
declare the engine it needs, and `openwatch` accepts either package. On
`openwatch` 0.8.0-rc.5 or earlier, a 0.10.0 or newer corpus from any source
makes every scan fail, and no package prevents it. Install `kensa-rules`
only from the OpenWatch release that matches your `openwatch`, and do not
configure a Kensa package repository on an OpenWatch host. The upgrade
runbook shows how to tell the packages apart and restore OpenWatch's.
(CP `bugs/OW-081`)

### Security

- **Disabling, deleting, or resetting the password of a user ends every
Expand Down Expand Up @@ -61,6 +81,59 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

### Changed

- **Kensa 0.10.0.** The rule corpus grows from 769 to 779 rules and gains two
framework keys, `nist_800_171` (NIST SP 800-171 Rev 2, cited at objective
level such as `3.1.11[b]`) and `cmmc_l2`. Each is referenced by 324 rules.

**Framework names now come from Kensa everywhere.** The lens chips, the
rule library and scan detail, the report picker and every new report's
scope label (cover, OSCAL title, file name) use one vocabulary. NIST
800-53, "NIST SP 800-171 Rev 2" and "CMMC Level 2" are distinct wherever
they appear; reports used to call both NIST frameworks "NIST" and CMMC
"CMMC". Some familiar names change: "CIS RHEL 9" is now "CIS (RHEL 9)" and
"PCI DSS 4" is "PCI DSS 4.0". Ubuntu benchmarks read "CIS (ubuntu22)" until
Kensa formats Ubuntu versions (CP `features/KN-OW-023`). Reports generated before the upgrade keep the
names they were generated with, and signed report content, which carries
the exact framework key, is unchanged.

**Upgrade `openwatch` and `kensa-rules` together.** An earlier `openwatch`
cannot load the 0.10.0 corpus: the service starts and every scan fails.
`openwatch` now declares the Kensa engine it links, and `kensa-rules`
requires an engine at least as new as itself. With the `kensa-rules`
package from an OpenWatch release, a rules-only upgrade onto an older
`openwatch` is refused with nothing changed, by `dnf`, `rpm -U`, `apt` and
a bare `dpkg -i`; both packages in one transaction are accepted. The
`kensa-rules` package Kensa publishes does not carry this check (see Known
limitations). Rolling `openwatch` back now means rolling `kensa-rules`
back in the same command; the upgrade runbook shows how (CP
`bugs/OW-081`).

**Verdicts change on existing hosts, so scores can move after the first scan
on this release.** The change comes from the rules, not the hosts:

- `no-unauthorized-accounts` passed every host without comparing anything.
It now reports skipped until `authorized_local_accounts` is declared.
- `shell-timeout` fails RHEL hosts set between 601 and 900 seconds and
requires `TMOUT` to be readonly on RHEL. It absorbs `shell-timeout-600`
and `shell-idle-timeout-tmout`, whose old verdicts leave the current score
after each host's next completed scan.
- Rules that passed without checking now report a real verdict:
`security-updates-installed`, `nftables-default-deny`,
`journald-to-rsyslog`, `selinux-user-mapping` and
`firewalld-loopback-source`.
- Eight audit and session rules, and `no-unauthorized-accounts`, now run on
RHEL 8 instead of reporting not applicable.

Eight new scan variables ship with no default, and seven rules report
skipped until theirs is declared. Settings marks them "Configure me",
alongside the three placeholder defaults it already marked, and the
scanning guide lists them under "Scan variables". Values are not type
checked when saved (CP `bugs/OW-080`).

The remediation "NIST" projected lift counts NIST SP 800-53 rules only.
Matching every `nist` key would have folded in the new 800-171 mapping,
quoting a NIST gain for 19 rules that are not in 800-53.

- **When an outcome cannot be confirmed, OpenWatch says so.** A sign-in,
refresh, logout or administrative change whose commit result is unknown
answers 503 `server.error`, not retryable, and claims neither success nor
Expand Down Expand Up @@ -104,18 +177,6 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
until it expires and names the remedy that works: an administrator can
end it by resetting the user's password. (#880)

### Known limitations

- **Changing your own password does not sign out your other sessions.** They
stay valid until their absolute limit, 12 hours by default. To end them, ask
an administrator to reset your password. (CP `bugs/OW-072`)
- **Settings shows only the current session.** It cannot list or revoke other
sessions.
- **A Bearer-only logout revokes nothing.** An access token presented alone
stays valid until it expires, 30 minutes after issue, and a refresh token
returned in the login body has no revoke route. (CP `bugs/OW-062`)


## [0.8.0-rc.5] Eyrie (2026-09-19)

`v0.8.0-rc.4` built, passed every machine gate, and its assets were
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ OpenWatch, it is a query: answered in seconds, backed by machine-verifiable
evidence, exportable as CSV, JSON, PDF or OSCAL.

OpenWatch is a continuous compliance platform for Linux fleets under CIS,
STIG, NIST 800-53 and PCI DSS. It connects to your servers over SSH, runs the
769-rule [Kensa](https://github.com/Hanalyx/kensa) corpus, and keeps posture
STIG, NIST 800-53, NIST 800-171, CMMC Level 2 and PCI DSS. It connects to your
servers over SSH, runs the 779-rule [Kensa](https://github.com/Hanalyx/kensa)
corpus, and keeps posture
as a timeline: what is passing now, what was passing last Tuesday, what
drifted since your last assessment, and what needs attention before the next
one. **[Read the introduction](docs/guides/INTRODUCTION.md)** for what it does
Expand All @@ -31,7 +32,7 @@ and how it is built.
> React 19 + TanStack frontend (`frontend/`), PostgreSQL-only. The current
> version is `0.8.0-rc.5`, on the general-availability line that opened with `0.2.0`.

![OpenWatch Host Management: a fleet of RHEL and Ubuntu hosts with per-host compliance scores against the 769-rule Kensa corpus](docs/images/host-management.png)
![OpenWatch Host Management: a fleet of RHEL and Ubuntu hosts with per-host compliance scores against the Kensa corpus](docs/images/host-management.png)

## Deploy in 10 minutes

Expand Down Expand Up @@ -86,7 +87,7 @@ model. Then three starting points: an **operator** reads

## Part of the Hanalyx Compliance Platform

OpenWatch is the compliance operating system: the dashboard, the scheduler, the governance layer. **[Kensa](https://github.com/Hanalyx/kensa)** is the compliance engine underneath: 769 rules, 29 remediation mechanisms, automatic rollback, all over SSH.
OpenWatch is the compliance operating system: the dashboard, the scheduler, the governance layer. **[Kensa](https://github.com/Hanalyx/kensa)** is the compliance engine underneath: 779 rules, 29 remediation mechanisms, automatic rollback, all over SSH.

If you want a CLI that integrates into scripts and pipelines, start with Kensa. If you want a platform for your team with a dashboard, scheduling, and audit workflows, start here.

Expand Down
2 changes: 1 addition & 1 deletion THIRD-PARTY-NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ OpenWatch and their licenses. It is generated; see "Regeneration" below.
| `modernc.org/mathutil` | v1.7.1 | BSD |
| `modernc.org/memory` | v1.11.0 | BSD |
| `modernc.org/sqlite` | v1.53.0 | BSD |
| `github.com/Hanalyx/kensa` | v0.9.0 | BSL-1.1 |
| `github.com/Hanalyx/kensa` | v0.10.0 | BSL-1.1 |
| `github.com/apapsch/go-jsonmerge/v2` | v2.0.0 | MIT |
| `github.com/boombuler/barcode` | v1.1.0 | MIT |
| `github.com/BurntSushi/toml` | v1.6.0 | MIT |
Expand Down
45 changes: 36 additions & 9 deletions api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2002,9 +2002,10 @@ paths:
uses; unused ones are not listed). Each entry carries the
built-in default, the operator override when set, the count
and ids of affected rules, and the configure_me flag marking
organization-specific placeholder defaults
(rsyslog_remote_server, chrony_ntp_pool, banner_text) that
operators should always review. Spec api-system-scan-config.
variables the operator has to decide: the organization-specific
placeholder defaults (rsyslog_remote_server, chrony_ntp_pool,
banner_text) and every variable the corpus ships with no value,
whose rule skips until it is declared. Spec api-system-scan-config.
responses:
'200':
description: Corpus-used variables sorted by name
Expand Down Expand Up @@ -5502,9 +5503,16 @@ components:

HostComplianceFramework:
type: object
required: [framework_id, rule_count, passing, failing, score_pct, envelope]
required: [framework_id, label, rule_count, passing, failing, score_pct, envelope]
properties:
framework_id: {type: string}
label:
type: string
description: >-
Display label for framework_id, taken from Kensa's framework
vocabulary ("NIST SP 800-171 Rev 2", "CIS (RHEL 9)"). An id Kensa
does not know is returned as it is. "All rules" on the overall
entry, whose framework_id is "all".
rule_count:
type: integer
format: int64
Expand Down Expand Up @@ -6360,7 +6368,7 @@ components:
description: The referencing rule ids, sorted
configure_me:
type: boolean
description: Organization-specific placeholder default the operator should always review
description: The built-in value cannot be right for a real site (a placeholder, or no value at all), so the operator has to set it

ScanVariableOverrides:
type: object
Expand Down Expand Up @@ -6882,12 +6890,17 @@ components:

ReportFramework:
type: object
required: [framework, rule_count]
required: [framework, label, rule_count]
description: A framework lens present in the fleet, with its rule count.
properties:
framework:
type: string
description: The framework_refs key (e.g. cis_rhel9_v2.0.0).
description: The framework_refs key (e.g. cis_rhel9).
label:
type: string
description: >-
Display label for the key, from Kensa's framework vocabulary. An
id Kensa does not know is returned as it is.
rule_count:
type: integer
description: Distinct rules mapped to this framework across the fleet.
Expand Down Expand Up @@ -7048,12 +7061,19 @@ components:

RuleList:
type: object
required: [rules, total]
required: [rules, total, framework_labels]
properties:
rules:
type: array
items: {$ref: '#/components/schemas/RuleListItem'}
total: {type: integer, description: total rules in the library}
framework_labels:
type: object
additionalProperties: {type: string}
description: >-
Display label, from Kensa's framework vocabulary, for every
framework id used as a framework_refs key in this response. An id
Kensa does not know maps to itself.

ScanRuleResult:
type: object
Expand Down Expand Up @@ -7081,12 +7101,19 @@ components:

ScanDetail:
type: object
required: [scan, results]
required: [scan, results, framework_labels]
properties:
scan: {$ref: '#/components/schemas/ScanSummary'}
results:
type: array
items: {$ref: '#/components/schemas/ScanRuleResult'}
framework_labels:
type: object
additionalProperties: {type: string}
description: >-
Display label, from Kensa's framework vocabulary, for every
framework id used as a framework_refs key in this response. An id
Kensa does not know maps to itself.

ScanCheckEvidence:
type: object
Expand Down
8 changes: 6 additions & 2 deletions docs/guides/INSTALLATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -439,8 +439,12 @@ Install **both** files in one transaction. `openwatch` declares a hard
dependency on `kensa-rules`: the rule corpus the scan engine loads from
`/usr/share/kensa/rules`. Installing `openwatch` alone fails the dependency
check (by design: a corpus-less node cannot scan). `kensa-rules` is `noarch`
and versioned on the Kensa content line (for example `0.8.0`), independent of the
platform version, so the rules can update without re-releasing OpenWatch.
and versioned on the Kensa module OpenWatch links (for example `0.10.0`),
independent of the platform version. It requires an `openwatch` whose Kensa
engine is at least that version, so a newer corpus arrives with the
`openwatch` release that links it. Install `kensa-rules` from the same
OpenWatch release as `openwatch`; the upgrade runbook explains why a
`kensa-rules` package from a Kensa release is not checked.

Use the filenames you downloaded (`aarch64` for the arm64 openwatch RPM; the
`kensa-rules` package is the same `noarch` file for every arch). Installing the
Expand Down
Loading
Loading