1
0
Fork 0
mirror of https://github.com/external-secrets/external-secrets.git synced 2024-12-14 11:57:59 +00:00

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>
This commit is contained in:
btfhernandez 2024-08-07 02:27:04 -05:00 committed by GitHub
parent e359df615a
commit 77f5d0ad91
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 1767 additions and 0 deletions

View file

@ -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"`
}

View file

@ -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

View file

@ -391,6 +391,101 @@ func (in *AzureKVProvider) DeepCopy() *AzureKVProvider {
return out
}
// 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
@ -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.

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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 | | |

View file

@ -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
```

View file

@ -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

View file

@ -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
go.mod
View file

@ -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
go.sum
View file

@ -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=

View file

@ -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{},
})
}

View file

@ -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)
}
})
}
}

View file

@ -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"