Adding security layers to your App on OpenShift - Part 6: PKI as a Service with Vault and Cert Manager

Laurent Broudoux
ITNEXT
Published in
10 min readMar 12, 2020

--

Today, securing your apps is a « must have » but it’s difficult to introduce it without modifying code if you didn’t think about it at the very beginning. Luckily, the new cloud native patterns brought by containers and platforms like Kubernetes offers simple ways to address security concerns without touching code.

In the very first part of this series, we have learned how to deploy our fruits-catalog application as containers. We have exposed the benefits of containerization on OpenShift, the Red Hat Kubernetes distribution and finished on how we can easily secure access to the application via a Route with TLS support.

However, we did not dive into the details on how TLS certificates were issued and dispatched… We apply the default configuration that uses a wildcard certificate — and which may be a pretty bad idea for mission critical platforms. This new part is about how to automate certificate management in this highly volatile cloud native environments. We’ll delve into the certificate issuing details using Cert-Manager and Hashicorp Vault to deploy a PKI as a Service approach.

This part was realized and proof-read with Nicolas Ehrman from Hashicorp. Thanks Nico for your help and advices!

Part 6 — PKI as a Service with Vault and Cert Manager

This post is part of a bigger series about how to add security layers to an existing application. This other parts published so far are:

What we want to achieve in this part?

As introduced above, default OpenShift setup makes use of wildcard certificates for securing all the routes that require TLS transport. As this could be a real good idea in a number of situations like non production environments, startup company with very few endpoints to expose or obsessed with cost (even if Let’s Encrypt now provides free wildcard certificates) ; this is usually a bad idea for most companies. Actually wildcard certificates are contradictory with a security principle that is risk mitigation. Once a wildcard certificate is compromised or broken or need to be refreshed, all the subdomain and thus applications using it are impacted!

On the other side, managing certificate issuing, revoking and renewal for each and every application — specially in a highly volatile environment — can be really a huge task… if not automated. And that’s the point where Cert Manager enters the picture! Cert Manager is automation software for automating certificates management in a cloud native world. It allows issuing, refreshing and revoking certs and integrates very well with Vault where we would like to store our Root and Intermediate Certification Authorities.

So we will deploy Cert Manager and its companion for OpenShift called Cert Utils. This components will be used at deployment time to issue certs on upcoming deployments requests and to inject them into our resources. The new components will react on two new events on our deployment chain:

  • the creation/update/deletion of a new Kubernetes resource called a Certificate(which is indeed a certificate issuing request),
  • the creation/update/deletion of our already existing Route

The complete flow can be described as follow:

  1. On a “change event” on the application Certificate, Cert Manager will take care of issuing a new Certificate corresponding to request parameters. For that, Cert Manager will call Vault that owns our Root Private Key Infrastructure and some Intermediate Private Key Infrastructure,
  2. Before issuing a new Certificate, Vault will check Cert Manager authenticity and permissions using Kubernetes as the authentication provider. It will then create a specific Certificate, signed by the Root CA,
  3. Cert Manager will store this new Certificate as a Kubernetes secret. The certificate information are indeed represented by 3 properties into this secret: ca.crt, tls.crt and tls.key
  4. On a “change event” on the application Route, Cert Utils will retrieve the previously created secret,
  5. It will then mutate — that means here “inject into” — the route to use this new certificate. The route is re-configured to use and expose access to the application endpoint using this newly generated certificate.

How to apply it?

Before diving into this hands-on section, we assumed you have already deployed and configured Vault to use Kubernetes authentication. How to do this is detailed into part 3 of this series.

And now let’s start configuring Vault for hosting our PKI. The first step is to mount a new pki backend. Next, Vault must be configured with a CA certificate and associated private key. We’ll take advantage of the backend’s self-signed root generation support, but Vault also supports generating an intermediate CA (with a CSR for signing) or setting a PEM-encoded certificate and private key bundle directly into the backend.

In our setup, we will use a root certificate to only be used to sign CA intermediate certificates. As it’s a root, we’ll want to set a long maximum life time for the certificate; since it honors the maximum mount TTL, first we adjust that before generating a new CA Certificate corresponding to our global cluster URL — this is the $COMMON_NAME:

$ vault secrets enable -tls-skip-verify pki
Success! Enabled the pki secrets engine at: pki/
$ vault secrets tune -tls-skip-verify -max-lease-ttl=8760h pki
Success! Tuned the secrets engine at: pki/
$ export COMMON_NAME=$(echo $(oc whoami --show-server) | sed -E -n 's=https://api.(.*):6443=\1=p')$ vault write -tls-skip-verify pki/root/generate/internal \
common_name="$COMMON_NAME Root Authority" \
ttl=8760h

We now have to setup our intermediate CA and for that we will use a different PKI backend named pki_int. We can also tune here the maximum TTL of the issued certificates that should be less or equal to the root CA TTL. Then we have to ask the intermediate to generate a certificate signing request that will be stored into a local file called pki_int.csr. Then we take the signing request from the intermediate authority and sign it using the root authority ; before setting the resulting signed certificate as the intermediate authority. The commands below are doing that:

$ vault secrets enable -tls-skip-verify -path=pki_int pki
Success! Enabled the pki secrets engine at: pki_int/
$ vault secrets tune -tls-skip-verify -max-lease-ttl=30m pki_int
Success! Tuned the secrets engine at: pki_int/
$ vault write -tls-skip-verify -field=csr pki_int/intermediate/generate/internal \
common_name="apps.$COMMON_NAME Intermediate Authority" \
ttl=4760h >> pki_int.csr
$ vault write -tls-skip-verify -field=certificate pki/root/sign-intermediate csr=@pki_int.csr format=pem_bundle ttl=4760h >> signed_certificate.pem$ vault write -tls-skip-verify pki_int/intermediate/set-signed certificate=@signed_certificate.pem
Success! Data written to: pki_int/intermediate/set-signed

Finally we have to configure the URL endpoints for using this intermediate PKI. We also have to configure a role — a logical name that maps to a policy used to generate new certificates. For example, we used here a example-opentlc-com role that will be referenced by the policy cert-manager-vault-issuer-pki-policy created with third command:

$ vault write -tls-skip-verify pki_int/config/urls \
issuing_certificates="$VAULT_ADDR/v1/pki_int/ca" \
crl_distribution_points="$VAULT_ADDR/v1/pki_int/crl"
Success! Data written to: pki_int/config/urls
$ vault write -tls-skip-verify pki_int/roles/example-opentlc-com \
allowed_domains="apps.$COMMON_NAME" \
allow_subdomains=true \
max_ttl=1h
Success! Data written to: pki_int/roles/example-opentlc-com
$ vault policy write -tls-skip-verify cert-manager-vault-issuer-pki-policy ./k8s/cert-manager-vault-issuer-policy-pki.hcl
Success! Uploaded policy: cert-manager-vault-issuer-pki-policy

Now that Vault part is OK, let’s deal with Cert Manager stuff! We will start creating a new project to host our new components cert-manager and cert-utils. We need to annotate this new namespace to tell Cert Manager to not validate this project.

$ oc new-project cert-manager
$ oc label namespace cert-manager certmanager.k8s.io/disable-validation=true
$ oc apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.13.0/cert-manager-openshift.yaml

For OpenShift, we need to deploy the Cert Utils companion component. This can be done using the Helm packaging utility: you’ll need to have the helm CLI tool available on your station.

$ helm version
version.BuildInfo{Version:"v3.0.3", GitCommit:"ac925eb7279f4a6955df663a0128044a8a6b7593", GitTreeState:"clean", GoVersion:"go1.13.7"}
$ export CERT_UTILS_CHART_VERSION=$(helm search repo cert-utils-operator/cert-utils-operator | grep cert-utils-operator/cert-utils-operator | awk '{print $2}')$ helm fetch cert-utils-operator/cert-utils-operator --version ${CERT_UTILS_CHART_VERSION}$ helm template cert-utils-operator-${CERT_UTILS_CHART_VERSION}.tgz --namespace cert-manager | oc apply -f - -n cert-manager

On Cert Manager, we have to declare an Issuer that will be able to handle certificate creation using some PKI. Luckily, Cert Manager has a Vault implementation for that. In order to only allow this issuer to request new Certificate to Vault, we have to create a dedicated Service Account whose token and associated certificate will be used to configure this vault-issuer. On the Vault side, before actually creating the issuer, we have to declare that this cert-manager-vault-issuer Service Account could be used to trigger the previously declared policy:

$ oc create sa cert-manager-vault-issuer -n cert-manager$ export CERT_MANAGER_ISSUER_TOKEN=$(oc get sa/cert-manager-vault-issuer -n cert-manager -o yaml | grep cert-manager-vault-issuer-token | awk '{print $3}')$ export VAULT_LISTENER_CERT=$(oc exec -n fruits-catalog -it $(oc get pods -n fruits-catalog | grep vault | awk '{print $1}') -- /bin/sh -c "cat /var/run/secrets/kubernetes.io/certs/tls.crt")# Report this values in vault-issuer.yml
$ sed -i '' 's=CERT_MANAGER_ISSUER_TOKEN='"$CERT_MANAGER_ISSUER_TOKEN"'=' k8s/vault-issuer.yml
$ sed -i '' 's=VAULT_LISTENER_CERT='"$(echo $VAULT_LISTENER_CERT | base64)"'=' k8s/vault-issuer.yml
$ vault write -tls-skip-verify auth/kubernetes/role/cert-manager-vault-issuer \
bound_service_account_names=cert-manager-vault-issuer \
bound_service_account_namespaces=cert-manager \
policies=cert-manager-vault-issuer-pki-policy \
ttl=24h
Success! Data written to: auth/kubernetes/role/cert-manager-vault-issuer
$ oc apply -f ./k8s/vault-issuer.yml -n cert-manager

And it should now be ready to run!

Check it works as expected

Now finally create a Certificate resource that will be handled by Cert Manager to issue a new certificate and create the requested secret:

$ export FRUITS_CATALOG_ROUTE=$(oc get routes fruits-catalog -n fruits-catalog | grep fruits-catalog | awk '{print $2}')$ sed -i '' 's=FRUITS_CATALOG_ROUTE='"$FRUITS_CATALOG_ROUTE"'=g' k8s/fruits-catalog-route-certificate.yml$ oc apply -f ./k8s/fruits-catalog-route-certificate.yml -n fruits-catalog

After a few seconds, check a new fruits-catalog-route-secret secret has been created and holds keys and ca.crt entries:

$ oc get secrets fruits-catalog-route-secret -o yaml -n fruits-catalog
apiVersion: v1
data:
ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURsRENDQW55Z0F3SUJBZ0lVYitML1luZStaNGhLcUFNRXZwaW0wcDRsSnZVd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1N6RkpNRWNHQTFVRUF4TkFZMngxYzNSbGNpMXdZWEpwY3kweVltUTBMbkJoY21sekxUSmlaRFF1WlhoaApiWEJzWlM1dmNHVnVkR3hqTG1OdmJTQlNiMjkwSUVGMWRHaHZjbWwwZVRBZUZ3MHlNREF6TURZeE16VTBOREphCkZ3MHlNREE1TWpBeU1UVTFNVEphTUZneFZqQlVCZ05WQkFNVFRXRndjSE11WTJ4MWMzUmxjaTF3WVhKcGN5MHkKWW1RMExuQmhjbWx6TFRKaVpEUXVaWGhoYlhCc1pTNXZjR1Z1ZEd4akxtTnZiU0JKYm5SbGNtMWxaR2xoZEdVZwpRWFYwYUc5eWFYUjVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdKb1RabGtSCkNPWDBNdHBzVitpaHdQeHRFOG1vVTZMVVpsd2FLR2NuNk5CVWRDdHhpdGpoTGpqLzliTjdFT3I0ZkYxQXIxL1QKcFlodThPSEdaNDZNQlhzZjVJOUE1dmJIVlBLZnhDMFZsckgzd1dPend1M2pFRGFPdE11NDZFYjlMeWhNRXk5QQpMaXYyalhSdEFhRWpOU0F5WlJtRTF0RWRNWTdha2xjVkc3a21jLy9qblN3NUJwSEpuNElnZVFoQTgzSDl1WWoxCmN6Y3ZnL1pMbk0zTmVJY01mTFhtZDFSN2NZZEhBam1adDdFMGZuVTBkaDdQajlmRnZ4MUIraDFMYnRRQ1J3a2oKeXAvbmlrb0tIQkJLZk41SkxwYkJ6TXFpNm1uVmdlbzJjNUx2Y0t2WmlmUEMzTlFodFBRODRoYWNmWUtMVm5ESApKN2ZybS83VzV3aW9zd0lEQVFBQm8yTXdZVEFPQmdOVkhROEJBZjhFQkFNQ0FRWXdEd1lEVlIwVEFRSC9CQVV3CkF3RUIvekFkQmdOVkhRNEVGZ1FVa0daN3l1Rm1pcGl3a2JNdDdOUGxIQTdTNlZjd0h3WURWUjBqQkJnd0ZvQVUKVElUZVBtMUdQT0tFbXpyTjhSaGJBUm9IQ0VFd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFEcVE5cWhaTHJ3bwpISXE0YlJOUTJYODI4WTZsYno0V3BrdDNGalQyNCtwMWVFaGdLYVhvV2d5RmlxekxxRnZoSmhRUFRhZ2JYL1JUCmgyODgwd2hvYTROUExxR2pZK0hUdjRkcnZNaVJWY0I3S0F3WUFLbWxiamluRk9MNS9abzROVFJESUJPWGpQWVcKcVFBRUxtb1FvNVpXQ01HOC8ycWFoRWVYM3dvV293Mm5oRW1DLzNFTTV4NEhLRGZuOXA3azYvZFU4NmN3TnhPNwpYcVJZcExyQ2xnUUpLS09aYWJPeHdSVk02TmNGUTR0c3ZGRGZGOXJEdjNXc0VhV1JkRlA0RzdjeUhSOGk2a1J3CmFHUFdaTHQyRk9xZ1locWNWU094V3pMbWhEaFg4bkN2S1pEUHhWa1pBcUFyVGVidlRHZDhyZkVsVDdrVDkrTFQKMVVRSWN4ZmNCcm89Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
[...]

So now it’s time to annotate your route so that this secret entries will be injected into your route to present this specific certificate to the users accessing the application:

$ oc annotate routes/fruits-catalog cert-utils-operator.redhat-cop.io/certs-from-secret=fruits-catalog-route-secret -n fruits-catalog

Here again, wait a few seconds before checking the route details and getting certificate informations:

$ oc get route fruits-catalog -o yaml -n fruits-catalog
apiVersion: route.openshift.io/v1
kind: Route
metadata:
annotations:
cert-utils-operator.redhat-cop.io/certs-from-secret: fruits-catalog-route-secret
[...]
labels:
app: fruits-catalog
expose: "true"
group: com.github.lbroudoux.msa
provider: fabric8
version: 1.0.0-SNAPSHOT
name: fruits-catalog
namespace: fruits-catalog
[...]
port:
targetPort: 8080
tls:
caCertificate: |-
-----BEGIN CERTIFICATE-----
MIIDlDCCAnygAwIBAgIUb+L/Yne+Z4hKqAMEvpim0p4lJvUwDQYJKoZIhvcNAQEL
BQAwSzFJMEcGA1UEAxNAY2x1c3Rlci1wYXJpcy0yYmQ0LnBhcmlzLTJiZDQuZXhh
bXBsZS5vcGVudGxjLmNvbSBSb290IEF1dGhvcml0eTAeFw0yMDAzMDYxMzU0NDJa
Fw0yMDA5MjAyMTU1MTJaMFgxVjBUBgNVBAMTTWFwcHMuY2x1c3Rlci1wYXJpcy0y
YmQ0LnBhcmlzLTJiZDQuZXhhbXBsZS5vcGVudGxjLmNvbSBJbnRlcm1lZGlhdGUg
QXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwJoTZlkR
COX0MtpsV+ihwPxtE8moU6LUZlwaKGcn6NBUdCtxitjhLjj/9bN7EOr4fF1Ar1/T
pYhu8OHGZ46MBXsf5I9A5vbHVPKfxC0VlrH3wWOzwu3jEDaOtMu46Eb9LyhMEy9A
Liv2jXRtAaEjNSAyZRmE1tEdMY7aklcVG7kmc//jnSw5BpHJn4IgeQhA83H9uYj1
czcvg/ZLnM3NeIcMfLXmd1R7cYdHAjmZt7E0fnU0dh7Pj9fFvx1B+h1LbtQCRwkj
yp/nikoKHBBKfN5JLpbBzMqi6mnVgeo2c5LvcKvZifPC3NQhtPQ84hacfYKLVnDH
J7frm/7W5wioswIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw
AwEB/zAdBgNVHQ4EFgQUkGZ7yuFmipiwkbMt7NPlHA7S6VcwHwYDVR0jBBgwFoAU
TITePm1GPOKEmzrN8RhbARoHCEEwDQYJKoZIhvcNAQELBQADggEBADqQ9qhZLrwo
HIq4bRNQ2X828Y6lbz4Wpkt3FjT24+p1eEhgKaXoWgyFiqzLqFvhJhQPTagbX/RT
h2880whoa4NPLqGjY+HTv4drvMiRVcB7KAwYAKmlbjinFOL5/Zo4NTRDIBOXjPYW
qQAELmoQo5ZWCMG8/2qahEeX3woWow2nhEmC/3EM5x4HKDfn9p7k6/dU86cwNxO7
XqRYpLrClgQJKKOZabOxwRVM6NcFQ4tsvFDfF9rDv3WsEaWRdFP4G7cyHR8i6kRw
aGPWZLt2FOqgYhqcVSOxWzLmhDhX8nCvKZDPxVkZAqArTebvTGd8rfElT7kT9+LT
1UQIcxfcBro=
-----END CERTIFICATE-----
[...]

You can even validate that the caCertificate of the route is the base64 decoded string found into the ca.crt secret entry:

$ echo LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURsRENDQW55Z0F3SUJBZ0lVYitML1luZStaNGhLcUFNRXZwaW0wcDRsSnZVd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1N6RkpNRWNHQTFVRUF4TkFZMngxYzNSbGNpMXdZWEpwY3kweVltUTBMbkJoY21sekxUSmlaRFF1WlhoaApiWEJzWlM1dmNHVnVkR3hqTG1OdmJTQlNiMjkwSUVGMWRHaHZjbWwwZVRBZUZ3MHlNREF6TURZeE16VTBOREphCkZ3MHlNREE1TWpBeU1UVTFNVEphTUZneFZqQlVCZ05WQkFNVFRXRndjSE11WTJ4MWMzUmxjaTF3WVhKcGN5MHkKWW1RMExuQmhjbWx6TFRKaVpEUXVaWGhoYlhCc1pTNXZjR1Z1ZEd4akxtTnZiU0JKYm5SbGNtMWxaR2xoZEdVZwpRWFYwYUc5eWFYUjVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdKb1RabGtSCkNPWDBNdHBzVitpaHdQeHRFOG1vVTZMVVpsd2FLR2NuNk5CVWRDdHhpdGpoTGpqLzliTjdFT3I0ZkYxQXIxL1QKcFlodThPSEdaNDZNQlhzZjVJOUE1dmJIVlBLZnhDMFZsckgzd1dPend1M2pFRGFPdE11NDZFYjlMeWhNRXk5QQpMaXYyalhSdEFhRWpOU0F5WlJtRTF0RWRNWTdha2xjVkc3a21jLy9qblN3NUJwSEpuNElnZVFoQTgzSDl1WWoxCmN6Y3ZnL1pMbk0zTmVJY01mTFhtZDFSN2NZZEhBam1adDdFMGZuVTBkaDdQajlmRnZ4MUIraDFMYnRRQ1J3a2oKeXAvbmlrb0tIQkJLZk41SkxwYkJ6TXFpNm1uVmdlbzJjNUx2Y0t2WmlmUEMzTlFodFBRODRoYWNmWUtMVm5ESApKN2ZybS83VzV3aW9zd0lEQVFBQm8yTXdZVEFPQmdOVkhROEJBZjhFQkFNQ0FRWXdEd1lEVlIwVEFRSC9CQVV3CkF3RUIvekFkQmdOVkhRNEVGZ1FVa0daN3l1Rm1pcGl3a2JNdDdOUGxIQTdTNlZjd0h3WURWUjBqQkJnd0ZvQVUKVElUZVBtMUdQT0tFbXpyTjhSaGJBUm9IQ0VFd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFEcVE5cWhaTHJ3bwpISXE0YlJOUTJYODI4WTZsYno0V3BrdDNGalQyNCtwMWVFaGdLYVhvV2d5RmlxekxxRnZoSmhRUFRhZ2JYL1JUCmgyODgwd2hvYTROUExxR2pZK0hUdjRkcnZNaVJWY0I3S0F3WUFLbWxiamluRk9MNS9abzROVFJESUJPWGpQWVcKcVFBRUxtb1FvNVpXQ01HOC8ycWFoRWVYM3dvV293Mm5oRW1DLzNFTTV4NEhLRGZuOXA3azYvZFU4NmN3TnhPNwpYcVJZcExyQ2xnUUpLS09aYWJPeHdSVk02TmNGUTR0c3ZGRGZGOXJEdjNXc0VhV1JkRlA0RzdjeUhSOGk2a1J3CmFHUFdaTHQyRk9xZ1locWNWU094V3pMbWhEaFg4bkN2S1pEUHhWa1pBcUFyVGVidlRHZDhyZkVsVDdrVDkrTFQKMVVRSWN4ZmNCcm89Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0= | base64 -D-----BEGIN CERTIFICATE-----MIIDlDCCAnygAwIBAgIUb+L/Yne+Z4hKqAMEvpim0p4lJvUwDQYJKoZIhvcNAQEL
BQAwSzFJMEcGA1UEAxNAY2x1c3Rlci1wYXJpcy0yYmQ0LnBhcmlzLTJiZDQuZXhh
bXBsZS5vcGVudGxjLmNvbSBSb290IEF1dGhvcml0eTAeFw0yMDAzMDYxMzU0NDJa
Fw0yMDA5MjAyMTU1MTJaMFgxVjBUBgNVBAMTTWFwcHMuY2x1c3Rlci1wYXJpcy0y
YmQ0LnBhcmlzLTJiZDQuZXhhbXBsZS5vcGVudGxjLmNvbSBJbnRlcm1lZGlhdGUg
QXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwJoTZlkR
COX0MtpsV+ihwPxtE8moU6LUZlwaKGcn6NBUdCtxitjhLjj/9bN7EOr4fF1Ar1/T
pYhu8OHGZ46MBXsf5I9A5vbHVPKfxC0VlrH3wWOzwu3jEDaOtMu46Eb9LyhMEy9A
Liv2jXRtAaEjNSAyZRmE1tEdMY7aklcVG7kmc//jnSw5BpHJn4IgeQhA83H9uYj1
czcvg/ZLnM3NeIcMfLXmd1R7cYdHAjmZt7E0fnU0dh7Pj9fFvx1B+h1LbtQCRwkj
yp/nikoKHBBKfN5JLpbBzMqi6mnVgeo2c5LvcKvZifPC3NQhtPQ84hacfYKLVnDH
J7frm/7W5wioswIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw
AwEB/zAdBgNVHQ4EFgQUkGZ7yuFmipiwkbMt7NPlHA7S6VcwHwYDVR0jBBgwFoAU
TITePm1GPOKEmzrN8RhbARoHCEEwDQYJKoZIhvcNAQELBQADggEBADqQ9qhZLrwo
HIq4bRNQ2X828Y6lbz4Wpkt3FjT24+p1eEhgKaXoWgyFiqzLqFvhJhQPTagbX/RT
h2880whoa4NPLqGjY+HTv4drvMiRVcB7KAwYAKmlbjinFOL5/Zo4NTRDIBOXjPYW
qQAELmoQo5ZWCMG8/2qahEeX3woWow2nhEmC/3EM5x4HKDfn9p7k6/dU86cwNxO7
XqRYpLrClgQJKKOZabOxwRVM6NcFQ4tsvFDfF9rDv3WsEaWRdFP4G7cyHR8i6kRw
aGPWZLt2FOqgYhqcVSOxWzLmhDhX8nCvKZDPxVkZAqArTebvTGd8rfElT7kT9+LT
1UQIcxfcBro=
-----END CERTIFICATE----

Now you can simply open a browser window using the URL of the route that has been mutated to use our custom certificate issued by our PKI. You’ll see that application is now served using TLS with this certificate informations.

You can also have a look at the Vault backend UI — going to the intermediate PKI details — to check that is has actually generated a new certificate. The first one being the reference that was previously generated and signed by our Root CA.

Conclusion

We have seen in this part how very fast moving application like cloud native ones can benefit of a fully automated Public Key Infrastructure. Once setup done, your developers or deployers are fully autonomous for requesting and using certificates tailor-made for their application. Moreover those requests and usages are managed through regular Kubernetes resources that can easily versioned and secured using a GitOps approach!

The very nice part of this configuration — you’ll be able to check it if you have deployed the demo and wait for about 45 min 😉 — is that Vault + Cert Manager + Cert Utils automatically manage the renewal of issued certificates. Before expiration of certificate, Cert Manager will made a new issue request to Vault and will update the secret with new certificate. Cert Utils will be notified of secret update and will in turn update the Route. Finally OpenShift router will re-deploy a new Route using a rolling-update mechanism that guarantee that application users will not suffer any downtime.

Thanks for your time reading this post: I know this is a long and complicated one. I hope you learned a few stuffs here. Feedback or encouragement are much appreciated through claps or comments 🙏

--

--

Coding architect, geek, committed to open source, @MicrocksIO founder, ex-Red Hatter. #distributed, #architecture, #eventdriven, #java, #api