Configure DNS SAN Certificates with Citadel#

This guide describes the process of consuming Aspen Mesh certificates by Gateways to terminate TLS traffic coming from end user clients. By default, all Aspen Mesh generated certificates only include SPIFFE URI in SAN which is not compatible with end user clients like browsers or curl. Adding DNS SAN entries in certificates enables you to terminate TLS traffic coming from browsers and other end user clients at Aspen Mesh gateways.

Warning

Use this guide Configure Gateways to use Aspen Mesh minted certificates for using this functionality.

Prerequisites#

  • DNS name configured to point to the cluster via A, ALIAS, or CNAME records

  • ServiceAccount configured for the service that should have the DNS added to the certificate as we generate DNS certificates triggered based on service account existence.

  • jq installed

Setup#

  1. In the override values file for the Aspen Mesh install, add the following global values

    global:
       customDnsNames:
       - serviceAccountName: httpbin
         serviceAccountNamespace: default
         dnsName: test.example.com
    

    Use the ServiceAccount and DNS name configured in the prerequisistes for the serviceAccountName/Namespace and dnsName values.

  2. Deploy the new values to the cluster via a helm upgrade. If the ServiceAccount exists before the upgrade, the secret generated by Citadel must be deleted in order to refresh the certificate with the new DNS name.

  3. Save the following script to translate the Citadel certificate into a kubernetes.io/tls certificate for use in the Aspen Mesh Gateway as ingress-gw.sh. This step is needed as the keys in secret generated by Citadel and expected keys in secrets referenced by Gateway resource are different.

    #!/bin/bash
    
    set -eEuo pipefail
    OLD_SECRET=istio.$1
    KEY=$(kubectl -n $2 get secret $OLD_SECRET -o json | jq '.data | ."key.pem"')
    CERT=$(kubectl -n $2 get secret $OLD_SECRET -o json | jq '.data | ."cert-chain.pem"')
    ROOT_CERT=$(kubectl -n $2 get secret $OLD_SECRET -o json | jq '.data | ."root-cert.pem"')
    NEW_SECRET=$1-certs
    kubectl apply -n istio-system -f - << EOF
    apiVersion: v1
    kind: Secret
    metadata:
      name: ${NEW_SECRET}
    data:
      key: ${KEY}
      cert: ${CERT}
      cacert: ${ROOT_CERT}
    type: kubernetes.io/generic
    EOF
    

    Usage: ./ingress-gw.sh <SERVICE_ACCOUNT_NAME> <SERVICE_ACCOUNT_NAMESPACE>

    This script will generate a new secret in the istio-system namespace called SERVICE_ACCOUNT_NAME-certs where SERVICE_ACCOUNT_NAME is the name of the ServiceAccount used in the above override values file.

  4. Create the Istio Gateway using the new secret name for TLS configuration

    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: dns-certs-gateway
    spec:
      selector:
        istio: ingressgateway
      servers:
      - port:
          number: 443
          name: https
          protocol: HTTPS
        hosts:
        - <DNS_NAME>
        tls:
          mode: <MUTUAL|SIMPLE>
          credentialName: <SERVICE_ACCOUNT_NAME>-certs
    
  5. Create the VirtualService associated with the service you wish to expose and connect it to the Gateway. An example can be found in the Example Deployment section of this document.

  6. Add the Citadel root certificate to your OS’s root certificate authorities. The following commands will extract the root certificate into a file called root.crt in your current directory. Make sure you can run kubectl commands against the cluster.

    • Mac

      • $ kubectl get cm istio-security -n istio-system -ojsonpath={$.data.caTLSRootCert} | base64 -D  > root.crt`
        
      • Adding the certificate to the root store

        • $ sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain root.crt`
          
    • Linux

      • $ kubectl get cm istio-security -n istio-system -ojsonpath={$.data.caTLSRootCert} | base64 -D  > root.crt`
        
      • Adding the certificate to the root store

        • Refer to the Linux distribution’s instructions on adding a new root CA

    • Windows

      • > kubectl get cm istio-security -n istio-system -ojsonpath={$.data.caTLSRootCert} > root.b64
        > certutil -decode root.b64 root.crt
        > del root.b64
        
      • Adding the certificate to the root store

        • In an Administrative PowerShell window

          Import-Certificate -FilePath "root.crt" -CertStoreLocation Cert:\LocalMachine\Root
          

Note

  • Windows may have trouble connecting to the Gateway if it is configured with Mutual TLS

  • Certificate generation using the script is currently a manual process. If a new certificate is generated or the certificate is rotated, the ingress-gw.sh should be run again to refresh the Gateway certificate.

Example Deployment#

Here we will deploy a copy of httpbin from the samples directory in the Aspen Mesh install and expose it using the certificate generated from its service account. These steps assume that Aspen Mesh has been installed into the cluster and that the current directory is the root of the untar’d installer.

  1. Enable Istio Sidecar injection in the default namespace

    $ kubectl label namespace default istio-injection=enabled
    
  2. Deploy the httpbin sample application

    $ kubectl apply -f samples/httpbin/httpbin.yaml
    
  3. Verify that a Aspen Mesh Citadel secret is created for the service account

    $ kubectl get secret istio.httpbin -n default
    NAME            TYPE                    DATA   AGE
    istio.httpbin   istio.io/key-and-cert   3      1m
    
  4. Add the custom DNS name to your override values file, replacing test.example.com with your own DNS name.

    global:
        customDnsNames:
        - serviceAccountName: httpbin
          serviceAccountNamespace: default
          dnsName: test.example.com
    
  5. Deploy the values using helm upgrade to apply the changes

    $ helm upgrade istiod manifests/charts/istio-control/istio-discovery \
          --namespace=istio-system \
          --values=YOUR_OVERRIDE_VALUES_FILE \
          --values=values.yaml
    
  6. Delete the istio.httpbin secret to force Citadel to regenerate the certificate with the specified DNS name.

    $ kubectl delete secret istio.httpbin -n default
    
  7. Use the ingress-gw.sh to create the Istio Gateway certificate secret

    $ ./ingress-gw.sh httpbin default
    
  8. Deploy the following Gateway and VirtualService to your cluster replacing test.example.com with your own DNS name

    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: httpbin-gateway
      namespace: default
    spec:
      selector:
        istio: ingressgateway
      servers:
      - port:
          number: 443
          name: https
          protocol: HTTPS
        hosts:
        - "test.example.com"
        tls:
          mode: SIMPLE
          credentialName: httpbin-certs
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: httpbin
      namespace: default
    spec:
      hosts:
      - "*"
      gateways:
      - httpbin-gateway
      http:
      - route:
        - destination:
            host: httpbin
            port:
              number: 8000
    
  9. Observe that navigating to the dns name provided above now generates an error when connecting, stating that the root certificate is not trusted.

    ../_images/before.png
  10. Follow the instructions in step 6 from the previous instructions to trust the root CA from the cluster

  11. Observe that now the connection is trusted and you can see the httpbin page when navigating to the DNS name.

    ../_images/after.png