Using Vault as a Certificate Manager on OpenShift

- 13 mins

In this post, we will proceed to try on some of the features that Vault can offer as a secrets manager. One of the feature is that Vault is able to be configured as a certifcate manager. This enables your services to establish their identity and communicate securely over the network with other services or clients internal or external to the cluster.

Jetstack’s cert-manager enables Vault’s PKI secrets engine to dynamically generate X.509 certificates within OpenShift through an Issuer interface. In this guide, we will configure the PKI secrets engine and OpenShift authentication. Then install Jetstack’s cert-manager, configure it to use Vault, and request a certificate.

Most of the steps documented below are taken from Vault documentation here. We are using Red Hat OpenShift as the Kubernetes plaform for the steps below.

Prerequisites

The following are required prior to the setup:

Configure PKI secrets engine

First, enable the PKI secrets engine at the default path on Vault.

$ oc exec -it vault-0 -- /bin/sh
/ $
$ vault secrets enable pki
Success! Enabled the pki secrets engine at: pki/

By default the KPI secrets engine sets the time-to-live (TTL) to 30 days. A certificate can have its lease extended to ensure certificate rotation on a yearly basis (8760h). Tuning of this TTL can be done easily on Vault.

$ vault secrets tune -max-lease-ttl=8760h pki
Success! Tuned the secrets engine at: pki/

Generate a self-signed certificate valid for 8760h

$ vault write pki/root/generate/internal \
    common_name=example.com \
    ttl=8760h
Key              Value
---              -----
certificate      -----BEGIN CERTIFICATE-----
## ...
-----END CERTIFICATE-----
expiration       1619120269
issuing_ca       -----BEGIN CERTIFICATE-----
## ...
-----END CERTIFICATE-----
serial_number    65:37:b5:b3:91:6c:7b:d8:33:22:03:28:b1:58:ff:be:8a:72:a4:c0

Configure the PKI secrets engine certificate issuing and certificate revocation list (CRL) endpoints to use the Vault service in the default namespace.

$ vault write pki/config/urls \
    issuing_certificates="http://vault.default:8200/v1/pki/ca" \
    crl_distribution_points="http://vault.default:8200/v1/pki/crl"
Success! Data written to: pki/config/urls

Configure a role named example-dot-com that enables the creation of certificates example.com domain with any subdomains. The role, example-dot-com, is a logical name that maps to a policy used to generate credentials.

$ vault write pki/roles/example-dot-com \
    allowed_domains=example.com \
    allow_subdomains=true \
    max_ttl=72h
Success! Data written to: pki/roles/example-dot-com

Next, we will also need to define the policy in Vault which maps to all these paths where OpenShift will trigger. This will be attached to the OpenShift service account used later on. Here, we create the policy pki, that enables read access to the PKI secrets engine path. These paths enable the token to view all the roles created for this PKI secrets engine and access the sign and issues operations for the example-dot-com role.

$ vault policy write pki - <<EOF
path "pki*"                        { capabilities = ["read", "list"] }
path "pki/roles/example-dot-com"   { capabilities = ["create", "update"] }
path "pki/sign/example-dot-com"    { capabilities = ["create", "update"] }
path "pki/issue/example-dot-com"   { capabilities = ["create"] }
EOF
Success! Uploaded policy: pki

Configure Kubernetes authentication

Enable the Kubernetes authentication method

$ vault auth enable kubernetes
Success! Enabled kubernetes auth method at: kubernetes/

Configure the Kubernetes authentication method to use the service account token, the location of the Kubernetes host, and its certificate.

$ vault write auth/kubernetes/config \
    token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
    kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
    kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
Success! Data written to: auth/kubernetes/config

The token_reviewer_jwt and kubernetes_ca_cert reference files written to the container by Kubernetes. The environment variable KUBERNETES_PORT_443_TCP_ADDR references the internal network address of the Kubernetes host.

Finally, create a Kubernetes authentication role named issuer that binds the pki policy with a Kubernetes service account named issuer. This isser service account will be used by Jetstack cert-manager later. The role connects the Kubernetes service account, issuer, in the default namespace with the pki Vault policy. The token generated by Vault, issued to service account issuer will be vaild for 20 minutes.

$ vault write auth/kubernetes/role/issuer \
    bound_service_account_names=issuer \
    bound_service_account_namespaces=default \
    policies=pki \
    ttl=20m
Success! Data written to: auth/kubernetes/role/issuer

Exit from the vault-0 pod

$ exit

Deploy Cert Manager

Jetstack’s cert-manager is a Kubernetes add-on that automates the management and issuance of TLS certificates from various issuing sources. Vault can be configured as one of those sources. To understand more about this cert-manager, check out the links below.

Install Jetstack’s cert-manager’s version 0.14.3 resources.

$ oc apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.14.3/cert-manager.crds.yaml
customresourcedefinition.apiextensions.k8s.io/challenges.acme.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/orders.acme.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/clusterissuers.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/issuers.cert-manager.io created

Create a project named cert-manager to host the cert-manager.

$ oc new-project cert-manager
Now using project "cert-manager" on server "https://api.cluster-sgp-3a38.sgp-3a38.example.opentlc.com:6443".

You can add applications to this project with the 'new-app' command. For example, try:

    oc new-app ruby~https://github.com/sclorg/ruby-ex.git

to build a new example application in Python. Or use kubectl to deploy a simple Kubernetes application:

    kubectl create deployment hello-node --image=gcr.io/hello-minikube-zero-install/hello-node

We will add the Helm repository of Jetstack so as to install cert-manager

$ helm repo add jetstack https://charts.jetstack.io
"jetstack" has been added to your repositories

$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "jetstack" chart repository
Update Complete.  Happy Helming!

$ helm install cert-manager \
    --namespace cert-manager \
    --version v0.14.3 \
   jetstack/cert-manager
NAME: cert-manager
## ...

We should be able to see the cert-manager pods running in the cert-manager project

$ oc get pods 
NAME                                      READY   STATUS    RESTARTS   AGE
cert-manager-77fcb5c94-zx2kc              1/1     Running   0          10s
cert-manager-cainjector-8fff85c75-gtw4j   1/1     Running   0          10s
cert-manager-webhook-54647cbd5-pddrt      1/1     Running   0          10s

Configure an issuer and generate a certificate

The cert-manager enables you to define Issuers that interface with the Vault certificate generating endpoints. These Issuers are invoked when a Certificate is created.

Create the issuer service account in the default project

$ oc create serviceaccount issuer -n default
serviceaccount/issuer created

The service account generated a secret that is required by the Issuer.

Get all the secrets in the default namespace. Look for the secret name required by the issuer service account. It should be named similar to issuer-token-xxxxx. Capture the secret name with a variable ISSUER_SECRET_REF.

$ oc get secrets -n default
...
default-token-mlm2n           kubernetes.io/service-account-token   3      13d
issuer-token-lmzpj            kubernetes.io/service-account-token   3      47s
sh.helm.release.v1.vault.v1   helm.sh/release.v1                    1      28m
vault-token-749nd             kubernetes.io/service-account-token   3      28m
...
$ ISSUER_SECRET_REF=$(oc get serviceaccount issuer -o json | jq -r ".secrets[].name")

Create an Issuer custom resource for cert-manager to consume. Name it issuer.yml Then create this object with oc create -f command.

apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
  name: vault-issuer
  namespace: default
spec:
  vault:
    server: http://vault.default
    path: pki/sign/example-dot-com
    auth:
      kubernetes:
        mountPath: /v1/auth/kubernetes
        role: issuer
        secretRef:
          name: $ISSUER_SECRET_REF
          key: token
$ oc create -f issuer.yml
issuer.cert-manager.io/vault-issuer created

Generate a certificate named example-com. Create a file, say certificate.yml with the contents below. Then create the object with oc create -f.

apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: example-com
  namespace: default
spec:
  secretName: example-com-tls
  issuerRef:
    name: vault-issuer
  commonName: www.example.com
  dnsNames:
  - www.example.com
$ oc create -f certificate.yml
certificate.cert-manager.io/example-com created

As seen above, the Certificate, named example-com, requests from Vault the certificate through the Issuer, named vault-issuer. The common name and DNS names are names within the allowed domains for the configured Vault endpoint as defined earlier on in the pki policy.

We can check if the cerificate is being issued successfully by describing the certificate object in the default project.

$ oc describe certificate example-com -n default

Name:         example-com
Namespace:    default
## ...
Events:
  Type    Reason        Age   From          Message
  ----    ------        ----  ----          -------
  Normal  GeneratedKey  9m   cert-manager  Generated a new private key
  Normal  Requested     9m   cert-manager  Created new CertificateRequest resource "example-com-1055631450"
  Normal  Issued        10s   cert-manager  Certificate issued successfully
 

The certificate custom resource also gives information with regards to the expiry details of the certificate issued.

...
Status:
  Conditions:
    Last Transition Time:  2020-07-24T06:22:49Z
    Message:               Certificate is up to date and has not expired
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Not After:               2020-07-29T06:22:39Z
...

As seen above, Vault can be configured in conjunction with a certificate manager like Jetstack’s cert-manager. Cert-manager can help to request and issue certificates, obtained dynamically from Vault. Besides creation, certificates can also be revoked and removed. Automated tracking, renewal and issuance of certifcates is definitely very useful in administrating various applications within a Kubernetes cluster.

comments powered by Disqus
rss facebook twitter github gitlab youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora quora