The Kubernetes Certificates API
Manually signing certificates with the cluster CA requires access to the CA private key on the control plane node. That key is the most sensitive secret in the entire cluster. Handing out access to it for routine certificate issuance is a serious security risk. Kubernetes provides a built-in API for requesting and approving certificates without ever touching the CA key directly.
The CertificateSigningRequest resource
The CertificateSigningRequest (CSR) resource represents a request for a signed certificate. A user or an automated process creates a CSR object in Kubernetes. An administrator (or an automated controller) reviews it and approves or denies it. When approved, the Kubernetes controller manager signs the CSR using the cluster CA and stores the result in the CSR object’s status.certificate field.
kubectl get certificatesigningrequestsRun that now. In the simulator, the cluster is pre-configured and may have no pending CSRs. The command still confirms the Certificates API is reachable and shows you the column layout: NAME, AGE, SIGNERNAME, REQUESTOR, REQUESTEDDURATION, CONDITION.
The CONDITION column is the key field. It shows Pending for a CSR waiting for a decision, Approved for one the controller manager has signed, and Denied for one explicitly rejected.
The workflow
The developer generates their private key and a CSR file locally (using openssl, as shown in the previous lesson). They base64-encode the CSR file contents and embed that in a Kubernetes manifest. They submit the manifest. The admin inspects it and approves it. The signed certificate becomes available in the cluster object, and the developer retrieves it.
Building the CSR manifest
Start with the scaffolding:
nano developer-csr.yamlThe manifest needs the CSR contents, a signer, and the intended usages. Build it field by field.
First, the metadata and the encoded request:
apiVersion: certificates.k8s.io/v1kind: CertificateSigningRequestmetadata: name: developerspec: request: <base64-encoded-csr>The request field holds the base64-encoded content of the .csr file. In a real workflow, you would generate this with:
# reference - not available in simulatorcat developer.csr | base64 | tr -d '\n'Then add the signer and usages:
apiVersion: certificates.k8s.io/v1kind: CertificateSigningRequestmetadata: name: developerspec: request: <base64-encoded-csr> signerName: kubernetes.io/kube-apiserver-client usages: - client authThe signerName field tells Kubernetes which signing authority to use. kubernetes.io/kube-apiserver-client instructs the controller manager to sign with the cluster CA, producing a certificate valid for authenticating to the API server. The usages field restricts what the certificate can do. A client auth usage cannot be used as a server certificate, and vice versa.
kubectl apply -f developer-csr.yamlInspecting a pending CSR
After submitting, list all CSRs to confirm it arrived:
kubectl get certificatesigningrequestsYou should see developer in the list with CONDITION Pending. Get the full object to verify the spec was recorded correctly:
kubectl get csr developer -o yamlThe status section is empty at this point: no certificate yet, because no approval has been given.
You create a CSR object and immediately run kubectl get csr. The CONDITION column shows Pending. What does that mean?
Try it: kubectl get certificatesigningrequests
Reveal answer
Pending means the CSR has been received but no admin has approved or denied it yet. The controller manager will not sign it until an explicit approval is given. A CSR stays Pending indefinitely until acted on or deleted.
Approving and denying
An administrator reviews the request and approves it:
kubectl certificate approve developerAfter approval, the controller manager signs the CSR immediately. List the CSRs again:
kubectl get certificatesigningrequestsThe CONDITION column now shows Approved. To retrieve the signed certificate:
kubectl get csr developer -o yamlThe status.certificate field now contains the base64-encoded signed certificate. In a real workflow, the developer decodes that field and adds it to their kubeconfig alongside their private key.
If the request is invalid or should not be granted, an admin denies it instead:
# reference - not available in simulatorkubectl certificate deny developerOnce a CSR is approved or denied, the decision cannot be reversed by the same admin action. To reissue, delete the CSR object and have the developer submit a new one. A denied CSR stays visible in the cluster for auditing purposes until explicitly deleted.
What is the security advantage of using the Certificates API instead of signing certificates directly with the CA private key?
Reveal answer
The CA private key never leaves the control plane node, and no individual user needs access to it for routine certificate issuance. An admin only needs permission to approve or deny CSR objects, which is a much narrower privilege. The signing itself is performed internally by the controller manager. This limits the blast radius if an admin account is compromised.
The identity encoded in the CSR
The CSR object’s spec.request contains the original CSR with its subject fields. When the controller manager signs it, those fields are carried into the resulting certificate. The CN in the CSR subject becomes the Kubernetes username. Each O value becomes a group. This means the admin approving the CSR should verify the subject before approving: a CSR with CN=cluster-admin in the subject would, if approved, grant the bearer an admin-level username in RBAC. (RBAC rules themselves are covered in the RBAC module.)
kubectl get certificatesigningrequestsRunning this command one final time, note the REQUESTOR column. It shows which Kubernetes user created the CSR object, which is distinct from the identity being requested inside the CSR. Both pieces of information are relevant when auditing certificate issuance.
The Certificates API, the PKI layout, certificate creation, and diagnostic inspection together form the complete picture of how Kubernetes manages TLS security. Every connection in the cluster, from kubectl to the API server to etcd, depends on these building blocks working correctly.