Перейти до змісту

Внутрішня TLS-комунікація між подами у Kubernetes за допомогою cert-manager

Written by:

Igor Gorovyy
DevOps Engineer Lead & Senior Solutions Architect

LinkedIn


w

Чому cert-manager є критично важливим для Kubernetes

В сучасному світі DevSecOps і zero-trust архітектури, безпека внутрішньої комунікації між мікросервісами стає не просто рекомендацією, а обов'язковою вимогою. cert-manager вирішує одну з найскладніших проблем в управлінні Kubernetes кластерами — автоматизацію життєвого циклу TLS-сертифікатів.

Основні проблеми, які вирішує cert-manager:

  • Ручне управління сертифікатами: без автоматизації адміністратори змушені вручну генерувати, поновлювати та розповсюджувати сертифікати
  • Простої через прострочені сертифікати: людський фактор часто призводить до несвоєчасного оновлення сертифікатів
  • Неконсистентність у безпеці: різні команди можуть використовувати різні підходи до TLS
  • Складність масштабування: зі збільшенням кількості сервісів управління сертифікатами стає неконтрольованим

Ключові переваги cert-manager:

  1. Повна автоматизація: автоматичне генерування, поновлення та розподіл сертифікатів
  2. Підтримка різних Issuer-ів: від самопідписаних до Let's Encrypt і корпоративних CA
  3. Kubernetes-native: інтеграція через CRD (Custom Resource Definitions)
  4. Високий рівень безпеки: дотримання найкращих практик TLS
  5. Зменшення операційних витрат: мінімізація ручної роботи DevOps команд
  6. Compliance: автоматичне дотримання корпоративних політик безпеки

Цей документ описує, як налаштувати внутрішню TLS або mTLS-комунікацію між подами у Kubernetes, використовуючи самопідписаний сертифікат через cert-manager.


Ключові концепції cert-manager архітектура взіємодії

graph TD
    A[Certificate] -->|CSR| B[CertificateRequest]
    B -->|відправляється до| C[Issuer / ClusterIssuer]
    C -->|підписує| D[CertificateRequest]
    D -->|випускає| E[Secret]
    E -->|містить| F[tls.crt / tls.key / ca.crt]
    F -->|монтується до| G[Pod]
    C --> H[CA / SelfSigned / ACME]
  • Issuer / ClusterIssuer: об'єкти, що визначають джерело сертифікатів (наприклад, SelfSigned, CA, ACME). Issuer діє в межах namespace, ClusterIssuer — глобально.
  • Certificate: описує бажаний TLS-сертифікат для сервісу. cert-manager автоматично створює сертифікат і розміщує його в Secret.
  • CertificateRequest: внутрішній об'єкт, який cert-manager генерує при обробці Certificate. Містить CSR-запит.
  • Secret: об'єкт Kubernetes, де зберігається готовий сертифікат (tls.crt, tls.key, ca.crt).
  • SelfSigned / CA Issuer: джерела сертифікатів, які не потребують зовнішніх служб. Підходять для внутрішньої інфраструктури.
  • Renewal: автоматичне оновлення сертифікатів до завершення терміну дії (типово за 30 днів).
  • Solver (для ACME): механізм підтвердження володіння доменом (HTTP01, DNS01). Не актуальний для SelfSigned/CA.

Алгоритм дій

  1. Створити SelfSigned Issuer для генерації root CA.
  2. Згенерувати root CA сертифікат, який буде довіреним центром сертифікації.
  3. Створити CA Issuer на основі root CA.
  4. Згенерувати TLS сертифікати для кожного сервісу, які будуть підписані CA.
  5. Підключити сертифікати до Deployment-ів, щоб сервіси могли використовувати TLS.
  6. Налаштувати HTTPS/HTTP клієнт і сервер з використанням сертифікатів.
  7. (Опціонально) Налаштувати взаємну автентифікацію (mTLS).

Процесс

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 Маніфести

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 на базі root CA

apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: ca-issuer
  namespace: default
spec:
  ca:
    secretName: root-ca-secret

4. Сертифікат для 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. Сертифікат для 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 у Deployment

volumeMounts:
  - name: tls-cert
    mountPath: /etc/tls
    readOnly: true

volumes:
  - name: tls-cert
    secret:
      secretName: service-a-tls

Як це працює 🚀

  1. SelfSigned Issuer генерує CA-сертифікат (root-ca), що зберігається в root-ca-secret.
  2. CA Issuer використовує root-ca для підпису інших сертифікатів.
  3. Для кожного сервісу cert-manager:

  4. створює CSR

  5. підписує через ca-issuer
  6. зберігає результат у Secret (*.tls), який включає tls.crt, tls.key, ca.crt

  7. TLS сертифікат монтується у под через volume + mountPath — детальний процес:

Крок 4.1: Визначення Volume у Deployment

volumes:
- name: tls-cert                    # Ім'я volume
  secret:
    secretName: service-a-tls       # Посилання на Secret з сертифікатом
    defaultMode: 0400               # Права доступу (тільки читання для власника)
    items:                          # (Опціонально) вибіркове монтування файлів
    - key: tls.crt
      path: server.crt
    - key: tls.key
      path: server.key
    - key: ca.crt
      path: ca.crt

Крок 4.2: Монтування у контейнер

volumeMounts:
- name: tls-cert                    # Ім'я volume (має співпадати з volumes.name)
  mountPath: /etc/tls               # Шлях у файловій системі контейнера
  readOnly: true                    # Обов'язково для безпеки
  subPath: ""                       # (Опціонально) монтування підпапки

Крок 4.3: Результат у контейнері Після монтування у контейнері з'являються файли:

/etc/tls/
├── tls.crt          # Публічний сертифікат (PEM format)
├── tls.key          # Приватний ключ (PEM format)  
└── ca.crt           # Сертифікат центру сертифікації

Крок 4.4: Внутрішній механізм Kubernetes

graph TD
    A[kubelet] -->|Запит Secret| B[Kubernetes API Server]
    B -->|Перевірка RBAC| C[etcd]
    C -->|Повертає Secret data| B
    B -->|Передає kubelet| A
    A -->|Створює tmpfs| D[In-Memory Volume]
    D -->|Монтує файли| E[Container Filesystem]

    F[Secret Update] -->|Automatic| G[Volume Refresh]
    H[Pod Restart] -->|Cleanup| I[tmpfs Cleared]

Крок 4.5: Безпекові аспекти монтування
* tmpfs storage: Сертифікати зберігаються в оперативній пам'яті, не на диску
* Automatic cleanup: При видаленні Pod сертифікати автоматично зникають
* RBAC validation: kubelet перевіряє права ServiceAccount на читання Secret
* Namespace isolation: Secret доступний тільки в межах свого namespace
* File permissions: defaultMode встановлює безпечні права доступу (0400 = read-only для власника)

Крок 4.6: Practical використання у коді

// Go приклад читання сертифіката
cert, err := tls.LoadX509KeyPair("/etc/tls/tls.crt", "/etc/tls/tls.key")
if err != nil {
    log.Fatal(err)
}

// Налаштування HTTPS сервера
server := &http.Server{
    Addr:      ":8443",
    TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}},
}

// Node.js приклад використання сертифікатів
const https = require('https');
const fs = require('fs');
const express = require('express');

const app = express();

// Читання сертифікатів
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,        // Для mTLS
  rejectUnauthorized: true  // Строга перевірка сертифікатів
};

app.get('/', (req, res) => {
  res.send('Secure HTTPS server with cert-manager certificates!');
});

// Створення HTTPS сервера
https.createServer(options, app).listen(8443, () => {
  console.log('HTTPS Server running on port 8443');
});

// Приклад HTTPS клієнта для 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();
  1. Додатки використовують сертифікати для запуску HTTPS-серверів або здійснення запитів через HTTPS
  2. При mTLS: обидві сторони автентифікують одна одну на основі ca.crt

TLS vs mTLS

TLS (односторонній) mTLS (взаємний)
Сервер має сертифікат
Клієнт має сертифікат
Перевірка клієнта

Корисні посилання