Sfoglia il codice sorgente

feat(providers): Nebius MysteryBox integration (#5868)

Anna Kuzmenko 3 mesi fa
parent
commit
0e7f4495a8
37 ha cambiato i file con 4730 aggiunte e 1 eliminazioni
  1. 62 0
      apis/externalsecrets/v1/secretstore_nebius_types.go
  2. 4 0
      apis/externalsecrets/v1/secretstore_types.go
  3. 59 0
      apis/externalsecrets/v1/zz_generated.deepcopy.go
  4. 121 0
      config/crds/bases/external-secrets.io_clustersecretstores.yaml
  5. 121 0
      config/crds/bases/external-secrets.io_secretstores.yaml
  6. 228 0
      deploy/crds/bundle.yaml
  7. 161 0
      docs/api/spec.md
  8. 2 1
      docs/introduction/stability-support.md
  9. 86 0
      docs/provider/nebius-mysterybox.md
  10. 14 0
      docs/snippets/nebius-mysterybox-external-secret-all.yaml
  11. 15 0
      docs/snippets/nebius-mysterybox-external-secret-by-key.yaml
  12. 17 0
      docs/snippets/nebius-mysterybox-secret-store.yaml
  13. 5 0
      go.mod
  14. 6 0
      go.sum
  15. 1 0
      hack/api-docs/mkdocs.yml
  16. 30 0
      pkg/register/nebiusmysterybox.go
  17. 45 0
      providers/v1/nebius/common/sdk/iam/fake_token_exchanger.go
  18. 103 0
      providers/v1/nebius/common/sdk/iam/grpc_token_exchanger.go
  19. 35 0
      providers/v1/nebius/common/sdk/iam/token_exchanger.go
  20. 48 0
      providers/v1/nebius/common/sdk/mysterybox/client.go
  21. 151 0
      providers/v1/nebius/common/sdk/mysterybox/fake/fake_client.go
  22. 158 0
      providers/v1/nebius/common/sdk/mysterybox/grpc_client.go
  23. 228 0
      providers/v1/nebius/common/sdk/mysterybox/grpc_client_test.go
  24. 61 0
      providers/v1/nebius/common/sdk/sdk.go
  25. 116 0
      providers/v1/nebius/go.mod
  26. 276 0
      providers/v1/nebius/go.sum
  27. 351 0
      providers/v1/nebius/mysterybox/provider.go
  28. 960 0
      providers/v1/nebius/mysterybox/provider_test.go
  29. 172 0
      providers/v1/nebius/mysterybox/secrets_client.go
  30. 145 0
      providers/v1/nebius/mysterybox/token_getter.go
  31. 299 0
      providers/v1/nebius/mysterybox/token_getter_test.go
  32. 56 0
      providers/v1/nebius/mysterybox/utils.go
  33. 198 0
      providers/v1/nebius/mysterybox/validation.go
  34. 359 0
      providers/v1/nebius/mysterybox/validation_test.go
  35. 5 0
      runtime/constants/constants.go
  36. 16 0
      tests/__snapshot__/clustersecretstore-v1.yaml
  37. 16 0
      tests/__snapshot__/secretstore-v1.yaml

+ 62 - 0
apis/externalsecrets/v1/secretstore_nebius_types.go

@@ -0,0 +1,62 @@
+// /*
+// 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"
+
+// NebiusAuth defines the authentication method for the Nebius provider.
+// +kubebuilder:validation:XValidation:rule="has(self.serviceAccountCredsSecretRef) || has(self.tokenSecretRef)",message="either serviceAccountCredsSecretRef or tokenSecretRef must be set"
+type NebiusAuth struct {
+	// ServiceAccountCreds references a Kubernetes Secret key that contains a JSON
+	// document with service account credentials used to get an IAM token.
+	//
+	// Expected JSON structure:
+	// {
+	//   "subject-credentials": {
+	//     "alg": "RS256",
+	//     "private-key": "-----BEGIN PRIVATE KEY-----\n<private-key>\n-----END PRIVATE KEY-----\n",
+	//     "kid": "<public-key-id>",
+	//     "iss": "<issuer-service-account-id>",
+	//     "sub": "<subject-service-account-id>"
+	//   }
+	// }
+	// +optional
+	ServiceAccountCreds esmeta.SecretKeySelector `json:"serviceAccountCredsSecretRef,omitempty"`
+	// Token authenticates with Nebius Mysterybox by presenting a token.
+	// +optional
+	Token esmeta.SecretKeySelector `json:"tokenSecretRef,omitempty"`
+}
+
+// NebiusCAProvider The provider for the CA bundle to use to validate Nebius server certificate.
+type NebiusCAProvider struct {
+	// +optional
+	Certificate esmeta.SecretKeySelector `json:"certSecretRef,omitempty"`
+}
+
+// NebiusMysteryboxProvider Configures a store to sync secrets using the Nebius Mysterybox provider.
+type NebiusMysteryboxProvider struct {
+
+	// NebiusMysterybox API endpoint
+	APIDomain string `json:"apiDomain"`
+
+	// Auth defines parameters to authenticate in MysteryBox
+	Auth NebiusAuth `json:"auth"`
+
+	// The provider for the CA bundle to use to validate NebiusMysterybox server certificate.
+	// +optional
+	CAProvider *NebiusCAProvider `json:"caProvider,omitempty"`
+}

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

@@ -219,6 +219,10 @@ type SecretStoreProvider struct {
 	// Barbican configures this store to sync secrets using the OpenStack Barbican provider
 	// +optional
 	Barbican *BarbicanProvider `json:"barbican,omitempty"`
+
+	// NebiusMysterybox configures this store to sync secrets using NebiusMysterybox provider
+	// +optional
+	NebiusMysterybox *NebiusMysteryboxProvider `json:"nebiusmysterybox,omitempty"`
 }
 
 // CAProviderType defines the type of provider for certificate authority.

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

@@ -2613,6 +2613,60 @@ func (in *NTLMProtocol) DeepCopy() *NTLMProtocol {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *NebiusAuth) DeepCopyInto(out *NebiusAuth) {
+	*out = *in
+	in.ServiceAccountCreds.DeepCopyInto(&out.ServiceAccountCreds)
+	in.Token.DeepCopyInto(&out.Token)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NebiusAuth.
+func (in *NebiusAuth) DeepCopy() *NebiusAuth {
+	if in == nil {
+		return nil
+	}
+	out := new(NebiusAuth)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *NebiusCAProvider) DeepCopyInto(out *NebiusCAProvider) {
+	*out = *in
+	in.Certificate.DeepCopyInto(&out.Certificate)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NebiusCAProvider.
+func (in *NebiusCAProvider) DeepCopy() *NebiusCAProvider {
+	if in == nil {
+		return nil
+	}
+	out := new(NebiusCAProvider)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *NebiusMysteryboxProvider) DeepCopyInto(out *NebiusMysteryboxProvider) {
+	*out = *in
+	in.Auth.DeepCopyInto(&out.Auth)
+	if in.CAProvider != nil {
+		in, out := &in.CAProvider, &out.CAProvider
+		*out = new(NebiusCAProvider)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NebiusMysteryboxProvider.
+func (in *NebiusMysteryboxProvider) DeepCopy() *NebiusMysteryboxProvider {
+	if in == nil {
+		return nil
+	}
+	out := new(NebiusMysteryboxProvider)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *NgrokAuth) DeepCopyInto(out *NgrokAuth) {
 	*out = *in
@@ -3493,6 +3547,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
 		*out = new(BarbicanProvider)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.NebiusMysterybox != nil {
+		in, out := &in.NebiusMysterybox, &out.NebiusMysterybox
+		*out = new(NebiusMysteryboxProvider)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreProvider.

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

@@ -3561,6 +3561,127 @@ spec:
                             type: string
                         type: object
                     type: object
+                  nebiusmysterybox:
+                    description: NebiusMysterybox configures this store to sync secrets
+                      using NebiusMysterybox provider
+                    properties:
+                      apiDomain:
+                        description: NebiusMysterybox API endpoint
+                        type: string
+                      auth:
+                        description: Auth defines parameters to authenticate in MysteryBox
+                        properties:
+                          serviceAccountCredsSecretRef:
+                            description: |-
+                              ServiceAccountCreds references a Kubernetes Secret key that contains a JSON
+                              document with service account credentials used to get an IAM token.
+
+                              Expected JSON structure:
+                              {
+                                "subject-credentials": {
+                                  "alg": "RS256",
+                                  "private-key": "-----BEGIN PRIVATE KEY-----\n<private-key>\n-----END PRIVATE KEY-----\n",
+                                  "kid": "<public-key-id>",
+                                  "iss": "<issuer-service-account-id>",
+                                  "sub": "<subject-service-account-id>"
+                                }
+                              }
+                            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
+                          tokenSecretRef:
+                            description: Token authenticates with Nebius Mysterybox
+                              by presenting a token.
+                            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
+                        type: object
+                        x-kubernetes-validations:
+                        - message: either serviceAccountCredsSecretRef or tokenSecretRef
+                            must be set
+                          rule: has(self.serviceAccountCredsSecretRef) || has(self.tokenSecretRef)
+                      caProvider:
+                        description: The provider for the CA bundle to use to validate
+                          NebiusMysterybox server certificate.
+                        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
+                        type: object
+                    required:
+                    - apiDomain
+                    - auth
+                    type: object
                   ngrok:
                     description: Ngrok configures this store to sync secrets using
                       the ngrok provider.

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

@@ -3561,6 +3561,127 @@ spec:
                             type: string
                         type: object
                     type: object
+                  nebiusmysterybox:
+                    description: NebiusMysterybox configures this store to sync secrets
+                      using NebiusMysterybox provider
+                    properties:
+                      apiDomain:
+                        description: NebiusMysterybox API endpoint
+                        type: string
+                      auth:
+                        description: Auth defines parameters to authenticate in MysteryBox
+                        properties:
+                          serviceAccountCredsSecretRef:
+                            description: |-
+                              ServiceAccountCreds references a Kubernetes Secret key that contains a JSON
+                              document with service account credentials used to get an IAM token.
+
+                              Expected JSON structure:
+                              {
+                                "subject-credentials": {
+                                  "alg": "RS256",
+                                  "private-key": "-----BEGIN PRIVATE KEY-----\n<private-key>\n-----END PRIVATE KEY-----\n",
+                                  "kid": "<public-key-id>",
+                                  "iss": "<issuer-service-account-id>",
+                                  "sub": "<subject-service-account-id>"
+                                }
+                              }
+                            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
+                          tokenSecretRef:
+                            description: Token authenticates with Nebius Mysterybox
+                              by presenting a token.
+                            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
+                        type: object
+                        x-kubernetes-validations:
+                        - message: either serviceAccountCredsSecretRef or tokenSecretRef
+                            must be set
+                          rule: has(self.serviceAccountCredsSecretRef) || has(self.tokenSecretRef)
+                      caProvider:
+                        description: The provider for the CA bundle to use to validate
+                          NebiusMysterybox server certificate.
+                        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
+                        type: object
+                    required:
+                    - apiDomain
+                    - auth
+                    type: object
                   ngrok:
                     description: Ngrok configures this store to sync secrets using
                       the ngrok provider.

+ 228 - 0
deploy/crds/bundle.yaml

@@ -5405,6 +5405,120 @@ spec:
                               type: string
                           type: object
                       type: object
+                    nebiusmysterybox:
+                      description: NebiusMysterybox configures this store to sync secrets using NebiusMysterybox provider
+                      properties:
+                        apiDomain:
+                          description: NebiusMysterybox API endpoint
+                          type: string
+                        auth:
+                          description: Auth defines parameters to authenticate in MysteryBox
+                          properties:
+                            serviceAccountCredsSecretRef:
+                              description: |-
+                                ServiceAccountCreds references a Kubernetes Secret key that contains a JSON
+                                document with service account credentials used to get an IAM token.
+
+                                Expected JSON structure:
+                                {
+                                  "subject-credentials": {
+                                    "alg": "RS256",
+                                    "private-key": "-----BEGIN PRIVATE KEY-----\n<private-key>\n-----END PRIVATE KEY-----\n",
+                                    "kid": "<public-key-id>",
+                                    "iss": "<issuer-service-account-id>",
+                                    "sub": "<subject-service-account-id>"
+                                  }
+                                }
+                              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
+                            tokenSecretRef:
+                              description: Token authenticates with Nebius Mysterybox by presenting a token.
+                              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
+                          type: object
+                          x-kubernetes-validations:
+                            - message: either serviceAccountCredsSecretRef or tokenSecretRef must be set
+                              rule: has(self.serviceAccountCredsSecretRef) || has(self.tokenSecretRef)
+                        caProvider:
+                          description: The provider for the CA bundle to use to validate NebiusMysterybox server certificate.
+                          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
+                          type: object
+                      required:
+                        - apiDomain
+                        - auth
+                      type: object
                     ngrok:
                       description: Ngrok configures this store to sync secrets using the ngrok provider.
                       properties:
@@ -17046,6 +17160,120 @@ spec:
                               type: string
                           type: object
                       type: object
+                    nebiusmysterybox:
+                      description: NebiusMysterybox configures this store to sync secrets using NebiusMysterybox provider
+                      properties:
+                        apiDomain:
+                          description: NebiusMysterybox API endpoint
+                          type: string
+                        auth:
+                          description: Auth defines parameters to authenticate in MysteryBox
+                          properties:
+                            serviceAccountCredsSecretRef:
+                              description: |-
+                                ServiceAccountCreds references a Kubernetes Secret key that contains a JSON
+                                document with service account credentials used to get an IAM token.
+
+                                Expected JSON structure:
+                                {
+                                  "subject-credentials": {
+                                    "alg": "RS256",
+                                    "private-key": "-----BEGIN PRIVATE KEY-----\n<private-key>\n-----END PRIVATE KEY-----\n",
+                                    "kid": "<public-key-id>",
+                                    "iss": "<issuer-service-account-id>",
+                                    "sub": "<subject-service-account-id>"
+                                  }
+                                }
+                              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
+                            tokenSecretRef:
+                              description: Token authenticates with Nebius Mysterybox by presenting a token.
+                              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
+                          type: object
+                          x-kubernetes-validations:
+                            - message: either serviceAccountCredsSecretRef or tokenSecretRef must be set
+                              rule: has(self.serviceAccountCredsSecretRef) || has(self.tokenSecretRef)
+                        caProvider:
+                          description: The provider for the CA bundle to use to validate NebiusMysterybox server certificate.
+                          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
+                          type: object
+                      required:
+                        - apiDomain
+                        - auth
+                      type: object
                     ngrok:
                       description: Ngrok configures this store to sync secrets using the ngrok provider.
                       properties:

+ 161 - 0
docs/api/spec.md

@@ -7182,6 +7182,153 @@ External Secrets meta/v1.SecretKeySelector
 </tr>
 </tbody>
 </table>
+<h3 id="external-secrets.io/v1.NebiusAuth">NebiusAuth
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1.NebiusMysteryboxProvider">NebiusMysteryboxProvider</a>)
+</p>
+<p>
+<p>NebiusAuth defines the authentication method for the Nebius provider.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>serviceAccountCredsSecretRef</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>
+<em>(Optional)</em>
+<p>ServiceAccountCreds references a Kubernetes Secret key that contains a JSON
+document with service account credentials used to get an IAM token.</p>
+<p>Expected JSON structure:
+{
+&ldquo;subject-credentials&rdquo;: {
+&ldquo;alg&rdquo;: &ldquo;RS256&rdquo;,
+&ldquo;private-key&rdquo;: &ldquo;&mdash;&ndash;BEGIN PRIVATE KEY&mdash;&ndash;\n<private-key>\n&mdash;&ndash;END PRIVATE KEY&mdash;&ndash;\n&rdquo;,
+&ldquo;kid&rdquo;: &ldquo;<public-key-id>&rdquo;,
+&ldquo;iss&rdquo;: &ldquo;<issuer-service-account-id>&rdquo;,
+&ldquo;sub&rdquo;: &ldquo;<subject-service-account-id>&rdquo;
+}
+}</p>
+</td>
+</tr>
+<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>
+<em>(Optional)</em>
+<p>Token authenticates with Nebius Mysterybox by presenting a token.</p>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1.NebiusCAProvider">NebiusCAProvider
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1.NebiusMysteryboxProvider">NebiusMysteryboxProvider</a>)
+</p>
+<p>
+<p>NebiusCAProvider The provider for the CA bundle to use to validate Nebius server certificate.</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>
+<em>(Optional)</em>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1.NebiusMysteryboxProvider">NebiusMysteryboxProvider
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1.SecretStoreProvider">SecretStoreProvider</a>)
+</p>
+<p>
+<p>NebiusMysteryboxProvider Configures a store to sync secrets using the Nebius Mysterybox provider.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>apiDomain</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<p>NebiusMysterybox API endpoint</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>auth</code></br>
+<em>
+<a href="#external-secrets.io/v1.NebiusAuth">
+NebiusAuth
+</a>
+</em>
+</td>
+<td>
+<p>Auth defines parameters to authenticate in MysteryBox</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>caProvider</code></br>
+<em>
+<a href="#external-secrets.io/v1.NebiusCAProvider">
+NebiusCAProvider
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>The provider for the CA bundle to use to validate NebiusMysterybox server certificate.</p>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1.NgrokAuth">NgrokAuth
 </h3>
 <p>
@@ -9496,6 +9643,20 @@ BarbicanProvider
 <p>Barbican configures this store to sync secrets using the OpenStack Barbican provider</p>
 </td>
 </tr>
+<tr>
+<td>
+<code>nebiusmysterybox</code></br>
+<em>
+<a href="#external-secrets.io/v1.NebiusMysteryboxProvider">
+NebiusMysteryboxProvider
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>NebiusMysterybox configures this store to sync secrets using NebiusMysterybox provider</p>
+</td>
+</tr>
 </tbody>
 </table>
 <h3 id="external-secrets.io/v1.SecretStoreRef">SecretStoreRef

+ 2 - 1
docs/introduction/stability-support.md

@@ -88,7 +88,7 @@ The following table describes the stability level of each provider and who's res
 | [ngrok](https://external-secrets.io/latest/provider/ngrok)                                                 |     alpha | [@jonstacks](https://github.com/jonstacks)                                                          |
 | [Barbican](https://external-secrets.io/latest/provider/barbican)                                           |     alpha | [@rkferreira](https://github.com/rkferreira)                                                        |
 | [Devolutions Server](https://external-secrets.io/latest/provider/devolutions-server)                       |     alpha | [@rbstp](https://github.com/rbstp)                                                                  |
-
+| [Nebius MysteryBox](https://external-secrets.io/latest/provider/nebius-mysterybox)                         | alpha     | [@greenmapc](https://github.com/greenmapc)                                                          |
 
 ## Provider Feature Support
 
@@ -128,6 +128,7 @@ The following table show the support for features across different providers.
 | ngrok                     |              |              |                      |                         |        x         |      x      |                             |
 | Barbican                  |      x       |              |                      |                         |        x         |             |                             |
 | Devolutions Server        |              |              |                      |                         |        x         |      x      |                             |
+| Nebius Mysterybox         |              |              |                      |                         |        x         |             |                             |
 
 ## Support Policy
 

+ 86 - 0
docs/provider/nebius-mysterybox.md

@@ -0,0 +1,86 @@
+## Nebius MysteryBox
+
+External Secrets Operator integrates with [Nebius MysteryBox](https://docs.nebius.com/mysterybox/overview).
+
+### Authentication
+
+Currently, only [Service Account credentials](https://docs.nebius.com/grpc-api/auth) authorization is supported.
+
+
+Before you start, create a service account and grant it permission to read desired secrets in MysteryBox.
+For details on required roles and permissions, see [MysteryBox get method](https://docs.nebius.com/mysterybox/secrets/get).
+
+You will need to create a Kubernetes Secret with desired auth parameters and structure.
+The Kubernetes secret must be in a Subject Credentials format:
+
+```json
+{
+  "subject-credentials": {
+    "alg": "RS256",
+    "private-key": "-----BEGIN PRIVATE KEY-----\n<private-key>\n-----END PRIVATE KEY-----\n",
+    "kid": "<public-key-ID>",
+    "iss": "<service_account_ID>",
+    "sub": "<service_account_ID>"
+  }
+}
+```
+
+Follow the [instruction](https://docs.nebius.com/iam/service-accounts/authorized-keys#create) to generate the secret.
+
+### Examples
+
+#### SecretStore
+
+First, create a SecretStore with a Nebius MysteryBox backend.
+
+```yaml
+{% include 'nebius-mysterybox-secret-store.yaml' %}
+```
+
+#### Getting a secret by key
+
+You can get a secret by its secretID and key.
+
+```yaml
+{% include 'nebius-mysterybox-external-secret-by-key.yaml' %}
+```
+
+#### Getting a full secret (all keys retrieved)
+
+Another way is to get a full secret that will be imported. When fetching the full secret, each key–value pair from MysteryBox is mapped to a separate entry in the target Kubernetes Secret’s `data` field.
+
+
+```yaml
+{% include 'nebius-mysterybox-external-secret-all.yaml' %}
+```
+
+Example of a target secret:
+```yaml
+apiVersion: v1
+kind: Secret
+metadata:
+  name: <your-k8s-secret-name>
+type: Opaque
+data:
+  <entry-key-1>: <base64-of-value-1>
+  <entry-key-2>: <base64-of-value-2>
+```
+
+#### Additional usage
+
+There is also a possibility to specify Version variable to get a secret.
+
+```yaml
+...
+ data:
+    - secretKey: <secretKey>
+      remoteRef:
+        key: <secretID>
+        version: <secretVersion>
+
+```
+
+!!! tip inline end
+    When the `version` field is not specified, a primary version of the secret will be retrieved.
+
+

+ 14 - 0
docs/snippets/nebius-mysterybox-external-secret-all.yaml

@@ -0,0 +1,14 @@
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  name: mysterybox-external-secret
+spec:
+  secretStoreRef:
+    kind: SecretStore
+    name: mysterybox-secretstore
+  target:
+    name: imported-secret-map
+    creationPolicy: Owner
+  dataFrom:
+    - extract:
+        key: mysteryboxSecretID

+ 15 - 0
docs/snippets/nebius-mysterybox-external-secret-by-key.yaml

@@ -0,0 +1,15 @@
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  name: mysterybox-external-secret
+spec:
+  secretStoreRef:
+    kind: SecretStore
+    name: mysterybox-secretstore
+  target:
+    name: imported-secret-by-key
+    creationPolicy: Owner
+  data:
+    - secretKey: password
+      remoteRef:
+        key: mysteryboxSecretID

+ 17 - 0
docs/snippets/nebius-mysterybox-secret-store.yaml

@@ -0,0 +1,17 @@
+apiVersion: external-secrets.io/v1
+kind: SecretStore
+metadata:
+  name: mysterybox
+spec:
+  provider:
+    nebiusmysterybox:
+      apiDomain: api.nebius.cloud:443 # In enterprise development or testing environments, replace it with an internal or environment-specific domain
+      auth:
+        serviceAccountCredsSecretRef:
+          name: sa-credentials
+          key: sa-credentials-key
+      # [OPTIONAL] Use if apiDomain uses an internal/self-signed CA or custom TLS certificate
+      caProvider:
+        certSecretRef:
+          name: <cert-secret>
+          key: <cert-secret-key>.crt

+ 5 - 0
go.mod

@@ -40,6 +40,7 @@ replace (
 	github.com/external-secrets/external-secrets/providers/v1/infisical => ./providers/v1/infisical
 	github.com/external-secrets/external-secrets/providers/v1/keepersecurity => ./providers/v1/keepersecurity
 	github.com/external-secrets/external-secrets/providers/v1/kubernetes => ./providers/v1/kubernetes
+	github.com/external-secrets/external-secrets/providers/v1/nebius => ./providers/v1/nebius
 	github.com/external-secrets/external-secrets/providers/v1/ngrok => ./providers/v1/ngrok
 	github.com/external-secrets/external-secrets/providers/v1/onboardbase => ./providers/v1/onboardbase
 	github.com/external-secrets/external-secrets/providers/v1/onepassword => ./providers/v1/onepassword
@@ -152,6 +153,7 @@ require (
 	github.com/external-secrets/external-secrets/providers/v1/infisical v0.0.0-00010101000000-000000000000
 	github.com/external-secrets/external-secrets/providers/v1/keepersecurity v0.0.0-00010101000000-000000000000
 	github.com/external-secrets/external-secrets/providers/v1/kubernetes v0.0.0-00010101000000-000000000000
+	github.com/external-secrets/external-secrets/providers/v1/nebius v0.0.0-00010101000000-000000000000
 	github.com/external-secrets/external-secrets/providers/v1/ngrok v0.0.0-00010101000000-000000000000
 	github.com/external-secrets/external-secrets/providers/v1/onboardbase v0.0.0-00010101000000-000000000000
 	github.com/external-secrets/external-secrets/providers/v1/onepassword v0.0.0-00010101000000-000000000000
@@ -175,6 +177,7 @@ require (
 
 require (
 	al.essio.dev/pkg/shellescape v1.6.0 // indirect
+	buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1 // indirect
 	cloud.google.com/go/auth v0.17.0 // indirect
 	cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
 	cloud.google.com/go/compute/metadata v0.9.0 // indirect
@@ -287,6 +290,7 @@ require (
 	github.com/google/s2a-go v0.1.9 // indirect
 	github.com/gophercloud/gophercloud/v2 v2.8.0 // indirect
 	github.com/grafana/grafana-openapi-client-go v0.0.0-20250925215610-d92957c70d5c // indirect
+	github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 // indirect
 	github.com/hashicorp/go-secure-stdlib/awsutil v0.3.0 // indirect
 	github.com/hashicorp/go-uuid v1.0.3 // indirect
 	github.com/hashicorp/golang-lru v1.0.2 // indirect
@@ -312,6 +316,7 @@ require (
 	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
 	github.com/muesli/cancelreader v0.2.2 // indirect
 	github.com/muesli/termenv v0.16.0 // indirect
+	github.com/nebius/gosdk v0.0.0-20260204094009-511fd4d4f7a1 // indirect
 	github.com/ngrok/ngrok-api-go/v7 v7.6.0 // indirect
 	github.com/opentracing/basictracer-go v1.1.0 // indirect
 	github.com/passbolt/go-passbolt v0.7.2 // indirect

+ 6 - 0
go.sum

@@ -1,5 +1,7 @@
 al.essio.dev/pkg/shellescape v1.6.0 h1:NxFcEqzFSEVCGN2yq7Huv/9hyCEGVa/TncnOOBBeXHA=
 al.essio.dev/pkg/shellescape v1.6.0/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
+buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1 h1:31on4W/yPcV4nZHL4+UCiCvLPsMqe/vJcNg8Rci0scc=
+buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1/go.mod h1:fUl8CEN/6ZAMk6bP8ahBJPUJw7rbp+j4x+wCcYi2IG4=
 cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
 cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
@@ -647,6 +649,8 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+
 github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
 github.com/grafana/grafana-openapi-client-go v0.0.0-20250925215610-d92957c70d5c h1:55vWLZG/i92lrRIfsGScIyvnIOYZEqJv+I715dMCUSE=
 github.com/grafana/grafana-openapi-client-go v0.0.0-20250925215610-d92957c70d5c/go.mod h1:sMcpxegie6TcvI6eVm+MbNneNC249GGWRcEO1M+UfSE=
+github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/EyXF4lPgLoUtcSWquBM0Rs=
+github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
@@ -890,6 +894,8 @@ github.com/nats-io/nats.go v1.12.1/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/
 github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s=
 github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
+github.com/nebius/gosdk v0.0.0-20260204094009-511fd4d4f7a1 h1:Lt5HZDEeSNJJbppzFEtlOYMGlGIOVi6YoC9YJMHv60A=
+github.com/nebius/gosdk v0.0.0-20260204094009-511fd4d4f7a1/go.mod h1:8r4EhhGJ+RMUfdiVVpZ8pEb0b+O7hLG8JXDAgGyu89o=
 github.com/ngrok/ngrok-api-go/v7 v7.6.0 h1:DW9FqEgSN6+Dgl25O8ha1LS49CqX2c9vO0Z53CN8Vqs=
 github.com/ngrok/ngrok-api-go/v7 v7.6.0/go.mod h1:Si/pYAJmbCuo4Fb3xz0MF6N5ubRvPdUixETBwhFvBf0=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=

+ 1 - 0
hack/api-docs/mkdocs.yml

@@ -160,6 +160,7 @@ nav:
       - Volcengine: provider/volcengine.md
       - ngrok: provider/ngrok.md
       - Devolutions Server: provider/devolutions-server.md
+      - Nebius MysteryBox: provider/nebius-mysterybox.md
   - Examples:
       - FluxCD: examples/gitops-using-fluxcd.md
       - Anchore Engine: examples/anchore-engine-credentials.md

+ 30 - 0
pkg/register/nebiusmysterybox.go

@@ -0,0 +1,30 @@
+//go:build nebiusmysterybox || 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"
+	mbox "github.com/external-secrets/external-secrets/providers/v1/nebius/mysterybox"
+)
+
+func init() {
+	// Register nebiusmysterybox provider
+	esv1.Register(mbox.NewProvider(), mbox.ProviderSpec(), mbox.MaintenanceStatus())
+}

+ 45 - 0
providers/v1/nebius/common/sdk/iam/fake_token_exchanger.go

@@ -0,0 +1,45 @@
+// /*
+// 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 iam
+
+import (
+	"context"
+	"fmt"
+	"sync/atomic"
+	"time"
+)
+
+// FakeTokenExchanger simulates the process of exchanging credentials to obtain IAM tokens.
+// Calls keeps track of how many times the token exchange method has been invoked.
+// ReturnError, when set to true, forces the token exchange method to return an error.
+type FakeTokenExchanger struct {
+	Calls       atomic.Int64
+	ReturnError bool
+}
+
+// ExchangeIamToken exchanges credentials to generate a new IAM token with a fixed 100-second validity period.
+func (f *FakeTokenExchanger) ExchangeIamToken(_ context.Context, _, _ string, issuedAt time.Time, _ []byte) (*Token, error) {
+	f.Calls.Add(1)
+	if f.ReturnError {
+		return nil, fmt.Errorf("fake error")
+	}
+	return &Token{
+		Token:     fmt.Sprintf("token-%d", f.Calls.Load()),
+		ExpiresAt: issuedAt.Add(100 * time.Second), // lifetime is 100 seconds
+		IssuedAt:  issuedAt,
+	}, nil
+}

+ 103 - 0
providers/v1/nebius/common/sdk/iam/grpc_token_exchanger.go

@@ -0,0 +1,103 @@
+// /*
+// 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 iam
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"time"
+
+	"github.com/go-logr/logr"
+	"github.com/nebius/gosdk/auth"
+	iam "github.com/nebius/gosdk/services/nebius/iam/v1"
+
+	"github.com/external-secrets/external-secrets/providers/v1/nebius/common/sdk"
+)
+
+const (
+	errInvalidSubjectCreds        = "invalid subject credentials: malformed JSON"
+	errSubjectCredsCannotBeSigned = "invalid subject credentials: cannot be signed %w"
+)
+
+// GrpcTokenExchanger is a client for exchanging credentials over gRPC to obtain IAM tokens.
+type GrpcTokenExchanger struct {
+	logger                   logr.Logger
+	exchangeTokenObserveCall func(err error)
+}
+
+// NewGrpcTokenExchanger creates a new instance of GrpcTokenExchanger with the specified logger and callback function.
+func NewGrpcTokenExchanger(logger logr.Logger, exchangeTokenObserveCallFunc func(err error)) *GrpcTokenExchanger {
+	return &GrpcTokenExchanger{
+		logger:                   logger,
+		exchangeTokenObserveCall: exchangeTokenObserveCallFunc,
+	}
+}
+
+// ExchangeIamToken exchanges subject credentials for a new IAM token using a gRPC-based token exchange service.
+func (t *GrpcTokenExchanger) ExchangeIamToken(ctx context.Context, apiDomain, subjectCreds string, issuedAt time.Time, caCertificate []byte) (*Token, error) {
+	parsedSubjectCreds := &auth.ServiceAccountCredentials{}
+	if err := json.Unmarshal([]byte(subjectCreds), parsedSubjectCreds); err != nil {
+		if t.exchangeTokenObserveCall != nil {
+			t.exchangeTokenObserveCall(err)
+		}
+		return nil, errors.New(errInvalidSubjectCreds)
+	}
+
+	reader := auth.NewPrivateKeyParser(
+		[]byte(parsedSubjectCreds.SubjectCredentials.PrivateKey),
+		parsedSubjectCreds.SubjectCredentials.KeyID,
+		parsedSubjectCreds.SubjectCredentials.Subject,
+	)
+	tokenRequester := auth.NewServiceAccountExchangeTokenRequester(reader)
+
+	iamSdk, err := sdk.NewSDK(ctx, apiDomain, caCertificate)
+	if err != nil {
+		if t.exchangeTokenObserveCall != nil {
+			t.exchangeTokenObserveCall(err)
+		}
+		return nil, err
+	}
+	defer func() { _ = iamSdk.Close() }()
+
+	tokenExchanger := iam.NewTokenExchangeService(iamSdk)
+
+	req, err := tokenRequester.GetExchangeTokenRequest(ctx)
+	if err != nil {
+		if t.exchangeTokenObserveCall != nil {
+			t.exchangeTokenObserveCall(err)
+		}
+		return nil, fmt.Errorf(errSubjectCredsCannotBeSigned, err)
+	}
+
+	tok, err := tokenExchanger.Exchange(ctx, req)
+	if t.exchangeTokenObserveCall != nil {
+		t.exchangeTokenObserveCall(err)
+	}
+	if err != nil {
+		return nil, err
+	}
+
+	return &Token{
+		Token:     tok.GetAccessToken(),
+		ExpiresAt: issuedAt.Add(time.Duration(tok.GetExpiresIn()) * time.Second),
+		IssuedAt:  issuedAt,
+	}, nil
+}
+
+var _ TokenExchanger = &GrpcTokenExchanger{}

+ 35 - 0
providers/v1/nebius/common/sdk/iam/token_exchanger.go

@@ -0,0 +1,35 @@
+// /*
+// 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 iam provides a client interface and implementations for Nebius IAM.
+package iam
+
+import (
+	"context"
+	"time"
+)
+
+// Token represents an IAM token with its value, expiration time, and issuance time.
+type Token struct {
+	Token     string
+	ExpiresAt time.Time
+	IssuedAt  time.Time
+}
+
+// TokenExchanger is an interface for exchanging credentials to obtain IAM tokens.
+type TokenExchanger interface {
+	ExchangeIamToken(ctx context.Context, apiDomain, subjectCreds string, issuedAt time.Time, caCertificate []byte) (*Token, error)
+}

+ 48 - 0
providers/v1/nebius/common/sdk/mysterybox/client.go

@@ -0,0 +1,48 @@
+// /*
+// 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 mysterybox provides a client interface and implementations for Nebius Mysterybox service.
+package mysterybox
+
+import "context"
+
+// Client is an interface that contains the main methods to interact with Secret Service.
+type Client interface {
+	GetSecret(ctx context.Context, token, secretID, versionID string) (*Payload, error)
+	GetSecretByKey(ctx context.Context, token, secretID, versionID, key string) (*PayloadEntry, error)
+	Close() error
+}
+
+// Payload represents a secret version payload returned by the Nebius Mysterybox service.
+// It contains the version identifier and the list of key/value entries.
+type Payload struct {
+	VersionID string
+	Entries   []Entry
+}
+
+// PayloadEntry represents a single entry from a secret version payload identified by key.
+type PayloadEntry struct {
+	VersionID string
+	Entry     Entry
+}
+
+// Entry is a key/value item within a secret payload.
+// Only one of StringValue or BinaryValue is expected to be set depending on the secret's data type.
+type Entry struct {
+	Key         string
+	StringValue string
+	BinaryValue []byte
+}

+ 151 - 0
providers/v1/nebius/common/sdk/mysterybox/fake/fake_client.go

@@ -0,0 +1,151 @@
+// /*
+// 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"
+	"sync"
+	"sync/atomic"
+
+	"github.com/google/uuid"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
+
+	"github.com/external-secrets/external-secrets/providers/v1/nebius/common/sdk/mysterybox"
+)
+
+type FakeMysteryboxClient struct {
+	mysteryboxService *MysteryboxService
+	Closed            int32
+}
+
+func (f *FakeMysteryboxClient) Close() error {
+	atomic.AddInt32(&f.Closed, 1)
+	return nil
+}
+
+func (f *FakeMysteryboxClient) GetSecret(_ context.Context, _, secretId, versionId string) (*mysterybox.Payload, error) {
+	secret, err := f.mysteryboxService.GetSecret(secretId, versionId)
+	if err != nil {
+		return nil, err
+	}
+	return &mysterybox.Payload{
+		VersionID: secret.VersionId,
+		Entries:   secret.Entries,
+	}, nil
+}
+
+func (f *FakeMysteryboxClient) GetSecretByKey(_ context.Context, _, secretID, versionID, key string) (*mysterybox.PayloadEntry, error) {
+	secret, err := f.mysteryboxService.GetSecret(secretID, versionID)
+	if err != nil {
+		return nil, err
+	}
+	for _, entry := range secret.Entries {
+		if entry.Key == key {
+			return &mysterybox.PayloadEntry{
+				VersionID: secret.VersionId,
+				Entry:     entry,
+			}, nil
+		}
+	}
+
+	return nil, notFoundError()
+}
+
+type MysteryboxService struct {
+	mu         sync.RWMutex
+	secretData map[string]map[string][]mysterybox.Entry
+}
+
+func InitMysteryboxService() *MysteryboxService {
+	return &MysteryboxService{
+		secretData: make(map[string]map[string][]mysterybox.Entry),
+	}
+}
+
+func (s *MysteryboxService) GetSecret(secretId, versionId string) (*Secret, error) {
+	s.mu.RLock()
+	defer s.mu.RUnlock()
+
+	data, ok := s.secretData[secretId]
+	if !ok {
+		return nil, notFoundError()
+	}
+	dataByVersion, ok := data[versionId] // if a version is empty -> "" (latest/primary) version will be taken
+	if !ok {
+		return nil, notFoundError()
+	}
+	return &Secret{
+		Id:        secretId,
+		VersionId: versionId,
+		Entries:   dataByVersion,
+	}, nil
+}
+
+func (s *MysteryboxService) CreateSecret(payloadEntries []mysterybox.Entry) *Secret {
+	if len(payloadEntries) == 0 {
+		return nil
+	}
+	secretId := uuid.NewString()
+	versionId := uuid.NewString()
+
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	versionData := make(map[string][]mysterybox.Entry)
+	versionData[versionId] = payloadEntries
+	versionData[""] = payloadEntries // latest version is primary
+	s.secretData[secretId] = versionData
+
+	return &Secret{
+		Id:        secretId,
+		VersionId: versionId,
+		Entries:   payloadEntries,
+	}
+}
+
+func (s *MysteryboxService) CreateNewSecretVersion(secretId string, payloadEntries []mysterybox.Entry) (string, error) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	versions, ok := s.secretData[secretId]
+	if !ok {
+		return "", notFoundError()
+	}
+	versionId := uuid.NewString()
+	versions[versionId] = payloadEntries
+	versions[""] = payloadEntries // latest version is primary
+	return versionId, nil
+}
+
+func NewFakeMysteryboxClient(service *MysteryboxService) *FakeMysteryboxClient {
+	return &FakeMysteryboxClient{
+		mysteryboxService: service,
+	}
+}
+
+type Secret struct {
+	Id        string
+	VersionId string
+	Entries   []mysterybox.Entry
+}
+
+func notFoundError() error {
+	return status.Error(codes.NotFound, "not found")
+}
+
+var _ mysterybox.Client = &FakeMysteryboxClient{}

+ 158 - 0
providers/v1/nebius/common/sdk/mysterybox/grpc_client.go

@@ -0,0 +1,158 @@
+// /*
+// 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 mysterybox
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/nebius/gosdk"
+	proto "github.com/nebius/gosdk/proto/nebius/mysterybox/v1"
+	mbox "github.com/nebius/gosdk/services/nebius/mysterybox/v1"
+	"google.golang.org/grpc"
+
+	"github.com/external-secrets/external-secrets/providers/v1/nebius/common/sdk"
+)
+
+const (
+	notSupportedPayloadType = "payload type not supported, key: %v"
+)
+
+// GrpcClient provides methods for interacting with a gRPC payload service and managing secret data via an SDK.
+type GrpcClient struct {
+	PayloadService mbox.PayloadService
+	sdk            *gosdk.SDK
+}
+
+// Close shuts down the underlying gRPC SDK connection and releases associated resources.
+func (c *GrpcClient) Close() error {
+	return c.sdk.Close()
+}
+
+// GetSecretByKey retrieves a specific key's payload for a given secretID and versionID using a provided token.
+// It returns the payload containing the key, its value (string or binary), versionID, or an error if retrieval fails.
+func (c *GrpcClient) GetSecretByKey(ctx context.Context, token, secretID, versionID, key string) (*PayloadEntry, error) {
+	payloadRequest := proto.GetPayloadByKeyRequest{
+		SecretId:  secretID,
+		VersionId: versionID,
+		Key:       key,
+	}
+
+	entry, err := c.PayloadService.GetByKey(
+		ctx,
+		&payloadRequest,
+		grpc.PerRPCCredentials(PerRPCCredentials{IamToken: token}),
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	if entry.GetData() == nil {
+		return nil, fmt.Errorf("received nil data for key: %v", key)
+	}
+
+	payloadEntry := PayloadEntry{
+		VersionID: entry.GetVersionId(),
+		Entry: Entry{
+			Key: entry.GetData().GetKey(),
+		},
+	}
+
+	switch entry.GetData().Payload.(type) {
+	case *proto.Payload_StringValue:
+		payloadEntry.Entry.StringValue = entry.GetData().GetStringValue()
+	case *proto.Payload_BinaryValue:
+		payloadEntry.Entry.BinaryValue = entry.GetData().GetBinaryValue()
+	default:
+		return nil, fmt.Errorf(notSupportedPayloadType, key)
+	}
+
+	return &payloadEntry, nil
+}
+
+// GetSecret retrieves the secret payload associated with a given secretID and versionID using the provided token.
+// It returns the payload containing the secret version and entries or an error if the retrieval fails.
+func (c *GrpcClient) GetSecret(ctx context.Context, token, secretID, versionID string) (*Payload, error) {
+	payloadRequest := proto.GetPayloadRequest{
+		SecretId:  secretID,
+		VersionId: versionID,
+	}
+	payload, err := c.PayloadService.Get(
+		ctx,
+		&payloadRequest,
+		grpc.PerRPCCredentials(PerRPCCredentials{IamToken: token}),
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	payloadEntries := make([]Entry, 0, len(payload.Data))
+	for _, entry := range payload.GetData() {
+		payloadEntry := Entry{
+			Key: entry.Key,
+		}
+
+		switch entry.Payload.(type) {
+		case *proto.Payload_StringValue:
+			payloadEntry.StringValue = entry.GetStringValue()
+		case *proto.Payload_BinaryValue:
+			payloadEntry.BinaryValue = entry.GetBinaryValue()
+		default:
+			return nil, fmt.Errorf(notSupportedPayloadType, entry.Key)
+		}
+
+		payloadEntries = append(payloadEntries, payloadEntry)
+	}
+
+	return &Payload{
+		VersionID: payload.VersionId,
+		Entries:   payloadEntries,
+	}, nil
+}
+
+// NewNebiusMysteryboxClientGrpc initializes a new gRPC client for Nebius Mysterybox using the provided context, API domain, and CA certificate.
+func NewNebiusMysteryboxClientGrpc(ctx context.Context, apiDomain string, caCertificate []byte) (*GrpcClient, error) {
+	mysteryboxSdk, err := sdk.NewSDK(ctx, apiDomain, caCertificate)
+
+	if err != nil {
+		return nil, err
+	}
+	return &GrpcClient{
+		mbox.NewPayloadService(mysteryboxSdk),
+		mysteryboxSdk,
+	}, nil
+}
+
+// PerRPCCredentials represents authentication credentials for each RPC call, including an IAM token for authorization.
+type PerRPCCredentials struct {
+	IamToken string
+}
+
+// GetRequestMetadata returns request metadata as a map for RPC authorization using the IAM token.
+// It includes an "Authorization" header with a Bearer token constructed from the IAM token.
+func (c PerRPCCredentials) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) {
+	return map[string]string{"Authorization": "Bearer " + c.IamToken}, nil
+}
+
+// RequireTransportSecurity specifies whether the transport should use a secure connection when sending credentials.
+func (PerRPCCredentials) RequireTransportSecurity() bool {
+	return true
+}
+
+var _ Client = &GrpcClient{}

+ 228 - 0
providers/v1/nebius/common/sdk/mysterybox/grpc_client_test.go

@@ -0,0 +1,228 @@
+// /*
+// 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 mysterybox
+
+import (
+	"bytes"
+	"context"
+	"errors"
+	"testing"
+
+	mbox "github.com/nebius/gosdk/proto/nebius/mysterybox/v1"
+	tassert "github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"google.golang.org/grpc"
+)
+
+const (
+	defaultVersion     = "version1"
+	alternativeVersion = "version2"
+	notFoundError      = "not found"
+)
+
+type FakePayloadService struct {
+	data map[string]map[string]*mbox.SecretPayload
+}
+
+func (f *FakePayloadService) Get(_ context.Context, r *mbox.GetPayloadRequest, _ ...grpc.CallOption) (*mbox.SecretPayload, error) {
+	version := extractVersionFromRequest(r.VersionId)
+	val, ok := f.data[r.GetSecretId()][version]
+	if !ok {
+		return nil, errors.New("secret not found")
+	}
+	return val, nil
+}
+
+func (f *FakePayloadService) GetByKey(_ context.Context, r *mbox.GetPayloadByKeyRequest, _ ...grpc.CallOption) (*mbox.SecretPayloadEntry, error) {
+	version := extractVersionFromRequest(r.VersionId)
+	payload, ok := f.data[r.GetSecretId()][version]
+	if !ok {
+		return nil, errors.New("secret not found")
+	}
+
+	for _, p := range payload.GetData() {
+		if p.Key == r.GetKey() {
+			return &mbox.SecretPayloadEntry{VersionId: payload.VersionId, Data: &mbox.Payload{Key: p.Key, Payload: p.Payload}}, nil
+		}
+	}
+
+	return nil, errors.New(notFoundError)
+}
+
+func InitFakePayloadService() *FakePayloadService {
+	mysteryboxData := map[string]map[string]*mbox.SecretPayload{}
+	mysteryboxData["secret1Id"] = make(map[string]*mbox.SecretPayload)
+	mysteryboxData["secret1Id"][defaultVersion] = &mbox.SecretPayload{
+		VersionId: defaultVersion,
+		Data: []*mbox.Payload{
+			{
+				Key:     "key1",
+				Payload: &mbox.Payload_StringValue{StringValue: "test string secret"},
+			}, {
+				Key:     "key2",
+				Payload: &mbox.Payload_BinaryValue{BinaryValue: []byte("test byte secret")},
+			},
+		},
+	}
+	mysteryboxData["secret2Id"] = make(map[string]*mbox.SecretPayload)
+	mysteryboxData["secret2Id"][defaultVersion] = &mbox.SecretPayload{
+		VersionId: defaultVersion,
+		Data: []*mbox.Payload{
+			{
+				Key:     "key3",
+				Payload: &mbox.Payload_StringValue{StringValue: "test string secret"},
+			},
+		},
+	}
+	mysteryboxData["secret2Id"][alternativeVersion] = &mbox.SecretPayload{
+		VersionId: alternativeVersion,
+		Data: []*mbox.Payload{
+			{
+				Key:     "key3",
+				Payload: &mbox.Payload_StringValue{StringValue: "test string secret alternative"},
+			},
+		},
+	}
+	return &FakePayloadService{
+		data: mysteryboxData,
+	}
+}
+
+func TestGetSecret(t *testing.T) {
+	t.Parallel()
+	client := &GrpcClient{PayloadService: InitFakePayloadService()}
+
+	tests := []struct {
+		name     string
+		secretID string
+		expected map[string][]byte
+		version  string
+		wantErr  string
+	}{
+		{
+			name:     "Get secret's payload",
+			secretID: "secret1Id",
+			expected: map[string][]byte{
+				"key1": []byte("test string secret"),
+				"key2": []byte("test byte secret"),
+			}},
+		{
+			name:     "Get secret's payload by version",
+			secretID: "secret2Id",
+			expected: map[string][]byte{
+				"key3": []byte("test string secret alternative"),
+			},
+			version: alternativeVersion,
+		},
+		{
+			name:     "Get secret's payload by version not found",
+			secretID: "secret2Id",
+			wantErr:  notFoundError,
+			version:  "another-version",
+		},
+		{
+			name:     "Not found secret",
+			secretID: "nope",
+			wantErr:  notFoundError,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			payload, err := client.GetSecret(context.Background(), "token", tt.secretID, tt.version)
+			if tt.wantErr != "" {
+				tassert.Error(t, err)
+				tassert.Contains(t, err.Error(), tt.wantErr)
+				return
+			}
+
+			require.NoError(t, err)
+			if tt.version == "" {
+				tassert.Equal(t, defaultVersion, payload.VersionID, "Payload version must be default")
+			} else {
+				tassert.Equal(t, tt.version, payload.VersionID)
+			}
+
+			expected := make(map[string][]byte, len(tt.expected))
+			for k, v := range tt.expected {
+				expected[k] = v
+			}
+
+			tassert.Equal(t, len(payload.Entries), len(expected))
+			for _, entry := range payload.Entries {
+				value, _ := expected[entry.Key]
+				if (entry.BinaryValue != nil && bytes.Equal(value, entry.BinaryValue)) || (entry.StringValue != "" && bytes.Equal(value, []byte(entry.StringValue))) {
+					delete(expected, entry.Key)
+					continue
+				}
+			}
+			tassert.Empty(t, expected, "not all expected entries found: %+v", expected)
+		})
+	}
+}
+
+func TestGetSecretByKey(t *testing.T) {
+	t.Parallel()
+
+	client := &GrpcClient{PayloadService: InitFakePayloadService()}
+
+	tests := []struct {
+		name     string
+		secretID string
+		key      string
+		wantStr  string
+		wantBin  []byte
+		wantErr  string
+	}{
+		{name: "Get secret's string payload", secretID: "secret1Id", key: "key1", wantStr: "test string secret"},
+		{name: "Get secret's binary payload", secretID: "secret1Id", key: "key2", wantBin: []byte("test byte secret")},
+		{name: "Not found key", secretID: "secret1Id", key: "missing", wantErr: notFoundError},
+		{name: "Not found secret", secretID: "nope", key: "any", wantErr: notFoundError},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+			payload, err := client.GetSecretByKey(context.Background(), "token", tt.secretID, "", tt.key)
+			if tt.wantErr != "" {
+				tassert.Error(t, err)
+				tassert.Contains(t, err.Error(), tt.wantErr)
+				return
+			}
+			require.NoError(t, err)
+			require.NotNil(t, payload)
+			require.NotNil(t, payload.Entry)
+
+			if tt.wantStr != "" {
+				tassert.Nil(t, payload.Entry.BinaryValue)
+				tassert.Equal(t, tt.wantStr, payload.Entry.StringValue)
+			}
+			if tt.wantBin != nil {
+				tassert.Empty(t, payload.Entry.StringValue)
+				tassert.Equal(t, tt.wantBin, payload.Entry.BinaryValue)
+			}
+		})
+	}
+}
+
+func extractVersionFromRequest(requestVersion string) string {
+	if requestVersion == "" {
+		return defaultVersion
+	}
+	return requestVersion
+}

+ 61 - 0
providers/v1/nebius/common/sdk/sdk.go

@@ -0,0 +1,61 @@
+// /*
+// 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 sdk contains Nebius contains logic to create Nebius sdk for interaction with any Nebius service.
+package sdk
+
+import (
+	"context"
+	"crypto/tls"
+	"crypto/x509"
+	"errors"
+	"time"
+
+	"github.com/nebius/gosdk"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials"
+	"google.golang.org/grpc/keepalive"
+)
+
+// NewSDK initializes a new gosdk.SDK instance using the provided context, API domain, and CA certificate.
+// It sets up TLS configuration, including support for custom CA certificates, and gRPC dial options.
+// Returns the initialized SDK instance or an error if the setup fails.
+func NewSDK(ctx context.Context, apiDomain string, caCertificate []byte) (*gosdk.SDK, error) {
+	tlsCfg := &tls.Config{MinVersion: tls.VersionTLS12}
+
+	if len(caCertificate) > 0 {
+		certPool := x509.NewCertPool()
+		if !certPool.AppendCertsFromPEM(caCertificate) {
+			return nil, errors.New("failed to append CA certificate. PEM parse error")
+		}
+		tlsCfg.RootCAs = certPool
+	}
+
+	sdk, err := gosdk.New(
+		ctx,
+		gosdk.WithDomain(apiDomain),
+		gosdk.WithDialOptions(
+			grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg)),
+			grpc.WithKeepaliveParams(keepalive.ClientParameters{
+				Time:                time.Second * 30,
+				Timeout:             time.Second * 5,
+				PermitWithoutStream: false,
+			}),
+		),
+	)
+
+	return sdk, err
+}

+ 116 - 0
providers/v1/nebius/go.mod

@@ -0,0 +1,116 @@
+module github.com/external-secrets/external-secrets/providers/v1/nebius
+
+go 1.25.7
+
+require (
+	github.com/external-secrets/external-secrets/apis v0.0.0
+	github.com/external-secrets/external-secrets/runtime v0.0.0
+	github.com/go-logr/logr v1.4.3
+	github.com/google/uuid v1.6.0
+	github.com/hashicorp/golang-lru v1.0.2
+	github.com/nebius/gosdk v0.0.0-20260204094009-511fd4d4f7a1
+	github.com/spf13/pflag v1.0.10
+	github.com/stretchr/testify v1.11.1
+	golang.org/x/net v0.49.0
+	golang.org/x/sync v0.19.0
+	google.golang.org/grpc v1.76.0
+	k8s.io/api v0.35.0
+	k8s.io/apimachinery v0.35.0
+	k8s.io/utils v0.0.0-20260108192941-914a6e750570
+	sigs.k8s.io/controller-runtime v0.23.1
+)
+
+require (
+	buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1 // indirect
+	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.1-0.20241028115027-8cb06fe3c8b0 // indirect
+	github.com/aws/aws-sdk-go-v2 v1.39.6 // indirect
+	github.com/beorn7/perks v1.0.1 // indirect
+	github.com/cenkalti/backoff/v4 v4.3.0 // 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/fsnotify/fsnotify v1.9.0 // indirect
+	github.com/fxamacker/cbor/v2 v2.9.0 // indirect
+	github.com/go-openapi/jsonpointer v0.22.4 // indirect
+	github.com/go-openapi/jsonreference v0.21.4 // indirect
+	github.com/go-openapi/swag v0.25.4 // indirect
+	github.com/go-openapi/swag/cmdutils v0.25.4 // indirect
+	github.com/go-openapi/swag/conv v0.25.4 // indirect
+	github.com/go-openapi/swag/fileutils v0.25.4 // indirect
+	github.com/go-openapi/swag/jsonname v0.25.4 // indirect
+	github.com/go-openapi/swag/jsonutils v0.25.4 // indirect
+	github.com/go-openapi/swag/loading v0.25.4 // indirect
+	github.com/go-openapi/swag/mangling v0.25.4 // indirect
+	github.com/go-openapi/swag/netutils v0.25.4 // indirect
+	github.com/go-openapi/swag/stringutils v0.25.4 // indirect
+	github.com/go-openapi/swag/typeutils v0.25.4 // indirect
+	github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
+	github.com/goccy/go-json v0.10.5 // indirect
+	github.com/gofrs/flock v0.13.0 // indirect
+	github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
+	github.com/google/btree v1.1.3 // indirect
+	github.com/google/gnostic-models v0.7.1 // indirect
+	github.com/google/go-cmp v0.7.0 // indirect
+	github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 // 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/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/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.5 // 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/x448/float16 v0.8.4 // indirect
+	go.opentelemetry.io/auto/sdk v1.2.1 // indirect
+	go.opentelemetry.io/otel v1.38.0 // indirect
+	go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
+	go.yaml.in/yaml/v2 v2.4.3 // indirect
+	go.yaml.in/yaml/v3 v3.0.4 // indirect
+	golang.org/x/crypto v0.47.0 // indirect
+	golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
+	golang.org/x/oauth2 v0.34.0 // indirect
+	golang.org/x/sys v0.40.0 // indirect
+	golang.org/x/term v0.39.0 // indirect
+	golang.org/x/text v0.33.0 // indirect
+	golang.org/x/time v0.14.0 // indirect
+	gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
+	google.golang.org/protobuf v1.36.11 // indirect
+	gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
+	gopkg.in/inf.v0 v0.9.1 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+	k8s.io/apiextensions-apiserver v0.35.0 // indirect
+	k8s.io/client-go v0.35.0 // indirect
+	k8s.io/klog/v2 v2.130.1 // indirect
+	k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4 // 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.2-0.20260122202528-d9cc6641c482 // 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
+)

+ 276 - 0
providers/v1/nebius/go.sum

@@ -0,0 +1,276 @@
+buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1 h1:31on4W/yPcV4nZHL4+UCiCvLPsMqe/vJcNg8Rci0scc=
+buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.10-20250912141014-52f32327d4b0.1/go.mod h1:fUl8CEN/6ZAMk6bP8ahBJPUJw7rbp+j4x+wCcYi2IG4=
+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.1-0.20241028115027-8cb06fe3c8b0 h1:ecMw5jYFlWLY7EP3IKOELA8/CTy6cT/biq36sPZpBtw=
+github.com/Masterminds/sprig/v3 v3.3.1-0.20241028115027-8cb06fe3c8b0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
+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/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
+github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+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/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/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+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.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=
+github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80=
+github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8=
+github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4=
+github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU=
+github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ=
+github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4=
+github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=
+github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4=
+github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU=
+github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y=
+github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk=
+github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=
+github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=
+github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA=
+github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY=
+github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo=
+github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM=
+github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s=
+github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE=
+github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48=
+github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg=
+github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0=
+github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg=
+github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8=
+github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0=
+github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw=
+github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=
+github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw=
+github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc=
+github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4=
+github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=
+github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
+github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
+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/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
+github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+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.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c=
+github.com/google/gnostic-models v0.7.1/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-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
+github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
+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/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/EyXF4lPgLoUtcSWquBM0Rs=
+github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc=
+github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
+github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+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/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/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/nebius/gosdk v0.0.0-20260204094009-511fd4d4f7a1 h1:Lt5HZDEeSNJJbppzFEtlOYMGlGIOVi6YoC9YJMHv60A=
+github.com/nebius/gosdk v0.0.0-20260204094009-511fd4d4f7a1/go.mod h1:8r4EhhGJ+RMUfdiVVpZ8pEb0b+O7hLG8JXDAgGyu89o=
+github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
+github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
+github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
+github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
+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/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.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
+github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
+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/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/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=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
+go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
+go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
+go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
+go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
+go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
+go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
+go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
+go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
+go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
+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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
+golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
+golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
+golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
+golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
+golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
+golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
+golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
+golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
+golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
+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.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
+golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
+golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
+golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
+golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
+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.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
+golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
+gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
+gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
+gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
+gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
+google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
+google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
+google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
+google.golang.org/protobuf v1.36.11/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.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY=
+k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA=
+k8s.io/apiextensions-apiserver v0.35.0 h1:3xHk2rTOdWXXJM+RDQZJvdx0yEOgC0FgQ1PlJatA5T4=
+k8s.io/apiextensions-apiserver v0.35.0/go.mod h1:E1Ahk9SADaLQ4qtzYFkwUqusXTcaV2uw3l14aqpL2LU=
+k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8=
+k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
+k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE=
+k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o=
+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-20260127142750-a19766b6e2d4 h1:HhDfevmPS+OalTjQRKbTHppRIz01AWi8s45TMXStgYY=
+k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
+k8s.io/utils v0.0.0-20260108192941-914a6e750570 h1:JT4W8lsdrGENg9W+YwwdLJxklIuKWdRm+BC+xt33FOY=
+k8s.io/utils v0.0.0-20260108192941-914a6e750570/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
+sigs.k8s.io/controller-runtime v0.23.1 h1:TjJSM80Nf43Mg21+RCy3J70aj/W6KyvDtOlpKf+PupE=
+sigs.k8s.io/controller-runtime v0.23.1/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0=
+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.2-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs=
+sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482/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=

+ 351 - 0
providers/v1/nebius/mysterybox/provider.go

@@ -0,0 +1,351 @@
+// /*
+// 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 mysterybox contains the logic to work with Nebius MysteryBox API.
+package mysterybox
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"strings"
+	"sync"
+
+	"github.com/go-logr/logr"
+	lru "github.com/hashicorp/golang-lru"
+	"github.com/spf13/pflag"
+	"k8s.io/utils/clock"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	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/providers/v1/nebius/common/sdk/iam"
+	"github.com/external-secrets/external-secrets/providers/v1/nebius/common/sdk/mysterybox"
+	"github.com/external-secrets/external-secrets/runtime/constants"
+	"github.com/external-secrets/external-secrets/runtime/esutils/resolvers"
+	"github.com/external-secrets/external-secrets/runtime/feature"
+	"github.com/external-secrets/external-secrets/runtime/metrics"
+)
+
+var (
+	log                                   = ctrl.Log.WithName("provider").WithName("nebius").WithName("mysterybox")
+	mysteryboxTokensCacheSize             int
+	mysteryboxConnectionsCacheSize        int
+	defaultTokenCacheSize                 = 2 << 11
+	defaultMysteryboxConnectionsCacheSize = 2 << 6
+)
+
+// NewMysteryboxClient is a function that describes how to create a Nebius MysteryBox client to interact within.
+type NewMysteryboxClient func(ctx context.Context, apiDomain string, caCertificate []byte) (mysterybox.Client, error)
+
+// SecretsClientConfig holds configuration for interacting with.
+type SecretsClientConfig struct {
+	APIDomain           string
+	ServiceAccountCreds *esmeta.SecretKeySelector
+	Token               *esmeta.SecretKeySelector
+	CACertificate       *esmeta.SecretKeySelector
+}
+
+// ClientCacheKey represents a unique key for identifying cached MysteryBox clients.
+// It is composed of an API domain and a hash of the CA certificate.
+type ClientCacheKey struct {
+	APIDomain string
+	CAHash    string
+}
+
+// Provider is a struct for managing MysteryBox clients.
+type Provider struct {
+	Logger                      logr.Logger
+	NewMysteryboxClient         NewMysteryboxClient
+	TokenGetter                 TokenGetter
+	mysteryboxClientsCache      *lru.Cache
+	tokenInitMutex              sync.Mutex
+	cacheInitMutex              sync.Mutex
+	mysteryboxClientsCacheMutex sync.Mutex
+}
+
+// Capabilities returns the capabilities of the secret store, indicating it is read-only.
+func (p *Provider) Capabilities() esv1.SecretStoreCapabilities {
+	return esv1.SecretStoreReadOnly
+}
+
+// NewClient creates and returns a new SecretsClient for the specified SecretStore and namespace context.
+func (p *Provider) NewClient(ctx context.Context, store esv1.GenericStore, kube client.Client, namespace string) (esv1.SecretsClient, error) {
+	clientConfig, err := parseConfig(store)
+	if err != nil {
+		return nil, err
+	}
+
+	var caCert []byte
+	if clientConfig.CACertificate != nil {
+		caCert, err = p.getCaCert(ctx, clientConfig, store, kube, namespace)
+		if err != nil {
+			return nil, fmt.Errorf("read CA certificate %s/%s: %w", namespace, clientConfig.CACertificate.Name, err)
+		}
+	}
+
+	// lazy initialization with a current flag value
+	if err = p.initTokenGetter(); err != nil {
+		return nil, fmt.Errorf("init token getter: %w", err)
+	}
+
+	iamToken, err := p.getIamToken(ctx, clientConfig, store, kube, namespace, caCert)
+	if err != nil {
+		p.Logger.Info("Could not get IAM token", "store", store.GetNamespacedName(), "err", err)
+		return nil, err
+	}
+
+	mysteryboxGrpcClient, err := p.createOrGetMysteryboxClient(ctx, clientConfig.APIDomain, caCert)
+	if err != nil {
+		p.Logger.Info("Could not create or get MysteryBox Client", "store", store.GetNamespacedName(), "err", err)
+		return nil, err
+	}
+
+	return &SecretsClient{
+		mysteryboxClient: mysteryboxGrpcClient,
+		token:            iamToken,
+	}, nil
+}
+
+// getIamToken retrieves an IAM token based on the provided SecretsClientConfig and authentication options.
+// It supports token retrieval from a predefined secret or via service account credentials with the TokenGetter.
+func (p *Provider) getIamToken(ctx context.Context, config *SecretsClientConfig, store esv1.GenericStore, kube client.Client, namespace string, caCert []byte) (string, error) {
+	if config.Token.Name != "" {
+		iamToken, err := resolvers.SecretKeyRef(
+			ctx,
+			kube,
+			store.GetKind(),
+			namespace,
+			config.Token,
+		)
+		if err != nil {
+			return "", fmt.Errorf("read token secret %s/%s: %w", namespace, config.Token.Name, err)
+		}
+		return strings.TrimSpace(iamToken), nil
+	}
+	if config.ServiceAccountCreds.Name != "" {
+		subjectCreds, err := resolvers.SecretKeyRef(
+			ctx,
+			kube,
+			store.GetKind(),
+			namespace,
+			config.ServiceAccountCreds,
+		)
+		if err != nil {
+			return "", fmt.Errorf("read service account creds %s/%s: %w", namespace, config.ServiceAccountCreds.Name, err)
+		}
+		token, err := p.TokenGetter.GetToken(ctx, config.APIDomain, subjectCreds, caCert)
+		if err != nil {
+			return "", fmt.Errorf(errFailedToRetrieveToken, err)
+		}
+		return strings.TrimSpace(token), nil
+	}
+
+	return "", errors.New(errMissingAuthOptions)
+}
+
+// createOrGetMysteryboxClient initializes or retrieves a cached MysteryBox client for a specified API domain and certificate.
+func (p *Provider) createOrGetMysteryboxClient(ctx context.Context, apiDomain string, caCertificate []byte) (mysterybox.Client, error) {
+	// lazy initialization with a current flag value
+	if err := p.initMysteryboxClientsCache(); err != nil {
+		return nil, err
+	}
+
+	cacheKey := ClientCacheKey{
+		APIDomain: apiDomain,
+		CAHash:    HashBytes(caCertificate),
+	}
+
+	// lock to avoid race and connections leaks during client creation for the same key
+	p.mysteryboxClientsCacheMutex.Lock()
+	defer p.mysteryboxClientsCacheMutex.Unlock()
+
+	if value, ok := p.mysteryboxClientsCache.Get(cacheKey); ok {
+		p.Logger.V(1).Info("Reusing cached MysteryBox client", "apiDomain", apiDomain)
+		return value.(mysterybox.Client), nil
+	}
+	p.Logger.Info("Creating a new MysteryBox client", "apiDomain", apiDomain)
+	mysteryboxClient, err := p.NewMysteryboxClient(ctx, apiDomain, caCertificate)
+	if err != nil {
+		return nil, err
+	}
+	p.mysteryboxClientsCache.Add(cacheKey, mysteryboxClient)
+	return mysteryboxClient, nil
+}
+
+// getCaCert retrieves and returns the CA certificate as a byte slice for the specified secret in the given namespace.
+func (p *Provider) getCaCert(ctx context.Context, config *SecretsClientConfig, store esv1.GenericStore, kube client.Client, namespace string) ([]byte, error) {
+	caCert, err := resolvers.SecretKeyRef(
+		ctx,
+		kube,
+		store.GetKind(),
+		namespace,
+		config.CACertificate,
+	)
+	if err != nil {
+		return nil, err
+	}
+	return []byte(strings.TrimSpace(caCert)), nil
+}
+
+func parseConfig(store esv1.GenericStore) (*SecretsClientConfig, error) {
+	nebiusMysteryboxProvider, err := getNebiusMysteryboxProvider(store)
+	if err != nil {
+		return nil, err
+	}
+
+	if nebiusMysteryboxProvider.APIDomain == "" {
+		return nil, errors.New(errMissingAPIDomain)
+	}
+
+	var caCertificate *esmeta.SecretKeySelector
+	if nebiusMysteryboxProvider.CAProvider != nil {
+		caCertificate = &nebiusMysteryboxProvider.CAProvider.Certificate
+	}
+	return &SecretsClientConfig{
+		APIDomain:           strings.TrimSpace(nebiusMysteryboxProvider.APIDomain),
+		ServiceAccountCreds: &nebiusMysteryboxProvider.Auth.ServiceAccountCreds,
+		Token:               &nebiusMysteryboxProvider.Auth.Token,
+		CACertificate:       caCertificate,
+	}, nil
+}
+
+func newMysteryboxClient(ctx context.Context, apiDomain string, caCertificate []byte) (mysterybox.Client, error) {
+	return mysterybox.NewNebiusMysteryboxClientGrpc(ctx, apiDomain, caCertificate)
+}
+
+func (p *Provider) initMysteryboxClientsCache() error {
+	p.cacheInitMutex.Lock()
+	defer p.cacheInitMutex.Unlock()
+
+	if p.mysteryboxClientsCache != nil {
+		return nil
+	}
+
+	var err error
+	var cache *lru.Cache
+	cache, err = lru.NewWithEvict(
+		mysteryboxConnectionsCacheSize,
+		func(key, _ interface{}) {
+			p.Logger.V(1).Info("Evicting a Nebius MysteryBox client", "apiDomain", key.(ClientCacheKey).APIDomain)
+
+			// We intentionally do not call Close() on the evicted client here.
+			// This avoids "dial is closed" errors for active
+			// reconciliation loops that might still be using this client instance
+			// at the moment of eviction.
+			//
+			// If this approach leads to resource leaks in the future, we should consider
+			// implementing a reference counter to safely close the client only when
+			// it's no longer used by any active session.
+		},
+	)
+	if err == nil {
+		p.mysteryboxClientsCache = cache
+		return nil
+	}
+	return fmt.Errorf("init clients cache: %w", err)
+}
+
+func (p *Provider) initTokenGetter() error {
+	p.tokenInitMutex.Lock()
+	defer p.tokenInitMutex.Unlock()
+
+	if p.TokenGetter != nil {
+		return nil
+	}
+
+	var err error
+	c := clock.RealClock{}
+	tokenExchangerLogger := ctrl.Log.WithName("provider").WithName("nebius").WithName("iam").WithName("grpctokenexchanger")
+	tokenExchangeObserveFunction := func(err error) {
+		metrics.ObserveAPICall(constants.ProviderNebiusMysterybox, constants.CallNebiusMysteryboxAuth, err)
+	}
+	var tokenGetter TokenGetter
+	tokenGetter, err = NewCachedTokenGetter(
+		mysteryboxTokensCacheSize,
+		iam.NewGrpcTokenExchanger(
+			tokenExchangerLogger,
+			tokenExchangeObserveFunction,
+		), c)
+	if err == nil {
+		p.TokenGetter = tokenGetter
+	}
+
+	return err
+}
+
+// NewProvider creates a new Provider instance.
+func NewProvider() esv1.Provider {
+	return &Provider{
+		Logger:              log,
+		NewMysteryboxClient: newMysteryboxClient,
+	}
+}
+
+// MaintenanceStatus returns the maintenance status of the provider.
+func MaintenanceStatus() esv1.MaintenanceStatus {
+	return esv1.MaintenanceStatusMaintained
+}
+
+// ProviderSpec returns the provider specification for registration.
+func ProviderSpec() *esv1.SecretStoreProvider {
+	return &esv1.SecretStoreProvider{
+		NebiusMysterybox: &esv1.NebiusMysteryboxProvider{},
+	}
+}
+
+func init() {
+	fs := pflag.NewFlagSet("nebius", pflag.ExitOnError)
+	fs.IntVar(
+		&mysteryboxTokensCacheSize,
+		"mysterybox-tokens-cache-size",
+		defaultTokenCacheSize,
+		"Size of Nebius MysteryBox token cache. "+
+			"External secrets will reuse the Nebius IAM token without requesting a new one on each request.",
+	)
+	fs.IntVar(
+		&mysteryboxConnectionsCacheSize,
+		"mysterybox-connections-cache-size",
+		defaultMysteryboxConnectionsCacheSize,
+		"Size of Nebius MysteryBox grpc clients cache. External secrets will reuse the "+
+			"connection to MysteryBox for the configuration without opening a new one on each request.",
+	)
+	feature.Register(feature.Feature{
+		Flags: fs,
+		Initialize: func() {
+			if mysteryboxTokensCacheSize <= 0 {
+				log.Error(nil, "invalid token cache size, use default",
+					"got", mysteryboxTokensCacheSize,
+					"default", defaultTokenCacheSize,
+				)
+				mysteryboxTokensCacheSize = defaultTokenCacheSize
+			}
+			if mysteryboxConnectionsCacheSize <= 0 {
+				log.Error(nil, "invalid connections cache size, use default",
+					"got", mysteryboxConnectionsCacheSize,
+					"default", defaultMysteryboxConnectionsCacheSize,
+				)
+				mysteryboxConnectionsCacheSize = defaultMysteryboxConnectionsCacheSize
+			}
+			log.Info(
+				"Registered Nebius MysteryBox provider",
+				"token cache size", mysteryboxTokensCacheSize,
+				"clients cache size", mysteryboxConnectionsCacheSize,
+			)
+		},
+	})
+}

+ 960 - 0
providers/v1/nebius/mysterybox/provider_test.go

@@ -0,0 +1,960 @@
+// /*
+// 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 mysterybox
+
+import (
+	"context"
+	b64 "encoding/base64"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"sync"
+	"sync/atomic"
+	"testing"
+
+	"github.com/google/uuid"
+	lru "github.com/hashicorp/golang-lru"
+	"github.com/nebius/gosdk/auth"
+	tassert "github.com/stretchr/testify/assert"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	ctrl "sigs.k8s.io/controller-runtime"
+	k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
+	clientfake "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"
+	"github.com/external-secrets/external-secrets/providers/v1/nebius/common/sdk/mysterybox"
+	"github.com/external-secrets/external-secrets/providers/v1/nebius/common/sdk/mysterybox/fake"
+)
+
+const (
+	tokenSecretName   = "tokenSecretName"
+	tokenSecretKey    = "tokenSecretKey"
+	saCredsSecretName = "saCredsSecretName"
+	saCredsSecretKey  = "saCredsSecretKey"
+	authRefName       = "authRefSecretName"
+	authRefKey        = "authRefSecretKey"
+	apiDomain         = "api.public"
+	tokenToBeIssued   = "token-to-be-issued"
+)
+
+var (
+	logger = ctrl.Log.WithName("provider").WithName("nebius").WithName("mysterybox")
+)
+
+func setupClientWithTokenAuth(t *testing.T, entries []mysterybox.Entry, tokenGetter TokenGetter) (context.Context, *SecretsClient, *fake.Secret, k8sclient.Client, *fake.MysteryboxService) {
+	t.Helper()
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	mysteryboxService := fake.InitMysteryboxService()
+	k8sClient := clientfake.NewClientBuilder().Build()
+
+	secret := mysteryboxService.CreateSecret(entries)
+
+	provider := newProvider(
+		t,
+		func(ctx context.Context, apiDomain string, caCertificate []byte) (mysterybox.Client, error) {
+			return fake.NewFakeMysteryboxClient(mysteryboxService), nil
+		},
+		tokenGetter,
+	)
+	createK8sSecret(ctx, t, k8sClient, namespace, tokenSecretName, tokenSecretKey, []byte("token"))
+	store := newNebiusMysteryboxSecretStoreWithAuthTokenKey(apiDomain, namespace, tokenSecretName, tokenSecretKey)
+	client, err := provider.NewClient(ctx, store, k8sClient, namespace)
+	tassert.NoError(t, err)
+
+	mysteryboxSecretsClient, ok := client.(*SecretsClient)
+	tassert.True(t, ok, "expected *SecretsClient, got %T", client)
+	return ctx, mysteryboxSecretsClient, secret, k8sClient, mysteryboxService
+}
+
+func TestNewClient_GetTokenError(t *testing.T) {
+	t.Parallel()
+	tokenGetter := faketokenGetter{returnError: true}
+
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	mysteryboxService := fake.InitMysteryboxService()
+	k8sClient := clientfake.NewClientBuilder().Build()
+	createK8sSecret(ctx, t, k8sClient, namespace, saCredsSecretName, saCredsSecretKey, []byte("PRIVATE KEY"))
+
+	provider := newProvider(
+		t,
+		func(ctx context.Context, apiDomain string, caCertificate []byte) (mysterybox.Client, error) {
+			return fake.NewFakeMysteryboxClient(mysteryboxService), nil
+		},
+		&tokenGetter,
+	)
+
+	_, err := provider.NewClient(ctx, newNebiusMysteryboxSecretStoreWithServiceAccountCreds(apiDomain, namespace, saCredsSecretName, saCredsSecretKey), k8sClient, namespace)
+	tassert.Error(t, err)
+	tassert.ErrorContains(t, err, "failed to retrieve iam token by credentials")
+}
+
+func TestGetSecret(t *testing.T) {
+	t.Parallel()
+	entries := []mysterybox.Entry{
+		{Key: "key1", StringValue: "string"},
+		{Key: "key2", StringValue: "string2"},
+		{Key: "key3", BinaryValue: []byte("binaryValue")},
+	}
+
+	tests := []struct {
+		name       string
+		prepare    func(ctx context.Context, client *SecretsClient, secret *fake.Secret, svc *fake.MysteryboxService) ([]byte, error)
+		expectJSON map[string]string
+		expectRaw  []byte
+	}{
+		{
+			name: "get all entries as JSON",
+			prepare: func(ctx context.Context, client *SecretsClient, secret *fake.Secret, _ *fake.MysteryboxService) ([]byte, error) {
+				return client.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secret.Id})
+			},
+			expectJSON: map[string]string{
+				"key1": "string",
+				"key2": "string2",
+				"key3": b64.StdEncoding.EncodeToString([]byte("binaryValue")),
+			},
+		},
+		{
+			name: "string entry by key",
+			prepare: func(ctx context.Context, client *SecretsClient, secret *fake.Secret, _ *fake.MysteryboxService) ([]byte, error) {
+				return client.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secret.Id, Property: "key1"})
+			},
+			expectRaw: []byte("string"),
+		},
+		{
+			name: "binary entry by key",
+			prepare: func(ctx context.Context, client *SecretsClient, secret *fake.Secret, _ *fake.MysteryboxService) ([]byte, error) {
+				return client.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secret.Id, Property: "key3"})
+			},
+			expectRaw: []byte("binaryValue"),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+			ctx, client, secret, _, svc := setupClientWithTokenAuth(t, entries, nil)
+			result, err := tt.prepare(ctx, client, secret, svc)
+			tassert.NoError(t, err)
+			if tt.expectJSON != nil {
+				tassert.Equal(t, tt.expectJSON, unmarshalStringMap(t, result))
+			} else {
+				tassert.Equal(t, tt.expectRaw, result)
+			}
+		})
+	}
+}
+
+func TestGetSecret_ByVersionId(t *testing.T) {
+	t.Parallel()
+	ctx, client, secret, _, mboxService := setupClientWithTokenAuth(t, []mysterybox.Entry{{Key: "key", StringValue: "string_value"}}, nil)
+	_, err := mboxService.CreateNewSecretVersion(secret.Id, []mysterybox.Entry{
+		{Key: "new_key", StringValue: "updated_string_value"},
+		{Key: "new", StringValue: "new"},
+	})
+	tassert.NoError(t, err)
+
+	result, err := client.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secret.Id, Property: "key", Version: secret.VersionId})
+	tassert.NoError(t, err)
+	tassert.Equal(t, []byte("string_value"), result)
+}
+
+func TestGetSecretMap(t *testing.T) {
+	t.Parallel()
+	allEntries := []mysterybox.Entry{
+		{Key: "key1", StringValue: "string"},
+		{Key: "key2", StringValue: "string2"},
+		{Key: "key3", BinaryValue: []byte("binaryValue")},
+	}
+
+	tests := []struct {
+		name      string
+		entries   []mysterybox.Entry
+		prepare   func(ctx context.Context, client *SecretsClient, secret *fake.Secret, svc *fake.MysteryboxService) (map[string][]byte, error)
+		expectMap map[string][]byte
+	}{
+		{
+			name:    "all entries",
+			entries: allEntries,
+			prepare: func(ctx context.Context, client *SecretsClient, secret *fake.Secret, _ *fake.MysteryboxService) (map[string][]byte, error) {
+				return client.GetSecretMap(ctx, esv1.ExternalSecretDataRemoteRef{Key: secret.Id})
+			},
+			expectMap: map[string][]byte{
+				"key1": []byte("string"),
+				"key2": []byte("string2"),
+				"key3": []byte("binaryValue"),
+			},
+		},
+		{
+			name:    "by version id",
+			entries: []mysterybox.Entry{{Key: "key", StringValue: "string_value"}},
+			prepare: func(ctx context.Context, client *SecretsClient, secret *fake.Secret, svc *fake.MysteryboxService) (map[string][]byte, error) {
+				_, err := svc.CreateNewSecretVersion(secret.Id, []mysterybox.Entry{{Key: "new_key", StringValue: "updated_string_value"}, {Key: "new", StringValue: "new"}})
+				if err != nil {
+					return nil, err
+				}
+				return client.GetSecretMap(ctx, esv1.ExternalSecretDataRemoteRef{Key: secret.Id, Version: secret.VersionId})
+			},
+			expectMap: map[string][]byte{
+				"key": []byte("string_value"),
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+			ctx, client, secret, _, svc := setupClientWithTokenAuth(t, tt.entries, nil)
+			result, err := tt.prepare(ctx, client, secret, svc)
+			tassert.NoError(t, err)
+			tassert.Equal(t, tt.expectMap, result)
+		})
+	}
+}
+
+func TestNewClient_ValidationErrors(t *testing.T) {
+	t.Parallel()
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	mysteryboxService := fake.InitMysteryboxService()
+	k8sClient := clientfake.NewClientBuilder().Build()
+	createK8sSecret(ctx, t, k8sClient, namespace, tokenSecretName, tokenSecretKey, []byte("token"))
+
+	tokenToIssue := tokenToBeIssued
+	notExistingSecretName := "not-existing-secret"
+	notExistingSecretKey := "not-existing-secret-key"
+
+	cache, err := lru.New(10)
+	tassert.NoError(t, err)
+	tokenGetter := &faketokenGetter{tokenToIssue: tokenToIssue}
+
+	newProvider := func() *Provider {
+		return &Provider{
+			Logger: logger,
+			NewMysteryboxClient: func(ctx context.Context, apiDomain string, caCertificate []byte) (mysterybox.Client, error) {
+				return fake.NewFakeMysteryboxClient(mysteryboxService), nil
+			},
+			TokenGetter:            tokenGetter,
+			mysteryboxClientsCache: cache,
+		}
+	}
+
+	tests := []struct {
+		name      string
+		storeSpec func() *esv1.SecretStore
+		expectErr string
+	}{
+		{
+			name: "missing api domain",
+			storeSpec: func() *esv1.SecretStore {
+				return &esv1.SecretStore{
+					ObjectMeta: metav1.ObjectMeta{Namespace: namespace},
+					Spec: esv1.SecretStoreSpec{
+						Provider: &esv1.SecretStoreProvider{
+							NebiusMysterybox: &esv1.NebiusMysteryboxProvider{},
+						},
+					},
+				}
+			},
+			expectErr: errMissingAPIDomain,
+		},
+		{
+			name: "missing auth options",
+			storeSpec: func() *esv1.SecretStore {
+				return &esv1.SecretStore{
+					ObjectMeta: metav1.ObjectMeta{Namespace: namespace},
+					Spec: esv1.SecretStoreSpec{
+						Provider: &esv1.SecretStoreProvider{
+							NebiusMysterybox: &esv1.NebiusMysteryboxProvider{APIDomain: apiDomain},
+						},
+					},
+				}
+			},
+			expectErr: errMissingAuthOptions,
+		},
+		{
+			name: "specified token secret does not exist in kubernetes secrets",
+			storeSpec: func() *esv1.SecretStore {
+				return &esv1.SecretStore{
+					ObjectMeta: metav1.ObjectMeta{Namespace: namespace},
+					Spec: esv1.SecretStoreSpec{
+						Provider: &esv1.SecretStoreProvider{
+							NebiusMysterybox: &esv1.NebiusMysteryboxProvider{
+								APIDomain: apiDomain,
+								Auth: esv1.NebiusAuth{
+									Token: esmeta.SecretKeySelector{
+										Namespace: &namespace,
+										Name:      notExistingSecretName,
+										Key:       tokenSecretKey,
+									},
+								},
+							},
+						},
+					},
+				}
+			},
+			expectErr: fmt.Sprintf("read token secret %s/%s: cannot get Kubernetes secret", namespace, notExistingSecretName),
+		},
+		{
+			name: "specified service account " +
+				"creds secret does not exist in kubernetes secrets",
+			storeSpec: func() *esv1.SecretStore {
+				return &esv1.SecretStore{
+					ObjectMeta: metav1.ObjectMeta{Namespace: namespace},
+					Spec: esv1.SecretStoreSpec{
+						Provider: &esv1.SecretStoreProvider{
+							NebiusMysterybox: &esv1.NebiusMysteryboxProvider{
+								APIDomain: apiDomain,
+								Auth: esv1.NebiusAuth{
+									ServiceAccountCreds: esmeta.SecretKeySelector{
+										Namespace: &namespace,
+										Name:      notExistingSecretName,
+										Key:       "secretKey",
+									},
+								},
+							},
+						},
+					},
+				}
+			},
+			expectErr: fmt.Sprintf("read service account creds %s/%s: cannot get Kubernetes secret", namespace, notExistingSecretName),
+		},
+		{
+			name: "specified token secret's key does not exist in the secret",
+			storeSpec: func() *esv1.SecretStore {
+				return &esv1.SecretStore{
+					ObjectMeta: metav1.ObjectMeta{Namespace: namespace},
+					Spec: esv1.SecretStoreSpec{
+						Provider: &esv1.SecretStoreProvider{
+							NebiusMysterybox: &esv1.NebiusMysteryboxProvider{
+								APIDomain: apiDomain,
+								Auth: esv1.NebiusAuth{
+									Token: esmeta.SecretKeySelector{
+										Namespace: &namespace,
+										Name:      tokenSecretName,
+										Key:       notExistingSecretKey,
+									},
+								},
+							},
+						},
+					},
+				}
+			},
+			expectErr: fmt.Sprintf("cannot find secret data for key: %q", notExistingSecretKey),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+			p := newProvider()
+			store := tt.storeSpec()
+			_, err := p.NewClient(ctx, store, k8sClient, namespace)
+			tassert.Error(t, err)
+			tassert.ErrorContains(t, err, tt.expectErr)
+		})
+	}
+}
+
+func TestNewClient_AuthWithSecretAccountCreds(t *testing.T) {
+	t.Parallel()
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	mysteryboxService := fake.InitMysteryboxService()
+	k8sClient := clientfake.NewClientBuilder().Build()
+
+	secret := mysteryboxService.CreateSecret([]mysterybox.Entry{{Key: "k", StringValue: "v"}})
+
+	providedCreds, _ := json.Marshal(&auth.ServiceAccountCredentials{
+		SubjectCredentials: auth.SubjectCredentials{
+			PrivateKey: "-----BEGIN PRIVATE KEY-----\nTEST-KEY\n-----END PRIVATE KEY-----",
+			KeyID:      "keyId",
+			Subject:    "subjectId",
+			Issuer:     "subjectId",
+		},
+	})
+
+	tokenToIssue := tokenToBeIssued
+
+	tokenGetter := &faketokenGetter{
+		tokenToIssue: tokenToIssue,
+	}
+
+	cache, err := lru.New(10)
+	tassert.NoError(t, err)
+
+	p := &Provider{
+		Logger: logger,
+		NewMysteryboxClient: func(ctx context.Context, apiDomain string, caCertificate []byte) (mysterybox.Client, error) {
+			return fake.NewFakeMysteryboxClient(mysteryboxService), nil
+		},
+		mysteryboxClientsCache: cache,
+	}
+	settokenGetterWorkaround(tokenGetter, p)
+
+	createK8sSecret(ctx, t, k8sClient, namespace, authRefName, authRefKey, providedCreds)
+	store := newNebiusMysteryboxSecretStoreWithServiceAccountCreds(apiDomain, namespace, authRefName, authRefKey)
+
+	client, err := p.NewClient(ctx, store, k8sClient, namespace)
+	tassert.NoError(t, err)
+
+	msc, ok := client.(*SecretsClient)
+	tassert.True(t, ok, "expected *MysteryboxSecretsClient, got %T", client)
+	tassert.Equal(t, tokenToIssue, msc.token, fmt.Sprintf("token mismatch: got %q want %q (issued by TokenGetter)", msc.token, tokenToIssue))
+
+	// also ensure TokenGetter was exercised with the domain and creds we expect
+	tassert.Equal(t, int32(1), tokenGetter.calls, "expected TokenGetter to be called once")
+	tassert.Equal(t, apiDomain, tokenGetter.gotDomain, "expected TokenGetter to be called with the correct domain")
+	tassert.Equal(t, string(providedCreds), tokenGetter.gotCreds, "expected TokenGetter to be called with the correct creds")
+	tassert.Nil(t, tokenGetter.gotCACert, "expected TokenGetter to be called without CA cert")
+
+	got, err := msc.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secret.Id, Property: "k"})
+	tassert.NoError(t, err)
+	tassert.Equal(t, []byte("v"), got)
+}
+
+func TestGetSecret_NotFound(t *testing.T) {
+	t.Parallel()
+	// Use table-driven tests to cover all NotFound scenarios in one place
+	cases := []struct {
+		name           string
+		entries        []mysterybox.Entry
+		prepare        func(ctx context.Context, client *SecretsClient, secret *fake.Secret) ([]byte, error)
+		expectErrEqual func(t *testing.T, err error, secret *fake.Secret)
+	}{
+		{
+			name:    "SecretID not found",
+			entries: []mysterybox.Entry{{Key: "key1", StringValue: "string"}},
+			prepare: func(ctx context.Context, client *SecretsClient, _ *fake.Secret) ([]byte, error) {
+				desiredSecretId := "notexists"
+				return client.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: desiredSecretId})
+			},
+			expectErrEqual: func(t *testing.T, err error, _ *fake.Secret) {
+				tassert.Error(t, err)
+				tassert.ErrorIs(t, err, esv1.NoSecretErr)
+				tassert.EqualError(t, err, fmt.Errorf(errSecretNotFound, "notexists", esv1.NoSecretErr).Error())
+			},
+		},
+		{
+			name:    "Version not found",
+			entries: []mysterybox.Entry{{Key: "key1", StringValue: "string"}},
+			prepare: func(ctx context.Context, client *SecretsClient, secret *fake.Secret) ([]byte, error) {
+				desiredVersion := "notexistversion"
+				return client.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secret.Id, Version: desiredVersion})
+			},
+			expectErrEqual: func(t *testing.T, err error, secret *fake.Secret) {
+				tassert.Error(t, err)
+				tassert.ErrorIs(t, err, esv1.NoSecretErr)
+				tassert.EqualError(t, err, fmt.Errorf(errSecretVersionNotFound, "notexistversion", secret.Id, esv1.NoSecretErr).Error())
+			},
+		},
+		{
+			name:    "Property key not found in version",
+			entries: []mysterybox.Entry{{Key: "key1", StringValue: "string"}},
+			prepare: func(ctx context.Context, client *SecretsClient, secret *fake.Secret) ([]byte, error) {
+				desiredKey := "key"
+				return client.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secret.Id, Version: secret.VersionId, Property: desiredKey})
+			},
+			expectErrEqual: func(t *testing.T, err error, secret *fake.Secret) {
+				tassert.Error(t, err)
+				tassert.ErrorIs(t, err, esv1.NoSecretErr)
+				tassert.EqualError(t, err, fmt.Errorf(errSecretVersionByKeyNotFound, secret.VersionId, secret.Id, "key", esv1.NoSecretErr).Error())
+			},
+		},
+		{
+			name:    "Property key not found",
+			entries: []mysterybox.Entry{{Key: "key1", StringValue: "string"}},
+			prepare: func(ctx context.Context, client *SecretsClient, secret *fake.Secret) ([]byte, error) {
+				desiredKey := "key"
+				return client.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secret.Id, Property: desiredKey})
+			},
+			expectErrEqual: func(t *testing.T, err error, secret *fake.Secret) {
+				tassert.Error(t, err)
+				tassert.ErrorIs(t, err, esv1.NoSecretErr)
+				tassert.EqualError(t, err, fmt.Errorf(errSecretByKeyNotFound, "key", secret.Id, esv1.NoSecretErr).Error())
+			},
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			ctx, client, secret, _, _ := setupClientWithTokenAuth(t, tc.entries, nil)
+			_, err := tc.prepare(ctx, client, secret)
+			tc.expectErrEqual(t, err, secret)
+		})
+	}
+}
+
+func TestGetSecretMap_NotFound(t *testing.T) {
+	t.Parallel()
+
+	cases := []struct {
+		name           string
+		entries        []mysterybox.Entry
+		prepare        func(ctx context.Context, client *SecretsClient, secret *fake.Secret) (map[string][]byte, error)
+		expectErrEqual func(t *testing.T, err error, secret *fake.Secret)
+	}{
+		{
+			name:    "SecretID not found",
+			entries: []mysterybox.Entry{{Key: "key1", StringValue: "string"}},
+			prepare: func(ctx context.Context, client *SecretsClient, _ *fake.Secret) (map[string][]byte, error) {
+				desiredSecretId := "notexists"
+				return client.GetSecretMap(ctx, esv1.ExternalSecretDataRemoteRef{Key: desiredSecretId})
+			},
+			expectErrEqual: func(t *testing.T, err error, _ *fake.Secret) {
+				tassert.Error(t, err)
+				tassert.ErrorIs(t, err, esv1.NoSecretErr)
+				tassert.EqualError(t, err, fmt.Errorf(errSecretNotFound, "notexists", esv1.NoSecretErr).Error())
+			},
+		},
+		{
+			name:    "Version not found",
+			entries: []mysterybox.Entry{{Key: "key1", StringValue: "string"}},
+			prepare: func(ctx context.Context, client *SecretsClient, secret *fake.Secret) (map[string][]byte, error) {
+				desiredVersion := "notexistversion"
+				return client.GetSecretMap(ctx, esv1.ExternalSecretDataRemoteRef{Key: secret.Id, Version: desiredVersion})
+			},
+			expectErrEqual: func(t *testing.T, err error, secret *fake.Secret) {
+				tassert.Error(t, err)
+				tassert.ErrorIs(t, err, esv1.NoSecretErr)
+				tassert.EqualError(t, err, fmt.Errorf(errSecretVersionNotFound, "notexistversion", secret.Id, esv1.NoSecretErr).Error())
+			},
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			ctx, client, secret, _, _ := setupClientWithTokenAuth(t, tc.entries, nil)
+			_, err := tc.prepare(ctx, client, secret)
+			tc.expectErrEqual(t, err, secret)
+		})
+	}
+}
+
+func TestCreateOrGetMysteryboxClient_CachesByKey(t *testing.T) {
+	t.Parallel()
+	ctx := context.Background()
+
+	cache, err := lru.New(10)
+	tassert.NoError(t, err)
+	var factoryCalls int32
+	p := &Provider{
+		Logger: logger,
+		NewMysteryboxClient: func(ctx context.Context, apiDomain string, caCertificate []byte) (mysterybox.Client, error) {
+			atomic.AddInt32(&factoryCalls, 1)
+			return fake.NewFakeMysteryboxClient(nil), nil
+		},
+		mysteryboxClientsCache: cache,
+	}
+
+	// same domain + same CA
+	_, err = p.createOrGetMysteryboxClient(ctx, "api.nebius.example", []byte("CA1"))
+	tassert.NoError(t, err)
+	_, err = p.createOrGetMysteryboxClient(ctx, "api.nebius.example", []byte("CA1"))
+	tassert.NoError(t, err)
+
+	// different CA
+	_, err = p.createOrGetMysteryboxClient(ctx, "api.nebius.example", []byte("CA2"))
+	tassert.NoError(t, err)
+
+	_, err = p.createOrGetMysteryboxClient(ctx, "other.nebius.example", []byte("CA1"))
+	tassert.NoError(t, err)
+
+	tassert.Equal(t, int32(3), atomic.LoadInt32(&factoryCalls), fmt.Sprintf("factory called %d times, want %d", atomic.LoadInt32(&factoryCalls), 3))
+}
+
+func TestCreateOrGetMysteryboxClient_EmptyCA_EqualsNil(t *testing.T) {
+	t.Parallel()
+	ctx := context.Background()
+
+	cache, err := lru.New(10)
+	tassert.NoError(t, err)
+
+	var factoryCalls int32
+	p := &Provider{
+		Logger: logger,
+		NewMysteryboxClient: func(ctx context.Context, apiDomain string, caCertificate []byte) (mysterybox.Client, error) {
+			atomic.AddInt32(&factoryCalls, 1)
+			return fake.NewFakeMysteryboxClient(nil), nil
+		},
+		mysteryboxClientsCache: cache,
+	}
+
+	_, err = p.createOrGetMysteryboxClient(ctx, "api.nebius.example", nil)
+	tassert.NoError(t, err)
+	_, err = p.createOrGetMysteryboxClient(ctx, "api.nebius.example", []byte{})
+	tassert.NoError(t, err)
+
+	tassert.Equal(t, int32(1), atomic.LoadInt32(&factoryCalls), fmt.Sprintf("factory called %d times, want %d when CA=nil and CA=empty should map to same key", factoryCalls, 1))
+}
+
+func TestMysteryboxClientsCache_EvictionClosesClient(t *testing.T) {
+	t.Parallel()
+	ctx := context.Background()
+
+	var created []*fake.FakeMysteryboxClient
+	p := &Provider{
+		Logger: logger,
+		NewMysteryboxClient: func(ctx context.Context, apiDomain string, caCertificate []byte) (mysterybox.Client, error) {
+			c := &fake.FakeMysteryboxClient{}
+			created = append(created, c)
+			return c, nil
+		},
+	}
+
+	setCacheSizeWorkaround(t, 1, p)
+
+	_, err := p.createOrGetMysteryboxClient(ctx, "domain-a", nil)
+	tassert.NoError(t, err)
+
+	tassert.Len(t, created, 1, "expected 1 client created, got %d", len(created))
+
+	_, err = p.createOrGetMysteryboxClient(ctx, "domain-b", nil)
+	tassert.NoError(t, err)
+
+	tassert.Len(t, created, 2, "expected 2 clients created, got %d", len(created))
+
+	// clients are not closed because of the eviction policy for provider's mysterybox clients connections cache
+	tassert.Equal(t, int32(0), atomic.LoadInt32(&created[0].Closed), "expected second client not to be closed")
+	tassert.Equal(t, int32(0), atomic.LoadInt32(&created[1].Closed), "expected second client not to be closed")
+}
+
+// concurrent tests
+
+func TestCreateOrGetMysteryboxClient_Concurrent_SingleClient(t *testing.T) {
+	t.Parallel()
+	clientData := ClientData{domain: "api.nebius.example", ca: []byte("CA1")}
+
+	ctx := context.Background()
+	cache, err := lru.New(10)
+	tassert.NoError(t, err)
+
+	var factoryCalls int32
+	p := &Provider{
+		Logger: logger,
+		NewMysteryboxClient: func(ctx context.Context, apiDomain string, caCertificate []byte) (mysterybox.Client, error) {
+			atomic.AddInt32(&factoryCalls, 1)
+			return fake.NewFakeMysteryboxClient(nil), nil
+		},
+		mysteryboxClientsCache: cache,
+	}
+
+	const goroutines = 8
+	var wg sync.WaitGroup
+	wg.Add(goroutines)
+	start := make(chan struct{})
+
+	errs := make([]error, goroutines)
+	for i := 0; i < goroutines; i++ {
+		go func(ix int) {
+			defer wg.Done()
+			<-start
+			_, err := p.createOrGetMysteryboxClient(ctx, clientData.domain, clientData.ca)
+			errs[ix] = err
+		}(i)
+	}
+	close(start)
+	wg.Wait()
+
+	for i, err := range errs {
+		if err != nil {
+			tassert.NoError(t, err, "goroutine %d", i)
+		}
+	}
+
+	tassert.Equal(t, int32(1), atomic.LoadInt32(&factoryCalls), fmt.Sprintf("factory called %d times, want %d for concurrent same-key requests", factoryCalls, 1))
+}
+
+func TestCreateOrGetMysteryboxClient_Concurrent_MultipleClients(t *testing.T) {
+	clientRequests := []ClientData{
+		{domain: "api.nebius.example1", ca: []byte("CA1")},
+		{domain: "api.nebius.example1", ca: []byte("CA1")}, // duplicate
+		{domain: "api.nebius.example1", ca: []byte("CA2")}, // the same domain, different CA
+		{domain: "api.nebius.example2", ca: []byte("CA2")}, // different domain, the same CA
+		{domain: "api.nebius.example1", ca: []byte{}},      // the same domain, empty CA
+	}
+
+	ctx := context.Background()
+	cache, err := lru.New(10)
+	tassert.NoError(t, err)
+	var factoryCalls int32
+	p := &Provider{
+		Logger: logger,
+		NewMysteryboxClient: func(ctx context.Context, apiDomain string, caCertificate []byte) (mysterybox.Client, error) {
+			atomic.AddInt32(&factoryCalls, 1)
+			return fake.NewFakeMysteryboxClient(nil), nil
+		},
+		mysteryboxClientsCache: cache,
+	}
+
+	var wg sync.WaitGroup
+	wg.Add(len(clientRequests))
+	start := make(chan struct{})
+
+	errs := make([]error, len(clientRequests))
+	for i, r := range clientRequests {
+		go func(ix int, req ClientData) {
+			defer wg.Done()
+			<-start
+			_, err := p.createOrGetMysteryboxClient(ctx, req.domain, req.ca)
+			errs[ix] = err
+		}(i, r)
+	}
+	close(start)
+	wg.Wait()
+
+	for i, err := range errs {
+		if err != nil {
+			tassert.NoError(t, err, "goroutine %d", i)
+		}
+	}
+
+	tassert.Equal(t, int32(4), atomic.LoadInt32(&factoryCalls), fmt.Sprintf("factory called %d times, want %d", atomic.LoadInt32(&factoryCalls), 4))
+}
+
+func TestMysteryboxClientsCache_ConcurrentEviction_CloseOnce(t *testing.T) {
+	ctx := context.Background()
+
+	var created []*fake.FakeMysteryboxClient
+	var mu sync.Mutex
+
+	p := &Provider{
+		Logger: logger,
+		NewMysteryboxClient: func(ctx context.Context, apiDomain string, caCertificate []byte) (mysterybox.Client, error) {
+			c := &fake.FakeMysteryboxClient{}
+			mu.Lock()
+			created = append(created, c)
+			mu.Unlock()
+			return c, nil
+		},
+	}
+	setCacheSizeWorkaround(t, 1, p)
+
+	var wg sync.WaitGroup
+	wg.Add(3)
+	start := make(chan struct{})
+
+	go func() {
+		defer wg.Done()
+		<-start
+		_, _ = p.createOrGetMysteryboxClient(ctx, "domain-a", nil)
+	}()
+
+	go func() {
+		defer wg.Done()
+		<-start
+		_, _ = p.createOrGetMysteryboxClient(ctx, "domain-b", nil)
+	}()
+	go func() {
+		defer wg.Done()
+		<-start
+		_, _ = p.createOrGetMysteryboxClient(ctx, "domain-c", nil)
+	}()
+
+	close(start)
+	wg.Wait()
+
+	tassert.Len(t, created, 3, "expected 3 clients created, got %d", len(created))
+	// clients are not closed because of the eviction policy for provider's mysterybox clients connections cache
+	tassert.Equal(t, int32(0), atomic.LoadInt32(&created[0].Closed), "expected first client not to be closed on eviction")
+	tassert.Equal(t, int32(0), atomic.LoadInt32(&created[1].Closed), "expected first client not to be closed on eviction")
+	tassert.Equal(t, int32(0), atomic.LoadInt32(&created[2].Closed), "expected first client not to be closed on eviction")
+}
+
+func TestNewClient_Concurrent_SameConfig_SingleClient_DifferentTokens(t *testing.T) {
+	ctx := context.Background()
+
+	namespace := uuid.NewString()
+	mboxSvc := fake.InitMysteryboxService()
+	k8sClient := clientfake.NewClientBuilder().Build()
+
+	secret := mboxSvc.CreateSecret([]mysterybox.Entry{{Key: "k", StringValue: "v"}})
+
+	var factoryCalls int32
+	tokenToIssue := tokenToBeIssued
+
+	tokenGetter := &faketokenGetter{tokenToIssue: tokenToIssue}
+
+	p := &Provider{
+		Logger: logger,
+		NewMysteryboxClient: func(ctx context.Context, apiDomain string, caCertificate []byte) (mysterybox.Client, error) {
+			atomic.AddInt32(&factoryCalls, 1)
+			return fake.NewFakeMysteryboxClient(mboxSvc), nil
+		},
+	}
+	settokenGetterWorkaround(tokenGetter, p)
+
+	creds := []byte(`{"private_key":"KEY","key_id":"id","subject":"sub","issuer":"iss"}`)
+	createK8sSecret(ctx, t, k8sClient, namespace, authRefName, authRefKey, creds)
+
+	store := newNebiusMysteryboxSecretStoreWithServiceAccountCreds(apiDomain, namespace, authRefName, authRefKey)
+
+	const goroutines = 10
+	var wg sync.WaitGroup
+	wg.Add(goroutines)
+	start := make(chan struct{})
+
+	clients := make([]esv1.SecretsClient, goroutines)
+	errs := make([]error, goroutines)
+
+	for i := 0; i < goroutines; i++ {
+		go func(ix int) {
+			defer wg.Done()
+			<-start
+			c, err := p.NewClient(ctx, store, k8sClient, namespace)
+			clients[ix], errs[ix] = c, err
+		}(i)
+	}
+	close(start)
+	wg.Wait()
+
+	for i := 0; i < goroutines; i++ {
+		tassert.NoError(t, errs[i], "NewClient error: %w", errs[i])
+		msc := clients[i].(*SecretsClient)
+		got, err := msc.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secret.Id, Property: "k"})
+		tassert.NoError(t, err)
+		tassert.Equal(t, []byte("v"), got)
+	}
+
+	tassert.Equal(t, int32(goroutines), atomic.LoadInt32(&tokenGetter.calls), fmt.Sprintf("TokenGetter.GetToken called %d times, want %d", factoryCalls, goroutines))
+	tassert.Equal(t, int32(1), atomic.LoadInt32(&factoryCalls), fmt.Sprintf("NewMysteryboxClient called %d times, want 1", factoryCalls))
+}
+
+// helpers
+
+func newNebiusMysteryboxSecretStoreWithAuthTokenKey(apiDomain, namespace, tokenSecretName, tokenSecretKey string) esv1.GenericStore {
+	return &esv1.SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: namespace,
+		},
+		Spec: esv1.SecretStoreSpec{
+			Provider: &esv1.SecretStoreProvider{
+				NebiusMysterybox: &esv1.NebiusMysteryboxProvider{
+					APIDomain: apiDomain,
+					Auth: esv1.NebiusAuth{
+						Token: esmeta.SecretKeySelector{
+							Name: tokenSecretName,
+							Key:  tokenSecretKey,
+						},
+					},
+				},
+			},
+		},
+	}
+}
+
+func newNebiusMysteryboxSecretStoreWithServiceAccountCreds(apiDomain, namespace, keySecretName, keySecretKey string) esv1.GenericStore {
+	return &esv1.SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: namespace,
+		},
+		Spec: esv1.SecretStoreSpec{
+			Provider: &esv1.SecretStoreProvider{
+				NebiusMysterybox: &esv1.NebiusMysteryboxProvider{
+					APIDomain: apiDomain,
+					Auth: esv1.NebiusAuth{
+						ServiceAccountCreds: esmeta.SecretKeySelector{
+							Name: keySecretName,
+							Key:  keySecretKey,
+						},
+					},
+				},
+			},
+		},
+	}
+}
+
+func createK8sSecret(ctx context.Context, t *testing.T, k8sClient k8sclient.Client, namespace, secretName, secretKey string, secretValue []byte) {
+	err := k8sClient.Create(ctx, &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: namespace,
+			Name:      secretName,
+		},
+		Data: map[string][]byte{secretKey: secretValue},
+	})
+	tassert.NoError(t, err)
+}
+
+func unmarshalStringMap(t *testing.T, data []byte) map[string]string {
+	stringMap := make(map[string]string)
+	err := json.Unmarshal(data, &stringMap)
+	tassert.NoError(t, err)
+	return stringMap
+}
+
+func newProvider(t *testing.T, newMysteryboxClientFunc NewMysteryboxClient, tokenGetter TokenGetter) *Provider {
+	t.Helper()
+	cache, err := lru.New(10)
+	tassert.NoError(t, err)
+	return &Provider{
+		Logger:                 logger,
+		NewMysteryboxClient:    newMysteryboxClientFunc,
+		mysteryboxClientsCache: cache,
+		TokenGetter:            tokenGetter,
+	}
+}
+
+type faketokenGetter struct {
+	calls        int32
+	returnError  bool
+	gotDomain    string
+	gotCreds     string
+	gotCACert    []byte
+	tokenToIssue string
+
+	mu sync.Mutex
+}
+
+func (f *faketokenGetter) GetToken(_ context.Context, apiDomain, subjectCreds string, caCert []byte) (string, error) {
+	atomic.AddInt32(&f.calls, 1)
+
+	f.mu.Lock()
+	defer f.mu.Unlock()
+
+	f.gotDomain = apiDomain
+	f.gotCreds = subjectCreds
+	f.gotCACert = caCert
+	if f.returnError {
+		return "", errors.New("internal error")
+	}
+
+	return f.tokenToIssue, nil
+}
+
+func setCacheSizeWorkaround(t *testing.T, size int, p *Provider) {
+	t.Helper()
+	err := p.initMysteryboxClientsCache()
+	tassert.NoError(t, err)
+	p.mysteryboxClientsCache.Resize(size)
+}
+
+func settokenGetterWorkaround(tokenGetter TokenGetter, p *Provider) {
+	p.tokenInitMutex.Lock()
+	defer p.tokenInitMutex.Unlock()
+
+	p.TokenGetter = tokenGetter
+}
+
+type ClientData struct {
+	domain string
+	ca     []byte
+}

+ 172 - 0
providers/v1/nebius/mysterybox/secrets_client.go

@@ -0,0 +1,172 @@
+// /*
+// 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 mysterybox
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
+	corev1 "k8s.io/api/core/v1"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	"github.com/external-secrets/external-secrets/providers/v1/nebius/common/sdk/mysterybox"
+	"github.com/external-secrets/external-secrets/runtime/constants"
+	"github.com/external-secrets/external-secrets/runtime/metrics"
+)
+
+const (
+	errNotImplemented             = "not implemented"
+	errJSONMarshal                = "failed to marshal JSON"
+	errSecretNotFound             = "secret %q not found: %w"
+	errSecretByKeyNotFound        = "key %q not found in secret %q: %w"
+	errSecretVersionByKeyNotFound = "version %q of secret %q not found by key %q: %w"
+	errSecretVersionNotFound      = "version %q of secret %q not found: %w"
+)
+
+// SecretsClient provides methods to interact with secrets in the Mysterybox service.
+// It wraps a mysterybox.Client instance and uses a token for authentication.
+type SecretsClient struct {
+	mysteryboxClient mysterybox.Client
+	token            string
+}
+
+// GetSecret retrieves the value of a secret from Mysterybox based on the provided reference.
+func (c *SecretsClient) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
+	secretKey := ref.Property
+	if secretKey == "" {
+		payload, err := c.mysteryboxClient.GetSecret(ctx, c.token, ref.Key, ref.Version)
+		metrics.ObserveAPICall(constants.ProviderNebiusMysterybox, constants.CallNebiusMysteryboxGetSecret, err)
+		if err != nil {
+			return nil, handleGetSecretError(err, ref)
+		}
+		keyToValue := make(map[string]any, len(payload.Entries))
+		for _, entry := range payload.Entries {
+			value := getValue(&entry)
+			keyToValue[entry.Key] = value
+		}
+		out, err := json.Marshal(keyToValue)
+		if err != nil {
+			return nil, errors.New(errJSONMarshal)
+		}
+		return out, nil
+	}
+	payloadEntry, err := c.mysteryboxClient.GetSecretByKey(ctx, c.token, ref.Key, ref.Version, secretKey)
+	metrics.ObserveAPICall(constants.ProviderNebiusMysterybox, constants.CallNebiusMysteryboxGetSecretByKey, err)
+	if err != nil {
+		return nil, handleGetSecretByKeyError(err, ref)
+	}
+	return getValueAsBinary(&payloadEntry.Entry), nil
+}
+
+// GetSecretMap retrieves a map of secret key-value pairs from Mysterybox using the provided reference.
+func (c *SecretsClient) GetSecretMap(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+	payload, err := c.mysteryboxClient.GetSecret(ctx, c.token, ref.Key, ref.Version)
+	metrics.ObserveAPICall(constants.ProviderNebiusMysterybox, constants.CallNebiusMysteryboxGetSecret, err)
+	if err != nil {
+		return nil, handleGetSecretError(err, ref)
+	}
+	secretMap := make(map[string][]byte, len(payload.Entries))
+	for _, entry := range payload.Entries {
+		value := getValueAsBinary(&entry)
+		secretMap[entry.Key] = value
+	}
+	return secretMap, nil
+}
+
+// DeleteSecret not implemented for Nebius Mysterybox provider.
+func (c *SecretsClient) DeleteSecret(_ context.Context, _ esv1.PushSecretRemoteRef) error {
+	return errors.New(errNotImplemented)
+}
+
+// SecretExists not implemented for Nebius Mysterybox provider.
+func (c *SecretsClient) SecretExists(_ context.Context, _ esv1.PushSecretRemoteRef) (bool, error) {
+	return false, errors.New(errNotImplemented)
+}
+
+// PushSecret not implemented for Nebius Mysterybox provider.
+func (c *SecretsClient) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1.PushSecretData) error {
+	return errors.New(errNotImplemented)
+}
+
+// Validate checks the configuration of the SecretsClient and returns its validation result.
+func (c *SecretsClient) Validate() (esv1.ValidationResult, error) {
+	return esv1.ValidationResultReady, nil
+}
+
+// GetAllSecrets not implemented for Nebius Mysterybox provider.
+func (c *SecretsClient) GetAllSecrets(_ context.Context, _ esv1.ExternalSecretFind) (map[string][]byte, error) {
+	return nil, errors.New(errNotImplemented)
+}
+
+// Close cleans up resources when the provider is done being used.
+func (c *SecretsClient) Close(_ context.Context) error {
+	return nil
+}
+
+func getValueAsBinary(entry *mysterybox.Entry) []byte {
+	if entry.BinaryValue != nil {
+		return entry.BinaryValue
+	}
+	return []byte(entry.StringValue)
+}
+
+func getValue(entry *mysterybox.Entry) any {
+	if entry.BinaryValue != nil {
+		return entry.BinaryValue
+	}
+	return entry.StringValue
+}
+
+func handleGetSecretError(err error, ref esv1.ExternalSecretDataRemoteRef) error {
+	if err == nil {
+		return nil
+	}
+	st, ok := status.FromError(err)
+	if !ok {
+		return err // not a grpc error
+	}
+	if st.Code() == codes.NotFound {
+		if ref.Version != "" {
+			return fmt.Errorf(errSecretVersionNotFound, ref.Version, ref.Key, esv1.NoSecretErr)
+		}
+		return fmt.Errorf(errSecretNotFound, ref.Key, esv1.NoSecretErr)
+	}
+	return MapGrpcErrors("get secret", err)
+}
+
+func handleGetSecretByKeyError(err error, ref esv1.ExternalSecretDataRemoteRef) error {
+	if err == nil {
+		return nil
+	}
+	st, ok := status.FromError(err)
+	if !ok {
+		return err // not a grpc error
+	}
+	if st.Code() == codes.NotFound {
+		if ref.Version != "" {
+			return fmt.Errorf(errSecretVersionByKeyNotFound, ref.Version, ref.Key, ref.Property, esv1.NoSecretErr)
+		}
+		return fmt.Errorf(errSecretByKeyNotFound, ref.Property, ref.Key, esv1.NoSecretErr)
+	}
+	return MapGrpcErrors("get secret by key", err)
+}
+
+var _ esv1.SecretsClient = &SecretsClient{}

