Skip to main content

Raymii.org Raymii.org Logo

Quis custodiet ipsos custodes?
Home | About | All pages | Cluster Status | RSS Feed

nameConstraints on your Self Signed Root CA in Kubernetes with cert-manager

Published: 17-07-2024 23:22 | Author: Remy van Elst | Text only version of this article



If you have set up a Self Signed Root CA for your local Kubernetes Cluster and have trusted the Root Certificate, you are at risk if the key is compromised. If the key is stolen, it can be used to create trusted certificates for everything. Luckily there is something we can do, using nameConstraints to limit the scope of the Root Certificate to, in our case, a single domain (k3s.homelab.mydomain.org). This means that if your key would be compromised, it would only be able to issue certificates for anything under that domain, not your bank for example.

Recently I removed all Google Ads from this site due to their invasive tracking, as well as Google Analytics. Please, if you found this content useful, consider a small donation using any of the options below:

I'm developing an open source monitoring app called Leaf Node Monitoring, for windows, linux & android. Go check it out!

Consider sponsoring me on Github. It means the world to me if you show your appreciation and you'll help pay the server costs.

You can also sponsor me by getting a Digital Ocean VPS. With this referral link you'll get $200 credit for 60 days. Spend $25 after your credit expires and I'll get $25!

RFC 5280 provides for something called Name Constraints, which allow an X.509 CA to have a scope limited to certain names, including the parent domains of the certificates issued by the CA. For example, a host constraint of .example.com allows the CA to issue certificates for anything under .example.com, but not any other host. For other hosts, clients will fail to validate the chain. More info here.

We'll end up with the following error for certificates not under your homelab domain name:

firefox error

Kubernetes cert-manager has experimental support for nameConstraints, quoting the documentation:

This is an Alpha Feature and is only enabled with the --feature-gates=NameConstraints=true option set on both the controller and webhook components.

More info on feature gates can be found here:

Alpha: feature is not yet stable and might be removed or changed in the future. Alpha features are disabled by default and need to be explicitly enabled by the user (to test the feature).

Configure the feature gate on cert-manager

If you have followed my previous guide on setting up a Self Signed Root CA you will already have a working setup for cert-manager via the Helm chart. If not, go follow that guide.

If you have a different installation method, check here for more info on configuring feature gates.

You have append the following two parameters to the Helm install command to enable the feature gate for NameConstraints:

--set webhook.featureGates="NameConstraints=true" \
  --set featureGates="NameConstraints=true"

The full Helm install command then becomes:

helm upgrade --install \
  cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.15.1 \
  --set crds.enabled=true \
  --set webhook.timeoutSeconds=4 \
  --set replicaCount=2 \
  --set podDisruptionBudget.enabled=true \
  --set podDisruptionBudget.minAvailable=1 \
  --set webhook.featureGates="NameConstraints=true" \
  --set featureGates="NameConstraints=true"

You can also choose to create a values.yaml file for this Helm install. All possible options can be found here.

Adding nameConstraints to a Root Certificate

We have to add the following to the spec of our Root CA:

vim spnw-root-ca.yaml

Add under spec:

nameConstraints:
critical: true
permitted:
  dnsDomains:
  - k3s.homelab.mydomain.org

Repeat this for spnw-intermediate-ca1.yaml

You have to delete the current CA and corresponding secret first:

kubectl -n cert-manager delete secret spnw-root-ca-secret 
kubectl -n cert-manager delete -f spnw-root-ca.yaml 
kubectl -n cert-manager delete -f spnw-intermediate-ca1.yaml

Apply the changes:

kubectl -n cert-manager apply -f .

If you have not enabled the feature gate you will receive an error:

for: "spnw-root-ca.yaml": error when patching "spnw-root-ca.yaml":
admission webhook "webhook.cert-manager.io" denied the request:
spec.nameConstraints: Forbidden: feature gate NameConstraints must be
enabled    

You can now get the Secret and check to see if the nameConstraint is present:

 kubectl get secret spnw-root-ca-secret -n cert-manager -o json |
 jq -r '.data["tls.crt"]' |  base64 --decode | openssl x509 -noout -text

Output:

Certificate:
  Data:
    Version: 3 (0x2)
    [...]
    X509v3 extensions:
      [...]
      X509v3 Basic Constraints: critical
        CA:TRUE         
      X509v3 Name Constraints: critical
        Permitted:
          DNS:k3s.homelab.mydomain.org

The following part:

X509v3 Name Constraints: critical
  Permitted:
    DNS:k3s.homelab.mydomain.org

means that any certificate issued for a hostname not within k3s.homelab.mydomain.org will fail to validate, which is exactly what we want.

You must remove the old certificate from your trusted root store (Windows certmgr.msc or Firefox) and add this new certificate. Then you must re-issue all certificates from services you have already issued.

Validating nameConstraints

To test the constraint, I've set up a wildcard DNS domain to k8s.homelab.mydomain.org (note k8s instead of k3s) and issued a certificate for my echoapp test service. Using OpenSSL to validate that fails with a permitted subtree violation, which is the exact error for this failure, the domain does not match the nameConstraints:

openssl verify -CAfile <(kubectl -n cert-manager get secret spnw-root-ca-secret -o jsonpath='{.data.tls\.crt}' | base64 --decode) -untrusted  <(kubectl -n cert-manager get secret spnw-intermediate-ca1-secret -o jsonpath='{.data.tls\.crt}' | base64 --decode) <(kubectl -n echoapp get secret echo-cert  -o jsonpath='{.data.tls\.crt}' | base64 --decode)

Output:

CN = echo.k8s.homelab.mydomain.org
error 47 at 0 depth lookup: permitted subtree violation
error /dev/fd/61: verification failed

Firefox also complains with an SEC_ERROR_CERT_NOT_IN_NAME_SPACE error:

firefox error

If you manually want to set up nameConstraints with openssl, this is a good guide.

Tags: aarch64 , apache , arm , armbian , certificates , chain , cloud , helm , k3s , k8s , kubernetes , linux , openssl , orange-pi , pki , private-key , public-key , raspberry-pi , s_client , ssl , tutorials