Browse Source

feat(provider): add Volcengine provider support (#5306)

Add support for Volcengine (BytePlus) as a new provider for External Secrets. This includes:
- Implementation of the provider with support for both static credentials and IRSA authentication
- Documentation and examples for configuration
- CRD updates and test cases
- Integration with Volcengine KMS for secret management

Signed-off-by: yanfuhai <yanfuhai@bytedance.com>
Co-authored-by: Riccardo M. Cefala <riccardo@cefala.net>
Kevin Yan 6 months ago
parent
commit
29bcac14d1

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

@@ -210,6 +210,10 @@ type SecretStoreProvider struct {
 	// CloudruSM configures this store to sync secrets using the Cloud.ru Secret Manager provider
 	// +optional
 	CloudruSM *CloudruSMProvider `json:"cloudrusm,omitempty"`
+
+	// Volcengine configures this store to sync secrets using the Volcengine provider
+	// +optional
+	Volcengine *VolcengineProvider `json:"volcengine,omitempty"`
 }
 
 type CAProviderType string

+ 54 - 0
apis/externalsecrets/v1/secretstore_volcengine_types.go

@@ -0,0 +1,54 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1
+
+import (
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+)
+
+// VolcengineProvider defines the configuration for the Volcengine provider.
+type VolcengineProvider struct {
+	// Region specifies the Volcengine region to connect to.
+	Region string `json:"region"`
+
+	// Auth defines the authentication method to use.
+	// If not specified, the provider will try to use IRSA (IAM Role for Service Account).
+	// +optional
+	Auth *VolcengineAuth `json:"auth,omitempty"`
+}
+
+// VolcengineAuth defines the authentication method for the Volcengine provider.
+// Only one of the fields should be set.
+type VolcengineAuth struct {
+	// SecretRef defines the static credentials to use for authentication.
+	// If not set, IRSA is used.
+	// +optional
+	SecretRef *VolcengineAuthSecretRef `json:"secretRef,omitempty"`
+}
+
+// VolcengineAuthSecretRef defines the secret reference for static credentials.
+type VolcengineAuthSecretRef struct {
+	// AccessKeyID is the reference to the secret containing the Access Key ID.
+	AccessKeyID esmeta.SecretKeySelector `json:"accessKeyID"`
+
+	// SecretAccessKey is the reference to the secret containing the Secret Access Key.
+	SecretAccessKey esmeta.SecretKeySelector `json:"secretAccessKey"`
+
+	// Token is the reference to the secret containing the STS(Security Token Service) Token.
+	// +optional
+	Token *esmeta.SecretKeySelector `json:"token,omitempty"`
+}

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

@@ -3304,6 +3304,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
 		*out = new(CloudruSMProvider)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.Volcengine != nil {
+		in, out := &in.Volcengine, &out.Volcengine
+		*out = new(VolcengineProvider)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreProvider.
@@ -4044,6 +4049,68 @@ func (in *VaultUserPassAuth) DeepCopy() *VaultUserPassAuth {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VolcengineAuth) DeepCopyInto(out *VolcengineAuth) {
+	*out = *in
+	if in.SecretRef != nil {
+		in, out := &in.SecretRef, &out.SecretRef
+		*out = new(VolcengineAuthSecretRef)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolcengineAuth.
+func (in *VolcengineAuth) DeepCopy() *VolcengineAuth {
+	if in == nil {
+		return nil
+	}
+	out := new(VolcengineAuth)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VolcengineAuthSecretRef) DeepCopyInto(out *VolcengineAuthSecretRef) {
+	*out = *in
+	in.AccessKeyID.DeepCopyInto(&out.AccessKeyID)
+	in.SecretAccessKey.DeepCopyInto(&out.SecretAccessKey)
+	if in.Token != nil {
+		in, out := &in.Token, &out.Token
+		*out = new(apismetav1.SecretKeySelector)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolcengineAuthSecretRef.
+func (in *VolcengineAuthSecretRef) DeepCopy() *VolcengineAuthSecretRef {
+	if in == nil {
+		return nil
+	}
+	out := new(VolcengineAuthSecretRef)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VolcengineProvider) DeepCopyInto(out *VolcengineProvider) {
+	*out = *in
+	if in.Auth != nil {
+		in, out := &in.Auth, &out.Auth
+		*out = new(VolcengineAuth)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolcengineProvider.
+func (in *VolcengineProvider) DeepCopy() *VolcengineProvider {
+	if in == nil {
+		return nil
+	}
+	out := new(VolcengineProvider)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *WebhookCAProvider) DeepCopyInto(out *WebhookCAProvider) {
 	*out = *in
 	if in.Namespace != nil {

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

@@ -5004,6 +5004,116 @@ spec:
                     required:
                     - server
                     type: object
+                  volcengine:
+                    description: Volcengine configures this store to sync secrets
+                      using the Volcengine provider
+                    properties:
+                      auth:
+                        description: |-
+                          Auth defines the authentication method to use.
+                          If not specified, the provider will try to use IRSA (IAM Role for Service Account).
+                        properties:
+                          secretRef:
+                            description: |-
+                              SecretRef defines the static credentials to use for authentication.
+                              If not set, IRSA is used.
+                            properties:
+                              accessKeyID:
+                                description: AccessKeyID is the reference to the secret
+                                  containing the Access Key ID.
+                                properties:
+                                  key:
+                                    description: |-
+                                      A key in the referenced Secret.
+                                      Some instances of this field may be defaulted, in others it may be required.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[-._a-zA-Z0-9]+$
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      The namespace of the Secret resource being referred to.
+                                      Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                    maxLength: 63
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                    type: string
+                                type: object
+                              secretAccessKey:
+                                description: SecretAccessKey is the reference to the
+                                  secret containing the Secret Access Key.
+                                properties:
+                                  key:
+                                    description: |-
+                                      A key in the referenced Secret.
+                                      Some instances of this field may be defaulted, in others it may be required.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[-._a-zA-Z0-9]+$
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      The namespace of the Secret resource being referred to.
+                                      Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                    maxLength: 63
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                    type: string
+                                type: object
+                              token:
+                                description: Token is the reference to the secret
+                                  containing the STS(Security Token Service) Token.
+                                properties:
+                                  key:
+                                    description: |-
+                                      A key in the referenced Secret.
+                                      Some instances of this field may be defaulted, in others it may be required.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[-._a-zA-Z0-9]+$
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      The namespace of the Secret resource being referred to.
+                                      Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                    maxLength: 63
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                    type: string
+                                type: object
+                            required:
+                            - accessKeyID
+                            - secretAccessKey
+                            type: object
+                        type: object
+                      region:
+                        description: Region specifies the Volcengine region to connect
+                          to.
+                        type: string
+                    required:
+                    - region
+                    type: object
                   webhook:
                     description: Webhook configures this store to sync secrets using
                       a generic templated webhook

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

@@ -5004,6 +5004,116 @@ spec:
                     required:
                     - server
                     type: object
+                  volcengine:
+                    description: Volcengine configures this store to sync secrets
+                      using the Volcengine provider
+                    properties:
+                      auth:
+                        description: |-
+                          Auth defines the authentication method to use.
+                          If not specified, the provider will try to use IRSA (IAM Role for Service Account).
+                        properties:
+                          secretRef:
+                            description: |-
+                              SecretRef defines the static credentials to use for authentication.
+                              If not set, IRSA is used.
+                            properties:
+                              accessKeyID:
+                                description: AccessKeyID is the reference to the secret
+                                  containing the Access Key ID.
+                                properties:
+                                  key:
+                                    description: |-
+                                      A key in the referenced Secret.
+                                      Some instances of this field may be defaulted, in others it may be required.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[-._a-zA-Z0-9]+$
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      The namespace of the Secret resource being referred to.
+                                      Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                    maxLength: 63
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                    type: string
+                                type: object
+                              secretAccessKey:
+                                description: SecretAccessKey is the reference to the
+                                  secret containing the Secret Access Key.
+                                properties:
+                                  key:
+                                    description: |-
+                                      A key in the referenced Secret.
+                                      Some instances of this field may be defaulted, in others it may be required.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[-._a-zA-Z0-9]+$
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      The namespace of the Secret resource being referred to.
+                                      Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                    maxLength: 63
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                    type: string
+                                type: object
+                              token:
+                                description: Token is the reference to the secret
+                                  containing the STS(Security Token Service) Token.
+                                properties:
+                                  key:
+                                    description: |-
+                                      A key in the referenced Secret.
+                                      Some instances of this field may be defaulted, in others it may be required.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[-._a-zA-Z0-9]+$
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    maxLength: 253
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      The namespace of the Secret resource being referred to.
+                                      Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                    maxLength: 63
+                                    minLength: 1
+                                    pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                    type: string
+                                type: object
+                            required:
+                            - accessKeyID
+                            - secretAccessKey
+                            type: object
+                        type: object
+                      region:
+                        description: Region specifies the Volcengine region to connect
+                          to.
+                        type: string
+                    required:
+                    - region
+                    type: object
                   webhook:
                     description: Webhook configures this store to sync secrets using
                       a generic templated webhook

+ 204 - 0
deploy/crds/bundle.yaml

@@ -6710,6 +6710,108 @@ spec:
                       required:
                         - server
                       type: object
+                    volcengine:
+                      description: Volcengine configures this store to sync secrets using the Volcengine provider
+                      properties:
+                        auth:
+                          description: |-
+                            Auth defines the authentication method to use.
+                            If not specified, the provider will try to use IRSA (IAM Role for Service Account).
+                          properties:
+                            secretRef:
+                              description: |-
+                                SecretRef defines the static credentials to use for authentication.
+                                If not set, IRSA is used.
+                              properties:
+                                accessKeyID:
+                                  description: AccessKeyID is the reference to the secret containing the Access Key ID.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                                secretAccessKey:
+                                  description: SecretAccessKey is the reference to the secret containing the Secret Access Key.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                                token:
+                                  description: Token is the reference to the secret containing the STS(Security Token Service) Token.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                              required:
+                                - accessKeyID
+                                - secretAccessKey
+                              type: object
+                          type: object
+                        region:
+                          description: Region specifies the Volcengine region to connect to.
+                          type: string
+                      required:
+                        - region
+                      type: object
                     webhook:
                       description: Webhook configures this store to sync secrets using a generic templated webhook
                       properties:
@@ -17725,6 +17827,108 @@ spec:
                       required:
                         - server
                       type: object
+                    volcengine:
+                      description: Volcengine configures this store to sync secrets using the Volcengine provider
+                      properties:
+                        auth:
+                          description: |-
+                            Auth defines the authentication method to use.
+                            If not specified, the provider will try to use IRSA (IAM Role for Service Account).
+                          properties:
+                            secretRef:
+                              description: |-
+                                SecretRef defines the static credentials to use for authentication.
+                                If not set, IRSA is used.
+                              properties:
+                                accessKeyID:
+                                  description: AccessKeyID is the reference to the secret containing the Access Key ID.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                                secretAccessKey:
+                                  description: SecretAccessKey is the reference to the secret containing the Secret Access Key.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                                token:
+                                  description: Token is the reference to the secret containing the STS(Security Token Service) Token.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        A key in the referenced Secret.
+                                        Some instances of this field may be defaulted, in others it may be required.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[-._a-zA-Z0-9]+$
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      maxLength: 253
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        The namespace of the Secret resource being referred to.
+                                        Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.
+                                      maxLength: 63
+                                      minLength: 1
+                                      pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                                      type: string
+                                  type: object
+                              required:
+                                - accessKeyID
+                                - secretAccessKey
+                              type: object
+                          type: object
+                        region:
+                          description: Region specifies the Volcengine region to connect to.
+                          type: string
+                      required:
+                        - region
+                      type: object
                     webhook:
                       description: Webhook configures this store to sync secrets using a generic templated webhook
                       properties:

+ 153 - 0
docs/api/spec.md

@@ -8828,6 +8828,20 @@ CloudruSMProvider
 <p>CloudruSM configures this store to sync secrets using the Cloud.ru Secret Manager provider</p>
 </td>
 </tr>
+<tr>
+<td>
+<code>volcengine</code></br>
+<em>
+<a href="#external-secrets.io/v1.VolcengineProvider">
+VolcengineProvider
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>Volcengine configures this store to sync secrets using the Volcengine provider</p>
+</td>
+</tr>
 </tbody>
 </table>
 <h3 id="external-secrets.io/v1.SecretStoreRef">SecretStoreRef
@@ -10984,6 +10998,145 @@ method</p>
 </tr>
 </tbody>
 </table>
+<h3 id="external-secrets.io/v1.VolcengineAuth">VolcengineAuth
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1.VolcengineProvider">VolcengineProvider</a>)
+</p>
+<p>
+<p>VolcengineAuth defines the authentication method for the Volcengine provider.
+Only one of the fields should be set.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>secretRef</code></br>
+<em>
+<a href="#external-secrets.io/v1.VolcengineAuthSecretRef">
+VolcengineAuthSecretRef
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>SecretRef defines the static credentials to use for authentication.
+If not set, IRSA is used.</p>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1.VolcengineAuthSecretRef">VolcengineAuthSecretRef
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1.VolcengineAuth">VolcengineAuth</a>)
+</p>
+<p>
+<p>VolcengineAuthSecretRef defines the secret reference for static credentials.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>accessKeyID</code></br>
+<em>
+<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#SecretKeySelector">
+External Secrets meta/v1.SecretKeySelector
+</a>
+</em>
+</td>
+<td>
+<p>AccessKeyID is the reference to the secret containing the Access Key ID.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>secretAccessKey</code></br>
+<em>
+<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#SecretKeySelector">
+External Secrets meta/v1.SecretKeySelector
+</a>
+</em>
+</td>
+<td>
+<p>SecretAccessKey is the reference to the secret containing the Secret Access Key.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>token</code></br>
+<em>
+<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#SecretKeySelector">
+External Secrets meta/v1.SecretKeySelector
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>Token is the reference to the secret containing the STS(Security Token Service) Token.</p>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1.VolcengineProvider">VolcengineProvider
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1.SecretStoreProvider">SecretStoreProvider</a>)
+</p>
+<p>
+<p>VolcengineProvider defines the configuration for the Volcengine provider.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>region</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<p>Region specifies the Volcengine region to connect to.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>auth</code></br>
+<em>
+<a href="#external-secrets.io/v1.VolcengineAuth">
+VolcengineAuth
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>Auth defines the authentication method to use.
+If not specified, the provider will try to use IRSA (IAM Role for Service Account).</p>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1.WebhookCAProvider">WebhookCAProvider
 </h3>
 <p>

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

@@ -90,6 +90,7 @@ The following table describes the stability level of each provider and who's res
 | [Bitwarden Secrets Manager](https://external-secrets.io/latest/provider/bitwarden-secrets-manager)         | alpha     | [@skarlso](https://github.com/Skarlso)                                                              |
 | [Previder](https://external-secrets.io/latest/provider/previder)                                           | stable    | [@previder](https://github.com/previder)                                                            |
 | [Cloud.ru](https://external-secrets.io/latest/provider/cloudru)                                            | alpha     | [@default23](https://github.com/default23)                                                          |
+| [Volcengine](https://external-secrets.io/latest/provider/volcengine)                                       | alpha     | [@kevinyancn](https://github.com/kevinyancn)                                                        |
 
 
 ## Provider Feature Support
@@ -128,6 +129,7 @@ The following table show the support for features across different providers.
 | Bitwarden Secrets Manager |      x       |              |                      |                         |        x         |      x      |              x              |
 | Previder                  |      x       |              |                      |                         |        x         |             |                             |
 | Cloud.ru                  |      x       |      x       |                      |            x            |        x         |             |              x              |
+| Volcengine                |              |              |                      |                         |        x         |             |                             |
 
 ## Support Policy
 

+ 142 - 0
docs/provider/volcengine.md

@@ -0,0 +1,142 @@
+# Volcengine Provider
+
+## Quick start
+
+This guide demonstrates how to use the Volcengine (BytePlus) provider.
+
+### Step 1 
+
+Create a secret in the [Volcengine KMS](https://console.volcengine.com/kms).
+
+### Step 2
+
+Create a `SecretStore`.
+
+#### Case 1: IRSA is not enabled
+
+You need to provide a Kubernetes `Secret` containing the credentials (Access Key ID, Secret Access Key and STS token) for accessing Volcengine KMS.
+
+```yaml
+apiVersion: v1
+kind: Secret
+metadata:
+  name: volcengine-creds
+type: Opaque
+data:
+  accessKeyID: YOUR_ACCESS_KEY_ID_IN_BASE64
+  secretAccessKey: YOUR_SECRET_ACCESS_KEY_IN_BASE64
+  sts-token: YOUR_STS_TOKEN_IN_BASE64 # Optional
+---
+apiVersion: external-secrets.io/v1
+kind: SecretStore
+metadata:
+  name: volcengine-kms
+spec:
+  provider:
+    volcengine:
+      # Region (Required)
+      region: "cn-beijing"
+      auth:
+        secretRef:
+          accessKeyID:
+            name: volcengine-creds
+            key: accessKeyID
+          secretAccessKey:
+            name: volcengine-creds
+            key: secretAccessKey
+          # (Optional, provide the Secret reference for the STS token if you are using one)
+          token:
+            name: volcengine-creds
+            key: sts-token
+```
+
+#### Case 2: IRSA is enabled
+
+When the `auth` block is not specified or does not contain secretRef, IRSA is enabled by default. 
+
+```yaml
+apiVersion: external-secrets.io/v1
+kind: SecretStore
+metadata:
+  name: volcengine-kms
+spec:
+  provider:
+    volcengine:
+      # Region (Required)
+      region: "cn-beijing"
+```
+
+Add service account and environment variables in helm `values.yaml` as below to enable IRSA.
+
+```yaml
+# Environment variables of external-secrets Pod
+extraEnv:
+  - name: VOLCENGINE_OIDC_ROLE_TRN
+    value: "YOUR_ROLE_TRN"
+  - name: VOLCENGINE_OIDC_TOKEN_FILE
+    value: "/var/run/secrets/vke.volcengine.com/irsa-tokens/token"
+# Volume mounts of external-secrets Pod
+extraVolumeMounts:
+- mountPath: /var/run/secrets/vke.volcengine.com/irsa-tokens
+  name: irsa-oidc-token
+  readOnly: true
+extraVolumes:
+- name: irsa-oidc-token
+  projected:
+    defaultMode: 420
+    sources:
+    - serviceAccountToken:
+        audience: sts.volcengine.com
+        expirationSeconds: 3600
+        path: token
+# Service account of external-secrets Pod
+serviceAccount:
+  name: "YOUR_SERVICE_ACCOUNT_NAME"
+```
+
+Note:
+
+- Ensure that your role has the permission `KMSFullAccess` .
+
+### Step 3
+
+Create `ExternalSecret`.
+
+#### Case 1: Get the entire Secret (JSON format) from the secret manager and extract a single property
+
+```yaml
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  name: my-app-secret
+spec:
+  secretStoreRef:
+    name: volcengine-kms
+    kind: SecretStore
+  target:
+    name: db-credentials
+  data:
+  - secretKey: password
+    remoteRef:
+      key: "my-app/db/credentials" # The name of the secret in the secret manager
+      property: "password" # The field name in the JSON
+```
+
+#### Case 2: Do not specify a property, get the entire Secret from the secret manager and sync all its key-value pairs
+
+```yaml
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  name: my-app-secret
+spec:
+  secretStoreRef:
+    name: volcengine-kms
+    kind: SecretStore
+  target:
+    name: db-credentials
+  data:
+  - secretKey: password
+    remoteRef:
+      key: "my-app/db/credentials" # The name of the secret in the secret manager
+```

+ 23 - 0
docs/snippets/volcengine-external-secrets.yaml

@@ -0,0 +1,23 @@
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  name: my-app-secret
+spec:
+  secretStoreRef:
+    name: volcengine-kms
+    kind: SecretStore
+
+  target:
+    name: db-credentials # The name of the K8s Secret to be created/updated
+
+  data:
+  # Case 1: Get the entire Secret (JSON format) from the secret manager and extract a single property
+  - secretKey: password
+    remoteRef:
+      key: "my-app/db/credentials" # The name of the secret in the secret manager
+      property: "password" # The field name in the JSON
+
+  # Case 2: Do not specify a property, get the entire Secret from the secret manager and sync all its key-value pairs
+  - secretKey: password
+    remoteRef:
+      key: "my-app/db/credentials"

+ 29 - 0
docs/snippets/volcengine-secret-store.yaml

@@ -0,0 +1,29 @@
+apiVersion: external-secrets.io/v1
+kind: SecretStore
+metadata:
+  name: volcengine-kms
+spec:
+  provider:
+    volcengine:
+      # Region (Required)
+      region: "cn-beijing"
+      
+      # Authentication (Choose one)
+      auth:
+        # Method 1: IRSA (Recommended)
+        # When the auth block is empty or does not contain secretRef, IRSA is enabled by default.
+        # The Pod's ServiceAccount must be associated with an IAM Role via Annotation,
+        # and the VOLCENGINE_ROLE_TRN and VOLCENGINE_OIDC_TOKEN_FILE environment variables must be injected into the ESO Pod.
+        
+        # Method 2: Static Credentials
+        secretRef:
+          accessKeyID:
+            name: volcengine-creds
+            key: accessKeyID
+          secretAccessKey:
+            name: volcengine-creds
+            key: secretAccessKey
+          # (Optional, provide the Secret reference for the STS token if you are using one)
+          token:
+            name: volcengine-creds
+            key: sts-token

+ 2 - 0
go.mod

@@ -114,6 +114,7 @@ require (
 	github.com/sethvargo/go-password v0.3.1
 	github.com/spf13/pflag v1.0.10
 	github.com/tidwall/sjson v1.2.5
+	github.com/volcengine/volcengine-go-sdk v1.1.33
 	gitlab.com/gitlab-org/api/client-go v0.144.1
 	k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912
 	sigs.k8s.io/yaml v1.6.0
@@ -243,6 +244,7 @@ require (
 	github.com/tetratelabs/wazero v1.9.0 // indirect
 	github.com/texttheater/golang-levenshtein v1.0.1 // indirect
 	github.com/tjfoc/gmsm v1.4.1 // indirect
+	github.com/volcengine/volc-sdk-golang v1.0.23 // indirect
 	github.com/x448/float16 v0.8.4 // indirect
 	github.com/xanzy/ssh-agent v0.3.3 // indirect
 	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect

+ 6 - 0
go.sum

@@ -233,6 +233,7 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
 github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
 github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
+github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
 github.com/avast/retry-go/v4 v4.6.1 h1:VkOLRubHdisGrHnTu89g08aQEWEgRU7LVEop3GbIcMk=
 github.com/avast/retry-go/v4 v4.6.1/go.mod h1:V6oF8njAwxJ5gRo1Q7Cxab24xs5NCWZBeaHHBklR8mA=
 github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
@@ -923,6 +924,10 @@ github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaO
 github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
 github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
 github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
+github.com/volcengine/volc-sdk-golang v1.0.23 h1:anOslb2Qp6ywnsbyq9jqR0ljuO63kg9PY+4OehIk5R8=
+github.com/volcengine/volc-sdk-golang v1.0.23/go.mod h1:AfG/PZRUkHJ9inETvbjNifTDgut25Wbkm2QoYBTbvyU=
+github.com/volcengine/volcengine-go-sdk v1.1.33 h1:qqiG/QVtJJN8fEzjpne5oP40sbazZl7dNlcriyEwH1E=
+github.com/volcengine/volcengine-go-sdk v1.1.33/go.mod h1:oxoVo+A17kvkwPkIeIHPVLjSw7EQAm+l/Vau1YGHN+A=
 github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
 github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
 github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
@@ -1438,6 +1443,7 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
 google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

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

@@ -153,6 +153,7 @@ nav:
       - Infisical: provider/infisical.md
       - Previder: provider/previder.md
       - OpenBao: provider/openbao.md
+      - Volcengine: provider/volcengine.md
   - Examples:
       - FluxCD: examples/gitops-using-fluxcd.md
       - Anchore Engine: examples/anchore-engine-credentials.md

+ 1 - 0
pkg/provider/register/register.go

@@ -52,6 +52,7 @@ import (
 	_ "github.com/external-secrets/external-secrets/pkg/provider/secretserver"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/senhasegura"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/vault"
+	_ "github.com/external-secrets/external-secrets/pkg/provider/volcengine"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/webhook"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/yandex/certificatemanager"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox"

+ 101 - 0
pkg/provider/volcengine/auth.go

@@ -0,0 +1,101 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package volcengine
+
+import (
+	"context"
+	"errors"
+	"fmt"
+
+	"github.com/volcengine/volcengine-go-sdk/volcengine"
+	"github.com/volcengine/volcengine-go-sdk/volcengine/credentials"
+	"github.com/volcengine/volcengine-go-sdk/volcengine/session"
+	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+)
+
+// NewSession creates a new Volcengine session based on the provider configuration.
+// It follows the credential chain:
+// 1. Static credentials from a Kubernetes secret (if specified in auth.secretRef).
+// 2. IRSA (IAM Role for Service Account) via environment variables (if auth.secretRef is not specified).
+func NewSession(ctx context.Context, provider *esv1.VolcengineProvider, kube client.Client, namespace string) (*session.Session, error) {
+	if provider == nil {
+		return nil, errors.New("volcengine provider can not be nil")
+	}
+	if provider.Region == "" {
+		return nil, errors.New("region must be specified")
+	}
+
+	var creds *credentials.Credentials
+
+	if provider.Auth != nil && provider.Auth.SecretRef != nil {
+		// If SecretRef is provided, use static credentials.
+		accessKeyID, err := getSecretValue(ctx, kube, namespace, provider.Auth.SecretRef.AccessKeyID)
+		if err != nil {
+			return nil, fmt.Errorf("failed to get accessKeyID: %w", err)
+		}
+		secretAccessKey, err := getSecretValue(ctx, kube, namespace, provider.Auth.SecretRef.SecretAccessKey)
+		if err != nil {
+			return nil, fmt.Errorf("failed to get secretAccessKey: %w", err)
+		}
+		token := []byte{}
+		if provider.Auth.SecretRef.Token != nil {
+			token, err = getSecretValue(ctx, kube, namespace, *provider.Auth.SecretRef.Token)
+			if err != nil {
+				return nil, fmt.Errorf("failed to get token: %w", err)
+			}
+		}
+		creds = credentials.NewStaticCredentials(string(accessKeyID), string(secretAccessKey), string(token))
+	} else {
+		// If SecretRef is not provided, automatically use the default credential chain,
+		// which includes environment variables and IRSA.
+		creds = credentials.NewCredentials(credentials.NewOIDCCredentialsProviderFromEnv())
+	}
+
+	config := volcengine.NewConfig().
+		WithCredentials(creds).
+		WithRegion(provider.Region)
+	sess, err := session.NewSession(config)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create new Volcengine session: %w", err)
+	}
+	return sess, nil
+}
+
+// getSecretValue retrieves a value from a Kubernetes secret.
+func getSecretValue(ctx context.Context, kube client.Client, namespace string, secretSelector esmeta.SecretKeySelector) ([]byte, error) {
+	secret := &v1.Secret{}
+	ref := types.NamespacedName{
+		Namespace: namespace,
+		Name:      secretSelector.Name,
+	}
+
+	if err := kube.Get(ctx, ref, secret); err != nil {
+		return nil, fmt.Errorf("failed to get secret %s in namespace %s: %w", ref.Name, ref.Namespace, err)
+	}
+
+	value, ok := secret.Data[secretSelector.Key]
+	if !ok {
+		return nil, fmt.Errorf("key %q not found in secret %s in namespace %s", secretSelector.Key, ref.Name, ref.Namespace)
+	}
+
+	return value, nil
+}

+ 301 - 0
pkg/provider/volcengine/auth_test.go

@@ -0,0 +1,301 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package volcengine
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+)
+
+func TestNewSession_should_return_session_with_default_credentials_when_auth_is_nil(t *testing.T) {
+	store := &esv1.VolcengineProvider{
+		Region: testRegion,
+		Auth:   nil,
+	}
+	kube := fake.NewClientBuilder().Build()
+
+	sess, err := NewSession(context.Background(), store, kube, testNamespace)
+
+	assert.NoError(t, err)
+	assert.NotNil(t, sess)
+	assert.Equal(t, testRegion, *sess.Config.Region)
+	_, err = sess.Config.Credentials.Get()
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "missing required environment variables: VOLCENGINE_OIDC_TOKEN_FILE or VOLCENGINE_OIDC_ROLE_TRN")
+}
+
+func TestNewSession_should_return_session_with_default_credentials_when_secretref_is_nil(t *testing.T) {
+	store := &esv1.VolcengineProvider{
+		Region: testRegion,
+		Auth:   &esv1.VolcengineAuth{},
+	}
+	kube := fake.NewClientBuilder().Build()
+
+	sess, err := NewSession(context.Background(), store, kube, testNamespace)
+
+	assert.NoError(t, err)
+	assert.NotNil(t, sess)
+	assert.Equal(t, testRegion, *sess.Config.Region)
+	_, err = sess.Config.Credentials.Get()
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "missing required environment variables: VOLCENGINE_OIDC_TOKEN_FILE or VOLCENGINE_OIDC_ROLE_TRN")
+}
+
+func TestNewSession_should_return_session_with_static_credentials_when_secretref_is_provided(t *testing.T) {
+	secret := &v1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      secretName,
+			Namespace: testNamespace,
+		},
+		Data: map[string][]byte{
+			accessKeyIDKey:     []byte(testAccessKeyID),
+			secretAccessKeyKey: []byte(testSecretAccessKey),
+		},
+	}
+	store := &esv1.VolcengineProvider{
+		Region: testRegion,
+		Auth: &esv1.VolcengineAuth{
+			SecretRef: &esv1.VolcengineAuthSecretRef{
+				AccessKeyID: esmeta.SecretKeySelector{
+					Name: secretName,
+					Key:  accessKeyIDKey,
+				},
+				SecretAccessKey: esmeta.SecretKeySelector{
+					Name: secretName,
+					Key:  secretAccessKeyKey,
+				},
+			},
+		},
+	}
+
+	scheme := runtime.NewScheme()
+	_ = v1.AddToScheme(scheme)
+	kube := fake.NewClientBuilder().WithScheme(scheme).WithObjects(secret).Build()
+
+	sess, err := NewSession(context.Background(), store, kube, testNamespace)
+
+	assert.NoError(t, err)
+	assert.NotNil(t, sess)
+	assert.Equal(t, testRegion, *sess.Config.Region)
+	creds, err := sess.Config.Credentials.Get()
+	assert.NoError(t, err)
+	assert.Equal(t, testAccessKeyID, creds.AccessKeyID)
+	assert.Equal(t, testSecretAccessKey, creds.SecretAccessKey)
+}
+
+func TestNewSession_should_return_error_when_accesskeyid_secret_is_not_found(t *testing.T) {
+	store := &esv1.VolcengineProvider{
+		Region: testRegion,
+		Auth: &esv1.VolcengineAuth{
+			SecretRef: &esv1.VolcengineAuthSecretRef{
+				AccessKeyID: esmeta.SecretKeySelector{
+					Name: "non-existent-secret",
+					Key:  accessKeyIDKey,
+				},
+			},
+		},
+	}
+	kube := fake.NewClientBuilder().Build()
+
+	_, err := NewSession(context.Background(), store, kube, testNamespace)
+
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "failed to get accessKeyID")
+}
+
+func TestNewSession_should_return_error_when_secretaccesskey_secret_is_not_found(t *testing.T) {
+	secret := &v1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      secretName,
+			Namespace: testNamespace,
+		},
+		Data: map[string][]byte{
+			accessKeyIDKey: []byte(testAccessKeyID),
+		},
+	}
+	store := &esv1.VolcengineProvider{
+		Region: testRegion,
+		Auth: &esv1.VolcengineAuth{
+			SecretRef: &esv1.VolcengineAuthSecretRef{
+				AccessKeyID: esmeta.SecretKeySelector{
+					Name: secretName,
+					Key:  accessKeyIDKey,
+				},
+				SecretAccessKey: esmeta.SecretKeySelector{
+					Name: "non-existent-secret",
+					Key:  secretAccessKeyKey,
+				},
+			},
+		},
+	}
+	scheme := runtime.NewScheme()
+	_ = v1.AddToScheme(scheme)
+	kube := fake.NewClientBuilder().WithScheme(scheme).WithObjects(secret).Build()
+
+	_, err := NewSession(context.Background(), store, kube, testNamespace)
+
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "failed to get secretAccessKey")
+}
+
+func TestNewSession_should_return_error_when_accesskeyid_key_is_not_found_in_secret(t *testing.T) {
+	secret := &v1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      secretName,
+			Namespace: testNamespace,
+		},
+		Data: map[string][]byte{},
+	}
+	store := &esv1.VolcengineProvider{
+		Region: testRegion,
+		Auth: &esv1.VolcengineAuth{
+			SecretRef: &esv1.VolcengineAuthSecretRef{
+				AccessKeyID: esmeta.SecretKeySelector{
+					Name: secretName,
+					Key:  "non-existent-key",
+				},
+			},
+		},
+	}
+	scheme := runtime.NewScheme()
+	_ = v1.AddToScheme(scheme)
+	kube := fake.NewClientBuilder().WithScheme(scheme).WithObjects(secret).Build()
+
+	_, err := NewSession(context.Background(), store, kube, testNamespace)
+
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "key \"non-existent-key\" not found in secret")
+}
+
+func TestNewSession_should_return_session_with_token_credentials_when_secretref_is_provided(t *testing.T) {
+	secret := &v1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      secretName,
+			Namespace: testNamespace,
+		},
+		Data: map[string][]byte{
+			accessKeyIDKey:     []byte(testAccessKeyID),
+			secretAccessKeyKey: []byte(testSecretAccessKey),
+			tokenKey:           []byte(testSessionToken),
+		},
+	}
+	store := &esv1.VolcengineProvider{
+		Region: testRegion,
+		Auth: &esv1.VolcengineAuth{
+			SecretRef: &esv1.VolcengineAuthSecretRef{
+				AccessKeyID: esmeta.SecretKeySelector{
+					Name: secretName,
+					Key:  accessKeyIDKey,
+				},
+				SecretAccessKey: esmeta.SecretKeySelector{
+					Name: secretName,
+					Key:  secretAccessKeyKey,
+				},
+				Token: &esmeta.SecretKeySelector{
+					Name: secretName,
+					Key:  tokenKey,
+				},
+			},
+		},
+	}
+
+	scheme := runtime.NewScheme()
+	_ = v1.AddToScheme(scheme)
+	kube := fake.NewClientBuilder().WithScheme(scheme).WithObjects(secret).Build()
+
+	sess, err := NewSession(context.Background(), store, kube, testNamespace)
+
+	assert.NoError(t, err)
+	assert.NotNil(t, sess)
+	assert.Equal(t, testRegion, *sess.Config.Region)
+	creds, err := sess.Config.Credentials.Get()
+	assert.NoError(t, err)
+	assert.Equal(t, testAccessKeyID, creds.AccessKeyID)
+	assert.Equal(t, testSecretAccessKey, creds.SecretAccessKey)
+	assert.Equal(t, testSessionToken, creds.SessionToken)
+}
+
+func TestNewSession_should_return_error_when_secretaccesskey_key_is_not_found_in_secret(t *testing.T) {
+	secret := &v1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      secretName,
+			Namespace: testNamespace,
+		},
+		Data: map[string][]byte{
+			accessKeyIDKey: []byte(testAccessKeyID),
+		},
+	}
+	store := &esv1.VolcengineProvider{
+		Region: testRegion,
+		Auth: &esv1.VolcengineAuth{
+			SecretRef: &esv1.VolcengineAuthSecretRef{
+				AccessKeyID: esmeta.SecretKeySelector{
+					Name: secretName,
+					Key:  accessKeyIDKey,
+				},
+				SecretAccessKey: esmeta.SecretKeySelector{
+					Name: secretName,
+					Key:  "non-existent-key",
+				},
+			},
+		},
+	}
+	scheme := runtime.NewScheme()
+	_ = v1.AddToScheme(scheme)
+	kube := fake.NewClientBuilder().WithScheme(scheme).WithObjects(secret).Build()
+
+	_, err := NewSession(context.Background(), store, kube, testNamespace)
+
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "key \"non-existent-key\" not found in secret")
+}
+
+func TestNewSession_should_return_error_when_store_is_nil(t *testing.T) {
+	ctx := context.Background()
+	var store *esv1.VolcengineProvider
+	var kube client.Client
+	namespace := "default"
+
+	sess, err := NewSession(ctx, store, kube, namespace)
+
+	assert.Error(t, err)
+	assert.Nil(t, sess)
+	assert.Equal(t, "volcengine provider can not be nil", err.Error())
+}
+
+func TestNewSession_should_return_error_when_region_is_empty(t *testing.T) {
+	ctx := context.Background()
+	store := &esv1.VolcengineProvider{}
+	var kube client.Client
+	namespace := "default"
+
+	sess, err := NewSession(ctx, store, kube, namespace)
+
+	assert.Error(t, err)
+	assert.Nil(t, sess)
+	assert.Equal(t, "region must be specified", err.Error())
+}