+ 145 - 0
providers/v1/nebius/mysterybox/token_getter.go

@@ -0,0 +1,145 @@
+// /*
+// 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 mysterybox
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+
+	lru "github.com/hashicorp/golang-lru"
+	"github.com/nebius/gosdk/auth"
+	"golang.org/x/sync/singleflight"
+	"k8s.io/utils/clock"
+
+	"github.com/external-secrets/external-secrets/providers/v1/nebius/common/sdk/iam"
+)
+
+const (
+	errInvalidSubjectCreds = "invalid subject credentials: malformed JSON"
+)
+
+// TokenGetter is an interface for generating and retrieving authentication tokens.
+type TokenGetter interface {
+	GetToken(ctx context.Context, apiDomain, subjectCreds string, caCert []byte) (string, error)
+}
+
+type tokenCacheKey struct {
+	APIDomain        string
+	PublicKeyID      string
+	ServiceAccountID string
+	PrivateKeyHash   string
+}
+
+func (k *tokenCacheKey) String() string {
+	return k.APIDomain + "|" + k.PublicKeyID + "|" + k.ServiceAccountID + "|" + k.PrivateKeyHash
+}
+
+// CachedTokenGetter is responsible for managing Nebius IAM token caching and token exchange processes.
+type CachedTokenGetter struct {
+	TokenExchanger iam.TokenExchanger
+	Clock          clock.Clock
+	tokenCache     *lru.Cache
+	sf             singleflight.Group
+}
+
+// NewCachedTokenGetter initializes a CachedTokenGetter with the specified cache size, token exchanger, and clock.
+// Returns a CachedTokenGetter instance and an error if LRU cache creation fails.
+func NewCachedTokenGetter(cacheSize int, tokenExchanger iam.TokenExchanger, clock clock.Clock) (*CachedTokenGetter, error) {
+	cache, err := lru.New(cacheSize)
+	if err != nil {
+		return nil, err
+	}
+	return &CachedTokenGetter{
+		tokenCache:     cache,
+		TokenExchanger: tokenExchanger,
+		Clock:          clock,
+	}, nil
+}
+
+func isTokenExpired(token *iam.Token, clk clock.Clock) bool {
+	now := clk.Now()
+	if token.ExpiresAt.After(now) {
+		total := token.ExpiresAt.Sub(token.IssuedAt)
+		remaining := token.ExpiresAt.Sub(now)
+		if remaining > total/10 {
+			return false
+		}
+	}
+	return true
+}
+
+// GetToken retrieves an IAM token for the given API domain and subject credentials, using a cache to optimize requests.
+// It exchanges credentials for a new token if no valid cached token exists or the cached token is nearing expiration.
+func (c *CachedTokenGetter) GetToken(ctx context.Context, apiDomain, subjectCreds string, caCert []byte) (string, error) {
+	byteCreds := []byte(subjectCreds)
+	cacheKey, err := buildTokenCacheKey(byteCreds, apiDomain)
+
+	if err != nil {
+		return "", err
+	}
+
+	value, ok := c.tokenCache.Get(*cacheKey)
+	if ok {
+		token := value.(*iam.Token)
+		tokenExpired := isTokenExpired(token, c.Clock)
+		if !tokenExpired {
+			return token.Token, nil
+		}
+	}
+
+	tokenCacheKeyString := cacheKey.String()
+
+	token, err, _ := c.sf.Do(tokenCacheKeyString, func() (any, error) {
+		if v, ok := c.tokenCache.Get(*cacheKey); ok {
+			tok := v.(*iam.Token)
+			if !isTokenExpired(tok, c.Clock) {
+				return tok.Token, nil
+			}
+		}
+
+		newToken, err := c.TokenExchanger.ExchangeIamToken(ctx, apiDomain, subjectCreds, c.Clock.Now(), caCert)
+		if err != nil {
+			return "", fmt.Errorf("could not exchange creds to iam token: %w", MapGrpcErrors("create token", err))
+		}
+
+		c.tokenCache.Add(*cacheKey, newToken)
+		return newToken.Token, nil
+	})
+
+	if err != nil {
+		return "", err
+	}
+	return token.(string), nil
+}
+
+func buildTokenCacheKey(subjectCreds []byte, apiDomain string) (*tokenCacheKey, error) {
+	parsedSubjectCreds := &auth.ServiceAccountCredentials{}
+	err := json.Unmarshal(subjectCreds, parsedSubjectCreds)
+	if err != nil {
+		return nil, errors.New(errInvalidSubjectCreds)
+	}
+	return &tokenCacheKey{
+		APIDomain:        apiDomain,
+		PublicKeyID:      parsedSubjectCreds.SubjectCredentials.KeyID,
+		ServiceAccountID: parsedSubjectCreds.SubjectCredentials.Subject,
+		PrivateKeyHash:   HashBytes([]byte(parsedSubjectCreds.SubjectCredentials.PrivateKey)),
+	}, nil
+}
+
+var _ TokenGetter = &CachedTokenGetter{}

