DevOps/Study

CI/CD Study 8주차 Vault (1/2)

juyeon22 2025. 12. 13. 22:36

CI/CD Study 8주차 Vault (1/2)

가시다님이 운영하시는 CI/CD Study 8주차(Vault/VSO on K8S) 내용을 정리한 게시글 입니다.

1. 실습 환경 구성

8주차 Vault를 학습하기 위해 필요한 지식 안내 및 Vault에 대한 간단한 소개를 해보겠습니다.


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

# Setup Helm repo
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update

# Create a Kubernetes namespace.
kubectl create namespace vault


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

helm upgrade vault hashicorp/vault -n vault -f vault-values.yaml --install --version 0.31.0

# Unseal Key 초기화
kubectl exec vault-0 -n vault -- vault operator init \
    -key-shares=1 \
    -key-threshold=1 \
    -format=json > cluster-keys.json

# Unseal Key 확인
jq -r ".unseal_keys_b64[]" cluster-keys.json
# oT6m3/M0bq/tPYD8Z7DpTxpYqOOy/IhkrA6Qzf50r2E=

# unseal key 변수 저장
VAULT_UNSEAL_KEY=$(jq -r ".unseal_keys_b64[]" cluster-keys.json)

# Unseal
kubectl exec vault-0 -n vault -- vault operator unseal $VAULT_UNSEAL_KEY

# root_token 확인
jq -r ".root_token" cluster-keys.json
# hvs.oa6P2GniLQREwEgQB0aRXsUh

# NodePort로 공개한 30000 NodePort로 설정
export VAULT_ADDR='http://localhost:30000'

# Root Token으로 로그인
vault login
# Token (will be hidden): hvs.oa6P2GniLQREwEgQB0aRXsUh
# Success! You are now authenticated. 

# Vault UI 접속
wslview http://127.0.0.1:30000

# audit 용 pv(pvc) 에 저장될 수 있게 file audit log 설정
# Vault Audit 로그만 동작하지 않는게 아니라 Vault 자체 동작 수행 중지 (일반적인 Log와는 다름)
vault audit enable file file_path=/vault/logs/audit.log
# Success! Enabled the file audit device at: file/

2. Vault on k8s

k8s 에서 Secret인증과 Vault 접근 프로세스는 다음과 같습니다.

Vault는 Pod생성 시 할당된 Token(k8s에서 신뢰 하는 Token)을 사용하여 인증을 진행합니다.

Pod가 Vault에 접근 시 해당 Token을 사용하여 접근 후 Vault는 k8s API에 호출하여 정상적인 Token인지 확인 합니다.

API서버는 Namespace와 Service Account 정보를 Vault에게 리턴을 해줍니다.

Vault는 리턴받은 정보를 통해 접근 가능한 Secret과 Policy가 적용된 Token을 Pod에게 리턴하여 Vault에 접근할 수 있도록 해줍니다.

Figure 2.1 k8s에서 작동하는 vault 설명

실습

# Secret Engine 생성
vault secrets enable -path=secret kv-v2
# Success! Enabled the kv-v2 secrets engine at: secret/

# Secret 값 추가
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-03T12:42:21.03907989Z

# Secret 확인
export VAULT_ROOT_TOKEN=hvs.oa6P2GniLQREwEgQB0aRXsUh

curl -s --header "X-Vault-Token: $VAULT_ROOT_TOKEN" --request GET \
  http://127.0.0.1:30000/v1/secret/data/webapp/config | jq

  # ....
  # "data": {
  #   "data": {
  #     "password": "static-password",
  #     "username": "static-user"
  #   },
  # ....

Vault를 이용한 k8s Authentication 설정

현재 Engine 에 적용된 설정은 webapp Pod가 secret/data/webapp/config 경로에 존재하는 username, password 정보를 읽기만 할 수 있는 Token을 사용할 수 있또록 해보겠습니다.

# RBAC 툴 미설 치 되어있는 경우 설치
kubectl krew install rbac-tool rolesum

