Set up mutual TLS with a private CA

This page provides instructions for creating a private certificate authority (CA) by using the Certificate Authority Service and uploading your certificates to a Certificate Manager TrustConfig resource.

You also create the network security resources required for configuring mutual TLS for Application Load Balancers.

Before you begin

  • Review the Mutual TLS overview.
  • Install the Google Cloud CLI. For a complete overview of the tool, see the gcloud CLI overview. You can find commands related to load balancing in the API and gcloud CLI reference.

    If you haven't run the gcloud CLI previously, first run gcloud init to authenticate.

  • Make sure that you know how to create CA pools.

  • If you are using global external Application Load Balancer or classic Application Load Balancer, make sure you have set up a load balancer with any of the following supported backends:

    • VM instance group backends
    • Cloud Storage buckets (Supported only if there is at least one backend service also attached to the load balancer, in addition to the backend bucket)
    • Cloud Run, App Engine, or Cloud Run functions
    • Hybrid connectivity
  • If you are using regional external Application Load Balancer, cross-region internal Application Load Balancer, or regional internal Application Load Balancer, make sure you have set up a load balancer with any of the following supported backends:

    • VM instance group backends
    • Cloud Run
    • Hybrid connectivity

Permissions

To get the permissions that you need to complete this guide, ask your administrator to grant you the following IAM roles on the project:

  • To create load balancer resources such as TargetHTTPProxy: Compute Load Balancer Admin (roles/compute.loadBalancerAdmin)
  • To use Certificate Manager resources: Certificate Manager Owner (roles/certificatemanager.owner)
  • To create security and networking components: Compute Network Admin (roles/compute.networkAdmin) and Compute Security Admin (roles/compute.securityAdmin)
  • To create a project (optional): Project Creator (roles/resourcemanager.projectCreator)

For more information about granting roles, see Manage access.

You might also be able to get the required permissions through custom roles or other predefined roles.

Create a private CA

Create a private CA by using the CA Service, and then create a root certificate:

  1. To create a CA pool, use the gcloud privateca pools create command:

    gcloud privateca pools create CA_POOL \
       --location=us-central1
    

    Replace CA_POOL with the ID or name of the parent CA pool.

  2. To create a private CA in the CA pool, use the gcloud privateca roots create command:

    gcloud privateca roots create CA_ROOT \
       --pool=CA_POOL \
       --subject="CN=my-ca, O=Test LLC" \
       --location=us-central1
    

    Replace the following:

    • CA_ROOT: the ID or name of the private CA
    • CA_POOL: the ID or name of the parent CA pool
  3. To describe the new CA and create the root.cert file, use the gcloud privateca roots describe command:

    gcloud privateca roots describe CA_ROOT \
       --pool=CA_POOL \
       --location=us-central1 \
       --format='value(pemCaCertificates)' > root.cert
    
    export ROOT=$(cat root.cert | sed 's/^[ ]*//g' | tr '\n' $ | sed 's/\$/\\n/g')
    

    Replace the following:

    • CA_ROOT: the ID or name of the private CA
    • CA_POOL: the ID or name of the parent CA pool

    For more information, see the following:

Create TrustConfig with private CA

Create a Certificate Manager TrustConfig resource that represents your PKI by using the root certificate generated by using the private CA. We assume that the TrustConfig resource is a simple trust store with a single trust anchor that represents a root certificate.

In the following steps, replace TRUST_CONFIG_NAME with the name of the TrustConfig resource.

  • To create the trust_config.yaml file, use the following command:

    cat << EOF > trust_config.yaml
    name: TRUST_CONFIG_NAME
    trustStores:
    - trustAnchors:
       - pemCertificate: "${ROOT?}"
    EOF
    
  • To create the Certificate Manager TrustConfig resources, use the gcloud certificate-manager trust-configs import command:

    gcloud certificate-manager trust-configs import TRUST_CONFIG_NAME  \
       --source=trust_config.yaml \
       --location=REGION
    

    Replace the following:

    REGION: use global for cross-region internal Application Load Balancer, global external Application Load Balancer, or classic Application Load Balancer. For regional external Application Load Balancer or regional internal Application Load Balancer, use the region where you configured the load balancer.

Create the Client Authentication resources

A Client Authentication (also called ServerTLSPolicy) resource lets you specify the server-side TLS mode and the TrustConfig resource to use when validating client certificates. When the client presents an invalid certificate or no certificate to the load balancer, the clientValidationMode specifies how the client connection is handled. For more information, see MTLS client validation modes.

  • When the clientValidationMode is set to ALLOW_INVALID_OR_MISSING_CLIENT_CERT, all requests are passed to the backend even if the validation fails or the client certificate is missing.
  • When the clientValidationMode is set to REJECT_INVALID, only requests that supply a client certificate that can be validated against a TrustConfig resource are passed to the backend.