+ 299 - 0
providers/v1/nebius/mysterybox/token_getter_test.go

@@ -0,0 +1,299 @@
+// /*
+// 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 mysterybox
+
+import (
+	"context"
+	"encoding/json"
+	"strconv"
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/nebius/gosdk/auth"
+	tassert "github.com/stretchr/testify/assert"
+	trequire "github.com/stretchr/testify/require"
+	clocktesting "k8s.io/utils/clock/testing"
+
+	"github.com/external-secrets/external-secrets/providers/v1/nebius/common/sdk/iam"
+)
+
+type tokenTestEnv struct {
+	ctx                context.Context
+	clk                *clocktesting.FakeClock
+	fakeTokenExchanger *iam.FakeTokenExchanger
+	cachedTokenGetter  *CachedTokenGetter
+}
+
+func newTokenTestEnv(t *testing.T) *tokenTestEnv {
+	t.Helper()
+	clk := clocktesting.NewFakeClock(time.Unix(0, 0))
+	ex := &iam.FakeTokenExchanger{}
+	svc, err := NewCachedTokenGetter(10, ex, clk)
+	trequire.NoError(t, err)
+	return &tokenTestEnv{ctx: context.Background(), clk: clk, fakeTokenExchanger: ex, cachedTokenGetter: svc}
+}
+
+func buildSubjectCredsJSON(t *testing.T, privateKey, keyID, subject string) string {
+	t.Helper()
+	b, err := json.Marshal(&auth.ServiceAccountCredentials{
+		SubjectCredentials: auth.SubjectCredentials{
+			PrivateKey: privateKey,
+			KeyID:      keyID,
+			Subject:    subject,
+			Issuer:     subject,
+		},
+	})
+	trequire.NoError(t, err)
+	return string(b)
+}
+
+func TestGetToken_CachesUntilTenPercentLeft(t *testing.T) {
+	t.Parallel()
+	env := newTokenTestEnv(t)
+
+	ctx := env.ctx
+	creds := buildSubjectCredsJSON(t, "priv-A", "kid-A", "sa-A")
+
+	token1, err := env.cachedTokenGetter.GetToken(ctx, "api.example", creds, nil)
+	tassert.NoError(t, err)
+	tassert.Equal(t, "token-1", token1)
+	tassert.Equal(t, int64(1), env.fakeTokenExchanger.Calls.Load())
+
+	// add 5 seconds (remaining > 10%)
+	addSecondsToClock(env.clk, 5)
+	token2, err := env.cachedTokenGetter.GetToken(ctx, "api.example", creds, nil)
+	tassert.NoError(t, err)
+	tassert.Equal(t, token1, token2)
+	tassert.Equal(t, int64(1), env.fakeTokenExchanger.Calls.Load())
+
+	// after >90% elapsed -> should refresh
+	addSecondsToClock(env.clk, 91) // total 96s
+	token3, err := env.cachedTokenGetter.GetToken(ctx, "api.example", creds, nil)
+	tassert.NoError(t, err)
+	tassert.NotEqual(t, token1, token3)
+	tassert.Equal(t, int64(2), env.fakeTokenExchanger.Calls.Load())
+}
+
+func TestGetToken_SeparateCacheEntriesPerSubjectCreds(t *testing.T) {
+	t.Parallel()
+	env := newTokenTestEnv(t)
+
+	ctx := env.ctx
+	credsA := buildSubjectCredsJSON(t, "priv-A", "kid-A", "sa-A")
+	credsB := buildSubjectCredsJSON(t, "priv-B", "kid-B", "sa-B")
+
+	tokenA1, err := env.cachedTokenGetter.GetToken(ctx, "api.example", credsA, nil)
+	tassert.NoError(t, err)
+	tassert.Equal(t, "token-1", tokenA1)
+
+	tokenB1, err := env.cachedTokenGetter.GetToken(ctx, "api.example", credsB, nil)
+	tassert.NoError(t, err)
+	tassert.Equal(t, "token-2", tokenB1)
+
+	tassert.Equal(t, int64(2), env.fakeTokenExchanger.Calls.Load())
+
+	// check token cached
+	addSecondsToClock(env.clk, 1)
+	tokA2, err := env.cachedTokenGetter.GetToken(ctx, "api.example", credsA, nil)
+	tassert.NoError(t, err)
+	tassert.Equal(t, tokenA1, tokA2)
+	tassert.Equal(t, int64(2), env.fakeTokenExchanger.Calls.Load())
+}
+
+func TestGetToken_InvalidSubjectCreds_ReturnsError(t *testing.T) {
+	t.Parallel()
+	env := newTokenTestEnv(t)
+
+	_, err := env.cachedTokenGetter.GetToken(env.ctx, "api.example", "not a json", nil)
+	tassert.Error(t, err)
+}
+
+func addSecondsToClock(clk *clocktesting.FakeClock, second time.Duration) {
+	clk.SetTime(clk.Now().Add(second * time.Second))
+}
+
+func TestGetToken_SeparateCacheEntriesPerApiDomain(t *testing.T) {
+	t.Parallel()
+	env := newTokenTestEnv(t)
+	ctx := env.ctx
+	creds := buildSubjectCredsJSON(t, "priv-A", "kid-A", "sa-A")
+
+	tokA1, err := env.cachedTokenGetter.GetToken(ctx, "api.one", creds, nil)
+	tassert.NoError(t, err)
+	tassert.Equal(t, "token-1", tokA1)
+
+	tokB1, err := env.cachedTokenGetter.GetToken(ctx, "api.two", creds, nil)
+	tassert.NoError(t, err)
+	tassert.Equal(t, "token-2", tokB1)
+	tassert.NotEqual(t, tokA1, tokB1)
+	tassert.Equal(t, int64(2), env.fakeTokenExchanger.Calls.Load())
+
+	tokA2, err := env.cachedTokenGetter.GetToken(ctx, "api.one", creds, nil)
+	tassert.NoError(t, err)
+	tassert.Equal(t, tokA1, tokA2)
+	tassert.Equal(t, int64(2), env.fakeTokenExchanger.Calls.Load())
+}
+
+func TestGetToken_LRUEviction(t *testing.T) {
+	t.Parallel()
+	clk := clocktesting.NewFakeClock(time.Unix(0, 0))
+	ex := &iam.FakeTokenExchanger{}
+	svc, err := NewCachedTokenGetter(2, ex, clk)
+	tassert.NoError(t, err)
+	ctx := context.Background()
+	creds := buildSubjectCredsJSON(t, "priv-A", "kid-A", "sa-A")
+
+	tok1, err := svc.GetToken(ctx, "api.first", creds, nil)
+	tassert.NoError(t, err)
+	tassert.Equal(t, "token-1", tok1)
+
+	tok2, err := svc.GetToken(ctx, "api.second", creds, nil)
+	tassert.NoError(t, err)
+	tassert.Equal(t, "token-2", tok2)
+	tassert.Equal(t, int64(2), ex.Calls.Load())
+
+	tok1again, err := svc.GetToken(ctx, "api.first", creds, nil)
+	tassert.NoError(t, err)
+	tassert.Equal(t, tok1, tok1again)
+	tassert.Equal(t, int64(2), ex.Calls.Load())
+
+	tok3, err := svc.GetToken(ctx, "api.third", creds, nil)
+	tassert.NoError(t, err)
+	tassert.Equal(t, "token-3", tok3)
+	tassert.Equal(t, int64(3), ex.Calls.Load())
+
+	secondAgain, err := svc.GetToken(ctx, "api.second", creds, nil)
+	tassert.NoError(t, err)
+	tassert.Equal(t, "token-4", secondAgain)
+	tassert.Equal(t, int64(4), ex.Calls.Load())
+}
+
+func TestGetToken_AfterExpiration_Refreshes(t *testing.T) {
+	t.Parallel()
+	env := newTokenTestEnv(t)
+	ctx := env.ctx
+	creds := buildSubjectCredsJSON(t, "priv-A", "kid-A", "sa-A")
+
+	_, err := env.cachedTokenGetter.GetToken(ctx, "api.example", creds, nil)
+	tassert.NoError(t, err)
+	addSecondsToClock(env.clk, 101)
+
+	tok2, err := env.cachedTokenGetter.GetToken(ctx, "api.example", creds, nil)
+	tassert.NoError(t, err)
+	tassert.Equal(t, int64(2), env.fakeTokenExchanger.Calls.Load())
+	tassert.Equal(t, "token-2", tok2)
+}
+
+func TestGetToken_CacheKeyChangesOnKeyRotation(t *testing.T) {
+	t.Parallel()
+	env := newTokenTestEnv(t)
+	ctx := env.ctx
+
+	base := buildSubjectCredsJSON(t, "priv-A", "kid-A", "sa-A")
+	rotatedKeyID := buildSubjectCredsJSON(t, "priv-A", "kid-B", "sa-A")
+	rotatedPriv := buildSubjectCredsJSON(t, "priv-B", "kid-A", "sa-A")
+	rotatedSubject := buildSubjectCredsJSON(t, "priv-A", "kid-A", "sa-B")
+
+	t1, _ := env.cachedTokenGetter.GetToken(ctx, "api", base, nil)
+	t2, _ := env.cachedTokenGetter.GetToken(ctx, "api", rotatedKeyID, nil)
+	t3, _ := env.cachedTokenGetter.GetToken(ctx, "api", rotatedPriv, nil)
+	t4, _ := env.cachedTokenGetter.GetToken(ctx, "api", rotatedSubject, nil)
+
+	tassert.NotEqual(t, t1, t2)
+	tassert.NotEqual(t, t1, t3)
+	tassert.NotEqual(t, t1, t4)
+	tassert.Equal(t, int64(4), env.fakeTokenExchanger.Calls.Load())
+}
+
+func TestGetToken_ExchangerErrorIsWrapped(t *testing.T) {
+	t.Parallel()
+	clk := clocktesting.NewFakeClock(time.Unix(0, 0))
+	svc, err := NewCachedTokenGetter(10, &iam.FakeTokenExchanger{ReturnError: true}, clk)
+	trequire.NoError(t, err)
+
+	_, err = svc.GetToken(context.Background(), "api", buildSubjectCredsJSON(t, "p", "k", "s"), nil)
+	tassert.Error(t, err)
+	tassert.Contains(t, err.Error(), "could not exchange creds to iam token:")
+}
+
+func TestGetToken_Singleflight_DedupesConcurrentSameKey(t *testing.T) {
+	t.Parallel()
+	clk := clocktesting.NewFakeClock(time.Unix(0, 0))
+	ex := &iam.FakeTokenExchanger{}
+	svc, err := NewCachedTokenGetter(10, ex, clk)
+	trequire.NoError(t, err)
+
+	creds := buildSubjectCredsJSON(t, "priv-A", "kid-A", "sa-A")
+
+	const n = 50
+	start := make(chan struct{})
+	var wg sync.WaitGroup
+	wg.Add(n)
+
+	tokens := make([]string, n)
+	errs := make([]error, n)
+
+	for i := 0; i < n; i++ {
+		go func() {
+			defer wg.Done()
+			<-start
+			tok, err := svc.GetToken(context.Background(), "api.example", creds, nil)
+			tokens[i] = tok
+			errs[i] = err
+		}()
+	}
+
+	close(start)
+	wg.Wait()
+
+	for i := 0; i < n; i++ {
+		tassert.NoError(t, errs[i])
+		tassert.Equal(t, tokens[0], tokens[i])
+	}
+	tassert.Equal(t, int64(1), ex.Calls.Load())
+}
+
+func TestGetToken_ConcurrentDifferentKeys_NoRaceAndWorks(t *testing.T) {
+	t.Parallel()
+	clk := clocktesting.NewFakeClock(time.Unix(0, 0))
+	ex := &iam.FakeTokenExchanger{}
+	svc, err := NewCachedTokenGetter(2, ex, clk)
+	trequire.NoError(t, err)
+
+	creds := buildSubjectCredsJSON(t, "priv-A", "kid-A", "sa-A")
+
+	const n = 50
+	start := make(chan struct{})
+	var wg sync.WaitGroup
+	wg.Add(n)
+
+	for i := 0; i < n; i++ {
+		go func() {
+			defer wg.Done()
+			<-start
+			domain := "api." + strconv.Itoa(i%5)
+			_, err := svc.GetToken(context.Background(), domain, creds, nil)
+			tassert.NoError(t, err)
+		}()
+	}
+
+	close(start)
+	wg.Wait()
+
+	tassert.GreaterOrEqual(t, ex.Calls.Load(), int64(1)) // lru cache is small, no guarantees
+}

