You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by vi...@apache.org on 2026/06/05 23:25:52 UTC
(superset-kubernetes-operator) branch main updated: chore: image attestations, downgrade warning, release hygiene (#120)
This is an automated email from the ASF dual-hosted git repository.
villebro pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/superset-kubernetes-operator.git
The following commit(s) were added to refs/heads/main by this push:
new 49ca48b chore: image attestations, downgrade warning, release hygiene (#120)
49ca48b is described below
commit 49ca48b2d3863e0e9cd57ba7d321e2a6283447c0
Author: Ville Brofeldt <33...@users.noreply.github.com>
AuthorDate: Fri Jun 5 16:25:46 2026 -0700
chore: image attestations, downgrade warning, release hygiene (#120)
---
.gitattributes | 6 ++++++
.github/workflows/release.yml | 2 ++
.rat-excludes | 4 ++++
docs/reference/releases.md | 14 ++++++++++++++
docs/reference/security.md | 7 +++++--
docs/user-guide/lifecycle.md | 7 +++++++
internal/controller/lifecycle.go | 13 +++++++++++++
internal/controller/lifecycle_pipeline_test.go | 21 +++++++++++++++++++++
8 files changed, 72 insertions(+), 2 deletions(-)
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..6c993ab
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,6 @@
+# Keep editor/agent context files in-tree for local development, but exclude
+# them from the `git archive` source release tarball. AGENTS.md is the canonical
+# (vendor-neutral) file and ships in the release; CLAUDE.md and GEMINI.md are
+# symlinks to it and are tool-specific clutter in an official ASF source release.
+CLAUDE.md export-ignore
+GEMINI.md export-ignore
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index eafb96b..1d5bf63 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -72,6 +72,8 @@ jobs:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+ sbom: true
+ provenance: mode=max
cache-from: type=gha
cache-to: type=gha,mode=max
diff --git a/.rat-excludes b/.rat-excludes
index dc51770..e2e78e2 100644
--- a/.rat-excludes
+++ b/.rat-excludes
@@ -19,6 +19,7 @@
# Git
.*\.gitignore
+.*\.gitattributes
# Go
.*go\.mod
@@ -45,6 +46,9 @@
.*PULL_REQUEST_TEMPLATE\.md
.*ISSUE_TEMPLATE/.*\.md
.*\.claude.*
+# Editor/agent context symlinks → AGENTS.md (which carries the header itself)
+CLAUDE\.md
+GEMINI\.md
.*rat-results\.txt
.*Dockerfile\.cross
.*codecov\.yml
diff --git a/docs/reference/releases.md b/docs/reference/releases.md
index f101c72..5194d0c 100644
--- a/docs/reference/releases.md
+++ b/docs/reference/releases.md
@@ -30,5 +30,19 @@ releases.
- Initial release.
+### Known limitations
+
+- **Websocket server is experimental.** The websocket server is not yet well
+ supported and is pending security hardening; it is not recommended for
+ production use.
+- **Downgrade protection requires semver image tags.** Downgrades are detected
+ and blocked only when both image tags are valid semver. Non-semver tags
+ (`latest`, date stamps, digest pins) cannot be ordered, so the operator emits a
+ `VersionComparisonSkipped` warning and proceeds without blocking. See
+ [Lifecycle](../user-guide/lifecycle.md).
+- **Task failure messages may include credential fragments.** Lifecycle task
+ failure output is truncated into `status` and could contain fragments of task
+ stdout, including credentials. See [security.md](security.md).
+
[Unreleased]: https://github.com/apache/superset-kubernetes-operator/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/apache/superset-kubernetes-operator/releases/tag/v0.1.0
diff --git a/docs/reference/security.md b/docs/reference/security.md
index f7b8ec7..fd7979b 100644
--- a/docs/reference/security.md
+++ b/docs/reference/security.md
@@ -414,8 +414,11 @@ The release pipeline produces signed multi-architecture artifacts:
so its digest refreshes are inherently un-soaked. This residual exposure is accepted
and bounded by the minimal distroless base and by keyless Cosign signing of the
image this project itself publishes.
-- **Future work:** SBOM and SLSA build provenance generation are tracked as
- enhancements for later releases.
+- **SBOM & provenance:** Each published image carries a per-platform Software
+ Bill of Materials (SPDX) and SLSA build provenance, attached as in-toto
+ attestations by BuildKit at build time. Inspect them with
+ `docker buildx imagetools inspect ghcr.io/apache/superset-kubernetes-operator:<tag> --format '{{ json .SBOM }}'`
+ (or `.Provenance`), or with `cosign download attestation`.
## What Is Generally Out of Scope
diff --git a/docs/user-guide/lifecycle.md b/docs/user-guide/lifecycle.md
index 341665a..c4c02b7 100644
--- a/docs/user-guide/lifecycle.md
+++ b/docs/user-guide/lifecycle.md
@@ -178,6 +178,13 @@ The operator also performs semver comparison on image tags and blocks downgrades
to prevent accidental database corruption. A blocked downgrade sets
`status.phase: Blocked` — revert the image tag to resolve.
+Downgrade protection requires semantically comparable (semver) image tags.
+Non-semver tags — such as `latest`, date stamps, or digest pins — cannot be
+ordered, so the operator cannot detect a downgrade and lifecycle tasks proceed
+normally. In that case it emits a `VersionComparisonSkipped` warning event
+rather than blocking. Use semver tags in Staging and Production if you rely on
+downgrade protection.
+
## Drain Behavior
Each task declares whether it requires components to be drained (scaled to zero)
diff --git a/internal/controller/lifecycle.go b/internal/controller/lifecycle.go
index ee2da2d..7978613 100644
--- a/internal/controller/lifecycle.go
+++ b/internal/controller/lifecycle.go
@@ -449,6 +449,19 @@ func (r *SupersetReconciler) checkUpgradeGates(
}
contextMatches := upgradeContextMatches(superset.Status.Lifecycle.Upgrade, oldTag, newTag, direction, approvalToken)
+
+ // Non-semver tags (e.g. "latest", date stamps, digest pins) cannot be
+ // ordered, so downgrade protection does not apply — a downgrade expressed
+ // with such tags would run forward-only migrations against an older image
+ // undetected. Surface this once per distinct image transition so operators
+ // can intervene; the !contextMatches guard prevents per-reconcile spam.
+ if direction == DirectionUnknown && !contextMatches {
+ log.Info("Image version change could not be compared (non-semver tags); downgrade protection skipped",
+ "from", oldTag, "to", newTag)
+ r.Recorder.Eventf(superset, nil, corev1.EventTypeWarning, "VersionComparisonSkipped", "Lifecycle",
+ "Cannot compare image tags %q and %q (non-semver); downgrade protection does not apply", oldTag, newTag)
+ }
+
if !contextMatches {
superset.Status.Lifecycle.Upgrade = &supersetv1alpha1.UpgradeContext{
FromVersion: oldTag,
diff --git a/internal/controller/lifecycle_pipeline_test.go b/internal/controller/lifecycle_pipeline_test.go
index 438fa44..472de04 100644
--- a/internal/controller/lifecycle_pipeline_test.go
+++ b/internal/controller/lifecycle_pipeline_test.go
@@ -349,4 +349,25 @@ func TestCheckUpgradeGates(t *testing.T) {
assert.Equal(t, "2.0.0", s.Status.Lifecycle.Upgrade.FromVersion)
assert.Equal(t, "3.0.0", s.Status.Lifecycle.Upgrade.ToVersion)
})
+
+ t.Run("non-semver tags proceed but emit a warning", func(t *testing.T) {
+ rec := events.NewFakeRecorder(10)
+ nr := &SupersetReconciler{Recorder: rec}
+ s := &supersetv1alpha1.Superset{
+ ObjectMeta: metav1.ObjectMeta{Name: "test"},
+ Status: supersetv1alpha1.SupersetStatus{Lifecycle: &supersetv1alpha1.LifecycleStatus{}},
+ }
+ // "latest" → "main" cannot be ordered: not a downgrade, so not gated.
+ _, gated := nr.checkUpgradeGates(ctx, s, true, "apache/superset:latest", "apache/superset:main")
+ assert.False(t, gated)
+ assert.NotEqual(t, lifecyclePhaseBlocked, s.Status.Lifecycle.Phase)
+ require.NotNil(t, s.Status.Lifecycle.Upgrade)
+ assert.Equal(t, string(DirectionUnknown), s.Status.Lifecycle.Upgrade.Direction)
+ select {
+ case ev := <-rec.Events:
+ assert.Contains(t, ev, "VersionComparisonSkipped")
+ default:
+ t.Fatal("expected a VersionComparisonSkipped warning event")
+ }
+ })
}