To create the ServerTLSPolicy resource, complete the following steps:

  1. Based on how you want to handle the connection, select one of the following options.

    In the following steps, replace SERVER_TLS_POLICY_NAME with the name of the server TLS policy, and replace PROJECT_ID with the ID of your Google Cloud project.

    • Option 1: clientValidationMode is set to ALLOW_INVALID_OR_MISSING_CLIENT_CERT.

      To create the server_tls_policy.yaml file, use the following command:

      global

      For external Application Load Balancers and cross-region internal Application Load Balancers, use the command:

      cat << EOF > server_tls_policy.yaml
      name: SERVER_TLS_POLICY_NAME
      mtlsPolicy:
        clientValidationMode: ALLOW_INVALID_OR_MISSING_CLIENT_CERT
        clientValidationTrustConfig: projects/PROJECT_ID/locations/global/trustConfigs/TRUST_CONFIG_NAME
      EOF
      

      regional

      For regional external Application Load Balancers and regional internal Application Load Balancers, use the command:

      cat << EOF > server_tls_policy.yaml
      name: SERVER_TLS_POLICY_NAME
      mtlsPolicy:
        clientValidationMode: ALLOW_INVALID_OR_MISSING_CLIENT_CERT
        clientValidationTrustConfig: projects/PROJECT_ID/locations/REGION/trustConfigs/TRUST_CONFIG_NAME
      EOF
      
    • Option 2: clientValidationMode is set to REJECT_INVALID.

      To create the server_tls_policy.yaml file, use the following command:

      global

      For external Application Load Balancers and cross-region internal Application Load Balancers, use the command:

      cat << EOF > server_tls_policy.yaml
      name: SERVER_TLS_POLICY_NAME
      mtlsPolicy:
        clientValidationMode: REJECT_INVALID
        clientValidationTrustConfig: projects/PROJECT_ID/locations/global/trustConfigs/TRUST_CONFIG_NAME
      EOF
      

      regional

      For regional external Application Load Balancers and regional internal Application Load Balancers, use the command:

      cat << EOF > server_tls_policy.yaml
      name: SERVER_TLS_POLICY_NAME
      mtlsPolicy:
        clientValidationMode: REJECT_INVALID
        clientValidationTrustConfig: projects/PROJECT_ID/locations/REGION/trustConfigs/TRUST_CONFIG_NAME
      EOF
      
  2. To create the ServerTlsPolicy resource, use the gcloud network-security server-tls-policies import command:

    global

    For external Application Load Balancers and cross-region internal Application Load Balancers, use the command:

    gcloud network-security server-tls-policies import SERVER_TLS_POLICY_NAME \
      --source=server_tls_policy.yaml \
      --location=global
    

    regional

    For regional external Application Load Balancers and regional internal Application Load Balancers, use the command:

    gcloud network-security server-tls-policies import SERVER_TLS_POLICY_NAME \
      --source=server_tls_policy.yaml \
      --location=REGION
    
  3. Optional: List all the Client Authentication (ServerTlsPolicies) resources in the specified location of the current project.

    Console

    1. In the Google Cloud console, go to the Client authentication page.

      Go to Client authentication

    2. All the ServerTlsPolicies resources are displayed.

    gcloud

    To list all the Client Authentication (ServerTlsPolicies) resources, use the gcloud network-security server-tls-policies list command:

    gcloud network-security server-tls-policies list \
      --location=REGION
    

    Replace the following:

    REGION: use global for cross-region internal Application Load Balancer, global external Application Load Balancer, or classic Application Load Balancer. For regional external Application Load Balancer or regional internal Application Load Balancer, use the region where you configured the load balancer.

Set up mTLS for the load balancer