+ 56 - 0
providers/v1/nebius/mysterybox/utils.go

@@ -0,0 +1,56 @@
+// /*
+// 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 mysterybox
+
+import (
+	"crypto/sha256"
+	"encoding/hex"
+	"fmt"
+
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
+)
+
+// HashBytes calculate a hash of the bytes by sha256 algorithm.
+func HashBytes(b []byte) string {
+	sum := sha256.Sum256(b)
+	return hex.EncodeToString(sum[:])
+}
+
+// MapGrpcErrors maps grpc errors to human-readable errors.
+func MapGrpcErrors(op string, err error) error {
+	st, ok := status.FromError(err)
+	if !ok {
+		return err
+	}
+
+	//nolint:exhaustive // intentionally handle only specific gRPC codes
+	switch st.Code() {
+	case codes.NotFound:
+		return fmt.Errorf("%s: not found: %w", op, err)
+	case codes.Unauthenticated, codes.PermissionDenied:
+		return fmt.Errorf("%s: auth error: %w", op, err)
+	case codes.Unavailable:
+		return fmt.Errorf("%s: service unavailable: %w", op, err)
+	case codes.DeadlineExceeded:
+		return fmt.Errorf("%s: deadline exceeded: %w", op, err)
+	case codes.Internal:
+		return fmt.Errorf("%s: internal error: %w", op, err)
+	default:
+		return err
+	}
+}

