mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
Feat: Add Passbolt Provider (#3334)
* add passbolt provider Signed-off-by: Thorben Below <56894536+thorbenbelow@users.noreply.github.com> * Fix: return err for unimplemented methods Signed-off-by: Thorben Below <56894536+thorbenbelow@users.noreply.github.com> --------- Signed-off-by: Thorben Below <56894536+thorbenbelow@users.noreply.github.com>
This commit is contained in:
parent
e0bdcd0d97
commit
432c6bf9ab
19 changed files with 1109 additions and 0 deletions
32
apis/externalsecrets/v1beta1/secretsstore_passbolt_types.go
Normal file
32
apis/externalsecrets/v1beta1/secretsstore_passbolt_types.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
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"
|
||||
)
|
||||
|
||||
// Passbolt contains a secretRef for the passbolt credentials.
|
||||
type PassboltAuth struct {
|
||||
PasswordSecretRef *esmeta.SecretKeySelector `json:"passwordSecretRef"`
|
||||
PrivateKeySecretRef *esmeta.SecretKeySelector `json:"privateKeySecretRef"`
|
||||
}
|
||||
|
||||
type PassboltProvider struct {
|
||||
// Auth defines the information necessary to authenticate against Passbolt Server
|
||||
Auth *PassboltAuth `json:"auth"`
|
||||
// Host defines the Passbolt Server to connect to
|
||||
Host string `json:"host"`
|
||||
}
|
|
@ -160,6 +160,9 @@ type SecretStoreProvider struct {
|
|||
|
||||
// +optional
|
||||
PasswordDepot *PasswordDepotProvider `json:"passworddepot,omitempty"`
|
||||
|
||||
// +optional
|
||||
Passbolt *PassboltProvider `json:"passbolt,omitempty"`
|
||||
}
|
||||
|
||||
type CAProviderType string
|
||||
|
|
|
@ -1920,6 +1920,51 @@ func (in *OracleSecretRef) DeepCopy() *OracleSecretRef {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PassboltAuth) DeepCopyInto(out *PassboltAuth) {
|
||||
*out = *in
|
||||
if in.PasswordSecretRef != nil {
|
||||
in, out := &in.PasswordSecretRef, &out.PasswordSecretRef
|
||||
*out = new(metav1.SecretKeySelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.PrivateKeySecretRef != nil {
|
||||
in, out := &in.PrivateKeySecretRef, &out.PrivateKeySecretRef
|
||||
*out = new(metav1.SecretKeySelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PassboltAuth.
|
||||
func (in *PassboltAuth) DeepCopy() *PassboltAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PassboltAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PassboltProvider) DeepCopyInto(out *PassboltProvider) {
|
||||
*out = *in
|
||||
if in.Auth != nil {
|
||||
in, out := &in.Auth, &out.Auth
|
||||
*out = new(PassboltAuth)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PassboltProvider.
|
||||
func (in *PassboltProvider) DeepCopy() *PassboltProvider {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PassboltProvider)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PasswordDepotAuth) DeepCopyInto(out *PasswordDepotAuth) {
|
||||
*out = *in
|
||||
|
@ -2245,6 +2290,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
|
|||
*out = new(PasswordDepotProvider)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Passbolt != nil {
|
||||
in, out := &in.Passbolt, &out.Passbolt
|
||||
*out = new(PassboltProvider)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreProvider.
|
||||
|
|
|
@ -3263,6 +3263,63 @@ spec:
|
|||
- region
|
||||
- vault
|
||||
type: object
|
||||
passbolt:
|
||||
properties:
|
||||
auth:
|
||||
description: Auth defines the information necessary to authenticate
|
||||
against Passbolt Server
|
||||
properties:
|
||||
passwordSecretRef:
|
||||
description: |-
|
||||
A reference to a specific 'key' within a Secret resource,
|
||||
In some instances, `key` is a required field.
|
||||
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
|
||||
privateKeySecretRef:
|
||||
description: |-
|
||||
A reference to a specific 'key' within a Secret resource,
|
||||
In some instances, `key` is a required field.
|
||||
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
|
||||
required:
|
||||
- passwordSecretRef
|
||||
- privateKeySecretRef
|
||||
type: object
|
||||
host:
|
||||
description: Host defines the Passbolt Server to connect to
|
||||
type: string
|
||||
required:
|
||||
- auth
|
||||
- host
|
||||
type: object
|
||||
passworddepot:
|
||||
description: Configures a store to sync secrets with a Password
|
||||
Depot instance.
|
||||
|
|
|
@ -3263,6 +3263,63 @@ spec:
|
|||
- region
|
||||
- vault
|
||||
type: object
|
||||
passbolt:
|
||||
properties:
|
||||
auth:
|
||||
description: Auth defines the information necessary to authenticate
|
||||
against Passbolt Server
|
||||
properties:
|
||||
passwordSecretRef:
|
||||
description: |-
|
||||
A reference to a specific 'key' within a Secret resource,
|
||||
In some instances, `key` is a required field.
|
||||
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
|
||||
privateKeySecretRef:
|
||||
description: |-
|
||||
A reference to a specific 'key' within a Secret resource,
|
||||
In some instances, `key` is a required field.
|
||||
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
|
||||
required:
|
||||
- passwordSecretRef
|
||||
- privateKeySecretRef
|
||||
type: object
|
||||
host:
|
||||
description: Host defines the Passbolt Server to connect to
|
||||
type: string
|
||||
required:
|
||||
- auth
|
||||
- host
|
||||
type: object
|
||||
passworddepot:
|
||||
description: Configures a store to sync secrets with a Password
|
||||
Depot instance.
|
||||
|
|
|
@ -3685,6 +3685,60 @@ spec:
|
|||
- region
|
||||
- vault
|
||||
type: object
|
||||
passbolt:
|
||||
properties:
|
||||
auth:
|
||||
description: Auth defines the information necessary to authenticate against Passbolt Server
|
||||
properties:
|
||||
passwordSecretRef:
|
||||
description: |-
|
||||
A reference to a specific 'key' within a Secret resource,
|
||||
In some instances, `key` is a required field.
|
||||
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
|
||||
privateKeySecretRef:
|
||||
description: |-
|
||||
A reference to a specific 'key' within a Secret resource,
|
||||
In some instances, `key` is a required field.
|
||||
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
|
||||
required:
|
||||
- passwordSecretRef
|
||||
- privateKeySecretRef
|
||||
type: object
|
||||
host:
|
||||
description: Host defines the Passbolt Server to connect to
|
||||
type: string
|
||||
required:
|
||||
- auth
|
||||
- host
|
||||
type: object
|
||||
passworddepot:
|
||||
description: Configures a store to sync secrets with a Password Depot instance.
|
||||
properties:
|
||||
|
@ -8955,6 +9009,60 @@ spec:
|
|||
- region
|
||||
- vault
|
||||
type: object
|
||||
passbolt:
|
||||
properties:
|
||||
auth:
|
||||
description: Auth defines the information necessary to authenticate against Passbolt Server
|
||||
properties:
|
||||
passwordSecretRef:
|
||||
description: |-
|
||||
A reference to a specific 'key' within a Secret resource,
|
||||
In some instances, `key` is a required field.
|
||||
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
|
||||
privateKeySecretRef:
|
||||
description: |-
|
||||
A reference to a specific 'key' within a Secret resource,
|
||||
In some instances, `key` is a required field.
|
||||
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
|
||||
required:
|
||||
- passwordSecretRef
|
||||
- privateKeySecretRef
|
||||
type: object
|
||||
host:
|
||||
description: Host defines the Passbolt Server to connect to
|
||||
type: string
|
||||
required:
|
||||
- auth
|
||||
- host
|
||||
type: object
|
||||
passworddepot:
|
||||
description: Configures a store to sync secrets with a Password Depot instance.
|
||||
properties:
|
||||
|
|
|
@ -5019,6 +5019,91 @@ External Secrets meta/v1.SecretKeySelector
|
|||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3 id="external-secrets.io/v1beta1.PassboltAuth">PassboltAuth
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#external-secrets.io/v1beta1.PassboltProvider">PassboltProvider</a>)
|
||||
</p>
|
||||
<p>
|
||||
<p>Passbolt contains a secretRef for the passbolt credentials.</p>
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>passwordSecretRef</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>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>privateKeySecretRef</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>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3 id="external-secrets.io/v1beta1.PassboltProvider">PassboltProvider
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#external-secrets.io/v1beta1.SecretStoreProvider">SecretStoreProvider</a>)
|
||||
</p>
|
||||
<p>
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>auth</code></br>
|
||||
<em>
|
||||
<a href="#external-secrets.io/v1beta1.PassboltAuth">
|
||||
PassboltAuth
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Auth defines the information necessary to authenticate against Passbolt Server</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>host</code></br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Host defines the Passbolt Server to connect to</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3 id="external-secrets.io/v1beta1.PasswordDepotAuth">PasswordDepotAuth
|
||||
</h3>
|
||||
<p>
|
||||
|
@ -5918,6 +6003,19 @@ PasswordDepotProvider
|
|||
<em>(Optional)</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>passbolt</code></br>
|
||||
<em>
|
||||
<a href="#external-secrets.io/v1beta1.PassboltProvider">
|
||||
PassboltProvider
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3 id="external-secrets.io/v1beta1.SecretStoreRef">SecretStoreRef
|
||||
|
|
|
@ -54,6 +54,7 @@ The following table describes the stability level of each provider and who's res
|
|||
| [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/) |
|
||||
| [Pulumi ESC](https://external-secrets.io/latest/provider/pulumi) | alpha | [@dirien](https://github.com/dirien) |
|
||||
| [Passbolt](https://external-secrets.io/latest/provider/passbolt) | alpha | |
|
||||
|
||||
## Provider Feature Support
|
||||
|
||||
|
@ -82,6 +83,7 @@ The following table show the support for features across different providers.
|
|||
| Conjur | | | | | x | | |
|
||||
| Delinea | x | | | | x | | |
|
||||
| Pulumi ESC | x | | | | x | | |
|
||||
| Passbolt | x | | | | x | | |
|
||||
|
||||
## Support Policy
|
||||
|
||||
|
|
39
docs/provider/passbolt.md
Normal file
39
docs/provider/passbolt.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
External Secrets Operator integrates with [Passbolt API](https://www.passbolt.com/) to sync Passbolt to secrets held on the Kubernetes cluster.
|
||||
|
||||
|
||||
|
||||
### Creating a Passbolt secret store
|
||||
|
||||
Be sure the `passbolt` provider is listed in the `Kind=SecretStore` and auth and host are set.
|
||||
The API requires a password and private key provided in a secret.
|
||||
|
||||
```yaml
|
||||
{% include 'passbolt-secret-store.yaml' %}
|
||||
```
|
||||
|
||||
|
||||
### Creating an external secret
|
||||
|
||||
To sync a Passbolt secret to a Kubernetes secret, a `Kind=ExternalSecret` is needed.
|
||||
By default the secret contains name, username, uri, password and description.
|
||||
|
||||
To only select a single property add the `property` key.
|
||||
|
||||
```yaml
|
||||
{% include 'passbolt-external-secret-example.yaml' %}
|
||||
```
|
||||
|
||||
The above external secret will lead to the creation of a secret in the following form:
|
||||
|
||||
```yaml
|
||||
{% include 'passbolt-secret-example.yaml' %}
|
||||
```
|
||||
|
||||
|
||||
### Finding a secret by name
|
||||
|
||||
Instead of retrieving secrets by ID you can also use `dataFrom` to search for secrets by name.
|
||||
|
||||
```yaml
|
||||
{% include 'passbolt-external-secret-findbyname.yaml' %}
|
||||
```
|
19
docs/snippets/passbolt-external-secret-example.yaml
Normal file
19
docs/snippets/passbolt-external-secret-example.yaml
Normal file
|
@ -0,0 +1,19 @@
|
|||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: passbolt-example-simple
|
||||
spec:
|
||||
refreshInterval: "15s"
|
||||
secretStoreRef:
|
||||
name: passbolt
|
||||
kind: SecretStore
|
||||
target:
|
||||
name: passbolt-example
|
||||
data:
|
||||
- secretKey: full_secret
|
||||
remoteRef:
|
||||
key: e22487a8-feb8-4591-95aa-14b193930cb4 # Replace with ID of exising Passbolt secret
|
||||
- secretKey: password_only
|
||||
remoteRef:
|
||||
key: e22487a8-feb8-4591-95aa-14b193930cb4 # Replace with ID of exising Passbolt secret
|
||||
property: password # You can limit the secret to only display one property
|
15
docs/snippets/passbolt-external-secret-findbyname.yaml
Normal file
15
docs/snippets/passbolt-external-secret-findbyname.yaml
Normal file
|
@ -0,0 +1,15 @@
|
|||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: passbolt-example
|
||||
spec:
|
||||
refreshInterval: "15s"
|
||||
secretStoreRef:
|
||||
name: passbolt
|
||||
kind: SecretStore
|
||||
target:
|
||||
name: passbolt-example
|
||||
dataFrom:
|
||||
- find:
|
||||
name:
|
||||
regexp: ".*"
|
8
docs/snippets/passbolt-secret-example.yaml
Normal file
8
docs/snippets/passbolt-secret-example.yaml
Normal file
|
@ -0,0 +1,8 @@
|
|||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: passbolt-example
|
||||
data:
|
||||
full_secret: '{"name":"passbolt-secret","username":"some-username","password":"supersecretpassword","uri":"passbolt.com","description":"some description"}'
|
||||
password_only: supersecretpassword
|
||||
type: Opaque
|
15
docs/snippets/passbolt-secret-store.yaml
Normal file
15
docs/snippets/passbolt-secret-store.yaml
Normal file
|
@ -0,0 +1,15 @@
|
|||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: SecretStore
|
||||
metadata:
|
||||
name: passbolt
|
||||
spec:
|
||||
provider:
|
||||
passbolt:
|
||||
host: https://passbolt.passbolt.svc.cluster.local
|
||||
auth:
|
||||
passwordSecretRef:
|
||||
key: password
|
||||
name: passbolt-credentials
|
||||
privateKeySecretRef:
|
||||
key: privateKey
|
||||
name: passbolt-credentials
|
3
go.mod
3
go.mod
|
@ -82,6 +82,7 @@ require (
|
|||
github.com/keeper-security/secrets-manager-go/core v1.6.2
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.21
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1
|
||||
github.com/passbolt/go-passbolt v0.7.0
|
||||
github.com/pulumi/esc v0.8.3
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.26
|
||||
github.com/sethvargo/go-password v0.2.0
|
||||
|
@ -96,6 +97,8 @@ require (
|
|||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.7.4 // indirect
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
||||
github.com/agext/levenshtein v1.2.3 // indirect
|
||||
github.com/alessio/shellescape v1.4.2 // indirect
|
||||
|
|
7
go.sum
7
go.sum
|
@ -123,8 +123,13 @@ github.com/PaesslerAG/gval v1.2.2/go.mod h1:XRFLwvmkTEdYziLdaCeCa5ImcGVrfQbeNUbV
|
|||
github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8=
|
||||
github.com/PaesslerAG/jsonpath v0.1.1 h1:c1/AToHQMVsduPAa4Vh6xp2U0evy4t8SWp8imEsylIk=
|
||||
github.com/PaesslerAG/jsonpath v0.1.1/go.mod h1:lVboNxFGal/VwW6d9JzIy56bUsYAP6tH/x80vjnCseY=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.7.4 h1:Vz/8+HViFFnf2A6XX8JOvZMrA6F5puwNvvF21O1mRlo=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.7.4/go.mod h1:IhkNEDaxec6NyzSI0PlxapinnwPVIESk8/76da3Ct3g=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
||||
|
@ -668,6 +673,8 @@ github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:Ff
|
|||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
|
||||
github.com/oracle/oci-go-sdk/v65 v65.63.1 h1:dYL7sk9L1+C9LCmoq+zjPMNteuJJfk54YExq/4pV9xQ=
|
||||
github.com/oracle/oci-go-sdk/v65 v65.63.1/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0=
|
||||
github.com/passbolt/go-passbolt v0.7.0 h1:zwwTCwL3vjTTKln1hxwKuzzax4R/yvxGXSZhMh0OY5Y=
|
||||
github.com/passbolt/go-passbolt v0.7.0/go.mod h1:af3TVSJ+0A4sXeK8KgVzhV8Tej/i25biFIQjhL0FOMk=
|
||||
github.com/pgavlin/fx v0.1.6 h1:r9jEg69DhNoCd3Xh0+5mIbdbS3PqWrVWujkY76MFRTU=
|
||||
github.com/pgavlin/fx v0.1.6/go.mod h1:KWZJ6fqBBSh8GxHYqwYCf3rYE7Gp2p0N8tJp8xv9u9M=
|
||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||
|
|
|
@ -113,6 +113,7 @@ nav:
|
|||
- Cloak End 2 End Encrypted Secrets: provider/cloak.md
|
||||
- Scaleway: provider/scaleway.md
|
||||
- Delinea: provider/delinea.md
|
||||
- Passbolt: provider/passbolt.md
|
||||
- Pulumi ESC: provider/pulumi.md
|
||||
- Onboardbase: provider/onboardbase.md
|
||||
- Examples:
|
||||
|
|
296
pkg/provider/passbolt/passbolt.go
Normal file
296
pkg/provider/passbolt/passbolt.go
Normal file
|
@ -0,0 +1,296 @@
|
|||
/*
|
||||
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 passbolt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
|
||||
"github.com/passbolt/go-passbolt/api"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kclient "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"
|
||||
)
|
||||
|
||||
const (
|
||||
errPassboltStoreMissingProvider = "missing: spec.provider.passbolt"
|
||||
errPassboltStoreMissingAuth = "missing: spec.provider.passbolt.auth"
|
||||
errPassboltStoreMissingAuthPassword = "missing: spec.provider.passbolt.auth.passwordSecretRef"
|
||||
errPassboltStoreMissingAuthPrivateKey = "missing: spec.provider.passbolt.auth.privateKeySecretRef"
|
||||
errPassboltStoreMissingHost = "missing: spec.provider.passbolt.host"
|
||||
errPassboltExternalSecretMissingFindNameRegExp = "missing: find.name.regexp"
|
||||
errPassboltStoreHostSchemeNotHTTPS = "host Url has to be https scheme"
|
||||
errPassboltSecretPropertyInvalid = "property must be one of name, username, uri, password or description"
|
||||
errNotImplemented = "not implemented"
|
||||
)
|
||||
|
||||
type ProviderPassbolt struct {
|
||||
client Client
|
||||
}
|
||||
|
||||
func (provider *ProviderPassbolt) Capabilities() esv1beta1.SecretStoreCapabilities {
|
||||
return esv1beta1.SecretStoreReadOnly
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
CheckSession(ctx context.Context) bool
|
||||
Login(ctx context.Context) error
|
||||
Logout(ctx context.Context) error
|
||||
GetResource(ctx context.Context, resourceID string) (*api.Resource, error)
|
||||
GetResources(ctx context.Context, opts *api.GetResourcesOptions) ([]api.Resource, error)
|
||||
GetResourceType(ctx context.Context, typeID string) (*api.ResourceType, error)
|
||||
DecryptMessage(message string) (string, error)
|
||||
GetSecret(ctx context.Context, resourceID string) (*api.Secret, error)
|
||||
}
|
||||
|
||||
func (provider *ProviderPassbolt) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
config := store.GetSpec().Provider.Passbolt
|
||||
|
||||
password, err := resolvers.SecretKeyRef(
|
||||
ctx,
|
||||
kube,
|
||||
store.GetKind(),
|
||||
namespace,
|
||||
config.Auth.PasswordSecretRef,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
privateKey, err := resolvers.SecretKeyRef(
|
||||
ctx,
|
||||
kube,
|
||||
store.GetKind(),
|
||||
namespace,
|
||||
config.Auth.PrivateKeySecretRef,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client, err := api.NewClient(nil, "", config.Host, privateKey, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
provider.client = client
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func (provider *ProviderPassbolt) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
|
||||
return false, fmt.Errorf(errNotImplemented)
|
||||
}
|
||||
|
||||
func (provider *ProviderPassbolt) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
|
||||
if err := assureLoggedIn(ctx, provider.client); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secret, err := provider.getPassboltSecret(ctx, ref.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ref.Property == "" {
|
||||
return utils.JSONMarshal(secret)
|
||||
}
|
||||
|
||||
return secret.GetProp(ref.Property)
|
||||
}
|
||||
|
||||
func (provider *ProviderPassbolt) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.PushSecretData) error {
|
||||
return fmt.Errorf(errNotImplemented)
|
||||
}
|
||||
|
||||
func (provider *ProviderPassbolt) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
|
||||
return fmt.Errorf(errNotImplemented)
|
||||
}
|
||||
|
||||
func (provider *ProviderPassbolt) Validate() (esv1beta1.ValidationResult, error) {
|
||||
return esv1beta1.ValidationResultUnknown, nil
|
||||
}
|
||||
|
||||
func (provider *ProviderPassbolt) GetSecretMap(_ context.Context, _ esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
||||
return nil, fmt.Errorf(errNotImplemented)
|
||||
}
|
||||
|
||||
func (provider *ProviderPassbolt) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
|
||||
res := make(map[string][]byte)
|
||||
|
||||
if ref.Name == nil || ref.Name.RegExp == "" {
|
||||
return res, errors.New(errPassboltExternalSecretMissingFindNameRegExp)
|
||||
}
|
||||
|
||||
if err := assureLoggedIn(ctx, provider.client); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resources, err := provider.client.GetResources(ctx, &api.GetResourcesOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nameRegexp, err := regexp.Compile(ref.Name.RegExp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, resource := range resources {
|
||||
if !nameRegexp.MatchString(resource.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
secret, err := provider.getPassboltSecret(ctx, resource.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
marshaled, err := utils.JSONMarshal(secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res[resource.ID] = marshaled
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (provider *ProviderPassbolt) Close(ctx context.Context) error {
|
||||
return provider.client.Logout(ctx)
|
||||
}
|
||||
|
||||
func (provider *ProviderPassbolt) ValidateStore(store esv1beta1.GenericStore) (admission.Warnings, error) {
|
||||
config := store.GetSpec().Provider.Passbolt
|
||||
if config == nil {
|
||||
return nil, errors.New(errPassboltStoreMissingProvider)
|
||||
}
|
||||
|
||||
if config.Auth == nil {
|
||||
return nil, errors.New(errPassboltStoreMissingAuth)
|
||||
}
|
||||
|
||||
if config.Auth.PasswordSecretRef == nil || config.Auth.PasswordSecretRef.Name == "" || config.Auth.PasswordSecretRef.Key == "" {
|
||||
return nil, errors.New(errPassboltStoreMissingAuthPassword)
|
||||
}
|
||||
|
||||
if config.Auth.PrivateKeySecretRef == nil || config.Auth.PrivateKeySecretRef.Name == "" || config.Auth.PrivateKeySecretRef.Key == "" {
|
||||
return nil, errors.New(errPassboltStoreMissingAuthPrivateKey)
|
||||
}
|
||||
if config.Host == "" {
|
||||
return nil, errors.New(errPassboltStoreMissingHost)
|
||||
}
|
||||
|
||||
host, err := url.Parse(config.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if host.Scheme != "https" {
|
||||
return nil, errors.New(errPassboltStoreHostSchemeNotHTTPS)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
esv1beta1.Register(&ProviderPassbolt{}, &esv1beta1.SecretStoreProvider{
|
||||
Passbolt: &esv1beta1.PassboltProvider{},
|
||||
})
|
||||
}
|
||||
|
||||
type Secret struct {
|
||||
Name string `json:"name"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
URI string `json:"uri"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
func (ps Secret) GetProp(key string) ([]byte, error) {
|
||||
switch key {
|
||||
case "name":
|
||||
return []byte(ps.Name), nil
|
||||
case "username":
|
||||
return []byte(ps.Username), nil
|
||||
case "uri":
|
||||
return []byte(ps.URI), nil
|
||||
case "password":
|
||||
return []byte(ps.Password), nil
|
||||
case "description":
|
||||
return []byte(ps.Description), nil
|
||||
default:
|
||||
return nil, errors.New(errPassboltSecretPropertyInvalid)
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *ProviderPassbolt) getPassboltSecret(ctx context.Context, id string) (*Secret, error) {
|
||||
resource, err := provider.client.GetResource(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secret, err := provider.client.GetSecret(ctx, resource.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := Secret{
|
||||
Name: resource.Name,
|
||||
Username: resource.Username,
|
||||
URI: resource.URI,
|
||||
Description: resource.Description,
|
||||
}
|
||||
|
||||
raw, err := provider.client.DecryptMessage(secret.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resourceType, err := provider.client.GetResourceType(ctx, resource.ResourceTypeID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch resourceType.Slug {
|
||||
case "password-string":
|
||||
res.Password = raw
|
||||
case "password-and-description", "password-description-totp":
|
||||
var pwAndDesc api.SecretDataTypePasswordAndDescription
|
||||
if err := json.Unmarshal([]byte(raw), &pwAndDesc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.Password = pwAndDesc.Password
|
||||
res.Description = pwAndDesc.Description
|
||||
case "totp":
|
||||
default:
|
||||
return nil, fmt.Errorf("UnknownPassboltResourceType: %q", resourceType)
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func assureLoggedIn(ctx context.Context, client Client) error {
|
||||
if client.CheckSession(ctx) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return client.Login(ctx)
|
||||
}
|
298
pkg/provider/passbolt/passbolt_test.go
Normal file
298
pkg/provider/passbolt/passbolt_test.go
Normal file
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
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 passbolt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
g "github.com/onsi/gomega"
|
||||
"github.com/passbolt/go-passbolt/api"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
)
|
||||
|
||||
type PassboltClientMock struct {
|
||||
}
|
||||
|
||||
func (p *PassboltClientMock) CheckSession(_ context.Context) bool {
|
||||
return true
|
||||
}
|
||||
func (p *PassboltClientMock) Login(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
func (p *PassboltClientMock) Logout(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
func (p *PassboltClientMock) GetResource(_ context.Context, resourceID string) (*api.Resource, error) {
|
||||
resmap := map[string]api.Resource{
|
||||
"some-key1": {ID: "some-key1", Name: "some-name1", URI: "some-uri1"},
|
||||
"some-key2": {ID: "some-key2", Name: "some-name2", URI: "some-uri2"},
|
||||
}
|
||||
|
||||
if res, ok := resmap[resourceID]; ok {
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("ID not found")
|
||||
}
|
||||
|
||||
func (p *PassboltClientMock) GetResources(_ context.Context, _ *api.GetResourcesOptions) ([]api.Resource, error) {
|
||||
res := []api.Resource{
|
||||
{ID: "some-key1", Name: "some-name1", URI: "some-uri1"},
|
||||
{ID: "some-key2", Name: "some-name2", URI: "some-uri2"},
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (p *PassboltClientMock) GetResourceType(_ context.Context, _ string) (*api.ResourceType, error) {
|
||||
res := &api.ResourceType{Slug: "password-and-description"}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (p *PassboltClientMock) DecryptMessage(message string) (string, error) {
|
||||
return message, nil
|
||||
}
|
||||
|
||||
func (p *PassboltClientMock) GetSecret(_ context.Context, resourceID string) (*api.Secret, error) {
|
||||
resmap := map[string]api.Secret{
|
||||
"some-key1": {Data: `{"password": "some-password1", "description": "some-description1"}`},
|
||||
"some-key2": {Data: `{"password": "some-password2", "description": "some-description2"}`},
|
||||
}
|
||||
|
||||
if res, ok := resmap[resourceID]; ok {
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("ID not found")
|
||||
}
|
||||
|
||||
var clientMock = &PassboltClientMock{}
|
||||
|
||||
func TestValidateStore(t *testing.T) {
|
||||
p := &ProviderPassbolt{client: clientMock}
|
||||
|
||||
g.RegisterTestingT(t)
|
||||
store := &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
Passbolt: &esv1beta1.PassboltProvider{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// missing auth
|
||||
_, err := p.ValidateStore(store)
|
||||
g.Expect(err).To(g.BeEquivalentTo(fmt.Errorf(errPassboltStoreMissingAuth)))
|
||||
|
||||
// missing password
|
||||
store.Spec.Provider.Passbolt.Auth = &esv1beta1.PassboltAuth{
|
||||
PrivateKeySecretRef: &esmeta.SecretKeySelector{Key: "some-secret", Name: "privatekey"},
|
||||
}
|
||||
_, err = p.ValidateStore(store)
|
||||
g.Expect(err).To(g.BeEquivalentTo(fmt.Errorf(errPassboltStoreMissingAuthPassword)))
|
||||
|
||||
// missing privateKey
|
||||
store.Spec.Provider.Passbolt.Auth = &esv1beta1.PassboltAuth{
|
||||
PasswordSecretRef: &esmeta.SecretKeySelector{Key: "some-secret", Name: "password"},
|
||||
}
|
||||
_, err = p.ValidateStore(store)
|
||||
g.Expect(err).To(g.BeEquivalentTo(fmt.Errorf(errPassboltStoreMissingAuthPrivateKey)))
|
||||
|
||||
store.Spec.Provider.Passbolt.Auth = &esv1beta1.PassboltAuth{
|
||||
|
||||
PasswordSecretRef: &esmeta.SecretKeySelector{Key: "some-secret", Name: "password"},
|
||||
PrivateKeySecretRef: &esmeta.SecretKeySelector{Key: "some-secret", Name: "privatekey"},
|
||||
}
|
||||
|
||||
// missing host
|
||||
_, err = p.ValidateStore(store)
|
||||
g.Expect(err).To(g.BeEquivalentTo(fmt.Errorf(errPassboltStoreMissingHost)))
|
||||
|
||||
// not https
|
||||
store.Spec.Provider.Passbolt.Host = "http://passbolt.test"
|
||||
_, err = p.ValidateStore(store)
|
||||
g.Expect(err).To(g.BeEquivalentTo(fmt.Errorf(errPassboltStoreHostSchemeNotHTTPS)))
|
||||
|
||||
// spec ok
|
||||
store.Spec.Provider.Passbolt.Host = "https://passbolt.test"
|
||||
_, err = p.ValidateStore(store)
|
||||
g.Expect(err).To(g.BeNil())
|
||||
}
|
||||
|
||||
func TestClose(t *testing.T) {
|
||||
p := &ProviderPassbolt{client: clientMock}
|
||||
g.RegisterTestingT(t)
|
||||
err := p.Close(context.TODO())
|
||||
g.Expect(err).To(g.BeNil())
|
||||
}
|
||||
|
||||
func TestGetAllSecrets(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
ref esv1beta1.ExternalSecretFind
|
||||
expected map[string][]byte
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
desc: "no matches",
|
||||
ref: esv1beta1.ExternalSecretFind{
|
||||
Name: &esv1beta1.FindName{
|
||||
RegExp: "nonexistant",
|
||||
},
|
||||
},
|
||||
expected: map[string][]byte{},
|
||||
},
|
||||
{
|
||||
desc: "matches",
|
||||
ref: esv1beta1.ExternalSecretFind{
|
||||
Name: &esv1beta1.FindName{
|
||||
RegExp: "some-name.*",
|
||||
},
|
||||
},
|
||||
expected: map[string][]byte{
|
||||
"some-key1": []byte(`{"name":"some-name1","username":"","password":"some-password1","uri":"some-uri1","description":"some-description1"}`),
|
||||
"some-key2": []byte(`{"name":"some-name2","username":"","password":"some-password2","uri":"some-uri2","description":"some-description2"}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "missing find.name",
|
||||
ref: esv1beta1.ExternalSecretFind{},
|
||||
expectedErr: errPassboltExternalSecretMissingFindNameRegExp,
|
||||
},
|
||||
{
|
||||
desc: "empty find.name.regexp",
|
||||
ref: esv1beta1.ExternalSecretFind{
|
||||
Name: &esv1beta1.FindName{
|
||||
RegExp: "",
|
||||
},
|
||||
},
|
||||
expectedErr: errPassboltExternalSecretMissingFindNameRegExp,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
p := ProviderPassbolt{client: clientMock}
|
||||
|
||||
got, err := p.GetAllSecrets(ctx, tc.ref)
|
||||
if err != nil {
|
||||
if tc.expectedErr == "" {
|
||||
t.Fatalf("failed to call GetAllSecrets: %v", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), tc.expectedErr) {
|
||||
t.Fatalf("%q expected to contain substring %q", err.Error(), tc.expectedErr)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if tc.expectedErr != "" {
|
||||
t.Fatal("expected to receive an error but got nil")
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.expected, got); diff != "" {
|
||||
t.Fatalf("(-got, +want)\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSecret(t *testing.T) {
|
||||
g.RegisterTestingT(t)
|
||||
tbl := []struct {
|
||||
name string
|
||||
request esv1beta1.ExternalSecretDataRemoteRef
|
||||
expValue string
|
||||
expErr string
|
||||
}{
|
||||
{
|
||||
name: "return err when not found",
|
||||
request: esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: "nonexistent",
|
||||
},
|
||||
expErr: "ID not found",
|
||||
},
|
||||
{
|
||||
name: "get property from secret",
|
||||
request: esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: "some-key1",
|
||||
Property: "password",
|
||||
},
|
||||
expValue: "some-password1",
|
||||
},
|
||||
{
|
||||
name: "get full secret",
|
||||
request: esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: "some-key1",
|
||||
},
|
||||
expValue: `{"name":"some-name1","username":"","password":"some-password1","uri":"some-uri1","description":"some-description1"}`,
|
||||
},
|
||||
{
|
||||
name: "return err when using invalid property",
|
||||
request: esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: "some-key1",
|
||||
Property: "invalid",
|
||||
},
|
||||
expErr: errPassboltSecretPropertyInvalid,
|
||||
},
|
||||
}
|
||||
|
||||
for _, row := range tbl {
|
||||
t.Run(row.name, func(_ *testing.T) {
|
||||
p := &ProviderPassbolt{client: clientMock}
|
||||
|
||||
out, err := p.GetSecret(context.Background(), row.request)
|
||||
if row.expErr != "" {
|
||||
g.Expect(err).To(g.MatchError(row.expErr))
|
||||
} else {
|
||||
g.Expect(err).ToNot(g.HaveOccurred())
|
||||
}
|
||||
g.Expect(string(out)).To(g.Equal(row.expValue))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretExists(t *testing.T) {
|
||||
p := &ProviderPassbolt{client: clientMock}
|
||||
g.RegisterTestingT(t)
|
||||
_, err := p.SecretExists(context.TODO(), nil)
|
||||
g.Expect(err).To(g.BeEquivalentTo(fmt.Errorf(errNotImplemented)))
|
||||
}
|
||||
func TestPushSecret(t *testing.T) {
|
||||
p := &ProviderPassbolt{client: clientMock}
|
||||
g.RegisterTestingT(t)
|
||||
err := p.PushSecret(context.TODO(), nil, nil)
|
||||
g.Expect(err).To(g.BeEquivalentTo(fmt.Errorf(errNotImplemented)))
|
||||
}
|
||||
func TestDeleteSecret(t *testing.T) {
|
||||
p := &ProviderPassbolt{client: clientMock}
|
||||
g.RegisterTestingT(t)
|
||||
err := p.DeleteSecret(context.TODO(), nil)
|
||||
g.Expect(err).To(g.BeEquivalentTo(fmt.Errorf(errNotImplemented)))
|
||||
}
|
||||
func TestGetSecretMap(t *testing.T) {
|
||||
p := &ProviderPassbolt{client: clientMock}
|
||||
g.RegisterTestingT(t)
|
||||
_, err := p.GetSecretMap(context.TODO(), esv1beta1.ExternalSecretDataRemoteRef{})
|
||||
g.Expect(err).To(g.BeEquivalentTo(fmt.Errorf(errNotImplemented)))
|
||||
}
|
|
@ -36,6 +36,7 @@ import (
|
|||
_ "github.com/external-secrets/external-secrets/pkg/provider/onboardbase"
|
||||
_ "github.com/external-secrets/external-secrets/pkg/provider/onepassword"
|
||||
_ "github.com/external-secrets/external-secrets/pkg/provider/oracle"
|
||||
_ "github.com/external-secrets/external-secrets/pkg/provider/passbolt"
|
||||
_ "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/scaleway"
|
||||
|
|
Loading…
Reference in a new issue