kubectl rbac-tool lookup vault
#   SUBJECT | SUBJECT TYPE   | SCOPE       | NAMESPACE | ROLE                  | BINDING
# ----------+----------------+-------------+-----------+-----------------------+-----------------------
#   vault   | ServiceAccount | ClusterRole |           | system:auth-delegator | vault-server-binding

kubectl rolesum vault -n vault
# ServiceAccount: vault/vault
# • [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         [*]     [-]     [-]   ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖

# k8s Auth Enable 설정
vault auth enable kubernetes
# Success! Enabled kubernetes auth method at: kubernetes/

# K8S API 서버 정보 설정 : 현재 vault 가 k8s 에 설치되어 있으므로, 아래처럼 서비스명 주소 입력 가능
vault write auth/kubernetes/config \
    kubernetes_host="https://kubernetes.default.svc"

# Success! Data written to: auth/kubernetes/config

# 클라이언트가 secret/webapp/config에서 정의된 비밀 데이터에 접근 설정
vault policy write webapp - <<EOF
path "secret/data/webapp/config" {
  capabilities = ["read"]
}
EOF
# Success! Uploaded policy: webapp

# Kubernetes 서비스 계정 이름과 웹앱 정책을 연결하는 웹앱이라는 이름의 Kubernetes 인증 역할 생성
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

Sample Web Applcation 실행

Kubernetes Service Token을 읽고 Vault에 Login 후 Secret값을 가져오는 Applicaiton 입니다.

# vault 서비스 어카운트 생성
kubectl create sa vault

# 웹 애플리케이션 디플로이먼트 + 서비스(NodePort) 배포
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
---
apiVersion: v1
kind: Service
metadata:
  name: webapp
spec:
  selector:
    app: webapp
  type: NodePort
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
    nodePort: 30001
EOF

# 서비스 어카운트 토큰 확인 : 600초(10분)마다 갱신됨
kubectl exec -it deploy/webapp -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
# eyJhbGciOiJSUzI1NiIsImtpZCI6InpWWHhPa1R6NVRGWldNUlE5aDQ2eVlITlN4TDlRcHFYYXNCbkw0c0t2dzAifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzY0NzY5MjE5LCJpYXQiOjE3NjQ3Njg2MTksImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiMTY0YmM1NTgtZGNlZi00Y2FhLTk4YzgtY2VmYjE2MzJmNDllIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwibm9kZSI6eyJuYW1lIjoibXlrOHMtY29udHJvbC1wbGFuZSIsInVpZCI6ImZiZmI3YjVkLWUyMWItNDE1OS04OWFhLTk2NTU5YjMzYzhkZCJ9LCJwb2QiOnsibmFtZSI6IndlYmFwcC04OWRiYzY3NmMtN2JxOWIiLCJ1aWQiOiJjMzY5MmFlNS0yMzJjLTRjMmItODVjYS01ZDVhNDVmMTE4ZTIifSwic2VydmljZWFjY291bnQiOnsibmFtZSI6InZhdWx0IiwidWlkIjoiZjEwMjc0NTgtMWQ0MC00MDU5LTk1N2ItODY3MDA0MzhmMzEwIn19LCJuYmYiOjE3NjQ3Njg2MTksInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OnZhdWx0In0.kU_JXNeNDFI9XsnReEO_v8_uogNGQwWeWgd773uN7PP7sGapjW-XnQW1LC0O4DQBomyZ4j1JAQwOZZUwoktPXwMI6AHRdXLkjv4FBorH9XLHbIDzQix9A6Ew-fDcX8U-4r9u-cqYnCwXOonYHJJMISE5HNvTbW9070fN5Q0HRtg6kjI4xgo_9Hh6uWiBLTRZPLVghV4iN01PQePskqaZcdYb1nrbOBsDp3wcAvy6dKvwV8Wo7BYUOCtUWqRqpDkZ0BXcLbOWbjlpuvBWM4ychV3LdeFWL3bvKEdcO3Y3pOkfKmjQODWO5gOpxEU01D3K3kmjmHrN99l88m5C9szsBw(

# 웹 애플리케이션 접속 동작 확인 : 접속 시 토큰 정보를 확인 후 vault 서버에 로그인 후 시크릿 정보 가져와서 http 출력
curl 127.0.0.1:30001
# password:static-password username:static-user


# Secret 변경
vault kv put secret/webapp/config username="changed-user" password="changed-password"

# 변경된 정보 확인
curl 127.0.0.1:30001
# password:changed-password username:changed-user 

# 실습 환경제거
kind delete cluster --name myk8s

3. VSO(Vault Secrets Operator)

✅ VSO 란?

Vault의 시크릿을 Kubernetes Native Secret으로 동기화 해주는 Operator 입니다. 사용자가 VSO CRD를 생성하게 되면, Operator가 감지하여, Vault에 존재하는 Secret을 kubernetes Secret Resource로 변환을 합니다.


Figure 3.1 VSO 설명

VSO를 구성하는 주요 CRD는 다음과 같습니다.

  • VaultConnection

    • Vault 서버의 주소와 통신 설정을 정의
  • VaultAuth

    • Kubernetes Auth Method 등을 통해 Vault에 인증하는 방식을 정의
  • VaultStaticSecret

    • 정적 시크릿을 동기화할 때 사용
  • VaultDynamicSecret

    • 수명이 있고 동적으로 생성되는 시크릿을 관리
  • VaultPKISecret

    • PKI 엔진을 통해 인증서를 발급받고 동기화
sequenceDiagram
    autonumber
    participant DevOps as DevOps Engineer
    participant K8s as K8s API (Etcd)
    participant VSO as Vault Secrets Operator
    participant Vault as HashiCorp Vault
    participant App as Target App (Pod)

    Note over DevOps, K8s: 1. CRD 배포 단계
    DevOps->>K8s: Apply 'VaultAuth' & 'VaultStaticSecret' (CRD)

    Note over K8s, VSO: 2. 감지 및 인증 단계
    VSO->>K8s: Watch Event (새로운 CRD 감지)
    VSO->>Vault: 인증 요청 (예: Kubernetes Auth Method)
    Vault-->>VSO: Vault Token 발급

    Note over VSO, Vault: 3. 시크릿 인출(Fetch) 단계
    VSO->>Vault: 시크릿 데이터 요청 (Get Secret Path)
    Vault-->>VSO: 시크릿 데이터 반환 (JSON)

    Note over VSO, K8s: 4. 동기화 단계 (Critical Point)
    VSO->>VSO: 데이터 포맷 변환 (Vault → K8s Secret)
    VSO->>K8s: Native K8s 'Secret' 리소스 생성/업데이트
    Note right of K8s: ⚠️ 시크릿이 Etcd에 저장됨<br/>(보안 취약점 발생 가능 구간)

    Note over K8s, App: 5. 애플리케이션 소비 단계
    App->>K8s: Secret 마운트 (envFrom 또는 volumeMount)

    Note over VSO, Vault: 6. 갱신 루프 (Reconciliation)
    loop Refresh Period / TTL Expire
        VSO->>Vault: 변경 사항 확인 (Polling)
        opt 변경 감지 시
            VSO->>K8s: K8s Secret 업데이트
            K8s-->>App: (설정에 따라) Pod 재기동 또는 값 갱신
        end
    end

Figure 3.2 VSO 작동 흐름 설명

VSO 실습 환경 구성

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

# 노드에 기본 툴 설치
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'

# VSO 실습용 Repo Clone
git clone https://github.com/hashicorp-education/learn-vault-secrets-operator
cd learn-vault-secrets-operator

# Value.yaml 정의
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

# vault 설치
helm install vault hashicorp/vault -n vault --create-namespace --values vault-values.yaml --version 0.30.0
export VAULT_ADDR='http://localhost:30000'
vault login
# Token (will be hidden): root

# kubernetes 인증 활성화
vault auth enable -path demo-auth-mount kubernetes
# Success! Enabled kubernetes auth method at: demo-auth-mount/
vault write auth/demo-auth-mount/config kubernetes_host="https://kubernetes.default.svc"
# Success! Data written to: auth/demo-auth-mount/config

