diff --git a/Makefile b/Makefile index 8c3c4162..ada823b5 100644 --- a/Makefile +++ b/Makefile @@ -118,6 +118,10 @@ manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and Cust generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." +.PHONY: validate-rbac +validate-rbac: ## Validate that RBAC resourceNames in kubebuilder annotations match actual resource names. + ./hack/validate-rbac-resourcenames.sh + .PHONY: fmt fmt: ## Run go fmt against code. go fmt ./... @@ -280,13 +284,8 @@ OPERATOR_SDK ?= $(LOCALBIN)/operator-sdk operator-sdk: ## Download operator-sdk locally if necessary. ifeq (,$(wildcard $(OPERATOR_SDK))) ifeq (, $(shell which operator-sdk 2>/dev/null)) - @{ \ - set -e ;\ - mkdir -p $(dir $(OPERATOR_SDK)) ;\ - OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ - curl -sSLo $(OPERATOR_SDK) https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$${OS}_$${ARCH} ;\ - chmod +x $(OPERATOR_SDK) ;\ - } + mkdir -p $(dir $(OPERATOR_SDK)) + hack/operator-sdk.sh $(OPERATOR_SDK) $(OPERATOR_SDK_VERSION) else OPERATOR_SDK = $(shell which operator-sdk) endif @@ -370,7 +369,7 @@ catalog-push: ## Push a catalog image. ## verify the changes are working as expected. .PHONY: verify -verify: vet fmt golangci-lint verify-bindata verify-bindata-assets verify-generated +verify: vet fmt golangci-lint verify-bindata verify-bindata-assets verify-generated validate-rbac ## update the relevant data based on new changes. .PHONY: update diff --git a/bundle/manifests/external-secrets-operator.clusterserviceversion.yaml b/bundle/manifests/external-secrets-operator.clusterserviceversion.yaml index b07f2787..d89e3791 100644 --- a/bundle/manifests/external-secrets-operator.clusterserviceversion.yaml +++ b/bundle/manifests/external-secrets-operator.clusterserviceversion.yaml @@ -204,7 +204,7 @@ metadata: categories: Security console.openshift.io/disable-operand-delete: "true" containerImage: openshift.io/external-secrets-operator:latest - createdAt: "2025-08-18T11:50:12Z" + createdAt: "2025-08-21T05:49:03Z" features.operators.openshift.io/cnf: "false" features.operators.openshift.io/cni: "false" features.operators.openshift.io/csi: "false" @@ -379,19 +379,25 @@ spec: resources: - validatingwebhookconfigurations verbs: - - create - get - list + - watch + - apiGroups: + - admissionregistration.k8s.io + resourceNames: + - externalsecret-validate + - secretstore-validate + resources: + - validatingwebhookconfigurations + verbs: + - create - patch - update - - watch - apiGroups: - apiextensions.k8s.io resources: - customresourcedefinitions verbs: - - create - - delete - get - list - patch @@ -402,11 +408,21 @@ spec: resources: - deployments verbs: + - list + - watch + - apiGroups: + - apps + resourceNames: + - bitwarden-sdk-server + - external-secrets + - external-secrets-cert-controller + - external-secrets-webhook + resources: + - deployments + verbs: - create - get - - list - update - - watch - apiGroups: - cert-manager.io resources: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index a1b59121..a2d5bb1e 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -41,19 +41,25 @@ rules: resources: - validatingwebhookconfigurations verbs: - - create - get - list + - watch +- apiGroups: + - admissionregistration.k8s.io + resourceNames: + - externalsecret-validate + - secretstore-validate + resources: + - validatingwebhookconfigurations + verbs: + - create - patch - update - - watch - apiGroups: - apiextensions.k8s.io resources: - customresourcedefinitions verbs: - - create - - delete - get - list - patch @@ -64,11 +70,21 @@ rules: resources: - deployments verbs: + - list + - watch +- apiGroups: + - apps + resourceNames: + - bitwarden-sdk-server + - external-secrets + - external-secrets-cert-controller + - external-secrets-webhook + resources: + - deployments + verbs: - create - get - - list - update - - watch - apiGroups: - cert-manager.io resources: diff --git a/hack/operator-sdk.sh b/hack/operator-sdk.sh new file mode 100755 index 00000000..ce2014fe --- /dev/null +++ b/hack/operator-sdk.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +set -e + +OUTPUT_PATH=${1:-./bin/operator-sdk} +VERSION=${2:-"v1.39.0"} + +GOOS=$(go env GOOS) +GOARCH=$(go env GOARCH) +BIN="operator-sdk" +BIN_ARCH="${BIN}_${GOOS}_${GOARCH}" +OPERATOR_SDK_DL_URL="https://github.com/operator-framework/operator-sdk/releases/download/${VERSION}" + +if [[ "$GOOS" != "linux" && "$GOOS" != "darwin" ]]; then + echo "Unsupported OS $GOOS" + exit 1 +fi + +if [[ "$GOARCH" != "amd64" && "$GOARCH" != "arm64" && "$GOARCH" != "ppc64le" && "$GOARCH" != "s390x" ]]; then + echo "Unsupported architecture $GOARCH" + exit 1 +fi + +command -v curl &> /dev/null || { echo "can't find curl command" && exit 1; } + +TEMPDIR=$(mktemp -d) +BIN_PATH="${TEMPDIR}/${BIN_ARCH}" + +echo "> downloading binary" +curl --location -o "${BIN_PATH}" "${OPERATOR_SDK_DL_URL}/operator-sdk_${GOOS}_${GOARCH}" + +echo "> installing binary" +mv "${BIN_PATH}" "${OUTPUT_PATH}" +chmod +x "${OUTPUT_PATH}" +rm -rf "${TEMPDIR}" + +echo "> operator-sdk binary available at ${OUTPUT_PATH}" diff --git a/hack/validate-rbac-resourcenames.sh b/hack/validate-rbac-resourcenames.sh new file mode 100755 index 00000000..60d6ee2a --- /dev/null +++ b/hack/validate-rbac-resourcenames.sh @@ -0,0 +1,156 @@ +#!/bin/bash + +# validate-rbac-resourcenames.sh +# This script validates that the resourceNames in kubebuilder RBAC annotations +# match the actual resource names defined in the assets. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +# extract resourceNames from kubebuilder annotations +extract_kubebuilder_resourcenames() { + local resource_type="$1" + grep -E "^\s*//\s*\+kubebuilder:rbac:.*resources=${resource_type}.*resourceNames=" \ + "${PROJECT_ROOT}/pkg/controller/external_secrets/controller.go" | \ + sed -E 's/.*resourceNames=([^,]*).*/\1/' | \ + tr ';' '\n' | sort -u +} + +# extract actual resource names from assets +extract_asset_names() { + local pattern="$1" + find "${PROJECT_ROOT}/bindata/external-secrets/resources" "${PROJECT_ROOT}/bindata/external-secrets" -name "*.yml" -exec grep -l "kind: ${pattern}" {} \; 2>/dev/null | \ + xargs grep -h "^ name:" | \ + awk '{print $2}' | sort -u +} + +# extract CRD names from assets +extract_crd_names() { + # Get CRDs from config/crd/bases + local crd_names="" + if [[ -d "${PROJECT_ROOT}/config/crd/bases" ]]; then + crd_names=$(find "${PROJECT_ROOT}/config/crd/bases" -name "*.yml" -o -name "*.yaml" 2>/dev/null | \ + xargs grep -l "kind: CustomResourceDefinition" 2>/dev/null | \ + xargs grep -h "^ name:" 2>/dev/null | \ + awk '{print $2}') + fi + echo "${crd_names}" | grep -v "^$" | sort -u +} + +# compare kubebuilder resourceNames with actual resources +compare_resources() { + local resource_display_name="$1" + local kubebuilder_resources="$2" + local actual_resources="$3" + + echo "Kubebuilder resourceNames:" + echo "${kubebuilder_resources}" | sed 's/^/ - /' + echo + echo "Actual ${resource_display_name} names:" + echo "${actual_resources}" | sed 's/^/ - /' + echo + + # Compare - find missing and extra resources + local missing_in_kb + missing_in_kb=$(comm -23 <(echo "${actual_resources}") <(echo "${kubebuilder_resources}")) + + local extra_in_kb + extra_in_kb=$(comm -13 <(echo "${actual_resources}") <(echo "${kubebuilder_resources}")) + + local has_errors=false + + if [[ -n "${missing_in_kb}" ]]; then + echo "Missing in kubebuilder annotations:" + echo "${missing_in_kb}" | sed 's/^/ /' + has_errors=true + fi + + if [[ -n "${extra_in_kb}" ]]; then + echo "Extra in kubebuilder annotations (might be outdated):" + echo "${extra_in_kb}" | sed 's/^/ /' + fi + + if [[ "$has_errors" == "false" ]]; then + echo "${resource_display_name} validation passed" + return 0 + else + return 1 + fi +} + +# Generic validation function +validate_resource_type() { + local resource_display_name="$1" + local kubebuilder_resource_type="$2" + local asset_kind="$3" + local extract_func="$4" + + echo "Validating ${resource_display_name}..." + + # Extract from kubebuilder annotations + local kb_resources + kb_resources=$(extract_kubebuilder_resourcenames "${kubebuilder_resource_type}" || echo "") + + # Extract from assets using the specified function + local actual_resources + if [[ "$extract_func" == "extract_crd_names" ]]; then + actual_resources=$(extract_crd_names) + else + actual_resources=$(extract_asset_names "${asset_kind}") + fi + + # Compare and report + compare_resources "${resource_display_name}" "${kb_resources}" "${actual_resources}" +} + +validate_deployments() { + validate_resource_type "Deployments" "deployments" "Deployment" "extract_asset_names" +} + +validate_webhooks() { + validate_resource_type "ValidatingWebhookConfigurations" "validatingwebhookconfigurations" "ValidatingWebhookConfiguration" "extract_asset_names" +} + +validate_crds() { + validate_resource_type "CustomResourceDefinitions" "customresourcedefinitions" "" "extract_crd_names" +} + +validate_roles() { + validate_resource_type "Roles" "roles" "Role" "extract_asset_names" +} + +validate_rolebindings() { + validate_resource_type "RoleBindings" "rolebindings" "RoleBinding" "extract_asset_names" +} + +main() { + local exit_code=0 + + echo "Validating RBAC resourceNames consistency for external-secrets-operator" + echo "==================================================================================" + + validate_deployments || exit_code=1 + echo + validate_webhooks || exit_code=1 + echo + validate_crds || exit_code=1 + echo + validate_roles || exit_code=1 + echo + validate_rolebindings || exit_code=1 + echo + + if [[ $exit_code -eq 0 ]]; then + echo "All RBAC resourceNames validations passed!" + echo "The kubebuilder annotations are consistent with the actual resources." + else + echo "RBAC validation failed!" + echo "Please update the kubebuilder annotations in pkg/controller/external_secrets/controller.go" + fi + + return $exit_code +} + +main "$@" diff --git a/pkg/controller/external_secrets/controller.go b/pkg/controller/external_secrets/controller.go index 32960ff6..8d0764f7 100644 --- a/pkg/controller/external_secrets/controller.go +++ b/pkg/controller/external_secrets/controller.go @@ -97,16 +97,18 @@ type Reconciler struct { // +kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=get;list;watch;create;update;patch // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles;rolebindings;clusterroles;clusterrolebindings,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=admissionregistration.k8s.io,resources=validatingwebhookconfigurations,verbs=get;list;watch;create;update;patch +// +kubebuilder:rbac:groups=admissionregistration.k8s.io,resources=validatingwebhookconfigurations,verbs=get;list;watch +// +kubebuilder:rbac:groups=admissionregistration.k8s.io,resources=validatingwebhookconfigurations,verbs=create;update;patch,resourceNames=externalsecret-validate;secretstore-validate // +kubebuilder:rbac:groups="",resources=events;secrets;services;serviceaccounts,verbs=get;list;watch;create;update;delete;patch -// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update +// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=list;watch +// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;create;update,resourceNames=external-secrets;external-secrets-cert-controller;external-secrets-webhook;bitwarden-sdk-server // +kubebuilder:rbac:groups=cert-manager.io,resources=certificates;clusterissuers;issuers,verbs=get;list;watch;create;update // +kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch;create // +kubebuilder:rbac:groups="",resources=endpoints,verbs=get;list;watch;create // +kubebuilder:rbac:groups="",resources=serviceaccounts/token,verbs=create // +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get;list;watch;update;patch // +kubebuilder:rbac:groups=external-secrets.io,resources=clusterexternalsecrets;clustersecretstores;clusterpushsecrets;externalsecrets;secretstores;pushsecrets,verbs=get;list;watch;create;update;patch;delete;deletecollection // +kubebuilder:rbac:groups=external-secrets.io,resources=clusterexternalsecrets/finalizers;clustersecretstores/finalizers;externalsecrets/finalizers;pushsecrets/finalizers;secretstores/finalizers;clusterpushsecrets/finalizers,verbs=get;update;patch // +kubebuilder:rbac:groups=external-secrets.io,resources=clusterexternalsecrets/status;clustersecretstores/status;externalsecrets/status;pushsecrets/status;secretstores/status;clusterpushsecrets/status,verbs=get;update;patch