Từ Elasticsearch 8.x, security được bật default. TLS giữa các node là bắt buộc, không phải tuỳ chọn. Nhưng “TLS bật default” không có nghĩa “TLS đúng cách”. Đa số setup thực tế còn dùng self-signed cert quên rotate, beats nói HTTP không TLS, hoặc Kibana set verification_mode: none từ một bài tutorial cũ rồi quên.

Đây là bài 19 trong series Kibana từ A đến Z. Sau bài này bạn sẽ làm được:

  • Hiểu 3 channel TLS: transport, HTTP, Beats
  • Sinh CA và node certificate bằng elasticsearch-certutil
  • Cấu hình Kibana plus ES nói HTTPS đúng cách (verify cert)
  • Setup mTLS cho Beats ingest
  • Rotate cert mà không downtime
  • Tránh pitfall verification_mode: none và CA mismatch

Phần 1: Ba channel TLS phải cấu hình

                          [Public TLS - Let's Encrypt]
                                      |
              Browser/User ----- HTTPS:443 -------> Reverse Proxy
                                      |
                                      |  (internal HTTPS hoặc HTTP)
                                      v
                              Kibana :5601
                                      |
                                      |  [HTTP TLS - ES HTTP layer]
                                      v
                          Elasticsearch :9200
                              ^             ^
                              |             |  [Transport TLS - inter-node]
                              |             v
                          ES :9300 <-> ES :9300
                              ^
                              |  [Beats TLS - mTLS optional]
                              |
                          Filebeat / Metricbeat / Auditbeat

Bốn channel khác nhau:

  1. Public TLS: browser tới reverse proxy. Cert từ Let’s Encrypt hoặc commercial CA.
  2. Transport TLS: giữa node ES với nhau (port 9300). Bắt buộc từ ES 8.x.
  3. HTTP TLS: client (Kibana, Beats, curl) tới ES (port 9200). Khuyến nghị bật.
  4. Beats TLS: log shipper tới ES, có thể bật mTLS để authenticate beat.

Mỗi channel có cấu hình riêng. Không nhầm cert của channel này dùng cho channel khác.

Phần 2: Sinh CA và node cert bằng elasticsearch-certutil

ES ship sẵn tool bin/elasticsearch-certutil. Workflow chuẩn:

Bước 1: tạo CA

bin/elasticsearch-certutil ca \
  --out /tmp/elastic-stack-ca.p12 \
  --pass ""

.p12 là PKCS12 keystore chứa CA cert plus key. Đặt password rỗng cho lab, production phải đặt password lưu vào keystore.

Bước 2: tạo cert cho từng node

bin/elasticsearch-certutil cert \
  --ca /tmp/elastic-stack-ca.p12 \
  --ca-pass "" \
  --name "es-node-1" \
  --dns "es-node-1,es-node-1.cluster.local,localhost" \
  --ip "10.0.1.10,127.0.0.1" \
  --out /etc/elasticsearch/certs/es-node-1.p12 \
  --pass ""

DNS plus IP SAN quan trọng. Cluster nói chuyện qua DNS name, IP, hoặc localhost. Cert phải có đủ SAN, nếu không client báo Name does not match certificate.

Repeat cho mọi node trong cluster. Có thể sinh batch qua file YAML:

# instances.yml
instances:
  - name: "es-node-1"
    dns: ["es-node-1", "es-node-1.cluster.local"]
    ip: ["10.0.1.10"]
  - name: "es-node-2"
    dns: ["es-node-2", "es-node-2.cluster.local"]
    ip: ["10.0.1.11"]
  - name: "kibana"
    dns: ["kibana", "kibana.cluster.local"]
    ip: ["10.0.1.20"]
bin/elasticsearch-certutil cert \
  --ca /tmp/elastic-stack-ca.p12 \
  --ca-pass "" \
  --in instances.yml \
  --out /tmp/certs.zip \
  --pass ""

certs.zip chứa cert cho từng instance. Unzip plus phân phối.

Bước 3: extract CA cert riêng cho client

Client (Beats, curl) cần CA cert dạng PEM:

openssl pkcs12 \
  -in /tmp/elastic-stack-ca.p12 \
  -out /tmp/ca.crt \
  -nokeys -clcerts

File ca.crt distribute tới mọi client để verify.

Phần 3: Cấu hình Transport TLS

Trên MỌI node, elasticsearch.yml:

xpack.security.enabled: true

# Transport TLS - inter-node
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.keystore.path: certs/es-node-1.p12
xpack.security.transport.ssl.truststore.path: certs/es-node-1.p12

# HTTP TLS - client tới ES
xpack.security.http.ssl.enabled: true
xpack.security.http.ssl.verification_mode: certificate
xpack.security.http.ssl.keystore.path: certs/es-node-1.p12
xpack.security.http.ssl.truststore.path: certs/es-node-1.p12

Password keystore lưu vào ES keystore (không hardcode trong yml):

bin/elasticsearch-keystore add xpack.security.transport.ssl.keystore.secure_password
bin/elasticsearch-keystore add xpack.security.transport.ssl.truststore.secure_password
bin/elasticsearch-keystore add xpack.security.http.ssl.keystore.secure_password
bin/elasticsearch-keystore add xpack.security.http.ssl.truststore.secure_password

verification_mode options:

ModeBehaviour
fullVerify cert chain, hostname, expiry
certificateVerify chain plus expiry, KHÔNG verify hostname
noneKhông verify (KHÔNG dùng production)

certificate là default an toàn cho transport (inter-node) vì node nói chuyện qua nhiều hostname/IP. full cho HTTP TLS thì lý tưởng nhưng cần đảm bảo DNS plus SAN khớp.

Phần 4: Cấu hình Kibana nói HTTPS với ES

kibana.yml:

elasticsearch.hosts: ["https://es-node-1.cluster.local:9200", "https://es-node-2.cluster.local:9200"]

elasticsearch.ssl.certificateAuthorities: ["/etc/kibana/certs/ca.crt"]
elasticsearch.ssl.verificationMode: certificate

# HTTPS public cho browser (nếu không có reverse proxy lo TLS)
server.ssl.enabled: true
server.ssl.certificate: /etc/kibana/certs/kibana.crt
server.ssl.key: /etc/kibana/certs/kibana.key

3 điểm quan trọng:

  1. elasticsearch.hosts dùng https://, không http://. Tinh tế nhưng hay quên.
  2. certificateAuthorities: trỏ tới file CA PEM. Có thể list nhiều CA.
  3. server.ssl.enabled: true chỉ cần khi Kibana phục vụ HTTPS trực tiếp. Có reverse proxy TLS termination thì bỏ qua phần server.ssl.*.

Khi reverse proxy TLS termination plus Kibana nói plain HTTP:

server.ssl.enabled: false
server.host: "0.0.0.0"
server.publicBaseUrl: "https://kibana.example.com"

Trust browser HTTPS qua Nginx, Kibana không lo TLS phía browser. Vẫn HTTPS phía Elasticsearch.

Phần 5: TLS cho Beats

Filebeat config (filebeat.yml):

output.elasticsearch:
  hosts: ["https://es-node-1.cluster.local:9200"]
  ssl.certificate_authorities: ["/etc/filebeat/certs/ca.crt"]
  ssl.verification_mode: certificate

  # API key auth (đã setup ở bài 12)
  api_key: "${ES_API_KEY}"

mTLS để beat tự authenticate bằng client cert (thay vì username/password hoặc api_key):

output.elasticsearch:
  hosts: ["https://es-node-1.cluster.local:9200"]
  ssl.certificate_authorities: ["/etc/filebeat/certs/ca.crt"]
  ssl.certificate: "/etc/filebeat/certs/filebeat.crt"
  ssl.key: "/etc/filebeat/certs/filebeat.key"
  ssl.verification_mode: full

Cluster ES phải bật xpack.security.http.ssl.client_authentication: optional (hoặc required). Có PKI realm map cert subject sang role:

xpack.security.authc.realms:
  pki:
    pki1:
      order: 1
      certificate_authorities: ["/etc/elasticsearch/certs/ca.crt"]

Role mapping:

PUT _security/role_mapping/filebeat_writers
{
  "roles": ["filebeat_writer"],
  "enabled": true,
  "rules": {
    "field": { "dn": "CN=filebeat,*" }
  }
}

Lợi ích mTLS so với api_key: rotate dễ (chỉ rotate cert), không có secret nằm trong config file, audit log thấy DN của cert.

Phần 6: Public TLS với Let’s Encrypt

Cho reverse proxy serve kibana.example.com:

sudo certbot --nginx \
  -d kibana.example.com \
  --email [email protected] \
  --agree-tos --no-eff-email

Certbot tự thêm vào Nginx config:

ssl_certificate     /etc/letsencrypt/live/kibana.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/kibana.example.com/privkey.pem;

Auto renew qua systemd timer hoặc cron:

sudo systemctl enable --now certbot.timer

Verify auto renew:

sudo certbot renew --dry-run

Lưu ý: Let’s Encrypt cert 90 ngày. Nhỡ renew thì cert hết hạn, user nhận NET::ERR_CERT_DATE_INVALID. Đặt monitoring uptime cho expiry.

Phần 7: Rotate cert không downtime

Vì sao phải rotate:

  • CA cert thường 5-10 năm, không gấp.
  • Node cert thường 1-3 năm.
  • Compromised key.

Quy trình rotate node cert (không downtime, cluster vẫn xanh):

Bước 1: sinh cert mới với CA hiện tại

bin/elasticsearch-certutil cert \
  --ca /tmp/elastic-stack-ca.p12 \
  --ca-pass "" \
  --name "es-node-1" \
  --dns "es-node-1,es-node-1.cluster.local" \
  --ip "10.0.1.10" \
  --out /tmp/es-node-1-new.p12 \
  --pass ""

Bước 2: rolling restart từng node

Trên node 1:

# 1. Đặt cluster về stable mode
PUT _cluster/settings { "transient": { "cluster.routing.allocation.enable": "primaries" } }

# 2. Stop node
sudo systemctl stop elasticsearch

# 3. Swap cert
sudo cp /tmp/es-node-1-new.p12 /etc/elasticsearch/certs/es-node-1.p12
sudo systemctl reload-or-restart elasticsearch.service

# 4. Verify node rejoin
GET _cat/nodes?v

Repeat cho từng node. Cluster luôn có quorum nên service không gián đoạn.

Rotate CA: phức tạp hơn

Có 2 chiến lược:

  1. Dual CA: trong khi rotate, truststore chứa CẢ CA cũ và CA mới. Node cũ dùng cert CA cũ, node mới dùng cert CA mới. Sau khi tất cả node có cert CA mới, gỡ CA cũ.
  2. Big bang: stop cluster, rotate hết, start lại. Downtime ngắn nhưng đơn giản.

Lab/dev dùng big bang. Production dùng dual CA. ES từ 7.10 hỗ trợ multiple keystore entries cho phép có 2 CA cùng trust.

Phần 8: Verify TLS hoạt động

Test transport TLS

openssl s_client -connect es-node-1.cluster.local:9300 -showcerts

Output có cert chain. Empty = TLS off hoặc connection bị từ chối.

Test HTTP TLS

curl -v --cacert /tmp/ca.crt \
  -u elastic:$ES_PASS \
  https://es-node-1.cluster.local:9200/

Status 200 plus banner JSON = OK. SSL certificate problem = CA mismatch hoặc hostname mismatch.

Test mTLS

curl -v --cacert /tmp/ca.crt \
  --cert /etc/filebeat/certs/filebeat.crt \
  --key /etc/filebeat/certs/filebeat.key \
  https://es-node-1.cluster.local:9200/_security/_authenticate

Response trả username plus authentication_realm: pki1 = PKI realm work.

Inspect cert expiry

openssl x509 -in /etc/kibana/certs/kibana.crt -noout -dates
# notBefore=May 17 00:00:00 2026 GMT
# notAfter=Aug 15 00:00:00 2026 GMT

Alert khi notAfter còn dưới 30 ngày. Set rule trong Kibana monitoring index.

Phần 9: Pitfall hay gặp

Pitfall 1: verification_mode: none trong production

Một team tôi từng thấy copy config từ tutorial, set none cho mọi channel để “qua bước setup”. Rồi quên revert. 6 tháng sau, audit phát hiện. Fix: dùng certificate mặc định cho mọi channel, full cho HTTP cluster có DNS ổn định.

Pitfall 2: CA cert hết hạn

CA expiry = mọi node cert dưới nó tự fail. Trong setup certutil default, CA thường có expiry 3 năm. Set alert cho CA expiry sớm 6 tháng.

Pitfall 3: SAN thiếu IP localhost

Node thường health check chính nó qua 127.0.0.1. Nếu SAN không có 127.0.0.1, health check fail với verification_mode: full. Fix: thêm 127.0.0.1localhost vào SAN mọi node cert.

Pitfall 4: tin tưởng Cloudflare end-to-end

Cloudflare Full plus origin cert tự ký. Cloudflare verify cert origin với CA dùng cho cloudflare origin CA (Cloudflare có CA riêng nếu enable Origin Certificate). Nếu dùng cert Cloudflare-issued, ngắn (15 năm) và miễn phí. Đừng tin Cloudflare sẽ “magic” verify cert tự ký.

Pitfall 5: Kibana trust system CA only

Khi không set elasticsearch.ssl.certificateAuthorities, Kibana fall back system CA store (/etc/ssl/certs/). Self-signed cert không trust qua đường này. Luôn explicit set CA pem trong config.

Pitfall 6: Beats client cert quên password

ssl.key_passphrase bị bỏ quên → Beats không start, error chung chung “failed to parse private key”. Decrypt key trước hoặc set passphrase:

output.elasticsearch:
  ssl.key_passphrase: "${BEAT_KEY_PASS}"

Cheatsheet

ViệcCách
Sinh CAelasticsearch-certutil ca
Sinh node certelasticsearch-certutil cert --ca <ca.p12> --in instances.yml
Bật transport TLSxpack.security.transport.ssl.enabled: true
Bật HTTP TLSxpack.security.http.ssl.enabled: true
Kibana trỏ HTTPSelasticsearch.hosts: ["https://..."]
Filebeat trust CAssl.certificate_authorities: ["ca.crt"]
mTLS bật ESxpack.security.http.ssl.client_authentication: optional
PKI role map_security/role_mapping với rule field.dn
Verify modecertificate default, full khi DNS ổn
Check expiryopenssl x509 -noout -dates
Rolling restarttừng node với allocation.enable: primaries

Lời kết

TLS không phải tính năng, là kiểu mặc định. ES 8.x ép bật là việc tốt. Việc dev cần là không bypass nó (đừng verification_mode: none), không quên rotate, và biết cách distribute CA cho client. Khi setup xong và alert cert expiry chạy, bạn quên mất TLS đang chạy là dấu hiệu nó được làm đúng.

Bài 20 đóng Part 5 với chủ đề khó nhất: Upgrade ELK, minor version in-place và major version cluster-swap. TLS, ILM, snapshot, security plus mọi thứ đã setup từ bài 12 đến 19 phải sống sót qua upgrade mà không mất data hay downtime. Bài đó kèm matrix tương thích version plus runbook đã dùng trong drill.