Repository: elastic/cloud-on-k8s

Issue:
Configure labels or annotations on child resources
<!-- Feature request -->

## Proposal
Allow users to set labels on the "top level" resources created from the CRDs

**Use case. Why is this important?**
Today users can label the pods created by the ECK operator, but the statefulset for elasticsearch doesn't have any means of being labelled. Presumably the kibana would have the same issue - we use labels in monitoring, logs, generic discoverability, etc.

It'd be very helpful to have specifications in the CRD to apply labels to the deployments/statefulsets created from them.



Discussion:
@spencergilbert: I suppose also labelling the created services and all would be nice too@sebgl: Would it make sense to have all StatefulSets/Deployment inherit the labels applied to the Elasticsearch/Kibana resource?


Example:
```yaml
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: quickstart
  labels:
    foo: bar
spec:
  version: 7.6.0
  nodeSets:
  - name: default
    count: 1
```
would label the underlying StatefulSet with `foo: bar`. Same for Kibana Deployment. And all other resources: services, secrets, etc.

It feels like an easy way of labeling everything at once. Would that cover your use case @spencergilbert? Or would you expect to be able to specify per-NodeSet/StatefulSet labels?@spencergilbert: That would definitely solve my use case :+1:@charith-elastic: After prototyping how this would work, I think that it would be better to let users decide which labels or annotations they want to propagate to child objects. This is because some labels/annotations are probably meant for the CR only and having them propagated to children could result in confusion and/or unexpected behaviour (e.g. deleting by label selector).

I am proposing that we introduce two annotations that can be applied to CRs:
```yaml
eck.k8s.elastic.co/propagate-annotations: "comma,separated,list,of,annotations,to,propagate"
eck.k8s.elastic.co/propagate-labels: "comma,separated,list,of,labels,to,propagate"
```

Setting the annotation value to `*` would propagate all annotations/labels in the CR.@sebgl: Similar issue for setting headless services labels: https://github.com/elastic/cloud-on-k8s/issues/3990.
As Charith suggested above, I think it makes sense to label all child resources, and not just StatefulSets.@tschroeder-zendesk: I am currently unable to use ECK due to this issue as my company requires me to label all resources with my team. It's been open for a long time. Any possibility of doing this soon?@zpear: Any chance of this happening soon? My team would benefit from this as well@winterrobert: We have a use case where we want to annotate the data and master services to sync them into Consul for our service discovery; as this can't be done with the operator currently we have to work around it by patching the service resources using Server-Side Apply in Argo CD which adds complexity and is inconsistent.

+1 for configurable labels or annotations on child resources.@frivoire: I would also be interested in the possibility to define **labels** on the StatefulSets (of ES nodesets) and on the Deployments (of Kibana).
Because I need to have a `team` label on all K8S resources.

Maybe the solution to this issue is to create another structure: `.spec.nodeSets[*].stsTemplate` in the `Elasticsearch` resource in which we could fill `.metadata.labels`
(the same way that we can already do it for Pods using `Elasticsearch.spec.nodeSets[*].podTemplate.metadata.labels`).

And similar in other resources: `Kibana`, etc...@frivoire: I would like to emphatize on one response:

> After prototyping how this would work, I think that it would be better to let users decide which labels or annotations they want to propagate to child objects. This is because some labels/annotations are probably meant for the CR only and having them propagated to children could result in confusion and/or unexpected behaviour (e.g. deleting by label selector).

I totally agree with this "do **not** propagate all labels blindly, but let user choose" idea 👍 
My main example where it's important is when the Elasticsearch/Kibana resources are created by an **Helm** Chart:
```
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  annotations:
    meta.helm.sh/release-name: elastic-test
    meta.helm.sh/release-namespace: dev
  labels:
    app.kubernetes.io/managed-by: Helm
    helm.toolkit.fluxcd.io/name: elastic-test
    helm.toolkit.fluxcd.io/namespace: dev
  name: elastic-test
```
=> we do **not** want those helm labels to be put on the StatefulSet/Deployment resources, because they are not managed by Helm and Helm would mess them !


So, please do not propagate blindly the labels !
But a feature to be able to customize them would be very useful (cf my previous message) :)@tomsozolins: It would be helpful to add kubernetes recommended labels automatically on the pods -https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/

For example:
app.kubernetes.io/name: name
app.kubernetes.io/instance: instance

One use case where such labels are needed is network observability with cilium. Currently all eck pods are shown as Unknown App.@JKrehling: Is their any current workaround to being able to deploy the statefulsets with propagated labels? 
I liked the solution proposed in #3186 but it sounds like it never got merged due to not being able to clean up unused labels.  
Since this is opt in anyways I'm not sure the cleanup is something that is really necessary since the labels that are required seldom change and without it you are hindered more than if you had dangling ones that you would have to clean up manually.  @davidegreenwald: +1 on this request. This is preventing [Datadog unified service tagging](https://docs.datadoghq.com/getting_started/tagging/unified_service_tagging/?tab=kubernetes) best practices, which look for a set of labels at both the StatefulSet and pod level.@ebinsu: +1 on this request.@ashuec90: +1 may be provision for changing the sts name as well. Currently it adds -es from somewhere i am not able to find.
This would help in using the old pvc used by the elasticsearch which was deployed using archived repository https://github.com/elastic/helm-charts/tree/main/elasticsearch , so the upgrade can be done without snapshoting and restoring process 

Solution patch:
diff --git a/NOTICE.txt b/NOTICE.txt
index cd5be43ef..a349c096a 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -926,11 +926,11 @@ Contents of probable licence file $GOMODCACHE/github.com/google/go-containerregi
 
 --------------------------------------------------------------------------------
 Module  : github.com/google/uuid
-Version : v1.4.0
-Time    : 2023-10-26T15:24:04Z
+Version : v1.5.0
+Time    : 2023-12-12T17:21:37Z
 Licence : BSD-3-Clause
 
-Contents of probable licence file $GOMODCACHE/github.com/google/uuid@v1.4.0/LICENSE:
+Contents of probable licence file $GOMODCACHE/github.com/google/uuid@v1.5.0/LICENSE:
 
 Copyright (c) 2009,2014 Google Inc. All rights reserved.
 
diff --git a/build/Dockerfile-ubi b/build/Dockerfile-ubi
index 57744a4c0..df3add1e2 100644
--- a/build/Dockerfile-ubi
+++ b/build/Dockerfile-ubi
@@ -27,7 +27,7 @@ RUN --mount=type=cache,mode=0755,target=/go/pkg/mod \
 
 # ---------------------------------------------
 # Copy the operator binary into a lighter image
-FROM registry.access.redhat.com/ubi9/ubi-minimal:9.3-1361.1699548032
+FROM registry.access.redhat.com/ubi9/ubi-minimal:9.3-1475
 
 # Update the base image packages to the latest versions
 RUN microdnf update --setopt=tsflags=nodocs && microdnf clean all
