Getting wildcard SSL certificate in Kubernetes with cert-manager

Amet Umerov
Jun 17 · 4 min read

In this article, I’ll shortly describe how to get an SSL certificate with HTTP01 validation and a wildcard certificate with DNS01 validation on AWS example.

https://letsencrypt.org/

So we already have some ingress and HELM for our k8s cluster, and we want to get some certs for domain dummy.example.com.

Let’s install cert-manager using HELM:

helm install --namespace kube-system -n cert-manager stable/cert-manager

If you prefer to use the latest chart version for cert-manager you can follow the instructions here.

For issuing some certificates we need to have at least one Issuer or ClusterIssuer. The difference between them that Issuer works only inside one namespace, unlike ClusterIssuer which works globally for the cluster.

Let’s create ClusterIssuer:

cat <<EOF | kubectl create -f -
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
name: le-clusterissuer
namespace: kube-system
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: devops@example.com
privateKeySecretRef:
name: le-clusterissuer
http01: {}
EOF

What’s there:

  • le-clusteissuer — ClusterIssuer name

After creating ClusterIssuer we can check the status:

kubectl describe clusterissuer le-clusterissuer -n kube-system | egrep "Status|Message"
Status:
Message: The ACME account was registered with the ACME server
Status: True

So now we have ClusterIssuer, and we can create new certificates.

Certificate for dummy.example.com

Let’s create one (you should set up DNS record to your load balancer before doing that):

cat <<EOF | kubectl create -f -
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
name: dummy-stage
namespace: stage
spec:
secretName: cert-stage-dummy
dnsNames:
- dummy.example.com
acme:
config:
- http01:
ingressClass: nginx
domains:
- dummy.example.com
issuerRef:
name: le-clusterissuer
kind: ClusterIssuer
EOF

So here we created the certificate for the domain dummy.example.com:

  • with name dummy-stage

Check the certificate status:

kubectl describe certificates dummy-stage -n stage
...
Message: Certificate issued successfully
Reason: CertIssued
Status: True
Type: Ready
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal CreateOrder 38s cert-manager Created new ACME order, attempting validation...
Normal DomainVerified 5s cert-manager Domain "dummy.example.com" verified with "http-01" validation
Normal IssueCert 5s cert-manager Issuing certificate...
Normal CertObtained 3s cert-manager Obtained certificate from ACME server
Normal CertIssued 3s cert-manager Certificate issued successfully

Certificate issued successfully and now we can use it in our test ingress:

cat <<EOF | kubectl create -f -
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-stage
namespace: stage
annotations:
kubernetes.io/ingress.class: nginx
spec:
tls:
- hosts:
- dummy.example.com
secretName: cert-stage-dummy
rules:
- host: dummy.example.com
http:
paths:
- backend:
serviceName: somesvc
servicePort: 80
EOF

We set up here:

  • host dummy.example.com

Remember, we saved secret to the stage namespace, so ingress should be also created in the same namespace.

Checking it:

openssl s_client -servername dummy.example.com -connect dummy.example.com:443
CONNECTED(00000006)
...
---
Certificate chain
0 s:/CN=dummy.example.com
i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
1 s:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
i:/O=Digital Signature Trust Co./CN=DST Root CA X3
---
Server certificate
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
subject=/CN=dummy.example.com
issuer=/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
...
It works

Wildcard certificate *.example.com

For using wildcard certificates issuing we need to set up a new Issuer with DNS01 validation. Here is a list of available DNS01 providers.

In our example we use AWS, so let’s start with it.

Create a policy in AWS for Route53 access:

cat << EOF > json/letsencrypt-wildcard.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "route53:GetChange",
"Resource": "arn:aws:route53:::change/*"
},
{
"Effect": "Allow",
"Action": "route53:ChangeResourceRecordSets",
"Resource": "arn:aws:route53:::hostedzone/*"
},
{
"Effect": "Allow",
"Action": "route53:ListHostedZonesByName",
"Resource": "*"
}
]
}
EOF
aws iam create-policy --policy-name letsencrypt-wildcard --policy-document file://json/letsencrypt-wildcard.json

Create user and get CLI credentials AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY:

LE_POLICY_ARN=$(aws iam list-policies --output json --query 'Policies[*].[PolicyName,Arn]' --output text | grep letsencrypt-wildcard | awk '{print $2}')aws iam create-group --group-name letsencrypt-wildcard
aws iam attach-group-policy --policy-arn ${LE_POLICY_ARN} --group-name letsencrypt-wildcard
aws iam create-user --user-name letsencrypt-wildcard
aws iam add-user-to-group --user-name letsencrypt-wildcard --group-name letsencrypt-wildcard
aws iam create-access-key --user-name letsencrypt-wildcard

After getting AWS credentials we can create secret with AWS_SECRET_ACCESS_KEY:

AWS_ACCESS_KEY_ID=your-access-id
AWS_SECRET_ACCESS_KEY=your-access-secret
echo ${AWS_SECRET_ACCESS_KEY} > password.txt
kubectl create secret generic aws-route53-creds --from-file=password.txt -n stage
rm -f password.txt

Create an Issuer with DNS01 verification and credentials (replace AWS_ACCESS_KEY_ID to the valid value):

cat <<EOF | kubectl create -f -
apiVersion: certmanager.k8s.io/v1alpha1
kind: Issuer
metadata:
name: le-wildcard-issuer
namespace: stage
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: devops@example.com
privateKeySecretRef:
name: le-wildcard-issuer
dns01:
providers:
- name: aws-dns
route53:
region: eu-west-1
accessKeyID: AWS_ACCESS_KEY_ID
secretAccessKeySecretRef:
name: aws-route53-creds
key: password.txt
EOF

Creating a wildcard SSL certificate *.example.com:

cat <<EOF | kubectl create -f -
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
name: wildcard-stage
namespace: stage
spec:
secretName: cert-stage-wildcard
issuerRef:
name: le-wildcard-issuer
kind: Issuer
commonName: '*.example.com'
dnsNames:
- example.com
- '*.example.com'
acme:
config:
- dns01:
provider: aws-dns
domains:
- example.com
- '*.example.com'
EOF

Let’s test this certificate:

openssl s_client -servername wildcard.example.com -connect wildcard.example.com:443
...
Certificate chain
0 s:/CN=*.example.com
i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
1 s:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
i:/O=Digital Signature Trust Co./CN=DST Root CA X3
---
Server certificate
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
subject=/CN=*.example.com
issuer=/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
...

That works too.

Could we share certificates between namespaces?

Sure, (example for copying cert-stage-wildcard secret from stage namespace to preprod namespace):

kubectl get secret cert-stage-wildcard -n stage --export -o yaml | \
kubectl apply -n preprod -f -

Or you can use kubernetes-replicator for that:

See ya!

Amet Umerov

Written by

DevOps Engineer at preply.com