Manage Istio intermediate CAs

Instead of using Gloo Mesh self-signed certificates for the root CA certificate, you can generate your own Istio root CA certificate and key with the certificate management tool of your choice. You then have the following options:

Storing the root CA certificate and private key in the Gloo management cluster is not a recommended practice for production. The root CA certificate and key are very sensitive credentials, and if compromised, can be used to issue certificates for all components in your service mesh. To protect the root CA certificate and key, it is recommended to store them in an external certificate management system. For more information, see the managed CA lifecycle Setup options and your options to Bring your own CAs.

Before you begin

This guide assumes that you use the same names for components like clusters, workspaces, and namespaces as in the getting started. If you have different names, make sure to update the sample configuration files in this guide.
  1. Complete the multicluster getting started guide to set up the following testing environment.
    • Three clusters along with environment variables for the clusters and their Kubernetes contexts.
    • The Gloo Platform CLI, meshctl, along with other CLI tools such as kubectl and istioctl.
    • The Gloo management server in the management cluster, and the Gloo agents in the workload clusters.
    • Istio installed in the workload clusters.
    • A simple Gloo workspace setup.
  2. Install Bookinfo and other sample apps.
  3. Save the kubeconfig contexts for your clusters. Run kubectl config get-contexts, look for your cluster in the CLUSTER column, and get the context name in the NAME column. Note: Do not use context names with underscores. The context name is used as a SAN specification in the generated certificate that connects workload clusters to the management cluster, and underscores in SAN are not FQDN compliant. You can rename a context by running kubectl config rename-context "<oldcontext>" <newcontext>.
    export MGMT_CLUSTER=<mgmt-cluster-name>
    export REMOTE_CLUSTER1=<remote-cluster1-name>
    export REMOTE_CLUSTER2=<remote-cluster2-name>
    export MGMT_CONTEXT=<management-cluster-context>
    export REMOTE_CONTEXT1=<remote-cluster1-context>
    export REMOTE_CONTEXT2=<remote-cluster2-context>
    

Step 1: Create your own root CA certificate

To generate and store your own CA certificate and key, you have two general options:

Istio certificate generator

Istio provides a certificate generator tool that you can use to quickly generate self-signed root and intermediate CA certificates and keys.

  1. Download Istio to your local machine. Make sure to use a supported version that matches the version you plan to run in the workload clusters.

    ISTIO_VERSION=1.20.2
    curl -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIO_VERSION sh -
    
  2. Go to the certs directory.

    cd istio-$ISTIO_VERSION/tools/certs
    
  3. Generate a self-signed root CA certificate and private key.

    make -f Makefile.selfsigned.mk \
    ROOTCA_CN="Solo Root CA" \
    ROOTCA_ORG=Istio \
    root-ca
    
  4. Store the root CA certificate, private key and certificate chain in a Kubernetes secret on the management cluster.

    cp root-cert.pem ca-cert.pem
    cp root-key.pem ca-key.pem
    cp root-cert.pem cert-chain.pem
        
    kubectl --context $MGMT_CONTEXT -n gloo-mesh create secret generic my-root-trust-policy.gloo-mesh \
    --from-file=./ca-cert.pem \
    --from-file=./ca-key.pem \
    --from-file=./cert-chain.pem \
    --from-file=./root-cert.pem
    
  5. Continue with Step 2: Create a root trust policy.

OpenSSL

You can use OpenSSL to generate your own root CA certificate and private key.

  1. Make sure that you have the OpenSSL version of openssl, not LibreSSL. The openssl version must be at least 1.1.

    1. Check the openssl version that is installed. If you see LibreSSL in the output, continue to the next step.
      openssl version
      
    2. Install the OpenSSL version (not LibreSSL). For example, you might use Homebrew.
      brew install openssl
      
    3. Review the output of the OpenSSL installation for the path of the binary file. You can choose to export the binary to your path, or call the entire path whenever the following steps use an openssl command.
      • For example, openssl might be installed along the following path: /usr/local/opt/openssl@3/bin/
      • To run commands, you can append the path so that your terminal uses this installed version of OpenSSL, and not the default LibreSSL. /usr/local/opt/openssl@3/bin/openssl req -new -newkey rsa:4096 -x509 -sha256 -days 3650...
    1. Check the openssl version that is installed. If you see LibreSSL in the output, continue to the next step.
      openssl version
      
    2. Install the OpenSSL version (not LibreSSL). For example, you might use Homebrew.
      brew install openssl
      
    3. Review the output of the OpenSSL installation for the path of the binary file. You can choose to export the binary to your path, or call the entire path whenever the following steps use an openssl command.
      • For example, openssl might be installed along the following path: /usr/local/opt/openssl@3/bin/
      • To run commands, you can append the path so that your terminal uses this installed version of OpenSSL, and not the default LibreSSL.
        /usr/local/opt/openssl@3/bin/openssl req -new -newkey rsa:4096 -x509 -sha256 -days 3650...
        
  2. Create a configuration file for the root certificate authority.

    cat > "root-ca.conf" <<EOF
    [ req ]
    encrypt_key = no
    prompt = no
    utf8 = yes
    default_md = sha256
    default_bits = 4096
    req_extensions = req_ext
    x509_extensions = req_ext
    distinguished_name = req_dn
    [ req_ext ]
    subjectKeyIdentifier = hash
    basicConstraints = critical, CA:true
    keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, keyCertSign
    [ req_dn ]
    O = Istio
    CN = Root CA
    EOF
    
  3. Use the root CA configuration file to create a root CA certificate and private key.

    openssl req -new -config root-ca.conf -nodes -out root-ca.crt -keyout root-ca.key
    
  4. Use the root CA certificate and private key to sign the root CA certificate.

    openssl x509 -in root-ca.crt -out cert.pem -req -signkey root-ca.key -days 360
    
  5. Verify your root CA certificate settings.

    openssl x509 -in cert.pem -text -noout
    
  6. Store the root CA certificate, private key and certificate chain in a Kubernetes secret on the management cluster.

    cp cert.pem ca-cert.pem
    cp root-ca.key ca-key.pem
    cp root-ca.crt cert-chain.pem
        
    kubectl --context $MGMT_CONTEXT -n gloo-mesh create secret generic my-root-trust-policy.gloo-mesh \
    --from-file=./ca-cert.pem \
    --from-file=./ca-key.pem \
    --from-file=./cert-chain.pem \
    --from-file=./root-cert.pem
    
  7. Continue with Step 2: Create a root trust policy.

Step 2: Create a root trust policy

Use a Gloo root trust policy to configure the details of the root and intermediate CA certificates across clusters in the service mesh.

  1. Create the root trust policy in the management cluster and reference the root CA Kubernetes secret that you created in step 1 in the spec.config.mgmtServerCa.secretRef section. You can optionally customize the number of days the root and derived intermediate CA certificates are valid for by specifying the ttlDays in the mgmtServerCA (root CA) and intermediateCertOptions (intermediate CA) of your root trust policy.

    The following example policy sets up the root CA with the credentials that you stored in the my-root-trust-policy.gloo-mesh secret. The root CA certificate is valid for 730 days. The intermediate CA certificates that are automatically created by Gloo Mesh are valid for 1 day. For more information, see Certificate rotation overview.

    kubectl apply --context $MGMT_CONTEXT -f- << EOF 
    apiVersion: admin.gloo.solo.io/v2
    kind: RootTrustPolicy
    metadata:
      name: root-trust-policy
      namespace: gloo-mesh
    spec:
      config:
        autoRestartPods: true
        intermediateCertOptions:
          secretRotationGracePeriodRatio: 0.1
          ttlDays: 1
        mgmtServerCa: 
          generated:
            ttlDays: 730
          secretRef:
             name: my-root-trust-policy.gloo-mesh
             namespace: gloo-mesh
    EOF
    
  2. Verify that the cacerts Kubernetes secret was created in the istio-system namespace on the workload cluster. This secret represents the intermediate CA and is used by istiod to issue leaf certificates to the workloads in your service mesh.

    kubectl get secret cacerts -n istio-system --context $REMOTE_CONTEXT1 
    kubectl get secret cacerts -n istio-system --context $REMOTE_CONTEXT2
    
  3. Verify the certificate chain for the intermediate CA. Because the intermediate CA was derived from the root CA, the root CA must be listed as the root-cert in the cacerts Kubernetes secret.

    1. Get the root CA certificate from the root-trust-policy.gloo-mesh secret on the management cluster.
      kubectl get secret my-root-trust-policy.gloo-mesh -n gloo-mesh -o jsonpath='{.data.ca-cert\.pem}' --context $MGMT_CONTEXT| base64 --decode
      
    2. In each workload cluster, get the root CA certificate that is listed as the root-cert in the cacerts Kubernetes secret.
      kubectl get secret cacerts -n istio-system --context $REMOTE_CONTEXT1 -o jsonpath='{.data.root-cert\.pem}' | base64 --decode
      kubectl get secret cacerts -n istio-system --context $REMOTE_CONTEXT2 -o jsonpath='{.data.root-cert\.pem}' | base64 --decode
      
    3. Verify that the root CA certificate that is listed in the intermediate CA secret matches the root CA certificate that was created by the Gloo Mesh root trust policy.

Step 3: Verify workloads can communicate across cluster boundaries

With your root trust policy in place, verify that you can route mTLS traffic within your mesh across cluster boundaries.

  1. Create an Istio policy to enforce mTLS between workloads in the bookinfo namespace in both workload clusters.

    kubectl apply -n bookinfo --context $REMOTE_CONTEXT1 -f - <<EOF
    apiVersion: security.istio.io/v1beta1
    kind: PeerAuthentication
    metadata:
      name: default
    spec:
      mtls:
        mode: STRICT
    EOF
    
    kubectl apply -n bookinfo --context $REMOTE_CONTEXT2 -f - <<EOF
    apiVersion: security.istio.io/v1beta1
    kind: PeerAuthentication
    metadata:
      name: default
    spec:
      mtls:
        mode: STRICT
    EOF
    

  2. Create a virtual destination so that you can route requests from cluster 1 to the reviews app in cluster 2.

    kubectl apply --context $REMOTE_CONTEXT1 -f- <<EOF
    apiVersion: networking.gloo.solo.io/v2
    kind: VirtualDestination
    metadata: 
      name: reviews-vd
    spec:
      hosts:
      - reviews.vd
      ports:
      - number: 80
        protocol: HTTP
        targetPort:
          name: http
      services:
      - cluster: $REMOTE_CLUSTER1
        labels:
          app: reviews
    EOF
    
  3. Verify that you can send a request from the product page app in cluster 1 to the reviews-v3 app in cluster 2.

    kubectl exec $(kubectl get pod -l app=productpage -A --context ${REMOTE_CONTEXT1}  -o jsonpath='{.items[0].metadata.name}') -n bookinfo -c curl --context ${REMOTE_CONTEXT1} -- curl -vik reviews.vd/reviews/3
    

    Example output:

    HTTP/1.1 200 OK
    < x-powered-by: Servlet/3.1
    < content-type: application/json
    < date: Thu, 16 Mar 2023 18:21:42 GMT
    < content-language: en-US
    < content-length: 436
    < x-envoy-upstream-service-time: 1455
    < server: envoy
    < 
    { [436 bytes data]
    100   436  100   436    0     0    29x-powered-by: Servlet/3.1
    content-type: application/json
    date: Thu, 16 Mar 2023 18:21:42 GMT
    content-language: en-US
    content-length: 436
    x-envoy-upstream-service-time: 1455
    server: envoy
    
    {"id": "3","podname": "reviews-v3-58b6479b-q2tzn","clustername": "null","reviews": [{  "reviewer": "Reviewer1",  "text": "An extremely entertaining play by Shakespeare. The slapstick humour is refreshing!", "rating": {"stars": 5, "color": "red"}},{  "reviewer": "Reviewer2",  "text": "Absolutely fun and entertaining. The play lacks thematic depth when compared to other plays by Shakespeare.", "rating": {"stars": 4, "color": "red"}}]}4      0  0:00:01  0:00:01 --:--:--   294
    * Connection #0 to host reviews.vd left intact
    
  4. Verify that the product page app in cluster 1 and reviews-v3 app in cluster 2 share the same root CA.

    1. List the Istio proxies in your clusters and get the name of the product page (cluster 1) and reviews-v3 (cluster 2) proxies.

      istioctl --context $REMOTE_CONTEXT1 ps
      istioctl --context $REMOTE_CONTEXT2 ps
      
    2. Get the TLS credentials that the product page and reviews-v3 proxies use for mTLS connections.

      istioctl --context $REMOTE_CONTEXT1 pc secret <product-page-proxy-name> -o yaml
      istioctl --context $REMOTE_CONTEXT2 pc secret <reviews-v3-proxy-name> -o yaml
      
    3. In your CLI output, look for the root certificate in the ROOTCA section and copy the inlineBytes content.

      Example output:

            ...
            - lastUpdated: "2022-11-08T09:53:16.743Z"
              name: ROOTCA
              secret:
                '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret
                name: ROOTCA
                validationContext:
                  trustedCa:
                    inlineBytes: <root-certificate>
            

    4. Decode the content and verify that both proxies list the same root CA certificate.

      echo "<inlineBytes-content>" | base64 -d