Browse Source

Add Conjur provider (#2412)

* Add Conjur provider

Signed-off-by: David Hisel <David.Hisel@CyberArk.com>

* fix: lint

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>

* fix: unit tests

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>

---------

Signed-off-by: David Hisel <David.Hisel@CyberArk.com>
Signed-off-by: David Hisel <132942678+davidh-cyberark@users.noreply.github.com>
Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>
Co-authored-by: Moritz Johner <beller.moritz@googlemail.com>
David Hisel 2 years ago
parent
commit
de491a2790

+ 2 - 0
.gitignore

@@ -63,3 +63,5 @@ override.tf.json
 terraform.rc
 **/secrets/**
 .terraform.lock.hcl
+
+tmp/

+ 23 - 4
Makefile

@@ -221,19 +221,29 @@ docs.serve: ## Serve docs
 # ====================================================================================
 # Build Artifacts
 
+.PHONY: build.all
 build.all: docker.build helm.build ## Build all artifacts (docker image, helm chart)
 
-docker.image:
+.PHONY: docker.image
+docker.image:  ## Emit IMAGE_NAME:IMAGE_TAG
 	@echo $(IMAGE_NAME):$(IMAGE_TAG)
 
-docker.tag:
+.PHONY: docker.imagename
+docker.imagename:  ## Emit IMAGE_NAME
+	@echo $(IMAGE_NAME)
+
+.PHONY: docker.tag
+docker.tag:  ## Emit IMAGE_TAG
 	@echo $(IMAGE_TAG)
 
+.PHONY: docker.build
 docker.build: $(addprefix build-,$(ARCH)) ## Build the docker image
 	@$(INFO) docker build
-	@docker build -f $(DOCKERFILE) . $(DOCKER_BUILD_ARGS) -t $(IMAGE_NAME):$(IMAGE_TAG)
+	echo docker build -f $(DOCKERFILE) . $(DOCKER_BUILD_ARGS) -t $(IMAGE_NAME):$(IMAGE_TAG)
+	docker build -f $(DOCKERFILE) . $(DOCKER_BUILD_ARGS) -t $(IMAGE_NAME):$(IMAGE_TAG)
 	@$(OK) docker build
 
+.PHONY: docker.push
 docker.push: ## Push the docker image to the registry
 	@$(INFO) docker push
 	@docker push $(IMAGE_NAME):$(IMAGE_TAG)
@@ -244,6 +254,7 @@ docker.push: ## Push the docker image to the registry
 RELEASE_TAG ?= $(IMAGE_TAG)
 SOURCE_TAG ?= $(VERSION)$(TAG_SUFFIX)
 
+.PHONY: docker.promote
 docker.promote: ## Promote the docker image to the registry
 	@$(INFO) promoting $(SOURCE_TAG) to $(RELEASE_TAG)
 	docker manifest inspect --verbose $(IMAGE_NAME):$(SOURCE_TAG) > .tagmanifest
@@ -282,6 +293,14 @@ tf.show.%: ## Runs terrform show for a provider and outputs to a file
 # ====================================================================================
 # Help
 
+.PHONY: help
 # only comments after make target name are shown as help text
 help: ## Displays this help message
-	@echo -e "$$(grep -hE '^\S+:.*##' $(MAKEFILE_LIST) | sed -e 's/:.*##\s*/:/' -e 's/^\(.\+\):\(.*\)/\\x1b[36m\1\\x1b[m:\2/' | column -c2 -t -s : | sort)"
+	@echo -e "$$(grep -hE '^\S+:.*##' $(MAKEFILE_LIST) | sed -e 's/:.*##\s*/|/' -e 's/^\(.\+\):\(.*\)/\\x1b[36m\1\\x1b[m:\2/' | column -c2 -t -s'|' | sort)"
+
+
+.PHONY: clean
+clean:  ## Clean bins
+	@$(INFO) clean
+	@rm -f $(OUTPUT_DIR)/external-secrets-linux-*
+	@$(OK) go build $*

+ 33 - 0
apis/externalsecrets/v1beta1/secretstore_conjur_types.go

@@ -0,0 +1,33 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1beta1
+
+import esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+
+type ConjurProvider struct {
+	URL      string     `json:"url"`
+	CABundle string     `json:"caBundle,omitempty"`
+	Auth     ConjurAuth `json:"auth"`
+}
+
+type ConjurAuth struct {
+	Apikey *ConjurApikey `json:"apikey"`
+}
+
+type ConjurApikey struct {
+	Account   string                    `json:"account"`
+	UserRef   *esmeta.SecretKeySelector `json:"userRef"`
+	APIKeyRef *esmeta.SecretKeySelector `json:"apiKeyRef"`
+}

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

@@ -132,6 +132,10 @@ type SecretStoreProvider struct {
 	// KeeperSecurity configures this store to sync secrets using the KeeperSecurity provider
 	// +optional
 	KeeperSecurity *KeeperSecurityProvider `json:"keepersecurity,omitempty"`
+
+	// Conjur configures this store to sync secrets using conjur provider
+	// +optional
+	Conjur *ConjurProvider `json:"conjur,omitempty"`
 }
 
 type CAProviderType string

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

@@ -641,6 +641,67 @@ func (in *ClusterSecretStoreList) DeepCopyObject() runtime.Object {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ConjurApikey) DeepCopyInto(out *ConjurApikey) {
+	*out = *in
+	if in.UserRef != nil {
+		in, out := &in.UserRef, &out.UserRef
+		*out = new(metav1.SecretKeySelector)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.APIKeyRef != nil {
+		in, out := &in.APIKeyRef, &out.APIKeyRef
+		*out = new(metav1.SecretKeySelector)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConjurApikey.
+func (in *ConjurApikey) DeepCopy() *ConjurApikey {
+	if in == nil {
+		return nil
+	}
+	out := new(ConjurApikey)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ConjurAuth) DeepCopyInto(out *ConjurAuth) {
+	*out = *in
+	if in.Apikey != nil {
+		in, out := &in.Apikey, &out.Apikey
+		*out = new(ConjurApikey)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConjurAuth.
+func (in *ConjurAuth) DeepCopy() *ConjurAuth {
+	if in == nil {
+		return nil
+	}
+	out := new(ConjurAuth)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ConjurProvider) DeepCopyInto(out *ConjurProvider) {
+	*out = *in
+	in.Auth.DeepCopyInto(&out.Auth)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConjurProvider.
+func (in *ConjurProvider) DeepCopy() *ConjurProvider {
+	if in == nil {
+		return nil
+	}
+	out := new(ConjurProvider)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *DopplerAuth) DeepCopyInto(out *DopplerAuth) {
 	*out = *in
 	in.SecretRef.DeepCopyInto(&out.SecretRef)
@@ -1795,6 +1856,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
 		*out = new(KeeperSecurityProvider)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.Conjur != nil {
+		in, out := &in.Conjur, &out.Conjur
+		*out = new(ConjurProvider)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreProvider.

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

@@ -2192,6 +2192,76 @@ spec:
                     required:
                     - vaultUrl
                     type: object
+                  conjur:
+                    description: Conjur configures this store to sync secrets using
+                      conjur provider
+                    properties:
+                      auth:
+                        properties:
+                          apikey:
+                            properties:
+                              account:
+                                type: string
+                              apiKeyRef:
+                                description: A reference to a specific 'key' within
+                                  a Secret resource, In some instances, `key` is a
+                                  required field.
+                                properties:
+                                  key:
+                                    description: The key of the entry in the Secret
+                                      resource's `data` field to be used. Some instances
+                                      of this field may be defaulted, in others it
+                                      may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: Namespace of the resource being referred
+                                      to. Ignored if referent is not cluster-scoped.
+                                      cluster-scoped defaults to the namespace of
+                                      the referent.
+                                    type: string
+                                type: object
+                              userRef:
+                                description: A reference to a specific 'key' within
+                                  a Secret resource, In some instances, `key` is a
+                                  required field.
+                                properties:
+                                  key:
+                                    description: The key of the entry in the Secret
+                                      resource's `data` field to be used. Some instances
+                                      of this field may be defaulted, in others it
+                                      may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: Namespace of the resource being referred
+                                      to. Ignored if referent is not cluster-scoped.
+                                      cluster-scoped defaults to the namespace of
+                                      the referent.
+                                    type: string
+                                type: object
+                            required:
+                            - account
+                            - apiKeyRef
+                            - userRef
+                            type: object
+                        required:
+                        - apikey
+                        type: object
+                      caBundle:
+                        type: string
+                      url:
+                        type: string
+                    required:
+                    - auth
+                    - url
+                    type: object
                   doppler:
                     description: Doppler configures this store to sync secrets using
                       the Doppler provider

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

@@ -2192,6 +2192,76 @@ spec:
                     required:
                     - vaultUrl
                     type: object
+                  conjur:
+                    description: Conjur configures this store to sync secrets using
+                      conjur provider
+                    properties:
+                      auth:
+                        properties:
+                          apikey:
+                            properties:
+                              account:
+                                type: string
+                              apiKeyRef:
+                                description: A reference to a specific 'key' within
+                                  a Secret resource, In some instances, `key` is a
+                                  required field.
+                                properties:
+                                  key:
+                                    description: The key of the entry in the Secret
+                                      resource's `data` field to be used. Some instances
+                                      of this field may be defaulted, in others it
+                                      may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: Namespace of the resource being referred
+                                      to. Ignored if referent is not cluster-scoped.
+                                      cluster-scoped defaults to the namespace of
+                                      the referent.
+                                    type: string
+                                type: object
+                              userRef:
+                                description: A reference to a specific 'key' within
+                                  a Secret resource, In some instances, `key` is a
+                                  required field.
+                                properties:
+                                  key:
+                                    description: The key of the entry in the Secret
+                                      resource's `data` field to be used. Some instances
+                                      of this field may be defaulted, in others it
+                                      may be required.
+                                    type: string
+                                  name:
+                                    description: The name of the Secret resource being
+                                      referred to.
+                                    type: string
+                                  namespace:
+                                    description: Namespace of the resource being referred
+                                      to. Ignored if referent is not cluster-scoped.
+                                      cluster-scoped defaults to the namespace of
+                                      the referent.
+                                    type: string
+                                type: object
+                            required:
+                            - account
+                            - apiKeyRef
+                            - userRef
+                            type: object
+                        required:
+                        - apikey
+                        type: object
+                      caBundle:
+                        type: string
+                      url:
+                        type: string
+                    required:
+                    - auth
+                    - url
+                    type: object
                   doppler:
                     description: Doppler configures this store to sync secrets using
                       the Doppler provider

+ 102 - 0
deploy/crds/bundle.yaml

@@ -2061,6 +2061,57 @@ spec:
                       required:
                         - vaultUrl
                       type: object
+                    conjur:
+                      description: Conjur configures this store to sync secrets using conjur provider
+                      properties:
+                        auth:
+                          properties:
+                            apikey:
+                              properties:
+                                account:
+                                  type: string
+                                apiKeyRef:
+                                  description: A reference to a specific 'key' within a Secret resource, In some instances, `key` is a required field.
+                                  properties:
+                                    key:
+                                      description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      type: string
+                                    namespace:
+                                      description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                                      type: string
+                                  type: object
+                                userRef:
+                                  description: A reference to a specific 'key' within a Secret resource, In some instances, `key` is a required field.
+                                  properties:
+                                    key:
+                                      description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      type: string
+                                    namespace:
+                                      description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                                      type: string
+                                  type: object
+                              required:
+                                - account
+                                - apiKeyRef
+                                - userRef
+                              type: object
+                          required:
+                            - apikey
+                          type: object
+                        caBundle:
+                          type: string
+                        url:
+                          type: string
+                      required:
+                        - auth
+                        - url
+                      type: object
                     doppler:
                       description: Doppler configures this store to sync secrets using the Doppler provider
                       properties:
@@ -5656,6 +5707,57 @@ spec:
                       required:
                         - vaultUrl
                       type: object
+                    conjur:
+                      description: Conjur configures this store to sync secrets using conjur provider
+                      properties:
+                        auth:
+                          properties:
+                            apikey:
+                              properties:
+                                account:
+                                  type: string
+                                apiKeyRef:
+                                  description: A reference to a specific 'key' within a Secret resource, In some instances, `key` is a required field.
+                                  properties:
+                                    key:
+                                      description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      type: string
+                                    namespace:
+                                      description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                                      type: string
+                                  type: object
+                                userRef:
+                                  description: A reference to a specific 'key' within a Secret resource, In some instances, `key` is a required field.
+                                  properties:
+                                    key:
+                                      description: The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be defaulted, in others it may be required.
+                                      type: string
+                                    name:
+                                      description: The name of the Secret resource being referred to.
+                                      type: string
+                                    namespace:
+                                      description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                                      type: string
+                                  type: object
+                              required:
+                                - account
+                                - apiKeyRef
+                                - userRef
+                              type: object
+                          required:
+                            - apikey
+                          type: object
+                        caBundle:
+                          type: string
+                        url:
+                          type: string
+                      required:
+                        - auth
+                        - url
+                      type: object
                     doppler:
                       description: Doppler configures this store to sync secrets using the Doppler provider
                       properties:

+ 146 - 0
docs/api/spec.md

@@ -1643,6 +1643,138 @@ Kubernetes meta/v1.LabelSelector
 </tr>
 </tbody>
 </table>
+<h3 id="external-secrets.io/v1beta1.ConjurApikey">ConjurApikey
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1beta1.ConjurAuth">ConjurAuth</a>)
+</p>
+<p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>account</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>userRef</code></br>
+<em>
+<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#SecretKeySelector">
+External Secrets meta/v1.SecretKeySelector
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>apiKeyRef</code></br>
+<em>
+<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#SecretKeySelector">
+External Secrets meta/v1.SecretKeySelector
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1beta1.ConjurAuth">ConjurAuth
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1beta1.ConjurProvider">ConjurProvider</a>)
+</p>
+<p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>apikey</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.ConjurApikey">
+ConjurApikey
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+</tbody>
+</table>
+<h3 id="external-secrets.io/v1beta1.ConjurProvider">ConjurProvider
+</h3>
+<p>
+(<em>Appears on:</em>
+<a href="#external-secrets.io/v1beta1.SecretStoreProvider">SecretStoreProvider</a>)
+</p>
+<p>
+</p>
+<table>
+<thead>
+<tr>
+<th>Field</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>
+<code>url</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>caBundle</code></br>
+<em>
+string
+</em>
+</td>
+<td>
+</td>
+</tr>
+<tr>
+<td>
+<code>auth</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.ConjurAuth">
+ConjurAuth
+</a>
+</em>
+</td>
+<td>
+</td>
+</tr>
+</tbody>
+</table>
 <h3 id="external-secrets.io/v1beta1.DopplerAuth">DopplerAuth
 </h3>
 <p>
@@ -4719,6 +4851,20 @@ KeeperSecurityProvider
 <p>KeeperSecurity configures this store to sync secrets using the KeeperSecurity provider</p>
 </td>
 </tr>
+<tr>
+<td>
+<code>conjur</code></br>
+<em>
+<a href="#external-secrets.io/v1beta1.ConjurProvider">
+ConjurProvider
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>Conjur configures this store to sync secrets using conjur provider</p>
+</td>
+</tr>
 </tbody>
 </table>
 <h3 id="external-secrets.io/v1beta1.SecretStoreRef">SecretStoreRef

+ 20 - 10
docs/contributing/devguide.md

@@ -88,23 +88,33 @@ make crds.uninstall
 
 If you need to test some other k8s integrations and need the operator to be deployed to the actual cluster while developing, you can use the following workflow:
 
-```
+```shell
+# Start a local K8S cluster with KinD
 kind create cluster --name external-secrets
 
-export TAG=v2
-export IMAGE=eso-local
+export TAG=$(make docker.tag)
+export IMAGE=$(make docker.imagename)
 
-#For building in linux
-docker build . -t $IMAGE:$TAG --build-arg TARGETARCH=amd64 --build-arg TARGETOS=linux
+# Build docker image
+make docker.build
 
-#For building in MacOS (OSX)
-#docker build . -t $IMAGE:$TAG --build-arg TARGETARCH=amd64 --build-arg TARGETOS=darwin
+# Load docker image into local kind cluster
+kind load docker-image $IMAGE:$TAG -n external-secrets
 
-#For building in ARM
-#docker build . -t $IMAGE:$TAG --build-arg TARGETARCH=arm --build-arg TARGETOS=linux
+# (Optional) Pull the image from GitHub Repo to copy into kind
+#docker pull ghcr.io/external-secrets/external-secrets:v0.8.2
+#kind load docker-image ghcr.io/external-secrets/external-secrets:v0.8.2 -n external-secrets
 
+# Update helm charts and install to KinD cluster
 make helm.generate
-helm upgrade --install external-secrets ./deploy/charts/external-secrets/ --set image.repository=$IMAGE --set image.tag=$TAG
+helm upgrade --install external-secrets ./deploy/charts/external-secrets/ \
+--set image.repository=$IMAGE --set image.tag=$TAG \
+--set webhook.image.repository=$IMAGE --set webhook.image.tag=$TAG \
+--set certController.image.repository=$IMAGE --set certController.image.tag=$TAG
+
+
+# Command to delete the cluster when done
+# kind delete cluster -n external-secrets
 ```
 
 !!! note "Contributing Flow"

+ 2 - 0
docs/introduction/stability-support.md

@@ -50,6 +50,7 @@ The following table describes the stability level of each provider and who's res
 | [Doppler SecretOps Platform](https://external-secrets.io/latest/provider/doppler)                          |   alpha   |                                                                                         [@ryan-blunden](https://github.com/ryan-blunden/) [@nmanoogian](https://github.com/nmanoogian/) |
 | [Keeper Security](https://www.keepersecurity.com/)                                                         |   alpha   |                                                                                                                                              [@ppodevlab](https://github.com/ppodevlab) |
 | [Scaleway](https://external-secrets.io/latest/provider/scaleway)                                           |   alpha   |                                                                                                                                                   [@azert9](https://github.com/azert9/) |
+| [Conjur](https://external-secrets.io/latest/provider/conjur)                                               |   alpha   |                                                                                                                                 [@davidh-cyberark](https://github.com/davidh-cyberark/) |
 
 ## Provider Feature Support
 
@@ -75,6 +76,7 @@ The following table show the support for features across different providers.
 | Doppler                   |      x       |              |                      |                         |        x         |             |                             |
 | Keeper Security           |      x       |              |                      |                         |        x         |      x      |                             |
 | Scaleway                  |      x       |      x       |                      |                         |        x         |      x      |              x              |
+| Conjur                    |              |              |                      |                         |        x         |             |                             |
 
 ## Support Policy
 

+ 101 - 0
docs/provider/conjur.md

@@ -0,0 +1,101 @@
+## Conjur Provider
+
+The following sections outline what is needed to get your external-secrets Conjur provider setup.
+
+### Pre-requirements
+
+This section contains the list of the pre-requirements before installing the Conjur Provider.
+
+*   Running Conjur Server
+    -   These items will be needed in order to configure the secret-store
+        +   Conjur endpoint - include the scheme but no trailing '/', ex: https://myapi.example.com
+        +   Conjur credentials (hostid, apikey)
+        +   Certificate for Conjur server is OPTIONAL -- But, **when using a self-signed cert when setting up your Conjur server, it is strongly recommended to populate "caBundle" with self-signed cert in the secret-store definition**
+*   Kubernetes cluster
+    -   External Secrets Operator is installed
+
+### Create External Secret Store Definition
+
+Recommend to save as filename: `conjur-secret-store.yaml`
+
+```yaml
+{% include 'conjur-secret-store.yaml' %}
+```
+
+### Create External Secret Definition
+
+Important note: **Creds must live in the same namespace as a SecretStore  - the secret store may only reference secrets from the same namespace.**  When using a ClusterSecretStore this limitation is lifted and the creds can live in any namespace.
+
+Recommend to save as filename: `conjur-external-secret.yaml`
+
+```yaml
+{% include 'conjur-external-secret.yaml' %}}
+```
+
+### Create Kubernetes Secrets
+
+In order for the ESO **Conjur** provider to connect to the Conjur server, the creds should be stored as k8s secrets.  Please refer to <https://kubernetes.io/docs/concepts/configuration/secret/#creating-a-secret> for various methods to create secrets.  Here is one way to do it using `kubectl`
+
+***NOTE***: "conjur-creds" is the "name" used in "userRef" and "apikeyRef" in the conjur-secret-store definition
+
+```shell
+# This is all one line
+kubectl -n external-secrets create secret generic conjur-creds --from-literal=hostid=MYCONJURHOSTID --from-literal=apikey=MYAPIKEY
+
+# Example:
+# kubectl -n external-secrets create secret generic conjur-creds --from-literal=hostid=host/data/app1/host001 --from-literal=apikey=321blahblah
+```
+
+### Create the External Secrets Store
+
+```shell
+# WARNING: this will create the store configuration in the "external-secrets" namespace, adjust this to your own situation
+#
+kubectl apply -n external-secrets -f conjur-secret-store.yaml
+
+# WARNING: running the delete command will delete the secret store configuration
+#
+# If there is a need to delete the external secretstore
+# kubectl delete secretstore -n external-secrets conjur
+```
+
+### Create the External Secret
+
+```shell
+# WARNING: this will create the external-secret configuration in the "external-secrets" namespace, adjust this to your own situation
+#
+kubectl apply -n external-secrets -f conjur-external-secret.yaml
+
+# WARNING: running the delete command will delete the external-secrets configuration
+#
+# If there is a need to delete the external secret
+# kubectl delete externalsecret -n external-secrets conjur
+```
+
+### Getting the K8S Secret
+
+* Login to your Conjur server and verify that your secret exists
+* Review the value of your kubernetes secret to see that it contains the same value from Conjur
+
+```shell
+# WARNING: this command will reveal the stored secret in plain text
+#
+# Assuming the secret name is "secret00", this will show the value
+kubectl get secret -n external-secrets conjur -o jsonpath="{.data.secret00}"  | base64 --decode && echo
+```
+
+### Support
+
+Copyright (c) 2023 CyberArk Software Ltd. All rights reserved.
+
+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.

+ 14 - 0
docs/snippets/conjur-external-secret.yaml

@@ -0,0 +1,14 @@
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+  name: conjur
+spec:
+  refreshInterval: 10s
+  secretStoreRef:
+    # This name must match the metadata.name in the `SecretStore`
+    name: conjur
+    kind: SecretStore
+  data:
+  - secretKey: secret00
+    remoteRef:
+      key: data/app1/secret00

+ 21 - 0
docs/snippets/conjur-secret-store.yaml

@@ -0,0 +1,21 @@
+apiVersion: external-secrets.io/v1beta1
+kind: SecretStore
+metadata:
+  name: conjur
+spec:
+  provider:
+    conjur:
+      # Service URL
+      url: https://myapi.conjur.org
+      # [OPTIONAL] base64 encoded string of certificate
+      caBundle: OPTIONALxFIELDxxxBase64xCertxString==  
+      auth:
+        apikey:
+          # conjur account
+          account: conjur
+          userRef: # Get this from K8S secret
+            name: conjur-creds
+            key: hostid
+          apiKeyRef: # Get this from K8S secret
+            name: conjur-creds
+            key: apikey

+ 7 - 0
go.mod

@@ -71,6 +71,7 @@ require (
 	github.com/alibabacloud-go/tea-utils/v2 v2.0.1
 	github.com/aliyun/credentials-go v1.3.0
 	github.com/avast/retry-go/v4 v4.3.4
+	github.com/cyberark/conjur-api-go v0.11.0
 	github.com/golang-jwt/jwt/v5 v5.0.0
 	github.com/hashicorp/golang-lru v0.5.4
 	github.com/hashicorp/vault/api/auth/aws v0.4.1
@@ -84,19 +85,25 @@ require (
 
 require (
 	cloud.google.com/go/compute/metadata v0.2.3 // indirect
+	github.com/alessio/shellescape v1.4.1 // indirect
 	github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect
 	github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 // indirect
 	github.com/alibabacloud-go/endpoint-util v1.1.1 // indirect
 	github.com/alibabacloud-go/tea-utils v1.4.5 // indirect
 	github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
+	github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
 	github.com/clbanning/mxj/v2 v2.5.7 // indirect
+	github.com/danieljoos/wincred v1.1.2 // indirect
 	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
 	github.com/go-jose/go-jose/v3 v3.0.0 // indirect
 	github.com/go-playground/validator/v10 v10.14.1 // indirect
+	github.com/godbus/dbus/v5 v5.1.0 // indirect
 	github.com/google/s2a-go v0.1.4 // indirect
 	github.com/hashicorp/go-secure-stdlib/awsutil v0.2.2 // indirect
 	github.com/hashicorp/go-uuid v1.0.3 // indirect
+	github.com/sirupsen/logrus v1.9.0 // indirect
 	github.com/tjfoc/gmsm v1.4.1 // indirect
+	github.com/zalando/go-keyring v0.2.2 // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
 )

+ 16 - 0
go.sum

@@ -111,6 +111,8 @@ github.com/ahmetb/gen-crd-api-reference-docs v0.3.0/go.mod h1:TdjdkYhlOifCQWPs1U
 github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
 github.com/akeylesslabs/akeyless-go-cloud-id v0.3.4 h1:vTckjyBhHOBiOWSC/oaEU2Oo4OH5eAlQiwKu2RMxsFg=
 github.com/akeylesslabs/akeyless-go-cloud-id v0.3.4/go.mod h1:As/RomC2w/fa3y+yHRlVHPmkbP+zrKBFRow41y5dk+E=
+github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
+github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
 github.com/akeylesslabs/akeyless-go/v3 v3.3.7 h1:g4G7sRt165zVzgRBBTGTwAr3SgEucnkh2/kLPvQBP7o=
 github.com/akeylesslabs/akeyless-go/v3 v3.3.7/go.mod h1:XtPdOYw+rrG9bxyBzuQ5zAGCUTd364R2grs7GsZ5nos=
 github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo=
@@ -162,6 +164,8 @@ github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLj
 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
+github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
 github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
@@ -190,6 +194,10 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH
 github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/cyberark/conjur-api-go v0.11.0 h1:LIkdS0zSi2o9AlOwqrIAowxg26kyPFG+XOZSK0dq9dc=
+github.com/cyberark/conjur-api-go v0.11.0/go.mod h1:AbU7bDVW6ygUdgTDCKkh4wyfIVrOtdEeE/r01OE1EYo=
+github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
+github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -270,6 +278,8 @@ github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnD
 github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
 github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
+github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
@@ -574,6 +584,8 @@ github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR
 github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
 github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
@@ -644,6 +656,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/zalando/go-keyring v0.2.2 h1:f0xmpYiSrHtSNAVgwip93Cg8tuF45HJM6rHq/A5RI/4=
+github.com/zalando/go-keyring v0.2.2/go.mod h1:sI3evg9Wvpw3+n4SqplGSJUMwtDeROfD4nsFz4z9PG0=
 go.mongodb.org/mongo-driver v1.11.7 h1:LIwYxASDLGUg/8wOhgOOZhX8tQa/9tgZPgzZoVqJvcs=
 go.mongodb.org/mongo-driver v1.11.7/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqblyjsfuh8oXY=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@@ -853,9 +867,11 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

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

@@ -88,6 +88,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
+    - CyberArk Conjur: provider/conjur.md
     - Google Cloud Secret Manager: provider/google-secrets-manager.md
     - HashiCorp Vault: provider/hashicorp-vault.md
     - Kubernetes: provider/kubernetes.md

+ 202 - 0
pkg/provider/conjur/LICENSE

@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright (c) 2023 CyberArk Software Ltd. All rights reserved.
+
+   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.

File diff suppressed because it is too large
+ 312 - 0
pkg/provider/conjur/NOTICES.txt


+ 30 - 0
pkg/provider/conjur/fake/fake.go

@@ -0,0 +1,30 @@
+/*
+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"
+)
+
+type ConjurMockClient struct {
+}
+
+func (mc *ConjurMockClient) RetrieveSecret(secret string) (result []byte, err error) {
+	if secret == "error" {
+		err = errors.New("error")
+		return nil, err
+	}
+	return []byte("secret"), nil
+}

+ 230 - 0
pkg/provider/conjur/provider.go

@@ -0,0 +1,230 @@
+// Package conjur provides a Conjur provider for External Secrets.
+/*
+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 conjur
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"strings"
+
+	"github.com/cyberark/conjur-api-go/conjurapi"
+	"github.com/cyberark/conjur-api-go/conjurapi/authn"
+	corev1 "k8s.io/api/core/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+	"github.com/external-secrets/external-secrets/pkg/provider/conjur/util"
+	"github.com/external-secrets/external-secrets/pkg/utils"
+)
+
+var (
+	errConjurClient     = "cannot setup new Conjur client: %w"
+	errBadCertBundle    = "caBundle failed to base64 decode: %w"
+	errBadServiceUser   = "could not get Auth.Apikey.UserRef: %w"
+	errBadServiceAPIKey = "could not get Auth.Apikey.ApiKeyRef: %w"
+)
+
+// Provider is a provider for Conjur.
+type Provider struct {
+	ConjurClient Client
+	StoreKind    string
+	kube         client.Client
+	namespace    string
+}
+
+// Client is an interface for the Conjur client.
+type Client interface {
+	RetrieveSecret(secret string) (result []byte, err error)
+}
+
+// NewClient creates a new Conjur client.
+func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
+	prov, err := util.GetConjurProvider(store)
+	if err != nil {
+		return nil, err
+	}
+	p.StoreKind = store.GetObjectKind().GroupVersionKind().Kind
+	p.kube = kube
+	p.namespace = namespace
+
+	certBytes, decodeErr := utils.Decode(esv1beta1.ExternalSecretDecodeBase64, []byte(prov.CABundle))
+	if decodeErr != nil {
+		return nil, fmt.Errorf(errBadCertBundle, decodeErr)
+	}
+	cert := string(certBytes)
+
+	config := conjurapi.Config{
+		Account:      prov.Auth.Apikey.Account,
+		ApplianceURL: prov.URL,
+		SSLCert:      cert,
+	}
+
+	conjUser, secErr := p.secretKeyRef(ctx, prov.Auth.Apikey.UserRef)
+	if secErr != nil {
+		return nil, fmt.Errorf(errBadServiceUser, secErr)
+	}
+	conjAPIKey, secErr := p.secretKeyRef(ctx, prov.Auth.Apikey.APIKeyRef)
+	if secErr != nil {
+		return nil, fmt.Errorf(errBadServiceAPIKey, secErr)
+	}
+
+	conjur, err := conjurapi.NewClientFromKey(config,
+		authn.LoginPair{
+			Login:  conjUser,
+			APIKey: conjAPIKey,
+		},
+	)
+
+	if err != nil {
+		return nil, fmt.Errorf(errConjurClient, err)
+	}
+	p.ConjurClient = conjur
+	return p, nil
+}
+
+// GetAllSecrets returns all secrets from the provider.
+// NOT IMPLEMENTED.
+func (p *Provider) GetAllSecrets(_ context.Context, _ esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
+	// TO be implemented
+	return nil, fmt.Errorf("GetAllSecrets not implemented")
+}
+
+// GetSecret returns a single secret from the provider.
+func (p *Provider) GetSecret(_ context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
+	secretValue, err := p.ConjurClient.RetrieveSecret(ref.Key)
+	if err != nil {
+		return nil, err
+	}
+
+	return secretValue, nil
+}
+
+// PushSecret will write a single secret into the provider.
+func (p *Provider) PushSecret(_ context.Context, _ []byte, _ esv1beta1.PushRemoteRef) error {
+	// NOT IMPLEMENTED
+	return nil
+}
+
+func (p *Provider) DeleteSecret(_ context.Context, _ esv1beta1.PushRemoteRef) error {
+	// NOT IMPLEMENTED
+	return nil
+}
+
+// GetSecretMap returns multiple k/v pairs from the provider.
+func (p *Provider) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+	// Gets a secret as normal, expecting secret value to be a json object
+	data, err := p.GetSecret(ctx, ref)
+	if err != nil {
+		return nil, fmt.Errorf("error getting secret %s: %w", ref.Key, err)
+	}
+
+	// Maps the json data to a string:string map
+	kv := make(map[string]string)
+	err = json.Unmarshal(data, &kv)
+	if err != nil {
+		return nil, fmt.Errorf("unable to unmarshal secret %s: %w", ref.Key, err)
+	}
+
+	// Converts values in K:V pairs into bytes, while leaving keys as strings
+	secretData := make(map[string][]byte)
+	for k, v := range kv {
+		secretData[k] = []byte(v)
+	}
+	return secretData, nil
+}
+
+// Close closes the provider.
+func (p *Provider) Close(_ context.Context) error {
+	return nil
+}
+
+// Validate validates the provider.
+func (p *Provider) Validate() (esv1beta1.ValidationResult, error) {
+	return esv1beta1.ValidationResultReady, nil
+}
+
+// ValidateStore validates the store.
+func (p *Provider) ValidateStore(store esv1beta1.GenericStore) error {
+	prov, err := util.GetConjurProvider(store)
+	if err != nil {
+		return err
+	}
+
+	if prov.URL == "" {
+		return fmt.Errorf("conjur URL cannot be empty")
+	}
+	if prov.Auth.Apikey != nil {
+		if prov.Auth.Apikey.Account == "" {
+			return fmt.Errorf("missing Auth.ApiKey.Account")
+		}
+		if prov.Auth.Apikey.UserRef == nil {
+			return fmt.Errorf("missing Auth.Apikey.UserRef")
+		}
+		if prov.Auth.Apikey.APIKeyRef == nil {
+			return fmt.Errorf("missing Auth.Apikey.ApiKeyRef")
+		}
+		if err := utils.ValidateReferentSecretSelector(store, *prov.Auth.Apikey.UserRef); err != nil {
+			return fmt.Errorf("invalid Auth.Apikey.UserRef: %w", err)
+		}
+		if err := utils.ValidateReferentSecretSelector(store, *prov.Auth.Apikey.APIKeyRef); err != nil {
+			return fmt.Errorf("invalid Auth.Apikey.ApiKeyRef: %w", err)
+		}
+	}
+
+	// At least one auth must be configured
+	if prov.Auth.Apikey == nil {
+		return fmt.Errorf("missing Auth.* configuration")
+	}
+
+	return nil
+}
+
+// Capabilities returns the provider Capabilities (Read, Write, ReadWrite).
+func (p *Provider) Capabilities() esv1beta1.SecretStoreCapabilities {
+	return esv1beta1.SecretStoreReadOnly
+}
+
+func (p *Provider) secretKeyRef(ctx context.Context, secretRef *esmeta.SecretKeySelector) (string, error) {
+	secret := &corev1.Secret{}
+	ref := client.ObjectKey{
+		Namespace: p.namespace,
+		Name:      secretRef.Name,
+	}
+	if (p.StoreKind == esv1beta1.ClusterSecretStoreKind) &&
+		(secretRef.Namespace != nil) {
+		ref.Namespace = *secretRef.Namespace
+	}
+	err := p.kube.Get(ctx, ref, secret)
+	if err != nil {
+		return "", err
+	}
+
+	keyBytes, ok := secret.Data[secretRef.Key]
+	if !ok {
+		return "", err
+	}
+
+	value := string(keyBytes)
+	valueStr := strings.TrimSpace(value)
+	return valueStr, nil
+}
+
+func init() {
+	esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
+		Conjur: &esv1beta1.ConjurProvider{},
+	})
+}

+ 147 - 0
pkg/provider/conjur/provider_test.go

@@ -0,0 +1,147 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package conjur
+
+import (
+	"context"
+	"fmt"
+	"testing"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+	fakeconjur "github.com/external-secrets/external-secrets/pkg/provider/conjur/fake"
+)
+
+var (
+	svcURL     = "https://example.com"
+	svcUser    = "user"
+	svcApikey  = "apikey"
+	svcAccount = "account1"
+)
+
+type secretManagerTestCase struct {
+	err    error
+	refKey string
+}
+
+func TestConjurGetSecret(t *testing.T) {
+	p := Provider{}
+	p.ConjurClient = &fakeconjur.ConjurMockClient{}
+
+	testCases := []*secretManagerTestCase{
+		{
+			err:    nil,
+			refKey: "secret",
+		},
+		{
+			err:    fmt.Errorf("error"),
+			refKey: "error",
+		},
+	}
+
+	for _, tc := range testCases {
+		ref := makeValidRef(tc.refKey)
+		_, err := p.GetSecret(context.Background(), *ref)
+		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 makeValidRef(k string) *esv1beta1.ExternalSecretDataRemoteRef {
+	return &esv1beta1.ExternalSecretDataRemoteRef{
+		Key:     k,
+		Version: "default",
+	}
+}
+
+type ValidateStoreTestCase struct {
+	store *esv1beta1.SecretStore
+	err   error
+}
+
+func TestValidateStore(t *testing.T) {
+	testCases := []ValidateStoreTestCase{
+		{
+			store: makeSecretStore(svcURL, svcUser, svcApikey, svcAccount),
+			err:   nil,
+		},
+		{
+			store: makeSecretStore("", svcUser, svcApikey, svcAccount),
+			err:   fmt.Errorf("conjur URL cannot be empty"),
+		},
+		{
+			store: makeSecretStore(svcURL, "", svcApikey, svcAccount),
+			err:   fmt.Errorf("missing Auth.Apikey.UserRef"),
+		},
+		{
+			store: makeSecretStore(svcURL, svcUser, "", svcAccount),
+			err:   fmt.Errorf("missing Auth.Apikey.ApiKeyRef"),
+		},
+		{
+			store: makeSecretStore(svcURL, svcUser, svcApikey, ""),
+			err:   fmt.Errorf("missing Auth.ApiKey.Account"),
+		},
+	}
+	p := Provider{}
+	for _, tc := range testCases {
+		err := p.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 makeSecretStore(svcURL, svcUser, svcApikey, svcAccount string) *esv1beta1.SecretStore {
+	uref := &esmeta.SecretKeySelector{
+		Name: "user",
+		Key:  "conjur-hostid",
+	}
+	if svcUser == "" {
+		uref = nil
+	}
+	aref := &esmeta.SecretKeySelector{
+		Name: "apikey",
+		Key:  "conjur-apikey",
+	}
+	if svcApikey == "" {
+		aref = nil
+	}
+	store := &esv1beta1.SecretStore{
+		Spec: esv1beta1.SecretStoreSpec{
+			Provider: &esv1beta1.SecretStoreProvider{
+				Conjur: &esv1beta1.ConjurProvider{
+					URL: svcURL,
+					Auth: esv1beta1.ConjurAuth{
+						Apikey: &esv1beta1.ConjurApikey{
+							Account:   svcAccount,
+							UserRef:   uref,
+							APIKeyRef: aref,
+						},
+					},
+				},
+			},
+		},
+	}
+	return store
+}

+ 52 - 0
pkg/provider/conjur/util/provider.go

@@ -0,0 +1,52 @@
+/*
+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 util
+
+import (
+	"fmt"
+
+	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
+)
+
+const (
+	errNilStore         = "found nil store"
+	errMissingStoreSpec = "store is missing spec"
+	errMissingProvider  = "storeSpec is missing provider"
+	errInvalidProvider  = "invalid provider spec. Missing Conjur field in store %s"
+)
+
+// GetConjurProvider does the necessary nil checks on the generic store
+// it returns the conjur provider or an error.
+func GetConjurProvider(store esv1beta1.GenericStore) (*esv1beta1.ConjurProvider, error) {
+	if store == nil {
+		return nil, fmt.Errorf(errNilStore)
+	}
+	spec := store.GetSpec()
+	if spec == nil {
+		return nil, fmt.Errorf(errMissingStoreSpec)
+	}
+	if spec.Provider == nil {
+		return nil, fmt.Errorf(errMissingProvider)
+	}
+
+	if spec.Provider.Conjur == nil {
+		return nil, fmt.Errorf(errMissingProvider)
+	}
+
+	prov := spec.Provider.Conjur
+	if prov == nil {
+		return nil, fmt.Errorf(errInvalidProvider, store.GetObjectMeta().String())
+	}
+	return prov, 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/aws"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/azure/keyvault"
+	_ "github.com/external-secrets/external-secrets/pkg/provider/conjur"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/doppler"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/fake"
 	_ "github.com/external-secrets/external-secrets/pkg/provider/gcp/secretmanager"