๐งฉ 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"
}
|
(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)
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-v2mount: 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_statementsCREATE 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๋ ์ค์ ์ฑ์ด ์ฝ๋ Secretvso-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
|
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 ์ค์น
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
|
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
|
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
|
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 ๊ฒ์ฆ
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'
|
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 ์ธ์ฆ ํ
์คํธ
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
|
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"]
|
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
|
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
|