Post

CI/CD 8์ฃผ์ฐจ ์ •๋ฆฌ

๐Ÿงฉ Vault ์„ค์น˜ on K8S

1. K8S(kind) ์„ค์น˜

(1) kind ํด๋Ÿฌ์Šคํ„ฐ ์ƒ์„ฑ

1
2
3
4
5
6
7
8
9
10
11
12
13
kind create cluster --name myk8s --image kindest/node:v1.32.8 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  labels:
    ingress-ready: true
  extraPortMappings:
  - containerPort: 30000  # Vault Web UI
    hostPort: 30000
  - containerPort: 30001  # Sample application
    hostPort: 30001
EOF

(2) kind ์„ค์น˜ ํ™•์ธ

1
2
3
4
docker ps

CONTAINER ID   IMAGE                  COMMAND                  CREATED          STATUS          PORTS                                                             NAMES
5fbe8f63e01f   kindest/node:v1.32.8   "/usr/local/bin/entrโ€ฆ"   47 seconds ago   Up 46 seconds   0.0.0.0:30000-30001->30000-30001/tcp, 127.0.0.1:40687->6443/tcp   myk8s-control-plane
  • docker ps๋กœ kind ๋…ธ๋“œ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์˜ฌ๋ผ์™”๋Š”์ง€ ํ™•์ธํ•จ
  • 30000~30001 ํฌํŠธ์™€ API Server ํฌํŠธ(6443)๊ฐ€ ์ •์ƒ ๋ฐ”์ธ๋”ฉ๋œ ์ƒํƒœ ํ™•์ธํ•จ

(3) kind ๋…ธ๋“œ์— ๊ธฐ๋ณธ ์œ ํ‹ธ ์„ค์น˜

1
docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'

2. Vault ์„ค์น˜

(1) Vault ์„ค์น˜ ์ค€๋น„

1
2
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
1
2
3
4
5
6
helm search repo hashicorp/vault

NAME                            	CHART VERSION	APP VERSION	DESCRIPTION                          
hashicorp/vault                 	0.31.0       	1.20.4     	Official HashiCorp Vault Chart       
hashicorp/vault-secrets-gateway 	0.0.2        	0.1.0      	A Helm chart for Kubernetes          
hashicorp/vault-secrets-operator	1.1.0        	1.1.0      	Official Vault Secrets Operator Chart
  • Vault Helm Chart ์„ค์น˜๋ฅผ ์œ„ํ•ด Hashicorp Helm repo๋ฅผ ๋“ฑ๋กํ•˜๊ณ  ์ตœ์‹ ํ™”ํ•จ
  • ์„ค์น˜ ๊ฐ€๋Šฅํ•œ ์ฐจํŠธ ๋ชฉ๋ก(vault / vault-secrets-operator ๋“ฑ) ํ™•์ธํ•จ

(2) Vault ์ „์šฉ Namespace ์ƒ์„ฑ

1
2
3
kubectl create namespace vault

namespace/vault created

(3) Vault Helm values ๊ตฌ์„ฑ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
cat <<EOF > vault-values.yaml
global:
  enabled: true
  tlsDisable: true

server:
  standalone:
    enabled: true
    config: |
      ui = true

      listener "tcp" {
        address = "[::]:8200"
        cluster_address = "[::]:8201"
        tls_disable = 1
      }

      storage "file" {
        path = "/vault/data"
      }

  dataStorage:
    enabled: true
    size: "10Gi"
    mountPath: "/vault/data"

  auditStorage:
    enabled: true
    size: "10Gi"
    mountPath: "/vault/logs"

  service:
    enabled: true
    type: NodePort
    nodePort: 30000
  
ui:
  enabled: true

injector:
  enabled: false
EOF
  • Standalone ๋ชจ๋“œ + file storage ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌ์„ฑํ•จ
  • UI ํ™œ์„ฑํ™”, TLS ๋น„ํ™œ์„ฑํ™”, Service๋Š” NodePort(30000)๋กœ ๋…ธ์ถœํ•จ
  • data/audit PVC ๊ฐ๊ฐ 10Gi๋กœ ์„ค์ •ํ•จ
  • injector๋Š” ๋น„ํ™œ์„ฑํ™”ํ•จ

(4) Vault Helm ์„ค์น˜ ์ˆ˜ํ–‰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
helm upgrade vault hashicorp/vault -n vault -f vault-values.yaml --install --dry-run=client
helm upgrade vault hashicorp/vault -n vault -f vault-values.yaml --install --version 0.31.0

# ๊ฒฐ๊ณผ
Release "vault" does not exist. Installing it now.
NAME: vault
LAST DEPLOYED: Sat Dec 13 16:34:45 2025
NAMESPACE: vault
STATUS: deployed
REVISION: 1
NOTES:
Thank you for installing HashiCorp Vault!

Now that you have deployed Vault, you should look over the docs on using
Vault with Kubernetes available here:

https://developer.hashicorp.com/vault/docs

Your release is named vault. To learn more about the release, try:

  $ helm status vault
  $ helm get manifest vault
  • -dry-run=client๋กœ ๋ Œ๋”๋ง/๊ฒ€์ฆ ํ›„ ์‹ค์ œ ์„ค์น˜ ์ˆ˜ํ–‰ํ•จ
  • ์ฐจํŠธ ๋ฒ„์ „์€ 0.31.0, ์•ฑ ๋ฒ„์ „์€ 1.20.4๋กœ ์„ค์น˜๋จ
  • ๋ฆด๋ฆฌ์ฆˆ ์ด๋ฆ„์€ vault, ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋Š” vault์ž„

(5) Vault ๋ฆฌ์†Œ์Šค ์ƒํƒœ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
kubectl get sts,pods,svc,ep,pvc,cm -n vault

NAME                     READY   AGE
statefulset.apps/vault   0/1     38s

NAME          READY   STATUS    RESTARTS   AGE
pod/vault-0   0/1     Running   0          38s

NAME                     TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                         AGE
service/vault            NodePort    10.96.2.205     <none>        8200:30000/TCP,8201:31431/TCP   38s
service/vault-internal   ClusterIP   None            <none>        8200/TCP,8201/TCP               38s
service/vault-ui         ClusterIP   10.96.112.199   <none>        8200/TCP                        38s

NAME                       ENDPOINTS                         AGE
endpoints/vault            10.244.0.7:8201,10.244.0.7:8200   38s
endpoints/vault-internal   10.244.0.7:8201,10.244.0.7:8200   38s
endpoints/vault-ui         10.244.0.7:8200                   38s

NAME                                  STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/audit-vault-0   Bound    pvc-32f2a699-e587-46dc-a883-34148df076d3   10Gi       RWO            standard       <unset>                 38s
persistentvolumeclaim/data-vault-0    Bound    pvc-e01ff826-3c05-41a6-9198-e658ea251659   10Gi       RWO            standard       <unset>                 38s

NAME                         DATA   AGE
configmap/kube-root-ca.crt   1      4m21s
configmap/vault-config       1      38s
  • StatefulSet vault๊ฐ€ 0/1 READY ์ƒํƒœ๋กœ ๋Œ€๊ธฐ ์ค‘์ž„
  • Pod vault-0๋Š” Running ์ด์ง€๋งŒ READY๊ฐ€ 0/1์ด๋ผ ํŠธ๋ž˜ํ”ฝ ์ฒ˜๋ฆฌ ๋ถˆ๊ฐ€ ์ƒํƒœ์ž„
  • PVC(data/audit)๋Š” ์ •์ƒ Bound ์ƒํƒœ์ž„
  • Service๋Š” NodePort 30000์œผ๋กœ ๋…ธ์ถœ๋จ

(6) Vault Running(Ready) ์ƒํƒœ๊ฐ€ ์•ˆ ์˜ฌ๋ผ์˜ค๋Š” ํ˜„์ƒ ํ™•์ธ

(6-1) Vault Sealed ์ƒํƒœ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kubectl exec -ti vault-0 -n vault -- 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            1.20.4
Build Date         2025-09-23T13:22:38Z
Storage Type       file
HA Enabled         false
command terminated with exit code 2
  • vault status ๊ฒฐ๊ณผ Initialized=false, Sealed=true ํ™•์ธ๋จ
  • ํ˜„์žฌ๋Š” ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์€ ์ƒํƒœ๋ผ unseal ํ‚ค/threshold๊ฐ€ 0์œผ๋กœ ํ‘œ์‹œ๋จ

(6-2) Vault ๋กœ๊ทธ๋กœ ์›์ธ ํ™•์ธ

1
2
3
4
5
6
kubectl stern -n vault -l app.kubernetes.io/name=vault

...
vault-0 vault 2025-12-13T07:40:13.087Z [INFO]  core: security barrier not initialized
vault-0 vault 2025-12-13T07:40:13.087Z [INFO]  core: seal configuration missing, not initialized
...
  • ๋กœ๊ทธ์— security barrier not initialized / seal configuration missing, not initialized ์ถœ๋ ฅ๋จ
  • ์ฆ‰ Vault๊ฐ€ ์•„์ง init ๋˜์ง€ ์•Š์€ ์ƒํƒœ๋ผ barrier๊ฐ€ ๋งŒ๋“ค์–ด์ง€์ง€ ์•Š์•˜๊ณ  sealed ์„ค์ •๋„ ์—†์–ด์„œ โ€œ๋ฏธ์ดˆ๊ธฐํ™” ์ƒํƒœโ€์ž„

(7) Vault ์ดˆ๊ธฐํ™” ๋ฐ Unseal ์ˆ˜ํ–‰

1
2
3
4
kubectl exec vault-0 -n vault -- vault operator init \
    -key-shares=1 \
    -key-threshold=1 \
    -format=json > cluster-keys.json
  • https://developer.hashicorp.com/vault/docs/concepts/seal
  • Vault๋Š” ์„ค์น˜ ์งํ›„ Initialized=false, Sealed=true ์ƒํƒœ์ด๋ฏ€๋กœ ๋จผ์ € init์œผ๋กœ ์ดˆ๊ธฐํ™”๊ฐ€ ํ•„์š”ํ•จ
  • ์‹ค์Šต ๋ชฉ์ ์ด๋ผ key-shares=1, key-threshold=1๋กœ ๋‹จ์ผ ํ‚ค๋กœ ๋ด‰์ธํ•ด์ œ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๊ตฌ์„ฑํ•จ
  • ์ดˆ๊ธฐํ™” ๊ฒฐ๊ณผ(unseal key, root token)๋Š” cluster-keys.json์— ์ €์žฅํ•ด ๊ด€๋ฆฌํ•จ

(8) cluster-keys.json ํ‚ค/ํ† ํฐ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cat cluster-keys.json| jq

{
  "unseal_keys_b64": [
    "oKgQsF1DjLDru9+v8bOPBgd5pcoJi9eHW6HwnWNe6Kc="
  ],
  "unseal_keys_hex": [
    "a0a810b05d438cb0ebbbdfaff1b38f060779a5ca098bd7875ba1f09d635ee8a7"
  ],
  "unseal_shares": 1,
  "unseal_threshold": 1,
  "recovery_keys_b64": [],
  "recovery_keys_hex": [],
  "recovery_keys_shares": 0,
  "recovery_keys_threshold": 0,
  "root_token": "hvs.xxxxxxxxxxxxxxxxxxxxxxxx"
}

(9) Unseal Key ์ถ”์ถœ

1
2
3
jq -r ".unseal_keys_b64[]" cluster-keys.json

oKgQsF1DjLDru9+v8bOPBgd5pcoJi9eHW6HwnWNe6Kc=
1
VAULT_UNSEAL_KEY=$(jq -r ".unseal_keys_b64[]" cluster-keys.json)
  • json์—์„œ unseal_keys_b64 ๊ฐ’์„ ์ถ”์ถœํ•ด ํ™•์ธํ•จ
  • ์ดํ›„ CLI ์ž‘์—… ํŽธ์˜๋ฅผ ์œ„ํ•ด ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ์ €์žฅํ•จ

(10) Vault Unseal ์‹คํ–‰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
kubectl exec vault-0 -n vault -- vault operator unseal $VAULT_UNSEAL_KEY

Key             Value
---             -----
Seal Type       shamir
Initialized     true
Sealed          false
Total Shares    1
Threshold       1
Version         1.20.4
Build Date      2025-09-23T13:22:38Z
Storage Type    file
Cluster Name    vault-cluster-b801d884
Cluster ID      f96ed384-1f8e-b1e5-27d3-2ed5364ebec3
HA Enabled      false
  • vault operator unseal๋กœ vault-0 ๋ด‰์ธ ํ•ด์ œ ์ˆ˜ํ–‰ํ•จ
  • ๊ฒฐ๊ณผ์—์„œ Initialized=true, Sealed=false๋กœ ๋ณ€๊ฒฝ๋œ ๊ฒƒ์„ ํ™•์ธํ•จ
  • ๋ด‰์ธ ํ•ด์ œ๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด readiness probe๋„ ํ†ต๊ณผํ•˜๋ฉด์„œ Pod READY๊ฐ€ 1/1๋กœ ์˜ฌ๋ผ๊ฐ

(11) vault-0 Readiness ์ •์ƒํ™” ํ™•์ธ

1
2
3
4
kubectl get pod -n vault

NAME      READY   STATUS    RESTARTS   AGE
vault-0   1/1     Running   0          10m
  • Unseal ์™„๋ฃŒ ํ›„ vault-0๊ฐ€ READY 1/1๋กœ ์ „ํ™˜๋˜์–ด ์ •์ƒ ์„œ๋น„์Šค ๊ฐ€๋Šฅ ์ƒํƒœ ํ™•์ธํ•จ

3. Vault login with CLI

(1) Root Token ํ™•์ธ

1
2
3
jq -r ".root_token" cluster-keys.json

hvs.xxxxxxxxxxxxxxxxxxxxxxxx

(2) Vault NodePort ์„œ๋น„์Šค ํ™•์ธ

1
2
3
4
5
6
kubectl get svc -n vault

NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                         AGE
vault            NodePort    10.96.2.205     <none>        8200:30000/TCP,8201:31431/TCP   13m
vault-internal   ClusterIP   None            <none>        8200/TCP,8201/TCP               13m
vault-ui         ClusterIP   10.96.112.199   <none>        8200/TCP                        13m
  • Helm values์—์„œ Vault ์„œ๋น„์Šค NodePort๋ฅผ 30000์œผ๋กœ ์ง€์ •ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ํฌํŠธ๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•จ
  • kubectl get svc๋กœ NodePort ๋งคํ•‘ ํ™•์ธํ•จ

(3) VAULT_ADDR ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์„ค์ •

1
export VAULT_ADDR='http://localhost:30000'
  • NodePort๋กœ ๊ณต๊ฐœํ•œ ์ฃผ์†Œ๋ฅผ Vault CLI๊ฐ€ ๋ฐ”๋ผ๋ณด๋„๋ก VAULT_ADDR๋ฅผ ์„ค์ •ํ•จ
  • kind ํ™˜๊ฒฝ์—์„œ host โ†’ node ์ปจํ…Œ์ด๋„ˆ ํฌํŠธ ๋งคํ•‘์„ ์ด๋ฏธ ๊ฑธ์–ด๋‘” ์ƒํƒœ๋ผ localhost:30000 ์‚ฌ์šฉํ•จ

(4) Vault ์ƒํƒœ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
vault status

Key             Value
---             -----
Seal Type       shamir
Initialized     true
Sealed          false
Total Shares    1
Threshold       1
Version         1.20.4
Build Date      2025-09-23T13:22:38Z
Storage Type    file
Cluster Name    vault-cluster-b801d884
Cluster ID      f96ed384-1f8e-b1e5-27d3-2ed5364ebec3
HA Enabled      false
  • vault status๋กœ Initialized/Sealed ์ƒํƒœ ์žฌํ™•์ธํ•จ
  • Sealed=false์ด๋ฉด ์ •์ƒ์ ์œผ๋กœ ์š”์ฒญ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅํ•œ ์ƒํƒœ์ž„

(5) Root Token์œผ๋กœ Vault ๋กœ๊ทธ์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
vault login

Token (will be hidden): hvs.xxxxxxxxxxxxxxxxxxxxxxxx
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                  Value
---                  -----
token                hvs.xxxxxxxxxxxxxxxxxxxxxxxx
token_accessor       CIZO8jV0MUxQ6AS4o7oaFxaz
token_duration       โˆž
token_renewable      false
token_policies       ["root"]
identity_policies    []
policies             ["root"]

4. Vault UI ์ ‘์† ํ™•์ธ

(1) Vault Service(NodePort) ํ™•์ธ

1
2
3
4
5
6
7
kubectl get svc,ep -n vault vault

NAME            TYPE       CLUSTER-IP    EXTERNAL-IP   PORT(S)                         AGE
service/vault   NodePort   10.96.2.205   <none>        8200:30000/TCP,8201:31431/TCP   16m

NAME              ENDPOINTS                         AGE
endpoints/vault   10.244.0.7:8201,10.244.0.7:8200   16m
  • NodePort(30000)๋กœ UI์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•จ

(2) Vault UI ์ ‘์†

1
2
3
# ๋กœ๊ทธ์ธ ๋ฐฉ์‹์€ Token ์„ ํƒ ํ›„ root token ์ž…๋ ฅํ•˜์—ฌ Sign in ์ˆ˜ํ–‰ํ•จ
http://127.0.0.1:30000
hvs.xxxxxxxxxxxxxxxxxxxxxxxx


5. Vault Audit Log

(1) Vault Audit Log ์„ค์ •

  • https://developer.hashicorp.com/vault/docs/audit/file
  • Vault Audit log๋Š” ์š”์ฒญ/์‘๋‹ต ์ด๋ ฅ์„ ๋‚จ๊ธฐ๋Š” ํ•ต์‹ฌ ๋ณด์•ˆ ๊ธฐ๋Šฅ์ด๋ฉฐ ์ผ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ๊ทธ์ฒ˜๋Ÿผ โ€œ์žˆ์œผ๋ฉด ์ข‹์€โ€ ์ˆ˜์ค€์ด ์•„๋‹ˆ๋ผ ์šด์˜ ํ•„์ˆ˜ ๊ตฌ์„ฑ์— ๊ฐ€๊นŒ์›€
  • Vault ๊ณต์‹ Best Practice๋กœ Audit device ์ตœ์†Œ 2๊ฐœ ์ด์ƒ ํ™œ์„ฑํ™”๋ฅผ ๊ถŒ์žฅํ•จ
  • ํŠนํžˆ Audit ๋กœ๊ทธ ์ €์žฅ์†Œ(PVC)๊ฐ€ ๊ฝ‰ ์ฐจ๋ฉด Audit๋งŒ ๋ฉˆ์ถ”๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ Vault ์ž์ฒด ๋™์ž‘(์š”์ฒญ ์ฒ˜๋ฆฌ)๊นŒ์ง€ ๋ง‰ํž ์ˆ˜ ์žˆ์Œ
    • ์ผ๋ฐ˜์ ์ธ ๋กœ๊ทธ์ฒ˜๋Ÿผ โ€œ๋กœ๊ทธ ์ ์žฌ ์‹คํŒจํ•ด๋„ ์„œ๋น„์Šค๋Š” ๊ณ„์†โ€์ด ์•„๋‹Œ ์ ์ด ํ•ต์‹ฌ์ž„

(2) Audit PVC ํ™•์ธ

1
2
3
4
5
kubectl get pvc -n vault

NAME            STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
audit-vault-0   Bound    pvc-32f2a699-e587-46dc-a883-34148df076d3   10Gi       RWO            standard       <unset>                 19m
data-vault-0    Bound    pvc-e01ff826-3c05-41a6-9198-e658ea251659   10Gi       RWO            standard       <unset>                 19m
  • Helm values์—์„œ auditStorage๋ฅผ /vault/logs๋กœ ๋งˆ์šดํŠธํ–ˆ์œผ๋ฏ€๋กœ
  • audit-vault-0 PVC๊ฐ€ Bound ์ƒํƒœ์ธ์ง€ ํ™•์ธํ•˜์—ฌ audit ๋กœ๊ทธ ์ €์žฅ ์ค€๋น„ ์ƒํƒœ ์ ๊ฒ€ํ•จ

(3) File Audit Device ํ™œ์„ฑํ™”

1
2
3
vault audit enable file file_path=/vault/logs/audit.log

Success! Enabled the file audit device at: file/
  • audit ๋กœ๊ทธ๊ฐ€ PVC(/vault/logs)์— ์ €์žฅ๋˜๋„๋ก file audit device๋ฅผ ํ™œ์„ฑํ™”ํ•จ
  • ๋กœ๊ทธ ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ /vault/logs/audit.log๋กœ ์ง€์ •ํ•จ

(4) Audit Device ์„ค์ • ํ™•์ธ

1
2
3
4
5
vault audit list -detailed

Path     Type    Description    Replication    Options
----     ----    -----------    -----------    -------
file/    file    n/a            replicated     file_path=/vault/logs/audit.log
  • vault audit list -detailed๋กœ audit device๊ฐ€ file/ ๊ฒฝ๋กœ๋กœ ๋“ฑ๋ก๋๊ณ 
  • ์˜ต์…˜์— file_path=/vault/logs/audit.log๊ฐ€ ๋ฐ˜์˜๋๋Š”์ง€ ํ™•์ธํ•จ

(5) Audit Log ๊ธฐ๋ก ํ™•์ธ

1
2
3
4
5
kubectl exec -it vault-0 -n vault -- tail -f /vault/logs/audit.log

{"request":{"id":"db434e4e-f27a-d785-806e-daa442e9f387","namespace":{"id":"root"},"operation":"update","path":"sys/audit/test"},"time":"2025-12-13T07:54:25.301826447Z","type":"request"}
{"auth":{"accessor":"hmac-sha256:b7c608b656889f5e07ece5058b7e83fe84f4a84a3964083c8b9eff285eaad4bb","client_token":"hmac-sha256:9965556e3ad1c2d7f1ce766e7dd46fe683589aeb200a0e7201ff2182a46c5921","display_name":"root","policies":["root"],"policy_results":{"allowed":true,"granting_policies":[{"type":""},{"name":"root","namespace_id":"root","type":"acl"}]},"token_policies":["root"],"token_issue_time":"2025-12-13T07:42:20Z","token_type":"service"},"request":{"client_id":"0DHqvq2D77kL2/JTPSZkTMJbkFVmUu0TzMi0jiXcFy8=","client_token":"hmac-sha256:9965556e3ad1c2d7f1ce766e7dd46fe683589aeb200a0e7201ff2182a46c5921","client_token_accessor":"hmac-sha256:b7c608b656889f5e07ece5058b7e83fe84f4a84a3964083c8b9eff285eaad4bb","data":{"description":"hmac-sha256:488b5f5045b033b4d6f441aa5aa3e25858a5e25cd8c2ed3b6d41a6cc9b9244e9","local":false,"options":{"file_path":"hmac-sha256:348c40c34f2fe938fadd92ee5dc73ac8ba6d129a224ffdb1ca777790c8a51a96"},"type":"hmac-sha256:4fb2a95daa8291f9d3d383332529a76d2c99a1a2437919413de53d721fa83736"},"headers":{"user-agent":["Go-http-client/1.1"]},"id":"26b1258d-5014-63b5-4925-5180f887ad7e","mount_accessor":"system_e1ed47ae","mount_class":"secret","mount_point":"sys/","mount_running_version":"v1.20.4+builtin.vault","mount_type":"system","namespace":{"id":"root"},"operation":"update","path":"sys/audit/file","remote_address":"10.244.0.1","remote_port":62597},"time":"2025-12-13T07:54:25.302849709Z","type":"response"}
...
  • Vault Pod ๋‚ด๋ถ€์—์„œ audit ๋กœ๊ทธ ํŒŒ์ผ์„ tail ํ•˜์—ฌ ์‹ค์ œ ์š”์ฒญ/์‘๋‹ต์ด JSON ํ˜•ํƒœ๋กœ ์ ์žฌ๋˜๋Š”์ง€ ํ™•์ธํ•จ
  • audit enable ๊ด€๋ จ request/response ๋กœ๊ทธ๊ฐ€ ์ฐํžˆ๋Š” ๊ฒƒ์„ ํ†ตํ•ด ์ •์ƒ ๋™์ž‘ ํ™•์ธํ•จ

๐Ÿ” Vault ์‚ฌ์šฉ on K8S

1. Set a secret in Vault

(1) KV(v2) Secrets Engine ํ™œ์„ฑํ™”

1
2
3
vault secrets enable -path=secret kv-v2

