mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
Add support for Delinea Secret Server (#3468)
* implements secretserver Signed-off-by: Bill Hamilton <bill.hamilton@delinea.com> * bump to align e2e Signed-off-by: Gustavo Carvalho <gusfcarvalho@gmail.com> * bump Signed-off-by: Gustavo Carvalho <gusfcarvalho@gmail.com> --------- Signed-off-by: Bill Hamilton <bill.hamilton@delinea.com> Signed-off-by: Gustavo Carvalho <gusfcarvalho@gmail.com> Co-authored-by: Gustavo Carvalho <gusfcarvalho@gmail.com>
This commit is contained in:
parent
9512254a04
commit
1876ff88d7
26 changed files with 1721 additions and 0 deletions
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
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 SecretServerProviderRef 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/tss-sdk-go/blob/main/server/server.go.
|
||||||
|
type SecretServerProvider struct {
|
||||||
|
|
||||||
|
// Username is the secret server account username.
|
||||||
|
// +required
|
||||||
|
Username *SecretServerProviderRef `json:"username"`
|
||||||
|
|
||||||
|
// Password is the secret server account password.
|
||||||
|
// +required
|
||||||
|
Password *SecretServerProviderRef `json:"password"`
|
||||||
|
|
||||||
|
// ServerURL
|
||||||
|
// URL to your secret server installation
|
||||||
|
// +required
|
||||||
|
ServerURL string `json:"serverURL"`
|
||||||
|
}
|
|
@ -155,6 +155,11 @@ type SecretStoreProvider struct {
|
||||||
// +optional
|
// +optional
|
||||||
Delinea *DelineaProvider `json:"delinea,omitempty"`
|
Delinea *DelineaProvider `json:"delinea,omitempty"`
|
||||||
|
|
||||||
|
// SecretServer configures this store to sync secrets using SecretServer provider
|
||||||
|
// https://docs.delinea.com/online-help/secret-server/start.htm
|
||||||
|
// +optional
|
||||||
|
SecretServer *SecretServerProvider `json:"secretserver,omitempty"`
|
||||||
|
|
||||||
// Chef configures this store to sync secrets with chef server
|
// Chef configures this store to sync secrets with chef server
|
||||||
// +optional
|
// +optional
|
||||||
Chef *ChefProvider `json:"chef,omitempty"`
|
Chef *ChefProvider `json:"chef,omitempty"`
|
||||||
|
|
|
@ -2266,6 +2266,51 @@ func (in *ScalewayProviderSecretRef) DeepCopy() *ScalewayProviderSecretRef {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *SecretServerProvider) DeepCopyInto(out *SecretServerProvider) {
|
||||||
|
*out = *in
|
||||||
|
if in.Username != nil {
|
||||||
|
in, out := &in.Username, &out.Username
|
||||||
|
*out = new(SecretServerProviderRef)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.Password != nil {
|
||||||
|
in, out := &in.Password, &out.Password
|
||||||
|
*out = new(SecretServerProviderRef)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretServerProvider.
|
||||||
|
func (in *SecretServerProvider) DeepCopy() *SecretServerProvider {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(SecretServerProvider)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *SecretServerProviderRef) DeepCopyInto(out *SecretServerProviderRef) {
|
||||||
|
*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 SecretServerProviderRef.
|
||||||
|
func (in *SecretServerProviderRef) DeepCopy() *SecretServerProviderRef {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(SecretServerProviderRef)
|
||||||
|
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 *SecretStore) DeepCopyInto(out *SecretStore) {
|
func (in *SecretStore) DeepCopyInto(out *SecretStore) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
@ -2443,6 +2488,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
|
||||||
*out = new(DelineaProvider)
|
*out = new(DelineaProvider)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
|
if in.SecretServer != nil {
|
||||||
|
in, out := &in.SecretServer, &out.SecretServer
|
||||||
|
*out = new(SecretServerProvider)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
if in.Chef != nil {
|
if in.Chef != nil {
|
||||||
in, out := &in.Chef, &out.Chef
|
in, out := &in.Chef, &out.Chef
|
||||||
*out = new(ChefProvider)
|
*out = new(ChefProvider)
|
||||||
|
|
|
@ -3732,6 +3732,75 @@ spec:
|
||||||
- region
|
- region
|
||||||
- secretKey
|
- secretKey
|
||||||
type: object
|
type: object
|
||||||
|
secretserver:
|
||||||
|
description: |-
|
||||||
|
SecretServer configures this store to sync secrets using SecretServer provider
|
||||||
|
https://docs.delinea.com/online-help/secret-server/start.htm
|
||||||
|
properties:
|
||||||
|
password:
|
||||||
|
description: Password is the secret server account password.
|
||||||
|
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
|
||||||
|
serverURL:
|
||||||
|
description: |-
|
||||||
|
ServerURL
|
||||||
|
URL to your secret server installation
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
description: Username is the secret server account username.
|
||||||
|
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:
|
||||||
|
- password
|
||||||
|
- serverURL
|
||||||
|
- username
|
||||||
|
type: object
|
||||||
senhasegura:
|
senhasegura:
|
||||||
description: Senhasegura configures this store to sync secrets
|
description: Senhasegura configures this store to sync secrets
|
||||||
using senhasegura provider
|
using senhasegura provider
|
||||||
|
|
|
@ -3732,6 +3732,75 @@ spec:
|
||||||
- region
|
- region
|
||||||
- secretKey
|
- secretKey
|
||||||
type: object
|
type: object
|
||||||
|
secretserver:
|
||||||
|
description: |-
|
||||||
|
SecretServer configures this store to sync secrets using SecretServer provider
|
||||||
|
https://docs.delinea.com/online-help/secret-server/start.htm
|
||||||
|
properties:
|
||||||
|
password:
|
||||||
|
description: Password is the secret server account password.
|
||||||
|
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
|
||||||
|
serverURL:
|
||||||
|
description: |-
|
||||||
|
ServerURL
|
||||||
|
URL to your secret server installation
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
description: Username is the secret server account username.
|
||||||
|
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:
|
||||||
|
- password
|
||||||
|
- serverURL
|
||||||
|
- username
|
||||||
|
type: object
|
||||||
senhasegura:
|
senhasegura:
|
||||||
description: Senhasegura configures this store to sync secrets
|
description: Senhasegura configures this store to sync secrets
|
||||||
using senhasegura provider
|
using senhasegura provider
|
||||||
|
|
|
@ -4121,6 +4121,69 @@ spec:
|
||||||
- region
|
- region
|
||||||
- secretKey
|
- secretKey
|
||||||
type: object
|
type: object
|
||||||
|
secretserver:
|
||||||
|
description: |-
|
||||||
|
SecretServer configures this store to sync secrets using SecretServer provider
|
||||||
|
https://docs.delinea.com/online-help/secret-server/start.htm
|
||||||
|
properties:
|
||||||
|
password:
|
||||||
|
description: Password is the secret server account password.
|
||||||
|
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
|
||||||
|
serverURL:
|
||||||
|
description: |-
|
||||||
|
ServerURL
|
||||||
|
URL to your secret server installation
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
description: Username is the secret server account username.
|
||||||
|
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:
|
||||||
|
- password
|
||||||
|
- serverURL
|
||||||
|
- username
|
||||||
|
type: object
|
||||||
senhasegura:
|
senhasegura:
|
||||||
description: Senhasegura configures this store to sync secrets using senhasegura provider
|
description: Senhasegura configures this store to sync secrets using senhasegura provider
|
||||||
properties:
|
properties:
|
||||||
|
@ -9684,6 +9747,69 @@ spec:
|
||||||
- region
|
- region
|
||||||
- secretKey
|
- secretKey
|
||||||
type: object
|
type: object
|
||||||
|
secretserver:
|
||||||
|
description: |-
|
||||||
|
SecretServer configures this store to sync secrets using SecretServer provider
|
||||||
|
https://docs.delinea.com/online-help/secret-server/start.htm
|
||||||
|
properties:
|
||||||
|
password:
|
||||||
|
description: Password is the secret server account password.
|
||||||
|
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
|
||||||
|
serverURL:
|
||||||
|
description: |-
|
||||||
|
ServerURL
|
||||||
|
URL to your secret server installation
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
description: Username is the secret server account username.
|
||||||
|
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:
|
||||||
|
- password
|
||||||
|
- serverURL
|
||||||
|
- username
|
||||||
|
type: object
|
||||||
senhasegura:
|
senhasegura:
|
||||||
description: Senhasegura configures this store to sync secrets using senhasegura provider
|
description: Senhasegura configures this store to sync secrets using senhasegura provider
|
||||||
properties:
|
properties:
|
||||||
|
|
116
docs/api/spec.md
116
docs/api/spec.md
|
@ -5924,6 +5924,107 @@ External Secrets meta/v1.SecretKeySelector
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<h3 id="external-secrets.io/v1beta1.SecretServerProvider">SecretServerProvider
|
||||||
|
</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/tss-sdk-go/blob/main/server/server.go">https://github.com/DelineaXPM/tss-sdk-go/blob/main/server/server.go</a>.</p>
|
||||||
|
</p>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Field</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>username</code></br>
|
||||||
|
<em>
|
||||||
|
<a href="#external-secrets.io/v1beta1.SecretServerProviderRef">
|
||||||
|
SecretServerProviderRef
|
||||||
|
</a>
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Username is the secret server account username.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>password</code></br>
|
||||||
|
<em>
|
||||||
|
<a href="#external-secrets.io/v1beta1.SecretServerProviderRef">
|
||||||
|
SecretServerProviderRef
|
||||||
|
</a>
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Password is the secret server account password.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>serverURL</code></br>
|
||||||
|
<em>
|
||||||
|
string
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>ServerURL
|
||||||
|
URL to your secret server installation</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h3 id="external-secrets.io/v1beta1.SecretServerProviderRef">SecretServerProviderRef
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
(<em>Appears on:</em>
|
||||||
|
<a href="#external-secrets.io/v1beta1.SecretServerProvider">SecretServerProvider</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.SecretStore">SecretStore
|
<h3 id="external-secrets.io/v1beta1.SecretStore">SecretStore
|
||||||
</h3>
|
</h3>
|
||||||
<p>
|
<p>
|
||||||
|
@ -6432,6 +6533,21 @@ DelineaProvider
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
<code>secretserver</code></br>
|
||||||
|
<em>
|
||||||
|
<a href="#external-secrets.io/v1beta1.SecretServerProvider">
|
||||||
|
SecretServerProvider
|
||||||
|
</a>
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em>(Optional)</em>
|
||||||
|
<p>SecretServer configures this store to sync secrets using SecretServer provider
|
||||||
|
<a href="https://docs.delinea.com/online-help/secret-server/start.htm">https://docs.delinea.com/online-help/secret-server/start.htm</a></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
<code>chef</code></br>
|
<code>chef</code></br>
|
||||||
<em>
|
<em>
|
||||||
<a href="#external-secrets.io/v1beta1.ChefProvider">
|
<a href="#external-secrets.io/v1beta1.ChefProvider">
|
||||||
|
|
|
@ -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/) |
|
| [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) |
|
| [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/) |
|
| [Delinea](https://external-secrets.io/latest/provider/delinea) | alpha | [@michaelsauter](https://github.com/michaelsauter/) |
|
||||||
|
| [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) |
|
| [Pulumi ESC](https://external-secrets.io/latest/provider/pulumi) | alpha | [@dirien](https://github.com/dirien) |
|
||||||
| [Passbolt](https://external-secrets.io/latest/provider/passbolt) | alpha | |
|
| [Passbolt](https://external-secrets.io/latest/provider/passbolt) | alpha | |
|
||||||
| [Infisical](https://external-secrets.io/latest/provider/infisical) | alpha | [@akhilmhdh](https://github.com/akhilmhdh) |
|
| [Infisical](https://external-secrets.io/latest/provider/infisical) | alpha | [@akhilmhdh](https://github.com/akhilmhdh) |
|
||||||
|
@ -85,6 +86,7 @@ The following table show the support for features across different providers.
|
||||||
| Scaleway | x | x | | | x | x | x |
|
| Scaleway | x | x | | | x | x | x |
|
||||||
| Conjur | x | x | | | x | | |
|
| Conjur | x | x | | | x | | |
|
||||||
| Delinea | x | | | | x | | |
|
| Delinea | x | | | | x | | |
|
||||||
|
| SecretServer | x | | | | x | | |
|
||||||
| Pulumi ESC | x | | | | x | | |
|
| Pulumi ESC | x | | | | x | | |
|
||||||
| Passbolt | x | | | | x | | |
|
| Passbolt | x | | | | x | | |
|
||||||
| Infisical | x | | | x | x | | |
|
| Infisical | x | | | x | x | | |
|
||||||
|
|
133
docs/provider/secretserver.md
Normal file
133
docs/provider/secretserver.md
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
# Delinea Secret Server
|
||||||
|
|
||||||
|
External Secrets Operator integration with [Delinea Secret Server](https://docs.delinea.com/online-help/secret-server/start.htm).
|
||||||
|
|
||||||
|
### Creating a SecretStore
|
||||||
|
|
||||||
|
You need a username, password and a fully qualified Secret Server tenant URL to authenticate
|
||||||
|
i.e. `https://yourTenantName.secretservercloud.com`.
|
||||||
|
|
||||||
|
Both username and password can be specified either directly in your `SecretStore` yaml config, or by referencing a kubernetes secret.
|
||||||
|
|
||||||
|
To acquire a username and password, refer to the Secret Server [user management](https://docs.delinea.com/online-help/secret-server/users/creating-users/index.htm) documentation.
|
||||||
|
|
||||||
|
Both `username` and `password` can either be specified directly via the `value` field (example below)
|
||||||
|
>spec.provider.secretserver.username.value: "yourusername"<br />
|
||||||
|
spec.provider.secretserver.password.value: "yourpassword" <br />
|
||||||
|
|
||||||
|
Or you can reference a kubernetes secret (password example below).
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: SecretStore
|
||||||
|
metadata:
|
||||||
|
name: secret-server-store
|
||||||
|
spec:
|
||||||
|
provider:
|
||||||
|
secretserver:
|
||||||
|
serverURL: "https://yourtenantname.secretservercloud.com"
|
||||||
|
username:
|
||||||
|
value: "yourusername"
|
||||||
|
password:
|
||||||
|
secretRef:
|
||||||
|
name: <NAME_OF_K8S_SECRET>
|
||||||
|
key: <KEY_IN_K8S_SECRET>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Referencing Secrets
|
||||||
|
|
||||||
|
Secrets may be referenced by secret ID or secret name.
|
||||||
|
>Please note if using the secret name
|
||||||
|
the name field must not contain spaces or control characters.<br />
|
||||||
|
If multiple secrets are found, *`only the first found secret will be returned`*.
|
||||||
|
|
||||||
|
Please note: `Retrieving a specific version of a secret is not yet supported.`
|
||||||
|
|
||||||
|
Note that because all Secret Server secrets are JSON objects, you must specify the `remoteRef.property`
|
||||||
|
in your ExternalSecret configuration.<br />
|
||||||
|
You can access nested values or arrays using [gjson syntax](https://github.com/tidwall/gjson/blob/master/SYNTAX.md).
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: external-secrets.io/v1beta1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: secret-server-external-secret
|
||||||
|
spec:
|
||||||
|
refreshInterval: 15s
|
||||||
|
secretStoreRef:
|
||||||
|
kind: SecretStore
|
||||||
|
name: secret-server-store
|
||||||
|
data:
|
||||||
|
- secretKey: SecretServerValue #<SECRET_VALUE_RETURNED_HERE>
|
||||||
|
remoteRef:
|
||||||
|
key: "52622" #<SECRET_ID>
|
||||||
|
property: "array.0.value" #<GJSON_PROPERTY> * an empty property will return the entire secret
|
||||||
|
```
|
||||||
|
|
||||||
|
### Preparing your secret
|
||||||
|
You can either retrieve your entire secret or you can use a JSON formatted string
|
||||||
|
stored in your secret located at Items[0].ItemValue to retrieve a specific value.<br />
|
||||||
|
See example JSON secret below.
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
Using the json formatted secret below:
|
||||||
|
|
||||||
|
- Lookup a single top level property using secret ID.
|
||||||
|
|
||||||
|
>spec.data.remoteRef.key = 52622 (id of the secret)<br />
|
||||||
|
spec.data.remoteRef.property = "user" (Items.0.ItemValue user attribute)<br />
|
||||||
|
returns: marktwain@hannibal.com
|
||||||
|
|
||||||
|
- Lookup a nested property using secret name.
|
||||||
|
|
||||||
|
>spec.data.remoteRef.key = "external-secret-testing" (name of the secret)<br />
|
||||||
|
spec.data.remoteRef.property = "books.1" (Items.0.ItemValue books.1 attribute)<br />
|
||||||
|
returns: huckleberryFinn
|
||||||
|
|
||||||
|
- Lookup by secret ID (*secret name will work as well*) and return the entire secret.
|
||||||
|
|
||||||
|
>spec.data.remoteRef.key = "52622" (id of the secret)<br />
|
||||||
|
spec.data.remoteRef.property = "" <br />
|
||||||
|
returns: The entire secret in JSON format as displayed below
|
||||||
|
|
||||||
|
|
||||||
|
```JSON
|
||||||
|
{
|
||||||
|
"Name": "external-secret-testing",
|
||||||
|
"FolderID": 73,
|
||||||
|
"ID": 52622,
|
||||||
|
"SiteID": 1,
|
||||||
|
"SecretTemplateID": 6098,
|
||||||
|
"SecretPolicyID": -1,
|
||||||
|
"PasswordTypeWebScriptID": -1,
|
||||||
|
"LauncherConnectAsSecretID": -1,
|
||||||
|
"CheckOutIntervalMinutes": -1,
|
||||||
|
"Active": true,
|
||||||
|
"CheckedOut": false,
|
||||||
|
"CheckOutEnabled": false,
|
||||||
|
"AutoChangeEnabled": false,
|
||||||
|
"CheckOutChangePasswordEnabled": false,
|
||||||
|
"DelayIndexing": false,
|
||||||
|
"EnableInheritPermissions": true,
|
||||||
|
"EnableInheritSecretPolicy": true,
|
||||||
|
"ProxyEnabled": false,
|
||||||
|
"RequiresComment": false,
|
||||||
|
"SessionRecordingEnabled": false,
|
||||||
|
"WebLauncherRequiresIncognitoMode": false,
|
||||||
|
"Items": [
|
||||||
|
{
|
||||||
|
"ItemID": 280265,
|
||||||
|
"FieldID": 439,
|
||||||
|
"FileAttachmentID": 0,
|
||||||
|
"FieldName": "Data",
|
||||||
|
"Slug": "data",
|
||||||
|
"FieldDescription": "json text field",
|
||||||
|
"Filename": "",
|
||||||
|
"ItemValue": "{ \"user\": \"marktwain@hannibal.com\", \"occupation\": \"author\",\"books\":[ \"tomSawyer\",\"huckleberryFinn\",\"Pudd'nhead Wilson\"] }",
|
||||||
|
"IsFile": false,
|
||||||
|
"IsNotes": false,
|
||||||
|
"IsPassword": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
|
@ -44,6 +44,7 @@ require (
|
||||||
github.com/Azure/go-autorest/autorest v0.11.29
|
github.com/Azure/go-autorest/autorest v0.11.29
|
||||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.13
|
github.com/Azure/go-autorest/autorest/azure/auth v0.5.13
|
||||||
github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.2
|
github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.2
|
||||||
|
github.com/DelineaXPM/tss-sdk-go/v2 v2.0.1
|
||||||
github.com/akeylesslabs/akeyless-go-cloud-id v0.3.5
|
github.com/akeylesslabs/akeyless-go-cloud-id v0.3.5
|
||||||
github.com/akeylesslabs/akeyless-go/v3 v3.6.3
|
github.com/akeylesslabs/akeyless-go/v3 v3.6.3
|
||||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.271
|
github.com/aliyun/alibaba-cloud-sdk-go v1.62.271
|
||||||
|
|
|
@ -97,6 +97,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||||
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.2 h1:cmX2QC9s5kPqmghWLLZP8YRFO1ZD/C59BpNH2ujP99w=
|
github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.2 h1:cmX2QC9s5kPqmghWLLZP8YRFO1ZD/C59BpNH2ujP99w=
|
||||||
github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.2/go.mod h1:tNlpIXJlIwQlRbobXDPme4qv/Rc8+a1GbuUhE3m4JhQ=
|
github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.2/go.mod h1:tNlpIXJlIwQlRbobXDPme4qv/Rc8+a1GbuUhE3m4JhQ=
|
||||||
|
github.com/DelineaXPM/tss-sdk-go/v2 v2.0.1 h1:/rzzzaBuj/FYTcbt8sYZ9IzlnENqcgh5zKqBhHiBBm4=
|
||||||
|
github.com/DelineaXPM/tss-sdk-go/v2 v2.0.1/go.mod h1:xz6FXP2Do88Vc5Hx7OamZgZC1W45yfmLy4+iDKxlGXo=
|
||||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||||
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||||
|
|
|
@ -84,6 +84,9 @@ kubectl run --rm \
|
||||||
--env="DELINEA_TENANT=${DELINEA_TENANT:-}" \
|
--env="DELINEA_TENANT=${DELINEA_TENANT:-}" \
|
||||||
--env="DELINEA_CLIENT_ID=${DELINEA_CLIENT_ID:-}" \
|
--env="DELINEA_CLIENT_ID=${DELINEA_CLIENT_ID:-}" \
|
||||||
--env="DELINEA_CLIENT_SECRET=${DELINEA_CLIENT_SECRET:-}" \
|
--env="DELINEA_CLIENT_SECRET=${DELINEA_CLIENT_SECRET:-}" \
|
||||||
|
--env="SECRETSERVER_USERNAME=${SECRETSERVER_USERNAME:-}" \
|
||||||
|
--env="SECRETSERVER_PASSWORD=${SECRETSERVER_PASSWORD:-}" \
|
||||||
|
--env="SECRETSERVER_URL=${SECRETSERVER_URL:-}" \
|
||||||
--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"}}' \
|
||||||
|
|
|
@ -27,4 +27,5 @@ import (
|
||||||
_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/template"
|
_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/template"
|
||||||
_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/vault"
|
_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/vault"
|
||||||
_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/conjur"
|
_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/conjur"
|
||||||
|
_ "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/secretserver"
|
||||||
)
|
)
|
||||||
|
|
41
e2e/suites/provider/cases/secretserver/config.go
Normal file
41
e2e/suites/provider/cases/secretserver/config.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package secretserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
serverURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConfigFromEnv() (*config, error) {
|
||||||
|
var cfg config
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Required settings
|
||||||
|
cfg.username, err = getEnv("SECRETSERVER_USERNAME")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfg.password, err = getEnv("SECRETSERVER_PASSWORD")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfg.serverURL, err = getEnv("SECRETSERVER_URL")
|
||||||
|
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
|
||||||
|
}
|
58
e2e/suites/provider/cases/secretserver/provider.go
Normal file
58
e2e/suites/provider/cases/secretserver/provider.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package secretserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/DelineaXPM/tss-sdk-go/v2/server"
|
||||||
|
"github.com/external-secrets/external-secrets-e2e/framework"
|
||||||
|
"github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type secretStoreProvider struct {
|
||||||
|
api *server.Server
|
||||||
|
cfg *config
|
||||||
|
framework *framework.Framework
|
||||||
|
secretID map[string]int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *secretStoreProvider) init(cfg *config, f *framework.Framework) {
|
||||||
|
p.cfg = cfg
|
||||||
|
p.secretID = make(map[string]int)
|
||||||
|
p.framework = f
|
||||||
|
secretserverClient, err := server.New(server.Configuration{
|
||||||
|
Credentials: server.UserCredential{
|
||||||
|
Username: cfg.username,
|
||||||
|
Password: cfg.password,
|
||||||
|
},
|
||||||
|
ServerURL: cfg.serverURL,
|
||||||
|
})
|
||||||
|
gomega.Expect(err).ToNot(gomega.HaveOccurred())
|
||||||
|
|
||||||
|
p.api = secretserverClient
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
|
||||||
|
fields := make([]server.SecretField, 1)
|
||||||
|
fields[0].FieldID = 329 // Data
|
||||||
|
fields[0].ItemValue = val.Value
|
||||||
|
|
||||||
|
s, err := p.api.CreateSecret(server.Secret{
|
||||||
|
SecretTemplateID: 6051, // custom template
|
||||||
|
SiteID: 1,
|
||||||
|
FolderID: 10,
|
||||||
|
Name: key,
|
||||||
|
Fields: fields,
|
||||||
|
})
|
||||||
|
gomega.Expect(err).ToNot(gomega.HaveOccurred())
|
||||||
|
p.secretID[key] = s.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *secretStoreProvider) DeleteSecret(key string) {
|
||||||
|
err := p.api.DeleteSecret(p.secretID[key])
|
||||||
|
gomega.Expect(err).ToNot(gomega.HaveOccurred())
|
||||||
|
}
|
92
e2e/suites/provider/cases/secretserver/secretserver.go
Normal file
92
e2e/suites/provider/cases/secretserver/secretserver.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package secretserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_"fmt"
|
||||||
|
"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("[secretserver]", ginkgo.Label("secretserver"), func() {
|
||||||
|
|
||||||
|
f := framework.New("eso-secretserver")
|
||||||
|
|
||||||
|
// Initialization is deferred so that assertions work.
|
||||||
|
provider := &secretStoreProvider{}
|
||||||
|
|
||||||
|
ginkgo.BeforeEach(func() {
|
||||||
|
|
||||||
|
cfg, err := loadConfigFromEnv()
|
||||||
|
gomega.Expect(err).ToNot(gomega.HaveOccurred())
|
||||||
|
|
||||||
|
provider.init(cfg, f)
|
||||||
|
createResources(context.Background(), f, cfg)
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.DescribeTable("sync secrets", framework.TableFuncWithExternalSecret(f, provider),
|
||||||
|
ginkgo.Entry(common.JSONDataWithTemplate(f)),
|
||||||
|
ginkgo.Entry(common.JSONDataWithProperty(f)),
|
||||||
|
ginkgo.Entry(common.JSONDataWithoutTargetName(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)), // <--
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
func createResources(ctx context.Context, f *framework.Framework, cfg *config) {
|
||||||
|
|
||||||
|
secretName := "secretserver-credential"
|
||||||
|
secretKey := "password"
|
||||||
|
// 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.password,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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{
|
||||||
|
SecretServer: &esv1beta1.SecretServerProvider{
|
||||||
|
ServerURL: cfg.serverURL,
|
||||||
|
Username: &esv1beta1.SecretServerProviderRef{
|
||||||
|
Value: cfg.username,
|
||||||
|
},
|
||||||
|
Password: &esv1beta1.SecretServerProviderRef{
|
||||||
|
SecretRef: &esmeta.SecretKeySelector{
|
||||||
|
Name: secretName,
|
||||||
|
Key: secretKey,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = f.CRClient.Create(ctx, &secretStoreSpec)
|
||||||
|
gomega.Expect(err).ToNot(gomega.HaveOccurred())
|
||||||
|
}
|
1
go.mod
1
go.mod
|
@ -65,6 +65,7 @@ require (
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0
|
||||||
github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.2
|
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
|
github.com/Onboardbase/go-cryptojs-aes-decrypt v0.0.0-20230430095000-27c0d3a9016d
|
||||||
github.com/akeylesslabs/akeyless-go/v3 v3.6.3
|
github.com/akeylesslabs/akeyless-go/v3 v3.6.3
|
||||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.8
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.8
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -101,6 +101,8 @@ github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2
|
||||||
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.2 h1:cmX2QC9s5kPqmghWLLZP8YRFO1ZD/C59BpNH2ujP99w=
|
github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.2 h1:cmX2QC9s5kPqmghWLLZP8YRFO1ZD/C59BpNH2ujP99w=
|
||||||
github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.2/go.mod h1:tNlpIXJlIwQlRbobXDPme4qv/Rc8+a1GbuUhE3m4JhQ=
|
github.com/DelineaXPM/dsv-sdk-go/v2 v2.1.2/go.mod h1:tNlpIXJlIwQlRbobXDPme4qv/Rc8+a1GbuUhE3m4JhQ=
|
||||||
|
github.com/DelineaXPM/tss-sdk-go/v2 v2.0.1 h1:/rzzzaBuj/FYTcbt8sYZ9IzlnENqcgh5zKqBhHiBBm4=
|
||||||
|
github.com/DelineaXPM/tss-sdk-go/v2 v2.0.1/go.mod h1:xz6FXP2Do88Vc5Hx7OamZgZC1W45yfmLy4+iDKxlGXo=
|
||||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM=
|
github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM=
|
||||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
|
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
|
||||||
github.com/IBM/go-sdk-core/v5 v5.17.4 h1:VGb9+mRrnS2HpHZFM5hy4J6ppIWnwNrw0G+tLSgcJLc=
|
github.com/IBM/go-sdk-core/v5 v5.17.4 h1:VGb9+mRrnS2HpHZFM5hy4J6ppIWnwNrw0G+tLSgcJLc=
|
||||||
|
|
|
@ -116,6 +116,7 @@ nav:
|
||||||
- Cloak End 2 End Encrypted Secrets: provider/cloak.md
|
- Cloak End 2 End Encrypted Secrets: provider/cloak.md
|
||||||
- Scaleway: provider/scaleway.md
|
- Scaleway: provider/scaleway.md
|
||||||
- Delinea: provider/delinea.md
|
- Delinea: provider/delinea.md
|
||||||
|
- Secret Server: provider/delinea.md
|
||||||
- Passbolt: provider/passbolt.md
|
- Passbolt: provider/passbolt.md
|
||||||
- Pulumi ESC: provider/pulumi.md
|
- Pulumi ESC: provider/pulumi.md
|
||||||
- Onboardbase: provider/onboardbase.md
|
- Onboardbase: provider/onboardbase.md
|
||||||
|
|
|
@ -42,6 +42,7 @@ import (
|
||||||
_ "github.com/external-secrets/external-secrets/pkg/provider/passworddepot"
|
_ "github.com/external-secrets/external-secrets/pkg/provider/passworddepot"
|
||||||
_ "github.com/external-secrets/external-secrets/pkg/provider/pulumi"
|
_ "github.com/external-secrets/external-secrets/pkg/provider/pulumi"
|
||||||
_ "github.com/external-secrets/external-secrets/pkg/provider/scaleway"
|
_ "github.com/external-secrets/external-secrets/pkg/provider/scaleway"
|
||||||
|
_ "github.com/external-secrets/external-secrets/pkg/provider/secretserver"
|
||||||
_ "github.com/external-secrets/external-secrets/pkg/provider/senhasegura"
|
_ "github.com/external-secrets/external-secrets/pkg/provider/senhasegura"
|
||||||
_ "github.com/external-secrets/external-secrets/pkg/provider/vault"
|
_ "github.com/external-secrets/external-secrets/pkg/provider/vault"
|
||||||
_ "github.com/external-secrets/external-secrets/pkg/provider/webhook"
|
_ "github.com/external-secrets/external-secrets/pkg/provider/webhook"
|
||||||
|
|
147
pkg/provider/secretserver/client.go
Normal file
147
pkg/provider/secretserver/client.go
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
/*
|
||||||
|
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 secretserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/DelineaXPM/tss-sdk-go/v2/server"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
|
||||||
|
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||||
|
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type client struct {
|
||||||
|
api secretAPI
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ esv1beta1.SecretsClient = &client{}
|
||||||
|
|
||||||
|
// GetSecret supports two types:
|
||||||
|
// 1. Get the secrets using the secret ID in ref.key i.e. key: 53974
|
||||||
|
// 2. Get the secret using the secret "name" i.e. key: "secretNameHere"
|
||||||
|
// - Secret names must not contain spaces.
|
||||||
|
// - If using the secret "name" and multiple secrets are found ...
|
||||||
|
// the first secret in the array will be the secret returned.
|
||||||
|
// 3. get the full secret as json-encoded value
|
||||||
|
// by leaving the ref.Property empty.
|
||||||
|
// 4. get a specific value by using a key from the json formatted secret in Items.0.ItemValue.
|
||||||
|
// 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 contains no fields
|
||||||
|
if secret.Fields == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
jsonStr, err := json.Marshal(secret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// If no property is defined return the full secret as raw json
|
||||||
|
if ref.Property == "" {
|
||||||
|
return jsonStr, nil
|
||||||
|
}
|
||||||
|
// extract first "field" i.e. Items.0.ItemValue, data from secret using gjson
|
||||||
|
val := gjson.Get(string(jsonStr), "Items.0.ItemValue")
|
||||||
|
if !val.Exists() {
|
||||||
|
return nil, esv1beta1.NoSecretError{}
|
||||||
|
}
|
||||||
|
// extract specific value from data directly above using gjson
|
||||||
|
out := gjson.Get(val.String(), ref.Property)
|
||||||
|
if !out.Exists() {
|
||||||
|
return nil, esv1beta1.NoSecretError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(out.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not supported at this time.
|
||||||
|
func (c *client) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.PushSecretData) error {
|
||||||
|
return errors.New("pushing secrets is not supported by Secret Server at this time")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not supported at this time.
|
||||||
|
func (c *client) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
|
||||||
|
return errors.New("deleting secrets is not supported by Secret Server at this time")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not supported at this time.
|
||||||
|
func (c *client) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
|
||||||
|
return false, errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not supported at this time.
|
||||||
|
func (c *client) Validate() (esv1beta1.ValidationResult, error) {
|
||||||
|
return esv1beta1.ValidationResultReady, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
secretData := make(map[string]any)
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(secret.Fields[0].ItemValue), &secretData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make(map[string][]byte)
|
||||||
|
for k, v := range secretData {
|
||||||
|
data[k], err = utils.GetByteValue(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not supported at this time.
|
||||||
|
func (c *client) GetAllSecrets(_ context.Context, _ esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
|
||||||
|
return nil, errors.New("getting all secrets is not supported by Delinea Secret Server at this time")
|
||||||
|
}
|
||||||
|
|
||||||
|
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) (*server.Secret, error) {
|
||||||
|
if ref.Version != "" {
|
||||||
|
return nil, errors.New("specifying a version is not supported")
|
||||||
|
}
|
||||||
|
id, err := strconv.Atoi(ref.Key)
|
||||||
|
if err != nil {
|
||||||
|
s, err := c.api.Secrets(ref.Key, "Name")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(s) == 0 {
|
||||||
|
return nil, errors.New("unable to retrieve secret at this time")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &s[0], nil
|
||||||
|
}
|
||||||
|
return c.api.Secret(id)
|
||||||
|
}
|
162
pkg/provider/secretserver/client_test.go
Normal file
162
pkg/provider/secretserver/client_test.go
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
/*
|
||||||
|
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 secretserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DelineaXPM/tss-sdk-go/v2/server"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNotFound = errors.New("not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeAPI struct {
|
||||||
|
secrets []*server.Secret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeAPI) Secret(id int) (*server.Secret, error) {
|
||||||
|
for _, s := range f.secrets {
|
||||||
|
if s.ID == id {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeAPI) Secrets(searchText, _ string) ([]server.Secret, error) {
|
||||||
|
secret := make([]server.Secret, 1)
|
||||||
|
for _, s := range f.secrets {
|
||||||
|
if s.Name == searchText {
|
||||||
|
secret[0] = *s
|
||||||
|
return secret, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// createSecret assembles a server.Secret from file test_data.json.
|
||||||
|
func createSecret(id int, itemValue string) *server.Secret {
|
||||||
|
s, _ := getJSONData()
|
||||||
|
s.ID = id
|
||||||
|
s.Fields[0].ItemValue = itemValue
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func getJSONData() (*server.Secret, error) {
|
||||||
|
var s = &server.Secret{}
|
||||||
|
jsonFile, err := os.Open("test_data.json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer jsonFile.Close()
|
||||||
|
|
||||||
|
byteValue, _ := io.ReadAll(jsonFile)
|
||||||
|
err = json.Unmarshal(byteValue, &s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestClient() esv1beta1.SecretsClient {
|
||||||
|
return &client{
|
||||||
|
api: &fakeAPI{
|
||||||
|
secrets: []*server.Secret{
|
||||||
|
createSecret(1000, "{ \"user\": \"robertOppenheimer\", \"password\": \"badPassword\",\"server\":\"192.168.1.50\"}"),
|
||||||
|
createSecret(2000, "{ \"user\": \"helloWorld\", \"password\": \"badPassword\",\"server\":[ \"192.168.1.50\",\"192.168.1.51\"] }"),
|
||||||
|
createSecret(3000, "{ \"user\": \"chuckTesta\", \"password\": \"badPassword\",\"server\":\"192.168.1.50\"}"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSecret(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
c := newTestClient()
|
||||||
|
s, _ := getJSONData()
|
||||||
|
jsonStr, _ := json.Marshal(s)
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
ref esv1beta1.ExternalSecretDataRemoteRef
|
||||||
|
want []byte
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
"incorrect key returns nil and error": {
|
||||||
|
ref: esv1beta1.ExternalSecretDataRemoteRef{
|
||||||
|
Key: "0",
|
||||||
|
},
|
||||||
|
want: []byte(nil),
|
||||||
|
err: errNotFound,
|
||||||
|
},
|
||||||
|
"key = 'secret name' and user property returns a single value": {
|
||||||
|
ref: esv1beta1.ExternalSecretDataRemoteRef{
|
||||||
|
Key: "ESO-test-secret",
|
||||||
|
Property: "user",
|
||||||
|
},
|
||||||
|
want: []byte(`robertOppenheimer`),
|
||||||
|
},
|
||||||
|
"key and password property returns a single value": {
|
||||||
|
ref: esv1beta1.ExternalSecretDataRemoteRef{
|
||||||
|
Key: "1000",
|
||||||
|
Property: "password",
|
||||||
|
},
|
||||||
|
want: []byte(`badPassword`),
|
||||||
|
},
|
||||||
|
"key and nested property returns a single value": {
|
||||||
|
ref: esv1beta1.ExternalSecretDataRemoteRef{
|
||||||
|
Key: "2000",
|
||||||
|
Property: "server.1",
|
||||||
|
},
|
||||||
|
want: []byte(`192.168.1.51`),
|
||||||
|
},
|
||||||
|
"existent key with non-existing propery": {
|
||||||
|
ref: esv1beta1.ExternalSecretDataRemoteRef{
|
||||||
|
Key: "3000",
|
||||||
|
Property: "foo.bar",
|
||||||
|
},
|
||||||
|
err: esv1beta1.NoSecretError{},
|
||||||
|
},
|
||||||
|
"existent 'name' key with no propery": {
|
||||||
|
ref: esv1beta1.ExternalSecretDataRemoteRef{
|
||||||
|
Key: "1000",
|
||||||
|
},
|
||||||
|
want: jsonStr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
179
pkg/provider/secretserver/provider.go
Normal file
179
pkg/provider/secretserver/provider.go
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
/*
|
||||||
|
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 secretserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/DelineaXPM/tss-sdk-go/v2/server"
|
||||||
|
kubeClient "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"
|
||||||
|
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||||
|
"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errEmptyUserName = errors.New("username must not be empty")
|
||||||
|
errEmptyPassword = errors.New("password must be set")
|
||||||
|
errEmptyServerURL = errors.New("serverURL 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 secret server provider")
|
||||||
|
errClusterStoreRequiresNamespace = errors.New("when using a ClusterSecretStore, namespaces must be explicitly set")
|
||||||
|
errMissingSecretName = errors.New("must specify a secret name")
|
||||||
|
|
||||||
|
errMissingSecretKey = errors.New("must specify a secret key")
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
username, err := loadConfigSecret(ctx, store.GetKind(), cfg.Username, kube, namespace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
password, err := loadConfigSecret(ctx, store.GetKind(), cfg.Password, kube, namespace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
secretServer, err := server.New(server.Configuration{
|
||||||
|
Credentials: server.UserCredential{
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
},
|
||||||
|
ServerURL: cfg.ServerURL,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &client{
|
||||||
|
api: secretServer,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConfigSecret(
|
||||||
|
ctx context.Context,
|
||||||
|
storeKind string,
|
||||||
|
ref *esv1beta1.SecretServerProviderRef,
|
||||||
|
kube kubeClient.Client,
|
||||||
|
namespace string) (string, error) {
|
||||||
|
if ref.SecretRef == nil {
|
||||||
|
return ref.Value, nil
|
||||||
|
}
|
||||||
|
if err := validateSecretRef(ref); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return resolvers.SecretKeyRef(ctx, kube, storeKind, namespace, ref.SecretRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateStoreSecretRef(store esv1beta1.GenericStore, ref *esv1beta1.SecretServerProviderRef) error {
|
||||||
|
if ref.SecretRef != nil {
|
||||||
|
if err := utils.ValidateReferentSecretSelector(store, *ref.SecretRef); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return validateSecretRef(ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSecretRef(ref *esv1beta1.SecretServerProviderRef) 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.SecretServerProvider) bool {
|
||||||
|
if cfg.Username.SecretRef != nil && cfg.Username.SecretRef.Namespace == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if cfg.Password.SecretRef != nil && cfg.Password.SecretRef.Namespace == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConfig(store esv1beta1.GenericStore) (*esv1beta1.SecretServerProvider, error) {
|
||||||
|
if store == nil {
|
||||||
|
return nil, errMissingStore
|
||||||
|
}
|
||||||
|
storeSpec := store.GetSpec()
|
||||||
|
|
||||||
|
if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.SecretServer == nil {
|
||||||
|
return nil, errInvalidSpec
|
||||||
|
}
|
||||||
|
cfg := storeSpec.Provider.SecretServer
|
||||||
|
|
||||||
|
if cfg.Username == nil {
|
||||||
|
return nil, errEmptyUserName
|
||||||
|
}
|
||||||
|
if cfg.Password == nil {
|
||||||
|
return nil, errEmptyPassword
|
||||||
|
}
|
||||||
|
if cfg.ServerURL == "" {
|
||||||
|
return nil, errEmptyServerURL
|
||||||
|
}
|
||||||
|
|
||||||
|
err := validateStoreSecretRef(store, cfg.Username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = validateStoreSecretRef(store, cfg.Password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) ValidateStore(store esv1beta1.GenericStore) (admission.Warnings, error) {
|
||||||
|
_, err := getConfig(store)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
|
||||||
|
SecretServer: &esv1beta1.SecretServerProvider{},
|
||||||
|
})
|
||||||
|
}
|
351
pkg/provider/secretserver/provider_test.go
Normal file
351
pkg/provider/secretserver/provider_test.go
Normal file
|
@ -0,0 +1,351 @@
|
||||||
|
/*
|
||||||
|
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 secretserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DelineaXPM/tss-sdk-go/v2/server"
|
||||||
|
"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.SecretServerProvider
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
"true when Username references a secret without explicit namespace": {
|
||||||
|
cfg: esv1beta1.SecretServerProvider{
|
||||||
|
Username: &esv1beta1.SecretServerProviderRef{
|
||||||
|
SecretRef: &v1.SecretKeySelector{Name: "foo"},
|
||||||
|
},
|
||||||
|
Password: &esv1beta1.SecretServerProviderRef{SecretRef: nil},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
"true when password references a secret without explicit namespace": {
|
||||||
|
cfg: esv1beta1.SecretServerProvider{
|
||||||
|
Username: &esv1beta1.SecretServerProviderRef{SecretRef: nil},
|
||||||
|
Password: &esv1beta1.SecretServerProviderRef{
|
||||||
|
SecretRef: &v1.SecretKeySelector{Name: "foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
"false when neither Username or Password reference a secret": {
|
||||||
|
cfg: esv1beta1.SecretServerProvider{
|
||||||
|
Username: &esv1beta1.SecretServerProviderRef{SecretRef: nil},
|
||||||
|
Password: &esv1beta1.SecretServerProviderRef{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.SecretServerProviderRef{
|
||||||
|
SecretRef: &v1.SecretKeySelector{Name: "foo"}, Value: "foo",
|
||||||
|
}
|
||||||
|
testURL := "https://example.com"
|
||||||
|
|
||||||
|
tests := map[string]struct {
|
||||||
|
cfg esv1beta1.SecretServerProvider
|
||||||
|
want error
|
||||||
|
}{
|
||||||
|
"invalid without username": {
|
||||||
|
cfg: esv1beta1.SecretServerProvider{
|
||||||
|
Username: nil,
|
||||||
|
Password: validSecretRefUsingValue,
|
||||||
|
ServerURL: testURL,
|
||||||
|
},
|
||||||
|
want: errEmptyUserName,
|
||||||
|
},
|
||||||
|
"invalid without password": {
|
||||||
|
cfg: esv1beta1.SecretServerProvider{
|
||||||
|
Username: validSecretRefUsingValue,
|
||||||
|
Password: nil,
|
||||||
|
ServerURL: testURL,
|
||||||
|
},
|
||||||
|
want: errEmptyPassword,
|
||||||
|
},
|
||||||
|
"invalid without serverURL": {
|
||||||
|
cfg: esv1beta1.SecretServerProvider{
|
||||||
|
Username: validSecretRefUsingValue,
|
||||||
|
Password: validSecretRefUsingValue,
|
||||||
|
/*ServerURL: testURL,*/
|
||||||
|
},
|
||||||
|
want: errEmptyServerURL,
|
||||||
|
},
|
||||||
|
"invalid with ambiguous Username": {
|
||||||
|
cfg: esv1beta1.SecretServerProvider{
|
||||||
|
Username: ambiguousSecretRef,
|
||||||
|
Password: validSecretRefUsingValue,
|
||||||
|
ServerURL: testURL,
|
||||||
|
},
|
||||||
|
want: errSecretRefAndValueConflict,
|
||||||
|
},
|
||||||
|
"invalid with ambiguous Password": {
|
||||||
|
cfg: esv1beta1.SecretServerProvider{
|
||||||
|
Username: validSecretRefUsingValue,
|
||||||
|
Password: ambiguousSecretRef,
|
||||||
|
ServerURL: testURL,
|
||||||
|
},
|
||||||
|
want: errSecretRefAndValueConflict,
|
||||||
|
},
|
||||||
|
"invalid with invalid Username": {
|
||||||
|
cfg: esv1beta1.SecretServerProvider{
|
||||||
|
Username: makeSecretRefUsingValue(""),
|
||||||
|
Password: validSecretRefUsingValue,
|
||||||
|
ServerURL: testURL,
|
||||||
|
},
|
||||||
|
want: errSecretRefAndValueMissing,
|
||||||
|
},
|
||||||
|
"invalid with invalid Password": {
|
||||||
|
cfg: esv1beta1.SecretServerProvider{
|
||||||
|
Username: validSecretRefUsingValue,
|
||||||
|
Password: makeSecretRefUsingValue(""),
|
||||||
|
ServerURL: testURL,
|
||||||
|
},
|
||||||
|
want: errSecretRefAndValueMissing,
|
||||||
|
},
|
||||||
|
"valid with tenant/clientID/clientSecret": {
|
||||||
|
cfg: esv1beta1.SecretServerProvider{
|
||||||
|
Username: validSecretRefUsingValue,
|
||||||
|
Password: validSecretRefUsingValue,
|
||||||
|
ServerURL: testURL,
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, tc := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
s := esv1beta1.SecretStore{
|
||||||
|
Spec: esv1beta1.SecretStoreSpec{
|
||||||
|
Provider: &esv1beta1.SecretStoreProvider{
|
||||||
|
SecretServer: &tc.cfg,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
p := &Provider{}
|
||||||
|
_, got := p.ValidateStore(&s)
|
||||||
|
assert.Equal(t, tc.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewClient(t *testing.T) {
|
||||||
|
userNameKey := "username"
|
||||||
|
userNameValue := "foo"
|
||||||
|
passwordKey := "password"
|
||||||
|
passwordValue := generateRandomString()
|
||||||
|
|
||||||
|
clientSecret := &corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
userNameKey: []byte(userNameValue),
|
||||||
|
passwordKey: []byte(passwordValue),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
validProvider := &esv1beta1.SecretServerProvider{
|
||||||
|
Username: makeSecretRefUsingRef(clientSecret.Name, userNameKey),
|
||||||
|
Password: makeSecretRefUsingRef(clientSecret.Name, passwordKey),
|
||||||
|
ServerURL: "https://example.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := map[string]struct {
|
||||||
|
store esv1beta1.GenericStore // leave nil for namespaced store
|
||||||
|
provider *esv1beta1.SecretServerProvider // 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{
|
||||||
|
SecretServer: validProvider,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errCheck: func(t *testing.T, err error) {
|
||||||
|
assert.ErrorIs(t, err, errClusterStoreRequiresNamespace)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"dangling password ref": {
|
||||||
|
provider: &esv1beta1.SecretServerProvider{
|
||||||
|
Username: validProvider.Username,
|
||||||
|
Password: makeSecretRefUsingRef("typo", passwordKey),
|
||||||
|
ServerURL: validProvider.ServerURL,
|
||||||
|
},
|
||||||
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
||||||
|
errCheck: func(t *testing.T, err error) {
|
||||||
|
assert.True(t, kubeErrors.IsNotFound(err))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"dangling username ref": {
|
||||||
|
provider: &esv1beta1.SecretServerProvider{
|
||||||
|
Username: makeSecretRefUsingRef("typo", userNameKey),
|
||||||
|
Password: validProvider.Password,
|
||||||
|
ServerURL: validProvider.ServerURL,
|
||||||
|
},
|
||||||
|
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.SecretServerProvider{
|
||||||
|
Username: makeSecretRefUsingRef("", userNameKey),
|
||||||
|
Password: validProvider.Password,
|
||||||
|
ServerURL: validProvider.ServerURL,
|
||||||
|
},
|
||||||
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
||||||
|
errCheck: func(t *testing.T, err error) {
|
||||||
|
assert.ErrorIs(t, err, errMissingSecretName)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"secret ref without key": {
|
||||||
|
provider: &esv1beta1.SecretServerProvider{
|
||||||
|
Username: validProvider.Password,
|
||||||
|
Password: makeSecretRefUsingRef(clientSecret.Name, ""),
|
||||||
|
ServerURL: validProvider.ServerURL,
|
||||||
|
},
|
||||||
|
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.SecretServerProvider{
|
||||||
|
Username: makeSecretRefUsingRef(clientSecret.Name, "typo"),
|
||||||
|
Password: makeSecretRefUsingRef(clientSecret.Name, passwordKey),
|
||||||
|
ServerURL: validProvider.ServerURL,
|
||||||
|
},
|
||||||
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
||||||
|
errCheck: func(t *testing.T, err error) {
|
||||||
|
assert.EqualError(t, err, "cannot find secret data for key: \"typo\"")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"valid secret refs": {
|
||||||
|
provider: validProvider,
|
||||||
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
||||||
|
},
|
||||||
|
"secret values": {
|
||||||
|
provider: &esv1beta1.SecretServerProvider{
|
||||||
|
Username: makeSecretRefUsingValue(userNameValue),
|
||||||
|
Password: makeSecretRefUsingValue(passwordValue),
|
||||||
|
ServerURL: validProvider.ServerURL,
|
||||||
|
},
|
||||||
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
||||||
|
},
|
||||||
|
"cluster secret store": {
|
||||||
|
store: &esv1beta1.ClusterSecretStore{
|
||||||
|
TypeMeta: metav1.TypeMeta{Kind: esv1beta1.ClusterSecretStoreKind},
|
||||||
|
Spec: esv1beta1.SecretStoreSpec{
|
||||||
|
Provider: &esv1beta1.SecretStoreProvider{
|
||||||
|
SecretServer: &esv1beta1.SecretServerProvider{
|
||||||
|
Username: makeSecretRefUsingNamespacedRef(clientSecret.Namespace, clientSecret.Name, userNameKey),
|
||||||
|
Password: makeSecretRefUsingNamespacedRef(clientSecret.Namespace, clientSecret.Name, passwordKey),
|
||||||
|
ServerURL: validProvider.ServerURL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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{
|
||||||
|
SecretServer: 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)
|
||||||
|
secretServerClient, ok := delineaClient.api.(*server.Server)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, server.UserCredential{
|
||||||
|
Username: userNameValue,
|
||||||
|
Password: passwordValue,
|
||||||
|
}, secretServerClient.Configuration.Credentials)
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, sc)
|
||||||
|
tc.errCheck(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSecretRefUsingNamespacedRef(namespace, name, key string) *esv1beta1.SecretServerProviderRef {
|
||||||
|
return &esv1beta1.SecretServerProviderRef{
|
||||||
|
SecretRef: &v1.SecretKeySelector{Namespace: utils.Ptr(namespace), Name: name, Key: key},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSecretRefUsingValue(val string) *esv1beta1.SecretServerProviderRef {
|
||||||
|
return &esv1beta1.SecretServerProviderRef{Value: val}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSecretRefUsingRef(name, key string) *esv1beta1.SecretServerProviderRef {
|
||||||
|
return &esv1beta1.SecretServerProviderRef{
|
||||||
|
SecretRef: &v1.SecretKeySelector{Name: name, Key: key},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateRandomString() string {
|
||||||
|
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||||
|
b := make([]rune, 10)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = letters[rand.Intn(len(letters))]
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b)
|
||||||
|
}
|
26
pkg/provider/secretserver/secret_api.go
Normal file
26
pkg/provider/secretserver/secret_api.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
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 secretserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/DelineaXPM/tss-sdk-go/v2/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
// secretAPI represents the subset of the Secret Server API
|
||||||
|
// which is supported by tss-sdk-go/v2.
|
||||||
|
type secretAPI interface {
|
||||||
|
Secret(id int) (*server.Secret, error)
|
||||||
|
Secrets(searchText, field string) ([]server.Secret, error)
|
||||||
|
}
|
38
pkg/provider/secretserver/test_data.json
Normal file
38
pkg/provider/secretserver/test_data.json
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"Name": "ESO-test-secret",
|
||||||
|
"FolderID": 73,
|
||||||
|
"ID": 1000,
|
||||||
|
"SiteID": 1,
|
||||||
|
"SecretTemplateID": 6098,
|
||||||
|
"SecretPolicyID": -1,
|
||||||
|
"PasswordTypeWebScriptID": -1,
|
||||||
|
"LauncherConnectAsSecretID": -1,
|
||||||
|
"CheckOutIntervalMinutes": -1,
|
||||||
|
"Active": true,
|
||||||
|
"CheckedOut": false,
|
||||||
|
"CheckOutEnabled": false,
|
||||||
|
"AutoChangeEnabled": false,
|
||||||
|
"CheckOutChangePasswordEnabled": false,
|
||||||
|
"DelayIndexing": false,
|
||||||
|
"EnableInheritPermissions": false,
|
||||||
|
"EnableInheritSecretPolicy": false,
|
||||||
|
"ProxyEnabled": false,
|
||||||
|
"RequiresComment": false,
|
||||||
|
"SessionRecordingEnabled": false,
|
||||||
|
"WebLauncherRequiresIncognitoMode": false,
|
||||||
|
"Items": [
|
||||||
|
{
|
||||||
|
"ItemID": 286259,
|
||||||
|
"FieldID": 439,
|
||||||
|
"FileAttachmentID": 0,
|
||||||
|
"FieldName": "Data",
|
||||||
|
"Slug": "data",
|
||||||
|
"FieldDescription": "json text field",
|
||||||
|
"Filename": "",
|
||||||
|
"ItemValue": "{ \"user\": \"robertOppenheimer\", \"password\": \"badPassword\",\"server\":\"192.168.1.50\"}",
|
||||||
|
"IsFile": false,
|
||||||
|
"IsNotes": false,
|
||||||
|
"IsPassword": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in a new issue