Search

Integrating Conjur with Kubernetes Secrets

This article demonstrates how to set up Conjur to automatically populate Kubernetes Secrets with secrets stored in Conjur using the CyberArk Secrets Provider for Kubernetes. There are two ways to set up the Secrets Provider for Kubernetes. We can use:

  • An Init container, in which case the Secrets Provider for Kubernetes is deployed in the same pod as the application. It can only provide secrets to the application in the pod.
  • An application container, in which case the Secrets Provider for Kubernetes is deployed in its pod in a namespace and can provide secrets for all applications and pods in the same namespace.

The first few steps for both ways are the same but diverge later in the process. For this article, we’ll begin by reviewing the common steps, then branch into the two different procedures.

Our Tutorial Scenario

Let’s suppose our application connects to a database (DB). The connection string to that database consists of the DB name, username, and password. The DB name is relatively low risk, but we want to secure the username and password in Conjur. Let’s go through the steps of how to do this.

Prerequisites

Before we begin, we need to have a Kubernetes cluster running with a service account for our application and a terminal with all the necessary tools for the init container. We also need the Kubernetes Authenticator configured and enabled. For more information on how to do this, refer to the previous articles or review the official documentation on Enable Authenticators for Applications.

Common Setup

The common setup to both methods previously mentioned involves:

  1. Defining the application as a Conjur host policy
  2. Defining and granting access to secrets
  3. Mapping Conjur variables to Kubernetes Secrets
  4. Binding the cluster role to a Kubernetes service account

After these steps, the setup splits into a procedure for the Init container and a procedure for the application container.

These steps need to be performed by two roles, the Conjur admin and the application developer. The Conjur admin has administration rights in Conjur and the right to view the actual values of the secrets. The application developer is responsible for the deployment and maintenance of the application but does not need the rights to view the values of the secrets.

The Conjur admin needs to perform the first three steps listed above. Then, the application developer takes over and finalizes the setup with some input from the Conjur admin on how to reference the Conjur environment correctly.

Defining the Application as a Conjur Host Policy

In a previous article, Setting Up the Conjur Kubernetes Authenticator , we set up user roles and groups that we reuse here.

Let’s start by defining an environment shell file to hold all our environment variables, as follows:

secrets/env.sh

				
					APP_NAME=test-app
APP_NAMESPACE=test-app-namespace
APP_SERVICE_ACCOUNT_NAME=test-app-sa

AUTHENTICATOR_ID="dev"

APP_AUTHENTICATION_CONTAINER_NAME="secretless"

OSS_CONJUR_SERVICE_ACCOUNT_NAME="conjur-cluster"
OSS_CONJUR_NAMESPACE="conjur-server"
				
			

Now we generate the policy file, secrets/app.yml, in the secrets directory. It adds our application to Conjur.

				
					. ./secrets/env.sh
cat << EOL > ./secrets/app.yml
---
- !policy
  id: conjur/authn-k8s/${AUTHENTICATOR_ID}/apps
  owner: !group devops
  annotations:
    description: Identities permitted to authenticate
  body:
  - !layer
    annotations:
      description: Layer of authenticator identities permitted to call authn svc
  - &hosts
    - !host
      id: ${APP_NAME}
      annotations:
        authn-k8s/namespace: ${APP_NAMESPACE}
        authn-k8s/service-account: ${APP_SERVICE_ACCOUNT_NAME}
        authn-k8s/authentication-container-name: ${APP_AUTHENTICATION_CONTAINER_NAME}
        kubernetes: "true"
  - !grant
    role: !layer
    members: *hosts
EOL
				
			

We can review the script using the following code:

				
					cat secrets/app.yml
				
			

Once we have confirmation that our script was generated correctly, we load it.

				
					conjur policy load root /root/secrets/app.yml
				
			

This permits Conjur to authenticate our test-app using the dev Kubernetes Authenticator.

Defining and Granting Access to Secrets

Now we can define our secrets using variables. Since we expose the values of secrets, this has to be performed by the Conjur admin.

Let’s create a policy for the username and password variables.

				
					. ./secrets/env.sh
cat << EOL > ./secrets/app-secrets.yml
---
- !policy
  id: app/${APP_NAMESPACE}
  owner: !group kube_admin
  annotations:
    description: This policy contains the credentials to access the DB in conjur.

  body:
    - &variables
      - !variable username
      - !variable password
  
- !layer layer

- !permit
      resource: *variables
      privileges: [ read, execute ]
      roles: !layer layer

   
EOL
				
			

Load the policy into Conjur.

				
					conjur policy load root /root/secrets/app-secrets.yml
				
			

Confirm that we successfully added our variables by executing:

				
					conjur list
				
			
Now we assign values to the variables. We use the values myUser as the username and MyP@ssw0rd1 as the password. Our database name is myappDB, but since we decided that we don’t need to add it to Conjur, we leave it as is.
				
					conjur variable values add app/test-app-namespace/username &ldquo;myUser&rdquo;
conjur variable values add app/test-app-namespace/password &ldquo;MyP@ssw0rd1&rdquo;
				
			
The message confirms that we have assigned our variables.

Mapping Conjur Variables to Kubernetes Secrets

From this point on, the application developer takes over to finalize the setup for the application, and the Conjur admin only provides necessary information, such as the public certificate and public URL for the Conjur environment. We modify the original secrets files used to load the secrets in Kubernetes to reference the Conjur variable names instead of actual values. So, if the policy below is our original secrets file in Kubernetes, we use this command: Db-credentials.yml
				
					---
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
  annotations:
    kubernetes.io/service-account.name: "test-app-sa"
type: Opaque
data:
  DBName:   bXlhcHBEQg==
  username: dGhlLXVzZXItbmFtZQ==
  password: dGhlLXBhc3N3b3Jk
				
			

This swaps the username and password with a map to the variables, as shown below.

				
					---
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
data:
  DBName:  bXlhcHBEQg==
stringData:
  conjur-map: |-   
    username: app/test-app-namespace/username
    password: app/test-app-namespace/password
				
			
The conjur-map specifies Conjur variables instead of actual values. Note that secrets stored in Kubernetes are in base64 encoding. Policies applied with text values generate an error stating that the values can’t be decoded. Next, we apply our new secrets/db-credentials.yml file to Kubernetes.
				
					kubectl apply -f secrets/db-credentials.yml
				
			

Our updates are applied to Kubernetes, which now uses our mapping instead of trying to decode the base64 value.

Binding a Cluster Role to a Kubernetes Service Account

Kubernetes will not decode the secrets, but it cannot handle the values yet. So, we must bind our Conjur Service Account to our Cluster Authenticator role. These values were created during the setup and configuration of the Conjur Kubernetes Authenticator. This was covered in the previous article, Setting Up the Conjur Kubernetes Authenticator .

Let’s generate the file for binding to our test-app-namespace.

				
					. ./secrets/env.sh
cat << EOL > ./secrets/role-binding.yml
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: conjur-authn-rolebinding
  namespace: ${APP_NAMESPACE}
subjects:
- kind: ServiceAccount
  name: ${OSS_CONJUR_SERVICE_ACCOUNT_NAME}
  namespace: ${OSS_CONJUR_NAMESPACE}
roleRef:
  kind: ClusterRole
  name: my-cluster-role
  apiGroup: rbac.authorization.k8s.io
  
EOL
				
			

Now, we apply it.

				
					kubectl apply -f secrets/role-binding.yml
				
			

One thing to note is that before we applied our role binding, we didn’t touch anything in the Kubernetes cluster. Because all of the configurations are in Conjur, you might have come across a namespace error. This is because the namespace does not exist in Kubernetes yet, and this is the first time we are trying to assign anything to that namespace.

We create the namespace in Kubernetes by creating a secrets/namespace.yml file.

				
					. ./secrets/env.sh
cat << EOL > ./secrets/namespace.yml
---
apiVersion: v1
kind: Namespace
metadata:
  name: ${APP_NAMESPACE}
EOL
				
			

And apply it with:

				
					kubectl apply -f secrets/namespace.yml
				
			
The setup now follows different paths based on the way you want to set up the Secrets Provider.

Init Container

The setup for the Init container includes:
  • Creating and binding roles to the application service account
  • Setting up a configuration map
  • Setting up the application deployment manifest

Creating and Binding Roles to the Application Service Account

We must create a role that is bound to our test-app service account. This allows the Secrets Provider for Kubernetes to update Kubernetes Secrets with values from Conjur variables. secrets/secret-access-role.yml Load the role manifest into Kubernetes, as follows:
				
					kubectl apply -f secrets/secret-access-role.yml
				
			

Setting Up a Configuration Map

To create the configuration map, the Conjur admin must provide the following items to the application developer:

  • The Conjur URL, which is a domain name server. In our case, it is https://conjur.demo.com as in the previous article, Setting Up the Conjur Kubernetes Authenticator.
  • The Kubernetes Authenticator service ID for the Kubernetes cluster. For us, the value is dev.
  • The public certificate for Conjur, retrieved by the Conjur admin using the following command:
				
					openssl s_client -showcerts -connect conjur.demo.com:9443 < /dev/null 2> /dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'
				
			

To save the certificate to a file called conjur.pem, append the command with:

				
					> conjur.pem
				
			

We now set up the variables that we need with the information provided by the Conjur admin.

				
					CONJUR_SSL_CERTIFICATE=conjur.pem
CONJUR_APPLIANCE_URL=https://conjur.demo.com:9443
CONJUR_AUTHN_URL=$CONJUR_APPLIANCE_URL/authn-k8s/dev
CONJUR_ACCOUNT=default
				
			

Once all the variables are set, we create the configuration map using those variables. We do a dry run first to ensure the values are correct by executing the following:

				
					kubectl create configmap conjur-cm -n test-app-namespace \
  -o yaml \
  --dry-run=client \
  --from-literal CONJUR_ACCOUNT=${CONJUR_ACCOUNT} \
  --from-literal CONJUR_APPLIANCE_URL=${CONJUR_APPLIANCE_URL} \
  --from-literal CONJUR_AUTHN_URL=${CONJUR_AUTHN_URL} \
  --from-file "CONJUR_SSL_CERTIFICATE=${CONJUR_SSL_CERTIFICATE}"
				
			

If the values are correct, we execute the command to create and apply the configuration map.

				
					kubectl create configmap conjur-cm -n test-app-namespace \
  -o yaml \
  --dry-run=client \
  --from-literal CONJUR_ACCOUNT=${CONJUR_ACCOUNT} \
  --from-literal CONJUR_APPLIANCE_URL=${CONJUR_APPLIANCE_URL} \
  --from-literal CONJUR_AUTHN_URL=${CONJUR_AUTHN_URL} \
  --from-file "CONJUR_SSL_CERTIFICATE=${CONJUR_SSL_CERTIFICATE}" | kubectl apply -f -
				
			

The command returns a message stating that the configuration map was created. Or, if it already exists, it states that it was configured.

Setting Up the Application Deployment Manifest

The final step is to set up deployment. We do that by creating the following deployment file:

				
					. ./secrets/env.sh
cat << EOL > ./secrets/deployment.yml
---

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: ${APP_NAME}
  name: ${APP_NAME}
  namespace: ${APP_NAMESPACE}
spec:
  selector:
    matchLabels:
      app: ${APP_NAME}
  replicas: 1
  template:
    metadata:
      labels:
        app: ${APP_NAME}
    spec:
      serviceAccountName: ${APP_SERVICE_ACCOUNT_NAME}
      containers:
        - name: ${APP_NAME}
          image: myorg/test-app
          ports:
            - containerPort: 8080
          env:
            - name: DB_USERNAME
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: username
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: password
      initContainers:
      - image: cyberark/secrets-provider-for-k8s
        imagePullPolicy: Always
        name: cyberark-secrets-provider-for-k8s
        env:
          - name: CONJUR_AUTHN_LOGIN
            value: host/test-app
          - name: CONTAINER_MODE
            value: init
          - name: MY_POD_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: MY_POD_NAMESPACE
            valueFrom:
              fieldRef:
                fieldPath: metadata.namespace
          - name: K8S_SECRETS
            value: db-credentials
          - name: SECRETS_DESTINATION
            value: k8s_secrets
        envFrom:
          - configMapRef:
              name: conjur-cm
EOL
				
			

Apply the script with the following command:

				
					kubectl apply -f secrets/deployment.yml
				
			
And that concludes the init container setup.

Application Container

The setup for the init container includes:
  • Configuring the Secrets Provider using a Helm chart
  • Deploying the Secrets Provider for Kubernetes using a Helm chart

Configuring the Secrets Provider Using a Helm Chart

To set up the Secrets Provider as a separate application container, we generate a custom-values.yaml file that applies mandatory configuration values. These are replacement values for the default values in the values.yaml file provided by a Helm chart. Custom-values.yaml
				
					cat << EOL > ./secrets/custom-values.yaml
environment:
  k8sSecrets: [db-credentials]
  conjur:
    account: default
    applianceUrl: https://conjur-oss.cyberark.svc.cluster.local
    authnUrl: https://conjur-oss.cyberark.svc.cluster.local/authn-k8s/dev
    authnLogin:  host/secrets-provider-app
    sslCertificate:
      value: ""
EOL
				
			

We must be sure that we save the file in the same location in which we deploy the Secrets Provider using a Helm chart.

Deploying the Secrets Provider for Kubernetes Using a Helm Chart

We export the Conjur public certificate and save it to a file called conjur.pem. This is the same as in the Init container setup.

				
					openssl s_client -showcerts -connect conjur.demo.com:9443 < /dev/null 2> /dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > conjur.pem
				
			

Next, we access the Secrets Provider Helm chart in CyberArk’s Helm-Chart repository. This step was performed in the Article Setting Up the Conjur Kubernetes Authenticator,

				
					helm repo add cyberark https://cyberark.github.io/helm-charts
				
			

Once the Helm-Chart repo named cyberark is in place, we can install the secrets provider.

				
					helm install secrets-provider cyberark/secrets-provider -f secrets/custom-values.yaml --set-file environment.conjur.sslCertificate.value=conjur.pem -n test-app-namespace
				
			

When our resources are ready, the Secrets Provider job starts. Our application can now consume Kubernetes Secrets.

Limitation

A limitation that we must take note of is that the Secrets Provider is limited to fetching up to 50 Conjur secrets with an average of 100 characters in length for the variable paths.

Conclusion

Should you run into any trouble, check out CyberArk’s Troubleshooting page to enable logs and diagnose common issues.

For more information on the init container, check out Secrets Provider for Kubernetes – Init Container.

For more information about the application container, be sure to check out Secrets Provider for Kubernetes – Application Container.

If you’re interested in developing expert technical content that performs, let’s have a conversation today.

Facebook
Twitter
LinkedIn
Reddit
Email

POST INFORMATION

If you work in a tech space and aren’t sure if we cover you, hit the button below to get in touch with us. Tell us a little about your content goals or your project, and we’ll reach back within 2 business days. 

Share via
Copy link
Powered by Social Snap