Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 26 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ OPERATOR_SDK_VERSION ?= v1.39.0
# Image URL to use all building/pushing image targets
IMG ?= openshift.io/external-secrets-operator:latest
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
ENVTEST_K8S_VERSION = 1.31.0
ENVTEST_K8S_VERSION = 1.32.0

# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
Expand Down Expand Up @@ -127,8 +127,11 @@ vet: ## Run go vet against code.
go vet ./...

.PHONY: test
test: manifests generate fmt vet envtest ## Run tests.
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out
test: manifests generate fmt vet envtest test-apis test-unit ## Run tests.

.PHONY: test-unit
test-unit: vet ## Run unit tests.
go test $$(go list ./... | grep -vE 'test/[e2e|apis|utils]') -coverprofile cover.out

update-operand-manifests: helm yq
hack/update-external-secrets-manifests.sh $(EXTERNAL_SECRETS_VERSION)
Expand Down Expand Up @@ -250,6 +253,7 @@ YQ = $(LOCALBIN)/yq
HELM ?= $(LOCALBIN)/helm
REFERENCE_DOC_GENERATOR ?= $(LOCALBIN)/crd-ref-docs
GOVULNCHECK ?= $(LOCALBIN)/govulncheck
GINKGO ?= $(LOCALBIN)/ginkgo

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[claude-generated] Good documentation of the ignored vulnerabilities with links to vulnerability details. The added vulnerabilities (GO-2025-3956, GO-2025-3915) appear to be in vendored dependencies, which is appropriate to ignore if they don't affect the operator code.


## Tool Versions
YQ_VERSION = v4.45.2
Expand Down Expand Up @@ -283,11 +287,15 @@ crd-ref-docs: $(LOCALBIN) ## Download crd-ref-docs locally if necessary.
govulncheck: $(LOCALBIN) ## Download govulncheck locally if necessary.
$(call go-install-tool,$(GOVULNCHECK),golang.org/x/vuln/cmd/govulncheck)

.PHONY: ginkgo
ginkgo: $(LOCALBIN) ## Download ginkgo locally if necessary.
$(call go-install-tool,$(GINKGO),github.com/onsi/ginkgo/v2/ginkgo)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[claude-generated] The test structure changes properly separate API integration tests from unit tests. The test-apis target using ginkgo is well-implemented for running focused API validation tests.


# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
# $1 - target path with name of binary
# $2 - package url which can be installed
define go-install-tool
@[ -f "$(1)" ] || { \
@{ \
set -e; \
package=$(2) ;\
echo "Downloading $${package}" ;\
Expand Down Expand Up @@ -409,9 +417,21 @@ docs: crd-ref-docs

## perform vulnerabilities scan using govulncheck.
.PHONY: govulnscan
#GO-2025-3547 and GO-2025-3521 containing code is not directly used in the operator, hence will be ignored.
KNOWN_VULNERABILITIES:="GO-2025-3547|GO-2025-3521"
#The ignored vulnerabilities are not in the operator code, but in the vendored packages.
# - https://pkg.go.dev/vuln/GO-2025-3956
# - https://pkg.go.dev/vuln/GO-2025-3547
# - https://pkg.go.dev/vuln/GO-2025-3521
KNOWN_VULNERABILITIES:="GO-2025-3547|GO-2025-3521|GO-2025-3956|GO-2025-3915"
govulnscan: govulncheck $(OUTPUTS_PATH) ## Run govulncheck
- $(GOVULNCHECK) ./... > $(OUTPUTS_PATH)/govulcheck.results 2>&1
$(eval reported_vulnerabilities = $(strip $(shell grep "pkg.go.dev" $(OUTPUTS_PATH)/govulcheck.results | ([ -n $KNOWN_VULNERABILITIES ] && grep -Ev $(KNOWN_VULNERABILITIES) || cat) | wc -l)))
@(if [ $(reported_vulnerabilities) -ne 0 ]; then echo -e "\n-- ERROR -- $(reported_vulnerabilities) new vulnerabilities reported, please check\n"; exit 1; fi)

# Utilize controller-runtime provided envtest for API integration test
.PHONY: test-apis ## Run only the api integration tests.
test-apis: envtest ginkgo
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" ./hack/test-apis.sh

.PHONY: clean
clean:
rm -rf $(LOCALBIN) $(OUTPUTS_PATH) cover.out dist
2 changes: 1 addition & 1 deletion PROJECT
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ resources:
controller: true
domain: openshift.io
group: operator
kind: ExternalSecrets
kind: ExternalSecretsConfig
path: github.com/openshift/external-secrets-operator/api/v1alpha1
version: v1alpha1
version: "3"
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,18 @@ Using the External Secrets Operator ensures the following:
The External Secrets Operator for Red Hat OpenShift uses the [`external-secrets`](https://github.com/openshift/external-secrets) helm charts
to install application. The operator has three controllers to achieve the same:
- `external_secrets_manager` controller: This is responsible for
* reconciling the `externalsecretsmanagers.openshift.operator.io` resource.
* reconciling the `externalsecretsmanagers.operator.openshift.io` resource.
* providing the status of other controllers.
- `external_secrets` controller: This is responsible for
* reconciling the `externalsecrets.openshift.operator.io` resource.
* installing and managing the `external-secrets` application based on the user defined configurations in `externalsecrets.openshift.operator.io` resource.
* reconciling the `externalsecretsmanagers.openshift.operator.io` resource for the global configurations and updates the `external-scerets` deployment accordingly.
* reconciling the `externalsecretsconfigs.operator.openshift.io` resource.
* installing and managing the `external-secrets` application based on the user defined configurations in `externalsecretsconfigs.operator.openshift.io` resource.
* reconciling the `externalsecretsmanagers.operator.openshift.io` resource for the global configurations and updates the `external-secrets` deployment accordingly.
- `crd_annotator` controller:
* This is responsible for adding `cert-manager.io/inject-ca-from` annotation in the `external-secrets` provided CRDs.
* This is an optional controller, which will be activated only when [`cert-manager`](https://cert-manager.io/) is installed.
* When `cert-manager` is installed after External Secrets Operator installation, `external-secrets-operator-controller-manager` deployment must be restarted to activate the controller.

The operator automatically creates a cluster-scoped `externalsecretsmanagers.openshift.operator.io` object named `cluster`.
The operator automatically creates a cluster-scoped `externalsecretsmanagers.operator.openshift.io` object named `cluster`.

For more information about
- `external-secrets-operator for Red Hat OpenShift`, refer to the [link](https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/security_and_compliance/external-secrets-operator-for-red-hat-openshift)
Expand Down
10 changes: 3 additions & 7 deletions api/v1alpha1/conditions.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
package v1alpha1

const (
// Degraded is the condition type used to inform state of the operator when
// it has failed with irrecoverable error like permission issues.
// DebugEnabled has the following options:
// Degraded is the condition type used to inform state of the operator when it has failed with irrecoverable error like permission issues.
// Status:
// - True
// - False
// Reason:
// - Failed
Degraded string = "Degraded"

// Ready is the condition type used to inform state of readiness of the
// operator to process external-secrets enabling requests.
// Ready is the condition type used to inform state of readiness of the operator to process external-secrets enabling requests.
// Status:
// - True
// - False
Expand All @@ -22,8 +19,7 @@ const (
// - Ready: operand successfully deployed and ready
Ready string = "Ready"

// UpdateAnnotation is the condition type used to inform status of
// updating the annotations.
// UpdateAnnotation is the condition type used to inform status of updating the annotations.
// Status:
// - True
// - False
Expand Down
203 changes: 203 additions & 0 deletions api/v1alpha1/external_secrets_config_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package v1alpha1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func init() {
SchemeBuilder.Register(&ExternalSecretsConfig{}, &ExternalSecretsConfigList{})
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:object:root=true

// ExternalSecretsConfigList is a list of ExternalSecretsConfig objects.
type ExternalSecretsConfigList struct {
metav1.TypeMeta `json:",inline"`

// metadata is the standard list's metadata.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
metav1.ListMeta `json:"metadata"`
Items []ExternalSecretsConfig `json:"items"`
}

// +genclient
// +genclient:nonNamespaced
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:path=externalsecretsconfigs,scope=Cluster,categories={external-secrets-operator, external-secrets},shortName=esc;externalsecretsconfig;esconfig
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status"
// +kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].message"
// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:metadata:labels={"app.kubernetes.io/name=externalsecretsconfig", "app.kubernetes.io/part-of=external-secrets-operator"}

// ExternalSecretsConfig describes configuration and information about the managed external-secrets deployment.
// The name must be `cluster` as ExternalSecretsConfig is a singleton, allowing only one instance per cluster.
//
// When an ExternalSecretsConfig is created, the controller installs the external-secrets and keeps it in the desired state.
//
// +kubebuilder:validation:XValidation:rule="self.metadata.name == 'cluster'",message="ExternalSecretsConfig is a singleton, .metadata.name must be 'cluster'"
// +operator-sdk:csv:customresourcedefinitions:displayName="ExternalSecretsConfig"
type ExternalSecretsConfig struct {
metav1.TypeMeta `json:",inline"`

// metadata is the standard object's metadata.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
metav1.ObjectMeta `json:"metadata,omitempty"`

// spec is the specification of the desired behavior of the ExternalSecretsConfig.
Spec ExternalSecretsConfigSpec `json:"spec,omitempty"`

// status is the most recently observed status of the ExternalSecretsConfig.
Status ExternalSecretsConfigStatus `json:"status,omitempty"`
}

// ExternalSecretsConfigSpec is for configuring the external-secrets operand behavior.
// +kubebuilder:validation:XValidation:rule="!has(self.plugins) || !has(self.plugins.bitwardenSecretManagerProvider) || !has(self.plugins.bitwardenSecretManagerProvider.mode) || self.plugins.bitwardenSecretManagerProvider.mode != 'Enabled' || has(self.plugins.bitwardenSecretManagerProvider.secretRef) || (has(self.controllerConfig) && has(self.controllerConfig.certProvider) && has(self.controllerConfig.certProvider.certManager) && has(self.controllerConfig.certProvider.certManager.mode) && self.controllerConfig.certProvider.certManager.mode == 'Enabled')",message="secretRef or certManager must be configured when bitwardenSecretManagerProvider plugin is enabled"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[claude-generated] The XValidation rule for bitwardenSecretManagerProvider is quite complex and could benefit from breaking down into multiple smaller validation rules for better readability and error messages.

type ExternalSecretsConfigSpec struct {
// appConfig is for specifying the configurations for the `external-secrets` operand.
// +kubebuilder:validation:Optional
ApplicationConfig ApplicationConfig `json:"appConfig,omitempty"`

// plugins is for configuring the optional provider plugins.
// +kubebuilder:validation:Optional
Plugins PluginsConfig `json:"plugins,omitempty"`

// controllerConfig is for specifying the configurations for the controller to use while installing the `external-secrets` operand and the plugins.
// +kubebuilder:validation:Optional
ControllerConfig ControllerConfig `json:"controllerConfig,omitempty"`
}

// ExternalSecretsConfigStatus is the most recently observed status of the ExternalSecretsConfig.
type ExternalSecretsConfigStatus struct {
// conditions holds information of the current state of the external-secrets deployment.
ConditionalStatus `json:",inline"`

// externalSecretsImage is the name of the image and the tag used for deploying external-secrets.
ExternalSecretsImage string `json:"externalSecretsImage,omitempty"`

// BitwardenSDKServerImage is the name of the image and the tag used for deploying bitwarden-sdk-server.
BitwardenSDKServerImage string `json:"bitwardenSDKServerImage,omitempty"`
}

// ApplicationConfig is for specifying the configurations for the external-secrets operand.
type ApplicationConfig struct {
// operatingNamespace is for restricting the external-secrets operations to the provided namespace.
// When configured `ClusterSecretStore` and `ClusterExternalSecret` are implicitly disabled.
// +kubebuilder:validation:MinLength:=1
// +kubebuilder:validation:MaxLength:=63
// +kubebuilder:validation:Optional

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[claude-generated] Consider adding validation to ensure operatingNamespace follows Kubernetes namespace naming conventions (RFC 1123 label names).

OperatingNamespace string `json:"operatingNamespace,omitempty"`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in above claude comment, would it be better to validate OperatingNamespace as per RFC 1123 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

operatingNamespace is as per the RFC 1123 DNS Labels, which allows max of 63 characters for namespace.


// webhookConfig is for configuring external-secrets webhook specifics.
// +kubebuilder:validation:Optional
WebhookConfig *WebhookConfig `json:"webhookConfig,omitempty"`

// +kubebuilder:validation:Optional
CommonConfigs `json:",inline"`
}

// ControllerConfig is for specifying the configurations for the controller to use while installing the `external-secrets` operand and the plugins.
type ControllerConfig struct {
// certProvider is for defining the configuration for certificate providers used to manage TLS certificates for webhook and plugins.
// +kubebuilder:validation:Optional
CertProvider *CertProvidersConfig `json:"certProvider,omitempty"`

// labels to apply to all resources created for the external-secrets operand deployment.
// This field can have a maximum of 20 entries.
// +mapType=granular
// +kubebuilder:validation:MinProperties:=0
// +kubebuilder:validation:MaxProperties:=20
// +kubebuilder:validation:Optional
Labels map[string]string `json:"labels,omitempty"`

// periodicReconcileInterval specifies the time interval in seconds for periodic reconciliation by the operator.
// This controls how often the operator checks resources created for external-secrets operand to ensure they remain in desired state.
// Interval can have value between 120-18000 seconds (2 minutes to 5 hours). Defaults to 300 seconds (5 minutes) if not specified.
// +kubebuilder:default:=300
// +kubebuilder:validation:Minimum:=120
// +kubebuilder:validation:Maximum:=18000
// +kubebuilder:validation:Optional

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[claude-generated] The periodicReconcileInterval field has good validation constraints. Consider documenting the performance implications of different interval values in the field description.

PeriodicReconcileInterval uint32 `json:"periodicReconcileInterval,omitempty"`
}

// BitwardenSecretManagerProvider is for enabling the bitwarden secrets manager provider and for setting up the additional service required for connecting with the bitwarden server.
type BitwardenSecretManagerProvider struct {
// mode indicates bitwarden secrets manager provider state, which can be indicated by setting Enabled or Disabled.
// Enabled: Enables the Bitwarden provider plugin. The operator will ensure the plugin is deployed and its state is synchronized.
// Disabled: Disables reconciliation of the Bitwarden provider plugin. The plugin and its resources will remain in their current state and will not be managed by the operator.
// +kubebuilder:validation:Enum:=Enabled;Disabled
// +kubebuilder:default:=Disabled
// +kubebuilder:validation:Optional
Mode Mode `json:"mode,omitempty"`

// SecretRef is the Kubernetes secret containing the TLS key pair to be used for the bitwarden server.
// The issuer in CertManagerConfig will be utilized to generate the required certificate if the secret reference is not provided and CertManagerConfig is configured.
// The key names in secret for certificate must be `tls.crt`, for private key must be `tls.key` and for CA certificate key name must be `ca.crt`.
// +kubebuilder:validation:Optional
SecretRef *SecretReference `json:"secretRef,omitempty"`
}

// WebhookConfig is for configuring external-secrets webhook specifics.
type WebhookConfig struct {
// CertificateCheckInterval is for configuring the polling interval to check the certificate validity.
// +kubebuilder:default:="5m"
// +kubebuilder:validation:Optional
CertificateCheckInterval *metav1.Duration `json:"certificateCheckInterval,omitempty"`
}

// CertManagerConfig is for configuring cert-manager specifics.
// +kubebuilder:validation:XValidation:rule="self.mode != 'Enabled' || has(self.issuerRef)",message="issuerRef must be provided when mode is set to Enabled."
// +kubebuilder:validation:XValidation:rule="has(self.injectAnnotations) && self.injectAnnotations != 'false' ? self.mode != 'Disabled' : true",message="injectAnnotations can only be set when mode is set to Enabled."
type CertManagerConfig struct {
// mode indicates whether to use cert-manager for certificate management, instead of built-in cert-controller.
// Enabled: Makes use of cert-manager for obtaining the certificates for webhook server and other components.
// Disabled: Makes use of in-built cert-controller for obtaining the certificates for webhook server, which is the default behavior.
// This field is immutable once set.
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="mode is immutable once set"
// +kubebuilder:validation:Enum:=Enabled;Disabled
// +kubebuilder:validation:Required
Mode Mode `json:"mode,omitempty"`

// injectAnnotations is for adding the `cert-manager.io/inject-ca-from` annotation to the webhooks and CRDs to automatically setup webhook to use the cert-manager CA. This requires CA Injector to be enabled in cert-manager.
// Use `true` or `false` to indicate the preference. This field is immutable once set.
// +kubebuilder:validation:Enum:="true";"false"
// +kubebuilder:default:="false"
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="injectAnnotations is immutable once set"
// +kubebuilder:validation:Optional
InjectAnnotations string `json:"injectAnnotations,omitempty"`

// issuerRef contains details of the referenced object used for obtaining certificates.
// When `issuerRef.Kind` is `Issuer`, it must exist in the `external-secrets` namespace.
// This field is immutable once set.
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="issuerRef is immutable once set"
// +kubebuilder:validation:XValidation:rule="!has(self.kind) || self.kind.lowerAscii() == 'issuer' || self.kind.lowerAscii() == 'clusterissuer'",message="kind must be either 'Issuer' or 'ClusterIssuer'"
// +kubebuilder:validation:XValidation:rule="!has(self.group) || self.group.lowerAscii() == 'cert-manager.io'",message="group must be 'cert-manager.io'"
// +kubebuilder:validation:Optional
IssuerRef *ObjectReference `json:"issuerRef,omitempty"`

// certificateDuration is the validity period of the webhook certificate.
// +kubebuilder:default:="8760h"
// +kubebuilder:validation:Optional
CertificateDuration *metav1.Duration `json:"certificateDuration,omitempty"`

// certificateRenewBefore is the ahead time to renew the webhook certificate before expiry.
// +kubebuilder:default:="30m"
// +kubebuilder:validation:Optional
CertificateRenewBefore *metav1.Duration `json:"certificateRenewBefore,omitempty"`
}

// PluginsConfig is for configuring the optional plugins.
type PluginsConfig struct {
// bitwardenSecretManagerProvider is for enabling the bitwarden secrets manager provider plugin for connecting with the bitwarden secrets manager.
// +kubebuilder:validation:Optional
BitwardenSecretManagerProvider *BitwardenSecretManagerProvider `json:"bitwardenSecretManagerProvider,omitempty"`
}

// CertProvidersConfig defines the configuration for certificate providers used to manage TLS certificates for webhook and plugins.
type CertProvidersConfig struct {
// certManager is for configuring cert-manager provider specifics.
// +kubebuilder:validation:Optional
CertManager *CertManagerConfig `json:"certManager,omitempty"`
}
Loading