+ 198 - 0
providers/v1/nebius/mysterybox/validation.go

@@ -0,0 +1,198 @@
+// /*
+// 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 mysterybox
+
+import (
+	"errors"
+	"fmt"
+	"net"
+	"regexp"
+	"strings"
+
+	"golang.org/x/net/idna"
+	"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"
+)
+
+const (
+	errNilStore                                = "found nil store"
+	errMissingStoreSpec                        = "store is missing spec"
+	errMissingProvider                         = "storeSpec is missing provider"
+	errInvalidProvider                         = "invalid provider spec. Missing nebiusmysterybox field in store %s"
+	errMissingAuthOptions                      = "invalid auth configuration: none provided"
+	errInvalidAuthConfig                       = "invalid auth configuration: exactly one must be specified"
+	errInvalidTokenAuthConfig                  = "invalid token auth configuration: no secret key specified"
+	errInvalidSACredsAuthConfig                = "invalid ServiceAccount creds auth configuration: no secret key specified"
+	errFailedToRetrieveToken                   = "failed to retrieve iam token by credentials: %w"
+	errMissingAPIDomain                        = "API domain must be set"
+	errInvalidAPIDomain                        = "API domain is not valid"
+	errInvalidAPIDomainWithError               = errInvalidAPIDomain + ": %w"
+	errInvalidCertificateConfigNoNameSpecified = "invalid certificate configuration: no name specified"
+	errInvalidCertificateConfigNoKeySpecified  = "invalid certificate configuration: no key specified"
+)
+
+var labelRe = regexp.MustCompile(`^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$`)
+var numericRe = regexp.MustCompile(`^\d+$`)
+
+// ValidateStore validates the given Store and its associated properties for correctness and configuration integrity.
+func (p *Provider) ValidateStore(store esv1.GenericStore) (admission.Warnings, error) {
+	provider, err := getNebiusMysteryboxProvider(store)
+	if err != nil {
+		return nil, err
+	}
+
+	err = p.validateAPIDomain(provider.APIDomain)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := validateProviderAuth(provider); err != nil {
+		return nil, err
+	}
+
+	var selectors []*esmeta.SecretKeySelector
+
+	if provider.Auth.Token.Name != "" {
+		selectors = append(selectors, &provider.Auth.Token)
+	}
+	if provider.Auth.ServiceAccountCreds.Name != "" {
+		selectors = append(selectors, &provider.Auth.ServiceAccountCreds)
+	}
+	if provider.CAProvider != nil {
+		err := validateCertificate(&provider.CAProvider.Certificate)
+		if err != nil {
+			return nil, err
+		}
+		selectors = append(selectors, &provider.CAProvider.Certificate)
+	}
+
+	for _, selector := range selectors {
+		if err := esutils.ValidateSecretSelector(store, *selector); err != nil {
+			return nil, err
+		}
+		if err := esutils.ValidateReferentSecretSelector(store, *selector); err != nil {
+			return nil, err
+		}
+	}
+
+	return nil, nil
+}
+
+func getNebiusMysteryboxProvider(store esv1.GenericStore) (*esv1.NebiusMysteryboxProvider, error) {
+	if store == nil {
+		return nil, errors.New(errNilStore)
+	}
+	spc := store.GetSpec()
+	if spc == nil {
+		return nil, errors.New(errMissingStoreSpec)
+	}
+	if spc.Provider == nil {
+		return nil, errors.New(errMissingProvider)
+	}
+	provider := spc.Provider.NebiusMysterybox
+	if provider == nil {
+		return nil, fmt.Errorf(errInvalidProvider, store.GetNamespacedName())
+	}
+	return provider, nil
+}
+
+func validateProviderAuth(provider *esv1.NebiusMysteryboxProvider) error {
+	if provider.Auth.Token.Name == "" && provider.Auth.ServiceAccountCreds.Name == "" {
+		return errors.New(errMissingAuthOptions)
+	}
+	if provider.Auth.Token.Name != "" && provider.Auth.ServiceAccountCreds.Name != "" {
+		return errors.New(errInvalidAuthConfig)
+	}
+	if provider.Auth.Token.Name != "" && provider.Auth.Token.Key == "" {
+		return errors.New(errInvalidTokenAuthConfig)
+	}
+	if provider.Auth.ServiceAccountCreds.Name != "" && provider.Auth.ServiceAccountCreds.Key == "" {
+		return errors.New(errInvalidSACredsAuthConfig)
+	}
+	return nil
+}
+
+func (p *Provider) validateAPIDomain(apiDomain string) error {
+	if strings.TrimSpace(apiDomain) == "" {
+		return errors.New(errMissingAPIDomain)
+	}
+	if strings.Contains(apiDomain, "://") {
+		return fmt.Errorf(errInvalidAPIDomainWithError, errors.New("scheme is not allowed"))
+	}
+
+	var err error
+	host := apiDomain
+	// API Domain is allowed without port, it will be added in sdk in case it's missed
+	if strings.Contains(apiDomain, ":") {
+		host, _, err = net.SplitHostPort(apiDomain)
+		if err != nil {
+			p.Logger.Info("Error parsing apiDomain", "err", err)
+			return fmt.Errorf(errInvalidAPIDomainWithError, err)
+		}
+	}
+
+	err = validateDomainByRFC(host)
+	if err != nil {
+		p.Logger.Info("Error validating apiDomain", "err", err)
+		return fmt.Errorf(errInvalidAPIDomainWithError, err)
+	}
+
+	return nil
+}
+
+func validateDomainByRFC(domain string) error {
+	if strings.HasSuffix(domain, ".") {
+		return errors.New("domain must not end with a dot")
+	}
+	ascii, err := idna.Lookup.ToASCII(domain)
+	if err != nil {
+		return err
+	}
+	if ascii == "" || len(ascii) > 253 {
+		return errors.New("domain length out of range")
+	}
+	labels := strings.Split(ascii, ".")
+	if len(labels) < 2 {
+		return errors.New("domain must contain at least one dot")
+	}
+	for _, l := range labels {
+		if len(l) < 1 || len(l) > 63 {
+			return errors.New("label length out of range")
+		}
+		if !labelRe.MatchString(l) {
+			return errors.New("invalid label: " + l)
+		}
+	}
+	tld := labels[len(labels)-1]
+	if numericRe.MatchString(tld) {
+		return errors.New("numeric TLD is not allowed")
+	}
+	return nil
+}
+
+func validateCertificate(certificate *esmeta.SecretKeySelector) error {
+	if certificate.Name == "" {
+		return errors.New(errInvalidCertificateConfigNoNameSpecified)
+	}
+	if certificate.Key == "" {
+		return errors.New(errInvalidCertificateConfigNoKeySpecified)
+	}
+	return nil
+}

+ 359 - 0
providers/v1/nebius/mysterybox/validation_test.go

@@ -0,0 +1,359 @@
+// /*
+// 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 mysterybox
+
+import (
+	"strings"
+	"testing"
+
+	tassert "github.com/stretchr/testify/assert"
+	pointer "k8s.io/utils/ptr"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+)
+
+const (
+	utilsErrNamespaceNotAllowed = "namespace should either be empty or match the namespace of the SecretStore for a namespaced SecretStore"
+	utilsErrRequireNamespace    = "cluster scope requires namespace"
+	otherNs                     = "otherns"
+)
+
+func TestValidateStore(t *testing.T) {
+	t.Parallel()
+	p := &Provider{}
+
+	mkStore := func(cfg func(*esv1.SecretStore)) esv1.GenericStore {
+		st := &esv1.SecretStore{}
+		st.Namespace = "test-ns"
+		st.Spec.Provider = &esv1.SecretStoreProvider{NebiusMysterybox: &esv1.NebiusMysteryboxProvider{APIDomain: "api.public"}}
+		if cfg != nil {
+			cfg(st)
+		}
+		return st
+	}
+
+	tests := []struct {
+		name    string
+		store   esv1.GenericStore
+		wantErr string
+	}{
+		{
+			name:    "nil store",
+			store:   nil,
+			wantErr: errNilStore,
+		},
+		{
+			name:    "missing provider",
+			store:   mkStore(func(s *esv1.SecretStore) { s.Spec.Provider = nil }),
+			wantErr: errMissingProvider,
+		},
+		{
+			name:    "missing nebius provider",
+			store:   &esv1.SecretStore{Spec: esv1.SecretStoreSpec{Provider: &esv1.SecretStoreProvider{}}},
+			wantErr: "invalid provider spec.",
+		},
+		{
+			name:    "invalid auth: none provided",
+			store:   mkStore(func(s *esv1.SecretStore) {}),
+			wantErr: errMissingAuthOptions,
+		},
+		{
+			name: "invalid auth: both provided",
+			store: mkStore(func(s *esv1.SecretStore) {
+				nm := s.Spec.Provider.NebiusMysterybox
+				nm.Auth.Token = esmeta.SecretKeySelector{Name: "a", Key: "k"}
+				nm.Auth.ServiceAccountCreds = esmeta.SecretKeySelector{Name: "b", Key: "k"}
+			}),
+			wantErr: errInvalidAuthConfig,
+		},
+		{
+			name: "invalid token auth: missing key",
+			store: mkStore(func(s *esv1.SecretStore) {
+				nm := s.Spec.Provider.NebiusMysterybox
+				nm.Auth.Token = esmeta.SecretKeySelector{Name: "tok"}
+			}),
+			wantErr: errInvalidTokenAuthConfig,
+		},
+		{
+			name: "invalid token auth: missing name",
+			store: mkStore(func(s *esv1.SecretStore) {
+				nm := s.Spec.Provider.NebiusMysterybox
+				nm.Auth.Token = esmeta.SecretKeySelector{Key: "key"}
+			}),
+			wantErr: errMissingAuthOptions,
+		},
+		{
+			name: "invalid sa creds auth: missing key",
+			store: mkStore(func(s *esv1.SecretStore) {
+				nm := s.Spec.Provider.NebiusMysterybox
+				nm.Auth.ServiceAccountCreds = esmeta.SecretKeySelector{Name: "creds"}
+			}),
+			wantErr: errInvalidSACredsAuthConfig,
+		},
+		{
+			name: "invalid sa creds auth: missing name",
+			store: mkStore(func(s *esv1.SecretStore) {
+				nm := s.Spec.Provider.NebiusMysterybox
+				nm.Auth.ServiceAccountCreds = esmeta.SecretKeySelector{Key: "key"}
+			}),
+			wantErr: errMissingAuthOptions,
+		},
+		{
+			name: "valid: token auth",
+			store: mkStore(func(s *esv1.SecretStore) {
+				nm := s.Spec.Provider.NebiusMysterybox
+				nm.APIDomain = apiDomain
+				nm.Auth.Token = esmeta.SecretKeySelector{Name: "tok", Key: "k"}
+			}),
+		},
+		{
+			name: "valid: service account creds",
+			store: mkStore(func(s *esv1.SecretStore) {
+				nm := s.Spec.Provider.NebiusMysterybox
+				nm.APIDomain = apiDomain
+				nm.Auth.ServiceAccountCreds = esmeta.SecretKeySelector{Name: "creds", Key: "k"}
+			}),
+		},
+		{
+			name:    "missing apiDomain",
+			store:   mkStore(func(s *esv1.SecretStore) { s.Spec.Provider.NebiusMysterybox.APIDomain = "" }),
+			wantErr: errMissingAPIDomain,
+		},
+		{
+			name: "token selector different namespace (namespaced store)",
+			store: mkStore(func(s *esv1.SecretStore) {
+				ns := otherNs
+				nm := s.Spec.Provider.NebiusMysterybox
+				nm.Auth.Token = esmeta.SecretKeySelector{Name: "tok", Key: "k", Namespace: &ns}
+			}),
+			wantErr: utilsErrNamespaceNotAllowed,
+		},
+		{
+			name: "sa creds selector different namespace (namespaced store)",
+			store: mkStore(func(s *esv1.SecretStore) {
+				ns := otherNs
+				nm := s.Spec.Provider.NebiusMysterybox
+				nm.Auth.Token = esmeta.SecretKeySelector{}
+				nm.Auth.ServiceAccountCreds = esmeta.SecretKeySelector{Name: "creds", Key: "k", Namespace: &ns}
+			}),
+			wantErr: utilsErrNamespaceNotAllowed,
+		},
+		{
+			name: "ca cert specified without secret name",
+			store: mkStore(func(s *esv1.SecretStore) {
+				ns := otherNs
+				nm := s.Spec.Provider.NebiusMysterybox
+				nm.Auth.Token = esmeta.SecretKeySelector{Name: "tok", Key: "k"}
+				nm.CAProvider = &esv1.NebiusCAProvider{Certificate: esmeta.SecretKeySelector{Namespace: &ns}}
+			}),
+			wantErr: errInvalidCertificateConfigNoNameSpecified,
+		},
+		{
+			name: "ca cert specified without secret key",
+			store: mkStore(func(s *esv1.SecretStore) {
+				ns := otherNs
+				nm := s.Spec.Provider.NebiusMysterybox
+				nm.Auth.Token = esmeta.SecretKeySelector{Name: "tok", Key: "k"}
+				nm.CAProvider = &esv1.NebiusCAProvider{Certificate: esmeta.SecretKeySelector{Name: "cacert", Namespace: &ns}}
+			}),
+			wantErr: errInvalidCertificateConfigNoKeySpecified,
+		},
+		{
+			name: "ca cert selector different namespace (namespaced store)",
+			store: mkStore(func(s *esv1.SecretStore) {
+				ns := otherNs
+				nm := s.Spec.Provider.NebiusMysterybox
+				nm.Auth.Token = esmeta.SecretKeySelector{Name: "tok", Key: "k"}
+				nm.CAProvider = &esv1.NebiusCAProvider{Certificate: esmeta.SecretKeySelector{Name: "ca", Key: "tls.crt", Namespace: &ns}}
+			}),
+			wantErr: utilsErrNamespaceNotAllowed,
+		},
+		{
+			name: "matching selector namespace passes",
+			store: mkStore(func(s *esv1.SecretStore) {
+				ns := s.Namespace
+				nm := s.Spec.Provider.NebiusMysterybox
+				nm.Auth.Token = esmeta.SecretKeySelector{Name: "tok", Key: "k", Namespace: &ns}
+			}),
+			wantErr: "",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+			_, err := p.ValidateStore(tt.store)
+			if tt.wantErr == "" {
+				tassert.NoError(t, err, "%s: unexpected error", tt.name)
+				return
+			}
+			tassert.NotNil(t, err, "%s: expected error containing %q, got nil", tt.name, tt.wantErr)
+			if err != nil {
+				tassert.Contains(t, err.Error(), tt.wantErr, "%s: error %q does not contain %q", tt.name, err.Error(), tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestValidateStoreClusterScope(t *testing.T) {
+	t.Parallel()
+	p := &Provider{}
+
+	makeStore := func(cfg func(*esv1.NebiusMysteryboxProvider)) esv1.GenericStore {
+		css := &esv1.ClusterSecretStore{}
+		css.TypeMeta.Kind = esv1.ClusterSecretStoreKind
+		nm := &esv1.NebiusMysteryboxProvider{APIDomain: "api.public"}
+		if cfg != nil {
+			cfg(nm)
+		}
+		css.Spec.Provider = &esv1.SecretStoreProvider{NebiusMysterybox: nm}
+		return css
+	}
+
+	tests := []struct {
+		name    string
+		store   esv1.GenericStore
+		wantErr string
+	}{
+		{
+			name: "cluster: token selector requires namespace",
+			store: makeStore(func(nm *esv1.NebiusMysteryboxProvider) {
+				nm.Auth.Token = esmeta.SecretKeySelector{Name: "tok", Key: "k"}
+			}),
+			wantErr: utilsErrRequireNamespace,
+		},
+		{
+			name: "cluster: namespaced token passes",
+			store: makeStore(func(nm *esv1.NebiusMysteryboxProvider) {
+				nm.Auth.Token = esmeta.SecretKeySelector{Name: "tok", Key: "k", Namespace: pointer.To("ns1")}
+			}),
+			wantErr: "",
+		},
+		{
+			name: "cluster: sa creds selector requires namespace",
+			store: makeStore(func(nm *esv1.NebiusMysteryboxProvider) {
+				nm.Auth.ServiceAccountCreds = esmeta.SecretKeySelector{Name: "tok", Key: "k"}
+			}),
+			wantErr: utilsErrRequireNamespace,
+		},
+		{
+			name: "cluster: namespaced sa creds passes",
+			store: makeStore(func(nm *esv1.NebiusMysteryboxProvider) {
+				nm.Auth.ServiceAccountCreds = esmeta.SecretKeySelector{Name: "tok", Key: "k", Namespace: pointer.To("ns1")}
+			}),
+			wantErr: "",
+		},
+		{
+			name: "cluster: ca cert requires namespace",
+			store: makeStore(func(nm *esv1.NebiusMysteryboxProvider) {
+				nm.Auth.Token = esmeta.SecretKeySelector{Name: "tok", Key: "k", Namespace: pointer.To("ns1")}
+				nm.CAProvider = &esv1.NebiusCAProvider{Certificate: esmeta.SecretKeySelector{Name: "ca", Key: "tls.crt"}}
+			}),
+			wantErr: utilsErrRequireNamespace,
+		},
+		{
+			name: "cluster: namespaced ca cert passes",
+			store: makeStore(func(nm *esv1.NebiusMysteryboxProvider) {
+				nm.Auth.Token = esmeta.SecretKeySelector{Name: "tok", Key: "k", Namespace: pointer.To("ns1")}
+				nm.CAProvider = &esv1.NebiusCAProvider{Certificate: esmeta.SecretKeySelector{Name: "ca", Key: "tls.crt", Namespace: pointer.To("ns1")}}
+			}),
+			wantErr: "",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+			_, err := p.ValidateStore(tt.store)
+			if tt.wantErr == "" {
+				tassert.NoError(t, err, "%s: unexpected error", tt.name)
+				return
+			}
+			if err == nil {
+				tassert.Failf(t, "%s: expected error containing %q, got nil", tt.name, tt.wantErr)
+			} else {
+				tassert.Contains(t, err.Error(), tt.wantErr, "%s: expected error to contain substring", tt.name)
+			}
+		})
+	}
+}
+
+func TestValidateStore_APIDomainCases(t *testing.T) {
+	t.Parallel()
+	p := &Provider{}
+	mkStore := func(domain string) esv1.GenericStore {
+		st := &esv1.SecretStore{}
+		st.Namespace = "test-ns"
+		st.Spec.Provider = &esv1.SecretStoreProvider{NebiusMysterybox: &esv1.NebiusMysteryboxProvider{APIDomain: domain}}
+		nm := st.Spec.Provider.NebiusMysterybox
+		nm.Auth.Token = esmeta.SecretKeySelector{Name: "tok", Key: "k"}
+		return st
+	}
+	cases := []struct {
+		name   string
+		domain string
+		valid  bool
+	}{
+		{name: "simple domain with port", domain: "example.com:443", valid: true},
+		{name: "simple domain without port", domain: "example.com", valid: true},
+		{name: "subdomain", domain: "sub.example.com", valid: true},
+		{name: "hyphen in middle", domain: "a-b.com", valid: true},
+		{name: "uppercase allowed", domain: "EXAMPLE.COM", valid: true},
+
+		{name: "single label not allowed", domain: "com", valid: false},
+		{name: "empty label (double dot)", domain: "a..com", valid: false},
+		{name: "leading dot", domain: ".example.com", valid: false},
+		{name: "trailing dot", domain: "example.com.", valid: false},
+		{name: "label starts with hyphen", domain: "-abc.com", valid: false},
+		{name: "label ends with hyphen", domain: "abc-.com", valid: false},
+		{name: "invalid char underscore", domain: "ab_c.com", valid: false},
+		{name: "invalid char space", domain: "exa mple.com", valid: false},
+		{name: "numeric TLD not allowed", domain: "example.123", valid: false},
+		{name: "ip address not a domain", domain: "127.0.0.1", valid: false},
+	}
+
+	longLabel := strings.Repeat("a", 64) + ".com"
+	cases = append(cases, struct {
+		name   string
+		domain string
+		valid  bool
+	}{name: "label too long", domain: longLabel, valid: false})
+
+	manyLabels := strings.Repeat("a.", 127) + "a"
+	cases = append(cases, struct {
+		name   string
+		domain string
+		valid  bool
+	}{name: "domain too long", domain: manyLabels, valid: false})
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			store := mkStore(tc.domain)
+			_, err := p.ValidateStore(store)
+			if tc.valid {
+				tassert.NoError(t, err, "%s: expected valid, got error", tc.name)
+			} else {
+				tassert.Error(t, err, "%s: expected error for domain %q", tc.name, tc.domain)
+			}
+			if err != nil {
+				tassert.Contains(t, err.Error(), errInvalidAPIDomain, "%s: error should contain invalid api domain", tc.name)
+			}
+		})
+	}
+}

+ 5 - 0
runtime/constants/constants.go

@@ -121,6 +121,11 @@ const (
 	CallOnePasswordSDKFilesRead   = "FilesRead"
 	CallOnePasswordSDKVaultsList  = "VaultsList"
 
+	ProviderNebiusMysterybox           = "Nebius/Mysterybox"
+	CallNebiusMysteryboxGetSecret      = "GetSecret"
+	CallNebiusMysteryboxGetSecretByKey = "GetSecretByKey"
+	CallNebiusMysteryboxAuth           = "Auth"
+
 	StatusError   = "error"
 	StatusSuccess = "success"
 

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

@@ -533,6 +533,22 @@ spec:
           namespace: string
           type: "Secret" # "Secret", "ConfigMap"
         url: "kubernetes.default"
+    nebiusmysterybox:
+      apiDomain: string
+      auth:
+        serviceAccountCredsSecretRef:
+          key: string
+          name: string
+          namespace: string
+        tokenSecretRef:
+          key: string
+          name: string
+          namespace: string
+      caProvider:
+        certSecretRef:
+          key: string
+          name: string
+          namespace: string
     ngrok:
       apiUrl: "https://api.ngrok.com"
       auth:

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

@@ -533,6 +533,22 @@ spec:
           namespace: string
           type: "Secret" # "Secret", "ConfigMap"
         url: "kubernetes.default"
+    nebiusmysterybox:
+      apiDomain: string
+      auth:
+        serviceAccountCredsSecretRef:
+          key: string
+          name: string
+          namespace: string
+        tokenSecretRef:
+          key: string
+          name: string
+          namespace: string
+      caProvider:
+        certSecretRef:
+          key: string
+          name: string
+          namespace: string
     ngrok:
       apiUrl: "https://api.ngrok.com"
       auth: