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.
Create a
CertificateAuthority
resource and save it as a YAML file calledsubca.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
andclientAuth
.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.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.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.
Create a
CertificateAuthority
resource and save it as a YAML file calledsubca-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
andclientAuth
.KEY_ALGORITHYM The private key algorithm used for this certificate. Allowed values are RSA
,Ed25519
, orECDSA
. If the size is not provided, it defaults to 256 forECDSA
and 2048 forRSA
. Key size is ignored forEd25519
.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.Apply the custom resource to your Distributed Cloud instance:
kubectl apply -f subca-external.yaml --kubeconfig MANAGEMENT_API_SERVER_KUBECONFIG
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.
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 anX.509
CA certificate.Use the customer's root CA to request signed CA certificates for the
sub_ca.csr
file.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.If applicable, obtain the customer's root CA certificate and store it in the
ca.crt
file in the current directory.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"
Generate the
spec
to patch theCertificateAuthority
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…
Edit the
spec
field of theCertificateAuthority
resource:kubectl patch certificateauthority SUB_CA_NAME -n USER_PROJECT_NAMESPACE--patch-file patch.txt --type='merge'
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" }
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