From 26d5b3b6bd3612f53e63888795bc64a66a6dd0ac Mon Sep 17 00:00:00 2001 From: Stacky McStackface Date: Thu, 27 Nov 2025 11:49:33 +0000 Subject: [PATCH 1/4] chore: Generated commit to update templated files since the last template run up to stackabletech/operator-templating@863160b57e03183bd3de61de6f5e62f18886995a Reference-to: stackabletech/operator-templating@863160b (Add final job for GitHub checks) --- .github/workflows/build.yaml | 261 +++++ .github/workflows/build.yml | 484 --------- .github/workflows/integration-test.yml | 4 +- .github/workflows/pr_pre-commit.yaml | 4 +- .gitignore | 1 - .pre-commit-config.yaml | 23 + .yamllint.yaml | 3 + Makefile | 89 +- Tiltfile | 29 +- deploy/helm/secret-operator/crds/crds.yaml | 937 ++++++++++++++++++ .../secret-operator/templates/service.yaml | 6 +- docker/Dockerfile | 2 + renovate.json | 2 +- scripts/run-tests | 2 - 14 files changed, 1258 insertions(+), 589 deletions(-) create mode 100644 .github/workflows/build.yaml delete mode 100644 .github/workflows/build.yml create mode 100644 deploy/helm/secret-operator/crds/crds.yaml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 00000000..7f41c16c --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,261 @@ +# ============= +# This file is automatically generated from the templates in stackabletech/operator-templating +# DON'T MANUALLY EDIT THIS FILE +# ============= +--- +name: Build secret-operator Artifacts + +permissions: {} + +on: + push: + branches: + - main + tags: + - '[0-9][0-9].[0-9]+.[0-9]+-rc[0-9]+' + - '[0-9][0-9].[0-9]+.[0-9]+' + schedule: + # Run every Saturday morning: https://crontab.guru/#15_3_*_*_6 + - cron: '15 3 * * 6' + pull_request: + paths: + - '.github/workflows/build.yaml' + - 'rust-toolchain.toml' + - '.dockerignore' + - 'deploy/**' + - '.cargo/**' + - 'docker/**' + - 'Cargo.*' + - '*.rs' + +env: + OPERATOR_NAME: "secret-operator" + RUST_NIGHTLY_TOOLCHAIN_VERSION: "nightly-2025-10-23" + NIX_PKG_MANAGER_VERSION: "2.30.0" + RUST_TOOLCHAIN_VERSION: "1.89.0" + HADOLINT_VERSION: "v2.14.0" + PYTHON_VERSION: "3.14" + CARGO_TERM_COLOR: always + +jobs: + cargo-udeps: + name: Run cargo-udeps + runs-on: ubuntu-latest + env: + RUSTC_BOOTSTRAP: 1 + steps: + - name: Install host dependencies + uses: awalsh128/cache-apt-pkgs-action@2c09a5e66da6c8016428a2172bd76e5e4f14bb17 # v1.5.3 + with: + packages: protobuf-compiler krb5-user libkrb5-dev libclang-dev liblzma-dev libssl-dev pkg-config apt-transport-https + version: ubuntu-latest + + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + submodules: recursive + + - name: Install Rust ${{ env.RUST_TOOLCHAIN_VERSION }} toolchain + uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b + with: + toolchain: ${{ env.RUST_TOOLCHAIN_VERSION }} + + - name: Setup Rust Cache + uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 + with: + cache-all-crates: "true" + key: udeps + + - name: Install cargo-udeps + uses: stackabletech/cargo-install-action@8f7dbbcd2ebe22717efc132d0dd61e80841994b9 # cargo-udeps + + - name: Run cargo-udeps + run: cargo udeps --workspace --all-targets + + build-container-image: + name: Build/Publish ${{ matrix.runner.arch }} Image + permissions: + id-token: write + strategy: + fail-fast: false + matrix: + runner: + - { name: "ubuntu-latest", arch: "amd64" } + - { name: "ubicloud-standard-8-arm", arch: "arm64" } + runs-on: ${{ matrix.runner.name }} + outputs: + operator-version: ${{ steps.version.outputs.OPERATOR_VERSION }} + steps: + - name: Install host dependencies + uses: awalsh128/cache-apt-pkgs-action@2c09a5e66da6c8016428a2172bd76e5e4f14bb17 # v1.5.3 + with: + packages: protobuf-compiler krb5-user libkrb5-dev libclang-dev liblzma-dev libssl-dev pkg-config apt-transport-https + version: ${{ matrix.runner.name }} + + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + submodules: recursive + + - name: Update/Extract Operator Version + id: version + env: + PR_BASE_REF: ${{ github.event.pull_request.base.ref }} + PR_NUMBER: ${{ github.event.pull_request.number }} + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_DEBUG: ${{ runner.debug }} + shell: bash + run: | + set -euo pipefail + [ -n "$GITHUB_DEBUG" ] && set -x + + CURRENT_VERSION=$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[0].version') + + if [ "$GITHUB_EVENT_NAME" == 'pull_request' ]; then + # Include a PR suffix if this workflow is triggered by a PR + if [ "$PR_BASE_REF" == 'main' ]; then + NEW_VERSION="0.0.0-pr$PR_NUMBER" + else + NEW_VERSION="$CURRENT_VERSION-pr$PR_NUMBER" + fi + else + # Just use the current version if this workflow is run on push, schedule, etc... + NEW_VERSION="$CURRENT_VERSION" + fi + + sed -i "s/version = \"${CURRENT_VERSION}\"/version = \"${NEW_VERSION}\"/" Cargo.toml + echo "OPERATOR_VERSION=$NEW_VERSION" | tee -a "$GITHUB_OUTPUT" + + - name: Install Nix + uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2 + + - name: Install Rust ${{ env.RUST_TOOLCHAIN_VERSION }} Toolchain + uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b + with: + toolchain: ${{ env.RUST_TOOLCHAIN_VERSION }} + + - name: Build Container Image + id: build + uses: stackabletech/actions/build-container-image@29bea1b451c0c2e994bd495969286f95bf49ed6a # v0.11.0 + with: + image-name: ${{ env.OPERATOR_NAME }} + image-index-manifest-tag: ${{ steps.version.outputs.OPERATOR_VERSION }} + build-arguments: VERSION=${{ steps.version.outputs.OPERATOR_VERSION }} + container-file: docker/Dockerfile + + - name: Publish Container Image + uses: stackabletech/actions/publish-image@29bea1b451c0c2e994bd495969286f95bf49ed6a # v0.11.0 + with: + image-registry-uri: oci.stackable.tech + image-registry-username: robot$sdp+github-action-build + image-registry-password: ${{ secrets.HARBOR_ROBOT_SDP_GITHUB_ACTION_BUILD_SECRET }} + image-repository: sdp/${{ env.OPERATOR_NAME }} + image-manifest-tag: ${{ steps.build.outputs.image-manifest-tag }} + source-image-uri: ${{ steps.build.outputs.image-manifest-uri }} + + publish-index-manifest: + name: Publish/Sign ${{ needs.build-container-image.outputs.operator-version }} Index + needs: + - build-container-image + permissions: + id-token: write + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + + - name: Publish and Sign Image Index + uses: stackabletech/actions/publish-image-index-manifest@29bea1b451c0c2e994bd495969286f95bf49ed6a # v0.11.0 + with: + image-registry-uri: oci.stackable.tech + image-registry-username: robot$sdp+github-action-build + image-registry-password: ${{ secrets.HARBOR_ROBOT_SDP_GITHUB_ACTION_BUILD_SECRET }} + image-repository: sdp/${{ env.OPERATOR_NAME }} + image-index-manifest-tag: ${{ needs.build-container-image.outputs.operator-version }} + + publish-helm-chart: + name: Package/Publish ${{ needs.build-container-image.outputs.operator-version }} Helm Chart + needs: + - build-container-image + permissions: + id-token: write + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + submodules: recursive + + - name: Package, Publish, and Sign Helm Chart + uses: stackabletech/actions/publish-helm-chart@29bea1b451c0c2e994bd495969286f95bf49ed6a # v0.11.0 + with: + chart-registry-uri: oci.stackable.tech + chart-registry-username: robot$sdp-charts+github-action-build + chart-registry-password: ${{ secrets.HARBOR_ROBOT_SDP_CHARTS_GITHUB_ACTION_BUILD_SECRET }} + chart-repository: sdp-charts + chart-directory: deploy/helm/${{ env.OPERATOR_NAME }} + chart-version: ${{ needs.build-container-image.outputs.operator-version }} + app-version: ${{ needs.build-container-image.outputs.operator-version }} + + openshift-preflight-check: + name: Run OpenShift Preflight Check for ${{ needs.build-container-image.outputs.operator-version }}-${{ matrix.arch }} + needs: + - build-container-image + - publish-index-manifest + strategy: + fail-fast: false + matrix: + arch: + - amd64 + - arm64 + runs-on: ubuntu-latest + steps: + - name: Run OpenShift Preflight Check + uses: stackabletech/actions/run-openshift-preflight@29bea1b451c0c2e994bd495969286f95bf49ed6a # v0.11.0 + with: + image-index-uri: oci.stackable.tech/sdp/${{ env.OPERATOR_NAME }}:${{ needs.build-container-image.outputs.operator-version }} + image-architecture: ${{ matrix.arch }} + + # This job is a required check in GitHub Settings for this repository. + # It saves us having to list many required jobs, or work around dynamically + # named jobs (since there is no concept of required settings). + finished: + # WARNING: Do not change the name unless you will also be changing the + # Required Checks (in branch protections) in GitHub settings. + name: Finished Build and Publish + needs: + - cargo-udeps + - openshift-preflight-check + - publish-helm-chart + runs-on: ubuntu-latest + steps: + - run: echo "We are done here" + + notify: + name: Failure Notification + needs: + - build-container-image + - publish-index-manifest + - publish-helm-chart + runs-on: ubuntu-latest + if: failure() || github.run_attempt > 1 + steps: + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + + - name: Send Notification + uses: stackabletech/actions/send-slack-notification@29bea1b451c0c2e994bd495969286f95bf49ed6a # v0.11.0 + with: + publish-helm-chart-result: ${{ needs.publish-helm-chart.result }} + publish-manifests-result: ${{ needs.publish-index-manifest.result }} + build-result: ${{ needs.build-container-image.result }} + slack-token: ${{ secrets.SLACK_CONTAINER_IMAGE_TOKEN }} + channel-id: C07UG6JH44F # notifications-container-images + type: container-image-build diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index ae1963e9..00000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,484 +0,0 @@ -# ============= -# This file is automatically generated from the templates in stackabletech/operator-templating -# DON'T MANUALLY EDIT THIS FILE -# ============= ---- -name: Stackable Build Pipeline - -on: - push: - branches: - - main - - staging - - trying - - "renovate/**" - tags: - - '[0-9][0-9].[0-9]+.[0-9]+' - - '[0-9][0-9].[0-9]+.[0-9]+-rc[0-9]+' - pull_request: - merge_group: - schedule: - # Run every Saturday morning: https://crontab.guru/#15_3_*_*_6 - - cron: '15 3 * * 6' - workflow_dispatch: - -env: - CARGO_TERM_COLOR: always - CARGO_INCREMENTAL: '0' - CARGO_PROFILE_DEV_DEBUG: '0' - RUST_TOOLCHAIN_VERSION: "1.89.0" - RUST_NIGHTLY_TOOLCHAIN_VERSION: "nightly-2025-10-23" - PYTHON_VERSION: "3.14" - RUSTFLAGS: "-D warnings" - RUSTDOCFLAGS: "-D warnings" - RUST_LOG: "info" - -jobs: - # Identify unused dependencies - run_udeps: - name: Run Cargo Udeps - runs-on: ubuntu-latest - env: - RUSTC_BOOTSTRAP: 1 - steps: - - name: Install host dependencies - uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0 - with: - packages: protobuf-compiler krb5-user libkrb5-dev libclang-dev liblzma-dev libssl-dev pkg-config apt-transport-https - version: ubuntu-latest - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - persist-credentials: false - submodules: recursive - - uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 - with: - toolchain: ${{ env.RUST_TOOLCHAIN_VERSION }} - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 - with: - key: udeps - cache-all-crates: "true" - - uses: stackabletech/cargo-install-action@cargo-udeps - - run: cargo udeps --workspace --all-targets - - # This job evaluates the github environment to determine why this action is running and decides if - # Helm charts are published based on this. - # - # The following scenarios are identified: - # - all pull requests land are published: - # condition: github.event_name == "pull_request" - # - # - all tagged releases are published: - # condition: github.event_name == 'push' & github.ref.startswith('refs/tags/') - # - # - all pushes to main (i.e. PR-merges) and all scheduled/manual workflow runs on main land are published: - # condition: ( github.event_name == 'push' | github.event_name == 'schedule' | github.event_name == 'workflow_dispatch' ) & github.ref == 'refs/heads/main' - # - # Any other scenarios (e.g. when a branch is created/pushed) will cause the publish step to be skipped, most commonly this is expected to happen for the - # branches that the GitHub merge queue feature uses internally for which the checks need to run, but we do not want artifacts to be published. - check_helm_publish: - name: Decide if Helm charts are pushed to the helm repository based on action trigger - runs-on: ubuntu-latest - outputs: - skip_helm: ${{ steps.checkhelmpublish.outputs.skip_helm }} - steps: - - id: checkhelmpublish - env: - TRIGGER: ${{ github.event_name }} - GITHUB_REF: ${{ github.ref }} - run: | - if [[ "$TRIGGER" == "pull_request" ]]; then - echo "skip_helm=false" >> "$GITHUB_OUTPUT" - elif [[ ( "$TRIGGER" == "push" || "$TRIGGER" == "schedule" || "$TRIGGER" == "workflow_dispatch" ) && "$GITHUB_REF" == "refs/heads/main" ]]; then - echo "skip_helm=false" >> "$GITHUB_OUTPUT" - elif [[ "$TRIGGER" == "push" && $GITHUB_REF == refs/tags/* ]]; then - echo "skip_helm=false" >> "$GITHUB_OUTPUT" - else - echo "Unknown trigger and ref combination encountered, skipping publish step: $TRIGGER $GITHUB_REF" - echo "skip_helm=true" >> "$GITHUB_OUTPUT" - fi - - run_cargodeny: - name: Run Cargo Deny - runs-on: ubuntu-latest - strategy: - matrix: - checks: - - advisories - - bans licenses sources - - # Prevent sudden announcement of a new advisory from failing ci: - continue-on-error: ${{ matrix.checks == 'advisories' }} - - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - persist-credentials: false - submodules: recursive - - uses: EmbarkStudios/cargo-deny-action@f2ba7abc2abebaf185c833c3961145a3c275caad # v2.0.13 - with: - command: check ${{ matrix.checks }} - - run_rustfmt: - name: Run Rustfmt - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - persist-credentials: false - submodules: recursive - - uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 - with: - toolchain: ${{ env.RUST_NIGHTLY_TOOLCHAIN_VERSION }} - components: rustfmt - - env: - RUST_TOOLCHAIN_VERSION: ${{ env.RUST_NIGHTLY_TOOLCHAIN_VERSION }} - run: cargo "+$RUST_TOOLCHAIN_VERSION" fmt --all -- --check - - run_clippy: - name: Run Clippy - runs-on: ubuntu-latest - steps: - - name: Install host dependencies - uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0 - with: - packages: protobuf-compiler krb5-user libkrb5-dev libclang-dev liblzma-dev libssl-dev pkg-config apt-transport-https - version: ubuntu-latest - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - persist-credentials: false - submodules: recursive - - uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 - with: - toolchain: ${{ env.RUST_TOOLCHAIN_VERSION }} - components: clippy - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 - with: - key: clippy - cache-all-crates: "true" - # TODO (@Techassi): Remove this step (unmaintained action, kinda useless step anyway) - - name: Run clippy action to produce annotations - uses: giraffate/clippy-action@13b9d32482f25d29ead141b79e7e04e7900281e0 # v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - if: env.GITHUB_TOKEN != null - with: - clippy_flags: --all-targets -- -D warnings - reporter: 'github-pr-review' - github_token: ${{ secrets.GITHUB_TOKEN }} - # TODO (@Techassi): Remove, done by pre-commit - - name: Run clippy manually without annotations - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - if: env.GITHUB_TOKEN == null - run: cargo clippy --color never -q --all-targets -- -D warnings - - # TODO (@Techassi): Can be done by pre-commit - run_rustdoc: - name: Run RustDoc - runs-on: ubuntu-latest - steps: - - name: Install host dependencies - uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0 - with: - packages: protobuf-compiler krb5-user libkrb5-dev libclang-dev liblzma-dev libssl-dev pkg-config apt-transport-https - version: ubuntu-latest - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - submodules: recursive - - uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 - with: - toolchain: ${{ env.RUST_TOOLCHAIN_VERSION }} - components: rustfmt - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 - with: - key: doc - cache-all-crates: "true" - - run: cargo doc --document-private-items - - # TODO (@Techassi): Remove, done by pre-commit - run_tests: - name: Run Cargo Tests - runs-on: ubuntu-latest - steps: - - name: Install host dependencies - uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0 - with: - packages: protobuf-compiler krb5-user libkrb5-dev libclang-dev liblzma-dev libssl-dev pkg-config apt-transport-https - version: ubuntu-latest - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - persist-credentials: false - submodules: recursive - - uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 - with: - toolchain: ${{ env.RUST_TOOLCHAIN_VERSION }} - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 - with: - key: test - cache-all-crates: "true" - - run: cargo test - - - # Similar to check_charts, this tries to render the README, and see if there are unintended changes. - # This will save us from merging changes to the wrong file (instead of the templated source), and from - # forgetting to render out modifications to the README. - check_readme: - name: Check if committed README is the one we would render from the available parts - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - persist-credentials: false - submodules: recursive - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 - with: - python-version: ${{ env.PYTHON_VERSION }} - - name: Install jinja2-cli - run: pip install jinja2-cli==0.8.2 - - name: Regenerate charts - run: make render-readme - - name: Check if committed README were up to date - run: git diff --exit-code - - name: Git Diff showed uncommitted changes - if: ${{ failure() }} - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - script: | - core.setFailed('Committed README are not up to date, please make sure to apply them to the templated partials, and re-commit!') - - # This job cleans up the CRDs and Helm charts, followed by rebuilding them - # It then runs a `git diff` and fails the entire workflow, if any difference is encountered. - # - # Since CRD files are generated during the 'cargo build' process we need to run this once after - # removing the CRD files to ensure that the checked in versions match what the code expects. - # - # The reason for this step is, that developers are expected to check in up-to-date versions of charts - # as we'd otherwise have to build these in CI and commit them back to the PR, which - # creates all kinds of problems. - # This failsafe simply aborts anything that has not had charts rebuilt before pushing. - check_charts: - name: Check if committed Helm charts are up to date - runs-on: ubuntu-latest - steps: - - name: Install host dependencies - uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0 - with: - packages: protobuf-compiler krb5-user libkrb5-dev libclang-dev liblzma-dev libssl-dev pkg-config apt-transport-https - version: ubuntu-latest - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - persist-credentials: false - submodules: recursive - - name: Set up Helm - uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1 - with: - version: v3.16.1 - - name: Set up cargo - uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 - with: - toolchain: ${{ env.RUST_TOOLCHAIN_VERSION }} - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 - with: - key: charts - cache-all-crates: "true" - - name: Regenerate charts - run: make regenerate-charts - - name: Check if committed charts were up to date - run: git diff --exit-code - - name: Git Diff showed uncommitted changes - if: ${{ failure() }} - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - script: | - core.setFailed('Committed charts were not up to date, please regenerate and re-commit!') - - tests_passed: - name: All tests passed - needs: - - run_udeps - - run_cargodeny - - run_clippy - - run_rustfmt - - run_rustdoc - - run_tests - - check_charts - - check_readme - runs-on: ubuntu-latest - steps: - - name: log - run: echo All tests have passed! - - # TODO (@Techassi): Most of these publishing and signing tasks can be done by our own actions. - # Make use of them just like we do in docker-images. - package_and_publish: - name: Package Charts, Build Docker Image and publish them - ${{ matrix.runner }} - needs: - - tests_passed - - check_helm_publish - strategy: - matrix: - runner: ["ubuntu-latest", "ubicloud-standard-8-arm"] - runs-on: ${{ matrix.runner }} - timeout-minutes: 120 - permissions: - id-token: write - env: - OCI_REGISTRY_SDP_PASSWORD: ${{ secrets.HARBOR_ROBOT_SDP_GITHUB_ACTION_BUILD_SECRET }} - OCI_REGISTRY_SDP_USERNAME: "robot$sdp+github-action-build" - OCI_REGISTRY_SDP_CHARTS_PASSWORD: ${{ secrets.HARBOR_ROBOT_SDP_CHARTS_GITHUB_ACTION_BUILD_SECRET }} - OCI_REGISTRY_SDP_CHARTS_USERNAME: "robot$sdp-charts+github-action-build" - if: needs.check_helm_publish.outputs.skip_helm != 'true' - outputs: - IMAGE_TAG: ${{ steps.printtag.outputs.IMAGE_TAG }} - steps: - - name: Install host dependencies - uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0 - with: - packages: protobuf-compiler krb5-user libkrb5-dev libclang-dev liblzma-dev libssl-dev pkg-config apt-transport-https - version: ${{ matrix.runner }} - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - persist-credentials: false - submodules: recursive - - uses: cachix/install-nix-action@fd24c48048070c1be9acd18c9d369a83f0fe94d7 # v31.8.1 - - uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 - with: - toolchain: ${{ env.RUST_TOOLCHAIN_VERSION }} - components: rustfmt - # This step checks if the current run was triggered by a push to a pr (or a pr being created). - # If this is the case it changes the version of this project in all Cargo.toml files to include the suffix - # "-pr" so that the published artifacts can be linked to this PR. - - uses: stackabletech/cargo-install-action@main - with: - crate: cargo-edit - bin: cargo-set-version - - name: Update version if PR against main branch - if: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'main' }} - env: - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - PR_VERSION="0.0.0-pr${PR_NUMBER}" - cargo set-version --offline --workspace "$PR_VERSION" - - name: Update version if PR against non-main branch - # For PRs to be merged against a release branch, use the version that has already been set in the calling script. - # We can't rely on cargo set-version here as we will break semver rules when changing the version to make it - # specific to this PR e.g. 1.2.0 --> 1.2.0-pr678, so set it manually. - if: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref != 'main' }} - env: - PR_NUMBER: ${{ github.event.pull_request.number }} - shell: bash - run: | - set -euo pipefail - - MANIFEST_VERSION=$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[0].version') - PR_VERSION="${MANIFEST_VERSION}-pr${PR_NUMBER}" - sed -i "s/version = \"${MANIFEST_VERSION}\"/version = \"${PR_VERSION}\"/" Cargo.toml - - # Recreate charts and publish charts and docker image. - - name: Install cosign - uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 - - name: Install syft - uses: anchore/sbom-action/download-syft@8e94d75ddd33f69f691467e42275782e4bfefe84 # v0.20.9 - - name: Build Docker image and Helm chart - run: | - # Installing helm and yq on ubicloud-standard-8-arm only - if [ "$(arch)" = "aarch64" ]; then - curl -fsSL https://packages.buildkite.com/helm-linux/helm-debian/gpgkey | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://packages.buildkite.com/helm-linux/helm-debian/any/ any main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list - sudo apt-get -y update - sudo apt-get -y install helm - sudo wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_arm64 -O /usr/bin/yq && sudo chmod +x /usr/bin/yq - fi - - make build - - name: Publish Docker image and Helm chart - if: ${{ !github.event.pull_request.head.repo.fork }} - run: | - # We want to publish helmcharts only once as they have a common name, while still publishing both images with architecture specific tags - if [ "$(uname -m)" = "x86_64" ]; then - make publish - else - make docker-publish - fi - # Output the name of the published image to the Job output for later use - - id: printtag - name: Output image name and tag - if: ${{ !github.event.pull_request.head.repo.fork }} - run: echo "IMAGE_TAG=$(make print-docker-tag)" >> "$GITHUB_OUTPUT" - - create_manifest_list: - name: Build and publish manifest list - if: ${{ !github.event.pull_request.head.repo.fork }} - needs: - - package_and_publish - runs-on: ubuntu-latest - permissions: - id-token: write - env: - OCI_REGISTRY_SDP_PASSWORD: ${{ secrets.HARBOR_ROBOT_SDP_GITHUB_ACTION_BUILD_SECRET }} - OCI_REGISTRY_SDP_USERNAME: "robot$sdp+github-action-build" - OCI_REGISTRY_SDP_CHARTS_PASSWORD: ${{ secrets.HARBOR_ROBOT_SDP_CHARTS_GITHUB_ACTION_BUILD_SECRET }} - OCI_REGISTRY_SDP_CHARTS_USERNAME: "robot$sdp-charts+github-action-build" - steps: - - name: Install cosign - uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 - - name: Checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - persist-credentials: false - submodules: recursive - # This step checks if the current run was triggered by a push to a pr (or a pr being created). - # If this is the case it changes the version of this project in all Cargo.toml files to include the suffix - # "-pr" so that the published artifacts can be linked to this PR. - - uses: stackabletech/cargo-install-action@main - with: - crate: cargo-edit - bin: cargo-set-version - - name: Update version if PR against main branch - if: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'main' }} - env: - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - PR_VERSION="0.0.0-pr${PR_NUMBER}" - cargo set-version --offline --workspace "$PR_VERSION" - - name: Update version if PR against non-main branch - # For PRs to be merged against a release branch, use the version that has already been set in the calling script. - # We can't rely on cargo set-version here as we will break semver rules when changing the version to make it - # specific to this PR e.g. 1.2.0 --> 1.2.0-pr678, so set it manually. - if: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref != 'main' }} - env: - PR_NUMBER: ${{ github.event.pull_request.number }} - shell: bash - run: | - set -euo pipefail - - MANIFEST_VERSION=$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[0].version') - PR_VERSION="${MANIFEST_VERSION}-pr${PR_NUMBER}" - sed -i "s/version = \"${MANIFEST_VERSION}\"/version = \"${PR_VERSION}\"/" Cargo.toml - - name: Build manifest list - run: | - # Creating manifest list - make -e docker-manifest-list-build - # Pushing and signing manifest list - make -e docker-manifest-list-publish - - openshift_preflight: - name: Run the OpenShift Preflight check on the published images - if: ${{ !github.event.pull_request.head.repo.fork }} - needs: - - create_manifest_list - - package_and_publish - runs-on: ubuntu-latest - env: - IMAGE_TAG: ${{ needs.package_and_publish.outputs.IMAGE_TAG }} - steps: - - name: Install preflight - run: | - wget https://github.com/redhat-openshift-ecosystem/openshift-preflight/releases/download/1.10.0/preflight-linux-amd64 - chmod +x preflight-linux-amd64 - - name: Check container - run: | - ARCH_FOR_PREFLIGHT="$(arch | sed -e 's#x86_64#amd64#' | sed -e 's#aarch64#arm64#')" - ./preflight-linux-amd64 check container "$IMAGE_TAG" --platform "${ARCH_FOR_PREFLIGHT}" > preflight.out - - name: "Passed?" - run: '[ "$(jq -r .passed < preflight.out)" == true ]' diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index b49b7712..0f9f268e 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -41,7 +41,7 @@ jobs: # TODO: Enable the scheduled runs which hard-code what profile to use - name: Run Integration Test id: test - uses: stackabletech/actions/run-integration-test@75e0756966dea229d697165bfd06ba79abcda72c # v0.10.3 + uses: stackabletech/actions/run-integration-test@29bea1b451c0c2e994bd495969286f95bf49ed6a # v0.11.0 with: replicated-api-token: ${{ secrets.REPLICATED_API_TOKEN }} test-mode-input: ${{ inputs.test-mode-input }} @@ -51,7 +51,7 @@ jobs: - name: Send Notification if: ${{ failure() || github.run_attempt > 1 }} - uses: stackabletech/actions/send-slack-notification@75e0756966dea229d697165bfd06ba79abcda72c # v0.10.3 + uses: stackabletech/actions/send-slack-notification@29bea1b451c0c2e994bd495969286f95bf49ed6a # v0.11.0 with: slack-token: ${{ secrets.SLACK_INTEGRATION_TEST_TOKEN }} failed-tests: ${{ steps.test.outputs.failed-tests }} diff --git a/.github/workflows/pr_pre-commit.yaml b/.github/workflows/pr_pre-commit.yaml index 0d46fc8a..acbc963e 100644 --- a/.github/workflows/pr_pre-commit.yaml +++ b/.github/workflows/pr_pre-commit.yaml @@ -11,6 +11,7 @@ env: RUST_TOOLCHAIN_VERSION: "nightly-2025-10-23" HADOLINT_VERSION: "v2.14.0" PYTHON_VERSION: "3.14" + JINJA2_CLI_VERSION: "0.8.2" jobs: pre-commit: @@ -26,10 +27,11 @@ jobs: persist-credentials: false submodules: recursive fetch-depth: 0 - - uses: stackabletech/actions/run-pre-commit@75e0756966dea229d697165bfd06ba79abcda72c # v0.10.3 + - uses: stackabletech/actions/run-pre-commit@29bea1b451c0c2e994bd495969286f95bf49ed6a # v0.11.0 with: python-version: ${{ env.PYTHON_VERSION }} rust: ${{ env.RUST_TOOLCHAIN_VERSION }} hadolint: ${{ env.HADOLINT_VERSION }} nix: ${{ env.NIX_PKG_MANAGER_VERSION }} nix-github-token: ${{ secrets.GITHUB_TOKEN }} + jinja2-cli: ${{ env.JINJA2_CLI_VERSION }} diff --git a/.gitignore b/.gitignore index abc7ff9a..2dbc7ab0 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,6 @@ result image.tar tilt_options.json -local_values.yaml .direnv/ .direnvrc diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 60b6d59f..5461297f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,6 +14,13 @@ repos: args: ["--allow-missing-credentials"] - id: detect-private-key + - repo: https://github.com/EmbarkStudios/cargo-deny + # Failing to compile cargo-deny with version >0.18.3, needs rust 1.88.0 + rev: baa02b0a0c54e0578aae6bb7c7181ad00dc290af # 0.18.3 + hooks: + - id: cargo-deny + args: ["--all-features", "check", "advisories", "bans", "licenses", "sources"] + - repo: https://github.com/adrienverge/yamllint rev: 79a6b2b1392eaf49cdd32ac4f14be1a809bbd8f7 # 1.37.1 hooks: @@ -72,6 +79,22 @@ repos: pass_filenames: false files: Cargo\.lock + - id: check-readme + name: check-readme + language: system + entry: make render-readme + stages: [pre-commit, pre-merge-commit] + pass_filenames: false + files: .readme + + - id: cargo-doc + name: cargo-doc + language: system + entry: cargo doc --document-private-items + stages: [pre-commit, pre-merge-commit] + pass_filenames: false + files: \.rs$|Cargo\.(toml|lock) + - id: cargo-test name: cargo-test language: system diff --git a/.yamllint.yaml b/.yamllint.yaml index 08bf167f..020cb5f0 100644 --- a/.yamllint.yaml +++ b/.yamllint.yaml @@ -13,3 +13,6 @@ rules: indentation: indent-sequences: consistent comments-indentation: disable # This is generally useless and interferes with commented example values + braces: + max-spaces-inside: 1 + max-spaces-inside-empty: 0 diff --git a/Makefile b/Makefile index 9afd741a..7c13ca40 100644 --- a/Makefile +++ b/Makefile @@ -9,17 +9,11 @@ .PHONY: build publish -TAG := $(shell git rev-parse --short HEAD) OPERATOR_NAME := secret-operator VERSION := $(shell cargo metadata --format-version 1 | jq -r '.packages[] | select(.name=="stackable-${OPERATOR_NAME}") | .version') -ARCH := $(shell uname -m | sed -e 's#x86_64#amd64#' | sed -e 's#aarch64#arm64#') OCI_REGISTRY_HOSTNAME := oci.stackable.tech OCI_REGISTRY_PROJECT_IMAGES := sdp -OCI_REGISTRY_PROJECT_CHARTS := sdp-charts -# This will be overwritten by an environmental variable if called from the github action -HELM_CHART_NAME := ${OPERATOR_NAME} -HELM_CHART_ARTIFACT := target/helm/${OPERATOR_NAME}-${VERSION}.tgz SHELL=/usr/bin/env bash -euo pipefail @@ -33,78 +27,12 @@ render-docs: docker-build: docker build --force-rm --build-arg VERSION=${VERSION} -t "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-${ARCH}" -f docker/Dockerfile . -docker-publish: - # Push to Harbor - # We need to use "value" here to prevent the variable from being recursively expanded by make (username contains a dollar sign, since it's a Harbor bot) - docker login --username '${value OCI_REGISTRY_SDP_USERNAME}' --password '${OCI_REGISTRY_SDP_PASSWORD}' '${OCI_REGISTRY_HOSTNAME}' - DOCKER_OUTPUT=$$(docker push --all-tags '${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}');\ - # Obtain the digest of the pushed image from the output of `docker push`, because signing by tag is deprecated and will be removed from cosign in the future\ - REPO_DIGEST_OF_IMAGE=$$(echo "$$DOCKER_OUTPUT" | awk '/^${VERSION}-${ARCH}: digest: sha256:[0-9a-f]{64} size: [0-9]+$$/ { print $$3 }');\ - if [ -z "$$REPO_DIGEST_OF_IMAGE" ]; then\ - echo 'Could not find repo digest for container image: ${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-${ARCH}';\ - exit 1;\ - fi;\ - # This generates a signature and publishes it to the registry, next to the image\ - # Uses the keyless signing flow with Github Actions as identity provider\ - cosign sign -y "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}@$$REPO_DIGEST_OF_IMAGE";\ - # Generate the SBOM for the operator image, this leverages the already generated SBOM for the operator binary by cargo-cyclonedx\ - syft scan --output cyclonedx-json@1.5=sbom.json --select-catalogers "-cargo-auditable-binary-cataloger,+sbom-cataloger" --scope all-layers --source-name "${OPERATOR_NAME}" --source-version "${VERSION}-${ARCH}" "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}@$$REPO_DIGEST_OF_IMAGE";\ - # Determine the PURL for the container image\ - URLENCODED_REPO_DIGEST_OF_IMAGE=$$(echo "$$REPO_DIGEST_OF_IMAGE" | sed 's/:/%3A/g');\ - PURL="pkg:oci/${OPERATOR_NAME}@$$URLENCODED_REPO_DIGEST_OF_IMAGE?arch=${ARCH}&repository_url=${OCI_REGISTRY_HOSTNAME}%2F${OCI_REGISTRY_PROJECT_IMAGES}%2F${OPERATOR_NAME}";\ - # Get metadata from the image\ - IMAGE_DESCRIPTION=$$(docker inspect --format='{{.Config.Labels.description}}' "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-${ARCH}");\ - IMAGE_NAME=$$(docker inspect --format='{{.Config.Labels.name}}' "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-${ARCH}");\ - # Merge the SBOM with the metadata for the operator\ - jq -s '{"metadata":{"component":{"description":"'"$$IMAGE_NAME. $$IMAGE_DESCRIPTION"'","supplier":{"name":"Stackable GmbH","url":["https://stackable.tech/"]},"author":"Stackable GmbH","purl":"'"$$PURL"'","publisher":"Stackable GmbH"}}} * .[0]' sbom.json > sbom.merged.json;\ - # Attest the SBOM to the image\ - cosign attest -y --predicate sbom.merged.json --type cyclonedx "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}@$$REPO_DIGEST_OF_IMAGE" - -# This assumes "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-amd64 and "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-arm64 are built and pushed -docker-manifest-list-build: - docker manifest create "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}" --amend "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-amd64" --amend "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-arm64" - -docker-manifest-list-publish: - # Push to Harbor - # We need to use "value" here to prevent the variable from being recursively expanded by make (username contains a dollar sign, since it's a Harbor bot) - docker login --username '${value OCI_REGISTRY_SDP_USERNAME}' --password '${OCI_REGISTRY_SDP_PASSWORD}' '${OCI_REGISTRY_HOSTNAME}' - DIGEST_HARBOR=$$(docker manifest push "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}");\ - # Refer to image via its digest (oci.stackable.tech/sdp/airflow@sha256:0a1b2c...);\ - # This generates a signature and publishes it to the registry, next to the image\ - # Uses the keyless signing flow with Github Actions as identity provider\ - cosign sign -y "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}@$$DIGEST_HARBOR" - -# TODO remove if not used/needed -docker: docker-build docker-publish - -print-docker-tag: - @echo "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}" - -helm-publish: - # Push to Harbor - # We need to use "value" here to prevent the variable from being recursively expanded by make (username contains a dollar sign, since it's a Harbor bot) - helm registry login --username '${value OCI_REGISTRY_SDP_CHARTS_USERNAME}' --password '${OCI_REGISTRY_SDP_CHARTS_PASSWORD}' '${OCI_REGISTRY_HOSTNAME}' - # Obtain the digest of the pushed artifact from the output of `helm push`, because signing by tag is deprecated and will be removed from cosign in the future\ - HELM_OUTPUT=$$(helm push '${HELM_CHART_ARTIFACT}' 'oci://${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_CHARTS}' 2>&1);\ - REPO_DIGEST_OF_ARTIFACT=$$(echo "$$HELM_OUTPUT" | awk '/^Digest: sha256:[0-9a-f]{64}$$/ { print $$2 }');\ - if [ -z "$$REPO_DIGEST_OF_ARTIFACT" ]; then\ - echo 'Could not find repo digest for helm chart: ${HELM_CHART_NAME}';\ - exit 1;\ - fi;\ - # Login to Harbor, needed for cosign to be able to push the signature for the Helm chart\ - docker login --username '${value OCI_REGISTRY_SDP_CHARTS_USERNAME}' --password '${OCI_REGISTRY_SDP_CHARTS_PASSWORD}' '${OCI_REGISTRY_HOSTNAME}';\ - # This generates a signature and publishes it to the registry, next to the chart artifact\ - # Uses the keyless signing flow with Github Actions as identity provider\ - cosign sign -y "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_CHARTS}/${HELM_CHART_NAME}@$$REPO_DIGEST_OF_ARTIFACT" - -helm-package: - mkdir -p target/helm && helm package --destination target/helm deploy/helm/${OPERATOR_NAME} - ## Chart related targets compile-chart: version crds config chart-clean: rm -rf "deploy/helm/${OPERATOR_NAME}/configs" + rm -rf "deploy/helm/${OPERATOR_NAME}/crds" version: cat "deploy/helm/${OPERATOR_NAME}/Chart.yaml" | yq ".version = \"${VERSION}\" | .appVersion = \"${VERSION}\"" > "deploy/helm/${OPERATOR_NAME}/Chart.yaml.new" @@ -116,11 +44,9 @@ config: cp -r deploy/config-spec/* "deploy/helm/${OPERATOR_NAME}/configs";\ fi -# We generate a crds.yaml, so that the effect of code changes are visible. -# The operator will take care of the CRD rollout itself. crds: - mkdir -p extra - cargo run --bin stackable-"${OPERATOR_NAME}" -- crd > extra/crds.yaml + mkdir -p deploy/helm/"${OPERATOR_NAME}"/crds + cargo run --bin stackable-"${OPERATOR_NAME}" -- crd | yq eval '.metadata.annotations["helm.sh/resource-policy"]="keep"' - > "deploy/helm/${OPERATOR_NAME}/crds/crds.yaml" chart-lint: compile-chart docker run -it -v $(shell pwd):/build/helm-charts -w /build/helm-charts quay.io/helmpack/chart-testing:v3.5.0 ct lint --config deploy/helm/ct.yaml @@ -134,14 +60,7 @@ regenerate-charts: chart-clean compile-chart regenerate-nix: nix run --extra-experimental-features "nix-command flakes" -f . regenerateNixLockfiles -build: regenerate-charts regenerate-nix helm-package docker-build - -# This target is used by the CI -# It doesn't make use of any nix dependencies and thus aviods building the -# operator unnecessarily often. -build-ci: regenerate-charts helm-package docker-build - -publish: docker-publish helm-publish +build: regenerate-charts regenerate-nix docker-build check-nix: @which nix || (echo "Error: 'nix' is not installed. Please install it to proceed."; exit 1) diff --git a/Tiltfile b/Tiltfile index 53c3a8e4..ee0941c3 100644 --- a/Tiltfile +++ b/Tiltfile @@ -17,6 +17,11 @@ custom_build( outputs_image_ref_to='result/ref', ) +# Load the latest CRDs from Nix +watch_file('result') +if os.path.exists('result'): + k8s_yaml('result/crds.yaml') + # We need to set the correct image annotation on the operator Deployment to use e.g. # oci.stackable.tech/sandbox/opa-operator:7y19m3d8clwxlv34v5q2x4p7v536s00g instead of # oci.stackable.tech/sandbox/opa-operator:0.0.0-dev (which does not exist) @@ -30,12 +35,18 @@ helm_values = settings.get('helm_values', None) helm_override_image_repository = 'image.repository=' + registry + '/' + operator_name -k8s_yaml(helm( - 'deploy/helm/' + operator_name, - name=operator_name, - namespace="stackable-operators", - set=[ - helm_override_image_repository, - ], - values=helm_values, -)) +# Exclude stale CRDs from Helm chart, and apply the rest +helm_crds, helm_non_crds = filter_yaml( + helm( + 'deploy/helm/' + operator_name, + name=operator_name, + namespace="stackable-operators", + set=[ + helm_override_image_repository, + ], + values=helm_values, + ), + api_version = "^apiextensions\\.k8s\\.io/.*$", + kind = "^CustomResourceDefinition$", +) +k8s_yaml(helm_non_crds) diff --git a/deploy/helm/secret-operator/crds/crds.yaml b/deploy/helm/secret-operator/crds/crds.yaml new file mode 100644 index 00000000..19c870e5 --- /dev/null +++ b/deploy/helm/secret-operator/crds/crds.yaml @@ -0,0 +1,937 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: secretclasses.secrets.stackable.tech + annotations: + helm.sh/resource-policy: keep +spec: + group: secrets.stackable.tech + names: + categories: [] + kind: SecretClass + plural: secretclasses + shortNames: [] + singular: secretclass + scope: Cluster + versions: + - additionalPrinterColumns: [] + name: v1alpha2 + schema: + openAPIV3Schema: + description: Auto-generated derived type for SecretClassSpec via `CustomResource` + properties: + spec: + description: |- + A [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass) is a cluster-global Kubernetes resource + that defines a category of secrets that the Secret Operator knows how to provision. + properties: + backend: + description: |- + Each SecretClass is associated with a single + [backend](https://docs.stackable.tech/home/nightly/secret-operator/secretclass#backend), + which dictates the mechanism for issuing that kind of Secret. + oneOf: + - required: + - k8sSearch + - required: + - autoTls + - required: + - certManager + - required: + - kerberosKeytab + properties: + autoTls: + description: |- + The [`autoTls` backend](https://docs.stackable.tech/home/nightly/secret-operator/secretclass#backend-autotls) + issues a TLS certificate signed by the Secret Operator. + The certificate authority can be provided by the administrator, or managed automatically by the Secret Operator. + + A new certificate and key pair will be generated and signed for each Pod, keys or certificates are never reused. + properties: + additionalTrustRoots: + default: [] + description: Additional trust roots which are added to the provided `ca.crt` file. + items: + oneOf: + - required: + - configMap + - required: + - secret + properties: + configMap: + description: |- + Reference (name and namespace) to a Kubernetes ConfigMap object where additional + certificates are stored. + The extensions of the keys denote its contents: A key suffixed with `.crt` contains a stack + of base64 encoded DER certificates, a key suffixed with `.der` contains a binary DER + certificate. + properties: + name: + description: Name of the ConfigMap being referred to. + type: string + namespace: + description: Namespace of the ConfigMap being referred to. + type: string + required: + - name + - namespace + type: object + secret: + description: |- + Reference (name and namespace) to a Kubernetes Secret object where additional certificates + are stored. + The extensions of the keys denote its contents: A key suffixed with `.crt` contains a stack + of base64 encoded DER certificates, a key suffixed with `.der` contains a binary DER + certificate. + properties: + name: + description: Name of the Secret being referred to. + type: string + namespace: + description: Namespace of the Secret being referred to. + type: string + required: + - name + - namespace + type: object + type: object + type: array + ca: + description: Configures the certificate authority used to issue Pod certificates. + properties: + autoGenerate: + default: false + description: |- + Whether the certificate authority should be managed by Secret Operator, including being generated + if it does not already exist. + type: boolean + caCertificateLifetime: + default: 365d + description: |- + The lifetime of each generated certificate authority. + + Should always be more than double `maxCertificateLifetime`. + + If `autoGenerate: true` then the Secret Operator will prepare a new CA certificate the old CA approaches expiration. + If `autoGenerate: false` then the Secret Operator will log a warning instead. + type: string + caCertificateRetirementDuration: + default: 1h + description: |- + Duration at the end of the CA certificate lifetime where no signed certificate will exist. + + Retired (or expired) CA certificates will not be published and will not be used for + signing leaf certificates. + type: string + keyGeneration: + default: + rsa: + length: 2048 + description: |- + The algorithm used to generate a key pair and required configuration settings. + Currently only RSA and a key length of 2048, 3072 or 4096 bits can be configured. + oneOf: + - required: + - rsa + properties: + rsa: + properties: + length: + description: |- + The amount of bits used for generating the RSA keypair. + Currently, `2048`, `3072` and `4096` are supported. Defaults to `2048` bits. + enum: + - 2048 + - 3072 + - 4096 + type: integer + required: + - length + type: object + type: object + secret: + description: |- + Reference (name and namespace) to a Kubernetes Secret object where the CA certificate + and key is stored in the keys `ca.crt` and `ca.key` respectively. + properties: + name: + description: Name of the Secret being referred to. + type: string + namespace: + description: Namespace of the Secret being referred to. + type: string + required: + - name + - namespace + type: object + required: + - secret + type: object + maxCertificateLifetime: + default: 15d + description: |- + Maximum lifetime the created certificates are allowed to have. + In case consumers request a longer lifetime than allowed by this setting, + the lifetime will be the minimum of both, so this setting takes precedence. + The default value is 15 days. + + The maximum lifetime must be less than a quarter of the active CA certificate lifetime + where the active CA certificate lifetime is `ca.ca_certificate_lifetime - + ca.ca_certificate_retirement_duration` to ensure that two subjects always have a common + CA certificate in their trust stores – assuming that CAs are rotated at half of their + active lifetimes. + + For instance, if a pod is created right before half of the active CA lifetime has + passed, then it is signed by this CA but it does not know yet the new CA certificate + which is created right afterwards. If another pod is created so that its certificate + lifetime ends right after the first active CA lifetime then it is signed by the new CA. + The `max_certificate_lifetime` must be chosen so that these two pods have no + overlapping lifetimes, otherwise the first pod would see the second one signed by an + unknown CA certificate. This can be achieved by the mentioned formula. + type: string + required: + - ca + type: object + certManager: + description: |- + The [`certManager` backend][1] injects a TLS certificate issued by [cert-manager]. + + A new certificate will be requested the first time it is used by a Pod, it + will be reused after that (subject to cert-manager renewal rules). + + [1]: https://docs.stackable.tech/home/nightly/secret-operator/secretclass#backend-certmanager + [cert-manager]: https://cert-manager.io/ + properties: + defaultCertificateLifetime: + default: 1d + description: |- + The default lifetime of certificates. + + Defaults to 1 day. This may need to be increased for external issuers that impose rate limits (such as Let's Encrypt). + type: string + issuer: + description: A reference to the cert-manager issuer that the certificates should be requested from. + properties: + kind: + description: |- + The kind of the issuer, Issuer or ClusterIssuer. + + If Issuer then it must be in the same namespace as the Pods using it. + enum: + - Issuer + - ClusterIssuer + type: string + name: + description: The name of the issuer. + type: string + required: + - kind + - name + type: object + keyGeneration: + default: + rsa: + length: 2048 + description: |- + The algorithm used to generate a key pair and required configuration settings. + Currently only RSA and a key length of 2048, 3072 or 4096 bits can be configured. + oneOf: + - required: + - rsa + properties: + rsa: + properties: + length: + description: |- + The amount of bits used for generating the RSA keypair. + Currently, `2048`, `3072` and `4096` are supported. Defaults to `2048` bits. + enum: + - 2048 + - 3072 + - 4096 + type: integer + required: + - length + type: object + type: object + required: + - issuer + type: object + k8sSearch: + description: |- + The [`k8sSearch` backend](https://docs.stackable.tech/home/nightly/secret-operator/secretclass#backend-k8ssearch) + can be used to mount Secrets across namespaces into Pods. + properties: + searchNamespace: + description: Configures the namespace searched for Secret objects. + oneOf: + - required: + - pod + - required: + - name + properties: + name: + description: |- + The Secret objects are located in a single global namespace. + Should be used for secrets that are provisioned by the cluster administrator. + type: string + pod: + description: |- + The Secret objects are located in the same namespace as the Pod object. + Should be used for Secrets that are provisioned by the application administrator. + type: object + type: object + trustStoreConfigMapName: + description: |- + Name of a ConfigMap that contains the information required to validate against this SecretClass. + + Resolved relative to `search_namespace`. + + Required to request a TrustStore for this SecretClass. + nullable: true + type: string + required: + - searchNamespace + type: object + kerberosKeytab: + description: |- + The [`kerberosKeytab` backend](https://docs.stackable.tech/home/nightly/secret-operator/secretclass#backend-kerberoskeytab) + creates a Kerberos keytab file for a selected realm. + The Kerberos KDC and administrator credentials must be provided by the administrator. + properties: + admin: + description: Kerberos admin configuration settings. + oneOf: + - required: + - mit + - required: + - activeDirectory + properties: + activeDirectory: + description: Credentials should be provisioned in a Microsoft Active Directory domain. + properties: + generateSamAccountName: + description: |- + Allows samAccountName generation for new accounts to be customized. + Note that setting this field (even if empty) makes the Secret Operator take + over the generation duty from the domain controller. + nullable: true + properties: + prefix: + default: '' + description: A prefix to be prepended to generated samAccountNames. + type: string + totalLength: + default: 20 + description: |- + The total length of generated samAccountNames, _including_ `prefix`. + Must be larger than the length of `prefix`, but at most `20`. + + Note that this should be as large as possible, to minimize the risk of collisions. + format: uint8 + maximum: 255.0 + minimum: 0.0 + type: integer + type: object + ldapServer: + description: |- + An AD LDAP server, such as the AD Domain Controller. + This must match the server’s FQDN, or GSSAPI authentication will fail. + type: string + ldapTlsCaSecret: + description: |- + Reference (name and namespace) to a Kubernetes Secret object containing + the TLS CA (in `ca.crt`) that the LDAP server’s certificate should be authenticated against. + properties: + name: + description: Name of the Secret being referred to. + type: string + namespace: + description: Namespace of the Secret being referred to. + type: string + required: + - name + - namespace + type: object + passwordCacheSecret: + description: |- + Reference (name and namespace) to a Kubernetes Secret object where workload + passwords will be stored. This must not be accessible to end users. + properties: + name: + description: Name of the Secret being referred to. + type: string + namespace: + description: Namespace of the Secret being referred to. + type: string + required: + - name + - namespace + type: object + schemaDistinguishedName: + description: |- + The root Distinguished Name (DN) for AD-managed schemas, + typically `CN=Schema,CN=Configuration,{domain_dn}`. + type: string + userDistinguishedName: + description: |- + The root Distinguished Name (DN) where service accounts should be provisioned, + typically `CN=Users,{domain_dn}`. + type: string + required: + - ldapServer + - ldapTlsCaSecret + - passwordCacheSecret + - schemaDistinguishedName + - userDistinguishedName + type: object + mit: + description: Credentials should be provisioned in a MIT Kerberos Admin Server. + properties: + kadminServer: + description: |- + The hostname of the Kerberos Admin Server. + This should be provided by the Kerberos administrator. + type: string + required: + - kadminServer + type: object + type: object + adminKeytabSecret: + description: |- + Reference (`name` and `namespace`) to a K8s Secret object where a + keytab with administrative privileges is stored in the key `keytab`. + properties: + name: + description: Name of the Secret being referred to. + type: string + namespace: + description: Namespace of the Secret being referred to. + type: string + required: + - name + - namespace + type: object + adminPrincipal: + description: The admin principal. + type: string + kdc: + description: |- + The hostname of the Kerberos Key Distribution Center (KDC). + This should be provided by the Kerberos administrator. + type: string + realmName: + description: The name of the Kerberos realm. This should be provided by the Kerberos administrator. + type: string + required: + - admin + - adminKeytabSecret + - adminPrincipal + - kdc + - realmName + type: object + type: object + required: + - backend + type: object + required: + - spec + title: SecretClass + type: object + served: true + storage: true + subresources: {} + - additionalPrinterColumns: [] + name: v1alpha1 + schema: + openAPIV3Schema: + description: Auto-generated derived type for SecretClassSpec via `CustomResource` + properties: + spec: + description: |- + A [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass) is a cluster-global Kubernetes resource + that defines a category of secrets that the Secret Operator knows how to provision. + properties: + backend: + description: |- + Each SecretClass is associated with a single + [backend](https://docs.stackable.tech/home/nightly/secret-operator/secretclass#backend), + which dictates the mechanism for issuing that kind of Secret. + oneOf: + - required: + - k8sSearch + - required: + - autoTls + - required: + - experimentalCertManager + - required: + - kerberosKeytab + properties: + autoTls: + description: |- + The [`autoTls` backend](https://docs.stackable.tech/home/nightly/secret-operator/secretclass#backend-autotls) + issues a TLS certificate signed by the Secret Operator. + The certificate authority can be provided by the administrator, or managed automatically by the Secret Operator. + + A new certificate and key pair will be generated and signed for each Pod, keys or certificates are never reused. + properties: + additionalTrustRoots: + default: [] + description: Additional trust roots which are added to the provided `ca.crt` file. + items: + oneOf: + - required: + - configMap + - required: + - secret + properties: + configMap: + description: |- + Reference (name and namespace) to a Kubernetes ConfigMap object where additional + certificates are stored. + The extensions of the keys denote its contents: A key suffixed with `.crt` contains a stack + of base64 encoded DER certificates, a key suffixed with `.der` contains a binary DER + certificate. + properties: + name: + description: Name of the ConfigMap being referred to. + type: string + namespace: + description: Namespace of the ConfigMap being referred to. + type: string + required: + - name + - namespace + type: object + secret: + description: |- + Reference (name and namespace) to a Kubernetes Secret object where additional certificates + are stored. + The extensions of the keys denote its contents: A key suffixed with `.crt` contains a stack + of base64 encoded DER certificates, a key suffixed with `.der` contains a binary DER + certificate. + properties: + name: + description: Name of the Secret being referred to. + type: string + namespace: + description: Namespace of the Secret being referred to. + type: string + required: + - name + - namespace + type: object + type: object + type: array + ca: + description: Configures the certificate authority used to issue Pod certificates. + properties: + autoGenerate: + default: false + description: |- + Whether the certificate authority should be managed by Secret Operator, including being generated + if it does not already exist. + type: boolean + caCertificateLifetime: + default: 365d + description: |- + The lifetime of each generated certificate authority. + + Should always be more than double `maxCertificateLifetime`. + + If `autoGenerate: true` then the Secret Operator will prepare a new CA certificate the old CA approaches expiration. + If `autoGenerate: false` then the Secret Operator will log a warning instead. + type: string + caCertificateRetirementDuration: + default: 1h + description: |- + Duration at the end of the CA certificate lifetime where no signed certificate will exist. + + Retired (or expired) CA certificates will not be published and will not be used for + signing leaf certificates. + type: string + keyGeneration: + default: + rsa: + length: 2048 + description: |- + The algorithm used to generate a key pair and required configuration settings. + Currently only RSA and a key length of 2048, 3072 or 4096 bits can be configured. + oneOf: + - required: + - rsa + properties: + rsa: + properties: + length: + description: |- + The amount of bits used for generating the RSA keypair. + Currently, `2048`, `3072` and `4096` are supported. Defaults to `2048` bits. + enum: + - 2048 + - 3072 + - 4096 + type: integer + required: + - length + type: object + type: object + secret: + description: |- + Reference (name and namespace) to a Kubernetes Secret object where the CA certificate + and key is stored in the keys `ca.crt` and `ca.key` respectively. + properties: + name: + description: Name of the Secret being referred to. + type: string + namespace: + description: Namespace of the Secret being referred to. + type: string + required: + - name + - namespace + type: object + required: + - secret + type: object + maxCertificateLifetime: + default: 15d + description: |- + Maximum lifetime the created certificates are allowed to have. + In case consumers request a longer lifetime than allowed by this setting, + the lifetime will be the minimum of both, so this setting takes precedence. + The default value is 15 days. + + The maximum lifetime must be less than a quarter of the active CA certificate lifetime + where the active CA certificate lifetime is `ca.ca_certificate_lifetime - + ca.ca_certificate_retirement_duration` to ensure that two subjects always have a common + CA certificate in their trust stores – assuming that CAs are rotated at half of their + active lifetimes. + + For instance, if a pod is created right before half of the active CA lifetime has + passed, then it is signed by this CA but it does not know yet the new CA certificate + which is created right afterwards. If another pod is created so that its certificate + lifetime ends right after the first active CA lifetime then it is signed by the new CA. + The `max_certificate_lifetime` must be chosen so that these two pods have no + overlapping lifetimes, otherwise the first pod would see the second one signed by an + unknown CA certificate. This can be achieved by the mentioned formula. + type: string + required: + - ca + type: object + experimentalCertManager: + description: |- + The [`certManager` backend][1] injects a TLS certificate issued by [cert-manager]. + + A new certificate will be requested the first time it is used by a Pod, it + will be reused after that (subject to cert-manager renewal rules). + + [1]: https://docs.stackable.tech/home/nightly/secret-operator/secretclass#backend-certmanager + [cert-manager]: https://cert-manager.io/ + properties: + defaultCertificateLifetime: + default: 1d + description: |- + The default lifetime of certificates. + + Defaults to 1 day. This may need to be increased for external issuers that impose rate limits (such as Let's Encrypt). + type: string + issuer: + description: A reference to the cert-manager issuer that the certificates should be requested from. + properties: + kind: + description: |- + The kind of the issuer, Issuer or ClusterIssuer. + + If Issuer then it must be in the same namespace as the Pods using it. + enum: + - Issuer + - ClusterIssuer + type: string + name: + description: The name of the issuer. + type: string + required: + - kind + - name + type: object + keyGeneration: + default: + rsa: + length: 2048 + description: |- + The algorithm used to generate a key pair and required configuration settings. + Currently only RSA and a key length of 2048, 3072 or 4096 bits can be configured. + oneOf: + - required: + - rsa + properties: + rsa: + properties: + length: + description: |- + The amount of bits used for generating the RSA keypair. + Currently, `2048`, `3072` and `4096` are supported. Defaults to `2048` bits. + enum: + - 2048 + - 3072 + - 4096 + type: integer + required: + - length + type: object + type: object + required: + - issuer + type: object + k8sSearch: + description: |- + The [`k8sSearch` backend](https://docs.stackable.tech/home/nightly/secret-operator/secretclass#backend-k8ssearch) + can be used to mount Secrets across namespaces into Pods. + properties: + searchNamespace: + description: Configures the namespace searched for Secret objects. + oneOf: + - required: + - pod + - required: + - name + properties: + name: + description: |- + The Secret objects are located in a single global namespace. + Should be used for secrets that are provisioned by the cluster administrator. + type: string + pod: + description: |- + The Secret objects are located in the same namespace as the Pod object. + Should be used for Secrets that are provisioned by the application administrator. + type: object + type: object + trustStoreConfigMapName: + description: |- + Name of a ConfigMap that contains the information required to validate against this SecretClass. + + Resolved relative to `search_namespace`. + + Required to request a TrustStore for this SecretClass. + nullable: true + type: string + required: + - searchNamespace + type: object + kerberosKeytab: + description: |- + The [`kerberosKeytab` backend](https://docs.stackable.tech/home/nightly/secret-operator/secretclass#backend-kerberoskeytab) + creates a Kerberos keytab file for a selected realm. + The Kerberos KDC and administrator credentials must be provided by the administrator. + properties: + admin: + description: Kerberos admin configuration settings. + oneOf: + - required: + - mit + - required: + - activeDirectory + properties: + activeDirectory: + description: Credentials should be provisioned in a Microsoft Active Directory domain. + properties: + experimentalGenerateSamAccountName: + description: |- + Allows samAccountName generation for new accounts to be customized. + Note that setting this field (even if empty) makes the Secret Operator take + over the generation duty from the domain controller. + nullable: true + properties: + prefix: + default: '' + description: A prefix to be prepended to generated samAccountNames. + type: string + totalLength: + default: 20 + description: |- + The total length of generated samAccountNames, _including_ `prefix`. + Must be larger than the length of `prefix`, but at most `20`. + + Note that this should be as large as possible, to minimize the risk of collisions. + format: uint8 + maximum: 255.0 + minimum: 0.0 + type: integer + type: object + ldapServer: + description: |- + An AD LDAP server, such as the AD Domain Controller. + This must match the server’s FQDN, or GSSAPI authentication will fail. + type: string + ldapTlsCaSecret: + description: |- + Reference (name and namespace) to a Kubernetes Secret object containing + the TLS CA (in `ca.crt`) that the LDAP server’s certificate should be authenticated against. + properties: + name: + description: Name of the Secret being referred to. + type: string + namespace: + description: Namespace of the Secret being referred to. + type: string + required: + - name + - namespace + type: object + passwordCacheSecret: + description: |- + Reference (name and namespace) to a Kubernetes Secret object where workload + passwords will be stored. This must not be accessible to end users. + properties: + name: + description: Name of the Secret being referred to. + type: string + namespace: + description: Namespace of the Secret being referred to. + type: string + required: + - name + - namespace + type: object + schemaDistinguishedName: + description: |- + The root Distinguished Name (DN) for AD-managed schemas, + typically `CN=Schema,CN=Configuration,{domain_dn}`. + type: string + userDistinguishedName: + description: |- + The root Distinguished Name (DN) where service accounts should be provisioned, + typically `CN=Users,{domain_dn}`. + type: string + required: + - ldapServer + - ldapTlsCaSecret + - passwordCacheSecret + - schemaDistinguishedName + - userDistinguishedName + type: object + mit: + description: Credentials should be provisioned in a MIT Kerberos Admin Server. + properties: + kadminServer: + description: |- + The hostname of the Kerberos Admin Server. + This should be provided by the Kerberos administrator. + type: string + required: + - kadminServer + type: object + type: object + adminKeytabSecret: + description: |- + Reference (`name` and `namespace`) to a K8s Secret object where a + keytab with administrative privileges is stored in the key `keytab`. + properties: + name: + description: Name of the Secret being referred to. + type: string + namespace: + description: Namespace of the Secret being referred to. + type: string + required: + - name + - namespace + type: object + adminPrincipal: + description: The admin principal. + type: string + kdc: + description: |- + The hostname of the Kerberos Key Distribution Center (KDC). + This should be provided by the Kerberos administrator. + type: string + realmName: + description: The name of the Kerberos realm. This should be provided by the Kerberos administrator. + type: string + required: + - admin + - adminKeytabSecret + - adminPrincipal + - kdc + - realmName + type: object + type: object + required: + - backend + type: object + required: + - spec + title: SecretClass + type: object + served: true + storage: false + subresources: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: truststores.secrets.stackable.tech + annotations: + helm.sh/resource-policy: keep +spec: + group: secrets.stackable.tech + names: + categories: [] + kind: TrustStore + plural: truststores + shortNames: [] + singular: truststore + scope: Namespaced + versions: + - additionalPrinterColumns: [] + name: v1alpha1 + schema: + openAPIV3Schema: + description: Auto-generated derived type for TrustStoreSpec via `CustomResource` + properties: + spec: + description: |- + A [TrustStore](https://docs.stackable.tech/home/nightly/secret-operator/truststore) requests information about how to + validate secrets issued by a [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass). + + The requested information is written to a ConfigMap with the same name as the TrustStore. + properties: + format: + description: The [format](https://docs.stackable.tech/home/nightly/secret-operator/secretclass#format) that the data should be converted into. + enum: + - tls-pem + - tls-pkcs12 + - kerberos + - null + nullable: true + type: string + secretClassName: + description: The name of the SecretClass that the request concerns. + type: string + targetKind: + default: ConfigMap + description: |- + Which Kubernetes kind should be used to output the requested information to. + + The trust information (such as a `ca.crt`) can be considered public information, so we put + it in a `ConfigMap` by default. However, some tools might require it to be placed in a + `Secret`, so we also support that. + + Can be either `ConfigMap` or `Secret`, defaults to `ConfigMap`. + enum: + - Secret + - ConfigMap + type: string + required: + - secretClassName + type: object + required: + - spec + title: TrustStore + type: object + served: true + storage: true + subresources: {} diff --git a/deploy/helm/secret-operator/templates/service.yaml b/deploy/helm/secret-operator/templates/service.yaml index 5cf6439b..0cb05696 100644 --- a/deploy/helm/secret-operator/templates/service.yaml +++ b/deploy/helm/secret-operator/templates/service.yaml @@ -1,4 +1,4 @@ -{{- if .Values.maintenance.customResourceDefinitions.maintain }} + --- apiVersion: v1 kind: Service @@ -11,11 +11,9 @@ metadata: {{- include "operator.labels" . | nindent 4 }} spec: selector: - webhook.stackable.tech/conversion: enabled - {{- include "operator.selectorLabels" . | nindent 4 }} + {{- include "operator.selectorLabels" . | nindent 6 }} ports: - name: conversion-webhook protocol: TCP port: 8443 targetPort: 8443 -{{- end }} diff --git a/docker/Dockerfile b/docker/Dockerfile index 30d5bf19..512d970d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -21,6 +21,8 @@ FROM oci.stackable.tech/sdp/ubi9-rust-builder:latest AS builder FROM registry.access.redhat.com/ubi9/ubi-minimal:latest AS operator ARG VERSION +# NOTE (@Techassi): This is required for OpenShift/Red Hat certification +# Keeping this as "1" seems to be fine since a couple of years /shrug ARG RELEASE="1" # These are chosen at random and are this high on purpose to have very little chance to clash with an existing user or group on the host system diff --git a/renovate.json b/renovate.json index f28373c7..7af8ce96 100644 --- a/renovate.json +++ b/renovate.json @@ -3,5 +3,5 @@ "extends": [ "local>stackabletech/.github:renovate-config" ], - "ignorePaths": [".github/workflows/build.yml", ".github/workflows/general_daily_security.yml", ".github/workflows/integration-test.yml", ".github/workflows/pr_pre-commit.yaml"] + "ignorePaths": [".github/workflows/build.yaml", ".github/workflows/general_daily_security.yml", ".github/workflows/integration-test.yml", ".github/workflows/pr_pre-commit.yaml"] } diff --git a/scripts/run-tests b/scripts/run-tests index 7fa07fc5..00639f45 100755 --- a/scripts/run-tests +++ b/scripts/run-tests @@ -11,7 +11,6 @@ import shutil import subprocess import sys import tempfile -import time __version__ = "0.0.1" @@ -452,7 +451,6 @@ def main(argv) -> int: gen_tests(opts.test_suite, opts.namespace, opts.work_dir) with release_file(opts.operator, opts.skip_operator) as f: maybe_install_release(opts.skip_release, f, opts.listener_class_preset) - time.sleep(10) if opts.skip_tests: logging.info("Skip running tests.") else: From 54589bc50ab3a88098c7abc54e4ad0235bc50b06 Mon Sep 17 00:00:00 2001 From: Nick Larsen Date: Thu, 27 Nov 2025 15:30:23 +0100 Subject: [PATCH 2/4] ci: Add merge_group trigger and set most jobs to be skipped on merge_group --- .github/workflows/build.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 7f41c16c..95dcc742 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -27,6 +27,7 @@ on: - 'docker/**' - 'Cargo.*' - '*.rs' + merge_group: env: OPERATOR_NAME: "secret-operator" @@ -75,6 +76,7 @@ jobs: build-container-image: name: Build/Publish ${{ matrix.runner.arch }} Image + if: github.event_name != 'merge_group' permissions: id-token: write strategy: @@ -157,6 +159,7 @@ jobs: publish-index-manifest: name: Publish/Sign ${{ needs.build-container-image.outputs.operator-version }} Index + if: github.event_name != 'merge_group' needs: - build-container-image permissions: @@ -179,6 +182,7 @@ jobs: publish-helm-chart: name: Package/Publish ${{ needs.build-container-image.outputs.operator-version }} Helm Chart + if: github.event_name != 'merge_group' needs: - build-container-image permissions: @@ -204,6 +208,7 @@ jobs: openshift-preflight-check: name: Run OpenShift Preflight Check for ${{ needs.build-container-image.outputs.operator-version }}-${{ matrix.arch }} + if: github.event_name != 'merge_group' needs: - build-container-image - publish-index-manifest @@ -238,12 +243,12 @@ jobs: notify: name: Failure Notification + if: (failure() || github.run_attempt > 1) && github.event_name != 'merge_group' needs: - build-container-image - publish-index-manifest - publish-helm-chart runs-on: ubuntu-latest - if: failure() || github.run_attempt > 1 steps: - name: Checkout Repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 From b9245fa3510b0507fe8949735d1fcb3a15edf033 Mon Sep 17 00:00:00 2001 From: Nick Larsen Date: Thu, 27 Nov 2025 19:40:46 +0100 Subject: [PATCH 3/4] chore: Revert templating changes that are incompatible with secret-operator\'s reality --- .gitignore | 1 + Makefile | 89 +- Tiltfile | 29 +- deploy/helm/secret-operator/crds/crds.yaml | 937 ------------------ .../secret-operator/templates/service.yaml | 6 +- 5 files changed, 99 insertions(+), 963 deletions(-) delete mode 100644 deploy/helm/secret-operator/crds/crds.yaml diff --git a/.gitignore b/.gitignore index 2dbc7ab0..abc7ff9a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ result image.tar tilt_options.json +local_values.yaml .direnv/ .direnvrc diff --git a/Makefile b/Makefile index 7c13ca40..9afd741a 100644 --- a/Makefile +++ b/Makefile @@ -9,11 +9,17 @@ .PHONY: build publish +TAG := $(shell git rev-parse --short HEAD) OPERATOR_NAME := secret-operator VERSION := $(shell cargo metadata --format-version 1 | jq -r '.packages[] | select(.name=="stackable-${OPERATOR_NAME}") | .version') +ARCH := $(shell uname -m | sed -e 's#x86_64#amd64#' | sed -e 's#aarch64#arm64#') OCI_REGISTRY_HOSTNAME := oci.stackable.tech OCI_REGISTRY_PROJECT_IMAGES := sdp +OCI_REGISTRY_PROJECT_CHARTS := sdp-charts +# This will be overwritten by an environmental variable if called from the github action +HELM_CHART_NAME := ${OPERATOR_NAME} +HELM_CHART_ARTIFACT := target/helm/${OPERATOR_NAME}-${VERSION}.tgz SHELL=/usr/bin/env bash -euo pipefail @@ -27,12 +33,78 @@ render-docs: docker-build: docker build --force-rm --build-arg VERSION=${VERSION} -t "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-${ARCH}" -f docker/Dockerfile . +docker-publish: + # Push to Harbor + # We need to use "value" here to prevent the variable from being recursively expanded by make (username contains a dollar sign, since it's a Harbor bot) + docker login --username '${value OCI_REGISTRY_SDP_USERNAME}' --password '${OCI_REGISTRY_SDP_PASSWORD}' '${OCI_REGISTRY_HOSTNAME}' + DOCKER_OUTPUT=$$(docker push --all-tags '${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}');\ + # Obtain the digest of the pushed image from the output of `docker push`, because signing by tag is deprecated and will be removed from cosign in the future\ + REPO_DIGEST_OF_IMAGE=$$(echo "$$DOCKER_OUTPUT" | awk '/^${VERSION}-${ARCH}: digest: sha256:[0-9a-f]{64} size: [0-9]+$$/ { print $$3 }');\ + if [ -z "$$REPO_DIGEST_OF_IMAGE" ]; then\ + echo 'Could not find repo digest for container image: ${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-${ARCH}';\ + exit 1;\ + fi;\ + # This generates a signature and publishes it to the registry, next to the image\ + # Uses the keyless signing flow with Github Actions as identity provider\ + cosign sign -y "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}@$$REPO_DIGEST_OF_IMAGE";\ + # Generate the SBOM for the operator image, this leverages the already generated SBOM for the operator binary by cargo-cyclonedx\ + syft scan --output cyclonedx-json@1.5=sbom.json --select-catalogers "-cargo-auditable-binary-cataloger,+sbom-cataloger" --scope all-layers --source-name "${OPERATOR_NAME}" --source-version "${VERSION}-${ARCH}" "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}@$$REPO_DIGEST_OF_IMAGE";\ + # Determine the PURL for the container image\ + URLENCODED_REPO_DIGEST_OF_IMAGE=$$(echo "$$REPO_DIGEST_OF_IMAGE" | sed 's/:/%3A/g');\ + PURL="pkg:oci/${OPERATOR_NAME}@$$URLENCODED_REPO_DIGEST_OF_IMAGE?arch=${ARCH}&repository_url=${OCI_REGISTRY_HOSTNAME}%2F${OCI_REGISTRY_PROJECT_IMAGES}%2F${OPERATOR_NAME}";\ + # Get metadata from the image\ + IMAGE_DESCRIPTION=$$(docker inspect --format='{{.Config.Labels.description}}' "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-${ARCH}");\ + IMAGE_NAME=$$(docker inspect --format='{{.Config.Labels.name}}' "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-${ARCH}");\ + # Merge the SBOM with the metadata for the operator\ + jq -s '{"metadata":{"component":{"description":"'"$$IMAGE_NAME. $$IMAGE_DESCRIPTION"'","supplier":{"name":"Stackable GmbH","url":["https://stackable.tech/"]},"author":"Stackable GmbH","purl":"'"$$PURL"'","publisher":"Stackable GmbH"}}} * .[0]' sbom.json > sbom.merged.json;\ + # Attest the SBOM to the image\ + cosign attest -y --predicate sbom.merged.json --type cyclonedx "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}@$$REPO_DIGEST_OF_IMAGE" + +# This assumes "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-amd64 and "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-arm64 are built and pushed +docker-manifest-list-build: + docker manifest create "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}" --amend "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-amd64" --amend "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}-arm64" + +docker-manifest-list-publish: + # Push to Harbor + # We need to use "value" here to prevent the variable from being recursively expanded by make (username contains a dollar sign, since it's a Harbor bot) + docker login --username '${value OCI_REGISTRY_SDP_USERNAME}' --password '${OCI_REGISTRY_SDP_PASSWORD}' '${OCI_REGISTRY_HOSTNAME}' + DIGEST_HARBOR=$$(docker manifest push "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}");\ + # Refer to image via its digest (oci.stackable.tech/sdp/airflow@sha256:0a1b2c...);\ + # This generates a signature and publishes it to the registry, next to the image\ + # Uses the keyless signing flow with Github Actions as identity provider\ + cosign sign -y "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}@$$DIGEST_HARBOR" + +# TODO remove if not used/needed +docker: docker-build docker-publish + +print-docker-tag: + @echo "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_IMAGES}/${OPERATOR_NAME}:${VERSION}" + +helm-publish: + # Push to Harbor + # We need to use "value" here to prevent the variable from being recursively expanded by make (username contains a dollar sign, since it's a Harbor bot) + helm registry login --username '${value OCI_REGISTRY_SDP_CHARTS_USERNAME}' --password '${OCI_REGISTRY_SDP_CHARTS_PASSWORD}' '${OCI_REGISTRY_HOSTNAME}' + # Obtain the digest of the pushed artifact from the output of `helm push`, because signing by tag is deprecated and will be removed from cosign in the future\ + HELM_OUTPUT=$$(helm push '${HELM_CHART_ARTIFACT}' 'oci://${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_CHARTS}' 2>&1);\ + REPO_DIGEST_OF_ARTIFACT=$$(echo "$$HELM_OUTPUT" | awk '/^Digest: sha256:[0-9a-f]{64}$$/ { print $$2 }');\ + if [ -z "$$REPO_DIGEST_OF_ARTIFACT" ]; then\ + echo 'Could not find repo digest for helm chart: ${HELM_CHART_NAME}';\ + exit 1;\ + fi;\ + # Login to Harbor, needed for cosign to be able to push the signature for the Helm chart\ + docker login --username '${value OCI_REGISTRY_SDP_CHARTS_USERNAME}' --password '${OCI_REGISTRY_SDP_CHARTS_PASSWORD}' '${OCI_REGISTRY_HOSTNAME}';\ + # This generates a signature and publishes it to the registry, next to the chart artifact\ + # Uses the keyless signing flow with Github Actions as identity provider\ + cosign sign -y "${OCI_REGISTRY_HOSTNAME}/${OCI_REGISTRY_PROJECT_CHARTS}/${HELM_CHART_NAME}@$$REPO_DIGEST_OF_ARTIFACT" + +helm-package: + mkdir -p target/helm && helm package --destination target/helm deploy/helm/${OPERATOR_NAME} + ## Chart related targets compile-chart: version crds config chart-clean: rm -rf "deploy/helm/${OPERATOR_NAME}/configs" - rm -rf "deploy/helm/${OPERATOR_NAME}/crds" version: cat "deploy/helm/${OPERATOR_NAME}/Chart.yaml" | yq ".version = \"${VERSION}\" | .appVersion = \"${VERSION}\"" > "deploy/helm/${OPERATOR_NAME}/Chart.yaml.new" @@ -44,9 +116,11 @@ config: cp -r deploy/config-spec/* "deploy/helm/${OPERATOR_NAME}/configs";\ fi +# We generate a crds.yaml, so that the effect of code changes are visible. +# The operator will take care of the CRD rollout itself. crds: - mkdir -p deploy/helm/"${OPERATOR_NAME}"/crds - cargo run --bin stackable-"${OPERATOR_NAME}" -- crd | yq eval '.metadata.annotations["helm.sh/resource-policy"]="keep"' - > "deploy/helm/${OPERATOR_NAME}/crds/crds.yaml" + mkdir -p extra + cargo run --bin stackable-"${OPERATOR_NAME}" -- crd > extra/crds.yaml chart-lint: compile-chart docker run -it -v $(shell pwd):/build/helm-charts -w /build/helm-charts quay.io/helmpack/chart-testing:v3.5.0 ct lint --config deploy/helm/ct.yaml @@ -60,7 +134,14 @@ regenerate-charts: chart-clean compile-chart regenerate-nix: nix run --extra-experimental-features "nix-command flakes" -f . regenerateNixLockfiles -build: regenerate-charts regenerate-nix docker-build +build: regenerate-charts regenerate-nix helm-package docker-build + +# This target is used by the CI +# It doesn't make use of any nix dependencies and thus aviods building the +# operator unnecessarily often. +build-ci: regenerate-charts helm-package docker-build + +publish: docker-publish helm-publish check-nix: @which nix || (echo "Error: 'nix' is not installed. Please install it to proceed."; exit 1) diff --git a/Tiltfile b/Tiltfile index ee0941c3..53c3a8e4 100644 --- a/Tiltfile +++ b/Tiltfile @@ -17,11 +17,6 @@ custom_build( outputs_image_ref_to='result/ref', ) -# Load the latest CRDs from Nix -watch_file('result') -if os.path.exists('result'): - k8s_yaml('result/crds.yaml') - # We need to set the correct image annotation on the operator Deployment to use e.g. # oci.stackable.tech/sandbox/opa-operator:7y19m3d8clwxlv34v5q2x4p7v536s00g instead of # oci.stackable.tech/sandbox/opa-operator:0.0.0-dev (which does not exist) @@ -35,18 +30,12 @@ helm_values = settings.get('helm_values', None) helm_override_image_repository = 'image.repository=' + registry + '/' + operator_name -# Exclude stale CRDs from Helm chart, and apply the rest -helm_crds, helm_non_crds = filter_yaml( - helm( - 'deploy/helm/' + operator_name, - name=operator_name, - namespace="stackable-operators", - set=[ - helm_override_image_repository, - ], - values=helm_values, - ), - api_version = "^apiextensions\\.k8s\\.io/.*$", - kind = "^CustomResourceDefinition$", -) -k8s_yaml(helm_non_crds) +k8s_yaml(helm( + 'deploy/helm/' + operator_name, + name=operator_name, + namespace="stackable-operators", + set=[ + helm_override_image_repository, + ], + values=helm_values, +)) diff --git a/deploy/helm/secret-operator/crds/crds.yaml b/deploy/helm/secret-operator/crds/crds.yaml deleted file mode 100644 index 19c870e5..00000000 --- a/deploy/helm/secret-operator/crds/crds.yaml +++ /dev/null @@ -1,937 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: secretclasses.secrets.stackable.tech - annotations: - helm.sh/resource-policy: keep -spec: - group: secrets.stackable.tech - names: - categories: [] - kind: SecretClass - plural: secretclasses - shortNames: [] - singular: secretclass - scope: Cluster - versions: - - additionalPrinterColumns: [] - name: v1alpha2 - schema: - openAPIV3Schema: - description: Auto-generated derived type for SecretClassSpec via `CustomResource` - properties: - spec: - description: |- - A [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass) is a cluster-global Kubernetes resource - that defines a category of secrets that the Secret Operator knows how to provision. - properties: - backend: - description: |- - Each SecretClass is associated with a single - [backend](https://docs.stackable.tech/home/nightly/secret-operator/secretclass#backend), - which dictates the mechanism for issuing that kind of Secret. - oneOf: - - required: - - k8sSearch - - required: - - autoTls - - required: - - certManager - - required: - - kerberosKeytab - properties: - autoTls: - description: |- - The [`autoTls` backend](https://docs.stackable.tech/home/nightly/secret-operator/secretclass#backend-autotls) - issues a TLS certificate signed by the Secret Operator. - The certificate authority can be provided by the administrator, or managed automatically by the Secret Operator. - - A new certificate and key pair will be generated and signed for each Pod, keys or certificates are never reused. - properties: - additionalTrustRoots: - default: [] - description: Additional trust roots which are added to the provided `ca.crt` file. - items: - oneOf: - - required: - - configMap - - required: - - secret - properties: - configMap: - description: |- - Reference (name and namespace) to a Kubernetes ConfigMap object where additional - certificates are stored. - The extensions of the keys denote its contents: A key suffixed with `.crt` contains a stack - of base64 encoded DER certificates, a key suffixed with `.der` contains a binary DER - certificate. - properties: - name: - description: Name of the ConfigMap being referred to. - type: string - namespace: - description: Namespace of the ConfigMap being referred to. - type: string - required: - - name - - namespace - type: object - secret: - description: |- - Reference (name and namespace) to a Kubernetes Secret object where additional certificates - are stored. - The extensions of the keys denote its contents: A key suffixed with `.crt` contains a stack - of base64 encoded DER certificates, a key suffixed with `.der` contains a binary DER - certificate. - properties: - name: - description: Name of the Secret being referred to. - type: string - namespace: - description: Namespace of the Secret being referred to. - type: string - required: - - name - - namespace - type: object - type: object - type: array - ca: - description: Configures the certificate authority used to issue Pod certificates. - properties: - autoGenerate: - default: false - description: |- - Whether the certificate authority should be managed by Secret Operator, including being generated - if it does not already exist. - type: boolean - caCertificateLifetime: - default: 365d - description: |- - The lifetime of each generated certificate authority. - - Should always be more than double `maxCertificateLifetime`. - - If `autoGenerate: true` then the Secret Operator will prepare a new CA certificate the old CA approaches expiration. - If `autoGenerate: false` then the Secret Operator will log a warning instead. - type: string - caCertificateRetirementDuration: - default: 1h - description: |- - Duration at the end of the CA certificate lifetime where no signed certificate will exist. - - Retired (or expired) CA certificates will not be published and will not be used for - signing leaf certificates. - type: string - keyGeneration: - default: - rsa: - length: 2048 - description: |- - The algorithm used to generate a key pair and required configuration settings. - Currently only RSA and a key length of 2048, 3072 or 4096 bits can be configured. - oneOf: - - required: - - rsa - properties: - rsa: - properties: - length: - description: |- - The amount of bits used for generating the RSA keypair. - Currently, `2048`, `3072` and `4096` are supported. Defaults to `2048` bits. - enum: - - 2048 - - 3072 - - 4096 - type: integer - required: - - length - type: object - type: object - secret: - description: |- - Reference (name and namespace) to a Kubernetes Secret object where the CA certificate - and key is stored in the keys `ca.crt` and `ca.key` respectively. - properties: - name: - description: Name of the Secret being referred to. - type: string - namespace: - description: Namespace of the Secret being referred to. - type: string - required: - - name - - namespace - type: object - required: - - secret - type: object - maxCertificateLifetime: - default: 15d - description: |- - Maximum lifetime the created certificates are allowed to have. - In case consumers request a longer lifetime than allowed by this setting, - the lifetime will be the minimum of both, so this setting takes precedence. - The default value is 15 days. - - The maximum lifetime must be less than a quarter of the active CA certificate lifetime - where the active CA certificate lifetime is `ca.ca_certificate_lifetime - - ca.ca_certificate_retirement_duration` to ensure that two subjects always have a common - CA certificate in their trust stores – assuming that CAs are rotated at half of their - active lifetimes. - - For instance, if a pod is created right before half of the active CA lifetime has - passed, then it is signed by this CA but it does not know yet the new CA certificate - which is created right afterwards. If another pod is created so that its certificate - lifetime ends right after the first active CA lifetime then it is signed by the new CA. - The `max_certificate_lifetime` must be chosen so that these two pods have no - overlapping lifetimes, otherwise the first pod would see the second one signed by an - unknown CA certificate. This can be achieved by the mentioned formula. - type: string - required: - - ca - type: object - certManager: - description: |- - The [`certManager` backend][1] injects a TLS certificate issued by [cert-manager]. - - A new certificate will be requested the first time it is used by a Pod, it - will be reused after that (subject to cert-manager renewal rules). - - [1]: https://docs.stackable.tech/home/nightly/secret-operator/secretclass#backend-certmanager - [cert-manager]: https://cert-manager.io/ - properties: - defaultCertificateLifetime: - default: 1d - description: |- - The default lifetime of certificates. - - Defaults to 1 day. This may need to be increased for external issuers that impose rate limits (such as Let's Encrypt). - type: string - issuer: - description: A reference to the cert-manager issuer that the certificates should be requested from. - properties: - kind: - description: |- - The kind of the issuer, Issuer or ClusterIssuer. - - If Issuer then it must be in the same namespace as the Pods using it. - enum: - - Issuer - - ClusterIssuer - type: string - name: - description: The name of the issuer. - type: string - required: - - kind - - name - type: object - keyGeneration: - default: - rsa: - length: 2048 - description: |- - The algorithm used to generate a key pair and required configuration settings. - Currently only RSA and a key length of 2048, 3072 or 4096 bits can be configured. - oneOf: - - required: - - rsa - properties: - rsa: - properties: - length: - description: |- - The amount of bits used for generating the RSA keypair. - Currently, `2048`, `3072` and `4096` are supported. Defaults to `2048` bits. - enum: - - 2048 - - 3072 - - 4096 - type: integer - required: - - length - type: object - type: object - required: - - issuer - type: object - k8sSearch: - description: |- - The [`k8sSearch` backend](https://docs.stackable.tech/home/nightly/secret-operator/secretclass#backend-k8ssearch) - can be used to mount Secrets across namespaces into Pods. - properties: - searchNamespace: - description: Configures the namespace searched for Secret objects. - oneOf: - - required: - - pod - - required: - - name - properties: - name: - description: |- - The Secret objects are located in a single global namespace. - Should be used for secrets that are provisioned by the cluster administrator. - type: string - pod: - description: |- - The Secret objects are located in the same namespace as the Pod object. - Should be used for Secrets that are provisioned by the application administrator. - type: object - type: object - trustStoreConfigMapName: - description: |- - Name of a ConfigMap that contains the information required to validate against this SecretClass. - - Resolved relative to `search_namespace`. - - Required to request a TrustStore for this SecretClass. - nullable: true - type: string - required: - - searchNamespace - type: object - kerberosKeytab: - description: |- - The [`kerberosKeytab` backend](https://docs.stackable.tech/home/nightly/secret-operator/secretclass#backend-kerberoskeytab) - creates a Kerberos keytab file for a selected realm. - The Kerberos KDC and administrator credentials must be provided by the administrator. - properties: - admin: - description: Kerberos admin configuration settings. - oneOf: - - required: - - mit - - required: - - activeDirectory - properties: - activeDirectory: - description: Credentials should be provisioned in a Microsoft Active Directory domain. - properties: - generateSamAccountName: - description: |- - Allows samAccountName generation for new accounts to be customized. - Note that setting this field (even if empty) makes the Secret Operator take - over the generation duty from the domain controller. - nullable: true - properties: - prefix: - default: '' - description: A prefix to be prepended to generated samAccountNames. - type: string - totalLength: - default: 20 - description: |- - The total length of generated samAccountNames, _including_ `prefix`. - Must be larger than the length of `prefix`, but at most `20`. - - Note that this should be as large as possible, to minimize the risk of collisions. - format: uint8 - maximum: 255.0 - minimum: 0.0 - type: integer - type: object - ldapServer: - description: |- - An AD LDAP server, such as the AD Domain Controller. - This must match the server’s FQDN, or GSSAPI authentication will fail. - type: string - ldapTlsCaSecret: - description: |- - Reference (name and namespace) to a Kubernetes Secret object containing - the TLS CA (in `ca.crt`) that the LDAP server’s certificate should be authenticated against. - properties: - name: - description: Name of the Secret being referred to. - type: string - namespace: - description: Namespace of the Secret being referred to. - type: string - required: - - name - - namespace - type: object - passwordCacheSecret: - description: |- - Reference (name and namespace) to a Kubernetes Secret object where workload - passwords will be stored. This must not be accessible to end users. - properties: - name: - description: Name of the Secret being referred to. - type: string - namespace: - description: Namespace of the Secret being referred to. - type: string - required: - - name - - namespace - type: object - schemaDistinguishedName: - description: |- - The root Distinguished Name (DN) for AD-managed schemas, - typically `CN=Schema,CN=Configuration,{domain_dn}`. - type: string - userDistinguishedName: - description: |- - The root Distinguished Name (DN) where service accounts should be provisioned, - typically `CN=Users,{domain_dn}`. - type: string - required: - - ldapServer - - ldapTlsCaSecret - - passwordCacheSecret - - schemaDistinguishedName - - userDistinguishedName - type: object - mit: - description: Credentials should be provisioned in a MIT Kerberos Admin Server. - properties: - kadminServer: - description: |- - The hostname of the Kerberos Admin Server. - This should be provided by the Kerberos administrator. - type: string - required: - - kadminServer - type: object - type: object - adminKeytabSecret: - description: |- - Reference (`name` and `namespace`) to a K8s Secret object where a - keytab with administrative privileges is stored in the key `keytab`. - properties: - name: - description: Name of the Secret being referred to. - type: string - namespace: - description: Namespace of the Secret being referred to. - type: string - required: - - name - - namespace - type: object - adminPrincipal: - description: The admin principal. - type: string - kdc: - description: |- - The hostname of the Kerberos Key Distribution Center (KDC). - This should be provided by the Kerberos administrator. - type: string - realmName: - description: The name of the Kerberos realm. This should be provided by the Kerberos administrator. - type: string - required: - - admin - - adminKeytabSecret - - adminPrincipal - - kdc - - realmName - type: object - type: object - required: - - backend - type: object - required: - - spec - title: SecretClass - type: object - served: true - storage: true - subresources: {} - - additionalPrinterColumns: [] - name: v1alpha1 - schema: - openAPIV3Schema: - description: Auto-generated derived type for SecretClassSpec via `CustomResource` - properties: - spec: - description: |- - A [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass) is a cluster-global Kubernetes resource - that defines a category of secrets that the Secret Operator knows how to provision. - properties: - backend: - description: |- - Each SecretClass is associated with a single - [backend](https://docs.stackable.tech/home/nightly/secret-operator/secretclass#backend), - which dictates the mechanism for issuing that kind of Secret. - oneOf: - - required: - - k8sSearch - - required: - - autoTls - - required: - - experimentalCertManager - - required: - - kerberosKeytab - properties: - autoTls: - description: |- - The [`autoTls` backend](https://docs.stackable.tech/home/nightly/secret-operator/secretclass#backend-autotls) - issues a TLS certificate signed by the Secret Operator. - The certificate authority can be provided by the administrator, or managed automatically by the Secret Operator. - - A new certificate and key pair will be generated and signed for each Pod, keys or certificates are never reused. - properties: - additionalTrustRoots: - default: [] - description: Additional trust roots which are added to the provided `ca.crt` file. - items: - oneOf: - - required: - - configMap - - required: - - secret - properties: - configMap: - description: |- - Reference (name and namespace) to a Kubernetes ConfigMap object where additional - certificates are stored. - The extensions of the keys denote its contents: A key suffixed with `.crt` contains a stack - of base64 encoded DER certificates, a key suffixed with `.der` contains a binary DER - certificate. - properties: - name: - description: Name of the ConfigMap being referred to. - type: string - namespace: - description: Namespace of the ConfigMap being referred to. - type: string - required: - - name - - namespace - type: object - secret: - description: |- - Reference (name and namespace) to a Kubernetes Secret object where additional certificates - are stored. - The extensions of the keys denote its contents: A key suffixed with `.crt` contains a stack - of base64 encoded DER certificates, a key suffixed with `.der` contains a binary DER - certificate. - properties: - name: - description: Name of the Secret being referred to. - type: string - namespace: - description: Namespace of the Secret being referred to. - type: string - required: - - name - - namespace - type: object - type: object - type: array - ca: - description: Configures the certificate authority used to issue Pod certificates. - properties: - autoGenerate: - default: false - description: |- - Whether the certificate authority should be managed by Secret Operator, including being generated - if it does not already exist. - type: boolean - caCertificateLifetime: - default: 365d - description: |- - The lifetime of each generated certificate authority. - - Should always be more than double `maxCertificateLifetime`. - - If `autoGenerate: true` then the Secret Operator will prepare a new CA certificate the old CA approaches expiration. - If `autoGenerate: false` then the Secret Operator will log a warning instead. - type: string - caCertificateRetirementDuration: - default: 1h - description: |- - Duration at the end of the CA certificate lifetime where no signed certificate will exist. - - Retired (or expired) CA certificates will not be published and will not be used for - signing leaf certificates. - type: string - keyGeneration: - default: - rsa: - length: 2048 - description: |- - The algorithm used to generate a key pair and required configuration settings. - Currently only RSA and a key length of 2048, 3072 or 4096 bits can be configured. - oneOf: - - required: - - rsa - properties: - rsa: - properties: - length: - description: |- - The amount of bits used for generating the RSA keypair. - Currently, `2048`, `3072` and `4096` are supported. Defaults to `2048` bits. - enum: - - 2048 - - 3072 - - 4096 - type: integer - required: - - length - type: object - type: object - secret: - description: |- - Reference (name and namespace) to a Kubernetes Secret object where the CA certificate - and key is stored in the keys `ca.crt` and `ca.key` respectively. - properties: - name: - description: Name of the Secret being referred to. - type: string - namespace: - description: Namespace of the Secret being referred to. - type: string - required: - - name - - namespace - type: object - required: - - secret - type: object - maxCertificateLifetime: - default: 15d - description: |- - Maximum lifetime the created certificates are allowed to have. - In case consumers request a longer lifetime than allowed by this setting, - the lifetime will be the minimum of both, so this setting takes precedence. - The default value is 15 days. - - The maximum lifetime must be less than a quarter of the active CA certificate lifetime - where the active CA certificate lifetime is `ca.ca_certificate_lifetime - - ca.ca_certificate_retirement_duration` to ensure that two subjects always have a common - CA certificate in their trust stores – assuming that CAs are rotated at half of their - active lifetimes. - - For instance, if a pod is created right before half of the active CA lifetime has - passed, then it is signed by this CA but it does not know yet the new CA certificate - which is created right afterwards. If another pod is created so that its certificate - lifetime ends right after the first active CA lifetime then it is signed by the new CA. - The `max_certificate_lifetime` must be chosen so that these two pods have no - overlapping lifetimes, otherwise the first pod would see the second one signed by an - unknown CA certificate. This can be achieved by the mentioned formula. - type: string - required: - - ca - type: object - experimentalCertManager: - description: |- - The [`certManager` backend][1] injects a TLS certificate issued by [cert-manager]. - - A new certificate will be requested the first time it is used by a Pod, it - will be reused after that (subject to cert-manager renewal rules). - - [1]: https://docs.stackable.tech/home/nightly/secret-operator/secretclass#backend-certmanager - [cert-manager]: https://cert-manager.io/ - properties: - defaultCertificateLifetime: - default: 1d - description: |- - The default lifetime of certificates. - - Defaults to 1 day. This may need to be increased for external issuers that impose rate limits (such as Let's Encrypt). - type: string - issuer: - description: A reference to the cert-manager issuer that the certificates should be requested from. - properties: - kind: - description: |- - The kind of the issuer, Issuer or ClusterIssuer. - - If Issuer then it must be in the same namespace as the Pods using it. - enum: - - Issuer - - ClusterIssuer - type: string - name: - description: The name of the issuer. - type: string - required: - - kind - - name - type: object - keyGeneration: - default: - rsa: - length: 2048 - description: |- - The algorithm used to generate a key pair and required configuration settings. - Currently only RSA and a key length of 2048, 3072 or 4096 bits can be configured. - oneOf: - - required: - - rsa - properties: - rsa: - properties: - length: - description: |- - The amount of bits used for generating the RSA keypair. - Currently, `2048`, `3072` and `4096` are supported. Defaults to `2048` bits. - enum: - - 2048 - - 3072 - - 4096 - type: integer - required: - - length - type: object - type: object - required: - - issuer - type: object - k8sSearch: - description: |- - The [`k8sSearch` backend](https://docs.stackable.tech/home/nightly/secret-operator/secretclass#backend-k8ssearch) - can be used to mount Secrets across namespaces into Pods. - properties: - searchNamespace: - description: Configures the namespace searched for Secret objects. - oneOf: - - required: - - pod - - required: - - name - properties: - name: - description: |- - The Secret objects are located in a single global namespace. - Should be used for secrets that are provisioned by the cluster administrator. - type: string - pod: - description: |- - The Secret objects are located in the same namespace as the Pod object. - Should be used for Secrets that are provisioned by the application administrator. - type: object - type: object - trustStoreConfigMapName: - description: |- - Name of a ConfigMap that contains the information required to validate against this SecretClass. - - Resolved relative to `search_namespace`. - - Required to request a TrustStore for this SecretClass. - nullable: true - type: string - required: - - searchNamespace - type: object - kerberosKeytab: - description: |- - The [`kerberosKeytab` backend](https://docs.stackable.tech/home/nightly/secret-operator/secretclass#backend-kerberoskeytab) - creates a Kerberos keytab file for a selected realm. - The Kerberos KDC and administrator credentials must be provided by the administrator. - properties: - admin: - description: Kerberos admin configuration settings. - oneOf: - - required: - - mit - - required: - - activeDirectory - properties: - activeDirectory: - description: Credentials should be provisioned in a Microsoft Active Directory domain. - properties: - experimentalGenerateSamAccountName: - description: |- - Allows samAccountName generation for new accounts to be customized. - Note that setting this field (even if empty) makes the Secret Operator take - over the generation duty from the domain controller. - nullable: true - properties: - prefix: - default: '' - description: A prefix to be prepended to generated samAccountNames. - type: string - totalLength: - default: 20 - description: |- - The total length of generated samAccountNames, _including_ `prefix`. - Must be larger than the length of `prefix`, but at most `20`. - - Note that this should be as large as possible, to minimize the risk of collisions. - format: uint8 - maximum: 255.0 - minimum: 0.0 - type: integer - type: object - ldapServer: - description: |- - An AD LDAP server, such as the AD Domain Controller. - This must match the server’s FQDN, or GSSAPI authentication will fail. - type: string - ldapTlsCaSecret: - description: |- - Reference (name and namespace) to a Kubernetes Secret object containing - the TLS CA (in `ca.crt`) that the LDAP server’s certificate should be authenticated against. - properties: - name: - description: Name of the Secret being referred to. - type: string - namespace: - description: Namespace of the Secret being referred to. - type: string - required: - - name - - namespace - type: object - passwordCacheSecret: - description: |- - Reference (name and namespace) to a Kubernetes Secret object where workload - passwords will be stored. This must not be accessible to end users. - properties: - name: - description: Name of the Secret being referred to. - type: string - namespace: - description: Namespace of the Secret being referred to. - type: string - required: - - name - - namespace - type: object - schemaDistinguishedName: - description: |- - The root Distinguished Name (DN) for AD-managed schemas, - typically `CN=Schema,CN=Configuration,{domain_dn}`. - type: string - userDistinguishedName: - description: |- - The root Distinguished Name (DN) where service accounts should be provisioned, - typically `CN=Users,{domain_dn}`. - type: string - required: - - ldapServer - - ldapTlsCaSecret - - passwordCacheSecret - - schemaDistinguishedName - - userDistinguishedName - type: object - mit: - description: Credentials should be provisioned in a MIT Kerberos Admin Server. - properties: - kadminServer: - description: |- - The hostname of the Kerberos Admin Server. - This should be provided by the Kerberos administrator. - type: string - required: - - kadminServer - type: object - type: object - adminKeytabSecret: - description: |- - Reference (`name` and `namespace`) to a K8s Secret object where a - keytab with administrative privileges is stored in the key `keytab`. - properties: - name: - description: Name of the Secret being referred to. - type: string - namespace: - description: Namespace of the Secret being referred to. - type: string - required: - - name - - namespace - type: object - adminPrincipal: - description: The admin principal. - type: string - kdc: - description: |- - The hostname of the Kerberos Key Distribution Center (KDC). - This should be provided by the Kerberos administrator. - type: string - realmName: - description: The name of the Kerberos realm. This should be provided by the Kerberos administrator. - type: string - required: - - admin - - adminKeytabSecret - - adminPrincipal - - kdc - - realmName - type: object - type: object - required: - - backend - type: object - required: - - spec - title: SecretClass - type: object - served: true - storage: false - subresources: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: truststores.secrets.stackable.tech - annotations: - helm.sh/resource-policy: keep -spec: - group: secrets.stackable.tech - names: - categories: [] - kind: TrustStore - plural: truststores - shortNames: [] - singular: truststore - scope: Namespaced - versions: - - additionalPrinterColumns: [] - name: v1alpha1 - schema: - openAPIV3Schema: - description: Auto-generated derived type for TrustStoreSpec via `CustomResource` - properties: - spec: - description: |- - A [TrustStore](https://docs.stackable.tech/home/nightly/secret-operator/truststore) requests information about how to - validate secrets issued by a [SecretClass](https://docs.stackable.tech/home/nightly/secret-operator/secretclass). - - The requested information is written to a ConfigMap with the same name as the TrustStore. - properties: - format: - description: The [format](https://docs.stackable.tech/home/nightly/secret-operator/secretclass#format) that the data should be converted into. - enum: - - tls-pem - - tls-pkcs12 - - kerberos - - null - nullable: true - type: string - secretClassName: - description: The name of the SecretClass that the request concerns. - type: string - targetKind: - default: ConfigMap - description: |- - Which Kubernetes kind should be used to output the requested information to. - - The trust information (such as a `ca.crt`) can be considered public information, so we put - it in a `ConfigMap` by default. However, some tools might require it to be placed in a - `Secret`, so we also support that. - - Can be either `ConfigMap` or `Secret`, defaults to `ConfigMap`. - enum: - - Secret - - ConfigMap - type: string - required: - - secretClassName - type: object - required: - - spec - title: TrustStore - type: object - served: true - storage: true - subresources: {} diff --git a/deploy/helm/secret-operator/templates/service.yaml b/deploy/helm/secret-operator/templates/service.yaml index 0cb05696..5cf6439b 100644 --- a/deploy/helm/secret-operator/templates/service.yaml +++ b/deploy/helm/secret-operator/templates/service.yaml @@ -1,4 +1,4 @@ - +{{- if .Values.maintenance.customResourceDefinitions.maintain }} --- apiVersion: v1 kind: Service @@ -11,9 +11,11 @@ metadata: {{- include "operator.labels" . | nindent 4 }} spec: selector: - {{- include "operator.selectorLabels" . | nindent 6 }} + webhook.stackable.tech/conversion: enabled + {{- include "operator.selectorLabels" . | nindent 4 }} ports: - name: conversion-webhook protocol: TCP port: 8443 targetPort: 8443 +{{- end }} From a751362d4edccad8347b79a5e7b57f10685bd4b7 Mon Sep 17 00:00:00 2001 From: Nick Larsen Date: Thu, 27 Nov 2025 19:46:32 +0100 Subject: [PATCH 4/4] chore: Revert templating changes that are incompatible with secret-operator's reality --- scripts/run-tests | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/run-tests b/scripts/run-tests index 00639f45..7fa07fc5 100755 --- a/scripts/run-tests +++ b/scripts/run-tests @@ -11,6 +11,7 @@ import shutil import subprocess import sys import tempfile +import time __version__ = "0.0.1" @@ -451,6 +452,7 @@ def main(argv) -> int: gen_tests(opts.test_suite, opts.namespace, opts.work_dir) with release_file(opts.operator, opts.skip_operator) as f: maybe_install_release(opts.skip_release, f, opts.listener_class_preset) + time.sleep(10) if opts.skip_tests: logging.info("Skip running tests.") else: