Internal TLS Communication Between Pods in Kubernetes using cert-manager
Written by:
Igor Gorovyy
DevOps Engineer Lead & Senior Solutions Architect
Why cert-manager is critical for Kubernetes
In today's world of DevSecOps and zero-trust architecture, securing internal communication between microservices becomes not just a recommendation, but a mandatory requirement. cert-manager solves one of the most complex problems in Kubernetes cluster management — automating the TLS certificate lifecycle.
Key problems cert-manager solves:
- Manual certificate management: without automation, administrators are forced to manually generate, renew, and distribute certificates
- Outages due to expired certificates: human factor often leads to untimely certificate renewals
- Security inconsistency: different teams may use different approaches to TLS
- Scaling complexity: as the number of services increases, certificate management becomes uncontrollable
Key benefits of cert-manager:
- Full automation: automatic generation, renewal, and distribution of certificates
- Support for various Issuers: from self-signed to Let's Encrypt and enterprise CAs
- Kubernetes-native: integration through CRD (Custom Resource Definitions)
- High security level: adherence to TLS best practices
- Reduced operational costs: minimizing manual DevOps team work
- Compliance: automatic adherence to corporate security policies
This document describes how to configure internal TLS or mTLS communication between pods in Kubernetes using self-signed certificates through cert-manager.
Key cert-manager Concepts and Interaction Architecture
graph LR
A[Certificate] -->|CSR| B[CertificateRequest]
B -->|sent to| C[Issuer / ClusterIssuer]
C -->|signs| D[CertificateRequest]
D -->|issues| E[Secret]
E -->|contains| F[tls.crt / tls.key / ca.crt]
F -->|mounted to| G[Pod]
C --> H[CA / SelfSigned / ACME]
- Issuer / ClusterIssuer: objects that define certificate sources (e.g., SelfSigned, CA, ACME).
Issuer
operates within a namespace,ClusterIssuer
— globally. - Certificate: describes the desired TLS certificate for a service. cert-manager automatically creates the certificate and places it in a Secret.
- CertificateRequest: internal object that cert-manager generates when processing a
Certificate
. Contains CSR request. - Secret: Kubernetes object where the ready certificate is stored (
tls.crt
,tls.key
,ca.crt
). - SelfSigned / CA Issuer: certificate sources that don't require external services. Suitable for internal infrastructure.
- Renewal: automatic certificate renewal before expiration (typically 30 days before).
- Solver (for ACME): domain ownership verification mechanism (HTTP01, DNS01). Not relevant for SelfSigned/CA.
Action Algorithm
- Create SelfSigned Issuer for root CA generation.
- Generate root CA certificate, which will be the trusted certificate authority.
- Create CA Issuer based on root CA.
- Generate TLS certificates for each service, which will be signed by CA.
- Connect certificates to Deployments, so services can use TLS.
- Configure HTTPS/HTTP client and server using certificates.
- (Optional) Configure mutual authentication (mTLS).
Process Flow
graph LR
A1[SelfSigned Issuer] --> A2[Root CA Certificate]
A2 --> B1[CA Issuer]
B1 --> C1[service-a TLS cert]
B1 --> C2[service-b TLS cert]
C1 -->|Mounted as Secret| D1[service-a Pod]
C2 -->|Mounted as Secret| D2[service-b Pod]
D1 -->|HTTPS / mTLS| D2
📄 YAML Manifests
1. SelfSigned Issuer
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: selfsigned-issuer
namespace: default
spec:
selfSigned: {}
2. Root CA Certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: root-ca
namespace: default
spec:
isCA: true
commonName: my-org-root-ca
secretName: root-ca-secret
issuerRef:
name: selfsigned-issuer
kind: Issuer
3. CA Issuer based on root CA
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: ca-issuer
namespace: default
spec:
ca:
secretName: root-ca-secret
4. Certificate for service-a
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: service-a-cert
namespace: default
spec:
secretName: service-a-tls
duration: 8760h
renewBefore: 720h
commonName: service-a.default.svc.cluster.local
dnsNames:
- service-a
- service-a.default
- service-a.default.svc
- service-a.default.svc.cluster.local
issuerRef:
name: ca-issuer
kind: Issuer
5. Certificate for service-b
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: service-b-cert
namespace: default
spec:
secretName: service-b-tls
duration: 8760h
renewBefore: 720h
commonName: service-b.default.svc.cluster.local
dnsNames:
- service-b
- service-b.default
- service-b.default.svc
- service-b.default.svc.cluster.local
issuerRef:
name: ca-issuer
kind: Issuer
6. Volume in Deployment
volumeMounts:
- name: tls-cert
mountPath: /etc/tls
readOnly: true
volumes:
- name: tls-cert
secret:
secretName: service-a-tls
How It Works 🚀
SelfSigned Issuer
generates a CA certificate (root-ca
), stored inroot-ca-secret
.CA Issuer
usesroot-ca
to sign other certificates.-
For each service cert-manager:
-
creates CSR
- signs through
ca-issuer
-
stores result in
Secret
(*.tls
), which includestls.crt
,tls.key
,ca.crt
-
TLS certificate is mounted to pod via
volume + mountPath
— detailed process:
Step 4.1: Volume Definition in Deployment
volumes:
- name: tls-cert # Volume name
secret:
secretName: service-a-tls # Reference to Secret with certificate
defaultMode: 0400 # Access permissions (read-only for owner)
items: # (Optional) selective file mounting
- key: tls.crt
path: server.crt
- key: tls.key
path: server.key
- key: ca.crt
path: ca.crt
Step 4.2: Container Mount
volumeMounts:
- name: tls-cert # Volume name (must match volumes.name)
mountPath: /etc/tls # Path in container filesystem
readOnly: true # Mandatory for security
subPath: "" # (Optional) subfolder mounting
Step 4.3: Result in Container After mounting, files appear in the container:
/etc/tls/
├── tls.crt # Public certificate (PEM format)
├── tls.key # Private key (PEM format)
└── ca.crt # Certificate authority certificate
Step 4.4: Internal Kubernetes Mechanism
graph TD
A[kubelet] -->|Request Secret| B[Kubernetes API Server]
B -->|RBAC Check| C[etcd]
C -->|Returns Secret data| B
B -->|Passes to kubelet| A
A -->|Creates tmpfs| D[In-Memory Volume]
D -->|Mounts files| E[Container Filesystem]
F[Secret Update] -->|Automatic| G[Volume Refresh]
H[Pod Restart] -->|Cleanup| I[tmpfs Cleared]
Step 4.5: Security Aspects of Mounting
* tmpfs storage: Certificates are stored in RAM, not on disk
* Automatic cleanup: When Pod is deleted, certificates automatically disappear
* RBAC validation: kubelet checks ServiceAccount permissions to read Secret
* Namespace isolation: Secret is only accessible within its namespace
* File permissions: defaultMode sets secure access permissions (0400 = read-only for owner)
Step 4.6: Practical Code Usage
// Go example of certificate reading
cert, err := tls.LoadX509KeyPair("/etc/tls/tls.crt", "/etc/tls/tls.key")
if err != nil {
log.Fatal(err)
}
// HTTPS server configuration
server := &http.Server{
Addr: ":8443",
TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}},
}
// Node.js example using certificates
const https = require('https');
const fs = require('fs');
const express = require('express');
const app = express();
// Reading certificates
const options = {
key: fs.readFileSync('/etc/tls/tls.key'),
cert: fs.readFileSync('/etc/tls/tls.crt'),
ca: fs.readFileSync('/etc/tls/ca.crt'),
requestCert: true, // For mTLS
rejectUnauthorized: true // Strict certificate validation
};
app.get('/', (req, res) => {
res.send('Secure HTTPS server with cert-manager certificates!');
});
// Creating HTTPS server
https.createServer(options, app).listen(8443, () => {
console.log('HTTPS Server running on port 8443');
});
// Example HTTPS client for mTLS
const clientOptions = {
hostname: 'service-b.default.svc.cluster.local',
port: 8443,
path: '/',
method: 'GET',
key: fs.readFileSync('/etc/tls/tls.key'),
cert: fs.readFileSync('/etc/tls/tls.crt'),
ca: fs.readFileSync('/etc/tls/ca.crt')
};
const req = https.request(clientOptions, (res) => {
console.log('Status:', res.statusCode);
res.on('data', (data) => {
console.log(data.toString());
});
});
req.end();
- Applications use certificates to run HTTPS servers or make requests via HTTPS
- With mTLS: both sides authenticate each other based on
ca.crt
TLS vs mTLS
TLS (one-way) | mTLS (mutual) | |
---|---|---|
Server has certificate | ✅ | ✅ |
Client has certificate | ❌ | ✅ |
Client verification | ❌ | ✅ |