Secret

Penyimpanan data sensitif dengan Secret: encoding, mounting, dan keterbatasan yang perlu diakui.

Daftar isi

Secret adalah objek Kubernetes untuk menyimpan data sensitif seperti token, kata sandi, dan kunci privat, dengan pola konsumsi yang sama seperti ConfigMap. Topik ini membahas bentuk penyimpanan nilainya, perbedaan antara encoding dan enkripsi yang sering tertukar, tipe Secret yang umum, mounting ke dalam Pod, serta keterbatasan keamanan yang perlu diakui. Semua nilai pada contoh adalah nilai latihan yang jelas bukan kredensial nyata.

Secret sebagai objek data sensitif#

Struktur Secret menyerupai ConfigMap, yaitu pasangan kunci dan nilai pada bidang data, tetapi tujuannya berbeda. Kubernetes memberikan Secret perlakuan khusus pada beberapa titik, misalnya distribusi ke Node secara terbatas hanya pada Pod yang menggunakannya dan opsi enkripsi pada penyimpanannya. Berikut manifest Secret untuk aplikasi jadwal piket dengan nilai latihan:

yaml secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: piket-token
type: Opaque
data:
  username: cGlrZXQ=
  token: bmlsYWktbGF0aWhhbg==

Nilai pada data wajib berupa teks dengan encoding base64, dan penulisannya dilakukan sendiri oleh pembuat manifest. Bidang stringData menyediakan kemudahan sebaliknya, yaitu nilai ditulis apa adanya dan API server menyandikannya saat objek diterima, sehingga tidak tersimpan dalam bentuk aslinya pada API server. Namun, keduanya bermuara pada nilai yang sama di API server, dan manifest yang sudah berisi data umumnya lebih disukai dalam repositori konfigurasi. Batas ukuran total Secret juga sama dengan ConfigMap, yaitu sekitar 1 MiB, sehingga kredensial besar perlu dipisahkan ke beberapa objek.

Base64 adalah encoding, bukan enkripsi#

Penyandian base64 hanyalah cara mewakili data biner sebagai teks yang aman untuk dikirim, dan hasilnya dapat dibalik oleh siapa pun tanpa kunci. Tujuannya menjadikan data biner dapat ditulis pada manifest YAML yang hanya menampung teks, bukan menyembunyikan isinya. Perintah berikut menyandikan nilai latihan lalu memulihkannya:

bash
# menyandikan nilai latihan menjadi base64
printf 'nilai-latihan' | base64

# memulihkan teks asli dari hasil encoding
printf 'bmlsYWktbGF0aWhhbg==' | base64 -d
text Keluaran
bmlsYWktbGF0aWhhbg==
nilai-latihan

Skrip berikut membaca secret.yaml dari bundle, mendekode setiap nilai pada data, dan menampilkan hubungan kunci ke nilainya. Decode berhasil tanpa kunci apa pun, karena memang tidak ada kunci yang terlibat dalam proses ini:

python
import base64

import yaml

# baca manifest Secret dari bundle topik
with open("secret.yaml") as f:
    secret = yaml.safe_load(f)

print(f"Secret {secret['metadata']['name']} bertipe {secret['type']}")
for kunci, sandi in secret["data"].items():
    teks = base64.b64decode(sandi).decode()
    print(f"  {kunci} -> {teks}")
text Keluaran
Secret piket-token bertipe Opaque
  username -> piket
  token -> nilai-latihan

Nilai yang tertulis pada keluaran kubectl get secret juga masih berbentuk base64, sehingga pemula yang baru pertama kali melihatnya sering mengira isinya sudah terlindungi. Nilai asli hanya muncul setelah hasil itu didekode, dan decode dilakukan satu baris perintah saja. Perbedaan ini memperjelas bahwa Secret menyimpan bentuk teks yang dapat dibalik dan bukan bentuk terenkripsi.

Tipe Secret yang umum#

Bidang type menandai peruntukan sebuah Secret, dan beberapa tipe menuntut kunci tertentu yang divalidasi API server saat objek dibuat. Tipe yang paling sering ditemui dirangkum pada tabel berikut:

TipeKunci yang dituntutPeruntukan
Opaquetidak adadata sensitif umum, ditentukan sendiri
kubernetes.io/tlstls.crt dan tls.keypasangan sertifikat dan kunci privat TLS
kubernetes.io/dockerconfigjson.dockerconfigjsonkredensial registry image
kubernetes.io/basic-authusername dan passwordautentikasi dasar

Tipe Opaque adalah default dan menerima kunci bebas, sehingga contoh sebelumnya menggunakannya. Tipe kubernetes.io/tls menolak objek apabila salah satu kunci wajibnya tidak ada, sehingga kesalahan terdeteksi lebih cepat. Validasi kunci wajib terjadi pada saat pembuatan objek, sehingga pemilihan tipe yang tepat memindahkan deteksi kesalahan lebih awal daripada menunggu Pod gagal membaca nilainya. Tipe lainnya, misalnya kubernetes.io/service-account-token, digunakan secara internal oleh kluster dan jarang dibuat manual.

Menggunakan Secret dari Pod#

Pod mengonsumsi Secret melalui dua pintu yang sama dengan ConfigMap, yaitu variabel lingkungan dan volume. Manifest berikut menyuntikkan kunci token sebagai variabel lingkungan dan sekaligus me-mount seluruh Secret sebagai berkas:

yaml piket-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: piket
spec:
  containers:
    - name: piket
      image: busybox:1.36
      command: ["sh", "-c", "sleep 3600"]
      env:
        - name: TOKEN
          valueFrom:
            secretKeyRef:
              name: piket-token
              key: token
      volumeMounts:
        - name: kredensial
          mountPath: /var/run/secrets/piket
          readOnly: true
  volumes:
    - name: kredensial
      secret:
        secretName: piket-token

Container di atas membaca variabel TOKEN serta berkas /var/run/secrets/piket/username dan /var/run/secrets/piket/token, dengan setiap kunci menjadi satu berkas. Bidang items dapat membatasi kunci yang muncul dan mengganti nama berkasnya:

yaml Contoh: membatasi kunci dan nama berkasnya
secret:
  secretName: piket-token
  items:
    - key: token
      path: token
      mode: 0400

Bidang mode pada setiap entri mengatur izin berkasnya, dan kubelet menggunakan izin 0644 apabila bidang itu tidak diisi. Nilai seperti 0400 ditafsirkan sebagai bilangan oktal pada YAML, sehingga berkasnya hanya dapat dibaca dan tidak dapat ditulis.

Sifat pembaruannya juga sama, yaitu variabel lingkungan dibaca sekali saat container dimulai, sedangkan berkas yang di-mount diperbarui kubelet secara berkala. Volume Secret di belakang layar ditempatkan pada tmpfs, yaitu filesystem berbasis memori pada Node, sehingga isinya tidak menempel pada disk Node.

Keterbatasan yang perlu diakui#

Secret memberikan perlindungan hanya sejauh lapisan di sekitarnya aktif. Data pada etcd, penyimpanan di belakang API server, secara default tidak dienkripsi saat tersimpan di disk, sehingga akses baca ke etcd berarti akses ke seluruh Secret. Enkripsi saat tersimpan (encryption at rest) hanya aktif apabila kluster menetapkan konfigurasi EncryptionConfiguration secara eksplisit.

Lapisan perlindungan yang lazim disusun bertingkat, dimulai dari RBAC yang membatasi siapa yang boleh membaca Secret, dilanjutkan dengan enkripsi saat tersimpan pada etcd, dan pilihan mounting berkas daripada variabel lingkungan karena nilai di lingkungan ikut terekspos pada inspeksi proses. Sistem pengelolaan rahasia eksternal dapat mengambil peran tersendiri apabila kebutuhannya melebihi kemampuan Secret. Perintah berikut memperlihatkan cara membuat Secret tanpa menyandikan nilai secara manual:

bash
# tidak-dijalankan
# buat Secret dari nilai literal, encoding dilakukan kubectl
kubectl create secret generic piket-token --from-literal=token=nilai-latihan

# ringkas keadaan Secret yang sudah dibuat
kubectl get secret piket-token
text Contoh keluaran
secret/piket-token created
NAME          TYPE     DATA   AGE
piket-token   Opaque   1      9s

Kesalahan umum#

  • Menulis nilai mentah langsung pada bidang data. API server menolak objeknya dengan error decoding base64, karena nilai tersebut tidak sah sebagai base64. Perbaikannya adalah menyandikan nilai lebih dahulu atau memindahkan penulisannya ke bidang stringData.
  • Mengisi Secret bertipe kubernetes.io/tls tanpa kunci tls.key. API server menolak pembuatannya karena tipe tersebut menuntut pasangan kunci yang lengkap. Perbaikannya adalah melengkapi tls.crt dan tls.key sesuai tuntutan tipenya.
  • Berbagi satu Secret untuk banyak aplikasi dengan izin baca yang luas. Kebocoran pada satu aplikasi berarti kebocoran nilai yang sama pada seluruh penggunanya. Perbaikannya adalah memecah Secret per aplikasi beserta ServiceAccount-nya, sehingga izin baca hanya diberikan kepada pihak yang membutuhkan.

Ringkasan#

  • Secret menyimpan data sensitif sebagai pasangan kunci dan nilai dengan encoding base64 pada bidang data, dan stringData menerima nilai mentah yang disandikan API server.
  • Encoding base64 dapat dibalik oleh siapa pun, sehingga Secret tanpa lapisan lain bukan perlindungan yang memadai.
  • Pod mengonsumsi Secret melalui secretKeyRef untuk variabel lingkungan dan volume bertipe secret untuk berkas, dengan pembaruan berkas oleh kubelet secara berkala.
  • Keamanan Secret bergantung pada RBAC, enkripsi etcd saat tersimpan, dan pengurangan permukaan akses, bukan pada base64-nya.

Referensi#

  • Secrets — Kubernetes — Dokumentasi konsep Secret, mencakup tipe, pola konsumsi Pod, dan risiko keamanannya.