Manage TLS Certificates In a Kubernetes Cluster

Manage TLS Certificates In a Kubernetes Cluster

1 3 11
calendar_today agoschedule4 min read

Today's challenge was about understanding how Kubernetes uses TLS certificates to secure communication between its components, and getting hands-on by onboarding a "new user" into the cluster using a Certificate Signing Request (CSR).

If you've ever wondered how kubectl actually proves to the API server that you're allowed to run commands, this is the exercise that answers it.

A quick rule of thumb before diving in: if a filename or extension has the word "key" in it, it's a private key. Otherwise, it's a public certificate. Keep that in mind as you read through the commands below.


Step 1: Generate a private key and a CSR

openssl genrsa -out adeoye.key 2048
openssl req -new -key adeoye.key -out adeoye.csr -subj "/CN=adeoye"

genrsa creates the private key, and req -new uses that key to generate a Certificate Signing Request. The -subj "/CN=adeoye" flag sets the "Common Name" on the certificate — this becomes the username Kubernetes will recognize once the cert is issued.

Gotcha: I typed adam.key in my req command before actually creating a file with that name, and got:

Could not open file or uri for loading private key from adam.key: No such file or directory

Lesson learned — the key file has to exist before you reference it when generating the CSR. Double-check your filenames match across commands.

Step 2: Base64-encode the CSR

The CSR object in Kubernetes expects the request in base64 form inside a YAML manifest. This is where things got interesting:

cat adeoye.csr | base64 tr -d "/"

That's wrong — it treats tr as a filename argument for base64, not a separate piped command:

base64: extra operand '/'

The correct version needs a pipe (|) before tr, and the actual newline character (\n), not the literal string "/n":

cat adeoye.csr | base64 | tr -d "\n"

This gives you a single-line base64 blob with no line breaks — exactly what the CSR YAML needs.

Step 3: Create the CSR manifest and apply it

Create a csr.yaml file with the base64-encoded request embedded in it, following the structure from the Kubernetes docs, then:

kubectl apply -f csr.yaml
kubectl get csr
NAME     AGE   SIGNERNAME                            REQUESTOR          REQUESTEDDURATION   CONDITION
adeoye   25s   kubernetes.io/kube-apiserver-client   kubernetes-admin   24h                 Pending

The CSR shows up as Pending — Kubernetes has received the request but it hasn't been approved yet.

Step 4: Approve the CSR

kubectl certificate approve adeoye
certificatesigningrequest.certificates.k8s.io/adeoye approved

Checking again with kubectl get csr now shows Approved,Issued — the cluster has signed the certificate.

Gotcha: kubectl approve certificate adeoye doesn't work — the order of the words matters:

error: unknown command "approve" for "kubectl"

It's kubectl certificate approve <name>, not kubectl approve certificate <name>.

Step 5: Retrieve and decode the issued certificate

kubectl get csr adeoye -o yaml > issuecert.yaml

The issued certificate lives (base64-encoded) inside that YAML file under status.certificate. To read the actual PEM certificate, decode it:

echo "<base64-blob>" | base64 -d

This prints out the human-readable certificate:

-----BEGIN CERTIFICATE REQUEST-----
MIICVjCCAT4CAQAwETEPMA0GA1UEAww...
-----END CERTIFICATE REQUEST-----

Gotcha: Pasting a long base64 string directly into the terminal can trigger "bracketed paste mode," which shows up as weird escape codes and leaves your shell hanging on a dquote> prompt. If that happens, press Ctrl+C to cancel and try again — wrapping the string in quotes (echo "...") helps avoid it.

To save the decoded certificate directly to a file instead of just printing it, redirect the output:

echo "<base64-blob>" | base64 -d > learner.crt

Gotcha: Writing -o learner.crt instead of > learner.crt doesn't work here — -o isn't a flag for base64, and the shell will try to run it as its own command:

zsh: command not found: -o

Use the > redirect operator to send output to a file.

Repeating the process for a second user

To make sure the workflow actually sank in, I repeated the whole thing for a second user, learner, this time being more careful with the pipe syntax:

openssl genrsa -out learner.key 2048
openssl req -new -key learner.key -out learner.csr -subj "/CN=learner"
kubectl apply -f learner.yaml
kubectl certificate approve learner
kubectl get csr learner -o yaml > issuecert-learner.yaml
NAME      AGE     SIGNERNAME                            REQUESTOR          REQUESTEDDURATION   CONDITION
learner   4m37s   kubernetes.io/kube-apiserver-client   kubernetes-admin   7d                  Approved,Issued

Once the certificate was decoded and saved to learner.crt, it would be ready to plug into a kubeconfig for that user, giving them client-cert-based access to the cluster.


Key commands from today

Generate a private key:

openssl genrsa -out adeoye.key 2048

Generate a CSR:

openssl req -new -key adeoye.key -out adeoye.csr -subj "/CN=adeoye"

Approve a CSR:

kubectl certificate approve <certificate-signing-request-name>

Deny a CSR:

kubectl certificate deny <certificate-signing-request-name>

Reference: Kubernetes docs — Create CertificateSigningRequest


What I took away from today

  • A CSR workflow has four clean stages: generate a key → generate a CSR → submit it to the API server as a CertificateSigningRequest object → get it approved and retrieve the signed cert.
  • base64 | tr -d "\n" (with the pipe, and a real newline character) is the correct way to get a clean, single-line encoded string for a YAML manifest.
  • kubectl certificate approve — command, then subcommand, then name — not the other way around.
  • Almost all my mistakes today were syntax slips (missing pipes, wrong flags, mismatched filenames), not conceptual ones. That's usually a good sign you understand what you're doing, even when the terminal disagrees with you for a minute.
Part 3 of 3 in My Kubernetes Journey
🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

How I Built a React Portfolio in 7 Days That Landed ₹1.2L in Freelance Work

Dharanidharan - Feb 9

Post-Quantum TLS Migration: What Engineers Actually Need to Do Before 2030

nine - Apr 27

Deploy an Azure Kubernetes Service (AKS) cluster using Azure CLI

Clever Cottonmouth - Apr 16, 2025

One giant Kubernetes cluster for everything

Nicolas Fränkel - Mar 20, 2025

Resource Requests and Limits in Kubernetes (Hands-on with Metrics Server) #40DaysOfKubernetes – Task

AYANFE - Jul 1
chevron_left
245 Points15 Badges
Abuja,Nigeria.
5Posts
3Comments
5Connections
A gentleman with a rough edge.

Related Jobs

View all jobs →

Commenters (This Week)

2 comments
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!