For mutual TLS authentication to work, after you set up a load balancer, you need to update the target HTTPS proxy by using the ServerTLSPolicy resource.

  1. Ensure that you have already created the Client Authentication (ServerTLSPolicy) resource. For instructions, see Create the Client Authentication resources.

  2. To list all the target HTTPS proxies in your project, use the gcloud compute target-https-proxies list command:

    gcloud compute target-https-proxies list
    

    Note the name of the target HTTPS proxy to attach the ServerTLSPolicy resource. This name is referred to as TARGET_HTTPS_PROXY_NAME in the following steps.

  3. To export a target HTTPS proxy's configuration to a file, use the gcloud compute target-https-proxies export command.

    global

      gcloud compute target-https-proxies export TARGET_HTTPS_PROXY_NAME \
          --destination=TARGET_PROXY_FILENAME \
          --global
      

    Replace the following:

    • TARGET_HTTPS_PROXY_NAME: the name of the target proxy.
    • TARGET_PROXY_FILENAME: the name of a yaml file. For example, mtls_target_proxy.yaml.

    regional

     gcloud compute target-https-proxies export TARGET_HTTPS_PROXY_NAME \
         --destination=TARGET_PROXY_FILENAME \
         --region=REGION
     

    Replace the following:

    • TARGET_HTTPS_PROXY_NAME: the name of the target proxy.
    • TARGET_PROXY_FILENAME: the name of a yaml file. For example, mtls_target_proxy.yaml
    • REGION: the region where you configured the load balancer.
  4. List all the ServerTlsPolicies resources in the specified location of the current project.

    Console

    1. In the Google Cloud console, go to the Client authentication page.

      Go to Client authentication

    2. All the ServerTlsPolicies resources are displayed.

    gcloud

    To list all the Client authentication (ServerTlsPolicies) resources, use the gcloud network-security server-tls-policies list command:

    gcloud network-security server-tls-policies list \
      --location=REGION
    

    Replace the following:

    REGION: use global for cross-region internal Application Load Balancer, global external Application Load Balancer, or classic Application Load Balancer. For regional external Application Load Balancer or regional internal Application Load Balancer, use the region where you configured the load balancer.

    Note the name of the ServerTlsPolicies resource to configure mTLS. This name is referred to as SERVER_TLS_POLICY_NAME in the next step.

  5. To append the ServerTlsPolicy resource file TARGET_PROXY_FILENAME, use the following command. Replace PROJECT_ID with the ID of your Google Cloud project.

    echo "serverTlsPolicy: //networksecurity.googleapis.com/projects/PROJECT_ID/locations/REGION/serverTlsPolicies/SERVER_TLS_POLICY_NAME" >> TARGET_PROXY_FILENAME
    
  6. To import a target HTTPS proxy's configuration from a file, use the gcloud compute target-https-proxies import command.

    global

       gcloud compute target-https-proxies import TARGET_HTTPS_PROXY_NAME \
           --source=TARGET_PROXY_FILENAME \
           --global
       

    Replace the following:

    • TARGET_HTTPS_PROXY_NAME: the name of the target proxy.
    • TARGET_PROXY_FILENAME: the name of a yaml file. For example, mtls_target_proxy.yaml.

    regional

       gcloud compute target-https-proxies import TARGET_HTTPS_PROXY_NAME \
           --source=TARGET_PROXY_FILENAME \
           --region=REGION
       

    Replace the following:

    • TARGET_HTTPS_PROXY_NAME: the name of the target proxy.
    • TARGET_PROXY_FILENAME: the name of a yaml file. For example, mtls_target_proxy.yaml
    • REGION: the region where you configured the load balancer.

Add mTLS custom headers

When you enable mTLS, you can pass information about the mTLS connection using custom headers. You can also enable logging so that mTLS connection failures are captured in the logs.

Add mTLS custom headers to backend services

For global external Application Load Balancers or classic Application Load Balancers, you can use custom headers to pass information about the mTLS connection to backend services.

  1. To list all the backend services in the project, use the gcloud compute backend-services list command:

    gcloud compute backend-services list
    

    Note the name of the backend service to enable custom headers and logging. This name is referred to as BACKEND_SERVICE in the following step.

  2. To update the backend service, use the gcloud compute backend-services update command:

    gcloud compute backend-services update BACKEND_SERVICE \
      --global \
      --enable-logging \
      --logging-sample-rate=1 \
      --custom-request-header='X-Client-Cert-Present:{client_cert_present}' \
      --custom-request-header='X-Client-Cert-Chain-Verified:{client_cert_chain_verified}' \
      --custom-request-header='X-Client-Cert-Error:{client_cert_error}' \
      --custom-request-header='X-Client-Cert-Hash:{client_cert_sha256_fingerprint}' \
      --custom-request-header='X-Client-Cert-Serial-Number:{client_cert_serial_number}' \
      --custom-request-header='X-Client-Cert-SPIFFE:{client_cert_spiffe_id}' \
      --custom-request-header='X-Client-Cert-URI-SANs:{client_cert_uri_sans}' \
      --custom-request-header='X-Client-Cert-DNSName-SANs:{client_cert_dnsname_sans}' \
      --custom-request-header='X-Client-Cert-Valid-Not-Before:{client_cert_valid_not_before}' \
      --custom-request-header='X-Client-Cert-Valid-Not-After:{client_cert_valid_not_after}'
    

