From 06f9e38613746e6b06986ac2e4a14536460f2ff3 Mon Sep 17 00:00:00 2001 From: GatewayJ <18332154+GatewayJ@users.noreply.github.com> Date: Sat, 22 Aug 2026 22:48:12 +0800 Subject: [PATCH] chore(release): prepare operator 0.0.6 --- .github/actions/setup/action.yml | 2 +- .github/workflows/ci.yml | 3 + .github/workflows/helm-package.yml | 3 +- CHANGELOG.md | 120 ++++++++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- Dockerfile | 7 +- Makefile | 9 +- README.md | 2 +- deploy/rustfs-operator/Chart.yaml | 4 +- deploy/rustfs-operator/README.md | 4 +- .../templates/console-deployment.yaml | 2 +- .../rustfs-operator/templates/deployment.yaml | 2 +- deploy/rustfs-operator/values.yaml | 3 +- docs/operator-user-guide.md | 2 +- docs/operator-user-guide.zh-CN.md | 2 +- e2e/Cargo.lock | 2 +- examples/README.md | 4 +- scripts/check-release-metadata.sh | 150 ++++++++++++++++++ src/main.rs | 8 +- 20 files changed, 312 insertions(+), 21 deletions(-) create mode 100644 CHANGELOG.md create mode 100755 scripts/check-release-metadata.sh diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 0cb356ae..ed92d534 100755 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -68,4 +68,4 @@ runs: components: rustfmt, clippy - name: Install cargo-nextest - uses: taiki-e/install-action@cargo-nextest \ No newline at end of file + uses: taiki-e/install-action@cargo-nextest diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e1680f2c..bc4ed7a8 100755 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,6 +99,9 @@ jobs: with: version: 3.21.3 + - name: Check release metadata + run: make release-metadata-check + - name: Run tests run: | cargo nextest run --all --no-tests pass diff --git a/.github/workflows/helm-package.yml b/.github/workflows/helm-package.yml index 5ed89922..0ceab97e 100644 --- a/.github/workflows/helm-package.yml +++ b/.github/workflows/helm-package.yml @@ -85,7 +85,8 @@ jobs: set -eux helm package ./deploy/rustfs-operator \ --destination deploy/rustfs-operator/ \ - --version "${{ steps.version.outputs.chart_version }}" + --version "${{ steps.version.outputs.chart_version }}" \ + --app-version "${{ steps.version.outputs.chart_version }}" - name: Upload helm package as artifact uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..4c552463 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,120 @@ + + +# Changelog + +All notable changes to RustFS Operator are documented in this file. The format is based on +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and releases follow +[Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.0.6] - 2026-08-22 + +### Added + +- Restricted Pod Security defaults for generated RustFS workloads. +- Configurable Kubernetes cluster DNS domains and generated TLS SAN coverage. +- Kubernetes STS support with PolicyBinding authorization and managed or external TLS. +- OpenShift installation support that delegates UID and FSGroup selection to SCC admission. +- Tenant credential, RPC authentication, KMS, certificate, and provisioning lifecycle validation. + +### Changed + +- Removed legacy Tenant workload Roles and RoleBindings and disabled automatic ServiceAccount token + mounting for generated RustFS workloads. +- Changed `sts.tls.auto` from `true` to `false`; installations must now provide the STS TLS Secret + unless Operator-managed certificate generation is explicitly enabled. +- Restricted Console to one replica with a `Recreate` deployment strategy because sessions are + process-local. Console restarts and session Secret rotation invalidate active sessions. +- Tightened validation for credentials, security contexts, public TLS SANs, pool volume counts, and + immutable PVC template fields. +- Required an explicit runtime-image acknowledgement when a Tenant overrides the default RustFS + image. +- Defaulted chart-managed Operator and Console images to the immutable chart `appVersion` instead + of the mutable `latest` tag. + +### Fixed + +- Made repeated blocked status updates idempotent and hardened leader-election loss handling. +- Tolerated transient node lookup failures while preserving Pod cleanup safety. +- Protected existing RustFS users during provisioning reconciliation. +- Corrected monitoring responses wrapped by the RustFS API. +- Corrected STS SigV4 query encoding, bounded session duration, and rotated managed TLS certificates. +- Revoked Console sessions on logout and prevented pool volume-count overflow. +- Added finalizer RBAC required by Kubernetes and OpenShift admission. + +### Security + +- Applied authentication to an explicit protected Console API router instead of relying on a + fail-open path allowlist. +- Added admission limits for unauthenticated Console login and STS requests. +- Required cryptographically strong Console session keys and rejected empty credential Secrets. +- Bounded generated TLS SAN work and HTTP metrics label cardinality. + +### Upgrade notes + +#### Apply CRDs before upgrading the controller + +Helm does not upgrade CRDs already installed from a chart's `crds/` directory. Apply both packaged +CRDs before the Helm upgrade: + +```bash +kubectl apply --server-side --force-conflicts \ + --field-manager=rustfs-operator-crd-upgrade \ + -f deploy/rustfs-operator/crds/tenant-crd.yaml +kubectl apply --server-side --force-conflicts \ + --field-manager=rustfs-operator-crd-upgrade \ + -f deploy/rustfs-operator/crds/policybinding-crd.yaml +``` + +#### Review Tenant Kubernetes API access + +The Operator removes legacy Tenant workload RBAC and renders +`automountServiceAccountToken: false`. Standard RustFS workloads do not need Kubernetes API access. +Custom sidecars or scripts that do need it must use a user-owned ServiceAccount, least-privilege +RBAC, and an explicit projected token. This migration changes the StatefulSet Pod template and +causes a rolling restart. + +#### Choose the STS TLS owner + +The new default is `sts.tls.auto=false`. Pre-create the configured STS TLS Secret with `tls.crt`, +`tls.key`, and `ca.crt`, or explicitly preserve the previous behavior with: + +```yaml +sts: + tls: + auto: true +``` + +#### Plan Console session interruption + +The Console now uses one replica and a `Recreate` rollout. Plan for a brief Console interruption; +users must authenticate again after a restart or session Secret rotation. Tenant data-plane traffic +is unaffected. + +#### Rollback considerations + +- Back up Tenant resources, Helm values, and the installed CRDs before upgrading. +- Do not downgrade CRDs automatically; keep the newer schema unless compatibility with the older + controller has been verified. +- An older Operator may recreate legacy Tenant RBAC and revert the ServiceAccount token setting, + causing another Tenant rollout and restoring broader Kubernetes API access. +- Pin Operator and RustFS images independently, then verify Tenant readiness and S3 read/write data + before and after any rollback. + +[Unreleased]: https://github.com/rustfs/operator/compare/0.0.6...HEAD +[0.0.6]: https://github.com/rustfs/operator/compare/0.0.5...0.0.6 diff --git a/Cargo.lock b/Cargo.lock index afb0733c..382fed4b 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -1732,7 +1732,7 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "operator" -version = "0.1.0" +version = "0.0.6" dependencies = [ "async-trait", "axum", diff --git a/Cargo.toml b/Cargo.toml index dac3f38c..30b461c9 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "operator" -version = "0.1.0" +version = "0.0.6" edition = "2024" repository = "https://github.com/rustfs/operator" license = "Apache-2.0" diff --git a/Dockerfile b/Dockerfile index 97e4b1b8..1bef2d8f 100755 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,12 @@ # Base image for final stage (override with: docker build --build-arg BASE_IMAGE=...) ARG BASE_IMAGE=debian:bookworm-slim -# Use rust:bookworm so the binary is linked against glibc 2.36, matching final image. +# Use the latest stable Rust on bookworm so release binaries match CI and glibc 2.36. ARG RUST_BUILD_IMAGE=rust:bookworm +# Operator version embedded in release binaries. Local builds fall back to Cargo.toml. +ARG VERSION + # Build image for the static Console frontend. ARG NODE_BUILD_IMAGE=node:24-alpine @@ -49,6 +52,8 @@ RUN cargo chef cook --release --recipe-path recipe.json # Stage 3: Build the binary FROM rust-base AS builder +ARG VERSION +ENV RUSTFS_OPERATOR_VERSION=${VERSION} WORKDIR /app COPY . . COPY --from=cacher /app/target target diff --git a/Makefile b/Makefile index e0f4f5eb..439561f5 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -.PHONY: pre-commit fmt fmt-check clippy test build help +.PHONY: pre-commit release-metadata-check fmt fmt-check clippy test build help .PHONY: docker-build-operator docker-build-console-web docker-build-all .PHONY: console-lint console-build console-fmt console-fmt-check .PHONY: e2e-check e2e-live-create .e2e-live-install-cert-manager e2e-live-run e2e-live-update e2e-live-delete @@ -28,6 +28,7 @@ help: @echo "" @echo "Usage:" @echo " make pre-commit - Run the full local gate (Rust + frontend), matching CI" + @echo " make release-metadata-check - Verify release and Rust versions stay aligned" @echo " make fmt - Format Rust code" @echo " make fmt-check - Check Rust formatting without modifying files" @echo " make clippy - Run clippy checks" @@ -47,9 +48,13 @@ help: @echo " make e2e-live-delete - Delete live Kind environment and clean dedicated storage" # pre-commit checks: Rust main crate + e2e harness + frontend (lint + build + format checks) -pre-commit: fmt-check clippy test e2e-check console-lint console-build console-fmt-check +pre-commit: release-metadata-check fmt-check clippy test e2e-check console-lint console-build console-fmt-check @echo "pre-commit: all checks passed" +# Keep source, chart, CI, and container release metadata aligned. +release-metadata-check: + bash scripts/check-release-metadata.sh + # Format Rust code. fmt: cargo fmt --all diff --git a/README.md b/README.md index 8b3e9618..57d48072 100755 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A Kubernetes operator for [RustFS](https://rustfs.com/) object storage, written in Rust with [kube-rs](https://github.com/kube-rs/kube). It reconciles a **`Tenant` custom resource** (`rustfs.com/v1alpha1`), validates referenced credential and KMS Secrets, and applies ServiceAccounts, Services, and StatefulSets so RustFS runs inside your cluster, from single-node single-disk development tenants to erasure-coded distributed clusters. -**Status:** v0.1.0 pre-release — under active development. +**Status:** v0.0.6 — under active development. ## Features diff --git a/deploy/rustfs-operator/Chart.yaml b/deploy/rustfs-operator/Chart.yaml index 8032188c..43ec2df6 100755 --- a/deploy/rustfs-operator/Chart.yaml +++ b/deploy/rustfs-operator/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: rustfs-operator description: Kubernetes operator for managing RustFS storage clusters type: application -version: 0.1.0 -appVersion: "1.0.0-beta.9" +version: 0.0.6 +appVersion: "0.0.6" home: https://rustfs.com sources: - https://github.com/rustfs/operator diff --git a/deploy/rustfs-operator/README.md b/deploy/rustfs-operator/README.md index bb9d8c9c..4ea11f89 100755 --- a/deploy/rustfs-operator/README.md +++ b/deploy/rustfs-operator/README.md @@ -95,7 +95,7 @@ The following table lists the configurable parameters of the RustFS Operator cha |-----------|-------------|---------| | `operator.replicas` | Number of operator replicas | `1` | | `operator.image.repository` | Operator image repository | `rustfs/operator` | -| `operator.image.tag` | Operator image tag | `latest` | +| `operator.image.tag` | Operator image tag; empty uses `Chart.appVersion` | `""` | | `operator.image.pullPolicy` | Image pull policy | `IfNotPresent` | | `operator.imagePullSecrets` | Image pull secrets | `[]` | | `operator.leaderElect` | Enable leader election override (`null`/unset for auto by replicas) | `null` | @@ -248,7 +248,7 @@ The generated ClusterRole grants only `get`, `list`, and `watch` for Secrets and ```bash helm install rustfs-operator deploy/rustfs-operator/ \ --set operator.image.repository=myregistry/operator \ - --set operator.image.tag=v0.2.0 + --set operator.image.tag=0.0.6 ``` ### Increased Resources diff --git a/deploy/rustfs-operator/templates/console-deployment.yaml b/deploy/rustfs-operator/templates/console-deployment.yaml index 742599e7..78bb9baa 100755 --- a/deploy/rustfs-operator/templates/console-deployment.yaml +++ b/deploy/rustfs-operator/templates/console-deployment.yaml @@ -60,7 +60,7 @@ spec: {{- end }} containers: - name: console - image: "{{ .Values.console.image.repository }}:{{ .Values.console.image.tag | default .Values.operator.image.tag }}" + image: "{{ .Values.console.image.repository }}:{{ .Values.console.image.tag | default .Values.operator.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.console.image.pullPolicy }} command: ["./operator", "console", "--port", "{{ .Values.console.port }}"] env: diff --git a/deploy/rustfs-operator/templates/deployment.yaml b/deploy/rustfs-operator/templates/deployment.yaml index 5ba47d4a..e33c448b 100755 --- a/deploy/rustfs-operator/templates/deployment.yaml +++ b/deploy/rustfs-operator/templates/deployment.yaml @@ -68,7 +68,7 @@ spec: {{- end }} containers: - name: operator - image: "{{ .Values.operator.image.repository }}:{{ .Values.operator.image.tag }}" + image: "{{ .Values.operator.image.repository }}:{{ .Values.operator.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.operator.image.pullPolicy }} command: ["./operator", "server"] {{- $leaderElect := gt (int .Values.operator.replicas) 1 }} diff --git a/deploy/rustfs-operator/values.yaml b/deploy/rustfs-operator/values.yaml index 8f9c568b..ae52c8c0 100755 --- a/deploy/rustfs-operator/values.yaml +++ b/deploy/rustfs-operator/values.yaml @@ -19,7 +19,8 @@ operator: image: repository: rustfs/operator - tag: latest + # Defaults to Chart.appVersion so a released chart never selects a mutable image tag. + tag: "" pullPolicy: IfNotPresent # Image pull secrets for private registries diff --git a/docs/operator-user-guide.md b/docs/operator-user-guide.md index 6b56f0e0..01ac3569 100644 --- a/docs/operator-user-guide.md +++ b/docs/operator-user-guide.md @@ -283,7 +283,7 @@ operator: replicas: 2 image: repository: registry.example.com/rustfs/operator - tag: v0.1.0 + tag: 0.0.6 resources: requests: cpu: 200m diff --git a/docs/operator-user-guide.zh-CN.md b/docs/operator-user-guide.zh-CN.md index 426cf7b8..da61153f 100644 --- a/docs/operator-user-guide.zh-CN.md +++ b/docs/operator-user-guide.zh-CN.md @@ -259,7 +259,7 @@ operator: replicas: 2 image: repository: registry.example.com/rustfs/operator - tag: v0.1.0 + tag: 0.0.6 resources: requests: cpu: 200m diff --git a/e2e/Cargo.lock b/e2e/Cargo.lock index 2584d417..252608c4 100644 --- a/e2e/Cargo.lock +++ b/e2e/Cargo.lock @@ -2610,7 +2610,7 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "operator" -version = "0.1.0" +version = "0.0.6" dependencies = [ "async-trait", "axum", diff --git a/examples/README.md b/examples/README.md index 66027d04..eddcd7aa 100755 --- a/examples/README.md +++ b/examples/README.md @@ -562,5 +562,5 @@ spec: --- -**Version**: v0.1.0 -**Last Updated**: 2025-11-05 +**Version**: v0.0.6 +**Last Updated**: 2026-08-22 diff --git a/scripts/check-release-metadata.sh b/scripts/check-release-metadata.sh new file mode 100755 index 00000000..5e2faff0 --- /dev/null +++ b/scripts/check-release-metadata.sh @@ -0,0 +1,150 @@ +#!/usr/bin/env bash +# Copyright 2026 RustFS Team +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$repo_root" + +cargo_version="$({ + awk ' + /^\[package\]$/ { in_package = 1; next } + /^\[/ && in_package { exit } + in_package && $1 == "version" && $2 == "=" { + gsub(/"/, "", $3) + print $3 + exit + } + ' Cargo.toml +})" +chart_version="$(awk '$1 == "version:" { gsub(/"/, "", $2); print $2; exit }' deploy/rustfs-operator/Chart.yaml)" +app_version="$(awk '$1 == "appVersion:" { gsub(/"/, "", $2); print $2; exit }' deploy/rustfs-operator/Chart.yaml)" +toolchain_version="$({ + awk -F= ' + $1 ~ /^[[:space:]]*channel[[:space:]]*$/ { + gsub(/[[:space:]\"]/, "", $2) + print $2 + exit + } + ' rust-toolchain.toml +})" +setup_version="$({ + awk ' + /^ rust-version:$/ { in_input = 1; next } + in_input && $1 == "default:" { + gsub(/"/, "", $2) + print $2 + exit + } + ' .github/actions/setup/action.yml +})" +ci_version="$({ + awk ' + /name: Setup Rust environment/ { in_setup = 1; next } + in_setup && $1 == "rust-version:" { print $2; exit } + ' .github/workflows/ci.yml +})" +docker_rust_version="$({ + awk -F= ' + /^ARG RUST_BUILD_IMAGE=rust:/ { + if ($2 == "rust:bookworm") { + print "stable" + } else { + sub(/^rust:/, "", $2) + sub(/-bookworm$/, "", $2) + print $2 + } + exit + } + ' Dockerfile +})" + +lock_package_version() { + local lockfile="$1" + awk ' + $0 == "name = \"operator\"" { in_operator = 1; next } + in_operator && $1 == "version" && $2 == "=" { + gsub(/"/, "", $3) + print $3 + exit + } + in_operator && /^\[\[package\]\]$/ { in_operator = 0 } + ' "$lockfile" +} + +root_lock_version="$(lock_package_version Cargo.lock)" +e2e_lock_version="$(lock_package_version e2e/Cargo.lock)" + +require_value() { + local name="$1" + local value="$2" + if [[ -z "$value" ]]; then + echo "release metadata check failed: could not read $name" >&2 + exit 1 + fi +} + +require_equal() { + local name="$1" + local actual="$2" + local expected="$3" + if [[ "$actual" != "$expected" ]]; then + echo "release metadata check failed: $name is '$actual', expected '$expected'" >&2 + exit 1 + fi +} + +require_value "Cargo package version" "$cargo_version" +require_value "Helm chart version" "$chart_version" +require_value "Helm appVersion" "$app_version" +require_value "Rust toolchain version" "$toolchain_version" +require_value "setup action Rust version" "$setup_version" +require_value "CI Rust version" "$ci_version" +require_value "Docker Rust version" "$docker_rust_version" +require_value "Cargo.lock Operator version" "$root_lock_version" +require_value "e2e/Cargo.lock Operator version" "$e2e_lock_version" + +if [[ ! "$cargo_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$ ]]; then + echo "release metadata check failed: '$cargo_version' is not a supported release version" >&2 + exit 1 +fi + +require_equal "Helm chart version" "$chart_version" "$cargo_version" +require_equal "Helm appVersion" "$app_version" "$cargo_version" +require_equal "setup action Rust version" "$setup_version" "$toolchain_version" +require_equal "CI Rust version" "$ci_version" "$toolchain_version" +require_equal "Docker Rust version" "$docker_rust_version" "$toolchain_version" +require_equal "Cargo.lock Operator version" "$root_lock_version" "$cargo_version" +require_equal "e2e/Cargo.lock Operator version" "$e2e_lock_version" "$cargo_version" + +grep -Fq 'ENV RUSTFS_OPERATOR_VERSION=${VERSION}' Dockerfile || { + echo "release metadata check failed: Docker does not pass VERSION to the Rust build" >&2 + exit 1 +} +grep -Fq '.Values.operator.image.tag | default .Chart.AppVersion' deploy/rustfs-operator/templates/deployment.yaml || { + echo "release metadata check failed: Operator image does not default to Chart.appVersion" >&2 + exit 1 +} +grep -Fq '.Values.console.image.tag | default .Values.operator.image.tag | default .Chart.AppVersion' \ + deploy/rustfs-operator/templates/console-deployment.yaml || { + echo "release metadata check failed: Console image does not default to Chart.appVersion" >&2 + exit 1 +} +grep -Fq -- '--app-version "${{ steps.version.outputs.chart_version }}"' .github/workflows/helm-package.yml || { + echo "release metadata check failed: Helm packaging does not set appVersion from the release tag" >&2 + exit 1 +} + +echo "release metadata is consistent: operator=$cargo_version rust=$toolchain_version" diff --git a/src/main.rs b/src/main.rs index 41c9e612..403b15b3 100755 --- a/src/main.rs +++ b/src/main.rs @@ -20,10 +20,16 @@ shadow_rs::shadow!(build); const SERVICE_ACCOUNT_NAMESPACE_PATH: &str = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"; +const RELEASE_VERSION: &str = match option_env!("RUSTFS_OPERATOR_VERSION") { + Some(version) => version, + None => "", +}; #[allow(clippy::const_is_empty)] const SHORT_VERSION: &str = { - if !build::TAG.is_empty() { + if !RELEASE_VERSION.is_empty() { + RELEASE_VERSION + } else if !build::TAG.is_empty() { build::TAG } else if !build::SHORT_COMMIT.is_empty() { concat!("@", build::SHORT_COMMIT)