mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-15 17:51:01 +00:00
Merge pull request #1075 from lfraga/feat/provider-senhasegura-dsm
Add senhasegura DevOps Secrets Management (DSM) provider
This commit is contained in:
commit
9838d44bae
21 changed files with 1362 additions and 1 deletions
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
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"
|
||||
|
||||
/*
|
||||
SenhaseguraAuth tells the controller how to do auth in senhasegura
|
||||
*/
|
||||
type SenhaseguraAuth struct {
|
||||
ClientID string `json:"clientId"`
|
||||
ClientSecret esmeta.SecretKeySelector `json:"clientSecretSecretRef"`
|
||||
}
|
||||
|
||||
/*
|
||||
SenhaseguraModuleType enum defines senhasegura target module to fetch secrets
|
||||
+kubebuilder:validation:Enum=DSM
|
||||
*/
|
||||
type SenhaseguraModuleType string
|
||||
|
||||
const (
|
||||
/*
|
||||
SenhaseguraModuleDSM is the senhasegura DevOps Secrets Management module
|
||||
see: https://senhasegura.com/devops
|
||||
*/
|
||||
SenhaseguraModuleDSM SenhaseguraModuleType = "DSM"
|
||||
)
|
||||
|
||||
/*
|
||||
SenhaseguraProvider setup a store to sync secrets with senhasegura
|
||||
*/
|
||||
type SenhaseguraProvider struct {
|
||||
/* URL of senhasegura */
|
||||
URL string `json:"url"`
|
||||
|
||||
/* Module defines which senhasegura module should be used to get secrets */
|
||||
Module SenhaseguraModuleType `json:"module"`
|
||||
|
||||
/* Auth defines parameters to authenticate in senhasegura */
|
||||
Auth SenhaseguraAuth `json:"auth"`
|
||||
|
||||
// IgnoreSslCertificate defines if SSL certificate must be ignored
|
||||
// +kubebuilder:default=false
|
||||
IgnoreSslCertificate bool `json:"ignoreSslCertificate,omitempty"`
|
||||
}
|
|
@ -93,6 +93,10 @@ type SecretStoreProvider struct {
|
|||
// Fake configures a store with static key/value pairs
|
||||
// +optional
|
||||
Fake *FakeProvider `json:"fake,omitempty"`
|
||||
|
||||
// Senhasegura configures this store to sync secrets using senhasegura provider
|
||||
// +optional
|
||||
Senhasegura *SenhaseguraProvider `json:"senhasegura,omitempty"`
|
||||
}
|
||||
|
||||
type SecretStoreRetrySettings struct {
|
||||
|
|
|
@ -1351,6 +1351,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
|
|||
*out = new(FakeProvider)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Senhasegura != nil {
|
||||
in, out := &in.Senhasegura, &out.Senhasegura
|
||||
*out = new(SenhaseguraProvider)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreProvider.
|
||||
|
@ -1466,6 +1471,38 @@ func (in *SecretStoreStatusCondition) DeepCopy() *SecretStoreStatusCondition {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SenhaseguraAuth) DeepCopyInto(out *SenhaseguraAuth) {
|
||||
*out = *in
|
||||
in.ClientSecret.DeepCopyInto(&out.ClientSecret)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SenhaseguraAuth.
|
||||
func (in *SenhaseguraAuth) DeepCopy() *SenhaseguraAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SenhaseguraAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SenhaseguraProvider) DeepCopyInto(out *SenhaseguraProvider) {
|
||||
*out = *in
|
||||
in.Auth.DeepCopyInto(&out.Auth)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SenhaseguraProvider.
|
||||
func (in *SenhaseguraProvider) DeepCopy() *SenhaseguraProvider {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SenhaseguraProvider)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ServiceAccountAuth) DeepCopyInto(out *ServiceAccountAuth) {
|
||||
*out = *in
|
||||
|
|
|
@ -2164,6 +2164,56 @@ spec:
|
|||
- region
|
||||
- vault
|
||||
type: object
|
||||
senhasegura:
|
||||
description: Senhasegura configures this store to sync secrets
|
||||
using senhasegura provider
|
||||
properties:
|
||||
auth:
|
||||
description: Auth defines parameters to authenticate in senhasegura
|
||||
properties:
|
||||
clientId:
|
||||
type: string
|
||||
clientSecretSecretRef:
|
||||
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:
|
||||
- clientId
|
||||
- clientSecretSecretRef
|
||||
type: object
|
||||
ignoreSslCertificate:
|
||||
default: false
|
||||
description: IgnoreSslCertificate defines if SSL certificate
|
||||
must be ignored
|
||||
type: boolean
|
||||
module:
|
||||
description: Module defines which senhasegura module should
|
||||
be used to get secrets
|
||||
type: string
|
||||
url:
|
||||
description: URL of senhasegura
|
||||
type: string
|
||||
required:
|
||||
- auth
|
||||
- module
|
||||
- url
|
||||
type: object
|
||||
vault:
|
||||
description: Vault configures this store to sync secrets using
|
||||
Hashi provider
|
||||
|
|
|
@ -2167,6 +2167,56 @@ spec:
|
|||
- region
|
||||
- vault
|
||||
type: object
|
||||
senhasegura:
|
||||
description: Senhasegura configures this store to sync secrets
|
||||
using senhasegura provider
|
||||
properties:
|
||||
auth:
|
||||
description: Auth defines parameters to authenticate in senhasegura
|
||||
properties:
|
||||
clientId:
|
||||
type: string
|
||||
clientSecretSecretRef:
|
||||
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:
|
||||
- clientId
|
||||
- clientSecretSecretRef
|
||||
type: object
|
||||
ignoreSslCertificate:
|
||||
default: false
|
||||
description: IgnoreSslCertificate defines if SSL certificate
|
||||
must be ignored
|
||||
type: boolean
|
||||
module:
|
||||
description: Module defines which senhasegura module should
|
||||
be used to get secrets
|
||||
type: string
|
||||
url:
|
||||
description: URL of senhasegura
|
||||
type: string
|
||||
required:
|
||||
- auth
|
||||
- module
|
||||
- url
|
||||
type: object
|
||||
vault:
|
||||
description: Vault configures this store to sync secrets using
|
||||
Hashi provider
|
||||
|
|
|
@ -1939,6 +1939,46 @@ spec:
|
|||
- region
|
||||
- vault
|
||||
type: object
|
||||
senhasegura:
|
||||
description: Senhasegura configures this store to sync secrets using senhasegura provider
|
||||
properties:
|
||||
auth:
|
||||
description: Auth defines parameters to authenticate in senhasegura
|
||||
properties:
|
||||
clientId:
|
||||
type: string
|
||||
clientSecretSecretRef:
|
||||
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:
|
||||
- clientId
|
||||
- clientSecretSecretRef
|
||||
type: object
|
||||
ignoreSslCertificate:
|
||||
default: false
|
||||
description: IgnoreSslCertificate defines if SSL certificate must be ignored
|
||||
type: boolean
|
||||
module:
|
||||
description: Module defines which senhasegura module should be used to get secrets
|
||||
type: string
|
||||
url:
|
||||
description: URL of senhasegura
|
||||
type: string
|
||||
required:
|
||||
- auth
|
||||
- module
|
||||
- url
|
||||
type: object
|
||||
vault:
|
||||
description: Vault configures this store to sync secrets using Hashi provider
|
||||
properties:
|
||||
|
@ -4493,6 +4533,46 @@ spec:
|
|||
- region
|
||||
- vault
|
||||
type: object
|
||||
senhasegura:
|
||||
description: Senhasegura configures this store to sync secrets using senhasegura provider
|
||||
properties:
|
||||
auth:
|
||||
description: Auth defines parameters to authenticate in senhasegura
|
||||
properties:
|
||||
clientId:
|
||||
type: string
|
||||
clientSecretSecretRef:
|
||||
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:
|
||||
- clientId
|
||||
- clientSecretSecretRef
|
||||
type: object
|
||||
ignoreSslCertificate:
|
||||
default: false
|
||||
description: IgnoreSslCertificate defines if SSL certificate must be ignored
|
||||
type: boolean
|
||||
module:
|
||||
description: Module defines which senhasegura module should be used to get secrets
|
||||
type: string
|
||||
url:
|
||||
description: URL of senhasegura
|
||||
type: string
|
||||
required:
|
||||
- auth
|
||||
- module
|
||||
- url
|
||||
type: object
|
||||
vault:
|
||||
description: Vault configures this store to sync secrets using Hashi provider
|
||||
properties:
|
||||
|
|
144
docs/provider-senhasegura-dsm.md
Normal file
144
docs/provider-senhasegura-dsm.md
Normal file
|
@ -0,0 +1,144 @@
|
|||
## senhasegura DevOps Secrets Management (DSM)
|
||||
|
||||
External Secrets Operator integrates with [senhasegura](https://senhasegura.com/) [DevOps Secrets Management (DSM)](https://senhasegura.com/devops) module to sync application secrets to secrets held on the Kubernetes cluster.
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
Authentication in senhasegura uses DevOps Secrets Management (DSM) application authorization schema
|
||||
|
||||
You need to create an Kubernetes Secret with desired auth parameters, for example:
|
||||
|
||||
Instructions to setup authorizations and secrets in senhasegura DSM can be found at [senhasegura docs for DSM](https://helpcenter.senhasegura.io/docs/3.22/dsm) and [senhasegura YouTube channel](https://www.youtube.com/channel/UCpDms35l3tcrfb8kZSpeNYw/search?query=DSM%2C%20en-US)
|
||||
|
||||
```yaml
|
||||
{% include 'senhasegura-dsm-secret.yaml' %}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
To sync secrets between senhasegura and Kubernetes with External Secrets, we need to define an SecretStore or ClusterSecretStore resource with senhasegura provider, setting authentication in DSM module with Secret defined before
|
||||
|
||||
### SecretStore
|
||||
|
||||
``` yaml
|
||||
{% include 'senhasegura-dsm-secretstore.yaml' %}
|
||||
```
|
||||
|
||||
### ClusterSecretStore
|
||||
|
||||
``` yaml
|
||||
{% include 'senhasegura-dsm-clustersecretstore.yaml' %}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Syncing secrets
|
||||
|
||||
In examples below, consider that three secrets (api-settings, db-settings and hsm-settings) are defined in senhasegura DSM
|
||||
|
||||
---
|
||||
|
||||
**Secret Identifier: ** api-settings
|
||||
|
||||
**Secret data:**
|
||||
|
||||
```bash
|
||||
URL=https://example.com/api/example
|
||||
TOKEN=example-token-value
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Secret Identifier: ** db-settings
|
||||
|
||||
**Secret data:**
|
||||
|
||||
```bash
|
||||
DB_HOST='db.example'
|
||||
DB_PORT='5432'
|
||||
DB_USERNAME='example'
|
||||
DB_PASSWORD='example'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Secret Identifier: ** hsm-settings
|
||||
|
||||
**Secret data:**
|
||||
|
||||
```bash
|
||||
HSM_ADDRESS='hsm.example'
|
||||
HSM_PORT='9223'
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
### Sync DSM secrets using Secret Identifiers
|
||||
|
||||
You can fetch all key/value pairs for a given secret identifier If you leave the remoteRef.property empty. This returns the json-encoded secret value for that path.
|
||||
|
||||
If you only need a specific key, you can select it using remoteRef.property as the key name.
|
||||
|
||||
In this method, you can overwrites data name in Kubernetes Secret object (e.g API_SETTINGS and API_SETTINGS_TOKEN)
|
||||
|
||||
``` yaml
|
||||
{% include 'senhasegura-dsm-external-secret-single.yaml' %}
|
||||
```
|
||||
|
||||
Kubernetes Secret will be create with follow `.data.X`
|
||||
|
||||
```bash
|
||||
API_SETTINGS='[{"TOKEN":"example-token-value","URL":"https://example.com/api/example"}]'
|
||||
API_SETTINGS_TOKEN='example-token-value'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Sync DSM secrets using Secret Identifiers with automatically name assignments
|
||||
|
||||
If your app requires multiples secrets, it is not required to create multiple ExternalSecret resources, you can aggregate secrets using a single ExternalSecret resource
|
||||
|
||||
In this method, every secret data in senhasegura creates an Kubernetes Secret `.data.X` field
|
||||
|
||||
``` yaml
|
||||
{% include 'senhasegura-dsm-external-secret-multiple.yaml' %}
|
||||
```
|
||||
|
||||
Kubernetes Secret will be create with follow `.data.X`
|
||||
|
||||
```bash
|
||||
URL='https://example.com/api/example'
|
||||
TOKEN='example-token-value'
|
||||
DB_HOST='db.example'
|
||||
DB_PORT='5432'
|
||||
DB_USERNAME='example'
|
||||
DB_PASSWORD='example'
|
||||
```
|
||||
|
||||
<!-- https://github.com/external-secrets/external-secrets/pull/830#discussion_r858657107 -->
|
||||
|
||||
<!-- ### Sync all secrets from DSM authorization
|
||||
|
||||
You can sync all secrets that your authorization in DSM has using find, in a future release you will be able to filter secrets by name, path or tags
|
||||
|
||||
``` yaml
|
||||
{% include 'senhasegura-dsm-external-secret-all.yaml' %}
|
||||
```
|
||||
|
||||
Kubernetes Secret will be create with follow `.data.X`
|
||||
|
||||
```bash
|
||||
URL='https://example.com/api/example'
|
||||
TOKEN='example-token-value'
|
||||
DB_HOST='db.example'
|
||||
DB_PORT='5432'
|
||||
DB_USERNAME='example'
|
||||
DB_PASSWORD='example'
|
||||
HSM_ADDRESS='hsm.example'
|
||||
HSM_PORT='9223'
|
||||
``` -->
|
17
docs/snippets/senhasegura-dsm-clustersecretstore.yaml
Normal file
17
docs/snippets/senhasegura-dsm-clustersecretstore.yaml
Normal file
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ClusterSecretStore
|
||||
metadata:
|
||||
name: senhasegura
|
||||
spec:
|
||||
provider:
|
||||
senhasegura:
|
||||
url: "https://senhasegura.changeme.com"
|
||||
module: DSM # Select senhasegura DSM module to sync secrets
|
||||
auth:
|
||||
clientId: "CHANGEME"
|
||||
clientSecretSecretRef:
|
||||
name: senhasegura-dsm-auth
|
||||
key: CLIENT_SECRET
|
||||
namespace: senhasegura # Namespace of Secret "senhasegura-dsm-auth"
|
||||
ignoreSslCertificate: false # Optional
|
14
docs/snippets/senhasegura-dsm-external-secret-all.yaml
Normal file
14
docs/snippets/senhasegura-dsm-external-secret-all.yaml
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: example-secret
|
||||
spec:
|
||||
refreshInterval: "30s"
|
||||
secretStoreRef:
|
||||
name: senhasegura
|
||||
kind: SecretStore
|
||||
target:
|
||||
name: example-secret
|
||||
dataFrom:
|
||||
- find: {}
|
18
docs/snippets/senhasegura-dsm-external-secret-multiple.yaml
Normal file
18
docs/snippets/senhasegura-dsm-external-secret-multiple.yaml
Normal file
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: example-secret
|
||||
spec:
|
||||
refreshInterval: "30s"
|
||||
secretStoreRef:
|
||||
name: senhasegura
|
||||
kind: SecretStore
|
||||
target:
|
||||
name: example-secret
|
||||
dataFrom:
|
||||
# Define Kubernetes Secret key with any k/v pair in senhasegura Secret with identifier "api-settings" or "db-settings"
|
||||
- extract:
|
||||
key: api-settings
|
||||
- extract:
|
||||
key: db-settings
|
22
docs/snippets/senhasegura-dsm-external-secret-single.yaml
Normal file
22
docs/snippets/senhasegura-dsm-external-secret-single.yaml
Normal file
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: example-secret
|
||||
spec:
|
||||
refreshInterval: "30s"
|
||||
secretStoreRef:
|
||||
name: senhasegura
|
||||
kind: SecretStore
|
||||
target:
|
||||
name: example-secret
|
||||
data:
|
||||
# Define API_SETTINGS Kubernetes Secret key, with json-encoded values from senhasegura secret with identifier "api-settings"
|
||||
- secretKey: API_SETTINGS
|
||||
remoteRef:
|
||||
key: api-settings # Secret Identifier in senhasegura
|
||||
# Define API_SETTINGS_TOKEN Kubernetes Secret key, with single secret key (TOKEN) from senhasegura as string
|
||||
- secretKey: API_SETTINGS_TOKEN
|
||||
remoteRef:
|
||||
key: api-settings # Secret Identifier in senhasegura
|
||||
property: TOKEN # Optional, Key name within secret
|
7
docs/snippets/senhasegura-dsm-secret.yaml
Normal file
7
docs/snippets/senhasegura-dsm-secret.yaml
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: senhasegura-dsm-auth
|
||||
stringData:
|
||||
CLIENT_SECRET: "CHANGEME"
|
16
docs/snippets/senhasegura-dsm-secretstore.yaml
Normal file
16
docs/snippets/senhasegura-dsm-secretstore.yaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: SecretStore
|
||||
metadata:
|
||||
name: senhasegura
|
||||
spec:
|
||||
provider:
|
||||
senhasegura:
|
||||
url: "https://senhasegura.changeme.com"
|
||||
module: DSM # Select senhasegura DSM module to sync secrets
|
||||
auth:
|
||||
clientId: "CHANGEME"
|
||||
clientSecretSecretRef:
|
||||
name: senhasegura-dsm-auth
|
||||
key: CLIENT_SECRET
|
||||
ignoreSslCertificate: false # Optional
|
148
docs/spec.md
148
docs/spec.md
|
@ -3258,7 +3258,7 @@ GitlabProvider
|
|||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>GItlab configures this store to sync secrets using Gitlab Variables provider</p>
|
||||
<p>Gitlab configures this store to sync secrets using Gitlab Variables provider</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -3317,6 +3317,20 @@ FakeProvider
|
|||
<p>Fake configures a store with static key/value pairs</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>senhasegura</code></br>
|
||||
<em>
|
||||
<a href="#external-secrets.io/v1beta1.SenhaseguraProvider">
|
||||
SenhaseguraProvider
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Senhasegura configures this store to sync secrets using senhasegura provider</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3 id="external-secrets.io/v1beta1.SecretStoreRef">SecretStoreRef
|
||||
|
@ -3587,6 +3601,138 @@ Kubernetes meta/v1.Time
|
|||
<p>
|
||||
<p>SecretsClient provides access to secrets.</p>
|
||||
</p>
|
||||
<h3 id="external-secrets.io/v1beta1.SenhaseguraAuth">SenhaseguraAuth
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#external-secrets.io/v1beta1.SenhaseguraProvider">SenhaseguraProvider</a>)
|
||||
</p>
|
||||
<p>
|
||||
<pre><code>SenhaseguraAuth tells the controller how to do auth in senhasegura
|
||||
</code></pre>
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>clientId</code></br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>clientSecretSecretRef</code></br>
|
||||
<em>
|
||||
github.com/external-secrets/external-secrets/apis/meta/v1.SecretKeySelector
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3 id="external-secrets.io/v1beta1.SenhaseguraModuleType">SenhaseguraModuleType
|
||||
(<code>string</code> alias)</p></h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#external-secrets.io/v1beta1.SenhaseguraProvider">SenhaseguraProvider</a>)
|
||||
</p>
|
||||
<p>
|
||||
<pre><code>SenhaseguraModuleType enum defines senhasegura target module to fetch secrets
|
||||
</code></pre>
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Value</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr><td><p>"DSM"</p></td>
|
||||
<td><pre><code> SenhaseguraModuleDSM is the senhasegura DevOps Secrets Management module
|
||||
see: https://senhasegura.com/devops
|
||||
</code></pre>
|
||||
</td>
|
||||
</tr></tbody>
|
||||
</table>
|
||||
<h3 id="external-secrets.io/v1beta1.SenhaseguraProvider">SenhaseguraProvider
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#external-secrets.io/v1beta1.SecretStoreProvider">SecretStoreProvider</a>)
|
||||
</p>
|
||||
<p>
|
||||
<pre><code>SenhaseguraProvider setup a store to sync secrets with senhasegura
|
||||
</code></pre>
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>url</code></br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>URL of senhasegura</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>module</code></br>
|
||||
<em>
|
||||
<a href="#external-secrets.io/v1beta1.SenhaseguraModuleType">
|
||||
SenhaseguraModuleType
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Module defines which senhasegura module should be used to get secrets</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>auth</code></br>
|
||||
<em>
|
||||
<a href="#external-secrets.io/v1beta1.SenhaseguraAuth">
|
||||
SenhaseguraAuth
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Auth defines parameters to authenticate in senhasegura</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>ignoreSslCertificate</code></br>
|
||||
<em>
|
||||
bool
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>IgnoreSslCertificate defines if SSL certificate must be ignored</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3 id="external-secrets.io/v1beta1.ServiceAccountAuth">ServiceAccountAuth
|
||||
</h3>
|
||||
<p>
|
||||
|
|
|
@ -68,6 +68,8 @@ nav:
|
|||
- Webhook: provider-webhook.md
|
||||
- Fake: provider-fake.md
|
||||
- Kubernetes: provider-kubernetes.md
|
||||
- senhasegura:
|
||||
- DevOps Secrets Management (DSM): provider-senhasegura-dsm.md
|
||||
- Examples:
|
||||
- FluxCD: examples-gitops-using-fluxcd.md
|
||||
- Anchore Engine: examples-anchore-engine-credentials.md
|
||||
|
|
|
@ -27,6 +27,7 @@ import (
|
|||
_ "github.com/external-secrets/external-secrets/pkg/provider/ibm"
|
||||
_ "github.com/external-secrets/external-secrets/pkg/provider/kubernetes"
|
||||
_ "github.com/external-secrets/external-secrets/pkg/provider/oracle"
|
||||
_ "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/webhook"
|
||||
_ "github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox"
|
||||
|
|
146
pkg/provider/senhasegura/auth/iso.go
Normal file
146
pkg/provider/senhasegura/auth/iso.go
Normal file
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
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 auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
)
|
||||
|
||||
type ISOInterface interface {
|
||||
IsoSessionFromSecretRef(ctx context.Context, provider *esv1beta1.SenhaseguraProvider, store esv1beta1.GenericStore, kube client.Client, namespace string) (*SenhaseguraIsoSession, error)
|
||||
GetIsoToken(clientID, clientSecret, systemURL string, ignoreSslCertificate bool) (token string, err error)
|
||||
}
|
||||
|
||||
/*
|
||||
SenhaseguraIsoSession contains information about senhasegura ISO API for any request
|
||||
*/
|
||||
type SenhaseguraIsoSession struct {
|
||||
URL string
|
||||
Token string
|
||||
IgnoreSslCertificate bool
|
||||
isoClient ISOInterface
|
||||
}
|
||||
|
||||
/*
|
||||
isoGetTokenResponse contains response from OAuth2 authentication endpoint in senhasegura API
|
||||
*/
|
||||
type isoGetTokenResponse struct {
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
|
||||
var (
|
||||
errCannotCreateRequest = errors.New("cannot create request to senhasegura resource /iso/oauth2/token")
|
||||
errCannotDoRequest = errors.New("cannot do request in senhasegura, SSL certificate is valid ?")
|
||||
errInvalidResponseBody = errors.New("invalid HTTP response body received from senhasegura")
|
||||
errInvalidHTTPCode = errors.New("received invalid HTTP code from senhasegura")
|
||||
)
|
||||
|
||||
/*
|
||||
Authenticate check required authentication method based on provider spec and initialize ISO OAuth2 session
|
||||
*/
|
||||
func Authenticate(ctx context.Context, store esv1beta1.GenericStore, provider *esv1beta1.SenhaseguraProvider, kube client.Client, namespace string) (isoSession *SenhaseguraIsoSession, err error) {
|
||||
isoSession, err = isoSession.IsoSessionFromSecretRef(ctx, provider, store, kube, namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return isoSession, nil
|
||||
}
|
||||
|
||||
/*
|
||||
IsoSessionFromSecretRef initialize an ISO OAuth2 flow with .spec.provider.senhasegura.auth.isoSecretRef parameters
|
||||
*/
|
||||
func (s *SenhaseguraIsoSession) IsoSessionFromSecretRef(ctx context.Context, provider *esv1beta1.SenhaseguraProvider, store esv1beta1.GenericStore, kube client.Client, namespace string) (*SenhaseguraIsoSession, error) {
|
||||
clientSecret, err := getKubernetesSecret(ctx, provider.Auth.ClientSecret, store, kube, namespace)
|
||||
if err != nil {
|
||||
return &SenhaseguraIsoSession{}, err
|
||||
}
|
||||
|
||||
isoToken, err := s.GetIsoToken(provider.Auth.ClientID, clientSecret, provider.URL, provider.IgnoreSslCertificate)
|
||||
if err != nil {
|
||||
return &SenhaseguraIsoSession{}, err
|
||||
}
|
||||
|
||||
return &SenhaseguraIsoSession{
|
||||
URL: provider.URL,
|
||||
Token: isoToken,
|
||||
IgnoreSslCertificate: provider.IgnoreSslCertificate,
|
||||
isoClient: &SenhaseguraIsoSession{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
/*
|
||||
GetIsoToken calls senhasegura OAuth2 endpoint to get a token
|
||||
*/
|
||||
func (s *SenhaseguraIsoSession) GetIsoToken(clientID, clientSecret, systemURL string, ignoreSslCertificate bool) (token string, err error) {
|
||||
data := url.Values{}
|
||||
data.Set("grant_type", "client_credentials")
|
||||
data.Set("client_id", clientID)
|
||||
data.Set("client_secret", clientSecret)
|
||||
|
||||
u, _ := url.ParseRequestURI(systemURL)
|
||||
u.Path = "/iso/oauth2/token"
|
||||
|
||||
tr := &http.Transport{
|
||||
// nolint
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSslCertificate},
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: tr}
|
||||
|
||||
r, err := http.NewRequest("POST", u.String(), strings.NewReader(data.Encode()))
|
||||
if err != nil {
|
||||
return "", errCannotCreateRequest
|
||||
}
|
||||
|
||||
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
r.Header.Set("Content-Length", strconv.Itoa(len(data.Encode())))
|
||||
|
||||
resp, err := client.Do(r)
|
||||
if err != nil {
|
||||
return "", errCannotDoRequest
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return "", errInvalidHTTPCode
|
||||
}
|
||||
|
||||
respData, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", errInvalidResponseBody
|
||||
}
|
||||
|
||||
var respObj isoGetTokenResponse
|
||||
err = json.Unmarshal(respData, &respObj)
|
||||
if err != nil {
|
||||
return "", errInvalidResponseBody
|
||||
}
|
||||
|
||||
return respObj.AccessToken, nil
|
||||
}
|
57
pkg/provider/senhasegura/auth/kubernetes_secret.go
Normal file
57
pkg/provider/senhasegura/auth/kubernetes_secret.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
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 auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
errRequiredNamespaceNotFound = "invalid ClusterSecretStore: missing namespace in %s"
|
||||
errCannotFetchKubernetesSecret = "could not fetch Kubernetes secret %s"
|
||||
)
|
||||
|
||||
/*
|
||||
getKubernetesSecret get Kubernetes Secret based on object parameter in namespace where ESO is installed or another, if ClusterSecretStore is used
|
||||
*/
|
||||
func getKubernetesSecret(ctx context.Context, object esmeta.SecretKeySelector, store esv1beta1.GenericStore, kube client.Client, namespace string) (string, error) {
|
||||
ke := client.ObjectKey{
|
||||
Name: object.Name,
|
||||
Namespace: namespace, // Default to ExternalSecret namespace
|
||||
}
|
||||
|
||||
// Only ClusterStore is allowed to set namespace (and then it's required)
|
||||
if store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind {
|
||||
if object.Namespace == nil {
|
||||
return "", fmt.Errorf(errRequiredNamespaceNotFound, object.Key)
|
||||
}
|
||||
ke.Namespace = *object.Namespace
|
||||
}
|
||||
|
||||
secret := v1.Secret{}
|
||||
err := kube.Get(ctx, ke, &secret)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(errCannotFetchKubernetesSecret, object.Name)
|
||||
}
|
||||
|
||||
return string(secret.Data[object.Key]), nil
|
||||
}
|
227
pkg/provider/senhasegura/dsm/dsm.go
Normal file
227
pkg/provider/senhasegura/dsm/dsm.go
Normal file
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
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 dsm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
senhaseguraAuth "github.com/external-secrets/external-secrets/pkg/provider/senhasegura/auth"
|
||||
)
|
||||
|
||||
type clientDSMInterface interface {
|
||||
FetchSecrets() (respObj IsoDappResponse, err error)
|
||||
}
|
||||
|
||||
// https://github.com/external-secrets/external-secrets/issues/644
|
||||
var _ esv1beta1.SecretsClient = &DSM{}
|
||||
|
||||
/*
|
||||
DSM service for SenhaseguraProvider
|
||||
*/
|
||||
type DSM struct {
|
||||
isoSession *senhaseguraAuth.SenhaseguraIsoSession
|
||||
dsmClient clientDSMInterface
|
||||
}
|
||||
|
||||
/*
|
||||
IsoDappResponse is a response object from senhasegura /iso/dapp/response (DevOps Secrets Management API endpoint)
|
||||
Contains information about API request and Secrets linked with authorization
|
||||
*/
|
||||
type IsoDappResponse struct {
|
||||
Response struct {
|
||||
Status int `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Error bool `json:"error"`
|
||||
ErrorCode int `json:"error_code"`
|
||||
} `json:"response"`
|
||||
Application struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Tags []string `json:"tags"`
|
||||
System string `json:"system"`
|
||||
Environment string `json:"Environment"`
|
||||
Secrets []struct {
|
||||
SecretID string `json:"secret_id"`
|
||||
SecretName string `json:"secret_name"`
|
||||
Identity string `json:"identity"`
|
||||
Version string `json:"version"`
|
||||
ExpirationDate string `json:"expiration_date"`
|
||||
Engine string `json:"engine"`
|
||||
Data []map[string]string `json:"data"`
|
||||
} `json:"secrets"`
|
||||
} `json:"application"`
|
||||
}
|
||||
|
||||
var (
|
||||
errCannotCreateRequest = errors.New("cannot create request to senhasegura resource /iso/dapp/application")
|
||||
errCannotDoRequest = errors.New("cannot do request in senhasegura, SSL certificate is valid ?")
|
||||
errInvalidResponseBody = errors.New("invalid HTTP response body received from senhasegura")
|
||||
errInvalidHTTPCode = errors.New("received invalid HTTP code from senhasegura")
|
||||
errApplicationError = errors.New("received application error from senhasegura")
|
||||
)
|
||||
|
||||
/*
|
||||
New creates an senhasegura DSM client based on ISO session
|
||||
*/
|
||||
func New(isoSession *senhaseguraAuth.SenhaseguraIsoSession) (*DSM, error) {
|
||||
return &DSM{
|
||||
isoSession: isoSession,
|
||||
dsmClient: &DSM{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
/*
|
||||
GetSecret implements ESO interface and get a single secret from senhasegura provider with DSM service
|
||||
*/
|
||||
func (dsm *DSM) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (resp []byte, err error) {
|
||||
appSecrets, err := dsm.FetchSecrets()
|
||||
if err != nil {
|
||||
return []byte(""), err
|
||||
}
|
||||
|
||||
for _, v := range appSecrets.Application.Secrets {
|
||||
if ref.Key == v.Identity {
|
||||
// Return whole data content in json-encoded when ref.Property is empty
|
||||
if ref.Property == "" {
|
||||
jsonStr, err := json.Marshal(v.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return jsonStr, nil
|
||||
}
|
||||
|
||||
// Return raw data content when ref.Property is provided
|
||||
for _, v2 := range v.Data {
|
||||
for k, v3 := range v2 {
|
||||
if k == ref.Property {
|
||||
resp = []byte(v3)
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return []byte(""), esv1beta1.NoSecretErr
|
||||
}
|
||||
|
||||
/*
|
||||
GetSecretMap implements ESO interface and returns miltiple k/v pairs from senhasegura provider with DSM service
|
||||
*/
|
||||
func (dsm *DSM) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (secretData map[string][]byte, err error) {
|
||||
secretData = make(map[string][]byte)
|
||||
appSecrets, err := dsm.FetchSecrets()
|
||||
if err != nil {
|
||||
return secretData, err
|
||||
}
|
||||
|
||||
for _, v := range appSecrets.Application.Secrets {
|
||||
if v.Identity == ref.Key {
|
||||
for _, v2 := range v.Data {
|
||||
for k, v3 := range v2 {
|
||||
secretData[k] = []byte(v3)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return secretData, nil
|
||||
}
|
||||
|
||||
/*
|
||||
GetAllSecrets implements ESO interface and returns multiple secrets from senhasegura provider with DSM service
|
||||
|
||||
TODO: GetAllSecrets functionality is to get secrets from either regexp-matching against the names or via metadata label matching.
|
||||
https://github.com/external-secrets/external-secrets/pull/830#discussion_r858657107
|
||||
*/
|
||||
func (dsm *DSM) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (secretData map[string][]byte, err error) {
|
||||
return nil, fmt.Errorf("GetAllSecrets not implemented yet")
|
||||
}
|
||||
|
||||
/*
|
||||
fetchSecrets calls senhasegura DSM /iso/dapp/application API endpoint
|
||||
Return an IsoDappResponse with all related information from senhasegura provider with DSM service and error
|
||||
*/
|
||||
func (dsm *DSM) FetchSecrets() (respObj IsoDappResponse, err error) {
|
||||
u, _ := url.ParseRequestURI(dsm.isoSession.URL)
|
||||
u.Path = "/iso/dapp/application"
|
||||
|
||||
tr := &http.Transport{
|
||||
// nolint
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: dsm.isoSession.IgnoreSslCertificate},
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: tr}
|
||||
|
||||
r, err := http.NewRequest("GET", u.String(), nil)
|
||||
if err != nil {
|
||||
return respObj, errCannotCreateRequest
|
||||
}
|
||||
|
||||
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
r.Header.Set("Authorization", "Bearer "+dsm.isoSession.Token)
|
||||
|
||||
resp, err := client.Do(r)
|
||||
if err != nil {
|
||||
return respObj, errCannotDoRequest
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return respObj, errInvalidHTTPCode
|
||||
}
|
||||
|
||||
respData, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return respObj, errInvalidResponseBody
|
||||
}
|
||||
|
||||
err = json.Unmarshal(respData, &respObj)
|
||||
if err != nil {
|
||||
return respObj, errInvalidResponseBody
|
||||
}
|
||||
|
||||
if respObj.Response.Error {
|
||||
return respObj, errApplicationError
|
||||
}
|
||||
|
||||
return respObj, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Close implements ESO interface and do nothing in senhasegura
|
||||
*/
|
||||
func (dsm *DSM) Close(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate if has valid connection with senhasegura, credentials, authorization using fetchSecrets method
|
||||
// fetchSecrets method implement required check about request
|
||||
// https://github.com/external-secrets/external-secrets/pull/830#discussion_r833275463
|
||||
func (dsm *DSM) Validate() (esv1beta1.ValidationResult, error) {
|
||||
_, err := dsm.FetchSecrets()
|
||||
if err != nil {
|
||||
return esv1beta1.ValidationResultError, err
|
||||
}
|
||||
|
||||
return esv1beta1.ValidationResultReady, nil
|
||||
}
|
120
pkg/provider/senhasegura/provider.go
Normal file
120
pkg/provider/senhasegura/provider.go
Normal file
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
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 senhasegura
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
senhaseguraAuth "github.com/external-secrets/external-secrets/pkg/provider/senhasegura/auth"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/senhasegura/dsm"
|
||||
)
|
||||
|
||||
// https://github.com/external-secrets/external-secrets/issues/644
|
||||
var _ esv1beta1.Provider = &Provider{}
|
||||
|
||||
// Provider struct that satisfier ESO interface.
|
||||
type Provider struct{}
|
||||
|
||||
const (
|
||||
errUnknownProviderService = "unknown senhasegura Provider Service: %s"
|
||||
errNilStore = "nil store found"
|
||||
errMissingStoreSpec = "store is missing spec"
|
||||
errMissingProvider = "storeSpec is missing provider"
|
||||
errInvalidProvider = "invalid provider spec. Missing senhasegura field in store %s"
|
||||
errInvalidSenhaseguraURL = "invalid senhasegura URL"
|
||||
errInvalidSenhaseguraURLHTTPS = "invalid senhasegura URL, must be HTTPS for security reasons"
|
||||
errMissingClientID = "missing senhasegura authentication Client ID"
|
||||
)
|
||||
|
||||
/*
|
||||
Construct a new secrets client based on provided store
|
||||
*/
|
||||
func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
spec := store.GetSpec()
|
||||
provider := spec.Provider.Senhasegura
|
||||
|
||||
isoSession, err := senhaseguraAuth.Authenticate(ctx, store, provider, kube, namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if provider.Module == esv1beta1.SenhaseguraModuleDSM {
|
||||
return dsm.New(isoSession)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(errUnknownProviderService, provider.Module)
|
||||
}
|
||||
|
||||
// Validate store using Validating webhook during secret store creating
|
||||
// Checks here are usually the best experience for the user, as the SecretStore will not be created until it is a 'valid' one.
|
||||
// https://github.com/external-secrets/external-secrets/pull/830#discussion_r833278518
|
||||
func (p *Provider) ValidateStore(store esv1beta1.GenericStore) error {
|
||||
return validateStore(store)
|
||||
}
|
||||
|
||||
func validateStore(store esv1beta1.GenericStore) error {
|
||||
if store == nil {
|
||||
return fmt.Errorf(errNilStore)
|
||||
}
|
||||
|
||||
spec := store.GetSpec()
|
||||
if spec == nil {
|
||||
return fmt.Errorf(errMissingStoreSpec)
|
||||
}
|
||||
|
||||
if spec.Provider == nil {
|
||||
return fmt.Errorf(errMissingProvider)
|
||||
}
|
||||
|
||||
provider := spec.Provider.Senhasegura
|
||||
if provider == nil {
|
||||
return fmt.Errorf(errInvalidProvider, store.GetObjectMeta().String())
|
||||
}
|
||||
|
||||
url, err := url.Parse(provider.URL)
|
||||
if err != nil {
|
||||
return fmt.Errorf(errInvalidSenhaseguraURL)
|
||||
}
|
||||
|
||||
// senhasegura doesn't accept requests without SSL/TLS layer for security reasons
|
||||
// DSM doesn't provides gRPC schema, only HTTPS
|
||||
if url.Scheme != "https" {
|
||||
return fmt.Errorf(errInvalidSenhaseguraURLHTTPS)
|
||||
}
|
||||
|
||||
if url.Host == "" {
|
||||
return fmt.Errorf(errInvalidSenhaseguraURL)
|
||||
}
|
||||
|
||||
if provider.Auth.ClientID == "" {
|
||||
return fmt.Errorf(errMissingClientID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Register SenhaseguraProvider in ESO init
|
||||
*/
|
||||
func init() {
|
||||
esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
|
||||
Senhasegura: &esv1beta1.SenhaseguraProvider{},
|
||||
})
|
||||
}
|
146
pkg/provider/senhasegura/provider_test.go
Normal file
146
pkg/provider/senhasegura/provider_test.go
Normal file
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
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 senhasegura
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
)
|
||||
|
||||
func TestValidateStore(t *testing.T) {
|
||||
tbl := []struct {
|
||||
test string
|
||||
store esv1beta1.GenericStore
|
||||
expErr bool
|
||||
}{
|
||||
{
|
||||
test: "should not create provider due to nil store",
|
||||
store: nil,
|
||||
expErr: true,
|
||||
},
|
||||
{
|
||||
test: "should not create provider due to missing provider",
|
||||
expErr: true,
|
||||
store: &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: "should not create provider due to missing provider field",
|
||||
expErr: true,
|
||||
store: &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: "should not create provider due to missing provider module",
|
||||
expErr: true,
|
||||
store: &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
Senhasegura: &esv1beta1.SenhaseguraProvider{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: "should not create provider due to missing provider auth client ID",
|
||||
expErr: true,
|
||||
store: &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
Senhasegura: &esv1beta1.SenhaseguraProvider{
|
||||
Module: esv1beta1.SenhaseguraModuleDSM,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: "invalid module should return an error",
|
||||
expErr: true,
|
||||
store: &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
Senhasegura: &esv1beta1.SenhaseguraProvider{
|
||||
Module: "HIHIHIHHEHEHEHEHEHE",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: "should not create provider due senhasegura URL without https scheme",
|
||||
expErr: true,
|
||||
store: &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
Senhasegura: &esv1beta1.SenhaseguraProvider{
|
||||
Module: esv1beta1.SenhaseguraModuleDSM,
|
||||
URL: "http://dev.null",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: "should not create provider due senhasegura URL without valid name",
|
||||
expErr: true,
|
||||
store: &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
Senhasegura: &esv1beta1.SenhaseguraProvider{
|
||||
Module: esv1beta1.SenhaseguraModuleDSM,
|
||||
URL: "https://",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: "should create provider",
|
||||
expErr: false,
|
||||
store: &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
Senhasegura: &esv1beta1.SenhaseguraProvider{
|
||||
Module: esv1beta1.SenhaseguraModuleDSM,
|
||||
URL: "https://senhasegura.local",
|
||||
Auth: esv1beta1.SenhaseguraAuth{
|
||||
ClientID: "example",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for i := range tbl {
|
||||
row := tbl[i]
|
||||
t.Run(row.test, func(t *testing.T) {
|
||||
err := validateStore(row.store)
|
||||
if row.expErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue