Răsfoiți Sursa

fix: removed update dependency + add pkg/provider/ovh/ to CODEOWNERS.md files

Signed-off-by: Jordan Sauvain <jordan.sauvain@ovhcloud.com>
Jordan Sauvain 4 luni în urmă
părinte
comite
94fe1a8e4a
48 a modificat fișierele cu 4709 adăugiri și 0 ștergeri
  1. 1 0
      .github/CODEOWNERS.md
  2. 1 0
      CODEOWNERS.md
  3. 65 0
      apis/externalsecrets/v1/secretstore_ovh_types.go
  4. 2 0
      apis/externalsecrets/v1/secretstore_types.go
  5. 101 0
      apis/externalsecrets/v1/zz_generated.deepcopy.go
  6. 135 0
      config/crds/bases/external-secrets.io_clustersecretstores.yaml
  7. 135 0
      config/crds/bases/external-secrets.io_secretstores.yaml
  8. 246 0
      deploy/crds/bundle.yaml
  9. 209 0
      docs/api/spec.md
  10. 215 0
      docs/provider/ovhcloud.md
  11. 16 0
      docs/snippets/ovh-external-secret-data-nested-property.yaml
  12. 16 0
      docs/snippets/ovh-external-secret-data-property.yaml
  13. 15 0
      docs/snippets/ovh-external-secret-data.yaml
  14. 15 0
      docs/snippets/ovh-external-secret-dataFrom-extract-property.yaml
  15. 14 0
      docs/snippets/ovh-external-secret-dataFrom-extract.yaml
  16. 16 0
      docs/snippets/ovh-external-secret-dataFrom-find-byboth.yaml
  17. 14 0
      docs/snippets/ovh-external-secret-dataFrom-find-bypath.yaml
  18. 15 0
      docs/snippets/ovh-external-secret-dataFrom-find-byregexp.yaml
  19. 17 0
      docs/snippets/ovh-external-secret-example.yaml
  20. 28 0
      docs/snippets/ovh-mtls-secret-store.yaml
  21. 66 0
      docs/snippets/ovh-push-secret-migration.yaml
  22. 31 0
      docs/snippets/ovh-push-secret-rotation.yaml
  23. 23 0
      docs/snippets/ovh-secret-store-cas.yaml
  24. 22 0
      docs/snippets/ovh-token-secret-store.yaml
  25. 5 0
      go.mod
  26. 10 0
      go.sum
  27. 30 0
      pkg/register/ovh.go
  28. 25 0
      providers/v1/ovh/client_close.go
  29. 36 0
      providers/v1/ovh/client_delete_secret.go
  30. 196 0
      providers/v1/ovh/client_get_all_secrets.go
  31. 224 0
      providers/v1/ovh/client_get_all_secrets_test.go
  32. 36 0
      providers/v1/ovh/client_get_secret.go
  33. 59 0
      providers/v1/ovh/client_get_secret_map.go
  34. 132 0
      providers/v1/ovh/client_get_secret_map_test.go
  35. 119 0
      providers/v1/ovh/client_get_secret_test.go
  36. 194 0
      providers/v1/ovh/client_push_secret.go
  37. 237 0
      providers/v1/ovh/client_push_secret_test.go
  38. 36 0
      providers/v1/ovh/client_secret_exists.go
  39. 66 0
      providers/v1/ovh/client_secret_exists_test.go
  40. 127 0
      providers/v1/ovh/client_utils.go
  41. 425 0
      providers/v1/ovh/fake/fake_okms_client.go
  42. 113 0
      providers/v1/ovh/go.mod
  43. 295 0
      providers/v1/ovh/go.sum
  44. 300 0
      providers/v1/ovh/provider.go
  45. 552 0
      providers/v1/ovh/provider_test.go
  46. 34 0
      providers/v1/ovh/validate.go
  47. 20 0
      tests/__snapshot__/clustersecretstore-v1.yaml
  48. 20 0
      tests/__snapshot__/secretstore-v1.yaml

+ 1 - 0
.github/CODEOWNERS.md

@@ -45,6 +45,7 @@ pkg/provider/onboardbase/     @external-secrets/provider-onboardbase-reviewers
 pkg/provider/onepassword/     @external-secrets/provider-onepassword-reviewers
 pkg/provider/onepasswordsdk/  @external-secrets/provider-onepasswordsdk-reviewers
 pkg/provider/oracle/          @external-secrets/provider-oracle-reviewers
+pkg/provider/ovh/             @external-secrets/provider-ovh-reviewers
 pkg/provider/passbolt/        @external-secrets/provider-passbolt-reviewers
 pkg/provider/passworddepot/   @external-secrets/provider-passworddepot-reviewers
 pkg/provider/previder/        @external-secrets/provider-previder-reviewers

+ 1 - 0
CODEOWNERS.md

@@ -44,6 +44,7 @@ pkg/provider/onboardbase/     @external-secrets/provider-onboardbase-reviewers
 pkg/provider/onepassword/     @external-secrets/provider-onepassword-reviewers
 pkg/provider/onepasswordsdk/  @external-secrets/provider-onepasswordsdk-reviewers
 pkg/provider/oracle/          @external-secrets/provider-oracle-reviewers
+pkg/provider/ovh/             @external-secrets/provider-ovh-reviewers
 pkg/provider/passbolt/        @external-secrets/provider-passbolt-reviewers
 pkg/provider/passworddepot/   @external-secrets/provider-passworddepot-reviewers
 pkg/provider/previder/        @external-secrets/provider-previder-reviewers

+ 65 - 0
apis/externalsecrets/v1/secretstore_ovh_types.go

@@ -0,0 +1,65 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1
+
+import esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+
+// OvhProvider holds the configuration to synchronize secrets with OVHcloud's Secret Manager.
+// +optional
+type OvhProvider struct {
+	// specifies the OKMS server endpoint
+	// +required
+	Server string `json:"server"`
+	// specifies the OKMS ID
+	// +required
+	OkmsID string `json:"okmsid"`
+	// Enables or disables check-and-set (CAS) (default: false)
+	// +optional
+	CasRequired *bool `json:"casRequired,omitempty"`
+	// Setup a timeout in seconds when requests to the KMS are made (default: 30)
+	// +optional
+	// +kubebuilder:default=30
+	OkmsTimeout *uint32 `json:"okmsTimeout,omitempty"`
+	// Authentication method (mtls or token)
+	// +required
+	Auth OvhAuth `json:"auth"`
+}
+
+// OvhAuth tells the controller how to authenticate to OVHcloud's Secret Manager, either using mTLS or a token.
+// +required
+type OvhAuth struct {
+	// +optional
+	ClientMTLS *OvhClientMTLS `json:"mtls,omitempty"`
+	// +optional
+	ClientToken *OvhClientToken `json:"token,omitempty"`
+}
+
+// OvhClientMTLS defines the configuration required to authenticate to OVHcloud's Secret Manager using mTLS.
+// +optional
+type OvhClientMTLS struct {
+	// +required
+	ClientCertificate *esmeta.SecretKeySelector `json:"certSecretRef,omitempty"`
+	// +required
+	ClientKey *esmeta.SecretKeySelector `json:"keySecretRef,omitempty"`
+}
+
+// OvhClientToken defines the configuration required to authenticate to OVHcloud's Secret Manager using a token.
+// +optional
+type OvhClientToken struct {
+	// +required
+	ClientTokenSecret *esmeta.SecretKeySelector `json:"tokenSecretRef,omitempty"`
+}

+ 2 - 0
apis/externalsecrets/v1/secretstore_types.go