Add mTLS custom headers to URL map

For cross-region internal Application Load Balancer, regional external Application Load Balancer, or regional internal Application Load Balancer, you can use custom headers to pass information about the mTLS connection to the URL map.

To list all the URL maps in the project, use the gcloud compute url-maps list command:

   gcloud compute url-maps list
   

Note the name of the URL map to enable custom headers and logging. This name is referred to as URL_MAP_NAME in the following step.

global

To edit the URL map for a cross-region internal Application Load Balancer, use the gcloud compute url-maps edit command:

   gcloud compute url-maps edit URL_MAP_NAME --global
   

Following is a sample YAML file that shows you how to use variables in custom request headers (requestHeadersToAdd). You can use the same variables to send custom response headers (responseHeadersToAdd).

   headerAction:
      requestHeadersToAdd:
      - headerName: "X-Client-Cert-Present"
        headerValue: "{client_cert_present}"
      - headerName: "X-Client-Cert-Chain-Verified"
        headerValue: "{client_cert_chain_verified}"
      - headerName: "X-Client-Cert-Error"
        headerValue: "{client_cert_error}"
      - headerName: "X-Client-Cert-Hash"
        headerValue: "{client_cert_sha256_fingerprint}"
      - headerName: "X-Client-Cert-Serial-Number"
        headerValue: "{client_cert_serial_number}"
      - headerName: "X-Client-Cert-SPIFFE"
        headerValue: "{client_cert_spiffe_id}"
      - headerName: "X-Client-Cert-URI-SANs"
        headerValue: "{client_cert_uri_sans}"
      - headerName: "X-Client-Cert-DNSName-SANs"
        headerValue: "{client_cert_dnsname_sans}"
      - headerName: "X-Client-Cert-Valid-Not-Before"
        headerValue: "{client_cert_valid_not_before}"
      - headerName: "X-Client-Cert-Valid-Not-After"
        headerValue: "{client_cert_valid_not_after}"
      - headerName: "X-Client-Cert-Issuer-Dn"
        headerValue: "{client_cert_issuer_dn}"
      - headerName: "X-Client-Cert-Subject-Dn"
        headerValue: "{client_cert_subject_dn}"
      - headerName: "X-Client-Cert-Leaf"
        headerValue: "{client_cert_leaf}"
      - headerName: "X-Client-Cert-Chain"
        headerValue: "{client_cert_chain}"
   

regional

To edit the URL map for a regional external Application Load Balancer or a regional internal Application Load Balancer, use the gcloud compute url-maps edit command:

   gcloud compute url-maps edit URL_MAP_NAME --region=REGION
   

Following is a sample YAML file that shows you how to use variables in custom request headers (requestHeadersToAdd). You can use the same variables to send custom response headers (responseHeadersToAdd).

   defaultService: regions/REGION/backendServices/BACKEND_SERVICE_1
      name: regional-lb-map
      region: region/REGION
   headerAction:
      requestHeadersToAdd:
      - headerName: "X-Client-Cert-Present"
        headerValue: "{client_cert_present}"
      - headerName: "X-Client-Cert-Chain-Verified"
        headerValue: "{client_cert_chain_verified}"
      - headerName: "X-Client-Cert-Error"
        headerValue: "{client_cert_error}"
      - headerName: "X-Client-Cert-Hash"
        headerValue: "{client_cert_sha256_fingerprint}"
      - headerName: "X-Client-Cert-Serial-Number"
        headerValue: "{client_cert_serial_number}"
      - headerName: "X-Client-Cert-SPIFFE"
        headerValue: "{client_cert_spiffe_id}"
      - headerName: "X-Client-Cert-URI-SANs"
        headerValue: "{client_cert_uri_sans}"
      - headerName: "X-Client-Cert-DNSName-SANs"
        headerValue: "{client_cert_dnsname_sans}"
      - headerName: "X-Client-Cert-Valid-Not-Before"
        headerValue: "{client_cert_valid_not_before}"
      - headerName: "X-Client-Cert-Valid-Not-After"
        headerValue: "{client_cert_valid_not_after}"
      - headerName: "X-Client-Cert-Issuer-Dn"
        headerValue: "{client_cert_issuer_dn}"
      - headerName: "X-Client-Cert-Subject-Dn"
        headerValue: "{client_cert_subject_dn}"
      - headerName: "X-Client-Cert-Leaf"
        headerValue: "{client_cert_leaf}"
      - headerName: "X-Client-Cert-Chain"
        headerValue: "{client_cert_chain}"
   

What's next