Recently the topic came up about how to procure valid SSL certificates for a colleague’s project.  I knew I had used an earlier version of cert-manager in a post about self host container registries but wondered could the same tooling be used to quickly get valid certs in a manual fashion.  I was also interested in trying out the cert-manager krew plugin and this could be a nice chance to do so.  While automatic provisioning is easy, we'll address manually generating a valid cert and downloading it for use.

Setup

I wanted to do this all in Windows for a change so i used a Lenovo Ideapad Flex 14 (and not in WSL).

Update Azure CLI

I found that choco install was only offering version 2.2.0 and i knew Az CLI was up to at least version 2.9 so i went to the Azure CLI site to download the msi.

C:\WINDOWS\system32>choco install azure-cli
Chocolatey v0.10.15
Installing the following packages:
azure-cli
By installing you accept licenses for the packages.
azure-cli v2.2.0 already installed.
 Use --force to reinstall, specify a version to install, or try upgrade.

Chocolatey installed 0/1 packages.
 See the log for details (C:\ProgramData\chocolatey\logs\chocolatey.log).

Warnings:
 - azure-cli - azure-cli v2.2.0 already installed.
 Use --force to reinstall, specify a version to install, or try upgrade.

After the MSI install

C:\WINDOWS\system32>az version
{
  "azure-cli": "2.9.1",
  "azure-cli-command-modules-nspkg": "2.0.3",
  "azure-cli-core": "2.9.1",
  "azure-cli-nspkg": "3.0.4",
  "azure-cli-telemetry": "1.0.4",
  "extensions": {}
}

Once we have a current az cli, we can install the latest kubectl:

C:\WINDOWS\system32>az aks install-cli
Downloading client to "C:\Users\isaac\.azure-kubectl\kubectl.exe" from "https://storage.googleapis.com/kubernetes-release/release/v1.18.6/bin/windows/amd64/kubectl.exe"

C:\WINDOWS\system32>set PATH=C:\Users\isaac\.azure-kubectl\;%PATH%

C:\WINDOWS\system32>kubectl version
Client Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.6", GitCommit:"dff82dc0de47299ab66c83c626e08b245ab19037", GitTreeState:"clean", BuildDate:"2020-07-15T16:58:53Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"windows/amd64"}
Unable to connect to the server: dial tcp 192.168.1.225:6443: connectex: No connection could be made because the target machine actively refused it.

Next we need to install the krew plugin (you’ll want to install from administrator command prompt).  You can download the binary and config from the releases directory (https://github.com/kubernetes-sigs/krew/releases).  

C:\Users\isaac\Downloads>dir krew*
 Volume in drive C is Windows-SSD
 Volume Serial Number is 9024-7E16

 Directory of C:\Users\isaac\Downloads

08/01/2020  04:10 PM        11,927,552 krew.exe
08/01/2020  04:10 PM             2,193 krew.yaml
               2 File(s)     11,929,745 bytes
               0 Dir(s)  79,373,946,880 bytes free


C:\WINDOWS\system32>cd C:\Users\isaac\Downloads

C:\Users\isaac\Downloads>krew install --manifest=krew.yaml
←[31;1mWARNING←[0m: To be able to run kubectl plugins, you need to add the
"%USERPROFILE%\.krew\bin" directory to your PATH environment variable
and restart your shell.

Installing plugin: krew
Installed plugin: krew
\
 | Use this plugin:
 |      kubectl krew
 | Documentation:
 |      https://sigs.k8s.io/krew
 | Caveats:
 | \
 |  | krew is now installed! To start using kubectl plugins, you need to add
 |  | krew's installation directory to your PATH:
 |  |
 |  |   * macOS/Linux:
 |  |     - Add the following to your ~/.bashrc or ~/.zshrc:
 |  |         export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"
 |  |     - Restart your shell.
 |  |
 |  |   * Windows: Add %USERPROFILE%\.krew\bin to your PATH environment variable
 |  |
 |  | To list krew commands and to get help, run:
 |  |   $ kubectl krew
 |  | For a full list of available plugins, run:
 |  |   $ kubectl krew search
 |  |
 |  | You can find documentation at https://sigs.k8s.io/krew.
 | /
/

C:\Users\isaac\Downloads>set PATH=%USERPROFILE%\.krew\bin;%PATH%

Next we need to update krew

C:\Users\isaac\Downloads>kubectl krew update
Updated the local copy of plugin index.

C:\Users\isaac\Downloads>kubectl krew search
NAME                            DESCRIPTION                                         INSTALLED
access-matrix                   Show an RBAC access matrix for server resources     no
advise-psp                      Suggests PodSecurityPolicies for cluster.           unavailable on windows
apparmor-manager                Manage AppArmor profiles for cluster.               unavailable on windows
auth-proxy                      Authentication proxy to a pod or service            no
bulk-action                     Do bulk actions on Kubernetes resources.            unavailable on windows
ca-cert                         Print the PEM CA certificate of the current clu...  unavailable on windows
capture                         Triggers a Sysdig capture to troubleshoot the r...  unavailable on windows
cert-manager                    Manage cert-manager resources inside your cluster   no
change-ns                       View or change the current namespace via kubectl.   no
cilium                          Easily interact with Cilium agents.                 no
cluster-group                   Exec commands across a group of contexts.           unavailable on windows
config-cleanup                  Automatically clean up your kubeconfig              no
cssh                            SSH into Kubernetes nodes                           unavailable on windows
ctx                             Switch between contexts in your kubeconfig          unavailable on windows
custom-cols                     A "kubectl get" replacement with customizable c...  unavailable on windows
datadog                         Manage the Datadog Operator                         no
debug                           Attach ephemeral debug container to running pod     no
debug-shell                     Create pod with interactive kube-shell.             unavailable on windows
deprecations                    Checks for deprecated objects in a cluster          no
df-pv                           Show disk usage (like unix df) for persistent v...  no
doctor                          Scans your cluster and reports anomalies.           no
duck                            List custom resources with ducktype support         no
eksporter                       Export resources and removes a pre-defined set ...  no
emit-event                      Emit Kubernetes Events for the requested object     unavailable on windows
evict-pod                       Evicts the given pod                                unavailable on windows
example                         Prints out example manifest YAMLs                   no
exec-as                         Like kubectl exec, but offers a `user` flag to ...  unavailable on windows
exec-cronjob                    Run a CronJob immediately as Job                    unavailable on windows
fields                          Grep resources hierarchy by field name              no
fleet                           Shows config and resources of a fleet of clusters   no
fuzzy                           Fuzzy and partial string search for kubectl         no
gadget                          Gadgets for debugging and introspecting apps        unavailable on windows
get-all                         Like `kubectl get all` but _really_ everything      no
gke-credentials                 Fetch credentials for GKE clusters                  unavailable on windows
gopass                          Imports secrets from gopass                         no
grep                            Filter Kubernetes resources by matching their n...  no
gs                              Handle custom resources with Giant Swarm            unavailable on windows
iexec                           Interactive selection tool for `kubectl exec`       unavailable on windows
images                          Show container images used in the cluster.          no
ingress-nginx                   Interact with ingress-nginx                         no
ipick                           A kubectl wrapper for interactive resource sele...  no
konfig                          Merge, split or import kubeconfig files             no
krew                            Package manager for kubectl plugins.                yes
kubesec-scan                    Scan Kubernetes resources with kubesec.io.          no
kudo                            Declaratively build, install, and run operators...  unavailable on windows
kuttl                           Declaratively run and test operators                unavailable on windows
kyverno                         Kyverno is a policy engine for kubernetes           no
match-name                      Match names of pods and other API objects           no
modify-secret                   modify secret with implicit base64 translations     unavailable on windows
mtail                           Tail logs from multiple pods matching label sel...  unavailable on windows
neat                            Remove clutter from Kubernetes manifests to mak...  unavailable on windows
net-forward                     Proxy to arbitrary TCP services on a cluster ne...  unavailable on windows
node-admin                      List nodes and run privileged pod with chroot       unavailable on windows
node-restart                    Restart cluster nodes sequentially and gracefully   unavailable on windows
node-shell                      Spawn a root shell on a node via kubectl            unavailable on windows
np-viewer                       Network Policies rules viewer                       no
ns                              Switch between Kubernetes namespaces                unavailable on windows
oidc-login                      Log in to the OpenID Connect provider               no
open-svc                        Open the Kubernetes URL(s) for the specified se...  no
oulogin                         Login to a cluster via OpenUnison                   no
outdated                        Finds outdated container images running in a cl...  no
passman                         Store kubeconfig credentials in keychains or pa...  no
pod-dive                        Shows a pod's workload tree and info inside a node  no
pod-logs                        Display a list of pods to get logs from             unavailable on windows
pod-shell                       Display a list of pods to execute a shell in        unavailable on windows
podevents                       Show events for pods                                no
popeye                          Scans your clusters for potential resource issues   no
preflight                       Executes application preflight tests in a cluster   no
profefe                         Gather and manage pprof profiles from running pods  unavailable on windows
prompt                          Prompts for user confirmation when executing co...  unavailable on windows
prune-unused                    Prune unused resources                              unavailable on windows
psp-util                        Manage Pod Security Policy(PSP) and the related...  no
rbac-lookup                     Reverse lookup for RBAC                             unavailable on windows
rbac-view                       A tool to visualize your RBAC permissions.          no
resource-capacity               Provides an overview of resource requests, limi...  unavailable on windows
resource-snapshot               Prints a snapshot of nodes, pods and HPAs resou...  no
restart                         Restarts a pod with the given name                  unavailable on windows
rm-standalone-pods              Remove all pods without owner references            unavailable on windows
rolesum                         Summarize RBAC roles for subjects                   no
roll                            Rolling restart of all persistent pods in a nam...  unavailable on windows
schemahero                      Declarative database schema migrations via YAML     no
score                           Kubernetes static code analysis.                    unavailable on windows
service-tree                    Status for ingresses, services, and their backends  no
sick-pods                       Find and debug Pods that are "Not Ready"            no
snap                            Delete half of the pods in a namespace or cluster   unavailable on windows
sniff                           Start a remote packet capture on pods using tcp...  no
sort-manifests                  Sort manifest files in a proper order by Kind       unavailable on windows
split-yaml                      Split YAML output into one file per resource.       no
spy                             pod debugging tool for kubernetes clusters with...  unavailable on windows
sql                             Query the cluster via pseudo-SQL                    unavailable on windows
ssh-jump                        A kubectl plugin to SSH into Kubernetes nodes u...  unavailable on windows
sshd                            Run SSH server in a Pod                             unavailable on windows
ssm-secret                      Import/export secrets from/to AWS SSM param store   no
starboard                       Toolkit for finding risks in kubernetes resources   no
status                          Show status details of a given resource.            no
sudo                            Run Kubernetes commands impersonated as group s...  unavailable on windows
support-bundle                  Creates support bundles for off-cluster analysis    no
tail                            Stream logs from multiple pods and containers u...  unavailable on windows
tap                             Interactively proxy Kubernetes Services with ease   no
tmux-exec                       An exec multiplexer using Tmux                      unavailable on windows
topology                        Explore region topology for nodes or pods           no
trace                           bpftrace programs in a cluster                      no
tree                            Show a tree of object hierarchies through owner...  no
unused-volumes                  List unused PVCs                                    no
view-allocations                List allocations per resources, nodes, pods.        unavailable on windows
view-secret                     Decode Kubernetes secrets                           no
view-serviceaccount-kubeconfig  Show a kubeconfig setting to access the apiserv...  no
view-utilization                Shows cluster cpu and memory utilization            unavailable on windows
virt                            Control KubeVirt virtual machines using virtctl     no
warp                            Sync and execute local files in Pod                 unavailable on windows
who-can                         Shows who has RBAC permissions to access Kubern...  no
whoami                          Show the subject that's currently authenticated...  unavailable on windows

Azure Kubernetes Service (AKS)

If you don’t have a running cluster to use, you can setup a new one pretty easily.  Login to Azure and set subscription (if you have more than one).

C:\Users\isaac\Downloads>az login
C:\Users\isaac\Downloads>az account set --subscription 70abcdef-1234-1234-1234-987654321

I plan to use an existing resource group (idjaksdemo), however you may need to create a resource group first (az group create)

C:\Users\isaac\Downloads>az aks create --resource-group idjaksdemo --name idjaksdemo01 --node-count 3 --network-plugin azure --generate-ssh-keys
{
  "aadProfile": null,
  "addonProfiles": {
    "KubeDashboard": {
      "config": null,
      "enabled": true,
      "identity": null
    }
  },
  "agentPoolProfiles": [
    {
      "availabilityZones": null,
      "count": 3,
      "enableAutoScaling": null,
      "enableNodePublicIp": false,
      "maxCount": null,
      "maxPods": 30,
      "minCount": null,
      "mode": "System",
      "name": "nodepool1",
      "nodeLabels": {},
      "nodeTaints": null,
      "orchestratorVersion": "1.16.10",
      "osDiskSizeGb": 128,
      "osType": "Linux",
      "provisioningState": "Succeeded",
      "scaleSetEvictionPolicy": null,
      "scaleSetPriority": null,
      "spotMaxPrice": null,
      "tags": null,
      "type": "VirtualMachineScaleSets",
      "vmSize": "Standard_DS2_v2",
      "vnetSubnetId": null
    }
  ],
  "apiServerAccessProfile": null,
  "autoScalerProfile": null,
  "diskEncryptionSetId": null,
  "dnsPrefix": "idjaksdemo-idjaksdemo-70b42e",
  "enablePodSecurityPolicy": null,
  "enableRbac": true,
  "fqdn": "idjaksdemo-idjaksdemo-70b42e-b43cba45.hcp.eastus.azmk8s.io",
  "id": "/subscriptions/70abcdef-1234-1234-1234-987654321/resourcegroups/idjaksdemo/providers/Microsoft.ContainerService/managedClusters/idjaksdemo01",
  "identity": null,
  "identityProfile": null,
  "kubernetesVersion": "1.16.10",
  "linuxProfile": {
    "adminUsername": "azureuser",
    "ssh": {
      "publicKeys": [
        {
          "keyData": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDihWpPWuhb/J/hB3D3jPVoUCG4gjaWknbnZOVgOKszV5UTgpAB2BnvOfMZMjWr43M/bb9tmBxCCw6Mw0m8phuaskHP6rrsCHKBQw7CXAmDkKgH8+6ul3MYhyiEC4tjuWQXd17oWo7RGbA/EE5hIJzx5fDBbe2qjPdVJKFUq8TEwzdHCT4LRDGjCUaeu1qhxmJszCsQaAJqH7T1ah8HvnM+x++pux0MXIMu3p7Ay098lYuO9RxHvcXW1IH5RrUV+cgWZcW2JZSnIiRn1KXfyUvZf/fpLK9nUvnSYW+Q2WPdJVhLUXQ6OWk2D6Z3M0Mv1r839g4V62gryBj3hKtHGkwj isaac@DESKTOP-2SQ9NQM\n"
        }
      ]
    }
  },
  "location": "eastus",
  "maxAgentPools": 10,
  "name": "idjaksdemo01",
  "networkProfile": {
    "dnsServiceIp": "10.0.0.10",
    "dockerBridgeCidr": "172.17.0.1/16",
    "loadBalancerProfile": {
      "allocatedOutboundPorts": null,
      "effectiveOutboundIps": [
        {
          "id": "/subscriptions/70abcdef-1234-1234-1234-987654321/resourceGroups/MC_idjaksdemo_idjaksdemo01_eastus/providers/Microsoft.Network/publicIPAddresses/3e126549-b950-4625-9e80-1d785014be23",
          "resourceGroup": "MC_idjaksdemo_idjaksdemo01_eastus"
        }
      ],
      "idleTimeoutInMinutes": null,
      "managedOutboundIps": {
        "count": 1
      },
      "outboundIpPrefixes": null,
      "outboundIps": null
    },
    "loadBalancerSku": "Standard",
    "networkMode": null,
    "networkPlugin": "azure",
    "networkPolicy": null,
    "outboundType": "loadBalancer",
    "podCidr": null,
    "serviceCidr": "10.0.0.0/16"
  },
  "nodeResourceGroup": "MC_idjaksdemo_idjaksdemo01_eastus",
  "privateFqdn": null,
  "provisioningState": "Succeeded",
  "resourceGroup": "idjaksdemo",
  "servicePrincipalProfile": {
    "clientId": "8ab1be70-ca21-455c-97a3-3c9d8515ead2",
    "secret": null
  },
  "sku": {
    "name": "Basic",
    "tier": "Free"
  },
  "tags": null,
  "type": "Microsoft.ContainerService/ManagedClusters",
  "windowsProfile": {
    "adminPassword": null,
    "adminUsername": "azureuser"
  }
}

Helm

We need Helm next, if you need to install it, get Helm 3 from https://github.com/helm/helm/releases

C:\Users\isaac\Downloads>"C:\Program Files\helm3\helm.exe" version
version.BuildInfo{Version:"v3.3.0-rc.2", GitCommit:"8a4aeec08d67a7b84472007529e8097ec3742105", GitTreeState:"dirty", GoVersion:"go1.14.6"}

To type it all together, we need to get the kubeconfig for kubectl to use.

C:\Users\isaac\Downloads>az aks list -o table
Name          Location    ResourceGroup    KubernetesVersion    ProvisioningState    Fqdn
------------  ----------  ---------------  -------------------  -------------------  ----------------------------------------------------------
idjaksdemo01  eastus      idjaksdemo       1.16.10              Succeeded            idjaksdemo-idjaksdemo-70b42e-b43cba45.hcp.eastus.azmk8s.io

C:\Users\isaac\Downloads>az aks get-credentials -n idjaksdemo01 -g idjaksdemo --admin
Merged "idjaksdemo01-admin" as current context in C:\Users\isaac\.kube\config

C:\Users\isaac\Downloads>kubectl get nodes
NAME                                STATUS   ROLES   AGE     VERSION
aks-nodepool1-34730799-vmss000000   Ready    agent   3h19m   v1.16.10
aks-nodepool1-34730799-vmss000001   Ready    agent   3h19m   v1.16.10
aks-nodepool1-34730799-vmss000002   Ready    agent   3h19m   v1.16.10

Installing Credential Manager

Now that we have Kubectl, Helm, and work Kubernetes cluster, let’s install the cert manager following their guide (https://cert-manager.io/docs/installation/kubernetes/).

First, let’s check to see if the credential manager is already installed:

C:\Users\isaac\Downloads>kubectl get pods --all-namespaces
NAMESPACE     NAME                                         READY   STATUS    RESTARTS   AGE
kube-system   azure-cni-networkmonitor-7nhmb               1/1     Running   0          3h20m
kube-system   azure-cni-networkmonitor-f94pn               1/1     Running   0          3h20m
kube-system   azure-cni-networkmonitor-r99tj               1/1     Running   0          3h20m
kube-system   azure-ip-masq-agent-g425b                    1/1     Running   0          3h20m
kube-system   azure-ip-masq-agent-ggf5h                    1/1     Running   0          3h20m
kube-system   azure-ip-masq-agent-lrqp6                    1/1     Running   0          3h20m
kube-system   coredns-869cb84759-cdq8r                     1/1     Running   0          3h20m
kube-system   coredns-869cb84759-g9vpm                     1/1     Running   0          3h20m
kube-system   coredns-autoscaler-5b867494f-psz7w           1/1     Running   0          3h20m
kube-system   dashboard-metrics-scraper-566c858889-9dt86   1/1     Running   0          3h20m
kube-system   kube-proxy-gwzw7                             1/1     Running   0          3h20m
kube-system   kube-proxy-qkh2b                             1/1     Running   0          3h20m
kube-system   kube-proxy-tlxd7                             1/1     Running   0          3h20m
kube-system   kubernetes-dashboard-7f7d6bbd7f-sznqp        1/1     Running   0          3h20m
kube-system   metrics-server-6cd7558856-vtkf4              1/1     Running   0          3h20m
kube-system   tunnelfront-788b587d5d-cbt8t                 2/2     Running   0          3h20m

Create the namespace and add the helm repository that contains the current chart:

C:\Users\isaac\Downloads>kubectl create namespace cert-manager
namespace/cert-manager created

C:\Users\isaac\Downloads>helm repo add jetstack https://charts.jetstack.io
"jetstack" has been added to your repositories

Next, since we are running 1.16, we’ll need to use kubectl to install the Customer Resource Definitions (CRDS):

C:\Users\isaac\Downloads>kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.16.0/cert-manager.crds.yaml
customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/challenges.acme.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/clusterissuers.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/issuers.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/orders.acme.cert-manager.io created

Next we should install cert manager

C:\Users\isaac\Downloads>helm install cert-manager jetstack/cert-manager --namespace cert-manager --version v0.16.0
NAME: cert-manager
LAST DEPLOYED: Sat Aug  1 20:09:51 2020
NAMESPACE: cert-manager
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
cert-manager has been deployed successfully!

In order to begin issuing certificates, you will need to set up a ClusterIssuer
or Issuer resource (for example, by creating a 'letsencrypt-staging' issuer).

More information on the different types of issuers and how to configure them
can be found in our documentation:

https://cert-manager.io/docs/configuration/

For information on how to configure cert-manager to automatically provision
Certificates for Ingress resources, take a look at the `ingress-shim`
documentation:

https://cert-manager.io/docs/usage/ingress/

Verification it’s running

C:\Users\isaac\Downloads>kubectl get pods --namespace cert-manager
NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-69779b98cd-bf2ll              1/1     Running   0          40m
cert-manager-cainjector-7c4c4bbbb9-7jj77   1/1     Running   0          40m
cert-manager-webhook-6496b996cb-fhz8g      1/1     Running   0          40m

Next, let’s just verify the basics with a self-signed cert

You can use notepad to create  test-resources.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: cert-manager-test
---
apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
  name: test-selfsigned
  namespace: cert-manager-test
spec:
  selfSigned: {}
---
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: selfsigned-cert
  namespace: cert-manager-test
spec:
  dnsNames:
    - example.com
  secretName: selfsigned-cert-tls
  issuerRef:
    name: test-selfsigned

Then apply it

C:\Users\isaac\Downloads>kubectl apply -f test-resources.yaml
namespace/cert-manager-test created
issuer.cert-manager.io/test-selfsigned created
certificate.cert-manager.io/selfsigned-cert created

We can now see the self signed cert in the namespace

C:\Users\isaac\Downloads>kubectl describe certificate -n cert-manager-test
Name:         selfsigned-cert
Namespace:    cert-manager-test
Labels:       <none>
Annotations:  API Version:  cert-manager.io/v1beta1
Kind:         Certificate
Metadata:
  Creation Timestamp:  2020-08-02T01:55:16Z
  Generation:          1
  Resource Version:    25986
  Self Link:           /apis/cert-manager.io/v1beta1/namespaces/cert-manager-test/certificates/selfsigned-cert
  UID:                 9d239d5b-0279-44f2-9f25-a5776d082a29
Spec:
  Dns Names:
    example.com
  Issuer Ref:
    Name:       test-selfsigned
  Secret Name:  selfsigned-cert-tls
Status:
  Conditions:
    Last Transition Time:  2020-08-02T01:55:17Z
    Message:               Certificate is up to date and has not expired
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Not After:               2020-10-31T01:55:17Z
  Not Before:              2020-08-02T01:55:17Z
  Renewal Time:            2020-10-01T01:55:17Z
  Revision:                1
Events:
  Type    Reason     Age    From          Message
  ----    ------     ----   ----          -------
  Normal  Issuing    4m33s  cert-manager  Issuing certificate as Secret does not exist
  Normal  Generated  4m32s  cert-manager  Stored new private key in temporary Secret resource "selfsigned-cert-4gq2r"
  Normal  Requested  4m32s  cert-manager  Created new CertificateRequest resource "selfsigned-cert-zhhvq"
  Normal  Issuing    4m32s  cert-manager  The certificate has been successfully issued

We can see the cert in the namespace

C:\Users\isaac\Downloads>kubectl get CertificateRequest  -n cert-manager-test
NAME                    READY   AGE
selfsigned-cert-zhhvq   True    7m3s

C:\Users\isaac\Downloads>kubectl get CertificateRequest selfsigned-cert-zhhvq -n cert-manager-test -o yaml
apiVersion: cert-manager.io/v1beta1
kind: CertificateRequest
metadata:
  annotations:
    cert-manager.io/certificate-name: selfsigned-cert
    cert-manager.io/certificate-revision: "1"
    cert-manager.io/private-key-secret-name: selfsigned-cert-4gq2r
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"cert-manager.io/v1alpha2","kind":"Certificate","metadata":{"annotations":{},"name":"selfsigned-cert","namespace":"cert-manager-test"},"spec":{"dnsNames":["example.com"],"issuerRef":{"name":"test-selfsigned"},"secretName":"selfsigned-cert-tls"}}
  creationTimestamp: "2020-08-02T01:55:17Z"
  generateName: selfsigned-cert-
  generation: 1
  name: selfsigned-cert-zhhvq
  namespace: cert-manager-test
  ownerReferences:
  - apiVersion: cert-manager.io/v1alpha2
    blockOwnerDeletion: true
    controller: true
    kind: Certificate
    name: selfsigned-cert
    uid: 9d239d5b-0279-44f2-9f25-a5776d082a29
  resourceVersion: "25978"
  selfLink: /apis/cert-manager.io/v1beta1/namespaces/cert-manager-test/certificaterequests/selfsigned-cert-zhhvq
  uid: 92ccd754-74c6-40da-b3ff-429e6bd5fa47
spec:
  issuerRef:
    name: test-selfsigned
  request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ2JqQ0NBVllDQVFBd0FEQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU9YSAovdkpWQzQ2SXpCRlBScmZnWUREKzJaWkYxbGREY1NydnVBNVNGdkJhdGpuak40Y3p2M2JNb2xoaXBmRHJ0SVhICmx2TE4vNUUxM1BweVArNlplUHRtMzBvZ2hwZndRZUJjbkhmYlhKWmNHTGlKV0oyWU1zVVk0eVQ0NERnZ1k1VGUKZmpFY2h3OUJrVGdGZXFvQlZ3K2xFa3hvM3l4L0pYT2hndmovSzJoOUUwRVEvcFJkSUZxWDRUZW04MUtzY25haAp4VW9sdXpKZ1hKNDNUQlNNbGlLT0hzblhxbjBpR2wrZ2NoWmV3NGZHNUErZ0JsNFY2eG5YL3lSOWs0TUx3bjNjCnNSV1QyYzFqZlovYk03ZWs0dFVoc0xZQk8wYXhmcXR3b2FIM1k3bEc0dTZWNzYzdG1laU5JTE9ZT3ZWblk0ckcKTklQRDF3bHhVYVlyQmpzRWVNa0NBd0VBQWFBcE1DY0dDU3FHU0liM0RRRUpEakVhTUJnd0ZnWURWUjBSQkE4dwpEWUlMWlhoaGJYQnNaUzVqYjIwd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFLVEwxaFNKUkFoVjA5TGs0SzdLCmJuaDA3WDJwbHpDRmJrZWJVUHU4UWdLL1VvM2gvVnRaOE5OT1BUdTdPcTFtVDNOL3B2THRhbnBkWUlKejMzVzQKenN4U3B3bXJsZ01nNUlKT1dxRlBFd1pzSlhDSDFURHp4U3B6TWg1OGxJSGljVFQ0OVdzcTk3U2t0d0JmdFU0TQpZTHVneXdOMVJLMUk2YVZlTklZZnp5RlRjZlZxaEdUV1dkSVEzQzE0VzlzWHBTeTcrSlVUNnZYOXAybmFIQlJoCmRwQTBUbmNsQ1E5Z044cEhVTVVpdkh5NFBWUVNxNFZlcHd4V3Bsb2xIdVhwR0hTVzVTU3Fpc3pCc0RVNk5waWYKNkRxMWVqbUltUzFiMWpIOWdtMDFIcE14YjBJS2RoMEZPdmc2R3locUIzVDJHTDRvT2ZJQmZkYktSMkpPVUVJVAordTg9Ci0tLS0tRU5EIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLQo=
status:
  ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN4akNDQWE2Z0F3SUJBZ0lSQVBzYnVVc3UvTEFmVXduK1hPT3pUc0l3RFFZSktvWklodmNOQVFFTEJRQXcKQURBZUZ3MHlNREE0TURJd01UVTFNVGRhRncweU1ERXdNekV3TVRVMU1UZGFNQUF3Z2dFaU1BMEdDU3FHU0liMwpEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUURseC83eVZRdU9pTXdSVDBhMzRHQXcvdG1XUmRaWFEzRXE3N2dPClVoYndXclk1NHplSE03OTJ6S0pZWXFYdzY3U0Z4NWJ5emYrUk5kejZjai91bVhqN1p0OUtJSWFYOEVIZ1hKeDMKMjF5V1hCaTRpVmlkbURMRkdPTWsrT0E0SUdPVTNuNHhISWNQUVpFNEJYcXFBVmNQcFJKTWFOOHNmeVZ6b1lMNAoveXRvZlJOQkVQNlVYU0JhbCtFM3B2TlNySEoyb2NWS0pic3lZRnllTjB3VWpKWWlqaDdKMTZwOUlocGZvSElXClhzT0h4dVFQb0FaZUZlc1oxLzhrZlpPREM4SjkzTEVWazluTlkzMmYyek8zcE9MVkliQzJBVHRHc1g2cmNLR2gKOTJPNVJ1THVsZSt0N1pub2pTQ3ptRHIxWjJPS3hqU0R3OWNKY1ZHbUt3WTdCSGpKQWdNQkFBR2pPekE1TUE0RwpBMVVkRHdFQi93UUVBd0lGb0RBTUJnTlZIUk1CQWY4RUFqQUFNQmtHQTFVZEVRRUIvd1FQTUEyQ0MyVjRZVzF3CmJHVXVZMjl0TUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFDcWxVcWVWRnJDUStycGpycVJaaUdXVmhXZ1h6WEYKZUh6WGdZUG5pazZWclpCeUduamRKTmd0L0hRWUtBUkFMdFowZ3NnQWV3VWRQVUpQYU9TVmNpUmt0ZzdVTGdRLwpUeDZ6eXpoYlp5Y1dtWlp0T2xmZFZ3aFc1cUhsTThoUzAzWkpDdUxnd0YwUGtPbW0xTHpZYWhPdkhxRkxVRkFzCkNRdXdZQjFiYVB0RnlDelJRRlFvVkpDT29nOW05WTJyY1UzOFlnb21zeEUrcXNMU29qQjBGbU9hODRtNGhWNWcKLzNOMWw5WXBwY0RtT2JqU2hDajlHTEtuMEdWYkUrQitwRjVGM0ZLcWNNamFiMXhXYmNDQktEZjF2a1dWUnNrUApEd1l4aUViVHZtd3hvMmh5clBURDlOUmdUVWxyZmRwbjRPalMzTmRpdURITUJNMG84dXZabkg4YgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
  certificate: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN4akNDQWE2Z0F3SUJBZ0lSQVBzYnVVc3UvTEFmVXduK1hPT3pUc0l3RFFZSktvWklodmNOQVFFTEJRQXcKQURBZUZ3MHlNREE0TURJd01UVTFNVGRhRncweU1ERXdNekV3TVRVMU1UZGFNQUF3Z2dFaU1BMEdDU3FHU0liMwpEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUURseC83eVZRdU9pTXdSVDBhMzRHQXcvdG1XUmRaWFEzRXE3N2dPClVoYndXclk1NHplSE03OTJ6S0pZWXFYdzY3U0Z4NWJ5emYrUk5kejZjai91bVhqN1p0OUtJSWFYOEVIZ1hKeDMKMjF5V1hCaTRpVmlkbURMRkdPTWsrT0E0SUdPVTNuNHhISWNQUVpFNEJYcXFBVmNQcFJKTWFOOHNmeVZ6b1lMNAoveXRvZlJOQkVQNlVYU0JhbCtFM3B2TlNySEoyb2NWS0pic3lZRnllTjB3VWpKWWlqaDdKMTZwOUlocGZvSElXClhzT0h4dVFQb0FaZUZlc1oxLzhrZlpPREM4SjkzTEVWazluTlkzMmYyek8zcE9MVkliQzJBVHRHc1g2cmNLR2gKOTJPNVJ1THVsZSt0N1pub2pTQ3ptRHIxWjJPS3hqU0R3OWNKY1ZHbUt3WTdCSGpKQWdNQkFBR2pPekE1TUE0RwpBMVVkRHdFQi93UUVBd0lGb0RBTUJnTlZIUk1CQWY4RUFqQUFNQmtHQTFVZEVRRUIvd1FQTUEyQ0MyVjRZVzF3CmJHVXVZMjl0TUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFDcWxVcWVWRnJDUStycGpycVJaaUdXVmhXZ1h6WEYKZUh6WGdZUG5pazZWclpCeUduamRKTmd0L0hRWUtBUkFMdFowZ3NnQWV3VWRQVUpQYU9TVmNpUmt0ZzdVTGdRLwpUeDZ6eXpoYlp5Y1dtWlp0T2xmZFZ3aFc1cUhsTThoUzAzWkpDdUxnd0YwUGtPbW0xTHpZYWhPdkhxRkxVRkFzCkNRdXdZQjFiYVB0RnlDelJRRlFvVkpDT29nOW05WTJyY1UzOFlnb21zeEUrcXNMU29qQjBGbU9hODRtNGhWNWcKLzNOMWw5WXBwY0RtT2JqU2hDajlHTEtuMEdWYkUrQitwRjVGM0ZLcWNNamFiMXhXYmNDQktEZjF2a1dWUnNrUApEd1l4aUViVHZtd3hvMmh5clBURDlOUmdUVWxyZmRwbjRPalMzTmRpdURITUJNMG84dXZabkg4YgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
  conditions:
  - lastTransitionTime: "2020-08-02T01:55:17Z"
    message: Certificate fetched from issuer successfully
    reason: Issued
    status: "True"
    type: Ready

Lastly, we can cleanup and remove that self signed cert

C:\Users\isaac\Downloads>kubectl delete -f test-resources.yaml
namespace "cert-manager-test" deleted
issuer.cert-manager.io "test-selfsigned" deleted
certificate.cert-manager.io "selfsigned-cert" deleted

Create LetsEncrypt Issuers

Let’s create Staging and Production issuers

The staging issuer:

apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
  name: letsencrypt-staging
  namespace: default
spec:
  acme:
    # You must replace this email address with your own.
    # Let's Encrypt will use this to contact you about expiring
    # certificates, and issues related to your account.
    email: isaac.johnson@gmail.com
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      # Secret resource used to store the account's private key.
      name: letsencrypt-account-key
    # Add a single challenge solver, HTTP01 using nginx
    solvers:
    - dns01:
        cnameStrategy: Follow
        clouddns:
          project: enscluster
          serviceAccountSecretRef:
            name: clouddns-dns01-solver-svc-acct
            key: key.json

And production

apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
  namespace: default
spec:
  acme:
    # The ACME server URL
    server: https://acme-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: isaac.johnson@gmail.com
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-prod
    # Enable the HTTP-01 challenge provider
    solvers:
    - http01:
        ingress:
          class: nginx

Before we continue, we’ll need an nginx ingress controller to receive traffic

C:\Users\isaac\Downloads>helm repo add nginx-stable https://helm.nginx.com/stable
"nginx-stable" has been added to your repositories

C:\Users\isaac\Downloads>helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "nginx-stable" chart repository
...Successfully got an update from the "jetstack" chart repository
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈ Happy Helming!⎈

C:\Users\isaac\Downloads> helm install my-release nginx-stable/nginx-ingress
NAME: my-release
LAST DEPLOYED: Sat Aug  1 21:41:11 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
The NGINX Ingress Controller has been installed.

Take note of the chart and release (nginx-stable/nginx-ingress and "my-release").  We'll come back to that later

We now need to get the external IP to use in our DNS record

C:\Users\isaac\Downloads>kubectl get svc --all-namespaces
NAMESPACE      NAME                        TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)                      AGE
cert-manager   cert-manager                ClusterIP      10.0.207.7     <none>          9402/TCP                     92m
cert-manager   cert-manager-webhook        ClusterIP      10.0.22.45     <none>          443/TCP                      92m
default        kubernetes                  ClusterIP      10.0.0.1       <none>          443/TCP                      5h4m
default        my-release-nginx-ingress    LoadBalancer   10.0.120.83    52.190.47.225   80:32385/TCP,443:31855/TCP   63s
kube-system    dashboard-metrics-scraper   ClusterIP      10.0.231.231   <none>          8000/TCP                     5h3m
kube-system    kube-dns                    ClusterIP      10.0.0.10      <none>          53/UDP,53/TCP                5h3m
kube-system    kubernetes-dashboard        ClusterIP      10.0.161.1     <none>          443/TCP                      5h3m
kube-system    metrics-server              ClusterIP      10.0.59.71     <none>          443/TCP                      5h3m

Create an A DNS Record

Let’s set an A/AAAA record to test.  Testing.tpk.best to 52.190.47.225  (which we see above in the EXTERNAL-IP)

Using my NS provider, Gandi.net to set a pointer manually

We can now apply the issuers yaml to install them into the cluster:

C:\Users\isaac\Downloads>kubectl apply -f issuer-prod.yaml
clusterissuer.cert-manager.io/letsencrypt-prod created

C:\Users\isaac\Downloads>kubectl apply -f issuer-staging.yaml
issuer.cert-manager.io/letsencrypt-staging created

Getting a certificate

Let’s create a certificate manually, for instance testing-tpk-best.yml

apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: testing-tpk-best
  namespace: default
spec:
  secretName: testing.tpk.best-cert
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  commonName: testing.tpk.best
  dnsNames:
  - testing.tpk.best
  acme:
    config:
    - http01:
        ingressClass: nginx
      domains:
      - testing.tpk.best

Then apply it:

C:\Users\isaac\Downloads>kubectl apply -f testing-tpk-best.yml --validate=false
certificate.cert-manager.io/testing-tpk-best created

We can now verify it was created:

C:\Users\isaac\Downloads>kubectl get certificate
NAME               READY   SECRET                  AGE
testing-tpk-best   True    testing.tpk.best-cert   86m

C:\Users\isaac\Downloads>kubectl get certificate testing-tpk-best -o yaml
apiVersion: cert-manager.io/v1beta1
kind: Certificate
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"cert-manager.io/v1alpha2","kind":"Certificate","metadata":{"annotations":{},"name":"testing-tpk-best","namespace":"default"},"spec":{"acme":{"config":[{"domains":["testing.tpk.best"],"http01":{"ingressClass":"nginx"}}]},"commonName":"testing.tpk.best","dnsNames":["testing.tpk.best"],"issuerRef":{"kind":"ClusterIssuer","name":"letsencrypt-prod"},"secretName":"testing.tpk.best-cert"}}
  creationTimestamp: "2020-08-02T14:38:37Z"
  generation: 1
  name: testing-tpk-best
  namespace: default
  resourceVersion: "146491"
  selfLink: /apis/cert-manager.io/v1beta1/namespaces/default/certificates/testing-tpk-best
  uid: 2510f98c-2dbd-45ea-92e9-891763e37b74
spec:
  commonName: testing.tpk.best
  dnsNames:
  - testing.tpk.best
  issuerRef:
    kind: ClusterIssuer
    name: letsencrypt-prod
  secretName: testing.tpk.best-cert
status:
  conditions:
  - lastTransitionTime: "2020-08-02T14:39:03Z"
    message: Certificate is up to date and has not expired
    reason: Ready
    status: "True"
    type: Ready
  notAfter: "2020-10-31T13:39:02Z"
  notBefore: "2020-08-02T13:39:02Z"
  renewalTime: "2020-10-01T13:39:02Z"
  revision: 1

Next, get the crt and key from the secret that matches in the namespace:

kubectl get secrets testing.tpk.best-cert -o yaml > my_certs.yaml
the yaml output (note very long lines for line 3 and 4)

If we take the base64 value in tls.crt above and put into a text file, we can run that through certutil to get back the actual cert value:

C:\Users\isaac\Downloads>certutil -decode my_crt_b64.txt my_cert.crt
Input Length = 4744
Output Length = 3558
CertUtil: -decode command completed successfully.
a section of the public key, decrypted

We can do the same with the key

C:\Users\isaac\Downloads>certutil -decode my_key_b64.txt my_key.key
Input Length = 2236
Output Length = 1675
CertUtil: -decode command completed successfully.
a section of the private key, decrypted

Using it

We can now use those values either directly or referencing the secret in a variety of ways.  One way we can quick test it is to patch the helm release of our ingress controller.

Up until now our HTTPS traffic on the Nginx ingress was being served with a self-signed cert.  While this does encrypt traffic, the browser recognizes that it’s an invalid cert and lets us know.  Depending on your security settings, some browsers will block self-signed https traffic (443).

the broken lock or "not secure" of a browser viewing self-signed https websites

Let’s take those values we saw in the secret and apply them to the nginx release we applied at the start:

C:\Users\isaac\Downloads>helm upgrade my-release nginx-stable/nginx-ingress --set controller.defaultTLS.cert=LS0**********K --set controller.defaultTLS.key=LS0********==
Release "my-release" has been upgraded. Happy Helming!
NAME: my-release
LAST DEPLOYED: Sun Aug  2 12:00:57 2020
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None
NOTES:
The NGINX Ingress Controller has been installed.

Clearly i masked those.. But the values will be rather sizable when you do this yourself:

showing the large size of the base64 values

Now when we reload the DNS entry, a valid certificate is loaded:

lock icon indicates valid SSL cert

This is how we did it manually, but we can have annotations handle this work for us.

Automatic Certificates

We can refer to this guide and see one could just launch an Nginx ingress controller and reference the letsencyrpt-prod provider to have kubernetes do it for us:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: echo-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  tls:
  - hosts:
    - echo1.example.com
    - echo2.example.com
    secretName: echo-tls
  rules:
  - host: echo1.example.com
    http:
      paths:
      - backend:
          serviceName: echo1
          servicePort: 80
  - host: echo2.example.com
    http:
      paths:
      - backend:
          serviceName: echo2
          servicePort: 80

A similar guide for Joomla or Ghost uses the provider in much the same way:

helm install ghost bitnami/ghost --set ghostHost=blog.DOMAIN,serviceType=ClusterIP

with an ingress definition:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: joomla-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  rules:
  - host: DOMAIN
    http:
      paths:
      - backend:
          serviceName: joomla
          servicePort: http
        path: /
  - host: blog.DOMAIN
    http:
      paths:
      - backend:
          serviceName: ghost
          servicePort: 80
        path: /
  tls:
  - secretName: joomla-tls-cert
    hosts:
      - DOMAIN
      - blog.DOMAIN

Krew : Cert-Manager

We can use a newer cert-manager utility as provided via Krew:

C:\Users\isaac\Downloads>kubectl krew install cert-manager
Updated the local copy of plugin index.
Installing plugin: cert-manager
Installed plugin: cert-manager
\
 | Use this plugin:
 |      kubectl cert-manager
 | Documentation:
 |      https://github.com/jetstack/cert-manager
/
←[31;1mWARNING←[0m: You installed plugin "cert-manager" from the krew-index plugin repository.
   These plugins are not audited for security by the Krew maintainers.
   Run them at your own risk.

Presently, we can use this to see the status of the certs:

C:\Users\isaac\Downloads>kubectl cert-manager status certificate testing-tpk-best
Name: testing-tpk-best
Namespace: default
Conditions:
  Ready: True, Reason: Ready, Message: Certificate is up to date and has not expired
DNS Names:
- testing.tpk.best
Events:  <none>
Issuer:
  Name: letsencrypt-prod
  Kind: ClusterIssuer
Secret Name: testing.tpk.best-cert
Not Before: 2020-08-02T08:39:02-05:00
Not After: 2020-10-31T08:39:02-05:00
Renewal Time: 2020-10-01T08:39:02-05:00
No CertificateRequest found for this Certificate

The other really nice feature of the cert-manager is the ability to manually renew a certificate ahead of it’s expiry.

For instance, we can look at the one we requested earlier:

C:\Users\isaac\Downloads>kubectl cert-manager status certificate testing-tpk-best
Name: testing-tpk-best
Namespace: default
Conditions:
  Ready: True, Reason: Ready, Message: Certificate is up to date and has not expired
DNS Names:
- testing.tpk.best
Events:  <none>
Issuer:
  Name: letsencrypt-prod
  Kind: ClusterIssuer
Secret Name: testing.tpk.best-cert
Not Before: 2020-08-02T08:39:02-05:00
Not After: 2020-10-31T08:39:02-05:00
Renewal Time: 2020-10-01T08:39:02-05:00
No CertificateRequest found for this Certificate

Then trigger a manual renewal now

C:\Users\isaac\Downloads>kubectl cert-manager renew testing-tpk-best
Manually triggered issuance of Certificate default/testing-tpk-best

And lastly, verify that it did indeed renew it:

C:\Users\isaac\Downloads>kubectl cert-manager status certificate testing-tpk-best
Name: testing-tpk-best
Namespace: default
Conditions:
  Ready: True, Reason: Ready, Message: Certificate is up to date and has not expired
DNS Names:
- testing.tpk.best
Events:
  Type    Reason     Age                  From          Message
  ----    ------     ----                 ----          -------
  Normal  Reused     28s                  cert-manager  Reusing private key stored in existing Secret resource "testing.tpk.best-cert"
  Normal  Requested  27s                  cert-manager  Created new CertificateRequest resource "testing-tpk-best-z7jlp"
  Normal  Issuing    24s (x2 over 6h48m)  cert-manager  The certificate has been successfully issued
Issuer:
  Name: letsencrypt-prod
  Kind: ClusterIssuer
Secret Name: testing.tpk.best-cert
Not Before: 2020-08-02T15:27:12-05:00
Not After: 2020-10-31T15:27:12-05:00
Renewal Time: 2020-10-01T15:27:12-05:00
No CertificateRequest found for this Certificate

Granted, it's advanced by just a few hours - but then I only requested the certificate this morning.

Cleanup:

We need to first delete the cluster:

C:\Users\isaac\Downloads>az aks list -o table
Name          Location    ResourceGroup    KubernetesVersion    ProvisioningState    Fqdn
------------  ----------  ---------------  -------------------  -------------------  ----------------------------------------------------------
idjaksdemo01  eastus      idjaksdemo       1.16.10              Succeeded            idjaksdemo-idjaksdemo-70b42e-b43cba45.hcp.eastus.azmk8s.io

C:\Users\isaac\Downloads>az aks delete -n idjaksdemo01 -g idjaksdemo
Are you sure you want to perform this operation? (y/n): y

Then we should remove the DNS entry as well.

Deleting the A record

Summary

Using Kubernetes and the Cert-Manager service we can easily create valid automatic and manual SSL certificates.  We tested the validity of the certificate by applying it to the Nginx ingress controller.   Lastly, we took a look at a couple nice features of the cert-manager krew plugin for kubectl, namely status and renew.