Success! Enabled the kv-v2 secrets engine at: secret/
  • Kubernetes์—์„œ Vault๋ฅผ โ€œ์‹œํฌ๋ฆฟ ์ €์žฅ์†Œโ€๋กœ ์“ฐ๊ธฐ ์œ„ํ•ด ๋จผ์ € KV(v2) ์—”์ง„์„ ํ™œ์„ฑํ™”ํ•จ
  • ๊ฒฝ๋กœ๋ฅผ secret/๋กœ ์ง€์ •ํ•˜์—ฌ ์ดํ›„ secret/* ํ•˜์œ„์— ์‹œํฌ๋ฆฟ์„ ์ €์žฅํ•  ์ˆ˜ ์žˆ๊ฒŒ ๊ตฌ์„ฑํ•จ

(2) Secrets Engine ๋“ฑ๋ก ํ™•์ธ

1
2
3
4
5
6
7
8
vault secrets list -detailed

Path          Plugin       Accessor              Default TTL    Max TTL    Force No Cache    Replication    Seal Wrap    External Entropy Access    Options           Description                                                UUID                                    Version    Running Version          Running SHA256    Deprecation Status
----          ------       --------              -----------    -------    --------------    -----------    ---------    -----------------------    -------           -----------                                                ----                                    -------    ---------------          --------------    ------------------
cubbyhole/    cubbyhole    cubbyhole_eee41fb3    n/a            n/a        false             local          false        false                      map[]             per-token private secret storage                           6ccef8fb-cfa9-6f45-f962-9edda42bdf3c    n/a        v1.20.4+builtin.vault    n/a               n/a
identity/     identity     identity_835b0a05     system         system     false             replicated     false        false                      map[]             identity store                                             606e4462-8f91-d87b-1b72-85899f087ad0    n/a        v1.20.4+builtin.vault    n/a               n/a
secret/       kv           kv_00b73c10           system         system     false             replicated     false        false                      map[version:2]    n/a                                                        3c66051d-2139-fde4-f47b-5e2b0dfc9c24    n/a        v0.24.0+builtin          n/a               supported
sys/          system       system_e1ed47ae       n/a            n/a        false             replicated     true         false                      map[]             system endpoints used for control, policy and debugging    045f51bd-bbd4-7b8c-38a4-da38b6957630    n/a        v1.20.4+builtin.vault    n/a               n/a
1
2
3
4
5
6
7
8
vault secrets list

Path          Type         Accessor              Description
----          ----         --------              -----------
cubbyhole/    cubbyhole    cubbyhole_eee41fb3    per-token private secret storage
identity/     identity     identity_835b0a05     identity store
secret/       kv           kv_00b73c10           n/a
sys/          system       system_e1ed47ae       system endpoints used for control, policy and debugging
  • vault secrets list -detailed๋กœ secret/์ด kv ์—”์ง„์ด๋ฉฐ version:2 ์˜ต์…˜์ด ์ ์šฉ๋œ ๊ฒƒ์„ ํ™•์ธํ•จ
  • ๊ฐ„๋‹จ ํ™•์ธ์€ vault secrets list๋กœ๋„ ๊ฐ€๋Šฅํ•จ

(3) ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œํฌ๋ฆฟ ์ €์žฅ

1
2
3
4
5
6
7
8
9
10
11
12
13
vault kv put secret/webapp/config username="static-user" password="static-password"

====== Secret Path ======
secret/data/webapp/config

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-13T08:02:50.147040285Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1
  • secret/webapp/config ๊ฒฝ๋กœ์— username, password ๊ฐ’์„ ์ €์žฅํ•จ
  • KV v2 ํŠน์„ฑ์ƒ ์‹ค์ œ ์ €์žฅ ๊ฒฝ๋กœ๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ secret/data/... ํ˜•ํƒœ๋กœ ๊ด€๋ฆฌ๋จ
  • ๊ฒฐ๊ณผ์—์„œ version=1๋กœ ์ฒซ ๋ฒˆ์งธ ๋ฒ„์ „ ์ €์žฅ์ด ํ™•์ธ๋จ

(4) CLI๋กœ ์‹œํฌ๋ฆฟ ์กฐํšŒ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
vault kv get secret/webapp/config

====== Secret Path ======
secret/data/webapp/config

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-13T08:02:50.147040285Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

====== Data ======
Key         Value
---         -----
password    static-password
username    static-user

(5) Vault HTTP API๋กœ ์‹œํฌ๋ฆฟ ์กฐํšŒ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
export VAULT_ROOT_TOKEN=hvs.xxxxxxxxxxxxxxxxxxxxxxxx

curl -s --header "X-Vault-Token: $VAULT_ROOT_TOKEN" --request GET \
  http://127.0.0.1:30000/v1/secret/data/webapp/config | jq
  
{
  "request_id": "516ab3ad-ddbc-15ac-5ce6-fd09fd6f234a",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": {
    "data": {
      "password": "static-password",
      "username": "static-user"
    },
    "metadata": {
      "created_time": "2025-12-13T08:02:50.147040285Z",
      "custom_metadata": null,
      "deletion_time": "",
      "destroyed": false,
      "version": 1
    }
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null,
  "mount_type": "kv"
}
  


2. Configure K8S Authentication in Vault

(1) Vault ServiceAccount RBAC ๊ถŒํ•œ ํ™•์ธ

1
2
3
4
5
kubectl rbac-tool lookup vault

  SUBJECT | SUBJECT TYPE   | SCOPE       | NAMESPACE | ROLE                  | BINDING               
----------+----------------+-------------+-----------+-----------------------+-----------------------
  vault   | ServiceAccount | ClusterRole |           | system:auth-delegator | vault-server-binding  
1
2
3
4
5
6
7
8
9
10
11
12
kubectl rolesum vault -n vault

# ๊ฒฐ๊ณผ
ServiceAccount: vault/vault
Secrets:

Policies:

โ€ข [CRB] */vault-server-binding โŸถ  [CR] */system:auth-delegator
  Resource                                   Name  Exclude  Verbs  G L W C U P D DC  
  subjectaccessreviews.authorization.k8s.io  [*]     [-]     [-]   โœ– โœ– โœ– โœ” โœ– โœ– โœ– โœ–   
  tokenreviews.authentication.k8s.io         [*]     [-]     [-]   โœ– โœ– โœ– โœ” โœ– โœ– โœ– โœ–   
  • Vault๊ฐ€ Kubernetes Auth๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด TokenReview, SubjectAccessReview ๊ถŒํ•œ์ด ํ•„์š”ํ•จ
  • vault ServiceAccount๊ฐ€ system:auth-delegator ClusterRole์— ๋ฐ”์ธ๋”ฉ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•จ

(2) Vault Kubernetes Auth ํ™œ์„ฑํ™”

1
2
3
vault auth enable kubernetes

Success! Enabled kubernetes auth method at: kubernetes/

1
2
3
4
5
6
vault auth list -detailed

Path           Plugin        Accessor                    Default TTL    Max TTL    Token Type         Replication    Seal Wrap    External Entropy Access    Options    Description                UUID                                    Version    Running Version          Running SHA256    Deprecation Status
----           ------        --------                    -----------    -------    ----------         -----------    ---------    -----------------------    -------    -----------                ----                                    -------    ---------------          --------------    ------------------
kubernetes/    kubernetes    auth_kubernetes_12d43069    system         system     default-service    replicated     false        false                      map[]      n/a                        caeaab04-f63d-82c7-f6f7-e17ad941e0a0    n/a        v0.22.2+builtin          n/a               supported
token/         token         auth_token_d2de5f64         system         system     default-service    replicated     false        false                      map[]      token based credentials    d9f460a1-42a8-00a5-15f7-b0276a4fa229    n/a        v1.20.4+builtin.vault    n/a               n/a
  • Vault์— Kubernetes ์ธ์ฆ ๋ฐฉ์‹(kubernetes/)์„ ํ™œ์„ฑํ™”ํ•จ
  • ํ™œ์„ฑํ™” ํ›„ UI/CLI์—์„œ kubernetes/ auth path๊ฐ€ ์ถ”๊ฐ€๋œ ๊ฒƒ์„ ํ™•์ธ ๊ฐ€๋Šฅํ•จ

(3) Kubernetes API ์„œ๋ฒ„ ์ •๋ณด ์„ค์ •

1
2
3
4
vault write auth/kubernetes/config \
    kubernetes_host="https://kubernetes.default.svc"

Success! Data written to: auth/kubernetes/config
  • Vault๊ฐ€ Kubernetes API ์„œ๋ฒ„์— ํ† ํฐ ๊ฒ€์ฆ(TokenReview) ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋„๋ก kubernetes_host๋ฅผ ์„ค์ •ํ•จ
  • Vault๊ฐ€ ํด๋Ÿฌ์Šคํ„ฐ ๋‚ด๋ถ€์— ์„ค์น˜๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ https://kubernetes.default.svc ์„œ๋น„์Šค DNS๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•จ

(4) Kubernetes Auth ์„ค์ •๊ฐ’ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
vault read auth/kubernetes/config

Key                                  Value
---                                  -----
disable_iss_validation               true
disable_local_ca_jwt                 false
issuer                               n/a
kubernetes_ca_cert                   n/a
kubernetes_host                      https://kubernetes.default.svc
pem_keys                             []
token_reviewer_jwt_set               false
use_annotations_as_alias_metadata    false
  • vault read๋กœ Kubernetes Auth ์„ค์ •์ด ์ •์ƒ ๋ฐ˜์˜๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•จ
  • ํ•ต์‹ฌ ํ™•์ธ ํฌ์ธํŠธ๋Š” kubernetes_host ๊ฐ’์ž„

(5) Vault Policy ์ƒ์„ฑ(webapp)

1
2
3
4
5
6
7
vault policy write webapp - <<EOF
path "secret/data/webapp/config" {
  capabilities = ["read"]
}
EOF

Success! Uploaded policy: webapp

  • secret/data/webapp/config ๊ฒฝ๋กœ์— ๋Œ€ํ•ด read ๊ถŒํ•œ๋งŒ ํ—ˆ์šฉํ•˜๋Š” webapp ์ •์ฑ…์„ ์ƒ์„ฑํ•จ
  • KV v2๋Š” ๊ฒฝ๋กœ๊ฐ€ secret/data/... ํ˜•ํƒœ์ธ ์ ์ด ์ค‘์š”ํ•จ

(6) Kubernetes Auth Role ์ƒ์„ฑ(webapp)

1
2
3
4
5
6
7
8
vault write auth/kubernetes/role/webapp \
        bound_service_account_names=vault \
        bound_service_account_namespaces=default \
        policies=webapp \
        ttl=24h \
        audience="https://kubernetes.default.svc.cluster.local"

Success! Data written to: auth/kubernetes/role/webapp

  • Kubernetes์˜ ํŠน์ • ServiceAccount๊ฐ€ ๋กœ๊ทธ์ธํ–ˆ์„ ๋•Œ ์–ด๋–ค Vault ์ •์ฑ…์„ ๋ฐ›์„์ง€ โ€œRoleโ€๋กœ ๋งคํ•‘ํ•จ
  • ์•„๋ž˜ ์„ค์ •์€ default ๋„ค์ž„์ŠคํŽ˜์ด์Šค์˜ vault ServiceAccount๊ฐ€ ๋กœ๊ทธ์ธํ•˜๋ฉด webapp policy๋ฅผ ๋ฐ›๋„๋ก ๊ตฌ์„ฑํ•œ ๊ฒƒ์ž„
  • ํ† ํฐ TTL์„ 24์‹œ๊ฐ„์œผ๋กœ ๋ถ€์—ฌํ•˜๊ณ , audience๋ฅผ Kubernetes ๊ธฐ๋ณธ audience๋กœ ์ง€์ •ํ•จ

3. Launch a web application

(1) WebApp ์‹ค์Šต์šฉ ServiceAccount ์ƒ์„ฑ

1
2
3
kubectl create sa vault

serviceaccount/vault created

(2) Web Application ๋ฐฐํฌ + NodePort ์„œ๋น„์Šค ๋…ธ์ถœ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
  labels:
    app: webapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      serviceAccountName: vault
      containers:
        - name: app
          image: hashieducation/simple-vault-client:latest
          imagePullPolicy: Always
          env:
            - name: VAULT_ADDR
              value: 'http://vault.vault.svc:8200'
            - name: JWT_PATH
              value: '/var/run/secrets/kubernetes.io/serviceaccount/token'
            - name: SERVICE_PORT
              value: '8080'
          volumeMounts:
          - name: sa-token
            mountPath: /var/run/secrets/kubernetes.io/serviceaccount
            readOnly: true
      volumes:
      - name: sa-token
        projected:
          sources:
          - serviceAccountToken:
              path: token
              expirationSeconds: 600 # 10๋ถ„ ๋งŒ๋ฃŒ , It defaults to 1 hour and must be at least 10 minutes (600 seconds)
---
apiVersion: v1
kind: Service
metadata:
  name: webapp
spec:
  selector:
    app: webapp
  type: NodePort
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
    nodePort: 30001
EOF

# ๊ฒฐ๊ณผ
deployment.apps/webapp created
service/webapp created
  • hashieducation/simple-vault-client ์ด๋ฏธ์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋Š” webapp ๋””ํ”Œ๋กœ์ด๋จผํŠธ๋ฅผ ๋ฐฐํฌํ•จ
  • serviceAccountName: vault๋กœ ์ง€์ •ํ•˜์—ฌ ํŒŒ๋“œ๊ฐ€ SA ํ† ํฐ(JWT)์„ ์‚ฌ์šฉํ•˜๋„๋ก ๊ตฌ์„ฑํ•จ
  • Vault ์ฃผ์†Œ๋Š” ํด๋Ÿฌ์Šคํ„ฐ ๋‚ด๋ถ€ DNS(vault.vault.svc:8200)๋กœ ์„ค์ •ํ•จ
  • SA ํ† ํฐ์„ Projected Volume์œผ๋กœ ๋งˆ์šดํŠธํ•˜๊ณ , expirationSeconds: 600์œผ๋กœ 10๋ถ„ ๋‹จ์œ„ ํ† ํฐ ๊ฐฑ์‹ ๋˜๋„๋ก ์„ค์ •ํ•จ
  • ์„œ๋น„์Šค๋Š” NodePort 30001๋กœ ๋…ธ์ถœํ•˜์—ฌ ๋กœ์ปฌ์—์„œ 127.0.0.1:30001๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๊ตฌ์„ฑํ•จ

(3) WebApp ํŒŒ๋“œ ๊ธฐ๋™ ํ™•์ธ

1
2
3
4
kubectl get pod -l app=webapp

NAME                     READY   STATUS    RESTARTS   AGE
webapp-9484c6fd7-vmkrd   1/1     Running   0          29s

(4) ์ปจํ…Œ์ด๋„ˆ ์ฝ”๋“œ ๊ตฌ์กฐ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kubectl exec -it deploy/webapp -- cat /app/main.go

# ๊ฒฐ๊ณผ
package main

import (
	"fmt"
	"log"
	"os"
	"time"
	"bytes"
	"net/http"
	"io/ioutil"
	"encoding/json"
)
..
1
2
3
4
5
6
7
8
9
10
kubectl exec -it deploy/webapp -- cat /app/types.go

# ๊ฒฐ๊ณผ
package main

type VaultJWTPayload struct {
	Role string `json:"role"`
	JWT  string `json:"jwt"`
}
...
  • ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€ /app/main.go, /app/types.go ํ™•์ธ
  • ํ† ํฐ ๋ฐœ๊ธ‰ ํ›„ ์‹œํฌ๋ฆฟ์„ ์กฐํšŒํ•ด HTTP ์‘๋‹ต์œผ๋กœ ์ถœ๋ ฅํ•˜๋Š” ๋กœ์ง์ž„์„ ํ™•์ธํ•จ

(5) ServiceAccount ํ† ํฐ(JWT) ํ™•์ธ

1
2
3
kubectl exec -it deploy/webapp -- cat /var/run/secrets/kubernetes.io/serviceaccount/token

eyJhbGciOiJSUzI1NiIsImtpZCI6IkZ6YmpXNEkzd1NZUGNtQzlWbXhaNHgtTGtvMk8xNGctVTVsZ1NqaXhLVzgifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzY1NjIwMjg4LCJpYXQiOjE3NjU2MTk2ODgsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiYWY5YWZlNTgtMTQ4MS00MDNlLTgzZGEtNTY4NmVkOWIzOGNiIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwibm9kZSI6eyJuYW1lIjoibXlrOHMtY29udHJvbC1wbGFuZSIsInVpZCI6IjdlNTM0NWY2LWVjYzEtNGY1Zi05NTI3LTc4NDhjYzM2YmQ5NSJ9LCJwb2QiOnsibmFtZSI6IndlYmFwcC05NDg0YzZmZDctdm1rcmQiLCJ1aWQiOiI0ODlhZTIyYi0yZjc2LTQxZDItYThiZS1lODg3NzcxN2NmOTUifSwic2VydmljZWFjY291bnQiOnsibmFtZSI6InZhdWx0IiwidWlkIjoiYjdlN2M5ZDEtNjYxYy00MjEyLWE1OGUtOTJjZDU3Y2EwYTBlIn19LCJuYmYiOjE3NjU2MTk2ODgsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OnZhdWx0In0.duPn8p9DZMr9TfsW5nO_HVIP8lH2KAhNd_vBn90ML2wKH0v2lEnkqEct-zJiTEc2vhDB6SCCyTKLJG2bWPGzIZ34ZRzjDRkAkcLXKoefdXVYaMaf_NloP74vcDqHnQnjG43bOiyUQAAkIIXn5J1v6gTnCMmCV5IpX7irLheyj99PjmpfqBXMg1QgsRQgfr1USvVmCbQAO2L0jOPhUbZ7syxO_IdBKqWEVRJvvC3Wuuz5HV0cmNs0-KKhGPnVXlbNrdR-yINw8Vi1VEN6sfVb6rgHY_1PW1f2aaXZYeRcxrJvZggOGhAQktaBBKJGD1gNHRHdKTQLCoHio5WbLvLQJw
1
2
3
kubectl exec -it deploy/webapp -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | cut -d '.' -f2 | base64 -d ; echo "\"}"

{"aud":["https://kubernetes.default.svc.cluster.local"],"exp":1765620288,"iat":1765619688,"iss":"https://kubernetes.default.svc.cluster.local","jti":"af9afe58-1481-403e-83da-5686ed9b38cb","kubernetes.io":{"namespace":"default","node":{"name":"myk8s-control-plane","uid":"7e5345f6-ecc1-4f5f-9527-7848cc36bd95"},"pod":{"name":"webapp-9484c6fd7-vmkrd","uid":"489ae22b-2f76-41d2-a8be-e8877717cf95"},"serviceaccount":{"name":"vault","uid":"b7e7c9d1-661c-4212-a58e-92cd57ca0a0e"}},"nbf":1765619688,"sub":"system:serviceaccount:default:vault"}"}
  • JWT_PATH๋กœ ์ง€์ •ํ•œ ๊ฒฝ๋กœ์— SA ํ† ํฐ์ด ๋งˆ์šดํŠธ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•จ
  • Projected ServiceAccountToken ์„ค์ •(expirationSeconds: 600)์œผ๋กœ ํ† ํฐ์ด ์ฃผ๊ธฐ์ ์œผ๋กœ ๊ฐฑ์‹ ๋˜๋Š” ํ˜•ํƒœ์ž„
  • ํ† ํฐ payload๋ฅผ base64 decode ํ•˜์—ฌ aud, iss, sub(system:serviceaccount:default:vault) ๋“ฑ ํด๋ ˆ์ž„์„ ํ™•์ธํ•จ

(6) WebApp ์„œ๋น„์Šค(NodePort) ํ™•์ธ

1
2
3
4
5
kubectl get svc

NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1     <none>        443/TCP        150m
webapp       NodePort    10.96.62.78   <none>        80:30001/TCP   3m29s
  • webapp ์„œ๋น„์Šค๊ฐ€ NodePort 30001๋กœ ๋…ธ์ถœ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•จ
  • kind์—์„œ extraPortMappings๋กœ 30001์„ host์— ๋ฐ”์ธ๋”ฉ ํ•ด๋‘” ์ƒํƒœ๋ผ ๋กœ์ปฌ์—์„œ ๋ฐ”๋กœ ํ˜ธ์ถœ ๊ฐ€๋Šฅํ•จ

(7) WebApp ๋™์ž‘ ํ™•์ธ

1
2
3
curl 127.0.0.1:30001

password:static-password username:static-user
  • ์›น ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด webapp์ด
    • SA JWT๋ฅผ ์ฝ๊ณ 
    • Vault Kubernetes Auth๋กœ ๋กœ๊ทธ์ธํ•˜์—ฌ Vault ํ† ํฐ์„ ๋ฐœ๊ธ‰๋ฐ›๊ณ 
    • secret/webapp/config ์‹œํฌ๋ฆฟ์„ ์กฐํšŒํ•œ ๊ฒฐ๊ณผ๋ฅผ HTTP๋กœ ์ถœ๋ ฅํ•จ
  • curl ๊ฒฐ๊ณผ๋กœ username/password๊ฐ€ ๋ฐ˜ํ™˜๋˜๋ฉด end-to-end ํ๋ฆ„์ด ์ •์ƒ์ž„

(8) WebApp ๋กœ๊ทธ๋กœ Vault ๋กœ๊ทธ์ธ/ํ† ํฐ ๋ฐœ๊ธ‰ ํ™•์ธ

1
2
3
4
5
6
7
kubectl logs -l app=webapp -f

# ๊ฒฐ๊ณผ
2025/12/13 09:55:15 Listening on port 8080
2025/12/13 09:58:34 Received Request - Port forwarding is working.
Read JWT: eyJhbGciOiJSUzI1NiIsImtpZCI6IkZ6YmpXNEkzd1NZUGNtQzlWbXhaNHgtTGtvMk8xNGctVTVsZ1NqaXhLVzgifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzY1NjIwMjg4LCJpYXQiOjE3NjU2MTk2ODgsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiYWY5YWZlNTgtMTQ4MS00MDNlLTgzZGEtNTY4NmVkOWIzOGNiIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwibm9kZSI6eyJuYW1lIjoibXlrOHMtY29udHJvbC1wbGFuZSIsInVpZCI6IjdlNTM0NWY2LWVjYzEtNGY1Zi05NTI3LTc4NDhjYzM2YmQ5NSJ9LCJwb2QiOnsibmFtZSI6IndlYmFwcC05NDg0YzZmZDctdm1rcmQiLCJ1aWQiOiI0ODlhZTIyYi0yZjc2LTQxZDItYThiZS1lODg3NzcxN2NmOTUifSwic2VydmljZWFjY291bnQiOnsibmFtZSI6InZhdWx0IiwidWlkIjoiYjdlN2M5ZDEtNjYxYy00MjEyLWE1OGUtOTJjZDU3Y2EwYTBlIn19LCJuYmYiOjE3NjU2MTk2ODgsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OnZhdWx0In0.duPn8p9DZMr9TfsW5nO_HVIP8lH2KAhNd_vBn90ML2wKH0v2lEnkqEct-zJiTEc2vhDB6SCCyTKLJG2bWPGzIZ34ZRzjDRkAkcLXKoefdXVYaMaf_NloP74vcDqHnQnjG43bOiyUQAAkIIXn5J1v6gTnCMmCV5IpX7irLheyj99PjmpfqBXMg1QgsRQgfr1USvVmCbQAO2L0jOPhUbZ7syxO_IdBKqWEVRJvvC3Wuuz5HV0cmNs0-KKhGPnVXlbNrdR-yINw8Vi1VEN6sfVb6rgHY_1PW1f2aaXZYeRcxrJvZggOGhAQktaBBKJGD1gNHRHdKTQLCoHio5WbLvLQJw
Retrieved token:  hvs.xxxxxxxxxxxxxxxxxxxxxxxx
  • ๋กœ๊ทธ์—์„œ Read JWT๋กœ SA ํ† ํฐ์„ ์ฝ๋Š” ๊ณผ์ • ํ™•์ธ ๊ฐ€๋Šฅํ•จ
  • ์ด์–ด์„œ Retrieved token์œผ๋กœ Vault์—์„œ ๋ฐœ๊ธ‰๋œ ํ† ํฐ(hvs.*)์„ ๋ฐ›์•„์˜ค๋Š” ๊ณผ์ • ํ™•์ธ ๊ฐ€๋Šฅํ•จ
  • ์ฆ‰ โ€œKubernetes SA โ†’ Vault Kubernetes Auth โ†’ Vault Token โ†’ Secret Readโ€ ํ๋ฆ„์ด ์‹ค์ œ๋กœ ์ˆ˜ํ–‰๋จ

(9) ์„œ๋น„์Šค ์–ด์นด์šดํŠธ ํ† ํฐ(JWT) ํ™•์ธ - https://www.jwt.io/


4. Vault Secret ์—…๋ฐ์ดํŠธ ๋ฐ ๋ฐ˜์˜ ํ™•์ธ

(1) Vault Secret ์—…๋ฐ์ดํŠธ

1
2
3
4
5
6
7
8
9
10
11
12
13
vault kv put secret/webapp/config username="changed-user" password="changed-password"

====== Secret Path ======
secret/data/webapp/config

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-13T10:00:11.037967412Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            2

(2) ์—…๋ฐ์ดํŠธ๋œ Secret ์กฐํšŒ ๊ฒ€์ฆ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
vault kv get secret/webapp/config

====== Secret Path ======
secret/data/webapp/config

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-13T10:00:11.037967412Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            2

====== Data ======
Key         Value
---         -----
password    changed-password
username    changed-user
  • ์ถœ๋ ฅ ๊ฒฐ๊ณผ์—์„œ username/password๊ฐ€ ๋ณ€๊ฒฝ๋œ ๊ฐ’์œผ๋กœ ํ‘œ์‹œ๋˜๋Š”์ง€ ํ™•์ธํ•จ

(3) WebApp์—์„œ ๋ณ€๊ฒฝ ๋ฐ˜์˜ ํ™•์ธ

1
2
3
curl 127.0.0.1:30001

password:changed-password username:changed-user
  • webapp(NodePort:30001)์ด Vault์—์„œ ์‹œํฌ๋ฆฟ์„ ๋‹ค์‹œ ์กฐํšŒํ•ด HTTP ์‘๋‹ต์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š”์ง€ ํ™•์ธํ•จ
  • curl ๊ฒฐ๊ณผ๊ฐ€ ๋ณ€๊ฒฝ๋œ ๊ฐ’์œผ๋กœ ๋‚˜์˜ค๋ฉด end-to-end๋กœ โ€œVault ๋ณ€๊ฒฝ โ†’ ์•ฑ ๋ฐ˜์˜โ€์ด ์ •์ƒ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์ž„

(4) ์‹ค์Šต ํ™˜๊ฒฝ ์ •๋ฆฌ

1
2
3
4
kind delete cluster --name myk8s

Deleting cluster "myk8s" ...
Deleted nodes: ["myk8s-control-plane"]

โš™๏ธ Vault Secrets Operaor(VSO)

  • https://developer.hashicorp.com/vault/tutorials/kubernetes-introduction/vault-secrets-operator
  • VSO(Vault Secrets Operator)๋Š” Vault์— ์žˆ๋Š” Secret/Dynamic Credential์„ ๊ฐ€์ ธ์™€ Kubernetes Native Secret์œผ๋กœ ์ž๋™ ๋ฐ˜์˜/๋™๊ธฐํ™”ํ•˜๋Š” Operator์ž„
  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(๊ฐœ๋ฐœ์ž)์€ Vault CLI/API๋ฅผ ์ง์ ‘ ๋‹ค๋ฃฐ ํ•„์š” ์—†์ด Kubernetes Secret๋งŒ ์†Œ๋น„ํ•˜๋ฉด ๋˜๋Š” ๊ตฌ์กฐ๊ฐ€ ๋จ

1. K8S(kind) ์„ค์น˜

(1) kind ํด๋Ÿฌ์Šคํ„ฐ ์ƒ์„ฑ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
kind create cluster --name myk8s --image kindest/node:v1.32.8 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  labels:
    ingress-ready: true
  extraPortMappings:
  - containerPort: 80
    hostPort: 80
    protocol: TCP
  - containerPort: 443
    hostPort: 443
    protocol: TCP
  - containerPort: 30000  # Vault Web UI
    hostPort: 30000
  - containerPort: 30001  # Sample application
    hostPort: 30001
EOF

(2) kind ๋…ธ๋“œ ๊ธฐ๋ณธ ์œ ํ‹ธ ์„ค์น˜

1
docker exec -it myk8s-control-plane sh -c 'apt update && apt install tree psmisc lsof wget net-tools dnsutils tcpdump ngrep iputils-ping git vim -y'

2. Vault ์„ค์น˜: dev ๋ชจ๋“œ ํ™œ์„ฑํ™”

(1) Vault/VSO ๋ฒ„์ „ ์ „๋žต ํ™•์ธ

1
2
3
4
5
6
helm search repo hashicorp/vault

NAME                            	CHART VERSION	APP VERSION	DESCRIPTION                          
hashicorp/vault                 	0.31.0       	1.20.4     	Official HashiCorp Vault Chart       
hashicorp/vault-secrets-gateway 	0.0.2        	0.1.0      	A Helm chart for Kubernetes          
hashicorp/vault-secrets-operator	1.1.0        	1.1.0      	Official Vault Secrets Operator Chart
  • ์‹ค์Šต์€ ๋‚ฎ์€ ๋ฒ„์ „ ์กฐํ•ฉ์œผ๋กœ ์ง„ํ–‰ํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ•จ
1
2
3
NAME                                    CHART VERSION   APP VERSION     DESCRIPTION
hashicorp/vault                         0.28.1          1.17.2          Official HashiCorp Vault Chart
hashicorp/vault-secrets-operator        0.7.1           0.8.0           Official Vault Secrets Operator Chart

(2) VSO ์‹ค์Šต ๋ ˆํฌ ํด๋ก 

1
2
git clone https://github.com/hashicorp-education/learn-vault-secrets-operator
cd learn-vault-secrets-operator

(3) Vault(dev ๋ชจ๋“œ) ์„ค์ • ํŒŒ์ผ ์ž‘์„ฑ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
cat <<EOF > vault-values.yaml
server:
  image:
    repository: "hashicorp/vault"
    tag: "1.19.0"

  dev:
    enabled: true
    devRootToken: "root"

  logLevel: debug

  service:
    enabled: true
    type: ClusterIP
    port: 8200
    targetPort: 8200

ui:
  enabled: true
  serviceType: "NodePort"
  externalPort: 8200
  serviceNodePort: 30000

injector:
  enabled: "false"
EOF
  • ์‹ค์Šต ํŽธ์˜๋ฅผ ์œ„ํ•ด server.dev.enabled=true๋กœ dev ๋ชจ๋“œ ์‚ฌ์šฉํ•จ
  • dev ๋ชจ๋“œ๋Š” init/unseal ์—†์ด ์ฆ‰์‹œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋ฉฐ, root token์„ root๋กœ ๊ณ ์ •ํ•จ
  • UI๋Š” NodePort 30000์œผ๋กœ ๋…ธ์ถœํ•จ

(4) Vault ์„ค์น˜(Helm)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
helm install vault hashicorp/vault -n vault --create-namespace --values vault-values.yaml --version 0.30.0

# ๊ฒฐ๊ณผ
NAME: vault
LAST DEPLOYED: Sat Dec 13 19:28:19 2025
NAMESPACE: vault
STATUS: deployed
REVISION: 1
NOTES:
Thank you for installing HashiCorp Vault!

Now that you have deployed Vault, you should look over the docs on using
Vault with Kubernetes available here:

https://developer.hashicorp.com/vault/docs

Your release is named vault. To learn more about the release, try:

  $ helm status vault
  $ helm get manifest vault

(5) Vault ํŒŒ๋“œ ๊ธฐ๋™ ํ™•์ธ

1
2
3
4
kubectl get pods -n vault

NAME      READY   STATUS    RESTARTS   AGE
vault-0   1/1     Running   0          26s

3. Vault ์„ค์ •

(1) Vault ์ ‘์† ์ฃผ์†Œ ์„ค์ • ๋ฐ ๋กœ๊ทธ์ธ

1
export VAULT_ADDR='http://localhost:30000'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
vault login

Token (will be hidden): root
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                  Value
---                  -----
token                root
token_accessor       WZPwOa6gGPh0CIpsmtYF2u5c
token_duration       โˆž
token_renewable      false
token_policies       ["root"]
identity_policies    []
policies             ["root"]
  • UI/CLI ์ ‘๊ทผ์„ ์œ„ํ•ด NodePort(30000)๋ฅผ VAULT_ADDR๋กœ ์ง€์ •ํ•จ
  • devRootToken์œผ๋กœ ์„ค์ •ํ•œ root ํ† ํฐ์œผ๋กœ ๋กœ๊ทธ์ธํ•จ

(2) Kubernetes Auth ํ™œ์„ฑํ™”

1
2
3
vault auth enable -path demo-auth-mount kubernetes

Success! Enabled kubernetes auth method at: demo-auth-mount/
1
2
3
vault write auth/demo-auth-mount/config kubernetes_host="https://kubernetes.default.svc"

Success! Data written to: auth/demo-auth-mount/config
  • Kubernetes Auth๋ฅผ ๊ธฐ๋ณธ path๊ฐ€ ์•„๋‹Œ demo-auth-mount/๋กœ ํ™œ์„ฑํ™”ํ•จ
  • Vault๊ฐ€ TokenReview ๋“ฑ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก Kubernetes API ์„œ๋ฒ„ ์ฃผ์†Œ๋ฅผ ์„ค์ •ํ•จ

(3) KV v2 Secrets Engine ํ™œ์„ฑํ™”

1
2
3
vault secrets enable -path=kvv2 kv-v2

Success! Enabled the kv-v2 secrets engine at: kvv2/
  • KV v2 ์—”์ง„์„ kvv2/ ๊ฒฝ๋กœ๋กœ ํ™œ์„ฑํ™”ํ•˜์—ฌ ์‹œํฌ๋ฆฟ์„ ์ €์žฅํ•  ์ค€๋น„๋ฅผ ํ•จ

(4) Vault Policy ์ƒ์„ฑ(webapp)

1
2
3
4
5
tee webapp.json <<EOF
path "kvv2/data/webapp/config" {
   capabilities = ["read", "list"]
}
EOF
1
2
3
vault policy write webapp webapp.json

Success! Uploaded policy: webapp
  • kvv2/data/webapp/config ๊ฒฝ๋กœ์— ๋Œ€ํ•ด read, list ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜๋Š” ์ •์ฑ…์„ ์ž‘์„ฑํ•จ
  • ํŒŒ์ผ ๊ธฐ๋ฐ˜ ์ •์ฑ…(webapp.json)์œผ๋กœ ๊ด€๋ฆฌ ํ›„ Vault์— ์—…๋กœ๋“œํ•จ

(5) Kubernetes SA โ†” Vault Role ๋งคํ•‘(role1)

1
2
3
4
5
6
7
8
vault write auth/demo-auth-mount/role/role1 \
   bound_service_account_names=demo-static-app \
   bound_service_account_namespaces=app \
   policies=webapp \
   audience=vault \
   ttl=24h
   
Success! Data written to: auth/demo-auth-mount/role/role1
  • app ๋„ค์ž„์ŠคํŽ˜์ด์Šค์˜ demo-static-app ServiceAccount๊ฐ€ ๋กœ๊ทธ์ธํ•˜๋ฉด webapp ์ •์ฑ…์„ ๋ฐ›๋„๋ก role์„ ์ƒ์„ฑํ•จ
  • audience๋Š” vault, TTL์€ 24h๋กœ ์„ค์ •ํ•จ

(6) Vault์— ์‹œํฌ๋ฆฟ ์ €์žฅ

1
2
3
4
5
6
7
8
9
10
11
12
13
vault kv put kvv2/webapp/config username="static-user" password="static-password"

===== Secret Path =====
kvv2/data/webapp/config

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-13T10:33:17.086953754Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1
  • kvv2/webapp/config ๊ฒฝ๋กœ์— username/password๋ฅผ ์ €์žฅํ•จ
  • KV v2 ํŠน์„ฑ์ƒ ์‹ค์ œ ๋ฐ์ดํ„ฐ ๊ฒฝ๋กœ๋Š” kvv2/data/webapp/config๋กœ ๊ด€๋ฆฌ๋˜๋ฉฐ version์ด ์ฆ๊ฐ€ํ•จ

4. Vault Secrets Operator(VSO) ์„ค์น˜

(1) VSO Values ํŒŒ์ผ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cat vault/vault-operator-values.yaml

defaultVaultConnection:
  enabled: true
  address: "http://vault.vault.svc.cluster.local:8200"
  skipTLSVerify: false
controller:
  manager:
    clientCache:
      persistenceModel: direct-encrypted
      storageEncryption:
        enabled: true
        mount: demo-auth-mount
        keyName: vso-client-cache
        transitMount: demo-transit
        kubernetes:
          role: auth-role-operator
          serviceAccount: vault-secrets-operator-controller-manager
          tokenAudiences: ["vault"]
  • VSO ๊ธฐ๋ณธ Vault ์—ฐ๊ฒฐ ์ •๋ณด(defaultVaultConnection) ํ™•์ธํ•จ
  • Client cache ์˜์†ํ™” ๋ชจ๋ธ์„ direct-encrypted๋กœ ์‚ฌ์šฉํ•˜๋ฉฐ, Vault Transit ๊ธฐ๋ฐ˜ ์•”ํ˜ธํ™” ์„ค์ • ํฌํ•จ๋จ
  • Kubernetes Auth๋Š” demo-auth-mount ์‚ฌ์šฉ, Role์€ auth-role-operator, audience๋Š” vault ์‚ฌ์šฉ ๊ตฌ์„ฑ์ž„

(2) Vault Secrets Operator ์„ค์น˜

1
2
3
4
5
6
7
helm install vault-secrets-operator hashicorp/vault-secrets-operator -n vault-secrets-operator-system --create-namespace --values vault/vault-operator-values.yaml --version 0.7.1

NAME: vault-secrets-operator
LAST DEPLOYED: Sat Dec 13 19:38:01 2025
NAMESPACE: vault-secrets-operator-system
STATUS: deployed
REVISION: 1

(3) Helm ๋ฆด๋ฆฌ์ฆˆ ์„ค์น˜ ํ˜„ํ™ฉ ํ™•์ธ

1
2
3
4
5
helm list -A

NAME                  	NAMESPACE                    	REVISION	UPDATED                                	STATUS  	CHART                       	APP VERSION
vault                 	vault                        	1       	2025-12-13 19:28:19.64613302 +0900 KST 	deployed	vault-0.30.0                	1.19.0     
vault-secrets-operator	vault-secrets-operator-system	1       	2025-12-13 19:38:01.785715036 +0900 KST	deployed	vault-secrets-operator-0.7.1	0.7.1      
  • Vault์™€ VSO๊ฐ€ ๊ฐ๊ฐ ๋ณ„๋„ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋กœ ์ •์ƒ ๋ฐฐํฌ ์ƒํƒœ์ธ์ง€ ํ™•์ธํ•จ
  • Vault chart 0.30.0, VSO chart 0.7.1 ์„ค์น˜ ์ƒํƒœ ํ™•์ธํ•จ

(4) VSO CRD ์ƒ์„ฑ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
kubectl get crd | grep secrets.hashicorp.com

hcpauths.secrets.hashicorp.com                2025-12-13T10:38:01Z
hcpvaultsecretsapps.secrets.hashicorp.com     2025-12-13T10:38:01Z
secrettransformations.secrets.hashicorp.com   2025-12-13T10:38:01Z
vaultauths.secrets.hashicorp.com              2025-12-13T10:38:01Z
vaultconnections.secrets.hashicorp.com        2025-12-13T10:38:01Z
vaultdynamicsecrets.secrets.hashicorp.com     2025-12-13T10:38:01Z
vaultpkisecrets.secrets.hashicorp.com         2025-12-13T10:38:01Z
vaultstaticsecrets.secrets.hashicorp.com      2025-12-13T10:38:01Z

(5) VSO Pod ์ปจํ…Œ์ด๋„ˆ ๊ตฌ์„ฑ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
kubectl describe pod -n vault-secrets-operator-system

# ๊ฒฐ๊ณผ
Containers:
  kube-rbac-proxy:
    Container ID:  containerd://695d3200bbdb03da69d7e20d3b6cd271d3c544e2689a0e109baee54bcfd6d353
    Image:         gcr.io/kubebuilder/kube-rbac-proxy:v0.15.0
  ...
  manager:
    Container ID:  containerd://d45ee54bb67616f9f4da810b5df87148ad3f0ed7521c152ebbf152568f346bd6
    Image:         hashicorp/vault-secrets-operator:0.7.1
...    
  • VSO Pod๋Š” ๋ณดํ†ต manager + kube-rbac-proxy 2๊ฐœ ์ปจํ…Œ์ด๋„ˆ๋กœ ๊ตฌ์„ฑ๋จ
  • manager ์ด๋ฏธ์ง€๊ฐ€ hashicorp/vault-secrets-operator:0.7.1์ธ์ง€ ํ™•์ธํ•จ

(6) ๊ธฐ๋ณธ VaultConnection/VaultAuth CR ์ƒ์„ฑ ํ™•์ธ

1
2
3
4
5
6
7
kubectl get vaultconnections,vaultauths -n vault-secrets-operator-system

NAME                                            AGE
vaultconnection.secrets.hashicorp.com/default   4m29s

NAME                                                                          AGE
vaultauth.secrets.hashicorp.com/vault-secrets-operator-default-transit-auth   4m29s

(7) VaultAuth CR ์ŠคํŽ™ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
kubectl get vaultauth -n vault-secrets-operator-system vault-secrets-operator-default-transit-auth -o jsonpath='{.spec}' | jq

{
  "kubernetes": {
    "audiences": [
      "vault"
    ],
    "role": "auth-role-operator",
    "serviceAccount": "vault-secrets-operator-controller-manager",
    "tokenExpirationSeconds": 600
  },
  "method": "kubernetes",
  "mount": "demo-auth-mount",
  "storageEncryption": {
    "keyName": "vso-client-cache",
    "mount": "demo-transit"
  },
  "vaultConnectionRef": "default"
}
  • ์ธ์ฆ ๋ฐฉ์‹์ด kubernetes์ธ์ง€ ํ™•์ธํ•จ
  • mount๊ฐ€ demo-auth-mount์ธ์ง€ ํ™•์ธํ•จ
  • VSO๊ฐ€ ์‚ฌ์šฉํ•  Kubernetes Role/SA/audience/ํ† ํฐ ๋งŒ๋ฃŒ(600s) ํ™•์ธํ•จ
  • client cache ์•”ํ˜ธํ™” ์„ค์ •์ด transit ๊ธฐ๋ฐ˜์ธ์ง€ ํ™•์ธํ•จ

(8) VaultConnection CR ์ŠคํŽ™ ํ™•์ธ

1
2
3
4
5
6
kubectl get vaultconnection -n vault-secrets-operator-system default -o jsonpath='{.spec}' | jq

{
  "address": "http://vault.vault.svc.cluster.local:8200",
  "skipTLSVerify": false
}
  • VSO๊ฐ€ ์ ‘๊ทผํ•  Vault ์ฃผ์†Œ๊ฐ€ ์„œ๋น„์Šค DNS ๊ธฐ๋ฐ˜์œผ๋กœ ์„ค์ •๋˜์–ด ์žˆ์Œ ํ™•์ธํ•จ
  • TLS ๊ฒ€์ฆ ์Šคํ‚ต ์—ฌ๋ถ€(skipTLSVerify) ํ™•์ธํ•จ

(9) VSO ์ปจํŠธ๋กค๋Ÿฌ ServiceAccount RBAC ํ™•์ธ

1
2
3
4
5
6
7
kubectl rbac-tool lookup vault-secrets-operator-controller-manager

  SUBJECT                                   | SUBJECT TYPE   | SCOPE       | NAMESPACE                     | ROLE                                        | BINDING                                             
--------------------------------------------+----------------+-------------+-------------------------------+---------------------------------------------+-----------------------------------------------------
  vault-secrets-operator-controller-manager | ServiceAccount | ClusterRole |                               | vault-secrets-operator-proxy-role           | vault-secrets-operator-proxy-rolebinding            
  vault-secrets-operator-controller-manager | ServiceAccount | ClusterRole |                               | vault-secrets-operator-manager-role         | vault-secrets-operator-manager-rolebinding          
  vault-secrets-operator-controller-manager | ServiceAccount | Role        | vault-secrets-operator-system | vault-secrets-operator-leader-election-role | vault-secrets-operator-leader-election-rolebinding  
  • VSO ์ปจํŠธ๋กค๋Ÿฌ SA๊ฐ€ ์–ด๋–ค Role/ClusterRole์„ ๋ถ€์—ฌ๋ฐ›์•˜๋Š”์ง€ ํ™•์ธํ•จ
  • proxy ์—ญํ• ๊ณผ manager ์—ญํ• , leader election ์—ญํ•  ๋ฐ”์ธ๋”ฉ ํ™•์ธํ•จ

(10) VSO RBAC ๊ถŒํ•œ ๋ฒ”์œ„ ์ƒ์„ธ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
kubectl rolesum -n vault-secrets-operator-system vault-secrets-operator-controller-manager

ServiceAccount: vault-secrets-operator-system/vault-secrets-operator-controller-manager
Secrets:

Policies:
โ€ข [RB] vault-secrets-operator-system/vault-secrets-operator-leader-election-rolebinding โŸถ  [R] vault-secrets-operator-system/vault-secrets-operator-leader-election-role
  Resource                    Name  Exclude  Verbs  G L W C U P D DC  
  configmaps                  [*]     [-]     [-]   โœ” โœ” โœ” โœ” โœ” โœ” โœ” โœ–   
  events                      [*]     [-]     [-]   โœ– โœ– โœ– โœ” โœ– โœ” โœ– โœ–   
  leases.coordination.k8s.io  [*]     [-]     [-]   โœ” โœ” โœ” โœ” โœ” โœ” โœ” โœ–   

โ€ข [CRB] */vault-secrets-operator-manager-rolebinding โŸถ  [CR] */vault-secrets-operator-manager-role
  Resource                                                Name  Exclude  Verbs  G L W C U P D DC  
  configmaps                                              [*]     [-]     [-]   โœ” โœ” โœ” โœ– โœ– โœ– โœ– โœ–   
  daemonsets.apps                                         [*]     [-]     [-]   โœ” โœ” โœ” โœ– โœ– โœ” โœ– โœ–   
  deployments.apps                                        [*]     [-]     [-]   โœ” โœ” โœ” โœ– โœ– โœ” โœ– โœ–   
  events                                                  [*]     [-]     [-]   โœ– โœ– โœ– โœ” โœ– โœ” โœ– โœ–   
  hcpauths.secrets.hashicorp.com                          [*]     [-]     [-]   โœ” โœ” โœ” โœ” โœ” โœ” โœ” โœ–   
  hcpauths.secrets.hashicorp.com/finalizers               [*]     [-]     [-]   โœ– โœ– โœ– โœ– โœ” โœ– โœ– โœ–   
  hcpauths.secrets.hashicorp.com/status                   [*]     [-]     [-]   โœ” โœ– โœ– โœ– โœ” โœ” โœ– โœ–   
  hcpvaultsecretsapps.secrets.hashicorp.com               [*]     [-]     [-]   โœ” โœ” โœ” โœ” โœ” โœ” โœ” โœ–   
  hcpvaultsecretsapps.secrets.hashicorp.com/finalizers    [*]     [-]     [-]   โœ– โœ– โœ– โœ– โœ” โœ– โœ– โœ–   
  hcpvaultsecretsapps.secrets.hashicorp.com/status        [*]     [-]     [-]   โœ” โœ– โœ– โœ– โœ” โœ” โœ– โœ–   
  rollouts.argoproj.io                                    [*]     [-]     [-]   โœ” โœ” โœ” โœ– โœ– โœ” โœ– โœ–   
  secrets                                                 [*]     [-]     [-]   โœ” โœ” โœ” โœ” โœ” โœ” โœ” โœ”   
  secrettransformations.secrets.hashicorp.com             [*]     [-]     [-]   โœ” โœ” โœ” โœ” โœ” โœ” โœ” โœ–   
  secrettransformations.secrets.hashicorp.com/finalizers  [*]     [-]     [-]   โœ– โœ– โœ– โœ– โœ” โœ– โœ– โœ–   
  secrettransformations.secrets.hashicorp.com/status      [*]     [-]     [-]   โœ” โœ– โœ– โœ– โœ” โœ” โœ– โœ–   
  serviceaccounts                                         [*]     [-]     [-]   โœ” โœ” โœ” โœ– โœ– โœ– โœ– โœ–   
  serviceaccounts/token                                   [*]     [-]     [-]   โœ” โœ” โœ” โœ” โœ– โœ– โœ– โœ–   
  statefulsets.apps                                       [*]     [-]     [-]   โœ” โœ” โœ” โœ– โœ– โœ” โœ– โœ–   
  vaultauths.secrets.hashicorp.com                        [*]     [-]     [-]   โœ” โœ” โœ” โœ” โœ” โœ” โœ” โœ–   
  vaultauths.secrets.hashicorp.com/finalizers             [*]     [-]     [-]   โœ– โœ– โœ– โœ– โœ” โœ– โœ– โœ–   
  vaultauths.secrets.hashicorp.com/status                 [*]     [-]     [-]   โœ” โœ– โœ– โœ– โœ” โœ” โœ– โœ–   
  vaultconnections.secrets.hashicorp.com                  [*]     [-]     [-]   โœ” โœ” โœ” โœ” โœ” โœ” โœ” โœ–   
  vaultconnections.secrets.hashicorp.com/finalizers       [*]     [-]     [-]   โœ– โœ– โœ– โœ– โœ” โœ– โœ– โœ–   
  vaultconnections.secrets.hashicorp.com/status           [*]     [-]     [-]   โœ” โœ– โœ– โœ– โœ” โœ” โœ– โœ–   
  vaultdynamicsecrets.secrets.hashicorp.com               [*]     [-]     [-]   โœ” โœ” โœ” โœ” โœ” โœ” โœ” โœ–   
  vaultdynamicsecrets.secrets.hashicorp.com/finalizers    [*]     [-]     [-]   โœ– โœ– โœ– โœ– โœ” โœ– โœ– โœ–   
  vaultdynamicsecrets.secrets.hashicorp.com/status        [*]     [-]     [-]   โœ” โœ– โœ– โœ– โœ” โœ” โœ– โœ–   
  vaultpkisecrets.secrets.hashicorp.com                   [*]     [-]     [-]   โœ” โœ” โœ” โœ” โœ” โœ” โœ” โœ–   
  vaultpkisecrets.secrets.hashicorp.com/finalizers        [*]     [-]     [-]   โœ– โœ– โœ– โœ– โœ” โœ– โœ– โœ–   
  vaultpkisecrets.secrets.hashicorp.com/status            [*]     [-]     [-]   โœ” โœ– โœ– โœ– โœ” โœ” โœ– โœ–   
  vaultstaticsecrets.secrets.hashicorp.com                [*]     [-]     [-]   โœ” โœ” โœ” โœ” โœ” โœ” โœ” โœ–   
  vaultstaticsecrets.secrets.hashicorp.com/finalizers     [*]     [-]     [-]   โœ– โœ– โœ– โœ– โœ” โœ– โœ– โœ–   
  vaultstaticsecrets.secrets.hashicorp.com/status         [*]     [-]     [-]   โœ” โœ– โœ– โœ– โœ” โœ” โœ– โœ–   

โ€ข [CRB] */vault-secrets-operator-proxy-rolebinding โŸถ  [CR] */vault-secrets-operator-proxy-role
  Resource                                   Name  Exclude  Verbs  G L W C U P D DC  
  subjectaccessreviews.authorization.k8s.io  [*]     [-]     [-]   โœ– โœ– โœ– โœ” โœ– โœ– โœ– โœ–   
  tokenreviews.authentication.k8s.io         [*]     [-]     [-]   โœ– โœ– โœ– โœ” โœ– โœ– โœ– โœ–   
  • VSO๋Š” Vault์—์„œ ๊ฐ€์ ธ์˜จ ๊ฐ’์„ Kubernetes Secret๋กœ โ€œ์ƒ์„ฑ/๊ฐฑ์‹ โ€ํ•ด์•ผ ํ•˜๋ฏ€๋กœ secrets์— ๋Œ€ํ•œ ๊ฐ•ํ•œ ๊ถŒํ•œ ํ•„์š”ํ•จ
  • Deployment/StatefulSet/Rollout ๋“ฑ์— ๋ฐ˜์˜์„ ์œ„ํ•ด rollout ๊ด€๋ จ ๋ฆฌ์†Œ์Šค ์กฐํšŒ/ํŒจ์น˜ ๊ถŒํ•œ ํ•„์š”ํ•จ
  • Kubernetes Auth ๊ฒ€์ฆ์„ ์œ„ํ•œ TokenReview/SubjectAccessReview ๊ถŒํ•œ๋„ ํฌํ•จ๋จ

5. Deploy and sync a secret

(1) ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋„ค์ž„์ŠคํŽ˜์ด์Šค(app) ์ƒ์„ฑ

1
2
3
kubectl create ns app

namespace/app created

(2) VaultAuth CRD ์ŠคํŽ™ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
kubectl explain vaultauths.spec

# ๊ฒฐ๊ณผ
GROUP:      secrets.hashicorp.com
KIND:       VaultAuth
VERSION:    v1beta1

FIELD: spec <Object>

DESCRIPTION:
    VaultAuthSpec defines the desired state of VaultAuth
    
FIELDS:
  allowedNamespaces	<[]string>
    AllowedNamespaces Kubernetes Namespaces which are allow-listed for use with
    this AuthMethod.
    This field allows administrators to customize which Kubernetes namespaces
    are authorized to
    use with this AuthMethod. While Vault will still enforce its own rules, this
    has the added
    configurability of restricting which VaultAuthMethods can be used by which
    namespaces.
    Accepted values:
    []{"*"} - wildcard, all namespaces.
    []{"a", "b"} - list of namespaces.
    unset - disallow all namespaces except the Operator's the VaultAuthMethod's
    namespace, this
    is the default behavior.

  appRole	<Object>
    AppRole specific auth configuration, requires that the Method be set to
    `appRole`.

  aws	<Object>
    AWS specific auth configuration, requires that Method be set to `aws`.

  gcp	<Object>
    GCP specific auth configuration, requires that Method be set to `gcp`.

  headers	<map[string]string>
    Headers to be included in all Vault requests.

  jwt	<Object>
    JWT specific auth configuration, requires that the Method be set to `jwt`.

  kubernetes	<Object>
    Kubernetes specific auth configuration, requires that the Method be set to
    `kubernetes`.

  method	<string> -required-
  enum: kubernetes, jwt, appRole, aws, ....
    Method to use when authenticating to Vault.

  mount	<string> -required-
    Mount to use when authenticating to auth method.

  namespace	<string>
    Namespace to auth to in Vault

  params	<map[string]string>
    Params to use when authenticating to Vault

  storageEncryption	<Object>
    StorageEncryption provides the necessary configuration to encrypt the client
    storage cache.
    This should only be configured when client cache persistence with encryption
    is enabled.
    This is done by passing setting the manager's commandline argument
    --client-cache-persistence-model=direct-encrypted. Typically, there should
    only ever
    be one VaultAuth configured with StorageEncryption in the Cluster, and it
    should have
    the label: cacheStorageEncryption=true

  vaultConnectionRef	<string>
    VaultConnectionRef to the VaultConnection resource, can be prefixed with a
    namespace,
    eg: `namespaceA/vaultConnectionRefB`. If no namespace prefix is provided it
    will default to
    namespace of the VaultConnection CR. If no value is specified for
    VaultConnectionRef the
    Operator will default to the `default` VaultConnection, configured in the
    operator's namespace.
  • VSO์˜ ์ธ์ฆ ์„ค์ • ๋ฆฌ์†Œ์Šค์ธ VaultAuth CRD์˜ ์ŠคํŽ™ ๊ตฌ์กฐ๋ฅผ kubectl explain๋กœ ํ™•์ธํ•จ

(3) Secret ๋™๊ธฐํ™”๋ฅผ ์œ„ํ•œ Kubernetes Auth ์„ค์ • ์ ์šฉ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
cat vault/vault-auth-static.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  # SA bound to the VSO namespace for transit engine auth
  namespace: vault-secrets-operator-system
  name: demo-operator
---
apiVersion: v1
kind: ServiceAccount
metadata:
  namespace: app
  name: demo-static-app
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
  name: static-auth
  namespace: app
spec:
  method: kubernetes
  mount: demo-auth-mount
  kubernetes:
    role: role1
    serviceAccount: demo-static-app
    audiences:
      - vault
1
2
3
4
5
kubectl apply -f vault/vault-auth-static.yaml

serviceaccount/demo-operator created
serviceaccount/demo-static-app created
vaultauth.secrets.hashicorp.com/static-auth created
  • app ๋„ค์ž„์ŠคํŽ˜์ด์Šค์— Secret ์กฐํšŒ์šฉ SA(demo-static-app) ์ƒ์„ฑํ•จ
  • app ๋„ค์ž„์ŠคํŽ˜์ด์Šค์— VaultAuth(static-auth)๋ฅผ ์ƒ์„ฑํ•ด
    • demo-auth-mount auth mount ์‚ฌ์šฉ
    • Vault role role1 ์‚ฌ์šฉ
    • ํ† ํฐ audience๋Š” vault๋กœ ์ง€์ •ํ•จ
  • ๋ณ„๋„๋กœ vault-secrets-operator-system ๋„ค์ž„์ŠคํŽ˜์ด์Šค์— demo-operator SA๋„ ์ƒ์„ฑํ•˜์—ฌ(Transit ๋“ฑ ์šด์˜์šฉ ๋ชฉ์ ) ์—ญํ•  ๋ถ„๋ฆฌ ๊ธฐ๋ฐ˜ ๊ตฌ์„ฑํ•จ

(4) ServiceAccount/VaultAuth ์ƒ์„ฑ ํ™•์ธ

1
2
3
4
5
6
7
8
kubectl get sa,vaultauth -n app

NAME                             SECRETS   AGE
serviceaccount/default           0         2m12s
serviceaccount/demo-static-app   0         13s

NAME                                          AGE
vaultauth.secrets.hashicorp.com/static-auth   13s

(5) VaultStaticSecret CRD ์ŠคํŽ™ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
kubectl explain vaultstaticsecrets.spec

# ๊ฒฐ๊ณผ
GROUP:      secrets.hashicorp.com
KIND:       VaultStaticSecret
VERSION:    v1beta1

FIELD: spec <Object>

DESCRIPTION:
    VaultStaticSecretSpec defines the desired state of VaultStaticSecret
    
FIELDS:
  destination	<Object> -required-
    Destination provides configuration necessary for syncing the Vault secret to
    Kubernetes.

  hmacSecretData	<boolean>
    HMACSecretData determines whether the Operator computes the
    HMAC of the Secret's data. The MAC value will be stored in
    the resource's Status.SecretMac field, and will be used for drift detection
    and during incoming Vault secret comparison.
    Enabling this feature is recommended to ensure that Secret's data stays
    consistent with Vault.

  mount	<string> -required-
    Mount for the secret in Vault

  namespace	<string>
    Namespace to get the secret from in Vault

  path	<string> -required-
    Path of the secret in Vault, corresponds to the `path` parameter for,
    kv-v1:
    https://developer.hashicorp.com/vault/api-docs/secret/kv/kv-v1#read-secret
    kv-v2:
    https://developer.hashicorp.com/vault/api-docs/secret/kv/kv-v2#read-secret-version

  refreshAfter	<string>
    RefreshAfter a period of time, in duration notation e.g. 30s, 1m, 24h

  rolloutRestartTargets	<[]Object>
    RolloutRestartTargets should be configured whenever the application(s)
    consuming the Vault secret does
    not support dynamically reloading a rotated secret.
    In that case one, or more RolloutRestartTarget(s) can be configured here.
    The Operator will
    trigger a "rollout-restart" for each target whenever the Vault secret
    changes between reconciliation events.
    All configured targets wil be ignored if HMACSecretData is set to false.
    See RolloutRestartTarget for more details.

  type	<string> -required-
  enum: kv-v1, kv-v2
    Type of the Vault static secret

  vaultAuthRef	<string>
    VaultAuthRef to the VaultAuth resource, can be prefixed with a namespace,
    eg: `namespaceA/vaultAuthRefB`. If no namespace prefix is provided it will
    default to
    namespace of the VaultAuth CR. If no value is specified for VaultAuthRef the
    Operator will
    default to the `default` VaultAuth, configured in the operator's namespace.

  version	<integer>
    Version of the secret to fetch. Only valid for type kv-v2. Corresponds to
    version query parameter:
    https://developer.hashicorp.com/vault/api-docs/secret/kv/kv-v2#version
  • VaultStaticSecret์€ Vault์˜ ์ •์  ์‹œํฌ๋ฆฟ(KV) ์„ Kubernetes Secret์œผ๋กœ ๋™๊ธฐํ™”ํ•˜๋Š” CRD์ž„
  • ํ•ต์‹ฌ ํ•„๋“œ
    • type: kv-v1 / kv-v2
    • mount: Vault secrets engine mount ๊ฒฝ๋กœ
    • path: Vault ๋‚ด secret ๊ฒฝ๋กœ
    • destination: ์ƒ์„ฑ/๊ฐฑ์‹ ํ•  Kubernetes Secret ์ •๋ณด
    • refreshAfter: Vault ๊ฐ’์„ ์žฌ์กฐํšŒํ•ด ๋™๊ธฐํ™”ํ•˜๋Š” ์ฃผ๊ธฐ
    • vaultAuthRef: ์‚ฌ์šฉํ•  VaultAuth ๋ฆฌ์†Œ์Šค ์ง€์ •

(6) Vault โ†’ Kubernetes Secret ๋™๊ธฐํ™” ๋ฆฌ์†Œ์Šค ์ƒ์„ฑ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
cat vault/static-secret.yaml

apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
  name: vault-kv-app
  namespace: app
spec:
  type: kv-v2

  # mount path
  mount: kvv2

  # path of the secret
  path: webapp/config

  # dest k8s secret
  destination:
    name: secretkv
    create: true

  # static secret refresh interval
  refreshAfter: 30s

  # Name of the CRD to authenticate to Vault
  vaultAuthRef: static-auth
1
2
3
kubectl apply -f vault/static-secret.yaml

vaultstaticsecret.secrets.hashicorp.com/vault-kv-app created
  • VaultStaticSecret(vault-kv-app)๋ฅผ ์ƒ์„ฑํ•˜์—ฌ Vault์˜ kvv2/webapp/config๋ฅผ Kubernetes Secret secretkv๋กœ ์ž๋™ ์ƒ์„ฑ/๋™๊ธฐํ™”ํ•˜๋„๋ก ๊ตฌ์„ฑํ•จ
  • refreshAfter: 30s๋กœ 30์ดˆ๋งˆ๋‹ค Vault ๊ฐ’์„ ์žฌ์กฐํšŒํ•ด ๋ฐ˜์˜ํ•˜๋„๋ก ์„ค์ •ํ•จ
  • ์ธ์ฆ์€ ์•ž์„œ ๋งŒ๋“  vaultAuthRef: static-auth๋ฅผ ์‚ฌ์šฉํ•จ

(7) VaultStaticSecret ์ƒ์„ฑ ํ™•์ธ

1
2
3
4
kubectl get vaultstaticsecret -n app

NAME           AGE
vault-kv-app   11s

6. Rotate the static secret

(1) Kubernetes Secret ์ƒ์„ฑ ํ™•์ธ

1
2
3
4
kubectl get secret -n app

NAME       TYPE     DATA   AGE
secretkv   Opaque   3      63s
  • VSO๊ฐ€ Vault์˜ ์ •์  ์‹œํฌ๋ฆฟ์„ app ๋„ค์ž„์ŠคํŽ˜์ด์Šค์˜ Kubernetes Secret(secretkv)๋กœ ์ •์ƒ ์ƒ์„ฑํ–ˆ๋Š”์ง€ ํ™•์ธํ•จ

(2) Kubernetes Secret ๊ฐ’ ํ™•์ธ

1
2
3
4
5
6
7
kubectl krew install view-secret
kubectl view-secret -n app secretkv --all

# ๊ฒฐ๊ณผ
_raw='{"data":{"password":"static-password","username":"static-user"},"metadata":{"created_time":"2025-12-13T10:33:17.086953754Z","custom_metadata":null,"deletion_time":"","destroyed":false,"version":1}}'
password='static-password'
username='static-user'

(3) Vault Secret Rotate ์ˆ˜ํ–‰

1
2
3
4
5
6
7
8
9
10
11
12
13
vault kv put kvv2/webapp/config username="static-user2" password="static-password2"

===== Secret Path =====
kvv2/data/webapp/config

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-13T10:56:32.93205681Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            2
  • Vault์˜ kvv2/webapp/config ๊ฐ’์„ ์—…๋ฐ์ดํŠธํ•˜์—ฌ ์ •์  ์‹œํฌ๋ฆฟ์„ ํšŒ์ „(rotate)ํ•จ
  • KV v2 ํŠน์„ฑ์ƒ metadata์˜ version์ด 2๋กœ ์ฆ๊ฐ€ํ•จ

(4) VSO ๋™๊ธฐํ™” ๊ฒฐ๊ณผ ํ™•์ธ

1
2
3
4
5
kubectl view-secret -n app secretkv --all

_raw='{"data":{"password":"static-password2","username":"static-user2"},"metadata":{"created_time":"2025-12-13T10:56:32.93205681Z","custom_metadata":null,"deletion_time":"","destroyed":false,"version":2}}'
password='static-password2'
username='static-user2'

  • ์ผ์ • ์‹œ๊ฐ„(refreshAfter: 30s) ์ดํ›„ VSO๊ฐ€ Vault ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•˜๊ณ  Kubernetes Secret(secretkv) ๊ฐ’์„ ์ž๋™ ์—…๋ฐ์ดํŠธํ•จ
  • ์ถœ๋ ฅ ๊ฒฐ๊ณผ์—์„œ username/password๊ฐ€ static-user2/static-password2๋กœ ๋ณ€๊ฒฝ๋œ ๊ฒƒ์„ ํ™•์ธํ•จ
  • _raw์˜ metadata ๋ฒ„์ „๋„ 2๋กœ ๋ฐ˜์˜๋จ

(5) Secret ๋ฆฌ์†Œ์Šค๋Š” ์žฌ์ƒ์„ฑ๋˜์ง€ ์•Š๊ณ  ๊ฐ’๋งŒ ๋ณ€๊ฒฝ๋จ

1
2
3
4
kubectl get secret -n app

NAME       TYPE     DATA   AGE
secretkv   Opaque   3      4m30s
  • kubectl get secret์—์„œ AGE๊ฐ€ ์œ ์ง€๋˜๋Š” ๊ฒƒ์„ ํ†ตํ•ด Secret ์˜ค๋ธŒ์ ํŠธ ์ž์ฒด๋ฅผ ์ƒˆ๋กœ ๋งŒ๋“ค์ง€ ์•Š๊ณ  ๋™์ผ ๋ฆฌ์†Œ์Šค์˜ data๋งŒ ๊ฐฑ์‹ (update) ํ•œ ๊ฒƒ์„ ํ™•์ธํ•จ
  • ์ฆ‰, VSO๋Š” โ€œSecret ์žฌ์ƒ์„ฑโ€์ด ์•„๋‹ˆ๋ผ โ€œSecret ๋™๊ธฐํ™” ์—…๋ฐ์ดํŠธโ€ ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•จ

๐Ÿ˜ dynamic secret ๋™์  ์•”ํ˜ธ ์ฃผ๊ธฐ ๊ด€๋ฆฌ ์‹ค์Šต

1. PostgreSQL ํŒŒ๋“œ ๋ฐฐํฌ ๋ฐ Vault Database Secret Engine ์„ค์ •

(1) PostgreSQL ์ „์šฉ ๋„ค์ž„์ŠคํŽ˜์ด์Šค ์ƒ์„ฑ

1
2
3
kubectl create ns postgres

namespace/postgres created

(2) Bitnami Helm Repo ์ถ”๊ฐ€

1
helm repo add bitnami https://charts.bitnami.com/bitnami

(3) PostgreSQL ์„ค์น˜(Helm)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
helm upgrade --install postgres bitnami/postgresql --namespace postgres --set auth.audit.logConnections=true  --set auth.postgresPassword=secret-pass

# ๊ฒฐ๊ณผ
Release "postgres" does not exist. Installing it now.
NAME: postgres
LAST DEPLOYED: Sat Dec 13 20:12:10 2025
NAMESPACE: postgres
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: postgresql
CHART VERSION: 18.1.13
APP VERSION: 18.1.0
...
  • Bitnami PostgreSQL ์ฐจํŠธ๋กœ postgres ๋ฆด๋ฆฌ์ฆˆ๋ฅผ ์„ค์น˜ํ•จ
  • postgres ๊ณ„์ • ํŒจ์Šค์›Œ๋“œ๋ฅผ secret-pass๋กœ ๊ณ ์ • ์„ค์ •ํ•จ
  • auth.audit.logConnections=true๋กœ ์—ฐ๊ฒฐ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋„๋ก ์„ค์ •ํ•จ

(4) PostgreSQL ๋ฆฌ์†Œ์Šค ๋ฐฐํฌ ์ƒํƒœ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
kubectl get sts,pod,svc,ep,pvc,secret -n postgres

NAME                                   READY   AGE
statefulset.apps/postgres-postgresql   1/1     40s

NAME                        READY   STATUS    RESTARTS   AGE
pod/postgres-postgresql-0   1/1     Running   0          40s

NAME                             TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
service/postgres-postgresql      ClusterIP   10.96.122.63   <none>        5432/TCP   40s
service/postgres-postgresql-hl   ClusterIP   None           <none>        5432/TCP   40s

NAME                               ENDPOINTS         AGE
endpoints/postgres-postgresql      10.244.0.8:5432   40s
endpoints/postgres-postgresql-hl   10.244.0.8:5432   40s

NAME                                               STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/data-postgres-postgresql-0   Bound    pvc-447b64e3-b73a-4f0f-b25f-b6ec842d8010   8Gi        RWO            standard       <unset>                 40s

NAME                                    TYPE                 DATA   AGE
secret/postgres-postgresql              Opaque               1      40s
secret/sh.helm.release.v1.postgres.v1   helm.sh/release.v1   1      40s

(5) PostgreSQL Secret ๊ฐ’ ํ™•์ธ

1
2
3
kubectl view-secret -n postgres postgres-postgresql --all

secret-pass

(6) PostgreSQL ์ ‘์† ํ…Œ์ŠคํŠธ

1
2
3
4
5
6
7
8
9
10
11
kubectl exec -it -n postgres postgres-postgresql-0 -- sh -c "PGPASSWORD=secret-pass psql -U postgres -h localhost -c '\l'"

                                                     List of databases
   Name    |  Owner   | Encoding | Locale Provider |   Collate   |    Ctype    | Locale | ICU Rules |   Access privileges   
-----------+----------+----------+-----------------+-------------+-------------+--------+-----------+-----------------------
 postgres  | postgres | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |        |           | 
 template0 | postgres | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |        |           | =c/postgres          +
           |          |          |                 |             |             |        |           | postgres=CTc/postgres
 template1 | postgres | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |        |           | =c/postgres          +
           |          |          |                 |             |             |        |           | postgres=CTc/postgres
(3 rows)

2. PostgreSQL ๊ด€๋ จ ์„ค์ •

(1) Vault Database Secrets Engine ํ™œ์„ฑํ™”

1
2
3
vault secrets enable -path=demo-db database

Success! Enabled the database secrets engine at: demo-db/
  • PostgreSQL์— ๋Œ€ํ•ด ๋™์  DB ๊ณ„์ •(์ž„์‹œ ์‚ฌ์šฉ์ž/ํŒจ์Šค์›Œ๋“œ) ์„ ๋ฐœ๊ธ‰ํ•˜๊ธฐ ์œ„ํ•ด Database Secrets Engine์„ ํ™œ์„ฑํ™”ํ•จ
  • ์—”์ง„ ๊ฒฝ๋กœ๋Š” demo-db/๋กœ ์ง€์ •ํ•จ

(2) Database Secrets Engine ์—ฐ๊ฒฐ ์„ค์ •

1
2
3
4
5
6
7
8
vault write demo-db/config/demo-db \
   plugin_name=postgresql-database-plugin \
   allowed_roles="dev-postgres" \
   connection_url="postgresql://:@postgres-postgresql.postgres.svc.cluster.local:5432/postgres?sslmode=disable" \
   username="postgres" \
   password="secret-pass"
   
Success! Data written to: demo-db/config/demo-db
  • Vault๊ฐ€ PostgreSQL์— ์ ‘์†ํ•ด ๋™์  ๊ณ„์ •์„ ์ƒ์„ฑ/ํšŒ์ˆ˜ํ•  ์ˆ˜ ์žˆ๋„๋ก DB ์—ฐ๊ฒฐ ์ •๋ณด๋ฅผ ๋“ฑ๋กํ•จ
  • postgresql-database-plugin ์‚ฌ์šฉ
  • allowed_roles="dev-postgres"๋กœ ์ด ์—ฐ๊ฒฐ ์„ค์ •์—์„œ ํ—ˆ์šฉํ•  role์„ ์ œํ•œํ•จ
  • connection_url์— Kubernetes ์„œ๋น„์Šค DNS(postgres-postgresql.postgres.svc.cluster.local)๋ฅผ ์‚ฌ์šฉํ•ด ํด๋Ÿฌ์Šคํ„ฐ ๋‚ด๋ถ€ ํ†ต์‹ ํ•˜๋„๋ก ๊ตฌ์„ฑํ•จ
  • ๊ด€๋ฆฌ์ž ๊ณ„์ •์œผ๋กœ username=postgres, password=secret-pass๋ฅผ ๋“ฑ๋กํ•จ

(3) DB ์—ฐ๊ฒฐ ์„ค์ • ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
vault read demo-db/config/demo-db

Key                                   Value
---                                   -----
allowed_roles                         [dev-postgres]
connection_details                    map[connection_url:postgresql://:@postgres-postgresql.postgres.svc.cluster.local:5432/postgres?sslmode=disable username:postgres]
disable_automated_rotation            false
password_policy                       n/a
plugin_name                           postgresql-database-plugin
plugin_version                        n/a
root_credentials_rotate_statements    []
rotation_period                       0s
rotation_schedule                     n/a
rotation_window                       0
skip_static_role_import_rotation      false
verify_connection                     true

(4) PostgreSQL ๋™์  ๊ณ„์ • ๋ฐœ๊ธ‰ Role ์ƒ์„ฑ

1
2
3
4
5
6
7
8
9
10
11
vault write demo-db/roles/dev-postgres \
   db_name=demo-db \
   creation_statements="CREATE ROLE \"\" WITH LOGIN PASSWORD '' VALID UNTIL ''; \
      GRANT ALL PRIVILEGES ON DATABASE postgres TO \"\";" \
   revocation_statements="REVOKE ALL ON DATABASE postgres FROM  \"\";" \
   backend=demo-db \
   name=dev-postgres \
   default_ttl="10m" \
   max_ttl="20m"
   
Success! Data written to: demo-db/roles/dev-postgres
  • demo-db/roles/dev-postgres๋ฅผ ์ƒ์„ฑํ•˜์—ฌ Vault๊ฐ€ ๋™์ ์œผ๋กœ DB ์‚ฌ์šฉ์ž๋ฅผ ๋งŒ๋“ค๊ณ , TTL ๋งŒ๋ฃŒ ์‹œ ํšŒ์ˆ˜ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๊ตฌ์„ฑํ•จ
  • creation_statements
    • CREATE ROLE "" ... VALID UNTIL ""๋กœ ์œ ํšจ๊ธฐ๊ฐ„์ด ์žˆ๋Š” ๊ณ„์ • ์ƒ์„ฑ
    • ์ƒ์„ฑ๋œ ๊ณ„์ •์— postgres DB์— ๋Œ€ํ•œ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•จ
  • revocation_statements
    • ๋งŒ๋ฃŒ/ํšŒ์ˆ˜ ์‹œ ํ•ด๋‹น ๊ณ„์ •์˜ DB ๊ถŒํ•œ์„ ์ œ๊ฑฐํ•จ
  • TTL ์„ค์ •
    • default_ttl=10m: ๊ธฐ๋ณธ ๋ฐœ๊ธ‰ ์œ ํšจ๊ธฐ๊ฐ„ 10๋ถ„
    • max_ttl=20m: ์—ฐ์žฅํ•˜๋”๋ผ๋„ ์ตœ๋Œ€ 20๋ถ„์„ ๋„˜์ง€ ๋ชปํ•จ

(5) Role ์„ค์ •๊ฐ’ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
vault read demo-db/roles/dev-postgres

Key                      Value
---                      -----
creation_statements      [CREATE ROLE "" WITH LOGIN PASSWORD '' VALID UNTIL '';       GRANT ALL PRIVILEGES ON DATABASE postgres TO "";]
credential_type          password
db_name                  demo-db
default_ttl              10m
max_ttl                  20m
renew_statements         []
revocation_statements    [REVOKE ALL ON DATABASE postgres FROM  "";]
rollback_statements      []
  • ์ƒ์„ฑ/ํšŒ์ˆ˜ SQL, TTL ๊ฐ’์ด ์˜๋„๋Œ€๋กœ ๋“ค์–ด๊ฐ”๋Š”์ง€ ํ™•์ธํ•จ

(6) ๋™์  DB Credential ์กฐํšŒ ๊ถŒํ•œ Policy ์ƒ์„ฑ

1
2
3
4
5
6
7
vault policy write demo-auth-policy-db - <<EOF
path "demo-db/creds/dev-postgres" {
   capabilities = ["read"]
}
EOF

Success! Uploaded policy: demo-auth-policy-db
  • demo-db/creds/dev-postgres ๊ฒฝ๋กœ์—์„œ ๋ฐœ๊ธ‰๋˜๋Š” Credential์„ ์ฝ์„ ์ˆ˜ ์žˆ๋„๋ก ์ •์ฑ…์„ ์ƒ์„ฑํ•จ
  • ์ดํ›„ Kubernetes Auth role ๋“ฑ์— ์ด policy๋ฅผ ๋งคํ•‘ํ•˜๋ฉด ํŒŒ๋“œ๊ฐ€ ๋™์  DB ๊ณ„์ •์„ ๋ฐ›์•„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ

(7) PostgreSQL Role ๋ชฉ๋ก ํ™•์ธ(psql)

1
2
3
4
5
6
kubectl exec -it -n postgres postgres-postgresql-0 -- sh -c "PGPASSWORD=secret-pass psql -U postgres -h localhost -c '\du'"

                             List of roles
 Role name |                         Attributes                         
-----------+------------------------------------------------------------
 postgres  | Superuser, Create role, Create DB, Replication, Bypass RLS
  • ํ˜„์žฌ PostgreSQL์— ์–ด๋–ค role(๊ณ„์ •)์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•จ
  • ์ดˆ๊ธฐ ์ƒํƒœ์—์„œ๋Š” postgres ์Šˆํผ์œ ์ €๋งŒ ์กด์žฌํ•˜๋Š” ๊ฒƒ์ด ์ •์ƒ์ด๋ฉฐ, ์ดํ›„ Vault๊ฐ€ ๋™์  ๊ณ„์ •์„ ์ƒ์„ฑํ•˜๋ฉด ๋ชฉ๋ก์— ์ถ”๊ฐ€๋˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ฒ€์ฆ ๊ฐ€๋Šฅํ•จ

3. Setup dynamic secrets

(1) PostgreSQL Dynamic Secret์šฉ Kubernetes Auth Role ์ƒ์„ฑ

1
2
3
4
5
6
7
8
9
vault write auth/demo-auth-mount/role/auth-role \
   bound_service_account_names=demo-dynamic-app \
   bound_service_account_namespaces=demo-ns \
   token_ttl=0 \
   token_period=120 \
   token_policies=demo-auth-policy-db \
   audience=vault
   
Success! Data written to: auth/demo-auth-mount/role/auth-role

(2) ์ƒ์„ฑ๋œ Auth Role ์„ค์ • ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
vault read auth/demo-auth-mount/role/auth-role

Key                                         Value
---                                         -----
alias_name_source                           serviceaccount_uid
audience                                    vault
bound_service_account_names                 [demo-dynamic-app]
bound_service_account_namespace_selector    n/a
bound_service_account_namespaces            [demo-ns]
token_bound_cidrs                           []
token_explicit_max_ttl                      0s
token_max_ttl                               0s
token_no_default_policy                     false
token_num_uses                              0
token_period                                2m
token_policies                              [demo-auth-policy-db]
token_ttl                                   0s
token_type                                  default

4. Create the application

(1) demo-ns ๋„ค์ž„์ŠคํŽ˜์ด์Šค ์ƒ์„ฑ

1
2
kubectl create ns demo-ns
namespace/demo-ns created

(2) ๋™์  ์‹œํฌ๋ฆฟ ๋ฐฐํฌ ๋งค๋‹ˆํŽ˜์ŠคํŠธ ๊ตฌ์„ฑ ํ™•์ธ

1
2
3
ls dynamic-secrets/.
app-deployment.yaml   postgres                  vault-auth-operator.yaml           vault-dynamic-secret.yaml
app-secret.yaml       vault-auth-dynamic.yaml   vault-dynamic-secret-create.yaml   vault-operator-sa.yaml

(3) ๋™์  ์‹œํฌ๋ฆฟ ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฐฐํฌ

1
2
3
4
5
6
7
8
9
kubectl apply -f dynamic-secrets/.

deployment.apps/vso-db-demo created
secret/vso-db-demo created
serviceaccount/demo-dynamic-app created
vaultauth.secrets.hashicorp.com/dynamic-auth created
vaultdynamicsecret.secrets.hashicorp.com/vso-db-demo-create created
vaultdynamicsecret.secrets.hashicorp.com/vso-db-demo created
serviceaccount/demo-operator unchanged

(4) vso-db-demo ํŒŒ๋“œ ๊ธฐ๋™ ํ™•์ธ

1
2
3
4
5
6
kubectl get pod -n demo-ns

NAME                           READY   STATUS    RESTARTS   AGE
vso-db-demo-6ccd6f964f-cvqmh   1/1     Running   0          20s
vso-db-demo-6ccd6f964f-fcpwx   1/1     Running   0          20s
vso-db-demo-6ccd6f964f-xltqp   1/1     Running   0          6s

(5) ํŒŒ๋“œ์— Secret(/etc/secrets) ๋งˆ์šดํŠธ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
kubectl describe pod -n demo-ns

# ๊ฒฐ๊ณผ
...
    Mounts:
      /etc/secrets from secrets (ro)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-4rqt8 (ro)
...
Volumes:
  secrets:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  vso-db-demo
    Optional:    false
...    
  • ํŒŒ๋“œ์˜ /etc/secrets ๋ณผ๋ฅจ์ด K8S Secret vso-db-demo๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋งˆ์šดํŠธ๋˜๋Š”์ง€ ํ™•์ธํ•จ
  • ์ฆ‰, VSO๊ฐ€ Secret์„ ๊ฐฑ์‹ ํ•˜๋ฉด ํŒŒ๋“œ์—๋Š” ํŒŒ์ผ ํ˜•ํƒœ๋กœ ๊ฐ’์ด ๋ฐ˜์˜๋˜๋Š” ๊ตฌ์กฐ์ž„

(6) /etc/secrets ๋™์  ์ž๊ฒฉ์ฆ๋ช… ํŒŒ์ผ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
kubectl exec -it deploy/vso-db-demo -n demo-ns -- ls -al /etc/secrets

total 8
drwxrwxrwt 3 root root  140 Dec 13 11:21 .
drwxr-xr-x 1 root root 4096 Dec 13 11:21 ..
drwxr-xr-x 2 root root  100 Dec 13 11:21 ..2025_12_13_11_21_20.3821164146
lrwxrwxrwx 1 root root   32 Dec 13 11:21 ..data -> ..2025_12_13_11_21_20.3821164146
lrwxrwxrwx 1 root root   11 Dec 13 11:21 _raw -> ..data/_raw
lrwxrwxrwx 1 root root   15 Dec 13 11:21 password -> ..data/password
lrwxrwxrwx 1 root root   15 Dec 13 11:21 username -> ..data/username
1
2
3
kubectl exec -it deploy/vso-db-demo -n demo-ns -- cat /etc/secrets/username ; echo

v-demo-aut-dev-post-oDfUm4WPS5y7TgJAIleb-1765624880
1
2
3
kubectl exec -it deploy/vso-db-demo -n demo-ns -- cat /etc/secrets/password ; echo

sPkElMq2JWgjX6OD-43D
  • Secret ๋ณผ๋ฅจ ํŠน์„ฑ์ƒ ..data ์‹ฌ๋ณผ๋ฆญ ๋งํฌ ๊ตฌ์กฐ๋กœ ํŒŒ์ผ์ด ๋ฐฐ์น˜๋จ
  • username, password, _raw ํŒŒ์ผ์ด ์ƒ์„ฑ๋˜์–ด ๋™์  DB ๊ณ„์ •์ด ์ €์žฅ๋จ

(7) demo-ns ๋„ค์ž„์ŠคํŽ˜์ด์Šค Secret ๋™๊ธฐํ™” ๊ฒฐ๊ณผ ํ™•์ธ

1
2
3
4
5
kubectl get secret -n demo-ns

NAME                  TYPE     DATA   AGE
vso-db-demo           Opaque   3      3m16s
vso-db-demo-created   Opaque   3      3m16s
1
2
3
4
5
kubectl view-secret -n demo-ns vso-db-demo --all

_raw='{"password":"sPkElMq2JWgjX6OD-43D","username":"v-demo-aut-dev-post-oDfUm4WPS5y7TgJAIleb-1765624880"}'
password='sPkElMq2JWgjX6OD-43D'
username='v-demo-aut-dev-post-oDfUm4WPS5y7TgJAIleb-1765624880'
  • vso-db-demo๋Š” ์‹ค์ œ ์•ฑ์ด ์ฝ๋Š” Secret
  • vso-db-demo-created๋Š” ์ƒ์„ฑ ์‹œ์ /๋ฉ”ํƒ€ ๊ด€๋ฆฌ ๋ชฉ์ (์‹ค์Šต ๊ตฌ์„ฑ์— ๋”ฐ๋ผ ์ƒ์„ฑ)์œผ๋กœ ํ•จ๊ป˜ ์กด์žฌํ•จ
  • view-secret์œผ๋กœ username/password๊ฐ€ Vault์—์„œ ๋ฐœ๊ธ‰๋œ ๊ฐ’์œผ๋กœ ๋“ค์–ด์™”๋Š”์ง€ ํ™•์ธํ•จ

(8) VaultAuth ๋ฆฌ์†Œ์Šค ํ™•์ธ(dynamic-auth)

1
2
3
4
5
6
7
8
9
10
11
12
13
kubectl get vaultauth -n demo-ns dynamic-auth -o yaml

...
spec:
  kubernetes:
    audiences:
    - vault
    role: auth-role
    serviceAccount: demo-dynamic-app
    tokenExpirationSeconds: 600
  method: kubernetes
  mount: demo-auth-mount
...
  • demo-auth-mount๋ฅผ ์‚ฌ์šฉํ•œ Kubernetes Auth ๊ตฌ์„ฑ์ธ์ง€ ํ™•์ธํ•จ
  • Vault role์€ auth-role, SA๋Š” demo-dynamic-app, audience๋Š” vault๋กœ ์„ค์ •๋จ
  • tokenExpirationSeconds=600์œผ๋กœ K8S SA ํ† ํฐ์€ 10๋ถ„ ๋‹จ์œ„๋กœ ๊ฐฑ์‹ ๋˜๋Š” ํ˜•ํƒœ์ž„

(9) VaultDynamicSecret ๋ฆฌ์†Œ์Šค ํ™•์ธ(vso-db-demo)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
kubectl get vaultdynamicsecret -n demo-ns vso-db-demo -o yaml

...
spec:
  destination:
    create: false
    name: vso-db-demo
    overwrite: false
    transformation: {}
  mount: demo-db
  path: creds/dev-postgres
  renewalPercent: 67
  rolloutRestartTargets:
  - kind: Deployment
    name: vso-db-demo
  vaultAuthRef: dynamic-auth
status:
  lastGeneration: 2
  lastRenewalTime: 1765624880
  lastRuntimePodUID: 4781cbea-19a8-4675-bdec-fcc622299567
  secretLease:
    duration: 600
    id: demo-db/creds/dev-postgres/mMgHV5K7pyIXiCTOOwqG3Ed8
    renewable: true
    requestID: bfa058aa-aec9-8098-6c8d-df25c179ee82
  staticCredsMetaData:
    lastVaultRotation: 0
    rotationPeriod: 0
    ttl: 0
  vaultClientMeta:
    cacheKey: kubernetes-0a15983007d4f6e99deb3d
    id: c2afe154e8e28538ff782269b0552ef5cbfa41604a2cff01fd0004e6a60f4fcd
  • VSO๊ฐ€ Vault์—์„œ demo-db/creds/dev-postgres ๋ฅผ ์ฝ์–ด ๋™์  DB ๊ณ„์ •์„ ๋ฐœ๊ธ‰๋ฐ›๊ณ  ์ด๋ฅผ K8S Secret vso-db-demo๋กœ ๋™๊ธฐํ™”ํ•˜๋Š” ์„ค์ •์ž„
  • renewalPercent: 67๋กœ lease ๋งŒ๋ฃŒ ์ „ ์ผ์ • ๋น„์œจ ์‹œ์ ์— ๊ฐฑ์‹ ์„ ์‹œ๋„ํ•จ
  • rolloutRestartTargets๊ฐ€ Deployment๋กœ ์ง€์ •๋˜์–ด ์žˆ์–ด Secret ๋ณ€๊ฒฝ ์‹œ rollout-restart๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ๋  ์ˆ˜ ์žˆ์Œ
  • status์—์„œ lease ์ •๋ณด(๊ธฐ๊ฐ„ 600์ดˆ, renewable=true, lease id ๋“ฑ)๋ฅผ ํ™•์ธ ๊ฐ€๋Šฅํ•จ

(10) PostgreSQL์— ๋™์  ๊ณ„์ •์ด ์‹ค์ œ ์ƒ์„ฑ๋˜๋Š”์ง€ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
kubectl exec -it -n postgres postgres-postgresql-0 -- sh -c "PGPASSWORD=secret-pass psql -U postgres -h localhost -c '\du'"

                                                  List of roles
                      Role name                      |                         Attributes                         
-----------------------------------------------------+------------------------------------------------------------
 postgres                                            | Superuser, Create role, Create DB, Replication, Bypass RLS
 v-demo-aut-dev-post-6LhL36PwSmznWj1G1W3p-1765624880 | Password valid until 2025-12-13 11:31:25+00
 v-demo-aut-dev-post-WEfwYsV05xPot7LzG2I1-1765624880 | Password valid until 2025-12-13 11:31:25+00
 v-demo-aut-dev-post-XWri6OMU7jRRBFBBwM6d-1765624880 | Password valid until 2025-12-13 11:31:25+00
 v-demo-aut-dev-post-oDfUm4WPS5y7TgJAIleb-1765624880 | Password valid until 2025-12-13 11:31:25+00
  • \du๋กœ role ๋ชฉ๋ก์„ ๋ณด๋ฉด Vault๊ฐ€ ๋ฐœ๊ธ‰ํ•œ ์‚ฌ์šฉ์ž๋“ค์ด PostgreSQL์— ์‹ค์ œ๋กœ ์ƒ์„ฑ๋จ
  • ๊ฐ ๊ณ„์ •์—๋Š” Password valid until ...๊ฐ€ ๋ถ™์–ด ์žˆ์œผ๋ฉฐ, TTL ๊ธฐ๋ฐ˜ ์ž„์‹œ ๊ณ„์ •์œผ๋กœ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์ด ๋ช…์‹œ๋จ
  • ์•ฑ ํŒŒ๋“œ replicas๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ(์˜ˆ: 3๊ฐœ)์ธ ๊ฒฝ์šฐ, ๋™์‹œ/์ฃผ๊ธฐ ๋ฐœ๊ธ‰์œผ๋กœ ๋™์  ๊ณ„์ •์ด ์—ฌ๋Ÿฌ ๊ฐœ ์ƒ์„ฑ๋˜์–ด ๋ชฉ๋ก์— ๋‹ค์ˆ˜ ๋‚˜ํƒ€๋‚  ์ˆ˜ ์žˆ์Œ

(11) ๋™์  Secret ๊ฐฑ์‹  ์‹œ K8S Secret์€ โ€œ์žฌ์ƒ์„ฑโ€์ด ์•„๋‹ˆ๋ผ โ€œ๊ฐ’๋งŒ ๋ณ€๊ฒฝโ€

1
2
3
4
kubectl get secret -n demo-ns
NAME                  TYPE     DATA   AGE
vso-db-demo           Opaque   3      7m33s
vso-db-demo-created   Opaque   3      7m33s
1
2
3
4
5
kubectl view-secret -n demo-ns vso-db-demo --all

_raw='{"password":"sPkElMq2JWgjX6OD-43D","username":"v-demo-aut-dev-post-oDfUm4WPS5y7TgJAIleb-1765624880"}'
password='sPkElMq2JWgjX6OD-43D'
username='v-demo-aut-dev-post-oDfUm4WPS5y7TgJAIleb-1765624880'
  • 1์ฐจ ํ™•์ธ ์‹œ์ ์— vso-db-demo / vso-db-demo-created Secret์ด ์กด์žฌํ•จ
  • kubectl get secret์˜ AGE๊ฐ€ ์œ ์ง€๋˜๋ฏ€๋กœ Secret ๋ฆฌ์†Œ์Šค ์ž์ฒด๋Š” ์žฌ์ƒ์„ฑ๋˜์ง€ ์•Š์Œ
  • ๋Œ€์‹  kubectl view-secret๋กœ ๋ณด๋ฉด data(username/password)๋งŒ ๊ฐฑ์‹ ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธ ๊ฐ€๋Šฅํ•จ

(12) (10๋ถ„ ์ •๋„ ์ดํ›„) 2์ฐจ K8S Secret ํ™•์ธ

1
2
3
4
5
kubectl get secret -n demo-ns

NAME                  TYPE     DATA   AGE
vso-db-demo           Opaque   3      19m
vso-db-demo-created   Opaque   3      19m
1
2
3
4
5
kubectl view-secret -n demo-ns vso-db-demo --all

_raw='{"password":"SjlnS-5Gkcl24M42pUNl","username":"v-demo-aut-dev-post-Idi5jufqzLWK5XnGAhHw-1765625777"}'
password='SjlnS-5Gkcl24M42pUNl'
username='v-demo-aut-dev-post-Idi5jufqzLWK5XnGAhHw-1765625777'
  • AGE๋Š” ์ฆ๊ฐ€ํ–ˆ์ง€๋งŒ ๊ทธ๋Œ€๋กœ์ด๋ฏ€๋กœ Secret ์˜ค๋ธŒ์ ํŠธ๋Š” ์œ ์ง€
  • username/password ๊ฐ’์ด ๋ฐ”๋€Œ์–ด ๋™์  credential์ด rotate/renew ๋˜๋ฉฐ ๋™๊ธฐํ™”๋œ ๊ฒƒ์„ ํ™•์ธํ•จ

(13) Secret ๋ณ€๊ฒฝ์— ๋”ฐ๋ฅธ Deployment Rollout ์ด๋ฒคํŠธ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
kubectl describe deploy -n demo-ns
...
Events:
  Type    Reason             Age                    From                   Message
  ----    ------             ----                   ----                   -------
  Normal  ScalingReplicaSet  20m                    deployment-controller  Scaled up replica set vso-db-demo-69848c8d56 from 0 to 3
  Normal  ScalingReplicaSet  20m                    deployment-controller  Scaled up replica set vso-db-demo-6ccd6f964f from 0 to 1
  Normal  ScalingReplicaSet  20m                    deployment-controller  Scaled down replica set vso-db-demo-69848c8d56 from 3 to 2
  Normal  ScalingReplicaSet  20m                    deployment-controller  Scaled up replica set vso-db-demo-6ccd6f964f from 1 to 2
  Normal  ScalingReplicaSet  20m                    deployment-controller  Scaled down replica set vso-db-demo-69848c8d56 from 2 to 1
  Normal  ScalingReplicaSet  20m                    deployment-controller  Scaled up replica set vso-db-demo-6ccd6f964f from 2 to 3
  Normal  ScalingReplicaSet  20m                    deployment-controller  Scaled down replica set vso-db-demo-69848c8d56 from 1 to 0
  Normal  ScalingReplicaSet  5m50s                  deployment-controller  Scaled up replica set vso-db-demo-6647888d75 from 0 to 1
  Normal  ScalingReplicaSet  5m50s                  deployment-controller  Scaled down replica set vso-db-demo-6ccd6f964f from 3 to 2
  Normal  ScalingReplicaSet  5m50s                  deployment-controller  Scaled up replica set vso-db-demo-6647888d75 from 1 to 2
  Normal  ScalingReplicaSet  5m48s                  deployment-controller  Scaled down replica set vso-db-demo-6ccd6f964f from 2 to 1
  Normal  ScalingReplicaSet  5m48s                  deployment-controller  Scaled up replica set vso-db-demo-6647888d75 from 2 to 3
  Normal  ScalingReplicaSet  5m46s                  deployment-controller  Scaled down replica set vso-db-demo-6ccd6f964f from 1 to 0
  Normal  ScalingReplicaSet  5m46s                  deployment-controller  Scaled up replica set vso-db-demo-7f59964f7f from 0 to 1
  Normal  ScalingReplicaSet  5m46s                  deployment-controller  Scaled down replica set vso-db-demo-6647888d75 from 3 to 2
  Normal  ScalingReplicaSet  5m46s                  deployment-controller  Scaled up replica set vso-db-demo-7f59964f7f from 1 to 2
  Normal  ScalingReplicaSet  5m41s (x3 over 5m43s)  deployment-controller  (combined from similar events): Scaled down replica set vso-db-demo-6647888d75 from 1 to 0
  • VaultDynamicSecret์— rolloutRestartTargets๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด Secret ๋ณ€๊ฒฝ ์‹œ Deployment rollout์ด ํŠธ๋ฆฌ๊ฑฐ๋  ์ˆ˜ ์žˆ์Œ
  • kubectl describe deploy ์ด๋ฒคํŠธ์— ReplicaSet ์Šค์ผ€์ผ ์—…/๋‹ค์šด ๊ธฐ๋ก์ด ๋‚จ์œผ๋ฉฐ, ์ฃผ๊ธฐ์ ์œผ๋กœ ํŒŒ๋“œ๊ฐ€ ๊ต์ฒด๋˜๋Š” ํ˜•ํƒœ๋กœ ๊ด€์ฐฐ๋จ

(14) PostgreSQL ์‚ฌ์šฉ์ž ๋ชฉ๋ก์ด ๊ณ„์† ์ถ”๊ฐ€๋˜๋Š” ํ˜„์ƒ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
kubectl exec -it -n postgres postgres-postgresql-0 -- sh -c "PGPASSWORD=secret-pass psql -U postgres -h localhost -c '\du'"

                                                  List of roles
                      Role name                      |                         Attributes                         
-----------------------------------------------------+------------------------------------------------------------
 postgres                                            | Superuser, Create role, Create DB, Replication, Bypass RLS
 v-demo-aut-dev-post-6LhL36PwSmznWj1G1W3p-1765624880 | Password valid until 2025-12-13 11:41:25+00
 v-demo-aut-dev-post-FONYSNU9cqRnEIoFdcJx-1765625773 | Password valid until 2025-12-13 11:53:26+00
 v-demo-aut-dev-post-Idi5jufqzLWK5XnGAhHw-1765625777 | Password valid until 2025-12-13 11:53:34+00
 v-demo-aut-dev-post-WEfwYsV05xPot7LzG2I1-1765624880 | Password valid until 2025-12-13 11:31:25+00
 v-demo-aut-dev-post-XWri6OMU7jRRBFBBwM6d-1765624880 | Password valid until 2025-12-13 11:31:25+00
 v-demo-aut-dev-post-oDfUm4WPS5y7TgJAIleb-1765624880 | Password valid until 2025-12-13 11:41:25+00
  • ๋™์  Secret์ด ๊ฐฑ์‹ ๋˜๋ฉด์„œ ์ƒˆ๋กœ์šด DB ๊ณ„์ •์ด ๋ฐœ๊ธ‰๋˜๋ฉด \du ๋ชฉ๋ก์— ์ƒˆ role์ด ์ถ”๊ฐ€๋จ
  • TTL(Password valid until) ๊ธฐ๋ฐ˜์ด๋ผ ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด revoke/๋งŒ๋ฃŒ๋กœ ์ •๋ฆฌ๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ฐฑ์‹  ํƒ€์ด๋ฐ์— ๋”ฐ๋ผ ์ผ์‹œ์ ์œผ๋กœ ์—ฌ๋Ÿฌ ๊ณ„์ •์ด ๊ณต์กดํ•  ์ˆ˜ ์žˆ์Œ

(15) Vault์—์„œ lease ์กฐํšŒ ๋ฐ ํŠน์ • lease Revoke

1
2
3
4
5
6
vault list sys/leases/lookup/demo-db/creds/dev-postgres

Keys
----
S3m1OjrPMiwZZeRE02tqx1M7
Xaqjiodg0P7Nq7l0DW6QhyE5
1
2
vault lease revoke demo-db/creds/dev-postgres/S3m1OjrPMiwZZeRE02tqx1M7
All revocation operations queued successfully!
1
2
3
4
5
vault list sys/leases/lookup/demo-db/creds/dev-postgres

Keys
----
Xaqjiodg0P7Nq7l0DW6QhyE5
  • ๋™์  DB credential์€ Vault์—์„œ lease ๋‹จ์œ„๋กœ ๊ด€๋ฆฌ๋จ
  • sys/leases/lookup/...๋กœ ํ˜„์žฌ ๋ฐœ๊ธ‰๋œ lease key ๋ชฉ๋ก์„ ํ™•์ธ ๊ฐ€๋Šฅ
  • ํŠน์ • lease๋ฅผ vault lease revoke๋กœ ํšŒ์ˆ˜ํ•˜๋ฉด ํ•ด๋‹น credential(๊ณ„์ •/๊ถŒํ•œ)์ด ๋งŒ๋ฃŒ/ํšŒ์ˆ˜ ์ฒ˜๋ฆฌ๋จ

(16) Vault CLI(Token Auth)๋กœ ๋™์  ๊ณ„์ • ์ง์ ‘ ๋ฐœ๊ธ‰ ํ™•์ธ

1
2
3
4
5
6
7
8
9
vault read demo-db/creds/dev-postgres

Key                Value
---                -----
lease_id           demo-db/creds/dev-postgres/0W1x4enoQVQoooY7bvNEKnXh
lease_duration     10m
lease_renewable    true
password           uaC2Pr-iarcQhqpHT1Qz
username           v-token-dev-post-c9wsPVQpNe4YtfgKT9bv-1765626489
1
2
3
4
5
6
7
8
9
vault read demo-db/creds/dev-postgres

Key                Value
---                -----
lease_id           demo-db/creds/dev-postgres/iVaWodO61qpiWJnKMuPWE8Gu
lease_duration     10m
lease_renewable    true
password           DdEU-A1DfQ0HvNppK9qD
username           v-token-dev-post-PUuSunlUYypHV368CEAm-1765626491
1
2
3
4
5
6
7
8
9
10
11
12
13
14
kubectl exec -it -n postgres postgres-postgresql-0 -- sh -c "PGPASSWORD=secret-pass psql -U postgres -h localhost -c '\du'"

                                                  List of roles
                      Role name                      |                         Attributes                         
-----------------------------------------------------+------------------------------------------------------------
 postgres                                            | Superuser, Create role, Create DB, Replication, Bypass RLS
 v-demo-aut-dev-post-6LhL36PwSmznWj1G1W3p-1765624880 | Password valid until 2025-12-13 11:41:25+00
 v-demo-aut-dev-post-FONYSNU9cqRnEIoFdcJx-1765625773 | Password valid until 2025-12-13 11:53:26+00
 v-demo-aut-dev-post-Idi5jufqzLWK5XnGAhHw-1765625777 | Password valid until 2025-12-13 11:53:34+00
 v-demo-aut-dev-post-WEfwYsV05xPot7LzG2I1-1765624880 | Password valid until 2025-12-13 11:31:25+00
 v-demo-aut-dev-post-XWri6OMU7jRRBFBBwM6d-1765624880 | Password valid until 2025-12-13 11:31:25+00
 v-demo-aut-dev-post-oDfUm4WPS5y7TgJAIleb-1765624880 | Password valid until 2025-12-13 11:41:25+00
 v-token-dev-post-PUuSunlUYypHV368CEAm-1765626491    | Password valid until 2025-12-13 11:58:16+00
 v-token-dev-post-c9wsPVQpNe4YtfgKT9bv-1765626489    | Password valid until 2025-12-13 11:58:14+00
  • Kubernetes Auth๊ฐ€ ์•„๋‹ˆ๋ผ Vault CLI(Token Auth) ๋กœ๋„ ๋™์ผ ๊ฒฝ๋กœ๋ฅผ ์ฝ์œผ๋ฉด ์ฆ‰์‹œ ๋™์  ๊ณ„์ •์ด ๋ฐœ๊ธ‰๋จ
  • ์ถœ๋ ฅ์—๋Š” lease_id, lease_duration(10m), lease_renewable(true), username/password๊ฐ€ ํฌํ•จ๋จ
  • ๋ฐœ๊ธ‰๋œ v-token-* ๊ณ„์ •์ด PostgreSQL \du ๋ชฉ๋ก์— ์ถ”๊ฐ€๋˜๋Š” ๊ฒƒ์œผ๋กœ ๊ฒ€์ฆ ๊ฐ€๋Šฅํ•จ

(17) Vault ๋กœ๊ทธ๋กœ lease revoke/๋งŒ๋ฃŒ ์ฒ˜๋ฆฌ ํ™•์ธ

1
2
3
4
5
6
7
kubectl stern -n vault -l app.kubernetes.io/name=vault
...
vault-0 vault 2025-12-13T11:31:20.632Z [INFO]  expiration: revoked lease: lease_id=demo-db/creds/dev-postgres/JhbSLvzL9hxjT92Bzkxwkea0
vault-0 vault 2025-12-13T11:31:20.652Z [INFO]  expiration: revoked lease: lease_id=demo-db/creds/dev-postgres/KKiuAow8qVdUPoc4NWnuiUZR
vault-0 vault 2025-12-13T11:41:20.146Z [INFO]  expiration: revoked lease: lease_id=demo-db/creds/dev-postgres/fiTVX2vZs8wKD4YpMRELZOWm
vault-0 vault 2025-12-13T11:41:20.555Z [INFO]  expiration: revoked lease: lease_id=demo-db/creds/dev-postgres/mMgHV5K7pyIXiCTOOwqG3Ed8
vault-0 vault 2025-12-13T11:45:47.925Z [INFO]  expiration: revoked lease: lease_id=demo-db/creds/dev-postgres/S3m1OjrPMiwZZeRE02tqx1M7
  • Vault ์„œ๋ฒ„ ๋กœ๊ทธ์—์„œ expiration ๋ชจ๋“ˆ์ด lease ๋งŒ๋ฃŒ ๋˜๋Š” revoke ์ฒ˜๋ฆฌ ์‹œ์ ์„ ๊ธฐ๋กํ•จ
  • expiration: revoked lease: lease_id=... ๋กœ๊ทธ๊ฐ€ ๋ณด์ด๋ฉด ๋™์  credential ํšŒ์ˆ˜ ๋™์ž‘์ด ์ •์ƒ ์ˆ˜ํ–‰๋œ ๊ฒƒ์ž„

๐Ÿ” PKI secret

  • PKI secret์€ PKI ์ธ์ฆ์„œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋™์ ์œผ๋กœ ๋ฐœ๊ธ‰/๊ฐฑ์‹ ๋˜๋Š” ์ธ์ฆ์„œ(๋ฐ ํ‚ค) ๋ฅผ Vault๊ฐ€ ๊ด€๋ฆฌํ•˜๊ณ , ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋ณ€๊ฒฝ๋œ ์ธ์ฆ์„œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•˜๋Š” ๋ฐฉ์‹์ž„
  • ์•„๋ž˜๋Š” Vault์—์„œ PKI secrets engine ํ™œ์„ฑํ™” โ†’ Root CA ์ƒ์„ฑ โ†’ CA/CRL URL ์„ค์ • โ†’ Role ์ƒ์„ฑ โ†’ PKI ์ •์ฑ… ์ƒ์„ฑ ํ๋ฆ„ ์ •๋ฆฌ์ž„
  • https://developer.hashicorp.com/vault/tutorials/archive/kubernetes-cert-manager

1. ๊ธฐ๋ณธ ์„ค์ •

(1) PKI Secrets Engine ํ™œ์„ฑํ™” ๋ฐ TTL ํŠœ๋‹

1
2
3
vault secrets enable pki

Success! Enabled the pki secrets engine at: pki/
1
2
3
vault secrets tune -max-lease-ttl=8760h pki

Success! Tuned the secrets engine at: pki/
  • ๊ธฐ๋ณธ path(pki/)๋กœ PKI secrets engine ํ™œ์„ฑํ™”
  • ๊ธฐ๋ณธ TTL์€ 30์ผ์ด๋ฉฐ, max TTL์„ 8760h(1๋…„)๋กœ ํ™•์žฅ

(2) Root CA ์ƒ์„ฑ (internal)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
vault write pki/root/generate/internal \
    common_name=example.com \
    ttl=8760h
    
Key              Value
---              -----
certificate      -----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
expiration       1797162811
issuer_id        04f3933e-d15e-485a-a28c-fd98c1bf25e5
issuer_name      n/a
issuing_ca       -----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
key_id           8040af31-a02e-1aab-c018-4b2244baa886
key_name         n/a
serial_number    20:ea:c2:25:e7:31:72:7a:8a:b9:44:e5:a1:d5:da:5a:cf:c1:ec:c1

(3) ์ƒ์„ฑ๋œ ์ธ์ฆ์„œ ๋‚ด์šฉ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
echo "-----BEGIN CERTIFICATE-----..." | openssl x509 -noout -text

# ๊ฒฐ๊ณผ
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            20:ea:c2:25:e7:31:72:7a:8a:b9:44:e5:a1:d5:da:5a:cf:c1:ec:c1
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=example.com
        Validity
            Not Before: Dec 13 11:53:01 2025 GMT
            Not After : Dec 13 11:53:31 2026 GMT
        Subject: CN=example.com
...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign
            X509v3 Basic Constraints: critical
                CA:TRUE
            X509v3 Subject Key Identifier: 
                DE:04:26:B2:2E:D9:59:B5:AE:28:60:10:7C:97:03:8E:E6:6F:C9:96
            X509v3 Authority Key Identifier: 
                DE:04:26:B2:2E:D9:59:B5:AE:28:60:10:7C:97:03:8E:E6:6F:C9:96
            X509v3 Subject Alternative Name: 
                DNS:example.com
...

(4) CA/CRL URL ์„ค์ •

1
2
3
4
5
6
7
8
9
10
vault write pki/config/urls \
    issuing_certificates="http://vault.vault.svc:8200/v1/pki/ca" \
    crl_distribution_points="http://vault.vault.svc:8200/v1/pki/crl"

Key                        Value
---                        -----
crl_distribution_points    [http://vault.vault.svc:8200/v1/pki/crl]
enable_templating          false
issuing_certificates       [http://vault.vault.svc:8200/v1/pki/ca]
ocsp_servers               []
1
2
3
4
5
6
7
8
vault read pki/config/urls

Key                        Value
---                        -----
crl_distribution_points    [http://vault.vault.svc:8200/v1/pki/crl]
enable_templating          false
issuing_certificates       [http://vault.vault.svc:8200/v1/pki/ca]
ocsp_servers               []

(5) CA / CRL ๋‹ค์šด๋กœ๋“œ ๋ฐ ๊ฒ€์ฆ

1
2
http://127.0.0.1:30000/v1/pki/ca
http://127.0.0.1:30000/v1/pki/crl

  • NodePort(์˜ˆ: 30000)๋กœ ๋…ธ์ถœ๋œ Vault์—์„œ CA/CRL ๋‚ด๋ ค๋ฐ›์•„ ํ™•์ธ

  • ๋‹ค์šด๋กœ๋“œํ•œ CA ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
openssl x509 -noout -text -in ~/Downloads/ca.cer

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            20:ea:c2:25:e7:31:72:7a:8a:b9:44:e5:a1:d5:da:5a:cf:c1:ec:c1
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=example.com
        Validity
            Not Before: Dec 13 11:53:01 2025 GMT
            Not After : Dec 13 11:53:31 2026 GMT
        Subject: CN=example.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:f9:76:ab:a1:83:83:6e:ce:fe:3e:16:a6:80:e4:
                    ec:c7:c6:ef:31:11:62:fd:5c:85:05:99:b3:9b:6d:
                    54:3c:7c:61:bd:31:f6:40:89:a1:70:85:ef:55:fb:
                    9d:24:43:87:f9:ea:0d:80:21:af:9f:7b:e3:8e:4f:
                    3a:92:63:10:65:e3:68:39:9d:3c:fc:aa:87:c1:0f:
                    27:3c:f4:ea:61:cd:c3:9e:60:03:47:36:c1:14:c7:
                    49:fc:25:7f:33:8a:5f:60:88:93:b9:50:8b:ce:0a:
                    d6:b1:7a:0c:ec:ca:c5:29:1c:dd:54:b5:ec:7b:f1:
                    bf:ba:60:5a:82:7c:d4:f2:a2:02:5b:66:4d:44:09:
                    6a:3d:9e:d4:f0:ab:60:c0:bb:b2:04:0e:8b:0f:3b:
                    c7:6b:df:7d:5f:86:f8:2b:1a:32:4e:5e:f9:a9:f6:
                    6d:1b:ca:dd:17:43:97:81:6f:1b:e0:4f:d4:34:15:
                    66:e5:f7:c4:3c:00:5a:c9:44:69:85:7e:44:42:a6:
                    71:cc:52:72:85:37:60:f4:8e:88:12:ff:06:ba:76:
                    f8:e8:db:ae:94:8a:38:fa:80:c4:c2:ce:5c:0e:53:
                    79:51:b1:30:10:22:d1:bb:ea:e4:17:dd:1a:8a:d8:
                    26:96:6e:e0:d8:ea:69:58:e5:5e:16:70:90:df:2e:
                    dd:e7
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign
            X509v3 Basic Constraints: critical
                CA:TRUE
            X509v3 Subject Key Identifier: 
                DE:04:26:B2:2E:D9:59:B5:AE:28:60:10:7C:97:03:8E:E6:6F:C9:96
            X509v3 Authority Key Identifier: 
                DE:04:26:B2:2E:D9:59:B5:AE:28:60:10:7C:97:03:8E:E6:6F:C9:96
            X509v3 Subject Alternative Name: 
                DNS:example.com
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        58:bf:56:42:35:3d:9b:e2:09:53:c6:c4:d0:6b:f7:49:68:b0:
        00:04:b9:80:22:ee:93:8a:95:14:fc:2e:8d:52:f9:09:f5:78:
        85:cc:9d:bd:e3:b5:59:26:fa:c5:6d:b7:22:97:2b:e3:94:19:
        93:1d:6f:8f:09:82:e3:9c:74:48:19:58:43:51:00:a9:b5:d9:
        8c:2d:6b:a5:16:66:cc:7e:e7:6f:71:4a:46:cc:38:66:7e:ba:
        29:6f:ae:a6:f7:b2:4c:62:05:9f:86:31:cc:5e:7b:1b:20:6a:
        f9:b6:31:c2:b4:ad:0c:29:e9:9c:5e:a7:77:74:18:8c:2d:a3:
        4d:ce:d6:18:62:28:7c:82:68:7f:db:e4:b0:67:4a:bf:df:ab:
        d4:a0:75:58:8c:6b:a0:ca:65:f0:89:80:f7:3f:1c:50:9d:63:
        b5:ff:41:46:64:26:06:6a:5f:50:a5:6b:b0:eb:78:2c:e8:46:
        84:4b:c0:34:b3:0e:74:bb:74:67:52:aa:77:a2:26:4a:70:ba:
        ee:c0:a4:75:ec:0a:54:11:08:a7:d9:23:51:5d:7b:f0:91:f8:
        56:4a:48:58:24:81:4f:3a:b3:8c:a8:c5:c4:57:1e:5d:55:35:
        87:cf:b3:3c:ab:54:15:cb:07:6f:f2:df:62:7f:b1:b8:37:7d:
        18:a6:be:e6
  • ๋‹ค์šด๋กœ๋“œํ•œ CRL ํ™•์ธ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
openssl crl -noout -text -in ~/Downloads/crl.crl

Certificate Revocation List (CRL):
    Version 2 (0x1)
    Signature Algorithm: sha256WithRSAEncryption
    Issuer: CN=example.com
    Last Update: Dec 13 11:53:31 2025 GMT
    Next Update: Dec 16 11:53:31 2025 GMT
    CRL extensions:
        X509v3 Authority Key Identifier: 
            DE:04:26:B2:2E:D9:59:B5:AE:28:60:10:7C:97:03:8E:E6:6F:C9:96
        X509v3 CRL Number: 
            1
No Revoked Certificates.
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        cc:d5:f9:19:20:05:bf:7f:ca:55:f5:1c:56:94:96:18:c3:2e:
        d2:5c:97:be:6f:a4:63:5b:37:bf:5e:13:c6:f8:da:6f:ca:fd:
        33:6c:ad:9e:7f:c0:b2:2d:74:47:d5:11:1b:ba:3b:87:da:9e:
        c7:73:7b:4b:5b:14:b8:a1:77:7b:ed:d5:5b:0d:71:44:aa:39:
        7f:79:de:56:aa:f2:3d:d1:87:a0:2e:1f:61:e4:52:8e:ce:e5:
        90:71:a0:91:da:1a:84:62:0f:6b:17:98:1b:8c:33:31:79:19:
        68:34:26:ac:cc:0f:4e:a3:66:d7:88:d6:fd:59:64:ac:8b:2a:
        fb:a8:5a:5a:28:85:67:5d:bd:1e:0e:ab:7a:67:5e:6c:c4:6a:
        3a:19:87:fe:0b:95:20:ca:0a:b0:c1:95:6c:8e:ae:5d:76:a2:
        07:cc:be:d6:d3:c2:99:78:dc:22:92:19:7e:17:a7:2e:8d:ca:
        7f:e1:59:b8:b3:0f:10:fe:72:74:29:0e:86:e7:4b:a8:56:d9:
        f7:e1:06:81:d8:82:18:92:ae:35:43:9f:f2:90:15:39:46:46:
        7a:00:bb:69:0f:b2:70:a1:e6:cd:fc:2c:76:2a:f7:40:09:46:
        93:25:76:a7:8e:25:13:86:bb:1f:24:ba:5d:ad:17:1e:16:e7:
        d8:9a:96:b7

(6) PKI ์ •์ฑ…(Policy) ์ƒ์„ฑ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
vault write pki/roles/example-dot-com \
    allowed_domains=example.com \
    allow_subdomains=true \
    max_ttl=72h

Key                                   Value
---                                   -----
allow_any_name                        false
allow_bare_domains                    false
allow_glob_domains                    false
allow_ip_sans                         true
allow_localhost                       true
allow_subdomains                      true
allow_token_displayname               false
allow_wildcard_certificates           true
allowed_domains                       [example.com]
allowed_domains_template              false
allowed_other_sans                    []
allowed_serial_numbers                []
allowed_uri_sans                      []
allowed_uri_sans_template             false
allowed_user_ids                      []
basic_constraints_valid_for_non_ca    false
client_flag                           true
cn_validations                        [email hostname]
code_signing_flag                     false
country                               []
email_protection_flag                 false
enforce_hostnames                     true
ext_key_usage                         []
ext_key_usage_oids                    []
generate_lease                        false
issuer_ref                            default
key_bits                              2048
key_type                              rsa
key_usage                             [DigitalSignature KeyAgreement KeyEncipherment]
locality                              []
max_ttl                               72h
no_store                              false
not_after                             n/a
not_before_duration                   30s
organization                          []
ou                                    []
policy_identifiers                    []
postal_code                           []
province                              []
require_cn                            true
serial_number_source                  json-csr
server_flag                           true
signature_bits                        256
street_address                        []
ttl                                   0s
use_csr_common_name                   true
use_csr_sans                          true
use_pss                               false
1
2
3
4
5
6
7
vault policy write pki - <<EOF
path "pki*"                        { capabilities = ["read", "list"] }
path "pki/sign/example-dot-com"    { capabilities = ["create", "update"] }
path "pki/issue/example-dot-com"   { capabilities = ["create"] }
EOF

Success! Uploaded policy: pki

2. ๋™์ž‘ ์„ค์ • ๋ฐ ํ™•์ธ: Cert Manager ํ™œ์šฉ

(1) Vault Kubernetes ์ธ์ฆ ํ™œ์„ฑํ™” ๋ฐ Role ์ƒ์„ฑ

  • kubernetes auth ํ™œ์„ฑํ™”
1
2
3
vault auth enable kubernetes

Success! Enabled kubernetes auth method at: kubernetes/
  • ํด๋Ÿฌ์Šคํ„ฐ API ์ฃผ์†Œ ๋“ฑ๋ก
1
2
3
vault write auth/kubernetes/config kubernetes_host="https://kubernetes.default.svc"

Success! Data written to: auth/kubernetes/config
  • cert-manager๊ฐ€ ์‚ฌ์šฉํ•  role(issuer) ์ƒ์„ฑ
1
2
3
4
5
6
7
vault write auth/kubernetes/role/issuer \
    bound_service_account_names=issuer \
    bound_service_account_namespaces=default \
    policies=pki \
    ttl=20m

Success! Data written to: auth/kubernetes/role/issuer

(2) cert-manager ์„ค์น˜

  • CRD ์„ค์น˜
1
2
3
4
5
6
7
8
kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v1.12.3/cert-manager.crds.yaml

customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/challenges.acme.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/clusterissuers.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/issuers.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/orders.acme.cert-manager.io created
  • CRD ํ™•์ธ
1
2
3
4
5
6
7
8
kubectl get crd | grep cert-manager

certificaterequests.cert-manager.io           2025-12-13T12:09:05Z
certificates.cert-manager.io                  2025-12-13T12:09:05Z
challenges.acme.cert-manager.io               2025-12-13T12:09:05Z
clusterissuers.cert-manager.io                2025-12-13T12:09:05Z
issuers.cert-manager.io                       2025-12-13T12:09:05Z
orders.acme.cert-manager.io                   2025-12-13T12:09:05Z
  • cert-manager namespace ์ƒ์„ฑ
1
2
3
kubectl create namespace cert-manager

namespace/cert-manager created
  • helm repo ์ถ”๊ฐ€ ๋ฐ ์„ค์น˜
1
2
3
helm repo add jetstack https://charts.jetstack.io

"jetstack" has been added to your repositories
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
helm install cert-manager --namespace cert-manager --version v1.12.3 jetstack/cert-manager

NAME: cert-manager
LAST DEPLOYED: Sat Dec 13 21:10:32 2025
NAMESPACE: cert-manager
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
cert-manager v1.12.3 has been deployed successfully!

In order to begin issuing certificates, you will need to set up a ClusterIssuer
or Issuer resource (for example, by creating a 'letsencrypt-staging' issuer).

More information on the different types of issuers and how to configure them
can be found in our documentation:

https://cert-manager.io/docs/configuration/

For information on how to configure cert-manager to automatically provision
Certificates for Ingress resources, take a look at the `ingress-shim`
documentation:

https://cert-manager.io/docs/usage/ingress/
  • ํŒŒ๋“œ ํ™•์ธ
1
2
3
4
5
6
kubectl get pods --namespace cert-manager

NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-cainjector-74bf899bd6-l2lk6   1/1     Running   0          52s
cert-manager-fb6f6945f-j9vkk               1/1     Running   0          52s
cert-manager-webhook-8fc69bc68-w2zd5       1/1     Running   0          52s

(3) Vault Issuer ์—ฐ๋™์šฉ ServiceAccount/Secret ์ค€๋น„

  • default namespace์— issuer ServiceAccount ์ƒ์„ฑ
1
2
3
kubectl create serviceaccount issuer

serviceaccount/issuer created
  • ServiceAccount ํ† ํฐ Secret ์ƒ์„ฑ
1
2
3
4
5
6
7
8
9
10
11
12
issuer-secret.yaml <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: issuer-token-lmzpj
  annotations:
    kubernetes.io/service-account.name: issuer
type: kubernetes.io/service-account-token
EOF
kubectl apply -f issuer-secret.yaml

secret/issuer-token-lmzpj created
  • Secret ํ™•์ธ
1
2
3
4
kubectl get secrets

NAME                 TYPE                                  DATA   AGE
issuer-token-lmzpj   kubernetes.io/service-account-token   3      16s
  • Secret ์ด๋ฆ„์„ ๋ณ€์ˆ˜๋กœ ์ €์žฅ
1
2
3
4
ISSUER_SECRET_REF=$(kubectl get secrets --output=json | jq -r '.items[].metadata | select(.name|startswith("issuer-token-")).name')
echo $ISSUER_SECRET_REF

issuer-token-lmzpj
  • ํ† ํฐ ๊ฐ’ ํ™•์ธ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
kubectl view-secret $ISSUER_SECRET_REF --all

# ๊ฒฐ๊ณผ
ca.crt='-----BEGIN CERTIFICATE-----
MIIDBTCCAe2gAwIBAgIIfxU/3FwXtREwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKa3ViZXJuZXRlczAeFw0yNTEyMTMxMDE5MzBaFw0zNTEyMTExMDI0MzBaMBUx
EzARBgNVBAMTCmt1YmVybmV0ZXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDjg1VcWK/9XVEMn7HstJClhmlZNYb123eC9vHcDHsk6MtZzAiFVTgizyRa
KYGtZZaUdGVKmVVIVs2r1Vj97X2V/votPHchZ9OJw7h9z/1TWjN3Le8QTwqtYHRm
v3PqaAj5wOUqc/0mUiDg+8icELgh0dXHsvOALNvb8HHgtBNQ2U91RICBgX7bTC8m
dM7fjjbi+FT73wjXXaTWn6dXlXn2LanYxG7eXb087FuFErFdweDPkbAAjVrwNhOz
e8wT7zT73R0seAqMFqckWRWoaufS4Ai+jBOvVhWILywyl1wmUJ5N64Lw22YWbBaA
z9TgZEOaPQ47E6Ay8jYs4OcK7GdjAgMBAAGjWTBXMA4GA1UdDwEB/wQEAwICpDAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQZ8E41JXCiZlobPv8m8Phkd/68uTAV
BgNVHREEDjAMggprdWJlcm5ldGVzMA0GCSqGSIb3DQEBCwUAA4IBAQCoL+CRPjRM
rHYd+UoKVz1P8vQUshTKXDtlc/ZLALszKii61xFCQB9CdGklfvWLNmHA1tX+VtYE
BSno79xB74FnDJxaOl8y0kpt68ZmVNQoZcG0GdBV6/GKkTU2Zrm/y6vI2ncBrhFG
ueTt4fn0QLUjZQLEhWivEt2lpEjGTxYOa0u0IKhRenw1K2/mvIC2CvqFTJJF8lbZ
46PwWRdViL/0aSYS3Od8S7B0aaO/FemHpNjclExKB7XIOGlnIluVP+TFEbTjAlrf
kbU8yxrWrZgGMW1l1AFi/i9T+FKBy8XaNbZq/7+OrQE8wDEnjWM/PmIbmkDOxOim
D6SgjAxin2zW
-----END CERTIFICATE-----
'
namespace='default'
token='eyJhbGciOiJSUzI1NiIsImtpZCI6IklzeVpQdUIwc1Vod282R1lyZ0stZ2Z0aERfZ0dnS1lvTjY3Zng1ODg2bEUifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6Imlzc3Vlci10b2tlbi1sbXpwaiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJpc3N1ZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI4ZDMzMjQzZi0yYzRlLTRiNDgtOGI1MC1kOGI4MTk4NzU2OWMiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDppc3N1ZXIifQ.grg9Eqzj7A_MhWmb3y90_xdlGBUKIaLYJc7mZVw10vtTt0P00495lc6HtxS2IupEIw2-QovWIHPJeU3PQ4qhy_asCeWAjAWDyeYH0NqzIHgkLzDCfN_knPceH5fPqwL6E_tM2B2HBswnFL_hBgxeDrPO4MZ3bKCycoflIcNB2K4-3hLz-Iv5_eNuGXSAvfHheSTybviV5_ikkF6yR2cHE78DmhWKRBcHjONsu7A69wh-ZJdzuOpcp9ayjZmv3rqAiLsE9o_HoE6IlC0gWK9HQN9BvMR2gXmcZtTOFF-v50IWEgzmSyD93k1uloH-8JzQovk9RnnSGnbcpVyq0Ysirw'

(4) cert-manager Issuer ์ƒ์„ฑ (Vault๋ฅผ Issuer๋กœ ์‚ฌ์šฉ)

  • Vault๋ฅผ ์ธ์ฆ์„œ ๋ฐœ๊ธ‰์ž(Issuer)๋กœ ์„ค์ •
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cat > vault-issuer.yaml <<EOF
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: vault-issuer
  namespace: default
spec:
  vault:
    server: http://vault.vault.svc:8200
    path: pki/sign/example-dot-com
    auth:
      kubernetes:
        mountPath: /v1/auth/kubernetes
        role: issuer
        secretRef:
          name: $ISSUER_SECRET_REF
          key: token
EOF
1
2
3
kubectl apply --filename vault-issuer.yaml

issuer.cert-manager.io/vault-issuer created
  • Issuer ์ƒํƒœ ํ™•์ธ
1
2
3
4
kubectl get issuer.cert-manager.io/vault-issuer

NAME           READY   AGE
vault-issuer   True    15s

(5) Certificate ์ƒ์„ฑ ๋ฐ ๋ฐœ๊ธ‰ ํ™•์ธ

  • Certificate ๋ฆฌ์†Œ์Šค ์ƒ์„ฑ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
cat > example-com-cert.yaml <<EOF
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-com
  namespace: default
spec:
  secretName: $ISSUER_SECRET_REF
  issuerRef:
    name: vault-issuer
  commonName: www.example.com
  dnsNames:
  - www.example.com
EOF
1
2
3
kubectl apply --filename example-com-cert.yaml

certificate.cert-manager.io/example-com created
  • Certificate ์ƒํƒœ ํ™•์ธ
1
2
3
4
kubectl get certificate.cert-manager.io/example-com -owide

NAME          READY   SECRET               ISSUER         STATUS                                          AGE
example-com   True    issuer-token-lmzpj   vault-issuer   Certificate is up to date and has not expired   13s
  • Certificate ์ด๋ฒคํŠธ ํ™•์ธ (ํ‚ค ์ƒ์„ฑ โ†’ CSR โ†’ ๋ฐœ๊ธ‰)
1
2
3
4
5
6
7
8
9
10
kubectl describe certificate.cert-manager example-com

...
Events:
  Type    Reason     Age   From                                       Message
  ----    ------     ----  ----                                       -------
  Normal  Issuing    40s   cert-manager-certificates-trigger          Issuing certificate as Secret does not contain a private key
  Normal  Generated  39s   cert-manager-certificates-key-manager      Stored new private key in temporary Secret resource "example-com-jssf2"
  Normal  Requested  39s   cert-manager-certificates-request-manager  Created new CertificateRequest resource "example-com-74gk6"
  Normal  Issuing    39s   cert-manager-certificates-issuing          The certificate has been successfully issued
  • CertificateRequest ํ™•์ธ
1
2
3
4
kubectl get certificaterequests.cert-manager.io -owide

NAME                APPROVED   DENIED   READY   ISSUER         REQUESTOR                                         STATUS                                         AGE
example-com-74gk6   True                True    vault-issuer   system:serviceaccount:cert-manager:cert-manager   Certificate fetched from issuer successfully   113s

(6) ๋ฐœ๊ธ‰๋œ Secret(tls.crt/tls.key) ํ™•์ธ ๋ฐ openssl ๊ฒ€์ฆ

  • Secret ํ™•์ธ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
kubectl view-secret $ISSUER_SECRET_REF --all

ca.crt='-----BEGIN CERTIFICATE-----
MIIDBTCCAe2gAwIBAgIIfxU/3FwXtREwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKa3ViZXJuZXRlczAeFw0yNTEyMTMxMDE5MzBaFw0zNTEyMTExMDI0MzBaMBUx
EzARBgNVBAMTCmt1YmVybmV0ZXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDjg1VcWK/9XVEMn7HstJClhmlZNYb123eC9vHcDHsk6MtZzAiFVTgizyRa
KYGtZZaUdGVKmVVIVs2r1Vj97X2V/votPHchZ9OJw7h9z/1TWjN3Le8QTwqtYHRm
v3PqaAj5wOUqc/0mUiDg+8icELgh0dXHsvOALNvb8HHgtBNQ2U91RICBgX7bTC8m
dM7fjjbi+FT73wjXXaTWn6dXlXn2LanYxG7eXb087FuFErFdweDPkbAAjVrwNhOz
e8wT7zT73R0seAqMFqckWRWoaufS4Ai+jBOvVhWILywyl1wmUJ5N64Lw22YWbBaA
z9TgZEOaPQ47E6Ay8jYs4OcK7GdjAgMBAAGjWTBXMA4GA1UdDwEB/wQEAwICpDAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQZ8E41JXCiZlobPv8m8Phkd/68uTAV
BgNVHREEDjAMggprdWJlcm5ldGVzMA0GCSqGSIb3DQEBCwUAA4IBAQCoL+CRPjRM
rHYd+UoKVz1P8vQUshTKXDtlc/ZLALszKii61xFCQB9CdGklfvWLNmHA1tX+VtYE
BSno79xB74FnDJxaOl8y0kpt68ZmVNQoZcG0GdBV6/GKkTU2Zrm/y6vI2ncBrhFG
ueTt4fn0QLUjZQLEhWivEt2lpEjGTxYOa0u0IKhRenw1K2/mvIC2CvqFTJJF8lbZ
46PwWRdViL/0aSYS3Od8S7B0aaO/FemHpNjclExKB7XIOGlnIluVP+TFEbTjAlrf
kbU8yxrWrZgGMW1l1AFi/i9T+FKBy8XaNbZq/7+OrQE8wDEnjWM/PmIbmkDOxOim
D6SgjAxin2zW
-----END CERTIFICATE-----
'
namespace='default'
tls.crt='-----BEGIN CERTIFICATE-----
MIIDyzCCArOgAwIBAgIUGYZhNBEaUjfiQ42aZ1tIE0bln84wDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMjUxMjEzMTIxNjM2WhcNMjUx
MjE2MTIxNzA2WjAaMRgwFgYDVQQDEw93d3cuZXhhbXBsZS5jb20wggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCv6Qmc2HmM3QV2i2SoGMaEBJqvIVZ3rNPA
/LNHLNO1+JwExFy0JV+w4ouMG7w8dg3Ilf3yK0I8lBzodBwdpAyuyMK3eIgg7UPr
HD5ANyVu/yDXxQsDANlcRCHyxLWyuu08yICm8HZCT/OfrxlvNNMJ3mkqEyBo7gK+
VEVsGREFNsOp9MT/1eeMUN6AXTLblKNTYl0EUAi3UImk6o3jZ+qO4x7lXfWiGbCb
c9t0vEi7TG+8MmICocRER9jVNMYRm6HkuGQwqAZ+/uC5qa3e0LdEr6VFbc3jJgxB
vzIbA3aJQXx1s3J8K2tfDL3QWzAcmI8s0GYfDbRs+V/n4DzQkgWhAgMBAAGjggEL
MIIBBzAOBgNVHQ8BAf8EBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF
BwMCMB0GA1UdDgQWBBT2re+Vd3AJeaFXebDlvBBgMjrwVDAfBgNVHSMEGDAWgBTe
BCayLtlZta4oYBB8lwOO5m/JljBBBggrBgEFBQcBAQQ1MDMwMQYIKwYBBQUHMAKG
JWh0dHA6Ly92YXVsdC52YXVsdC5zdmM6ODIwMC92MS9wa2kvY2EwGgYDVR0RBBMw
EYIPd3d3LmV4YW1wbGUuY29tMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly92YXVs
dC52YXVsdC5zdmM6ODIwMC92MS9wa2kvY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQDm
N4ZzE3wvpNmHwxrKkZ6A+3t1QSlA7csDkpBBTX707y932CComZsP/UmgGph8GBVX
GD1iCeyRzG/XOE1+tbt0L+87VZyiJD6CE/KSBPlgNLTZQJLCR3We4aP6/Lc44LOY
RzTF71DmrOgzkHm6E47QjpZAL2kVnxFg5VaKiuByL7iH5W0tRabRVW4LBcfBtdqt
4T1Ook1WvniDnNjAhGX7widDpn9rceip4nDLrS2jnKpIFnfS7BHRjLV62UjiqOWW
J56X+fAIFQCpXATJttQ0kZx220M1k8KIDOLzSBHekAMhpBsYfnR7K+YrmNlfDPQg
faUoz1JIyEBz5ux1D/5q
-----END CERTIFICATE-----
'
tls.key='-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAr+kJnNh5jN0FdotkqBjGhASaryFWd6zTwPyzRyzTtficBMRc
tCVfsOKLjBu8PHYNyJX98itCPJQc6HQcHaQMrsjCt3iIIO1D6xw+QDclbv8g18UL
AwDZXEQh8sS1srrtPMiApvB2Qk/zn68ZbzTTCd5pKhMgaO4CvlRFbBkRBTbDqfTE
/9XnjFDegF0y25SjU2JdBFAIt1CJpOqN42fqjuMe5V31ohmwm3PbdLxIu0xvvDJi
AqHEREfY1TTGEZuh5LhkMKgGfv7guamt3tC3RK+lRW3N4yYMQb8yGwN2iUF8dbNy
fCtrXwy90FswHJiPLNBmHw20bPlf5+A80JIFoQIDAQABAoIBAQCW4ib5noBbv7SQ
4q1ata2IzT40mz7EdbxHmzjXAu8w9WY1fIwbhLcYTKjva1bA8W2PMIBaeJpexZgq
FnWLQwwR22eQ4VS6EWkhP99xSxvTogf0qzFvyQmnZ1VLG0jSXh1g9oXLKxP0ewl3
XUROq5ucqmW/zhoNhqFZyYSmXLXJpqrKzIAFOa6A+4gNyOtP2xJMvLP3wqLX4ZBn
JYVfmH+g5EykXdfhLEB2zUDlWhsHBGXxf4Ka3wfnzKZxNPJqbMwSpIizG+j7lkzh
9X1aMsxMC+7jDYNQDJyPoANC+CMJBeZtnGxWwW4itFFMCIJjrlTmiP7XeBO6EXO2
f++tAoUlAoGBAMBGVZl9uGL0zHNNZtT7TlClHETRyfJgTP4NRdYT+iFN7XWg4Wha
vnVNunTRC2k1nXMLzIAO4Fz4boFFsvNclCi3Gz6I6yCVWbg9toqZmIDK2uUo0ZVL
oaw+7MLnBOTL1u1mJE6eJlZrLYG8opt3eBsF56dGRwzTSg/CfU2PplJ7AoGBAOo2
QJ5QYZpigd5HqAbJ9l7QUyleaLE7WIYL+VAO3j6GV312jVr7rPYYOCSiTUlov9Zi
iyAqAXLsdxqzPKOItSkASeMpjxxWJ5FBOQ0Oj31wTq/btAq7552ICBUQUf3MjMIT
/tpCKBb6hNlspA2pEmf9E1tHOiQyCqkMBre5wiuTAoGBAJKiEQ3prwDoqDMWyGGM
9gDSqmhhhZ1ui8kD3kqRGaTkhT+73atz6OQUzynfctBdryHZ0a+nqLu+SqgTu5GU
/PjAC+r5CDflLnMvvVKeKIuwKJezNYKiFz4BDxbkj/rc6aBK0U2TlrE5M49JiMj/
p30UV8Jd+jlxuX2jWWQZNUKZAoGBAIC4BFd9scaZcOpq00u333FIaQwJWNxe004I
cqKvKTGPv7GyYAmq2+n8cY6grH011ojKa8/nhhhVIThJXYA69+VqxTDVfFOEfgZ0
pBgq8m1sNbKsuoxTrP2E73w0Ffu4WXuoZZ4qUcIfOLgN3zOqwfTov6SgxrFx1y4E
8AQ1USOFAoGAUT8xzqCFrOkRRgXu+dqLkGYCg7q4xA+n9TNMQqqCKks4TnJstfKt
LiSut9pjN+yKE8WbhPLXJfDMs/y3W6l9Abgip228E39wIiZ4c5Hwj6xueGjx189A
fzQbCzBHwqLze46tnYnxGjJjo55tcUaLg6dv5gt5XUNHTjs/rtLTf5I=
-----END RSA PRIVATE KEY-----
'
token='eyJhbGciOiJSUzI1NiIsImtpZCI6IklzeVpQdUIwc1Vod282R1lyZ0stZ2Z0aERfZ0dnS1lvTjY3Zng1ODg2bEUifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6Imlzc3Vlci10b2tlbi1sbXpwaiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJpc3N1ZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI4ZDMzMjQzZi0yYzRlLTRiNDgtOGI1MC1kOGI4MTk4NzU2OWMiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDppc3N1ZXIifQ.grg9Eqzj7A_MhWmb3y90_xdlGBUKIaLYJc7mZVw10vtTt0P00495lc6HtxS2IupEIw2-QovWIHPJeU3PQ4qhy_asCeWAjAWDyeYH0NqzIHgkLzDCfN_knPceH5fPqwL6E_tM2B2HBswnFL_hBgxeDrPO4MZ3bKCycoflIcNB2K4-3hLz-Iv5_eNuGXSAvfHheSTybviV5_ikkF6yR2cHE78DmhWKRBcHjONsu7A69wh-ZJdzuOpcp9ayjZmv3rqAiLsE9o_HoE6IlC0gWK9HQN9BvMR2gXmcZtTOFF-v50IWEgzmSyD93k1uloH-8JzQovk9RnnSGnbcpVyq0Ysirw'
  • tls.crt, tls.key ๊ฐ€ ์ถ”๊ฐ€๋˜์–ด TLS Secret ํ˜•ํƒœ๋กœ ํ™•์žฅ๋จ
  • ๊ธฐ์กด token/ca.crt ์™€ ํ•จ๊ป˜ ๋“ค์–ด์žˆ๋Š” ์ƒํƒœ๋กœ ํ™•์ธ๋จ

  • ๋ฐœ๊ธ‰๋œ ์ธ์ฆ์„œ ํ™•์ธ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
echo "-----BEGIN CERTIFICATE-----
MIIDyzCCArOgAwIBAgIUGYZhNBEaUjfiQ42aZ1tIE0bln84wDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMjUxMjEzMTIxNjM2WhcNMjUx
MjE2MTIxNzA2WjAaMRgwFgYDVQQDEw93d3cuZXhhbXBsZS5jb20wggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCv6Qmc2HmM3QV2i2SoGMaEBJqvIVZ3rNPA
/LNHLNO1+JwExFy0JV+w4ouMG7w8dg3Ilf3yK0I8lBzodBwdpAyuyMK3eIgg7UPr
HD5ANyVu/yDXxQsDANlcRCHyxLWyuu08yICm8HZCT/OfrxlvNNMJ3mkqEyBo7gK+
VEVsGREFNsOp9MT/1eeMUN6AXTLblKNTYl0EUAi3UImk6o3jZ+qO4x7lXfWiGbCb
c9t0vEi7TG+8MmICocRER9jVNMYRm6HkuGQwqAZ+/uC5qa3e0LdEr6VFbc3jJgxB
vzIbA3aJQXx1s3J8K2tfDL3QWzAcmI8s0GYfDbRs+V/n4DzQkgWhAgMBAAGjggEL
MIIBBzAOBgNVHQ8BAf8EBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF
BwMCMB0GA1UdDgQWBBT2re+Vd3AJeaFXebDlvBBgMjrwVDAfBgNVHSMEGDAWgBTe
BCayLtlZta4oYBB8lwOO5m/JljBBBggrBgEFBQcBAQQ1MDMwMQYIKwYBBQUHMAKG
JWh0dHA6Ly92YXVsdC52YXVsdC5zdmM6ODIwMC92MS9wa2kvY2EwGgYDVR0RBBMw
EYIPd3d3LmV4YW1wbGUuY29tMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly92YXVs
dC52YXVsdC5zdmM6ODIwMC92MS9wa2kvY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQDm
N4ZzE3wvpNmHwxrKkZ6A+3t1QSlA7csDkpBBTX707y932CComZsP/UmgGph8GBVX
GD1iCeyRzG/XOE1+tbt0L+87VZyiJD6CE/KSBPlgNLTZQJLCR3We4aP6/Lc44LOY
RzTF71DmrOgzkHm6E47QjpZAL2kVnxFg5VaKiuByL7iH5W0tRabRVW4LBcfBtdqt
4T1Ook1WvniDnNjAhGX7widDpn9rceip4nDLrS2jnKpIFnfS7BHRjLV62UjiqOWW
J56X+fAIFQCpXATJttQ0kZx220M1k8KIDOLzSBHekAMhpBsYfnR7K+YrmNlfDPQg
faUoz1JIyEBz5ux1D/5q
-----END CERTIFICATE-----" | openssl x509 -noout -text

# ๊ฒฐ๊ณผ
...
        Version: 3 (0x2)
        Serial Number:
            19:86:61:34:11:1a:52:37:e2:43:8d:9a:67:5b:48:13:46:e5:9f:ce
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=example.com
        Validity
            Not Before: Dec 13 12:16:36 2025 GMT
            Not After : Dec 16 12:17:06 2025 GMT
        Subject: CN=www.example.com
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment, Key Agreement
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Subject Key Identifier: 
                F6:AD:EF:95:77:70:09:79:A1:57:79:B0:E5:BC:10:60:32:3A:F0:54
            X509v3 Authority Key Identifier: 
                DE:04:26:B2:2E:D9:59:B5:AE:28:60:10:7C:97:03:8E:E6:6F:C9:96
            Authority Information Access: 
                CA Issuers - URI:http://vault.vault.svc:8200/v1/pki/ca
            X509v3 Subject Alternative Name: 
                DNS:www.example.com
            X509v3 CRL Distribution Points: 
                Full Name:
                  URI:http://vault.vault.svc:8200/v1/pki/crl
...

(7) ์‹ค์Šต ํ™˜๊ฒฝ ์‚ญ์ œ

1
2
3
4
kind delete cluster --name myk8s

Deleting cluster "myk8s" ...
Deleted nodes: ["myk8s-control-plane"]

๐Ÿ“ˆ Vault HA

1. kind ํด๋Ÿฌ์Šคํ„ฐ ์ƒ์„ฑ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
kind create cluster --name myk8s --image kindest/node:v1.32.8 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  labels:
    ingress-ready: true
  extraPortMappings:
  - containerPort: 80
    hostPort: 80
    protocol: TCP
  - containerPort: 443
    hostPort: 443
    protocol: TCP
  - containerPort: 30000
    hostPort: 30000
  - containerPort: 30001
    hostPort: 30001
- role: worker
- role: worker
- role: worker
EOF
  • control-plane 1 + worker 3 ๊ตฌ์„ฑ
  • ingress-nginx ์‚ฌ์šฉ์„ ์œ„ํ•ด 80/443 ํฌํŠธ ๋งคํ•‘
  • Vault UI NodePort(30000) ๋“ฑ ์ถ”๊ฐ€ ํฌํŠธ ๋งคํ•‘

2. Vault HA(Raft) ๋ฐฐํฌ (Helm)

(1) Vault ๋„ค์ž„์ŠคํŽ˜์ด์Šค ์ƒ์„ฑ

1
2
3
kubectl create ns vault

namespace/vault created

(2) HA values ํŒŒ์ผ ์ž‘์„ฑ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
cat << EOF > values-ha.yaml
server:
  replicas: 3

  # Vault HA mode
  ha:
    enabled: true
    replicas: 3
    raft:
      enabled: true
    config: |
      ui = true
      listener "tcp" {
        tls_disable = 1                           # TLS ๋ฐ”ํ™œ์„ฑํ™”
        address = "[::]:8200"                     # ๋ชจ๋“  IPv6 ์ฃผ์†Œ์—์„œ 8200 ํฌํŠธ ์ˆ˜์‹ 
        cluster_address = "[::]:8201"             # ํด๋Ÿฌ์Šคํ„ฐ ํ†ต์‹  ํฌํŠธ
      }

      service_registration "kubernetes" {}        # Kubernetes ์„œ๋น„์Šค ๋“ฑ๋ก ํ™œ์„ฑํ™”

  readinessProbe:
    enabled: true

  # PVC for Raft storage
  dataStorage:
    enabled: true
    size: 10Gi

  service:
    enabled: true
    type: ClusterIP
    port: 8200
    targetPort: 8200

ui:
  enabled: true
  serviceType: "NodePort"
  externalPort: 8200
  serviceNodePort: 30000

injector:
  enabled: false
EOF

(3) Helm ์„ค์น˜

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
helm install vault hashicorp/vault -n vault -f values-ha.yaml --version 0.31.0

NAME: vault
LAST DEPLOYED: Sat Dec 13 21:31:25 2025
NAMESPACE: vault
STATUS: deployed
REVISION: 1
NOTES:
Thank you for installing HashiCorp Vault!

Now that you have deployed Vault, you should look over the docs on using
Vault with Kubernetes available here:

https://developer.hashicorp.com/vault/docs

Your release is named vault. To learn more about the release, try:

  $ helm status vault
  $ helm get manifest vault

(4) ํŒŒ๋“œ ํ™•์ธ (์ดˆ๊ธฐ์—๋Š” sealed ์ƒํƒœ๋กœ READY 0/1)

1
2
3
4
5
6
kubectl get pods --selector='app.kubernetes.io/name=vault' -n vault

NAME      READY   STATUS    RESTARTS   AGE
vault-0   0/1     Running   0          54s
vault-1   0/1     Running   0          54s
vault-2   0/1     Running   0          54s

(5) PVC/PV ํ™•์ธ (Raft ์ €์žฅ์†Œ)

1
2
3
4
5
6
7
8
9
10
11
kubectl get pvc,pv -n vault

NAME                                 STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/data-vault-0   Bound    pvc-e26f3651-acbc-487d-9912-a6689e37be54   10Gi       RWO            standard       <unset>                 79s
persistentvolumeclaim/data-vault-1   Bound    pvc-2b8ff3ff-00f2-4608-9aae-deb4c3466146   10Gi       RWO            standard       <unset>                 79s
persistentvolumeclaim/data-vault-2   Bound    pvc-87b1459a-57fa-418a-a9db-c9e38c562c2a   10Gi       RWO            standard       <unset>                 79s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
persistentvolume/pvc-2b8ff3ff-00f2-4608-9aae-deb4c3466146   10Gi       RWO            Delete           Bound    vault/data-vault-1   standard       <unset>                          75s
persistentvolume/pvc-87b1459a-57fa-418a-a9db-c9e38c562c2a   10Gi       RWO            Delete           Bound    vault/data-vault-2   standard       <unset>                          74s
persistentvolume/pvc-e26f3651-acbc-487d-9912-a6689e37be54   10Gi       RWO            Delete           Bound    vault/data-vault-0   standard       <unset>                          74s

(6) ๋กœ๊ทธ ํ™•์ธ

1
2
3
4
5
6
7
8
9
kubectl stern -n vault vault-0

vault-0 vault 2025-12-13T12:32:14.261Z [INFO]  proxy environment: http_proxy="" https_proxy="" no_proxy=""
vault-0 vault 2025-12-13T12:32:14.303Z [INFO]  incrementing seal generation: generation=1
vault-0 vault 2025-12-13T12:32:14.304Z [INFO]  core: Initializing version history cache for core
vault-0 vault 2025-12-13T12:32:14.304Z [INFO]  events: Starting event system
vault-0 vault 2025-12-13T12:32:21.224Z [INFO]  core: security barrier not initialized
vault-0 vault 2025-12-13T12:32:21.224Z [INFO]  core: seal configuration missing, not initialized
...

3. Vault ์ดˆ๊ธฐํ™”(Init) ๋ฐ Unseal

(1) vault-0์—์„œ init ์ˆ˜ํ–‰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
kubectl exec -it vault-0 -n vault -- sh
/ $ vault operator init

# ๊ฒฐ๊ณผ
Unseal Key 1: xQJkGsv1g56rQSudtzSxdNb+muak/+pUxFN6b3qebebx
Unseal Key 2: dH7qGm95bZ1e85HHwNuM7Kn1o+eRzNbl4OugM057Xozk
Unseal Key 3: NJ4x0W1Ej/8zhL47FttZBZ5eOrgB11f5vYpgrqvFRWW8
Unseal Key 4: icUHy7ZeA3xd9qJDdnm23GqLCNfkBAINlPK4PTMllnG9
Unseal Key 5: ZH3QQ2M5iHmC3HQV3zKPYeHqTpGka/eEqyc/LF8i+aRi

Initial Root Token: hvs.xxxxxxxxxxxxxxxxxxxxxxxx

Vault initialized with 5 key shares and a key threshold of 3. Please securely
distribute the key shares printed above. When the Vault is re-sealed,
restarted, or stopped, you must supply at least 3 of these keys to unseal it
before it can start servicing requests.

Vault does not store the generated root key. Without at least 3 keys to
reconstruct the root key, Vault will remain permanently sealed!

It is possible to generate new unseal keys, provided you have a quorum of
existing unseal keys shares. See "vault operator rekey" for more information.
  • 5๊ฐœ key share, threshold 3
  • Root Token ์ƒ์„ฑ

(2) ์ดˆ๊ธฐ ์ƒํƒœ ํ™•์ธ (Initialized true, Sealed true)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/ $ vault status

Key                     Value
---                     -----
Seal Type               shamir
Initialized             true
Sealed                  true
Total Shares            5
Threshold               3
Unseal Progress         0/3
Unseal Nonce            n/a
Version                 1.20.4
Build Date              2025-09-23T13:22:38Z
Storage Type            raft
Removed From Cluster    false
HA Enabled              true

(3) Unseal key 3๊ฐœ ์ž…๋ ฅ ํ›„ sealed ํ•ด์ œ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/ $ vault operator unseal

Unseal Key (will be hidden): xQJkGsv1g56rQSudtzSxdNb+muak/+pUxFN6b3qebebx
Key                     Value
---                     -----
Seal Type               shamir
Initialized             true
Sealed                  true
Total Shares            5
Threshold               3
Unseal Progress         1/3
Unseal Nonce            47c5ba3e-67b2-fec0-aad5-75dce91a47ba
Version                 1.20.4
Build Date              2025-09-23T13:22:38Z
Storage Type            raft
Removed From Cluster    false
HA Enabled              true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/ $ vault operator unseal
Unseal Key (will be hidden): dH7qGm95bZ1e85HHwNuM7Kn1o+eRzNbl4OugM057Xozk
Key                     Value
---                     -----
Seal Type               shamir
Initialized             true
Sealed                  true
Total Shares            5
Threshold               3
Unseal Progress         2/3
Unseal Nonce            47c5ba3e-67b2-fec0-aad5-75dce91a47ba
Version                 1.20.4
Build Date              2025-09-23T13:22:38Z
Storage Type            raft
Removed From Cluster    false
HA Enabled              true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/ $ vault operator unseal
Unseal Key (will be hidden): NJ4x0W1Ej/8zhL47FttZBZ5eOrgB11f5vYpgrqvFRWW8
Key                     Value
---                     -----
Seal Type               shamir
Initialized             true
Sealed                  false
Total Shares            5
Threshold               3
Version                 1.20.4
Build Date              2025-09-23T13:22:38Z
Storage Type            raft
Cluster Name            vault-cluster-3a0a246b
Cluster ID              043fc090-6a7c-9198-a998-786196e8b66b
Removed From Cluster    false
HA Enabled              true
HA Cluster              https://vault-0.vault-internal:8201
HA Mode                 active
Active Since            2025-12-13T12:38:01.768089484Z
Raft Committed Index    37
Raft Applied Index      37

/ $ exit
  • threshold(3) ์ถฉ์กฑ ์‹œ Sealed: false
  • HA Mode๊ฐ€ active ๋กœ ํ‘œ์‹œ๋จ
1
2
3
4
5
6
kubectl get pods --selector='app.kubernetes.io/name=vault' -n vault

NAME      READY   STATUS    RESTARTS   AGE
vault-0   1/1     Running   0          8m12s
vault-1   0/1     Running   0          8m12s
vault-2   0/1     Running   0          8m12s

4. Raft Join + ๋‚˜๋จธ์ง€ ๋…ธ๋“œ Unseal

(1) vault-1, vault-2๋ฅผ raft cluster์— join

1
2
3
4
5
kubectl exec -n vault -it vault-1 -- vault operator raft join http://vault-0.vault-internal:8200

Key       Value
---       -----
Joined    true
1
2
3
4
5
kubectl exec -n vault -it vault-2 -- vault operator raft join http://vault-0.vault-internal:8200

Key       Value
---       -----
Joined    true

(2) vault-0์—์„œ root token ๋กœ๊ทธ์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kubectl exec -it vault-0 -n vault -- sh

/ $ vault login
Token (will be hidden): hvs.xxxxxxxxxxxxxxxxxxxxxxxx
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                  Value
---                  -----
token                hvs.xxxxxxxxxxxxxxxxxxxxxxxx
token_accessor       MAW2uNjALwZpAnu7tOBBjTBJ
token_duration       โˆž
token_renewable      false
token_policies       ["root"]
identity_policies    []
policies             ["root"]

์•„์ง ๋ฆฌ๋” ํ•˜๋‚˜๋งŒ ์กด์žฌ

1
2
3
4
5
6
7
/ $ vault operator raft list-peers

Node                                    Address                        State     Voter
----                                    -------                        -----     -----
3af25dff-1555-27bf-7f59-cbd55aa89448    vault-0.vault-internal:8201    leader    true

/ $ exit

(3) join๋งŒ์œผ๋กœ๋Š” follower ํŒŒ๋“œ๊ฐ€ sealed ์ƒํƒœ์ด๋ฏ€๋กœ, vault-1/2 ๊ฐ๊ฐ unseal ํ•„์š”

1
2
3
4
5
6
7
8
9
10
kubectl exec -it vault-1 -n vault -- sh

---------------------------------------
vault status
vault operator unseal
vault operator unseal
vault operator unseal
vault status
exit
---------------------------------------
1
2
3
4
5
6
kubectl get pods --selector='app.kubernetes.io/name=vault' -n vault

NAME      READY   STATUS    RESTARTS   AGE
vault-0   1/1     Running   0          15m
vault-1   1/1     Running   0          15m
vault-2   0/1     Running   0          15m
1
2
3
4
5
6
7
8
9
10
kubectl exec -it vault-2 -n vault -- sh

---------------------------------------
vault status
vault operator unseal
vault operator unseal
vault operator unseal
vault status
exit
---------------------------------------
1
2
3
4
5
6
kubectl get pods --selector='app.kubernetes.io/name=vault' -n vault

NAME      READY   STATUS    RESTARTS   AGE
vault-0   1/1     Running   0          17m
vault-1   1/1     Running   0          17m
vault-2   1/1     Running   0          17m
  • 3๊ฐœ ๋ชจ๋‘ READY 1/1 ํ™•์ธ

5. ๋กœ์ปฌ์—์„œ UI(NodePort)๋กœ Vault ์ ‘์† ๋ฐ Peer ํ™•์ธ

(1) ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ • (NodePort 30000)

1
2
export VAULT_ROOT_TOKEN=hvs.xxxxxxxxxxxxxxxxxxxxxxxx
export VAULT_ADDR='http://localhost:30000'

(2) ๋กœ๊ทธ์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
vault login
Token (will be hidden): hvs.xxxxxxxxxxxxxxxxxxxxxxxx
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                  Value
---                  -----
token                hvs.xxxxxxxxxxxxxxxxxxxxxxxx
token_accessor       MAW2uNjALwZpAnu7tOBBjTBJ
token_duration       โˆž
token_renewable      false
token_policies       ["root"]
identity_policies    []
policies             ["root"]

(3) raft peers ํ™•์ธ (leader 1 + follower 2)

1
2
3
4
5
6
7
vault operator raft list-peers

Node                                    Address                        State       Voter
----                                    -------                        -----       -----
3af25dff-1555-27bf-7f59-cbd55aa89448    vault-0.vault-internal:8201    leader      true
d7c30a24-4f01-b4ce-6752-b8dbf3e43e06    vault-1.vault-internal:8201    follower    true
a42215a1-ae84-cb7a-e981-33f720810d9e    vault-2.vault-internal:8201    follower    true

6. KV v2 ์‹œํฌ๋ฆฟ ์—”์ง„ ํ™œ์„ฑํ™” ๋ฐ ๋ฐ์ดํ„ฐ ์ €์žฅ/์กฐํšŒ

(1) KV v2 ํ™œ์„ฑํ™”

1
2
3
vault secrets enable -path=mysecret kv-v2

Success! Enabled the kv-v2 secrets engine at: mysecret/

(2) ์ƒ˜ํ”Œ ์‹œํฌ๋ฆฟ ์ €์žฅ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
vault kv put mysecret/logins/study \
  username="demo" \
  password="p@ssw0rd"

# ๊ฒฐ๊ณผ
======= Secret Path =======
mysecret/data/logins/study

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-13T12:52:13.376179475Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

(3) ๋ฐ์ดํ„ฐ ์กฐํšŒ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
vault kv get mysecret/logins/study

# ๊ฒฐ๊ณผ
======= Secret Path =======
mysecret/data/logins/study

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-13T12:52:13.376179475Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

====== Data ======
Key         Value
---         -----
password    p@ssw0rd
username    demo

7. ์ •์ฑ…(Policy) ์ƒ์„ฑ ์˜ˆ์‹œ

1
2
3
4
5
6
7
vault policy write restrict_study - <<EOF
path "mysecret/logins/study*" {
capabilities = ["deny"]
}
EOF

Success! Uploaded policy: restrict_study
  • mysecret/logins/study* ๊ฒฝ๋กœ๋ฅผ deny ์ฒ˜๋ฆฌํ•˜๋Š” ์ •์ฑ… ์ƒ์„ฑ

๐Ÿชช Vault Auth with LDAP

1. OpenLDAP ์„œ๋ฒ„ ๋ฐฐํฌ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
  name: openldap
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: openldap
  namespace: openldap
spec:
  replicas: 1
  selector:
    matchLabels:
      app: openldap
  template:
    metadata:
      labels:
        app: openldap
    spec:
      containers:
        - name: openldap
          image: osixia/openldap:1.5.0
          ports:
            - containerPort: 389
              name: ldap
            - containerPort: 636
              name: ldaps
          env:
            - name: LDAP_ORGANISATION    # ๊ธฐ๊ด€๋ช…, LDAP ๊ธฐ๋ณธ ์ •๋ณด ์ƒ์„ฑ ์‹œ ์‚ฌ์šฉ
              value: "Example Org"
            - name: LDAP_DOMAIN          # LDAP ๊ธฐ๋ณธ Base DN ์„ ์ž๋™ ์ƒ์„ฑ
              value: "example.org"
            - name: LDAP_ADMIN_PASSWORD  # LDAP ๊ด€๋ฆฌ์ž ํŒจ์Šค์›Œ๋“œ
              value: "admin"
            - name: LDAP_CONFIG_PASSWORD
              value: "admin"
        - name: phpldapadmin
          image: osixia/phpldapadmin:0.9.0
          ports:
            - containerPort: 80
              name: phpldapadmin
          env:
            - name: PHPLDAPADMIN_HTTPS
              value: "false"
            - name: PHPLDAPADMIN_LDAP_HOSTS
              value: "openldap"   # LDAP hostname inside cluster
---
apiVersion: v1
kind: Service
metadata:
  name: openldap
  namespace: openldap
spec:
  selector:
    app: openldap
  ports:
    - name: phpldapadmin
      port: 80
      targetPort: 80
      nodePort: 30001
    - name: ldap
      port: 389
      targetPort: 389
    - name: ldaps
      port: 636
      targetPort: 636
  type: NodePort
EOF

namespace/openldap created
deployment.apps/openldap created
service/openldap created
1
2
3
4
5
6
7
8
9
10
11
12
13
kubectl get deploy,pod,svc,ep -n openldap

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/openldap   1/1     1            1           24s

NAME                            READY   STATUS    RESTARTS   AGE
pod/openldap-54857b746c-9q6xd   2/2     Running   0          24s

NAME               TYPE       CLUSTER-IP    EXTERNAL-IP   PORT(S)                                    AGE
service/openldap   NodePort   10.96.1.204   <none>        80:30001/TCP,389:31824/TCP,636:31736/TCP   24s

NAME                 ENDPOINTS                                     AGE
endpoints/openldap   10.244.1.4:80,10.244.1.4:389,10.244.1.4:636   24s

2. OpenLDAP ์กฐ์ง๋„ ๊ตฌ์„ฑ

(1) OpenLDAP ์ปจํ…Œ์ด๋„ˆ ์ง„์ž…

1
2
kubectl -n openldap exec -it deploy/openldap -c openldap -- bash
root@openldap-54857b746c-9q6xd:/# 

(2) OU ์ƒ์„ฑ(organizationalUnit)

1
2
3
4
5
6
7
8
9
10
11
root@openldap-54857b746c-9q6xd:/# cat <<EOF | ldapadd -x -D "cn=admin,dc=example,dc=org" -w admin
> dn: ou=people,dc=example,dc=org
> objectClass: organizationalUnit
> ou: people
> 
> dn: ou=groups,dc=example,dc=org
> objectClass: organizationalUnit
> ou: groups
> EOF
adding new entry "ou=people,dc=example,dc=org"
adding new entry "ou=groups,dc=example,dc=org"

(3) ์‚ฌ์šฉ์ž ์ƒ์„ฑ (inetOrgPerson)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
root@openldap-54857b746c-9q6xd:/# cat <<EOF | ldapadd -x -D "cn=admin,dc=example,dc=org" -w admin
> dn: uid=alice,ou=people,dc=example,dc=org
> objectClass: inetOrgPerson
> cn: Alice
> sn: Kim
> uid: alice
> mail: alice@example.org
> userPassword: alice123
> 
> dn: uid=bob,ou=people,dc=example,dc=org
> objectClass: inetOrgPerson
> cn: Bob
> sn: Lee
> uid: bob
> mail: bob@example.org
> userPassword: bob123
> EOF
adding new entry "uid=alice,ou=people,dc=example,dc=org"
adding new entry "uid=bob,ou=people,dc=example,dc=org"

(4) ๊ทธ๋ฃน ์ƒ์„ฑ ๋ฐ ๋ฉค๋ฒ„ ์ง€์ •

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
root@openldap-54857b746c-9q6xd:/# cat <<EOF | ldapadd -x -D "cn=admin,dc=example,dc=org" -w admin
> dn: cn=devs,ou=groups,dc=example,dc=org
> objectClass: groupOfNames
> cn: devs
> member: uid=bob,ou=people,dc=example,dc=org
> 
> dn: cn=admins,ou=groups,dc=example,dc=org
> objectClass: groupOfNames
> cn: admins
> member: uid=alice,ou=people,dc=example,dc=org
> EOF
adding new entry "cn=devs,ou=groups,dc=example,dc=org"
adding new entry "cn=admins,ou=groups,dc=example,dc=org"

root@openldap-54857b746c-9q6xd:/# exit

(5) phpLDAPadmin ์ ‘์† ์ •๋ณด

1
2
3
4
http://127.0.0.1:30001

Login DN: cn=admin,dc=example,dc=org
Password: admin


3. Vault Auth with LDAP ์„ค์ •

(1) LDAP Auth ํ™œ์„ฑํ™”

1
2
3
vault auth enable ldap

Success! Enabled ldap auth method at: ldap/
1
2
3
4
5
6
vault auth list

Path      Type     Accessor               Description                Version
----      ----     --------               -----------                -------
ldap/     ldap     auth_ldap_96da3d21     n/a                        n/a
token/    token    auth_token_1257fa38    token based credentials    n/a

(2) LDAP Config ์„ค์ •

1
2
3
4
5
6
7
8
9
10
11
12
vault write auth/ldap/config \
    url="ldap://openldap.openldap.svc:389" \
    starttls=false \
    insecure_tls=true \
    binddn="cn=admin,dc=example,dc=org" \
    bindpass="admin" \
    userdn="ou=people,dc=example,dc=org" \
    groupdn="ou=groups,dc=example,dc=org" \
    groupfilter="(member=uid=,ou=people,dc=example,dc=org)" \
    groupattr="cn"
    
Success! Data written to: auth/ldap/config

(3) LDAP ์ธ์ฆ ํ…Œ์ŠคํŠธ

  • alice ๋กœ๊ทธ์ธ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vault login -method=ldap username=alice

Password (will be hidden): alice123
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                    Value
---                    -----
token                  hvs.xxxxxxxxxxxxxxxxxxxxxxxx
token_accessor         vzIEcUCjwQ1VtrwZOmt1fV82
token_duration         768h
token_renewable        true
token_policies         ["default"]
identity_policies      []
policies               ["default"]
token_meta_username    alice
  • ์ •์ฑ… ํ™•์ธ
1
2
3
4
5
vault token lookup -format=json | jq .data.policies

[
  "default"
]
  • ํ† ํฐ ์ƒ์„ธ ํ™•์ธ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
vault token lookup

Key                 Value
---                 -----
accessor            vzIEcUCjwQ1VtrwZOmt1fV82
creation_time       1765631074
creation_ttl        768h
display_name        ldap-alice
entity_id           d87709c5-2f64-8c84-e492-7c178a142f9c
expire_time         2026-01-14T13:04:34.472217287Z
explicit_max_ttl    0s
id                  hvs.xxxxxxxxxxxxxxxxxxxxxxxx
issue_time          2025-12-13T13:04:34.472223442Z
meta                map[username:alice]
num_uses            0
orphan              true
path                auth/ldap/login/alice
policies            [default]
renewable           true
ttl                 767h58m57s
type                service
  • bob ๋กœ๊ทธ์ธ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
vault login -method=ldap username=bob password=bob123

Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                    Value
---                    -----
token                  hvs.xxxxxxxxxxxxxxxxxxxxxxxx
token_accessor         YvMHJWu4hpQlDRmYUVoqeRV2
token_duration         768h
token_renewable        true
token_policies         ["default"]
identity_policies      []
policies               ["default"]
token_meta_username    bob
  • ํ† ํฐ ์ƒ์„ธ ํ™•์ธ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
vault token lookup

Key                 Value
---                 -----
accessor            YvMHJWu4hpQlDRmYUVoqeRV2
creation_time       1765631164
creation_ttl        768h
display_name        ldap-bob
entity_id           4ba5864b-0bc3-533d-cd42-e9419500a41b
expire_time         2026-01-14T13:06:04.60339669Z
explicit_max_ttl    0s
id                  hvs.xxxxxxxxxxxxxxxxxxxxxxxx
issue_time          2025-12-13T13:06:04.603404694Z
meta                map[username:bob]
num_uses            0
orphan              true
path                auth/ldap/login/bob
policies            [default]
renewable           true
ttl                 767h59m45s
type                service

4. LDAP ๊ทธ๋ฃน โ†’ Vault Policy ๋งคํ•‘

(1) bob ํ† ํฐ์œผ๋กœ policy ์ž‘์„ฑ ์‹œ๋„ ์‹œ 403 ๋ฐœ์ƒ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
vault policy write admin - <<EOF
path "*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
EOF

Error uploading policy: Error making API request.

URL: PUT http://localhost:30000/v1/sys/policies/acl/admin
Code: 403. Errors:

* 1 error occurred:
	* permission denied

(2) ํ˜„์žฌ ์‚ฌ์šฉ ํ† ํฐ ํ™•์ธ - bob

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
vault token lookup

Key                 Value
---                 -----
accessor            YvMHJWu4hpQlDRmYUVoqeRV2
creation_time       1765631164
creation_ttl        768h
display_name        ldap-bob
entity_id           4ba5864b-0bc3-533d-cd42-e9419500a41b
expire_time         2026-01-14T13:06:04.60339669Z
explicit_max_ttl    0s
id                  hvs.xxxxxxxxxxxxxxxxxxxxxxxx
issue_time          2025-12-13T13:06:04.603404694Z
meta                map[username:bob]
num_uses            0
orphan              true
path                auth/ldap/login/bob
policies            [default]
renewable           true
ttl                 767h57m39s
type                service
  • ํ˜„์žฌ ํ† ํฐ์ด default ์ •์ฑ…๋ฟ์ด๋ผ sys/policies/acl/* ์“ฐ๊ธฐ ๊ถŒํ•œ์ด ์—†์–ด ๊ฑฐ๋ถ€๋จ

(3) root ํ† ํฐ์œผ๋กœ ์žฌ๋กœ๊ทธ์ธ ํ›„ ์ •์ฑ… ์ƒ์„ฑ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
vault login

Token (will be hidden): hvs.xxxxxxxxxxxxxxxxxxxxxxxx
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                  Value
---                  -----
token                hvs.xxxxxxxxxxxxxxxxxxxxxxxx
token_accessor       MAW2uNjALwZpAnu7tOBBjTBJ
token_duration       โˆž
token_renewable      false
token_policies       ["root"]
identity_policies    []
policies             ["root"]
  • admin ์ •์ฑ… ์ƒ์„ฑ
1
2
3
4
5
6
7
vault policy write admin - <<EOF
path "*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
EOF

Success! Uploaded policy: admin
  • ์ •์ฑ… ํ™•์ธ
1
2
3
4
5
vault policy read admin

path "*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}

(4) LDAP admins ๊ทธ๋ฃน์— admin ์ •์ฑ… ์—ฐ๊ฒฐ

1
2
3
vault write auth/ldap/groups/admins policies=admin

Success! Data written to: auth/ldap/groups/admins

(5) alice ์žฌ๋กœ๊ทธ์ธ ํ›„ ์ •์ฑ… ์ ์šฉ ํ™•์ธ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
vault login -method=ldap username=alice password=alice123

Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                    Value
---                    -----
token                  hvs.xxxxxxxxxxxxxxxxxxxxxxxx
token_accessor         FdLxDMguy6rXssFmLyHRNlyf
token_duration         768h
token_renewable        true
token_policies         ["admin" "default"]
identity_policies      []
policies               ["admin" "default"]
token_meta_username    alice
  • ์ถœ๋ ฅ์—์„œ token_policies / policies์— admin์ด ํฌํ•จ๋˜๋Š”์ง€ ํ™•์ธํ•จ
  • LDAP admins ๊ทธ๋ฃน์— alice๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ Vault ์ •์ฑ…์ด ํ•จ๊ป˜ ๋ถ€์—ฌ๋˜๋Š” ํ๋ฆ„์ž„

(6) ์ •์ฑ… ๋‚ด์šฉ ํ™•์ธ

1
2
3
4
5
vault policy read admin

path "*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}

5. ํด๋Ÿฌ์Šคํ„ฐ ์‚ญ์ œ

1
2
3
4
kind delete cluster --name myk8s

Deleting cluster "myk8s" ...
Deleted nodes: ["myk8s-worker2" "myk8s-worker" "myk8s-worker3" "myk8s-control-plane"]

๐Ÿ“œ Vault Server TLS

1. kind ํด๋Ÿฌ์Šคํ„ฐ ์ƒ์„ฑ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kind create cluster --name myk8s --image kindest/node:v1.32.8 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  labels:
    ingress-ready: true
  extraPortMappings:
  - containerPort: 80
    hostPort: 80
    protocol: TCP
  - containerPort: 443
    hostPort: 443
    protocol: TCP
  - containerPort: 30000
    hostPort: 30000
EOF

2. ingress-nginx ๋ฐฐํฌ ๋ฐ SSL Passthrough ํ™œ์„ฑํ™”

(1) NGINX ingress ๋ฐฐํฌ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml

namespace/ingress-nginx created
serviceaccount/ingress-nginx created
serviceaccount/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
configmap/ingress-nginx-controller created
service/ingress-nginx-controller created
service/ingress-nginx-controller-admission created
deployment.apps/ingress-nginx-controller created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created
ingressclass.networking.k8s.io/nginx created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created

(2) SSL Passthrough ํ”Œ๋ž˜๊ทธ ํ™œ์„ฑํ™”

1
2
3
4
5
kubectl get deployment ingress-nginx-controller -n ingress-nginx -o yaml \
| sed '/- --publish-status-address=localhost/a\
        - --enable-ssl-passthrough' | kubectl apply -f -

deployment.apps/ingress-nginx-controller configured

(3) nodeSelector ์ง€์ •

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
kubectl patch deployment ingress-nginx-controller -n ingress-nginx \
  --type='merge' \
  -p='{
    "spec": {
      "template": {
        "spec": {
          "nodeSelector": {
            "ingress-ready": "true"
          }
        }
      }
    }
  }'

deployment.apps/ingress-nginx-controller patched

3. Helm ์„ค์น˜ ๋ฐ Vault Helm Repo ์ค€๋น„

1
2
3
4
5
6
7
8
9
10
11
12
13
docker exec -it myk8s-control-plane bash
root@myk8s-control-plane:/# curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

root@myk8s-control-plane:/# helm version
version.BuildInfo{Version:"v3.19.3", GitCommit:"0707f566a3f4ced24009ef14d67fe0ce69db4be9", GitTreeState:"clean", GoVersion:"go1.24.10"}

root@myk8s-control-plane:/# helm repo add hashicorp https://helm.releases.hashicorp.com
"hashicorp" has been added to your repositories

root@myk8s-control-plane:/# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "hashicorp" chart repository
Update Complete. โŽˆHappy Helming!โŽˆ
  • kind control-plane ์ปจํ…Œ์ด๋„ˆ์— helm ์„ค์น˜ ํ›„ hashicorp repo ๋“ฑ๋กํ•จ

4. CA ๋ฐ Vault ์„œ๋ฒ„ ์ธ์ฆ์„œ ์ƒ์„ฑ

(1) CA ์ƒ์„ฑ

1
2
3
4
5
6
7
8
9
10
root@myk8s-control-plane:/# mkdir /tls && cd /tls
root@myk8s-control-plane:/tls# openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes \
  -key ca.key \
  -subj "/CN=Vault-CA" \
  -days 3650 \
  -out ca.crt

root@myk8s-control-plane:/tls#  ls
ca.crt	ca.key

(2) Vault ์„œ๋ฒ„ ํ‚ค/CSR ์ƒ์„ฑ

1
2
3
4
5
6
7
root@myk8s-control-plane:/tls# openssl genrsa -out vault.key 2048
openssl req -new -key vault.key \
  -subj "/CN=vault.example.com" \
  -out vault.csr

root@myk8s-control-plane:/tls# ls
ca.crt	ca.key	vault.csr  vault.key

(3) CA๋กœ ์„œ๋ช… + SAN ์ง€์ •

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
root@myk8s-control-plane:/tls# openssl x509 -req \
  -in vault.csr \
  -CA ca.crt \
  -CAkey ca.key \
  -CAcreateserial \
  -out vault.crt \
  -days 3650 \
  -extensions v3_req \
  -extfile <(cat <<EOF
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = vault.example.com
DNS.2 = vault
DNS.3 = localhost
DNS.4 = vault.vault.svc.cluster.local
DNS.5 = vault.vault-internal
DNS.6 = vault-0.vault-internal
DNS.7 = vault-1.vault-internal
DNS.8 = vault-2.vault-internal
DNS.9 = 127.0.0.1
EOF
)

# ๊ฒฐ๊ณผ
Certificate request self-signature ok
subject=CN = vault.example.com

5. Kubernetes Secret ๋“ฑ๋ก

1
2
3
4
5
6
7
root@myk8s-control-plane:/tls# kubectl create namespace vault
kubectl -n vault create secret tls vault-tls \
  --cert=vault.crt \
  --key=vault.key
  
namespace/vault created
secret/vault-tls created
1
2
3
4
root@myk8s-control-plane:/tls# kubectl -n vault create secret generic vault-ca \
  --from-file=ca.crt=ca.crt

secret/vault-ca created
  • Vault ์ฐจํŠธ์—์„œ ์‚ฌ์šฉํ•  TLS Secret(vault-tls)๊ณผ CA Secret(vault-ca)์„ ์ƒ์„ฑํ•จ
    • vault-tls: ์„œ๋ฒ„ ์ธ์ฆ์„œ/ํ‚ค
    • vault-ca: init/ํด๋ผ์ด์–ธํŠธ ๊ฒ€์ฆ์šฉ CA

6. Vault TLS(HTTPS) Helm ์„ค์น˜

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
root@myk8s-control-plane:/tls# cat << EOF > values-tls.yaml
global:
  tlsDisable: false
  
injector:
  enabled: false

server:
  volumes:
    - name: vault-tls
      secret:
        secretName: vault-tls
    - name: vault-ca
      secret:
        secretName: vault-ca
  volumeMounts:
    - name: vault-tls
      mountPath: /vault/user-tls
      readOnly: true
    - name: vault-ca
      mountPath: /vault/user-ca
      readOnly: true

  # Vault HTTPS listener ์„ค์ •
  standalone:
    enabled: "true"
    config: |-
      ui = true
      listener "tcp" {
        tls_disable = 0
        address = "0.0.0.0:8200"
        cluster_address = "0.0.0.0:8201"
        tls_cert_file    = "/vault/user-tls/tls.crt"
        tls_key_file     = "/vault/user-tls/tls.key"
      }
      storage "file" {
        path = "/vault/data"
      }
      api_addr     = "https://vault.vault.svc.cluster.local:8200"
      cluster_addr = "https://vault-0.vault-internal:8201"
EOF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
root@myk8s-control-plane:/tls# helm install vault hashicorp/vault -n vault -f values-tls.yaml --version 0.29.0

NAME: vault
LAST DEPLOYED: Sat Dec 13 13:27:17 2025
NAMESPACE: vault
STATUS: deployed
REVISION: 1
NOTES:
Thank you for installing HashiCorp Vault!

Now that you have deployed Vault, you should look over the docs on using
Vault with Kubernetes available here:

https://developer.hashicorp.com/vault/docs

Your release is named vault. To learn more about the release, try:

  $ helm status vault
  $ helm get manifest vault
1
2
3
4
5
6
7
root@myk8s-control-plane:/tls# helm list -A

NAME 	NAMESPACE	REVISION	UPDATED                               	STATUS  	CHART       	APP VERSION
vault	vault    	1       	2025-12-13 13:27:17.58887749 +0000 UTC	deployed	vault-0.29.0	1.18.1

root@myk8s-control-plane:/tls# exit
exit
1
2
3
4
kubectl get pod -n vault

NAME      READY   STATUS    RESTARTS   AGE
vault-0   0/1     Running   0          91s

7. Vault status TLS ๊ฒ€์ฆ ์˜ค๋ฅ˜ ๋ฐ ์šฐํšŒ ํ™•์ธ

1
2
3
4
kubectl exec -ti vault-0 -n vault -- vault status

Error checking seal status: Get "https://127.0.0.1:8200/v1/sys/seal-status": tls: failed to verify certificate: x509: cannot validate certificate for 127.0.0.1 because it doesn't contain any IP SANs
command terminated with exit code 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kubectl exec -ti vault-0 -n vault -- vault status -tls-skip-verify

Key                Value
---                -----
Seal Type          shamir
Initialized        false
Sealed             true
Total Shares       0
Threshold          0
Unseal Progress    0/0
Unseal Nonce       n/a
Version            1.18.1
Build Date         2024-10-29T14:21:31Z
Storage Type       file
HA Enabled         false
command terminated with exit code 2
  • Pod ๋‚ด๋ถ€์—์„œ vault status ์‹คํ–‰ ์‹œ, ๊ธฐ๋ณธ ์š”์ฒญ ๋Œ€์ƒ์ด https://127.0.0.1:8200์ด์–ด์„œ ์ธ์ฆ์„œ์— IP SAN(127.0.0.1) ์ด ์—†๋‹ค๊ณ  ํŒ๋‹จ๋˜๋ฉด ๊ฒ€์ฆ์ด ์‹คํŒจํ•  ์ˆ˜ ์žˆ์Œ
  • ๋”ฐ๋ผ์„œ ์ดˆ๊ธฐ ํ™•์ธ์€ -tls-skip-verify๋กœ ์ง„ํ–‰ํ•จ

8. Vault init/unseal ๋ฐ Root Token ํ™•์ธ

1
2
3
4
kubectl exec vault-0 -n vault -- vault operator init -tls-skip-verify \
    -key-shares=1 \
    -key-threshold=1 \
    -format=json > cluster-keys.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
VAULT_UNSEAL_KEY=$(jq -r ".unseal_keys_b64[]" cluster-keys.json)
kubectl exec vault-0 -n vault -- vault operator unseal -tls-skip-verify $VAULT_UNSEAL_KEY

Key             Value
---             -----
Seal Type       shamir
Initialized     true
Sealed          false
Total Shares    1
Threshold       1
Version         1.18.1
Build Date      2024-10-29T14:21:31Z
Storage Type    file
Cluster Name    vault-cluster-75aab1d3
Cluster ID      030421b1-a0d4-9d26-c3f4-d670a89f6b8a
HA Enabled      false
  • Root Token ํ™•์ธ
1
2
3
jq -r ".root_token" cluster-keys.json

hvs.xxxxxxxxxxxxxxxxxxxxxxxx

9. NodePort(30000)๋กœ Vault ์™ธ๋ถ€ ๋…ธ์ถœ ๋ฐ ์ ‘์† ํ™•์ธ

1
2
3
kubectl patch svc -n vault vault -p '{"spec":{"type":"NodePort","ports":[{"port":8200,"targetPort":8200,"nodePort":30000}]}}' 

service/vault patched
1
export VAULT_ADDR='https://localhost:30000'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
vault status -tls-skip-verify

Key             Value
---             -----
Seal Type       shamir
Initialized     true
Sealed          false
Total Shares    1
Threshold       1
Version         1.18.1
Build Date      2024-10-29T14:21:31Z
Storage Type    file
Cluster Name    vault-cluster-75aab1d3
Cluster ID      030421b1-a0d4-9d26-c3f4-d670a89f6b8a
HA Enabled      false
  • Root Token์œผ๋กœ ๋กœ๊ทธ์ธ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
vault login -tls-skip-verify

Token (will be hidden): hvs.xxxxxxxxxxxxxxxxxxxxxxxx
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                  Value
---                  -----
token                hvs.xxxxxxxxxxxxxxxxxxxxxxxx
token_accessor       tphoWlDINUNf5TBBhJlSt0Pk
token_duration       โˆž
token_renewable      false
token_policies       ["root"]
identity_policies    []
policies             ["root"]

10. ingress-nginx SSL Passthrough๋กœ HTTPS ๋ผ์šฐํŒ…

(1) hosts ๋“ฑ๋ก

1
echo "127.0.0.1 vault.example.com" | sudo tee -a /etc/hosts

(2) Vault Pod ํฌํŠธ ํ™•์ธ

1
2
3
4
kubectl describe pod -n vault vault-0 | grep -i ports

    Ports:         8200/TCP (https), 8201/TCP (https-internal), 8202/TCP (https-rep)
    Host Ports:    0/TCP (https), 0/TCP (https-internal), 0/TCP (https-rep)

(3) Ingress ์ƒ์„ฑ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: vault-https
  namespace: vault
  annotations:
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
  ingressClassName: "nginx"
  rules:
    - host: vault.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: vault
                port:
                  number: 8200
  tls:
    - hosts:
        - vault.example.com
      secretName: vault-tls
EOF

ingress.networking.k8s.io/vault-https created

(4) Ingress ํ™•์ธ

1
2
3
4
kubectl get ingress -n vault

NAME          CLASS   HOSTS               ADDRESS     PORTS     AGE
vault-https   nginx   vault.example.com   localhost   80, 443   25s
  • HTTP๋กœ ์ ‘๊ทผ ์‹œ HTTPS๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋˜๋Š” ๋™์ž‘ ํ™•์ธํ•จ
1
http://vault.example.com

This post is licensed under CC BY 4.0 by the author.