diff --git a/cmd/manager/main.go b/cmd/manager/main.go
index de5768ca1..e38c992c3 100644
--- a/cmd/manager/main.go
+++ b/cmd/manager/main.go
@@ -1012,6 +1012,7 @@ func setupWebhook(
 		&kbv1beta1.Kibana{},
 		&emsv1alpha1.ElasticMapsServer{},
 		&logstashv1alpha1.Logstash{},
+		&policyv1alpha1.StackConfigPolicy{},
 	}
 	for _, obj := range webhookObjects {
 		if err := commonwebhook.SetupValidatingWebhookWithConfig(&commonwebhook.Config{
diff --git a/config/crds/v1/all-crds.yaml b/config/crds/v1/all-crds.yaml
index 0bba97e05..113395641 100644
--- a/config/crds/v1/all-crds.yaml
+++ b/config/crds/v1/all-crds.yaml
@@ -10234,6 +10234,43 @@ spec:
                       type: object
                     type: array
                     x-kubernetes-preserve-unknown-fields: true
+                  secureSettings:
+                    description: SecureSettings are additional Secrets that contain
+                      data to be configured to Elasticsearch's keystore.
+                    items:
+                      description: SecretSource defines a data source based on a Kubernetes
+                        Secret.
+                      properties:
+                        entries:
+                          description: Entries define how to project each key-value
+                            pair in the secret to filesystem paths. If not defined,
+                            all keys will be projected to similarly named paths in
+                            the filesystem. If defined, only the specified keys will
+                            be projected to the corresponding paths.
+                          items:
+                            description: KeyToPath defines how to map a key in a Secret
+                              object to a filesystem path.
+                            properties:
+                              key:
+                                description: Key is the key contained in the secret.
+                                type: string
+                              path:
+                                description: Path is the relative file path to map
+                                  the key to. Path must not be an absolute file path
+                                  and must not contain any ".." components.
+                                type: string
+                            required:
+                            - key
+                            type: object
+                          type: array
+                        secretName:
+                          description: SecretName is the name of the secret.
+                          type: string
+                      required:
+                      - secretName
+                      type: object
+                    type: array
+                    x-kubernetes-preserve-unknown-fields: true
                   securityRoleMappings:
                     description: SecurityRoleMappings holds the Role Mappings settings
                       (/_security/role_mapping)
@@ -10256,21 +10293,40 @@ spec:
                     description: Config holds the settings that go into kibana.yml.
                     type: object
                     x-kubernetes-preserve-unknown-fields: true
-                  secretMounts:
-                    description: SecretMounts are additional secrets that need to
-                      be mounted into the Kibana pods.
+                  secureSettings:
+                    description: SecureSettings are additional Secrets that contain
+                      data to be configured to Kibana's keystore.
                     items:
-                      description: SecretMount contains information about additional
-                        secrets to be mounted to the elasticsearch pods
+                      description: SecretSource defines a data source based on a Kubernetes
+                        Secret.
                       properties:
-                        mountPath:
-                          description: MountPath denotes the path to which the secret
-                            should be mounted to inside the elasticsearch pod
-                          type: string
+                        entries:
+                          description: Entries define how to project each key-value
+                            pair in the secret to filesystem paths. If not defined,
+                            all keys will be projected to similarly named paths in
+                            the filesystem. If defined, only the specified keys will
+                            be projected to the corresponding paths.
+                          items:
+                            description: KeyToPath defines how to map a key in a Secret
+                              object to a filesystem path.
+                            properties:
+                              key:
+                                description: Key is the key contained in the secret.
+                                type: string
+                              path:
+                                description: Path is the relative file path to map
+                                  the key to. Path must not be an absolute file path
+                                  and must not contain any ".." components.
+                                type: string
+                            required:
+                            - key
+                            type: object
+                          type: array
                         secretName:
-                          description: SecretName denotes the name of the secret that
-                            needs to be mounted to the elasticsearch pod
+                          description: SecretName is the name of the secret.
                           type: string
+                      required:
+                      - secretName
                       type: object
                     type: array
                     x-kubernetes-preserve-unknown-fields: true
@@ -10324,6 +10380,8 @@ spec:
                 type: object
                 x-kubernetes-map-type: atomic
               secureSettings:
+                description: 'Deprecated: SecureSettings only applies to Elasticsearch
+                  and is deprecated. It must be set per application instead.'
                 items:
                   description: SecretSource defines a data source based on a Kubernetes
                     Secret.
@@ -10384,25 +10442,33 @@ spec:
                 type: integer
               resourcesStatuses:
                 additionalProperties:
-                  description: ResourcePolicyStatus models the status of the policy
-                    for one resource to be configured.
-                  properties:
-                    currentVersion:
-                      format: int64
-                      type: integer
-                    error:
-                      properties:
-                        message:
-                          type: string
-                        version:
-                          format: int64
-                          type: integer
-                      type: object
-                    expectedVersion:
-                      format: int64
-                      type: integer
-                    phase:
-                      type: string
+                  additionalProperties:
+                    description: ResourcePolicyStatus models the status of the policy
+                      for one resource to be configured.
+                    properties:
+                      currentVersion:
+                        description: CurrentVersion denotes the current version of
+                          filesettings applied to the Elasticsearch cluster This field
+                          does not apply to Kibana resources
+                        format: int64
+                        type: integer
+                      error:
+                        properties:
+                          message:
+                            type: string
+                          version:
+                            format: int64
+                            type: integer
+                        type: object
+                      expectedVersion:
+                        description: ExpectedVersion denotes the expected version
+                          of filesettings that should be applied to the Elasticsearch
+                          cluster This field does not apply to Kibana resources
+                        format: int64
+                        type: integer
+                      phase:
+                        type: string
+                    type: object
                   type: object
                 description: ResourcesStatuses holds the status for each resource
                   to be configured.
diff --git a/config/crds/v1/bases/stackconfigpolicy.k8s.elastic.co_stackconfigpolicies.yaml b/config/crds/v1/bases/stackconfigpolicy.k8s.elastic.co_stackconfigpolicies.yaml
index 0e1b0c7cc..4bcd81848 100644
--- a/config/crds/v1/bases/stackconfigpolicy.k8s.elastic.co_stackconfigpolicies.yaml
+++ b/config/crds/v1/bases/stackconfigpolicy.k8s.elastic.co_stackconfigpolicies.yaml
@@ -104,6 +104,43 @@ spec:
                       type: object
                     type: array
                     x-kubernetes-preserve-unknown-fields: true
+                  secureSettings:
+                    description: SecureSettings are additional Secrets that contain
+                      data to be configured to Elasticsearch's keystore.
+                    items:
+                      description: SecretSource defines a data source based on a Kubernetes
+                        Secret.
+                      properties:
+                        entries:
+                          description: Entries define how to project each key-value
+                            pair in the secret to filesystem paths. If not defined,
+                            all keys will be projected to similarly named paths in
+                            the filesystem. If defined, only the specified keys will
+                            be projected to the corresponding paths.
+                          items:
+                            description: KeyToPath defines how to map a key in a Secret
+                              object to a filesystem path.
+                            properties:
+                              key:
+                                description: Key is the key contained in the secret.
+                                type: string
+                              path:
+                                description: Path is the relative file path to map
+                                  the key to. Path must not be an absolute file path
+                                  and must not contain any ".." components.
+                                type: string
+                            required:
+                            - key
+                            type: object
+                          type: array
+                        secretName:
+                          description: SecretName is the name of the secret.
+                          type: string
+                      required:
+                      - secretName
+                      type: object
+                    type: array
+                    x-kubernetes-preserve-unknown-fields: true
                   securityRoleMappings:
                     description: SecurityRoleMappings holds the Role Mappings settings
                       (/_security/role_mapping)
@@ -126,21 +163,40 @@ spec:
                     description: Config holds the settings that go into kibana.yml.
                     type: object
                     x-kubernetes-preserve-unknown-fields: true
-                  secretMounts:
-                    description: SecretMounts are additional secrets that need to
-                      be mounted into the Kibana pods.
+                  secureSettings:
+                    description: SecureSettings are additional Secrets that contain
+                      data to be configured to Kibana's keystore.
                     items:
-                      description: SecretMount contains information about additional
-                        secrets to be mounted to the elasticsearch pods
+                      description: SecretSource defines a data source based on a Kubernetes
+                        Secret.
                       properties:
-                        mountPath:
-                          description: MountPath denotes the path to which the secret
-                            should be mounted to inside the elasticsearch pod
-                          type: string
+                        entries:
+                          description: Entries define how to project each key-value
+                            pair in the secret to filesystem paths. If not defined,
+                            all keys will be projected to similarly named paths in
+                            the filesystem. If defined, only the specified keys will
+                            be projected to the corresponding paths.
+                          items:
+                            description: KeyToPath defines how to map a key in a Secret
+                              object to a filesystem path.
+                            properties:
+                              key:
+                                description: Key is the key contained in the secret.
+                                type: string
+                              path:
+                                description: Path is the relative file path to map
+                                  the key to. Path must not be an absolute file path
+                                  and must not contain any ".." components.
+                                type: string
+                            required:
+                            - key
+                            type: object
+                          type: array
                         secretName:
-                          description: SecretName denotes the name of the secret that
-                            needs to be mounted to the elasticsearch pod
+                          description: SecretName is the name of the secret.
                           type: string
+                      required:
+                      - secretName
                       type: object
                     type: array
                     x-kubernetes-preserve-unknown-fields: true
@@ -194,6 +250,8 @@ spec:
                 type: object
                 x-kubernetes-map-type: atomic
               secureSettings:
+                description: 'Deprecated: SecureSettings only applies to Elasticsearch
+                  and is deprecated. It must be set per application instead.'
                 items:
                   description: SecretSource defines a data source based on a Kubernetes
                     Secret.
@@ -254,25 +312,33 @@ spec:
                 type: integer
               resourcesStatuses:
                 additionalProperties:
-                  description: ResourcePolicyStatus models the status of the policy
-                    for one resource to be configured.
-                  properties:
-                    currentVersion:
-                      format: int64
-                      type: integer
-                    error:
-                      properties:
-                        message:
-                          type: string
-                        version:
-                          format: int64
-                          type: integer
-                      type: object
-                    expectedVersion:
-                      format: int64
-                      type: integer
-                    phase:
-                      type: string
+                  additionalProperties:
+                    description: ResourcePolicyStatus models the status of the policy
+                      for one resource to be configured.
+                    properties:
+                      currentVersion:
+                        description: CurrentVersion denotes the current version of
+                          filesettings applied to the Elasticsearch cluster This field
+                          does not apply to Kibana resources
+                        format: int64
+                        type: integer
+                      error:
+                        properties:
+                          message:
+                            type: string
+                          version:
+                            format: int64
+                            type: integer
+                        type: object
+                      expectedVersion:
+                        description: ExpectedVersion denotes the expected version
+                          of filesettings that should be applied to the Elasticsearch
+                          cluster This field does not apply to Kibana resources
+                        format: int64
+                        type: integer
+                      phase:
+                        type: string
+                    type: object
                   type: object
                 description: ResourcesStatuses holds the status for each resource
                   to be configured.
diff --git a/deploy/eck-operator/charts/eck-operator-crds/templates/all-crds.yaml b/deploy/eck-operator/charts/eck-operator-crds/templates/all-crds.yaml
index d8ac2d14b..756f1a813 100644
--- a/deploy/eck-operator/charts/eck-operator-crds/templates/all-crds.yaml
+++ b/deploy/eck-operator/charts/eck-operator-crds/templates/all-crds.yaml
@@ -10294,6 +10294,43 @@ spec:
                       type: object
                     type: array
                     x-kubernetes-preserve-unknown-fields: true
+                  secureSettings:
+                    description: SecureSettings are additional Secrets that contain
+                      data to be configured to Elasticsearch's keystore.
+                    items:
+                      description: SecretSource defines a data source based on a Kubernetes
+                        Secret.
+                      properties:
+                        entries:
+                          description: Entries define how to project each key-value
+                            pair in the secret to filesystem paths. If not defined,
+                            all keys will be projected to similarly named paths in
+                            the filesystem. If defined, only the specified keys will
+                            be projected to the corresponding paths.
+                          items:
+                            description: KeyToPath defines how to map a key in a Secret
+                              object to a filesystem path.
+                            properties:
+                              key:
+                                description: Key is the key contained in the secret.
+                                type: string
+                              path:
+                                description: Path is the relative file path to map
+                                  the key to. Path must not be an absolute file path
+                                  and must not contain any ".." components.
+                                type: string
+                            required:
+                            - key
+                            type: object
+                          type: array
+                        secretName:
+                          description: SecretName is the name of the secret.
+                          type: string
+                      required:
+                      - secretName
+                      type: object
+                    type: array
+                    x-kubernetes-preserve-unknown-fields: true
                   securityRoleMappings:
                     description: SecurityRoleMappings holds the Role Mappings settings
                       (/_security/role_mapping)
@@ -10316,21 +10353,40 @@ spec:
                     description: Config holds the settings that go into kibana.yml.
                     type: object
                     x-kubernetes-preserve-unknown-fields: true
-                  secretMounts:
-                    description: SecretMounts are additional secrets that need to
-                      be mounted into the Kibana pods.
+                  secureSettings:
+                    description: SecureSettings are additional Secrets that contain
+                      data to be configured to Kibana's keystore.
                     items:
-                      description: SecretMount contains information about additional
-                        secrets to be mounted to the elasticsearch pods
+                      description: SecretSource defines a data source based on a Kubernetes
+                        Secret.
                       properties:
-                        mountPath:
-                          description: MountPath denotes the path to which the secret
-                            should be mounted to inside the elasticsearch pod
-                          type: string
+                        entries:
+                          description: Entries define how to project each key-value
+                            pair in the secret to filesystem paths. If not defined,
+                            all keys will be projected to similarly named paths in
+                            the filesystem. If defined, only the specified keys will
+                            be projected to the corresponding paths.
+                          items:
+                            description: KeyToPath defines how to map a key in a Secret
+                              object to a filesystem path.
+                            properties:
+                              key:
+                                description: Key is the key contained in the secret.
+                                type: string
+                              path:
+                                description: Path is the relative file path to map
+                                  the key to. Path must not be an absolute file path
+                                  and must not contain any ".." components.
+                                type: string
+                            required:
+                            - key
+                            type: object
+                          type: array
                         secretName:
-                          description: SecretName denotes the name of the secret that
-                            needs to be mounted to the elasticsearch pod
+                          description: SecretName is the name of the secret.
                           type: string
+                      required:
+                      - secretName
                       type: object
                     type: array
                     x-kubernetes-preserve-unknown-fields: true
@@ -10384,6 +10440,8 @@ spec:
                 type: object
                 x-kubernetes-map-type: atomic
               secureSettings:
+                description: 'Deprecated: SecureSettings only applies to Elasticsearch
+                  and is deprecated. It must be set per application instead.'
                 items:
                   description: SecretSource defines a data source based on a Kubernetes
                     Secret.
@@ -10444,25 +10502,33 @@ spec:
                 type: integer
               resourcesStatuses:
                 additionalProperties:
-                  description: ResourcePolicyStatus models the status of the policy
-                    for one resource to be configured.
-                  properties:
-                    currentVersion:
-                      format: int64
-                      type: integer
-                    error:
-                      properties:
-                        message:
-                          type: string
-                        version:
-                          format: int64
-                          type: integer
-                      type: object
-                    expectedVersion:
-                      format: int64
-                      type: integer
-                    phase:
-                      type: string
+                  additionalProperties:
+                    description: ResourcePolicyStatus models the status of the policy
+                      for one resource to be configured.
+                    properties:
+                      currentVersion:
+                        description: CurrentVersion denotes the current version of
+                          filesettings applied to the Elasticsearch cluster This field
+                          does not apply to Kibana resources
+                        format: int64
+                        type: integer
+                      error:
+                        properties:
+                          message:
+                            type: string
+                          version:
+                            format: int64
+                            type: integer
+                        type: object
+                      expectedVersion:
+                        description: ExpectedVersion denotes the expected version
+                          of filesettings that should be applied to the Elasticsearch
+                          cluster This field does not apply to Kibana resources
+                        format: int64
+                        type: integer
+                      phase:
+                        type: string
+                    type: object
                   type: object
                 description: ResourcesStatuses holds the status for each resource
                   to be configured.
diff --git a/docs/orchestrating-elastic-stack-applications/elasticsearch/snapshots.asciidoc b/docs/orchestrating-elastic-stack-applications/elasticsearch/snapshots.asciidoc
index 953ca7d4e..bffacb9c0 100644
--- a/docs/orchestrating-elastic-stack-applications/elasticsearch/snapshots.asciidoc
+++ b/docs/orchestrating-elastic-stack-applications/elasticsearch/snapshots.asciidoc
@@ -22,8 +22,9 @@ What follows is a non-exhaustive list of configuration examples. The first examp
 
 * <<{p}-basic-snapshot-gcs>>
 
-The next two examples cover approaches that use Cloud-provider specific means to leverage Kubernetes service accounts to avoid having to configure snapshot repository credentials in Elasticsearch:
+The following examples cover approaches that use Cloud-provider specific means to leverage Kubernetes service accounts to avoid having to configure snapshot repository credentials in Elasticsearch:
 
+* <<{p}-basic-snapshot-azure>>
 * <<{p}-gke-workload-identiy>>
 * <<{p}-iam-service-accounts>>
 
@@ -439,3 +440,20 @@ Assuming you stored this in a file called `elasticsearch.yaml` you can in both c
 ----
 kubectl apply -f elasticsearch.yaml
 ----
+
+[id="{p}-basic-snapshot-azure"]
+=== Basic snapshot repository setup for Azure
+
+This is identical to <<{p}-basic-snapshot-gcs>> except for configuring storage account credentials through the Elasticsearch keystore.
+
+. Follow the Microsoft documentation to link:https://docs.microsoft.com/en-us/azure/storage/common/storage-account-create[set up an Azure storage account] with an access key, and then link:https://docs.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-portal[create a container].
+
+. Create two files for your Azure storage account credentials: `azure.client.secondary.account` and `azure.client.secondary.key` containing the associated values.
+
+. Next, follow the same steps as in <<{p}-secure-settings>> to create a Kubernetes secret from these files, reference it in the `secureSettings` section of the Elasticsearch resource and apply the modifications.
++
+[source,sh]
+----
+kubectl create secret generic az-storage-credentials --from-file=azure.client.default.account --from-file=azure.client.default.key -n <namespace>
+----
+
diff --git a/docs/orchestrating-elastic-stack-applications/stack-config-policy.asciidoc b/docs/orchestrating-elastic-stack-applications/stack-config-policy.asciidoc
index 2631bdb3c..d160c1afa 100644
--- a/docs/orchestrating-elastic-stack-applications/stack-config-policy.asciidoc
+++ b/docs/orchestrating-elastic-stack-applications/stack-config-policy.asciidoc
@@ -45,7 +45,7 @@ The following fields are optional:
 
 * `namespace` is the namespace of the `StackConfigPolicy` resource and used to identify the Elasticsearch clusters to which this policy applies. If it equals to the operator namespace, the policy applies to all namespaces managed by the operator, otherwise the policy only applies to the namespace of the policy.
 * `resourceSelector` is a link:https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/[label selector] to identify the Elasticsearch clusters to which this policy applies in combination with the namespace(s). No `resourceSelector` means all Elasticsearch clusters in the namespace(s).
-* `secureSettings` is a list of Secrets containing Secure Settings to inject into the keystore(s) of the Elasticsearch cluster(s) to which this policy applies, similar to the <<{p}-custom-images,Elasticsearch Secure Settings>>.
+* `secureSettings` is a list of Secrets containing Secure Settings to inject into the keystore(s) of the Elasticsearch cluster(s) to which this policy applies, similar to the <<{p}-es-secure-settings,Elasticsearch Secure Settings>>.
 
 Secure settings may be required to configure Cloud snapshot repositories (Azure, GCS, S3) if you are not using Cloud-provider specific means to leverage Kubernetes service accounts
 (<<{p}-gke-workload-identiy,GKE Workload Identity>> or <<{p}-iam-service-accounts,AWS IAM roles for service accounts>>, for example).
diff --git a/docs/reference/api-docs.asciidoc b/docs/reference/api-docs.asciidoc
index 3e15a7209..907ff4faa 100644
--- a/docs/reference/api-docs.asciidoc
+++ b/docs/reference/api-docs.asciidoc
@@ -699,7 +699,9 @@ SecretSource defines a data source based on a Kubernetes Secret.
 - xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-agent-v1alpha1-agentspec[$$AgentSpec$$]
 - xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-apm-v1-apmserverspec[$$ApmServerSpec$$]
 - xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-beat-v1beta1-beatspec[$$BeatSpec$$]
+- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-stackconfigpolicy-v1alpha1-elasticsearchconfigpolicyspec[$$ElasticsearchConfigPolicySpec$$]
 - xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-elasticsearch-v1-elasticsearchspec[$$ElasticsearchSpec$$]
+- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-stackconfigpolicy-v1alpha1-kibanaconfigpolicyspec[$$KibanaConfigPolicySpec$$]
 - xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-kibana-v1-kibanaspec[$$KibanaSpec$$]
 - xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-logstash-v1alpha1-logstashspec[$$LogstashSpec$$]
 - xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-stackconfigpolicy-v1alpha1-stackconfigpolicyspec[$$StackConfigPolicySpec$$]
@@ -2079,6 +2081,7 @@ Package v1alpha1 contains API schema definitions for managing StackConfigPolicy
 | *`indexTemplates`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-stackconfigpolicy-v1alpha1-indextemplates[$$IndexTemplates$$]__ | IndexTemplates holds the Index and Component Templates settings
 | *`config`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-common-v1-config[$$Config$$]__ | Config holds the settings that go into elasticsearch.yml.
 | *`secretMounts`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-stackconfigpolicy-v1alpha1-secretmount[$$SecretMount$$] array__ | SecretMounts are additional Secrets that need to be mounted into the Elasticsearch pods.
+| *`secureSettings`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-common-v1-secretsource[$$SecretSource$$] array__ | SecureSettings are additional Secrets that contain data to be configured to Elasticsearch's keystore.
 |===
 
 
@@ -2114,7 +2117,7 @@ Package v1alpha1 contains API schema definitions for managing StackConfigPolicy
 |===
 | Field | Description
 | *`config`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-common-v1-config[$$Config$$]__ | Config holds the settings that go into kibana.yml.
-| *`secretMounts`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-stackconfigpolicy-v1alpha1-secretmount[$$SecretMount$$] array__ | SecretMounts are additional secrets that need to be mounted into the Kibana pods.
+| *`secureSettings`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-common-v1-secretsource[$$SecretSource$$] array__ | SecureSettings are additional Secrets that contain data to be configured to Kibana's keystore.
 |===
 
 
@@ -2128,7 +2131,6 @@ SecretMount contains information about additional secrets to be mounted to the e
 .Appears In:
 ****
 - xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-stackconfigpolicy-v1alpha1-elasticsearchconfigpolicyspec[$$ElasticsearchConfigPolicySpec$$]
-- xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-stackconfigpolicy-v1alpha1-kibanaconfigpolicyspec[$$KibanaConfigPolicySpec$$]
 ****
 
 [cols="25a,75a", options="header"]
@@ -2171,7 +2173,7 @@ StackConfigPolicy represents a StackConfigPolicy resource in a Kubernetes cluste
 |===
 | Field | Description
 | *`resourceSelector`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#labelselector-v1-meta[$$LabelSelector$$]__ | 
-| *`secureSettings`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-common-v1-secretsource[$$SecretSource$$] array__ | 
+| *`secureSettings`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-common-v1-secretsource[$$SecretSource$$] array__ | Deprecated: SecureSettings only applies to Elasticsearch and is deprecated. It must be set per application instead.
 | *`elasticsearch`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-stackconfigpolicy-v1alpha1-elasticsearchconfigpolicyspec[$$ElasticsearchConfigPolicySpec$$]__ | 
 | *`kibana`* __xref:{anchor_prefix}-github-com-elastic-cloud-on-k8s-v2-pkg-apis-stackconfigpolicy-v1alpha1-kibanaconfigpolicyspec[$$KibanaConfigPolicySpec$$]__ | 
 |===
diff --git a/docs/reference/dependencies.asciidoc b/docs/reference/dependencies.asciidoc
index 2af9f0898..d403d3467 100644
--- a/docs/reference/dependencies.asciidoc
+++ b/docs/reference/dependencies.asciidoc
@@ -30,7 +30,7 @@ This page lists the third-party dependencies used to build {n}.
 | link:https://github.com/gobuffalo/flect[$$github.com/gobuffalo/flect$$] | v1.0.2 | MIT
 | link:https://github.com/google/go-cmp[$$github.com/google/go-cmp$$] | v0.6.0 | BSD-3-Clause
 | link:https://github.com/google/go-containerregistry[$$github.com/google/go-containerregistry$$] | v0.17.0 | Apache-2.0
-| link:https://github.com/google/uuid[$$github.com/google/uuid$$] | v1.4.0 | BSD-3-Clause
+| link:https://github.com/google/uuid[$$github.com/google/uuid$$] | v1.5.0 | BSD-3-Clause
 | link:https://github.com/hashicorp/go-multierror[$$github.com/hashicorp/go-multierror$$] | v1.1.1 | MPL-2.0
 | link:https://github.com/hashicorp/golang-lru[$$github.com/hashicorp/golang-lru/v2$$] | v2.0.7 | MPL-2.0
 | link:https://github.com/hashicorp/vault[$$github.com/hashicorp/vault/api$$] | v1.10.0 | MPL-2.0
diff --git a/go.mod b/go.mod
index 69ee17e6e..a39169f1a 100644
--- a/go.mod
+++ b/go.mod
@@ -14,7 +14,7 @@ require (
 	github.com/gobuffalo/flect v1.0.2
 	github.com/google/go-cmp v0.6.0
 	github.com/google/go-containerregistry v0.17.0
-	github.com/google/uuid v1.4.0
+	github.com/google/uuid v1.5.0
 	github.com/hashicorp/go-multierror v1.1.1
 	github.com/hashicorp/golang-lru/v2 v2.0.7
 	github.com/hashicorp/vault/api v1.10.0
diff --git a/go.sum b/go.sum
index 3abd41f5e..2f49072ce 100644
--- a/go.sum
+++ b/go.sum
@@ -204,8 +204,8 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJY
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
-github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
+github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
diff --git a/hack/deployer/clients/ocp/Dockerfile b/hack/deployer/clients/ocp/Dockerfile
index 613fafc22..42eade2f4 100644
--- a/hack/deployer/clients/ocp/Dockerfile
+++ b/hack/deployer/clients/ocp/Dockerfile
@@ -12,6 +12,6 @@ RUN curl -fsSLO https://mirror.openshift.com/pub/openshift-v4/clients/ocp/${CLIE
     mv oc /usr/local/bin/oc && \
     rm openshift-client-linux-${CLIENT_VERSION}.tar.gz
 
-FROM registry.access.redhat.com/ubi9/ubi-minimal:9.3-1361.1699548032
+FROM registry.access.redhat.com/ubi9/ubi-minimal:9.3-1475
 COPY --from=builder /usr/local/bin/openshift-install .
 COPY --from=builder /usr/local/bin/oc .
diff --git a/hack/helm/release/go.mod b/hack/helm/release/go.mod
index 275f34cde..13e6e6447 100644
--- a/hack/helm/release/go.mod
+++ b/hack/helm/release/go.mod
@@ -3,14 +3,14 @@ module github.com/elastic/cloud-on-k8s/hack/helm/release
 go 1.19
 
 require (
-	cloud.google.com/go/storage v1.35.1
+	cloud.google.com/go/storage v1.36.0
 	github.com/elastic/cloud-on-k8s/v2 v2.10.0
 	github.com/google/go-cmp v0.6.0
 	github.com/spf13/cobra v1.8.0
 	github.com/spf13/viper v1.17.0
-	google.golang.org/api v0.153.0
+	google.golang.org/api v0.154.0
 	gopkg.in/yaml.v3 v3.0.1
-	helm.sh/helm/v3 v3.13.2
+	helm.sh/helm/v3 v3.13.3
 )
 
 replace github.com/elastic/cloud-on-k8s/v2 => ../../..
@@ -20,7 +20,7 @@ replace github.com/elastic/cloud-on-k8s/v2 => ../../..
 exclude github.com/docker/docker v24.0.0+incompatible
 
 require (
-	cloud.google.com/go v0.110.10 // indirect
+	cloud.google.com/go v0.111.0 // indirect
 	cloud.google.com/go/compute v1.23.3 // indirect
 	cloud.google.com/go/compute/metadata v0.2.3 // indirect
 	cloud.google.com/go/iam v1.1.5 // indirect
@@ -52,6 +52,7 @@ require (
 	github.com/evanphx/json-patch v5.6.0+incompatible // indirect
 	github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
 	github.com/fatih/color v1.15.0 // indirect
+	github.com/felixge/httpsnoop v1.0.4 // indirect
 	github.com/fsnotify/fsnotify v1.6.0 // indirect
 	github.com/go-errors/errors v1.4.2 // indirect
 	github.com/go-gorp/gorp/v3 v3.1.0 // indirect
@@ -70,7 +71,7 @@ require (
 	github.com/google/gofuzz v1.2.0 // indirect
 	github.com/google/s2a-go v0.1.7 // indirect
 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
-	github.com/google/uuid v1.4.0 // indirect
+	github.com/google/uuid v1.5.0 // indirect
 	github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
 	github.com/googleapis/gax-go/v2 v2.12.0 // indirect
 	github.com/gorilla/mux v1.8.0 // indirect
@@ -142,39 +143,42 @@ require (
 	github.com/xeipuuv/gojsonschema v1.2.0 // indirect
 	github.com/xlab/treeprint v1.2.0 // indirect
 	go.opencensus.io v0.24.0 // indirect
-	go.opentelemetry.io/otel v1.14.0 // indirect
-	go.opentelemetry.io/otel/trace v1.14.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect
+	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
+	go.opentelemetry.io/otel v1.21.0 // indirect
+	go.opentelemetry.io/otel/metric v1.21.0 // indirect
+	go.opentelemetry.io/otel/trace v1.21.0 // indirect
 	go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	golang.org/x/crypto v0.16.0 // indirect
 	golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
-	golang.org/x/net v0.18.0 // indirect
-	golang.org/x/oauth2 v0.14.0 // indirect
+	golang.org/x/net v0.19.0 // indirect
+	golang.org/x/oauth2 v0.15.0 // indirect
 	golang.org/x/sync v0.5.0 // indirect
 	golang.org/x/sys v0.15.0 // indirect
 	golang.org/x/term v0.15.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
 	golang.org/x/time v0.5.0 // indirect
-	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
-	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
-	google.golang.org/grpc v1.59.0 // indirect
+	golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
+	google.golang.org/appengine v1.6.8 // indirect
+	google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 // indirect
+	google.golang.org/grpc v1.60.0 // indirect
 	google.golang.org/protobuf v1.31.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	k8s.io/api v0.28.4 // indirect
-	k8s.io/apiextensions-apiserver v0.28.3 // indirect
+	k8s.io/apiextensions-apiserver v0.28.4 // indirect
 	k8s.io/apimachinery v0.28.4 // indirect
-	k8s.io/apiserver v0.28.3 // indirect
-	k8s.io/cli-runtime v0.28.2 // indirect
+	k8s.io/apiserver v0.28.4 // indirect
+	k8s.io/cli-runtime v0.28.4 // indirect
 	k8s.io/client-go v0.28.4 // indirect
-	k8s.io/component-base v0.28.3 // indirect
+	k8s.io/component-base v0.28.4 // indirect
 	k8s.io/klog/v2 v2.110.1 // indirect
 	k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
-	k8s.io/kubectl v0.28.2 // indirect
+	k8s.io/kubectl v0.28.4 // indirect
 	k8s.io/utils v0.0.0-20230505201702-9f6742963106 // indirect
 	oras.land/oras-go v1.2.4 // indirect
 	sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
diff --git a/hack/helm/release/go.sum b/hack/helm/release/go.sum
index 109f6fde7..c021156b7 100644
--- a/hack/helm/release/go.sum
+++ b/hack/helm/release/go.sum
@@ -17,26 +17,20 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb
 cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
 cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
 cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
-cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME=
-cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk=
-cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y=
-cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic=
+cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM=
+cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
 cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
 cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
 cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
-cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0=
-cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78=
 cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
 cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
 cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
 cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
-cloud.google.com/go/iam v1.1.3 h1:18tKG7DzydKWUnLjonWcJO6wjSCAtzh4GcRKlH/Hrzc=
-cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE=
 cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI=
 cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
@@ -49,8 +43,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
-cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w=
-cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
+cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8=
+cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
@@ -105,6 +99,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
 github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
 github.com/containerd/containerd v1.7.6 h1:oNAVsnhPoy4BTPQivLgTzI9Oleml9l/+eYIDYXRCYo8=
 github.com/containerd/containerd v1.7.6/go.mod h1:SY6lrkkuJT40BVNO37tlYTSnKJnP5AXBc0fhx0q+TJ4=
@@ -143,6 +138,7 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
 github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA=
 github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
 github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
@@ -150,7 +146,8 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZM
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
 github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
-github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI=
 github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
 github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
@@ -219,6 +216,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
 github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k=
@@ -267,8 +265,8 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
-github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
+github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
 github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
@@ -530,10 +528,17 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
 go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
 go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
-go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM=
-go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU=
-go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M=
-go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
+go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
+go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
+go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
+go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
+go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
+go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
+go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
 go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY=
 go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
 go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -625,10 +630,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
-golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
-golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
-golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
-golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
+golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
+golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -638,10 +641,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY=
-golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0=
-golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0=
-golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM=
+golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
+golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -717,14 +718,13 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
-golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
 golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -782,8 +782,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
-golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
+golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -803,20 +803,17 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513
 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
 google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
 google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
-google.golang.org/api v0.151.0 h1:FhfXLO/NFdJIzQtCqjpysWwqKk8AzGWBUhMIx67cVDU=
-google.golang.org/api v0.151.0/go.mod h1:ccy+MJ6nrYFgE3WgRx/AMXOxOmU8Q4hSa+jjibzhxcg=
-google.golang.org/api v0.152.0 h1:t0r1vPnfMc260S2Ci+en7kfCZaLOPs5KI0sVV/6jZrY=
-google.golang.org/api v0.152.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY=
-google.golang.org/api v0.153.0 h1:N1AwGhielyKFaUqH07/ZSIQR3uNPcV7NVw0vj+j4iR4=
-google.golang.org/api v0.153.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY=
+google.golang.org/api v0.154.0 h1:X7QkVKZBskztmpPKWQXgjJRPA2dJYrL6r+sYPRLj050=
+google.golang.org/api v0.154.0/go.mod h1:qhSMkM85hgqiokIYsrRyKxrjfBeIhgl4Z2JmeRkYylc=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
+google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -853,18 +850,12 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D
 google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA=
-google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI=
-google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
-google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY=
-google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k=
-google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870=
-google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo=
-google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc=
+google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos=
+google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY=
+google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 h1:s1w3X6gQxwrLEpxnLd/qXTVLgQE2yXwaOaoa6IlY/+o=
+google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go.mod h1:CAny0tYF+0/9rmDB9fahA9YLzX3+AEVl1qXbv5hhj6c=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 h1:/jFB8jK5R3Sq3i/lmeZO0cATSzFfZaJq1J2Euan3XKU=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -881,8 +872,8 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
 google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
 google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
-google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
+google.golang.org/grpc v1.60.0 h1:6FQAR0kM31P6MRdeluor2w2gPaS4SVNrD/DNTxrQ15k=
+google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -917,8 +908,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
-helm.sh/helm/v3 v3.13.2 h1:IcO9NgmmpetJODLZhR3f3q+6zzyXVKlRizKFwbi7K8w=
-helm.sh/helm/v3 v3.13.2/go.mod h1:GIHDwZggaTGbedevTlrQ6DB++LBN6yuQdeGj0HNaDx0=
+helm.sh/helm/v3 v3.13.3 h1:0zPEdGqHcubehJHP9emCtzRmu8oYsJFRrlVF3TFj8xY=
+helm.sh/helm/v3 v3.13.3/go.mod h1:3OKO33yI3p4YEXtTITN2+4oScsHeQe71KuzhlZ+aPfg=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -928,24 +919,24 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY=
 k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0=
-k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08=
-k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc=
+k8s.io/apiextensions-apiserver v0.28.4 h1:AZpKY/7wQ8n+ZYDtNHbAJBb+N4AXXJvyZx6ww6yAJvU=
+k8s.io/apiextensions-apiserver v0.28.4/go.mod h1:pgQIZ1U8eJSMQcENew/0ShUTlePcSGFq6dxSxf2mwPM=
 k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8=
 k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg=
-k8s.io/apiserver v0.28.3 h1:8Ov47O1cMyeDzTXz0rwcfIIGAP/dP7L8rWbEljRcg5w=
-k8s.io/apiserver v0.28.3/go.mod h1:YIpM+9wngNAv8Ctt0rHG4vQuX/I5rvkEMtZtsxW2rNM=
-k8s.io/cli-runtime v0.28.2 h1:64meB2fDj10/ThIMEJLO29a1oujSm0GQmKzh1RtA/uk=
-k8s.io/cli-runtime v0.28.2/go.mod h1:bTpGOvpdsPtDKoyfG4EG041WIyFZLV9qq4rPlkyYfDA=
+k8s.io/apiserver v0.28.4 h1:BJXlaQbAU/RXYX2lRz+E1oPe3G3TKlozMMCZWu5GMgg=
+k8s.io/apiserver v0.28.4/go.mod h1:Idq71oXugKZoVGUUL2wgBCTHbUR+FYTWa4rq9j4n23w=
+k8s.io/cli-runtime v0.28.4 h1:IW3aqSNFXiGDllJF4KVYM90YX4cXPGxuCxCVqCD8X+Q=
+k8s.io/cli-runtime v0.28.4/go.mod h1:MLGRB7LWTIYyYR3d/DOgtUC8ihsAPA3P8K8FDNIqJ0k=
 k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY=
 k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4=
-k8s.io/component-base v0.28.3 h1:rDy68eHKxq/80RiMb2Ld/tbH8uAE75JdCqJyi6lXMzI=
-k8s.io/component-base v0.28.3/go.mod h1:fDJ6vpVNSk6cRo5wmDa6eKIG7UlIQkaFmZN2fYgIUD8=
+k8s.io/component-base v0.28.4 h1:c/iQLWPdUgI90O+T9TeECg8o7N3YJTiuz2sKxILYcYo=
+k8s.io/component-base v0.28.4/go.mod h1:m9hR0uvqXDybiGL2nf/3Lf0MerAfQXzkfWhUY58JUbU=
 k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=
 k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=
 k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ=
 k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM=
-k8s.io/kubectl v0.28.2 h1:fOWOtU6S0smdNjG1PB9WFbqEIMlkzU5ahyHkc7ESHgM=
-k8s.io/kubectl v0.28.2/go.mod h1:6EQWTPySF1fn7yKoQZHYf9TPwIl2AygHEcJoxFekr64=
+k8s.io/kubectl v0.28.4 h1:gWpUXW/T7aFne+rchYeHkyB8eVDl5UZce8G4X//kjUQ=
+k8s.io/kubectl v0.28.4/go.mod h1:CKOccVx3l+3MmDbkXtIUtibq93nN2hkDR99XDCn7c/c=
 k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/JIBVY9i+M4wpAU=
 k8s.io/utils v0.0.0-20230505201702-9f6742963106/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
 oras.land/oras-go v1.2.4 h1:djpBY2/2Cs1PV87GSJlxv4voajVOMZxqqtq9AB8YNvY=
diff --git a/hack/manifest-gen/go.mod b/hack/manifest-gen/go.mod
index 6b3d78063..e4ba7e427 100644
--- a/hack/manifest-gen/go.mod
+++ b/hack/manifest-gen/go.mod
@@ -4,7 +4,7 @@ go 1.19
 
 require (
 	github.com/spf13/cobra v1.8.0
-	helm.sh/helm/v3 v3.13.2
+	helm.sh/helm/v3 v3.13.3
 	sigs.k8s.io/kustomize/kyaml v0.16.0
 )
 
@@ -116,20 +116,20 @@ require (
 	google.golang.org/appengine v1.6.7 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect
 	google.golang.org/grpc v1.56.3 // indirect
-	google.golang.org/protobuf v1.30.0 // indirect
+	google.golang.org/protobuf v1.31.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
-	k8s.io/api v0.28.2 // indirect
-	k8s.io/apiextensions-apiserver v0.28.2 // indirect
-	k8s.io/apimachinery v0.28.2 // indirect
-	k8s.io/apiserver v0.28.2 // indirect
-	k8s.io/cli-runtime v0.28.2 // indirect
-	k8s.io/client-go v0.28.2 // indirect
-	k8s.io/component-base v0.28.2 // indirect
+	k8s.io/api v0.28.4 // indirect
+	k8s.io/apiextensions-apiserver v0.28.4 // indirect
+	k8s.io/apimachinery v0.28.4 // indirect
+	k8s.io/apiserver v0.28.4 // indirect
+	k8s.io/cli-runtime v0.28.4 // indirect
+	k8s.io/client-go v0.28.4 // indirect
+	k8s.io/component-base v0.28.4 // indirect
 	k8s.io/klog/v2 v2.100.1 // indirect
 	k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
-	k8s.io/kubectl v0.28.2 // indirect
+	k8s.io/kubectl v0.28.4 // indirect
 	k8s.io/utils v0.0.0-20230505201702-9f6742963106 // indirect
 	oras.land/oras-go v1.2.4 // indirect
 	sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
diff --git a/hack/manifest-gen/go.sum b/hack/manifest-gen/go.sum
index 462acec9d..4f6bae0de 100644
--- a/hack/manifest-gen/go.sum
+++ b/hack/manifest-gen/go.sum
@@ -463,8 +463,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
-google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -482,30 +482,30 @@ gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
-helm.sh/helm/v3 v3.13.2 h1:IcO9NgmmpetJODLZhR3f3q+6zzyXVKlRizKFwbi7K8w=
-helm.sh/helm/v3 v3.13.2/go.mod h1:GIHDwZggaTGbedevTlrQ6DB++LBN6yuQdeGj0HNaDx0=
+helm.sh/helm/v3 v3.13.3 h1:0zPEdGqHcubehJHP9emCtzRmu8oYsJFRrlVF3TFj8xY=
+helm.sh/helm/v3 v3.13.3/go.mod h1:3OKO33yI3p4YEXtTITN2+4oScsHeQe71KuzhlZ+aPfg=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-k8s.io/api v0.28.2 h1:9mpl5mOb6vXZvqbQmankOfPIGiudghwCoLl1EYfUZbw=
-k8s.io/api v0.28.2/go.mod h1:RVnJBsjU8tcMq7C3iaRSGMeaKt2TWEUXcpIt/90fjEg=
-k8s.io/apiextensions-apiserver v0.28.2 h1:J6/QRWIKV2/HwBhHRVITMLYoypCoPY1ftigDM0Kn+QU=
-k8s.io/apiextensions-apiserver v0.28.2/go.mod h1:5tnkxLGa9nefefYzWuAlWZ7RZYuN/765Au8cWLA6SRg=
-k8s.io/apimachinery v0.28.2 h1:KCOJLrc6gu+wV1BYgwik4AF4vXOlVJPdiqn0yAWWwXQ=
-k8s.io/apimachinery v0.28.2/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU=
-k8s.io/apiserver v0.28.2 h1:rBeYkLvF94Nku9XfXyUIirsVzCzJBs6jMn3NWeHieyI=
-k8s.io/apiserver v0.28.2/go.mod h1:f7D5e8wH8MWcKD7azq6Csw9UN+CjdtXIVQUyUhrtb+E=
-k8s.io/cli-runtime v0.28.2 h1:64meB2fDj10/ThIMEJLO29a1oujSm0GQmKzh1RtA/uk=
-k8s.io/cli-runtime v0.28.2/go.mod h1:bTpGOvpdsPtDKoyfG4EG041WIyFZLV9qq4rPlkyYfDA=
-k8s.io/client-go v0.28.2 h1:DNoYI1vGq0slMBN/SWKMZMw0Rq+0EQW6/AK4v9+3VeY=
-k8s.io/client-go v0.28.2/go.mod h1:sMkApowspLuc7omj1FOSUxSoqjr+d5Q0Yc0LOFnYFJY=
-k8s.io/component-base v0.28.2 h1:Yc1yU+6AQSlpJZyvehm/NkJBII72rzlEsd6MkBQ+G0E=
-k8s.io/component-base v0.28.2/go.mod h1:4IuQPQviQCg3du4si8GpMrhAIegxpsgPngPRR/zWpzc=
+k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY=
+k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0=
+k8s.io/apiextensions-apiserver v0.28.4 h1:AZpKY/7wQ8n+ZYDtNHbAJBb+N4AXXJvyZx6ww6yAJvU=
+k8s.io/apiextensions-apiserver v0.28.4/go.mod h1:pgQIZ1U8eJSMQcENew/0ShUTlePcSGFq6dxSxf2mwPM=
+k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8=
+k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg=
+k8s.io/apiserver v0.28.4 h1:BJXlaQbAU/RXYX2lRz+E1oPe3G3TKlozMMCZWu5GMgg=
+k8s.io/apiserver v0.28.4/go.mod h1:Idq71oXugKZoVGUUL2wgBCTHbUR+FYTWa4rq9j4n23w=
+k8s.io/cli-runtime v0.28.4 h1:IW3aqSNFXiGDllJF4KVYM90YX4cXPGxuCxCVqCD8X+Q=
+k8s.io/cli-runtime v0.28.4/go.mod h1:MLGRB7LWTIYyYR3d/DOgtUC8ihsAPA3P8K8FDNIqJ0k=
+k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY=
+k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4=
+k8s.io/component-base v0.28.4 h1:c/iQLWPdUgI90O+T9TeECg8o7N3YJTiuz2sKxILYcYo=
+k8s.io/component-base v0.28.4/go.mod h1:m9hR0uvqXDybiGL2nf/3Lf0MerAfQXzkfWhUY58JUbU=
 k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
 k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
 k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ=
 k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM=
-k8s.io/kubectl v0.28.2 h1:fOWOtU6S0smdNjG1PB9WFbqEIMlkzU5ahyHkc7ESHgM=
-k8s.io/kubectl v0.28.2/go.mod h1:6EQWTPySF1fn7yKoQZHYf9TPwIl2AygHEcJoxFekr64=
+k8s.io/kubectl v0.28.4 h1:gWpUXW/T7aFne+rchYeHkyB8eVDl5UZce8G4X//kjUQ=
+k8s.io/kubectl v0.28.4/go.mod h1:CKOccVx3l+3MmDbkXtIUtibq93nN2hkDR99XDCn7c/c=
 k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/JIBVY9i+M4wpAU=
 k8s.io/utils v0.0.0-20230505201702-9f6742963106/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
 oras.land/oras-go v1.2.4 h1:djpBY2/2Cs1PV87GSJlxv4voajVOMZxqqtq9AB8YNvY=
@@ -514,13 +514,9 @@ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMm
 sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
 sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0=
 sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY=
-sigs.k8s.io/kustomize/kyaml v0.15.0 h1:ynlLMAxDhrY9otSg5GYE2TcIz31XkGZ2Pkj7SdolD84=
-sigs.k8s.io/kustomize/kyaml v0.15.0/go.mod h1:+uMkBahdU1KNOj78Uta4rrXH+iH7wvg+nW7+GULvREA=
 sigs.k8s.io/kustomize/kyaml v0.16.0 h1:6J33uKSoATlKZH16unr2XOhDI+otoe2sR3M8PDzW3K0=
 sigs.k8s.io/kustomize/kyaml v0.16.0/go.mod h1:xOK/7i+vmE14N2FdFyugIshB8eF6ALpy7jI87Q2nRh4=
 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
 sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
-sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
-sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
 sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
 sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
diff --git a/pkg/apis/stackconfigpolicy/v1alpha1/stackconfigpolicy_types.go b/pkg/apis/stackconfigpolicy/v1alpha1/stackconfigpolicy_types.go
index 4f660f7c6..1a9ef3bf4 100644
--- a/pkg/apis/stackconfigpolicy/v1alpha1/stackconfigpolicy_types.go
+++ b/pkg/apis/stackconfigpolicy/v1alpha1/stackconfigpolicy_types.go
@@ -11,6 +11,8 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 
 	commonv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/common/v1"
+	eslabel "github.com/elastic/cloud-on-k8s/v2/pkg/controller/elasticsearch/label"
+	kblabel "github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/label"
 )
 
 const (
@@ -52,10 +54,11 @@ type StackConfigPolicyList struct {
 }
 
 type StackConfigPolicySpec struct {
-	ResourceSelector metav1.LabelSelector          `json:"resourceSelector,omitempty"`
-	SecureSettings   []commonv1.SecretSource       `json:"secureSettings,omitempty"`
-	Elasticsearch    ElasticsearchConfigPolicySpec `json:"elasticsearch,omitempty"`
-	Kibana           KibanaConfigPolicySpec        `json:"kibana,omitempty"`
+	ResourceSelector metav1.LabelSelector `json:"resourceSelector,omitempty"`
+	// Deprecated: SecureSettings only applies to Elasticsearch and is deprecated. It must be set per application instead.
+	SecureSettings []commonv1.SecretSource       `json:"secureSettings,omitempty"`
+	Elasticsearch  ElasticsearchConfigPolicySpec `json:"elasticsearch,omitempty"`
+	Kibana         KibanaConfigPolicySpec        `json:"kibana,omitempty"`
 }
 
 type ElasticsearchConfigPolicySpec struct {
@@ -86,17 +89,27 @@ type ElasticsearchConfigPolicySpec struct {
 	// SecretMounts are additional Secrets that need to be mounted into the Elasticsearch pods.
 	// +kubebuilder:pruning:PreserveUnknownFields
 	SecretMounts []SecretMount `json:"secretMounts,omitempty"`
+	// SecureSettings are additional Secrets that contain data to be configured to Elasticsearch's keystore.
+	// +kubebuilder:pruning:PreserveUnknownFields
+	SecureSettings []commonv1.SecretSource `json:"secureSettings,omitempty"`
 }
 
 type KibanaConfigPolicySpec struct {
 	// Config holds the settings that go into kibana.yml.
 	// +kubebuilder:pruning:PreserveUnknownFields
 	Config *commonv1.Config `json:"config,omitempty"`
-	// SecretMounts are additional secrets that need to be mounted into the Kibana pods.
+	// SecureSettings are additional Secrets that contain data to be configured to Kibana's keystore.
 	// +kubebuilder:pruning:PreserveUnknownFields
-	SecretMounts []SecretMount `json:"secretMounts,omitempty"`
+	SecureSettings []commonv1.SecretSource `json:"secureSettings,omitempty"`
 }
 
+type ResourceType string
+
+const (
+	ElasticsearchResourceType ResourceType = eslabel.Type
+	KibanaResourceType        ResourceType = kblabel.Type
+)
+
 type IndexTemplates struct {
 	// ComponentTemplates holds the Component Templates settings (/_component_template)
 	// +kubebuilder:pruning:PreserveUnknownFields
@@ -108,7 +121,7 @@ type IndexTemplates struct {
 
 type StackConfigPolicyStatus struct {
 	// ResourcesStatuses holds the status for each resource to be configured.
-	ResourcesStatuses map[string]ResourcePolicyStatus `json:"resourcesStatuses"`
+	ResourcesStatuses map[ResourceType]map[string]ResourcePolicyStatus `json:"resourcesStatuses"`
 	// Resources is the number of resources to be configured.
 	Resources int `json:"resources,omitempty"`
 	// Ready is the number of resources successfully configured.
@@ -147,8 +160,12 @@ var phaseOrder = map[PolicyPhase]int{
 
 // ResourcePolicyStatus models the status of the policy for one resource to be configured.
 type ResourcePolicyStatus struct {
-	Phase           PolicyPhase       `json:"phase,omitempty"`
-	CurrentVersion  int64             `json:"currentVersion,omitempty"`
+	Phase PolicyPhase `json:"phase,omitempty"`
+	// CurrentVersion denotes the current version of filesettings applied to the Elasticsearch cluster
+	// This field does not apply to Kibana resources
+	CurrentVersion int64 `json:"currentVersion,omitempty"`
+	// ExpectedVersion denotes the expected version of filesettings that should be applied to the Elasticsearch cluster
+	// This field does not apply to Kibana resources
 	ExpectedVersion int64             `json:"expectedVersion,omitempty"`
 	Error           PolicyStatusError `json:"error,omitempty"`
 }
@@ -168,7 +185,7 @@ type SecretMount struct {
 
 func NewStatus(scp StackConfigPolicy) StackConfigPolicyStatus {
 	status := StackConfigPolicyStatus{
-		ResourcesStatuses:  map[string]ResourcePolicyStatus{},
+		ResourcesStatuses:  map[ResourceType]map[string]ResourcePolicyStatus{},
 		Phase:              ReadyPhase,
 		ObservedGeneration: scp.Generation,
 	}
@@ -180,66 +197,96 @@ func (s *StackConfigPolicyStatus) setReadyCount() {
 	s.ReadyCount = fmt.Sprintf("%d/%d", s.Ready, s.Resources)
 }
 
-func (s *StackConfigPolicyStatus) AddPolicyErrorFor(resource types.NamespacedName, phase PolicyPhase, msg string) error {
-	if _, ok := s.ResourcesStatuses[resource.String()]; ok {
+// AddPolicyErrorFor adds given error message to status of a resource. Only one error can be reported per resource
+func (s *StackConfigPolicyStatus) AddPolicyErrorFor(resource types.NamespacedName, phase PolicyPhase, msg string, resourceType ResourceType) error {
+	resourceStatusKey := s.getResourceStatusKey(resource)
+	if s.ResourcesStatuses[resourceType] == nil {
+		s.ResourcesStatuses[resourceType] = make(map[string]ResourcePolicyStatus)
+	}
+	if status, exists := s.ResourcesStatuses[resourceType][resourceStatusKey]; exists && status.Error.Message != "" {
 		return fmt.Errorf("policy error already exists for resource %q", resource)
 	}
-	s.ResourcesStatuses[resource.String()] = ResourcePolicyStatus{
+	resourcePolicyStatus := ResourcePolicyStatus{
 		Phase: phase,
 		Error: PolicyStatusError{Message: msg},
 	}
+	s.ResourcesStatuses[resourceType][resourceStatusKey] = resourcePolicyStatus
 	s.Update()
 	return nil
 }
 
-func (s *StackConfigPolicyStatus) UpdateResourceStatusPhase(resource types.NamespacedName, status ResourcePolicyStatus, elasticsearchConfigAndMountsApplied bool) {
+func (s *StackConfigPolicyStatus) UpdateResourceStatusPhase(resource types.NamespacedName, status ResourcePolicyStatus, applicationConfigsApplied bool, resourceType ResourceType) error {
 	defer func() {
-		s.ResourcesStatuses[resource.String()] = status
+		if s.ResourcesStatuses[resourceType] == nil {
+			s.ResourcesStatuses[resourceType] = make(map[string]ResourcePolicyStatus)
+		}
+		s.ResourcesStatuses[resourceType][s.getResourceStatusKey(resource)] = status
 		s.Update()
 	}()
+	switch resourceType {
+	case ElasticsearchResourceType:
+		if !applicationConfigsApplied {
+			// New ElasticsearchConfig and Additional secrets not yet applied to the Elasticsearch pod
+			status.Phase = ApplyingChangesPhase
+			return nil
+		}
 
-	if !elasticsearchConfigAndMountsApplied {
-		// New ElasticsearchConfig and Additional secrets not yet applied to the Elasticsearch pod
-		status.Phase = ApplyingChangesPhase
-		return
-	}
+		if status.CurrentVersion == unknownVersion {
+			status.Phase = UnknownPhase
+			return nil
+		}
 
-	if status.CurrentVersion == unknownVersion {
-		status.Phase = UnknownPhase
-		return
-	}
+		if status.Error.Message != "" {
+			status.Phase = ErrorPhase
+			if status.ExpectedVersion > status.Error.Version {
+				status.Phase = ApplyingChangesPhase
+			}
+			return nil
+		}
 
-	if status.Error.Message != "" {
-		status.Phase = ErrorPhase
-		if status.ExpectedVersion > status.Error.Version {
+		if status.CurrentVersion == status.ExpectedVersion {
+			status.Phase = ReadyPhase
+			return nil
+		}
+		status.Phase = ApplyingChangesPhase
+	case KibanaResourceType:
+		if !applicationConfigsApplied {
+			// New KibanaConfig not yet applied to the Elasticsearch instance
 			status.Phase = ApplyingChangesPhase
+			return nil
+		}
+		if status.Error.Message != "" {
+			status.Phase = ErrorPhase
+			return nil
 		}
-		return
-	}
-
-	if status.CurrentVersion == status.ExpectedVersion {
 		status.Phase = ReadyPhase
-		return
+		return nil
+	default:
+		return fmt.Errorf("unknown resource type %s", resourceType)
 	}
-
-	status.Phase = ApplyingChangesPhase
+	return nil
 }
 
 // Update updates the policy status from its resources statuses.
 func (s *StackConfigPolicyStatus) Update() {
-	s.Resources = len(s.ResourcesStatuses)
+	s.Resources = 0
 	s.Ready = 0
 	s.Errors = 0
-	for _, status := range s.ResourcesStatuses {
-		resourcePhase := status.Phase
-		if resourcePhase == ReadyPhase {
-			s.Ready++
-		} else if resourcePhase == ErrorPhase {
-			s.Errors++
-		}
-		// update phase if that of the resource status is worse
-		if phaseOrder[resourcePhase] > phaseOrder[s.Phase] {
-			s.Phase = resourcePhase
+	for _, resourceStatusMap := range s.ResourcesStatuses {
+		for _, status := range resourceStatusMap {
+			s.Resources++
+			// Resource status can be for Kibana or Elasticsearch resources
+			resourcePhase := status.Phase
+
+			if resourcePhase == ReadyPhase {
+				s.Ready++
+			} else if resourcePhase == ErrorPhase {
+				s.Errors++
+			}
+			// update phase if that of the resource status is worse
+			if phaseOrder[resourcePhase] > phaseOrder[s.Phase] {
+				s.Phase = resourcePhase
+			}
 		}
 	}
 	s.setReadyCount()
@@ -254,3 +301,7 @@ func (s StackConfigPolicyStatus) IsDegraded(prev StackConfigPolicyStatus) bool {
 func (p *StackConfigPolicy) IsMarkedForDeletion() bool {
 	return !p.DeletionTimestamp.IsZero()
 }
+
+func (s StackConfigPolicyStatus) getResourceStatusKey(nsn types.NamespacedName) string {
+	return nsn.String()
+}
diff --git a/pkg/apis/stackconfigpolicy/v1alpha1/webhook.go b/pkg/apis/stackconfigpolicy/v1alpha1/webhook.go
index e71736c69..9fd610aa7 100644
--- a/pkg/apis/stackconfigpolicy/v1alpha1/webhook.go
+++ b/pkg/apis/stackconfigpolicy/v1alpha1/webhook.go
@@ -5,6 +5,8 @@
 package v1alpha1
 
 import (
+	"fmt"
+
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
@@ -18,7 +20,8 @@ import (
 
 const (
 	// webhookPath is the HTTP path for the StackConfigPolicy validating webhook.
-	webhookPath = "/validate-scp-k8s-elastic-co-v1alpha1-stackconfigpolicies"
+	webhookPath                  = "/validate-scp-k8s-elastic-co-v1alpha1-stackconfigpolicies"
+	SpecSecureSettingsDeprecated = "spec.SecureSettings is deprecated, secure settings must be set per application"
 )
 
 var (
@@ -78,6 +81,16 @@ func (p *StackConfigPolicy) validate() (admission.Warnings, error) {
 	return nil, nil
 }
 
+func (p *StackConfigPolicy) GetWarnings() []string {
+	if p == nil {
+		return nil
+	}
+	if len(p.Spec.SecureSettings) > 0 {
+		return []string{fmt.Sprintf("%s %s/%s: %s", Kind, p.Namespace, p.Name, SpecSecureSettingsDeprecated)}
+	}
+	return nil
+}
+
 func checkNoUnknownFields(policy *StackConfigPolicy) field.ErrorList {
 	return commonv1.NoUnknownFields(policy, policy.ObjectMeta)
 }
@@ -122,8 +135,11 @@ func validSettings(policy *StackConfigPolicy) field.ErrorList {
 	if !uniqueSecretMountPaths(policy.Spec.Elasticsearch.SecretMounts) {
 		return field.ErrorList{field.Invalid(field.NewPath("spec").Child("elasticsearch").Child("secretMounts"), policy.Spec.Elasticsearch.SecretMounts, "SecretMounts cannot have duplicate mount paths")}
 	}
+	if policy.Spec.Kibana.Config != nil {
+		settingsCount += len(policy.Spec.Kibana.Config.Data)
+	}
 	if settingsCount == 0 {
-		return field.ErrorList{field.Required(field.NewPath("spec").Child("elasticsearch"), "Elasticsearch settings are mandatory and must not be empty")}
+		return field.ErrorList{field.Required(field.NewPath("spec").Child("elasticsearch"), "One out of Elasticsearch or Kibana settings is mandatory, both must not be empty")}
 	}
 	return nil
 }
diff --git a/pkg/apis/stackconfigpolicy/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/stackconfigpolicy/v1alpha1/zz_generated.deepcopy.go
index c1c92d893..71a092633 100644
--- a/pkg/apis/stackconfigpolicy/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/stackconfigpolicy/v1alpha1/zz_generated.deepcopy.go
@@ -50,6 +50,13 @@ func (in *ElasticsearchConfigPolicySpec) DeepCopyInto(out *ElasticsearchConfigPo
 		*out = make([]SecretMount, len(*in))
 		copy(*out, *in)
 	}
+	if in.SecureSettings != nil {
+		in, out := &in.SecureSettings, &out.SecureSettings
+		*out = make([]v1.SecretSource, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ElasticsearchConfigPolicySpec.
@@ -92,10 +99,12 @@ func (in *KibanaConfigPolicySpec) DeepCopyInto(out *KibanaConfigPolicySpec) {
 		in, out := &in.Config, &out.Config
 		*out = (*in).DeepCopy()
 	}
-	if in.SecretMounts != nil {
-		in, out := &in.SecretMounts, &out.SecretMounts
-		*out = make([]SecretMount, len(*in))
-		copy(*out, *in)
+	if in.SecureSettings != nil {
+		in, out := &in.SecureSettings, &out.SecureSettings
+		*out = make([]v1.SecretSource, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
 	}
 }
 
@@ -244,9 +253,20 @@ func (in *StackConfigPolicyStatus) DeepCopyInto(out *StackConfigPolicyStatus) {
 	*out = *in
 	if in.ResourcesStatuses != nil {
 		in, out := &in.ResourcesStatuses, &out.ResourcesStatuses
-		*out = make(map[string]ResourcePolicyStatus, len(*in))
+		*out = make(map[ResourceType]map[string]ResourcePolicyStatus, len(*in))
 		for key, val := range *in {
-			(*out)[key] = val
+			var outVal map[string]ResourcePolicyStatus
+			if val == nil {
+				(*out)[key] = nil
+			} else {
+				inVal := (*in)[key]
+				in, out := &inVal, &outVal
+				*out = make(map[string]ResourcePolicyStatus, len(*in))
+				for key, val := range *in {
+					(*out)[key] = val
+				}
+			}
+			(*out)[key] = outVal
 		}
 	}
 }
diff --git a/pkg/controller/association/controller/agent_kibana.go b/pkg/controller/association/controller/agent_kibana.go
index 841912051..94c5bede2 100644
--- a/pkg/controller/association/controller/agent_kibana.go
+++ b/pkg/controller/association/controller/agent_kibana.go
@@ -17,7 +17,7 @@ import (
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/operator"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/version"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/elasticsearch/user"
-	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana"
+	kblabel "github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/label"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/rbac"
 )
 
@@ -39,8 +39,8 @@ func AddAgentKibana(mgr manager.Manager, accessReviewer rbac.AccessReviewer, par
 			}
 		},
 		AssociationConfAnnotationNameBase:     commonv1.KibanaConfigAnnotationNameBase,
-		AssociationResourceNameLabelName:      kibana.KibanaNameLabelName,
-		AssociationResourceNamespaceLabelName: kibana.KibanaNamespaceLabelName,
+		AssociationResourceNameLabelName:      kblabel.KibanaNameLabelName,
+		AssociationResourceNamespaceLabelName: kblabel.KibanaNamespaceLabelName,
 
 		ElasticsearchUserCreation: &association.ElasticsearchUserCreation{
 			ElasticsearchRef: getElasticsearchFromKibana,
diff --git a/pkg/controller/association/controller/apm_kibana.go b/pkg/controller/association/controller/apm_kibana.go
index b833eda09..9b038f083 100644
--- a/pkg/controller/association/controller/apm_kibana.go
+++ b/pkg/controller/association/controller/apm_kibana.go
@@ -18,7 +18,7 @@ import (
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/association"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/operator"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/elasticsearch/user"
-	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana"
+	kblabel "github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/label"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/k8s"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/rbac"
 )
@@ -41,8 +41,8 @@ func AddApmKibana(mgr manager.Manager, accessReviewer rbac.AccessReviewer, param
 			}
 		},
 		AssociationConfAnnotationNameBase:     commonv1.KibanaConfigAnnotationNameBase,
-		AssociationResourceNameLabelName:      kibana.KibanaNameLabelName,
-		AssociationResourceNamespaceLabelName: kibana.KibanaNamespaceLabelName,
+		AssociationResourceNameLabelName:      kblabel.KibanaNameLabelName,
+		AssociationResourceNamespaceLabelName: kblabel.KibanaNamespaceLabelName,
 
 		ElasticsearchUserCreation: &association.ElasticsearchUserCreation{
 			ElasticsearchRef: getElasticsearchFromKibana,
diff --git a/pkg/controller/association/controller/beat_kibana.go b/pkg/controller/association/controller/beat_kibana.go
index f23fa8b79..0a0688315 100644
--- a/pkg/controller/association/controller/beat_kibana.go
+++ b/pkg/controller/association/controller/beat_kibana.go
@@ -20,7 +20,7 @@ import (
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/operator"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/version"
 	esuser "github.com/elastic/cloud-on-k8s/v2/pkg/controller/elasticsearch/user"
-	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana"
+	kblabel "github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/label"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/rbac"
 )
 
@@ -42,8 +42,8 @@ func AddBeatKibana(mgr manager.Manager, accessReviewer rbac.AccessReviewer, para
 			}
 		},
 		AssociationConfAnnotationNameBase:     commonv1.KibanaConfigAnnotationNameBase,
-		AssociationResourceNameLabelName:      kibana.KibanaNameLabelName,
-		AssociationResourceNamespaceLabelName: kibana.KibanaNamespaceLabelName,
+		AssociationResourceNameLabelName:      kblabel.KibanaNameLabelName,
+		AssociationResourceNamespaceLabelName: kblabel.KibanaNamespaceLabelName,
 
 		ElasticsearchUserCreation: &association.ElasticsearchUserCreation{
 			ElasticsearchRef: getElasticsearchFromKibana,
diff --git a/pkg/controller/common/annotation/association.go b/pkg/controller/common/annotation/association.go
index c6c7b8ff2..50ebee8ed 100644
--- a/pkg/controller/common/annotation/association.go
+++ b/pkg/controller/common/annotation/association.go
@@ -10,13 +10,6 @@ import (
 	commonv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/common/v1"
 )
 
-const (
-	// CurrAssocStatusAnnotation describes the currently observed association status of an object.
-	CurrAssocStatusAnnotation = "association.k8s.elastic.co/current-status"
-	// PrevAssocStatusAnnotation describes the previously observed association status of an object.
-	PrevAssocStatusAnnotation = "association.k8s.elastic.co/previous-status"
-)
-
 // ForAssociationStatusChange constructs the annotation map for an association status change event.
 func ForAssociationStatusChange(prevStatus, currStatus commonv1.AssociationStatusMap) (map[string]string, error) {
 	return map[string]string{
diff --git a/pkg/controller/common/annotation/constants.go b/pkg/controller/common/annotation/constants.go
new file mode 100644
index 000000000..41d5a890f
--- /dev/null
+++ b/pkg/controller/common/annotation/constants.go
@@ -0,0 +1,28 @@
+// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+// or more contributor license agreements. Licensed under the Elastic License 2.0;
+// you may not use this file except in compliance with the Elastic License 2.0.
+
+package annotation
+
+const (
+	// CurrAssocStatusAnnotation describes the currently observed association status of an object.
+	CurrAssocStatusAnnotation = "association.k8s.elastic.co/current-status"
+	// PrevAssocStatusAnnotation describes the previously observed association status of an object.
+	PrevAssocStatusAnnotation = "association.k8s.elastic.co/previous-status"
+
+	// UpdateAnnotation is the name of the annotation applied to pods to force kubelet to resync secrets
+	UpdateAnnotation = "update.k8s.elastic.co/timestamp"
+
+	// FilebeatModuleAnnotation is the name of the annotation applied to pods to give a hint to filebeat so that it
+	// uses the appropriate module to analyze the logs of the container.
+	// https://www.elastic.co/guide/en/beats/filebeat/current/configuration-autodiscover-hints.html#_co_elastic_logsmodule
+	FilebeatModuleAnnotation = "co.elastic.logs/module"
+
+	SecureSettingsSecretsAnnotationName = "policy.k8s.elastic.co/secure-settings-secrets" //nolint:gosec
+	SettingsHashAnnotationName          = "policy.k8s.elastic.co/settings-hash"
+
+	KibanaConfigHashAnnotation = "policy.k8s.elastic.co/kibana-config-hash"
+
+	ElasticsearchConfigAndSecretMountsHashAnnotation = "policy.k8s.elastic.co/elasticsearch-config-mounts-hash" //nolint:gosec
+	SourceSecretAnnotationName                       = "policy.k8s.elastic.co/source-secret-name"               //nolint:gosec
+)
diff --git a/pkg/controller/common/annotation/pod.go b/pkg/controller/common/annotation/pod.go
index 97bd9b2f4..61e132589 100644
--- a/pkg/controller/common/annotation/pod.go
+++ b/pkg/controller/common/annotation/pod.go
@@ -16,16 +16,6 @@ import (
 	ulog "github.com/elastic/cloud-on-k8s/v2/pkg/utils/log"
 )
 
-const (
-	// UpdateAnnotation is the name of the annotation applied to pods to force kubelet to resync secrets
-	UpdateAnnotation = "update.k8s.elastic.co/timestamp"
-
-	// FilebeatModuleAnnotation is the name of the annotation applied to pods to give a hint to filebeat so that it
-	// uses the appropriate module to analyze the logs of the container.
-	// https://www.elastic.co/guide/en/beats/filebeat/current/configuration-autodiscover-hints.html#_co_elastic_logsmodule
-	FilebeatModuleAnnotation = "co.elastic.logs/module"
-)
-
 // MarkPodsAsUpdated updates a specific annotation on the pods to speedup secret propagation.
 func MarkPodsAsUpdated(
 	ctx context.Context,
diff --git a/pkg/controller/common/keystore/user_secret.go b/pkg/controller/common/keystore/user_secret.go
index 8ad1bc178..3e74526b4 100644
--- a/pkg/controller/common/keystore/user_secret.go
+++ b/pkg/controller/common/keystore/user_secret.go
@@ -22,7 +22,7 @@ import (
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/reconciler"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/volume"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/watches"
-	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/elasticsearch/filesettings"
+	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/stackconfigpolicy"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/k8s"
 	ulog "github.com/elastic/cloud-on-k8s/v2/pkg/utils/log"
 )
@@ -50,7 +50,7 @@ func secureSettingsVolume(
 	// user-provided Secrets referenced in the resource
 	secretSources := WatchedSecretNames(hasKeystore)
 	// user-provided Secrets referenced in a StackConfigPolicy that configures the resource
-	policySecretSources, err := filesettings.GetSecureSettingsSecretSources(ctx, r.K8sClient(), hasKeystore)
+	policySecretSources, err := stackconfigpolicy.GetSecureSettingsSecretSourcesForResources(ctx, r.K8sClient(), hasKeystore, hasKeystore.GetObjectKind().GroupVersionKind().Kind)
 	if err != nil {
 		return nil, "", pkgerrors.Wrap(err, "fail to get secure settings secret sources")
 	}
diff --git a/pkg/controller/common/labels/labels.go b/pkg/controller/common/labels/labels.go
index 1ff17401a..b0f76a7d3 100644
--- a/pkg/controller/common/labels/labels.go
+++ b/pkg/controller/common/labels/labels.go
@@ -16,8 +16,8 @@ const (
 	StackConfigPolicyOnDeleteLabelName = "asset.policy.k8s.elastic.co/on-delete"
 	// OrphanSecretResetOnPolicyDelete is used to set the data field of a secret to an empty map when the associated StackConfigPolicy or Elasticsearch is deleted.
 	OrphanSecretResetOnPolicyDelete = "reset"
-	// OrphanSecretResetOnPolicyDelete is used to delete the  secret when the associated stackconfigpolicy or Elasticsearch is deleted.
-	OrphanObjectDeleteOnPolicyDelete = "delete"
+	// OrphanSecretDeleteOnPolicyDelete is used to delete the secret when the associated StackConfigPolicy or Elasticsearch is deleted.
+	OrphanSecretDeleteOnPolicyDelete = "delete"
 )
 
 // TrueFalseLabel is a label that has a true/false value.
diff --git a/pkg/controller/elasticsearch/filesettings/reconciler.go b/pkg/controller/elasticsearch/filesettings/reconciler.go
index 86cff961e..cd5305c24 100644
--- a/pkg/controller/elasticsearch/filesettings/reconciler.go
+++ b/pkg/controller/elasticsearch/filesettings/reconciler.go
@@ -11,8 +11,10 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	esv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/elasticsearch/v1"
+	commonannotation "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/annotation"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/reconciler"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/k8s"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/maps"
@@ -22,9 +24,10 @@ var (
 	// managedLabels are the labels managed by the operator for the file settings Secret, which means that the operator
 	// will always take precedence to update or remove these labels.
 	managedLabels = []string{reconciler.SoftOwnerNamespaceLabel, reconciler.SoftOwnerNameLabel, reconciler.SoftOwnerKindLabel}
-	// managedAnnotations are the annotations managed by the operator for the file settings Secret, which means that the operator
+
+	// managedAnnotations are the annotations managed by the operator for the stack config policy related secrets, which means that the operator
 	// will always take precedence to update or remove these annotations.
-	managedAnnotations = []string{secureSettingsSecretsAnnotationName, settingsHashAnnotationName}
+	managedAnnotations = []string{commonannotation.SecureSettingsSecretsAnnotationName, commonannotation.SettingsHashAnnotationName, commonannotation.ElasticsearchConfigAndSecretMountsHashAnnotation, commonannotation.KibanaConfigHashAnnotation}
 )
 
 // ReconcileEmptyFileSettingsSecret reconciles an empty File settings Secret for the given Elasticsearch only when there is no Secret.
@@ -51,27 +54,31 @@ func ReconcileEmptyFileSettingsSecret(
 		return err
 	}
 
-	return ReconcileSecret(ctx, c, expectedSecret, es)
+	return ReconcileSecret(ctx, c, expectedSecret, &es)
 }
 
-// ReconcileSecret reconciles the given file settings Secret for the given Elasticsearch.
+// ReconcileSecret reconciles the given Secret.
 // This implementation is slightly different from reconciler.ReconcileSecret to allow resetting managed annotations.
 func ReconcileSecret(
 	ctx context.Context,
 	c k8s.Client,
 	expected corev1.Secret,
-	es esv1.Elasticsearch,
+	owner client.Object,
 ) error {
 	reconciled := &corev1.Secret{}
 	return reconciler.ReconcileResource(reconciler.Params{
 		Context:    ctx,
 		Client:     c,
-		Owner:      &es,
+		Owner:      owner,
 		Expected:   &expected,
 		Reconciled: reconciled,
 		NeedsUpdate: func() bool {
-			return !maps.IsSubset(expected.Labels, reconciled.Labels) ||
-				!maps.IsSubset(expected.Annotations, reconciled.Annotations) ||
+			// Secrets must be updated in the following cases:
+			// * the expected labels/annotations are not a subset of the existing labels/annotations,
+			// * the managed labels/annotations have been removed/changed,
+			// * the data itself has changed.
+			return (!maps.IsSubset(expected.Labels, reconciled.Labels) || !maps.IsEqualSubset(expected.Labels, reconciled.Labels, managedLabels)) ||
+				(!maps.IsSubset(expected.Annotations, reconciled.Annotations) || !maps.IsEqualSubset(expected.Annotations, reconciled.Annotations, managedAnnotations)) ||
 				!reflect.DeepEqual(expected.Data, reconciled.Data)
 		},
 		UpdateReconciled: func() {
diff --git a/pkg/controller/elasticsearch/filesettings/secret.go b/pkg/controller/elasticsearch/filesettings/secret.go
index 70b9a9bb5..a78d1cc29 100644
--- a/pkg/controller/elasticsearch/filesettings/secret.go
+++ b/pkg/controller/elasticsearch/filesettings/secret.go
@@ -18,6 +18,7 @@ import (
 	commonv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/common/v1"
 	esv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/elasticsearch/v1"
 	policyv1alpha1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/stackconfigpolicy/v1alpha1"
+	commonannotation "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/annotation"
 	commonlabel "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/labels"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/reconciler"
 	eslabel "github.com/elastic/cloud-on-k8s/v2/pkg/controller/elasticsearch/label"
@@ -25,9 +26,7 @@ import (
 )
 
 const (
-	secureSettingsSecretsAnnotationName = "policy.k8s.elastic.co/secure-settings-secrets" //nolint:gosec
-	settingsHashAnnotationName          = "policy.k8s.elastic.co/settings-hash"
-	SettingsSecretKey                   = "settings.json"
+	SettingsSecretKey = "settings.json"
 )
 
 // NewSettingsSecretWithVersion returns a new SettingsSecret for a given Elasticsearch and optionally a current settings
@@ -74,7 +73,7 @@ func NewSettingsSecret(version int64, es types.NamespacedName, currentSecret *co
 			Name:      esv1.FileSettingsSecretName(es.Name),
 			Labels:    eslabel.NewLabels(es),
 			Annotations: map[string]string{
-				settingsHashAnnotationName: settings.hash(),
+				commonannotation.SettingsHashAnnotationName: settings.hash(),
 			},
 		},
 		Data: map[string][]byte{
@@ -115,7 +114,7 @@ func extractVersion(settingsSecret corev1.Secret) (int64, error) {
 
 // hasChanged compares the hash of the given new Settings Secret with the hash stored in the annotation of the given Settings Secret.
 func hasChanged(settingsSecret corev1.Secret, newSettings Settings) bool {
-	return settingsSecret.Annotations[settingsHashAnnotationName] != newSettings.hash()
+	return settingsSecret.Annotations[commonannotation.SettingsHashAnnotationName] != newSettings.hash()
 }
 
 // SetSoftOwner sets the given StackConfigPolicy as soft owner of the Settings Secret using the "softOwned" labels.
@@ -130,20 +129,28 @@ func SetSoftOwner(settingsSecret *corev1.Secret, policy policyv1alpha1.StackConf
 
 // setSecureSettings stores the SecureSettings Secret sources referenced in the given StackConfigPolicy in the annotation of the Settings Secret.
 func setSecureSettings(settingsSecret *corev1.Secret, policy policyv1alpha1.StackConfigPolicy) error {
-	if len(policy.Spec.SecureSettings) == 0 {
+	//nolint:staticcheck
+	if len(policy.Spec.SecureSettings) == 0 && len(policy.Spec.Elasticsearch.SecureSettings) == 0 {
 		return nil
 	}
 
-	secretSources := make([]commonv1.NamespacedSecretSource, len(policy.Spec.SecureSettings))
-	for i, src := range policy.Spec.SecureSettings {
-		secretSources[i] = commonv1.NamespacedSecretSource{Namespace: policy.GetNamespace(), SecretName: src.SecretName, Entries: src.Entries}
+	var secretSources []commonv1.NamespacedSecretSource //nolint:prealloc
+	// Common secureSettings field, this is mainly there to maintain backwards compatibility
+	//nolint:staticcheck
+	for _, src := range policy.Spec.SecureSettings {
+		secretSources = append(secretSources, commonv1.NamespacedSecretSource{Namespace: policy.GetNamespace(), SecretName: src.SecretName, Entries: src.Entries})
+	}
+
+	// SecureSettings field under Elasticsearch in the StackConfigPolicy
+	for _, src := range policy.Spec.Elasticsearch.SecureSettings {
+		secretSources = append(secretSources, commonv1.NamespacedSecretSource{Namespace: policy.GetNamespace(), SecretName: src.SecretName, Entries: src.Entries})
 	}
 
 	bytes, err := json.Marshal(secretSources)
 	if err != nil {
 		return err
 	}
-	settingsSecret.Annotations[secureSettingsSecretsAnnotationName] = string(bytes)
+	settingsSecret.Annotations[commonannotation.SecureSettingsSecretsAnnotationName] = string(bytes)
 	return nil
 }
 
@@ -162,7 +169,7 @@ func CanBeOwnedBy(settingsSecret corev1.Secret, policy policyv1alpha1.StackConfi
 
 // getSecureSettings returns the SecureSettings Secret sources stores in an annotation of the given file settings Secret.
 func getSecureSettings(settingsSecret corev1.Secret) ([]commonv1.NamespacedSecretSource, error) {
-	rawString, ok := settingsSecret.Annotations[secureSettingsSecretsAnnotationName]
+	rawString, ok := settingsSecret.Annotations[commonannotation.SecureSettingsSecretsAnnotationName]
 	if !ok {
 		return []commonv1.NamespacedSecretSource{}, nil
 	}
diff --git a/pkg/controller/elasticsearch/nodespec/policy_config.go b/pkg/controller/elasticsearch/nodespec/policy_config.go
index 707106bbe..584aa2dd8 100644
--- a/pkg/controller/elasticsearch/nodespec/policy_config.go
+++ b/pkg/controller/elasticsearch/nodespec/policy_config.go
@@ -14,6 +14,7 @@ import (
 
 	esv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/elasticsearch/v1"
 	policyv1alpha1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/stackconfigpolicy/v1alpha1"
+	commonannotation "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/annotation"
 	common "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/settings"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/volume"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/stackconfigpolicy"
@@ -43,7 +44,7 @@ func getPolicyConfig(ctx context.Context, client k8s.Client, es esv1.Elasticsear
 
 	// Additional annotations to be applied on the Elasticsearch pods
 	policyConfig.PolicyAnnotations = map[string]string{
-		stackconfigpolicy.ElasticsearchConfigAndSecretMountsHashAnnotation: stackConfigPolicyConfigSecret.Annotations[stackconfigpolicy.ElasticsearchConfigAndSecretMountsHashAnnotation],
+		commonannotation.ElasticsearchConfigAndSecretMountsHashAnnotation: stackConfigPolicyConfigSecret.Annotations[commonannotation.ElasticsearchConfigAndSecretMountsHashAnnotation],
 	}
 
 	// Parse Elasticsearch config from the stack config policy secret.
diff --git a/pkg/controller/kibana/config_reconcile.go b/pkg/controller/kibana/config_reconcile.go
index 982ca2cd7..8f68984fe 100644
--- a/pkg/controller/kibana/config_reconcile.go
+++ b/pkg/controller/kibana/config_reconcile.go
@@ -19,6 +19,7 @@ import (
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/reconciler"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/tracing"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/volume"
+	kblabel "github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/label"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/k8s"
 )
 
@@ -96,7 +97,7 @@ func ReconcileConfigSecret(
 			Namespace: kb.Namespace,
 			Name:      SecretName(kb),
 			Labels: labels.AddCredentialsLabel(map[string]string{
-				KibanaNameLabelName: kb.Name,
+				kblabel.KibanaNameLabelName: kb.Name,
 			}),
 		},
 		Data: data,
diff --git a/pkg/controller/kibana/config_settings.go b/pkg/controller/kibana/config_settings.go
index 726b6f2a8..1e7d3fb79 100644
--- a/pkg/controller/kibana/config_settings.go
+++ b/pkg/controller/kibana/config_settings.go
@@ -80,7 +80,7 @@ type CanonicalConfig struct {
 }
 
 // NewConfigSettings returns the Kibana configuration settings for the given Kibana resource.
-func NewConfigSettings(ctx context.Context, client k8s.Client, kb kbv1.Kibana, v version.Version, ipFamily corev1.IPFamily) (CanonicalConfig, error) {
+func NewConfigSettings(ctx context.Context, client k8s.Client, kb kbv1.Kibana, v version.Version, ipFamily corev1.IPFamily, kibanaConfigFromPolicy *settings.CanonicalConfig) (CanonicalConfig, error) {
 	span, _ := apm.StartSpan(ctx, "new_config_settings", tracing.SpanTypeApp)
 	defer span.End()
 
@@ -157,8 +157,8 @@ func NewConfigSettings(ctx context.Context, client k8s.Client, kb kbv1.Kibana, v
 		}
 	}
 
-	// merge the configuration with userSettings last so they take precedence
-	if err = cfg.MergeWith(userSettings); err != nil {
+	// Kibana settings from a StackConfigPolicy takes precedence over user provided settings, merge them last.
+	if err = cfg.MergeWith(userSettings, kibanaConfigFromPolicy); err != nil {
 		return CanonicalConfig{}, err
 	}
 
diff --git a/pkg/controller/kibana/controller.go b/pkg/controller/kibana/controller.go
index b34db8f0d..199232083 100644
--- a/pkg/controller/kibana/controller.go
+++ b/pkg/controller/kibana/controller.go
@@ -32,6 +32,7 @@ import (
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/reconciler"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/tracing"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/watches"
+	kblabel "github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/label"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/k8s"
 	ulog "github.com/elastic/cloud-on-k8s/v2/pkg/utils/log"
 )
@@ -78,7 +79,7 @@ func addWatches(mgr manager.Manager, c controller.Controller, r *ReconcileKibana
 
 	// Watch Pods, to ensure `status.version` and version upgrades are correctly reconciled on any change.
 	// Watching Deployments only may lead to missing some events.
-	if err := watches.WatchPods(mgr, c, KibanaNameLabelName); err != nil {
+	if err := watches.WatchPods(mgr, c, kblabel.KibanaNameLabelName); err != nil {
 		return err
 	}
 
diff --git a/pkg/controller/kibana/driver.go b/pkg/controller/kibana/driver.go
index 32f374941..11d624500 100644
--- a/pkg/controller/kibana/driver.go
+++ b/pkg/controller/kibana/driver.go
@@ -33,10 +33,12 @@ import (
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/version"
 	commonvolume "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/volume"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/watches"
+	kblabel "github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/label"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/network"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/stackmon"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/k8s"
 	ulog "github.com/elastic/cloud-on-k8s/v2/pkg/utils/log"
+	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/maps"
 )
 
 // minSupportedVersion is the minimum version of Kibana supported by ECK. Currently this is set to version 6.8.0.
@@ -148,7 +150,12 @@ func (d *driver) Reconcile(
 		return results // will eventually retry
 	}
 
-	kbSettings, err := NewConfigSettings(ctx, d.client, *kb, d.version, d.ipFamily)
+	kibanaPolicyCfg, err := getPolicyConfig(ctx, d.client, *kb)
+	if err != nil {
+		return results.WithError(err)
+	}
+
+	kbSettings, err := NewConfigSettings(ctx, d.client, *kb, d.version, d.ipFamily, kibanaPolicyCfg.KibanaConfig)
 	if err != nil {
 		return results.WithError(err)
 	}
@@ -166,7 +173,7 @@ func (d *driver) Reconcile(
 	span, _ := apm.StartSpan(ctx, "reconcile_deployment", tracing.SpanTypeApp)
 	defer span.End()
 
-	deploymentParams, err := d.deploymentParams(ctx, kb)
+	deploymentParams, err := d.deploymentParams(ctx, kb, kibanaPolicyCfg.PodAnnotations)
 	if err != nil {
 		return results.WithError(err)
 	}
@@ -177,11 +184,11 @@ func (d *driver) Reconcile(
 		return results.WithError(err)
 	}
 
-	existingPods, err := k8s.PodsMatchingLabels(d.K8sClient(), kb.Namespace, map[string]string{KibanaNameLabelName: kb.Name})
+	existingPods, err := k8s.PodsMatchingLabels(d.K8sClient(), kb.Namespace, map[string]string{kblabel.KibanaNameLabelName: kb.Name})
 	if err != nil {
 		return results.WithError(err)
 	}
-	deploymentStatus, err := common.DeploymentStatus(ctx, state.Kibana.Status.DeploymentStatus, reconciledDp, existingPods, KibanaVersionLabelName)
+	deploymentStatus, err := common.DeploymentStatus(ctx, state.Kibana.Status.DeploymentStatus, reconciledDp, existingPods, kblabel.KibanaVersionLabelName)
 	if err != nil {
 		return results.WithError(err)
 	}
@@ -195,13 +202,13 @@ func (d *driver) Reconcile(
 // running multiple versions simultaneously may lead to concurrency bugs and data corruption.
 func (d *driver) getStrategyType(kb *kbv1.Kibana) (appsv1.DeploymentStrategyType, error) {
 	var pods corev1.PodList
-	var labels client.MatchingLabels = map[string]string{KibanaNameLabelName: kb.Name}
+	var labels client.MatchingLabels = map[string]string{kblabel.KibanaNameLabelName: kb.Name}
 	if err := d.client.List(context.Background(), &pods, client.InNamespace(kb.Namespace), labels); err != nil {
 		return "", err
 	}
 
 	for _, pod := range pods.Items {
-		ver, ok := pod.Labels[KibanaVersionLabelName]
+		ver, ok := pod.Labels[kblabel.KibanaVersionLabelName]
 		// if label is missing we assume that the last reconciliation was done by previous version of the operator
 		// to be safe, we assume the Kibana version has changed when operator was offline and use Recreate,
 		// otherwise we may run into data corruption/data loss.
@@ -213,7 +220,7 @@ func (d *driver) getStrategyType(kb *kbv1.Kibana) (appsv1.DeploymentStrategyType
 	return appsv1.RollingUpdateDeploymentStrategyType, nil
 }
 
-func (d *driver) deploymentParams(ctx context.Context, kb *kbv1.Kibana) (deployment.Params, error) {
+func (d *driver) deploymentParams(ctx context.Context, kb *kbv1.Kibana, policyAnnotations map[string]string) (deployment.Params, error) {
 	initContainersParameters, err := newInitContainersParameters(kb)
 	if err != nil {
 		return deployment.Params{}, err
@@ -280,6 +287,9 @@ func (d *driver) deploymentParams(ctx context.Context, kb *kbv1.Kibana) (deploym
 	// changes, which will trigger a rolling update)
 	kibanaPodSpec.Annotations[configHashAnnotationName] = fmt.Sprint(configHash.Sum32())
 
+	// add additional annotations related to the StackConfigPolicy
+	kibanaPodSpec.Annotations = maps.Merge(kibanaPodSpec.Annotations, policyAnnotations)
+
 	// decide the strategy type
 	strategyType, err := d.getStrategyType(kb)
 	if err != nil {
diff --git a/pkg/controller/kibana/labels.go b/pkg/controller/kibana/label/label.go
similarity index 66%
rename from pkg/controller/kibana/labels.go
rename to pkg/controller/kibana/label/label.go
index fe683cc6d..06e8f10ee 100644
--- a/pkg/controller/kibana/labels.go
+++ b/pkg/controller/kibana/label/label.go
@@ -2,7 +2,13 @@
 // or more contributor license agreements. Licensed under the Elastic License 2.0;
 // you may not use this file except in compliance with the Elastic License 2.0.
 
-package kibana
+package label
+
+import (
+	"k8s.io/apimachinery/pkg/types"
+
+	commonv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/common/v1"
+)
 
 const (
 	// KibanaNameLabelName used to represent a Kibana in k8s resources
@@ -17,3 +23,11 @@ const (
 	// Type represents the Kibana type
 	Type = "kibana"
 )
+
+// NewLabels constructs a new set of labels from Kibana definition.
+func NewLabels(kb types.NamespacedName) map[string]string {
+	return map[string]string{
+		KibanaNameLabelName:    kb.Name,
+		commonv1.TypeLabelName: Type,
+	}
+}
diff --git a/pkg/controller/kibana/pod.go b/pkg/controller/kibana/pod.go
index 31adf8538..85080d67b 100644
--- a/pkg/controller/kibana/pod.go
+++ b/pkg/controller/kibana/pod.go
@@ -20,6 +20,7 @@ import (
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/pod"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/version"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/volume"
+	kblabel "github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/label"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/network"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/stackmon"
 )
@@ -75,7 +76,8 @@ func readinessProbe(useTLS bool) corev1.Probe {
 
 func NewPodTemplateSpec(ctx context.Context, client k8sclient.Client, kb kbv1.Kibana, keystore *keystore.Resources, volumes []volume.VolumeLike) (corev1.PodTemplateSpec, error) {
 	labels := kb.GetIdentityLabels()
-	labels[KibanaVersionLabelName] = kb.Spec.Version
+	labels[kblabel.KibanaVersionLabelName] = kb.Spec.Version
+
 	ports := getDefaultContainerPorts(kb)
 	v, err := version.Parse(kb.Spec.Version)
 	if err != nil {
diff --git a/pkg/controller/kibana/policy_config_setting.go b/pkg/controller/kibana/policy_config_setting.go
new file mode 100644
index 000000000..fca3c12d1
--- /dev/null
+++ b/pkg/controller/kibana/policy_config_setting.go
@@ -0,0 +1,65 @@
+// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+// or more contributor license agreements. Licensed under the Elastic License 2.0;
+// you may not use this file except in compliance with the Elastic License 2.0.
+
+package kibana
+
+import (
+	"context"
+	"encoding/json"
+
+	corev1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/types"
+
+	kibanav1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/kibana/v1"
+	commonannotation "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/annotation"
+	common "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/settings"
+	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/stackconfigpolicy"
+	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/k8s"
+)
+
+// PolicyConfig is a structure for storing Kibana config from the StackConfigPolicy
+type PolicyConfig struct {
+	KibanaConfig   *common.CanonicalConfig
+	PodAnnotations map[string]string
+}
+
+// getPolicyConfig parses the StackConfigPolicy secret and returns a PolicyConfig struct
+func getPolicyConfig(ctx context.Context, client k8s.Client, kibana kibanav1.Kibana) (PolicyConfig, error) {
+	var policyConfig PolicyConfig
+	// Retrieve secret created by the StackConfigPolicy controller if it exists
+	// Check for stack config policy Kibana config secret
+	stackConfigPolicyConfigSecret := corev1.Secret{}
+	err := client.Get(ctx, types.NamespacedName{
+		Name:      stackconfigpolicy.GetPolicyConfigSecretName(kibana.Name),
+		Namespace: kibana.Namespace,
+	}, &stackConfigPolicyConfigSecret)
+	if err != nil && !apierrors.IsNotFound(err) {
+		return policyConfig, err
+	}
+
+	if apierrors.IsNotFound(err) {
+		return policyConfig, nil
+	}
+
+	// Additional annotations to be applied on the Kibana pods
+	policyConfig.PodAnnotations = map[string]string{
+		commonannotation.KibanaConfigHashAnnotation: stackConfigPolicyConfigSecret.Annotations[commonannotation.KibanaConfigHashAnnotation],
+	}
+
+	// Parse Kibana config from the stack config policy secret.
+	var kbConfigFromStackConfigPolicy map[string]interface{}
+	if string(stackConfigPolicyConfigSecret.Data[stackconfigpolicy.KibanaConfigKey]) != "" {
+		if err = json.Unmarshal(stackConfigPolicyConfigSecret.Data[stackconfigpolicy.KibanaConfigKey], &kbConfigFromStackConfigPolicy); err != nil {
+			return policyConfig, err
+		}
+	}
+	canonicalConfig, err := common.NewCanonicalConfigFrom(kbConfigFromStackConfigPolicy)
+	if err != nil {
+		return policyConfig, err
+	}
+	policyConfig.KibanaConfig = canonicalConfig
+
+	return policyConfig, nil
+}
diff --git a/pkg/controller/stackconfigpolicy/controller.go b/pkg/controller/stackconfigpolicy/controller.go
index a6abe6649..8e0e32366 100644
--- a/pkg/controller/stackconfigpolicy/controller.go
+++ b/pkg/controller/stackconfigpolicy/controller.go
@@ -26,7 +26,9 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 	"sigs.k8s.io/controller-runtime/pkg/source"
 
+	commonv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/common/v1"
 	esv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/elasticsearch/v1"
+	kibanav1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/kibana/v1"
 	policyv1alpha1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/stackconfigpolicy/v1alpha1"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common"
 	commonesclient "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/esclient"
@@ -37,9 +39,11 @@ import (
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/reconciler"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/tracing"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/version"
+	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/watches"
 	esclient "github.com/elastic/cloud-on-k8s/v2/pkg/controller/elasticsearch/client"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/elasticsearch/filesettings"
 	eslabel "github.com/elastic/cloud-on-k8s/v2/pkg/controller/elasticsearch/label"
+	kblabel "github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/label"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/k8s"
 	ulog "github.com/elastic/cloud-on-k8s/v2/pkg/utils/log"
 )
@@ -72,6 +76,7 @@ func newReconciler(mgr manager.Manager, params operator.Parameters) *ReconcileSt
 		recorder:         mgr.GetEventRecorderFor(controllerName),
 		licenseChecker:   license.NewLicenseChecker(k8sClient, params.OperatorNamespace),
 		params:           params,
+		dynamicWatches:   watches.NewDynamicWatches(),
 	}
 }
 
@@ -86,8 +91,18 @@ func addWatches(mgr manager.Manager, c controller.Controller, r *ReconcileStackC
 		return err
 	}
 
+	// watch for changes to Kibana and reconcile all StackConfigPolicy
+	if err := c.Watch(source.Kind(mgr.GetCache(), &kibanav1.Kibana{}), r.reconcileRequestForAllPolicies()); err != nil {
+		return err
+	}
+
 	// watch Secrets soft owned by StackConfigPolicy
-	return c.Watch(source.Kind(mgr.GetCache(), &corev1.Secret{}), reconcileRequestForSoftOwnerPolicy())
+	if err := c.Watch(source.Kind(mgr.GetCache(), &corev1.Secret{}), reconcileRequestForSoftOwnerPolicy()); err != nil {
+		return err
+	}
+
+	// watch dynamically refrenced secrets
+	return c.Watch(source.Kind(mgr.GetCache(), &corev1.Secret{}), r.dynamicWatches.Secrets)
 }
 
 func reconcileRequestForSoftOwnerPolicy() handler.EventHandler {
@@ -129,6 +144,7 @@ type ReconcileStackConfigPolicy struct {
 	recorder         record.EventRecorder
 	licenseChecker   license.Checker
 	params           operator.Parameters
+	dynamicWatches   watches.DynamicWatches
 	// iteration is the number of times this controller has run its Reconcile method
 	iteration uint64
 }
@@ -156,8 +172,7 @@ func (r *ReconcileStackConfigPolicy) Reconcile(ctx context.Context, request reco
 
 	// skip unmanaged resources
 	if common.IsUnmanaged(ctx, &policy) {
-		ulog.FromContext(ctx).Info("Object is currently not managed by this controller. Skipping reconciliation",
-			"namespace", policy.Namespace, "policy_name", policy.Name)
+		ulog.FromContext(ctx).Info("Object is currently not managed by this controller. Skipping reconciliation")
 		return reconcile.Result{}, nil
 	}
 
@@ -184,9 +199,13 @@ func (r *ReconcileStackConfigPolicy) Reconcile(ctx context.Context, request reco
 // clusters configured by a StackConfigPolicy.
 type esMap map[types.NamespacedName]esv1.Elasticsearch
 
+// kbMap is a type alias for a Map of Kibana indexed by NamespacedName useful to manipulate the Kibana
+// instances configured by a StackConfigPolicy.
+type kbMap map[types.NamespacedName]kibanav1.Kibana
+
 func (r *ReconcileStackConfigPolicy) doReconcile(ctx context.Context, policy policyv1alpha1.StackConfigPolicy) (*reconciler.Results, policyv1alpha1.StackConfigPolicyStatus) {
 	log := ulog.FromContext(ctx)
-	log.V(1).Info("Reconcile StackConfigPolicy", "namespace", policy.Namespace, "policy_name", policy.Name)
+	log.V(1).Info("Reconcile StackConfigPolicy")
 
 	results := reconciler.NewResult(ctx)
 	status := policyv1alpha1.NewStatus(policy)
@@ -199,12 +218,11 @@ func (r *ReconcileStackConfigPolicy) doReconcile(ctx context.Context, policy pol
 	}
 	if !enabled {
 		msg := "StackConfigPolicy is an enterprise feature. Enterprise features are disabled"
-		log.Info(msg, "namespace", policy.Namespace, "policy_name", policy.Name)
+		log.Info(msg)
 		r.recorder.Eventf(&policy, corev1.EventTypeWarning, events.EventReconciliationError, msg)
 		// we don't have a good way of watching for the license level to change so just requeue with a reasonably long delay
 		return results.WithResult(reconcile.Result{Requeue: true, RequeueAfter: 5 * time.Minute}), status
 	}
-
 	// run validation in case the webhook is disabled
 	if err := r.validate(ctx, &policy); err != nil {
 		status.Phase = policyv1alpha1.InvalidPhase
@@ -212,6 +230,30 @@ func (r *ReconcileStackConfigPolicy) doReconcile(ctx context.Context, policy pol
 		return results.WithError(err), status
 	}
 
+	// reconcile elasticsearch resources
+	results, status = r.reconcileElasticsearchResources(ctx, policy, status)
+
+	// reconcile kibana resources
+	kibanaResults, status := r.reconcileKibanaResources(ctx, policy, status)
+
+	// Combine results from kibana reconciliation with results from Elasticsearch reconciliation
+	results.WithResults(kibanaResults)
+
+	// requeue if not ready
+	if status.Phase != policyv1alpha1.ReadyPhase {
+		results.WithResult(defaultRequeue)
+	}
+
+	return results, status
+}
+
+func (r *ReconcileStackConfigPolicy) reconcileElasticsearchResources(ctx context.Context, policy policyv1alpha1.StackConfigPolicy, status policyv1alpha1.StackConfigPolicyStatus) (*reconciler.Results, policyv1alpha1.StackConfigPolicyStatus) {
+	defer tracing.Span(&ctx)()
+	log := ulog.FromContext(ctx)
+	log.V(1).Info("Reconcile Elasticsearch resources")
+
+	results := reconciler.NewResult(ctx)
+
 	// prepare the selector to find Elastic resources to configure
 	selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{
 		MatchLabels:      policy.Spec.ResourceSelector.MatchLabels,
@@ -235,7 +277,7 @@ func (r *ReconcileStackConfigPolicy) doReconcile(ctx context.Context, policy pol
 
 	configuredResources := esMap{}
 	for _, es := range esList.Items {
-		log.V(1).Info("Reconcile StackConfigPolicy", "policy_namespace", policy.Namespace, "policy_name", policy.Name, "es_namespace", es.Namespace, "es_name", es.Name)
+		log.V(1).Info("Reconcile StackConfigPolicy", "es_namespace", es.Namespace, "es_name", es.Name)
 		es := es
 
 		// keep the list of ES to be configured
@@ -251,7 +293,7 @@ func (r *ReconcileStackConfigPolicy) doReconcile(ctx context.Context, policy pol
 			err = fmt.Errorf("invalid version to configure resource Elasticsearch %s/%s: actual %s, expected >= %s", es.Namespace, es.Name, v, filesettings.FileBasedSettingsMinVersion)
 			r.recorder.Eventf(&policy, corev1.EventTypeWarning, events.EventReasonUnexpected, err.Error())
 			results.WithError(err)
-			err = status.AddPolicyErrorFor(esNsn, policyv1alpha1.ErrorPhase, err.Error())
+			err = status.AddPolicyErrorFor(esNsn, policyv1alpha1.ErrorPhase, err.Error(), policyv1alpha1.ElasticsearchResourceType)
 			if err != nil {
 				return results.WithError(err), status
 			}
@@ -275,7 +317,7 @@ func (r *ReconcileStackConfigPolicy) doReconcile(ctx context.Context, policy pol
 			err = fmt.Errorf("conflict: resource Elasticsearch %s/%s already configured by StackConfigpolicy %s/%s", es.Namespace, es.Name, currentOwner.Namespace, currentOwner.Name)
 			r.recorder.Eventf(&policy, corev1.EventTypeWarning, events.EventReasonUnexpected, err.Error())
 			results.WithError(err)
-			err = status.AddPolicyErrorFor(esNsn, policyv1alpha1.ConflictPhase, err.Error())
+			err = status.AddPolicyErrorFor(esNsn, policyv1alpha1.ConflictPhase, err.Error(), policyv1alpha1.ElasticsearchResourceType)
 			if err != nil {
 				return results.WithError(err), status
 			}
@@ -288,14 +330,14 @@ func (r *ReconcileStackConfigPolicy) doReconcile(ctx context.Context, policy pol
 			return results.WithError(err), status
 		}
 
-		if err := filesettings.ReconcileSecret(ctx, r.Client, expectedSecret, es); err != nil {
+		if err := filesettings.ReconcileSecret(ctx, r.Client, expectedSecret, &es); err != nil {
 			return results.WithError(err), status
 		}
 
 		// Copy all the Secrets that are present in spec.elasticsearch.secretMounts
 		if err := reconcileSecretMounts(ctx, r.Client, es, &policy); err != nil {
 			if apierrors.IsNotFound(err) {
-				err = status.AddPolicyErrorFor(esNsn, policyv1alpha1.ErrorPhase, err.Error())
+				err = status.AddPolicyErrorFor(esNsn, policyv1alpha1.ErrorPhase, err.Error(), policyv1alpha1.ElasticsearchResourceType)
 				if err != nil {
 					return results.WithError(err), status
 				}
@@ -310,8 +352,7 @@ func (r *ReconcileStackConfigPolicy) doReconcile(ctx context.Context, policy pol
 			return results.WithError(err), status
 		}
 
-		_, err = reconciler.ReconcileSecret(ctx, r.Client, expectedConfigSecret, &es)
-		if err != nil {
+		if err = filesettings.ReconcileSecret(ctx, r.Client, expectedConfigSecret, &es); err != nil {
 			return results.WithError(err), status
 		}
 
@@ -324,7 +365,7 @@ func (r *ReconcileStackConfigPolicy) doReconcile(ctx context.Context, policy pol
 		// get /_cluster/state to get the Settings currently configured in ES
 		currentSettings, err := r.getClusterStateFileSettings(ctx, es)
 		if err != nil {
-			err = status.AddPolicyErrorFor(esNsn, policyv1alpha1.UnknownPhase, err.Error())
+			err = status.AddPolicyErrorFor(esNsn, policyv1alpha1.UnknownPhase, err.Error(), policyv1alpha1.ElasticsearchResourceType)
 			if err != nil {
 				return results.WithError(err), status
 			}
@@ -333,21 +374,112 @@ func (r *ReconcileStackConfigPolicy) doReconcile(ctx context.Context, policy pol
 		}
 
 		// update the ES resource status for this ES
-		status.UpdateResourceStatusPhase(esNsn, newResourceStatus(currentSettings, expectedVersion), configAndSecretMountsApplied)
+		err = status.UpdateResourceStatusPhase(esNsn, newElasticsearchResourceStatus(currentSettings, expectedVersion), configAndSecretMountsApplied, policyv1alpha1.ElasticsearchResourceType)
+		if err != nil {
+			return results.WithError(err), status
+		}
+	}
+
+	// Add dynamic watches on the additional secret mounts
+	// This will also remove dynamic watches for secrets that no longer are refrenced in the stackconfigpolicy
+	if err = r.addDynamicWatchesOnAdditionalSecretMounts(policy); err != nil {
+		return results.WithError(err), status
 	}
 
 	// reset/delete Settings secrets for resources no longer selected by this policy
-	results.WithError(handleOrphanSoftOwnedSecrets(ctx, r.Client, k8s.ExtractNamespacedName(&policy), configuredResources))
+	results.WithError(handleOrphanSoftOwnedSecrets(ctx, r.Client, k8s.ExtractNamespacedName(&policy), configuredResources, nil, policyv1alpha1.ElasticsearchResourceType))
 
-	// requeue if not ready
-	if status.Phase != policyv1alpha1.ReadyPhase {
-		results.WithResult(defaultRequeue)
+	return results, status
+}
+
+func (r *ReconcileStackConfigPolicy) reconcileKibanaResources(ctx context.Context, policy policyv1alpha1.StackConfigPolicy, status policyv1alpha1.StackConfigPolicyStatus) (*reconciler.Results, policyv1alpha1.StackConfigPolicyStatus) {
+	defer tracing.Span(&ctx)()
+	log := ulog.FromContext(ctx)
+	log.V(1).Info("Reconcile Kibana Resources")
+
+	results := reconciler.NewResult(ctx)
+
+	// prepare the selector to find Kibana resources to configure
+	selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{
+		MatchLabels:      policy.Spec.ResourceSelector.MatchLabels,
+		MatchExpressions: policy.Spec.ResourceSelector.MatchExpressions,
+	})
+	if err != nil {
+		return results.WithError(err), status
 	}
+	listOpts := client.ListOptions{LabelSelector: selector}
+
+	// restrict the search to the policy namespace if it is different from the operator namespace
+	if policy.Namespace != r.params.OperatorNamespace {
+		listOpts.Namespace = policy.Namespace
+	}
+
+	// find the list of Kibana to configure
+	var kibanaList kibanav1.KibanaList
+	if err := r.Client.List(ctx, &kibanaList, &listOpts); err != nil {
+		return results.WithError(err), status
+	}
+
+	configuredResources := kbMap{}
+	for _, kibana := range kibanaList.Items {
+		log.V(1).Info("Reconcile StackConfigPolicy", "kibana_namespace", kibana.Namespace, "kibana_name", kibana.Name)
+		kibana := kibana
+
+		// keep the list of Kibana to be configured
+		kibanaNsn := k8s.ExtractNamespacedName(&kibana)
+
+		// check that there is no other policy that already owns the kibana config secret
+		currentOwner, ok, err := canBeOwned(ctx, r.Client, policy, kibana)
+		if err != nil {
+			return results.WithError(err), status
+		}
+
+		// record error if already owned by another stack config policy
+		if !ok {
+			err := fmt.Errorf("conflict: resource Kibana %s/%s already configured by StackConfigpolicy %s/%s", kibana.Namespace, kibana.Name, currentOwner.Namespace, currentOwner.Name)
+			r.recorder.Eventf(&policy, corev1.EventTypeWarning, events.EventReasonUnexpected, err.Error())
+			results.WithError(err)
+			if err := status.AddPolicyErrorFor(kibanaNsn, policyv1alpha1.ConflictPhase, err.Error(), policyv1alpha1.KibanaResourceType); err != nil {
+				return results.WithError(err), status
+			}
+			continue
+		}
+
+		// Create the Secret that holds the Kibana configuration.
+		if policy.Spec.Kibana.Config != nil {
+			// Only add to configured resources if Kibana config is set.
+			// This will help clean up the config secret if config gets removed from the stack config policy.
+			configuredResources[kibanaNsn] = kibana
+			expectedConfigSecret, err := newKibanaConfigSecret(policy, kibana)
+			if err != nil {
+				return results.WithError(err), status
+			}
+
+			if err = filesettings.ReconcileSecret(ctx, r.Client, expectedConfigSecret, &kibana); err != nil {
+				return results.WithError(err), status
+			}
+		}
+
+		// Check if required Kibana configs are applied.
+		configApplied, err := kibanaConfigApplied(r.Client, policy, kibana)
+		if err != nil {
+			return results.WithError(err), status
+		}
+
+		// update the Kibana resource status for this Kibana
+		err = status.UpdateResourceStatusPhase(kibanaNsn, policyv1alpha1.ResourcePolicyStatus{}, configApplied, policyv1alpha1.KibanaResourceType)
+		if err != nil {
+			return results.WithError(err), status
+		}
+	}
+
+	// delete Settings secrets for resources no longer selected by this policy
+	results.WithError(deleteOrphanSoftOwnedSecrets(ctx, r.Client, k8s.ExtractNamespacedName(&policy), nil, configuredResources, policyv1alpha1.KibanaResourceType))
 
 	return results, status
 }
 
-func newResourceStatus(currentSettings esclient.FileSettings, expectedVersion int64) policyv1alpha1.ResourcePolicyStatus {
+func newElasticsearchResourceStatus(currentSettings esclient.FileSettings, expectedVersion int64) policyv1alpha1.ResourcePolicyStatus {
 	status := policyv1alpha1.ResourcePolicyStatus{
 		CurrentVersion:  currentSettings.Version,
 		ExpectedVersion: expectedVersion,
@@ -405,8 +537,6 @@ func (r *ReconcileStackConfigPolicy) updateStatus(ctx context.Context, scp polic
 	}
 	ulog.FromContext(ctx).V(1).Info("Updating status",
 		"iteration", atomic.LoadUint64(&r.iteration),
-		"namespace", scp.Namespace,
-		"policy_name", scp.Name,
 		"status", status,
 	)
 	scp.Status = status
@@ -414,98 +544,160 @@ func (r *ReconcileStackConfigPolicy) updateStatus(ctx context.Context, scp polic
 }
 
 func (r *ReconcileStackConfigPolicy) onDelete(ctx context.Context, obj types.NamespacedName) error {
-	return handleOrphanSoftOwnedSecrets(ctx, r.Client, obj, nil)
+	defer tracing.Span(&ctx)()
+	// Remove dynamic watches on secrets
+	r.dynamicWatches.Secrets.RemoveHandlerForKey(additionalSecretMountsWatcherName(obj))
+	// Send empty resource type so that we reset/delete secrets for configured elasticsearch and kibana clusters
+	return handleOrphanSoftOwnedSecrets(ctx, r.Client, obj, nil, nil, "")
 }
 
-func handleOrphanSoftOwnedSecrets(ctx context.Context, c k8s.Client, softOwner types.NamespacedName, configuredEs esMap) error {
-	err := resetOrphanSoftOwnedSecrets(ctx, c, softOwner, configuredEs)
+func handleOrphanSoftOwnedSecrets(
+	ctx context.Context,
+	c k8s.Client,
+	softOwner types.NamespacedName,
+	configuredESResources esMap,
+	configuredKibanaResources kbMap,
+	resourceType policyv1alpha1.ResourceType,
+) error {
+	err := resetOrphanSoftOwnedFileSettingSecrets(ctx, c, softOwner, configuredESResources, resourceType)
 	if err != nil {
 		return err
 	}
-	return deleteOrphanSoftOwnedSecrets(ctx, c, softOwner, configuredEs)
+	return deleteOrphanSoftOwnedSecrets(ctx, c, softOwner, configuredESResources, configuredKibanaResources, resourceType)
 }
 
-// resetOrphanSoftOwnedSecrets resets secrets for the Elasticsearch clusters that are no longer configured
+// resetOrphanSoftOwnedFileSettingSecrets resets secrets for the Elasticsearch clusters that are no longer configured
 // by a given StackConfigPolicy.
 // An optional list of Elasticsearch currently configured by the policy can be provided to filter secrets not to be modified. Without list,
 // all secrets soft owned by the policy are reset.
-func resetOrphanSoftOwnedSecrets(ctx context.Context, c k8s.Client, softOwner types.NamespacedName, configuredEs esMap) error {
+func resetOrphanSoftOwnedFileSettingSecrets(
+	ctx context.Context,
+	c k8s.Client,
+	softOwner types.NamespacedName,
+	configuredESResources esMap,
+	resourceType policyv1alpha1.ResourceType,
+) error {
 	log := ulog.FromContext(ctx)
 	var secrets corev1.SecretList
+	matchLabels := client.MatchingLabels{
+		reconciler.SoftOwnerNamespaceLabel:              softOwner.Namespace,
+		reconciler.SoftOwnerNameLabel:                   softOwner.Name,
+		reconciler.SoftOwnerKindLabel:                   policyv1alpha1.Kind,
+		commonlabels.StackConfigPolicyOnDeleteLabelName: commonlabels.OrphanSecretResetOnPolicyDelete,
+	}
+
+	if resourceType != "" {
+		matchLabels[commonv1.TypeLabelName] = string(resourceType)
+	}
+
 	if err := c.List(ctx,
 		&secrets,
 		// search in all namespaces
 		// restrict to secrets on which we set the soft owner labels
-		client.MatchingLabels{
-			reconciler.SoftOwnerNamespaceLabel:              softOwner.Namespace,
-			reconciler.SoftOwnerNameLabel:                   softOwner.Name,
-			reconciler.SoftOwnerKindLabel:                   policyv1alpha1.Kind,
-			commonlabels.StackConfigPolicyOnDeleteLabelName: commonlabels.OrphanSecretResetOnPolicyDelete,
-		},
+		matchLabels,
 	); err != nil {
 		return err
 	}
 	for i := range secrets.Items {
 		s := secrets.Items[i]
+		configuredApplicationType := s.Labels[commonv1.TypeLabelName]
+		switch configuredApplicationType {
+		case eslabel.Type:
+			namespacedName := types.NamespacedName{
+				Namespace: s.Namespace,
+				Name:      s.Labels[eslabel.ClusterNameLabelName],
+			}
+			if _, exists := configuredESResources[namespacedName]; exists {
+				continue
+			}
 
-		esNsn := types.NamespacedName{
-			Namespace: s.Namespace,
-			Name:      s.Labels[eslabel.ClusterNameLabelName],
-		}
-		_, exist := configuredEs[esNsn]
-		if exist {
-			continue
-		}
+			log.V(1).Info("Reconcile empty file settings Secret for Elasticsearch",
+				"es_namespace", namespacedName.Namespace, "es_name", namespacedName.Name,
+				"owner_namespace", softOwner.Namespace, "owner_name", softOwner.Name)
 
-		log.V(1).Info("Reconcile empty file settings Secret for Elasticsearch",
-			"namespace", esNsn.Namespace, "es_name", esNsn.Name,
-			"owner_namespace", softOwner.Namespace, "owner_name", softOwner.Name)
+			var es esv1.Elasticsearch
+			err := c.Get(ctx, namespacedName, &es)
+			if err != nil && !apierrors.IsNotFound(err) {
+				return err
+			}
+			if apierrors.IsNotFound(err) {
+				// Elasticsearch has just been deleted
+				return nil
+			}
 
-		var es esv1.Elasticsearch
-		err := c.Get(ctx, esNsn, &es)
-		if err != nil && !apierrors.IsNotFound(err) {
-			return err
-		}
-		if apierrors.IsNotFound(err) {
-			// Elasticsearch has just been deleted
-			return nil
+			if err := filesettings.ReconcileEmptyFileSettingsSecret(ctx, c, es, false); err != nil {
+				return err
+			}
+		case kblabel.Type:
+			// Currently we do not reset labels for kibana, so we shouldn't hit this.
+			// Implement if needed in the future
+			continue
+		default:
+			return fmt.Errorf("secret configured for unknown application type %s", configuredApplicationType)
 		}
-
-		return filesettings.ReconcileEmptyFileSettingsSecret(ctx, c, es, false)
 	}
 	return nil
 }
 
-// deleteOrphanSoftOwnedSecrets deletes secrets for the Elasticsearch clusters that are no longer configured
+// deleteOrphanSoftOwnedSecrets deletes secrets for the Elasticsearch/Kibana clusters that are no longer configured
 // by a given StackConfigPolicy.
-func deleteOrphanSoftOwnedSecrets(ctx context.Context, c k8s.Client, softOwner types.NamespacedName, configuredEs esMap) error {
+func deleteOrphanSoftOwnedSecrets(
+	ctx context.Context,
+	c k8s.Client,
+	softOwner types.NamespacedName,
+	configuredESResources esMap,
+	configuredKibanaResources kbMap,
+	resourceType policyv1alpha1.ResourceType,
+) error {
 	var secrets corev1.SecretList
+	matchLabels := client.MatchingLabels{
+		reconciler.SoftOwnerNamespaceLabel:              softOwner.Namespace,
+		reconciler.SoftOwnerNameLabel:                   softOwner.Name,
+		reconciler.SoftOwnerKindLabel:                   policyv1alpha1.Kind,
+		commonlabels.StackConfigPolicyOnDeleteLabelName: commonlabels.OrphanSecretDeleteOnPolicyDelete,
+	}
+
+	if resourceType != "" {
+		matchLabels[commonv1.TypeLabelName] = string(resourceType)
+	}
 	if err := c.List(ctx,
 		&secrets,
 		// search in all namespaces
 		// restrict to secrets on which we set the soft owner labels
-		client.MatchingLabels{
-			reconciler.SoftOwnerNamespaceLabel:              softOwner.Namespace,
-			reconciler.SoftOwnerNameLabel:                   softOwner.Name,
-			reconciler.SoftOwnerKindLabel:                   policyv1alpha1.Kind,
-			commonlabels.StackConfigPolicyOnDeleteLabelName: commonlabels.OrphanObjectDeleteOnPolicyDelete,
-		},
+		matchLabels,
 	); err != nil {
 		return err
 	}
 
 	for i := range secrets.Items {
 		secret := secrets.Items[i]
-		esNsn := types.NamespacedName{
-			Namespace: secret.Namespace,
-			Name:      secret.Labels[eslabel.ClusterNameLabelName],
-		}
-		_, exist := configuredEs[esNsn]
-		if exist {
-			continue
+		exist := false
+		configuredApplicationType := secret.Labels[commonv1.TypeLabelName]
+
+		switch configuredApplicationType {
+		case eslabel.Type:
+			namespacedName := types.NamespacedName{
+				Namespace: secret.Namespace,
+				Name:      secret.Labels[eslabel.ClusterNameLabelName],
+			}
+			// check if they exist in the es map
+			if _, exist = configuredESResources[namespacedName]; exist {
+				continue
+			}
+		case kblabel.Type:
+			namespacedName := types.NamespacedName{
+				Namespace: secret.Namespace,
+				Name:      secret.Labels[kblabel.KibanaNameLabelName],
+			}
+			// check if they exist in the kb map
+			if _, exist = configuredKibanaResources[namespacedName]; exist {
+				continue
+			}
+		default:
+			return fmt.Errorf("secret configured for unknown application type %s", configuredApplicationType)
 		}
 
-		// given elasticsearchcluster is no longer managed by stack config policy, delete secret.
+		// given kibana/elasticsearch cluster is no longer managed by stack config policy, delete secret.
 		err := c.Delete(ctx, &secret)
 		if err != nil && !apierrors.IsNotFound(err) {
 			return err
@@ -533,22 +725,30 @@ func (r *ReconcileStackConfigPolicy) getClusterStateFileSettings(ctx context.Con
 	return clusterState.Metadata.ReservedState.FileSettings, nil
 }
 
-// elasticsearchConfigAndSecretMountsApplied checks if the elasticsearch config and secret mounts from the stack config policy have been applied to the Elasticsearch cluster.
-func elasticsearchConfigAndSecretMountsApplied(ctx context.Context, c k8s.Client, policy policyv1alpha1.StackConfigPolicy, es esv1.Elasticsearch) (bool, error) {
-	// Get Pods for the given Elasticsearch
-	podList := corev1.PodList{}
-	if err := c.List(ctx, &podList, client.MatchingLabels{
-		eslabel.ClusterNameLabelName: es.Name,
-	}); err != nil || len(podList.Items) == 0 {
-		return false, err
+func (r *ReconcileStackConfigPolicy) addDynamicWatchesOnAdditionalSecretMounts(policy policyv1alpha1.StackConfigPolicy) error {
+	// Add watches if there are additional secrets to be mounted
+	watcher := types.NamespacedName{
+		Name:      policy.Name,
+		Namespace: policy.Namespace,
 	}
 
-	elasticsearchAndMountsConfigHash := getElasticsearchConfigAndMountsHash(policy.Spec.Elasticsearch.Config, policy.Spec.Elasticsearch.SecretMounts)
-	for _, esPod := range podList.Items {
-		if esPod.Annotations[ElasticsearchConfigAndSecretMountsHashAnnotation] != elasticsearchAndMountsConfigHash {
-			return false, nil
-		}
+	var secretSources []commonv1.NamespacedSecretSource //nolint:prealloc
+	for _, secretMount := range policy.Spec.Elasticsearch.SecretMounts {
+		secretSources = append(secretSources, commonv1.NamespacedSecretSource{
+			SecretName: secretMount.SecretName,
+			Namespace:  policy.Namespace,
+		})
 	}
 
-	return true, nil
+	// Add dynamic watches on the secrets
+	return watches.WatchUserProvidedNamespacedSecrets(
+		watcher,
+		r.dynamicWatches,
+		additionalSecretMountsWatcherName(watcher),
+		secretSources,
+	)
+}
+
+func additionalSecretMountsWatcherName(watcher types.NamespacedName) string {
+	return fmt.Sprintf("%s-%s-additional-secret-mounts-watcher", watcher.Name, watcher.Namespace)
 }
diff --git a/pkg/controller/stackconfigpolicy/elasticsearch_config_settings.go b/pkg/controller/stackconfigpolicy/elasticsearch_config_settings.go
index 3fb7a86e8..af1eb1972 100644
--- a/pkg/controller/stackconfigpolicy/elasticsearch_config_settings.go
+++ b/pkg/controller/stackconfigpolicy/elasticsearch_config_settings.go
@@ -8,9 +8,12 @@ import (
 	"context"
 	"encoding/json"
 
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
 	commonv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/common/v1"
 	esv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/elasticsearch/v1"
 	policyv1alpha1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/stackconfigpolicy/v1alpha1"
+	commonannotation "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/annotation"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/hash"
 	commonlabels "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/labels"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/reconciler"
@@ -24,24 +27,27 @@ import (
 )
 
 const (
-	ElasticSearchConfigKey                           = "elasticsearch.json"
-	SecretsMountKey                                  = "secretMounts.json"
-	ElasticsearchConfigAndSecretMountsHashAnnotation = "policy.k8s.elastic.co/elasticsearch-config-mounts-hash" //nolint:gosec
-	SourceSecretAnnotationName                       = "policy.k8s.elastic.co/source-secret-name"               //nolint:gosec
+	ElasticSearchConfigKey = "elasticsearch.json"
+	SecretsMountKey        = "secretMounts.json"
 )
 
 func newElasticsearchConfigSecret(policy policyv1alpha1.StackConfigPolicy, es esv1.Elasticsearch) (corev1.Secret, error) {
-	secretMountBytes, err := json.Marshal(policy.Spec.Elasticsearch.SecretMounts)
-	if err != nil {
-		return corev1.Secret{}, err
+	data := make(map[string][]byte)
+	if len(policy.Spec.Elasticsearch.SecretMounts) > 0 {
+		secretMountBytes, err := json.Marshal(policy.Spec.Elasticsearch.SecretMounts)
+		if err != nil {
+			return corev1.Secret{}, err
+		}
+		data[SecretsMountKey] = secretMountBytes
 	}
+
 	elasticsearchAndMountsConfigHash := getElasticsearchConfigAndMountsHash(policy.Spec.Elasticsearch.Config, policy.Spec.Elasticsearch.SecretMounts)
-	var configDataJSONBytes []byte
 	if policy.Spec.Elasticsearch.Config != nil {
-		configDataJSONBytes, err = policy.Spec.Elasticsearch.Config.MarshalJSON()
+		configDataJSONBytes, err := policy.Spec.Elasticsearch.Config.MarshalJSON()
 		if err != nil {
 			return corev1.Secret{}, err
 		}
+		data[ElasticSearchConfigKey] = configDataJSONBytes
 	}
 	elasticsearchConfigSecret := corev1.Secret{
 		ObjectMeta: metav1.ObjectMeta{
@@ -52,20 +58,17 @@ func newElasticsearchConfigSecret(policy policyv1alpha1.StackConfigPolicy, es es
 				Namespace: es.Namespace,
 			}),
 			Annotations: map[string]string{
-				ElasticsearchConfigAndSecretMountsHashAnnotation: elasticsearchAndMountsConfigHash,
+				commonannotation.ElasticsearchConfigAndSecretMountsHashAnnotation: elasticsearchAndMountsConfigHash,
 			},
 		},
-		Data: map[string][]byte{
-			ElasticSearchConfigKey: configDataJSONBytes,
-			SecretsMountKey:        secretMountBytes,
-		},
+		Data: data,
 	}
 
-	// Set Elasticsearch as the soft owner
+	// Set StackConfigPolicy as the soft owner
 	filesettings.SetSoftOwner(&elasticsearchConfigSecret, policy)
 
 	// Add label to delete secret on deletion of the stack config policy
-	elasticsearchConfigSecret.Labels[commonlabels.StackConfigPolicyOnDeleteLabelName] = commonlabels.OrphanObjectDeleteOnPolicyDelete
+	elasticsearchConfigSecret.Labels[commonlabels.StackConfigPolicyOnDeleteLabelName] = commonlabels.OrphanSecretDeleteOnPolicyDelete
 
 	return elasticsearchConfigSecret, nil
 }
@@ -93,7 +96,7 @@ func reconcileSecretMounts(ctx context.Context, c k8s.Client, es esv1.Elasticsea
 					Namespace: es.Namespace,
 				}),
 				Annotations: map[string]string{
-					SourceSecretAnnotationName: secretMount.SecretName,
+					commonannotation.SourceSecretAnnotationName: secretMount.SecretName,
 				},
 			},
 			Data: additionalSecret.Data,
@@ -103,10 +106,9 @@ func reconcileSecretMounts(ctx context.Context, c k8s.Client, es esv1.Elasticsea
 		filesettings.SetSoftOwner(&expected, *policy)
 
 		// Set the secret to be deleted when the stack config policy is deleted.
-		expected.Labels[commonlabels.StackConfigPolicyOnDeleteLabelName] = commonlabels.OrphanObjectDeleteOnPolicyDelete
+		expected.Labels[commonlabels.StackConfigPolicyOnDeleteLabelName] = commonlabels.OrphanSecretDeleteOnPolicyDelete
 
-		_, err := reconciler.ReconcileSecret(ctx, c, expected, nil)
-		if err != nil {
+		if _, err := reconciler.ReconcileSecret(ctx, c, expected, nil); err != nil {
 			return err
 		}
 	}
@@ -119,3 +121,23 @@ func getElasticsearchConfigAndMountsHash(elasticsearchConfig *commonv1.Config, s
 	}
 	return hash.HashObject(secretMounts)
 }
+
+// elasticsearchConfigAndSecretMountsApplied checks if the Elasticsearch config and secret mounts from the stack config policy have been applied to the Elasticsearch cluster.
+func elasticsearchConfigAndSecretMountsApplied(ctx context.Context, c k8s.Client, policy policyv1alpha1.StackConfigPolicy, es esv1.Elasticsearch) (bool, error) {
+	// Get Pods for the given Elasticsearch
+	podList := corev1.PodList{}
+	if err := c.List(ctx, &podList, client.InNamespace(es.Namespace), client.MatchingLabels{
+		eslabel.ClusterNameLabelName: es.Name,
+	}); err != nil || len(podList.Items) == 0 {
+		return false, err
+	}
+
+	elasticsearchAndMountsConfigHash := getElasticsearchConfigAndMountsHash(policy.Spec.Elasticsearch.Config, policy.Spec.Elasticsearch.SecretMounts)
+	for _, esPod := range podList.Items {
+		if esPod.Annotations[commonannotation.ElasticsearchConfigAndSecretMountsHashAnnotation] != elasticsearchAndMountsConfigHash {
+			return false, nil
+		}
+	}
+
+	return true, nil
+}
diff --git a/pkg/controller/stackconfigpolicy/kibana_config_settings.go b/pkg/controller/stackconfigpolicy/kibana_config_settings.go
new file mode 100644
index 000000000..116bbe504
--- /dev/null
+++ b/pkg/controller/stackconfigpolicy/kibana_config_settings.go
@@ -0,0 +1,143 @@
+// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+// or more contributor license agreements. Licensed under the Elastic License 2.0;
+// you may not use this file except in compliance with the Elastic License 2.0.
+
+package stackconfigpolicy
+
+import (
+	"context"
+	"encoding/json"
+
+	corev1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	commonv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/common/v1"
+	kibanav1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/kibana/v1"
+	policyv1alpha1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/stackconfigpolicy/v1alpha1"
+	commonannotation "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/annotation"
+	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/hash"
+	commonlabels "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/labels"
+	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/reconciler"
+	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/elasticsearch/filesettings"
+	kblabel "github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/label"
+	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/k8s"
+)
+
+const (
+	KibanaConfigKey = "kibana.json"
+)
+
+func newKibanaConfigSecret(policy policyv1alpha1.StackConfigPolicy, kibana kibanav1.Kibana) (corev1.Secret, error) {
+	kibanaConfigHash := getKibanaConfigHash(policy.Spec.Kibana.Config)
+	configDataJSONBytes := []byte("")
+	var err error
+	if policy.Spec.Kibana.Config != nil {
+		if configDataJSONBytes, err = policy.Spec.Kibana.Config.MarshalJSON(); err != nil {
+			return corev1.Secret{}, err
+		}
+	}
+	kibanaConfigSecret := corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: kibana.Namespace,
+			Name:      GetPolicyConfigSecretName(kibana.Name),
+			Labels: kblabel.NewLabels(types.NamespacedName{
+				Name:      kibana.Name,
+				Namespace: kibana.Namespace,
+			}),
+			Annotations: map[string]string{
+				commonannotation.KibanaConfigHashAnnotation: kibanaConfigHash,
+			},
+		},
+		Data: map[string][]byte{
+			KibanaConfigKey: configDataJSONBytes,
+		},
+	}
+
+	// Set policy as the soft owner
+	filesettings.SetSoftOwner(&kibanaConfigSecret, policy)
+
+	// Add label to delete secret on deletion of the stack config policy
+	kibanaConfigSecret.Labels[commonlabels.StackConfigPolicyOnDeleteLabelName] = commonlabels.OrphanSecretDeleteOnPolicyDelete
+
+	// Add SecureSettings as annotation
+	if err = setKibanaSecureSettings(&kibanaConfigSecret, policy); err != nil {
+		return kibanaConfigSecret, err
+	}
+
+	return kibanaConfigSecret, nil
+}
+
+func getKibanaConfigHash(kibanaConfig *commonv1.Config) string {
+	if kibanaConfig != nil {
+		return hash.HashObject(kibanaConfig)
+	}
+	return ""
+}
+
+func GetPolicyConfigSecretName(kibanaName string) string {
+	return kibanaName + "-kb-policy-config"
+}
+
+func kibanaConfigApplied(c k8s.Client, policy policyv1alpha1.StackConfigPolicy, kb kibanav1.Kibana) (bool, error) {
+	existingKibanaPods, err := k8s.PodsMatchingLabels(c, kb.Namespace, map[string]string{"kibana.k8s.elastic.co/name": kb.Name})
+	if err != nil || len(existingKibanaPods) == 0 {
+		return false, err
+	}
+
+	kibanaConfigHash := getKibanaConfigHash(policy.Spec.Kibana.Config)
+	for _, kbPod := range existingKibanaPods {
+		if kbPod.Annotations[commonannotation.KibanaConfigHashAnnotation] != kibanaConfigHash {
+			return false, nil
+		}
+	}
+
+	return true, nil
+}
+
+func canBeOwned(ctx context.Context, c k8s.Client, policy policyv1alpha1.StackConfigPolicy, kb kibanav1.Kibana) (reconciler.SoftOwnerRef, bool, error) {
+	// Check if the secret already exists
+	var kibanaConfigSecret corev1.Secret
+	err := c.Get(ctx, types.NamespacedName{
+		Name:      GetPolicyConfigSecretName(kb.Name),
+		Namespace: kb.Namespace,
+	}, &kibanaConfigSecret)
+	if err != nil && !apierrors.IsNotFound(err) {
+		return reconciler.SoftOwnerRef{}, false, err
+	}
+
+	if apierrors.IsNotFound(err) {
+		// Secret does not exist, return true
+		return reconciler.SoftOwnerRef{}, true, nil
+	}
+
+	currentOwner, referenced := reconciler.SoftOwnerRefFromLabels(kibanaConfigSecret.Labels)
+	// either there is no soft owner
+	if !referenced {
+		return currentOwner, true, nil
+	}
+	// or the owner is already the given policy
+	canBeOwned := currentOwner.Kind == policy.Kind && currentOwner.Namespace == policy.Namespace && currentOwner.Name == policy.Name
+	return currentOwner, canBeOwned, nil
+}
+
+// setKibanaSecureSettings stores the SecureSettings Secret sources referenced in the given StackConfigPolicy for Kibana in the annotation of the Kibana config Secret.
+func setKibanaSecureSettings(settingsSecret *corev1.Secret, policy policyv1alpha1.StackConfigPolicy) error {
+	if len(policy.Spec.Kibana.SecureSettings) == 0 {
+		return nil
+	}
+
+	var secretSources []commonv1.NamespacedSecretSource //nolint:prealloc
+	// SecureSettings field under Kibana in the StackConfigPolicy
+	for _, src := range policy.Spec.Kibana.SecureSettings {
+		secretSources = append(secretSources, commonv1.NamespacedSecretSource{Namespace: policy.GetNamespace(), SecretName: src.SecretName, Entries: src.Entries})
+	}
+
+	bytes, err := json.Marshal(secretSources)
+	if err != nil {
+		return err
+	}
+	settingsSecret.Annotations[commonannotation.SecureSettingsSecretsAnnotationName] = string(bytes)
+	return nil
+}
diff --git a/pkg/controller/stackconfigpolicy/secure_setings.go b/pkg/controller/stackconfigpolicy/secure_setings.go
new file mode 100644
index 000000000..4adde2439
--- /dev/null
+++ b/pkg/controller/stackconfigpolicy/secure_setings.go
@@ -0,0 +1,56 @@
+// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+// or more contributor license agreements. Licensed under the Elastic License 2.0;
+// you may not use this file except in compliance with the Elastic License 2.0.
+
+package stackconfigpolicy
+
+import (
+	"context"
+	"encoding/json"
+
+	corev1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	commonv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/common/v1"
+	commonannotation "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/annotation"
+	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/elasticsearch/filesettings"
+	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/k8s"
+	ulog "github.com/elastic/cloud-on-k8s/v2/pkg/utils/log"
+)
+
+func GetSecureSettingsSecretSourcesForResources(ctx context.Context, kubeClient k8s.Client, resource metav1.Object, resourceKind string) ([]commonv1.NamespacedSecretSource, error) {
+	switch resourceKind {
+	case "Elasticsearch":
+		return filesettings.GetSecureSettingsSecretSources(ctx, kubeClient, resource)
+	case "Kibana":
+		return getKibanaSecureSettingsSecretSources(ctx, kubeClient, resource)
+	default:
+		// Just return empty since there are no other resource type monitored by the stack config policy
+		log := ulog.FromContext(ctx)
+		log.Info("Unknown resource kind, stackconfigpolicy only monitors Elasticsearch and Kibana resources", "resource kind", resourceKind)
+		return []commonv1.NamespacedSecretSource{}, nil
+	}
+}
+
+func getKibanaSecureSettingsSecretSources(ctx context.Context, kubeClient k8s.Client, resource metav1.Object) ([]commonv1.NamespacedSecretSource, error) {
+	var secret corev1.Secret
+	if err := kubeClient.Get(ctx, types.NamespacedName{Namespace: resource.GetNamespace(), Name: GetPolicyConfigSecretName(resource.GetName())}, &secret); err != nil {
+		if apierrors.IsNotFound(err) {
+			return []commonv1.NamespacedSecretSource{}, nil
+		}
+		return nil, err
+	}
+
+	rawString, ok := secret.Annotations[commonannotation.SecureSettingsSecretsAnnotationName]
+	if !ok {
+		return []commonv1.NamespacedSecretSource{}, nil
+	}
+	var secretSources []commonv1.NamespacedSecretSource
+	if err := json.Unmarshal([]byte(rawString), &secretSources); err != nil {
+		return nil, err
+	}
+
+	return secretSources, nil
+}
diff --git a/pkg/utils/maps/maps.go b/pkg/utils/maps/maps.go
index 2e4d88395..d2dfd4f89 100644
--- a/pkg/utils/maps/maps.go
+++ b/pkg/utils/maps/maps.go
@@ -19,6 +19,16 @@ func IsSubset(toCheck, fullSet map[string]string) bool {
 	return true
 }
 
+// IsEqualSubset compares the values of the given subset of keys between two maps and returns true if they are equal.
+func IsEqualSubset(map1, map2 map[string]string, keySubSet []string) bool {
+	for _, key := range keySubSet {
+		if map1[key] != map2[key] {
+			return false
+		}
+	}
+	return true
+}
+
 // Merge merges source into destination, overwriting existing values if necessary.
 func Merge(dest, src map[string]string) map[string]string {
 	if dest == nil {



Test patch:
diff --git a/pkg/apis/stackconfigpolicy/v1alpha1/webhook_test.go b/pkg/apis/stackconfigpolicy/v1alpha1/webhook_test.go
index 9f06b9dbb..324b6b128 100644
--- a/pkg/apis/stackconfigpolicy/v1alpha1/webhook_test.go
+++ b/pkg/apis/stackconfigpolicy/v1alpha1/webhook_test.go
@@ -42,6 +42,20 @@ func TestWebhook(t *testing.T) {
 			},
 			Check: test.ValidationWebhookSucceeded,
 		},
+		{
+			Name:      "create-valid-kibana",
+			Operation: admissionv1beta1.Create,
+			Object: func(t *testing.T, uid string) []byte {
+				t.Helper()
+				m := mkStackConfigPolicy(uid)
+				m.Spec.Elasticsearch = policyv1alpha1.ElasticsearchConfigPolicySpec{}
+				m.Spec.Kibana = policyv1alpha1.KibanaConfigPolicySpec{
+					Config: &commonv1.Config{Data: map[string]interface{}{"a": "b"}},
+				}
+				return serialize(t, m)
+			},
+			Check: test.ValidationWebhookSucceeded,
+		},
 		{
 			Name:      "unknown-field",
 			Operation: admissionv1beta1.Create,
@@ -87,7 +101,7 @@ func TestWebhook(t *testing.T) {
 				return serialize(t, m)
 			},
 			Check: test.ValidationWebhookFailed(
-				"Elasticsearch settings are mandatory and must not be empty",
+				"One out of Elasticsearch or Kibana settings is mandatory, both must not be empty",
 			),
 		},
 		{
diff --git a/pkg/controller/elasticsearch/filesettings/reconciler_test.go b/pkg/controller/elasticsearch/filesettings/reconciler_test.go
index c1d1af92a..ba1afd2a7 100644
--- a/pkg/controller/elasticsearch/filesettings/reconciler_test.go
+++ b/pkg/controller/elasticsearch/filesettings/reconciler_test.go
@@ -168,6 +168,26 @@ func TestReconcileSecret(t *testing.T) {
 				}),
 			),
 		},
+		{
+			name: "remove secure settings from expected",
+			c: k8s.NewFakeClient(withOwnerRef(t, createSecret("s", sampleData,
+				map[string]string{"existing": "existing"},
+				map[string]string{
+					"policy.k8s.elastic.co/secure-settings-secrets": `[{"secretName":"secret-1"}]`,
+					"policy.k8s.elastic.co/settings-hash":           "hash-1",
+				}),
+			)),
+			expected: createSecret("s", sampleData, map[string]string{"existing": "existing"}, map[string]string{
+				"policy.k8s.elastic.co/settings-hash": "hash-1",
+			}),
+			want: withOwnerRef(t, createSecret("s", sampleData,
+				map[string]string{
+					"existing": "existing", // keep existing
+				}, map[string]string{
+					"policy.k8s.elastic.co/settings-hash": "hash-1",
+				}),
+			),
+		},
 		{
 			name: "override soft owner labels",
 			c: k8s.NewFakeClient(withOwnerRef(t, createSecret("s", sampleData,
@@ -197,7 +217,7 @@ func TestReconcileSecret(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			err := ReconcileSecret(context.Background(), tt.c, *tt.expected, *owner)
+			err := ReconcileSecret(context.Background(), tt.c, *tt.expected, owner)
 			require.NoError(t, err)
 
 			var secret corev1.Secret
diff --git a/pkg/controller/elasticsearch/nodespec/podspec_test.go b/pkg/controller/elasticsearch/nodespec/podspec_test.go
index 6d03b9d5a..22a047c43 100644
--- a/pkg/controller/elasticsearch/nodespec/podspec_test.go
+++ b/pkg/controller/elasticsearch/nodespec/podspec_test.go
@@ -20,6 +20,7 @@ import (
 	commonv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/common/v1"
 	esv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/elasticsearch/v1"
 	policyv1alpha1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/stackconfigpolicy/v1alpha1"
+	commonannotation "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/annotation"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/defaults"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/hash"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/keystore"
@@ -30,7 +31,6 @@ import (
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/elasticsearch/settings"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/elasticsearch/user"
 	esvolume "github.com/elastic/cloud-on-k8s/v2/pkg/controller/elasticsearch/volume"
-	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/stackconfigpolicy"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/k8s"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/pointer"
 )
@@ -245,7 +245,7 @@ func TestBuildPodTemplateSpec(t *testing.T) {
 			volume.NewSecretVolumeWithMountPath("test-es-secretname", "test-es-secretname", "/usr/test"),
 		},
 		PolicyAnnotations: map[string]string{
-			stackconfigpolicy.ElasticsearchConfigAndSecretMountsHashAnnotation: elasticsearchConfigAndMountsHash,
+			commonannotation.ElasticsearchConfigAndSecretMountsHashAnnotation: elasticsearchConfigAndMountsHash,
 		},
 	}
 
@@ -475,11 +475,11 @@ func Test_buildAnnotations(t *testing.T) {
 			name: "With policy annotations",
 			args: args{
 				policyAnnotations: map[string]string{
-					stackconfigpolicy.ElasticsearchConfigAndSecretMountsHashAnnotation: "testhash",
+					commonannotation.ElasticsearchConfigAndSecretMountsHashAnnotation: "testhash",
 				},
 			},
 			expectedAnnotations: map[string]string{
-				stackconfigpolicy.ElasticsearchConfigAndSecretMountsHashAnnotation: "testhash",
+				commonannotation.ElasticsearchConfigAndSecretMountsHashAnnotation: "testhash",
 			},
 		},
 	}
diff --git a/pkg/controller/elasticsearch/nodespec/policy_config_test.go b/pkg/controller/elasticsearch/nodespec/policy_config_test.go
index cad12794a..5b33794e7 100644
--- a/pkg/controller/elasticsearch/nodespec/policy_config_test.go
+++ b/pkg/controller/elasticsearch/nodespec/policy_config_test.go
@@ -13,6 +13,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	esv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/elasticsearch/v1"
+	commonannotation "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/annotation"
 	common "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/settings"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/volume"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/stackconfigpolicy"
@@ -103,7 +104,7 @@ func mkConfigSecret(name string, namespace string) corev1.Secret {
 			Name:      name,
 			Namespace: namespace,
 			Annotations: map[string]string{
-				stackconfigpolicy.ElasticsearchConfigAndSecretMountsHashAnnotation: "testhash",
+				commonannotation.ElasticsearchConfigAndSecretMountsHashAnnotation: "testhash",
 			},
 		},
 		Data: map[string][]byte{stackconfigpolicy.ElasticSearchConfigKey: []byte(`{"logger.org.elasticsearch.discovery": "DEBUG"}`),
diff --git a/pkg/controller/kibana/config_reconcile_test.go b/pkg/controller/kibana/config_reconcile_test.go
index 7908eb0a4..3c8302715 100644
--- a/pkg/controller/kibana/config_reconcile_test.go
+++ b/pkg/controller/kibana/config_reconcile_test.go
@@ -17,6 +17,7 @@ import (
 	kbv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/kibana/v1"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/settings"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/version"
+	kblabel "github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/label"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/k8s"
 )
 
@@ -63,7 +64,7 @@ func TestReconcileConfigSecret(t *testing.T) {
 						ObjectMeta: metav1.ObjectMeta{
 							Name:      "test-kb-config",
 							Namespace: "test-ns",
-							Labels:    map[string]string{KibanaNameLabelName: defaultKibana.Name},
+							Labels:    map[string]string{kblabel.KibanaNameLabelName: defaultKibana.Name},
 						},
 						Data: map[string][]byte{},
 					}},
@@ -84,7 +85,7 @@ func TestReconcileConfigSecret(t *testing.T) {
 						ObjectMeta: metav1.ObjectMeta{
 							Name:      "test-kb-config",
 							Namespace: "test-ns",
-							Labels:    map[string]string{KibanaNameLabelName: defaultKibana.Name},
+							Labels:    map[string]string{kblabel.KibanaNameLabelName: defaultKibana.Name},
 						},
 						Data: map[string][]byte{
 							SettingsFilename: []byte("eW8h"),
@@ -108,7 +109,7 @@ func TestReconcileConfigSecret(t *testing.T) {
 			assert.NoError(t, err)
 
 			var secrets corev1.SecretList
-			labelSelector := client.MatchingLabels(map[string]string{KibanaNameLabelName: tt.args.kb.Name})
+			labelSelector := client.MatchingLabels(map[string]string{kblabel.KibanaNameLabelName: tt.args.kb.Name})
 			err = k8sClient.List(context.Background(), &secrets, labelSelector)
 			assert.NoError(t, err)
 			err = tt.assertions(secrets)
diff --git a/pkg/controller/kibana/config_settings_test.go b/pkg/controller/kibana/config_settings_test.go
index 62db5c1a5..70236ee82 100644
--- a/pkg/controller/kibana/config_settings_test.go
+++ b/pkg/controller/kibana/config_settings_test.go
@@ -194,9 +194,10 @@ func TestNewConfigSettings(t *testing.T) {
 		},
 	}
 	type args struct {
-		client   k8s.Client
-		kb       func() kbv1.Kibana
-		ipFamily corev1.IPFamily
+		client                 k8s.Client
+		kb                     func() kbv1.Kibana
+		ipFamily               corev1.IPFamily
+		kibanaConfigFromPolicy *settings.CanonicalConfig
 	}
 	tests := []struct {
 		name    string
@@ -495,6 +496,24 @@ func TestNewConfigSettings(t *testing.T) {
 			},
 			want: append(defaultConfig, []byte(`foo: bar`)...),
 		},
+		{
+			name: "with kibana config from stackconfigpolicy",
+			args: args{
+				client: k8s.NewFakeClient(existingSecret),
+				kb: func() kbv1.Kibana {
+					kb := mkKibana()
+					kb.Spec.Config = &commonv1.Config{
+						Data: map[string]interface{}{
+							"foo": "bar",
+						},
+					}
+					return kb
+				},
+				ipFamily:               corev1.IPv4Protocol,
+				kibanaConfigFromPolicy: settings.MustCanonicalConfig(map[string]interface{}{"foo": "bars"}),
+			},
+			want: append(defaultConfig, []byte(`foo: bars`)...),
+		},
 		{
 			name: "test existing secret does not prevent updates to config, e.g. spec takes precedence even if there is a secret indicating otherwise",
 			args: args{
@@ -545,7 +564,7 @@ func TestNewConfigSettings(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			kb := tt.args.kb()
 			v := version.From(7, 6, 0)
-			got, err := NewConfigSettings(context.Background(), tt.args.client, kb, v, tt.args.ipFamily)
+			got, err := NewConfigSettings(context.Background(), tt.args.client, kb, v, tt.args.ipFamily, tt.args.kibanaConfigFromPolicy)
 			if tt.wantErr {
 				require.Error(t, err)
 			}
@@ -571,7 +590,7 @@ func TestNewConfigSettingsCreateEncryptionKeys(t *testing.T) {
 	client := k8s.NewFakeClient()
 	kb := mkKibana()
 	v := version.MustParse(kb.Spec.Version)
-	got, err := NewConfigSettings(context.Background(), client, kb, v, corev1.IPv4Protocol)
+	got, err := NewConfigSettings(context.Background(), client, kb, v, corev1.IPv4Protocol, nil)
 	require.NoError(t, err)
 	for _, key := range []string{XpackSecurityEncryptionKey, XpackReportingEncryptionKey, XpackEncryptedSavedObjectsEncryptionKey} {
 		val, err := (*ucfg.Config)(got.CanonicalConfig).String(key, -1, settings.Options...)
@@ -597,7 +616,7 @@ func TestNewConfigSettingsExistingEncryptionKey(t *testing.T) {
 	}
 	client := k8s.NewFakeClient(existingSecret)
 	v := version.MustParse(kb.Spec.Version)
-	got, err := NewConfigSettings(context.Background(), client, kb, v, corev1.IPv4Protocol)
+	got, err := NewConfigSettings(context.Background(), client, kb, v, corev1.IPv4Protocol, nil)
 	require.NoError(t, err)
 	var gotCfg map[string]interface{}
 	require.NoError(t, got.Unpack(&gotCfg))
@@ -626,7 +645,7 @@ func TestNewConfigSettingsExplicitEncryptionKey(t *testing.T) {
 	kb.Spec.Config = &cfg
 	client := k8s.NewFakeClient()
 	v := version.MustParse(kb.Spec.Version)
-	got, err := NewConfigSettings(context.Background(), client, kb, v, corev1.IPv4Protocol)
+	got, err := NewConfigSettings(context.Background(), client, kb, v, corev1.IPv4Protocol, nil)
 	require.NoError(t, err)
 	val, err := (*ucfg.Config)(got.CanonicalConfig).String(XpackSecurityEncryptionKey, -1, settings.Options...)
 	require.NoError(t, err)
@@ -639,7 +658,7 @@ func TestNewConfigSettingsPre760(t *testing.T) {
 	kb.Spec.Version = "7.5.0"
 	client := k8s.NewFakeClient()
 	v := version.MustParse(kb.Spec.Version)
-	got, err := NewConfigSettings(context.Background(), client, kb, v, corev1.IPv4Protocol)
+	got, err := NewConfigSettings(context.Background(), client, kb, v, corev1.IPv4Protocol, nil)
 	require.NoError(t, err)
 	assert.Equal(t, 0, len(got.CanonicalConfig.HasKeys([]string{XpackEncryptedSavedObjects})))
 }
diff --git a/pkg/controller/kibana/driver_test.go b/pkg/controller/kibana/driver_test.go
index 35ed33bba..0f8af3c6a 100644
--- a/pkg/controller/kibana/driver_test.go
+++ b/pkg/controller/kibana/driver_test.go
@@ -27,6 +27,7 @@ import (
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/deployment"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/watches"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/elasticsearch/settings"
+	kblabel "github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/label"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/network"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/compare"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/k8s"
@@ -49,8 +50,8 @@ func Test_getStrategyType(t *testing.T) {
 					Name:      fmt.Sprintf("pod-%v-%v-%v", kbName, version, i),
 					Namespace: "default",
 					Labels: map[string]string{
-						KibanaNameLabelName:    kbName,
-						KibanaVersionLabelName: version,
+						kblabel.KibanaNameLabelName:    kbName,
+						kblabel.KibanaVersionLabelName: version,
 					},
 				},
 			})
@@ -65,7 +66,7 @@ func Test_getStrategyType(t *testing.T) {
 				t.FailNow()
 			}
 
-			delete(pod.Labels, KibanaVersionLabelName)
+			delete(pod.Labels, kblabel.KibanaVersionLabelName)
 		}
 
 		return objects
@@ -196,8 +197,9 @@ func Test_getStrategyType(t *testing.T) {
 
 func TestDriverDeploymentParams(t *testing.T) {
 	type args struct {
-		kb             func() *kbv1.Kibana
-		initialObjects func() []client.Object
+		kb                func() *kbv1.Kibana
+		initialObjects    func() []client.Object
+		policyAnnotations map[string]string
 	}
 
 	tests := []struct {
@@ -224,6 +226,16 @@ func TestDriverDeploymentParams(t *testing.T) {
 			want:    expectedDeploymentParams(),
 			wantErr: false,
 		},
+		{
+			name: "with policy annotations",
+			args: args{
+				kb:                kibanaFixture,
+				initialObjects:    defaultInitialObjects,
+				policyAnnotations: map[string]string{"policy.k8s.elastic.co/kibana-config-hash": "2123345"},
+			},
+			want:    expectedDeploymentWithPolicyAnnotations(map[string]string{"policy.k8s.elastic.co/kibana-config-hash": "2123345"}),
+			wantErr: false,
+		},
 		{
 			name: "with TLS disabled",
 			args: args{
@@ -363,7 +375,7 @@ func TestDriverDeploymentParams(t *testing.T) {
 			d, err := newDriver(client, w, record.NewFakeRecorder(100), kb, corev1.IPv4Protocol)
 			require.NoError(t, err)
 
-			got, err := d.deploymentParams(context.Background(), kb)
+			got, err := d.deploymentParams(context.Background(), kb, tt.args.policyAnnotations)
 			if tt.wantErr {
 				require.Error(t, err)
 				return
@@ -587,6 +599,15 @@ func expectedDeploymentParams() deployment.Params {
 	}
 }
 
+func expectedDeploymentWithPolicyAnnotations(policyAnnotations map[string]string) deployment.Params {
+	deploymentParams := expectedDeploymentParams()
+
+	for k, v := range policyAnnotations {
+		deploymentParams.PodTemplateSpec.Annotations[k] = v
+	}
+	return deploymentParams
+}
+
 func kibanaFixture() *kbv1.Kibana {
 	kbFixture := &kbv1.Kibana{
 		ObjectMeta: metav1.ObjectMeta{
@@ -769,8 +790,8 @@ func mkService() corev1.Service {
 			Name:      "kibana-test-kb-http",
 			Namespace: "test",
 			Labels: map[string]string{
-				KibanaNameLabelName:    "kibana-test",
-				commonv1.TypeLabelName: Type,
+				kblabel.KibanaNameLabelName: "kibana-test",
+				commonv1.TypeLabelName:      kblabel.Type,
 			},
 		},
 		Spec: corev1.ServiceSpec{
@@ -782,8 +803,8 @@ func mkService() corev1.Service {
 				},
 			},
 			Selector: map[string]string{
-				KibanaNameLabelName:    "kibana-test",
-				commonv1.TypeLabelName: Type,
+				kblabel.KibanaNameLabelName: "kibana-test",
+				commonv1.TypeLabelName:      kblabel.Type,
 			},
 		},
 	}
diff --git a/pkg/controller/kibana/pod_test.go b/pkg/controller/kibana/pod_test.go
index 80651ed5b..567331692 100644
--- a/pkg/controller/kibana/pod_test.go
+++ b/pkg/controller/kibana/pod_test.go
@@ -20,6 +20,7 @@ import (
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/keystore"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/version"
 	commonvolume "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/volume"
+	kblabel "github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/label"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/network"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/k8s"
 )
@@ -149,9 +150,9 @@ func TestNewPodTemplateSpec(t *testing.T) {
 					PodTemplate: corev1.PodTemplateSpec{
 						ObjectMeta: metav1.ObjectMeta{
 							Labels: map[string]string{
-								"label1":            "value1",
-								"label2":            "value2",
-								KibanaNameLabelName: "overridden-kibana-name",
+								"label1":                    "value1",
+								"label2":                    "value2",
+								kblabel.KibanaNameLabelName: "overridden-kibana-name",
 							},
 						},
 					},
@@ -159,10 +160,10 @@ func TestNewPodTemplateSpec(t *testing.T) {
 				}},
 			assertions: func(pod corev1.PodTemplateSpec) {
 				labels := (&kbv1.Kibana{ObjectMeta: metav1.ObjectMeta{Name: "kibana-name"}}).GetIdentityLabels()
-				labels[KibanaVersionLabelName] = "7.4.0"
+				labels[kblabel.KibanaVersionLabelName] = "7.4.0"
 				labels["label1"] = "value1"
 				labels["label2"] = "value2"
-				labels[KibanaNameLabelName] = "overridden-kibana-name"
+				labels[kblabel.KibanaNameLabelName] = "overridden-kibana-name"
 				assert.Equal(t, labels, pod.Labels)
 			},
 		},
diff --git a/pkg/controller/kibana/policy_config_setting_test.go b/pkg/controller/kibana/policy_config_setting_test.go
new file mode 100644
index 000000000..7c28b19bc
--- /dev/null
+++ b/pkg/controller/kibana/policy_config_setting_test.go
@@ -0,0 +1,117 @@
+// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+// or more contributor license agreements. Licensed under the Elastic License 2.0;
+// you may not use this file except in compliance with the Elastic License 2.0.
+
+package kibana
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	kibanav1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/kibana/v1"
+	common "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/settings"
+	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/k8s"
+)
+
+func Test_getPolicyConfig(t *testing.T) {
+	canonicalConfig := common.MustCanonicalConfig(map[string]interface{}{
+		"xpack.canvas.enabled": true,
+	})
+	for _, tt := range []struct {
+		name    string
+		kb      kibanav1.Kibana
+		want    PolicyConfig
+		wantErr bool
+		client  k8s.Client
+	}{
+		{
+			name: "create valid policy config",
+			kb: kibanav1.Kibana{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-kb",
+					Namespace: "test-ns",
+				},
+			},
+			want: PolicyConfig{
+				KibanaConfig: canonicalConfig,
+				PodAnnotations: map[string]string{
+					"policy.k8s.elastic.co/kibana-config-hash": "123456",
+				},
+			},
+			client: k8s.NewFakeClient(mkKibanaConfigSecret("test-ns", "test-policy", "test-policy-ns", "123456")),
+		},
+		{
+			name: "create invalid policy config",
+			kb: kibanav1.Kibana{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-kb",
+					Namespace: "test-ns",
+				},
+			},
+			want: PolicyConfig{
+				PodAnnotations: map[string]string{
+					"policy.k8s.elastic.co/kibana-config-hash": "123456",
+				},
+			},
+			wantErr: true,
+			client:  k8s.NewFakeClient(mkInvalidConfigSecret("test-ns", "test-policy", "test-policy-ns", "123456")),
+		},
+		{
+			name: "policy kibana config secret not present",
+			kb: kibanav1.Kibana{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-kb",
+					Namespace: "test-ns",
+				},
+			},
+			want:    PolicyConfig{},
+			wantErr: false,
+			client:  k8s.NewFakeClient(),
+		},
+	} {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := getPolicyConfig(context.Background(), tt.client, tt.kb)
+			if !tt.wantErr {
+				require.NoError(t, err)
+			} else {
+				require.Error(t, err)
+			}
+			require.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func mkKibanaConfigSecret(namespace string, owningPolicyName string, owningPolicyNamespace string, hashValue string) *corev1.Secret {
+	return &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: namespace,
+			Name:      "test-kb-kb-policy-config",
+			Labels: map[string]string{
+				"asset.policy.k8s.elastic.co/on-delete": "delete",
+				"kibana.k8s.elastic.co/name":            "test-kb",
+				"common.k8s.elastic.co/type":            "kibana",
+				"eck.k8s.elastic.co/owner-kind":         "StackConfigPolicy",
+				"eck.k8s.elastic.co/owner-name":         owningPolicyName,
+				"eck.k8s.elastic.co/owner-namespace":    owningPolicyNamespace,
+			},
+			Annotations: map[string]string{
+				"policy.k8s.elastic.co/kibana-config-hash": hashValue,
+			},
+		},
+		Data: map[string][]byte{
+			"kibana.json": []byte(`{"xpack.canvas.enabled":true}`),
+		},
+	}
+}
+
+func mkInvalidConfigSecret(namespace string, owningPolicyName string, owningPolicyNamespace string, hashValue string) *corev1.Secret {
+	secret := mkKibanaConfigSecret(namespace, owningPolicyName, owningPolicyNamespace, hashValue)
+	secret.Data = map[string][]byte{
+		"kibana.json": []byte(`test`),
+	}
+	return secret
+}
diff --git a/pkg/controller/stackconfigpolicy/controller_test.go b/pkg/controller/stackconfigpolicy/controller_test.go
index 7438fded3..252595f98 100644
--- a/pkg/controller/stackconfigpolicy/controller_test.go
+++ b/pkg/controller/stackconfigpolicy/controller_test.go
@@ -11,6 +11,7 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 	corev1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -21,12 +22,15 @@ import (
 
 	commonv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/common/v1"
 	esv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/elasticsearch/v1"
+	kibanav1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/kibana/v1"
 	policyv1alpha1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/stackconfigpolicy/v1alpha1"
+	commonannotation "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/annotation"
 	commonesclient "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/esclient"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/hash"
 	commonlabels "github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/labels"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/license"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/reconciler"
+	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/watches"
 	esclient "github.com/elastic/cloud-on-k8s/v2/pkg/controller/elasticsearch/client"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/elasticsearch/filesettings"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/elasticsearch/label"
@@ -113,6 +117,11 @@ func TestReconcileStackConfigPolicy_Reconcile(t *testing.T) {
 		},
 		Spec: policyv1alpha1.StackConfigPolicySpec{
 			ResourceSelector: metav1.LabelSelector{MatchLabels: map[string]string{"label": "test"}},
+			SecureSettings: []commonv1.SecretSource{
+				{
+					SecretName: "shared-secret1",
+				},
+			},
 			Elasticsearch: policyv1alpha1.ElasticsearchConfigPolicySpec{
 				ClusterSettings: &commonv1.Config{Data: map[string]interface{}{
 					"indices.recovery.max_bytes_per_sec": "42mb",
@@ -123,17 +132,34 @@ func TestReconcileStackConfigPolicy_Reconcile(t *testing.T) {
 						MountPath:  "/usr/test",
 					},
 				},
+				SecureSettings: []commonv1.SecretSource{
+					{
+						SecretName: "shared-secret",
+					},
+				},
 				Config: &commonv1.Config{
 					Data: map[string]interface{}{
 						"logger.org.elasticsearch.discovery": "DEBUG",
 					},
 				},
 			},
+			Kibana: policyv1alpha1.KibanaConfigPolicySpec{
+				Config: &commonv1.Config{
+					Data: map[string]interface{}{
+						"xpack.canvas.enabled": true,
+					},
+				},
+				SecureSettings: []commonv1.SecretSource{
+					{
+						SecretName: "shared-secret",
+					},
+				},
+			},
 		},
 	}
 	elasticsearchConfigAndMountsHash := getElasticsearchConfigAndMountsHash(policyFixture.Spec.Elasticsearch.Config, policyFixture.Spec.Elasticsearch.SecretMounts)
 	esPodFixture := getEsPod("ns", map[string]string{
-		ElasticsearchConfigAndSecretMountsHashAnnotation: elasticsearchConfigAndMountsHash,
+		commonannotation.ElasticsearchConfigAndSecretMountsHashAnnotation: elasticsearchConfigAndMountsHash,
 	})
 	esFixture := esv1.Elasticsearch{ObjectMeta: metav1.ObjectMeta{
 		Namespace: "ns",
@@ -160,7 +186,8 @@ func TestReconcileStackConfigPolicy_Reconcile(t *testing.T) {
 	}
 	secretHash, err := getSettingsHash(secretFixture)
 	assert.NoError(t, err)
-	secretFixture.Annotations = map[string]string{"policy.k8s.elastic.co/settings-hash": secretHash}
+	secretFixture.Annotations = map[string]string{"policy.k8s.elastic.co/settings-hash": secretHash,
+		"policy.k8s.elastic.co/secure-settings-secrets": `[{"namespace":"ns","secretName":"shared-secret"},{"namespace":"ns","secretName":"shared-secret1"}]`}
 
 	conflictingSecretFixture := secretFixture.DeepCopy()
 	conflictingSecretFixture.Labels["eck.k8s.elastic.co/owner-name"] = "another-policy"
@@ -175,7 +202,8 @@ func TestReconcileStackConfigPolicy_Reconcile(t *testing.T) {
 			Namespace: "ns",
 			Labels: map[string]string{
 				"elasticsearch.k8s.elastic.co/cluster-name":     "another-es",
-				commonlabels.StackConfigPolicyOnDeleteLabelName: commonlabels.OrphanObjectDeleteOnPolicyDelete,
+				"common.k8s.elastic.co/type":                    "elasticsearch",
+				commonlabels.StackConfigPolicyOnDeleteLabelName: commonlabels.OrphanSecretDeleteOnPolicyDelete,
 				reconciler.SoftOwnerNamespaceLabel:              policyFixture.Namespace,
 				reconciler.SoftOwnerNameLabel:                   policyFixture.Name,
 				reconciler.SoftOwnerKindLabel:                   policyv1alpha1.Kind,
@@ -217,6 +245,15 @@ func TestReconcileStackConfigPolicy_Reconcile(t *testing.T) {
 		}
 	}
 
+	kibanaFixture := kibanav1.Kibana{ObjectMeta: metav1.ObjectMeta{
+		Namespace: "ns",
+		Name:      "test-kb",
+		Labels:    map[string]string{"label": "test"},
+	}}
+
+	kibanaConfigSecretFixture := MkKibanaConfigSecret("ns", policyFixture.Name, policyFixture.Namespace, "3077592849")
+	addSecureSettingsAnnotationToSecret(kibanaConfigSecretFixture, "ns")
+
 	type args struct {
 		client           k8s.Client
 		esClientProvider commonesclient.Provider
@@ -295,12 +332,16 @@ func TestReconcileStackConfigPolicy_Reconcile(t *testing.T) {
 			},
 			pre: func(r ReconcileStackConfigPolicy) {
 				// before the reconciliation, settings are not empty
-				settings := r.getSettings(t, k8s.ExtractNamespacedName(orphanSecretFixture))
+				settings := r.getSettings(t, k8s.ExtractNamespacedName(&secretFixture))
+				assert.NotEmpty(t, settings.State.ClusterSettings.Data)
+				settings = r.getSettings(t, k8s.ExtractNamespacedName(orphanSecretFixture))
 				assert.NotEmpty(t, settings.State.ClusterSettings)
 			},
 			post: func(r ReconcileStackConfigPolicy, recorder record.FakeRecorder) {
 				// after the reconciliation, settings are empty
-				settings := r.getSettings(t, k8s.ExtractNamespacedName(orphanSecretFixture))
+				settings := r.getSettings(t, k8s.ExtractNamespacedName(&secretFixture))
+				assert.Empty(t, settings.State.ClusterSettings.Data)
+				settings = r.getSettings(t, k8s.ExtractNamespacedName(orphanSecretFixture))
 				assert.Empty(t, settings.State.ClusterSettings.Data)
 			},
 			wantErr: false,
@@ -323,6 +364,25 @@ func TestReconcileStackConfigPolicy_Reconcile(t *testing.T) {
 			wantRequeue:      true,
 			wantRequeueAfter: true,
 		},
+		{
+			name: "Reconcile Kibana already owned by another policy",
+			args: args{
+				client:         k8s.NewFakeClient(&policyFixture, &kibanaFixture, MkKibanaConfigSecret("ns", "another-policy", "ns", "testvalue")),
+				licenseChecker: &license.MockLicenseChecker{EnterpriseEnabled: true},
+			},
+			post: func(r ReconcileStackConfigPolicy, recorder record.FakeRecorder) {
+				events := fetchEvents(&recorder)
+				assert.ElementsMatch(t, []string{"Warning Unexpected conflict: resource Kibana ns/test-kb already configured by StackConfigpolicy ns/another-policy"}, events)
+
+				policy := r.getPolicy(t, k8s.ExtractNamespacedName(&policyFixture))
+				assert.Equal(t, 1, policy.Status.Resources)
+				assert.Equal(t, 0, policy.Status.Ready)
+				assert.Equal(t, policyv1alpha1.ConflictPhase, policy.Status.Phase)
+			},
+			wantErr:          true,
+			wantRequeue:      true,
+			wantRequeueAfter: true,
+		},
 		{
 			name: "Reconcile Elasticsearch already owned by another policy",
 			args: args{
@@ -372,7 +432,7 @@ func TestReconcileStackConfigPolicy_Reconcile(t *testing.T) {
 				policy := r.getPolicy(t, k8s.ExtractNamespacedName(&policyFixture))
 				assert.Equal(t, 1, policy.Status.Resources)
 				assert.Equal(t, 0, policy.Status.Ready)
-				assert.Equal(t, policyv1alpha1.UnknownPhase, policy.Status.ResourcesStatuses["ns/test-es"].Phase)
+				assert.Equal(t, policyv1alpha1.UnknownPhase, policy.Status.ResourcesStatuses["elasticsearch"]["ns/test-es"].Phase)
 				assert.Equal(t, policyv1alpha1.ReadyPhase, policy.Status.Phase)
 			},
 			wantErr:          false,
@@ -425,7 +485,7 @@ func TestReconcileStackConfigPolicy_Reconcile(t *testing.T) {
 				assert.Equal(t, 1, policy.Status.Resources)
 				assert.Equal(t, 0, policy.Status.Ready)
 				assert.Equal(t, policyv1alpha1.ApplyingChangesPhase, policy.Status.Phase)
-				assert.Equal(t, "invalid cluster settings", policy.Status.ResourcesStatuses["ns/test-es"].Error.Message)
+				assert.Equal(t, "invalid cluster settings", policy.Status.ResourcesStatuses["elasticsearch"]["ns/test-es"].Error.Message)
 			},
 			wantErr:          false,
 			wantRequeue:      true,
@@ -456,7 +516,7 @@ func TestReconcileStackConfigPolicy_Reconcile(t *testing.T) {
 		{
 			name: "Happy path",
 			args: args{
-				client:           k8s.NewFakeClient(&policyFixture, &esFixture, &secretFixture, secretMountsSecretFixture, esPodFixture),
+				client:           k8s.NewFakeClient(&policyFixture, &esFixture, &secretFixture, secretMountsSecretFixture, esPodFixture, &kibanaFixture, mkKibanaPod("ns", true, "3077592849")),
 				licenseChecker:   &license.MockLicenseChecker{EnterpriseEnabled: true},
 				esClientProvider: fakeClientProvider(clusterStateFileSettingsFixture(42, nil), nil),
 			},
@@ -467,8 +527,8 @@ func TestReconcileStackConfigPolicy_Reconcile(t *testing.T) {
 					Name:      "test-policy",
 				}, &policy)
 				assert.NoError(t, err)
-				assert.Equal(t, 1, policy.Status.Resources)
-				assert.Equal(t, 1, policy.Status.Ready)
+				assert.Equal(t, 2, policy.Status.Resources)
+				assert.Equal(t, 2, policy.Status.Ready)
 				assert.Equal(t, policyv1alpha1.ReadyPhase, policy.Status.Phase)
 				var esSecret corev1.Secret
 				// Verify the config secret created by the stack config policy controller
@@ -486,6 +546,10 @@ func TestReconcileStackConfigPolicy_Reconcile(t *testing.T) {
 
 				// Verify the secret mounts secret
 				assertExpectedESSecretContent(t, r.Client, esFixture.Name, *secretMountsSecretFixture, policy.Spec.Elasticsearch.SecretMounts)
+				assertKibanaConfigSecret(t, r.Client, kibanaFixture.Name, *kibanaConfigSecretFixture)
+
+				// Verify dynamic watches are added
+				assert.NotEmpty(t, r.dynamicWatches.Secrets.Registrations())
 			},
 			wantErr:          false,
 			wantRequeue:      false,
@@ -537,6 +601,98 @@ func TestReconcileStackConfigPolicy_Reconcile(t *testing.T) {
 			},
 			wantErr: false,
 		},
+		{
+			name: "Delete orphan soft owned kibana config secrets when StackConfigPolicy does not exist",
+			args: args{
+				client:           k8s.NewFakeClient(&esFixture, &kibanaFixture, kibanaConfigSecretFixture),
+				licenseChecker:   &license.MockLicenseChecker{EnterpriseEnabled: true},
+				esClientProvider: fakeClientProvider(clusterStateFileSettingsFixture(42, nil), nil),
+			},
+			pre: func(r ReconcileStackConfigPolicy) {
+				// before the reconciliation, config exist
+				var configSecret corev1.Secret
+				err := r.Client.Get(context.Background(), types.NamespacedName{
+					Name:      "test-kb-kb-policy-config",
+					Namespace: "ns",
+				}, &configSecret)
+				assert.NoError(t, err)
+			},
+			post: func(r ReconcileStackConfigPolicy, recorder record.FakeRecorder) {
+				// after the reconciliation, the config secrets do not exist
+				var kibanaConfigSecret corev1.Secret
+				err := r.Client.Get(context.Background(), types.NamespacedName{
+					Name:      "test-kb-kb-policy-config",
+					Namespace: "ns",
+				}, &kibanaConfigSecret)
+				assert.True(t, apierrors.IsNotFound(err))
+			},
+			wantErr: false,
+		},
+		{
+			name: "Delete orphan soft owned kibana config secrets when Kibana does not exist",
+			args: args{
+				client:           k8s.NewFakeClient(&policyFixture, kibanaConfigSecretFixture),
+				licenseChecker:   &license.MockLicenseChecker{EnterpriseEnabled: true},
+				esClientProvider: fakeClientProvider(clusterStateFileSettingsFixture(42, nil), nil),
+			},
+			pre: func(r ReconcileStackConfigPolicy) {
+				// before the reconciliation, config exist
+				var configSecret corev1.Secret
+				err := r.Client.Get(context.Background(), types.NamespacedName{
+					Name:      "test-kb-kb-policy-config",
+					Namespace: "ns",
+				}, &configSecret)
+				assert.NoError(t, err)
+			},
+			post: func(r ReconcileStackConfigPolicy, recorder record.FakeRecorder) {
+				// after the reconciliation, the config secrets do not exist
+				var kibanaConfigSecret corev1.Secret
+				err := r.Client.Get(context.Background(), types.NamespacedName{
+					Name:      "test-kb-kb-policy-config",
+					Namespace: "ns",
+				}, &kibanaConfigSecret)
+				assert.True(t, apierrors.IsNotFound(err))
+			},
+			wantErr: false,
+		},
+		{
+			name: "Elasticsearch cluster is unreachable and Kibana has reconciled successfully",
+			args: args{
+				client:           k8s.NewFakeClient(&policyFixture, &esFixture, &secretFixture, secretMountsSecretFixture, esPodFixture, &kibanaFixture, mkKibanaPod("ns", true, "3077592849")),
+				licenseChecker:   &license.MockLicenseChecker{EnterpriseEnabled: true},
+				esClientProvider: fakeClientProvider(esclient.FileSettings{}, errors.New("elasticsearch client failed")),
+			},
+			post: func(r ReconcileStackConfigPolicy, recorder record.FakeRecorder) {
+				policy := r.getPolicy(t, k8s.ExtractNamespacedName(&policyFixture))
+				assert.Equal(t, 2, policy.Status.Resources)
+				assert.Equal(t, 1, policy.Status.Ready)
+				assert.Equal(t, policyv1alpha1.UnknownPhase, policy.Status.ResourcesStatuses["elasticsearch"]["ns/test-es"].Phase)
+				assert.Equal(t, policyv1alpha1.ReadyPhase, policy.Status.ResourcesStatuses["kibana"]["ns/test-kb"].Phase)
+				assert.Equal(t, policyv1alpha1.ReadyPhase, policy.Status.Phase)
+			},
+			wantErr:          false,
+			wantRequeue:      true,
+			wantRequeueAfter: true,
+		},
+		{
+			name: "Elasticsearch reconciled successfully and Kibana config not yet applied",
+			args: args{
+				client:           k8s.NewFakeClient(&policyFixture, &esFixture, &secretFixture, secretMountsSecretFixture, esPodFixture, &kibanaFixture, mkKibanaPod("ns", true, "testhash")),
+				licenseChecker:   &license.MockLicenseChecker{EnterpriseEnabled: true},
+				esClientProvider: fakeClientProvider(clusterStateFileSettingsFixture(42, nil), nil),
+			},
+			post: func(r ReconcileStackConfigPolicy, recorder record.FakeRecorder) {
+				policy := r.getPolicy(t, k8s.ExtractNamespacedName(&policyFixture))
+				assert.Equal(t, 2, policy.Status.Resources)
+				assert.Equal(t, 1, policy.Status.Ready)
+				assert.Equal(t, policyv1alpha1.ReadyPhase, policy.Status.ResourcesStatuses["elasticsearch"]["ns/test-es"].Phase)
+				assert.Equal(t, policyv1alpha1.ApplyingChangesPhase, policy.Status.ResourcesStatuses["kibana"]["ns/test-kb"].Phase)
+				assert.Equal(t, policyv1alpha1.ApplyingChangesPhase, policy.Status.Phase)
+			},
+			wantErr:          false,
+			wantRequeue:      true,
+			wantRequeueAfter: true,
+		},
 	}
 
 	for _, tt := range tests {
@@ -547,6 +703,7 @@ func TestReconcileStackConfigPolicy_Reconcile(t *testing.T) {
 				esClientProvider: tt.args.esClientProvider,
 				recorder:         fakeRecorder,
 				licenseChecker:   tt.args.licenseChecker,
+				dynamicWatches:   watches.NewDynamicWatches(),
 			}
 			if tt.pre != nil {
 				tt.pre(reconciler)
@@ -597,3 +754,16 @@ func assertExpectedESSecretContent(t *testing.T, c client.Client, esName string,
 		assert.Equal(t, expectedSecret.Data, secretMountsSecret.Data)
 	}
 }
+
+func assertKibanaConfigSecret(t *testing.T, c client.Client, kibanaName string, expectedKibanaSecret corev1.Secret) {
+	t.Helper()
+	var kibanaSecret corev1.Secret
+	err := c.Get(context.Background(), types.NamespacedName{
+		Namespace: "ns",
+		Name:      kibanaName + "-kb-policy-config",
+	}, &kibanaSecret)
+	require.NoError(t, err)
+	require.Equal(t, expectedKibanaSecret.Labels, kibanaSecret.Labels)
+	require.Equal(t, expectedKibanaSecret.Annotations, kibanaSecret.Annotations)
+	require.Equal(t, expectedKibanaSecret.Data, kibanaSecret.Data)
+}
diff --git a/pkg/controller/stackconfigpolicy/elasticsearch_config_settings_test.go b/pkg/controller/stackconfigpolicy/elasticsearch_config_settings_test.go
index 1cf190b9f..a989490d9 100644
--- a/pkg/controller/stackconfigpolicy/elasticsearch_config_settings_test.go
+++ b/pkg/controller/stackconfigpolicy/elasticsearch_config_settings_test.go
@@ -8,7 +8,7 @@ import (
 	"context"
 	"testing"
 
-	"github.com/magiconair/properties/assert"
+	"github.com/stretchr/testify/require"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
@@ -112,7 +112,7 @@ func Test_reconcileSecretMountSecretsESNamespace(t *testing.T) {
 						return
 					}
 
-					assert.Equal(t, expectedSecret.Data, getSecretMountSecret(t, esv1.ESNamer.Suffix(tt.args.es.Name, secretMount.SecretName), "test-ns", "test-policy", "test-policy-ns", "delete").Data, "secrets do not match")
+					require.Equal(t, expectedSecret.Data, getSecretMountSecret(t, esv1.ESNamer.Suffix(tt.args.es.Name, secretMount.SecretName), "test-ns", "test-policy", "test-policy-ns", "delete").Data, "secrets do not match")
 				}
 			}
 		})
@@ -127,6 +127,7 @@ func getSecretMountSecret(t *testing.T, name string, namespace string, policyNam
 			Namespace: namespace,
 			Labels: map[string]string{
 				"elasticsearch.k8s.elastic.co/cluster-name": "another-es",
+				"common.k8s.elastic.co/type":                "elasticsearch",
 				"asset.policy.k8s.elastic.co/on-delete":     orphanObjectOnPolicyDeleteStratergy,
 				"eck.k8s.elastic.co/owner-namespace":        policyNamespace,
 				"eck.k8s.elastic.co/owner-name":             policyName,
diff --git a/pkg/controller/stackconfigpolicy/kibana_config_settings_test.go b/pkg/controller/stackconfigpolicy/kibana_config_settings_test.go
new file mode 100644
index 000000000..e1f1c983f
--- /dev/null
+++ b/pkg/controller/stackconfigpolicy/kibana_config_settings_test.go
@@ -0,0 +1,370 @@
+// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+// or more contributor license agreements. Licensed under the Elastic License 2.0;
+// you may not use this file except in compliance with the Elastic License 2.0.
+
+package stackconfigpolicy
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	commonv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/common/v1"
+	kibanav1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/kibana/v1"
+	policyv1alpha1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/stackconfigpolicy/v1alpha1"
+	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/reconciler"
+	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/k8s"
+)
+
+func Test_newKibanaConfigSecret(t *testing.T) {
+	type args struct {
+		kb     kibanav1.Kibana
+		policy *policyv1alpha1.StackConfigPolicy
+	}
+
+	tests := []struct {
+		name string
+		args args
+		want corev1.Secret
+	}{
+		{
+			name: "construct valid kibana config secret",
+			args: args{
+				kb: kibanav1.Kibana{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "test-kb",
+						Namespace: "test-ns",
+					},
+				},
+				policy: &policyv1alpha1.StackConfigPolicy{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "test-policy",
+						Namespace: "test-policy-ns",
+					},
+					Spec: policyv1alpha1.StackConfigPolicySpec{
+						Kibana: policyv1alpha1.KibanaConfigPolicySpec{
+							Config: &commonv1.Config{
+								Data: map[string]interface{}{
+									"xpack.canvas.enabled": true,
+								},
+							},
+							SecureSettings: []commonv1.SecretSource{
+								{
+									SecretName: "shared-secret",
+								},
+							},
+						},
+					},
+				},
+			},
+			want: corev1.Secret{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "test-ns",
+					Name:      "test-kb-kb-policy-config",
+					Labels: map[string]string{
+						"asset.policy.k8s.elastic.co/on-delete": "delete",
+						"kibana.k8s.elastic.co/name":            "test-kb",
+						"common.k8s.elastic.co/type":            "kibana",
+						"eck.k8s.elastic.co/owner-kind":         "",
+						"eck.k8s.elastic.co/owner-name":         "test-policy",
+						"eck.k8s.elastic.co/owner-namespace":    "test-policy-ns",
+					},
+					Annotations: map[string]string{
+						"policy.k8s.elastic.co/kibana-config-hash":      "3077592849",
+						"policy.k8s.elastic.co/secure-settings-secrets": `[{"namespace":"test-policy-ns","secretName":"shared-secret"}]`,
+					},
+				},
+				Data: map[string][]byte{
+					"kibana.json": []byte(`{"xpack.canvas.enabled":true}`),
+				},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := newKibanaConfigSecret(*tt.args.policy, tt.args.kb)
+			require.NoError(t, err)
+			require.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func Test_kibanaConfigApplied(t *testing.T) {
+	type args struct {
+		kb     kibanav1.Kibana
+		policy *policyv1alpha1.StackConfigPolicy
+		client k8s.Client
+	}
+
+	tests := []struct {
+		name string
+		args args
+		want bool
+	}{
+		{
+			name: "config applied successfully",
+			args: args{
+				kb: kibanav1.Kibana{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "test-kb",
+						Namespace: "test-ns",
+					},
+				},
+				policy: &policyv1alpha1.StackConfigPolicy{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "test-policy",
+						Namespace: "test-policy-ns",
+					},
+					Spec: policyv1alpha1.StackConfigPolicySpec{
+						Kibana: policyv1alpha1.KibanaConfigPolicySpec{
+							Config: &commonv1.Config{
+								Data: map[string]interface{}{
+									"xpack.canvas.enabled": true,
+								},
+							},
+						},
+					},
+				},
+				client: k8s.NewFakeClient(mkKibanaPod("test-ns", true, "3077592849")),
+			},
+			want: true,
+		},
+		{
+			name: "config not applied yet",
+			args: args{
+				kb: kibanav1.Kibana{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "test-kb",
+						Namespace: "test-ns",
+					},
+				},
+				policy: &policyv1alpha1.StackConfigPolicy{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "test-policy",
+						Namespace: "test-policy-ns",
+					},
+					Spec: policyv1alpha1.StackConfigPolicySpec{
+						Kibana: policyv1alpha1.KibanaConfigPolicySpec{
+							Config: &commonv1.Config{
+								Data: map[string]interface{}{
+									"xpack.canvas.enabled": true,
+								},
+							},
+						},
+					},
+				},
+				client: k8s.NewFakeClient(mkKibanaPod("test-ns", false, "3077592849")),
+			},
+			want: false,
+		},
+		{
+			name: "no pods running for given Kibana instance",
+			args: args{
+				kb: kibanav1.Kibana{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "test-kb",
+						Namespace: "test-ns",
+					},
+				},
+				policy: &policyv1alpha1.StackConfigPolicy{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "test-policy",
+						Namespace: "test-policy-ns",
+					},
+					Spec: policyv1alpha1.StackConfigPolicySpec{
+						Kibana: policyv1alpha1.KibanaConfigPolicySpec{
+							Config: &commonv1.Config{
+								Data: map[string]interface{}{
+									"xpack.canvas.enabled": true,
+								},
+							},
+						},
+					},
+				},
+				client: k8s.NewFakeClient(),
+			},
+			want: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := kibanaConfigApplied(tt.args.client, *tt.args.policy, tt.args.kb)
+			require.NoError(t, err)
+			require.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func Test_canBeOwned(t *testing.T) {
+	type args struct {
+		kb     kibanav1.Kibana
+		policy *policyv1alpha1.StackConfigPolicy
+		client k8s.Client
+	}
+
+	tests := []struct {
+		name           string
+		args           args
+		wantSecretRef  reconciler.SoftOwnerRef
+		wantCanbeOwned bool
+	}{
+		{
+			name: "secret owned by current policy",
+			args: args{
+				kb: kibanav1.Kibana{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "test-kb",
+						Namespace: "test-ns",
+					},
+				},
+				policy: &policyv1alpha1.StackConfigPolicy{
+					TypeMeta: metav1.TypeMeta{
+						Kind: "StackConfigPolicy",
+					},
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "test-policy",
+						Namespace: "test-policy-ns",
+					},
+					Spec: policyv1alpha1.StackConfigPolicySpec{
+						Kibana: policyv1alpha1.KibanaConfigPolicySpec{
+							Config: &commonv1.Config{
+								Data: map[string]interface{}{
+									"xpack.canvas.enabled": true,
+								},
+							},
+						},
+					},
+				},
+				client: k8s.NewFakeClient(MkKibanaConfigSecret("test-ns", "test-policy", "test-policy-ns", "3077592849")),
+			},
+			wantSecretRef: reconciler.SoftOwnerRef{
+				Namespace: "test-policy-ns",
+				Name:      "test-policy",
+				Kind:      "StackConfigPolicy",
+			},
+			wantCanbeOwned: true,
+		},
+		{
+			name: "secret owned by another policy",
+			args: args{
+				kb: kibanav1.Kibana{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "test-kb",
+						Namespace: "test-ns",
+					},
+				},
+				policy: &policyv1alpha1.StackConfigPolicy{
+					TypeMeta: metav1.TypeMeta{
+						Kind: "StackConfigPolicy",
+					},
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "test-policy",
+						Namespace: "test-policy-ns",
+					},
+					Spec: policyv1alpha1.StackConfigPolicySpec{
+						Kibana: policyv1alpha1.KibanaConfigPolicySpec{
+							Config: &commonv1.Config{
+								Data: map[string]interface{}{
+									"xpack.canvas.enabled": true,
+								},
+							},
+						},
+					},
+				},
+				client: k8s.NewFakeClient(MkKibanaConfigSecret("test-ns", "test-another-policy", "test-policy-ns", "3077592849")),
+			},
+			wantSecretRef: reconciler.SoftOwnerRef{
+				Namespace: "test-policy-ns",
+				Name:      "test-another-policy",
+				Kind:      "StackConfigPolicy",
+			},
+			wantCanbeOwned: false,
+		},
+		{
+			name: "secret does not exist",
+			args: args{
+				kb: kibanav1.Kibana{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "test-kb",
+						Namespace: "test-ns",
+					},
+				},
+				policy: &policyv1alpha1.StackConfigPolicy{
+					TypeMeta: metav1.TypeMeta{
+						Kind: "StackConfigPolicy",
+					},
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "test-policy",
+						Namespace: "test-policy-ns",
+					},
+					Spec: policyv1alpha1.StackConfigPolicySpec{
+						Kibana: policyv1alpha1.KibanaConfigPolicySpec{
+							Config: &commonv1.Config{
+								Data: map[string]interface{}{
+									"xpack.canvas.enabled": true,
+								},
+							},
+						},
+					},
+				},
+				client: k8s.NewFakeClient(),
+			},
+			wantCanbeOwned: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			secretRef, canBeOwned, err := canBeOwned(context.Background(), tt.args.client, *tt.args.policy, tt.args.kb)
+			require.NoError(t, err)
+			require.Equal(t, tt.wantSecretRef, secretRef)
+			require.Equal(t, tt.wantCanbeOwned, canBeOwned)
+		})
+	}
+}
+
+func mkKibanaPod(namespace string, hashapplied bool, hashValue string) *corev1.Pod {
+	pod := corev1.Pod{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test-kibana-pod",
+			Namespace: namespace,
+			Labels: map[string]string{
+				"kibana.k8s.elastic.co/name": "test-kb",
+			},
+			Annotations: make(map[string]string),
+		},
+	}
+
+	if hashapplied {
+		pod.Annotations["policy.k8s.elastic.co/kibana-config-hash"] = hashValue
+	}
+	return &pod
+}
+
+func MkKibanaConfigSecret(namespace string, owningPolicyName string, owningPolicyNamespace string, hashValue string) *corev1.Secret {
+	return &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: namespace,
+			Name:      "test-kb-kb-policy-config",
+			Labels: map[string]string{
+				"asset.policy.k8s.elastic.co/on-delete": "delete",
+				"kibana.k8s.elastic.co/name":            "test-kb",
+				"common.k8s.elastic.co/type":            "kibana",
+				"eck.k8s.elastic.co/owner-kind":         "StackConfigPolicy",
+				"eck.k8s.elastic.co/owner-name":         owningPolicyName,
+				"eck.k8s.elastic.co/owner-namespace":    owningPolicyNamespace,
+			},
+			Annotations: map[string]string{
+				"policy.k8s.elastic.co/kibana-config-hash": hashValue,
+			},
+		},
+		Data: map[string][]byte{
+			"kibana.json": []byte(`{"xpack.canvas.enabled":true}`),
+		},
+	}
+}
diff --git a/pkg/controller/stackconfigpolicy/secure_settings_test.go b/pkg/controller/stackconfigpolicy/secure_settings_test.go
new file mode 100644
index 000000000..b6874b602
--- /dev/null
+++ b/pkg/controller/stackconfigpolicy/secure_settings_test.go
@@ -0,0 +1,107 @@
+// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+// or more contributor license agreements. Licensed under the Elastic License 2.0;
+// you may not use this file except in compliance with the Elastic License 2.0.
+
+package stackconfigpolicy
+
+import (
+	"context"
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	commonv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/common/v1"
+	esv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/elasticsearch/v1"
+	kibanav1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/kibana/v1"
+	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/k8s"
+)
+
+func Test_GetSecureSettingsSecretSourcesForResources(t *testing.T) {
+	type args struct {
+		resource     metav1.Object
+		resourceKind string
+		client       k8s.Client
+	}
+
+	kibanaConfigSecretFixture := MkKibanaConfigSecret("test-kb-ns", "test-policy", "test-policy-ns", "")
+	addSecureSettingsAnnotationToSecret(kibanaConfigSecretFixture, "test-policy-ns")
+
+	elasticsearchConfigSecretFixture := &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test-es-es-file-settings",
+			Namespace: "test-es-ns",
+		},
+	}
+	addSecureSettingsAnnotationToSecret(elasticsearchConfigSecretFixture, "test-policy-ns")
+
+	tests := []struct {
+		name string
+		args args
+		want []commonv1.NamespacedSecretSource
+	}{
+		{
+			name: "Get secure settings secrets for Kibana",
+			args: args{
+				resource: &kibanav1.Kibana{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "test-kb",
+						Namespace: "test-kb-ns",
+					},
+				},
+				resourceKind: "Kibana",
+				client:       k8s.NewFakeClient(kibanaConfigSecretFixture),
+			},
+			want: []commonv1.NamespacedSecretSource{
+				{
+					SecretName: "shared-secret",
+					Namespace:  "test-policy-ns",
+				},
+			},
+		},
+		{
+			name: "Get secure settings secrets for Elasticsearch",
+			args: args{
+				resource: &esv1.Elasticsearch{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "test-es",
+						Namespace: "test-es-ns",
+					},
+				},
+				resourceKind: "Elasticsearch",
+				client:       k8s.NewFakeClient(elasticsearchConfigSecretFixture),
+			},
+			want: []commonv1.NamespacedSecretSource{
+				{
+					SecretName: "shared-secret",
+					Namespace:  "test-policy-ns",
+				},
+			},
+		},
+		{
+			name: "secure settings for unknow kind",
+			args: args{
+				resourceKind: "UnknownKind",
+				client:       k8s.NewFakeClient(elasticsearchConfigSecretFixture),
+			},
+			want: []commonv1.NamespacedSecretSource{},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := GetSecureSettingsSecretSourcesForResources(context.Background(), tt.args.client, tt.args.resource, tt.args.resourceKind)
+			require.NoError(t, err)
+			require.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func addSecureSettingsAnnotationToSecret(secret *corev1.Secret, namespace string) {
+	if secret.Annotations == nil {
+		secret.Annotations = make(map[string]string)
+	}
+	secret.Annotations["policy.k8s.elastic.co/secure-settings-secrets"] = fmt.Sprintf(`[{"namespace":"%s","secretName":"shared-secret"}]`, namespace)
+}
diff --git a/pkg/utils/maps/maps_test.go b/pkg/utils/maps/maps_test.go
index 970de29cb..2bfba8ea8 100644
--- a/pkg/utils/maps/maps_test.go
+++ b/pkg/utils/maps/maps_test.go
@@ -278,3 +278,70 @@ func TestContainsKeys(t *testing.T) {
 		})
 	}
 }
+
+func TestIsEqualSubset(t *testing.T) {
+	tests := []struct {
+		name string
+		map1 map[string]string
+		map2 map[string]string
+		keys []string
+		want bool
+	}{
+		{
+			name: "when a key is changed",
+			map1: map[string]string{
+				"a": "x",
+				"b": "y",
+			},
+			map2: map[string]string{
+				"a": "w",
+				"b": "y",
+			},
+			keys: []string{"a"},
+			want: false,
+		},
+		{
+			name: "when a given key to compare does not exist in one of the maps",
+			map1: map[string]string{
+				"a": "x",
+				"b": "y",
+			},
+			map2: map[string]string{
+				"b": "y",
+			},
+			keys: []string{"a"},
+			want: false,
+		},
+		{
+			name: "when a given key to compare does not exist in both maps",
+			map1: map[string]string{
+				"b": "y",
+			},
+			map2: map[string]string{
+				"b": "y",
+			},
+			keys: []string{"a"},
+			want: true,
+		},
+		{
+			name: "when a key not in the keys to be compared list is changed",
+			map1: map[string]string{
+				"a": "x",
+				"b": "y",
+			},
+			map2: map[string]string{
+				"a": "x",
+				"b": "w",
+			},
+			keys: []string{"a"},
+			want: true,
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			have := IsEqualSubset(tc.map1, tc.map2, tc.keys)
+			require.Equal(t, tc.want, have)
+		})
+	}
+}
diff --git a/test/e2e/test/k8s_client.go b/test/e2e/test/k8s_client.go
index 489859534..efbc460ea 100644
--- a/test/e2e/test/k8s_client.go
+++ b/test/e2e/test/k8s_client.go
@@ -41,7 +41,7 @@ import (
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/elasticsearch/label"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/elasticsearch/volume"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/enterprisesearch"
-	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana"
+	kblabel "github.com/elastic/cloud-on-k8s/v2/pkg/controller/kibana/label"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/logstash"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/controller/maps"
 	"github.com/elastic/cloud-on-k8s/v2/pkg/utils/k8s"
@@ -415,7 +415,7 @@ func ESPodListOptionsByNodeSet(esNamespace, esName, nodeSetName string) []k8scli
 func KibanaPodListOptions(kbNamespace, kbName string) []k8sclient.ListOption {
 	ns := k8sclient.InNamespace(kbNamespace)
 	matchLabels := k8sclient.MatchingLabels(map[string]string{
-		kibana.KibanaNameLabelName: kbName,
+		kblabel.KibanaNameLabelName: kbName,
 	})
 	return []k8sclient.ListOption{ns, matchLabels}
 }