mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
Add support for Delinea DevOps Secrets Vault (#2415)
* Add support for Delinea DevOps Secrets Vault Closes #1709. Signed-off-by: Michael Sauter <michael.sauter@boehringer-ingelheim.com> * fix: remove merge conflict Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> * Improve documentation Signed-off-by: Michael Sauter <michael.sauter@boehringer-ingelheim.com> --------- Signed-off-by: Michael Sauter <michael.sauter@boehringer-ingelheim.com> Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> Co-authored-by: Moritz Johner <beller.moritz@googlemail.com> Co-authored-by: Moritz Johner <moolen@users.noreply.github.com>
This commit is contained in:
parent
69fe93ea49
commit
bdf437c2e1
26 changed files with 1653 additions and 1 deletions
51
apis/externalsecrets/v1beta1/secretsstore_delinea_types.go
Normal file
51
apis/externalsecrets/v1beta1/secretsstore_delinea_types.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
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 DelineaProviderSecretRef 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// See https://github.com/DelineaXPM/dsv-sdk-go/blob/main/vault/vault.go.
|
||||||
|
type DelineaProvider struct {
|
||||||
|
|
||||||
|
// ClientID is the non-secret part of the credential.
|
||||||
|
ClientID *DelineaProviderSecretRef `json:"clientId"`
|
||||||
|
|
||||||
|
// ClientSecret is the secret part of the credential.
|
||||||
|
ClientSecret *DelineaProviderSecretRef `json:"clientSecret"`
|
||||||
|
|
||||||
|
// Tenant is the chosen hostname / site name.
|
||||||
|
Tenant string `json:"tenant"`
|
||||||
|
|
||||||
|
// URLTemplate
|
||||||
|
// If unset, defaults to "https://%s.secretsvaultcloud.%s/v1/%s%s".
|
||||||
|
// +optional
|
||||||
|
URLTemplate string `json:"urlTemplate,omitempty"`
|
||||||
|
|
||||||
|
// TLD is based on the server location that was chosen during provisioning.
|
||||||
|
// If unset, defaults to "com".
|
||||||
|
// +optional
|
||||||
|
TLD string `json:"tld,omitempty"`
|
||||||
|
}
|
|
@ -136,6 +136,11 @@ type SecretStoreProvider struct {
|
||||||
// Conjur configures this store to sync secrets using conjur provider
|
// Conjur configures this store to sync secrets using conjur provider
|
||||||
// +optional
|
// +optional
|
||||||
Conjur *ConjurProvider `json:"conjur,omitempty"`
|
Conjur *ConjurProvider `json:"conjur,omitempty"`
|
||||||
|
|
||||||
|
// Delinea DevOps Secrets Vault
|
||||||
|
// https://docs.delinea.com/online-help/products/devops-secrets-vault/current
|
||||||
|
// +optional
|
||||||
|
Delinea *DelineaProvider `json:"delinea,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CAProviderType string
|
type CAProviderType string
|
||||||
|
|
|
@ -701,6 +701,51 @@ func (in *ConjurProvider) DeepCopy() *ConjurProvider {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *DelineaProvider) DeepCopyInto(out *DelineaProvider) {
|
||||||
|
*out = *in
|
||||||
|
if in.ClientID != nil {
|
||||||
|
in, out := &in.ClientID, &out.ClientID
|
||||||
|
*out = new(DelineaProviderSecretRef)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.ClientSecret != nil {
|
||||||
|
in, out := &in.ClientSecret, &out.ClientSecret
|
||||||
|
*out = new(DelineaProviderSecretRef)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DelineaProvider.
|
||||||
|
func (in *DelineaProvider) DeepCopy() *DelineaProvider {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(DelineaProvider)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *DelineaProviderSecretRef) DeepCopyInto(out *DelineaProviderSecretRef) {
|
||||||
|
*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 DelineaProviderSecretRef.
|
||||||
|
func (in *DelineaProviderSecretRef) DeepCopy() *DelineaProviderSecretRef {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(DelineaProviderSecretRef)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *DopplerAuth) DeepCopyInto(out *DopplerAuth) {
|
func (in *DopplerAuth) DeepCopyInto(out *DopplerAuth) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
@ -1861,6 +1906,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
|
||||||
*out = new(ConjurProvider)
|
*out = new(ConjurProvider)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
|
if in.Delinea != nil {
|
||||||
|
in, out := &in.Delinea, &out.Delinea
|
||||||
|
*out = new(DelineaProvider)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreProvider.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreProvider.
|
||||||
|
|
|
@ -2262,6 +2262,78 @@ spec:
|
||||||
- auth
|
- auth
|
||||||
- url
|
- url
|
||||||
type: object
|
type: object
|
||||||
|
delinea:
|
||||||
|
description: Delinea DevOps Secrets Vault https://docs.delinea.com/online-help/products/devops-secrets-vault/current
|
||||||
|
properties:
|
||||||
|
clientId:
|
||||||
|
description: ClientID is the non-secret part of the credential.
|
||||||
|
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:
|
||||||
|
description: ClientSecret is the secret part of the credential.
|
||||||
|
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
|
||||||
|
tenant:
|
||||||
|
description: Tenant is the chosen hostname / site name.
|
||||||
|
type: string
|
||||||
|
tld:
|
||||||
|
description: TLD is based on the server location that was
|
||||||
|
chosen during provisioning. If unset, defaults to "com".
|
||||||
|
type: string
|
||||||
|
urlTemplate:
|
||||||
|
description: URLTemplate If unset, defaults to "https://%s.secretsvaultcloud.%s/v1/%s%s".
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- clientId
|
||||||
|
- clientSecret
|
||||||
|
- tenant
|
||||||
|
type: object
|
||||||
doppler:
|
doppler:
|
||||||
description: Doppler configures this store to sync secrets using
|
description: Doppler configures this store to sync secrets using
|
||||||
the Doppler provider
|
the Doppler provider
|
||||||
|
|
|
@ -2262,6 +2262,78 @@ spec:
|
||||||
- auth
|
- auth
|
||||||
- url
|
- url
|
||||||
type: object
|
type: object
|
||||||
|
delinea:
|
||||||
|
description: Delinea DevOps Secrets Vault https://docs.delinea.com/online-help/products/devops-secrets-vault/current
|
||||||
|
properties:
|
||||||
|
clientId:
|
||||||
|
description: ClientID is the non-secret part of the credential.
|
||||||
|
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:
|
||||||
|
description: ClientSecret is the secret part of the credential.
|
||||||
|
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
|
||||||
|
tenant:
|
||||||
|
description: Tenant is the chosen hostname / site name.
|
||||||
|
type: string
|
||||||
|
tld:
|
||||||
|
description: TLD is based on the server location that was
|
||||||
|
chosen during provisioning. If unset, defaults to "com".
|
||||||
|
type: string
|
||||||
|
urlTemplate:
|
||||||
|
description: URLTemplate If unset, defaults to "https://%s.secretsvaultcloud.%s/v1/%s%s".
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- clientId
|
||||||
|
- clientSecret
|
||||||
|
- tenant
|
||||||
|
type: object
|
||||||
doppler:
|
doppler:
|
||||||
description: Doppler configures this store to sync secrets using
|
description: Doppler configures this store to sync secrets using
|
||||||
the Doppler provider
|
the Doppler provider
|
||||||
|
|
|
@ -2109,6 +2109,63 @@ spec:
|
||||||
- auth
|
- auth
|
||||||
- url
|
- url
|
||||||
type: object
|
type: object
|
||||||
|
delinea:
|
||||||
|
description: Delinea DevOps Secrets Vault https://docs.delinea.com/online-help/products/devops-secrets-vault/current
|
||||||
|
properties:
|
||||||
|
clientId:
|
||||||
|
description: ClientID is the non-secret part of the credential.
|
||||||
|
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:
|
||||||
|
description: ClientSecret is the secret part of the credential.
|
||||||
|
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
|
||||||
|
tenant:
|
||||||
|
description: Tenant is the chosen hostname / site name.
|
||||||
|
type: string
|
||||||
|
tld:
|
||||||
|
description: TLD is based on the server location that was chosen during provisioning. If unset, defaults to "com".
|
||||||
|
type: string
|
||||||
|
urlTemplate:
|
||||||
|
description: URLTemplate If unset, defaults to "https://%s.secretsvaultcloud.%s/v1/%s%s".
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- clientId
|
||||||
|
- clientSecret
|
||||||
|
- tenant
|
||||||
|
type: object
|
||||||
doppler:
|
doppler:
|
||||||
description: Doppler configures this store to sync secrets using the Doppler provider
|
description: Doppler configures this store to sync secrets using the Doppler provider
|
||||||
properties:
|
properties:
|
||||||
|
@ -5755,6 +5812,63 @@ spec:
|
||||||
- auth
|
- auth
|
||||||
- url
|
- url
|
||||||
type: object
|
type: object
|
||||||
|
delinea:
|
||||||
|
description: Delinea DevOps Secrets Vault https://docs.delinea.com/online-help/products/devops-secrets-vault/current
|
||||||
|
properties:
|
||||||
|
clientId:
|
||||||
|
description: ClientID is the non-secret part of the credential.
|
||||||
|
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:
|
||||||
|
description: ClientSecret is the secret part of the credential.
|
||||||
|
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
|
||||||
|
tenant:
|
||||||
|
description: Tenant is the chosen hostname / site name.
|
||||||
|
type: string
|
||||||
|
tld:
|
||||||
|
description: TLD is based on the server location that was chosen during provisioning. If unset, defaults to "com".
|
||||||
|
type: string
|
||||||
|
urlTemplate:
|
||||||
|
description: URLTemplate If unset, defaults to "https://%s.secretsvaultcloud.%s/v1/%s%s".
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- clientId
|
||||||
|
- clientSecret
|
||||||
|
- tenant
|
||||||
|
type: object
|
||||||
doppler:
|
doppler:
|
||||||
description: Doppler configures this store to sync secrets using the Doppler provider
|
description: Doppler configures this store to sync secrets using the Doppler provider
|
||||||
properties:
|
properties:
|
||||||
|
|
141
docs/api/spec.md
141
docs/api/spec.md
|
@ -1775,6 +1775,132 @@ ConjurAuth
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<h3 id="external-secrets.io/v1beta1.DelineaProvider">DelineaProvider
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
(<em>Appears on:</em>
|
||||||
|
<a href="#external-secrets.io/v1beta1.SecretStoreProvider">SecretStoreProvider</a>)
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<p>See <a href="https://github.com/DelineaXPM/dsv-sdk-go/blob/main/vault/vault.go">https://github.com/DelineaXPM/dsv-sdk-go/blob/main/vault/vault.go</a>.</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.DelineaProviderSecretRef">
|
||||||
|
DelineaProviderSecretRef
|
||||||
|
</a>
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>ClientID is the non-secret part of the credential.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>clientSecret</code></br>
|
||||||
|
<em>
|
||||||
|
<a href="#external-secrets.io/v1beta1.DelineaProviderSecretRef">
|
||||||
|
DelineaProviderSecretRef
|
||||||
|
</a>
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>ClientSecret is the secret part of the credential.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>tenant</code></br>
|
||||||
|
<em>
|
||||||
|
string
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Tenant is the chosen hostname / site name.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>urlTemplate</code></br>
|
||||||
|
<em>
|
||||||
|
string
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em>(Optional)</em>
|
||||||
|
<p>URLTemplate
|
||||||
|
If unset, defaults to “https://%s.secretsvaultcloud.%s/v1/%s%s”.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>tld</code></br>
|
||||||
|
<em>
|
||||||
|
string
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em>(Optional)</em>
|
||||||
|
<p>TLD is based on the server location that was chosen during provisioning.
|
||||||
|
If unset, defaults to “com”.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h3 id="external-secrets.io/v1beta1.DelineaProviderSecretRef">DelineaProviderSecretRef
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
(<em>Appears on:</em>
|
||||||
|
<a href="#external-secrets.io/v1beta1.DelineaProvider">DelineaProvider</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.DopplerAuth">DopplerAuth
|
<h3 id="external-secrets.io/v1beta1.DopplerAuth">DopplerAuth
|
||||||
</h3>
|
</h3>
|
||||||
<p>
|
<p>
|
||||||
|
@ -4865,6 +4991,21 @@ ConjurProvider
|
||||||
<p>Conjur configures this store to sync secrets using conjur provider</p>
|
<p>Conjur configures this store to sync secrets using conjur provider</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>delinea</code></br>
|
||||||
|
<em>
|
||||||
|
<a href="#external-secrets.io/v1beta1.DelineaProvider">
|
||||||
|
DelineaProvider
|
||||||
|
</a>
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em>(Optional)</em>
|
||||||
|
<p>Delinea DevOps Secrets Vault
|
||||||
|
<a href="https://docs.delinea.com/online-help/products/devops-secrets-vault/current">https://docs.delinea.com/online-help/products/devops-secrets-vault/current</a></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<h3 id="external-secrets.io/v1beta1.SecretStoreRef">SecretStoreRef
|
<h3 id="external-secrets.io/v1beta1.SecretStoreRef">SecretStoreRef
|
||||||
|
|
|
@ -52,6 +52,7 @@ The following table describes the stability level of each provider and who's res
|
||||||
| [Keeper Security](https://www.keepersecurity.com/) | alpha | [@ppodevlab](https://github.com/ppodevlab) |
|
| [Keeper Security](https://www.keepersecurity.com/) | alpha | [@ppodevlab](https://github.com/ppodevlab) |
|
||||||
| [Scaleway](https://external-secrets.io/latest/provider/scaleway) | alpha | [@azert9](https://github.com/azert9/) |
|
| [Scaleway](https://external-secrets.io/latest/provider/scaleway) | alpha | [@azert9](https://github.com/azert9/) |
|
||||||
| [Conjur](https://external-secrets.io/latest/provider/conjur) | alpha | [@davidh-cyberark](https://github.com/davidh-cyberark/) |
|
| [Conjur](https://external-secrets.io/latest/provider/conjur) | alpha | [@davidh-cyberark](https://github.com/davidh-cyberark/) |
|
||||||
|
| [Delinea](https://external-secrets.io/latest/provider/delinea) | alpha | [@michaelsauter](https://github.com/michaelsauter/) |
|
||||||
|
|
||||||
## Provider Feature Support
|
## Provider Feature Support
|
||||||
|
|
||||||
|
@ -78,6 +79,7 @@ The following table show the support for features across different providers.
|
||||||
| Keeper Security | x | | | | x | x | |
|
| Keeper Security | x | | | | x | x | |
|
||||||
| Scaleway | x | x | | | x | x | x |
|
| Scaleway | x | x | | | x | x | x |
|
||||||
| Conjur | | | | | x | | |
|
| Conjur | | | | | x | | |
|
||||||
|
| Delinea | x | | | | x | | |
|
||||||
|
|
||||||
## Support Policy
|
## Support Policy
|
||||||
|
|
||||||
|
|
56
docs/provider/delinea.md
Normal file
56
docs/provider/delinea.md
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
## Delinea DevOps Secrets Vault
|
||||||
|
|
||||||
|
External Secrets Operator integrates with [Delinea DevOps Secrets Vault](https://docs.delinea.com/online-help/products/devops-secrets-vault/current).
|
||||||
|
|
||||||
|
Please note that the [Delinea Secret Server](https://delinea.com/products/secret-server) product is NOT in scope of this integration.
|
||||||
|
|
||||||
|
### Creating a SecretStore
|
||||||
|
|
||||||
|
You need client ID, client secret and tenant to authenticate with DSV.
|
||||||
|
Both client ID and client secret can be specified either directly in the config, or by referencing a kubernetes secret.
|
||||||
|
|
||||||
|
To acquire client ID and client secret, refer to the [policy management](https://docs.delinea.com/dsv/current/tutorials/policy.md) and [client management](https://docs.delinea.com/dsv/current/usage/cli-ref/client.md) documentation.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: SecretStore
|
||||||
|
metadata:
|
||||||
|
name: secret-store
|
||||||
|
spec:
|
||||||
|
provider:
|
||||||
|
delinea:
|
||||||
|
tenant: <TENANT>
|
||||||
|
tld: <TLD>
|
||||||
|
clientId:
|
||||||
|
value: <CLIENT_ID>
|
||||||
|
clientSecret:
|
||||||
|
secretRef:
|
||||||
|
name: <NAME_OF_KUBE_SECRET>
|
||||||
|
key: <KEY_IN_KUBE_SECRET>
|
||||||
|
```
|
||||||
|
|
||||||
|
Both `clientId` and `clientSecret` can either be specified directly via the `value` field or can reference a kubernetes secret.
|
||||||
|
|
||||||
|
The `tenant` field must correspond to the host name / site name of your DevOps vault. If you selected a region other than the US you must also specify the TLD, e.g. `tld: eu`.
|
||||||
|
|
||||||
|
If required, the URL template (`urlTemplate`) can be customized as well.
|
||||||
|
|
||||||
|
### Referencing Secrets
|
||||||
|
|
||||||
|
Secrets can be referenced by path. Getting a specific version of a secret is not yet supported.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: secret
|
||||||
|
spec:
|
||||||
|
refreshInterval: 20s
|
||||||
|
secretStoreRef:
|
||||||
|
kind: SecretStore
|
||||||
|
name: secret-store
|
||||||
|
data:
|
||||||
|
- secretKey: <KEY_IN_KUBE_SECRET>
|
||||||
|
remoteRef:
|
||||||
|
key: <SECRET_PATH>
|
||||||
|
```
|
|
@ -39,6 +39,7 @@ require (
|
||||||
cloud.google.com/go/secretmanager v1.11.1
|
cloud.google.com/go/secretmanager v1.11.1
|
||||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible
|
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible
|
||||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12
|
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12
|
||||||
|
github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.0
|
||||||
github.com/akeylesslabs/akeyless-go-cloud-id v0.3.4
|
github.com/akeylesslabs/akeyless-go-cloud-id v0.3.4
|
||||||
github.com/akeylesslabs/akeyless-go/v3 v3.3.12
|
github.com/akeylesslabs/akeyless-go/v3 v3.3.12
|
||||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.271
|
github.com/aliyun/alibaba-cloud-sdk-go v1.62.271
|
||||||
|
|
|
@ -77,6 +77,8 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM
|
||||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
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/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.0 h1:+XXJ43iH4js8LIBr4MUGq1J09ycivNkTNhtn4mFyhY8=
|
||||||
|
github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.0/go.mod h1:NTdQaRBIRZ/8gIzs010CS/u69aVSmqD1zbESW25y2cE=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/akeylesslabs/akeyless-go-cloud-id v0.3.4 h1:vTckjyBhHOBiOWSC/oaEU2Oo4OH5eAlQiwKu2RMxsFg=
|
github.com/akeylesslabs/akeyless-go-cloud-id v0.3.4 h1:vTckjyBhHOBiOWSC/oaEU2Oo4OH5eAlQiwKu2RMxsFg=
|
||||||
github.com/akeylesslabs/akeyless-go-cloud-id v0.3.4/go.mod h1:As/RomC2w/fa3y+yHRlVHPmkbP+zrKBFRow41y5dk+E=
|
github.com/akeylesslabs/akeyless-go-cloud-id v0.3.4/go.mod h1:As/RomC2w/fa3y+yHRlVHPmkbP+zrKBFRow41y5dk+E=
|
||||||
|
|
|
@ -79,6 +79,11 @@ kubectl run --rm \
|
||||||
--env="SCALEWAY_PROJECT_ID=${SCALEWAY_PROJECT_ID:-}" \
|
--env="SCALEWAY_PROJECT_ID=${SCALEWAY_PROJECT_ID:-}" \
|
||||||
--env="SCALEWAY_ACCESS_KEY=${SCALEWAY_ACCESS_KEY:-}" \
|
--env="SCALEWAY_ACCESS_KEY=${SCALEWAY_ACCESS_KEY:-}" \
|
||||||
--env="SCALEWAY_SECRET_KEY=${SCALEWAY_SECRET_KEY:-}" \
|
--env="SCALEWAY_SECRET_KEY=${SCALEWAY_SECRET_KEY:-}" \
|
||||||
|
--env="DELINEA_TLD=${DELINEA_TLD:-}" \
|
||||||
|
--env="DELINEA_URL_TEMPLATE=${DELINEA_URL_TEMPLATE:-}" \
|
||||||
|
--env="DELINEA_TENANT=${DELINEA_TENANT:-}" \
|
||||||
|
--env="DELINEA_CLIENT_ID=${DELINEA_CLIENT_ID:-}" \
|
||||||
|
--env="DELINEA_CLIENT_SECRET=${DELINEA_CLIENT_SECRET:-}" \
|
||||||
--env="VERSION=${VERSION}" \
|
--env="VERSION=${VERSION}" \
|
||||||
--env="TEST_SUITES=${TEST_SUITES}" \
|
--env="TEST_SUITES=${TEST_SUITES}" \
|
||||||
--overrides='{ "apiVersion": "v1", "spec":{"serviceAccountName": "external-secrets-e2e"}}' \
|
--overrides='{ "apiVersion": "v1", "spec":{"serviceAccountName": "external-secrets-e2e"}}' \
|
||||||
|
|
47
e2e/suites/provider/cases/delinea/config.go
Normal file
47
e2e/suites/provider/cases/delinea/config.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package delinea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
tld string
|
||||||
|
urlTemplate string
|
||||||
|
tenant string
|
||||||
|
clientID string
|
||||||
|
clientSecret string
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConfigFromEnv() (*config, error) {
|
||||||
|
var cfg config
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Optional settings
|
||||||
|
cfg.tld, _ = getEnv("DELINEA_TLD")
|
||||||
|
cfg.urlTemplate, _ = getEnv("DELINEA_URL_TEMPLATE")
|
||||||
|
|
||||||
|
// Required settings
|
||||||
|
cfg.tenant, err = getEnv("DELINEA_TENANT")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfg.clientID, err = getEnv("DELINEA_CLIENT_ID")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfg.clientSecret, err = getEnv("DELINEA_CLIENT_SECRET")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnv(name string) (string, error) {
|
||||||
|
value, ok := os.LookupEnv(name)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("environment variable %q is not set", name)
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
115
e2e/suites/provider/cases/delinea/delinea.go
Normal file
115
e2e/suites/provider/cases/delinea/delinea.go
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
package delinea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/external-secrets/external-secrets-e2e/framework"
|
||||||
|
"github.com/external-secrets/external-secrets-e2e/suites/provider/cases/common"
|
||||||
|
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||||
|
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||||
|
"github.com/onsi/ginkgo/v2"
|
||||||
|
"github.com/onsi/gomega"
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = ginkgo.Describe("[delinea]", ginkgo.Label("delinea"), func() {
|
||||||
|
|
||||||
|
f := framework.New("eso-delinea")
|
||||||
|
|
||||||
|
// Initialization is deferred so that assertions work.
|
||||||
|
provider := &secretStoreProvider{}
|
||||||
|
|
||||||
|
ginkgo.BeforeEach(func() {
|
||||||
|
|
||||||
|
cfg, err := loadConfigFromEnv()
|
||||||
|
gomega.Expect(err).ToNot(gomega.HaveOccurred())
|
||||||
|
|
||||||
|
provider.init(cfg)
|
||||||
|
|
||||||
|
createResources(context.Background(), f, cfg)
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.DescribeTable("sync secrets", framework.TableFunc(f, provider),
|
||||||
|
|
||||||
|
ginkgo.Entry(common.JSONDataWithProperty(f)),
|
||||||
|
ginkgo.Entry(common.JSONDataWithoutTargetName(f)),
|
||||||
|
ginkgo.Entry(common.JSONDataWithTemplate(f)),
|
||||||
|
ginkgo.Entry(common.JSONDataWithTemplateFromLiteral(f)),
|
||||||
|
ginkgo.Entry(common.TemplateFromConfigmaps(f)),
|
||||||
|
ginkgo.Entry(common.JSONDataFromSync(f)),
|
||||||
|
ginkgo.Entry(common.JSONDataFromRewrite(f)),
|
||||||
|
ginkgo.Entry(common.NestedJSONWithGJSON(f)),
|
||||||
|
ginkgo.Entry(common.DockerJSONConfig(f)),
|
||||||
|
ginkgo.Entry(common.DataPropertyDockerconfigJSON(f)),
|
||||||
|
ginkgo.Entry(common.SSHKeySyncDataProperty(f)),
|
||||||
|
ginkgo.Entry(common.DecodingPolicySync(f)),
|
||||||
|
|
||||||
|
// V1Alpha1 is not supported.
|
||||||
|
// ginkgo.Entry(common.SyncV1Alpha1(f)),
|
||||||
|
|
||||||
|
// Non-JSON values are not supported by DSV.
|
||||||
|
// ginkgo.Entry(common.SimpleDataSync(f)),
|
||||||
|
// ginkgo.Entry(common.SyncWithoutTargetName(f)),
|
||||||
|
// ginkgo.Entry(common.SSHKeySync(f)),
|
||||||
|
// ginkgo.Entry(common.DeletionPolicyDelete(f)),
|
||||||
|
|
||||||
|
// FindByName is not supported.
|
||||||
|
// ginkgo.Entry(common.FindByName(f)),
|
||||||
|
// ginkgo.Entry(common.FindByNameAndRewrite(f)),
|
||||||
|
// ginkgo.Entry(common.FindByNameWithPath(f)),
|
||||||
|
|
||||||
|
// FindByTag is not supported.
|
||||||
|
// ginkgo.Entry(common.FindByTag(f)),
|
||||||
|
// ginkgo.Entry(common.FindByTagWithPath(f)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
func createResources(ctx context.Context, f *framework.Framework, cfg *config) {
|
||||||
|
|
||||||
|
secretName := "delinea-credential"
|
||||||
|
secretKey := "client-secret"
|
||||||
|
|
||||||
|
// Creating a secret to hold the Delinea client secret.
|
||||||
|
secretSpec := v1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: secretName,
|
||||||
|
Namespace: f.Namespace.Name,
|
||||||
|
},
|
||||||
|
StringData: map[string]string{
|
||||||
|
secretKey: cfg.clientSecret,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := f.CRClient.Create(ctx, &secretSpec)
|
||||||
|
gomega.Expect(err).ToNot(gomega.HaveOccurred())
|
||||||
|
|
||||||
|
// Creating SecretStore.
|
||||||
|
secretStoreSpec := esv1beta1.SecretStore{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: f.Namespace.Name,
|
||||||
|
Namespace: f.Namespace.Name,
|
||||||
|
},
|
||||||
|
Spec: esv1beta1.SecretStoreSpec{
|
||||||
|
Provider: &esv1beta1.SecretStoreProvider{
|
||||||
|
Delinea: &esv1beta1.DelineaProvider{
|
||||||
|
Tenant: cfg.tenant,
|
||||||
|
TLD: cfg.tld,
|
||||||
|
URLTemplate: cfg.urlTemplate,
|
||||||
|
ClientID: &esv1beta1.DelineaProviderSecretRef{
|
||||||
|
Value: cfg.clientID,
|
||||||
|
},
|
||||||
|
ClientSecret: &esv1beta1.DelineaProviderSecretRef{
|
||||||
|
SecretRef: &esmeta.SecretKeySelector{
|
||||||
|
Name: secretName,
|
||||||
|
Key: secretKey,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = f.CRClient.Create(ctx, &secretStoreSpec)
|
||||||
|
gomega.Expect(err).ToNot(gomega.HaveOccurred())
|
||||||
|
}
|
47
e2e/suites/provider/cases/delinea/provider.go
Normal file
47
e2e/suites/provider/cases/delinea/provider.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package delinea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/DelineaXPM/dsv-sdk-go/v2/vault"
|
||||||
|
"github.com/external-secrets/external-secrets-e2e/framework"
|
||||||
|
"github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
type secretStoreProvider struct {
|
||||||
|
api *vault.Vault
|
||||||
|
cfg *config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *secretStoreProvider) init(cfg *config) {
|
||||||
|
|
||||||
|
p.cfg = cfg
|
||||||
|
|
||||||
|
dsvClient, err := vault.New(vault.Configuration{
|
||||||
|
Credentials: vault.ClientCredential{
|
||||||
|
ClientID: cfg.clientID,
|
||||||
|
ClientSecret: cfg.clientSecret,
|
||||||
|
},
|
||||||
|
Tenant: cfg.tenant,
|
||||||
|
URLTemplate: cfg.urlTemplate,
|
||||||
|
TLD: cfg.tld,
|
||||||
|
})
|
||||||
|
gomega.Expect(err).ToNot(gomega.HaveOccurred())
|
||||||
|
|
||||||
|
p.api = dsvClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *secretStoreProvider) CreateSecret(key string, val framework.SecretEntry) {
|
||||||
|
var data map[string]interface{}
|
||||||
|
err := json.Unmarshal([]byte(val.Value), &data)
|
||||||
|
gomega.Expect(err).ToNot(gomega.HaveOccurred())
|
||||||
|
_, err = p.api.CreateSecret(key, &vault.SecretCreateRequest{
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
gomega.Expect(err).ToNot(gomega.HaveOccurred())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *secretStoreProvider) DeleteSecret(key string) {
|
||||||
|
err := p.api.DeleteSecret(key)
|
||||||
|
gomega.Expect(err).ToNot(gomega.HaveOccurred())
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ import (
|
||||||
_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/aws/parameterstore"
|
_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/aws/parameterstore"
|
||||||
_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/aws/secretsmanager"
|
_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/aws/secretsmanager"
|
||||||
_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/azure"
|
_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/azure"
|
||||||
|
_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/delinea"
|
||||||
_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/gcp"
|
_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/gcp"
|
||||||
_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/kubernetes"
|
_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/kubernetes"
|
||||||
_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/scaleway"
|
_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/scaleway"
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -63,6 +63,7 @@ require github.com/1Password/connect-sdk-go v1.5.1
|
||||||
require (
|
require (
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0
|
||||||
|
github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.0
|
||||||
github.com/akeylesslabs/akeyless-go/v3 v3.3.12
|
github.com/akeylesslabs/akeyless-go/v3 v3.3.12
|
||||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4
|
||||||
github.com/alibabacloud-go/kms-20160120/v3 v3.0.2
|
github.com/alibabacloud-go/kms-20160120/v3 v3.0.2
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -87,6 +87,8 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkM
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
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/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.0 h1:+XXJ43iH4js8LIBr4MUGq1J09ycivNkTNhtn4mFyhY8=
|
||||||
|
github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.0/go.mod h1:NTdQaRBIRZ/8gIzs010CS/u69aVSmqD1zbESW25y2cE=
|
||||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM=
|
github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM=
|
||||||
github.com/IBM/go-sdk-core/v5 v5.13.4 h1:kJvBNQOwhFRkXCPapjNvKVC7n7n2vd1Nr6uUtDZGcfo=
|
github.com/IBM/go-sdk-core/v5 v5.13.4 h1:kJvBNQOwhFRkXCPapjNvKVC7n7n2vd1Nr6uUtDZGcfo=
|
||||||
github.com/IBM/go-sdk-core/v5 v5.13.4/go.mod h1:gKRSB+YyKsGlRQW7v5frlLbue5afulSvrRa4O26o4MM=
|
github.com/IBM/go-sdk-core/v5 v5.13.4/go.mod h1:gKRSB+YyKsGlRQW7v5frlLbue5afulSvrRa4O26o4MM=
|
||||||
|
|
|
@ -106,6 +106,7 @@ nav:
|
||||||
- Doppler: provider/doppler.md
|
- Doppler: provider/doppler.md
|
||||||
- Keeper Security: provider/keeper-security.md
|
- Keeper Security: provider/keeper-security.md
|
||||||
- Scaleway: provider/scaleway.md
|
- Scaleway: provider/scaleway.md
|
||||||
|
- Delinea: provider/delinea.md
|
||||||
- Examples:
|
- Examples:
|
||||||
- FluxCD: examples/gitops-using-fluxcd.md
|
- FluxCD: examples/gitops-using-fluxcd.md
|
||||||
- Anchore Engine: examples/anchore-engine-credentials.md
|
- Anchore Engine: examples/anchore-engine-credentials.md
|
||||||
|
|
148
pkg/provider/delinea/client.go
Normal file
148
pkg/provider/delinea/client.go
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
/*
|
||||||
|
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 delinea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/DelineaXPM/dsv-sdk-go/v2/vault"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
|
||||||
|
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
errSecretKeyFmt = "cannot find secret data for key: %q"
|
||||||
|
errUnexpectedKey = "unexpected key in data: %s"
|
||||||
|
errSecretFormat = "secret data for property %s not in expected format: %s"
|
||||||
|
)
|
||||||
|
|
||||||
|
type client struct {
|
||||||
|
api secretAPI
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ esv1beta1.SecretsClient = &client{}
|
||||||
|
|
||||||
|
// GetSecret supports two types:
|
||||||
|
// 1. get the full secret as json-encoded value
|
||||||
|
// by leaving the ref.Property empty.
|
||||||
|
// 2. get a key from the secret.
|
||||||
|
// Nested values are supported by specifying a gjson expression
|
||||||
|
func (c *client) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
|
||||||
|
secret, err := c.getSecret(ctx, ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Return nil if secret value is null
|
||||||
|
if secret.Data == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
jsonStr, err := json.Marshal(secret.Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// return raw json if no property is defined
|
||||||
|
if ref.Property == "" {
|
||||||
|
return jsonStr, nil
|
||||||
|
}
|
||||||
|
// extract key from secret using gjson
|
||||||
|
val := gjson.Get(string(jsonStr), ref.Property)
|
||||||
|
if !val.Exists() {
|
||||||
|
return nil, esv1beta1.NoSecretError{}
|
||||||
|
}
|
||||||
|
return []byte(val.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) PushSecret(_ context.Context, _ []byte, _ esv1beta1.PushRemoteRef) error {
|
||||||
|
return errors.New("pushing secrets is not supported by Delinea DevOps Secrets Vault")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) DeleteSecret(_ context.Context, _ esv1beta1.PushRemoteRef) error {
|
||||||
|
return errors.New("deleting secrets is not supported by Delinea DevOps Secrets Vault")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) Validate() (esv1beta1.ValidationResult, error) {
|
||||||
|
return esv1beta1.ValidationResultReady, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSecret gets the full secret as json-encoded value.
|
||||||
|
func (c *client) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
||||||
|
secret, err := c.getSecret(ctx, ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
byteMap := make(map[string][]byte, len(secret.Data))
|
||||||
|
for k := range secret.Data {
|
||||||
|
byteMap[k], err = getTypedKey(secret.Data, k)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return byteMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllSecrets lists secrets matching the given criteria and return their latest versions.
|
||||||
|
func (c *client) GetAllSecrets(_ context.Context, _ esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
|
||||||
|
return nil, errors.New("getting all secrets is not supported by Delinea DevOps Secrets Vault")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) Close(context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSecret retrieves the secret referenced by ref from the Vault API.
|
||||||
|
func (c *client) getSecret(_ context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (*vault.Secret, error) {
|
||||||
|
if ref.Version != "" {
|
||||||
|
return nil, errors.New("specifying a version is not yet supported")
|
||||||
|
}
|
||||||
|
return c.api.Secret(ref.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTypedKey is copied from pkg/provider/vault/vault.go.
|
||||||
|
func getTypedKey(data map[string]interface{}, key string) ([]byte, error) {
|
||||||
|
v, ok := data[key]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf(errUnexpectedKey, key)
|
||||||
|
}
|
||||||
|
switch t := v.(type) {
|
||||||
|
case string:
|
||||||
|
return []byte(t), nil
|
||||||
|
case map[string]interface{}:
|
||||||
|
return json.Marshal(t)
|
||||||
|
case []string:
|
||||||
|
return []byte(strings.Join(t, "\n")), nil
|
||||||
|
case []byte:
|
||||||
|
return t, nil
|
||||||
|
// also covers int and float32 due to json.Marshal
|
||||||
|
case float64:
|
||||||
|
return []byte(strconv.FormatFloat(t, 'f', -1, 64)), nil
|
||||||
|
case json.Number:
|
||||||
|
return []byte(t.String()), nil
|
||||||
|
case []interface{}:
|
||||||
|
return json.Marshal(t)
|
||||||
|
case bool:
|
||||||
|
return []byte(strconv.FormatBool(t)), nil
|
||||||
|
case nil:
|
||||||
|
return []byte(nil), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf(errSecretFormat, key, reflect.TypeOf(t))
|
||||||
|
}
|
||||||
|
}
|
117
pkg/provider/delinea/client_test.go
Normal file
117
pkg/provider/delinea/client_test.go
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
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 delinea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DelineaXPM/dsv-sdk-go/v2/vault"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeAPI struct {
|
||||||
|
secrets []*vault.Secret
|
||||||
|
}
|
||||||
|
|
||||||
|
// createVaultSecret assembles a vault.Secret.
|
||||||
|
// vault.Secret has unexported nested types, and is therefore quite
|
||||||
|
// tricky from outside the vault package. This function facilitates easy setup.
|
||||||
|
func createVaultSecret(path string, data map[string]interface{}) *vault.Secret {
|
||||||
|
s := &vault.Secret{}
|
||||||
|
s.Path = path
|
||||||
|
s.Data = data
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secret returns secret matching path.
|
||||||
|
func (f *fakeAPI) Secret(path string) (*vault.Secret, error) {
|
||||||
|
for _, s := range f.secrets {
|
||||||
|
if s.Path == path {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestClient() esv1beta1.SecretsClient {
|
||||||
|
return &client{
|
||||||
|
api: &fakeAPI{
|
||||||
|
secrets: []*vault.Secret{
|
||||||
|
createVaultSecret("a", map[string]interface{}{}),
|
||||||
|
createVaultSecret("b", map[string]interface{}{
|
||||||
|
"hello": "world",
|
||||||
|
}),
|
||||||
|
createVaultSecret("c", map[string]interface{}{
|
||||||
|
"foo": map[string]string{"bar": "baz"},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSecret(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
c := newTestClient()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
ref esv1beta1.ExternalSecretDataRemoteRef
|
||||||
|
want []byte
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
"querying for the key returns the map": {
|
||||||
|
ref: esv1beta1.ExternalSecretDataRemoteRef{
|
||||||
|
Key: "b",
|
||||||
|
},
|
||||||
|
want: []byte(`{"hello":"world"}`),
|
||||||
|
},
|
||||||
|
"querying for the key and property returns a single value": {
|
||||||
|
ref: esv1beta1.ExternalSecretDataRemoteRef{
|
||||||
|
Key: "b",
|
||||||
|
Property: "hello",
|
||||||
|
},
|
||||||
|
want: []byte(`world`),
|
||||||
|
},
|
||||||
|
"querying for the key and nested property returns a single value": {
|
||||||
|
ref: esv1beta1.ExternalSecretDataRemoteRef{
|
||||||
|
Key: "c",
|
||||||
|
Property: "foo.bar",
|
||||||
|
},
|
||||||
|
want: []byte(`baz`),
|
||||||
|
},
|
||||||
|
"querying for existent key and non-existing propery": {
|
||||||
|
ref: esv1beta1.ExternalSecretDataRemoteRef{
|
||||||
|
Key: "c",
|
||||||
|
Property: "foo.bar.x",
|
||||||
|
},
|
||||||
|
err: esv1beta1.NoSecretErr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
got, err := c.GetSecret(ctx, tc.ref)
|
||||||
|
if tc.err == nil {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tc.want, got)
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, got)
|
||||||
|
assert.ErrorIs(t, err, tc.err)
|
||||||
|
assert.Equal(t, tc.err, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
207
pkg/provider/delinea/provider.go
Normal file
207
pkg/provider/delinea/provider.go
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
/*
|
||||||
|
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 delinea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/DelineaXPM/dsv-sdk-go/v2/vault"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
kubeClient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
|
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||||
|
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errEmptyTenant = errors.New("tenant must not be empty")
|
||||||
|
errEmptyClientID = errors.New("clientID must be set")
|
||||||
|
errEmptyClientSecret = errors.New("clientSecret must be set")
|
||||||
|
errSecretRefAndValueConflict = errors.New("cannot specify both secret reference and value")
|
||||||
|
errSecretRefAndValueMissing = errors.New("must specify either secret reference or direct value")
|
||||||
|
errMissingStore = errors.New("missing store specification")
|
||||||
|
errInvalidSpec = errors.New("invalid specification for delinea provider")
|
||||||
|
errMissingSecretName = errors.New("must specify a secret name")
|
||||||
|
errMissingSecretKey = errors.New("must specify a secret key")
|
||||||
|
errClusterStoreRequiresNamespace = errors.New("when using a ClusterSecretStore, namespaces must be explicitly set")
|
||||||
|
|
||||||
|
errNoSuchKeyFmt = "no such key in secret: %q"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Provider struct{}
|
||||||
|
|
||||||
|
var _ esv1beta1.Provider = &Provider{}
|
||||||
|
|
||||||
|
// Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
|
||||||
|
func (p *Provider) Capabilities() esv1beta1.SecretStoreCapabilities {
|
||||||
|
return esv1beta1.SecretStoreReadOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kubeClient.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||||
|
cfg, err := getConfig(store)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if store.GetKind() == esv1beta1.ClusterSecretStoreKind && doesConfigDependOnNamespace(cfg) {
|
||||||
|
// we are not attached to a specific namespace, but some config values are dependent on it
|
||||||
|
return nil, errClusterStoreRequiresNamespace
|
||||||
|
}
|
||||||
|
|
||||||
|
clientID, err := loadConfigSecret(ctx, cfg.ClientID, kube, namespace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
clientSecret, err := loadConfigSecret(ctx, cfg.ClientSecret, kube, namespace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dsvClient, err := vault.New(vault.Configuration{
|
||||||
|
Credentials: vault.ClientCredential{
|
||||||
|
ClientID: clientID,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
},
|
||||||
|
Tenant: cfg.Tenant,
|
||||||
|
TLD: cfg.TLD,
|
||||||
|
URLTemplate: cfg.URLTemplate,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &client{
|
||||||
|
api: dsvClient,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConfigSecret(ctx context.Context, ref *esv1beta1.DelineaProviderSecretRef, kube kubeClient.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
|
||||||
|
}
|
||||||
|
|
||||||
|
objKey := kubeClient.ObjectKey{Namespace: namespace, Name: ref.SecretRef.Name}
|
||||||
|
secret := corev1.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 validateStoreSecretRef(store esv1beta1.GenericStore, ref *esv1beta1.DelineaProviderSecretRef) error {
|
||||||
|
if ref.SecretRef != nil {
|
||||||
|
if err := utils.ValidateReferentSecretSelector(store, *ref.SecretRef); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return validateSecretRef(ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSecretRef(ref *esv1beta1.DelineaProviderSecretRef) error {
|
||||||
|
if ref.SecretRef != nil {
|
||||||
|
if ref.Value != "" {
|
||||||
|
return errSecretRefAndValueConflict
|
||||||
|
}
|
||||||
|
if ref.SecretRef.Name == "" {
|
||||||
|
return errMissingSecretName
|
||||||
|
}
|
||||||
|
if ref.SecretRef.Key == "" {
|
||||||
|
return errMissingSecretKey
|
||||||
|
}
|
||||||
|
} else if ref.Value == "" {
|
||||||
|
return errSecretRefAndValueMissing
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func doesConfigDependOnNamespace(cfg *esv1beta1.DelineaProvider) bool {
|
||||||
|
if cfg.ClientID.SecretRef != nil && cfg.ClientID.SecretRef.Namespace == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.ClientSecret.SecretRef != nil && cfg.ClientSecret.SecretRef.Namespace == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConfig(store esv1beta1.GenericStore) (*esv1beta1.DelineaProvider, error) {
|
||||||
|
if store == nil {
|
||||||
|
return nil, errMissingStore
|
||||||
|
}
|
||||||
|
storeSpec := store.GetSpec()
|
||||||
|
|
||||||
|
if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Delinea == nil {
|
||||||
|
return nil, errInvalidSpec
|
||||||
|
}
|
||||||
|
cfg := storeSpec.Provider.Delinea
|
||||||
|
|
||||||
|
if cfg.Tenant == "" {
|
||||||
|
return nil, errEmptyTenant
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.ClientID == nil {
|
||||||
|
return nil, errEmptyClientID
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.ClientSecret == nil {
|
||||||
|
return nil, errEmptyClientSecret
|
||||||
|
}
|
||||||
|
|
||||||
|
err := validateStoreSecretRef(store, cfg.ClientID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateStoreSecretRef(store, cfg.ClientSecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) ValidateStore(store esv1beta1.GenericStore) error {
|
||||||
|
_, err := getConfig(store)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
|
||||||
|
Delinea: &esv1beta1.DelineaProvider{},
|
||||||
|
})
|
||||||
|
}
|
369
pkg/provider/delinea/provider_test.go
Normal file
369
pkg/provider/delinea/provider_test.go
Normal file
|
@ -0,0 +1,369 @@
|
||||||
|
/*
|
||||||
|
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 delinea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DelineaXPM/dsv-sdk-go/v2/vault"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
kubeErrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
kubeClient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||||
|
|
||||||
|
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||||
|
v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||||
|
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDoesConfigDependOnNamespace(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
cfg esv1beta1.DelineaProvider
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
"true when client ID references a secret without explicit namespace": {
|
||||||
|
cfg: esv1beta1.DelineaProvider{
|
||||||
|
ClientID: &esv1beta1.DelineaProviderSecretRef{
|
||||||
|
SecretRef: &v1.SecretKeySelector{Name: "foo"},
|
||||||
|
},
|
||||||
|
ClientSecret: &esv1beta1.DelineaProviderSecretRef{SecretRef: nil},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
"true when client secret references a secret without explicit namespace": {
|
||||||
|
cfg: esv1beta1.DelineaProvider{
|
||||||
|
ClientID: &esv1beta1.DelineaProviderSecretRef{SecretRef: nil},
|
||||||
|
ClientSecret: &esv1beta1.DelineaProviderSecretRef{
|
||||||
|
SecretRef: &v1.SecretKeySelector{Name: "foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
"false when neither client ID nor secret reference a secret": {
|
||||||
|
cfg: esv1beta1.DelineaProvider{
|
||||||
|
ClientID: &esv1beta1.DelineaProviderSecretRef{SecretRef: nil},
|
||||||
|
ClientSecret: &esv1beta1.DelineaProviderSecretRef{SecretRef: nil},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, tc := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
got := doesConfigDependOnNamespace(&tc.cfg)
|
||||||
|
assert.Equal(t, tc.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateStore(t *testing.T) {
|
||||||
|
validSecretRefUsingValue := makeSecretRefUsingValue("foo")
|
||||||
|
ambiguousSecretRef := &esv1beta1.DelineaProviderSecretRef{
|
||||||
|
SecretRef: &v1.SecretKeySelector{Name: "foo"}, Value: "foo",
|
||||||
|
}
|
||||||
|
tests := map[string]struct {
|
||||||
|
cfg esv1beta1.DelineaProvider
|
||||||
|
want error
|
||||||
|
}{
|
||||||
|
"invalid without tenant": {
|
||||||
|
cfg: esv1beta1.DelineaProvider{
|
||||||
|
Tenant: "",
|
||||||
|
ClientID: validSecretRefUsingValue,
|
||||||
|
ClientSecret: validSecretRefUsingValue,
|
||||||
|
},
|
||||||
|
want: errEmptyTenant,
|
||||||
|
},
|
||||||
|
"invalid without clientID": {
|
||||||
|
cfg: esv1beta1.DelineaProvider{
|
||||||
|
Tenant: "foo",
|
||||||
|
// ClientID omitted
|
||||||
|
ClientSecret: validSecretRefUsingValue,
|
||||||
|
},
|
||||||
|
want: errEmptyClientID,
|
||||||
|
},
|
||||||
|
"invalid without clientSecret": {
|
||||||
|
cfg: esv1beta1.DelineaProvider{
|
||||||
|
Tenant: "foo",
|
||||||
|
ClientID: validSecretRefUsingValue,
|
||||||
|
// ClientSecret omitted
|
||||||
|
},
|
||||||
|
want: errEmptyClientSecret,
|
||||||
|
},
|
||||||
|
"invalid with ambiguous clientID": {
|
||||||
|
cfg: esv1beta1.DelineaProvider{
|
||||||
|
Tenant: "foo",
|
||||||
|
ClientID: ambiguousSecretRef,
|
||||||
|
ClientSecret: validSecretRefUsingValue,
|
||||||
|
},
|
||||||
|
want: errSecretRefAndValueConflict,
|
||||||
|
},
|
||||||
|
"invalid with ambiguous clientSecret": {
|
||||||
|
cfg: esv1beta1.DelineaProvider{
|
||||||
|
Tenant: "foo",
|
||||||
|
ClientID: validSecretRefUsingValue,
|
||||||
|
ClientSecret: ambiguousSecretRef,
|
||||||
|
},
|
||||||
|
want: errSecretRefAndValueConflict,
|
||||||
|
},
|
||||||
|
"invalid with invalid clientID": {
|
||||||
|
cfg: esv1beta1.DelineaProvider{
|
||||||
|
Tenant: "foo",
|
||||||
|
ClientID: makeSecretRefUsingValue(""),
|
||||||
|
ClientSecret: validSecretRefUsingValue,
|
||||||
|
},
|
||||||
|
want: errSecretRefAndValueMissing,
|
||||||
|
},
|
||||||
|
"invalid with invalid clientSecret": {
|
||||||
|
cfg: esv1beta1.DelineaProvider{
|
||||||
|
Tenant: "foo",
|
||||||
|
ClientID: validSecretRefUsingValue,
|
||||||
|
ClientSecret: makeSecretRefUsingValue(""),
|
||||||
|
},
|
||||||
|
want: errSecretRefAndValueMissing,
|
||||||
|
},
|
||||||
|
"valid with tenant/clientID/clientSecret": {
|
||||||
|
cfg: esv1beta1.DelineaProvider{
|
||||||
|
Tenant: "foo",
|
||||||
|
ClientID: validSecretRefUsingValue,
|
||||||
|
ClientSecret: validSecretRefUsingValue,
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, tc := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
s := esv1beta1.SecretStore{
|
||||||
|
Spec: esv1beta1.SecretStoreSpec{
|
||||||
|
Provider: &esv1beta1.SecretStoreProvider{
|
||||||
|
Delinea: &tc.cfg,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
p := &Provider{}
|
||||||
|
got := p.ValidateStore(&s)
|
||||||
|
assert.Equal(t, tc.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateStoreBailsOnUnexpectedStore(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
store esv1beta1.GenericStore
|
||||||
|
want error
|
||||||
|
}{
|
||||||
|
"missing store": {nil, errMissingStore},
|
||||||
|
"missing spec": {&esv1beta1.SecretStore{}, errInvalidSpec},
|
||||||
|
"missing provider": {&esv1beta1.SecretStore{
|
||||||
|
Spec: esv1beta1.SecretStoreSpec{Provider: nil},
|
||||||
|
}, errInvalidSpec},
|
||||||
|
"missing delinea": {&esv1beta1.SecretStore{
|
||||||
|
Spec: esv1beta1.SecretStoreSpec{Provider: &esv1beta1.SecretStoreProvider{
|
||||||
|
Delinea: nil,
|
||||||
|
}},
|
||||||
|
}, errInvalidSpec},
|
||||||
|
}
|
||||||
|
for name, tc := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
p := &Provider{}
|
||||||
|
got := p.ValidateStore(tc.store)
|
||||||
|
assert.Equal(t, tc.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewClient(t *testing.T) {
|
||||||
|
tenant := "foo"
|
||||||
|
clientIDKey := "username"
|
||||||
|
clientIDValue := "client id"
|
||||||
|
clientSecretKey := "password"
|
||||||
|
clientSecretValue := "client secret"
|
||||||
|
|
||||||
|
clientSecret := &corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
clientIDKey: []byte(clientIDValue),
|
||||||
|
clientSecretKey: []byte(clientSecretValue),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
validProvider := &esv1beta1.DelineaProvider{
|
||||||
|
Tenant: tenant,
|
||||||
|
ClientID: makeSecretRefUsingRef(clientSecret.Name, clientIDKey),
|
||||||
|
ClientSecret: makeSecretRefUsingRef(clientSecret.Name, clientSecretKey),
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := map[string]struct {
|
||||||
|
store esv1beta1.GenericStore // leave nil for namespaced store
|
||||||
|
provider *esv1beta1.DelineaProvider // discarded when store is set
|
||||||
|
kube kubeClient.Client
|
||||||
|
errCheck func(t *testing.T, err error)
|
||||||
|
}{
|
||||||
|
"missing provider config": {
|
||||||
|
provider: nil,
|
||||||
|
errCheck: func(t *testing.T, err error) {
|
||||||
|
assert.ErrorIs(t, err, errInvalidSpec)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"namespace-dependent cluster secret store": {
|
||||||
|
store: &esv1beta1.ClusterSecretStore{
|
||||||
|
TypeMeta: metav1.TypeMeta{Kind: esv1beta1.ClusterSecretStoreKind},
|
||||||
|
Spec: esv1beta1.SecretStoreSpec{
|
||||||
|
Provider: &esv1beta1.SecretStoreProvider{
|
||||||
|
Delinea: validProvider,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errCheck: func(t *testing.T, err error) {
|
||||||
|
assert.ErrorIs(t, err, errClusterStoreRequiresNamespace)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"dangling client ID ref": {
|
||||||
|
provider: &esv1beta1.DelineaProvider{
|
||||||
|
Tenant: tenant,
|
||||||
|
ClientID: makeSecretRefUsingRef("typo", clientIDKey),
|
||||||
|
ClientSecret: makeSecretRefUsingRef(clientSecret.Name, clientSecretKey),
|
||||||
|
},
|
||||||
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
||||||
|
errCheck: func(t *testing.T, err error) {
|
||||||
|
assert.True(t, kubeErrors.IsNotFound(err))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"dangling client secret ref": {
|
||||||
|
provider: &esv1beta1.DelineaProvider{
|
||||||
|
Tenant: tenant,
|
||||||
|
ClientID: makeSecretRefUsingRef(clientSecret.Name, clientIDKey),
|
||||||
|
ClientSecret: makeSecretRefUsingRef("typo", clientSecretKey),
|
||||||
|
},
|
||||||
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
||||||
|
errCheck: func(t *testing.T, err error) {
|
||||||
|
assert.True(t, kubeErrors.IsNotFound(err))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"secret ref without name": {
|
||||||
|
provider: &esv1beta1.DelineaProvider{
|
||||||
|
Tenant: tenant,
|
||||||
|
ClientID: makeSecretRefUsingRef("", clientIDKey),
|
||||||
|
ClientSecret: makeSecretRefUsingRef(clientSecret.Name, clientSecretKey),
|
||||||
|
},
|
||||||
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
||||||
|
errCheck: func(t *testing.T, err error) {
|
||||||
|
assert.ErrorIs(t, err, errMissingSecretName)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"secret ref without key": {
|
||||||
|
provider: &esv1beta1.DelineaProvider{
|
||||||
|
Tenant: tenant,
|
||||||
|
ClientID: makeSecretRefUsingRef(clientSecret.Name, ""),
|
||||||
|
ClientSecret: makeSecretRefUsingRef(clientSecret.Name, clientSecretKey),
|
||||||
|
},
|
||||||
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
||||||
|
errCheck: func(t *testing.T, err error) {
|
||||||
|
assert.ErrorIs(t, err, errMissingSecretKey)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"secret ref with non-existent keys": {
|
||||||
|
provider: &esv1beta1.DelineaProvider{
|
||||||
|
Tenant: tenant,
|
||||||
|
ClientID: makeSecretRefUsingRef(clientSecret.Name, "typo"),
|
||||||
|
ClientSecret: makeSecretRefUsingRef(clientSecret.Name, clientSecretKey),
|
||||||
|
},
|
||||||
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
||||||
|
errCheck: func(t *testing.T, err error) {
|
||||||
|
assert.EqualError(t, err, fmt.Sprintf(errNoSuchKeyFmt, "typo"))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"valid secret refs": {
|
||||||
|
provider: validProvider,
|
||||||
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
||||||
|
},
|
||||||
|
"secret values": {
|
||||||
|
provider: &esv1beta1.DelineaProvider{
|
||||||
|
Tenant: tenant,
|
||||||
|
ClientID: makeSecretRefUsingValue(clientIDValue),
|
||||||
|
ClientSecret: makeSecretRefUsingValue(clientSecretValue),
|
||||||
|
},
|
||||||
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
||||||
|
},
|
||||||
|
"cluster secret store": {
|
||||||
|
store: &esv1beta1.ClusterSecretStore{
|
||||||
|
TypeMeta: metav1.TypeMeta{Kind: esv1beta1.ClusterSecretStoreKind},
|
||||||
|
Spec: esv1beta1.SecretStoreSpec{
|
||||||
|
Provider: &esv1beta1.SecretStoreProvider{
|
||||||
|
Delinea: &esv1beta1.DelineaProvider{
|
||||||
|
Tenant: tenant,
|
||||||
|
ClientID: makeSecretRefUsingNamespacedRef(clientSecret.Namespace, clientSecret.Name, clientIDKey),
|
||||||
|
ClientSecret: makeSecretRefUsingNamespacedRef(clientSecret.Namespace, clientSecret.Name, clientSecretKey),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, tc := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
p := &Provider{}
|
||||||
|
store := tc.store
|
||||||
|
if store == nil {
|
||||||
|
store = &esv1beta1.SecretStore{
|
||||||
|
TypeMeta: metav1.TypeMeta{Kind: esv1beta1.SecretStoreKind},
|
||||||
|
Spec: esv1beta1.SecretStoreSpec{
|
||||||
|
Provider: &esv1beta1.SecretStoreProvider{
|
||||||
|
Delinea: tc.provider,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sc, err := p.NewClient(context.Background(), store, tc.kube, clientSecret.Namespace)
|
||||||
|
if tc.errCheck == nil {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
delineaClient, ok := sc.(*client)
|
||||||
|
assert.True(t, ok)
|
||||||
|
dsvClient, ok := delineaClient.api.(*vault.Vault)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, vault.Configuration{
|
||||||
|
Credentials: vault.ClientCredential{
|
||||||
|
ClientID: clientIDValue,
|
||||||
|
ClientSecret: clientSecretValue,
|
||||||
|
},
|
||||||
|
Tenant: tenant,
|
||||||
|
TLD: "com", // Default from Delinea
|
||||||
|
URLTemplate: "https://%s.secretsvaultcloud.%s/v1/%s%s", // Default from Delinea
|
||||||
|
}, dsvClient.Configuration)
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, sc)
|
||||||
|
tc.errCheck(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSecretRefUsingRef(name, key string) *esv1beta1.DelineaProviderSecretRef {
|
||||||
|
return &esv1beta1.DelineaProviderSecretRef{
|
||||||
|
SecretRef: &v1.SecretKeySelector{Name: name, Key: key},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSecretRefUsingNamespacedRef(namespace, name, key string) *esv1beta1.DelineaProviderSecretRef {
|
||||||
|
return &esv1beta1.DelineaProviderSecretRef{
|
||||||
|
SecretRef: &v1.SecretKeySelector{Namespace: utils.Ptr(namespace), Name: name, Key: key},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSecretRefUsingValue(val string) *esv1beta1.DelineaProviderSecretRef {
|
||||||
|
return &esv1beta1.DelineaProviderSecretRef{Value: val}
|
||||||
|
}
|
25
pkg/provider/delinea/secret_api.go
Normal file
25
pkg/provider/delinea/secret_api.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
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 delinea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/DelineaXPM/dsv-sdk-go/v2/vault"
|
||||||
|
)
|
||||||
|
|
||||||
|
// secretAPI represents the subset of the Delinea DevOps Secrets Vault API
|
||||||
|
// which is supported by dsv-sdk-go/v2.
|
||||||
|
// See https://dsv.secretsvaultcloud.com/api for full API documentation.
|
||||||
|
type secretAPI interface {
|
||||||
|
Secret(path string) (*vault.Secret, error)
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import (
|
||||||
_ "github.com/external-secrets/external-secrets/pkg/provider/aws"
|
_ "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/azure/keyvault"
|
||||||
_ "github.com/external-secrets/external-secrets/pkg/provider/conjur"
|
_ "github.com/external-secrets/external-secrets/pkg/provider/conjur"
|
||||||
|
_ "github.com/external-secrets/external-secrets/pkg/provider/delinea"
|
||||||
_ "github.com/external-secrets/external-secrets/pkg/provider/doppler"
|
_ "github.com/external-secrets/external-secrets/pkg/provider/doppler"
|
||||||
_ "github.com/external-secrets/external-secrets/pkg/provider/fake"
|
_ "github.com/external-secrets/external-secrets/pkg/provider/fake"
|
||||||
_ "github.com/external-secrets/external-secrets/pkg/provider/gcp/secretmanager"
|
_ "github.com/external-secrets/external-secrets/pkg/provider/gcp/secretmanager"
|
||||||
|
|
Loading…
Reference in a new issue