@@ -87,6 +87,8 @@ type SecretStoreProvider struct {
 	// +optional
 	Vault *VaultProvider `json:"vault,omitempty"`
 
+	Ovh *OvhProvider `json:"ovh,omitempty"`
+
 	// GCPSM configures this store to sync secrets using Google Cloud Platform Secret Manager provider
 	// +optional
 	GCPSM *GCPSMProvider `json:"gcpsm,omitempty"`

+ 101 - 0
apis/externalsecrets/v1/zz_generated.deepcopy.go

@@ -2926,6 +2926,102 @@ func (in *OracleSecretRef) DeepCopy() *OracleSecretRef {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *OvhAuth) DeepCopyInto(out *OvhAuth) {
+	*out = *in
+	if in.ClientMTLS != nil {
+		in, out := &in.ClientMTLS, &out.ClientMTLS
+		*out = new(OvhClientMTLS)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.ClientToken != nil {
+		in, out := &in.ClientToken, &out.ClientToken
+		*out = new(OvhClientToken)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OvhAuth.
+func (in *OvhAuth) DeepCopy() *OvhAuth {
+	if in == nil {
+		return nil
+	}
+	out := new(OvhAuth)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *OvhClientMTLS) DeepCopyInto(out *OvhClientMTLS) {
+	*out = *in
+	if in.ClientCertificate != nil {
+		in, out := &in.ClientCertificate, &out.ClientCertificate
+		*out = new(apismetav1.SecretKeySelector)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.ClientKey != nil {
+		in, out := &in.ClientKey, &out.ClientKey
+		*out = new(apismetav1.SecretKeySelector)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OvhClientMTLS.
+func (in *OvhClientMTLS) DeepCopy() *OvhClientMTLS {
+	if in == nil {
+		return nil
+	}
+	out := new(OvhClientMTLS)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *OvhClientToken) DeepCopyInto(out *OvhClientToken) {
+	*out = *in
+	if in.ClientTokenSecret != nil {
+		in, out := &in.ClientTokenSecret, &out.ClientTokenSecret
+		*out = new(apismetav1.SecretKeySelector)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OvhClientToken.
+func (in *OvhClientToken) DeepCopy() *OvhClientToken {
+	if in == nil {
+		return nil
+	}
+	out := new(OvhClientToken)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *OvhProvider) DeepCopyInto(out *OvhProvider) {
+	*out = *in
+	if in.CasRequired != nil {
+		in, out := &in.CasRequired, &out.CasRequired
+		*out = new(bool)
+		**out = **in
+	}
+	if in.OkmsTimeout != nil {
+		in, out := &in.OkmsTimeout, &out.OkmsTimeout
+		*out = new(uint32)
+		**out = **in
+	}
+	in.Auth.DeepCopyInto(&out.Auth)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OvhProvider.
+func (in *OvhProvider) DeepCopy() *OvhProvider {
+	if in == nil {
+		return nil
+	}
+	out := new(OvhProvider)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *PassboltAuth) DeepCopyInto(out *PassboltAuth) {
 	*out = *in
@@ -3313,6 +3409,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
 		*out = new(VaultProvider)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.Ovh != nil {
+		in, out := &in.Ovh, &out.Ovh
+		*out = new(OvhProvider)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.GCPSM != nil {
 		in, out := &in.GCPSM, &out.GCPSM
 		*out = new(GCPSMProvider)

+ 135 - 0
config/crds/bases/external-secrets.io_clustersecretstores.yaml

@@ -4005,6 +4005,141 @@ spec:
                     - region
                     - vault
                     type: object
+                  ovh:
+                    description: OvhProvider holds the configuration to synchronize
+                      secrets with OVHcloud's Secret Manager.
+                    properties:
+                      auth:
+                        description: Authentication method (mtls or token)
+                        properties:
+                          mtls:
+                            description: OvhClientMTLS defines the configuration required
+                              to authenticate to OVHcloud's Secret Manager using mTLS.
+                            properties:
+                              certSecretRef:
+                                description: |-
+                                  SecretKeySelector is a reference to a specific 'key' within a Secret resource.
+                                  In some instances, `key` is a required field.
+                                properties:
+                                  key:
+                                    description: |-
+                                      A key in the referenced Secret.
+                                      Some instances of this field may be defaulted, in others it may be required.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[-._a-zA-Z0-9]+$
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      The namespace of the Secret resource being referred to.
+                                      Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                    maxLength: 63
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                    type: string
+                                type: object
+                              keySecretRef:
+                                description: |-
+                                  SecretKeySelector is a reference to a specific 'key' within a Secret resource.
+                                  In some instances, `key` is a required field.
+                                properties:
+                                  key:
+                                    description: |-
+                                      A key in the referenced Secret.
+                                      Some instances of this field may be defaulted, in others it may be required.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[-._a-zA-Z0-9]+$
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      The namespace of the Secret resource being referred to.
+                                      Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                    maxLength: 63
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                    type: string
+                                type: object
+                            required:
+                            - certSecretRef
+                            - keySecretRef
+                            type: object
+                          token:
+                            description: OvhClientToken defines the configuration
+                              required to authenticate to OVHcloud's Secret Manager
+                              using a token.
+                            properties:
+                              tokenSecretRef:
+                                description: |-
+                                  SecretKeySelector is a reference to a specific 'key' within a Secret resource.
+                                  In some instances, `key` is a required field.
+                                properties:
+                                  key:
+                                    description: |-
+                                      A key in the referenced Secret.
+                                      Some instances of this field may be defaulted, in others it may be required.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[-._a-zA-Z0-9]+$
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      The namespace of the Secret resource being referred to.
+                                      Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                    maxLength: 63
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                    type: string
+                                type: object
+                            required:
+                            - tokenSecretRef
+                            type: object
+                        type: object
+                        x-kubernetes-validations:
+                        - message: Exactly one of 'mtls' or 'token' must be specified
+                          rule: (has(self.mtls) && !has(self.token)) || (!has(self.mtls) && has(self.token))
+                      casRequired:
+                        description: 'Enables or disables check-and-set (CAS) (default:
+                          false)'
+                        type: boolean
+                      okmsTimeout:
+                        default: 30
+                        description: 'Setup a timeout in seconds when requests to
+                          the KMS are made (default: 30)'
+                        format: int32
+                        type: integer
+                      okmsid:
+                        description: specifies the OKMS ID
+                        type: string
+                      server:
+                        description: specifies the OKMS server endpoint
+                        type: string
+                    required:
+                    - auth
+                    - okmsid
+                    - server
+                    type: object
                   passbolt:
                     description: |-
                       PassboltProvider provides access to Passbolt secrets manager.

+ 135 - 0
config/crds/bases/external-secrets.io_secretstores.yaml

@@ -4005,6 +4005,141 @@ spec:
                     - region
                     - vault
                     type: object
+                  ovh:
+                    description: OvhProvider holds the configuration to synchronize
+                      secrets with OVHcloud's Secret Manager.
+                    properties:
+                      auth:
+                        description: Authentication method (mtls or token)
+                        properties:
+                          mtls:
+                            description: OvhClientMTLS defines the configuration required
+                              to authenticate to OVHcloud's Secret Manager using mTLS.
+                            properties:
+                              certSecretRef:
+                                description: |-
+                                  SecretKeySelector is a reference to a specific 'key' within a Secret resource.
+                                  In some instances, `key` is a required field.
+                                properties:
+                                  key:
+                                    description: |-
+                                      A key in the referenced Secret.
+                                      Some instances of this field may be defaulted, in others it may be required.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[-._a-zA-Z0-9]+$
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      The namespace of the Secret resource being referred to.
+                                      Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                    maxLength: 63
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                    type: string
+                                type: object
+                              keySecretRef:
+                                description: |-
+                                  SecretKeySelector is a reference to a specific 'key' within a Secret resource.
+                                  In some instances, `key` is a required field.
+                                properties:
+                                  key:
+                                    description: |-
+                                      A key in the referenced Secret.
+                                      Some instances of this field may be defaulted, in others it may be required.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[-._a-zA-Z0-9]+$
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      The namespace of the Secret resource being referred to.
+                                      Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                    maxLength: 63
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                    type: string
+                                type: object
+                            required:
+                            - certSecretRef
+                            - keySecretRef
+                            type: object
+                          token:
+                            description: OvhClientToken defines the configuration
+                              required to authenticate to OVHcloud's Secret Manager
+                              using a token.
+                            properties:
+                              tokenSecretRef:
+                                description: |-
+                                  SecretKeySelector is a reference to a specific 'key' within a Secret resource.
+                                  In some instances, `key` is a required field.
+                                properties:
+                                  key:
+                                    description: |-
+                                      A key in the referenced Secret.
+                                      Some instances of this field may be defaulted, in others it may be required.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[-._a-zA-Z0-9]+$
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      The namespace of the Secret resource being referred to.
+                                      Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                    maxLength: 63
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                    type: string
+                                type: object
+                            required:
+                            - tokenSecretRef
+                            type: object
+                        type: object
+                        x-kubernetes-validations:
+                        - message: Exactly one of 'mtls' or 'token' must be specified
+                          rule: (has(self.mtls) && !has(self.token)) || (!has(self.mtls) && has(self.token))
+                      casRequired:
+                        description: 'Enables or disables check-and-set (CAS) (default:
+                          false)'
+                        type: boolean
+                      okmsTimeout:
+                        default: 30
+                        description: 'Setup a timeout in seconds when requests to
+                          the KMS are made (default: 30)'
+                        format: int32
+                        type: integer
+                      okmsid:
+                        description: specifies the OKMS ID
+                        type: string
+                      server:
+                        description: specifies the OKMS server endpoint
+                        type: string
+                    required:
+                    - auth
+                    - okmsid
+                    - server
+                    type: object
                   passbolt:
                     description: |-
                       PassboltProvider provides access to Passbolt secrets manager.

+ 246 - 0
deploy/crds/bundle.yaml

@@ -5814,6 +5814,129 @@ spec:
                         - region
                         - vault
                       type: object
+                    ovh:
+                      description: OvhProvider holds the configuration to synchronize secrets with OVHcloud's Secret Manager.
+                      properties:
+                        auth:
+                          description: Authentication method (mtls or token)
+                          properties:
+                            mtls:
+                              description: OvhClientMTLS defines the configuration required to authenticate to OVHcloud's Secret Manager using mTLS.
+                              properties:
+                                certSecretRef:
+                                  description: |-
+                                    SecretKeySelector is a reference to a specific 'key' within a Secret resource.
+                                    In some instances, `key` is a required field.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                                keySecretRef:
+                                  description: |-
+                                    SecretKeySelector is a reference to a specific 'key' within a Secret resource.
+                                    In some instances, `key` is a required field.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                              required:
+                                - certSecretRef
+                                - keySecretRef
+                              type: object
+                            token:
+                              description: OvhClientToken defines the configuration required to authenticate to OVHcloud's Secret Manager using a token.
+                              properties:
+                                tokenSecretRef:
+                                  description: |-
+                                    SecretKeySelector is a reference to a specific 'key' within a Secret resource.
+                                    In some instances, `key` is a required field.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                              required:
+                                - tokenSecretRef
+                              type: object
+                          type: object
+                        casRequired:
+                          description: 'Enables or disables check-and-set (CAS) (default: false)'
+                          type: boolean
+                        okmsTimeout:
+                          default: 30
+                          description: 'Setup a timeout in seconds when requests to the KMS are made (default: 30)'
+                          format: int32
+                          type: integer
+                        okmsid:
+                          description: specifies the OKMS ID
+                          type: string
+                        server:
+                          description: specifies the OKMS server endpoint
+                          type: string
+                      required:
+                        - auth
+                        - okmsid
+                        - server
+                      type: object
                     passbolt:
                       description: |-
                         PassboltProvider provides access to Passbolt secrets manager.
@@ -17455,6 +17578,129 @@ spec:
                         - region
                         - vault
                       type: object
+                    ovh:
+                      description: OvhProvider holds the configuration to synchronize secrets with OVHcloud's Secret Manager.
+                      properties:
+                        auth:
+                          description: Authentication method (mtls or token)
+                          properties:
+                            mtls:
+                              description: OvhClientMTLS defines the configuration required to authenticate to OVHcloud's Secret Manager using mTLS.
+                              properties:
+                                certSecretRef:
+                                  description: |-
+                                    SecretKeySelector is a reference to a specific 'key' within a Secret resource.
+                                    In some instances, `key` is a required field.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                                keySecretRef:
+                                  description: |-
+                                    SecretKeySelector is a reference to a specific 'key' within a Secret resource.
+                                    In some instances, `key` is a required field.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                              required:
+                                - certSecretRef
+                                - keySecretRef
+                              type: object
+                            token:
+                              description: OvhClientToken defines the configuration required to authenticate to OVHcloud's Secret Manager using a token.
+                              properties:
+                                tokenSecretRef:
+                                  description: |-
+                                    SecretKeySelector is a reference to a specific 'key' within a Secret resource.
+                                    In some instances, `key` is a required field.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                              required:
+                                - tokenSecretRef
+                              type: object
+                          type: object
+                        casRequired:
+                          description: 'Enables or disables check-and-set (CAS) (default: false)'
+                          type: boolean
+                        okmsTimeout:
+                          default: 30
+                          description: 'Setup a timeout in seconds when requests to the KMS are made (default: 30)'
+                          format: int32
+                          type: integer
+                        okmsid:
+                          description: specifies the OKMS ID
+                          type: string
+                        server:
+                          description: specifies the OKMS server endpoint
+                          type: string
+                      required:
+                        - auth
+                        - okmsid
+                        - server
+                      type: object
                     passbolt:
                       description: |-
                         PassboltProvider provides access to Passbolt secrets manager.

+ 209 - 0
docs/api/spec.md

@@ -8023,6 +8023,203 @@ External Secrets meta/v1.SecretKeySelector
 </tr>
 </tbody>
 </table>
+<h3 id="external-secrets.io/v1.OvhAuth">OvhAuth
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1.OvhProvider">OvhProvider</a>)
+</p>
+<p>
+<p>OvhAuth tells the controller how to authenticate to OVHcloud&rsquo;s Secret Manager, either using mTLS or a token.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>mtls</code></br>
+<em>
+<a href="#external-secrets.io/v1.OvhClientMTLS">
+OvhClientMTLS
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+</td>
+</tr>
+<tr>
+<td>
+<code>token</code></br>
+<em>
+<a href="#external-secrets.io/v1.OvhClientToken">
+OvhClientToken
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1.OvhClientMTLS">OvhClientMTLS
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1.OvhAuth">OvhAuth</a>)
+</p>
+<p>
+<p>OvhClientMTLS defines the configuration required to authenticate to OVHcloud&rsquo;s Secret Manager using mTLS.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>certSecretRef</code></br>
+<em>
+<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#SecretKeySelector">
+External Secrets meta/v1.SecretKeySelector
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>keySecretRef</code></br>
+<em>
+<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#SecretKeySelector">
+External Secrets meta/v1.SecretKeySelector
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1.OvhClientToken">OvhClientToken
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1.OvhAuth">OvhAuth</a>)
+</p>
+<p>
+<p>OvhClientToken defines the configuration required to authenticate to OVHcloud&rsquo;s Secret Manager using a token.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>tokenSecretRef</code></br>
+<em>
+<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#SecretKeySelector">
+External Secrets meta/v1.SecretKeySelector
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1.OvhProvider">OvhProvider
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1.SecretStoreProvider">SecretStoreProvider</a>)
+</p>
+<p>
+<p>OvhProvider holds the configuration to synchronize secrets with OVHcloud&rsquo;s Secret Manager.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>server</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<p>specifies the OKMS server endpoint</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>okmsid</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<p>specifies the OKMS ID</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>casRequired</code></br>
+<em>
+bool
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>Enables or disables check-and-set (CAS) (default: false)</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>okmsTimeout</code></br>
+<em>
+uint32
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>Setup a timeout in seconds when requests to the KMS are made (default: 30)</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>auth</code></br>
+<em>
+<a href="#external-secrets.io/v1.OvhAuth">
+OvhAuth
+</a>
+</em>
+</td>
+<td>
+<p>Authentication method (mtls or token)</p>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1.PassboltAuth">PassboltAuth
 </h3>
 <p>
@@ -9030,6 +9227,18 @@ VaultProvider
 </tr>
 <tr>
 <td>
+<code>ovh</code></br>
+<em>
+<a href="#external-secrets.io/v1.OvhProvider">
+OvhProvider
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
 <code>gcpsm</code></br>
 <em>
 <a href="#external-secrets.io/v1.GCPSMProvider">

+ 215 - 0
docs/provider/ovhcloud.md

@@ -0,0 +1,215 @@
+## Secrets Manager
+
+External Secrets Operator integrates with [OVHcloud KMS](https://www.ovhcloud.com/fr/identity-security-operations/key-management-service/).  
+
+This guide demonstrates:
+- how to set up a `ClusterSecretStore`/`SecretStore` with the OVH provider.
+- `ExternalSecret` use cases with examples.
+- `PushSecret` use cases with examples.
+
+This guide assumes:
+- External Secrets Operator is already installed
+- You have access to OVHcloud Secret Manager
+- Required credentials are already created
+
+### <u>SecretStore</u>
+
+**OVH provider supports both `token` and `mTLS` authentication.**
+
+Token authentication:
+```yaml
+{% include 'ovh-token-secret-store.yaml' %}
+```
+mTLS authentication:
+```yaml
+{% include 'ovh-mtls-secret-store.yaml' %}
+```
+
+!!! note
+     A `ClusterSecretStore` configuration is the same except you have to provide the `tokenSecretRef` `namespace`.  
+     `ExternalSecret` objects must be in the same `namespace` as the `tokenSecretRef` provided to your `ClusterSecretStore`.
+
+### <u>ExternalSecret</u>
+ 
+For these examples, we will assume you have the following secret in your Secret Manager:
+```json
+{
+  "path": "creds",
+  "data": {
+    "type": "credential",
+    "users": {
+      "kevin": {
+        "token": "kevin token value"
+      },
+      "laura": {
+        "token": "laura token value"
+      }
+    }
+  }
+}
+```
+`path` refers to the secret's path in OVH Secret Manager.
+
+```yaml
+{% include 'ovh-external-secret-example.yaml' %}
+```
+
+| Field      | Description                                                            | Required |
+|------------|------------------------------------------------------------------------|----------|
+| version    | Secret version to retrieve                                             | No       |
+| property   | Specific key or nested key in the secret                               | No       |
+| secretKey  | The key inside the Kubernetes Secret that will hold the secret's value | Yes      |
+
+#### Fetch the whole secret
+
+- Using `spec.data`
+```yaml
+{% include 'ovh-external-secret-data.yaml' %}
+```
+Resulting Kubernetes Secret data:
+```json
+{
+  "foo": {
+    "type": "credential",
+    "users": {
+      "kevin": {
+        "token": "kevin token value"
+      },
+      "laura": {
+        "token": "laura token value"
+      }
+    }
+  }
+}
+```
+- Using `spec.dataFrom.extract`
+```yaml
+{% include 'ovh-external-secret-dataFrom-extract.yaml' %}
+```
+Resulting Kubernetes Secret data:
+```json
+{
+  "type": "credential",
+  "users": {
+    "kevin": {
+      "token": "kevin token value"
+    },
+    "laura": {
+      "token": "laura token value"
+    }
+  }
+}
+```
+
+#### Fetch scalar/nested values
+- Scalar value using `data`
+```yaml
+{% include 'ovh-external-secret-data-property.yaml' %}
+```
+Resulting Kubernetes Secret data:
+```json
+{
+  "foo": "credential"
+}
+```
+- Nested value using `data`
+```yaml
+{% include 'ovh-external-secret-data-nested-property.yaml' %}
+```
+Resulting Kubernetes Secret data:
+```json
+{
+  "kevin-token": "kevin token value"
+}
+```
+- Nested value using `dataFrom.extract`
+```yaml
+{% include 'ovh-external-secret-dataFrom-extract-property.yaml' %}
+```
+Resulting Kubernetes Secret data:
+```json
+{
+  "kevin": {
+    "token": "kevin token value"
+  },
+  "laura": {
+    "token": "laura token value"
+  }
+}
+```
+
+!!! warning
+     Scalar values cannot be retrieved using `dataFrom.extract` because no Kubernetes secret key can be specified, which would imply storing a value without a corresponding key.
+
+#### Fetch multiple secrets
+
+Extract multiple secrets, with filtering support.  
+You can filter either by path or/and regular expression. Path filtering occurs first if you use both.
+
+For these examples, we will assume you have the following secrets in your Secret Manager: `path/to/secret/secret1`, `path/to/secret/secret2`, `path/to/config/config2`, `path/to/config/config3`, `secret-example2`.
+- Path filtering
+```yaml
+{% include 'ovh-external-secret-dataFrom-find-bypath.yaml' %}
+```
+Resulting Kubernetes Secret data:
+```json
+{
+  "path/to/secret/secret1": "secret1 value",
+  "path/to/secret/secret2": "secret2 value"
+}
+```
+!!! note
+     If path is left empty or is "/", every secret will be retrieved from your Secret Manager.
+
+- Regular expression filtering
+```yaml
+{% include 'ovh-external-secret-dataFrom-find-byregexp.yaml' %}
+```
+Resulting Kubernetes Secret data:
+```json
+{
+  "path/to/secret/secret2": "secret2 value",
+  "path/to/config/config2": "config2 value",
+  "path/to/config/config3": "config3 value",
+  "secret-example2": "secret-example2 value"
+}
+```
+!!! note
+     If name.regexp is left empty, every secret will be retrieved from your Secret Manager.
+
+- Combination of both
+```yaml
+{% include 'ovh-external-secret-dataFrom-find-byboth.yaml' %}
+```
+Resulting Kubernetes Secret data:
+```json
+{
+  "path/to/secret/secret2": "secret2 value",
+  "path/to/config/config2": "config2 value"
+}
+```
+
+!!! note
+     When both are combined, path filtering occurs first.
+
+### <u>PushSecret</u>
+
+#### Check-And-Set
+Check-And-Set can be enabled/disabled (default: enabled), in the Secret Store configuration:
+```yaml
+{% include 'ovh-secret-store-cas.yaml' %}
+```
+
+#### Secret Rotation
+```yaml
+{% include 'ovh-push-secret-rotation.yaml' %}
+```
+
+With this configuration, the secret is automatically rotated every 6 hours in the OVH Secret Manager.
+
+#### Secret migration
+```yaml
+{% include 'ovh-push-secret-migration.yaml' %}
+```
+
+This example demonstrates how to fetch a secret from a HashiCorp Vault KV secrets engine and sync it into OVH Secret Manager.

+ 16 - 0
docs/snippets/ovh-external-secret-data-nested-property.yaml

@@ -0,0 +1,16 @@
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  name: external-secret-ovh
+  namespace: default
+spec:
+  secretStoreRef:
+    name: secret-store-ovh
+    kind: SecretStore
+  target:
+    name: secret-example
+  data:
+    - secretKey: foo
+      remoteRef:
+        key: creds
+        property: users.kevin.token

+ 16 - 0
docs/snippets/ovh-external-secret-data-property.yaml

@@ -0,0 +1,16 @@
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  name: external-secret-ovh
+  namespace: default
+spec:
+  secretStoreRef:
+    name: secret-store-ovh
+    kind: SecretStore
+  target:
+    name: secret-example
+  data:
+    - secretKey: foo
+      remoteRef:
+        key: creds
+        property: type

+ 15 - 0
docs/snippets/ovh-external-secret-data.yaml

@@ -0,0 +1,15 @@
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  name: external-secret-ovh
+  namespace: default
+spec:
+  secretStoreRef:
+    name: secret-store-ovh
+    kind: SecretStore
+  target:
+    name: secret-example
+  data:
+    - secretKey: foo
+      remoteRef:
+        key: creds

+ 15 - 0
docs/snippets/ovh-external-secret-dataFrom-extract-property.yaml

@@ -0,0 +1,15 @@
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  name: external-secret-ovh
+  namespace: default
+spec:
+  secretStoreRef:
+    name: secret-store-ovh
+    kind: SecretStore
+  target:
+    name: secret-example
+  dataFrom:
+  - extract:
+      key: creds
+      property: users

+ 14 - 0
docs/snippets/ovh-external-secret-dataFrom-extract.yaml

@@ -0,0 +1,14 @@
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  name: external-secret-ovh
+  namespace: default
+spec:
+  secretStoreRef:
+    name: secret-store-ovh
+    kind: SecretStore
+  target:
+    name: secret-example
+  dataFrom:
+  - extract:
+      key: creds

+ 16 - 0
docs/snippets/ovh-external-secret-dataFrom-find-byboth.yaml

@@ -0,0 +1,16 @@
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  name: external-secret-ovh
+  namespace: default
+spec:
+  secretStoreRef:
+    name: secret-store-ovh
+    kind: SecretStore
+  target:
+    name: secret-example
+  dataFrom:
+  - find:
+      path: "path/to"
+      name:
+        regexp: "2$"

+ 14 - 0
docs/snippets/ovh-external-secret-dataFrom-find-bypath.yaml

@@ -0,0 +1,14 @@
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  name: external-secret-ovh
+  namespace: default
+spec:
+  secretStoreRef:
+    name: secret-store-ovh
+    kind: SecretStore
+  target:
+    name: secret-example
+  dataFrom:
+  - find:
+      path: "path/to/secret"

+ 15 - 0
docs/snippets/ovh-external-secret-dataFrom-find-byregexp.yaml

@@ -0,0 +1,15 @@
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  name: external-secret-ovh
+  namespace: default
+spec:
+  secretStoreRef:
+    name: secret-store-ovh
+    kind: SecretStore
+  target:
+    name: secret-example
+  dataFrom:
+  - find:
+      name:
+        regexp: "[2-3]"

+ 17 - 0
docs/snippets/ovh-external-secret-example.yaml

@@ -0,0 +1,17 @@
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  name: external-secret-ovh
+  namespace: default
+spec:
+  secretStoreRef:
+    name: secret-store-ovh
+    kind: SecretStore
+  target:
+    name: secret-example
+  data:
+    - secretKey: foo
+      remoteRef:
+        key: creds
+        version: version
+        property: property

+ 28 - 0
docs/snippets/ovh-mtls-secret-store.yaml

@@ -0,0 +1,28 @@
+apiVersion: external-secrets.io/v1
+kind: SecretStore
+metadata:
+  name: secret-store-ovh
+  namespace: default
+spec:
+  provider:
+    ovh:
+      server: "https://eu-west-rbx.okms.ovh.net"
+      okmsid: "734b9b45-8b1a-469c-b140-b10bd6540017"
+      auth:
+        mtls:
+          certSecretRef:
+            name: ovh-mtls
+            key: tls.crt
+          keySecretRef:
+            name: ovh-mtls
+            key: tls.key
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: ovh-mtls
+  namespace: default
+type: kubernetes.io/tls
+data:
+  tls.crt: BASE64_CERT_PLACEHOLDER # "client certificate value"
+  tls.key: BASE64_KEY_PLACEHOLDER  # "client key value"

+ 66 - 0
docs/snippets/ovh-push-secret-migration.yaml

@@ -0,0 +1,66 @@
+apiVersion: external-secrets.io/v1
+kind: SecretStore
+metadata:
+  name: secret-store-vault
+  namespace: default
+spec:
+  provider:
+    vault:
+      server: "https://my.vault.server:8200"
+      path: "secret"
+      version: "v2"
+      auth:
+        tokenSecretRef:
+          name: vault-token
+          key: token
+---
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  name: external-secret-vault
+  namespace: default
+spec:
+  secretStoreRef:
+    name: secret-store-vault
+    kind: SecretStore
+  refreshPolicy: Periodic
+  refreshInterval: "10s"
+  target:
+    name: creds-secret-vault
+  dataFrom:
+    - extract:
+        key: example
+---
+apiVersion: external-secrets.io/v1
+kind: SecretStore
+metadata:
+  name: secret-store-ovh
+  namespace: default
+spec:
+  provider:
+    ovh:
+      server: <kms-endpoint>
+      okmsid: <okms-id>
+      auth:
+        token:
+          tokenSecretRef:
+            name: ovh-token
+            key: token
+---
+apiVersion: external-secrets.io/v1alpha1
+kind: PushSecret
+metadata:
+  name: push-secret-ovh
+spec:
+  secretStoreRefs:
+    - name: secret-store-ovh
+      kind: SecretStore
+  selector:
+    secret:
+      name: creds-secret-vault
+  refreshInterval: 10s
+  data:
+    - match:
+        secretKey: "secretKey"
+        remoteRef:
+          remoteKey: "creds-secret-migrated"

+ 31 - 0
docs/snippets/ovh-push-secret-rotation.yaml

@@ -0,0 +1,31 @@
+apiVersion: generators.external-secrets.io/v1alpha1
+kind: Password
+metadata:
+  name: my-password-generator
+spec:
+  length: 32
+  digits: 5
+  symbols: 5
+  symbolCharacters: "-_^$%*ù/;:,?"
+  noUpper: false
+  allowRepeat: true
+---
+apiVersion: external-secrets.io/v1alpha1
+kind: PushSecret
+metadata:
+  name: push-secret-ovh
+spec:
+  refreshInterval: 6h0m0s
+  secretStoreRefs:
+    - name: secret-store-ovh
+      kind: SecretStore
+  selector:
+    generatorRef:
+      apiVersion: generators.external-secrets.io/v1alpha1
+      kind: Password
+      name: my-password-generator
+  data:
+    - match:
+        secretKey: password # property in the generator output
+        remoteRef:
+          remoteKey: prod/myql/password

+ 23 - 0
docs/snippets/ovh-secret-store-cas.yaml

@@ -0,0 +1,23 @@
+apiVersion: external-secrets.io/v1
+kind: SecretStore
+metadata:
+  name: secret-store-ovh
+  namespace: default
+spec:
+  provider:
+    ovh:
+      server: <kms-endpoint>
+      okmsid: <okms-id>
+      auth:
+        token:
+          tokenSecretRef:
+            name: ovh-token
+            key: token
+      casRequired: false
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: ovh-token
+data:
+  token: BASE64_TOKEN_PLACEHOLDER # "token value"

+ 22 - 0
docs/snippets/ovh-token-secret-store.yaml

@@ -0,0 +1,22 @@
+apiVersion: external-secrets.io/v1 
+kind: SecretStore
+metadata:
+  name: secret-store-ovh
+  namespace: default
+spec:
+  provider:
+    ovh:
+      server: <kms-endpoint>
+      okmsid: <okms-id>
+      auth:
+        token:
+          tokenSecretRef:
+            name: ovh-token
+            key: token
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: ovh-token
+data:
+  token: dG9rZW4gdmFsdWU= # "token value"

+ 5 - 0
go.mod

@@ -45,6 +45,7 @@ replace (
 	github.com/external-secrets/external-secrets/providers/v1/onepassword => ./providers/v1/onepassword
 	github.com/external-secrets/external-secrets/providers/v1/onepasswordsdk => ./providers/v1/onepasswordsdk
 	github.com/external-secrets/external-secrets/providers/v1/oracle => ./providers/v1/oracle
+	github.com/external-secrets/external-secrets/providers/v1/ovh => ./providers/v1/ovh
 	github.com/external-secrets/external-secrets/providers/v1/passbolt => ./providers/v1/passbolt
 	github.com/external-secrets/external-secrets/providers/v1/passworddepot => ./providers/v1/passworddepot
 	github.com/external-secrets/external-secrets/providers/v1/previder => ./providers/v1/previder
@@ -157,6 +158,7 @@ require (
 	github.com/external-secrets/external-secrets/providers/v1/onepassword v0.0.0-00010101000000-000000000000
 	github.com/external-secrets/external-secrets/providers/v1/onepasswordsdk v0.0.0-00010101000000-000000000000
 	github.com/external-secrets/external-secrets/providers/v1/oracle v0.0.0-00010101000000-000000000000
+	github.com/external-secrets/external-secrets/providers/v1/ovh v0.0.0-00010101000000-000000000000
 	github.com/external-secrets/external-secrets/providers/v1/passbolt v0.0.0-00010101000000-000000000000
 	github.com/external-secrets/external-secrets/providers/v1/passworddepot v0.0.0-00010101000000-000000000000
 	github.com/external-secrets/external-secrets/providers/v1/previder v0.0.0-00010101000000-000000000000
@@ -199,6 +201,7 @@ require (
 	github.com/ProtonMail/gopenpgp/v2 v2.9.0 // indirect
 	github.com/agext/levenshtein v1.2.3 // indirect
 	github.com/akeylesslabs/akeyless-go/v4 v4.3.0 // indirect
+	github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
 	github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
 	github.com/atotto/clipboard v0.1.4 // indirect
 	github.com/aws/aws-sdk-go-v2 v1.39.6 // indirect
@@ -313,7 +316,9 @@ require (
 	github.com/muesli/cancelreader v0.2.2 // indirect
 	github.com/muesli/termenv v0.16.0 // indirect
 	github.com/ngrok/ngrok-api-go/v7 v7.6.0 // indirect
+	github.com/oapi-codegen/runtime v1.1.2 // indirect
 	github.com/opentracing/basictracer-go v1.1.0 // indirect
+	github.com/ovh/okms-sdk-go v0.5.1 // indirect
 	github.com/passbolt/go-passbolt v0.7.2 // indirect
 	github.com/pgavlin/fx v0.1.6 // indirect
 	github.com/pgavlin/fx/v2 v2.0.12 // indirect

+ 10 - 0
go.sum

@@ -164,6 +164,7 @@ github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ek
 github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
 github.com/ProtonMail/gopenpgp/v2 v2.9.0 h1:ruLzBmwe4dR1hdnrsEJ/S7psSBmV15gFttFUPP/+/kE=
 github.com/ProtonMail/gopenpgp/v2 v2.9.0/go.mod h1:IldDyh9Hv1ZCCYatTuuEt1XZJ0OPjxLpTarDfglih7s=
+github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
 github.com/Shopify/sarama v1.30.1/go.mod h1:hGgx05L/DiW8XYBXeJdKIN6V2QUy2H6JqME5VT1NLRw=
 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
@@ -189,6 +190,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
 github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
+github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
+github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
 github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
 github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -259,6 +262,7 @@ github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdn
 github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
 github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
 github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
+github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
 github.com/bradleyfalzon/ghinstallation/v2 v2.17.0 h1:SmbUK/GxpAspRjSQbB6ARvH+ArzlNzTtHydNyXUQ6zg=
 github.com/bradleyfalzon/ghinstallation/v2 v2.17.0/go.mod h1:vuD/xvJT9Y+ZVZRv4HQ42cMyPFIYqpc7AbB4Gvt/DlY=
 github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
@@ -758,6 +762,7 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
@@ -893,6 +898,8 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI
 github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
 github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
 github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
+github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI=
+github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
 github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -919,6 +926,8 @@ github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mo
 github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE=
 github.com/oracle/oci-go-sdk/v65 v65.103.0 h1:HfyZx+JefCPK3At0Xt45q+wr914jDXuoyzOFX3XCbno=
 github.com/oracle/oci-go-sdk/v65 v65.103.0/go.mod h1:oB8jFGVc/7/zJ+DbleE8MzGHjhs2ioCz5stRTdZdIcY=
+github.com/ovh/okms-sdk-go v0.5.1 h1:oS8w/BXyGgnBzaGh3zFIyw73LwJ2B+UkNqeVBT2epUU=
+github.com/ovh/okms-sdk-go v0.5.1/go.mod h1:dJpK0dmnRphZgttfsjg6c5yLgcZp6LXPR/6CFwxY124=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/passbolt/go-passbolt v0.7.2 h1:1kmtMq9Banqj5b6dFHV5M4M/1dOzdY0/gEjuj/JKDRs=
@@ -1044,6 +1053,7 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
 github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
 github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
 github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs=
 github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
 github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=

+ 30 - 0
pkg/register/ovh.go

@@ -0,0 +1,30 @@
+//go:build ovh || all_providers
+
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package register provides explicit registration of all providers and generators.
+package register
+
+import (
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	ovh "github.com/external-secrets/external-secrets/providers/v1/ovh"
+)
+
+func init() {
+	// Register ovh provider
+	esv1.Register(ovh.NewProvider(), ovh.ProviderSpec(), ovh.MaintenanceStatus())
+}

+ 25 - 0
providers/v1/ovh/client_close.go

@@ -0,0 +1,25 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package ovh
+
+import (
+	"context"
+)
+
+func (cl *ovhClient) Close(_ context.Context) error {
+	return nil
+}

+ 36 - 0
providers/v1/ovh/client_delete_secret.go

@@ -0,0 +1,36 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package ovh
+
+import (
+	"context"
+	"errors"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+)
+
+// If deletionPolicy is set to Delete, the Secret Manager Secret
+// created from the Push Secret will be automatically removed
+// when the associated Push Secret is deleted.
+func (cl *ovhClient) DeleteSecret(ctx context.Context, remoteRef esv1.PushSecretRemoteRef) error {
+	err := cl.okmsClient.DeleteSecretV2(ctx, cl.okmsID, remoteRef.GetRemoteKey())
+
+	if err != nil && errors.Is(handleOkmsError(err), esv1.NoSecretErr) {
+		return nil
+	}
+	return err
+}

+ 196 - 0
providers/v1/ovh/client_get_all_secrets.go

@@ -0,0 +1,196 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package ovh
+
+import (
+	"context"
+	"errors"
+	"regexp"
+
+	"github.com/google/uuid"
+	"github.com/ovh/okms-sdk-go/types"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+)
+
+// GetAllSecret retrieves multiple secrets from the Secret Manager.
+// You can optionally filter secrets by name using a regular expression.
+// When path is set to "/" or left empty, the search starts from the Secret Manager root.
+func (cl *ovhClient) GetAllSecrets(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
+	// List Secret Manager secrets.
+	secrets, err := getSecretsList(ctx, cl.okmsClient, cl.okmsID, ref.Path)
+	if err != nil {
+		return map[string][]byte{}, err
+	}
+	if len(secrets) == 0 {
+		return map[string][]byte{}, errors.New("no secrets found in the secret manager")
+	}
+
+	// Compile the regular expression defined in ref.Name.RegExp, if present.
+	var regex *regexp.Regexp
+
+	if ref.Name != nil {
+		regex, err = regexp.Compile(ref.Name.RegExp)
+		if err != nil || regex == nil {
+			return map[string][]byte{}, errors.New("failed to parse regexp")
+		}
+	}
+
+	return filterSecretsListWithRegexp(ctx, cl, secrets, regex, ref)
+}
+
+// Retrieve secrets located under the specified path.
+// If the path is omitted, all secrets from the Secret Manager are returned.
+func getSecretsList(ctx context.Context, okmsClient OkmsClient, okmsID uuid.UUID, path *string) ([]string, error) {
+	var formatPath string
+
+	// if path ends with '/' (and is not "/"), returns an empty list.
+	// Secrets are not supposed to begin with '/'.
+	if path == nil || *path == "" {
+		formatPath = ""
+	} else if len(*path) > 1 &&
+		(*path)[len(*path)-1] == '/' &&
+		(*path)[len(*path)-2] == '/' {
+		return []string{}, nil
+	} else {
+		formatPath = *path
+	}
+
+	// Ensure `formatPath` does not end with '/', otherwise, GetSecretsMetadata
+	// will not be able to retrieve secrets as it should.
+	if formatPath != "" && formatPath[len(formatPath)-1] == '/' {
+		formatPath = formatPath[:len(formatPath)-1]
+	}
+
+	return recursivelyGetSecretsList(ctx, okmsClient, okmsID, formatPath)
+}
+
+// Recursively traverses the path to retrieve all secrets it contains.
+//
+// The recursion stops when the for loop finishes iterating over the list
+// returned by GetSecretsMetadata, or when an error occurs.
+//
+// A recursive call is triggered whenever a key ends with '/'.
+//
+// Example:
+// Given the secrets ["secret1", "path/secret", "path/to/secret"] stored in the
+// Secret Manager, an initial call to recursivelyGetSecretsList with path="path"
+// will cause GetSecretsMetadata to return ["secret", "to/"]
+// (see Note below for details on this behavior).
+//
+// - "secret" is added to the local secret list.
+// - "to/" triggers a recursive call with path="path/to".
+//
+// In the second call, GetSecretsMetadata returns ["secret"], which is added to
+// the local list. Since no key ends with '/', the recursion stops and the list
+// is returned and merged into the result of the first call.
+//
+// Note: OVH's SDK GetSecretsMetadata does not return full paths.
+// It returns only the next element of the hierarchy, and adds a trailing '/'
+// when the element is a directory (i.e., not the last component).
+//
+// Examples:
+//
+//	secret1 = "path/to/secret1"
+//	secret2 = "path/secret2"
+//	secret3 = "path/secrets/secret3"
+//
+// For the path "path", GetSecretsMetadata returns:
+//
+//	["to/", "secret2", "secrets/"]
+func recursivelyGetSecretsList(ctx context.Context, okmsClient OkmsClient, okmsID uuid.UUID, path string) ([]string, error) {
+	var secrets *types.GetMetadataResponse
+	var err error
+
+	// Retrieve the list of KMS secrets for the given path.
+	// If no path is provided, retrieve all existing secrets from KMS.
+	if path != "" && path[0] == '/' {
+		return []string{}, nil
+	}
+	if secrets, err = okmsClient.GetSecretsMetadata(ctx, okmsID, path, true); err != nil {
+		return nil, err
+	}
+	if secrets == nil || secrets.Data == nil || secrets.Data.Keys == nil || len(*secrets.Data.Keys) == 0 {
+		return nil, nil
+	}
+
+	return secretListLoop(ctx, secrets, okmsClient, okmsID, path)
+}
+
+// Loop over each key under 'path'.
+// If a key represents a directory (ends with '/')
+// and is valid (does not begin with '/' and does not contain successive '/'),
+// a recursive call is made.
+// Otherwise, the key is a secret and is added to the result list.
+func secretListLoop(ctx context.Context, secrets *types.GetMetadataResponse, okmsClient OkmsClient, okmsID uuid.UUID, path string) ([]string, error) {
+	var secretsList []string
+
+	for _, key := range *secrets.Data.Keys {
+		if key == "" || key[0] == '/' {
+			continue
+		}
+
+		var toAppend []string
+		var err error
+		if key[len(key)-1] == '/' {
+			toAppend, err = recursivelyGetSecretsList(ctx, okmsClient, okmsID, joinPath(key[:len(key)-1], path))
+			if err != nil {
+				return nil, err
+			}
+		} else {
+			toAppend = []string{
+				joinPath(key, path),
+			}
+		}
+		secretsList = append(secretsList, toAppend...)
+	}
+
+	return secretsList, nil
+}
+
+func joinPath(key, path string) string {
+	if path != "" {
+		return path + "/" + key
+	}
+	return key
+}
+
+// Filter the list of secrets using a regular expression.
+func filterSecretsListWithRegexp(ctx context.Context, cl *ovhClient, secrets []string, regex *regexp.Regexp, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
+	secretsDataMap := make(map[string][]byte)
+	for _, secret := range secrets {
+		// Insert the secret if no regex is provided;
+		// otherwise, insert only matching secrets.
+		if ref.Name == nil || (regex != nil && regex.MatchString(secret)) {
+			secretToInsert, err := cl.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{
+				Key:                secret,
+				ConversionStrategy: ref.ConversionStrategy,
+				DecodingStrategy:   ref.DecodingStrategy,
+			})
+			if err != nil && !errors.Is(err, esv1.NoSecretErr) {
+				return map[string][]byte{}, err
+			}
+			if !errors.Is(err, esv1.NoSecretErr) {
+				secretsDataMap[secret] = secretToInsert
+			}
+		}
+	}
+	if len(secretsDataMap) == 0 {
+		return map[string][]byte{}, errors.New("no secrets matched the regexp")
+	}
+	return secretsDataMap, nil
+}

+ 224 - 0
providers/v1/ovh/client_get_all_secrets_test.go

@@ -0,0 +1,224 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package ovh
+
+import (
+	"bytes"
+	"context"
+	"testing"
+
+	kclient "sigs.k8s.io/controller-runtime/pkg/client"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	"github.com/external-secrets/external-secrets/providers/v1/ovh/fake"
+)
+
+func TestGetAllSecrets(t *testing.T) {
+	path1 := "pattern1"
+	path2 := "pattern2/test"
+	path3 := "nil resp"
+	path4 := "nil data struct"
+	path5 := "nil secrets list"
+	path6 := "empty secrets list"
+	path7 := "error response"
+	testCases := map[string]struct {
+		shouldmap map[string][]byte
+		errshould string
+		kube      kclient.Client
+		refFind   esv1.ExternalSecretFind
+	}{
+		"No secrets found (nil response)": {
+			errshould: "no secrets found in the secret manager",
+			refFind: esv1.ExternalSecretFind{
+				Path: &path3,
+			},
+		},
+		"No secrets found (nil Data struct)": {
+			errshould: "no secrets found in the secret manager",
+			refFind: esv1.ExternalSecretFind{
+				Path: &path4,
+			},
+		},
+		"No secrets found (nil secrets list)": {
+			errshould: "no secrets found in the secret manager",
+			refFind: esv1.ExternalSecretFind{
+				Path: &path5,
+			},
+		},
+		"No secrets found (empty secrets list)": {
+			errshould: "no secrets found in the secret manager",
+			refFind: esv1.ExternalSecretFind{
+				Path: &path6,
+			},
+		},
+		"Error response": {
+			errshould: "error response",
+			refFind: esv1.ExternalSecretFind{
+				Path: &path7,
+			},
+		},
+		"Invalid Regex": {
+			errshould: "failed to parse regexp",
+			refFind: esv1.ExternalSecretFind{
+				Name: &esv1.FindName{
+					RegExp: "\\wa\\w([a]",
+				},
+			},
+		},
+		"Empty Regex": {
+			shouldmap: map[string][]byte{
+				"pattern1/path1":            []byte("{\"projects\":{\"project1\":\"Name\",\"project2\":\"Name\"}}"),
+				"pattern1/path2":            []byte("{\"key\":\"value\"}"),
+				"pattern1/path3":            []byte("{\"root\":{\"sub1\":{\"value\":\"string\"},\"sub2\":\"Name\"},\"test\":\"value\",\"test1\":\"value1\"}"),
+				"pattern2/test/test-secret": []byte("{\"test4\":\"value4\"}"),
+				"pattern2/test/test.secret": []byte("{\"test5\":\"value5\"}"),
+				"pattern2/secret":           []byte("{\"test6\":\"value6\"}"),
+				"1secret":                   []byte("{\"test7\":\"value7\"}"),
+				"pattern2/test/test;secret": []byte("{\"test8\":\"value8\"}"),
+			},
+			refFind: esv1.ExternalSecretFind{
+				Name: &esv1.FindName{
+					RegExp: "",
+				},
+			},
+		},
+		"No Regexp Match": {
+			errshould: "no secrets matched the regexp",
+			refFind: esv1.ExternalSecretFind{
+				Name: &esv1.FindName{
+					RegExp: "^noMatch.*$",
+				},
+			},
+		},
+		"Regex pattern containing '.' or '-' only": {
+			shouldmap: map[string][]byte{
+				"pattern2/test/test-secret": []byte("{\"test4\":\"value4\"}"),
+				"pattern2/test/test.secret": []byte("{\"test5\":\"value5\"}"),
+			},
+			refFind: esv1.ExternalSecretFind{
+				Name: &esv1.FindName{
+					RegExp: ".*[.|-].*",
+				},
+			},
+		},
+		"Regex pattern starting with alphanumeric character": {
+			shouldmap: map[string][]byte{
+				"pattern1/path1":            []byte("{\"projects\":{\"project1\":\"Name\",\"project2\":\"Name\"}}"),
+				"pattern1/path2":            []byte("{\"key\":\"value\"}"),
+				"pattern1/path3":            []byte("{\"root\":{\"sub1\":{\"value\":\"string\"},\"sub2\":\"Name\"},\"test\":\"value\",\"test1\":\"value1\"}"),
+				"pattern2/test/test-secret": []byte("{\"test4\":\"value4\"}"),
+				"pattern2/test/test.secret": []byte("{\"test5\":\"value5\"}"),
+				"pattern2/secret":           []byte("{\"test6\":\"value6\"}"),
+				"pattern2/test/test;secret": []byte("{\"test8\":\"value8\"}"),
+			},
+			refFind: esv1.ExternalSecretFind{
+				Name: &esv1.FindName{
+					RegExp: "^[A-Za-z].*$",
+				},
+			},
+		},
+		"Regex pattern without ';' character": {
+			shouldmap: map[string][]byte{
+				"pattern1/path1":            []byte("{\"projects\":{\"project1\":\"Name\",\"project2\":\"Name\"}}"),
+				"pattern1/path2":            []byte("{\"key\":\"value\"}"),
+				"pattern1/path3":            []byte("{\"root\":{\"sub1\":{\"value\":\"string\"},\"sub2\":\"Name\"},\"test\":\"value\",\"test1\":\"value1\"}"),
+				"pattern2/test/test-secret": []byte("{\"test4\":\"value4\"}"),
+				"pattern2/test/test.secret": []byte("{\"test5\":\"value5\"}"),
+				"pattern2/secret":           []byte("{\"test6\":\"value6\"}"),
+				"1secret":                   []byte("{\"test7\":\"value7\"}"),
+			},
+			refFind: esv1.ExternalSecretFind{
+				Name: &esv1.FindName{
+					RegExp: "^[^;]+$",
+				},
+			},
+		},
+		"Path pattern1": {
+			shouldmap: map[string][]byte{
+				"pattern1/path1": []byte("{\"projects\":{\"project1\":\"Name\",\"project2\":\"Name\"}}"),
+				"pattern1/path2": []byte("{\"key\":\"value\"}"),
+				"pattern1/path3": []byte("{\"root\":{\"sub1\":{\"value\":\"string\"},\"sub2\":\"Name\"},\"test\":\"value\",\"test1\":\"value1\"}"),
+			},
+			refFind: esv1.ExternalSecretFind{
+				Path: &path1,
+			},
+		},
+		"Path pattern2/test": {
+			shouldmap: map[string][]byte{
+				"pattern2/test/test-secret": []byte("{\"test4\":\"value4\"}"),
+				"pattern2/test/test.secret": []byte("{\"test5\":\"value5\"}"),
+				"pattern2/test/test;secret": []byte("{\"test8\":\"value8\"}"),
+			},
+			refFind: esv1.ExternalSecretFind{
+				Path: &path2,
+			},
+		},
+		"Secrets found without path": {
+			shouldmap: map[string][]byte{
+				"pattern1/path1":            []byte("{\"projects\":{\"project1\":\"Name\",\"project2\":\"Name\"}}"),
+				"pattern1/path2":            []byte("{\"key\":\"value\"}"),
+				"pattern1/path3":            []byte("{\"root\":{\"sub1\":{\"value\":\"string\"},\"sub2\":\"Name\"},\"test\":\"value\",\"test1\":\"value1\"}"),
+				"pattern2/test/test-secret": []byte("{\"test4\":\"value4\"}"),
+				"pattern2/test/test.secret": []byte("{\"test5\":\"value5\"}"),
+				"pattern2/secret":           []byte("{\"test6\":\"value6\"}"),
+				"1secret":                   []byte("{\"test7\":\"value7\"}"),
+				"pattern2/test/test;secret": []byte("{\"test8\":\"value8\"}"),
+			},
+		},
+	}
+
+	ctx := context.Background()
+	for name, testCase := range testCases {
+		t.Run(name, func(t *testing.T) {
+			cl := &ovhClient{
+				okmsClient: fake.FakeOkmsClient{
+					TestCase: name,
+				},
+				kube: testCase.kube,
+			}
+			secrets, err := cl.GetAllSecrets(ctx, testCase.refFind)
+
+			if err != nil && (err.Error() == "unknown case" || err.Error() == "unknown path") {
+				t.Fatalf("unexpected fake client case: %v", err)
+			}
+			if testCase.errshould != "" {
+				if err == nil {
+					t.Error()
+				}
+				if err.Error() != testCase.errshould {
+					t.Error()
+				}
+				return
+			}
+			if err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+			if len(testCase.shouldmap) != 0 {
+				if len(testCase.shouldmap) != len(secrets) {
+					t.Error()
+				}
+				for key, value := range secrets {
+					if _, ok := testCase.shouldmap[key]; !ok {
+						t.Error()
+					} else if !bytes.Equal(testCase.shouldmap[key], value) {
+						t.Error()
+					}
+				}
+			}
+		})
+	}
+}

+ 36 - 0
providers/v1/ovh/client_get_secret.go

@@ -0,0 +1,36 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package ovh
+
+import (
+	"context"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+)
+
+// GetSecret retrieves a single secret from the provider.
+// The created secret will store the entire secret value under the specified key.
+// You can specify a key, a property and a version.
+func (cl *ovhClient) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
+	// Retrieve the KMS secret using the OVH SDK.
+	secretData, _, err := getSecretWithOvhSDK(ctx, cl.okmsClient, cl.okmsID, ref)
+	if err != nil {
+		return []byte{}, err
+	}
+
+	return secretData, nil
+}

+ 59 - 0
providers/v1/ovh/client_get_secret_map.go

@@ -0,0 +1,59 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package ovh
+
+import (
+	"context"
+	"encoding/json"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	"github.com/external-secrets/external-secrets/runtime/esutils"
+)
+
+// GetSecretMap retrieves a single secret from the provider.
+// The created secret will have the same keys as the Secret Manager secret.
+// You can specify a key, a property, and a version.
+// If a property is provided, it should reference only nested values.
+func (cl *ovhClient) GetSecretMap(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+	// Retrieve secret from KMS.
+	secretDataBytes, _, err := getSecretWithOvhSDK(ctx, cl.okmsClient, cl.okmsID, ref)
+	if err != nil {
+		return map[string][]byte{}, err
+	}
+	if len(secretDataBytes) == 0 {
+		return map[string][]byte{}, nil
+	}
+
+	// Unmarshal the secret value into a map[string]any
+	// so it can be passed to esutils.GetByteValueFromMap.
+	var rawSecretDataMap map[string]any
+	err = json.Unmarshal(secretDataBytes, &rawSecretDataMap)
+	if err != nil {
+		return map[string][]byte{}, err
+	}
+
+	// Convert the map[string]any into map[string][]byte.
+	secretDataMap := make(map[string][]byte, len(rawSecretDataMap))
+	for key := range rawSecretDataMap {
+		secretDataMap[key], err = esutils.GetByteValueFromMap(rawSecretDataMap, key)
+		if err != nil {
+			return map[string][]byte{}, err
+		}
+	}
+
+	return secretDataMap, nil
+}

+ 132 - 0
providers/v1/ovh/client_get_secret_map_test.go

@@ -0,0 +1,132 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package ovh
+
+import (
+	"context"
+	"reflect"
+	"testing"
+
+	kclient "sigs.k8s.io/controller-runtime/pkg/client"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	"github.com/external-secrets/external-secrets/providers/v1/ovh/fake"
+)
+
+func TestGetSecretMap(t *testing.T) {
+	testCases := map[string]struct {
+		shouldmap map[string][]byte
+		errshould string
+		kube      kclient.Client
+		ref       esv1.ExternalSecretDataRemoteRef
+	}{
+		"Valid Secret": {
+			shouldmap: map[string][]byte{
+				"key": []byte("value"),
+			},
+			ref: esv1.ExternalSecretDataRemoteRef{
+				Key: "key",
+			},
+		},
+		"Non-existent Secret": {
+			errshould: "Secret does not exist",
+			ref: esv1.ExternalSecretDataRemoteRef{
+				Key: "key",
+			},
+		},
+		"Secret without data": {
+			shouldmap: map[string][]byte{},
+			ref: esv1.ExternalSecretDataRemoteRef{
+				Key: "key",
+			},
+		},
+		"MetaDataPolicy: Fetch": {
+			errshould: "fetch metadata policy not supported",
+			ref: esv1.ExternalSecretDataRemoteRef{
+				MetadataPolicy: "Fetch",
+				Key:            "key",
+			},
+		},
+		"Valid property that gets Nested Json": {
+			shouldmap: map[string][]byte{
+				"project1": []byte("Name"),
+				"project2": []byte("Name"),
+			},
+			ref: esv1.ExternalSecretDataRemoteRef{
+				Property: "projects",
+				Key:      "key",
+			},
+		},
+		"Invalid property": {
+			errshould: "secret property \"Invalid Property\" not found",
+			ref: esv1.ExternalSecretDataRemoteRef{
+				Property: "Invalid Property",
+				Key:      "key",
+			},
+		},
+		"Empty property": {
+			shouldmap: map[string][]byte{
+				"key": []byte("value"),
+			},
+			ref: esv1.ExternalSecretDataRemoteRef{
+				Property: "",
+				Key:      "key",
+			},
+		},
+		"Secret Version": {
+			shouldmap: map[string][]byte{
+				"key": []byte("value"),
+			},
+			ref: esv1.ExternalSecretDataRemoteRef{
+				Key: "key",
+			},
+		},
+		"Invalid Secret Version": {
+			errshould: "ID=\"\", Request-ID:\"\", Code=17125378, System=CCM, Component=Secret Manager, Category=Not Found",
+			ref: esv1.ExternalSecretDataRemoteRef{
+				Key: "key",
+			},
+		},
+	}
+
+	for name, testCase := range testCases {
+		t.Run(name, func(t *testing.T) {
+			ctx := context.Background()
+			cl := &ovhClient{
+				okmsClient: &fake.FakeOkmsClient{
+					TestCase: name,
+				},
+				kube: testCase.kube,
+			}
+			secret, err := cl.GetSecretMap(ctx, testCase.ref)
+			if testCase.errshould != "" {
+				if err == nil {
+					t.Error()
+				} else if err.Error() != testCase.errshould {
+					t.Error()
+				}
+			} else {
+				if err != nil {
+					t.Error()
+				}
+				if !reflect.DeepEqual(secret, testCase.shouldmap) {
+					t.Error()
+				}
+			}
+		})
+	}
+}

+ 119 - 0
providers/v1/ovh/client_get_secret_test.go

@@ -0,0 +1,119 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package ovh
+
+import (
+	"context"
+	"testing"
+
+	kclient "sigs.k8s.io/controller-runtime/pkg/client"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	"github.com/external-secrets/external-secrets/providers/v1/ovh/fake"
+)
+
+func TestGetSecret(t *testing.T) {
+	testCases := map[string]struct {
+		should    string
+		errshould string
+		kube      kclient.Client
+		ref       esv1.ExternalSecretDataRemoteRef
+	}{
+		"Valid Secret": {
+			should: "{\"key\":\"value\"}",
+			ref: esv1.ExternalSecretDataRemoteRef{
+				Key: "key",
+			},
+		},
+		"Non-existent Secret": {
+			errshould: "Secret does not exist",
+			ref: esv1.ExternalSecretDataRemoteRef{
+				Key: "key",
+			},
+		},
+		"Secret without data": {
+			errshould: "empty secret",
+			ref: esv1.ExternalSecretDataRemoteRef{
+				Key: "key",
+			},
+		},
+		"MetaDataPolicy: Fetch": {
+			errshould: "fetch metadata policy not supported",
+			ref: esv1.ExternalSecretDataRemoteRef{
+				Key:            "key",
+				MetadataPolicy: "Fetch",
+			},
+		},
+		"Valid property that gets Nested Json": {
+			should: "{\"project1\":\"Name\",\"project2\":\"Name\"}",
+			ref: esv1.ExternalSecretDataRemoteRef{
+				Key:      "key",
+				Property: "projects",
+			},
+		},
+		"Valid property that gets non_Nested Json": {
+			should: "Name",
+			ref: esv1.ExternalSecretDataRemoteRef{
+				Key:      "key",
+				Property: "projects.project1",
+			},
+		},
+		"Invalid property": {
+			errshould: "secret property \"Invalid Property\" not found",
+			ref: esv1.ExternalSecretDataRemoteRef{
+				Key:      "key",
+				Property: "Invalid Property",
+			},
+		},
+		"Empty property": {
+			should: "{\"key\":\"value\"}",
+			ref: esv1.ExternalSecretDataRemoteRef{
+				Key:      "key",
+				Property: "",
+			},
+		},
+		"Secret Version": {
+			should: "{\"key\":\"value\"}",
+			ref: esv1.ExternalSecretDataRemoteRef{
+				Key: "key",
+			},
+		},
+		"Invalid Secret Version": {
+			errshould: "ID=\"\", Request-ID:\"\", Code=17125378, System=CCM, Component=Secret Manager, Category=Not Found",
+			ref: esv1.ExternalSecretDataRemoteRef{
+				Key: "key",
+			},
+		},
+	}
+	for name, testCase := range testCases {
+		t.Run(name, func(t *testing.T) {
+			ctx := context.Background()
+			cl := &ovhClient{
+				okmsClient: &fake.FakeOkmsClient{
+					TestCase: name,
+				},
+				kube: testCase.kube,
+			}
+			secret, err := cl.GetSecret(ctx, testCase.ref)
+			if testCase.errshould != "" && err != nil && err.Error() != testCase.errshould {
+				t.Error()
+			} else if testCase.should != "" && string(secret) != testCase.should {
+				t.Error()
+			}
+		})
+	}
+}

+ 194 - 0
providers/v1/ovh/client_push_secret.go

@@ -0,0 +1,194 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package ovh
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"errors"
+
+	"github.com/google/uuid"
+	"github.com/ovh/okms-sdk-go/types"
+	corev1 "k8s.io/api/core/v1"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+)
+
+// Create or update a secret.
+//
+// If updatePolicy is set to Replace, the secret will be written, possibly overwriting an existing secret.
+// If set to IfNotExists, it will not overwrite an existing secret.
+func (cl *ovhClient) PushSecret(ctx context.Context, secret *corev1.Secret, data esv1.PushSecretData) error {
+	if secret == nil {
+		return errors.New("nil secret")
+	}
+	if len(secret.Data) == 0 {
+		return errors.New("cannot push empty secret")
+	}
+
+	// Check if the secret already exists.
+	// This determines which method to use: create or update.
+	remoteSecret, currentVersion, err := getSecretWithOvhSDK(ctx, cl.okmsClient, cl.okmsID, esv1.ExternalSecretDataRemoteRef{
+		Key: data.GetRemoteKey(),
+	})
+	if err != nil && !errors.Is(err, esv1.NoSecretErr) {
+		return err
+	}
+	secretExists := !errors.Is(err, esv1.NoSecretErr)
+
+	// Build the secret to be pushed.
+	secretToPush, err := buildSecretToPush(secret, data)
+	if err != nil {
+		return err
+	}
+
+	// Compare the data of secretToPush with that of remoteSecret.
+	equal, err := compareSecretsData(secretToPush, remoteSecret)
+	if err != nil {
+		return err
+	}
+	if equal {
+		return nil
+	}
+
+	// Set cas according to client configuration
+	if !cl.cas {
+		currentVersion = nil
+	}
+
+	// Push the secret.
+	return pushNewSecret(
+		ctx,
+		cl.okmsClient,
+		cl.okmsID,
+		secretToPush,
+		data.GetRemoteKey(),
+		currentVersion,
+		secretExists)
+}
+
+// Compare the secret to push with the remote secret.
+// If they are equal, do not push the secret.
+func compareSecretsData(secretToPush map[string]any, remoteSecret []byte) (bool, error) {
+	if len(remoteSecret) == 0 {
+		return false, nil
+	}
+
+	secretToPushByte, err := json.Marshal(secretToPush)
+	if err != nil {
+		return false, err
+	}
+
+	return bytes.Equal(secretToPushByte, remoteSecret), nil
+}
+
+// Build the secret to be pushed.
+//
+// If remoteProperty is defined, it will be used as the key to store the secret value.
+// If secretKey is not defined, the entire secret value will be pushed.
+// Otherwise, only the value of the specified secretKey will be pushed.
+func buildSecretToPush(secret *corev1.Secret, data esv1.PushSecretData) (map[string]any, error) {
+	// Retrieve the secret value to push based on secretKey.
+	var secretValueToPush map[string]any
+	var err error
+
+	secretKey := data.GetSecretKey()
+	if secretKey == "" {
+		secretValueToPush, err = extractAllSecretValues(secret.Data)
+	} else {
+		secretValueToPush, err = extractSecretKeyValue(secret.Data, secretKey)
+	}
+
+	if err != nil {
+		return map[string]any{}, err
+	}
+
+	// Build the secret to push using remoteProperty.
+	secretToPush := make(map[string]any)
+	property := data.GetProperty()
+
+	if property == "" {
+		secretToPush = secretValueToPush
+	} else {
+		secretToPush[property] = secretValueToPush
+	}
+
+	return secretToPush, nil
+}
+
+func extractAllSecretValues(data map[string][]byte) (map[string]any, error) {
+	var err error
+	secretValueToPush := make(map[string]any)
+
+	for key, value := range data {
+		var decoded any
+		if json.Unmarshal(value, &decoded) != nil {
+			secretValueToPush[key] = string(value)
+			continue
+		}
+		var cleanJSON []byte
+		if cleanJSON, err = json.Marshal(decoded); err != nil {
+			return map[string]any{}, err
+		}
+
+		secretValueToPush[key] = json.RawMessage(cleanJSON)
+	}
+
+	return secretValueToPush, nil
+}
+
+func extractSecretKeyValue(data map[string][]byte, secretKey string) (map[string]any, error) {
+	secretValueToPush := make(map[string]any)
+
+	value, ok := data[secretKey]
+	if !ok {
+		return nil, errors.New("secretKey not found in secret data")
+	}
+	var decoded any
+	if json.Unmarshal(value, &decoded) != nil {
+		secretValueToPush[secretKey] = string(value)
+	} else {
+		secretValueToPush[secretKey] = json.RawMessage(value)
+	}
+
+	return secretValueToPush, nil
+}
+
+// This pushes the created/updated secret.
+func pushNewSecret(ctx context.Context, okmsClient OkmsClient, okmsID uuid.UUID, secretToPush map[string]any, path string, cas *uint32, secretExists bool) error {
+	var err error
+
+	if !secretExists {
+		// Create a secret.
+		_, err = okmsClient.PostSecretV2(ctx, okmsID, types.PostSecretV2Request{
+			Path: path,
+			Version: types.SecretV2VersionShort{
+				Data: &secretToPush,
+			},
+		})
+	} else {
+		// Update a secret.
+		_, err = okmsClient.PutSecretV2(ctx, okmsID, path, cas, types.PutSecretV2Request{
+			Version: &types.SecretV2VersionShort{
+				Data: &secretToPush,
+			},
+		})
+	}
+
+	return err
+}

+ 237 - 0
providers/v1/ovh/client_push_secret_test.go

@@ -0,0 +1,237 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package ovh
+
+import (
+	"context"
+	"testing"
+
+	v1 "k8s.io/api/core/v1"
+
+	"github.com/external-secrets/external-secrets/providers/v1/ovh/fake"
+	testingfake "github.com/external-secrets/external-secrets/runtime/testing/fake"
+)
+
+func TestPushSecret(t *testing.T) {
+	testCases := map[string]struct {
+		should string
+		secret *v1.Secret
+		data   testingfake.PushSecretData
+	}{
+		"Nil Secret": {
+			should: "nil secret",
+			secret: nil,
+			data: testingfake.PushSecretData{
+				SecretKey: "secretKey",
+				RemoteKey: "remoteKey",
+				Property:  "property",
+			},
+		},
+		"Nil Secret Data": {
+			should: "cannot push empty secret",
+			secret: &v1.Secret{
+				Data: nil,
+			},
+			data: testingfake.PushSecretData{
+				SecretKey: "secretKey",
+				RemoteKey: "remoteKey",
+				Property:  "property",
+			},
+		},
+		"Empty Secret Data": {
+			should: "cannot push empty secret",
+			secret: &v1.Secret{
+				Data: map[string][]byte{},
+			},
+			data: testingfake.PushSecretData{
+				SecretKey: "secretKey",
+				RemoteKey: "remoteKey",
+				Property:  "property",
+			},
+		},
+		"Empty Remote Key": {
+			should: "spec.data.remoteRef.key cannot be empty",
+			secret: &v1.Secret{
+				Data: map[string][]byte{
+					"key": []byte("value"),
+				},
+			},
+			data: testingfake.PushSecretData{
+				RemoteKey: "",
+			},
+		},
+		"Empty Secret Key / Empty Property / Existing Remote Key (Equal Data)": {
+			should: "",
+			secret: &v1.Secret{
+				Data: map[string][]byte{
+					"test4": []byte(`"value4"`),
+				},
+			},
+			data: testingfake.PushSecretData{
+				SecretKey: "",
+				RemoteKey: "pattern2/test/test-secret",
+				Property:  "",
+			},
+		},
+		"Empty Secret Key / Property / Existing Remote Key (Equal Data)": {
+			should: `{"property":{"test4":"value4"}}`,
+			secret: &v1.Secret{
+				Data: map[string][]byte{
+					"test4": []byte(`"value4"`),
+				},
+			},
+			data: testingfake.PushSecretData{
+				SecretKey: "",
+				RemoteKey: "pattern2/test/test-secret",
+				Property:  "property",
+			},
+		},
+		"Empty Secret Key / Empty Property / Existing Remote Key (Non-Equal Data)": {
+			should: `{"new-test4":"new-value4"}`,
+			secret: &v1.Secret{
+				Data: map[string][]byte{
+					"new-test4": []byte(`"new-value4"`),
+				},
+			},
+			data: testingfake.PushSecretData{
+				SecretKey: "",
+				RemoteKey: "pattern2/test/test-secret",
+				Property:  "",
+			},
+		},
+		"Empty Secret Key / Property / Existing Remote Key (Non-Equal Data)": {
+			should: `{"property":{"new-test4":"new-value4"}}`,
+			secret: &v1.Secret{
+				Data: map[string][]byte{
+					"new-test4": []byte(`"new-value4"`),
+				},
+			},
+			data: testingfake.PushSecretData{
+				SecretKey: "",
+				RemoteKey: "pattern2/test/test-secret",
+				Property:  "property",
+			},
+		},
+		"Empty Secret Key / Empty Property / Non-Existent Remote Key": {
+			should: `{"root":{"sub1":{"value":"string"},"sub2":"Name"},"test":"value","test1":"value1"}`,
+			secret: &v1.Secret{
+				Data: map[string][]byte{
+					"root":  []byte(`{"sub1":{"value":"string"},"sub2":"Name"}`),
+					"test":  []byte(`"value"`),
+					"test1": []byte(`"value1"`),
+				},
+			},
+			data: testingfake.PushSecretData{
+				SecretKey: "",
+				RemoteKey: "non-existent",
+				Property:  "",
+			},
+		},
+		"Empty Secret Key / Property / Non-Existent Remote Key": {
+			should: `{"property":{"root":{"sub1":{"value":"string"},"sub2":"Name"},"test":"value","test1":"value1"}}`,
+			secret: &v1.Secret{
+				Data: map[string][]byte{
+					"root":  []byte(`{"sub1":{"value":"string"},"sub2":"Name"}`),
+					"test":  []byte(`"value"`),
+					"test1": []byte(`"value1"`),
+				},
+			},
+			data: testingfake.PushSecretData{
+				SecretKey: "",
+				RemoteKey: "non-existent",
+				Property:  "property",
+			},
+		},
+		"Secret Key / Empty Property / Existing Remote Key": {
+			should: `{"test":"value"}`,
+			secret: &v1.Secret{
+				Data: map[string][]byte{
+					"root":  []byte(`{"sub1":{"value":"string"},"sub2":"Name"}`),
+					"test":  []byte(`"value"`),
+					"test1": []byte(`"value1"`),
+				},
+			},
+			data: testingfake.PushSecretData{
+				SecretKey: "test",
+				RemoteKey: "pattern1/path3",
+				Property:  "",
+			},
+		},
+		"Secret Key / Property / Existing Remote Key": {
+			should: `{"property":{"test":"value"}}`,
+			secret: &v1.Secret{
+				Data: map[string][]byte{
+					"root":  []byte(`{"sub1":{"value":"string"},"sub2":"Name"}`),
+					"test":  []byte(`"value"`),
+					"test1": []byte(`"value1"`),
+				},
+			},
+			data: testingfake.PushSecretData{
+				SecretKey: "test",
+				RemoteKey: "pattern1/path3",
+				Property:  "property",
+			},
+		},
+		"Secret Key / Property / Non-Existent Remote Key": {
+			should: `{"property":{"test":"value"}}`,
+			secret: &v1.Secret{
+				Data: map[string][]byte{
+					"root":  []byte(`{"sub1":{"value":"string"},"sub2":"Name"}`),
+					"test":  []byte(`"value"`),
+					"test1": []byte(`"value1"`),
+				},
+			},
+			data: testingfake.PushSecretData{
+				SecretKey: "test",
+				RemoteKey: "non-existent",
+				Property:  "property",
+			},
+		},
+		"Secret Key / Empty Property / Non-Existent Remote Key": {
+			should: `{"test":"value"}`,
+			secret: &v1.Secret{
+				Data: map[string][]byte{
+					"root":  []byte(`{"sub1":{"value":"string"},"sub2":"Name"}`),
+					"test":  []byte(`"value"`),
+					"test1": []byte(`"value1"`),
+				},
+			},
+			data: testingfake.PushSecretData{
+				SecretKey: "test",
+				RemoteKey: "non-existent",
+				Property:  "",
+			},
+		},
+	}
+
+	ctx := context.Background()
+	for name, testCase := range testCases {
+		t.Run(name, func(t *testing.T) {
+			cl := ovhClient{
+				okmsClient: &fake.FakeOkmsClient{
+					TestCase: name,
+				},
+			}
+			err := cl.PushSecret(ctx, testCase.secret, testCase.data)
+			if err != nil && testCase.should != err.Error() {
+				t.Error()
+			} else if err == nil && testCase.should != "" {
+				t.Error()
+			}
+		})
+	}
+}

+ 36 - 0
providers/v1/ovh/client_secret_exists.go

@@ -0,0 +1,36 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package ovh
+
+import (
+	"context"
+	"errors"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+)
+
+func (cl *ovhClient) SecretExists(ctx context.Context, remoteRef esv1.PushSecretRemoteRef) (bool, error) {
+	// Check if the secret exists using the OVH SDK.
+	_, err := cl.okmsClient.GetSecretV2(ctx, cl.okmsID, remoteRef.GetRemoteKey(), nil, nil)
+	if err != nil && errors.Is(handleOkmsError(err), esv1.NoSecretErr) {
+		return false, nil
+	} else if err != nil {
+		return false, err
+	}
+
+	return true, nil
+}

+ 66 - 0
providers/v1/ovh/client_secret_exists_test.go

@@ -0,0 +1,66 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package ovh
+
+import (
+	"context"
+	"testing"
+
+	kclient "sigs.k8s.io/controller-runtime/pkg/client"
+
+	"github.com/external-secrets/external-secrets/providers/v1/ovh/fake"
+	testingfake "github.com/external-secrets/external-secrets/runtime/testing/fake"
+)
+
+func TestSecretExists(t *testing.T) {
+	testCases := map[string]struct {
+		should    bool
+		errshould string
+		kube      kclient.Client
+		remoteRef testingfake.PushSecretData
+	}{
+		"Valid Secret": {
+			should:    true,
+			remoteRef: testingfake.PushSecretData{},
+		},
+		"Non-existent Secret": {
+			should:    false,
+			remoteRef: testingfake.PushSecretData{},
+		},
+		"Error case": {
+			errshould: "SecretExists error",
+			remoteRef: testingfake.PushSecretData{},
+		},
+	}
+	for name, testCase := range testCases {
+		t.Run(name, func(t *testing.T) {
+			cl := &ovhClient{
+				kube: testCase.kube,
+				okmsClient: &fake.FakeOkmsClient{
+					TestCase: name,
+				},
+			}
+			ctx := context.Background()
+			exists, err := cl.SecretExists(ctx, testCase.remoteRef)
+			if testCase.errshould != "" && err != nil && err.Error() != testCase.errshould {
+				t.Error()
+			} else if (testCase.should && !exists) || (!testCase.should && exists) {
+				t.Error()
+			}
+		})
+	}
+}

+ 127 - 0
providers/v1/ovh/client_utils.go

@@ -0,0 +1,127 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package ovh
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"strconv"
+
+	"github.com/google/uuid"
+	"github.com/ovh/okms-sdk-go"
+	"github.com/tidwall/gjson"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+)
+
+func getSecretWithOvhSDK(ctx context.Context, kmsClient OkmsClient, okmsID uuid.UUID, ref esv1.ExternalSecretDataRemoteRef) ([]byte, *uint32, error) {
+	// Check if the remoteRef key is empty.
+	if ref.Key == "" {
+		return []byte{}, nil, errors.New("spec.data.remoteRef.key cannot be empty")
+	}
+
+	// Check MetaDataPolicy (not supported).
+	if ref.MetadataPolicy == esv1.ExternalSecretMetadataPolicyFetch {
+		return []byte{}, nil, errors.New("fetch metadata policy not supported")
+	}
+
+	// Decode the secret version.
+	versionAddr, err := decodeSecretVersion(ref.Version)
+	if err != nil {
+		return []byte{}, nil, err
+	}
+
+	// Retrieve the KMS secret.
+	includeData := true
+	secret, err := kmsClient.GetSecretV2(ctx, okmsID, ref.Key, versionAddr, &includeData)
+	if err != nil {
+		return []byte{}, nil, handleOkmsError(err)
+	}
+	if secret == nil {
+		return []byte{}, nil, esv1.NoSecretErr
+	}
+	if secret.Version == nil || secret.Version.Data == nil {
+		return []byte{}, nil, errors.New("secret version data is missing")
+	}
+
+	// Retrieve KMS Secret property if needed.
+	var secretData []byte
+
+	if ref.Property == "" {
+		secretData, err = json.Marshal(secret.Version.Data)
+	} else {
+		secretData, err = getPropertyValue(*secret.Version.Data, ref.Property)
+	}
+
+	var currentVersion *uint32
+	if secret.Metadata != nil {
+		currentVersion = secret.Metadata.CurrentVersion
+	}
+	return secretData, currentVersion, err
+}
+
+// Decode a secret version.
+//
+// Returns nil if no version is provided; in that case, the OVH SDK uses the latest version.
+func decodeSecretVersion(strVersion string) (*uint32, error) {
+	var version *uint32
+
+	if strVersion != "" {
+		v, err := strconv.Atoi(strVersion)
+		if err != nil {
+			return version, err
+		}
+		tmpVersion := uint32(v)
+		if int(tmpVersion) != v {
+			return nil, errors.New("overflow occurred while decoding secret version")
+		}
+		version = &tmpVersion
+	}
+	return version, nil
+}
+
+// Retrieve the value of the secret property.
+func getPropertyValue(data map[string]any, property string) ([]byte, error) {
+	// Marshal data into bytes so it can be passed to gjson.Get.
+	secretData, err := json.Marshal(data)
+	if err != nil {
+		return []byte{}, err
+	}
+
+	// Retrieve the property value if it exists.
+	secretDataResult := gjson.Get(string(secretData), property)
+	if !secretDataResult.Exists() {
+		return []byte{}, fmt.Errorf("secret property \"%s\" not found", property)
+	}
+
+	return []byte(secretDataResult.String()), nil
+}
+
+// Returns an okms.KmsError struct representing the KMS response
+// (error_code, error_id, errors, request_id).
+func handleOkmsError(err error) error {
+	okmsError := okms.AsKmsError(err)
+
+	if okmsError == nil {
+		return fmt.Errorf("failed to parse okms error: %w", err)
+	} else if okmsError.ErrorCode == 17125377 {
+		return esv1.NoSecretErr
+	}
+	return okmsError
+}

+ 425 - 0
providers/v1/ovh/fake/fake_okms_client.go

@@ -0,0 +1,425 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package fake
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"strings"
+
+	"github.com/google/uuid"
+	"github.com/ovh/okms-sdk-go"
+	"github.com/ovh/okms-sdk-go/types"
+)
+
+type FakeOkmsClient struct {
+	TestCase string
+}
+
+func (kmsClient FakeOkmsClient) GetSecretV2(_ context.Context, _ uuid.UUID, path string, _ *uint32, _ *bool) (*types.GetSecretV2Response, error) {
+	// Called by GetSecret() & GetSecretMap()
+
+	// Metadata
+	CasRequired := true
+	CreatedAt := "string"
+	DeactivateVersionAfter := "string"
+	MaxVersions := uint32(10)
+	UpdatedAt := "string"
+
+	// Version
+	Data := map[string]any{
+		"key": "value",
+	}
+	NestedData := map[string]any{
+		"projects": map[string]any{
+			"project1": "Name",
+			"project2": "Name",
+		},
+		"test": "value",
+	}
+	DeactivatedAt := "string"
+	Warnings := []string{}
+	dataSecretV2Response := &types.GetSecretV2Response{
+		Metadata: &types.SecretV2Metadata{
+			CasRequired:            &CasRequired,
+			CreatedAt:              &CreatedAt,
+			DeactivateVersionAfter: &DeactivateVersionAfter,
+			MaxVersions:            &MaxVersions,
+			UpdatedAt:              &UpdatedAt,
+		},
+		Path: &kmsClient.TestCase,
+		Version: &types.SecretV2Version{
+			CreatedAt:     "string",
+			Data:          &Data,
+			DeactivatedAt: &DeactivatedAt,
+			Id:            1,
+			State:         "string",
+			Warnings:      &Warnings,
+		},
+	}
+	nestedDataSecretV2Response := &types.GetSecretV2Response{
+		Metadata: &types.SecretV2Metadata{
+			CasRequired:            &CasRequired,
+			CreatedAt:              &CreatedAt,
+			DeactivateVersionAfter: &DeactivateVersionAfter,
+			MaxVersions:            &MaxVersions,
+			UpdatedAt:              &UpdatedAt,
+		},
+		Path: &kmsClient.TestCase,
+		Version: &types.SecretV2Version{
+			CreatedAt:     "string",
+			Data:          &NestedData,
+			DeactivatedAt: &DeactivatedAt,
+			Id:            1,
+			State:         "string",
+			Warnings:      &Warnings,
+		},
+	}
+
+	// Test Cases
+	switch kmsClient.TestCase {
+	case "Valid Secret":
+		return dataSecretV2Response, nil
+	case "MetaDataPolicy: Fetch":
+		return dataSecretV2Response, nil
+	case "Non-existent Secret":
+		kmsError := okms.NewKmsErrorFromBytes([]byte("{\"error_code\":17125377}"))
+		return nil, kmsError
+	case "Secret without data":
+		return &types.GetSecretV2Response{
+			Metadata: &types.SecretV2Metadata{
+				CasRequired:            &CasRequired,
+				CreatedAt:              &CreatedAt,
+				DeactivateVersionAfter: &DeactivateVersionAfter,
+				MaxVersions:            &MaxVersions,
+				UpdatedAt:              &UpdatedAt,
+			},
+			Path: &kmsClient.TestCase,
+			Version: &types.SecretV2Version{
+				CreatedAt:     "string",
+				Data:          &map[string]interface{}{},
+				DeactivatedAt: &DeactivatedAt,
+				Id:            1,
+				State:         "string",
+				Warnings:      &Warnings,
+			},
+		}, nil
+	case "Valid property that gets Nested Json":
+		return nestedDataSecretV2Response, nil
+	case "Valid property that gets non_Nested Json":
+		return nestedDataSecretV2Response, nil
+	case "Invalid property":
+		return dataSecretV2Response, nil
+	case "Empty property":
+		return dataSecretV2Response, nil
+	case "Secret Version":
+		return dataSecretV2Response, nil
+	case "Invalid Secret Version":
+		kmsError := okms.NewKmsErrorFromBytes([]byte("{\"error_code\":17125378}"))
+		return nil, kmsError
+	case "Error case":
+		return &types.GetSecretV2Response{}, errors.New("SecretExists error")
+	}
+
+	if path == "" {
+		return &types.GetSecretV2Response{}, errors.New("unknown case")
+	}
+
+	// Called by GetAllSecrets()
+	data1 := map[string]any{
+		"projects": map[string]any{
+			"project1": "Name",
+			"project2": "Name",
+		},
+	}
+	data2 := map[string]any{
+		"key": "value",
+	}
+	data3 := map[string]any{
+		"root": map[string]any{
+			"sub1": map[string]any{
+				"value": "string",
+			},
+			"sub2": "Name",
+		},
+		"test":  "value",
+		"test1": "value1",
+	}
+	data4 := map[string]any{
+		"test4": "value4",
+	}
+	data5 := map[string]any{
+		"test5": "value5",
+	}
+	data6 := map[string]any{
+		"test6": "value6",
+	}
+	data7 := map[string]any{
+		"test7": "value7",
+	}
+	data8 := map[string]any{
+		"test8": "value8",
+	}
+	switch path {
+	case "pattern1/path1":
+		return &types.GetSecretV2Response{
+			Metadata: &types.SecretV2Metadata{
+				CasRequired:            &CasRequired,
+				CreatedAt:              &CreatedAt,
+				DeactivateVersionAfter: &DeactivateVersionAfter,
+				MaxVersions:            &MaxVersions,
+				UpdatedAt:              &UpdatedAt,
+			},
+			Path: &kmsClient.TestCase,
+			Version: &types.SecretV2Version{
+				CreatedAt:     "string",
+				Data:          &data1,
+				DeactivatedAt: &DeactivatedAt,
+				Id:            1,
+				State:         "string",
+				Warnings:      &Warnings,
+			},
+		}, nil
+	case "pattern1/path2":
+		return &types.GetSecretV2Response{
+			Metadata: &types.SecretV2Metadata{
+				CasRequired:            &CasRequired,
+				CreatedAt:              &CreatedAt,
+				DeactivateVersionAfter: &DeactivateVersionAfter,
+				MaxVersions:            &MaxVersions,
+				UpdatedAt:              &UpdatedAt,
+			},
+			Path: &kmsClient.TestCase,
+			Version: &types.SecretV2Version{
+				CreatedAt:     "string",
+				Data:          &data2,
+				DeactivatedAt: &DeactivatedAt,
+				Id:            1,
+				State:         "string",
+				Warnings:      &Warnings,
+			},
+		}, nil
+	case "pattern1/path3":
+		return &types.GetSecretV2Response{
+			Metadata: &types.SecretV2Metadata{
+				CasRequired:            &CasRequired,
+				CreatedAt:              &CreatedAt,
+				DeactivateVersionAfter: &DeactivateVersionAfter,
+				MaxVersions:            &MaxVersions,
+				UpdatedAt:              &UpdatedAt,
+			},
+			Path: &kmsClient.TestCase,
+			Version: &types.SecretV2Version{
+				CreatedAt:     "string",
+				Data:          &data3,
+				DeactivatedAt: &DeactivatedAt,
+				Id:            1,
+				State:         "string",
+				Warnings:      &Warnings,
+			},
+		}, nil
+	case "pattern2/test/test-secret":
+		return &types.GetSecretV2Response{
+			Metadata: &types.SecretV2Metadata{
+				CasRequired:            &CasRequired,
+				CreatedAt:              &CreatedAt,
+				DeactivateVersionAfter: &DeactivateVersionAfter,
+				MaxVersions:            &MaxVersions,
+				UpdatedAt:              &UpdatedAt,
+			},
+			Path: &kmsClient.TestCase,
+			Version: &types.SecretV2Version{
+				CreatedAt:     "string",
+				Data:          &data4,
+				DeactivatedAt: &DeactivatedAt,
+				Id:            1,
+				State:         "string",
+				Warnings:      &Warnings,
+			},
+		}, nil
+	case "pattern2/test/test.secret":
+		return &types.GetSecretV2Response{
+			Metadata: &types.SecretV2Metadata{
+				CasRequired:            &CasRequired,
+				CreatedAt:              &CreatedAt,
+				DeactivateVersionAfter: &DeactivateVersionAfter,
+				MaxVersions:            &MaxVersions,
+				UpdatedAt:              &UpdatedAt,
+			},
+			Path: &kmsClient.TestCase,
+			Version: &types.SecretV2Version{
+				CreatedAt:     "string",
+				Data:          &data5,
+				DeactivatedAt: &DeactivatedAt,
+				Id:            1,
+				State:         "string",
+				Warnings:      &Warnings,
+			},
+		}, nil
+	case "pattern2/secret":
+		return &types.GetSecretV2Response{
+			Metadata: &types.SecretV2Metadata{
+				CasRequired:            &CasRequired,
+				CreatedAt:              &CreatedAt,
+				DeactivateVersionAfter: &DeactivateVersionAfter,
+				MaxVersions:            &MaxVersions,
+				UpdatedAt:              &UpdatedAt,
+			},
+			Path: &kmsClient.TestCase,
+			Version: &types.SecretV2Version{
+				CreatedAt:     "string",
+				Data:          &data6,
+				DeactivatedAt: &DeactivatedAt,
+				Id:            1,
+				State:         "string",
+				Warnings:      &Warnings,
+			},
+		}, nil
+	case "1secret":
+		return &types.GetSecretV2Response{
+			Metadata: &types.SecretV2Metadata{
+				CasRequired:            &CasRequired,
+				CreatedAt:              &CreatedAt,
+				DeactivateVersionAfter: &DeactivateVersionAfter,
+				MaxVersions:            &MaxVersions,
+				UpdatedAt:              &UpdatedAt,
+			},
+			Path: &kmsClient.TestCase,
+			Version: &types.SecretV2Version{
+				CreatedAt:     "string",
+				Data:          &data7,
+				DeactivatedAt: &DeactivatedAt,
+				Id:            1,
+				State:         "string",
+				Warnings:      &Warnings,
+			},
+		}, nil
+	case "pattern2/test/test;secret":
+		return &types.GetSecretV2Response{
+			Metadata: &types.SecretV2Metadata{
+				CasRequired:            &CasRequired,
+				CreatedAt:              &CreatedAt,
+				DeactivateVersionAfter: &DeactivateVersionAfter,
+				MaxVersions:            &MaxVersions,
+				UpdatedAt:              &UpdatedAt,
+			},
+			Path: &kmsClient.TestCase,
+			Version: &types.SecretV2Version{
+				CreatedAt:     "string",
+				Data:          &data8,
+				DeactivatedAt: &DeactivatedAt,
+				Id:            1,
+				State:         "string",
+				Warnings:      &Warnings,
+			},
+		}, nil
+	case "non-existent":
+		kmsError := okms.NewKmsErrorFromBytes([]byte("{\"error_code\":17125377}"))
+		return &types.GetSecretV2Response{}, kmsError
+	}
+	return &types.GetSecretV2Response{}, errors.New("unknown path")
+}
+
+func (kmsClient FakeOkmsClient) GetSecretsMetadata(_ context.Context, _ uuid.UUID, path string, _ bool) (*types.GetMetadataResponse, error) {
+	switch path {
+	case "nil resp":
+		return nil, nil
+	case "nil data struct":
+		return &types.GetMetadataResponse{}, nil
+	case "nil secrets list":
+		return &types.GetMetadataResponse{
+			Data: &types.SecretMetadata{},
+		}, nil
+	case "empty secrets list":
+		return &types.GetMetadataResponse{
+			Data: &types.SecretMetadata{
+				Keys: &[]string{},
+			},
+		}, nil
+	case "error response":
+		return nil, errors.New("error response")
+	}
+
+	paths := []string{
+		"pattern1/path1",
+		"pattern1/path2",
+		"pattern1/path3",
+		"pattern2/test/test-secret",
+		"pattern2/test/test.secret",
+		"pattern2/secret",
+		"1secret",
+		"pattern2/test/test;secret",
+	}
+	resp := &types.GetMetadataResponse{
+		Data: &types.SecretMetadata{
+			Keys: &[]string{},
+		},
+	}
+	if path == "" {
+		resp.Data.Keys = &paths
+		return resp, nil
+	}
+
+	for _, path_elem := range paths {
+		posStart := strings.Index(path_elem, path)
+		if posStart == 0 {
+			if len(path) == len(path_elem) {
+				*resp.Data.Keys = append(*resp.Data.Keys, path_elem)
+			} else if len(path) < len(path_elem) && path_elem[len(path)] == '/' {
+				path_elem = path_elem[len(path)+1:]
+				posSlash := strings.Index(path_elem, "/")
+				if posSlash >= 0 {
+					*resp.Data.Keys = append(*resp.Data.Keys, path_elem[:posSlash])
+				} else {
+					*resp.Data.Keys = append(*resp.Data.Keys, path_elem)
+				}
+			}
+		}
+	}
+
+	return resp, nil
+}
+
+func (kmsClient FakeOkmsClient) ListSecretV2(_ context.Context, _ uuid.UUID, _ *uint32, _ *string) (*types.ListSecretV2ResponseWithPagination, error) {
+	return nil, nil
+}
+
+func (client FakeOkmsClient) WithCustomHeader(_, _ string) *okms.Client {
+	return &okms.Client{}
+}
+
+func (client FakeOkmsClient) PostSecretV2(_ context.Context, _ uuid.UUID, body types.PostSecretV2Request) (*types.PostSecretV2Response, error) {
+	secretDataByte, err := json.Marshal(body.Version.Data)
+	if err != nil {
+		return nil, err
+	}
+	return nil, fmt.Errorf("%s", string(secretDataByte))
+}
+
+func (client FakeOkmsClient) PutSecretV2(_ context.Context, _ uuid.UUID, _ string, _ *uint32, body types.PutSecretV2Request) (*types.PutSecretV2Response, error) {
+	secretDataByte, err := json.Marshal(body.Version.Data)
+	if err != nil {
+		return nil, err
+	}
+	return nil, fmt.Errorf("%s", string(secretDataByte))
+}
+
+func (client FakeOkmsClient) DeleteSecretV2(_ context.Context, _ uuid.UUID, _ string) error {
+	return nil
+}

+ 113 - 0
providers/v1/ovh/go.mod

@@ -0,0 +1,113 @@
+module github.com/external-secrets/external-secrets/providers/v1/ovh
+
+go 1.25.5
+
+require (
+	github.com/external-secrets/external-secrets/apis v0.0.0
+	github.com/external-secrets/external-secrets/runtime v0.0.0-00010101000000-000000000000
+	github.com/google/uuid v1.6.0
+	github.com/ovh/okms-sdk-go v0.5.1
+	github.com/tidwall/gjson v1.18.0
+	k8s.io/api v0.34.1
+	k8s.io/apimachinery v0.34.1
+	sigs.k8s.io/controller-runtime v0.22.3
+)
+
+require (
+	dario.cat/mergo v1.0.2 // indirect
+	github.com/Masterminds/goutils v1.1.1 // indirect
+	github.com/Masterminds/semver/v3 v3.4.0 // indirect
+	github.com/Masterminds/sprig/v3 v3.3.0 // indirect
+	github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
+	github.com/aws/aws-sdk-go-v2 v1.39.6 // indirect
+	github.com/beorn7/perks v1.0.1 // indirect
+	github.com/cespare/xxhash/v2 v2.3.0 // indirect
+	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
+	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
+	github.com/emicklei/go-restful/v3 v3.13.0 // indirect
+	github.com/evanphx/json-patch/v5 v5.9.11 // indirect
+	github.com/fatih/color v1.18.0 // indirect
+	github.com/fsnotify/fsnotify v1.9.0 // indirect
+	github.com/fxamacker/cbor/v2 v2.9.0 // indirect
+	github.com/go-logr/logr v1.4.3 // indirect
+	github.com/go-openapi/jsonpointer v0.22.1 // indirect
+	github.com/go-openapi/jsonreference v0.21.2 // indirect
+	github.com/go-openapi/swag v0.25.1 // indirect
+	github.com/go-openapi/swag/cmdutils v0.25.1 // indirect
+	github.com/go-openapi/swag/conv v0.25.1 // indirect
+	github.com/go-openapi/swag/fileutils v0.25.1 // indirect
+	github.com/go-openapi/swag/jsonname v0.25.1 // indirect
+	github.com/go-openapi/swag/jsonutils v0.25.1 // indirect
+	github.com/go-openapi/swag/loading v0.25.1 // indirect
+	github.com/go-openapi/swag/mangling v0.25.1 // indirect
+	github.com/go-openapi/swag/netutils v0.25.1 // indirect
+	github.com/go-openapi/swag/stringutils v0.25.1 // indirect
+	github.com/go-openapi/swag/typeutils v0.25.1 // indirect
+	github.com/go-openapi/swag/yamlutils v0.25.1 // indirect
+	github.com/goccy/go-json v0.10.5 // indirect
+	github.com/gofrs/flock v0.13.0 // indirect
+	github.com/gogo/protobuf v1.3.2 // indirect
+	github.com/google/btree v1.1.3 // indirect
+	github.com/google/gnostic-models v0.7.0 // indirect
+	github.com/google/go-cmp v0.7.0 // indirect
+	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
+	github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
+	github.com/huandu/xstrings v1.5.0 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/lestrrat-go/blackmagic v1.0.4 // indirect
+	github.com/lestrrat-go/httpcc v1.0.1 // indirect
+	github.com/lestrrat-go/httprc v1.0.6 // indirect
+	github.com/lestrrat-go/iter v1.0.2 // indirect
+	github.com/lestrrat-go/jwx/v2 v2.1.6 // indirect
+	github.com/lestrrat-go/option v1.0.1 // indirect
+	github.com/mattn/go-colorable v0.1.14 // indirect
+	github.com/mitchellh/copystructure v1.2.0 // indirect
+	github.com/mitchellh/reflectwalk v1.0.2 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
+	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+	github.com/oapi-codegen/runtime v1.1.2 // indirect
+	github.com/oracle/oci-go-sdk/v65 v65.103.0 // indirect
+	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
+	github.com/prometheus/client_golang v1.23.2 // indirect
+	github.com/prometheus/client_model v0.6.2 // indirect
+	github.com/prometheus/common v0.67.2 // indirect
+	github.com/prometheus/procfs v0.19.2 // indirect
+	github.com/segmentio/asm v1.2.1 // indirect
+	github.com/shopspring/decimal v1.4.0 // indirect
+	github.com/sony/gobreaker v1.0.0 // indirect
+	github.com/spf13/cast v1.10.0 // indirect
+	github.com/spf13/pflag v1.0.10 // indirect
+	github.com/tidwall/match v1.2.0 // indirect
+	github.com/tidwall/pretty v1.2.1 // indirect
+	github.com/x448/float16 v0.8.4 // indirect
+	go.yaml.in/yaml/v2 v2.4.3 // indirect
+	go.yaml.in/yaml/v3 v3.0.4 // indirect
+	golang.org/x/crypto v0.46.0 // indirect
+	golang.org/x/net v0.47.0 // indirect
+	golang.org/x/oauth2 v0.32.0 // indirect
+	golang.org/x/sync v0.19.0 // indirect
+	golang.org/x/sys v0.39.0 // indirect
+	golang.org/x/term v0.38.0 // indirect
+	golang.org/x/text v0.32.0 // indirect
+	golang.org/x/time v0.14.0 // indirect
+	gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
+	google.golang.org/protobuf v1.36.10 // indirect
+	gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
+	gopkg.in/inf.v0 v0.9.1 // indirect
+	k8s.io/apiextensions-apiserver v0.34.1 // indirect
+	k8s.io/client-go v0.34.1 // indirect
+	k8s.io/klog/v2 v2.130.1 // indirect
+	k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
+	k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
+	sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
+	sigs.k8s.io/randfill v1.0.0 // indirect
+	sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
+	sigs.k8s.io/yaml v1.6.0 // indirect
+	software.sslmate.com/src/go-pkcs12 v0.6.0 // indirect
+)
+
+replace (
+	github.com/external-secrets/external-secrets/apis => ../../../apis
+	github.com/external-secrets/external-secrets/runtime => ../../../runtime
+)

+ 295 - 0
providers/v1/ovh/go.sum

@@ -0,0 +1,295 @@
+dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
+dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
+github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
+github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
+github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
+github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
+github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
+github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
+github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
+github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
+github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
+github.com/aws/aws-sdk-go-v2 v1.39.6 h1:2JrPCVgWJm7bm83BDwY5z8ietmeJUbh3O2ACnn+Xsqk=
+github.com/aws/aws-sdk-go-v2 v1.39.6/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE=
+github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM=
+github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
+github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
+github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k=
+github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
+github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
+github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
+github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
+github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
+github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
+github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
+github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
+github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
+github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
+github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
+github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
+github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
+github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk=
+github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM=
+github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU=
+github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ=
+github.com/go-openapi/swag v0.25.1 h1:6uwVsx+/OuvFVPqfQmOOPsqTcm5/GkBhNwLqIR916n8=
+github.com/go-openapi/swag v0.25.1/go.mod h1:bzONdGlT0fkStgGPd3bhZf1MnuPkf2YAys6h+jZipOo=
+github.com/go-openapi/swag/cmdutils v0.25.1 h1:nDke3nAFDArAa631aitksFGj2omusks88GF1VwdYqPY=
+github.com/go-openapi/swag/cmdutils v0.25.1/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=
+github.com/go-openapi/swag/conv v0.25.1 h1:+9o8YUg6QuqqBM5X6rYL/p1dpWeZRhoIt9x7CCP+he0=
+github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs=
+github.com/go-openapi/swag/fileutils v0.25.1 h1:rSRXapjQequt7kqalKXdcpIegIShhTPXx7yw0kek2uU=
+github.com/go-openapi/swag/fileutils v0.25.1/go.mod h1:+NXtt5xNZZqmpIpjqcujqojGFek9/w55b3ecmOdtg8M=
+github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU=
+github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo=
+github.com/go-openapi/swag/jsonutils v0.25.1 h1:AihLHaD0brrkJoMqEZOBNzTLnk81Kg9cWr+SPtxtgl8=
+github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo=
+github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1 h1:DSQGcdB6G0N9c/KhtpYc71PzzGEIc/fZ1no35x4/XBY=
+github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1/go.mod h1:kjmweouyPwRUEYMSrbAidoLMGeJ5p6zdHi9BgZiqmsg=
+github.com/go-openapi/swag/loading v0.25.1 h1:6OruqzjWoJyanZOim58iG2vj934TysYVptyaoXS24kw=
+github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc=
+github.com/go-openapi/swag/mangling v0.25.1 h1:XzILnLzhZPZNtmxKaz/2xIGPQsBsvmCjrJOWGNz/ync=
+github.com/go-openapi/swag/mangling v0.25.1/go.mod h1:CdiMQ6pnfAgyQGSOIYnZkXvqhnnwOn997uXZMAd/7mQ=
+github.com/go-openapi/swag/netutils v0.25.1 h1:2wFLYahe40tDUHfKT1GRC4rfa5T1B4GWZ+msEFA4Fl4=
+github.com/go-openapi/swag/netutils v0.25.1/go.mod h1:CAkkvqnUJX8NV96tNhEQvKz8SQo2KF0f7LleiJwIeRE=
+github.com/go-openapi/swag/stringutils v0.25.1 h1:Xasqgjvk30eUe8VKdmyzKtjkVjeiXx1Iz0zDfMNpPbw=
+github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg=
+github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3I3ysiFZqukA=
+github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8=
+github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk=
+github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg=
+github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
+github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
+github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
+github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
+github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
+github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
+github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
+github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
+github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
+github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
+github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
+github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
+github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
+github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
+github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
+github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
+github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
+github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
+github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
+github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
+github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k=
+github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
+github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
+github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
+github.com/lestrrat-go/jwx/v2 v2.1.6 h1:hxM1gfDILk/l5ylers6BX/Eq1m/pnxe9NBwW6lVfecA=
+github.com/lestrrat-go/jwx/v2 v2.1.6/go.mod h1:Y722kU5r/8mV7fYDifjug0r8FK8mZdw0K0GpJw/l8pU=
+github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
+github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
+github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
+github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
+github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
+github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
+github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
+github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI=
+github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
+github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
+github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
+github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
+github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
+github.com/oracle/oci-go-sdk/v65 v65.103.0 h1:HfyZx+JefCPK3At0Xt45q+wr914jDXuoyzOFX3XCbno=
+github.com/oracle/oci-go-sdk/v65 v65.103.0/go.mod h1:oB8jFGVc/7/zJ+DbleE8MzGHjhs2ioCz5stRTdZdIcY=
+github.com/ovh/okms-sdk-go v0.5.1 h1:oS8w/BXyGgnBzaGh3zFIyw73LwJ2B+UkNqeVBT2epUU=
+github.com/ovh/okms-sdk-go v0.5.1/go.mod h1:dJpK0dmnRphZgttfsjg6c5yLgcZp6LXPR/6CFwxY124=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
+github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
+github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8=
+github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko=
+github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
+github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
+github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
+github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
+github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
+github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
+github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
+github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
+github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=
+github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
+github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
+github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
+github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
+github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
+github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
+github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
+github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
+github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
+github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
+github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
+go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
+go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
+go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
+golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
+golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
+golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
+golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
+golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
+golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
+golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
+golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
+golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
+golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
+golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+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=
+gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
+gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
+google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
+google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
+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=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
+gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=
+k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM=
+k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk=
+k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI=
+k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc=
+k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4=
+k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
+k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY=
+k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8=
+k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
+k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
+k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=
+k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
+k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
+k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y=
+sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8=
+sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
+sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
+sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
+sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
+sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
+sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
+sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
+sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
+software.sslmate.com/src/go-pkcs12 v0.6.0 h1:f3sQittAeF+pao32Vb+mkli+ZyT+VwKaD014qFGq6oU=
+software.sslmate.com/src/go-pkcs12 v0.6.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=

+ 300 - 0
providers/v1/ovh/provider.go

@@ -0,0 +1,300 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package ovh implements a provider that enables synchronization with OVHcloud's Secret Manager.
+package ovh
+
+import (
+	"context"
+	"crypto/tls"
+	"errors"
+	"fmt"
+	"net/http"
+	"reflect"
+	"time"
+
+	"github.com/google/uuid"
+	"github.com/ovh/okms-sdk-go"
+	"github.com/ovh/okms-sdk-go/types"
+	kclient "sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+	"github.com/external-secrets/external-secrets/runtime/esutils/resolvers"
+)
+
+// Provider implements the ESO Provider interface for OVHcloud.
+type Provider struct {
+	SecretKeyRef func(ctx context.Context, c kclient.Client, storeKind string, esNamespace string, ref *esmeta.SecretKeySelector) (string, error)
+}
+
+// OkmsClient defines an interface for interacting with the OVH OKMS service.
+// It allows for both real API calls and mocking for unit tests.
+type OkmsClient interface {
+	GetSecretV2(ctx context.Context, okmsID uuid.UUID, path string, version *uint32, includeData *bool) (*types.GetSecretV2Response, error)
+	ListSecretV2(ctx context.Context, okmsID uuid.UUID, pageSize *uint32, pageCursor *string) (*types.ListSecretV2ResponseWithPagination, error)
+	PostSecretV2(ctx context.Context, okmsID uuid.UUID, body types.PostSecretV2Request) (*types.PostSecretV2Response, error)
+	PutSecretV2(ctx context.Context, okmsID uuid.UUID, path string, cas *uint32, body types.PutSecretV2Request) (*types.PutSecretV2Response, error)
+	DeleteSecretV2(ctx context.Context, okmsID uuid.UUID, path string) error
+	WithCustomHeader(key, value string) *okms.Client
+	GetSecretsMetadata(ctx context.Context, okmsID uuid.UUID, path string, list bool) (*types.GetMetadataResponse, error)
+}
+
+type ovhClient struct {
+	ovhStoreNameSpace string
+	ovhStoreKind      string
+	kube              kclient.Client
+	okmsID            uuid.UUID
+	cas               bool
+	okmsTimeout       time.Duration
+	okmsClient        OkmsClient
+}
+
+var _ esv1.SecretsClient = &ovhClient{}
+
+// NewClient creates a new Provider client.
+func (p *Provider) NewClient(ctx context.Context, store esv1.GenericStore, kube kclient.Client, namespace string) (esv1.SecretsClient, error) {
+	// Validate Store before creating a client from it.
+	_, err := p.ValidateStore(store)
+	if err != nil {
+		return nil, err
+	}
+
+	if kube == nil {
+		return nil, errors.New("controller-runtime client is nil")
+	}
+
+	ovhStore := store.GetSpec().Provider.Ovh
+	// ovhClient configuration.
+	okmsID, err := uuid.Parse(ovhStore.OkmsID)
+	if err != nil {
+		return nil, err
+	}
+
+	cas := false
+	if ovhStore.CasRequired != nil {
+		cas = *ovhStore.CasRequired
+	}
+
+	okmsTimeout := 30 * time.Second
+	if ovhStore.OkmsTimeout != nil {
+		okmsTimeout = time.Duration(*ovhStore.OkmsTimeout) * time.Second
+	}
+	cl := &ovhClient{
+		ovhStoreNameSpace: namespace,
+		ovhStoreKind:      store.GetKind(),
+		kube:              kube,
+		okmsID:            okmsID,
+		cas:               cas,
+		okmsTimeout:       okmsTimeout,
+	}
+
+	// Authentication configuration: token or mTLS.
+	if p.SecretKeyRef == nil {
+		p.SecretKeyRef = resolvers.SecretKeyRef
+	}
+	if ovhStore.Auth.ClientToken != nil {
+		err = configureHTTPTokenClient(ctx, p, cl,
+			ovhStore.Server, ovhStore.Auth.ClientToken)
+	} else if ovhStore.Auth.ClientMTLS != nil {
+		err = configureHTTPMTLSClient(ctx, p, cl,
+			ovhStore.Server, ovhStore.Auth.ClientMTLS)
+	}
+	return cl, err
+}
+
+// Configure the client to use the provided token for HTTP requests.
+func configureHTTPTokenClient(ctx context.Context, p *Provider, cl *ovhClient, server string, clientToken *esv1.OvhClientToken) error {
+	token, err := getToken(ctx, p, cl, clientToken)
+	if err != nil {
+		return err
+	}
+	bearerToken := fmt.Sprintf("Bearer %s", token)
+
+	// Request a new OKMS client from the OVH SDK.
+	httpClient := &http.Client{
+		Timeout: cl.okmsTimeout,
+	}
+	cl.okmsClient, err = okms.NewRestAPIClientWithHttp(server, httpClient)
+	if err != nil {
+		return err
+	}
+	if cl.okmsClient == nil {
+		return errors.New("failed to get new okms client")
+	}
+
+	// Add a custom header.
+	cl.okmsClient.WithCustomHeader("Authorization", bearerToken)
+	cl.okmsClient.WithCustomHeader("Content-type", "application/json")
+
+	return nil
+}
+
+// Configure the client to use mTLS for HTTP requests.
+func configureHTTPMTLSClient(ctx context.Context, p *Provider, cl *ovhClient, server string, clientMTLS *esv1.OvhClientMTLS) error {
+	tlsCert, err := getMTLS(ctx, p, cl, clientMTLS)
+	if err != nil {
+		return err
+	}
+
+	// HTTP client configuration using mTLS.
+	httpClient := http.Client{
+		Timeout: cl.okmsTimeout,
+		Transport: &http.Transport{
+			TLSClientConfig: &tls.Config{
+				MinVersion:   tls.VersionTLS12,
+				Certificates: []tls.Certificate{tlsCert},
+			},
+		},
+	}
+
+	// Request a new OKMS client from the OVH SDK (mTLS configured).
+	cl.okmsClient, err = okms.NewRestAPIClientWithHttp(server, &httpClient)
+	if err != nil {
+		return err
+	}
+	if cl.okmsClient == nil {
+		return errors.New("failed to get new okms client")
+	}
+
+	return err
+}
+
+// Retrieve the token value from the Kubernetes secret.
+func getToken(ctx context.Context, p *Provider, cl *ovhClient, clientToken *esv1.OvhClientToken) (string, error) {
+	// ClienTokenSecret refers to the Kubernetes secret that stores the token.
+	tokenSecretRef := clientToken.ClientTokenSecret
+	if tokenSecretRef == nil {
+		return "", errors.New("ovh store auth.token.tokenSecretRef cannot be empty")
+	}
+
+	// Retrieve the token value.
+	token, err := p.SecretKeyRef(ctx, cl.kube,
+		cl.ovhStoreKind, cl.ovhStoreNameSpace, tokenSecretRef)
+	if err != nil {
+		return "", err
+	}
+	if token == "" {
+		return "", errors.New("ovh store auth.token.tokenSecretRef cannot be empty")
+	}
+
+	return token, nil
+}
+
+// Retrieve the client key and certificate from the Kubernetes secret.
+func getMTLS(ctx context.Context, p *Provider, cl *ovhClient, clientMTLS *esv1.OvhClientMTLS) (tls.Certificate, error) {
+	const (
+		emptyKeySecretRef  = "ovh store auth.mtls.keySecretRef cannot be empty"
+		emptyCertSecretRef = "ovh store auth.mtls.certSecretRef cannot be empty"
+	)
+	// keySecretRef refers to the Kubernetes secret object
+	// containing the client key.
+	keyRef := clientMTLS.ClientKey
+	if keyRef == nil {
+		return tls.Certificate{}, errors.New(emptyKeySecretRef)
+	}
+	// Retrieve the value of keySecretRef from the Kubernetes secret.
+	clientKey, err := p.SecretKeyRef(ctx, cl.kube,
+		cl.ovhStoreKind, cl.ovhStoreNameSpace, keyRef)
+	if err != nil {
+		return tls.Certificate{}, err
+	}
+	if clientKey == "" {
+		return tls.Certificate{}, errors.New(emptyKeySecretRef)
+	}
+
+	// certSecretRef refers to the Kubernetes secret object
+	// containing the client certificate.
+	certRef := clientMTLS.ClientCertificate
+	if certRef == nil {
+		return tls.Certificate{}, errors.New(emptyCertSecretRef)
+	}
+	// Retrieve the value of certSecretRef from the Kubernetes secret.
+	clientCert, err := p.SecretKeyRef(ctx, cl.kube,
+		cl.ovhStoreKind, cl.ovhStoreNameSpace, certRef)
+	if err != nil {
+		return tls.Certificate{}, err
+	}
+	if clientCert == "" {
+		return tls.Certificate{}, errors.New(emptyCertSecretRef)
+	}
+
+	cert, err := tls.X509KeyPair([]byte(clientCert), []byte(clientKey))
+
+	return cert, err
+}
+
+// ValidateStore statically validate the Secret Store specification.
+func (p *Provider) ValidateStore(store esv1.GenericStore) (admission.Warnings, error) {
+	// Nil checks.
+	if store == nil || reflect.ValueOf(store).IsNil() {
+		return nil, errors.New("store is nil")
+	}
+	spec := store.GetSpec()
+	if spec == nil {
+		return nil, errors.New("store spec is nil")
+	}
+	provider := spec.Provider
+	if provider == nil {
+		return nil, errors.New("store provider is nil")
+	}
+	if provider.Ovh == nil {
+		return nil, errors.New("ovh store provider is nil")
+	}
+	if provider.Ovh.Server == "" {
+		return nil, errors.New("ovh store server is required")
+	}
+	if provider.Ovh.OkmsID == "" {
+		return nil, errors.New("ovh store okmsID is required")
+	}
+
+	// Validate the provider's authentication method.
+	auth := provider.Ovh.Auth
+	if auth.ClientMTLS == nil && auth.ClientToken == nil {
+		return nil, errors.New("missing authentication method")
+	} else if auth.ClientMTLS != nil && auth.ClientToken != nil {
+		return nil, errors.New("only one authentication method allowed (mtls | token)")
+	} else if auth.ClientMTLS != nil &&
+		(auth.ClientMTLS.ClientCertificate == nil ||
+			auth.ClientMTLS.ClientKey == nil) {
+		return nil, errors.New("missing tls certificate or key")
+	}
+
+	return nil, nil
+}
+
+// Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
+func (p *Provider) Capabilities() esv1.SecretStoreCapabilities {
+	return esv1.SecretStoreReadWrite
+}
+
+// NewProvider creates a new Provider instance.
+func NewProvider() esv1.Provider {
+	return &Provider{}
+}
+
+// ProviderSpec returns the provider specification for registration.
+func ProviderSpec() *esv1.SecretStoreProvider {
+	return &esv1.SecretStoreProvider{
+		Ovh: &esv1.OvhProvider{},
+	}
+}
+
+// MaintenanceStatus returns the maintenance status of the provider.
+func MaintenanceStatus() esv1.MaintenanceStatus {
+	return esv1.MaintenanceStatusMaintained
+}

+ 552 - 0
providers/v1/ovh/provider_test.go

@@ -0,0 +1,552 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package ovh
+
+import (
+	"context"
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/pem"
+	"sync"
+	"testing"
+
+	corev1 "k8s.io/api/core/v1"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	kclient "sigs.k8s.io/controller-runtime/pkg/client"
+	fakeBuilder "sigs.k8s.io/controller-runtime/pkg/client/fake"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+)
+
+type EphemeralMTLS struct {
+	Once    sync.Once
+	keyPEM  string
+	certPEM string
+}
+
+var (
+	ephemeralMTLS = EphemeralMTLS{}
+	namespace     = "namespace"
+	scheme        = runtime.NewScheme()
+	_             = corev1.AddToScheme(scheme)
+	kube          = fakeBuilder.NewClientBuilder().
+			WithScheme(scheme).
+			WithObjects(&corev1.Secret{
+			ObjectMeta: v1.ObjectMeta{
+				Name:      "my-secret",
+				Namespace: "default",
+			},
+			Data: map[string][]byte{
+				"key": []byte("value"),
+			},
+		}).Build()
+)
+
+func (eph *EphemeralMTLS) SecretKeyRef(_ context.Context, _ kclient.Client, _, _ string, ref *esmeta.SecretKeySelector) (string, error) {
+	if ref.Name == "Valid token auth" {
+		return "Valid", nil
+	}
+	if ref.Name == "Valid mtls client certificate" || ref.Name == "Valid mtls client key" {
+		var err error
+		eph.Once.Do(func() {
+			var privKey *rsa.PrivateKey
+			privKey, err = rsa.GenerateKey(rand.Reader, 2048)
+			if err != nil {
+				return
+			}
+			eph.keyPEM = string(pem.EncodeToMemory(&pem.Block{
+				Type:  "RSA PRIVATE KEY",
+				Bytes: x509.MarshalPKCS1PrivateKey(privKey),
+			}))
+
+			template := x509.Certificate{}
+			var cert []byte
+			cert, err = x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)
+			if err != nil {
+				return
+			}
+			eph.certPEM = string(pem.EncodeToMemory(&pem.Block{
+				Type:  "CERTIFICATE",
+				Bytes: cert,
+			}))
+		})
+
+		if err != nil {
+			return "", err
+		}
+
+		if ref.Name == "Valid mtls client certificate" {
+			return eph.certPEM, nil
+		}
+		return eph.keyPEM, nil
+	}
+	return "", nil
+}
+
+func TestNewClient(t *testing.T) {
+	tests := map[string]struct {
+		should string
+		kube   kclient.Client
+		err    bool
+		store  *esv1.SecretStore
+	}{
+		"Nil store": {
+			should: "store is nil",
+			err:    true,
+			kube:   kube,
+		},
+		"Nil provider": {
+			should: "store provider is nil",
+			err:    true,
+			kube:   kube,
+			store: &esv1.SecretStore{
+				Spec: esv1.SecretStoreSpec{
+					Provider: nil,
+				},
+			},
+		},
+		"Nil ovh provider": {
+			should: "ovh store provider is nil",
+			err:    true,
+			kube:   kube,
+			store: &esv1.SecretStore{
+				Spec: esv1.SecretStoreSpec{
+					Provider: &esv1.SecretStoreProvider{
+						AWS: &esv1.AWSProvider{},
+					},
+				},
+			},
+		},
+		"Nil controller-runtime client": {
+			should: "controller-runtime client is nil",
+			err:    true,
+			store: &esv1.SecretStore{
+				Spec: esv1.SecretStoreSpec{
+					Provider: &esv1.SecretStoreProvider{
+						Ovh: &esv1.OvhProvider{
+							Auth: esv1.OvhAuth{
+								ClientToken: &esv1.OvhClientToken{
+									ClientTokenSecret: &esmeta.SecretKeySelector{
+										Name:      "Valid token auth",
+										Namespace: &namespace,
+										Key:       "string",
+									},
+								},
+							},
+							Server: "server",
+							OkmsID: "okmsID",
+						},
+					},
+				},
+			},
+		},
+		"Authentication method conflict": {
+			should: "only one authentication method allowed (mtls | token)",
+			err:    true,
+			kube:   kube,
+			store: &esv1.SecretStore{
+				Spec: esv1.SecretStoreSpec{
+					Provider: &esv1.SecretStoreProvider{
+						Ovh: &esv1.OvhProvider{
+							Server: "string",
+							OkmsID: "11111111-1111-1111-1111-111111111111",
+							Auth: esv1.OvhAuth{
+								ClientMTLS: &esv1.OvhClientMTLS{
+									ClientCertificate: &esmeta.SecretKeySelector{
+										Name:      "string",
+										Namespace: &namespace,
+										Key:       "string",
+									},
+									ClientKey: &esmeta.SecretKeySelector{
+										Name:      "string",
+										Namespace: &namespace,
+										Key:       "string",
+									},
+								},
+								ClientToken: &esv1.OvhClientToken{
+									ClientTokenSecret: &esmeta.SecretKeySelector{
+										Name:      "string",
+										Namespace: &namespace,
+										Key:       "string",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"Authentication method empty": {
+			should: "missing authentication method",
+			err:    true,
+			kube:   kube,
+			store: &esv1.SecretStore{
+				Spec: esv1.SecretStoreSpec{
+					Provider: &esv1.SecretStoreProvider{
+						Ovh: &esv1.OvhProvider{
+							Server: "string",
+							OkmsID: "11111111-1111-1111-1111-111111111111",
+							Auth:   esv1.OvhAuth{},
+						},
+					},
+				},
+			},
+		},
+		"Valid token auth": {
+			should: "",
+			err:    false,
+			kube:   kube,
+			store: &esv1.SecretStore{
+				Spec: esv1.SecretStoreSpec{
+					Provider: &esv1.SecretStoreProvider{
+						Ovh: &esv1.OvhProvider{
+							Server: "string",
+							OkmsID: "11111111-1111-1111-1111-111111111111",
+							Auth: esv1.OvhAuth{
+								ClientToken: &esv1.OvhClientToken{
+									ClientTokenSecret: &esmeta.SecretKeySelector{
+										Name:      "Valid token auth",
+										Namespace: &namespace,
+										Key:       "string",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"Empty token auth": {
+			should: "ovh store auth.token.tokenSecretRef cannot be empty",
+			err:    true,
+			kube:   kube,
+			store: &esv1.SecretStore{
+				Spec: esv1.SecretStoreSpec{
+					Provider: &esv1.SecretStoreProvider{
+						Ovh: &esv1.OvhProvider{
+							Server: "string",
+							OkmsID: "11111111-1111-1111-1111-111111111111",
+							Auth: esv1.OvhAuth{
+								ClientToken: &esv1.OvhClientToken{
+									ClientTokenSecret: &esmeta.SecretKeySelector{},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"Valid mtls auth": {
+			should: "",
+			err:    false,
+			kube:   kube,
+			store: &esv1.SecretStore{
+				Spec: esv1.SecretStoreSpec{
+					Provider: &esv1.SecretStoreProvider{
+						Ovh: &esv1.OvhProvider{
+							Server: "string",
+							OkmsID: "11111111-1111-1111-1111-111111111111",
+							Auth: esv1.OvhAuth{
+								ClientMTLS: &esv1.OvhClientMTLS{
+									ClientCertificate: &esmeta.SecretKeySelector{
+										Name:      "Valid mtls client certificate",
+										Namespace: &namespace,
+										Key:       "string",
+									},
+									ClientKey: &esmeta.SecretKeySelector{
+										Name:      "Valid mtls client key",
+										Namespace: &namespace,
+										Key:       "string",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"Empty mtls client certificate": {
+			should: "missing tls certificate or key",
+			err:    true,
+			kube:   kube,
+			store: &esv1.SecretStore{
+				Spec: esv1.SecretStoreSpec{
+					Provider: &esv1.SecretStoreProvider{
+						Ovh: &esv1.OvhProvider{
+							Server: "string",
+							OkmsID: "11111111-1111-1111-1111-111111111111",
+							Auth: esv1.OvhAuth{
+								ClientMTLS: &esv1.OvhClientMTLS{
+									ClientKey: &esmeta.SecretKeySelector{
+										Name:      "Valid mtls client key",
+										Namespace: &namespace,
+										Key:       "string",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"Empty mtls client key": {
+			should: "missing tls certificate or key",
+			err:    true,
+			kube:   kube,
+			store: &esv1.SecretStore{
+				Spec: esv1.SecretStoreSpec{
+					Provider: &esv1.SecretStoreProvider{
+						Ovh: &esv1.OvhProvider{
+							Server: "string",
+							OkmsID: "11111111-1111-1111-1111-111111111111",
+							Auth: esv1.OvhAuth{
+								ClientMTLS: &esv1.OvhClientMTLS{
+									ClientCertificate: &esmeta.SecretKeySelector{
+										Name:      "Valid mtls client certificate",
+										Namespace: &namespace,
+										Key:       "string",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	ctx := context.Background()
+	for name, testCase := range tests {
+		t.Run(name, func(t *testing.T) {
+			provider := Provider{
+				SecretKeyRef: ephemeralMTLS.SecretKeyRef,
+			}
+			_, err := provider.NewClient(ctx, testCase.store, testCase.kube, "namespace")
+			if testCase.err == true {
+				if err == nil {
+					t.Error()
+				} else if err.Error() != testCase.should {
+					t.Error()
+				}
+			} else if err != nil {
+				t.Error()
+			}
+		})
+	}
+}
+
+func TestValidateStore(t *testing.T) {
+	var namespace string = "namespace"
+	tests := map[string]struct {
+		should string
+		err    bool
+		kube   kclient.Client
+		store  *esv1.SecretStore
+	}{
+		"Nil store": {
+			should: "store provider is nil",
+			err:    true,
+			kube:   kube,
+			store: &esv1.SecretStore{
+				Spec: esv1.SecretStoreSpec{
+					Provider: nil,
+				},
+			},
+		},
+		"Nil ovh provider": {
+			should: "ovh store provider is nil",
+			err:    true,
+			kube:   kube,
+			store: &esv1.SecretStore{
+				Spec: esv1.SecretStoreSpec{
+					Provider: &esv1.SecretStoreProvider{
+						AWS: &esv1.AWSProvider{},
+					},
+				},
+			},
+		},
+		"Authentication method conflict": {
+			should: "only one authentication method allowed (mtls | token)",
+			err:    true,
+			kube:   kube,
+			store: &esv1.SecretStore{
+				Spec: esv1.SecretStoreSpec{
+					Provider: &esv1.SecretStoreProvider{
+						Ovh: &esv1.OvhProvider{
+							Server: "string",
+							OkmsID: "11111111-1111-1111-1111-111111111111",
+							Auth: esv1.OvhAuth{
+								ClientMTLS: &esv1.OvhClientMTLS{
+									ClientCertificate: &esmeta.SecretKeySelector{
+										Name:      "string",
+										Namespace: &namespace,
+										Key:       "string",
+									},
+									ClientKey: &esmeta.SecretKeySelector{
+										Name:      "string",
+										Namespace: &namespace,
+										Key:       "string",
+									},
+								},
+								ClientToken: &esv1.OvhClientToken{
+									ClientTokenSecret: &esmeta.SecretKeySelector{
+										Name:      "string",
+										Namespace: &namespace,
+										Key:       "string",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"Valid token auth": {
+			should: "",
+			err:    false,
+			kube:   kube,
+			store: &esv1.SecretStore{
+				Spec: esv1.SecretStoreSpec{
+					Provider: &esv1.SecretStoreProvider{
+						Ovh: &esv1.OvhProvider{
+							Server: "string",
+							OkmsID: "11111111-1111-1111-1111-111111111111",
+							Auth: esv1.OvhAuth{
+								ClientToken: &esv1.OvhClientToken{
+									ClientTokenSecret: &esmeta.SecretKeySelector{
+										Name:      "string",
+										Namespace: &namespace,
+										Key:       "string",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"Valid mtls auth": {
+			should: "",
+			err:    false,
+			kube:   kube,
+			store: &esv1.SecretStore{
+				Spec: esv1.SecretStoreSpec{
+					Provider: &esv1.SecretStoreProvider{
+						Ovh: &esv1.OvhProvider{
+							Server: "string",
+							OkmsID: "11111111-1111-1111-1111-111111111111",
+							Auth: esv1.OvhAuth{
+								ClientMTLS: &esv1.OvhClientMTLS{
+									ClientCertificate: &esmeta.SecretKeySelector{
+										Name:      "string",
+										Namespace: &namespace,
+										Key:       "string",
+									},
+									ClientKey: &esmeta.SecretKeySelector{
+										Name:      "string",
+										Namespace: &namespace,
+										Key:       "string",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"Invalid mtls auth: missing client certificate": {
+			should: "missing tls certificate or key",
+			err:    true,
+			kube:   kube,
+			store: &esv1.SecretStore{
+				Spec: esv1.SecretStoreSpec{
+					Provider: &esv1.SecretStoreProvider{
+						Ovh: &esv1.OvhProvider{
+							Server: "string",
+							OkmsID: "11111111-1111-1111-1111-111111111111",
+							Auth: esv1.OvhAuth{
+								ClientMTLS: &esv1.OvhClientMTLS{
+									ClientKey: &esmeta.SecretKeySelector{
+										Name:      "string",
+										Namespace: &namespace,
+										Key:       "string",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"Invalid mtls auth: missing key certificate": {
+			should: "missing tls certificate or key",
+			err:    true,
+			kube:   kube,
+			store: &esv1.SecretStore{
+				Spec: esv1.SecretStoreSpec{
+					Provider: &esv1.SecretStoreProvider{
+						Ovh: &esv1.OvhProvider{
+							Server: "string",
+							OkmsID: "11111111-1111-1111-1111-111111111111",
+							Auth: esv1.OvhAuth{
+								ClientMTLS: &esv1.OvhClientMTLS{
+									ClientCertificate: &esmeta.SecretKeySelector{
+										Name:      "string",
+										Namespace: &namespace,
+										Key:       "string",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"Empty auth": {
+			should: "missing authentication method",
+			err:    true,
+			kube:   kube,
+			store: &esv1.SecretStore{
+				Spec: esv1.SecretStoreSpec{
+					Provider: &esv1.SecretStoreProvider{
+						Ovh: &esv1.OvhProvider{
+							Server: "string",
+							OkmsID: "11111111-1111-1111-1111-111111111111",
+						},
+					},
+				},
+			},
+		},
+	}
+	for name, testCase := range tests {
+		t.Run(name, func(t *testing.T) {
+			provider := Provider{}
+			_, err := provider.ValidateStore(testCase.store)
+			if testCase.err == true {
+				if err == nil {
+					t.Error()
+				} else if err.Error() != testCase.should {
+					t.Error()
+				}
+			} else if err != nil {
+				t.Error()
+			}
+		})
+	}
+}

+ 34 - 0
providers/v1/ovh/validate.go

@@ -0,0 +1,34 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package ovh
+
+import (
+	"context"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+)
+
+// Dynamically validate the Secret Store configuration.
+//
+// An HTTP request is sent to the provider to verify authorization.
+func (cl *ovhClient) Validate() (esv1.ValidationResult, error) {
+	_, err := cl.okmsClient.ListSecretV2(context.Background(), cl.okmsID, nil, nil)
+	if err != nil {
+		return esv1.ValidationResultError, err
+	}
+	return esv1.ValidationResultReady, nil
+}

+ 20 - 0
tests/__snapshot__/clustersecretstore-v1.yaml

@@ -600,6 +600,26 @@ spec:
         name: string
         namespace: string
       vault: string
+    ovh:
+      auth:
+        mtls:
+          certSecretRef:
+            key: string
+            name: string
+            namespace: string
+          keySecretRef:
+            key: string
+            name: string
+            namespace: string
+        token:
+          tokenSecretRef:
+            key: string
+            name: string
+            namespace: string
+      casRequired: true
+      okmsTimeout: 30
+      okmsid: string
+      server: string
     passbolt:
       auth:
         passwordSecretRef:

+ 20 - 0
tests/__snapshot__/secretstore-v1.yaml

@@ -600,6 +600,26 @@ spec:
         name: string
         namespace: string
       vault: string
+    ovh:
+      auth:
+        mtls:
+          certSecretRef:
+            key: string
+            name: string
+            namespace: string
+          keySecretRef:
+            key: string
+            name: string
+            namespace: string
+        token:
+          tokenSecretRef:
+            key: string
+            name: string
+            namespace: string
+      casRequired: true
+      okmsTimeout: 30
+      okmsid: string
+      server: string
     passbolt:
       auth:
         passwordSecretRef: