Kubernetes Authentication with Conjur

One of the biggest challenges in containerized environments like Kubernetes is the secure distribution of credentials and other secrets needed for applications to do useful work. Using a secrets vault like CyberArk Conjur is a great way to keep your sensitive data under lock and key, but how do you then protect secrets as they’re being distributed to containers? Conjur’s Kubernetes integration provides a Kubernetes-native solution that distributes secrets securely through mutual TLS established with SPIFFE-compliant x509 certificates.

Verifying Identity with Mutual TLS

A standard TLS connection is established between a client and server by performing a TLS handshake. The server certificate given to the client during the handshake allows the client to verify the server’s identity through certificate chain of trust and asymmetric cryptography.

Mutual TLS requires that the client also present a public certificate to the server. Once this connection is established, each party can verify the other’s identity and be sure it is only sending messages to the intended recipient. This two-way identity verification is crucial when distributing secrets. In our Kubernetes integration, we use Conjur to issue a public certificate to the client.

The SPIFFE Standard

Conjur leverages the Kubernetes API to securely issue x509 certificates. These certificates use a SPIFFE-compliant resource ID as the Subject Alternative Name field value. Conforming to SPIFFE provides certificate recipients with unique identities that will be trusted by any services that already consume SPIFFE.

Conjur with Kubernetes

Conjur’s Kubernetes integration consists of two pieces which can be thought of as the server and client in the TLS handshake. The first piece is a plugin to Conjur that adds Kubernetes authentication capabilities. Conjur (with this plugin installed) is the server. The second piece of the integration is a sidecar container that is deployed alongside a user’s application. This sidecar container handles the authentication with Conjur on behalf of the application. This sidecar container is the client.

Verifying Conjur

When a deployed Conjur instance is initialized, it generates a self-signed public certificate. Any application that wishes to talk directly to Conjur must use this certificate to establish a secure TLS connection. To make the certificate available to an application pod, the certificate is copied from a Conjur pod and stored in a ConfigMap:

$ ssl_cert=$(kubectl exec [conjur-pod-name] -- cat /opt/conjur/etc/ssl/conjur.pem)

$ kubectl create configmap conjur-public-cert --from-file=ssl-certificate=<(echo "$ssl_cert")

We then provide this cert to the containers in our application pod by modifying the deployment manifest to create an environment variable in each container:

- name: CONJUR_SSL_CERTIFICATE
  valueFrom:
    configMapKeyRef:
      name: conjur-public-cert
      key: ssl-certificate

Now that the sidecar has Conjur’s public certificate, all requests made to Conjur are over a secure one-way TLS connection. To achieve mutual TLS, the sidecar must have its own public certificate to present to Conjur.

Verifying the Sidecar

To obtain its own certificate, the sidecar uses Conjur’s public certificate to establish a secure one-way TLS connection with Conjur and send over a Certificate Signing Request that includes the following fields:

  • Common Name (CN): this is the host identity that will be used in Conjur policy to represent the application and looks something like [my-namespace].deployment.[my-application]
  • Subject Alternate Name (SAN): this is a SPIFFE-compliant ID representing the application pod and looks something like spiffe://cluster.local/namespace/[my-namespace]/podname/[my-pod]
  • The public key from a key pair generated by the sidecar
  • A signature to prevent tampering

At this point it’s important to note that the connection between Conjur and the sidecar is not yet mutual TLS. That is, the sidecar can be sure that it’s communicating with Conjur, but the reverse is not true. Conjur has no way of knowing that it’s communicating with the real sidecar. Conjur cannot safely return a certificate in response to the CSR without risking it reaching an unintended recipient. This opens the possibility of an attacker providing a CSR with a SAN for a different resource and receiving a certificate that can be used to identify falsely as the spoofed resource.

To ensure that certificates are only delivered to the resources specified in the CSR, Conjur uses the Kubernetes API to inject the certificate directly into the pod specified by the SAN field. This guarantees that a certificate will not be issued to an attacker presenting a spoofed CSR. After injecting the certificate, Conjur returns an empty success response to the CSR request, signaling to the sidecar that it can now read its certificate from pod memory.

Using Mutual TLS to Authenticate

With its freshly-minted certificate in hand, the sidecar can now establish a mutual TLS connection with Conjur. This allows both parties to safely communicate with the correct host during authentication. The sidecar authenticates with Conjur and receives a JWT access token, which the sidecar writes to an in-memory volume mount that is shared with the application container.

The application can now use this access token to retrieve secrets from Conjur using Summon or one of Conjur’s API clients. One-way TLS is sufficient for this connection since possession of the access token, which is only accessible from within the application’s pod, is enough to verify the application’s identity.

Check It Out

The Kubernetes authenticator can currently be used with Conjur Enterprise. You can find more details about setting up this integration in our documentation. Conjur open-source is also freely-available through GitHub or conjur.org with Kubernetes integration coming soon!

Thanks for reading. Come visit us on the CyberArk Commons if you have any questions or feedback. We would love to hear from you.