This document describes how to integrate CyberArk Secrets Management (Conjur) with Kubernetes or OpenShift. The integration is done using the External Secrets Operator (ESO). As the dummy application that reads secrets we use the image nmatsui/hello-world-api.
To follow this guide, you need to have a Conjur instance (Cloud, Enterprise or Open Source) running. And you need to have a Kubernetes cluster running. If you don't have one, you can use Minikube or k3s to simulate a cluster locally or on your own server/VM.
The following tools (on your local machine) are required to follow this guide:
In this demo we deploy the External Secrets Operator, Demo Application and the Reload in the hello-world-eso namespace.
First, we need to install the External Secrets Operator (ESO) in our Kubernetes cluster.
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets \
external-secrets/external-secrets \
-n hello-world-eso \
--create-namespace \
--set installCRDs=true
To make sure Kubernetes can access Conjur, we need to create a secret with the Conjur URL and the API key.
Create a host in conjur that can access to at least one secret. In this documentation we assume
that the host is named host/testing/database-backup-script1
and has access to variable
testing/database-password1
. And that the api key of this host is api-key
.
Kubernetes Secrets are base64 encoded. So we need to convert the API key and host id to base64:
echo -n "host/serverxyz" | base64 # Output: dGVzdGluZy9kYXRhYmFzZS1wYXNzd29yZDE=
echo -n "host-api-key" | base64 # Output: YXBpLWtleQ==
Create a file (secret.yml) with the following content (replace the placeholders <host_id_base64>
and <host_id_base64>
with the base64 encoded values):
---
apiVersion: v1
kind: Secret
metadata:
name: conjur-creds
data:
host_id: <host_id_base64>
host_api_key: <host_id_base64>
and apply the secret to the cluster (for this demo we use the namespace hello-world-eso
):
kubectl apply -f secret.yml -n hello-world-eso
NB: Storing a secret in code is not recommended. In a production environment you should use a
Now we need to create an External Secret Store that points to the Conjur secret (secret-store.yml). The External Secret Store is a custom resource that tells the External Secrets Operator how to connect to the secret store (in this case Conjur).
---
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: conjur
spec:
provider:
conjur:
# Service URL
url: https://conjur.url
# [OPTIONAL] base64 encoded string of the Conjur certificate
# caBundle: <ca_bundle>
auth:
apikey:
# conjur account
account: admin
userRef: # Get this from K8S secret
name: conjur-creds
key: host_id
apiKeyRef: # Get this from K8S secret
name: conjur-creds
key: host_api_key
Apply the External Secret Store to the cluster:
kubectl apply -f secret-store.yml -n hello-world-eso
Now we can create an External Secret that points to the Conjur secret (external-secret.yml). The external secret will map the Conjur secret to a Kubernetes secret.
---
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: DB_PASSWORD1 # The key of the secret in Kubernetes
remoteRef:
key: testing/database-password1 # Reference to the Conjur secret
Apply the External Secret to the cluster:
kubectl apply -f external-secret.yml -n hello-world-eso
Now we can create a dummy application that reads the secret from Kubernetes
(hello-world.yml. We use the image nmatsui/hello-world-api
for this.
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world
spec:
replicas: 1
selector:
matchLabels:
app: hello-world
template:
metadata:
labels:
app: hello-world
spec:
containers:
- name: hello-world
image: nmatsui/hello-world-api:latest
imagePullPolicy: Always
ports:
- containerPort: 8000
env:
- name: DB_PASSWORD1 # The name of the environment variable for in the container;
valueFrom:
secretKeyRef:
name: conjur
key: DB_PASSWORD1 # The key of the secret in Kubernetes;
Apply the dummy application to the cluster:
kubectl apply -f hello-world.yml -n hello-world-eso
The following tests can be done to see if the integration works.
To test if the integration works, you can read the secret in the following way:
kubectl get secret -n hello-world-eso conjur -o jsonpath="{.data.DB_PASSWORD1}" | base64 --decode && echo
This is a convenient way to see if the secret is available in Kubernetes and to check if a secret changed after a rotation in Conjur.
To see if the secret is available in the dummy application, you can do:
- Get the pod name of the dummy application:
kubectl get pods -n hello-world-eso
Assume that the output contains:
NAME READY STATUS RESTARTS AGE
hello-world-5fd89f4c49-l2psr 1/1 Running 0 42m
- Read the secret from the container:
kubectl exec -it hello-world-5fd89f4c49-l2psr -n hello-world-eso -- env | grep DB_PASSWORD1
The output should be like <secret_value>
The External Secrets Operator can automatically reload pods when a secret changes. To enable this feature, you can use the package Stakater's Reloader. This package watches for changes in secrets and configmaps and reloads the pods that use them.
- Install the Reloader package:
helm repo add stakater https://stakater.github.io/stakater-charts
helm install stakater/reloader -n hello-world-eso --set reloader.watchGlobally=false --generate-name
- Add the annotation
reloader.stakater.com/auto: "true"
to the deployment of the dummy application (hello-world.yml
). This results in the following yaml file:
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world
annotations: # Add this line
reloader.stakater.com/auto: "true" # Add this line
spec:
replicas: 1
selector:
matchLabels:
app: hello-world
template:
metadata:
labels:
app: hello-world
spec:
containers:
- name: hello-world
image: nmatsui/hello-world-api:latest
imagePullPolicy: Always
ports:
- containerPort: 8000
env:
- name: DB_PASSWORD1
valueFrom:
secretKeyRef:
name: conjur
key: DB_PASSWORD1
- Apply the changes to the cluster:
kubectl apply -f hello-world.yml -n hello-world-eso
When you update a secret in Conjur, the pods will automatically be recreated to reflect the change.
The following sources have been used for this how-to: