From ec5ed545e8ea9bd7a66f6f5dafc6772f484e3302 Mon Sep 17 00:00:00 2001 From: Stacky McStackface Date: Tue, 2 Dec 2025 09:00:25 +0000 Subject: [PATCH] chore: Generated commit to update templated files since the last template run up to stackabletech/operator-templating@9cbb811001527bb72c1ddc66750bcbdfed6d70a8 Reference-to: stackabletech/operator-templating@9cbb811 (Use detect-changes action) --- .github/workflows/build.yaml | 70 +- .gitignore | 1 - Makefile | 89 +- Tiltfile | 29 +- deploy/helm/secret-operator/crds/crds.yaml | 937 ++++++++++++++++++ .../secret-operator/templates/service.yaml | 6 +- scripts/run-tests | 2 - 7 files changed, 1013 insertions(+), 121 deletions(-) create mode 100644 deploy/helm/secret-operator/crds/crds.yaml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 95dcc742..1eef5cf7 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -18,15 +18,7 @@ on: # 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' + # Do not limit by paths. This workflow contains a required job. merge_group: env: @@ -39,8 +31,41 @@ env: CARGO_TERM_COLOR: always jobs: + # This workflow contains a "required job", and GitHub Actions isn't clever + # enough to detect that it should be skipped, and therefore pass (like they + # allow for skipping jobs in a workflow). + # Therefore, we have to move path filters/globs down to an actual job, and + # emit an output that can be used to skip irrelevant jobs. + detect-changes: + name: Detect relevant changed files + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: stackabletech/actions/detect-changes@babe44d7b1db87f8e7731c011151d22a8a374191 # v0.12.0 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Check for changed files + id: check + uses: ./.github/actions/detect-changes + with: + patterns: | + - '.github/workflows/build.yaml' + - 'rust-toolchain.toml' + - '.dockerignore' + - 'deploy/**' + - '.cargo/**' + - 'docker/**' + - 'Cargo.*' + - '*.rs' + outputs: + detected: ${{ steps.check.outputs.detected }} + cargo-udeps: name: Run cargo-udeps + if: needs.detect-changes.outputs.detected == 'true' + needs: [detect-changes] runs-on: ubuntu-latest env: RUSTC_BOOTSTRAP: 1 @@ -76,7 +101,8 @@ jobs: build-container-image: name: Build/Publish ${{ matrix.runner.arch }} Image - if: github.event_name != 'merge_group' + if: (github.event_name != 'merge_group') && needs.detect-changes.outputs.detected == 'true' + needs: [detect-changes] permissions: id-token: write strategy: @@ -140,7 +166,7 @@ jobs: - name: Build Container Image id: build - uses: stackabletech/actions/build-container-image@29bea1b451c0c2e994bd495969286f95bf49ed6a # v0.11.0 + uses: stackabletech/actions/build-container-image@babe44d7b1db87f8e7731c011151d22a8a374191 # v0.12.0 with: image-name: ${{ env.OPERATOR_NAME }} image-index-manifest-tag: ${{ steps.version.outputs.OPERATOR_VERSION }} @@ -148,7 +174,7 @@ jobs: container-file: docker/Dockerfile - name: Publish Container Image - uses: stackabletech/actions/publish-image@29bea1b451c0c2e994bd495969286f95bf49ed6a # v0.11.0 + uses: stackabletech/actions/publish-image@babe44d7b1db87f8e7731c011151d22a8a374191 # v0.12.0 with: image-registry-uri: oci.stackable.tech image-registry-username: robot$sdp+github-action-build @@ -159,8 +185,9 @@ jobs: publish-index-manifest: name: Publish/Sign ${{ needs.build-container-image.outputs.operator-version }} Index - if: github.event_name != 'merge_group' + if: (github.event_name != 'merge_group') && needs.detect-changes.outputs.detected == 'true' needs: + - detect-changes - build-container-image permissions: id-token: write @@ -172,7 +199,7 @@ jobs: persist-credentials: false - name: Publish and Sign Image Index - uses: stackabletech/actions/publish-image-index-manifest@29bea1b451c0c2e994bd495969286f95bf49ed6a # v0.11.0 + uses: stackabletech/actions/publish-image-index-manifest@babe44d7b1db87f8e7731c011151d22a8a374191 # v0.12.0 with: image-registry-uri: oci.stackable.tech image-registry-username: robot$sdp+github-action-build @@ -182,8 +209,9 @@ jobs: publish-helm-chart: name: Package/Publish ${{ needs.build-container-image.outputs.operator-version }} Helm Chart - if: github.event_name != 'merge_group' + if: (github.event_name != 'merge_group') && needs.detect-changes.outputs.detected == 'true' needs: + - detect-changes - build-container-image permissions: id-token: write @@ -196,7 +224,7 @@ jobs: submodules: recursive - name: Package, Publish, and Sign Helm Chart - uses: stackabletech/actions/publish-helm-chart@29bea1b451c0c2e994bd495969286f95bf49ed6a # v0.11.0 + uses: stackabletech/actions/publish-helm-chart@babe44d7b1db87f8e7731c011151d22a8a374191 # v0.12.0 with: chart-registry-uri: oci.stackable.tech chart-registry-username: robot$sdp-charts+github-action-build @@ -208,8 +236,9 @@ 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' + if: (github.event_name != 'merge_group') && needs.detect-changes.outputs.detected == 'true' needs: + - detect-changes - build-container-image - publish-index-manifest strategy: @@ -221,7 +250,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Run OpenShift Preflight Check - uses: stackabletech/actions/run-openshift-preflight@29bea1b451c0c2e994bd495969286f95bf49ed6a # v0.11.0 + uses: stackabletech/actions/run-openshift-preflight@babe44d7b1db87f8e7731c011151d22a8a374191 # v0.12.0 with: image-index-uri: oci.stackable.tech/sdp/${{ env.OPERATOR_NAME }}:${{ needs.build-container-image.outputs.operator-version }} image-architecture: ${{ matrix.arch }} @@ -243,8 +272,9 @@ jobs: notify: name: Failure Notification - if: (failure() || github.run_attempt > 1) && github.event_name != 'merge_group' + if: (failure() || github.run_attempt > 1) && github.event_name != 'merge_group' && needs.detect-changes.outputs.detected == 'true' needs: + - detect-changes - build-container-image - publish-index-manifest - publish-helm-chart @@ -256,7 +286,7 @@ jobs: persist-credentials: false - name: Send Notification - uses: stackabletech/actions/send-slack-notification@29bea1b451c0c2e994bd495969286f95bf49ed6a # v0.11.0 + uses: stackabletech/actions/send-slack-notification@babe44d7b1db87f8e7731c011151d22a8a374191 # v0.12.0 with: publish-helm-chart-result: ${{ needs.publish-helm-chart.result }} publish-manifests-result: ${{ needs.publish-index-manifest.result }} 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/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/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: