Author : MD TAREQ HASSAN | Updated : 2021/06/24

Prerequisites

Now, get AKS Credentials To Execute kubectl Commands

Concepts

See: Understanding TLS and Certificate Authority

What Is Lets Encrypt?

Cert Manager

Issuer

ClusterIssuer

Built-in certificate issuers

Cluster Resource Namespace

Referencing secret created by ClusterIssuer

ACME Challenge

Links

Installing Cert Manager

Namespace

#
# Create the namespace for cert-manager
#
kubectl create namespace cert-manager

#
# Label the cert-manager namespace to disable resource validation
#
kubectl label namespace cert-manager cert-manager.io/disable-validation=true

Installing cert-manager using Helm chart

#
# Add the Jetstack Helm repository
#
helm repo add jetstack https://charts.jetstack.io

#
# Update your local Helm chart repository cache
#
helm repo update

#
# Install cert-manager using helm chart
#
# Check version: https://artifacthub.io/packages/helm/cert-manager/cert-manager
# Install latest version
#
# Automatically install cert-manager CRDs as part of your Helm release
# Make sure 'cert-manager' namespace is created and resource validation is disabled (see above)
# "--create-namespace" -> could be used but need to disable resource validation, that's why namespace is created beforehand
#
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--version v1.4.0 \
--set installCRDs=true

#
# Helm chart installation when CRDs are created beforehand using kubectl command
# 
# helm install cert-manager jetstack/cert-manager \
#  --namespace cert-manager \
#  --version v1.4.0
#

#
# Some additional flag (if needed)
#
--set ingressShim.defaultACMEChallengeType=dns01 \
--set ingressShim.defaultACMEDNS01ChallengeProvider=AzureDNS \
--set ingressShim.defaultIssuerName=letsencrypt-prod \
--set ingressShim.defaultIssuerKind=ClusterIssuer \

Verify installation

kubectl get pods --namespace cert-manager

Alternative CRDs installation (before installing cert-manager helm chart): Installing cert-manager CRDs with kubectl

#
# Install the CustomResourceDefinition resources separately
# Note: --validate=false is required per https://github.com/jetstack/cert-manager/issues/2208#issuecomment-541311021
#
kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.14/deploy/manifests/00-crds.yaml --validate=false
# kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.4.0/cert-manager.crds.yaml

Issuer Resource

ACME Type Issuer

We will use Let’s Encrypt as ACME Type ClusterIssuer (Other issuer types: https://cert-manager.io/docs/configuration/#supported-issuer-types)

ACME Challenge

We will use ACME DNS01 challenge

ClusterIssuer

See below

Staging Server And HTTP01 Challenge

letsencrypt-staging.yaml

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
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: <YOUR.EMAIL@ADDRESS>

    # ACME server URL for Let’s Encrypt’s staging environment.
    # The staging environment will not issue trusted certificates but is
    # used to ensure that the verification process is working properly
    # before moving to production
    server: https://acme-staging-v02.api.letsencrypt.org/directory

    privateKeySecretRef:
      # Secret resource used to store the account's private key.
      name: letsencrypt-secret

    # Enable the HTTP-01 challenge provider
    # you prove ownership of a domain by ensuring that a particular
    # file is present at the domain
    solvers:
    - http01:
        ingress:
            class: azure/application-gateway

Create ClusterIssuer

#
# Since ClusterIssuer is non-namespaced, no need to specify namespace in yaml or kubectl command
#
kubectl apply -f letsencrypt-staging.yaml

Checking

#
# Check clusterissuer
#
kubectl describe clusterissuer letsencrypt-staging

#
# Issued certificate will be stored as K8s secret in 'cert-manager' namesapce
#
# This secret (tls certificate) will be referred in spec.tls section of ingress manifest yaml
#
kubectl get secret letsencrypt-secret -n cert-manager -o yaml

Create a sample web app, publish to Azure container registry and deploy to AKS. Now create an ingress for that web app.

letsencrypt-staging-ingress.yaml


Production Server And DNS01 Challenge

Managed Indentity For ClusterIssuer

Prepare managed identity and K8s pod identity

#
# Required variables
#
$ResourceGroup = "fusys-poc-rg"
$AksName = "fusys-poc-aks"
$DnsZoneName = "fusyspoc.com"
$CertManagerNamespace = "cert-manager"
$ManagedIdentityName = "dns-zone-contributor-mid"


#
# Create managed identity
#
$ManagedIdentity = $(az identity create --name $ManagedIdentityName --resource-group $ResourceGroup)
#echo $ManagedIdentity

#
# Extract required information from that managed identity
#
$ResourceId = (echo $ManagedIdentity | jq -r ".id") 
#echo $ResourceId

$ClientId = (echo $ManagedIdentity | jq -r ".clientId") 
#echo $ClientId

$PrincipleId = (echo $ManagedIdentity | jq -r ".principalId") 
#echo $PrincipleId

#
# Get DNS Zone resource id
#
$DnsZoneResourceId = $(az network dns zone show --name $DnsZoneName --resource-group $ResourceGroup --query "id" -o tsv)
#echo $DnsZoneResourceId 

#
# Create role assignment "DNS Zone Contributor"
#
az role assignment create --role "DNS Zone Contributor" --assignee $PrincipleId --scope $DnsZoneResourceId

#
# Create pod-identity (AzureIdentity,AzureIdentityBinding)
#
az aks pod-identity add `
--resource-group $ResourceGroup `
--cluster-name $AksName `
--namespace $CertManagerNamespace `
--name $ManagedIdentityName `
--identity-resource-id $ResourceId

#
# Check that the identity and identity binding are created
#
# Take note of identity binding name (will be used in cert-manager pod)
#
kubectl get AzureIdentity,AzureIdentityBinding -n $CertManagerNamespace
#echo $ManagedIdentityName-binding

Install cert-manager with pod identity

# 
# If you already installed cert-manager (with helm) without pod identity, 
# you need to uninstall and then re-install with pod identity
#
# Get releases
# Uninstall cert-manager
#
# helm uninstall RELEASE_NAME
#
helm list --all --all-namespaces
helm uninstall cert-manager


#
# Now install cert-manager
#
# If you already installed cert-manager, you need to re-install with "--set podLabels.aadpodidbinding=xxx-binding"
#
helm install cert-manager jetstack/cert-manager `
--namespace $CertManagerNamespace `
--version v1.4.0 `
--set installCRDs=true `
--set podLabels.aadpodidbinding=$ManagedIdentityName-binding

Gather following information about DNS Zone (will be used in ClusterIssuer dns01 solver)

#
# Resource group and DNS Zone
#
$DnsZoneName = "fusyspoc.com"
$ResourceGroup = "fusys-poc-rg"


#
# Get subcription id
#
$DnsZoneResourceId = (az resource show -g $ResourceGroup -n $DnsZoneName --resource-type "Microsoft.Network/dnsZones" --query id --output tsv)
echo $DnsZoneResourceId

$SubscriptionId = $DnsZoneResourceId.Split('/')[2]
echo $SubscriptionId

letsencrypt-prod.yaml

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    email: test@hovermind.com
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-secret
    solvers:
    - dns01:
        azureDNS:
          subscriptionID: 2f38b28b-b089-4a76-8109-9ef207934b0e
          resourceGroupName: fusys-poc-rg
          hostedZoneName: fusyspoc.com
          # Azure Cloud Environment, default to AzurePublicCloud
          environment: AzurePublicCloud

Create ClusterIssuer

#
# Since ClusterIssuer is non-namespaced, no need to specify namespace in yaml or kubectl command
#
kubectl apply -f letsencrypt-prod.yaml

Checking

#
# Check clusterissuer
#
kubectl describe clusterissuer letsencrypt-prod

#
# Issued certificate will be stored as K8s secret in 'cert-manager' namesapce
#
# This secret (tls certificate) will be referred in spec.tls section of ingress manifest yaml
#
kubectl get secret letsencrypt-secret -n cert-manager -o yaml

letsencrypt-prod-ingress.yaml