# 시크릿(엔진v2) 활성화
vault secrets enable -path=kvv2 kv-v2
# Success! Enabled the kv-v2 secrets engine at: kvv2/

# Vault policy JSON파일로 생성
tee webapp.json <<EOF
path "kvv2/data/webapp/config" {
   capabilities = ["read", "list"]
}
EOF
vault policy write webapp webapp.json
# Success! Uploaded policy: webapp

# Role Mapping 적용
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

# Secret 생성
vault kv put kvv2/webapp/config username="static-user" password="static-password"

# VSO 설치
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

# VSO 파드에 서비스 어카운트가 사용 가능한 Role 확인
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

static secret 실습

VaultAuth CRD와 VaultStaticSecret CRD 사용 실습에 대해서 진행해보겠습니다.

Figure 3.3 Static Secret 실습 개요

실습 시나리오는 다음과 같습니다.

  1. Vault 에 Secret/Policy/Role 생성
  2. VSO 가 → Vault 에 로그인 후 Token 받음
    • VSO가 Vault 에 로그인 과정은 VaultAuth CRD를 통해서 작동
  3. VSO 가 전달받은 Token 으로 담아 → Vault 에 Secret 요청 후 받음
    • VSO가 Vault 에 Secret 요청 과정은 VaultStaticSecret CRD를 통해서 작동
  4. VSO 는 K8S Secret 에 값을 업데이트
  5. VSO 는 주기적으로(현재 설정은 30초 마다) → Vault 에 Secret 요청 후 받음
# Deploy and sync a secret
# Name Space 생성
kubectl create ns app

# CRD 확인
kubectl explain vaultauths.spec


# VaultAuth, ServiceAccount 정보 확인
cat vault/vault-auth-static.yaml


# SeviceAccount, VaultAuth 적용
kubectl apply -f vault/vault-auth-static.yaml

# CRD 확인 : vaultstaticsecrets
kubectl explain vaultstaticsecrets.spec

# VaultStaticSecret 정보 확인
cat vault/static-secret.yaml

# VaultStaticSecret 적용
kubectl apply -f vault/static-secret.yaml

# K8S Secret 값 확인
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-12T04:46:09.262712484Z","custom_metadata":null,"deletion_time":"","destroyed":false,"version":1}}'
# password='static-password'
# username='static-user'

# 시크릿 업데이트 Rotate the secret.
vault kv put kvv2/webapp/config username="static-user2" password="static-password2"

kubectl view-secret -n app secretkv --all
# _raw='{"data":{"password":"static-password2","username":"static-user2"},"metadata":{"created_time":"2025-12-12T05:30:03.683919828Z","custom_metadata":null,"deletion_time":"","destroyed":false,"version":2}}'
# password='static-password2'
# username='static-user2'

# 신규 생성이 아닌 리소스 data 의 값만 변경
kubectl get secret -n app
# NAME       TYPE     DATA   AGE
# secretkv   Opaque   3      10m

dynamic secret 실습

VaultAuth CRD와 VaultDynamicSecret CRD 사용 실습에 대해서 진행해보겠습니다.

Figure 3.4 Dynamic Secret 실습 개요

시나리오는 Vault 가 자동으로 암호를 갱신(삭제/재생성)하고, VSO가 해당 암호를 K8S Secret 에 동기화하는 방식입니다.

# PostgreSQL 파드 배포 및 Vault Database Secret Engine 설정

kubectl create ns postgres

# Helm Repo 추가
helm repo add bitnami https://charts.bitnami.com/bitnami
# Install PostgreSQL : 암호 secret-pass
helm upgrade --install postgres bitnami/postgresql --namespace postgres --set auth.audit.logConnections=true  --set auth.postgresPassword=secret-pass

# Enable an instance of the Database Secrets Engine
vault secrets enable -path=demo-db database
# Success! Enabled the database secrets engine at: demo-db/

# Configure the Database Secrets Engine
vault write demo-db/config/demo-db \
   plugin_name=postgresql-database-plugin \
   allowed_roles="dev-postgres" \
   connection_url="postgresql://{{username}}:{{password}}@postgres-postgresql.postgres.svc.cluster.local:5432/postgres?sslmode=disable" \
   username="postgres" \
   password="secret-pass"

# Success! Data written to: demo-db/config/demo-db

# DB 사용자 동적 생성 Role 등록
vault write demo-db/roles/dev-postgres \
   db_name=demo-db \
   creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
      GRANT ALL PRIVILEGES ON DATABASE postgres TO \"{{name}}\";" \
   revocation_statements="REVOKE ALL ON DATABASE postgres FROM  \"{{name}}\";" \
   backend=demo-db \
   name=dev-postgres \
   default_ttl="10m" \
   max_ttl="20m"

# Success! Data written to: demo-db/roles/dev-postgres

# Create the demo-auth-policy-db policy.
vault policy write demo-auth-policy-db - <<EOF
path "demo-db/creds/dev-postgres" {
   capabilities = ["read"]
}
EOF
# Success! Uploaded policy: demo-auth-policy-db

# Create a new role for the dynamic secret.
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

kubectl create ns demo-ns

kubectl apply -f dynamic-secrets/.

# 파드에 /etc/secrets 정보 확인
kubectl exec -it deploy/vso-db-demo -n demo-ns -- ls -al /etc/secrets
# drwxrwxrwt 3 root root  140 Dec 12 05:56 .
# drwxr-xr-x 1 root root 4096 Dec 12 05:57 ..
# drwxr-xr-x 2 root root  100 Dec 12 05:56 ..2025_12_12_05_56_48.2640089308
# lrwxrwxrwx 1 root root   32 Dec 12 05:56 ..data -> ..2025_12_12_05_56_48.2640089308
# lrwxrwxrwx 1 root root   11 Dec 12 05:56 _raw -> ..data/_raw
# lrwxrwxrwx 1 root root   15 Dec 12 05:56 password -> ..data/password
# lrwxrwxrwx 1 root root   15 Dec 12 05:56 username -> ..data/username

kubectl exec -it deploy/vso-db-demo -n demo-ns -- cat /etc/secrets/username ; echo
# v-demo-aut-dev-post-TDy0uwaLwz6PaOdc5ggo-1765519008

kubectl exec -it deploy/vso-db-demo -n demo-ns -- cat /etc/secrets/password ; echo
# nJVVOej8UPvhe6ai-XMO

kubectl view-secret -n demo-ns vso-db-demo --all
# _raw='{"password":"nJVVOej8UPvhe6ai-XMO","username":"v-demo-aut-dev-post-TDy0uwaLwz6PaOdc5ggo-1765519008"}'
# password='nJVVOej8UPvhe6ai-XMO'
# username='v-demo-aut-dev-post-TDy0uwaLwz6PaOdc5ggo-1765519008'

# psql 확인
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-0CoXMAxhavrs20jK4J0e-1765519008 | Password valid until 2025-12-12 06:06:53+00
#  v-demo-aut-dev-post-TDy0uwaLwz6PaOdc5ggo-1765519008 | Password valid until 2025-12-12 06:06:53+00
#  v-demo-aut-dev-post-YuvcHnB4OeBGHSCr7aWr-1765519008 | Password valid until 2025-12-12 06:06:53+00
#  v-demo-aut-dev-post-xt0MGD8JJaaw8QVSZzs2-1765519008 | Password valid until 2025-12-12 06:06:53+00

# (10분 정도 이후) 2차 K8S Secret 확인
kubectl view-secret -n demo-ns vso-db-demo --all
# _raw='{"password":"lZMP-hIIKSVs7AjpF3dj","username":"v-demo-aut-dev-post-RoET0Jz9yffX44PmGhtP-1765519979"}'
# password='lZMP-hIIKSVs7AjpF3dj'
# username='v-demo-aut-dev-post-RoET0Jz9yffX44PmGhtP-1765519979'