Browse Source

feat: add beyondtrust provider (#3683)

* feat: add beyondtrust provider

Signed-off-by: Felipe Hernandez <fhernandez@beyondtrust.com>

* feat: edit go.mod and go.sum files

Signed-off-by: Felipe Hernandez <fhernandez@beyondtrust.com>

* feat: change test file name (provider_test.go)

Signed-off-by: Felipe Hernandez <fhernandez@beyondtrust.com>

* feat: solve PR comments

Signed-off-by: Felipe Hernandez <fhernandez@beyondtrust.com>

* feat: organize attributes in a higher hierarchy

Signed-off-by: Felipe Hernandez <fhernandez@beyondtrust.com>

* fix: fix sonar cloud issues and go.mod file conflicts

Signed-off-by: Felipe Hernandez <fhernandez@beyondtrust.com>

* fix: fix PR comments and apply table driven tests

Signed-off-by: Felipe Hernandez <fhernandez@beyondtrust.com>

* fix: fix PR comments

Signed-off-by: Felipe Hernandez <fhernandez@beyondtrust.com>

* fix: fix lint issues

Signed-off-by: Felipe Hernandez <fhernandez@beyondtrust.com>

* fix: fix lint issues on tests

Signed-off-by: Felipe Hernandez <fhernandez@beyondtrust.com>

* fix: run make fmt

Signed-off-by: Felipe Hernandez <fhernandez@beyondtrust.com>

* fix: apply camelCase to yaml attributes

Signed-off-by: Felipe Hernandez <fhernandez@beyondtrust.com>

* fix: solve go.mod file conflict

Signed-off-by: Felipe Hernandez <fhernandez@beyondtrust.com>

* fix: run make check-diff

Signed-off-by: Felipe Hernandez <fhernandez@beyondtrust.com>

---------

Signed-off-by: Felipe Hernandez <fhernandez@beyondtrust.com>
Signed-off-by: btfhernandez <133419363+btfhernandez@users.noreply.github.com>
btfhernandez 1 year ago
parent
commit
77f5d0ad91

+ 63 - 0
apis/externalsecrets/v1beta1/secretstore_beyondtrust_types.go

@@ -0,0 +1,63 @@
+/*
+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
+
+    http://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 v1beta1
+
+import esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+
+type BeyondTrustProviderSecretRef struct {
+
+	// Value can be specified directly to set a value without using a secret.
+	// +optional
+	Value string `json:"value,omitempty"`
+
+	// SecretRef references a key in a secret that will be used as value.
+	// +optional
+	SecretRef *esmeta.SecretKeySelector `json:"secretRef,omitempty"`
+}
+
+// Configures a store to sync secrets using BeyondTrust Password Safe.
+type BeyondtrustAuth struct {
+	// +required - API OAuth Client ID.
+	ClientID *BeyondTrustProviderSecretRef `json:"clientId"`
+	// +required - API OAuth Client Secret.
+	ClientSecret *BeyondTrustProviderSecretRef `json:"clientSecret"`
+	// Content of the certificate (cert.pem) for use when authenticating with an OAuth client Id using a Client Certificate.
+	Certificate *BeyondTrustProviderSecretRef `json:"certificate,omitempty"`
+	// Certificate private key (key.pem). For use when authenticating with an OAuth client Id
+	CertificateKey *BeyondTrustProviderSecretRef `json:"certificateKey,omitempty"`
+}
+
+// Configures a store to sync secrets using BeyondTrust Password Safe.
+type BeyondtrustServer struct {
+	// +required - BeyondTrust Password Safe API URL. https://example.com:443/beyondtrust/api/public/V3.
+	APIURL string `json:"apiUrl"`
+	// The secret retrieval type. SECRET = Secrets Safe (credential, text, file). MANAGED_ACCOUNT = Password Safe account associated with a system.
+	RetrievalType string `json:"retrievalType,omitempty"`
+	// A character that separates the folder names.
+	Separator string `json:"separator,omitempty"`
+	// +required - Indicates whether to verify the certificate authority on the Secrets Safe instance. Warning - false is insecure, instructs the BT provider not to verify the certificate authority.
+	VerifyCA bool `json:"verifyCA"`
+	// Timeout specifies a time limit for requests made by this Client. The timeout includes connection time, any redirects, and reading the response body. Defaults to 45 seconds.
+	ClientTimeOutSeconds int `json:"clientTimeOutSeconds,omitempty"`
+}
+
+type BeyondtrustProvider struct {
+
+	// Auth configures how the operator authenticates with Beyondtrust.
+	Auth *BeyondtrustAuth `json:"auth"`
+
+	// Auth configures how API server works.
+	Server *BeyondtrustServer `json:"server"`
+}

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

@@ -185,6 +185,10 @@ type SecretStoreProvider struct {
 	// Infisical configures this store to sync secrets using the Infisical provider
 	// +optional
 	Infisical *InfisicalProvider `json:"infisical,omitempty"`
+
+	// Beyondtrust configures this store to sync secrets using Password Safe provider.
+	// +optional
+	Beyondtrust *BeyondtrustProvider `json:"beyondtrust,omitempty"`
 }
 
 type CAProviderType string

+ 100 - 0
apis/externalsecrets/v1beta1/zz_generated.deepcopy.go

@@ -392,6 +392,101 @@ func (in *AzureKVProvider) DeepCopy() *AzureKVProvider {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *BeyondTrustProviderSecretRef) DeepCopyInto(out *BeyondTrustProviderSecretRef) {
+	*out = *in
+	if in.SecretRef != nil {
+		in, out := &in.SecretRef, &out.SecretRef
+		*out = new(metav1.SecretKeySelector)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BeyondTrustProviderSecretRef.
+func (in *BeyondTrustProviderSecretRef) DeepCopy() *BeyondTrustProviderSecretRef {
+	if in == nil {
+		return nil
+	}
+	out := new(BeyondTrustProviderSecretRef)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *BeyondtrustAuth) DeepCopyInto(out *BeyondtrustAuth) {
+	*out = *in
+	if in.ClientID != nil {
+		in, out := &in.ClientID, &out.ClientID
+		*out = new(BeyondTrustProviderSecretRef)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.ClientSecret != nil {
+		in, out := &in.ClientSecret, &out.ClientSecret
+		*out = new(BeyondTrustProviderSecretRef)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.Certificate != nil {
+		in, out := &in.Certificate, &out.Certificate
+		*out = new(BeyondTrustProviderSecretRef)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.CertificateKey != nil {
+		in, out := &in.CertificateKey, &out.CertificateKey
+		*out = new(BeyondTrustProviderSecretRef)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BeyondtrustAuth.
+func (in *BeyondtrustAuth) DeepCopy() *BeyondtrustAuth {
+	if in == nil {
+		return nil
+	}
+	out := new(BeyondtrustAuth)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *BeyondtrustProvider) DeepCopyInto(out *BeyondtrustProvider) {
+	*out = *in
+	if in.Auth != nil {
+		in, out := &in.Auth, &out.Auth
+		*out = new(BeyondtrustAuth)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.Server != nil {
+		in, out := &in.Server, &out.Server
+		*out = new(BeyondtrustServer)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BeyondtrustProvider.
+func (in *BeyondtrustProvider) DeepCopy() *BeyondtrustProvider {
+	if in == nil {
+		return nil
+	}
+	out := new(BeyondtrustProvider)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *BeyondtrustServer) DeepCopyInto(out *BeyondtrustServer) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BeyondtrustServer.
+func (in *BeyondtrustServer) DeepCopy() *BeyondtrustServer {
+	if in == nil {
+		return nil
+	}
+	out := new(BeyondtrustServer)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *BitwardenSecretsManagerAuth) DeepCopyInto(out *BitwardenSecretsManagerAuth) {
 	*out = *in
 	in.SecretRef.DeepCopyInto(&out.SecretRef)
@@ -2543,6 +2638,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
 		*out = new(InfisicalProvider)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.Beyondtrust != nil {
+		in, out := &in.Beyondtrust, &out.Beyondtrust
+		*out = new(BeyondtrustProvider)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreProvider.

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

@@ -2297,6 +2297,156 @@ spec:
                     required:
                     - vaultUrl
                     type: object
+                  beyondtrust:
+                    description: Beyondtrust configures this store to sync secrets
+                      using Password Safe provider.
+                    properties:
+                      auth:
+                        description: Auth configures how the operator authenticates
+                          with Beyondtrust.
+                        properties:
+                          certificate:
+                            description: Content of the certificate (cert.pem) for
+                              use when authenticating with an OAuth client Id using
+                              a Client Certificate.
+                            properties:
+                              secretRef:
+                                description: SecretRef references a key in a secret
+                                  that will be used as value.
+                                properties:
+                                  key:
+                                    description: |-
+                                      The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                      defaulted, in others it may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                      to the namespace of the referent.
+                                    type: string
+                                type: object
+                              value:
+                                description: Value can be specified directly to set
+                                  a value without using a secret.
+                                type: string
+                            type: object
+                          certificateKey:
+                            description: Certificate private key (key.pem). For use
+                              when authenticating with an OAuth client Id
+                            properties:
+                              secretRef:
+                                description: SecretRef references a key in a secret
+                                  that will be used as value.
+                                properties:
+                                  key:
+                                    description: |-
+                                      The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                      defaulted, in others it may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                      to the namespace of the referent.
+                                    type: string
+                                type: object
+                              value:
+                                description: Value can be specified directly to set
+                                  a value without using a secret.
+                                type: string
+                            type: object
+                          clientId:
+                            properties:
+                              secretRef:
+                                description: SecretRef references a key in a secret
+                                  that will be used as value.
+                                properties:
+                                  key:
+                                    description: |-
+                                      The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                      defaulted, in others it may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                      to the namespace of the referent.
+                                    type: string
+                                type: object
+                              value:
+                                description: Value can be specified directly to set
+                                  a value without using a secret.
+                                type: string
+                            type: object
+                          clientSecret:
+                            properties:
+                              secretRef:
+                                description: SecretRef references a key in a secret
+                                  that will be used as value.
+                                properties:
+                                  key:
+                                    description: |-
+                                      The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                      defaulted, in others it may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                      to the namespace of the referent.
+                                    type: string
+                                type: object
+                              value:
+                                description: Value can be specified directly to set
+                                  a value without using a secret.
+                                type: string
+                            type: object
+                        required:
+                        - clientId
+                        - clientSecret
+                        type: object
+                      server:
+                        description: Auth configures how API server works.
+                        properties:
+                          apiUrl:
+                            type: string
+                          clientTimeOutSeconds:
+                            description: Timeout specifies a time limit for requests
+                              made by this Client. The timeout includes connection
+                              time, any redirects, and reading the response body.
+                              Defaults to 45 seconds.
+                            type: integer
+                          retrievalType:
+                            description: The secret retrieval type. SECRET = Secrets
+                              Safe (credential, text, file). MANAGED_ACCOUNT = Password
+                              Safe account associated with a system.
+                            type: string
+                          separator:
+                            description: A character that separates the folder names.
+                            type: string
+                          verifyCA:
+                            type: boolean
+                        required:
+                        - apiUrl
+                        - verifyCA
+                        type: object
+                    required:
+                    - auth
+                    - server
+                    type: object
                   bitwardensecretsmanager:
                     description: BitwardenSecretsManager configures this store to
                       sync secrets using BitwardenSecretsManager provider

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

@@ -2297,6 +2297,156 @@ spec:
                     required:
                     - vaultUrl
                     type: object
+                  beyondtrust:
+                    description: Beyondtrust configures this store to sync secrets
+                      using Password Safe provider.
+                    properties:
+                      auth:
+                        description: Auth configures how the operator authenticates
+                          with Beyondtrust.
+                        properties:
+                          certificate:
+                            description: Content of the certificate (cert.pem) for
+                              use when authenticating with an OAuth client Id using
+                              a Client Certificate.
+                            properties:
+                              secretRef:
+                                description: SecretRef references a key in a secret
+                                  that will be used as value.
+                                properties:
+                                  key:
+                                    description: |-
+                                      The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                      defaulted, in others it may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                      to the namespace of the referent.
+                                    type: string
+                                type: object
+                              value:
+                                description: Value can be specified directly to set
+                                  a value without using a secret.
+                                type: string
+                            type: object
+                          certificateKey:
+                            description: Certificate private key (key.pem). For use
+                              when authenticating with an OAuth client Id
+                            properties:
+                              secretRef:
+                                description: SecretRef references a key in a secret
+                                  that will be used as value.
+                                properties:
+                                  key:
+                                    description: |-
+                                      The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                      defaulted, in others it may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                      to the namespace of the referent.
+                                    type: string
+                                type: object
+                              value:
+                                description: Value can be specified directly to set
+                                  a value without using a secret.
+                                type: string
+                            type: object
+                          clientId:
+                            properties:
+                              secretRef:
+                                description: SecretRef references a key in a secret
+                                  that will be used as value.
+                                properties:
+                                  key:
+                                    description: |-
+                                      The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                      defaulted, in others it may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                      to the namespace of the referent.
+                                    type: string
+                                type: object
+                              value:
+                                description: Value can be specified directly to set
+                                  a value without using a secret.
+                                type: string
+                            type: object
+                          clientSecret:
+                            properties:
+                              secretRef:
+                                description: SecretRef references a key in a secret
+                                  that will be used as value.
+                                properties:
+                                  key:
+                                    description: |-
+                                      The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                      defaulted, in others it may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: |-
+                                      Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                      to the namespace of the referent.
+                                    type: string
+                                type: object
+                              value:
+                                description: Value can be specified directly to set
+                                  a value without using a secret.
+                                type: string
+                            type: object
+                        required:
+                        - clientId
+                        - clientSecret
+                        type: object
+                      server:
+                        description: Auth configures how API server works.
+                        properties:
+                          apiUrl:
+                            type: string
+                          clientTimeOutSeconds:
+                            description: Timeout specifies a time limit for requests
+                              made by this Client. The timeout includes connection
+                              time, any redirects, and reading the response body.
+                              Defaults to 45 seconds.
+                            type: integer
+                          retrievalType:
+                            description: The secret retrieval type. SECRET = Secrets
+                              Safe (credential, text, file). MANAGED_ACCOUNT = Password
+                              Safe account associated with a system.
+                            type: string
+                          separator:
+                            description: A character that separates the folder names.
+                            type: string
+                          verifyCA:
+                            type: boolean
+                        required:
+                        - apiUrl
+                        - verifyCA
+                        type: object
+                    required:
+                    - auth
+                    - server
+                    type: object
                   bitwardensecretsmanager:
                     description: BitwardenSecretsManager configures this store to
                       sync secrets using BitwardenSecretsManager provider

+ 256 - 0
deploy/crds/bundle.yaml

@@ -2807,6 +2807,134 @@ spec:
                       required:
                         - vaultUrl
                       type: object
+                    beyondtrust:
+                      description: Beyondtrust configures this store to sync secrets using Password Safe provider.
+                      properties:
+                        auth:
+                          description: Auth configures how the operator authenticates with Beyondtrust.
+                          properties:
+                            certificate:
+                              description: Content of the certificate (cert.pem) for use when authenticating with an OAuth client Id using a Client Certificate.
+                              properties:
+                                secretRef:
+                                  description: SecretRef references a key in a secret that will be used as value.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                        defaulted, in others it may be required.
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                        to the namespace of the referent.
+                                      type: string
+                                  type: object
+                                value:
+                                  description: Value can be specified directly to set a value without using a secret.
+                                  type: string
+                              type: object
+                            certificateKey:
+                              description: Certificate private key (key.pem). For use when authenticating with an OAuth client Id
+                              properties:
+                                secretRef:
+                                  description: SecretRef references a key in a secret that will be used as value.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                        defaulted, in others it may be required.
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                        to the namespace of the referent.
+                                      type: string
+                                  type: object
+                                value:
+                                  description: Value can be specified directly to set a value without using a secret.
+                                  type: string
+                              type: object
+                            clientId:
+                              properties:
+                                secretRef:
+                                  description: SecretRef references a key in a secret that will be used as value.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                        defaulted, in others it may be required.
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                        to the namespace of the referent.
+                                      type: string
+                                  type: object
+                                value:
+                                  description: Value can be specified directly to set a value without using a secret.
+                                  type: string
+                              type: object
+                            clientSecret:
+                              properties:
+                                secretRef:
+                                  description: SecretRef references a key in a secret that will be used as value.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                        defaulted, in others it may be required.
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                        to the namespace of the referent.
+                                      type: string
+                                  type: object
+                                value:
+                                  description: Value can be specified directly to set a value without using a secret.
+                                  type: string
+                              type: object
+                          required:
+                            - clientId
+                            - clientSecret
+                          type: object
+                        server:
+                          description: Auth configures how API server works.
+                          properties:
+                            apiUrl:
+                              type: string
+                            clientTimeOutSeconds:
+                              description: Timeout specifies a time limit for requests made by this Client. The timeout includes connection time, any redirects, and reading the response body. Defaults to 45 seconds.
+                              type: integer
+                            retrievalType:
+                              description: The secret retrieval type. SECRET = Secrets Safe (credential, text, file). MANAGED_ACCOUNT = Password Safe account associated with a system.
+                              type: string
+                            separator:
+                              description: A character that separates the folder names.
+                              type: string
+                            verifyCA:
+                              type: boolean
+                          required:
+                            - apiUrl
+                            - verifyCA
+                          type: object
+                      required:
+                        - auth
+                        - server
+                      type: object
                     bitwardensecretsmanager:
                       description: BitwardenSecretsManager configures this store to sync secrets using BitwardenSecretsManager provider
                       properties:
@@ -8441,6 +8569,134 @@ spec:
                       required:
                         - vaultUrl
                       type: object
+                    beyondtrust:
+                      description: Beyondtrust configures this store to sync secrets using Password Safe provider.
+                      properties:
+                        auth:
+                          description: Auth configures how the operator authenticates with Beyondtrust.
+                          properties:
+                            certificate:
+                              description: Content of the certificate (cert.pem) for use when authenticating with an OAuth client Id using a Client Certificate.
+                              properties:
+                                secretRef:
+                                  description: SecretRef references a key in a secret that will be used as value.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                        defaulted, in others it may be required.
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                        to the namespace of the referent.
+                                      type: string
+                                  type: object
+                                value:
+                                  description: Value can be specified directly to set a value without using a secret.
+                                  type: string
+                              type: object
+                            certificateKey:
+                              description: Certificate private key (key.pem). For use when authenticating with an OAuth client Id
+                              properties:
+                                secretRef:
+                                  description: SecretRef references a key in a secret that will be used as value.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                        defaulted, in others it may be required.
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                        to the namespace of the referent.
+                                      type: string
+                                  type: object
+                                value:
+                                  description: Value can be specified directly to set a value without using a secret.
+                                  type: string
+                              type: object
+                            clientId:
+                              properties:
+                                secretRef:
+                                  description: SecretRef references a key in a secret that will be used as value.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                        defaulted, in others it may be required.
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                        to the namespace of the referent.
+                                      type: string
+                                  type: object
+                                value:
+                                  description: Value can be specified directly to set a value without using a secret.
+                                  type: string
+                              type: object
+                            clientSecret:
+                              properties:
+                                secretRef:
+                                  description: SecretRef references a key in a secret that will be used as value.
+                                  properties:
+                                    key:
+                                      description: |-
+                                        The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
+                                        defaulted, in others it may be required.
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      type: string
+                                    namespace:
+                                      description: |-
+                                        Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
+                                        to the namespace of the referent.
+                                      type: string
+                                  type: object
+                                value:
+                                  description: Value can be specified directly to set a value without using a secret.
+                                  type: string
+                              type: object
+                          required:
+                            - clientId
+                            - clientSecret
+                          type: object
+                        server:
+                          description: Auth configures how API server works.
+                          properties:
+                            apiUrl:
+                              type: string
+                            clientTimeOutSeconds:
+                              description: Timeout specifies a time limit for requests made by this Client. The timeout includes connection time, any redirects, and reading the response body. Defaults to 45 seconds.
+                              type: integer
+                            retrievalType:
+                              description: The secret retrieval type. SECRET = Secrets Safe (credential, text, file). MANAGED_ACCOUNT = Password Safe account associated with a system.
+                              type: string
+                            separator:
+                              description: A character that separates the folder names.
+                              type: string
+                            verifyCA:
+                              type: boolean
+                          required:
+                            - apiUrl
+                            - verifyCA
+                          type: object
+                      required:
+                        - auth
+                        - server
+                      type: object
                     bitwardensecretsmanager:
                       description: BitwardenSecretsManager configures this store to sync secrets using BitwardenSecretsManager provider
                       properties:

+ 243 - 0
docs/api/spec.md

@@ -1013,6 +1013,235 @@ string
 </tr>
 </tbody>
 </table>
+<h3 id="external-secrets.io/v1beta1.BeyondTrustProviderSecretRef">BeyondTrustProviderSecretRef
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1beta1.BeyondtrustAuth">BeyondtrustAuth</a>)
+</p>
+<p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>value</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>Value can be specified directly to set a value without using a secret.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>secretRef</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>SecretRef references a key in a secret that will be used as value.</p>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1beta1.BeyondtrustAuth">BeyondtrustAuth
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1beta1.BeyondtrustProvider">BeyondtrustProvider</a>)
+</p>
+<p>
+<p>Configures a store to sync secrets using BeyondTrust Password Safe.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>clientId</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.BeyondTrustProviderSecretRef">
+BeyondTrustProviderSecretRef
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>clientSecret</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.BeyondTrustProviderSecretRef">
+BeyondTrustProviderSecretRef
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>certificate</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.BeyondTrustProviderSecretRef">
+BeyondTrustProviderSecretRef
+</a>
+</em>
+</td>
+<td>
+<p>Content of the certificate (cert.pem) for use when authenticating with an OAuth client Id using a Client Certificate.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>certificateKey</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.BeyondTrustProviderSecretRef">
+BeyondTrustProviderSecretRef
+</a>
+</em>
+</td>
+<td>
+<p>Certificate private key (key.pem). For use when authenticating with an OAuth client Id</p>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1beta1.BeyondtrustProvider">BeyondtrustProvider
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1beta1.SecretStoreProvider">SecretStoreProvider</a>)
+</p>
+<p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>auth</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.BeyondtrustAuth">
+BeyondtrustAuth
+</a>
+</em>
+</td>
+<td>
+<p>Auth configures how the operator authenticates with Beyondtrust.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>server</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.BeyondtrustServer">
+BeyondtrustServer
+</a>
+</em>
+</td>
+<td>
+<p>Auth configures how API server works.</p>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1beta1.BeyondtrustServer">BeyondtrustServer
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1beta1.BeyondtrustProvider">BeyondtrustProvider</a>)
+</p>
+<p>
+<p>Configures a store to sync secrets using BeyondTrust Password Safe.</p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>apiUrl</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>retrievalType</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<p>The secret retrieval type. SECRET = Secrets Safe (credential, text, file). MANAGED_ACCOUNT = Password Safe account associated with a system.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>separator</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+<p>A character that separates the folder names.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>verifyCA</code></br>
+<em>
+bool
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>clientTimeOutSeconds</code></br>
+<em>
+int
+</em>
+</td>
+<td>
+<p>Timeout specifies a time limit for requests made by this Client. The timeout includes connection time, any redirects, and reading the response body. Defaults to 45 seconds.</p>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1beta1.BitwardenSecretsManagerAuth">BitwardenSecretsManagerAuth
 </h3>
 <p>
@@ -6660,6 +6889,20 @@ InfisicalProvider
 <p>Infisical configures this store to sync secrets using the Infisical provider</p>
 </td>
 </tr>
+<tr>
+<td>
+<code>beyondtrust</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.BeyondtrustProvider">
+BeyondtrustProvider
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>Beyondtrust configures this store to sync secrets using Password Safe provider.</p>
+</td>
+</tr>
 </tbody>
 </table>
 <h3 id="external-secrets.io/v1beta1.SecretStoreRef">SecretStoreRef

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

@@ -53,6 +53,7 @@ The following table describes the stability level of each provider and who's res
 | [Scaleway](https://external-secrets.io/latest/provider/scaleway)                                           |   alpha   |                                                                                                                                                   [@azert9](https://github.com/azert9/) |
 | [Conjur](https://external-secrets.io/latest/provider/conjur)                                               |   stable   |                                                                                                                                 [@davidh-cyberark](https://github.com/davidh-cyberark/) [@szh](https://github.com/szh) |
 | [Delinea](https://external-secrets.io/latest/provider/delinea)                                             |   alpha   |                                                                                                                                     [@michaelsauter](https://github.com/michaelsauter/) |
+| [Beyondtrust](https://external-secrets.io/latest/provider/beyondtrust)                                     |   alpha   |                                                                                                                                       [@btfhernandez](https://github.com/btfhernandez/) |
 | [SecretServer](https://external-secrets.io/latest/provider/secretserver)                                   |   alpha   |                                                                                                                                     [@billhamilton](https://github.com/pacificcode/) |
 | [Pulumi ESC](https://external-secrets.io/latest/provider/pulumi)                                           |   alpha   |                                                                                                                                                  [@dirien](https://github.com/dirien) |
 | [Passbolt](https://external-secrets.io/latest/provider/passbolt)                                           |   alpha   |                                                                                                                                                   |
@@ -86,6 +87,7 @@ The following table show the support for features across different providers.
 | Scaleway                  |      x       |      x       |                      |                         |        x         |      x      |              x              |
 | Conjur                    |      x       |      x       |                      |                         |        x         |             |                             |
 | Delinea                   |      x       |              |                      |                         |        x         |             |                             |
+| Beyondtrust               |      x       |              |                      |                         |        x         |             |                             |
 | SecretServer              |      x       |              |                      |                         |        x         |             |                             |
 | Pulumi ESC                |      x       |              |                      |                         |        x         |             |                             |
 | Passbolt                  |      x       |              |                      |                         |        x         |             |                             |

+ 124 - 0
docs/provider/beyondtrust.md

@@ -0,0 +1,124 @@
+## BeyondTrust Password Safe
+
+External Secrets Operator integrates with [BeyondTrust Password Safe](https://www.beyondtrust.com/docs/beyondinsight-password-safe/).
+
+Warning: The External Secrets Operator secure usage involves taking several measures. Please see [Security Best Practices](https://external-secrets.io/latest/guides/security-best-practices/) for more information.
+
+Warning: If the BT provider secret is deleted it will still exist in the Kubernetes secrets.
+
+### Prerequisites
+The BT provider supports retrieval of a secret from BeyondInsight/Password Safe versions 23.1 or greater.
+
+For this provider to retrieve a secret the Password Safe/Secrets Safe instance must be preconfigured with the secret in question and authorized to read it.
+
+### Authentication
+
+BeyondTrust [OAuth Authentication](https://www.beyondtrust.com/docs/beyondinsight-password-safe/ps/admin/configure-api-registration.htm).
+
+1. Create an API access registration in BeyondInsight
+2. Create or use an existing Secrets Safe Group
+3. Create or use an existing Application User
+4. Add API registration to the Application user
+5. Add the user to the group
+6. Add the Secrets Safe Feature to the group
+
+> NOTE: The ClentID and ClientSecret must be stored in a Kubernetes secret in order for the SecretStore to read the configuration.
+
+```sh
+kubectl create secret generic bt-secret --from-literal ClientSecret="<your secret>"
+kubectl create secret generic bt-id --from-literal ClientId="<your ID>"
+```
+### Client Certificate
+Download the pfx certificate from Secrets Safe extract the certificate and create two Kubernetes secret.
+
+```sh
+openssl pkcs12 -in client_certificate.pfx -nocerts -out ps_key.pem -nodes
+openssl pkcs12 -in client_certificate.pfx -clcerts -nokeys -out ps_cert.pem
+
+# Copy the text from the ps_key.pem to a file.
+-----BEGIN PRIVATE KEY-----
+...
+-----END PRIVATE KEY-----
+
+# Copy the text from the ps_cert.pem to a file.
+-----BEGIN CERTIFICATE-----
+...
+-----END CERTIFICATE-----
+
+kubectl create secret generic bt-certificate --from-file=ClientCertificate=./ps_cert.pem
+kubectl create secret generic bt-certificatekey --from-file=ClientCertificateKey=./ps_key.pem
+```
+
+### Creating a SecretStore
+
+You can follow the below example to create a `SecretStore` resource.
+You can also use a `ClusterSecretStore` allowing you to reference secrets from all namespaces. [ClusterSecretStore](https://external-secrets.io/latest/api/clustersecretstore/)
+
+```sh
+kubectl apply -f secret-store.yml
+```
+
+```yaml
+apiVersion: external-secrets.io/v1beta1
+kind: SecretStore
+metadata:
+ name: secretstore-beyondtrust
+spec:
+ provider:
+   beyondtrust:
+    apiurl: https://example.com:443/BeyondTrust/api/public/v3/
+    certificate:
+      secretRef:
+          name: bt-certificate
+          key: ClientCertificate
+    certificatekey:
+      secretRef:
+          name: bt-certificatekey
+          key: ClientCertificateKey
+    clientsecret:
+      secretRef:
+        name: bt-secret
+        key: ClientSecret
+    clientid:
+      secretRef:
+        name: bt-id
+        key: ClientId
+    retrievaltype: MANAGED_ACCOUNT
+    verifyca: true
+    clienttimeoutseconds: 45
+```
+
+### Creating a ExternalSecret
+
+You can follow the below example to create a `ExternalSecret` resource. Secrets can be referenced by path.
+You can also use a `ClusterExternalSecret` allowing you to reference secrets from all namespaces.
+
+```sh
+kubectl apply -f external-secret.yml
+```
+
+```yaml
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+ name: beyondtrust-external-secret
+spec:
+ refreshInterval: 300s
+ secretStoreRef:
+   kind: SecretStore
+   name: secretstore-beyondtrust
+ target:
+   name: my-beyondtrust-secret # name of secret to create in k8s secrets (etcd)
+   creationPolicy: Owner
+ data:
+   - secretKey: secretKey
+     remoteRef:
+       key: system01/managed_account01
+```
+
+### Get the K8s secret
+
+```shell
+# WARNING: this command will reveal the stored secret in plain text
+kubectl get secret my-beyondtrust-secret -o jsonpath="{.data.secretKey}" | base64 --decode && echo
+```

+ 16 - 0
docs/snippets/beyondtrust-external-secret.yaml

@@ -0,0 +1,16 @@
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+ name: beyondtrust-external-secret
+spec:
+ refreshInterval: 300s
+ secretStoreRef:
+   kind: SecretStore
+   name: secretstore-beyondtrust
+ target:
+   name: my-beyondtrust-secret # name of secret to create in k8s secrets (etcd)
+   creationPolicy: Owner
+ data:
+   - secretKey: secretKey
+     remoteRef:
+       key: system01/managed_account01

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

@@ -0,0 +1,29 @@
+apiVersion: external-secrets.io/v1beta1
+kind: SecretStore
+metadata:
+ name: secretstore-beyondtrust
+spec:
+ provider:
+   beyondtrust:
+    auth:
+      certificate:
+        secretRef:
+            name: bt-certificate
+            key: ClientCertificate
+      certificateKey:
+        secretRef:
+            name: bt-certificatekey
+            key: ClientCertificateKey
+      clientSecret:
+        secretRef:
+          name: bt-secret
+          key: ClientSecret
+      clientId:
+        secretRef:
+          name: bt-id
+          key: ClientId
+    server:
+      retrievalType: MANAGED_ACCOUNT
+      verifyCA: true
+      clientTimeOutSeconds: 45
+      apiurl: https://example.ps-dev.beyondtrustcloud.com:443/BeyondTrust/api/public/v3/

+ 2 - 0
go.mod

@@ -65,6 +65,7 @@ require (
 	dario.cat/mergo v1.0.0
 	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0
 	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0
+	github.com/BeyondTrust/go-client-library-passwordsafe v0.6.0
 	github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.2
 	github.com/DelineaXPM/tss-sdk-go/v2 v2.0.1
 	github.com/Onboardbase/go-cryptojs-aes-decrypt v0.0.0-20230430095000-27c0d3a9016d
@@ -76,6 +77,7 @@ require (
 	github.com/alibabacloud-go/tea-utils/v2 v2.0.6
 	github.com/aliyun/credentials-go v1.3.6
 	github.com/avast/retry-go/v4 v4.6.0
+	github.com/cenkalti/backoff/v4 v4.3.0
 	github.com/cyberark/conjur-api-go v0.12.4
 	github.com/fortanix/sdkms-client-go v0.4.0
 	github.com/go-openapi/strfmt v0.23.0

+ 4 - 0
go.sum

@@ -95,6 +95,8 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp
 github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
 github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
 github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
+github.com/BeyondTrust/go-client-library-passwordsafe v0.6.0 h1:3zdjZl8h3/9DzTnpWqAzhiUqMwIzpU+EL0grJ7BODV8=
+github.com/BeyondTrust/go-client-library-passwordsafe v0.6.0/go.mod h1:TnbBwWYg9rtfDxQGF7pmD0gCPcbWgCUQIqum3dFMRTk=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.2 h1:cmX2QC9s5kPqmghWLLZP8YRFO1ZD/C59BpNH2ujP99w=
@@ -201,6 +203,8 @@ github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N
 github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
 github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
 github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
+github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
+github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=

+ 338 - 0
pkg/provider/beyondtrust/provider.go

@@ -0,0 +1,338 @@
+/*
+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
+
+    http://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 implieclient.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package beyondtrust
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net/url"
+	"strings"
+	"time"
+
+	auth "github.com/BeyondTrust/go-client-library-passwordsafe/api/authentication"
+	"github.com/BeyondTrust/go-client-library-passwordsafe/api/logging"
+	managed_account "github.com/BeyondTrust/go-client-library-passwordsafe/api/managed_account"
+	"github.com/BeyondTrust/go-client-library-passwordsafe/api/secrets"
+	"github.com/BeyondTrust/go-client-library-passwordsafe/api/utils"
+	"github.com/cenkalti/backoff/v4"
+	v1 "k8s.io/api/core/v1"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	esoClient "github.com/external-secrets/external-secrets/pkg/utils"
+)
+
+const (
+	errNilStore             = "nil store found"
+	errMissingStoreSpec     = "store is missing spec"
+	errMissingProvider      = "storeSpec is missing provider"
+	errInvalidProvider      = "invalid provider spec. Missing field in store %s"
+	errInvalidHostURL       = "invalid host URL"
+	errNoSuchKeyFmt         = "no such key in secret: %q"
+	errInvalidRetrievalPath = "invalid retrieval path. Provide one path, separator and name"
+	errNotImplemented       = "not implemented"
+)
+
+var (
+	errSecretRefAndValueConflict = errors.New("cannot specify both secret reference and value")
+	errMissingSecretName         = errors.New("must specify a secret name")
+	errMissingSecretKey          = errors.New("must specify a secret key")
+	ESOLogger                    = ctrl.Log.WithName("provider").WithName("beyondtrust")
+	maxFileSecretSizeBytes       = 5000000
+)
+
+// Provider is a Password Safe secrets provider implementing NewClient and ValidateStore for the esv1beta1.Provider interface.
+type Provider struct {
+	apiURL        string
+	retrievaltype string
+	authenticate  auth.AuthenticationObj
+	log           logging.LogrLogger
+	separator     string
+}
+
+// Capabilities implements v1beta1.Provider.
+func (*Provider) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadOnly
+}
+
+// Close implements v1beta1.SecretsClient.
+func (*Provider) Close(_ context.Context) error {
+	return nil
+}
+
+// DeleteSecret implements v1beta1.SecretsClient.
+func (*Provider) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
+	return fmt.Errorf(errNotImplemented)
+}
+
+// GetSecretMap implements v1beta1.SecretsClient.
+func (*Provider) GetSecretMap(_ context.Context, _ esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+	return make(map[string][]byte), fmt.Errorf(errNotImplemented)
+}
+
+// PushSecret implements v1beta1.SecretsClient.
+func (*Provider) PushSecret(_ context.Context, _ *v1.Secret, _ esv1beta1.PushSecretData) error {
+	return fmt.Errorf(errNotImplemented)
+}
+
+// Validate implements v1beta1.SecretsClient.
+func (p *Provider) Validate() (esv1beta1.ValidationResult, error) {
+	timeout := 15 * time.Second
+	clientURL := p.apiURL
+
+	if err := esoClient.NetworkValidate(clientURL, timeout); err != nil {
+		ESOLogger.Error(err, "Network Validate", "clientURL:", clientURL)
+		return esv1beta1.ValidationResultError, err
+	}
+
+	return esv1beta1.ValidationResultReady, nil
+}
+
+func (*Provider) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
+	return false, fmt.Errorf(errNotImplemented)
+}
+
+// NewClient this is where we initialize the SecretClient and return it for the controller to use.
+func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
+	config := store.GetSpec().Provider.Beyondtrust
+	logger := logging.NewLogrLogger(&ESOLogger)
+	apiURL := config.Server.APIURL
+	certificate := ""
+	certificateKey := ""
+	clientTimeOutInSeconds := 45
+	retryMaxElapsedTimeMinutes := 15
+	separator := "/"
+
+	if config.Server.Separator != "" {
+		separator = config.Server.Separator
+	}
+
+	if config.Server.ClientTimeOutSeconds != 0 {
+		clientTimeOutInSeconds = config.Server.ClientTimeOutSeconds
+	}
+
+	backoffDefinition := backoff.NewExponentialBackOff()
+	backoffDefinition.InitialInterval = 1 * time.Second
+	backoffDefinition.MaxElapsedTime = time.Duration(retryMaxElapsedTimeMinutes) * time.Second
+	backoffDefinition.RandomizationFactor = 0.5
+
+	clientID, err := loadConfigSecret(ctx, config.Auth.ClientID, kube, namespace)
+	if err != nil {
+		return nil, fmt.Errorf("error loading clientID: %w", err)
+	}
+
+	clientSecret, err := loadConfigSecret(ctx, config.Auth.ClientSecret, kube, namespace)
+	if err != nil {
+		return nil, fmt.Errorf("error loading clientSecret: %w", err)
+	}
+
+	if config.Auth.Certificate != nil && config.Auth.CertificateKey != nil {
+		loadedCertificate, err := loadConfigSecret(ctx, config.Auth.Certificate, kube, namespace)
+		if err != nil {
+			return nil, fmt.Errorf("error loading Certificate: %w", err)
+		}
+
+		certificate = loadedCertificate
+
+		loadedCertificateKey, err := loadConfigSecret(ctx, config.Auth.CertificateKey, kube, namespace)
+		if err != nil {
+			return nil, fmt.Errorf("error loading Certificate Key: %w", err)
+		}
+
+		certificateKey = loadedCertificateKey
+	}
+
+	// Create an instance of ValidationParams
+	params := utils.ValidationParams{
+		ClientID:                   clientID,
+		ClientSecret:               clientSecret,
+		ApiUrl:                     &apiURL,
+		ClientTimeOutInSeconds:     clientTimeOutInSeconds,
+		Separator:                  &separator,
+		VerifyCa:                   config.Server.VerifyCA,
+		Logger:                     logger,
+		Certificate:                certificate,
+		CertificateKey:             certificateKey,
+		RetryMaxElapsedTimeMinutes: &retryMaxElapsedTimeMinutes,
+		MaxFileSecretSizeBytes:     &maxFileSecretSizeBytes,
+	}
+
+	errorsInInputs := utils.ValidateInputs(params)
+
+	if errorsInInputs != nil {
+		return nil, fmt.Errorf("error in Inputs: %w", errorsInInputs)
+	}
+
+	// creating a http client
+	httpClientObj, err := utils.GetHttpClient(clientTimeOutInSeconds, config.Server.VerifyCA, certificate, certificateKey, logger)
+
+	if err != nil {
+		return nil, fmt.Errorf("error creating http client: %w", err)
+	}
+
+	// instantiating authenticate obj, injecting httpClient object
+	authenticate, _ := auth.Authenticate(*httpClientObj, backoffDefinition, apiURL, clientID, clientSecret, logger, retryMaxElapsedTimeMinutes)
+
+	return &Provider{
+		apiURL:        config.Server.APIURL,
+		retrievaltype: config.Server.RetrievalType,
+		authenticate:  *authenticate,
+		log:           *logger,
+		separator:     separator,
+	}, nil
+}
+
+func loadConfigSecret(ctx context.Context, ref *esv1beta1.BeyondTrustProviderSecretRef, kube client.Client, defaultNamespace string) (string, error) {
+	if ref.SecretRef == nil {
+		return ref.Value, nil
+	}
+
+	if err := validateSecretRef(ref); err != nil {
+		return "", err
+	}
+
+	namespace := defaultNamespace
+	if ref.SecretRef.Namespace != nil {
+		namespace = *ref.SecretRef.Namespace
+	}
+
+	ESOLogger.Info("using k8s secret", "name:", ref.SecretRef.Name, "namespace:", namespace)
+	objKey := client.ObjectKey{Namespace: namespace, Name: ref.SecretRef.Name}
+	secret := v1.Secret{}
+	err := kube.Get(ctx, objKey, &secret)
+	if err != nil {
+		return "", err
+	}
+
+	value, ok := secret.Data[ref.SecretRef.Key]
+	if !ok {
+		return "", fmt.Errorf(errNoSuchKeyFmt, ref.SecretRef.Key)
+	}
+
+	return string(value), nil
+}
+
+func validateSecretRef(ref *esv1beta1.BeyondTrustProviderSecretRef) error {
+	if ref.SecretRef != nil {
+		if ref.Value != "" {
+			return errSecretRefAndValueConflict
+		}
+		if ref.SecretRef.Name == "" {
+			return errMissingSecretName
+		}
+		if ref.SecretRef.Key == "" {
+			return errMissingSecretKey
+		}
+	}
+
+	return nil
+}
+
+func (p *Provider) GetAllSecrets(_ context.Context, _ esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
+	return nil, fmt.Errorf("GetAllSecrets not implemented")
+}
+
+// GetSecret reads the secret from the Password Safe server and returns it. The controller uses the value here to
+// create the Kubernetes secret.
+func (p *Provider) GetSecret(_ context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
+	managedAccountType := !strings.EqualFold(p.retrievaltype, "SECRET")
+
+	retrievalPaths := utils.ValidatePaths([]string{ref.Key}, managedAccountType, p.separator, &p.log)
+
+	if len(retrievalPaths) != 1 {
+		return nil, fmt.Errorf(errInvalidRetrievalPath)
+	}
+
+	retrievalPath := retrievalPaths[0]
+
+	_, err := p.authenticate.GetPasswordSafeAuthentication()
+	if err != nil {
+		return nil, fmt.Errorf("error getting authentication: %w", err)
+	}
+
+	managedFetch := func() (string, error) {
+		ESOLogger.Info("retrieve managed account value", "retrievalPath:", retrievalPath)
+		manageAccountObj, _ := managed_account.NewManagedAccountObj(p.authenticate, &p.log)
+		return manageAccountObj.GetSecret(retrievalPath, p.separator)
+	}
+	unmanagedFetch := func() (string, error) {
+		ESOLogger.Info("retrieve secrets safe value", "retrievalPath:", retrievalPath)
+		secretObj, _ := secrets.NewSecretObj(p.authenticate, &p.log, maxFileSecretSizeBytes)
+		return secretObj.GetSecret(retrievalPath, p.separator)
+	}
+	fetch := unmanagedFetch
+	if managedAccountType {
+		fetch = managedFetch
+	}
+	returnSecret, err := fetch()
+	if err != nil {
+		if serr := p.authenticate.SignOut(); serr != nil {
+			return nil, errors.Join(err, serr)
+		}
+		return nil, fmt.Errorf("error getting secret/managed account: %w", err)
+	}
+	return []byte(returnSecret), nil
+}
+
+// ValidateStore validates the store configuration to prevent unexpected errors.
+func (p *Provider) ValidateStore(store esv1beta1.GenericStore) (admission.Warnings, error) {
+	if store == nil {
+		return nil, fmt.Errorf(errNilStore)
+	}
+
+	spec := store.GetSpec()
+
+	if spec == nil {
+		return nil, fmt.Errorf(errMissingStoreSpec)
+	}
+
+	if spec.Provider == nil {
+		return nil, fmt.Errorf(errMissingProvider)
+	}
+
+	provider := spec.Provider.Beyondtrust
+	if provider == nil {
+		return nil, fmt.Errorf(errInvalidProvider, store.GetObjectMeta().String())
+	}
+
+	apiURL, err := url.Parse(provider.Server.APIURL)
+	if err != nil {
+		return nil, fmt.Errorf(errInvalidHostURL)
+	}
+
+	if provider.Auth.ClientID.SecretRef != nil {
+		return nil, err
+	}
+
+	if provider.Auth.ClientSecret.SecretRef != nil {
+		return nil, err
+	}
+
+	if apiURL.Host == "" {
+		return nil, fmt.Errorf(errInvalidHostURL)
+	}
+
+	return nil, nil
+}
+
+// registers the provider object to process on each reconciliation loop.
+func init() {
+	esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
+		Beyondtrust: &esv1beta1.BeyondtrustProvider{},
+	})
+}

+ 285 - 0
pkg/provider/beyondtrust/provider_test.go

@@ -0,0 +1,285 @@
+/*
+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
+	http://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 implieclient.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package beyondtrust
+
+import (
+	"context"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"k8s.io/client-go/tools/clientcmd"
+	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
+	kubeclient "sigs.k8s.io/controller-runtime/pkg/client"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+)
+
+const (
+	errTestCase  = "Test case Failed"
+	fakeAPIURL   = "https://example.com:443/BeyondTrust/api/public/v3/"
+	clientID     = "12345678-25fg-4b05-9ced-35e7dd5093ae"
+	clientSecret = "12345678-25fg-4b05-9ced-35e7dd5093ae"
+)
+
+func createMockPasswordSafeClient(t *testing.T) kubeclient.Client {
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		switch r.URL.Path {
+		case "/Auth/SignAppin":
+			_, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"fake@beyondtrust.com"}`))
+			if err != nil {
+				t.Error(errTestCase)
+			}
+
+		case "/Auth/Signout":
+			_, err := w.Write([]byte(``))
+			if err != nil {
+				t.Error(errTestCase)
+			}
+
+		case "/secrets-safe/secrets":
+			_, err := w.Write([]byte(`[{"SecretType": "FILE", "Password": "credential_in_sub_3_password","Id": "12345678-07d6-4955-175a-08db047219ce","Title": "credential_in_sub_3"}]`))
+			if err != nil {
+				t.Error(errTestCase)
+			}
+
+		case "/secrets-safe/secrets/12345678-07d6-4955-175a-08db047219ce/file/download":
+			_, err := w.Write([]byte(`fake_password`))
+			if err != nil {
+				t.Error(errTestCase)
+			}
+
+		default:
+			http.NotFound(w, r)
+		}
+	}))
+	t.Cleanup(server.Close)
+
+	clientConfig := clientcmd.NewDefaultClientConfig(clientcmdapi.Config{
+		Clusters: map[string]*clientcmdapi.Cluster{
+			"test": {
+				Server: server.URL,
+			},
+		},
+		AuthInfos: map[string]*clientcmdapi.AuthInfo{
+			"test": {
+				Token: "token",
+			},
+		},
+		Contexts: map[string]*clientcmdapi.Context{
+			"test": {
+				Cluster:  "test",
+				AuthInfo: "test",
+			},
+		},
+		CurrentContext: "test",
+	}, &clientcmd.ConfigOverrides{})
+
+	restConfig, err := clientConfig.ClientConfig()
+	assert.Nil(t, err)
+	c, err := kubeclient.New(restConfig, kubeclient.Options{})
+	assert.Nil(t, err)
+
+	return c
+}
+
+func TestNewClient(t *testing.T) {
+	type args struct {
+		store    esv1beta1.SecretStore
+		kube     kubeclient.Client
+		provider esv1beta1.Provider
+	}
+	tests := []struct {
+		name              string
+		nameSpace         string
+		args              args
+		validateErrorNil  bool
+		validateErrorText bool
+		expectedErrorText string
+	}{
+		{
+			name:      "Client ok",
+			nameSpace: "test",
+			args: args{
+				store: esv1beta1.SecretStore{
+					Spec: esv1beta1.SecretStoreSpec{
+						Provider: &esv1beta1.SecretStoreProvider{
+							Beyondtrust: &esv1beta1.BeyondtrustProvider{
+								Server: &esv1beta1.BeyondtrustServer{
+									APIURL:        fakeAPIURL,
+									RetrievalType: "SECRET",
+								},
+
+								Auth: &esv1beta1.BeyondtrustAuth{
+									ClientID: &esv1beta1.BeyondTrustProviderSecretRef{
+										Value: clientID,
+									},
+									ClientSecret: &esv1beta1.BeyondTrustProviderSecretRef{
+										Value: clientSecret,
+									},
+								},
+							},
+						},
+					},
+				},
+				kube:     createMockPasswordSafeClient(t),
+				provider: &Provider{},
+			},
+			validateErrorNil:  true,
+			validateErrorText: false,
+		},
+		{
+			name:      "Bad Client Id",
+			nameSpace: "test",
+			args: args{
+				store: esv1beta1.SecretStore{
+					Spec: esv1beta1.SecretStoreSpec{
+						Provider: &esv1beta1.SecretStoreProvider{
+							Beyondtrust: &esv1beta1.BeyondtrustProvider{
+								Server: &esv1beta1.BeyondtrustServer{
+									APIURL:        fakeAPIURL,
+									RetrievalType: "SECRET",
+								},
+
+								Auth: &esv1beta1.BeyondtrustAuth{
+									ClientID: &esv1beta1.BeyondTrustProviderSecretRef{
+										Value: "6138d050",
+									},
+									ClientSecret: &esv1beta1.BeyondTrustProviderSecretRef{
+										Value: clientSecret,
+									},
+								},
+							},
+						},
+					},
+				},
+				kube:     createMockPasswordSafeClient(t),
+				provider: &Provider{},
+			},
+			validateErrorNil:  false,
+			validateErrorText: true,
+			expectedErrorText: "error in Inputs: Key: 'UserInputValidaton.ClientId' Error:Field validation for 'ClientId' failed on the 'min' tag",
+		},
+		{
+			name:      "Bad Client Secret",
+			nameSpace: "test",
+			args: args{
+				store: esv1beta1.SecretStore{
+					Spec: esv1beta1.SecretStoreSpec{
+						Provider: &esv1beta1.SecretStoreProvider{
+							Beyondtrust: &esv1beta1.BeyondtrustProvider{
+								Server: &esv1beta1.BeyondtrustServer{
+									APIURL:        fakeAPIURL,
+									RetrievalType: "SECRET",
+								},
+
+								Auth: &esv1beta1.BeyondtrustAuth{
+									ClientSecret: &esv1beta1.BeyondTrustProviderSecretRef{
+										Value: "8i7U0Yulabon8mTc",
+									},
+									ClientID: &esv1beta1.BeyondTrustProviderSecretRef{
+										Value: clientID,
+									},
+								},
+							},
+						},
+					},
+				},
+				kube:     createMockPasswordSafeClient(t),
+				provider: &Provider{},
+			},
+			validateErrorNil:  false,
+			validateErrorText: true,
+			expectedErrorText: "error in Inputs: Key: 'UserInputValidaton.ClientSecret' Error:Field validation for 'ClientSecret' failed on the 'min' tag",
+		},
+		{
+			name:      "Bad Separator",
+			nameSpace: "test",
+			args: args{
+				store: esv1beta1.SecretStore{
+					Spec: esv1beta1.SecretStoreSpec{
+						Provider: &esv1beta1.SecretStoreProvider{
+							Beyondtrust: &esv1beta1.BeyondtrustProvider{
+								Server: &esv1beta1.BeyondtrustServer{
+									APIURL:        fakeAPIURL,
+									Separator:     "//",
+									RetrievalType: "SECRET",
+								},
+								Auth: &esv1beta1.BeyondtrustAuth{
+									ClientID: &esv1beta1.BeyondTrustProviderSecretRef{
+										Value: clientID,
+									},
+									ClientSecret: &esv1beta1.BeyondTrustProviderSecretRef{
+										Value: clientSecret,
+									},
+								},
+							},
+						},
+					},
+				},
+				kube:     createMockPasswordSafeClient(t),
+				provider: &Provider{},
+			},
+			validateErrorNil:  false,
+			validateErrorText: true,
+			expectedErrorText: "error in Inputs: Key: 'UserInputValidaton.Separator' Error:Field validation for 'Separator' failed on the 'max' tag",
+		},
+		{
+			name:      "Time Out",
+			nameSpace: "test",
+			args: args{
+				store: esv1beta1.SecretStore{
+					Spec: esv1beta1.SecretStoreSpec{
+						Provider: &esv1beta1.SecretStoreProvider{
+							Beyondtrust: &esv1beta1.BeyondtrustProvider{
+								Server: &esv1beta1.BeyondtrustServer{
+									APIURL:               fakeAPIURL,
+									Separator:            "/",
+									ClientTimeOutSeconds: 400,
+									RetrievalType:        "SECRET",
+								},
+								Auth: &esv1beta1.BeyondtrustAuth{
+									ClientID: &esv1beta1.BeyondTrustProviderSecretRef{
+										Value: clientID,
+									},
+									ClientSecret: &esv1beta1.BeyondTrustProviderSecretRef{
+										Value: clientSecret,
+									},
+								},
+							},
+						},
+					},
+				},
+				kube:     createMockPasswordSafeClient(t),
+				provider: &Provider{},
+			},
+			validateErrorNil:  false,
+			validateErrorText: true,
+			expectedErrorText: "error in Inputs: Key: 'UserInputValidaton.ClientTimeOutinSeconds' Error:Field validation for 'ClientTimeOutinSeconds' failed on the 'lte' tag",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			_, err := tt.args.provider.NewClient(context.Background(), &tt.args.store, tt.args.kube, tt.nameSpace)
+			if err != nil && tt.validateErrorNil {
+				t.Errorf("ProviderBeyondtrust.NewClient() error = %v", err)
+			}
+
+			if err != nil && tt.validateErrorText {
+				assert.Equal(t, err.Error(), tt.expectedErrorText)
+			}
+		})
+	}
+}

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

@@ -21,6 +21,7 @@ import (
 	_ "github.com/external-secrets/external-secrets/pkg/provider/alibaba"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/aws"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/azure/keyvault"
+	_ "github.com/external-secrets/external-secrets/pkg/provider/beyondtrust"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/bitwarden"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/chef"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/conjur"