Now that I have installed a haproxy ingress controller it is time to add ssl to our website. For this I will be using an ssl certificate generated on cloudflare by certmanager. Since I am hosting the DNS on cloudflare and mostly using wildcard certificates I am kind of forced to use dns authenticator on the cloudflare side to authenticate the ACME challenge. For this I need to go to the cloudflare account and create an API token that has access to the dns zones. For this after you logged in to cloudflare account and clicked on the domain a bit lower on the right side you will see a link with Get your API token.
You click on it and on the next pannel you click on Create Token you give it a name and configure it with the following rights:
Save the token for now and we will be using it a bit later. For instaling the certmanager in the kubernetes cluster you can simply run the following command:
#kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.6.1/cert-manager.yaml
This will install all the required resources into the kubernetes in a namespace called cert-manager. Once you make sure all the pods are up and running you can move forward. To make sure that everything is up and running you can run:
#kubectl -n cert-manager get pods NAME READY STATUS RESTARTS AGE cert-manager-55658cdf68-7vwrf 1/1 Running 1 39h cert-manager-cainjector-967788869-7k4xl 1/1 Running 6 39h cert-manager-webhook-7b86bc6578-pwcr7 1/1 Running 0 39h
Now we need to create a secret with the api token generated from cloudflare.
#cat secret.yml --- apiVersion: v1 kind: Secret metadata: name: cloudflare-api-token-secret namespace: cert-manager type: Opaque stringData: api-token: <plain text format of the token>
Note: If you configure a cluster wide certmanager then the secret needs to be created in the
cert-manager namespace (For this you need to make sure you have access to the whole cluster and not only to a namespace). Otherwise you can create the secret on the namespace you are controlling and have access to.
Next step is to create an issuer. Again if you have access to the whole cluster then the issuer kind can
ClusterIssuer and that is created cluster wide (not tied to a namespace). If on the other hand you have access to one namespace only then the issue kind will be
Issuer. In this case I will be using
DNS01 type of issuer. There are lots of different kind of issuers that can be used. For more information check this link.
#cat issuer.yml apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: le-global-issuer spec: acme: email: email@example.com server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: name: letsencrypt-key solvers: - dns01: cloudflare: email: firstname.lastname@example.org apiTokenSecretRef: name: cloudflare-api-token-secret key: api-token
As you can see the
acme.email has to be any e-mail you want to register your ACME account on. The letsencryt-key is a secret your issuer key will be stored on (can have any name). As you can see the solvers are set for
dns01 type and it's using cloudflare. for the e-mail in here you need to use the e-mail address your cloudflare account is configured for. and the
apiTokenSecretRef has the name and key configured in the secret a bit earlier. I can check if the issuer/clusterissuer was created correctly by running:
#kubectl get clusterIssuers kubectl get clusterIssuers NAME READY AGE le-global-issuer True 40h #kubectl describe clusterissuer le-global-issuer Name: le-global-issuer Namespace: ... Status: Acme: Last Registered Email: email@example.com Uri: https://acme-v02.api.letsencrypt.org/acme/acct/362899090 Conditions: Last Transition Time: 2022-01-13T19:44:53Z Message: The ACME account was registered with the ACME server Observed Generation: 1 Reason: ACMEAccountRegistered Status: True Type: Ready Events: <none>
By running describe you will see lots of information however the most important is the
Status section where you can see if the ACME account was registered and it is in
Note: Everything works the same with
Issuer with the difference that you need to have the namespace specified and the type would be
Issuer instead of
Now it is time to generate the certificates. For this need to create a certificate type of manifest file.
#cat certificate.yml apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: certificate namespace: default spec: dnsNames: - "*.example.com" secretName: example issuerRef: name: le-global-issuer kind: ClusterIssuer
Note: The certificate needs to be created on the namespace your application is running at, and the
issuerRef needs to reference the type of your issuer.
You can check the status of your certificate by running:
# kubectl get cr -n default NAME APPROVED DENIED READY ISSUER REQUESTOR AGE certificate-<random-string> True True le-global-issuer system:serviceaccount:cert-manager:cert-manager 40h
After some time you will see that the
Custom Resource will have the Approved state as True. That mean the certificate was successfully generated. And if you check the secrets on the namespace you created it there will be a secret created with the certificate name of type
Now we go back to the Ingestor operator created earlier to add the TLS reference to it and add some more annotations for example for automatic redirect from http to https.
#cat ingress.yml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: web-ingress namespace: default annotations: ingress.class: "haproxy" haproxy.org/ssl-redirect: "true" haproxy.org/ssl-redirect-code: "301 spec: tls: - hosts: - "*.redcapcloud.com" secretName: certificate rules: - host: example.com http: paths: - path: / backend: name: web port: number: 80
After deploying the updated ingress manifest we can check if it's working:
#curl -H "Host: example.com" http://<public ip address> -ILk HTTP/1.1 301 Moved Permanently content-length: 0 location: https://example.com:443/ HTTP/2 200 date: Sat, 15 Jan 2022 12:09:42 GMT content-type: text/html;charset=utf-8 set-cookie: JSESSIONID=node0948mbyovct2p16qpwflu3mmyw99083.node0; Path=/; HttpOnly; SameSite=None expires: Thu, 01 Jan 1970 00:00:00 GMT content-length: 3259 server: Jetty(9.4.38.v20210224) set-cookie: SERVERID=SRV_1; path=/ cache-control: private
As you can see everything works as expected the http traffic is redirected to https and if you open the domain in a browser you can see it has a valid certificate deployed.