Create a subordinate certificate authority

This page describes the steps to create a subordinate certificate authority (Sub CA).

Sub CAs are responsible for issuing certificates directly to end entities, such as users, computers, and devices. They are cryptographically signed by a parent CA, often the root CA. Systems that trust the root CA automatically trust the Sub CAs and the certificates they issue.

The CA certificate signer could be either another CA created in CA Service, for example, root CA, or an external CA. With external CAs, CA Service generates a certificate signing request (CSR) that the external CA must sign.

Before you begin

To get the permissions you need to create a Sub certificate authority, ask your Organization IAM Admin to grant you the Certificate Authority Service Admin (certificate-authority-service-admin) role. For more information on roles, see Role definitions.

Get the kubeconfig file

To run commands against the Management API server, ensure you have the following resources:

  • Sign in and generate the kubeconfig file for the Management API server if you don't have one.

  • Use the path to the kubeconfig file of the Management API server to replace MANAGEMENT_API_SERVER_KUBECONFIG in these instructions.

Create a managed Sub CA

For a managed Sub CA, the signer of the CA certificate is another CA (root CA) created in CA Service.

To create a managed Sub CA, apply a custom resource to your Distributed Cloud Appliance instance.

  1. Create a CertificateAuthority resource and save it as a YAML file called subca.yaml:

    apiVersion: pki.security.gdc.goog/v1
    kind: CertificateAuthority
    metadata:
      Name: SUB_CA_NAME
      namespace: USER_PROJECT_NAMESPACE
    spec:
      caProfile:
        commonName: COMMON_NAME
        duration: DURATION
        renewBefore: RENEW_BEFORE
        organizations:
        - ORGANIZATIONS
        organizationalUnits:
        - ORGANIZATIONAL_UNITS
        countries:
        - COUNTRIES
        localities:
        - LOCALITIES
        provinces:
        - PROVINCES
        streetAddresses:
        - STREET_ADDRESSES
        postalCodes:
        - POSTAL_CODES
      caCertificate:
        managedSubCA:
          certificateAuthorityRef:
            name: ROOT_CA_NAME
            namespace: USER_PROJECT_NAMESPACE
      certificateProfile:
        keyUsage:
          - digitalSignature
          - keyCertSign
          - crlSign
        extendedKeyUsage:
          - EXTENDED_KEY_USAGE
      secretConfig:
        secretName: SECRET_NAME
        privateKeyConfig:
          algorithm: KEY_ALGORITHM
          size: KEY_SIZE
      acme:
        enabled: ACME_ENABLED
    

    Replace the following variables:

    Variable Description
    SUB_CA_NAME The name of the Sub CA.
    USER_PROJECT_NAMESPACE The name of the namespace where the user project resides.
    COMMON_NAME The common name of the CA certificate.
    DURATION The requested lifetime of the CA certificate.
    ROOT_CA_NAME The name of the root CA.
    SECRET_NAME The name of the Kubernetes Secret that holds the private key and signed CA certificate.

    The following variables are optional values:

    Variable Description
    RENEW_BEFORE The rotation time before the CA certificate expires.
    ORGANIZATIONS Organizations to be used on the certificate.
    ORGANIZATIONAL_UNITS Organizational units to be used on the certificate.
    COUNTRIES Countries to be used on the certificate.
    LOCALITIES Cities to be used on the certificate.
    PROVINCES State or Provinces to be used on the certificate.
    STREET_ADDRESSES Street addresses to be used on the certificate.
    POSTAL_CODES Postal codes to be used on the certificate.
    EXTENDED_KEY_USAGE The extended key usage for the certificate. If provided, the allowed values are serverAuth and clientAuth.
    KEY_ALGORITHYM The private key algorithm used for this certificate. Allowed values are RSA, Ed25519, or ECDSA. If the size is not provided, it defaults to 256 for ECDSA and 2048 for RSA. Key size is ignored for Ed25519.
    KEY_SIZE The size, in bits, of the private key for this certificate depends on the algorithm. RSA allows 2048, 3072, 4096, or 8192 (default 2048). ECDSA allows 256, 384, or 521 (default 256). Ed25519 ignores size.
    ACME_ENABLED If set to true, CA runs in ACME mode and outputs the ACME server URL. You can then use the ACME client and protocol to manage certificates.
  2. Apply the custom resource to your Distributed Cloud instance:

    kubectl apply -f subca.yaml --kubeconfig MANAGEMENT_API_SERVER_KUBECONFIG
    

    Replace MANAGEMENT_API_SERVER_KUBECONFIG with the path to the kubeconfig file of the Management API server.

  3. Verify the readiness of the Sub CA. It takes ~40 minutes for the CA to become ready:

    kubectl --kubeconfig MANAGEMENT_API_SERVER_KUBECONFIG -n USER_PROJECT_NAMESPACE get certificateauthority.pki.security.gdc.goog/SUB_CA_NAME -ojson | jq -r ' 
    .status.conditions[] | select( .type as $id | "Ready" | index($id))'
    

    The output looks similar to the following:

    {
      "lastTransitionTime": "2025-01-24T17:09:29Z",
      "message": "CA reconciled",
      "observedGeneration": 2,
      "reason": "Ready",
      "status": "True",
      "type": "Ready"
    }
    

Create a Sub CA from an external CA

This Sub CA supports signing leaf certificates with external or user managed CAs. It generates a CSR for users to sign.

  1. Create a CertificateAuthority resource and save it as a YAML file called subca-external.yaml:

    apiVersion: pki.security.gdc.goog/v1
    kind: CertificateAuthority
    metadata:
      Name: SUB_CA_NAME
      namespace: USER_PROJECT_NAMESPACE
    spec:
      caProfile:
        commonName: COMMON_NAME
        duration: DURATION
        renewBefore: RENEW_BEFORE
        organizations:
        - ORGANIZATION
        organizationalUnits:
        - ORGANIZATIONAL_UNITS
        countries:
        - COUNTRIES
        localities:
        - LOCALITIES
        provinces:
        - PROVINCES
        streetAddresses:
        - STREET_ADDRESSES
        postalCodes:
        - POSTAL_CODES
      caCertificate:
        externalCA: {}
      certificateProfile:
        keyUsage:
          - digitalSignature
          - keyCertSign
          - crlSign
        extendedKeyUsage:
          - EXTENDED_KEY_USAGE
      secretConfig:
        secretName: SECRET_NAME
        privateKeyConfig:
          algorithm: KEY_ALGORITHM
          size: KEY_SIZE
      acme:
        enabled: ACME_ENABLED
    

    Replace the following variables:

    Variable Description
    SUB_CA_NAME The name of the subCA.
    USER_PROJECT_NAMESPACE The project ID for the project where you want to import the image.
    COMMON_NAME The common name of the CA certificate.
    DURATION The requested lifetime of the CA certificate
    SECRET_NAME The name of the Kubernetes Secret that holds the private key and signed CA certificate.

    The following variables are optional values:

    Variable Description
    RENEW_BEFORE The rotation time before the CA certificate expires.
    ORGANIZATION Organization to be used on the certificate.
    ORGANIZATIONAL_UNITS Organizational units to be used on the certificate.
    COUNTRIES Countries to be used on the certificate.
    LOCALITIES Cities to be used on the certificate.
    PROVINCES State or Provinces to be used on the certificate.
    STREET_ADDRESSES Street addresses to be used on the certificate.
    POSTAL_CODES Postal codes to be used on the certificate.
    EXTENDED_KEY_USAGE The extended key usage for the certificate. If provided, the allowed values are serverAuth and clientAuth.
    KEY_ALGORITHYM The private key algorithm used for this certificate. Allowed values are RSA, Ed25519, or ECDSA. If the size is not provided, it defaults to 256 for ECDSA and 2048 for RSA. Key size is ignored for Ed25519.
    KEY_SIZE The size, in bits, of the private key for this certificate depends on the algorithm. RSA allows 2048, 3072, 4096, or 8192 (default 2048). ECDSA allows 256, 384, or 521 (default 256). Ed25519 ignores size.
    ACME_ENABLED If set to true, CA runs in ACME mode and outputs the ACME server URL. You can then use the ACME client and protocol to manage certificates.
  2. Apply the custom resource to your Distributed Cloud instance:

    kubectl apply -f subca-external.yaml --kubeconfig MANAGEMENT_API_SERVER_KUBECONFIG
    
  3. A CSR for the Sub-CA is generated within the GDC Management API server. You must download the CSR and sign it. Once signed, you can upload the signed certificate into the GDC Management API server.

  4. Gather the certificate signing requests (CSR) from your Distributed Cloud environment:

    kubectl get certificateauthorities SUB_CA_NAME -n USER_PROJECT_NAMESPACE -ojson | jq -j '"echo ", .status.externalCA.csr, " | base64 -d > ","sub_ca.csr\n"' | bash
    

    The command generates a CSR file called sub_ca.csr in the current directory. This file contains a CSR for an X.509 CA certificate.

  5. Use the customer's root CA to request signed CA certificates for the sub_ca.csr file.

  6. For an approved certificate signing request, you must obtain a CA certificate signed by the customer's root CA. Store the certificate in the sub_ca.crt file in the current directory.

  7. If applicable, obtain the customer's root CA certificate and store it in the ca.crt file in the current directory.

  8. Verify the Subject Alternative Name (SAN) extensions in the certificate:

    openssl x509 -text -noout -in sub_ca.crt | grep -A 1 "Subject Alternative Name"
    

    If the CA certificate has a Common Name (CN) rather than a SAN, verify the CN in the certificate:

    openssl x509 -text -noout -in sub_ca.crt | grep -A 1 "Subject: CN"
    
  9. Generate the spec to patch the CertificateAuthority resource:

    echo "spec:
      caCertificate:
        externalCA:
          signedCertificate:
            certificate: $(base64 -w0 SUB_CA_NAME.crt)
            ca: $(base64 -w0 ca.crt)" > patch.txt
    

    The content in the patch.txt file looks similar to the following:

    spec:
      caCertificate:
        externalCA:
          signedCertificate:
            certificate: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURSekNDQ…
            ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURRVENDQ…
    
  10. Edit the spec field of the CertificateAuthority resource:

    kubectl patch certificateauthority SUB_CA_NAME -n USER_PROJECT_NAMESPACE--patch-file patch.txt --type='merge'
    
  11. Verify the readiness of the bring your own (BYO) Sub CA. It normally takes around 40 minutes for the CA to become ready:

    kubectl -n USER_PROJECT_NAMESPACE get certificateauthority.pki.security.gdc.goog/SUB_CA_NAME -ojson | jq -r ' .status.conditions[] | select( .type as $id | "Ready" | index($id))'
    

    The output looks similar to the following:

    {
      "lastTransitionTime": "2024-04-30T22:10:50Z",
      "message": "Certificate authority is ready for use",
      "observedGeneration": 3,
      "reason": "Ready",
      "status": "True",
      "type": "Ready"
    }
    
  12. Verify the expiration date of the signed CA certificates:

    kubectl -n USER_PROJECT_NAMESPACE get secret SECRET_NAME -ojson | jq -j '"echo ", .metadata.name, " $(echo ", .data["tls.crt"], "| base64 -d | openssl x509 -enddate -noout)\n"' | bash
    

List CAs

To list all of the Certificate Authority Service resources in your Distributed Cloud air-gapped instance, do the following:

Use the certificateauthorities parameter to list all CertificateAuthority resources:

   kubectl --kubeconfig MANAGEMENT_API_SERVER_KUBECONFIG -n USER_PROJECT_NAMESPACE get certificateauthorities

The output looks similar to the following:

   NAMESPACE    NAME              READY   REASON   AGE
   foo          root-ca           True    Ready    7h24m
   foo          sub-ca            True    Ready    7h24m