mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
Implementation of Chef External Secrets Provider (#3127)
* Adding the details for chef provider secret store. Issue: https://github.com/external-secrets/external-secrets/issues/2905 This commit intends to add the chef provider structure to the existing list of external-secrets providers. It defines the structure of the SecretStore and ClusterSecretStore for chef Provider. The yaml resource will contain 3 important parts to identify and connect to chef server to reconcile secrets. They are: 1. serverurl: This is the URL to the chef server. 2. username: The username to connect to the chef server. 3. auth: The password to connect to the chef server. It is a reference to an already existing kubernetes secret containing the password. This commit also contains the auto generated CRDs using the `make generate` command. Signed-off-by: Subroto Roy <subrotoroy007@gmail.com> * Implementation for Chef ESO provided Signed-off-by: vardhanreddy13 <vvv.vardhanreddy@gmail.com> * - implemented Chef eso, added required methods - added unit test cases - added sample documentation Issue: https://github.com/external-secrets/external-secrets/issues/2905 Signed-off-by: Sourav Patnaik <souravpatnaik123@gmail.com> * Added Documentation for Authentication Signed-off-by: Subroto Roy <subrotoroy007@gmail.com> * added documentation for Chef eso Issue: https://github.com/external-secrets/external-secrets/issues/2905 Signed-off-by: Sourav Patnaik <souravpatnaik123@gmail.com> * Updated chef ESO documentation Signed-off-by: vardhanreddy13 <vvv.vardhanreddy@gmail.com> * updated ValidateStore method signature Issue: https://github.com/external-secrets/external-secrets/issues/2905 Signed-off-by: Sourav Patnaik <souravpatnaik123@gmail.com> * made changes in chef provider to satisfy 'make docs' Issue: https://github.com/external-secrets/external-secrets/issues/2905 Signed-off-by: Sourav Patnaik <souravpatnaik123@gmail.com> * - updated code as per review comment, make reviewable suggestions Issue: https://github.com/external-secrets/external-secrets/issues/2905 Signed-off-by: Sourav Patnaik <souravpatnaik123@gmail.com> * modified chef provider code as per review comment Issue: https://github.com/external-secrets/external-secrets/issues/2905 Signed-off-by: Sourav Patnaik <souravpatnaik123@gmail.com> --------- Signed-off-by: Subroto Roy <subrotoroy007@gmail.com> Signed-off-by: vardhanreddy13 <vvv.vardhanreddy@gmail.com> Signed-off-by: Sourav Patnaik <souravpatnaik123@gmail.com> Co-authored-by: Subroto Roy <subrotoroy007@gmail.com> Co-authored-by: vardhanreddy13 <vvv.vardhanreddy@gmail.com>
This commit is contained in:
parent
e726087851
commit
a012f4829c
16 changed files with 1461 additions and 0 deletions
38
apis/externalsecrets/v1beta1/secretstore_chef_types.go
Normal file
38
apis/externalsecrets/v1beta1/secretstore_chef_types.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
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"
|
||||
)
|
||||
|
||||
// ChefAuth contains a secretRef for credentials.
|
||||
type ChefAuth struct {
|
||||
SecretRef ChefAuthSecretRef `json:"secretRef"`
|
||||
}
|
||||
|
||||
// ChefAuthSecretRef holds secret references for chef server login credentials.
|
||||
type ChefAuthSecretRef struct {
|
||||
// SecretKey is the Signing Key in PEM format, used for authentication.
|
||||
SecretKey esmeta.SecretKeySelector `json:"privateKeySecretRef"`
|
||||
}
|
||||
|
||||
// ChefProvider configures a store to sync secrets using basic chef server connection credentials.
|
||||
type ChefProvider struct {
|
||||
// Auth defines the information necessary to authenticate against chef Server
|
||||
Auth *ChefAuth `json:"auth"`
|
||||
// UserName should be the user ID on the chef server
|
||||
UserName string `json:"username"`
|
||||
// ServerURL is the chef server URL used to connect to. If using orgs you should include your org in the url and terminate the url with a "/"
|
||||
ServerURL string `json:"serverUrl"`
|
||||
}
|
|
@ -141,6 +141,10 @@ type SecretStoreProvider struct {
|
|||
// https://docs.delinea.com/online-help/products/devops-secrets-vault/current
|
||||
// +optional
|
||||
Delinea *DelineaProvider `json:"delinea,omitempty"`
|
||||
|
||||
// Chef configures this store to sync secrets with chef server
|
||||
// +optional
|
||||
Chef *ChefProvider `json:"chef,omitempty"`
|
||||
}
|
||||
|
||||
type CAProviderType string
|
||||
|
|
|
@ -418,6 +418,58 @@ func (in *CertAuth) DeepCopy() *CertAuth {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ChefAuth) DeepCopyInto(out *ChefAuth) {
|
||||
*out = *in
|
||||
in.SecretRef.DeepCopyInto(&out.SecretRef)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChefAuth.
|
||||
func (in *ChefAuth) DeepCopy() *ChefAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ChefAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ChefAuthSecretRef) DeepCopyInto(out *ChefAuthSecretRef) {
|
||||
*out = *in
|
||||
in.SecretKey.DeepCopyInto(&out.SecretKey)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChefAuthSecretRef.
|
||||
func (in *ChefAuthSecretRef) DeepCopy() *ChefAuthSecretRef {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ChefAuthSecretRef)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ChefProvider) DeepCopyInto(out *ChefProvider) {
|
||||
*out = *in
|
||||
if in.Auth != nil {
|
||||
in, out := &in.Auth, &out.Auth
|
||||
*out = new(ChefAuth)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChefProvider.
|
||||
func (in *ChefProvider) DeepCopy() *ChefProvider {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ChefProvider)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClusterExternalSecret) DeepCopyInto(out *ClusterExternalSecret) {
|
||||
*out = *in
|
||||
|
@ -1992,6 +2044,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
|
|||
*out = new(DelineaProvider)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Chef != nil {
|
||||
in, out := &in.Chef, &out.Chef
|
||||
*out = new(ChefProvider)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreProvider.
|
||||
|
|
|
@ -2202,6 +2202,56 @@ spec:
|
|||
required:
|
||||
- vaultUrl
|
||||
type: object
|
||||
chef:
|
||||
description: Chef configures this store to sync secrets with chef
|
||||
server
|
||||
properties:
|
||||
auth:
|
||||
description: Auth defines the information necessary to authenticate
|
||||
against chef Server
|
||||
properties:
|
||||
secretRef:
|
||||
description: ChefAuthSecretRef holds secret references
|
||||
for chef server login credentials.
|
||||
properties:
|
||||
privateKeySecretRef:
|
||||
description: SecretKey is the Signing Key in PEM format,
|
||||
used for authentication.
|
||||
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:
|
||||
- privateKeySecretRef
|
||||
type: object
|
||||
required:
|
||||
- secretRef
|
||||
type: object
|
||||
serverUrl:
|
||||
description: ServerURL is the chef server URL used to connect
|
||||
to. If using orgs you should include your org in the url
|
||||
and terminate the url with a "/"
|
||||
type: string
|
||||
username:
|
||||
description: UserName should be the user ID on the chef server
|
||||
type: string
|
||||
required:
|
||||
- auth
|
||||
- serverUrl
|
||||
- username
|
||||
type: object
|
||||
conjur:
|
||||
description: Conjur configures this store to sync secrets using
|
||||
conjur provider
|
||||
|
|
|
@ -2202,6 +2202,56 @@ spec:
|
|||
required:
|
||||
- vaultUrl
|
||||
type: object
|
||||
chef:
|
||||
description: Chef configures this store to sync secrets with chef
|
||||
server
|
||||
properties:
|
||||
auth:
|
||||
description: Auth defines the information necessary to authenticate
|
||||
against chef Server
|
||||
properties:
|
||||
secretRef:
|
||||
description: ChefAuthSecretRef holds secret references
|
||||
for chef server login credentials.
|
||||
properties:
|
||||
privateKeySecretRef:
|
||||
description: SecretKey is the Signing Key in PEM format,
|
||||
used for authentication.
|
||||
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:
|
||||
- privateKeySecretRef
|
||||
type: object
|
||||
required:
|
||||
- secretRef
|
||||
type: object
|
||||
serverUrl:
|
||||
description: ServerURL is the chef server URL used to connect
|
||||
to. If using orgs you should include your org in the url
|
||||
and terminate the url with a "/"
|
||||
type: string
|
||||
username:
|
||||
description: UserName should be the user ID on the chef server
|
||||
type: string
|
||||
required:
|
||||
- auth
|
||||
- serverUrl
|
||||
- username
|
||||
type: object
|
||||
conjur:
|
||||
description: Conjur configures this store to sync secrets using
|
||||
conjur provider
|
||||
|
|
|
@ -2665,6 +2665,49 @@ spec:
|
|||
required:
|
||||
- vaultUrl
|
||||
type: object
|
||||
chef:
|
||||
description: Chef configures this store to sync secrets with chef server
|
||||
properties:
|
||||
auth:
|
||||
description: Auth defines the information necessary to authenticate against chef Server
|
||||
properties:
|
||||
secretRef:
|
||||
description: ChefAuthSecretRef holds secret references for chef server login credentials.
|
||||
properties:
|
||||
privateKeySecretRef:
|
||||
description: SecretKey is the Signing Key in PEM format, used for authentication.
|
||||
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:
|
||||
- privateKeySecretRef
|
||||
type: object
|
||||
required:
|
||||
- secretRef
|
||||
type: object
|
||||
serverUrl:
|
||||
description: ServerURL is the chef server URL used to connect to. If using orgs you should include your org in the url and terminate the url with a "/"
|
||||
type: string
|
||||
username:
|
||||
description: UserName should be the user ID on the chef server
|
||||
type: string
|
||||
required:
|
||||
- auth
|
||||
- serverUrl
|
||||
- username
|
||||
type: object
|
||||
conjur:
|
||||
description: Conjur configures this store to sync secrets using conjur provider
|
||||
properties:
|
||||
|
@ -7639,6 +7682,49 @@ spec:
|
|||
required:
|
||||
- vaultUrl
|
||||
type: object
|
||||
chef:
|
||||
description: Chef configures this store to sync secrets with chef server
|
||||
properties:
|
||||
auth:
|
||||
description: Auth defines the information necessary to authenticate against chef Server
|
||||
properties:
|
||||
secretRef:
|
||||
description: ChefAuthSecretRef holds secret references for chef server login credentials.
|
||||
properties:
|
||||
privateKeySecretRef:
|
||||
description: SecretKey is the Signing Key in PEM format, used for authentication.
|
||||
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:
|
||||
- privateKeySecretRef
|
||||
type: object
|
||||
required:
|
||||
- secretRef
|
||||
type: object
|
||||
serverUrl:
|
||||
description: ServerURL is the chef server URL used to connect to. If using orgs you should include your org in the url and terminate the url with a "/"
|
||||
type: string
|
||||
username:
|
||||
description: UserName should be the user ID on the chef server
|
||||
type: string
|
||||
required:
|
||||
- auth
|
||||
- serverUrl
|
||||
- username
|
||||
type: object
|
||||
conjur:
|
||||
description: Conjur configures this store to sync secrets using conjur provider
|
||||
properties:
|
||||
|
|
131
docs/api/spec.md
131
docs/api/spec.md
|
@ -1108,6 +1108,123 @@ External Secrets meta/v1.SecretKeySelector
|
|||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3 id="external-secrets.io/v1beta1.ChefAuth">ChefAuth
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#external-secrets.io/v1beta1.ChefProvider">ChefProvider</a>)
|
||||
</p>
|
||||
<p>
|
||||
<p>ChefAuth contains a secretRef for credentials.</p>
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>secretRef</code></br>
|
||||
<em>
|
||||
<a href="#external-secrets.io/v1beta1.ChefAuthSecretRef">
|
||||
ChefAuthSecretRef
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3 id="external-secrets.io/v1beta1.ChefAuthSecretRef">ChefAuthSecretRef
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#external-secrets.io/v1beta1.ChefAuth">ChefAuth</a>)
|
||||
</p>
|
||||
<p>
|
||||
<p>ChefAuthSecretRef holds secret references for chef server login credentials.</p>
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<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>
|
||||
<p>SecretKey is the Signing Key in PEM format, used for authentication.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3 id="external-secrets.io/v1beta1.ChefProvider">ChefProvider
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#external-secrets.io/v1beta1.SecretStoreProvider">SecretStoreProvider</a>)
|
||||
</p>
|
||||
<p>
|
||||
<p>ChefProvider configures a store to sync secrets using basic chef server connection credentials.</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.ChefAuth">
|
||||
ChefAuth
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Auth defines the information necessary to authenticate against chef Server</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>username</code></br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>UserName should be the user ID on the chef server</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>serverUrl</code></br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>ServerURL is the chef server URL used to connect to. If using orgs you should include your org in the url and terminate the url with a “/”</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3 id="external-secrets.io/v1beta1.ClusterExternalSecret">ClusterExternalSecret
|
||||
</h3>
|
||||
<p>
|
||||
|
@ -5289,6 +5406,20 @@ DelineaProvider
|
|||
<a href="https://docs.delinea.com/online-help/products/devops-secrets-vault/current">https://docs.delinea.com/online-help/products/devops-secrets-vault/current</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>chef</code></br>
|
||||
<em>
|
||||
<a href="#external-secrets.io/v1beta1.ChefProvider">
|
||||
ChefProvider
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Chef configures this store to sync secrets with chef server</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3 id="external-secrets.io/v1beta1.SecretStoreRef">SecretStoreRef
|
||||
|
|
113
docs/provider/chef.md
Normal file
113
docs/provider/chef.md
Normal file
|
@ -0,0 +1,113 @@
|
|||
## Chef
|
||||
|
||||
`Chef External Secrets provider` will enable users to seamlessly integrate their Chef-based secret management with Kubernetes through the existing External Secrets framework.
|
||||
|
||||
In many enterprises, legacy applications and infrastructure are still tightly integrated with the Chef/Chef Infra Server/Chef Server Cluster for configuration and secrets management. Teams often rely on [Chef data bags](https://docs.chef.io/data_bags/) to securely store sensitive information such as application secrets and infrastructure configurations. These data bags serve as a centralized repository for managing and distributing sensitive data across the Chef ecosystem.
|
||||
|
||||
**NOTE:** `Chef External Secrets provider` is designed only to fetch data from the Chef data bags into Kubernetes secrets, it won't update/delete any item in the data bags.
|
||||
|
||||
### Authentication
|
||||
|
||||
Every request made to the Chef Infra server needs to be authenticated. [Authentication](https://docs.chef.io/server/auth/) is done using the Private keys of the Chef Users. The User needs to have appropriate [Permissions](https://docs.chef.io/server/server_orgs/#permissions) to the data bags containing the data that they want to fetch using the External Secrets Operator.
|
||||
|
||||
The following command can be used to create Chef Users:
|
||||
```sh
|
||||
chef-server-ctl user-create USER_NAME FIRST_NAME [MIDDLE_NAME] LAST_NAME EMAIL 'PASSWORD' (options)
|
||||
```
|
||||
|
||||
More details on the above command are available here [Chef User Create Option](https://docs.chef.io/server/server_users/#user-create). The above command will return the default private key (PRIVATE_KEY_VALUE), which we will use for authentication. Additionally, a Chef User with access to specific data bags, a private key pair with an expiration date can be created with the help of the [knife user key](https://docs.chef.io/server/auth/#knife-user-key) command.
|
||||
|
||||
### Create a secret containing your private key
|
||||
|
||||
We need to store the above User's API key into a secret resource.
|
||||
Example:
|
||||
```sh
|
||||
kubectl create secret generic chef-user-secret -n vivid --from-literal=user-private-key='PRIVATE_KEY_VALUE'
|
||||
```
|
||||
|
||||
### Creating ClusterSecretStore
|
||||
|
||||
The Chef `ClusterSecretStore` is a cluster-scoped SecretStore that can be referenced by all Chef `ExternalSecrets` from all namespaces. You can follow the below example to create a `ClusterSecretStore` resource.
|
||||
|
||||
```yaml
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ClusterSecretStore
|
||||
metadata:
|
||||
name: vivid-clustersecretstore # name of ClusterSecretStore
|
||||
spec:
|
||||
provider:
|
||||
chef:
|
||||
username: user # Chef User name
|
||||
serverUrl: https://manage.chef.io/organizations/testuser/ # Chef server URL
|
||||
auth:
|
||||
secretRef:
|
||||
privateKeySecretRef:
|
||||
key: user-private-key # name of the key inside Secret resource
|
||||
name: chef-user-secret # name of Kubernetes Secret resource containing the Chef User's private key
|
||||
namespace: vivid # the namespace in which the above Secret resource resides
|
||||
```
|
||||
|
||||
### Creating SecretStore
|
||||
|
||||
Chef `SecretStores` are bound to a namespace and can not reference resources across namespaces. For cross-namespace SecretStores, you must use Chef `ClusterSecretStores`.
|
||||
|
||||
You can follow the below example to create a `SecretStore` resource.
|
||||
|
||||
```yaml
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: SecretStore
|
||||
metadata:
|
||||
name: vivid-secretstore # name of SecretStore
|
||||
namespace: vivid # must be required for kind: SecretStore
|
||||
spec:
|
||||
provider:
|
||||
chef:
|
||||
username: user # Chef User name
|
||||
serverUrl: https://manage.chef.io/organizations/testuser/ # Chef server URL
|
||||
auth:
|
||||
secretRef:
|
||||
privateKeySecretRef:
|
||||
name: chef-user-secret # name of Kubernetes Secret resource containing the Chef User's private key
|
||||
key: user-private-key # name of the key inside Secret resource
|
||||
namespace: vivid # the ns where the k8s secret resource containing Chef User's private key resides
|
||||
|
||||
```
|
||||
|
||||
### Creating ExternalSecret
|
||||
|
||||
The Chef `ExternalSecret` describes what data should be fetched from Chef Data bags, and how the data should be transformed and saved as a Kind=Secret.
|
||||
|
||||
You can follow the below example to create an `ExternalSecret` resource.
|
||||
```yaml
|
||||
{% include 'chef-external-secret.yaml' %}
|
||||
```
|
||||
|
||||
When the above `ClusterSecretStore` and `ExternalSecret` resources are created, the `ExternalSecret` will connect to the Chef Server using the private key and will fetch the data bags contained in the `vivid-credentials` secret resource.
|
||||
|
||||
To get all data items inside the data bag, you can use the `dataFrom` directive:
|
||||
```yaml
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: vivid-external-secrets # name of ExternalSecret
|
||||
namespace: vivid # namespace inside which the ExternalSecret will be created
|
||||
annotations:
|
||||
company/contacts: user.a@company.com, user.b@company.com
|
||||
company/team: vivid-dev
|
||||
labels:
|
||||
app.kubernetes.io/name: external-secrets
|
||||
spec:
|
||||
refreshInterval: 15m
|
||||
secretStoreRef:
|
||||
name: vivid-clustersecretstore # name of ClusterSecretStore
|
||||
kind: ClusterSecretStore
|
||||
dataFrom:
|
||||
- extract:
|
||||
key: vivid_global # only data bag name
|
||||
target:
|
||||
name: vivid_global_all_cred # name of Kubernetes Secret resource that will be created and will contain the obtained secrets
|
||||
creationPolicy: Owner
|
||||
|
||||
```
|
||||
|
||||
follow : [this file](https://github.com/external-secrets/external-secrets/blob/main/apis/externalsecrets/v1beta1/secretstore_chef_types.go) for more info
|
48
docs/snippets/chef-external-secret.yaml
Normal file
48
docs/snippets/chef-external-secret.yaml
Normal file
|
@ -0,0 +1,48 @@
|
|||
{% raw %}
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: vivid-external-secrets # name of ExternalSecret
|
||||
namespace: vivid # namespace inside which the ExternalSecret will be created
|
||||
annotations:
|
||||
company/contacts: user.a@company.com, user.b@company.com
|
||||
company/team: vivid-dev
|
||||
labels:
|
||||
app.kubernetes.io/name: external-secrets
|
||||
spec:
|
||||
refreshInterval: 15m
|
||||
secretStoreRef:
|
||||
name: vivid-clustersecretstore # name of ClusterSecretStore
|
||||
kind: ClusterSecretStore
|
||||
data:
|
||||
- secretKey: USERNAME
|
||||
remoteRef:
|
||||
key: vivid_prod/global_user # databagName/dataItemName
|
||||
property: username # a json key in dataItem
|
||||
- secretKey: PASSWORD
|
||||
remoteRef:
|
||||
key: vivid_prod/global_user
|
||||
property: password
|
||||
- secretKey: APIKEY
|
||||
remoteRef:
|
||||
key: vivid_global/apikey
|
||||
property: api_key
|
||||
- secretKey: APP_PROPERTIES
|
||||
remoteRef:
|
||||
key: vivid_global/app_properties # databagName/dataItemName , it will fetch all key-vlaues present in the dataItem
|
||||
target:
|
||||
name: vivid-credentials # name of kubernetes Secret resource that will be created and will contain the obtained secrets
|
||||
creationPolicy: Owner
|
||||
template:
|
||||
mergePolicy: Replace
|
||||
engineVersion: v2
|
||||
data:
|
||||
secrets.json: |
|
||||
{
|
||||
"username": "{{ .USERNAME }}",
|
||||
"password": "{{ .PASSWORD }}",
|
||||
"app_apikey": "{{ .APIKEY }}",
|
||||
"app_properties": "{{ .APP_PROPERTIES }}"
|
||||
}
|
||||
|
||||
{% endraw %}
|
1
go.mod
1
go.mod
|
@ -151,6 +151,7 @@ require (
|
|||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-chef/chef v0.28.4
|
||||
github.com/go-logr/zapr v1.3.0 // indirect
|
||||
github.com/go-openapi/errors v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.20.2 // indirect
|
||||
|
|
6
go.sum
6
go.sum
|
@ -200,6 +200,8 @@ github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbi
|
|||
github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/ctdk/goiardi v0.11.10 h1:IB/3Afl1pC2Q4KGwzmhHPAoJfe8VtU51wZ2V0QkvsL0=
|
||||
github.com/ctdk/goiardi v0.11.10/go.mod h1:Pr6Cj6Wsahw45myttaOEZeZ0LE7p1qzWmzgsBISkrNI=
|
||||
github.com/cyberark/conjur-api-go v0.11.1 h1:vjaMkw0geJsA+ikMM6UDLg4VLFQWKo/B0i9IWlOQ1f0=
|
||||
github.com/cyberark/conjur-api-go v0.11.1/go.mod h1:n1p46Hj9l8wkZjM17cVYdfcatyPboWyioLGlC0QszCs=
|
||||
github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs=
|
||||
|
@ -245,6 +247,8 @@ github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uq
|
|||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-chef/chef v0.28.4 h1:NvvEfBnS9sv6y+9NiBKf01kVAK+4LDKnCpYV8LjMi90=
|
||||
github.com/go-chef/chef v0.28.4/go.mod h1:7RU1oCrRErTrkmIszkhJ9vHw7Bv2hZ1Vv1C1qKj01fc=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
|
@ -583,6 +587,8 @@ github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqSc
|
|||
github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/r3labs/diff v0.0.0-20191120142937-b4ed99a31f5a h1:2v4Ipjxa3sh+xn6GvtgrMub2ci4ZLQMvTaYIba2lfdc=
|
||||
github.com/r3labs/diff v0.0.0-20191120142937-b4ed99a31f5a/go.mod h1:ozniNEFS3j1qCwHKdvraMn1WJOsUxHd7lYfukEIS4cs=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
|
|
|
@ -89,6 +89,7 @@ nav:
|
|||
- AWS Secrets Manager: provider/aws-secrets-manager.md
|
||||
- AWS Parameter Store: provider/aws-parameter-store.md
|
||||
- Azure Key Vault: provider/azure-key-vault.md
|
||||
- Chef: provider/chef.md
|
||||
- CyberArk Conjur: provider/conjur.md
|
||||
- Google Cloud Secret Manager: provider/google-secrets-manager.md
|
||||
- HashiCorp Vault: provider/hashicorp-vault.md
|
||||
|
|
343
pkg/provider/chef/chef.go
Normal file
343
pkg/provider/chef/chef.go
Normal file
|
@ -0,0 +1,343 @@
|
|||
/*
|
||||
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 chef
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-chef/chef"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/tidwall/gjson"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
kclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
"github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/pkg/metrics"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
errChefStore = "received invalid Chef SecretStore resource: %w"
|
||||
errMissingStore = "missing store"
|
||||
errMissingStoreSpec = "missing store spec"
|
||||
errMissingProvider = "missing provider"
|
||||
errMissingChefProvider = "missing chef provider"
|
||||
errMissingUserName = "missing username"
|
||||
errMissingServerURL = "missing serverurl"
|
||||
errMissingAuth = "cannot initialize Chef Client: no valid authType was specified"
|
||||
errMissingSecretKey = "missing Secret Key"
|
||||
errInvalidClusterStoreMissingPKNamespace = "invalid ClusterSecretStore: missing privateKeySecretRef.Namespace"
|
||||
errFetchK8sSecret = "could not fetch SecretKey Secret: %w"
|
||||
errInvalidURL = "invalid serverurl: %w"
|
||||
errChefClient = "unable to create chef client: %w"
|
||||
errChefProvider = "missing or invalid spec: %w"
|
||||
errUninitalizedChefProvider = "chef provider is not initialized"
|
||||
errNoDatabagItemFound = "data bag item %s not found in data bag %s"
|
||||
errNoDatabagItemPropertyFound = "property %s not found in data bag item"
|
||||
errCannotListDataBagItems = "unable to list items in data bag %s, may be given data bag doesn't exists or it is empty"
|
||||
errUnableToConvertToJSON = "unable to convert databagItem into JSON"
|
||||
errInvalidFormat = "invalid key format in data section. Expected value 'databagName/databagItemName'"
|
||||
errStoreValidateFailed = "unable to validate provided store. Check if username, serverUrl and privateKey are correct"
|
||||
errServerURLNoEndSlash = "serverurl does not end with slash(/)"
|
||||
errInvalidDataform = "invalid key format in dataForm section. Expected only 'databagName'"
|
||||
|
||||
ProviderChef = "Chef"
|
||||
CallChefGetDataBagItem = "GetDataBagItem"
|
||||
CallChefListDataBagItems = "ListDataBagItems"
|
||||
CallChefGetUser = "GetUser"
|
||||
)
|
||||
|
||||
var contextTimeout = time.Second * 25
|
||||
|
||||
type DatabagFetcher interface {
|
||||
GetItem(databagName string, databagItem string) (item chef.DataBagItem, err error)
|
||||
ListItems(name string) (data *chef.DataBagListResult, err error)
|
||||
}
|
||||
|
||||
type UserInterface interface {
|
||||
Get(name string) (user chef.User, err error)
|
||||
}
|
||||
|
||||
type Providerchef struct {
|
||||
clientName string
|
||||
databagService DatabagFetcher
|
||||
userService UserInterface
|
||||
log logr.Logger
|
||||
}
|
||||
|
||||
var _ v1beta1.SecretsClient = &Providerchef{}
|
||||
var _ v1beta1.Provider = &Providerchef{}
|
||||
|
||||
func init() {
|
||||
v1beta1.Register(&Providerchef{}, &v1beta1.SecretStoreProvider{
|
||||
Chef: &v1beta1.ChefProvider{},
|
||||
})
|
||||
}
|
||||
|
||||
func (providerchef *Providerchef) NewClient(ctx context.Context, store v1beta1.GenericStore, kube kclient.Client, namespace string) (v1beta1.SecretsClient, error) {
|
||||
chefProvider, err := getChefProvider(store)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errChefProvider, err)
|
||||
}
|
||||
|
||||
credentialsSecret := &corev1.Secret{}
|
||||
objectKey := types.NamespacedName{
|
||||
Name: chefProvider.Auth.SecretRef.SecretKey.Name,
|
||||
Namespace: namespace,
|
||||
}
|
||||
|
||||
if store.GetObjectKind().GroupVersionKind().Kind == v1beta1.ClusterSecretStoreKind {
|
||||
if chefProvider.Auth.SecretRef.SecretKey.Namespace == nil {
|
||||
return nil, fmt.Errorf(errInvalidClusterStoreMissingPKNamespace)
|
||||
}
|
||||
objectKey.Namespace = *chefProvider.Auth.SecretRef.SecretKey.Namespace
|
||||
}
|
||||
|
||||
if err := kube.Get(ctx, objectKey, credentialsSecret); err != nil {
|
||||
return nil, fmt.Errorf(errFetchK8sSecret, err)
|
||||
}
|
||||
|
||||
secretKey := credentialsSecret.Data[chefProvider.Auth.SecretRef.SecretKey.Key]
|
||||
if len(secretKey) == 0 {
|
||||
return nil, fmt.Errorf(errMissingSecretKey)
|
||||
}
|
||||
|
||||
client, err := chef.NewClient(&chef.Config{
|
||||
Name: chefProvider.UserName,
|
||||
Key: string(secretKey),
|
||||
BaseURL: chefProvider.ServerURL,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errChefClient, err)
|
||||
}
|
||||
|
||||
providerchef.clientName = chefProvider.UserName
|
||||
providerchef.databagService = client.DataBags
|
||||
providerchef.userService = client.Users
|
||||
providerchef.log = ctrl.Log.WithName("provider").WithName("chef").WithName("secretsmanager")
|
||||
return providerchef, nil
|
||||
}
|
||||
|
||||
// Close closes the client connection.
|
||||
func (providerchef *Providerchef) Close(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks if the client is configured correctly
|
||||
// to be able to retrieve secrets from the provider.
|
||||
func (providerchef *Providerchef) Validate() (v1beta1.ValidationResult, error) {
|
||||
_, err := providerchef.userService.Get(providerchef.clientName)
|
||||
metrics.ObserveAPICall(ProviderChef, CallChefGetUser, err)
|
||||
if err != nil {
|
||||
return v1beta1.ValidationResultError, fmt.Errorf(errStoreValidateFailed)
|
||||
}
|
||||
return v1beta1.ValidationResultReady, nil
|
||||
}
|
||||
|
||||
// GetAllSecrets Retrieves a map[string][]byte with the Databag names as key and the Databag's Items as secrets.
|
||||
func (providerchef *Providerchef) GetAllSecrets(_ context.Context, _ v1beta1.ExternalSecretFind) (map[string][]byte, error) {
|
||||
return nil, fmt.Errorf("dataFrom.find not suppported")
|
||||
}
|
||||
|
||||
// GetSecret returns a databagItem present in the databag. format example: databagName/databagItemName.
|
||||
func (providerchef *Providerchef) GetSecret(ctx context.Context, ref v1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
|
||||
if utils.IsNil(providerchef.databagService) {
|
||||
return nil, fmt.Errorf(errUninitalizedChefProvider)
|
||||
}
|
||||
|
||||
key := ref.Key
|
||||
databagName := ""
|
||||
databagItem := ""
|
||||
nameSplitted := strings.Split(key, "/")
|
||||
if len(nameSplitted) > 1 {
|
||||
databagName = nameSplitted[0]
|
||||
databagItem = nameSplitted[1]
|
||||
}
|
||||
providerchef.log.Info("fetching secret value", "databag Name:", databagName, "databag Item:", databagItem)
|
||||
if databagName != "" && databagItem != "" {
|
||||
return getSingleDatabagItemWithContext(ctx, providerchef, databagName, databagItem, ref.Property)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(errInvalidFormat)
|
||||
}
|
||||
|
||||
func getSingleDatabagItemWithContext(ctx context.Context, providerchef *Providerchef, dataBagName, databagItemName, propertyName string) ([]byte, error) {
|
||||
ctxWithTimeout, cancel := context.WithTimeout(ctx, contextTimeout)
|
||||
defer cancel()
|
||||
type result = struct {
|
||||
values []byte
|
||||
err error
|
||||
}
|
||||
getWithTimeout := func() chan result {
|
||||
resultChan := make(chan result, 1)
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
ditem, err := providerchef.databagService.GetItem(dataBagName, databagItemName)
|
||||
metrics.ObserveAPICall(ProviderChef, CallChefGetDataBagItem, err)
|
||||
if err != nil {
|
||||
resultChan <- result{err: fmt.Errorf(errNoDatabagItemFound, databagItemName, dataBagName)}
|
||||
return
|
||||
}
|
||||
jsonByte, err := json.Marshal(ditem)
|
||||
if err != nil {
|
||||
resultChan <- result{err: fmt.Errorf(errUnableToConvertToJSON)}
|
||||
return
|
||||
}
|
||||
if propertyName != "" {
|
||||
propertyValue, err := getPropertyFromDatabagItem(jsonByte, propertyName)
|
||||
if err != nil {
|
||||
resultChan <- result{err: err}
|
||||
return
|
||||
}
|
||||
resultChan <- result{values: propertyValue}
|
||||
} else {
|
||||
resultChan <- result{values: jsonByte}
|
||||
}
|
||||
}()
|
||||
return resultChan
|
||||
}
|
||||
select {
|
||||
case <-ctxWithTimeout.Done():
|
||||
return nil, ctxWithTimeout.Err()
|
||||
case r := <-getWithTimeout():
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
return r.values, nil
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
A path is a series of keys separated by a dot.
|
||||
A key may contain special wildcard characters '*' and '?'.
|
||||
To access an array value use the index as the key.
|
||||
To get the number of elements in an array or to access a child path, use the '#' character.
|
||||
The dot and wildcard characters can be escaped with '\'.
|
||||
|
||||
refer https://github.com/tidwall/gjson#:~:text=JSON%20byte%20slices.-,Path%20Syntax,-Below%20is%20a
|
||||
*/
|
||||
func getPropertyFromDatabagItem(jsonByte []byte, propertyName string) ([]byte, error) {
|
||||
result := gjson.GetBytes(jsonByte, propertyName)
|
||||
|
||||
if !result.Exists() {
|
||||
return nil, fmt.Errorf(errNoDatabagItemPropertyFound, propertyName)
|
||||
}
|
||||
return []byte(result.Str), nil
|
||||
}
|
||||
|
||||
// GetSecretMap returns multiple k/v pairs from the provider, for dataFrom.extract.key
|
||||
// dataFrom.extract.key only accepts dataBagName, example : dataFrom.extract.key: myDatabag
|
||||
// databagItemName or Property not expected in key.
|
||||
func (providerchef *Providerchef) GetSecretMap(ctx context.Context, ref v1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
||||
if utils.IsNil(providerchef.databagService) {
|
||||
return nil, fmt.Errorf(errUninitalizedChefProvider)
|
||||
}
|
||||
databagName := ref.Key
|
||||
|
||||
if strings.Contains(databagName, "/") {
|
||||
return nil, fmt.Errorf(errInvalidDataform)
|
||||
}
|
||||
getAllSecrets := make(map[string][]byte)
|
||||
providerchef.log.Info("fetching all items from", "databag:", databagName)
|
||||
dataItems, err := providerchef.databagService.ListItems(databagName)
|
||||
metrics.ObserveAPICall(ProviderChef, CallChefListDataBagItems, err)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errCannotListDataBagItems, databagName)
|
||||
}
|
||||
|
||||
for dataItem := range *dataItems {
|
||||
dItem, err := getSingleDatabagItemWithContext(ctx, providerchef, databagName, dataItem, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errNoDatabagItemFound, dataItem, databagName)
|
||||
}
|
||||
getAllSecrets[dataItem] = dItem
|
||||
}
|
||||
return getAllSecrets, nil
|
||||
}
|
||||
|
||||
// ValidateStore checks if the provided store is valid.
|
||||
func (providerchef *Providerchef) ValidateStore(store v1beta1.GenericStore) (admission.Warnings, error) {
|
||||
chefProvider, err := getChefProvider(store)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errChefStore, err)
|
||||
}
|
||||
// check namespace compared to kind
|
||||
if err := utils.ValidateSecretSelector(store, chefProvider.Auth.SecretRef.SecretKey); err != nil {
|
||||
return nil, fmt.Errorf(errChefStore, err)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// getChefProvider validates the incoming store and return the chef provider.
|
||||
func getChefProvider(store v1beta1.GenericStore) (*v1beta1.ChefProvider, error) {
|
||||
if store == nil {
|
||||
return nil, fmt.Errorf(errMissingStore)
|
||||
}
|
||||
storeSpec := store.GetSpec()
|
||||
if storeSpec == nil {
|
||||
return nil, fmt.Errorf(errMissingStoreSpec)
|
||||
}
|
||||
provider := storeSpec.Provider
|
||||
if provider == nil {
|
||||
return nil, fmt.Errorf(errMissingProvider)
|
||||
}
|
||||
chefProvider := storeSpec.Provider.Chef
|
||||
if chefProvider == nil {
|
||||
return nil, fmt.Errorf(errMissingChefProvider)
|
||||
}
|
||||
if chefProvider.UserName == "" {
|
||||
return chefProvider, fmt.Errorf(errMissingUserName)
|
||||
}
|
||||
if chefProvider.ServerURL == "" {
|
||||
return chefProvider, fmt.Errorf(errMissingServerURL)
|
||||
}
|
||||
if !strings.HasSuffix(chefProvider.ServerURL, "/") {
|
||||
return chefProvider, fmt.Errorf(errServerURLNoEndSlash)
|
||||
}
|
||||
// check valid URL
|
||||
if _, err := url.ParseRequestURI(chefProvider.ServerURL); err != nil {
|
||||
return chefProvider, fmt.Errorf(errInvalidURL, err)
|
||||
}
|
||||
if chefProvider.Auth == nil {
|
||||
return chefProvider, fmt.Errorf(errMissingAuth)
|
||||
}
|
||||
if chefProvider.Auth.SecretRef.SecretKey.Key == "" {
|
||||
return chefProvider, fmt.Errorf(errMissingSecretKey)
|
||||
}
|
||||
|
||||
return chefProvider, nil
|
||||
}
|
||||
|
||||
// Not Implemented DeleteSecret.
|
||||
func (providerchef *Providerchef) DeleteSecret(_ context.Context, _ v1beta1.PushSecretRemoteRef) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
// Not Implemented PushSecret.
|
||||
func (providerchef *Providerchef) PushSecret(_ context.Context, _ *corev1.Secret, _ v1beta1.PushSecretData) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
// Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
|
||||
func (providerchef *Providerchef) Capabilities() v1beta1.SecretStoreCapabilities {
|
||||
return v1beta1.SecretStoreReadOnly
|
||||
}
|
428
pkg/provider/chef/chef_test.go
Normal file
428
pkg/provider/chef/chef_test.go
Normal file
|
@ -0,0 +1,428 @@
|
|||
/*
|
||||
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 chef
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-chef/chef"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
fake "github.com/external-secrets/external-secrets/pkg/provider/chef/fake"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
name = "chef-demo-user"
|
||||
baseURL = "https://chef.cloudant.com/organizations/myorg/"
|
||||
noEndSlashInvalidBaseURL = "no end slash invalid base URL"
|
||||
baseInvalidURL = "invalid base URL/"
|
||||
authName = "chef-demo-auth-name"
|
||||
authKey = "chef-demo-auth-key"
|
||||
authNamespace = "chef-demo-auth-namespace"
|
||||
kind = "SecretStore"
|
||||
apiversion = "external-secrets.io/v1beta1"
|
||||
databagName = "databag01"
|
||||
)
|
||||
|
||||
type chefTestCase struct {
|
||||
mockClient *fake.ChefMockClient
|
||||
databagName string
|
||||
databagItemName string
|
||||
property string
|
||||
ref *esv1beta1.ExternalSecretDataRemoteRef
|
||||
apiErr error
|
||||
expectError string
|
||||
expectedData map[string][]byte
|
||||
expectedByte []byte
|
||||
}
|
||||
|
||||
type ValidateStoreTestCase struct {
|
||||
store *esv1beta1.SecretStore
|
||||
err error
|
||||
}
|
||||
|
||||
// type storeModifier func(*esv1beta1.SecretStore) *esv1beta1.SecretStore
|
||||
|
||||
func makeValidChefTestCase() *chefTestCase {
|
||||
smtc := chefTestCase{
|
||||
mockClient: &fake.ChefMockClient{},
|
||||
databagName: "databag01",
|
||||
databagItemName: "item01",
|
||||
property: "",
|
||||
apiErr: nil,
|
||||
expectError: "",
|
||||
expectedData: map[string][]byte{"item01": []byte(`"https://chef.com/organizations/dev/data/databag01/item01"`)},
|
||||
expectedByte: []byte(`{"item01":"{\"id\":\"databag01-item01\",\"some_key\":\"fe7f29ede349519a1\",\"some_password\":\"dolphin_123zc\",\"some_username\":\"testuser\"}"}`),
|
||||
}
|
||||
|
||||
smtc.ref = makeValidRef(smtc.databagName, smtc.databagItemName, smtc.property)
|
||||
smtc.mockClient.WithListItems(smtc.databagName, smtc.apiErr)
|
||||
smtc.mockClient.WithItem(smtc.databagName, smtc.databagItemName, smtc.apiErr)
|
||||
return &smtc
|
||||
}
|
||||
|
||||
func makeInValidChefTestCase() *chefTestCase {
|
||||
smtc := chefTestCase{
|
||||
mockClient: &fake.ChefMockClient{},
|
||||
databagName: "databag01",
|
||||
databagItemName: "item03",
|
||||
property: "",
|
||||
apiErr: errors.New("unable to convert databagItem into JSON"),
|
||||
expectError: "unable to convert databagItem into JSON",
|
||||
expectedData: nil,
|
||||
expectedByte: nil,
|
||||
}
|
||||
|
||||
smtc.ref = makeValidRef(smtc.databagName, smtc.databagItemName, smtc.property)
|
||||
smtc.mockClient.WithListItems(smtc.databagName, smtc.apiErr)
|
||||
smtc.mockClient.WithItem(smtc.databagName, smtc.databagItemName, smtc.apiErr)
|
||||
return &smtc
|
||||
}
|
||||
|
||||
func makeValidRef(databag, dataitem, property string) *esv1beta1.ExternalSecretDataRemoteRef {
|
||||
return &esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: databag + "/" + dataitem,
|
||||
Property: property,
|
||||
}
|
||||
}
|
||||
|
||||
func makeinValidRef() *esv1beta1.ExternalSecretDataRemoteRef {
|
||||
return &esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: "",
|
||||
}
|
||||
}
|
||||
|
||||
func makeValidRefForGetSecretMap(databag string) *esv1beta1.ExternalSecretDataRemoteRef {
|
||||
return &esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: databag,
|
||||
}
|
||||
}
|
||||
|
||||
func makeValidChefTestCaseCustom(tweaks ...func(smtc *chefTestCase)) *chefTestCase {
|
||||
smtc := makeValidChefTestCase()
|
||||
for _, fn := range tweaks {
|
||||
fn(smtc)
|
||||
}
|
||||
return smtc
|
||||
}
|
||||
|
||||
func TestChefGetSecret(t *testing.T) {
|
||||
nilClient := func(smtc *chefTestCase) {
|
||||
smtc.mockClient = nil
|
||||
smtc.expectedByte = nil
|
||||
smtc.expectError = "chef provider is not initialized"
|
||||
}
|
||||
|
||||
invalidDatabagName := func(smtc *chefTestCase) {
|
||||
smtc.databagName = "databag02"
|
||||
smtc.expectedByte = nil
|
||||
smtc.ref = makeinValidRef()
|
||||
smtc.expectError = "invalid key format in data section. Expected value 'databagName/databagItemName'"
|
||||
}
|
||||
|
||||
invalidDatabagItemName := func(smtc *chefTestCase) {
|
||||
smtc.expectError = "data bag item item02 not found in data bag databag01"
|
||||
smtc.databagName = databagName
|
||||
smtc.databagItemName = "item02"
|
||||
smtc.expectedByte = nil
|
||||
smtc.ref = makeValidRef(smtc.databagName, smtc.databagItemName, "")
|
||||
}
|
||||
|
||||
noProperty := func(smtc *chefTestCase) {
|
||||
smtc.expectError = "property findProperty not found in data bag item"
|
||||
smtc.databagName = databagName
|
||||
smtc.databagItemName = "item01"
|
||||
smtc.expectedByte = nil
|
||||
smtc.ref = makeValidRef(smtc.databagName, smtc.databagItemName, "findProperty")
|
||||
}
|
||||
|
||||
withProperty := func(smtc *chefTestCase) {
|
||||
smtc.expectedByte = []byte("foundProperty")
|
||||
smtc.databagName = "databag03"
|
||||
smtc.databagItemName = "item03"
|
||||
smtc.ref = makeValidRef(smtc.databagName, smtc.databagItemName, "findProperty")
|
||||
}
|
||||
|
||||
successCases := []*chefTestCase{
|
||||
makeValidChefTestCase(),
|
||||
makeValidChefTestCaseCustom(nilClient),
|
||||
makeValidChefTestCaseCustom(invalidDatabagName),
|
||||
makeValidChefTestCaseCustom(invalidDatabagItemName),
|
||||
makeValidChefTestCaseCustom(noProperty),
|
||||
makeValidChefTestCaseCustom(withProperty),
|
||||
makeInValidChefTestCase(),
|
||||
}
|
||||
|
||||
sm := Providerchef{
|
||||
databagService: &chef.DataBagService{},
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
for k, v := range successCases {
|
||||
sm.databagService = v.mockClient
|
||||
out, err := sm.GetSecret(ctx, *v.ref)
|
||||
if err != nil && !utils.ErrorContains(err, v.expectError) {
|
||||
t.Errorf("[case %d] expected error: %v, got: %v", k, v.expectError, err)
|
||||
} else if v.expectError != "" && err == nil {
|
||||
t.Errorf("[case %d] expected error: %v, got: nil", k, v.expectError)
|
||||
}
|
||||
if !bytes.Equal(out, v.expectedByte) {
|
||||
t.Errorf("[case %d] expected secret: %s, got: %s", k, v.expectedByte, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestChefGetSecretMap(t *testing.T) {
|
||||
nilClient := func(smtc *chefTestCase) {
|
||||
smtc.mockClient = nil
|
||||
smtc.expectedByte = nil
|
||||
smtc.expectError = "chef provider is not initialized"
|
||||
}
|
||||
|
||||
databagHasSlash := func(smtc *chefTestCase) {
|
||||
smtc.expectedByte = nil
|
||||
smtc.ref = makeinValidRef()
|
||||
smtc.ref.Key = "data/Bag02"
|
||||
smtc.expectError = "invalid key format in dataForm section. Expected only 'databagName'"
|
||||
}
|
||||
|
||||
withProperty := func(smtc *chefTestCase) {
|
||||
smtc.expectedByte = []byte(`{"item01":"{\"id\":\"databag01-item01\",\"some_key\":\"fe7f29ede349519a1\",\"some_password\":\"dolphin_123zc\",\"some_username\":\"testuser\"}"}`)
|
||||
smtc.databagName = databagName
|
||||
smtc.ref = makeValidRefForGetSecretMap(smtc.databagName)
|
||||
}
|
||||
|
||||
withProperty2 := func(smtc *chefTestCase) {
|
||||
smtc.expectError = "unable to list items in data bag 123, may be given data bag doesn't exists or it is empty"
|
||||
smtc.expectedByte = nil
|
||||
smtc.databagName = "123"
|
||||
smtc.ref = makeValidRefForGetSecretMap(smtc.databagName)
|
||||
}
|
||||
|
||||
successCases := []*chefTestCase{
|
||||
makeValidChefTestCaseCustom(nilClient),
|
||||
makeValidChefTestCaseCustom(databagHasSlash),
|
||||
makeValidChefTestCaseCustom(withProperty),
|
||||
makeValidChefTestCaseCustom(withProperty2),
|
||||
}
|
||||
|
||||
pc := Providerchef{
|
||||
databagService: &chef.DataBagService{},
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
for k, v := range successCases {
|
||||
pc.databagService = v.mockClient
|
||||
out, err := pc.GetSecretMap(ctx, *v.ref)
|
||||
if err != nil && !utils.ErrorContains(err, v.expectError) {
|
||||
t.Errorf("[case %d] expected error: %v, got: %v", k, v.expectError, err)
|
||||
} else if v.expectError != "" && err == nil {
|
||||
t.Errorf("[case %d] expected error: %v, got: nil", k, v.expectError)
|
||||
}
|
||||
if !bytes.Equal(out["item01"], v.expectedByte) {
|
||||
t.Errorf("[case %d] unexpected secret: expected %s, got %s", k, v.expectedByte, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeSecretStore(name, baseURL string, auth *esv1beta1.ChefAuth) *esv1beta1.SecretStore {
|
||||
store := &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
Chef: &esv1beta1.ChefProvider{
|
||||
UserName: name,
|
||||
ServerURL: baseURL,
|
||||
Auth: auth,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
||||
func makeAuth(name, namespace, key string) *esv1beta1.ChefAuth {
|
||||
return &esv1beta1.ChefAuth{
|
||||
SecretRef: esv1beta1.ChefAuthSecretRef{
|
||||
SecretKey: v1.SecretKeySelector{
|
||||
Name: name,
|
||||
Key: key,
|
||||
Namespace: &namespace,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateStore(t *testing.T) {
|
||||
testCases := []ValidateStoreTestCase{
|
||||
{
|
||||
store: makeSecretStore("", baseURL, makeAuth(authName, authNamespace, authKey)),
|
||||
err: fmt.Errorf("received invalid Chef SecretStore resource: missing username"),
|
||||
},
|
||||
{
|
||||
store: makeSecretStore(name, "", makeAuth(authName, authNamespace, authKey)),
|
||||
err: fmt.Errorf("received invalid Chef SecretStore resource: missing serverurl"),
|
||||
},
|
||||
{
|
||||
store: makeSecretStore(name, baseURL, nil),
|
||||
err: fmt.Errorf("received invalid Chef SecretStore resource: cannot initialize Chef Client: no valid authType was specified"),
|
||||
},
|
||||
{
|
||||
store: makeSecretStore(name, baseInvalidURL, makeAuth(authName, authNamespace, authKey)),
|
||||
err: fmt.Errorf("received invalid Chef SecretStore resource: invalid serverurl: parse \"invalid base URL/\": invalid URI for request"),
|
||||
},
|
||||
{
|
||||
store: makeSecretStore(name, noEndSlashInvalidBaseURL, makeAuth(authName, authNamespace, authKey)),
|
||||
err: fmt.Errorf("received invalid Chef SecretStore resource: serverurl does not end with slash(/)"),
|
||||
},
|
||||
{
|
||||
store: makeSecretStore(name, baseURL, makeAuth(authName, authNamespace, "")),
|
||||
err: fmt.Errorf("received invalid Chef SecretStore resource: missing Secret Key"),
|
||||
},
|
||||
{
|
||||
store: makeSecretStore(name, baseURL, makeAuth(authName, authNamespace, authKey)),
|
||||
err: fmt.Errorf("received invalid Chef SecretStore resource: namespace not allowed with namespaced SecretStore"),
|
||||
},
|
||||
{
|
||||
store: &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: nil,
|
||||
},
|
||||
},
|
||||
err: fmt.Errorf("received invalid Chef SecretStore resource: missing provider"),
|
||||
},
|
||||
{
|
||||
store: &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
Chef: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
err: fmt.Errorf("received invalid Chef SecretStore resource: missing chef provider"),
|
||||
},
|
||||
}
|
||||
pc := Providerchef{}
|
||||
for _, tc := range testCases {
|
||||
_, err := pc.ValidateStore(tc.store)
|
||||
if tc.err != nil && err != nil && err.Error() != tc.err.Error() {
|
||||
t.Errorf("test failed! want: %v, got: %v", tc.err, err)
|
||||
} else if tc.err == nil && err != nil {
|
||||
t.Errorf("want: nil got: err %v", err)
|
||||
} else if tc.err != nil && err == nil {
|
||||
t.Errorf("want: err %v got: nil", tc.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
store := &esv1beta1.SecretStore{TypeMeta: metav1.TypeMeta{Kind: "ClusterSecretStore"},
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
Chef: &esv1beta1.ChefProvider{
|
||||
Auth: makeAuth(authName, authNamespace, authKey),
|
||||
UserName: name,
|
||||
ServerURL: baseURL,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected := fmt.Sprintf("could not fetch SecretKey Secret: secrets %q not found", authName)
|
||||
expectedMissingStore := "missing or invalid spec: missing store"
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
kube := clientfake.NewClientBuilder().WithObjects(&corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "creds",
|
||||
Namespace: "default",
|
||||
}, TypeMeta: metav1.TypeMeta{
|
||||
Kind: kind,
|
||||
APIVersion: apiversion,
|
||||
},
|
||||
}).Build()
|
||||
|
||||
pc := Providerchef{databagService: &fake.ChefMockClient{}}
|
||||
_, errMissingStore := pc.NewClient(ctx, nil, kube, "default")
|
||||
if !ErrorContains(errMissingStore, expectedMissingStore) {
|
||||
t.Errorf("CheckNewClient unexpected error: %s, expected: '%s'", errMissingStore.Error(), expectedMissingStore)
|
||||
}
|
||||
_, err := pc.NewClient(ctx, store, kube, "default")
|
||||
if !ErrorContains(err, expected) {
|
||||
t.Errorf("CheckNewClient unexpected error: %s, expected: '%s'", err.Error(), expected)
|
||||
}
|
||||
}
|
||||
|
||||
func ErrorContains(out error, want string) bool {
|
||||
if out == nil {
|
||||
return want == ""
|
||||
}
|
||||
if want == "" {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(out.Error(), want)
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
pc := Providerchef{}
|
||||
var mockClient *fake.ChefMockClient
|
||||
pc.userService = mockClient
|
||||
pc.clientName = "correctUser"
|
||||
_, err := pc.Validate()
|
||||
t.Log("Error: ", err)
|
||||
pc.clientName = "wrongUser"
|
||||
_, err = pc.Validate()
|
||||
t.Log("Error: ", err)
|
||||
}
|
||||
|
||||
func TestCapabilities(t *testing.T) {
|
||||
pc := Providerchef{}
|
||||
capabilities := pc.Capabilities()
|
||||
t.Log(capabilities)
|
||||
}
|
||||
|
||||
// Test Cases To be added when Close function is implemented.
|
||||
func TestClose(_ *testing.T) {
|
||||
pc := Providerchef{}
|
||||
pc.Close(context.Background())
|
||||
}
|
||||
|
||||
// Test Cases To be added when GetAllSecrets function is implemented.
|
||||
func TestGetAllSecrets(_ *testing.T) {
|
||||
pc := Providerchef{}
|
||||
pc.GetAllSecrets(context.Background(), esv1beta1.ExternalSecretFind{})
|
||||
}
|
||||
|
||||
// Test Cases To be implemented when DeleteSecret function is implemented.
|
||||
func TestDeleteSecret(_ *testing.T) {
|
||||
pc := Providerchef{}
|
||||
pc.DeleteSecret(context.Background(), nil)
|
||||
}
|
||||
|
||||
// Test Cases To be implemented when PushSecret function is implemented.
|
||||
func TestPushSecret(_ *testing.T) {
|
||||
pc := Providerchef{}
|
||||
pc.PushSecret(context.Background(), &corev1.Secret{}, nil)
|
||||
}
|
104
pkg/provider/chef/fake/fake.go
Normal file
104
pkg/provider/chef/fake/fake.go
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
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 fake
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/go-chef/chef"
|
||||
)
|
||||
|
||||
const (
|
||||
CORRECTUSER = "correctUser"
|
||||
testitem = "item03"
|
||||
DatabagName = "databag01"
|
||||
)
|
||||
|
||||
type ChefMockClient struct {
|
||||
getItem func(databagName string, databagItem string) (item chef.DataBagItem, err error)
|
||||
listItems func(name string) (data *chef.DataBagListResult, err error)
|
||||
getUser func(name string) (user chef.User, err error)
|
||||
}
|
||||
|
||||
func (mc *ChefMockClient) GetItem(databagName, databagItem string) (item chef.DataBagItem, err error) {
|
||||
return mc.getItem(databagName, databagItem)
|
||||
}
|
||||
|
||||
func (mc *ChefMockClient) ListItems(name string) (data *chef.DataBagListResult, err error) {
|
||||
return mc.listItems(name)
|
||||
}
|
||||
|
||||
func (mc *ChefMockClient) Get(name string) (user chef.User, err error) {
|
||||
if name == CORRECTUSER {
|
||||
user = chef.User{
|
||||
UserName: name,
|
||||
}
|
||||
err = nil
|
||||
} else {
|
||||
user = chef.User{}
|
||||
err = errors.New("message")
|
||||
}
|
||||
return user, err
|
||||
}
|
||||
|
||||
func (mc *ChefMockClient) WithItem(_, _ string, _ error) {
|
||||
if mc != nil {
|
||||
mc.getItem = func(dataBagName, databagItemName string) (item chef.DataBagItem, err error) {
|
||||
ret := make(map[string]interface{})
|
||||
switch {
|
||||
case dataBagName == DatabagName && databagItemName == "item01":
|
||||
jsonstring := `{"id":"` + dataBagName + `-` + databagItemName + `","some_key":"fe7f29ede349519a1","some_password":"dolphin_123zc","some_username":"testuser"}`
|
||||
ret[databagItemName] = jsonstring
|
||||
case dataBagName == "databag03" && databagItemName == testitem:
|
||||
jsonMap := make(map[string]string)
|
||||
jsonMap["id"] = testitem
|
||||
jsonMap["findProperty"] = "foundProperty"
|
||||
return jsonMap, nil
|
||||
case dataBagName == DatabagName && databagItemName == testitem:
|
||||
return math.Inf(1), nil
|
||||
default:
|
||||
str := "https://chef.com/organizations/dev/data/" + dataBagName + "/" + databagItemName + ": 404"
|
||||
return nil, errors.New(str)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *ChefMockClient) WithListItems(_ string, _ error) {
|
||||
if mc != nil {
|
||||
mc.listItems = func(databagName string) (data *chef.DataBagListResult, err error) {
|
||||
ret := make(chef.DataBagListResult)
|
||||
if databagName == DatabagName {
|
||||
jsonstring := fmt.Sprintf("https://chef.com/organizations/dev/data/%s/item01", databagName)
|
||||
ret["item01"] = jsonstring
|
||||
} else {
|
||||
return nil, fmt.Errorf("data bag not found: %s", databagName)
|
||||
}
|
||||
return &ret, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *ChefMockClient) WithUser(_ string, _ error) {
|
||||
if mc != nil {
|
||||
mc.getUser = func(name string) (user chef.User, err error) {
|
||||
return chef.User{
|
||||
UserName: name,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@ import (
|
|||
_ "github.com/external-secrets/external-secrets/pkg/provider/alibaba"
|
||||
_ "github.com/external-secrets/external-secrets/pkg/provider/aws"
|
||||
_ "github.com/external-secrets/external-secrets/pkg/provider/azure/keyvault"
|
||||
_ "github.com/external-secrets/external-secrets/pkg/provider/chef"
|
||||
_ "github.com/external-secrets/external-secrets/pkg/provider/conjur"
|
||||
_ "github.com/external-secrets/external-secrets/pkg/provider/delinea"
|
||||
_ "github.com/external-secrets/external-secrets/pkg/provider/doppler"
|
||||
|
|
Loading…
Reference in a new issue