Enject secrets đến pods sử dụng Vault agent

Lâu rồi không viết bài mới tại mình … lười quá. Nay viết bài này như là tài liệu note lại vì ông anh đồng nghiệp lâu năm cứ nhằn “sao mày ko share document cho taooo !!!” =)))

Bài viết sẽ chia sẽ cách để bạn enject secrets đến pods sử dụng vault agent.  Bài toán đặt ra là trong mô hình K8S của bạn cần một giải pháp lưu trữ secrets đáp ứng nhu cầu vừa bảo mật vừa hỗ trợ dev team một cách nhanh chóng khi họ cần update key-value một cách liên tục. Giải pháp mình sẽ sử dụng vault để tạo các căp kv (key-value) và sử dụng vault agent để nhúng vào pods mình cần sau đó thủ thuật chút để có thể biến các kv này như các biến trong env.

1.  Cài đặt Consul và Vault high-available mode

Consul là service mesh giải pháp lưu trữ kv như backend storage, còn Vault là một công cụ dùng để quản lý Secret, nó được phát triển bởi công ty Hashicorp. Vault phải có một trình quản lý storage backend để cấu hình và mã hoá khi vault chạy dưới mode HA. Bài này mình sẽ cài cả consul và vault bằng helm.

Tạo file `helm-consul-values.yml`:

global:
datacenter: vault-kubernetes-tutorial

client:
enabled: true

server:
replicas: 1
bootstrapExpect: 1
disruptionBudget:
maxUnavailable: 0

Nạp repo hashicorp trên helm:

helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
helm install consul hashicorp/consul --values helm-consul-values.yml

Tiếp tục tạo fiel helm-vault-values.yml:

server:
affinity: ""
ha:
enabled: true

install Vault:

helm install vault hashicorp/vault --values helm-vault-values.yml

Vault pod và Vault agent injector được deploy vào default namespace.

Các pods vault-{0,1,2} và vault-agent-injector đã được deploy. Ban đầu nó sẽ không ở trạng thái running như trong hình đâu bỏi vì readnessProbe.

kubectl exec vault-0 -- vault status
Key Value
--- -----
Seal Type shamir
Initialized false
Sealed true
Total Shares 0
Threshold 0
Unseal Progress 0/0
Unseal Nonce n/a
Version n/a
HA Enabled false
command terminated with exit code 2

đó là do Vault đang bị sealed. mình cần unseal, trước hết khởi tạo vault:

kubectl exec vault-0 -- vault operator init -key-shares=1 -key-threshold=1 -format=json > cluster-keys.json

VAULT_UNSEAL_KEY=$(cat cluster-keys.json | jq -r ".unseal_keys_b64[]")

kubectl exec vault-0 -- vault operator unseal $VAULT_UNSEAL_KEY
kubectl exec vault-1 -- vault operator unseal $VAULT_UNSEAL_KEY
kubectl exec vault-2 -- vault operator unseal $VAULT_UNSEAL_KEY

2. Vault agent injector là gì và hoạt động ra sao?

Vault agent injector là một controller được add vào như một sidecar và init container đến pods trong runtime.

Job của init container này sẽ xác thực bằng cách dùng service account của k8s và lấy secrets từ vault server đặt chúng trong shared location (In memory volume) để application có thể access đến được.

Vậy nó hoạt động thế nào?

Ví dụ, nếu một pod được triển khai với anotation như sau: “vault.hashicorp.com/agent-inject: ‘true'”, sau đây là những gì sẽ xảy ra.

Vì vậy, khi pod được khởi đầu, nó sẽ bao gồm container application, một sidecar và một init container.

Container khởi tạo có trách nhiệm lấy các secrets. Ngoài ra, một container sidecar là cần thiết nếu application của bạn sử dụng các dynamic secrets. dynamic secrets là các secrets được tạo ra theo yêu cầu với thời gian hết hạn. Container sidecar đảm bảo rằng các bí mật mới nhất có mặt bên trong pod sau mỗi lần renew secrets.

3. Tạo Vault secrets và Policy

Đầu tiên exec vào Vault-0

kubectl exec -it vault-0 -- /bin/sh

Enable vault kv engine (key-value store).

vault secrets enable -version=2 -path="kickir" kv

Tạo thử một secrets nào:

vault kv put kickir/staging/webcreds APPLICATION_ENV="staging"

Tạo policy cho kv này, lưu ý là version 2 thì bạn phải thêm /data vào nữa nhé, không lỗi sml ko biết đường debug đâu:

vault policy write kickir-staging - <<EOH
path "kickir/data/staging/webcreds" {
    capabilities = ["read"]
}
EOH

Enable Kubernetes authentication.

vault auth enable kubernetes
vault write auth/kubernetes/config \
    token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \ 
    kubernetes_host="https://YOUR_ADDR_CLUSTER_K8S:6443" \ 
    kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt

Tạo Vault role tên kickirstaging và mount đến service account vault-auth và namespace default:

vault write auth/kubernetes/role/kickirstaging \
   bound_service_account_names=vault-auth \
   bound_service_account_namespaces=default \
   policies=kickir-staging \
   ttl=72h

Bây giờ exit khỏi pod, quay trờ lại k8s cluster và tạo service account tương ứng:

kubectl create serviceaccount vault-auth

4. Injecting Secrets With Vault Agents

Mặc định Vault agent sẽ ghi secrets đến path /vault/secrets/, đây là pod volume shared in memory.

OK, giờ cùng mình tạo một file deployment tên deployment-env.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 1
  template:
    metadata:
      annotations:
        vault.hashicorp.com/agent-inject: 'true'
        vault.hashicorp.com/role: 'kickirstaging'
        vault.hashicorp.com/agent-pre-populate-only: 'true'
        vault.hashicorp.com/agent-inject-secret-database-config: 'kickir/staging/webcreds'
        vault.hashicorp.com/agent-inject-template-database-config: |
          {{- with secret "kickir/staging/webcreds" -}}{{- range $k, $v := .Data.data }}
          export {{ $k }}={{ $v }}
          {{- end }}{{- end -}}
      labels:
        app: nginx
    spec:
      serviceAccountName: vault-auth
      containers:
        - name: nginx
          image: nginx
          ports:
          - containerPort: 80
          command: ["/bin/bash"]
          args:
            - "-c"
            - >
              source /vault/secrets/database-config &&
              env >> ~/.bashrc &&
              source ~/.bashrc &&
              nginx -g 'daemon off;'
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  namespace: default
spec:
  selector:
    app: nginx
  type: ClusterIP
  sessionAffinity: None
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800
  ports:
  - name: nginx
    protocol: TCP
    port: 80

vault.hashicorp.com/agent-inject: ‘true’ – Enable sidecar và init container.
vault.hashicorp.com/role: ‘kickirstaging‘ : Gắn vào role kickirstaging đã tạo trước đó trên vault.
vault.vault.hashicorp.com/agent-inject-secret-database-config: ‘kickir/staging/webcreds’: gắn path vào path /vault/secrets/database-config

vault.hashicorp.com/agent-inject-template-database-config: với anotation này mình dùng loop để đọc ghi tất cả kv trên vault vào file, sau đó dùng Command trên manifest để nạp vào như biến trên env.

Apply deployment:

kubectl apply -f deployment-env.yaml

Mình có thêm một vài kv nữa, hãy cùng xem lại trong Vault-0:

Và đây là giá trị được ghi vào nginx deployment của mình:

Giờ thử dùng command echo print ra thử xem nó đã đc export như env hay không nha:

Vậy là success.

5. Phân quyền access trong Vault-UI

Bạn có thể dùng expose port forward hoặc gắn domain vào ingress để access vault-ui

Mình cần UI vì mình sẽ share quyền access cho dev team để họ tự managed kv của họ, như vậy quá trình phát triển phần mềm sẽ diễn ra liên tục không phụ thuộc bên DevOps hay bên vận hành nữa.

Đặng nhập bằng method token, root_token bạn đã lưu lại khi nãy

Trong menu bạn chọn access >> Authentication Methods >> Enabled new method:

Chọn username & password

Khi tạo xong, bạn vào method userpass >> create new user:

Tại đây bạn nhập username, password và Genarate token’s policies bạn điền tên dev vào (policy này mình sẽ tạo sau)

Save lại.

Quay lại main menu, chọn Policies >> Create ACL policies:

Bạn sẽ tạo dev policy tại đây:

path "kickir/*" {
capabilities = [ "list" ]
}

path "kickir/data/*" {
capabilities = [ "list" ]
}

path "kickir/data/staging/webcreds" {
capabilities = [ "create", "read", "update", "delete", "list" ]
}

Như vậy user này sẽ full quyền trên kv path kickir/data/staging/webcreds

Xong, logout ra và login bằng method user password thử xem:

kết quả ta đã access được kv engine này rồi:

Mình tạo thử các kv khác thử:

Save lại, restart lại deployment:

Done, như vậy mình đã hướng dẫn xong tuts này, cám ơn các bạn đã theo dõi. 🙂