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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ how many clusters were excluded for insufficient data. A score built from
six clusters is a weaker claim than one built from sixty, and the report
says which one you are looking at.

A score is also only as good as what the scan could see. A scan that could
not observe a cluster says so and is not graded, and a finding is never
produced from a read that failed.

Fleetsweeper is therefore not a replacement for policy engines, CIS
benchmarks, or vulnerability scanning. Those check clusters against
absolute rules, and they are the tools that catch the failure mode above.
Expand Down
54 changes: 54 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,60 @@ stability contract and the GitHub release notes for the full feature list.
5. Verify `kubectl logs deploy/fleetsweeper | grep migrate` shows no errors
and `/readyz` returns 200.

## v0.14.0

No schema migration. Reports, scores, and a few API shapes change on the
first scan after the upgrade, because a scan that could not see something no
longer reports it as healthy.

- **Reports can be ungraded.** The Fleet Score carries a `graded` flag and a
`coverage` object (`expected_runs`, `observed_runs`, `blind_runs`,
`blind_clusters`, `fraction`). A score is graded only when every cluster has
an observed scanner run and at least 80 percent of expected runs are
observed. An ungraded score has an empty `grade`, and its dashboard and
brief lead with the coverage gap.

- **Clusters can be `unknown`.** A cluster that looks healthy but had a
scanner run that errored or never ran reads `unknown` instead of `healthy`.
Its cluster score is charged like degraded. The Fleet Score does not charge
unknown clusters, the coverage gate covers them.

- **Metrics averages are optional.** `avg_cpu_percent`, `avg_memory_percent`,
`max_cpu_percent`, and `max_memory_percent` are omitted when no node
resolved a usage percentage, instead of reading 0. Clients that read them
as numbers must handle absence.

- **Ack bodies carry only context.** `POST /api/findings/{fingerprint}/ack`
and `POST /api/alerts/{fingerprint}/ack` accept `ack_by`, `reason`, and
`snooze_until`. A body that names `cluster`, `scanner`, or `title` is
rejected with 400 instead of being ignored. A fingerprint that no current
finding carries returns 404. A fleet or clusterless finding needs a key
with `*` scope, and so does an ack on an alert with no cluster.

- **Group management is admin only.** `POST /api/groups` and
`DELETE /api/groups/{name}` refuse operators, including operators with `*`
scope.

- **Non-root counts use the effective user.** The workload-security scanner
adds `baseline_containers` and `run_as_non_root_containers`, and renames
`unknown_root_containers` to `run_as_unknown_containers`. A container is
non-root when its effective `runAsUser` is above 0, or when `runAsNonRoot`
is true and no user is set. An effective `runAsUser` of 0 is root even under
`runAsNonRoot: true`, and `runAsNonRoot: false` alone is unknown. Scans
stored before the upgrade contribute 0 to the new baseline fractions.

- **Privileged namespaces count as unenforced.** A namespace whose Pod
Security enforce label is `privileged` now counts in `unenforced_count`, not
`enforced_count`. The `security/psa-unenforced` finding charges only
namespaces with no enforce label. Namespaces set to `privileged` are listed
in the finding and not charged.

- **Two new Prometheus series.** `fleetsweeper_fleet_score_graded` is 1 when
the scan is graded and 0 otherwise. `fleetsweeper_coverage_ratio` is the
share of expected scanner runs that observed the fleet. `fleetsweeper_fleet_score`
is not emitted while the scan is ungraded, so alert rules that read it
should also check the graded series.

## v0.13.0

No schema migration. Findings, counts, and some outputs change on the first
Expand Down
4 changes: 2 additions & 2 deletions deploy/helm/fleetsweeper/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ apiVersion: v2
name: fleetsweeper
description: Multi-cluster Kubernetes fleet comparison and audit
type: application
version: 0.9.0
appVersion: "0.9.0"
version: 0.14.0
appVersion: "0.14.0"
home: https://github.com/dcadolph/fleetsweeper
sources:
- https://github.com/dcadolph/fleetsweeper
Expand Down
6 changes: 3 additions & 3 deletions docs/operator/rbac.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ curl -X POST -H "Authorization: Bearer $BOOTSTRAP" \
https://fleetsweeper.example.com/api/admin/keys
```

The response contains the raw token. Save it now; the server stores only
The response contains the raw token. Save it now. The server stores only
its SHA-256 hash and cannot reveal it again.

## Listing and revoking
Expand Down Expand Up @@ -118,7 +118,7 @@ Filters:

Audit entries include actor identity, method, path, status, duration, and
a short error excerpt for failures. Read-only requests are not audited to
keep the volume manageable; they are still captured by the request log at
keep the volume manageable. They are still captured by the request log at
info level.

## Whoami
Expand All @@ -135,7 +135,7 @@ A fresh deployment looks like this:
2. Start fleetsweeper. The token grants admin access.
3. Mint a scoped operator key per pipeline:
`fleetsweeper apikey create --name ci-prod --role operator --scope 'group:prod'`
4. Hand the scoped key to that pipeline; rotate the bootstrap token to
4. Hand the scoped key to that pipeline. Rotate the bootstrap token to
long-term storage as the emergency credential.
5. Use `GET /api/admin/audit` to confirm the pipelines are using their own
keys and the bootstrap token is no longer being used in normal traffic.
2 changes: 1 addition & 1 deletion docs/production-readiness.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ What Fleetsweeper ships ready for the box.
| ---------------------- | -------------- |
| HA backend | SQLite default, PostgreSQL via `--db-driver=postgres` |
| Multi-replica safe | Kubernetes Lease-based leader election (`coordination.k8s.io/v1`) |
| Multi-tenant RBAC | Scoped API keys (`admin`, `operator`, `viewer`) with cluster scope (`*`, names, or `group:<n>`) |
| Multi-tenant RBAC | Scoped API keys (`admin`, `operator`, `viewer`) with cluster scope (`*`, names, or `group:<n>`) on writes. Reads are not scoped |
| Audit log | Every mutating request captured. Queryable at `GET /api/admin/audit`. Retention via `--audit-retention` |
| Declarative ops | `ClusterScan` CRD with an in-process reconciler |
| GitOps integrations | `FleetDriftReport` CR plus `PolicyReport` (wgpolicyk8s.io) |
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/google/go-containerregistry v0.21.5
github.com/jackc/pgx/v5 v5.9.2
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
go.opentelemetry.io/otel v1.43.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0
Expand Down Expand Up @@ -73,7 +74,6 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/vbatts/tar-split v0.12.2 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
Expand Down
5 changes: 3 additions & 2 deletions internal/explain/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,9 @@ func topics() []Topic {
Remediation: []string{
"Drop privileged: true unless the workload genuinely needs full host access.",
"Add seccompProfile: { type: RuntimeDefault } as a baseline.",
"Move legitimately-privileged workloads into a dedicated namespace with " +
"Pod Security enforce=privileged for clarity.",
"Move legitimately-privileged workloads into a dedicated namespace labeled " +
"enforce=privileged. That label enforces nothing, so the namespace is " +
"listed by security/psa-unenforced but not charged.",
},
Related: []string{"security", "rbac-audit"},
},
Expand Down
Loading