Browse Source

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>
Sourav Patnaik 2 years ago
parent
commit
a012f4829c

+ 38 - 0
apis/externalsecrets/v1beta1/secretstore_chef_types.go

@@ -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"`
+}

+ 4 - 0
apis/externalsecrets/v1beta1/secretstore_types.go

@@ -141,6 +141,10 @@ type SecretStoreProvider struct {
 	// https://docs.delinea.com/online-help/products/devops-secrets-vault/current
 	// https://docs.delinea.com/online-help/products/devops-secrets-vault/current
 	// +optional
 	// +optional
 	Delinea *DelineaProvider `json:"delinea,omitempty"`
 	Delinea *DelineaProvider `json:"delinea,omitempty"`
+
+	// Chef configures this store to sync secrets with chef server
+	// +optional
+	Chef *ChefProvider `json:"chef,omitempty"`
 }
 }
 
 
 type CAProviderType string
 type CAProviderType string

+ 57 - 0
apis/externalsecrets/v1beta1/zz_generated.deepcopy.go

@@ -419,6 +419,58 @@ func (in *CertAuth) DeepCopy() *CertAuth {
 }
 }
 
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *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) {
 func (in *ClusterExternalSecret) DeepCopyInto(out *ClusterExternalSecret) {
 	*out = *in
 	*out = *in
 	out.TypeMeta = in.TypeMeta
 	out.TypeMeta = in.TypeMeta
@@ -1992,6 +2044,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
 		*out = new(DelineaProvider)
 		*out = new(DelineaProvider)
 		(*in).DeepCopyInto(*out)
 		(*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.
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreProvider.

+ 50 - 0
config/crds/bases/external-secrets.io_clustersecretstores.yaml

@@ -2202,6 +2202,56 @@ spec:
                     required:
                     required:
                     - vaultUrl
                     - vaultUrl
                     type: object
                     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:
                   conjur:
                     description: Conjur configures this store to sync secrets using
                     description: Conjur configures this store to sync secrets using
                       conjur provider
                       conjur provider

+ 50 - 0
config/crds/bases/external-secrets.io_secretstores.yaml

@@ -2202,6 +2202,56 @@ spec:
                     required:
                     required:
                     - vaultUrl
                     - vaultUrl
                     type: object
                     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:
                   conjur:
                     description: Conjur configures this store to sync secrets using
                     description: Conjur configures this store to sync secrets using
                       conjur provider
                       conjur provider

+ 86 - 0
deploy/crds/bundle.yaml

@@ -2665,6 +2665,49 @@ spec:
                       required:
                       required:
                         - vaultUrl
                         - vaultUrl
                       type: object
                       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:
                     conjur:
                       description: Conjur configures this store to sync secrets using conjur provider
                       description: Conjur configures this store to sync secrets using conjur provider
                       properties:
                       properties:
@@ -7639,6 +7682,49 @@ spec:
                       required:
                       required:
                         - vaultUrl
                         - vaultUrl
                       type: object
                       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:
                     conjur:
                       description: Conjur configures this store to sync secrets using conjur provider
                       description: Conjur configures this store to sync secrets using conjur provider
                       properties:
                       properties:

+ 131 - 0
docs/api/spec.md

@@ -1108,6 +1108,123 @@ External Secrets meta/v1.SecretKeySelector
 </tr>
 </tr>
 </tbody>
 </tbody>
 </table>
 </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 &ldquo;/&rdquo;</p>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1beta1.ClusterExternalSecret">ClusterExternalSecret
 <h3 id="external-secrets.io/v1beta1.ClusterExternalSecret">ClusterExternalSecret
 </h3>
 </h3>
 <p>
 <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>
 <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>
 </td>
 </tr>
 </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>
 </tbody>
 </table>
 </table>
 <h3 id="external-secrets.io/v1beta1.SecretStoreRef">SecretStoreRef
 <h3 id="external-secrets.io/v1beta1.SecretStoreRef">SecretStoreRef

+ 113 - 0
docs/provider/chef.md

@@ -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 - 0
docs/snippets/chef-external-secret.yaml

@@ -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 - 0
go.mod

@@ -151,6 +151,7 @@ require (
 	github.com/fatih/color v1.16.0 // indirect
 	github.com/fatih/color v1.16.0 // indirect
 	github.com/fsnotify/fsnotify v1.7.0 // indirect
 	github.com/fsnotify/fsnotify v1.7.0 // indirect
 	github.com/ghodss/yaml v1.0.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-logr/zapr v1.3.0 // indirect
 	github.com/go-openapi/errors v0.21.0 // indirect
 	github.com/go-openapi/errors v0.21.0 // indirect
 	github.com/go-openapi/jsonpointer v0.20.2 // indirect
 	github.com/go-openapi/jsonpointer v0.20.2 // indirect

+ 6 - 0
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/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/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/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 h1:vjaMkw0geJsA+ikMM6UDLg4VLFQWKo/B0i9IWlOQ1f0=
 github.com/cyberark/conjur-api-go v0.11.1/go.mod h1:n1p46Hj9l8wkZjM17cVYdfcatyPboWyioLGlC0QszCs=
 github.com/cyberark/conjur-api-go v0.11.1/go.mod h1:n1p46Hj9l8wkZjM17cVYdfcatyPboWyioLGlC0QszCs=
 github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs=
 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/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 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 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 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-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/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/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 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
 github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
 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/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.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=

+ 1 - 0
hack/api-docs/mkdocs.yml

@@ -89,6 +89,7 @@ nav:
     - AWS Secrets Manager: provider/aws-secrets-manager.md
     - AWS Secrets Manager: provider/aws-secrets-manager.md
     - AWS Parameter Store: provider/aws-parameter-store.md
     - AWS Parameter Store: provider/aws-parameter-store.md
     - Azure Key Vault: provider/azure-key-vault.md
     - Azure Key Vault: provider/azure-key-vault.md
+    - Chef: provider/chef.md
     - CyberArk Conjur: provider/conjur.md
     - CyberArk Conjur: provider/conjur.md
     - Google Cloud Secret Manager: provider/google-secrets-manager.md
     - Google Cloud Secret Manager: provider/google-secrets-manager.md
     - HashiCorp Vault: provider/hashicorp-vault.md
     - HashiCorp Vault: provider/hashicorp-vault.md

+ 343 - 0
pkg/provider/chef/chef.go

@@ -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 - 0
pkg/provider/chef/chef_test.go

@@ -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 - 0
pkg/provider/chef/fake/fake.go

@@ -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
+		}
+	}
+}

+ 1 - 0
pkg/provider/register/register.go

@@ -22,6 +22,7 @@ import (
 	_ "github.com/external-secrets/external-secrets/pkg/provider/alibaba"
 	_ "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/aws"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/azure/keyvault"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/azure/keyvault"
+	_ "github.com/external-secrets/external-secrets/pkg/provider/chef"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/conjur"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/conjur"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/delinea"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/delinea"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/doppler"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/doppler"