Просмотр исходного кода

feat: support fetching secrets and certificates by name in Yandex Lockbox & Certificate Manager (#5022)

* Add new meaningOfKey field

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Add an implementation of the get by name feature with meaningOfKey in Yandex Lockbox

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Add stub implementation in ycm provider for local launch

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Fixed the cache issue

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Fix an issue with v1beta

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Remove unnecessary arguments

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Fix an issue with naming

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Fix the issue with the meaningOfKey fork

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Fix the issue with unsupported resourceKeyType

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

* Change fetching structure

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Fix case order in swith statement

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Fix naming

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Fix the issue with enum naming

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Fix comments of the new fields

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

* Add a check for the simultaneous existence of two fields

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Remove duplicates from the code

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Fix the issue with error handling

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Fix the issue with naming

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

* Add new field explanation in docs

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Remove unnecessary chap; add some more comments

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Remove meaningOfKey and add new fields

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Remove meaningOfKey field

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Simplify docs

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Change naming

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

* Add get by name feature in ycm

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Fix the issue with log message

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

* Add the versionID field to the requests

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Add folder specific data

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

* Add base tests for ycm provider

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Added a test to check when folderID is not set in FetchByName

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Remove duplicate in folderValue field

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

* Change folderMap to folderAndNameMap and flatten the error check

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Fix tests naming

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

* Refactor folderAndNameMap structure

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Refactor folderAndName map key and value namings

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

* Add some base tests

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Remove unnecessary check

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Add test when secret is not found by name

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

* Set default folderIds and secretNames

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Refactor tests naming

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

* Add tests for checking different entries when fetching by name option enabled

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Add some more tests

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Remove unnecessary condition

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Add test when folderID isn't set

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Remove duplicate in folderValue field

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Fix the issue with filling folderId and name

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Remove unnecessary method

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Change tests name

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

* Rename folderMap to folderAndNameMap and flatten error checks

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Validate folderId and name before mapping entries

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

* Refactor the structure of folderAndNameMap

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Refactor folderAndName map key and value namings

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Fix codestyle issues

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

* Add about new fields in docs

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Remove duplicate

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Remove duplicates

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

* Fix the issue with prinln

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

* Change fetching configuration

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Remove useless check

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

Fix docs

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

* Fix the lint issue

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

* Fix the lint issue

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

* Rebase onto main

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

* Run make helm.test.update and make test.crds.update

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>

---------

Signed-off-by: asrormirzoev <asrormirzoev@yandex-team.ru>
Co-authored-by: asrormirzoev <asrormirzoev@yandex-team.ru>
Co-authored-by: Gergely Brautigam <skarlso777@gmail.com>
Asror Mirzoev 9 месяцев назад
Родитель
Сommit
9d7cb59caa
28 измененных файлов с 1617 добавлено и 122 удалено
  1. 16 0
      apis/externalsecrets/v1/secretstore_yandex_types.go
  2. 4 0
      apis/externalsecrets/v1/secretstore_yandexcertificatemanager_types.go
  3. 4 0
      apis/externalsecrets/v1/secretstore_yandexlockbox_types.go
  4. 65 0
      apis/externalsecrets/v1/zz_generated.deepcopy.go
  5. 48 0
      config/crds/bases/external-secrets.io_clustersecretstores.yaml
  6. 48 0
      config/crds/bases/external-secrets.io_secretstores.yaml
  7. 38 2
      deploy/charts/external-secrets/tests/__snapshot__/crds_test.yaml.snap
  8. 72 0
      deploy/crds/bundle.yaml
  9. 110 0
      docs/api/spec.md
  10. 8 2
      docs/provider/yandex-certificate-manager.md
  11. 9 3
      docs/provider/yandex-lockbox.md
  12. 25 3
      pkg/provider/yandex/certificatemanager/certificatemanager.go
  13. 462 65
      pkg/provider/yandex/certificatemanager/certificatemanager_test.go
  14. 28 6
      pkg/provider/yandex/certificatemanager/certificatemanagersecretgetter.go
  15. 1 0
      pkg/provider/yandex/certificatemanager/client/client.go
  16. 54 4
      pkg/provider/yandex/certificatemanager/client/fakeclient.go
  17. 22 1
      pkg/provider/yandex/certificatemanager/client/grpcclient.go
  18. 13 5
      pkg/provider/yandex/common/provider.go
  19. 2 2
      pkg/provider/yandex/common/secretgetter.go
  20. 7 5
      pkg/provider/yandex/common/secretsclient.go
  21. 1 0
      pkg/provider/yandex/lockbox/client/client.go
  22. 55 4
      pkg/provider/yandex/lockbox/client/fakeclient.go
  23. 23 0
      pkg/provider/yandex/lockbox/client/grpcclient.go
  24. 25 3
      pkg/provider/yandex/lockbox/lockbox.go
  25. 431 10
      pkg/provider/yandex/lockbox/lockbox_test.go
  26. 30 7
      pkg/provider/yandex/lockbox/lockboxsecretgetter.go
  27. 8 0
      tests/__snapshot__/clustersecretstore-v1.yaml
  28. 8 0
      tests/__snapshot__/secretstore-v1.yaml

+ 16 - 0
apis/externalsecrets/v1/secretstore_yandex_types.go

@@ -27,3 +27,19 @@ type YandexAuth struct {
 type YandexCAProvider struct {
 	Certificate esmeta.SecretKeySelector `json:"certSecretRef,omitempty"`
 }
+
+// ByID configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as secret ID.
+type ByID struct{}
+
+// ByName configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as secret name.
+type ByName struct {
+	// The folder to fetch secrets from
+	FolderID string `json:"folderID"`
+}
+
+// +kubebuilder:validation:MinProperties=1
+// +kubebuilder:validation:MaxProperties=1
+type FetchingPolicy struct {
+	ByID   *ByID   `json:"byID,omitempty"`
+	ByName *ByName `json:"byName,omitempty"`
+}

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

@@ -26,4 +26,8 @@ type YandexCertificateManagerProvider struct {
 	// The provider for the CA bundle to use to validate Yandex.Cloud server certificate.
 	// +optional
 	CAProvider *YandexCAProvider `json:"caProvider,omitempty"`
+
+	// FetchingPolicy configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as certificate ID or certificate name
+	// +optional
+	FetchingPolicy *FetchingPolicy `json:"fetching,omitempty"`
 }

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

@@ -26,4 +26,8 @@ type YandexLockboxProvider struct {
 	// The provider for the CA bundle to use to validate Yandex.Cloud server certificate.
 	// +optional
 	CAProvider *YandexCAProvider `json:"caProvider,omitempty"`
+
+	// FetchingPolicy configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as secret ID or secret name
+	// +optional
+	FetchingPolicy *FetchingPolicy `json:"fetching,omitempty"`
 }

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

@@ -611,6 +611,36 @@ func (in *BitwardenSecretsManagerSecretRef) DeepCopy() *BitwardenSecretsManagerS
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ByID) DeepCopyInto(out *ByID) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ByID.
+func (in *ByID) DeepCopy() *ByID {
+	if in == nil {
+		return nil
+	}
+	out := new(ByID)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ByName) DeepCopyInto(out *ByName) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ByName.
+func (in *ByName) DeepCopy() *ByName {
+	if in == nil {
+		return nil
+	}
+	out := new(ByName)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *CAProvider) DeepCopyInto(out *CAProvider) {
 	*out = *in
@@ -1748,6 +1778,31 @@ func (in *FakeProviderData) DeepCopy() *FakeProviderData {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FetchingPolicy) DeepCopyInto(out *FetchingPolicy) {
+	*out = *in
+	if in.ByID != nil {
+		in, out := &in.ByID, &out.ByID
+		*out = new(ByID)
+		**out = **in
+	}
+	if in.ByName != nil {
+		in, out := &in.ByName, &out.ByName
+		*out = new(ByName)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FetchingPolicy.
+func (in *FetchingPolicy) DeepCopy() *FetchingPolicy {
+	if in == nil {
+		return nil
+	}
+	out := new(FetchingPolicy)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *FindName) DeepCopyInto(out *FindName) {
 	*out = *in
@@ -4088,6 +4143,11 @@ func (in *YandexCertificateManagerProvider) DeepCopyInto(out *YandexCertificateM
 		*out = new(YandexCAProvider)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.FetchingPolicy != nil {
+		in, out := &in.FetchingPolicy, &out.FetchingPolicy
+		*out = new(FetchingPolicy)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new YandexCertificateManagerProvider.
@@ -4109,6 +4169,11 @@ func (in *YandexLockboxProvider) DeepCopyInto(out *YandexLockboxProvider) {
 		*out = new(YandexCAProvider)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.FetchingPolicy != nil {
+		in, out := &in.FetchingPolicy, &out.FetchingPolicy
+		*out = new(FetchingPolicy)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new YandexLockboxProvider.

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

@@ -5228,6 +5228,30 @@ spec:
                                 type: string
                             type: object
                         type: object
+                      fetching:
+                        description: FetchingPolicy configures the provider to interpret
+                          the `data.secretKey.remoteRef.key` field in ExternalSecret
+                          as certificate ID or certificate name
+                        maxProperties: 1
+                        minProperties: 1
+                        properties:
+                          byID:
+                            description: ByID configures the provider to interpret
+                              the `data.secretKey.remoteRef.key` field in ExternalSecret
+                              as secret ID.
+                            type: object
+                          byName:
+                            description: ByName configures the provider to interpret
+                              the `data.secretKey.remoteRef.key` field in ExternalSecret
+                              as secret name.
+                            properties:
+                              folderID:
+                                description: The folder to fetch secrets from
+                                type: string
+                            required:
+                            - folderID
+                            type: object
+                        type: object
                     required:
                     - auth
                     type: object
@@ -5304,6 +5328,30 @@ spec:
                                 type: string
                             type: object
                         type: object
+                      fetching:
+                        description: FetchingPolicy configures the provider to interpret
+                          the `data.secretKey.remoteRef.key` field in ExternalSecret
+                          as secret ID or secret name
+                        maxProperties: 1
+                        minProperties: 1
+                        properties:
+                          byID:
+                            description: ByID configures the provider to interpret
+                              the `data.secretKey.remoteRef.key` field in ExternalSecret
+                              as secret ID.
+                            type: object
+                          byName:
+                            description: ByName configures the provider to interpret
+                              the `data.secretKey.remoteRef.key` field in ExternalSecret
+                              as secret name.
+                            properties:
+                              folderID:
+                                description: The folder to fetch secrets from
+                                type: string
+                            required:
+                            - folderID
+                            type: object
+                        type: object
                     required:
                     - auth
                     type: object

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

@@ -5228,6 +5228,30 @@ spec:
                                 type: string
                             type: object
                         type: object
+                      fetching:
+                        description: FetchingPolicy configures the provider to interpret
+                          the `data.secretKey.remoteRef.key` field in ExternalSecret
+                          as certificate ID or certificate name
+                        maxProperties: 1
+                        minProperties: 1
+                        properties:
+                          byID:
+                            description: ByID configures the provider to interpret
+                              the `data.secretKey.remoteRef.key` field in ExternalSecret
+                              as secret ID.
+                            type: object
+                          byName:
+                            description: ByName configures the provider to interpret
+                              the `data.secretKey.remoteRef.key` field in ExternalSecret
+                              as secret name.
+                            properties:
+                              folderID:
+                                description: The folder to fetch secrets from
+                                type: string
+                            required:
+                            - folderID
+                            type: object
+                        type: object
                     required:
                     - auth
                     type: object
@@ -5304,6 +5328,30 @@ spec:
                                 type: string
                             type: object
                         type: object
+                      fetching:
+                        description: FetchingPolicy configures the provider to interpret
+                          the `data.secretKey.remoteRef.key` field in ExternalSecret
+                          as secret ID or secret name
+                        maxProperties: 1
+                        minProperties: 1
+                        properties:
+                          byID:
+                            description: ByID configures the provider to interpret
+                              the `data.secretKey.remoteRef.key` field in ExternalSecret
+                              as secret ID.
+                            type: object
+                          byName:
+                            description: ByName configures the provider to interpret
+                              the `data.secretKey.remoteRef.key` field in ExternalSecret
+                              as secret name.
+                            properties:
+                              folderID:
+                                description: The folder to fetch secrets from
+                                type: string
+                            required:
+                            - folderID
+                            type: object
+                        type: object
                     required:
                     - auth
                     type: object

+ 38 - 2
deploy/charts/external-secrets/tests/__snapshot__/crds_test.yaml.snap

@@ -4731,7 +4731,7 @@ should match snapshot of default values:
                               description: Yandex.Cloud API endpoint (e.g. 'api.cloud.yandex.net:443')
                               type: string
                             auth:
-                              description: Auth defines the information necessary to authenticate against Yandex Certificate Manager
+                              description: Auth defines the information necessary to authenticate against Yandex.Cloud
                               properties:
                                 authorizedKeySecretRef:
                                   description: The authorized key used for authentication
@@ -4792,6 +4792,24 @@ should match snapshot of default values:
                                       type: string
                                   type: object
                               type: object
+                            fetching:
+                              description: FetchingPolicy configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as certificate ID or certificate name
+                              maxProperties: 1
+                              minProperties: 1
+                              properties:
+                                byID:
+                                  description: ByID configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as secret ID.
+                                  type: object
+                                byName:
+                                  description: ByName configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as secret name.
+                                  properties:
+                                    folderID:
+                                      description: The folder to fetch secrets from
+                                      type: string
+                                  required:
+                                    - folderID
+                                  type: object
+                              type: object
                           required:
                             - auth
                           type: object
@@ -4802,7 +4820,7 @@ should match snapshot of default values:
                               description: Yandex.Cloud API endpoint (e.g. 'api.cloud.yandex.net:443')
                               type: string
                             auth:
-                              description: Auth defines the information necessary to authenticate against Yandex Lockbox
+                              description: Auth defines the information necessary to authenticate against Yandex.Cloud
                               properties:
                                 authorizedKeySecretRef:
                                   description: The authorized key used for authentication
@@ -4863,6 +4881,24 @@ should match snapshot of default values:
                                       type: string
                                   type: object
                               type: object
+                            fetching:
+                              description: FetchingPolicy configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as secret ID or secret name
+                              maxProperties: 1
+                              minProperties: 1
+                              properties:
+                                byID:
+                                  description: ByID configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as secret ID.
+                                  type: object
+                                byName:
+                                  description: ByName configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as secret name.
+                                  properties:
+                                    folderID:
+                                      description: The folder to fetch secrets from
+                                      type: string
+                                  required:
+                                    - folderID
+                                  type: object
+                              type: object
                           required:
                             - auth
                           type: object

+ 72 - 0
deploy/crds/bundle.yaml

@@ -6919,6 +6919,24 @@ spec:
                                   type: string
                               type: object
                           type: object
+                        fetching:
+                          description: FetchingPolicy configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as certificate ID or certificate name
+                          maxProperties: 1
+                          minProperties: 1
+                          properties:
+                            byID:
+                              description: ByID configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as secret ID.
+                              type: object
+                            byName:
+                              description: ByName configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as secret name.
+                              properties:
+                                folderID:
+                                  description: The folder to fetch secrets from
+                                  type: string
+                              required:
+                                - folderID
+                              type: object
+                          type: object
                       required:
                         - auth
                       type: object
@@ -6990,6 +7008,24 @@ spec:
                                   type: string
                               type: object
                           type: object
+                        fetching:
+                          description: FetchingPolicy configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as secret ID or secret name
+                          maxProperties: 1
+                          minProperties: 1
+                          properties:
+                            byID:
+                              description: ByID configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as secret ID.
+                              type: object
+                            byName:
+                              description: ByName configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as secret name.
+                              properties:
+                                folderID:
+                                  description: The folder to fetch secrets from
+                                  type: string
+                              required:
+                                - folderID
+                              type: object
+                          type: object
                       required:
                         - auth
                       type: object
@@ -17860,6 +17896,24 @@ spec:
                                   type: string
                               type: object
                           type: object
+                        fetching:
+                          description: FetchingPolicy configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as certificate ID or certificate name
+                          maxProperties: 1
+                          minProperties: 1
+                          properties:
+                            byID:
+                              description: ByID configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as secret ID.
+                              type: object
+                            byName:
+                              description: ByName configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as secret name.
+                              properties:
+                                folderID:
+                                  description: The folder to fetch secrets from
+                                  type: string
+                              required:
+                                - folderID
+                              type: object
+                          type: object
                       required:
                         - auth
                       type: object
@@ -17931,6 +17985,24 @@ spec:
                                   type: string
                               type: object
                           type: object
+                        fetching:
+                          description: FetchingPolicy configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as secret ID or secret name
+                          maxProperties: 1
+                          minProperties: 1
+                          properties:
+                            byID:
+                              description: ByID configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as secret ID.
+                              type: object
+                            byName:
+                              description: ByName configures the provider to interpret the `data.secretKey.remoteRef.key` field in ExternalSecret as secret name.
+                              properties:
+                                folderID:
+                                  description: The folder to fetch secrets from
+                                  type: string
+                              required:
+                                - folderID
+                              type: object
+                          type: object
                       required:
                         - auth
                       type: object

+ 110 - 0
docs/api/spec.md

@@ -1595,6 +1595,45 @@ External Secrets meta/v1.SecretKeySelector
 </tr>
 </tbody>
 </table>
+<h3 id="external-secrets.io/v1.ByID">ByID
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1.FetchingPolicy">FetchingPolicy</a>)
+</p>
+<p>
+<p>ByID configures the provider to interpret the <code>data.secretKey.remoteRef.key</code> field in ExternalSecret as secret ID.</p>
+</p>
+<h3 id="external-secrets.io/v1.ByName">ByName
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1.FetchingPolicy">FetchingPolicy</a>)
+</p>
+<p>
+<p>ByName configures the provider to interpret the <code>data.secretKey.remoteRef.key</code> field in ExternalSecret as secret name.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>folderID</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<p>The folder to fetch secrets from</p>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1.CAProvider">CAProvider
 </h3>
 <p>
@@ -4803,6 +4842,49 @@ string
 </tr>
 </tbody>
 </table>
+<h3 id="external-secrets.io/v1.FetchingPolicy">FetchingPolicy
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1.YandexCertificateManagerProvider">YandexCertificateManagerProvider</a>, 
+<a href="#external-secrets.io/v1.YandexLockboxProvider">YandexLockboxProvider</a>)
+</p>
+<p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>byID</code></br>
+<em>
+<a href="#external-secrets.io/v1.ByID">
+ByID
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>byName</code></br>
+<em>
+<a href="#external-secrets.io/v1.ByName">
+ByName
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1.FindName">FindName
 </h3>
 <p>
@@ -11215,6 +11297,20 @@ YandexCAProvider
 <p>The provider for the CA bundle to use to validate Yandex.Cloud server certificate.</p>
 </td>
 </tr>
+<tr>
+<td>
+<code>fetching</code></br>
+<em>
+<a href="#external-secrets.io/v1.FetchingPolicy">
+FetchingPolicy
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>FetchingPolicy configures the provider to interpret the <code>data.secretKey.remoteRef.key</code> field in ExternalSecret as certificate ID or certificate name</p>
+</td>
+</tr>
 </tbody>
 </table>
 <h3 id="external-secrets.io/v1.YandexLockboxProvider">YandexLockboxProvider
@@ -11273,6 +11369,20 @@ YandexCAProvider
 <p>The provider for the CA bundle to use to validate Yandex.Cloud server certificate.</p>
 </td>
 </tr>
+<tr>
+<td>
+<code>fetching</code></br>
+<em>
+<a href="#external-secrets.io/v1.FetchingPolicy">
+FetchingPolicy
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>FetchingPolicy configures the provider to interpret the <code>data.secretKey.remoteRef.key</code> field in ExternalSecret as secret ID or secret name</p>
+</td>
+</tr>
 </tbody>
 </table>
 <hr/>

+ 8 - 2
docs/provider/yandex-certificate-manager.md

@@ -37,6 +37,12 @@ spec:
         authorizedKeySecretRef:
           name: yc-auth
           key: authorized-key
+
+    # Optionally, to enable fetching secrets by name:
+    #
+    # fetching: # place "fetching:" on the same level as "auth:"
+    #   byName:
+    #     folderId: ***** # ID of the folder to fetch certificates from
 ```
 
 **NOTE:** In case of a `ClusterSecretStore`, Be sure to provide `namespace` in all `authorizedKeySecretRef` with the namespace where the secret resides.
@@ -76,11 +82,11 @@ spec:
   data:
     - secretKey: tls.crt # the target k8s secret key
       remoteRef:
-        key: ***** # the certificate ID
+        key: ***** # either ID or name of the certificate, depending on fetching policy byID / byName
         property: chain
     - secretKey: tls.key # the target k8s secret key
       remoteRef:
-        key: ***** # the certificate ID
+        key: ***** # either ID or name of the certificate, depending on fetching policy byID / byName
         property: privateKey
 ```
 The following property values are possible:

+ 9 - 3
docs/provider/yandex-lockbox.md

@@ -37,6 +37,12 @@ spec:
         authorizedKeySecretRef:
           name: yc-auth
           key: authorized-key
+
+    # Optionally, to enable fetching secrets by name:
+    #
+    # fetching: # place "fetching:" on the same level as "auth:"
+    #   byName:
+    #     folderId: ***** # ID of the folder to fetch secrets from
 ```
 
 **NOTE:** In case of a `ClusterSecretStore`, Be sure to provide `namespace` in all `authorizedKeySecretRef` with the namespace where the secret resides.
@@ -77,11 +83,11 @@ spec:
   data:
   - secretKey: password # the target k8s secret key
     remoteRef:
-      key: ***** # ID of lockbox-secret
+      key: ***** # either ID or name of the secret, depending on fetching policy byID / byName
       property: password # (optional) payload entry key of lockbox-secret
 ```
 
 The operator will fetch the Yandex Lockbox secret and inject it as a `Kind=Secret`
 ```yaml
-kubectl get secret k8s-secret -n <namespace> | -o jsonpath='{.data.password}' | base64 -d
-```
+kubectl get secret k8s-secret -n <namespace> -o jsonpath='{.data.password}' | base64 -d
+```

+ 25 - 3
pkg/provider/yandex/certificatemanager/certificatemanager.go

@@ -48,10 +48,32 @@ func adaptInput(store esv1.GenericStore) (*common.SecretsClientInput, error) {
 		caCertificate = &storeSpecYandexCertificateManager.CAProvider.Certificate
 	}
 
+	var resourceKeyType common.ResourceKeyType
+	var folderID string
+	policy := storeSpecYandexCertificateManager.FetchingPolicy
+	if policy != nil {
+		switch {
+		case policy.ByName != nil:
+			if policy.ByName.FolderID == "" {
+				return nil, errors.New("folderID is required when fetching policy is 'byName'")
+			}
+			resourceKeyType = common.ResourceKeyTypeName
+			folderID = policy.ByName.FolderID
+
+		case policy.ByID != nil:
+			resourceKeyType = common.ResourceKeyTypeId
+
+		default:
+			return nil, errors.New("invalid Yandex Certificate Manager SecretStore: requires either 'byName' or 'byID' policy")
+		}
+	}
+
 	return &common.SecretsClientInput{
-		APIEndpoint:   storeSpecYandexCertificateManager.APIEndpoint,
-		AuthorizedKey: authorizedKey,
-		CACertificate: caCertificate,
+		APIEndpoint:     storeSpecYandexCertificateManager.APIEndpoint,
+		AuthorizedKey:   authorizedKey,
+		CACertificate:   caCertificate,
+		ResourceKeyType: resourceKeyType,
+		FolderID:        folderID,
 	}, nil
 }
 

+ 462 - 65
pkg/provider/yandex/certificatemanager/certificatemanager_test.go

@@ -42,6 +42,7 @@ const (
 	errMissingKey                    = "invalid Yandex Certificate Manager SecretStore resource: missing AuthorizedKey Name"
 	errSecretPayloadPermissionDenied = "unable to request certificate content to get secret: permission denied"
 	errSecretPayloadNotFound         = "unable to request certificate content to get secret: certificate not found"
+	errSecretPayloadVersionNotFound  = "unable to request certificate content to get secret: version not found"
 )
 
 func TestNewClient(t *testing.T) {
@@ -107,10 +108,12 @@ func TestGetSecretWithoutProperty(t *testing.T) {
 	certificate1 := uuid.NewString()
 	certificate2 := uuid.NewString()
 	privateKey := uuid.NewString()
-	certificateID, _ := fakeCertificateManagerServer.CreateCertificate(authorizedKey, &certificatemanager.GetCertificateContentResponse{
-		CertificateChain: []string{certificate1, certificate2},
-		PrivateKey:       privateKey,
-	})
+	certificateID, _ := fakeCertificateManagerServer.CreateCertificate(authorizedKey,
+		"folderId", "certificateName",
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{certificate1, certificate2},
+			PrivateKey:       privateKey,
+		})
 
 	k8sClient := clientfake.NewClientBuilder().Build()
 	const authorizedKeySecretName = "authorizedKeySecretName"
@@ -142,10 +145,12 @@ func TestGetSecretWithProperty(t *testing.T) {
 	certificate1 := uuid.NewString()
 	certificate2 := uuid.NewString()
 	privateKey := uuid.NewString()
-	certificateID, _ := fakeCertificateManagerServer.CreateCertificate(authorizedKey, &certificatemanager.GetCertificateContentResponse{
-		CertificateChain: []string{certificate1, certificate2},
-		PrivateKey:       privateKey,
-	})
+	certificateID, _ := fakeCertificateManagerServer.CreateCertificate(authorizedKey,
+		"folderId", "certificateName",
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{certificate1, certificate2},
+			PrivateKey:       privateKey,
+		})
 
 	k8sClient := clientfake.NewClientBuilder().Build()
 	const authorizedKeySecretName = "authorizedKeySecretName"
@@ -193,10 +198,12 @@ func TestGetSecretByVersionID(t *testing.T) {
 	oldCertificate1 := uuid.NewString()
 	oldCertificate2 := uuid.NewString()
 	oldPrivateKey := uuid.NewString()
-	certificateID, oldVersionID := fakeCertificateManagerServer.CreateCertificate(authorizedKey, &certificatemanager.GetCertificateContentResponse{
-		CertificateChain: []string{oldCertificate1, oldCertificate2},
-		PrivateKey:       oldPrivateKey,
-	})
+	certificateID, oldVersionID := fakeCertificateManagerServer.CreateCertificate(authorizedKey,
+		"folderId", "certificateName",
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{oldCertificate1, oldCertificate2},
+			PrivateKey:       oldPrivateKey,
+		})
 
 	k8sClient := clientfake.NewClientBuilder().Build()
 	const authorizedKeySecretName = "authorizedKeySecretName"
@@ -220,10 +227,11 @@ func TestGetSecretByVersionID(t *testing.T) {
 	newCertificate1 := uuid.NewString()
 	newCertificate2 := uuid.NewString()
 	newPrivateKey := uuid.NewString()
-	newVersionID := fakeCertificateManagerServer.AddVersion(certificateID, &certificatemanager.GetCertificateContentResponse{
-		CertificateChain: []string{newCertificate1, newCertificate2},
-		PrivateKey:       newPrivateKey,
-	})
+	newVersionID := fakeCertificateManagerServer.AddVersion(certificateID,
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{newCertificate1, newCertificate2},
+			PrivateKey:       newPrivateKey,
+		})
 
 	data, err = secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: certificateID, Version: oldVersionID})
 	tassert.Nil(t, err)
@@ -250,10 +258,12 @@ func TestGetSecretUnauthorized(t *testing.T) {
 
 	fakeClock := clock.NewFakeClock()
 	fakeCertificateManagerServer := client.NewFakeCertificateManagerServer(fakeClock, time.Hour)
-	certificateID, _ := fakeCertificateManagerServer.CreateCertificate(authorizedKeyA, &certificatemanager.GetCertificateContentResponse{
-		CertificateChain: []string{uuid.NewString()},
-		PrivateKey:       uuid.NewString(),
-	})
+	certificateID, _ := fakeCertificateManagerServer.CreateCertificate(authorizedKeyA,
+		"folderId", "certificateName",
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{uuid.NewString()},
+			PrivateKey:       uuid.NewString(),
+		})
 
 	k8sClient := clientfake.NewClientBuilder().Build()
 	const authorizedKeySecretName = "authorizedKeySecretName"
@@ -290,12 +300,14 @@ func TestGetSecretNotFound(t *testing.T) {
 	_, err = secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: "no-secret-with-this-id"})
 	tassert.EqualError(t, err, errSecretPayloadNotFound)
 
-	certificateID, _ := fakeCertificateManagerServer.CreateCertificate(authorizedKey, &certificatemanager.GetCertificateContentResponse{
-		CertificateChain: []string{uuid.NewString()},
-		PrivateKey:       uuid.NewString(),
-	})
+	certificateID, _ := fakeCertificateManagerServer.CreateCertificate(authorizedKey,
+		"folderId", "certificateName",
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{uuid.NewString()},
+			PrivateKey:       uuid.NewString(),
+		})
 	_, err = secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: certificateID, Version: "no-version-with-this-id"})
-	tassert.EqualError(t, err, "unable to request certificate content to get secret: version not found")
+	tassert.EqualError(t, err, errSecretPayloadVersionNotFound)
 }
 
 func TestGetSecretWithTwoNamespaces(t *testing.T) {
@@ -309,16 +321,20 @@ func TestGetSecretWithTwoNamespaces(t *testing.T) {
 	fakeCertificateManagerServer := client.NewFakeCertificateManagerServer(fakeClock, time.Hour)
 	certificate1 := uuid.NewString()
 	privateKey1 := uuid.NewString()
-	certificateID1, _ := fakeCertificateManagerServer.CreateCertificate(authorizedKey1, &certificatemanager.GetCertificateContentResponse{
-		CertificateChain: []string{certificate1},
-		PrivateKey:       privateKey1,
-	})
+	certificateID1, _ := fakeCertificateManagerServer.CreateCertificate(authorizedKey1,
+		"folderId", "certificateName1",
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{certificate1},
+			PrivateKey:       privateKey1,
+		})
 	certificate2 := uuid.NewString()
 	privateKey2 := uuid.NewString()
-	certificateID2, _ := fakeCertificateManagerServer.CreateCertificate(authorizedKey2, &certificatemanager.GetCertificateContentResponse{
-		CertificateChain: []string{certificate2},
-		PrivateKey:       privateKey2,
-	})
+	certificateID2, _ := fakeCertificateManagerServer.CreateCertificate(authorizedKey2,
+		"folderId", "certificateName2",
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{certificate2},
+			PrivateKey:       privateKey2,
+		})
 
 	k8sClient := clientfake.NewClientBuilder().Build()
 	const authorizedKeySecretName = "authorizedKeySecretName"
@@ -363,17 +379,21 @@ func TestGetSecretWithTwoApiEndpoints(t *testing.T) {
 	fakeCertificateManagerServer1 := client.NewFakeCertificateManagerServer(fakeClock, time.Hour)
 	certificate1 := uuid.NewString()
 	privateKey1 := uuid.NewString()
-	certificateID1, _ := fakeCertificateManagerServer1.CreateCertificate(authorizedKey1, &certificatemanager.GetCertificateContentResponse{
-		CertificateChain: []string{certificate1},
-		PrivateKey:       privateKey1,
-	})
+	certificateID1, _ := fakeCertificateManagerServer1.CreateCertificate(authorizedKey1,
+		"folderId", "certificateName",
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{certificate1},
+			PrivateKey:       privateKey1,
+		})
 	fakeCertificateManagerServer2 := client.NewFakeCertificateManagerServer(fakeClock, time.Hour)
 	certificate2 := uuid.NewString()
 	privateKey2 := uuid.NewString()
-	certificateID2, _ := fakeCertificateManagerServer2.CreateCertificate(authorizedKey2, &certificatemanager.GetCertificateContentResponse{
-		CertificateChain: []string{certificate2},
-		PrivateKey:       privateKey2,
-	})
+	certificateID2, _ := fakeCertificateManagerServer2.CreateCertificate(authorizedKey2,
+		"folderId", "certificateName",
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{certificate2},
+			PrivateKey:       privateKey2,
+		})
 
 	k8sClient := clientfake.NewClientBuilder().Build()
 	const authorizedKeySecretName1 = "authorizedKeySecretName1"
@@ -423,10 +443,12 @@ func TestGetSecretWithIamTokenExpiration(t *testing.T) {
 	fakeCertificateManagerServer := client.NewFakeCertificateManagerServer(fakeClock, tokenExpirationTime)
 	certificate := uuid.NewString()
 	privateKey := uuid.NewString()
-	certificateID, _ := fakeCertificateManagerServer.CreateCertificate(authorizedKey, &certificatemanager.GetCertificateContentResponse{
-		CertificateChain: []string{certificate},
-		PrivateKey:       privateKey,
-	})
+	certificateID, _ := fakeCertificateManagerServer.CreateCertificate(authorizedKey,
+		"folderId", "certificateName",
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{certificate},
+			PrivateKey:       privateKey,
+		})
 
 	k8sClient := clientfake.NewClientBuilder().Build()
 	const authorizedKeySecretName = "authorizedKeySecretName"
@@ -467,14 +489,18 @@ func TestGetSecretWithIamTokenCleanup(t *testing.T) {
 	fakeClock := clock.NewFakeClock()
 	tokenExpirationDuration := time.Hour
 	fakeCertificateManagerServer := client.NewFakeCertificateManagerServer(fakeClock, tokenExpirationDuration)
-	certificateID1, _ := fakeCertificateManagerServer.CreateCertificate(authorizedKey1, &certificatemanager.GetCertificateContentResponse{
-		CertificateChain: []string{uuid.NewString()},
-		PrivateKey:       uuid.NewString(),
-	})
-	certificateID2, _ := fakeCertificateManagerServer.CreateCertificate(authorizedKey2, &certificatemanager.GetCertificateContentResponse{
-		CertificateChain: []string{uuid.NewString()},
-		PrivateKey:       uuid.NewString(),
-	})
+	certificateID1, _ := fakeCertificateManagerServer.CreateCertificate(authorizedKey1,
+		"folderId", "certificateName1",
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{uuid.NewString()},
+			PrivateKey:       uuid.NewString(),
+		})
+	certificateID2, _ := fakeCertificateManagerServer.CreateCertificate(authorizedKey2,
+		"folderId", "certificateName2",
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{uuid.NewString()},
+			PrivateKey:       uuid.NewString(),
+		})
 
 	var err error
 
@@ -547,10 +573,12 @@ func TestGetSecretMap(t *testing.T) {
 	certificate1 := uuid.NewString()
 	certificate2 := uuid.NewString()
 	privateKey := uuid.NewString()
-	certificateID, _ := fakeCertificateManagerServer.CreateCertificate(authorizedKey, &certificatemanager.GetCertificateContentResponse{
-		CertificateChain: []string{certificate1, certificate2},
-		PrivateKey:       privateKey,
-	})
+	certificateID, _ := fakeCertificateManagerServer.CreateCertificate(authorizedKey,
+		"folderId", "certificateName",
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{certificate1, certificate2},
+			PrivateKey:       privateKey,
+		})
 
 	k8sClient := clientfake.NewClientBuilder().Build()
 	const authorizedKeySecretName = "authorizedKeySecretName"
@@ -584,10 +612,12 @@ func TestGetSecretMapByVersionID(t *testing.T) {
 	fakeCertificateManagerServer := client.NewFakeCertificateManagerServer(fakeClock, time.Hour)
 	oldCertificate := uuid.NewString()
 	oldPrivateKey := uuid.NewString()
-	certificateID, oldVersionID := fakeCertificateManagerServer.CreateCertificate(authorizedKey, &certificatemanager.GetCertificateContentResponse{
-		CertificateChain: []string{oldCertificate},
-		PrivateKey:       oldPrivateKey,
-	})
+	certificateID, oldVersionID := fakeCertificateManagerServer.CreateCertificate(authorizedKey,
+		"folderId", "certificateName",
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{oldCertificate},
+			PrivateKey:       oldPrivateKey,
+		})
 
 	k8sClient := clientfake.NewClientBuilder().Build()
 	const authorizedKeySecretName = "authorizedKeySecretName"
@@ -613,10 +643,11 @@ func TestGetSecretMapByVersionID(t *testing.T) {
 
 	newCertificate := uuid.NewString()
 	newPrivateKey := uuid.NewString()
-	newVersionID := fakeCertificateManagerServer.AddVersion(certificateID, &certificatemanager.GetCertificateContentResponse{
-		CertificateChain: []string{newCertificate},
-		PrivateKey:       newPrivateKey,
-	})
+	newVersionID := fakeCertificateManagerServer.AddVersion(certificateID,
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{newCertificate},
+			PrivateKey:       newPrivateKey,
+		})
 
 	data, err = secretsClient.GetSecretMap(ctx, esv1.ExternalSecretDataRemoteRef{Key: certificateID, Version: oldVersionID})
 	tassert.Nil(t, err)
@@ -641,6 +672,322 @@ func TestGetSecretMapByVersionID(t *testing.T) {
 	)
 }
 
+func TestGetSecretWithByNameFetchingPolicyWithoutProperty(t *testing.T) {
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	authorizedKey := newFakeAuthorizedKey()
+
+	fakeClock := clock.NewFakeClock()
+	fakeCertificateManagerServer := client.NewFakeCertificateManagerServer(fakeClock, time.Hour)
+	certificate1 := uuid.NewString()
+	certificate2 := uuid.NewString()
+	privateKey := uuid.NewString()
+	folderID := uuid.NewString()
+	const certificateName = "certificateName"
+	_, _ = fakeCertificateManagerServer.CreateCertificate(authorizedKey,
+		folderID, certificateName,
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{certificate1, certificate2},
+			PrivateKey:       privateKey,
+		})
+
+	k8sClient := clientfake.NewClientBuilder().Build()
+	const authorizedKeySecretName = "authorizedKeySecretName"
+	const authorizedKeySecretKey = "authorizedKeySecretKey"
+	err := createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, authorizedKey))
+	tassert.Nil(t, err)
+	store := newYandexCertificateManagerSecretStoreWithFetchByName("", namespace, authorizedKeySecretName, authorizedKeySecretKey, folderID)
+
+	provider := newCertificateManagerProvider(fakeClock, fakeCertificateManagerServer)
+	secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
+	tassert.Nil(t, err)
+	data, err := secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: certificateName})
+	tassert.Nil(t, err)
+
+	tassert.Equal(
+		t,
+		strings.TrimSpace(strings.Join([]string{certificate1, certificate2, privateKey}, "\n")),
+		strings.TrimSpace(string(data)),
+	)
+}
+
+func TestGetSecretWithByNameFetchingPolicyWithProperty(t *testing.T) {
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	authorizedKey := newFakeAuthorizedKey()
+
+	fakeClock := clock.NewFakeClock()
+	fakeCertificateManagerServer := client.NewFakeCertificateManagerServer(fakeClock, time.Hour)
+	certificate1 := uuid.NewString()
+	certificate2 := uuid.NewString()
+	privateKey := uuid.NewString()
+	folderID := uuid.NewString()
+	const certificateName = "certificateName"
+	_, _ = fakeCertificateManagerServer.CreateCertificate(authorizedKey,
+		folderID, certificateName,
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{certificate1, certificate2},
+			PrivateKey:       privateKey,
+		})
+
+	k8sClient := clientfake.NewClientBuilder().Build()
+	const authorizedKeySecretName = "authorizedKeySecretName"
+	const authorizedKeySecretKey = "authorizedKeySecretKey"
+	err := createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, authorizedKey))
+	tassert.Nil(t, err)
+	store := newYandexCertificateManagerSecretStoreWithFetchByName("", namespace, authorizedKeySecretName, authorizedKeySecretKey, folderID)
+
+	provider := newCertificateManagerProvider(fakeClock, fakeCertificateManagerServer)
+	secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
+	tassert.Nil(t, err)
+
+	chainData, err := secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: certificateName, Property: chainProperty})
+	tassert.Nil(t, err)
+	tassert.Equal(
+		t,
+		strings.TrimSpace(certificate1+"\n"+certificate2),
+		strings.TrimSpace(string(chainData)),
+	)
+
+	privateKeyData, err := secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: certificateName, Property: privateKeyProperty})
+	tassert.Nil(t, err)
+	tassert.Equal(
+		t,
+		strings.TrimSpace(privateKey),
+		strings.TrimSpace(string(privateKeyData)),
+	)
+
+	chainAndPrivateKeyData, err := secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: certificateName, Property: chainAndPrivateKeyProperty})
+	tassert.Nil(t, err)
+	tassert.Equal(
+		t,
+		strings.TrimSpace(strings.Join([]string{certificate1, certificate2, privateKey}, "\n")),
+		strings.TrimSpace(string(chainAndPrivateKeyData)),
+	)
+}
+
+func TestGetSecretWithByNameFetchingPolicyAndVersionID(t *testing.T) {
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	authorizedKey := newFakeAuthorizedKey()
+
+	fakeClock := clock.NewFakeClock()
+	fakeCertificateManagerServer := client.NewFakeCertificateManagerServer(fakeClock, time.Hour)
+	oldCertificate1 := uuid.NewString()
+	oldCertificate2 := uuid.NewString()
+	oldPrivateKey := uuid.NewString()
+	folderID := uuid.NewString()
+	const certificateName = "certificateName"
+	certificateID, oldVersionID := fakeCertificateManagerServer.CreateCertificate(authorizedKey,
+		folderID, certificateName,
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{oldCertificate1, oldCertificate2},
+			PrivateKey:       oldPrivateKey,
+		})
+
+	k8sClient := clientfake.NewClientBuilder().Build()
+	const authorizedKeySecretName = "authorizedKeySecretName"
+	const authorizedKeySecretKey = "authorizedKeySecretKey"
+	err := createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, authorizedKey))
+	tassert.Nil(t, err)
+	store := newYandexCertificateManagerSecretStoreWithFetchByName("", namespace, authorizedKeySecretName, authorizedKeySecretKey, folderID)
+
+	provider := newCertificateManagerProvider(fakeClock, fakeCertificateManagerServer)
+	secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
+	tassert.Nil(t, err)
+	data, err := secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: certificateName, Version: oldVersionID})
+	tassert.Nil(t, err)
+
+	tassert.Equal(
+		t,
+		strings.TrimSpace(strings.Join([]string{oldCertificate1, oldCertificate2, oldPrivateKey}, "\n")),
+		strings.TrimSpace(string(data)),
+	)
+
+	newCertificate1 := uuid.NewString()
+	newCertificate2 := uuid.NewString()
+	newPrivateKey := uuid.NewString()
+	newVersionID := fakeCertificateManagerServer.AddVersion(certificateID,
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{newCertificate1, newCertificate2},
+			PrivateKey:       newPrivateKey,
+		})
+
+	data, err = secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: certificateName, Version: oldVersionID})
+	tassert.Nil(t, err)
+	tassert.Equal(
+		t,
+		strings.TrimSpace(strings.Join([]string{oldCertificate1, oldCertificate2, oldPrivateKey}, "\n")),
+		strings.TrimSpace(string(data)),
+	)
+
+	data, err = secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: certificateName, Version: newVersionID})
+	tassert.Nil(t, err)
+	tassert.Equal(
+		t,
+		strings.TrimSpace(strings.Join([]string{newCertificate1, newCertificate2, newPrivateKey}, "\n")),
+		strings.TrimSpace(string(data)),
+	)
+}
+
+func TestGetSecretWithByNameFetchingPolicyUnauthorized(t *testing.T) {
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	authorizedKeyA := newFakeAuthorizedKey()
+	authorizedKeyB := newFakeAuthorizedKey()
+
+	fakeClock := clock.NewFakeClock()
+	fakeCertificateManagerServer := client.NewFakeCertificateManagerServer(fakeClock, time.Hour)
+	folderID := uuid.NewString()
+	certificateName := "certificateName"
+	_, _ = fakeCertificateManagerServer.CreateCertificate(authorizedKeyA,
+		folderID, certificateName,
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{uuid.NewString()},
+			PrivateKey:       uuid.NewString(),
+		})
+
+	k8sClient := clientfake.NewClientBuilder().Build()
+	const authorizedKeySecretName = "authorizedKeySecretName"
+	const authorizedKeySecretKey = "authorizedKeySecretKey"
+	err := createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, authorizedKeyB))
+	tassert.Nil(t, err)
+	store := newYandexCertificateManagerSecretStoreWithFetchByName("", namespace, authorizedKeySecretName, authorizedKeySecretKey, folderID)
+
+	provider := newCertificateManagerProvider(fakeClock, fakeCertificateManagerServer)
+	secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
+	tassert.Nil(t, err)
+	_, err = secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: certificateName})
+	tassert.EqualError(t, err, errSecretPayloadPermissionDenied)
+}
+
+func TestGetSecretWithByNameFetchingPolicyNotFound(t *testing.T) {
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	authorizedKey := newFakeAuthorizedKey()
+
+	fakeClock := clock.NewFakeClock()
+	fakeCertificateManagerServer := client.NewFakeCertificateManagerServer(fakeClock, time.Hour)
+	folderID := uuid.NewString()
+	k8sClient := clientfake.NewClientBuilder().Build()
+	const authorizedKeySecretName = "authorizedKeySecretName"
+	const authorizedKeySecretKey = "authorizedKeySecretKey"
+	err := createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, authorizedKey))
+	tassert.Nil(t, err)
+	store := newYandexCertificateManagerSecretStoreWithFetchByName("", namespace, authorizedKeySecretName, authorizedKeySecretKey, folderID)
+
+	provider := newCertificateManagerProvider(fakeClock, fakeCertificateManagerServer)
+	secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
+	tassert.Nil(t, err)
+	_, err = secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: "no-secret-with-this-name"})
+	tassert.EqualError(t, err, errSecretPayloadNotFound)
+
+	certificateName := "certificateName"
+	_, _ = fakeCertificateManagerServer.CreateCertificate(authorizedKey,
+		folderID, certificateName,
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{uuid.NewString()},
+			PrivateKey:       uuid.NewString(),
+		})
+	_, err = secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: certificateName, Version: "no-version-with-this-id"})
+	tassert.EqualError(t, err, errSecretPayloadVersionNotFound)
+}
+
+func TestGetSecretWithByNameFetchingPolicyWithoutFolderID(t *testing.T) {
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	authorizedKey := newFakeAuthorizedKey()
+
+	fakeClock := clock.NewFakeClock()
+	fakeCertificateManagerServer := client.NewFakeCertificateManagerServer(fakeClock, time.Hour)
+	k8sClient := clientfake.NewClientBuilder().Build()
+	const authorizedKeySecretName = "authorizedKeySecretName"
+	const authorizedKeySecretKey = "authorizedKeySecretKey"
+	err := createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, authorizedKey))
+	tassert.Nil(t, err)
+	store := newYandexCertificateManagerSecretStoreWithFetchByName("", namespace, authorizedKeySecretName, authorizedKeySecretKey, "")
+	provider := newCertificateManagerProvider(fakeClock, fakeCertificateManagerServer)
+	_, err = provider.NewClient(ctx, store, k8sClient, namespace)
+	tassert.EqualError(t, err, "folderID is required when fetching policy is 'byName'")
+}
+
+func TestGetSecretWithByIDFetchingPolicyWithoutProperty(t *testing.T) {
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	authorizedKey := newFakeAuthorizedKey()
+
+	fakeClock := clock.NewFakeClock()
+	fakeCertificateManagerServer := client.NewFakeCertificateManagerServer(fakeClock, time.Hour)
+	certificate1 := uuid.NewString()
+	certificate2 := uuid.NewString()
+	privateKey := uuid.NewString()
+	certificateID, _ := fakeCertificateManagerServer.CreateCertificate(authorizedKey,
+		"folderId", "certificateName",
+		&certificatemanager.GetCertificateContentResponse{
+			CertificateChain: []string{certificate1, certificate2},
+			PrivateKey:       privateKey,
+		})
+
+	k8sClient := clientfake.NewClientBuilder().Build()
+	const authorizedKeySecretName = "authorizedKeySecretName"
+	const authorizedKeySecretKey = "authorizedKeySecretKey"
+	err := createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, authorizedKey))
+	tassert.Nil(t, err)
+	store := newYandexCertificateManagerSecretStoreWithFetchByID("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
+
+	provider := newCertificateManagerProvider(fakeClock, fakeCertificateManagerServer)
+	secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
+	tassert.Nil(t, err)
+	data, err := secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: certificateID})
+	tassert.Nil(t, err)
+
+	tassert.Equal(
+		t,
+		strings.TrimSpace(strings.Join([]string{certificate1, certificate2, privateKey}, "\n")),
+		strings.TrimSpace(string(data)),
+	)
+}
+func TestGetSecretWithInvalidFetchingPolicy(t *testing.T) {
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	authorizedKey := newFakeAuthorizedKey()
+
+	fakeClock := clock.NewFakeClock()
+	fakeCertificateManagerServer := client.NewFakeCertificateManagerServer(fakeClock, time.Hour)
+
+	k8sClient := clientfake.NewClientBuilder().Build()
+	const authorizedKeySecretName = "authorizedKeySecretName"
+	const authorizedKeySecretKey = "authorizedKeySecretKey"
+	err := createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, authorizedKey))
+	tassert.Nil(t, err)
+	store := &esv1.SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: namespace,
+		},
+		Spec: esv1.SecretStoreSpec{
+			Provider: &esv1.SecretStoreProvider{
+				YandexCertificateManager: &esv1.YandexCertificateManagerProvider{
+					APIEndpoint: "",
+					Auth: esv1.YandexAuth{
+						AuthorizedKey: esmeta.SecretKeySelector{
+							Name: authorizedKeySecretName,
+							Key:  authorizedKeySecretKey,
+						},
+					},
+					FetchingPolicy: &esv1.FetchingPolicy{
+						ByID:   nil,
+						ByName: nil,
+					},
+				},
+			},
+		},
+	}
+
+	provider := newCertificateManagerProvider(fakeClock, fakeCertificateManagerServer)
+	_, err = provider.NewClient(ctx, store, k8sClient, namespace)
+	tassert.EqualError(t, err, "invalid Yandex Certificate Manager SecretStore: requires either 'byName' or 'byID' policy")
+}
+
 // helper functions
 
 func newCertificateManagerProvider(clock clock.Clock, fakeCertificateManagerServer *client.FakeCertificateManagerServer) *common.YandexCloudProvider {
@@ -679,6 +1026,56 @@ func newYandexCertificateManagerSecretStore(apiEndpoint, namespace, authorizedKe
 	}
 }
 
+func newYandexCertificateManagerSecretStoreWithFetchByName(apiEndpoint, namespace, authorizedKeySecretName, authorizedKeySecretKey, folderID string) esv1.GenericStore {
+	return &esv1.SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: namespace,
+		},
+		Spec: esv1.SecretStoreSpec{
+			Provider: &esv1.SecretStoreProvider{
+				YandexCertificateManager: &esv1.YandexCertificateManagerProvider{
+					APIEndpoint: apiEndpoint,
+					Auth: esv1.YandexAuth{
+						AuthorizedKey: esmeta.SecretKeySelector{
+							Name: authorizedKeySecretName,
+							Key:  authorizedKeySecretKey,
+						},
+					},
+					FetchingPolicy: &esv1.FetchingPolicy{
+						ByName: &esv1.ByName{
+							FolderID: folderID,
+						},
+					},
+				},
+			},
+		},
+	}
+}
+
+func newYandexCertificateManagerSecretStoreWithFetchByID(apiEndpoint, namespace, authorizedKeySecretName, authorizedKeySecretKey string) esv1.GenericStore {
+	return &esv1.SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: namespace,
+		},
+		Spec: esv1.SecretStoreSpec{
+			Provider: &esv1.SecretStoreProvider{
+				YandexCertificateManager: &esv1.YandexCertificateManagerProvider{
+					APIEndpoint: apiEndpoint,
+					Auth: esv1.YandexAuth{
+						AuthorizedKey: esmeta.SecretKeySelector{
+							Name: authorizedKeySecretName,
+							Key:  authorizedKeySecretKey,
+						},
+					},
+					FetchingPolicy: &esv1.FetchingPolicy{
+						ByID: &esv1.ByID{},
+					},
+				},
+			},
+		},
+	}
+}
+
 func toJSON(t *testing.T, v any) []byte {
 	jsonBytes, err := json.Marshal(v)
 	tassert.Nil(t, err)

+ 28 - 6
pkg/provider/yandex/certificatemanager/certificatemanagersecretgetter.go

@@ -21,6 +21,7 @@ import (
 
 	"github.com/external-secrets/external-secrets/pkg/provider/yandex/certificatemanager/client"
 	"github.com/external-secrets/external-secrets/pkg/provider/yandex/common"
+	api "github.com/yandex-cloud/go-genproto/yandex/cloud/certificatemanager/v1"
 )
 
 const (
@@ -40,12 +41,11 @@ func newCertificateManagerSecretGetter(certificateManagerClient client.Certifica
 	}, nil
 }
 
-func (g *certificateManagerSecretGetter) GetSecret(ctx context.Context, iamToken, resourceID, versionID, property string) ([]byte, error) {
-	response, err := g.certificateManagerClient.GetCertificateContent(ctx, iamToken, resourceID, versionID)
+func (g *certificateManagerSecretGetter) GetSecret(ctx context.Context, iamToken, resourceID string, resourceKeyType common.ResourceKeyType, folderID, versionID, property string) ([]byte, error) {
+	response, err := g.fetchCertificateContentResponse(ctx, iamToken, resourceID, resourceKeyType, folderID, versionID)
 	if err != nil {
 		return nil, fmt.Errorf("unable to request certificate content to get secret: %w", err)
 	}
-
 	chain := trimAndJoin(response.CertificateChain...)
 	privateKey := trimAndJoin(response.PrivateKey)
 
@@ -61,12 +61,11 @@ func (g *certificateManagerSecretGetter) GetSecret(ctx context.Context, iamToken
 	}
 }
 
-func (g *certificateManagerSecretGetter) GetSecretMap(ctx context.Context, iamToken, resourceID, versionID string) (map[string][]byte, error) {
-	response, err := g.certificateManagerClient.GetCertificateContent(ctx, iamToken, resourceID, versionID)
+func (g *certificateManagerSecretGetter) GetSecretMap(ctx context.Context, iamToken, resourceID string, resourceKeyType common.ResourceKeyType, folderID, versionID string) (map[string][]byte, error) {
+	response, err := g.fetchCertificateContentResponse(ctx, iamToken, resourceID, resourceKeyType, folderID, versionID)
 	if err != nil {
 		return nil, fmt.Errorf("unable to request certificate content to get secret map: %w", err)
 	}
-
 	chain := strings.Join(response.CertificateChain, "\n")
 	privateKey := response.PrivateKey
 
@@ -76,6 +75,21 @@ func (g *certificateManagerSecretGetter) GetSecretMap(ctx context.Context, iamTo
 	}, nil
 }
 
+func (g *certificateManagerSecretGetter) fetchCertificateContentResponse(ctx context.Context, iamToken, resourceID string, resourceKeyType common.ResourceKeyType, folderID, versionID string) (*api.GetCertificateContentResponse, error) {
+	switch resourceKeyType {
+	case common.ResourceKeyTypeId:
+		return g.certificateManagerClient.GetCertificateContent(ctx, iamToken, resourceID, versionID)
+	case common.ResourceKeyTypeName:
+		responseEx, err := g.certificateManagerClient.GetExCertificateContent(ctx, iamToken, folderID, resourceID, versionID)
+		if err != nil {
+			return nil, err
+		}
+		return convertToGetCertificateContentResponse(responseEx), nil
+	default:
+		return nil, fmt.Errorf("unsupported resource key type '%v'", resourceKeyType)
+	}
+}
+
 func trimAndJoin(elems ...string) string {
 	var sb strings.Builder
 	for _, elem := range elems {
@@ -84,3 +98,11 @@ func trimAndJoin(elems ...string) string {
 	}
 	return sb.String()
 }
+
+func convertToGetCertificateContentResponse(response *api.GetExCertificateContentResponse) *api.GetCertificateContentResponse {
+	return &api.GetCertificateContentResponse{
+		CertificateId:    response.CertificateId,
+		CertificateChain: response.CertificateChain,
+		PrivateKey:       response.PrivateKey,
+	}
+}

+ 1 - 0
pkg/provider/yandex/certificatemanager/client/client.go

@@ -23,4 +23,5 @@ import (
 // Requests the content of the given certificate from Certificate Manager.
 type CertificateManagerClient interface {
 	GetCertificateContent(ctx context.Context, iamToken, certificateID, versionID string) (*api.GetCertificateContentResponse, error)
+	GetExCertificateContent(ctx context.Context, iamToken, folderID, name, versionID string) (*api.GetExCertificateContentResponse, error)
 }

+ 54 - 4
pkg/provider/yandex/certificatemanager/client/fakeclient.go

@@ -19,6 +19,7 @@ import (
 	"errors"
 	"time"
 
+	"github.com/go-logr/logr"
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
 	"github.com/google/uuid"
@@ -42,14 +43,20 @@ func (c *fakeCertificateManagerClient) GetCertificateContent(_ context.Context,
 	return c.fakeCertificateManagerServer.getCertificateContent(iamToken, certificateID, versionID)
 }
 
+func (c *fakeCertificateManagerClient) GetExCertificateContent(_ context.Context, iamToken, folderID, name, versionID string) (*api.GetExCertificateContentResponse, error) {
+	return c.fakeCertificateManagerServer.getExCertificateContent(iamToken, folderID, name, versionID)
+}
+
 // Fakes Yandex Certificate Manager service backend.
 type FakeCertificateManagerServer struct {
-	certificateMap map[certificateKey]certificateValue // certificate specific data
-	versionMap     map[versionKey]versionValue         // version specific data
-	tokenMap       map[tokenKey]tokenValue             // token specific data
+	certificateMap   map[certificateKey]certificateValue     // certificate specific data
+	versionMap       map[versionKey]versionValue             // version specific data
+	tokenMap         map[tokenKey]tokenValue                 // token specific data
+	folderAndNameMap map[folderAndNameKey]folderAndNameValue // folderAndName specific data
 
 	tokenExpirationDuration time.Duration
 	clock                   clock.Clock
+	logger                  logr.Logger
 }
 
 type certificateKey struct {
@@ -78,17 +85,27 @@ type tokenValue struct {
 	expiresAt     time.Time
 }
 
+type folderAndNameKey struct {
+	folderID string
+	name     string
+}
+
+type folderAndNameValue struct {
+	certificateID string
+}
+
 func NewFakeCertificateManagerServer(clock clock.Clock, tokenExpirationDuration time.Duration) *FakeCertificateManagerServer {
 	return &FakeCertificateManagerServer{
 		certificateMap:          make(map[certificateKey]certificateValue),
 		versionMap:              make(map[versionKey]versionValue),
 		tokenMap:                make(map[tokenKey]tokenValue),
+		folderAndNameMap:        make(map[folderAndNameKey]folderAndNameValue),
 		tokenExpirationDuration: tokenExpirationDuration,
 		clock:                   clock,
 	}
 }
 
-func (s *FakeCertificateManagerServer) CreateCertificate(authorizedKey *iamkey.Key, content *api.GetCertificateContentResponse) (string, string) {
+func (s *FakeCertificateManagerServer) CreateCertificate(authorizedKey *iamkey.Key, folderID, name string, content *api.GetCertificateContentResponse) (string, string) {
 	certificateID := uuid.NewString()
 	versionID := uuid.NewString()
 
@@ -96,6 +113,12 @@ func (s *FakeCertificateManagerServer) CreateCertificate(authorizedKey *iamkey.K
 	s.versionMap[versionKey{certificateID, ""}] = versionValue{content} // empty versionID corresponds to the latest version
 	s.versionMap[versionKey{certificateID, versionID}] = versionValue{content}
 
+	if _, exists := s.folderAndNameMap[folderAndNameKey{folderID, name}]; exists {
+		s.logger.Error(nil, "ERROR: On the fake server, you cannot add two certificates with the same name in the same folder.")
+	}
+
+	s.folderAndNameMap[folderAndNameKey{folderID, name}] = folderAndNameValue{certificateID}
+
 	return certificateID, versionID
 }
 
@@ -135,3 +158,30 @@ func (s *FakeCertificateManagerServer) getCertificateContent(iamToken, certifica
 
 	return s.versionMap[versionKey{certificateID, versionID}].content, nil
 }
+
+func (s *FakeCertificateManagerServer) getExCertificateContent(iamToken, folderID, name, versionID string) (*api.GetExCertificateContentResponse, error) {
+	if _, ok := s.folderAndNameMap[folderAndNameKey{folderID, name}]; !ok {
+		return nil, errors.New("certificate not found")
+	}
+	certificateID := s.folderAndNameMap[folderAndNameKey{folderID, name}].certificateID
+	if _, ok := s.versionMap[versionKey{certificateID, versionID}]; !ok {
+		return nil, errors.New("version not found")
+	}
+	if _, ok := s.tokenMap[tokenKey{iamToken}]; !ok {
+		return nil, errors.New("unauthenticated")
+	}
+	if s.tokenMap[tokenKey{iamToken}].expiresAt.Before(s.clock.CurrentTime()) {
+		return nil, errors.New("iam token expired")
+	}
+
+	if !cmp.Equal(s.tokenMap[tokenKey{iamToken}].authorizedKey, s.certificateMap[certificateKey{certificateID}].expectedAuthorizedKey, cmpopts.IgnoreUnexported(iamkey.Key{})) {
+		return nil, errors.New("permission denied")
+	}
+	certificateChain := s.versionMap[versionKey{certificateID, versionID}].content.CertificateChain
+	privateKey := s.versionMap[versionKey{certificateID, versionID}].content.PrivateKey
+	return &api.GetExCertificateContentResponse{
+		CertificateId:    certificateID,
+		CertificateChain: certificateChain,
+		PrivateKey:       privateKey,
+	}, nil
+}

+ 22 - 1
pkg/provider/yandex/certificatemanager/client/grpcclient.go

@@ -43,11 +43,32 @@ func NewGrpcCertificateManagerClient(ctx context.Context, apiEndpoint string, au
 	return &grpcCertificateManagerClient{api.NewCertificateContentServiceClient(conn)}, nil
 }
 
-func (c *grpcCertificateManagerClient) GetCertificateContent(ctx context.Context, iamToken, certificateID, _ string) (*api.GetCertificateContentResponse, error) {
+func (c *grpcCertificateManagerClient) GetCertificateContent(ctx context.Context, iamToken, certificateID, versionID string) (*api.GetCertificateContentResponse, error) {
 	response, err := c.certificateContentServiceClient.Get(
 		ctx,
 		&api.GetCertificateContentRequest{
 			CertificateId: certificateID,
+			VersionId:     versionID,
+		},
+		grpc.PerRPCCredentials(common.PerRPCCredentials{IamToken: iamToken}),
+	)
+	if err != nil {
+		return nil, err
+	}
+	return response, nil
+}
+
+func (c *grpcCertificateManagerClient) GetExCertificateContent(ctx context.Context, iamToken, folderID, name, versionID string) (*api.GetExCertificateContentResponse, error) {
+	response, err := c.certificateContentServiceClient.GetEx(
+		ctx,
+		&api.GetExCertificateContentRequest{
+			Identifier: &api.GetExCertificateContentRequest_FolderAndName{
+				FolderAndName: &api.FolderAndName{
+					FolderId:        folderID,
+					CertificateName: name,
+				},
+			},
+			VersionId: versionID,
 		},
 		grpc.PerRPCCredentials(common.PerRPCCredentials{IamToken: iamToken}),
 	)

+ 13 - 5
pkg/provider/yandex/common/provider.go

@@ -100,11 +100,20 @@ type IamToken struct {
 }
 
 type SecretsClientInput struct {
-	APIEndpoint   string
-	AuthorizedKey *esmeta.SecretKeySelector
-	CACertificate *esmeta.SecretKeySelector
+	APIEndpoint     string
+	AuthorizedKey   *esmeta.SecretKeySelector
+	CACertificate   *esmeta.SecretKeySelector
+	ResourceKeyType ResourceKeyType
+	FolderID        string
 }
 
+type ResourceKeyType int
+
+const (
+	ResourceKeyTypeId   ResourceKeyType = iota
+	ResourceKeyTypeName ResourceKeyType = iota
+)
+
 func (p *YandexCloudProvider) Capabilities() esv1.SecretStoreCapabilities {
 	return esv1.SecretStoreReadOnly
 }
@@ -161,7 +170,7 @@ func (p *YandexCloudProvider) NewClient(ctx context.Context, store esv1.GenericS
 		return nil, fmt.Errorf("failed to create IAM token: %w", err)
 	}
 
-	return &yandexCloudSecretsClient{secretGetter, nil, iamToken.Token}, nil
+	return &yandexCloudSecretsClient{secretGetter, nil, iamToken.Token, input.ResourceKeyType, input.FolderID}, nil
 }
 
 func (p *YandexCloudProvider) getOrCreateSecretGetter(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key, caCertificate []byte) (SecretGetter, error) {
@@ -170,7 +179,6 @@ func (p *YandexCloudProvider) getOrCreateSecretGetter(ctx context.Context, apiEn
 
 	if _, ok := p.secretGetteMap[apiEndpoint]; !ok {
 		p.logger.Info("creating SecretGetter", "apiEndpoint", apiEndpoint)
-
 		secretGetter, err := p.newSecretGetterFunc(ctx, apiEndpoint, authorizedKey, caCertificate)
 		if err != nil {
 			return nil, err

+ 2 - 2
pkg/provider/yandex/common/secretgetter.go

@@ -20,6 +20,6 @@ import (
 
 // Adapts the secrets received from a remote Yandex.Cloud service for the format expected by v1beta1.SecretsClient.
 type SecretGetter interface {
-	GetSecret(ctx context.Context, iamToken, resourceID, versionID, property string) ([]byte, error)
-	GetSecretMap(ctx context.Context, iamToken, resourceID, versionID string) (map[string][]byte, error)
+	GetSecret(ctx context.Context, iamToken, resourceKey string, resourceKeyType ResourceKeyType, folderID, versionID, property string) ([]byte, error)
+	GetSecretMap(ctx context.Context, iamToken, resourceKey string, resourceKeyType ResourceKeyType, folderID, versionID string) (map[string][]byte, error)
 }

+ 7 - 5
pkg/provider/yandex/common/secretsclient.go

@@ -32,13 +32,15 @@ var _ esv1.SecretsClient = &yandexCloudSecretsClient{}
 
 // Implementation of v1beta1.SecretsClient.
 type yandexCloudSecretsClient struct {
-	secretGetter SecretGetter
-	secretSetter SecretSetter
-	iamToken     string
+	secretGetter    SecretGetter
+	secretSetter    SecretSetter
+	iamToken        string
+	resourceKeyType ResourceKeyType
+	folderId        string
 }
 
 func (c *yandexCloudSecretsClient) GetSecret(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
-	return c.secretGetter.GetSecret(ctx, c.iamToken, ref.Key, ref.Version, ref.Property)
+	return c.secretGetter.GetSecret(ctx, c.iamToken, ref.Key, c.resourceKeyType, c.folderId, ref.Version, ref.Property)
 }
 
 func (c *yandexCloudSecretsClient) DeleteSecret(_ context.Context, _ esv1.PushSecretRemoteRef) error {
@@ -58,7 +60,7 @@ func (c *yandexCloudSecretsClient) Validate() (esv1.ValidationResult, error) {
 }
 
 func (c *yandexCloudSecretsClient) GetSecretMap(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
-	return c.secretGetter.GetSecretMap(ctx, c.iamToken, ref.Key, ref.Version)
+	return c.secretGetter.GetSecretMap(ctx, c.iamToken, ref.Key, c.resourceKeyType, c.folderId, ref.Version)
 }
 
 func (c *yandexCloudSecretsClient) GetAllSecrets(_ context.Context, _ esv1.ExternalSecretFind) (map[string][]byte, error) {

+ 1 - 0
pkg/provider/yandex/lockbox/client/client.go

@@ -23,4 +23,5 @@ import (
 // Requests the payload of the given secret from Lockbox.
 type LockboxClient interface {
 	GetPayloadEntries(ctx context.Context, iamToken, secretID, versionID string) ([]*api.Payload_Entry, error)
+	GetExPayload(ctx context.Context, iamToken, folderID, name, versionID string) (map[string][]byte, error)
 }

+ 55 - 4
pkg/provider/yandex/lockbox/client/fakeclient.go

@@ -19,6 +19,8 @@ import (
 	"errors"
 	"time"
 
+	"github.com/go-logr/logr"
+
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
 	"github.com/google/uuid"
@@ -42,14 +44,20 @@ func (c *fakeLockboxClient) GetPayloadEntries(_ context.Context, iamToken, secre
 	return c.fakeLockboxServer.getEntries(iamToken, secretID, versionID)
 }
 
+func (c *fakeLockboxClient) GetExPayload(_ context.Context, iamToken, folderID, name, versionID string) (map[string][]byte, error) {
+	return c.fakeLockboxServer.getExPayload(iamToken, folderID, name, versionID)
+}
+
 // Fakes Yandex Lockbox service backend.
 type FakeLockboxServer struct {
-	secretMap  map[secretKey]secretValue   // secret specific data
-	versionMap map[versionKey]versionValue // version specific data
-	tokenMap   map[tokenKey]tokenValue     // token specific data
+	secretMap        map[secretKey]secretValue               // secret specific data
+	versionMap       map[versionKey]versionValue             // version specific data
+	tokenMap         map[tokenKey]tokenValue                 // token specific data
+	folderAndNameMap map[folderAndNameKey]folderAndNameValue // folderAndName specific data
 
 	tokenExpirationDuration time.Duration
 	clock                   clock.Clock
+	logger                  logr.Logger
 }
 
 type secretKey struct {
@@ -69,6 +77,15 @@ type versionValue struct {
 	entries []*api.Payload_Entry
 }
 
+type folderAndNameKey struct {
+	folderID string
+	name     string
+}
+
+type folderAndNameValue struct {
+	secretID string
+}
+
 type tokenKey struct {
 	token string
 }
@@ -83,12 +100,13 @@ func NewFakeLockboxServer(clock clock.Clock, tokenExpirationDuration time.Durati
 		secretMap:               make(map[secretKey]secretValue),
 		versionMap:              make(map[versionKey]versionValue),
 		tokenMap:                make(map[tokenKey]tokenValue),
+		folderAndNameMap:        make(map[folderAndNameKey]folderAndNameValue),
 		tokenExpirationDuration: tokenExpirationDuration,
 		clock:                   clock,
 	}
 }
 
-func (s *FakeLockboxServer) CreateSecret(authorizedKey *iamkey.Key, entries ...*api.Payload_Entry) (string, string) {
+func (s *FakeLockboxServer) CreateSecret(authorizedKey *iamkey.Key, folderID, name string, entries ...*api.Payload_Entry) (string, string) {
 	secretID := uuid.NewString()
 	versionID := uuid.NewString()
 
@@ -96,6 +114,11 @@ func (s *FakeLockboxServer) CreateSecret(authorizedKey *iamkey.Key, entries ...*
 	s.versionMap[versionKey{secretID, ""}] = versionValue{entries} // empty versionID corresponds to the latest version
 	s.versionMap[versionKey{secretID, versionID}] = versionValue{entries}
 
+	if _, exists := s.folderAndNameMap[folderAndNameKey{folderID, name}]; exists {
+		s.logger.Error(nil, "On the fake server, you cannot add two certificates with the same name in the same folder")
+	}
+	s.folderAndNameMap[folderAndNameKey{folderID, name}] = folderAndNameValue{secretID}
+
 	return secretID, versionID
 }
 
@@ -135,3 +158,31 @@ func (s *FakeLockboxServer) getEntries(iamToken, secretID, versionID string) ([]
 
 	return s.versionMap[versionKey{secretID, versionID}].entries, nil
 }
+
+func (s *FakeLockboxServer) getExPayload(iamToken, folderID, name, versionID string) (map[string][]byte, error) {
+	if _, ok := s.folderAndNameMap[folderAndNameKey{folderID, name}]; !ok {
+		return nil, errors.New("secret not found")
+	}
+	secretID := s.folderAndNameMap[folderAndNameKey{folderID, name}].secretID
+	if _, ok := s.versionMap[versionKey{secretID, versionID}]; !ok {
+		return nil, errors.New("version not found")
+	}
+
+	if s.tokenMap[tokenKey{iamToken}].expiresAt.Before(s.clock.CurrentTime()) {
+		return nil, errors.New("iam token expired")
+	}
+	if !cmp.Equal(s.tokenMap[tokenKey{iamToken}].authorizedKey, s.secretMap[secretKey{secretID}].expectedAuthorizedKey, cmpopts.IgnoreUnexported(iamkey.Key{})) {
+		return nil, errors.New("permission denied")
+	}
+	entries := s.versionMap[versionKey{secretID, versionID}].entries
+	out := make(map[string][]byte, len(entries))
+	for _, e := range entries {
+		switch e.Value.(type) {
+		case *api.Payload_Entry_TextValue:
+			out[e.Key] = []byte(e.GetTextValue())
+		case *api.Payload_Entry_BinaryValue:
+			out[e.Key] = e.GetBinaryValue()
+		}
+	}
+	return out, nil
+}

+ 23 - 0
pkg/provider/yandex/lockbox/client/grpcclient.go

@@ -57,3 +57,26 @@ func (c *grpcLockboxClient) GetPayloadEntries(ctx context.Context, iamToken, sec
 	}
 	return payload.Entries, nil
 }
+
+func (c *grpcLockboxClient) GetExPayload(ctx context.Context, iamToken, folderID, name, versionID string) (map[string][]byte, error) {
+	request := &api.GetExRequest{
+		Identifier: &api.GetExRequest_FolderAndName{
+			FolderAndName: &api.FolderAndName{
+				FolderId:   folderID,
+				SecretName: name,
+			},
+		},
+		VersionId: versionID,
+	}
+
+	response, err := c.lockboxPayloadClient.GetEx(
+		ctx,
+		request,
+		grpc.PerRPCCredentials(common.PerRPCCredentials{IamToken: iamToken}),
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	return response.Entries, nil
+}

+ 25 - 3
pkg/provider/yandex/lockbox/lockbox.go

@@ -48,10 +48,32 @@ func adaptInput(store esv1.GenericStore) (*common.SecretsClientInput, error) {
 		caCertificate = &storeSpecYandexLockbox.CAProvider.Certificate
 	}
 
+	var resourceKeyType common.ResourceKeyType
+	var folderID string
+	policy := storeSpecYandexLockbox.FetchingPolicy
+	if policy != nil {
+		switch {
+		case policy.ByName != nil:
+			if policy.ByName.FolderID == "" {
+				return nil, errors.New("folderID is required when fetching policy is 'byName'")
+			}
+			resourceKeyType = common.ResourceKeyTypeName
+			folderID = policy.ByName.FolderID
+
+		case policy.ByID != nil:
+			resourceKeyType = common.ResourceKeyTypeId
+
+		default:
+			return nil, errors.New("invalid Yandex Lockbox SecretStore: requires either 'byName' or 'byID' policy")
+		}
+	}
+
 	return &common.SecretsClientInput{
-		APIEndpoint:   storeSpecYandexLockbox.APIEndpoint,
-		AuthorizedKey: authorizedKey,
-		CACertificate: caCertificate,
+		APIEndpoint:     storeSpecYandexLockbox.APIEndpoint,
+		AuthorizedKey:   authorizedKey,
+		CACertificate:   caCertificate,
+		ResourceKeyType: resourceKeyType,
+		FolderID:        folderID,
 	}, nil
 }
 

+ 431 - 10
pkg/provider/yandex/lockbox/lockbox_test.go

@@ -42,6 +42,7 @@ const (
 	errMissingKey                    = "invalid Yandex Lockbox SecretStore resource: missing AuthorizedKey Name"
 	errSecretPayloadPermissionDenied = "unable to request secret payload to get secret: permission denied"
 	errSecretPayloadNotFound         = "unable to request secret payload to get secret: secret not found"
+	errSecretPayloadVersionNotFound  = "unable to request secret payload to get secret: version not found"
 )
 
 func TestNewClient(t *testing.T) {
@@ -107,6 +108,7 @@ func TestGetSecretForAllEntries(t *testing.T) {
 	k1, v1 := "k1", "v1"
 	k2, v2 := "k2", []byte("v2")
 	secretID, _ := fakeLockboxServer.CreateSecret(authorizedKey,
+		"folderId", "secretName",
 		textEntry(k1, v1),
 		binaryEntry(k2, v2),
 	)
@@ -144,6 +146,7 @@ func TestGetSecretForTextEntry(t *testing.T) {
 	k1, v1 := "k1", "v1"
 	k2, v2 := "k2", []byte("v2")
 	secretID, _ := fakeLockboxServer.CreateSecret(authorizedKey,
+		"folderId", "secretName",
 		textEntry(k1, v1),
 		binaryEntry(k2, v2),
 	)
@@ -174,6 +177,7 @@ func TestGetSecretForBinaryEntry(t *testing.T) {
 	k1, v1 := "k1", "v1"
 	k2, v2 := "k2", []byte("v2")
 	secretID, _ := fakeLockboxServer.CreateSecret(authorizedKey,
+		"folderId", "secretName",
 		textEntry(k1, v1),
 		binaryEntry(k2, v2),
 	)
@@ -201,8 +205,9 @@ func TestGetSecretByVersionID(t *testing.T) {
 
 	fakeClock := clock.NewFakeClock()
 	fakeLockboxServer := client.NewFakeLockboxServer(fakeClock, time.Hour)
-	oldKey, oldVal := "oldKey", "oldVal"
+	const oldKey, oldVal = "oldKey", "oldVal"
 	secretID, oldVersionID := fakeLockboxServer.CreateSecret(authorizedKey,
+		"folderId", "secretName",
 		textEntry(oldKey, oldVal),
 	)
 
@@ -221,10 +226,8 @@ func TestGetSecretByVersionID(t *testing.T) {
 
 	tassert.Equal(t, map[string]string{oldKey: oldVal}, unmarshalStringMap(t, data))
 
-	newKey, newVal := "newKey", "newVal"
-	newVersionID := fakeLockboxServer.AddVersion(secretID,
-		textEntry(newKey, newVal),
-	)
+	const newKey, newVal = "newKey", "newVal"
+	newVersionID := fakeLockboxServer.AddVersion(secretID, textEntry(newKey, newVal))
 
 	data, err = secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secretID, Version: oldVersionID})
 	tassert.Nil(t, err)
@@ -244,6 +247,7 @@ func TestGetSecretUnauthorized(t *testing.T) {
 	fakeClock := clock.NewFakeClock()
 	fakeLockboxServer := client.NewFakeLockboxServer(fakeClock, time.Hour)
 	secretID, _ := fakeLockboxServer.CreateSecret(authorizedKeyA,
+		"folderId", "secretName",
 		textEntry("k1", "v1"),
 	)
 
@@ -283,10 +287,11 @@ func TestGetSecretNotFound(t *testing.T) {
 	tassert.EqualError(t, err, errSecretPayloadNotFound)
 
 	secretID, _ := fakeLockboxServer.CreateSecret(authorizedKey,
+		"folderId", "secretName",
 		textEntry("k1", "v1"),
 	)
 	_, err = secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secretID, Version: "no-version-with-this-id"})
-	tassert.EqualError(t, err, "unable to request secret payload to get secret: version not found")
+	tassert.EqualError(t, err, errSecretPayloadVersionNotFound)
 }
 
 func TestGetSecretWithTwoNamespaces(t *testing.T) {
@@ -300,10 +305,13 @@ func TestGetSecretWithTwoNamespaces(t *testing.T) {
 	fakeLockboxServer := client.NewFakeLockboxServer(fakeClock, time.Hour)
 	k1, v1 := "k1", "v1"
 	secretID1, _ := fakeLockboxServer.CreateSecret(authorizedKey1,
+		"folderId", "secretName1",
 		textEntry(k1, v1),
 	)
 	k2, v2 := "k2", "v2"
 	secretID2, _ := fakeLockboxServer.CreateSecret(authorizedKey2,
+		"folderId", "secretName2",
+		textEntry(k2, v2),
 		textEntry(k2, v2),
 	)
 
@@ -350,11 +358,13 @@ func TestGetSecretWithTwoApiEndpoints(t *testing.T) {
 	fakeLockboxServer1 := client.NewFakeLockboxServer(fakeClock, time.Hour)
 	k1, v1 := "k1", "v1"
 	secretID1, _ := fakeLockboxServer1.CreateSecret(authorizedKey1,
+		"folderId", "secretName",
 		textEntry(k1, v1),
 	)
 	fakeLockboxServer2 := client.NewFakeLockboxServer(fakeClock, time.Hour)
 	k2, v2 := "k2", "v2"
 	secretID2, _ := fakeLockboxServer2.CreateSecret(authorizedKey2,
+		"folderId", "secretName",
 		textEntry(k2, v2),
 	)
 
@@ -406,6 +416,7 @@ func TestGetSecretWithIamTokenExpiration(t *testing.T) {
 	fakeLockboxServer := client.NewFakeLockboxServer(fakeClock, tokenExpirationTime)
 	k1, v1 := "k1", "v1"
 	secretID, _ := fakeLockboxServer.CreateSecret(authorizedKey,
+		"folderId", "secretName",
 		textEntry(k1, v1),
 	)
 
@@ -449,9 +460,11 @@ func TestGetSecretWithIamTokenCleanup(t *testing.T) {
 	tokenExpirationDuration := time.Hour
 	fakeLockboxServer := client.NewFakeLockboxServer(fakeClock, tokenExpirationDuration)
 	secretID1, _ := fakeLockboxServer.CreateSecret(authorizedKey1,
+		"folderId", "secretName1",
 		textEntry("k1", "v1"),
 	)
 	secretID2, _ := fakeLockboxServer.CreateSecret(authorizedKey2,
+		"folderId", "secretName2",
 		textEntry("k2", "v2"),
 	)
 
@@ -526,6 +539,7 @@ func TestGetSecretMap(t *testing.T) {
 	k1, v1 := "k1", "v1"
 	k2, v2 := "k2", []byte("v2")
 	secretID, _ := fakeLockboxServer.CreateSecret(authorizedKey,
+		"folderId", "secretName",
 		textEntry(k1, v1),
 		binaryEntry(k2, v2),
 	)
@@ -562,6 +576,7 @@ func TestGetSecretMapByVersionID(t *testing.T) {
 	fakeLockboxServer := client.NewFakeLockboxServer(fakeClock, time.Hour)
 	oldKey, oldVal := "oldKey", "oldVal"
 	secretID, oldVersionID := fakeLockboxServer.CreateSecret(authorizedKey,
+		"folderId", "secretName",
 		textEntry(oldKey, oldVal),
 	)
 
@@ -581,9 +596,7 @@ func TestGetSecretMapByVersionID(t *testing.T) {
 	tassert.Equal(t, map[string][]byte{oldKey: []byte(oldVal)}, data)
 
 	newKey, newVal := "newKey", "newVal"
-	newVersionID := fakeLockboxServer.AddVersion(secretID,
-		textEntry(newKey, newVal),
-	)
+	newVersionID := fakeLockboxServer.AddVersion(secretID, textEntry(newKey, newVal))
 
 	data, err = secretsClient.GetSecretMap(ctx, esv1.ExternalSecretDataRemoteRef{Key: secretID, Version: oldVersionID})
 	tassert.Nil(t, err)
@@ -594,7 +607,365 @@ func TestGetSecretMapByVersionID(t *testing.T) {
 	tassert.Equal(t, map[string][]byte{newKey: []byte(newVal)}, data)
 }
 
-// helper functions
+func TestGetSecretWithByNameFetchingPolicyForAllEntries(t *testing.T) {
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	authorizedKey := newFakeAuthorizedKey()
+
+	fakeClock := clock.NewFakeClock()
+	fakeLockboxServer := client.NewFakeLockboxServer(fakeClock, time.Hour)
+	folderId := uuid.NewString()
+	const secretName = "secretName"
+	k1, v1 := "k1", "v1"
+	k2, v2 := "k2", []byte("v2")
+	_, _ = fakeLockboxServer.CreateSecret(
+		authorizedKey,
+		folderId, secretName,
+		textEntry(k1, v1),
+		binaryEntry(k2, v2),
+	)
+
+	k8sClient := clientfake.NewClientBuilder().Build()
+	const authorizedKeySecretName = "authorizedKeySecretName"
+	const authorizedKeySecretKey = "authorizedKeySecretKey"
+	err := createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, authorizedKey))
+	tassert.Nil(t, err)
+	store := newYandexLockboxSecretStoreWithFetchByName("", namespace, authorizedKeySecretName, authorizedKeySecretKey, folderId)
+
+	provider := newLockboxProvider(fakeClock, fakeLockboxServer)
+	secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
+	tassert.Nil(t, err)
+
+	data, err := secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secretName})
+	tassert.Nil(t, err)
+	expected := map[string]string{
+		k1: base64([]byte(v1)),
+		k2: base64(v2),
+	}
+	tassert.Equal(t, expected, unmarshalStringMap(t, data))
+}
+
+func TestGetSecretWithByNameFetchingPolicyAndVersionID(t *testing.T) {
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	authorizedKey := newFakeAuthorizedKey()
+
+	fakeClock := clock.NewFakeClock()
+	fakeLockboxServer := client.NewFakeLockboxServer(fakeClock, time.Hour)
+	folderId := uuid.NewString()
+	const secretName = "secretName"
+	oldKey, oldVal := "oldKey", "oldVal"
+	secretID, oldVersionID := fakeLockboxServer.CreateSecret(authorizedKey,
+		folderId, secretName,
+		textEntry(oldKey, oldVal),
+	)
+
+	k8sClient := clientfake.NewClientBuilder().Build()
+	const authorizedKeySecretName = "authorizedKeySecretName"
+	const authorizedKeySecretKey = "authorizedKeySecretKey"
+	err := createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, authorizedKey))
+	tassert.Nil(t, err)
+	store := newYandexLockboxSecretStoreWithFetchByName("", namespace, authorizedKeySecretName, authorizedKeySecretKey, folderId)
+	provider := newLockboxProvider(fakeClock, fakeLockboxServer)
+	secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
+	tassert.Nil(t, err)
+	data, err := secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secretName, Version: oldVersionID})
+	tassert.Nil(t, err)
+
+	tassert.Equal(t, map[string]string{oldKey: base64([]byte(oldVal))}, unmarshalStringMap(t, data))
+
+	newKey, newVal := "newKey", "newVal"
+	newVersionID := fakeLockboxServer.AddVersion(secretID, textEntry(newKey, newVal))
+
+	data, err = secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secretName, Version: oldVersionID})
+	tassert.Nil(t, err)
+	tassert.Equal(t, map[string]string{oldKey: base64([]byte(oldVal))}, unmarshalStringMap(t, data))
+
+	data, err = secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secretName, Version: newVersionID})
+	tassert.Nil(t, err)
+	tassert.Equal(t, map[string]string{newKey: base64([]byte(newVal))}, unmarshalStringMap(t, data))
+}
+
+func TestGetSecretWithByNameFetchingPolicyForTextEntry(t *testing.T) {
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	authorizedKey := newFakeAuthorizedKey()
+
+	fakeClock := clock.NewFakeClock()
+	fakeLockboxServer := client.NewFakeLockboxServer(fakeClock, time.Hour)
+	folderId := uuid.NewString()
+	const secretName = "secretName"
+	k1, v1 := "k1", "v1"
+	k2, v2 := "k2", []byte("v2")
+	_, _ = fakeLockboxServer.CreateSecret(authorizedKey,
+		folderId, secretName,
+		textEntry(k1, v1),
+		binaryEntry(k2, v2),
+	)
+
+	k8sClient := clientfake.NewClientBuilder().Build()
+	const authorizedKeySecretName = "authorizedKeySecretName"
+	const authorizedKeySecretKey = "authorizedKeySecretKey"
+	err := createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, authorizedKey))
+	tassert.Nil(t, err)
+	store := newYandexLockboxSecretStoreWithFetchByName("", namespace, authorizedKeySecretName, authorizedKeySecretKey, folderId)
+	provider := newLockboxProvider(fakeClock, fakeLockboxServer)
+	secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
+	tassert.Nil(t, err)
+
+	data, err := secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secretName, Property: k1})
+	tassert.Nil(t, err)
+	tassert.Equal(t, v1, string(data))
+}
+
+func TestGetSecretWithByNameFetchingPolicyForBinaryEntry(t *testing.T) {
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	authorizedKey := newFakeAuthorizedKey()
+
+	fakeClock := clock.NewFakeClock()
+	fakeLockboxServer := client.NewFakeLockboxServer(fakeClock, time.Hour)
+	folderId := uuid.NewString()
+	const secretName = "secretName"
+	k1, v1 := "k1", "v1"
+	k2, v2 := "k2", []byte("v2")
+	_, _ = fakeLockboxServer.CreateSecret(authorizedKey,
+		folderId, secretName,
+		textEntry(k1, v1),
+		binaryEntry(k2, v2),
+	)
+
+	k8sClient := clientfake.NewClientBuilder().Build()
+	const authorizedKeySecretName = "authorizedKeySecretName"
+	const authorizedKeySecretKey = "authorizedKeySecretKey"
+	err := createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, authorizedKey))
+	tassert.Nil(t, err)
+	store := newYandexLockboxSecretStoreWithFetchByName("", namespace, authorizedKeySecretName, authorizedKeySecretKey, folderId)
+	provider := newLockboxProvider(fakeClock, fakeLockboxServer)
+	secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
+	tassert.Nil(t, err)
+
+	data, err := secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secretName, Property: k2})
+	tassert.Nil(t, err)
+	tassert.Equal(t, v2, data)
+}
+
+func TestGetSecretWithByNameFetchingPolicyNotFound(t *testing.T) {
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	authorizedKey := newFakeAuthorizedKey()
+
+	fakeClock := clock.NewFakeClock()
+	fakeLockboxServer := client.NewFakeLockboxServer(fakeClock, time.Hour)
+	folderId := uuid.NewString()
+	k8sClient := clientfake.NewClientBuilder().Build()
+	const authorizedKeySecretName = "authorizedKeySecretName"
+	const authorizedKeySecretKey = "authorizedKeySecretKey"
+	err := createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, authorizedKey))
+	tassert.Nil(t, err)
+	store := newYandexLockboxSecretStoreWithFetchByName("", namespace, authorizedKeySecretName, authorizedKeySecretKey, folderId)
+
+	provider := newLockboxProvider(fakeClock, fakeLockboxServer)
+	secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
+	tassert.Nil(t, err)
+
+	_, err = secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: "no-secret-with-such-name"})
+	tassert.EqualError(t, err, errSecretPayloadNotFound)
+	secretName := "secretName"
+	_, _ = fakeLockboxServer.CreateSecret(
+		authorizedKey,
+		folderId, secretName,
+		textEntry("k1", "v1"),
+	)
+	_, err = secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secretName, Version: "no-version-with-such-id"})
+	tassert.EqualError(t, err, errSecretPayloadVersionNotFound)
+}
+
+func TestGetSecretWithByNameFetchingPolicyUnauthorized(t *testing.T) {
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	authorizedKeyA := newFakeAuthorizedKey()
+	authorizedKeyB := newFakeAuthorizedKey()
+
+	fakeClock := clock.NewFakeClock()
+	fakeLockboxServer := client.NewFakeLockboxServer(fakeClock, time.Hour)
+	folderId := uuid.NewString()
+	secretName := "secretName"
+	_, _ = fakeLockboxServer.CreateSecret(authorizedKeyA,
+		folderId, secretName,
+		textEntry("k1", "v1"),
+	)
+
+	k8sClient := clientfake.NewClientBuilder().Build()
+	const authorizedKeySecretName = "authorizedKeySecretName"
+	const authorizedKeySecretKey = "authorizedKeySecretKey"
+	err := createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, authorizedKeyB))
+	tassert.Nil(t, err)
+	store := newYandexLockboxSecretStoreWithFetchByName("", namespace, authorizedKeySecretName, authorizedKeySecretKey, folderId)
+
+	provider := newLockboxProvider(fakeClock, fakeLockboxServer)
+	secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
+	tassert.Nil(t, err)
+	_, err = secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secretName})
+	tassert.EqualError(t, err, errSecretPayloadPermissionDenied)
+}
+
+func TestGetSecretWithByNameFetchingPolicyWithoutFolderID(t *testing.T) {
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	authorizedKey := newFakeAuthorizedKey()
+
+	fakeClock := clock.NewFakeClock()
+	fakeLockboxServer := client.NewFakeLockboxServer(fakeClock, time.Hour)
+
+	k8sClient := clientfake.NewClientBuilder().Build()
+	const authorizedKeySecretName = "authorizedKeySecretName"
+	const authorizedKeySecretKey = "authorizedKeySecretKey"
+	err := createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, authorizedKey))
+	tassert.Nil(t, err)
+	store := newYandexLockboxSecretStoreWithFetchByName("", namespace, authorizedKeySecretName, authorizedKeySecretKey, "")
+
+	provider := newLockboxProvider(fakeClock, fakeLockboxServer)
+	_, err = provider.NewClient(ctx, store, k8sClient, namespace)
+	tassert.EqualError(t, err, "folderID is required when fetching policy is 'byName'")
+}
+
+func TesGetSecretWithByIDFetchingPolicyForAllEntries(t *testing.T) {
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	authorizedKey := newFakeAuthorizedKey()
+
+	fakeClock := clock.NewFakeClock()
+	fakeLockboxServer := client.NewFakeLockboxServer(fakeClock, time.Hour)
+	k1, v1 := "k1", "v1"
+	k2, v2 := "k2", []byte("v2")
+	secretID, _ := fakeLockboxServer.CreateSecret(authorizedKey,
+		"folderId", "secret",
+		textEntry(k1, v1),
+		binaryEntry(k2, v2),
+	)
+
+	k8sClient := clientfake.NewClientBuilder().Build()
+	const authorizedKeySecretName = "authorizedKeySecretName"
+	const authorizedKeySecretKey = "authorizedKeySecretKey"
+	err := createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, authorizedKey))
+	tassert.Nil(t, err)
+	store := newYandexLockboxSecretStoreWithFetchByID("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
+	provider := newLockboxProvider(fakeClock, fakeLockboxServer)
+	secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
+	tassert.Nil(t, err)
+	data, err := secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secretID})
+	tassert.Nil(t, err)
+	expected := map[string]string{
+		k1: v1,
+		k2: base64(v2),
+	}
+	tassert.Equal(t, expected, unmarshalStringMap(t, data))
+}
+
+func TestGetSecretWithByIDFetchingPolicyForTextEntry(t *testing.T) {
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	authorizedKey := newFakeAuthorizedKey()
+
+	fakeClock := clock.NewFakeClock()
+	fakeLockboxServer := client.NewFakeLockboxServer(fakeClock, time.Hour)
+	k1, v1 := "k1", "v1"
+	k2, v2 := "k2", []byte("v2")
+	secretID, _ := fakeLockboxServer.CreateSecret(authorizedKey,
+		"folderId", "secret",
+		textEntry(k1, v1),
+		binaryEntry(k2, v2),
+	)
+
+	k8sClient := clientfake.NewClientBuilder().Build()
+	const authorizedKeySecretName = "authorizedKeySecretName"
+	const authorizedKeySecretKey = "authorizedKeySecretKey"
+	err := createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, authorizedKey))
+	tassert.Nil(t, err)
+	store := newYandexLockboxSecretStoreWithFetchByID("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
+	provider := newLockboxProvider(fakeClock, fakeLockboxServer)
+	secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
+	tassert.Nil(t, err)
+	data, err := secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secretID, Property: k1})
+	tassert.Nil(t, err)
+	tassert.Equal(t, v1, string(data))
+}
+
+func TestGetSecretWithByIDFetchingPolicyForBinaryEntry(t *testing.T) {
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	authorizedKey := newFakeAuthorizedKey()
+
+	fakeClock := clock.NewFakeClock()
+	fakeLockboxServer := client.NewFakeLockboxServer(fakeClock, time.Hour)
+	k1, v1 := "k1", "v1"
+	k2, v2 := "k2", []byte("v2")
+	secretID, _ := fakeLockboxServer.CreateSecret(authorizedKey,
+		"folderId", "secret",
+		textEntry(k1, v1),
+		binaryEntry(k2, v2),
+	)
+
+	k8sClient := clientfake.NewClientBuilder().Build()
+	const authorizedKeySecretName = "authorizedKeySecretName"
+	const authorizedKeySecretKey = "authorizedKeySecretKey"
+	err := createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, authorizedKey))
+	tassert.Nil(t, err)
+	store := newYandexLockboxSecretStoreWithFetchByID("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
+	provider := newLockboxProvider(fakeClock, fakeLockboxServer)
+	secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
+	tassert.Nil(t, err)
+	data, err := secretsClient.GetSecret(ctx, esv1.ExternalSecretDataRemoteRef{Key: secretID, Property: k2})
+	tassert.Nil(t, err)
+	tassert.Equal(t, v2, data)
+}
+
+func TestGetSecretWithInvalidFetchingPolicy(t *testing.T) {
+	ctx := context.Background()
+	namespace := uuid.NewString()
+	authorizedKey := newFakeAuthorizedKey()
+
+	fakeClock := clock.NewFakeClock()
+	fakeLockboxServer := client.NewFakeLockboxServer(fakeClock, time.Hour)
+	k8sClient := clientfake.NewClientBuilder().Build()
+	const authorizedKeySecretName = "authorizedKeySecretName"
+	const authorizedKeySecretKey = "authorizedKeySecretKey"
+	err := createK8sSecret(
+		ctx, t, k8sClient, namespace,
+		authorizedKeySecretName, authorizedKeySecretKey,
+		toJSON(t, authorizedKey),
+	)
+	tassert.Nil(t, err)
+	store := &esv1.SecretStore{
+		ObjectMeta: metav1.ObjectMeta{Namespace: namespace},
+		Spec: esv1.SecretStoreSpec{
+			Provider: &esv1.SecretStoreProvider{
+				YandexLockbox: &esv1.YandexLockboxProvider{
+					Auth: esv1.YandexAuth{
+						AuthorizedKey: esmeta.SecretKeySelector{
+							Name: authorizedKeySecretName,
+							Key:  authorizedKeySecretKey,
+						},
+					},
+					FetchingPolicy: &esv1.FetchingPolicy{
+						ByID:   nil,
+						ByName: nil,
+					},
+				},
+			},
+		},
+	}
+	provider := newLockboxProvider(fakeClock, fakeLockboxServer)
+	_, err = provider.NewClient(ctx, store, k8sClient, namespace)
+	tassert.EqualError(
+		t,
+		err,
+		"invalid Yandex Lockbox SecretStore: requires either 'byName' or 'byID' policy",
+	)
+}
+
+// helper fuxnctions
 
 func newLockboxProvider(clock clock.Clock, fakeLockboxServer *client.FakeLockboxServer) *common.YandexCloudProvider {
 	return common.InitYandexCloudProvider(
@@ -632,6 +1003,56 @@ func newYandexLockboxSecretStore(apiEndpoint, namespace, authorizedKeySecretName
 	}
 }
 
+func newYandexLockboxSecretStoreWithFetchByName(apiEndpoint, namespace, authorizedKeySecretName, authorizedKeySecretKey, folderID string) esv1.GenericStore {
+	return &esv1.SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: namespace,
+		},
+		Spec: esv1.SecretStoreSpec{
+			Provider: &esv1.SecretStoreProvider{
+				YandexLockbox: &esv1.YandexLockboxProvider{
+					APIEndpoint: apiEndpoint,
+					Auth: esv1.YandexAuth{
+						AuthorizedKey: esmeta.SecretKeySelector{
+							Name: authorizedKeySecretName,
+							Key:  authorizedKeySecretKey,
+						},
+					},
+					FetchingPolicy: &esv1.FetchingPolicy{
+						ByName: &esv1.ByName{
+							FolderID: folderID,
+						},
+					},
+				},
+			},
+		},
+	}
+}
+
+func newYandexLockboxSecretStoreWithFetchByID(apiEndpoint, namespace, authorizedKeySecretName, authorizedKeySecretKey string) esv1.GenericStore {
+	return &esv1.SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: namespace,
+		},
+		Spec: esv1.SecretStoreSpec{
+			Provider: &esv1.SecretStoreProvider{
+				YandexLockbox: &esv1.YandexLockboxProvider{
+					APIEndpoint: apiEndpoint,
+					Auth: esv1.YandexAuth{
+						AuthorizedKey: esmeta.SecretKeySelector{
+							Name: authorizedKeySecretName,
+							Key:  authorizedKeySecretKey,
+						},
+					},
+					FetchingPolicy: &esv1.FetchingPolicy{
+						ByID: &esv1.ByID{},
+					},
+				},
+			},
+		},
+	}
+}
+
 func toJSON(t *testing.T, v any) []byte {
 	jsonBytes, err := json.Marshal(v)
 	tassert.Nil(t, err)

+ 30 - 7
pkg/provider/yandex/lockbox/lockboxsecretgetter.go

@@ -36,12 +36,11 @@ func newLockboxSecretGetter(lockboxClient client.LockboxClient) (common.SecretGe
 	}, nil
 }
 
-func (g *lockboxSecretGetter) GetSecret(ctx context.Context, iamToken, resourceID, versionID, property string) ([]byte, error) {
-	entries, err := g.lockboxClient.GetPayloadEntries(ctx, iamToken, resourceID, versionID)
+func (g *lockboxSecretGetter) GetSecret(ctx context.Context, iamToken, resourceKey string, resourceKeyType common.ResourceKeyType, folderID, versionID, property string) ([]byte, error) {
+	entries, err := g.fetchPayloadEntries(ctx, iamToken, resourceKey, resourceKeyType, folderID, versionID)
 	if err != nil {
 		return nil, fmt.Errorf("unable to request secret payload to get secret: %w", err)
 	}
-
 	if property == "" {
 		keyToValue := make(map[string]any, len(entries))
 		for _, entry := range entries {
@@ -57,7 +56,6 @@ func (g *lockboxSecretGetter) GetSecret(ctx context.Context, iamToken, resourceI
 		}
 		return out, nil
 	}
-
 	entry, err := findEntryByKey(entries, property)
 	if err != nil {
 		return nil, err
@@ -65,12 +63,11 @@ func (g *lockboxSecretGetter) GetSecret(ctx context.Context, iamToken, resourceI
 	return getValueAsBinary(entry)
 }
 
-func (g *lockboxSecretGetter) GetSecretMap(ctx context.Context, iamToken, resourceID, versionID string) (map[string][]byte, error) {
-	entries, err := g.lockboxClient.GetPayloadEntries(ctx, iamToken, resourceID, versionID)
+func (g *lockboxSecretGetter) GetSecretMap(ctx context.Context, iamToken, resourceKey string, resourceKeyType common.ResourceKeyType, folderID, versionID string) (map[string][]byte, error) {
+	entries, err := g.fetchPayloadEntries(ctx, iamToken, resourceKey, resourceKeyType, folderID, versionID)
 	if err != nil {
 		return nil, fmt.Errorf("unable to request secret payload to get secret map: %w", err)
 	}
-
 	secretMap := make(map[string][]byte, len(entries))
 	for _, entry := range entries {
 		value, err := getValueAsBinary(entry)
@@ -82,6 +79,21 @@ func (g *lockboxSecretGetter) GetSecretMap(ctx context.Context, iamToken, resour
 	return secretMap, nil
 }
 
+func (g *lockboxSecretGetter) fetchPayloadEntries(ctx context.Context, iamToken, resourceKey string, resourceKeyType common.ResourceKeyType, folderID, versionID string) ([]*lockbox.Payload_Entry, error) {
+	switch resourceKeyType {
+	case common.ResourceKeyTypeId:
+		return g.lockboxClient.GetPayloadEntries(ctx, iamToken, resourceKey, versionID)
+	case common.ResourceKeyTypeName:
+		entriesMap, err := g.lockboxClient.GetExPayload(ctx, iamToken, folderID, resourceKey, versionID)
+		if err != nil {
+			return nil, err
+		}
+		return convertToPayloadEntries(entriesMap), nil
+	default:
+		return nil, fmt.Errorf("unsupported resource key type: %v", resourceKeyType)
+	}
+}
+
 func getValueAsIs(entry *lockbox.Payload_Entry) (any, error) {
 	switch entry.Value.(type) {
 	case *lockbox.Payload_Entry_TextValue:
@@ -112,3 +124,14 @@ func findEntryByKey(entries []*lockbox.Payload_Entry, key string) (*lockbox.Payl
 	}
 	return nil, fmt.Errorf("payload entry with key '%s' not found", key)
 }
+
+func convertToPayloadEntries(entriesMap map[string][]byte) []*lockbox.Payload_Entry {
+	entries := make([]*lockbox.Payload_Entry, 0, len(entriesMap))
+	for key, value := range entriesMap {
+		entries = append(entries, &lockbox.Payload_Entry{
+			Key:   key,
+			Value: &lockbox.Payload_Entry_BinaryValue{BinaryValue: value},
+		})
+	}
+	return entries
+}

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

@@ -795,6 +795,10 @@ spec:
           key: string
           name: string
           namespace: string
+      fetching:
+        byID: {}
+        byName:
+          folderID: string
     yandexlockbox:
       apiEndpoint: string
       auth:
@@ -807,6 +811,10 @@ spec:
           key: string
           name: string
           namespace: string
+      fetching:
+        byID: {}
+        byName:
+          folderID: string
   refreshInterval: 1
   retrySettings:
     maxRetries: 1

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

@@ -795,6 +795,10 @@ spec:
           key: string
           name: string
           namespace: string
+      fetching:
+        byID: {}
+        byName:
+          folderID: string
     yandexlockbox:
       apiEndpoint: string
       auth:
@@ -807,6 +811,10 @@ spec:
           key: string
           name: string
           namespace: string
+      fetching:
+        byID: {}
+        byName:
+          folderID: string
   refreshInterval: 1
   retrySettings:
     maxRetries: 1