+ 161 - 0
pkg/provider/volcengine/client.go

@@ -0,0 +1,161 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package volcengine
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+
+	"github.com/volcengine/volcengine-go-sdk/service/kms"
+	corev1 "k8s.io/api/core/v1"
+
+	esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+)
+
+const (
+	notImplemented = "not implemented"
+)
+
+var _ esapi.SecretsClient = &Client{}
+
+// Client is a client for the Volcengine provider.
+type Client struct {
+	kms kms.KMSAPI
+}
+
+// NewClient creates a new Volcengine client.
+func NewClient(kms kms.KMSAPI) *Client {
+	return &Client{
+		kms: kms,
+	}
+}
+
+// GetSecret retrieves a secret value from Volcengine Secrets Manager.
+func (c *Client) GetSecret(ctx context.Context, ref esapi.ExternalSecretDataRemoteRef) ([]byte, error) {
+	return c.getSecretValue(ctx, ref)
+}
+
+// SecretExists checks if a secret exists in Volcengine Secrets Manager.
+func (c *Client) SecretExists(ctx context.Context, remoteRef esapi.PushSecretRemoteRef) (bool, error) {
+	secretName := remoteRef.GetRemoteKey()
+	if secretName == "" {
+		return false, errors.New("secret name is empty")
+	}
+	_, err := c.kms.DescribeSecretWithContext(ctx, &kms.DescribeSecretInput{
+		SecretName: &secretName,
+	})
+	if err != nil {
+		return false, err
+	}
+	return true, nil
+}
+
+// Validate checks if the provider is configured correctly.
+func (c *Client) Validate() (esapi.ValidationResult, error) {
+	if c.kms != nil {
+		return esapi.ValidationResultReady, nil
+	}
+	return esapi.ValidationResultError, errors.New("kms client is not initialized")
+}
+
+// GetSecretMap retrieves a secret value and unmarshals it as a map.
+func (c *Client) GetSecretMap(ctx context.Context, ref esapi.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+	value, err := c.getSecretValue(ctx, ref)
+	if err != nil {
+		return nil, err
+	}
+
+	var rawSecretMap map[string]json.RawMessage
+	if err := json.Unmarshal(value, &rawSecretMap); err != nil {
+		return nil, fmt.Errorf("failed to unmarshal secret: %w", err)
+	}
+
+	secretMap := make(map[string][]byte, len(rawSecretMap))
+	for key, value := range rawSecretMap {
+		secretMap[key] = []byte(value)
+	}
+	return secretMap, nil
+}
+
+// GetAllSecrets retrieves all secrets matching the given criteria.
+func (c *Client) GetAllSecrets(ctx context.Context, ref esapi.ExternalSecretFind) (map[string][]byte, error) {
+	return nil, errors.New(notImplemented)
+}
+
+// PushSecret creates or updates a secret in Volcengine Secrets Manager.
+func (c *Client) PushSecret(ctx context.Context, secret *corev1.Secret, data esapi.PushSecretData) error {
+	return errors.New(notImplemented)
+}
+
+// DeleteSecret deletes a secret from Volcengine Secrets Manager.
+func (c *Client) DeleteSecret(ctx context.Context, remoteRef esapi.PushSecretRemoteRef) error {
+	return errors.New(notImplemented)
+}
+
+// Close is a no-op for the Volcengine client.
+func (c *Client) Close(_ context.Context) error {
+	return nil
+}
+
+func (c *Client) getSecretValue(ctx context.Context, ref esapi.ExternalSecretDataRemoteRef) ([]byte, error) {
+	output, err := c.kms.GetSecretValueWithContext(ctx, &kms.GetSecretValueInput{
+		SecretName: &ref.Key,
+		VersionID:  resolveVersion(ref),
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	if output.SecretValue == nil {
+		return nil, fmt.Errorf("secret %s has no value", ref.Key)
+	}
+
+	secret := []byte(*output.SecretValue)
+
+	if ref.Property == "" {
+		return secret, nil
+	}
+
+	return extractProperty(secret, ref.Property)
+}
+
+func extractProperty(secret []byte, property string) ([]byte, error) {
+	var secretMap map[string]json.RawMessage
+	if err := json.Unmarshal(secret, &secretMap); err != nil {
+		return nil, fmt.Errorf("failed to unmarshal secret: %w", err)
+	}
+
+	value, ok := secretMap[property]
+	if !ok {
+		return nil, fmt.Errorf("property %q not found in secret", property)
+	}
+
+	var s string
+	if json.Unmarshal(value, &s) == nil {
+		return []byte(s), nil
+	}
+	return value, nil
+}
+
+func resolveVersion(ref esapi.ExternalSecretDataRemoteRef) *string {
+	if ref.Version != "" {
+		return &ref.Version
+	}
+	return nil
+}

+ 370 - 0
pkg/provider/volcengine/client_test.go

@@ -0,0 +1,370 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package volcengine
+
+import (
+	"context"
+	"errors"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/volcengine/volcengine-go-sdk/service/kms"
+	"github.com/volcengine/volcengine-go-sdk/volcengine/request"
+	corev1 "k8s.io/api/core/v1"
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+
+	esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+)
+
+type fakePushSecretData struct {
+	Metadata  *apiextensionsv1.JSON
+	SecretKey string
+	RemoteKey string
+	Property  string
+}
+
+func (f fakePushSecretData) GetMetadata() *apiextensionsv1.JSON {
+	return f.Metadata
+}
+
+func (f fakePushSecretData) GetSecretKey() string {
+	return f.SecretKey
+}
+
+func (f fakePushSecretData) GetRemoteKey() string {
+	return f.RemoteKey
+}
+
+func (f fakePushSecretData) GetProperty() string {
+	return f.Property
+}
+
+type fakePushScretRemoteRef struct {
+	RemoteKey string
+	Property  string
+}
+
+func (f fakePushScretRemoteRef) GetRemoteKey() string {
+	return f.RemoteKey
+}
+
+func (f fakePushScretRemoteRef) GetProperty() string {
+	return f.Property
+}
+
+// MockKMSClient is a mock of KMSAPI interface.
+type MockKMSClient struct {
+	kms.KMSAPI
+	DescribeRegionsFunc           func(*kms.DescribeRegionsInput) (*kms.DescribeRegionsOutput, error)
+	DescribeSecretWithContextFunc func(context.Context, *kms.DescribeSecretInput, ...request.Option) (*kms.DescribeSecretOutput, error)
+	GetSecretValueWithContextFunc func(context.Context, *kms.GetSecretValueInput, ...request.Option) (*kms.GetSecretValueOutput, error)
+}
+
+// DescribeRegions mocks the DescribeRegions method.
+func (m *MockKMSClient) DescribeRegions(input *kms.DescribeRegionsInput) (*kms.DescribeRegionsOutput, error) {
+	if m.DescribeRegionsFunc != nil {
+		return m.DescribeRegionsFunc(input)
+	}
+	return nil, errors.New("DescribeRegions is not implemented")
+}
+
+// DescribeSecretWithContext mocks the DescribeSecretWithContext method.
+func (m *MockKMSClient) DescribeSecretWithContext(ctx context.Context, input *kms.DescribeSecretInput, opts ...request.Option) (*kms.DescribeSecretOutput, error) {
+	if m.DescribeSecretWithContextFunc != nil {
+		return m.DescribeSecretWithContextFunc(ctx, input, opts...)
+	}
+	return nil, errors.New("DescribeSecretWithContext is not implemented")
+}
+
+// GetSecretValueWithContext mocks the GetSecretValueWithContext method.
+func (m *MockKMSClient) GetSecretValueWithContext(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
+	if m.GetSecretValueWithContextFunc != nil {
+		return m.GetSecretValueWithContextFunc(ctx, input, opts...)
+	}
+	return nil, errors.New("GetSecretValueWithContext is not implemented")
+}
+
+func TestNew_should_return_a_new_client(t *testing.T) {
+	mockKMS := &MockKMSClient{}
+	client := NewClient(mockKMS)
+	assert.NotNil(t, client)
+	assert.Equal(t, mockKMS, client.kms)
+}
+
+func TestClient_PushSecret_should_return_not_implemented_error(t *testing.T) {
+	client := &Client{}
+	err := client.PushSecret(context.Background(), &corev1.Secret{}, &fakePushSecretData{})
+	assert.Error(t, err)
+	assert.Equal(t, notImplemented, err.Error())
+}
+
+func TestClient_DeleteSecret_should_return_not_implemented_error(t *testing.T) {
+	client := &Client{}
+	err := client.DeleteSecret(context.Background(), &fakePushSecretData{})
+	assert.Error(t, err)
+	assert.Equal(t, notImplemented, err.Error())
+}
+
+func TestClient_GetAllSecrets_should_return_not_implemented_error(t *testing.T) {
+	client := &Client{}
+	_, err := client.GetAllSecrets(context.Background(), esapi.ExternalSecretFind{})
+	assert.Error(t, err)
+	assert.Equal(t, notImplemented, err.Error())
+}
+
+func TestClient_Close_should_return_nil(t *testing.T) {
+	client := &Client{}
+	err := client.Close(context.Background())
+	assert.NoError(t, err)
+}
+
+func TestClient_Validate_should_return_ready_when_kms_client_is_initialized(t *testing.T) {
+	mockKMS := &MockKMSClient{
+		DescribeRegionsFunc: func(*kms.DescribeRegionsInput) (*kms.DescribeRegionsOutput, error) {
+			return &kms.DescribeRegionsOutput{}, nil
+		},
+	}
+	client := NewClient(mockKMS)
+	result, err := client.Validate()
+	assert.NoError(t, err)
+	assert.Equal(t, esapi.ValidationResultReady, result)
+}
+
+func TestClient_Validate_should_return_error_when_kms_client_is_not_initialized(t *testing.T) {
+	client := NewClient(nil)
+	result, err := client.Validate()
+	assert.Error(t, err)
+	assert.Equal(t, "kms client is not initialized", err.Error())
+	assert.Equal(t, esapi.ValidationResultError, result)
+}
+
+func TestClient_GetSecret_should_return_secret_value_when_property_is_empty(t *testing.T) {
+	secretValue := "my-secret-value"
+	mockKMS := &MockKMSClient{
+		GetSecretValueWithContextFunc: func(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
+			return &kms.GetSecretValueOutput{
+				SecretValue: &secretValue,
+			}, nil
+		},
+	}
+	client := NewClient(mockKMS)
+	value, err := client.GetSecret(context.Background(), esapi.ExternalSecretDataRemoteRef{
+		Key: "my-secret",
+	})
+	assert.NoError(t, err)
+	assert.Equal(t, []byte(secretValue), value)
+}
+
+func TestClient_GetSecret_should_return_property_value_when_secret_is_json_and_property_exists(t *testing.T) {
+	secretValue := `{"user":"admin","pass":"1234"}`
+	mockKMS := &MockKMSClient{
+		GetSecretValueWithContextFunc: func(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
+			return &kms.GetSecretValueOutput{
+				SecretValue: &secretValue,
+			}, nil
+		},
+	}
+	client := NewClient(mockKMS)
+	value, err := client.GetSecret(context.Background(), esapi.ExternalSecretDataRemoteRef{
+		Key:      "my-secret",
+		Property: "pass",
+	})
+	assert.NoError(t, err)
+	assert.Equal(t, []byte("1234"), value)
+}
+
+func TestClient_GetSecret_should_return_raw_json_value_when_property_is_json_object(t *testing.T) {
+	secretValue := `{"config":{"foo":"bar"},"pass":"1234"}`
+	mockKMS := &MockKMSClient{
+		GetSecretValueWithContextFunc: func(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
+			return &kms.GetSecretValueOutput{
+				SecretValue: &secretValue,
+			}, nil
+		},
+	}
+	client := NewClient(mockKMS)
+	value, err := client.GetSecret(context.Background(), esapi.ExternalSecretDataRemoteRef{
+		Key:      "my-secret",
+		Property: "config",
+	})
+	assert.NoError(t, err)
+	assert.Equal(t, []byte(`{"foo":"bar"}`), value)
+}
+
+func TestClient_GetSecret_should_return_error_when_property_does_not_exist(t *testing.T) {
+	secretValue := `{"user":"admin","pass":"1234"}`
+	mockKMS := &MockKMSClient{
+		GetSecretValueWithContextFunc: func(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
+			return &kms.GetSecretValueOutput{
+				SecretValue: &secretValue,
+			}, nil
+		},
+	}
+	client := NewClient(mockKMS)
+	_, err := client.GetSecret(context.Background(), esapi.ExternalSecretDataRemoteRef{
+		Key:      "my-secret",
+		Property: "non-existent",
+	})
+	assert.Error(t, err)
+	assert.Equal(t, `property "non-existent" not found in secret`, err.Error())
+}
+
+func TestClient_GetSecret_should_return_error_when_secret_is_not_valid_json(t *testing.T) {
+	secretValue := `not-a-json`
+	mockKMS := &MockKMSClient{
+		GetSecretValueWithContextFunc: func(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
+			return &kms.GetSecretValueOutput{
+				SecretValue: &secretValue,
+			}, nil
+		},
+	}
+	client := NewClient(mockKMS)
+	_, err := client.GetSecret(context.Background(), esapi.ExternalSecretDataRemoteRef{
+		Key:      "my-secret",
+		Property: "prop",
+	})
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "failed to unmarshal secret")
+}
+
+func TestClient_GetSecret_should_return_error_when_api_call_fails(t *testing.T) {
+	mockKMS := &MockKMSClient{
+		GetSecretValueWithContextFunc: func(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
+			return nil, errors.New("api error")
+		},
+	}
+	client := NewClient(mockKMS)
+	_, err := client.GetSecret(context.Background(), esapi.ExternalSecretDataRemoteRef{
+		Key: "my-secret",
+	})
+	assert.Error(t, err)
+	assert.Equal(t, "api error", err.Error())
+}
+
+func TestClient_GetSecret_should_return_error_when_secret_value_is_nil(t *testing.T) {
+	mockKMS := &MockKMSClient{
+		GetSecretValueWithContextFunc: func(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
+			return &kms.GetSecretValueOutput{
+				SecretValue: nil,
+			}, nil
+		},
+	}
+	client := NewClient(mockKMS)
+	_, err := client.GetSecret(context.Background(), esapi.ExternalSecretDataRemoteRef{
+		Key: "my-secret",
+	})
+	assert.Error(t, err)
+	assert.Equal(t, "secret my-secret has no value", err.Error())
+}
+
+func TestClient_GetSecretMap_should_return_map_when_secret_is_valid_json(t *testing.T) {
+	secretValue := `{"user":"admin"}`
+	mockKMS := &MockKMSClient{
+		GetSecretValueWithContextFunc: func(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
+			return &kms.GetSecretValueOutput{
+				SecretValue: &secretValue,
+			}, nil
+		},
+	}
+	client := NewClient(mockKMS)
+	secretMap, err := client.GetSecretMap(context.Background(), esapi.ExternalSecretDataRemoteRef{
+		Key: "my-secret",
+	})
+	assert.NoError(t, err)
+	expectedMap := map[string][]byte{
+		"user": []byte(`"admin"`),
+	}
+	assert.Equal(t, expectedMap, secretMap)
+}
+
+func TestClient_GetSecretMap_should_return_error_when_secret_is_not_valid_json(t *testing.T) {
+	secretValue := `not-a-json`
+	mockKMS := &MockKMSClient{
+		GetSecretValueWithContextFunc: func(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
+			return &kms.GetSecretValueOutput{
+				SecretValue: &secretValue,
+			}, nil
+		},
+	}
+	client := NewClient(mockKMS)
+	_, err := client.GetSecretMap(context.Background(), esapi.ExternalSecretDataRemoteRef{
+		Key: "my-secret",
+	})
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "failed to unmarshal secret")
+}
+
+func TestClient_GetSecretMap_should_return_error_when_api_call_fails(t *testing.T) {
+	mockKMS := &MockKMSClient{
+		GetSecretValueWithContextFunc: func(ctx context.Context, input *kms.GetSecretValueInput, opts ...request.Option) (*kms.GetSecretValueOutput, error) {
+			return nil, errors.New("api error")
+		},
+	}
+	client := NewClient(mockKMS)
+	_, err := client.GetSecretMap(context.Background(), esapi.ExternalSecretDataRemoteRef{
+		Key: "my-secret",
+	})
+	assert.Error(t, err)
+	assert.Equal(t, "api error", err.Error())
+}
+
+func TestClient_SecretExists_should_return_error_when_secret_name_is_empty(t *testing.T) {
+	mockKMS := &MockKMSClient{}
+	c := NewClient(mockKMS)
+
+	exists, err := c.SecretExists(context.Background(), fakePushScretRemoteRef{
+		RemoteKey: "",
+	})
+
+	assert.False(t, exists)
+	assert.Error(t, err)
+	assert.Equal(t, "secret name is empty", err.Error())
+}
+
+func TestClient_SecretExists_should_return_error_when_describe_secret_fails(t *testing.T) {
+	expectedErr := errors.New("failed to describe secret")
+	mockKMS := &MockKMSClient{
+		DescribeSecretWithContextFunc: func(ctx context.Context, input *kms.DescribeSecretInput, opts ...request.Option) (*kms.DescribeSecretOutput, error) {
+			return nil, expectedErr
+		},
+	}
+	c := NewClient(mockKMS)
+
+	exists, err := c.SecretExists(context.Background(), fakePushScretRemoteRef{
+		RemoteKey: "test-secret",
+	})
+
+	assert.False(t, exists)
+	assert.Error(t, err)
+	assert.Equal(t, expectedErr, err)
+}
+
+func TestClient_SecretExists_should_return_true_when_secret_exists(t *testing.T) {
+	mockKMS := &MockKMSClient{
+		DescribeSecretWithContextFunc: func(ctx context.Context, input *kms.DescribeSecretInput, opts ...request.Option) (*kms.DescribeSecretOutput, error) {
+			return &kms.DescribeSecretOutput{}, nil
+		},
+	}
+	c := NewClient(mockKMS)
+
+	exists, err := c.SecretExists(context.Background(), fakePushScretRemoteRef{
+		RemoteKey: "test-secret",
+	})
+
+	assert.True(t, exists)
+	assert.NoError(t, err)
+}

+ 109 - 0
pkg/provider/volcengine/provider.go

@@ -0,0 +1,109 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package volcengine
+
+import (
+	"context"
+	"errors"
+	"fmt"
+
+	kclient "sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	"github.com/external-secrets/external-secrets/pkg/utils"
+	"github.com/volcengine/volcengine-go-sdk/service/kms"
+)
+
+var _ esv1.Provider = &Provider{}
+
+// Provider implements the actual SecretsClient interface.
+type Provider struct{}
+
+// NewClient implements v1.Provider.
+func (p *Provider) NewClient(ctx context.Context, store esv1.GenericStore, kube kclient.Client, namespace string) (esv1.SecretsClient, error) {
+	volcengineProvider, err := getVolcengineProvider(store)
+	if err != nil {
+		return nil, err
+	}
+
+	sess, err := NewSession(ctx, volcengineProvider, kube, namespace)
+	if err != nil {
+		return nil, err
+	}
+
+	kms := kms.New(sess)
+	return NewClient(kms), nil
+}
+
+// ValidateStore implements v1.Provider.
+func (p *Provider) ValidateStore(store esv1.GenericStore) (admission.Warnings, error) {
+	volcengineProvider, err := getVolcengineProvider(store)
+	if err != nil {
+		return nil, err
+	}
+
+	if volcengineProvider.Region == "" {
+		return nil, fmt.Errorf("region is required")
+	}
+
+	// Use IRSA as auth is not specified.
+	if volcengineProvider.Auth == nil {
+		return nil, nil
+	}
+
+	return nil, validateAuthSecretRef(store, volcengineProvider.Auth.SecretRef)
+}
+
+// Capabilities implements v1.Provider.
+func (p *Provider) Capabilities() esv1.SecretStoreCapabilities {
+	return esv1.SecretStoreReadOnly
+}
+
+// validateAuthSecretRef validates the SecretRef for static credentials.
+func validateAuthSecretRef(store esv1.GenericStore, ref *esv1.VolcengineAuthSecretRef) error {
+	if ref == nil {
+		return errors.New("SecretRef is required when using static credentials")
+	}
+	if err := utils.ValidateReferentSecretSelector(store, ref.AccessKeyID); err != nil {
+		return fmt.Errorf("invalid AccessKeyID: %w", err)
+	}
+	if err := utils.ValidateReferentSecretSelector(store, ref.SecretAccessKey); err != nil {
+		return fmt.Errorf("invalid SecretAccessKey: %w", err)
+	}
+	if ref.Token != nil {
+		if err := utils.ValidateReferentSecretSelector(store, *ref.Token); err != nil {
+			return fmt.Errorf("invalid Token: %w", err)
+		}
+	}
+	return nil
+}
+
+// getVolcengineProvider gets the VolcengineProvider from the store spec.
+func getVolcengineProvider(store esv1.GenericStore) (*esv1.VolcengineProvider, error) {
+	spec := store.GetSpec()
+	if spec.Provider == nil || spec.Provider.Volcengine == nil {
+		return nil, fmt.Errorf("volcengine provider is nil")
+	}
+	return spec.Provider.Volcengine, nil
+}
+
+func init() {
+	esv1.Register(&Provider{}, &esv1.SecretStoreProvider{
+		Volcengine: &esv1.VolcengineProvider{},
+	}, esv1.MaintenanceStatusMaintained)
+}

+ 292 - 0
pkg/provider/volcengine/provider_test.go

@@ -0,0 +1,292 @@
+/*
+Copyright © 2025 ESO Maintainer Team
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package volcengine
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+)
+
+const (
+	testNamespace       = "default"
+	testAccessKeyID     = "test-access-key-id"
+	testSecretAccessKey = "test-secret-access-key"
+	testSessionToken    = "test-session-token"
+	testRegion          = "cn-beijing"
+	secretName          = "volcengine-secret"
+	accessKeyIDKey      = "accessKeyID"
+	secretAccessKeyKey  = "secretAccessKey"
+	tokenKey            = "token"
+	otherNamespace      = "other"
+)
+
+func TestProvider_NewClient(t *testing.T) {
+	p := &Provider{}
+	store := &esv1.SecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test-store",
+			Namespace: testNamespace,
+		},
+		Spec: esv1.SecretStoreSpec{
+			Provider: &esv1.SecretStoreProvider{
+				Volcengine: &esv1.VolcengineProvider{
+					Region: testRegion,
+					Auth: &esv1.VolcengineAuth{
+						SecretRef: &esv1.VolcengineAuthSecretRef{
+							AccessKeyID: esmeta.SecretKeySelector{
+								Name: secretName,
+								Key:  accessKeyIDKey,
+							},
+							SecretAccessKey: esmeta.SecretKeySelector{
+								Name: secretName,
+								Key:  secretAccessKeyKey,
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	secret := &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      secretName,
+			Namespace: testNamespace,
+		},
+		Data: map[string][]byte{
+			accessKeyIDKey:     []byte("test-access-key"),
+			secretAccessKeyKey: []byte("test-secret-key"),
+		},
+	}
+
+	scheme := runtime.NewScheme()
+	clientgoscheme.AddToScheme(scheme)
+	kube := fake.NewClientBuilder().WithScheme(scheme).WithObjects(secret).Build()
+
+	t.Run("case 1: Successful case", func(t *testing.T) {
+		c, err := p.NewClient(context.Background(), store, kube, testNamespace)
+		assert.NoError(t, err)
+		assert.NotNil(t, c)
+		_, ok := c.(*Client)
+		assert.True(t, ok)
+	})
+
+	t.Run("case 2: Volcengine provider is nil", func(t *testing.T) {
+		store.Spec.Provider.Volcengine = nil
+		c, err := p.NewClient(context.Background(), store, kube, testNamespace)
+		assert.Error(t, err)
+		assert.Nil(t, c)
+		assert.EqualError(t, err, "volcengine provider is nil")
+	})
+}
+
+func TestProvider_ValidateStore(t *testing.T) {
+	p := &Provider{}
+
+	t.Run("case 1: should return no error when using IRSA", func(t *testing.T) {
+		store := &esv1.SecretStore{
+			Spec: esv1.SecretStoreSpec{
+				Provider: &esv1.SecretStoreProvider{
+					Volcengine: &esv1.VolcengineProvider{
+						Region: testRegion,
+					},
+				},
+			},
+		}
+		_, err := p.ValidateStore(store)
+		assert.NoError(t, err)
+	})
+
+	t.Run("case 2: should return no error when using SecretRef", func(t *testing.T) {
+		store := &esv1.SecretStore{
+			Spec: esv1.SecretStoreSpec{
+				Provider: &esv1.SecretStoreProvider{
+					Volcengine: &esv1.VolcengineProvider{
+						Region: testRegion,
+						Auth: &esv1.VolcengineAuth{
+							SecretRef: &esv1.VolcengineAuthSecretRef{
+								AccessKeyID: esmeta.SecretKeySelector{
+									Name: "test",
+									Key:  accessKeyIDKey,
+								},
+								SecretAccessKey: esmeta.SecretKeySelector{
+									Name: "test",
+									Key:  secretAccessKeyKey,
+								},
+							},
+						},
+					},
+				},
+			},
+		}
+		_, err := p.ValidateStore(store)
+		assert.NoError(t, err)
+	})
+
+	t.Run("case 3: should return error when provider is nil", func(t *testing.T) {
+		store := &esv1.SecretStore{
+			Spec: esv1.SecretStoreSpec{
+				Provider: &esv1.SecretStoreProvider{
+					Volcengine: nil,
+				},
+			},
+		}
+		_, err := p.ValidateStore(store)
+		assert.Error(t, err)
+		assert.EqualError(t, err, "volcengine provider is nil")
+	})
+
+	t.Run("case 4: should return error when region is empty", func(t *testing.T) {
+		store := &esv1.SecretStore{
+			Spec: esv1.SecretStoreSpec{
+				Provider: &esv1.SecretStoreProvider{
+					Volcengine: &esv1.VolcengineProvider{},
+				},
+			},
+		}
+		_, err := p.ValidateStore(store)
+		assert.Error(t, err)
+		assert.EqualError(t, err, "region is required")
+	})
+
+	t.Run("case 5: should return error when SecretRef is nil", func(t *testing.T) {
+		store := &esv1.SecretStore{
+			Spec: esv1.SecretStoreSpec{
+				Provider: &esv1.SecretStoreProvider{
+					Volcengine: &esv1.VolcengineProvider{
+						Region: testRegion,
+						Auth:   &esv1.VolcengineAuth{},
+					},
+				},
+			},
+		}
+		_, err := p.ValidateStore(store)
+		assert.Error(t, err)
+		assert.EqualError(t, err, "SecretRef is required when using static credentials")
+	})
+
+	t.Run("case 6: should return error when AccessKeyID is invalid", func(t *testing.T) {
+		otherNamespace := otherNamespace
+		store := &esv1.SecretStore{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "volcengile-store",
+				Namespace: "kms-system",
+			},
+			Spec: esv1.SecretStoreSpec{
+				Provider: &esv1.SecretStoreProvider{
+					Volcengine: &esv1.VolcengineProvider{
+						Region: testRegion,
+						Auth: &esv1.VolcengineAuth{
+							SecretRef: &esv1.VolcengineAuthSecretRef{
+								AccessKeyID: esmeta.SecretKeySelector{
+									Name:      "",
+									Namespace: &otherNamespace,
+								},
+							},
+						},
+					},
+				},
+			},
+		}
+		_, err := p.ValidateStore(store)
+		assert.Error(t, err)
+		assert.Contains(t, err.Error(), "invalid AccessKeyID")
+	})
+
+	t.Run("case 7: should return error when SecretAccessKey is invalid", func(t *testing.T) {
+		otherNamespace := otherNamespace
+		store := &esv1.SecretStore{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "volcengile-store",
+				Namespace: "kms-system",
+			},
+			Spec: esv1.SecretStoreSpec{
+				Provider: &esv1.SecretStoreProvider{
+					Volcengine: &esv1.VolcengineProvider{
+						Region: testRegion,
+						Auth: &esv1.VolcengineAuth{
+							SecretRef: &esv1.VolcengineAuthSecretRef{
+								AccessKeyID: esmeta.SecretKeySelector{
+									Name: "test",
+									Key:  accessKeyIDKey,
+								},
+								SecretAccessKey: esmeta.SecretKeySelector{
+									Name:      "",
+									Namespace: &otherNamespace,
+								},
+							},
+						},
+					},
+				},
+			},
+		}
+		_, err := p.ValidateStore(store)
+		assert.Error(t, err)
+		assert.Contains(t, err.Error(), "invalid SecretAccessKey")
+	})
+
+	t.Run("case 8: should return error when Token is invalid", func(t *testing.T) {
+		otherNamespace := otherNamespace
+		store := &esv1.SecretStore{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "volcengile-store",
+				Namespace: "kms-system",
+			},
+			Spec: esv1.SecretStoreSpec{
+				Provider: &esv1.SecretStoreProvider{
+					Volcengine: &esv1.VolcengineProvider{
+						Region: testRegion,
+						Auth: &esv1.VolcengineAuth{
+							SecretRef: &esv1.VolcengineAuthSecretRef{
+								AccessKeyID: esmeta.SecretKeySelector{
+									Name: "test",
+									Key:  accessKeyIDKey,
+								},
+								SecretAccessKey: esmeta.SecretKeySelector{
+									Name: "test",
+									Key:  secretAccessKeyKey,
+								},
+								Token: &esmeta.SecretKeySelector{
+									Name:      "",
+									Namespace: &otherNamespace,
+								},
+							},
+						},
+					},
+				},
+			},
+		}
+		_, err := p.ValidateStore(store)
+		assert.Error(t, err)
+		assert.Contains(t, err.Error(), "invalid Token")
+	})
+}
+
+func TestProvider_Capabilities(t *testing.T) {
+	p := &Provider{}
+	assert.Equal(t, esv1.SecretStoreReadOnly, p.Capabilities())
+}