본문 바로가기
STUDY - CICD

8주차 (1) - Hashicorp Vault/VSO on K8S

by gaji3 2025. 12. 13.

** 가시다님이 진행하는 CI/CD Study 내용을 기반으로 정리 및 작성하였습니다.

 

1. Vault 설치 on K8S

K8S (kind) 설치

(⎈|N/A:N/A) gaji:~$ kind create cluster --name myk8s
Creating cluster "myk8s" ...
 ✓ Ensuring node image (kindest/node:v1.34.0) 🖼 
 ✓ Preparing nodes 📦  
 ✓ Writing configuration 📜 
 ✓ Starting control-plane 🕹️ 
 ✓ Installing CNI 🔌 
 ✓ Installing StorageClass 💾 
Set kubectl context to "kind-myk8s"
You can now use your cluster with:

kubectl cluster-info --context kind-myk8s

Thanks for using kind! 😊

# 설치 확인
(⎈|kind-myk8s:N/A) gaji:~$ docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED          STATUS          PORTS                       NAMES
4ece87249b61   kindest/node:v1.34.0   "/usr/local/bin/entr…"   49 seconds ago   Up 47 seconds   127.0.0.1:33517->6443/tcp   myk8s-control-plane

(⎈|kind-myk8s:N/A) gaji:~$ kubectl get node
NAME                  STATUS   ROLES           AGE   VERSION
myk8s-control-plane   Ready    control-plane   41s   v1.34.0


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

 

 

Vault 설치 - Docs , Helm

# Setup Helm repo
(⎈|kind-myk8s:N/A) gaji:~$ helm repo add hashicorp https://helm.releases.hashicorp.com
"hashicorp" already exists with the same configuration, skipping

(⎈|kind-myk8s:N/A) gaji:~$ helm repo update

Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "hashicorp" chart repository
...Successfully got an update from the "argo" chart repository
...Successfully got an update from the "prometheus-community" chart repository
...Successfully got an update from the "geek-cookbook" chart repository
...Successfully got an update from the "bitnami" chart repository
Update Complete. ⎈Happy Helming!⎈

(⎈|kind-myk8s:N/A) gaji:~$ 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.0.1           1.0.1           Official Vault Secrets Operator Chart


# Create a Kubernetes namespace.
(⎈|kind-myk8s:N/A) gaji:~$ kubectl create namespace vault
namespace/vault created


# 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 설치
(⎈|kind-myk8s:N/A) gaji:~$ helm upgrade vault hashicorp/vault -n vault -f vault-values.yaml --install --dry-run=client
Release "vault" does not exist. Installing it now.
NAME: vault
LAST DEPLOYED: Fri Dec  5 22:13:54 2025
NAMESPACE: vault
STATUS: pending-install
REVISION: 1
HOOKS:
---
# Source: vault/templates/tests/server-test.yaml
apiVersion: v1
kind: Pod
metadata:
  name: vault-server-test
  namespace: vault
  annotations:
    "helm.sh/hook": test
spec:

  containers:
    - name: vault-server-test
      image: hashicorp/vault:1.20.4
      imagePullPolicy: IfNotPresent
      env:
        - name: VAULT_ADDR
          value: http://vault.vault.svc:8200

      command:
        - /bin/sh
        - -c
        - |
          echo "Checking for sealed info in 'vault status' output"
          ATTEMPTS=10
          n=0
          until [ "$n" -ge $ATTEMPTS ]
          do
            echo "Attempt" $n...
            vault status -format yaml | grep -E '^sealed: (true|false)' && break
            n=$((n+1))
            sleep 5
          done
          if [ $n -ge $ATTEMPTS ]; then
            echo "timed out looking for sealed info in 'vault status' output"
            exit 1
          fi

          exit 0
  restartPolicy: Never
MANIFEST:
---
# Source: vault/templates/server-serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: vault
  namespace: vault
  labels:
    helm.sh/chart: vault-0.31.0
    app.kubernetes.io/name: vault
    app.kubernetes.io/instance: vault
    app.kubernetes.io/managed-by: Helm
---
# Source: vault/templates/server-config-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: vault-config
  namespace: vault
  labels:
    helm.sh/chart: vault-0.31.0
    app.kubernetes.io/name: vault
    app.kubernetes.io/instance: vault
    app.kubernetes.io/managed-by: Helm
data:
  extraconfig-from-values.hcl: |-
    ui = true

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

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

    disable_mlock = true
---
# Source: vault/templates/server-clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: vault-server-binding
  labels:
    helm.sh/chart: vault-0.31.0
    app.kubernetes.io/name: vault
    app.kubernetes.io/instance: vault
    app.kubernetes.io/managed-by: Helm
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:auth-delegator
subjects:
- kind: ServiceAccount
  name: vault
  namespace: vault
---
# Source: vault/templates/server-headless-service.yaml
# Service for Vault cluster
apiVersion: v1
kind: Service
metadata:
  name: vault-internal
  namespace: vault
  labels:
    helm.sh/chart: vault-0.31.0
    app.kubernetes.io/name: vault
    app.kubernetes.io/instance: vault
    app.kubernetes.io/managed-by: Helm
    vault-internal: "true"
  annotations:

spec:
  clusterIP: None
  publishNotReadyAddresses: true
  ports:
    - name: "http"
      port: 8200
      targetPort: 8200
    - name: https-internal
      port: 8201
      targetPort: 8201
  selector:
    app.kubernetes.io/name: vault
    app.kubernetes.io/instance: vault
    component: server
---
# Source: vault/templates/server-service.yaml
# Service for Vault cluster
apiVersion: v1
kind: Service
metadata:
  name: vault
  namespace: vault
  labels:
    helm.sh/chart: vault-0.31.0
    app.kubernetes.io/name: vault
    app.kubernetes.io/instance: vault
    app.kubernetes.io/managed-by: Helm
  annotations:

spec:
  type: NodePort
  externalTrafficPolicy: Cluster
  # We want the servers to become available even if they're not ready
  # since this DNS is also used for join operations.
  publishNotReadyAddresses: true
  ports:
    - name: http
      port: 8200
      targetPort: 8200
      nodePort: 30000
    - name: https-internal
      port: 8201
      targetPort: 8201
  selector:
    app.kubernetes.io/name: vault
    app.kubernetes.io/instance: vault
    component: server
---
# Source: vault/templates/ui-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: vault-ui
  namespace: vault
  labels:
    helm.sh/chart: vault-0.31.0
    app.kubernetes.io/name: vault-ui
    app.kubernetes.io/instance: vault
    app.kubernetes.io/managed-by: Helm
spec:
  selector:
    app.kubernetes.io/name: vault
    app.kubernetes.io/instance: vault
    component: server
  publishNotReadyAddresses: true
  ports:
    - name: http
      port: 8200
      targetPort: 8200
  type: ClusterIP
---
# Source: vault/templates/server-statefulset.yaml
# StatefulSet to run the actual vault server cluster.
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: vault
  namespace: vault
  labels:
    app.kubernetes.io/name: vault
    app.kubernetes.io/instance: vault
    app.kubernetes.io/managed-by: Helm
spec:
  serviceName: vault-internal
  podManagementPolicy: Parallel
  replicas: 1
  updateStrategy:
    type: OnDelete
  selector:
    matchLabels:
      app.kubernetes.io/name: vault
      app.kubernetes.io/instance: vault
      component: server
  template:
    metadata:
      labels:
        helm.sh/chart: vault-0.31.0
        app.kubernetes.io/name: vault
        app.kubernetes.io/instance: vault
        component: server
      annotations:
    spec:

      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchLabels:
                  app.kubernetes.io/name: vault
                  app.kubernetes.io/instance: "vault"
                  component: server
              topologyKey: kubernetes.io/hostname




      terminationGracePeriodSeconds: 10
      serviceAccountName: vault

      securityContext:
        runAsNonRoot: true
        runAsGroup: 1000
        runAsUser: 100
        fsGroup: 1000
      hostNetwork: false
      volumes:

        - name: config
          configMap:
            name: vault-config

        - name: home
          emptyDir: {}
      containers:
        - name: vault

          image: hashicorp/vault:1.20.4
          imagePullPolicy: IfNotPresent
          command:
          - "/bin/sh"
          - "-ec"
          args:
          - |
            cp /vault/config/extraconfig-from-values.hcl /tmp/storageconfig.hcl;
            [ -n "${HOST_IP}" ] && sed -Ei "s|HOST_IP|${HOST_IP?}|g" /tmp/storageconfig.hcl;
            [ -n "${POD_IP}" ] && sed -Ei "s|POD_IP|${POD_IP?}|g" /tmp/storageconfig.hcl;
            [ -n "${HOSTNAME}" ] && sed -Ei "s|HOSTNAME|${HOSTNAME?}|g" /tmp/storageconfig.hcl;
            [ -n "${API_ADDR}" ] && sed -Ei "s|API_ADDR|${API_ADDR?}|g" /tmp/storageconfig.hcl;
            [ -n "${TRANSIT_ADDR}" ] && sed -Ei "s|TRANSIT_ADDR|${TRANSIT_ADDR?}|g" /tmp/storageconfig.hcl;
            [ -n "${RAFT_ADDR}" ] && sed -Ei "s|RAFT_ADDR|${RAFT_ADDR?}|g" /tmp/storageconfig.hcl;
            /usr/local/bin/docker-entrypoint.sh vault server -config=/tmp/storageconfig.hcl

          securityContext:
            allowPrivilegeEscalation: false
          env:
            - name: HOST_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.hostIP
            - name: POD_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP
            - name: VAULT_K8S_POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: VAULT_K8S_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            - name: VAULT_ADDR
              value: "http://127.0.0.1:8200"
            - name: VAULT_API_ADDR
              value: "http://$(POD_IP):8200"
            - name: SKIP_CHOWN
              value: "true"
            - name: SKIP_SETCAP
              value: "true"
            - name: HOSTNAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: VAULT_CLUSTER_ADDR
              value: "https://$(HOSTNAME).vault-internal:8201"
            - name: HOME
              value: "/home/vault"



          volumeMounts:

            - name: audit
              mountPath: /vault/logs



            - name: data
              mountPath: /vault/data



            - name: config
              mountPath: /vault/config

            - name: home
              mountPath: /home/vault
          ports:
            - containerPort: 8200
              name: http
            - containerPort: 8201
              name: https-internal
            - containerPort: 8202
              name: http-rep
          readinessProbe:
            # Check status; unsealed vault servers return 0
            # The exit code reflects the seal status:
            #   0 - unsealed
            #   1 - error
            #   2 - sealed
            exec:
              command: ["/bin/sh", "-ec", "vault status -tls-skip-verify"]
            failureThreshold: 2
            initialDelaySeconds: 5
            periodSeconds: 5
            successThreshold: 1
            timeoutSeconds: 3
          lifecycle:
            # Vault container doesn't receive SIGTERM from Kubernetes
            # and after the grace period ends, Kube sends SIGKILL.  This
            # causes issues with graceful shutdowns such as deregistering itself
            # from Consul (zombie services).
            preStop:
              exec:
                command:
                - "/bin/sh"
                - "-c"
                # Adding a sleep here to give the pod eviction a
                # chance to propagate, so requests will not be made
                # to this pod while it's terminating
                - "sleep 5 && kill -SIGTERM $(pidof vault)"


  volumeClaimTemplates:
    - metadata:
        name: data


      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 10Gi

    - metadata:
        name: audit


      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 10Gi

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
  
  

(⎈|kind-myk8s:N/A) gaji:~$ helm upgrade vault hashicorp/vault -n vault -f vault-values.yaml --install --version 0.31.0
Release "vault" does not exist. Installing it now.
I1205 22:14:02.720752    6028 warnings.go:110] "Warning: spec.SessionAffinity is ignored for headless services"
NAME: vault
LAST DEPLOYED: Fri Dec  5 22:14:02 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



# 배포확인 : vault-0 파드는 기동 시 Readiness Probe 체크 실패 상태
## (참고) Readiness:      exec [/bin/sh -ec vault status -tls-skip-verify]
(⎈|kind-myk8s:N/A) gaji:~$ kubectl get sts,pods,svc,ep,pvc,cm -n vault
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
NAME                     READY   AGE
statefulset.apps/vault   0/1     57s

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

NAME                     TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                         AGE
service/vault            NodePort    10.96.251.171   <none>        8200:30000/TCP,8201:31550/TCP   57s
service/vault-internal   ClusterIP   None            <none>        8200/TCP,8201/TCP               57s
service/vault-ui         ClusterIP   10.96.34.118    <none>        8200/TCP                        57s

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

NAME                                  STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/audit-vault-0   Bound    pvc-6d320a6f-f940-41b1-beee-47d8c4d3ebe7   10Gi       RWO            standard       <unset>                 57s
persistentvolumeclaim/data-vault-0    Bound    pvc-df82f2c8-6589-4040-bac2-4832d314a926   10Gi       RWO            standard       <unset>                 57s

NAME                         DATA   AGE
configmap/kube-root-ca.crt   1      108s
configmap/vault-config       1      57s


# Vault Status 명령으로 Sealed 상태확인
(⎈|kind-myk8s:N/A) gaji:~$ 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 로그 확인
(⎈|kind-myk8s:N/A) gaji:~$ kubectl stern -n vault -l app.kubernetes.io/name=vault
+ vault-0 › vault
vault-0 vault ==> Vault server configuration:
vault-0 vault
vault-0 vault Administrative Namespace:
vault-0 vault              Api Address: http://10.244.0.7:8200
vault-0 vault                      Cgo: disabled
vault-0 vault          Cluster Address: https://vault-0.vault-internal:8201
vault-0 vault    Environment Variables: HOME, HOSTNAME, HOST_IP, KUBERNETES_PORT, KUBERNETES_PORT_443_TCP, KUBERNETES_PORT_443_TCP_ADDR, KUBERNETES_PORT_443_TCP_PORT, KUBERNETES_PORT_443_TCP_PROTO, KUBERNETES_SERVICE_HOST, KUBERNETES_SERVICE_PORT, KUBERNETES_SERVICE_PORT_HTTPS, NAME, PATH, POD_IP, PWD, SHLVL, SKIP_CHOWN, SKIP_SETCAP, VAULT_ADDR, VAULT_API_ADDR, VAULT_CLUSTER_ADDR, VAULT_K8S_NAMESPACE, VAULT_K8S_POD_NAME, VAULT_PORT, VAULT_PORT_8200_TCP, VAULT_PORT_8200_TCP_ADDR, VAULT_PORT_8200_TCP_PORT, VAULT_PORT_8200_TCP_PROTO, VAULT_PORT_8201_TCP, VAULT_PORT_8201_TCP_ADDR, VAULT_PORT_8201_TCP_PORT, VAULT_PORT_8201_TCP_PROTO, VAULT_SERVICE_HOST, VAULT_SERVICE_PORT, VAULT_SERVICE_PORT_HTTP, VAULT_SERVICE_PORT_HTTPS_INTERNAL, VAULT_UI_PORT, VAULT_UI_PORT_8200_TCP, VAULT_UI_PORT_8200_TCP_ADDR, VAULT_UI_PORT_8200_TCP_PORT, VAULT_UI_PORT_8200_TCP_PROTO, VAULT_UI_SERVICE_HOST, VAULT_UI_SERVICE_PORT, VAULT_UI_SERVICE_PORT_HTTP, 
VERSION
vault-0 vault               Go Version: go1.24.6
vault-0 vault               Listener 1: tcp (addr: "[::]:8200", cluster address: "[::]:8201", disable_request_limiter: "false", max_json_array_element_count: "10000", max_json_depth: "300", max_json_object_entry_count: "10000", max_json_string_value_length: "1048576", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled")
vault-0 vault                Log Level:
vault-0 vault                    Mlock: supported: true, enabled: false
vault-0 vault            Recovery Mode: false
vault-0 vault                  Storage: file
vault-0 vault                  Version: Vault v1.20.4, built 2025-09-23T13:22:38Z
vault-0 vault              Version Sha: 55bd8f18c6c84aa89fdede4850a622c57f03bd7e
vault-0 vault
vault-0 vault ==> Vault server started! Log data will stream in below:
vault-0 vault
vault-0 vault 2025-12-05T13:14:24.430Z [INFO]  proxy environment: http_proxy="" https_proxy="" no_proxy=""
vault-0 vault 2025-12-05T13:14:24.431Z [INFO]  incrementing seal generation: generation=1
vault-0 vault 2025-12-05T13:14:24.431Z [INFO]  core: Initializing version history cache for core
vault-0 vault 2025-12-05T13:14:24.431Z [INFO]  events: Starting event system
vault-0 vault 2025-12-05T13:14:30.450Z [INFO]  core: security barrier not initialized
vault-0 vault 2025-12-05T13:14:30.450Z [INFO]  core: seal configuration missing, not initialized
vault-0 vault 2025-12-05T13:14:35.439Z [INFO]  core: security barrier not initialized
vault-0 vault 2025-12-05T13:14:35.439Z [INFO]  core: seal configuration missing, not initialized
vault-0 vault 2025-12-05T13:14:40.445Z [INFO]  core: security barrier not initialized
vault-0 vault 2025-12-05T13:14:40.445Z [INFO]  core: seal configuration missing, not initialized
vault-0 vault 2025-12-05T13:14:45.440Z [INFO]  core: security barrier not initialized
vault-0 vault 2025-12-05T13:14:45.440Z [INFO]  core: seal configuration missing, not initialized
vault-0 vault 2025-12-05T13:14:50.439Z [INFO]  core: security barrier not initialized
=> 아직 initailizing 안 됨

 

 

 

Vault Unseal - Docs

  • Vault Unseal
    • Vault는 비밀정보(Secrets) 를 저장·관리하는 시스템이며, 보안상 서버가 처음 시작될 때는 잠긴(lock) 상태로 시작됨.
    • 이때 잠겨 있는 Vault를 열기(unseal) 과정이 바로 Unseal이라는 것!

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

# Initialize vault-0 with one key share and one key threshold.
(⎈|kind-myk8s:N/A) gaji:~$ kubectl exec vault-0 -n vault -- vault operator init \
>     -key-shares=1 \
   -key->     -key-threshold=1 \
>     -format=json > cluster-keys.json


# cluster-keys.json 파일 확인
(⎈|kind-myk8s:N/A) gaji:~$ cat cluster-keys.json| jq
{
  "unseal_keys_b64": [
    "cyao4b3aHoOxCaNn7hKKpz9qRQusH5Ily9bYeuUVtrY="
  ],
  "unseal_keys_hex": [
    "7326a8e1bdda1e83b109a367ee128aa73f6a450bac1f9225cbd6d87ae515b6b6"
  ],
  "unseal_shares": 1,
  "unseal_threshold": 1,
  "recovery_keys_b64": [],
  "recovery_keys_hex": [],
  "recovery_keys_shares": 0,
  "recovery_keys_threshold": 0,
  "root_token": "hvs.LxcSqLEIxLOjIutLIyDVZE1N"
}

# Display the unseal key found in cluster-keys.json.
# 아래 나오는 값이 unseal key임
(⎈|kind-myk8s:N/A) gaji:~$ jq -r ".unseal_keys_b64[]" cluster-keys.json
cyao4b3aHoOxCaNn7hKKpz9qRQusH5Ily9bYeuUVtrY=


# Create a variable named VAULT_UNSEAL_KEY to capture the Vault unseal key.
(⎈|kind-myk8s:N/A) gaji:~$ VAULT_UNSEAL_KEY=$(jq -r ".unseal_keys_b64[]" cluster-keys.json)


# Unseal Vault running on the vault-0 pod : The Vault server is initialized and unsealed.
(⎈|kind-myk8s:N/A) gaji:~$ 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-9c5b13b6
Cluster ID      3985982a-c85f-9d83-93d8-bfe70c9221af
HA Enabled      false

# vault-0 파드 확인 : Readiness Probe 체크 성공!
## (참고) Readiness:      exec [/bin/sh -ec vault status -tls-skip-verify]
(⎈|kind-myk8s:N/A) gaji:~$ kubectl get pod -n vault
NAME      READY   STATUS    RESTARTS   AGE
vault-0   1/1     Running   0          10m # 초반에 배포 후 올라오지 않았던 pod가 정상 동작함!


# Display the root token found in cluster-keys.json.
(⎈|kind-myk8s:N/A) gaji:~$ jq -r ".root_token" cluster-keys.json
hvs.Lxc#######################


# vault-0 파드 정보 확인
# Readiness:      exec [/bin/sh -ec vault status -tls-skip-verify] delay=5s timeout=3s period=5s #success=1 #failure=2
# 내용 중 Readiness를 보면 vault의 상태를 주기적으로 체크하고, seal 해체가 안 되면 실패로 확인해서 vault-0 파드가 제대로 동작하지 않은 것!
(⎈|kind-myk8s:N/A) gaji:~$ kubectl describe pod vault-0 -n vault
Name:             vault-0
Namespace:        vault
Priority:         0
Service Account:  vault
Node:             myk8s-control-plane/172.18.0.2
Start Time:       Fri, 05 Dec 2025 22:14:06 +0900
Labels:           app.kubernetes.io/instance=vault
                  app.kubernetes.io/name=vault
                  apps.kubernetes.io/pod-index=0
                  component=server
                  controller-revision-hash=vault-744d6cccc7
                  helm.sh/chart=vault-0.31.0
                  statefulset.kubernetes.io/pod-name=vault-0
Annotations:      <none>
Status:           Running
IP:               10.244.0.7
IPs:
  IP:           10.244.0.7
Controlled By:  StatefulSet/vault
Containers:
  vault:
    Container ID:  containerd://2ab16d1f6250144ed6bb21b003caf4e64aaa2aa37440c88c70f84e8e5b5edcaa
    Image:         hashicorp/vault:1.20.4
    Image ID:      docker.io/hashicorp/vault@sha256:268bb80aa9c6d13d65fcfa05c0c268caca068952240a8087291a6ce0b66e3a10
    Ports:         8200/TCP (http), 8201/TCP (https-internal), 8202/TCP (http-rep)
    Host Ports:    0/TCP (http), 0/TCP (https-internal), 0/TCP (http-rep)
    Command:
      /bin/sh
      -ec
    Args:
      cp /vault/config/extraconfig-from-values.hcl /tmp/storageconfig.hcl;
      [ -n "${HOST_IP}" ] && sed -Ei "s|HOST_IP|${HOST_IP?}|g" /tmp/storageconfig.hcl;
      [ -n "${POD_IP}" ] && sed -Ei "s|POD_IP|${POD_IP?}|g" /tmp/storageconfig.hcl;
      [ -n "${HOSTNAME}" ] && sed -Ei "s|HOSTNAME|${HOSTNAME?}|g" /tmp/storageconfig.hcl;
      [ -n "${API_ADDR}" ] && sed -Ei "s|API_ADDR|${API_ADDR?}|g" /tmp/storageconfig.hcl;
      [ -n "${TRANSIT_ADDR}" ] && sed -Ei "s|TRANSIT_ADDR|${TRANSIT_ADDR?}|g" /tmp/storageconfig.hcl;
      [ -n "${RAFT_ADDR}" ] && sed -Ei "s|RAFT_ADDR|${RAFT_ADDR?}|g" /tmp/storageconfig.hcl;
      /usr/local/bin/docker-entrypoint.sh vault server -config=/tmp/storageconfig.hcl

    State:          Running
      Started:      Fri, 05 Dec 2025 22:14:24 +0900
    Ready:          True
    Restart Count:  0
    Readiness:      exec [/bin/sh -ec vault status -tls-skip-verify] delay=5s timeout=3s period=5s #success=1 #failure=2
    Environment:
      HOST_IP:               (v1:status.hostIP)
      POD_IP:                (v1:status.podIP)
      VAULT_K8S_POD_NAME:   vault-0 (v1:metadata.name)
      VAULT_K8S_NAMESPACE:  vault (v1:metadata.namespace)
      VAULT_ADDR:           http://127.0.0.1:8200
      VAULT_API_ADDR:       http://$(POD_IP):8200
      SKIP_CHOWN:           true
      SKIP_SETCAP:          true
      HOSTNAME:             vault-0 (v1:metadata.name)
      VAULT_CLUSTER_ADDR:   https://$(HOSTNAME).vault-internal:8201
      HOME:                 /home/vault
    Mounts:
      /home/vault from home (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-l4bgz (ro)
      /vault/config from config (rw)
      /vault/data from data (rw)
      /vault/logs from audit (rw)
Conditions:
  Type                        Status
  PodReadyToStartContainers   True
  Initialized                 True
  Ready                       True
  ContainersReady             True
  PodScheduled                True
Volumes:
  audit:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  audit-vault-0
    ReadOnly:   false
  data:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  data-vault-0
    ReadOnly:   false
  config:
    Type:      ConfigMap (a volume populated by a ConfigMap)
    Name:      vault-config
    Optional:  false
  home:
    Type:       EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:
    SizeLimit:  <unset>
  kube-api-access-l4bgz:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    Optional:                false
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type     Reason     Age                   From               Message
  ----     ------     ----                  ----               -------
  Normal   Scheduled  13m                   default-scheduler  Successfully assigned vault/vault-0 to myk8s-control-plane
  Normal   Pulling    13m                   kubelet            Pulling image "hashicorp/vault:1.20.4"
  Normal   Pulled     13m                   kubelet            Successfully pulled image "hashicorp/vault:1.20.4" in 16.992s (16.992s 
including waiting). Image size: 186821690 bytes.
  Normal   Created    13m                   kubelet            Created container: vault
  Normal   Started    13m                   kubelet            Started container vault
  Warning  Unhealthy  8m15s (x63 over 13m)  kubelet            Readiness probe failed: 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

 

 

 

Vault login with CLI

  • 서비스 확인
# vault는 30000번으로 동작 중
(⎈|kind-myk8s:N/A) gaji:~$ k get svc -n vault
NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                         AGE
vault            NodePort    10.96.251.171   <none>        8200:30000/TCP,8201:31550/TCP   18m
vault-internal   ClusterIP   None            <none>        8200/TCP,8201/TCP               18m
vault-ui         ClusterIP   10.96.34.118    <none>        8200/TCP                        18m


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

# vault 상태확인
(⎈|kind-myk8s:N/A) gaji:~$ vault status
Error checking seal status: Get "http://localhost:30000/v1/sys/seal-status": dial tcp 127.0.0.1:30000: connect: connection refused

# 오류 해결을 위해 아래 2가지 설정
1) 포트포워딩 설정 (다른 터미널에서 진행)
(⎈|kind-myk8s:N/A) gaji:~$ kubectl port-forward -n vault svc/vault 8200:8200
Forwarding from 127.0.0.1:8200 -> 8200
Forwarding from [::1]:8200 -> 8200

2) localhost를 127.0.0.1로 변경
(⎈|kind-myk8s:N/A) gaji:~$ export VAULT_ADDR="http://127.0.0.1:8200"


# vault 상태확인
(⎈|kind-myk8s:N/A) gaji:~$ 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-9c5b13b6
Cluster ID      3985982a-c85f-9d83-93d8-bfe70c9221af
HA Enabled      false


# Root Token으로 로그인
(⎈|kind-myk8s:N/A) gaji:~$ 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                hvs.Lxc##################
token_accessor       D9NbfQUZvhwOnb5VbwJCLRUj
token_duration       ∞
token_renewable      false
token_policies       ["root"]
identity_policies    []
policies             ["root"]

 

 

 

  • 웹 접근
  • Windows의 경우, 위에서 포트포워딩 설정을 했기 때문에 30000번이 아닌 8200으로 접근해야함.

 

 

 

Vault Audit log : file 설정

# audit 용 pvc 확인 : /vault/logs 마운트 설정되어 있음
(⎈|kind-myk8s:N/A) gaji:~$ kubectl get pvc -n vault
NAME            STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE      
audit-vault-0   Bound    pvc-6d320a6f-f940-41b1-beee-47d8c4d3ebe7   10Gi       RWO            standard       <unset>                 32m      
data-vault-0    Bound    pvc-df82f2c8-6589-4040-bac2-4832d314a926   10Gi       RWO            standard       <unset>                 32m      


# audit 용 pv(pvc) 에 저장될 수 있게 file audit log 설정
(⎈|kind-myk8s:N/A) gaji:~$ vault audit enable file file_path=/vault/logs/audit.log
Success! Enabled the file audit device at: file/

(⎈|kind-myk8s:N/A) gaji:~$ vault audit list -detailed
Path     Type    Description    Replication    Options
----     ----    -----------    -----------    -------
file/    file    n/a            replicated     file_path=/vault/logs/audit.log


# 로그 확인
(⎈|kind-myk8s:N/A) gaji:~$ kubectl exec -it vault-0 -n vault -- tail -f /vault/logs/audit.log
{"request":{"id":"34771652-6d4d-0c63-a6ea-802579b8f7a6","namespace":{"id":"root"},"operation":"update","path":"sys/audit/test"},"time":"2025-12-05T13:46:47.622971122Z","type":"request"}
{"auth":{"accessor":"hmac-sha256:d4d5b7d7a25296e9c6400d78b7f021d2e6b352c04a36bbb1b2a5bec44a04d95d","client_token":"hmac-sha256:42b544ce036d5417a0bc13292fa84cbf1d063a33aee9476bb9a8c1fd462f594d","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-05T13:20:33Z","token_type":"service"},"request":{"client_id":"0DHqvq2D77kL2/JTPSZkTMJbkFVmUu0TzMi0jiXcFy8=","client_token":"hmac-sha256:42b544ce036d5417a0bc13292fa84cbf1d063a33aee9476bb9a8c1fd462f594d","client_token_accessor":"hmac-sha256:d4d5b7d7a25296e9c6400d78b7f021d2e6b352c04a36bbb1b2a5bec44a04d95d","data":{"description":"hmac-sha256:b6ce31e2fc9b3086537cfe3d383b74b80d7f630def87bef73849d4570ce97add","local":false,"options":{"file_path":"hmac-sha256:aaf8d3b728f5490a71882f560a2a6bae0aa3fed75142267a23b6bda1c0a802fe"},"type":"hmac-sha256:7d9370cf4dd6dbf4db5e31de4c57bc2b4e7234c240c228ae71b028898558da54"},"headers":{"user-agent":["Go-http-client/1.1"]},"id":"551d5a58-dc70-6349-57c2-4a02a3b7818b","mount_accessor":"system_e93b0826","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":"127.0.0.1","remote_port":34186},"time":"2025-12-05T13:46:47.625809621Z","type":"response"}
{"auth":{"accessor":"hmac-sha256:d4d5b7d7a25296e9c6400d78b7f021d2e6b352c04a36bbb1b2a5bec44a04d95d","client_token":"hmac-sha256:42b544ce036d5417a0bc13292fa84cbf1d063a33aee9476bb9a8c1fd462f594d","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-05T13:20:33Z","token_type":"service"},"request":{"client_id":"0DHqvq2D77kL2/JTPSZkTMJbkFVmUu0TzMi0jiXcFy8=","client_token":"hmac-sha256:42b544ce036d5417a0bc13292fa84cbf1d063a33aee9476bb9a8c1fd462f594d","client_token_accessor":"hmac-sha256:d4d5b7d7a25296e9c6400d78b7f021d2e6b352c04a36bbb1b2a5bec44a04d95d","headers":{"user-agent":["Go-http-client/1.1"]},"id":"5d752944-6c56-f4a1-4a87-3ceb0a80b6ee","mount_class":"secret","mount_point":"sys/","mount_running_version":"v1.20.4+builtin.vault","mount_type":"system","namespace":{"id":"root"},"operation":"read","path":"sys/audit","remote_address":"127.0.0.1","remote_port":42228},"time":"2025-12-05T13:47:02.231245612Z","type":"request"}
{"auth":{"accessor":"hmac-sha256:d4d5b7d7a25296e9c6400d78b7f021d2e6b352c04a36bbb1b2a5bec44a04d95d","client_token":"hmac-sha256:42b544ce036d5417a0bc13292fa84cbf1d063a33aee9476bb9a8c1fd462f594d","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-05T13:20:33Z","token_type":"service"},"request":{"client_id":"0DHqvq2D77kL2/JTPSZkTMJbkFVmUu0TzMi0jiXcFy8=","client_token":"hmac-sha256:42b544ce036d5417a0bc13292fa84cbf1d063a33aee9476bb9a8c1fd462f594d","client_token_accessor":"hmac-sha256:d4d5b7d7a25296e9c6400d78b7f021d2e6b352c04a36bbb1b2a5bec44a04d95d","headers":{"user-agent":["Go-http-client/1.1"]},"id":"5d752944-6c56-f4a1-4a87-3ceb0a80b6ee","mount_accessor":"system_e93b0826","mount_class":"secret","mount_point":"sys/","mount_running_version":"v1.20.4+builtin.vault","mount_type":"system","namespace":{"id":"root"},"operation":"read","path":"sys/audit","remote_address":"127.0.0.1","remote_port":42228},"response":{"data":{"file/":{"description":"hmac-sha256:b6ce31e2fc9b3086537cfe3d383b74b80d7f630def87bef73849d4570ce97add","local":false,"options":{"file_path":"hmac-sha256:aaf8d3b728f5490a71882f560a2a6bae0aa3fed75142267a23b6bda1c0a802fe"},"path":"hmac-sha256:6d671d1c6a6d753f24fc7bcdc0f05805a296ac660ea79980fc952185179a58d3","type":"hmac-sha256:7d9370cf4dd6dbf4db5e31de4c57bc2b4e7234c240c228ae71b028898558da54"}},"mount_accessor":"system_e93b0826","mount_class":"secret","mount_point":"sys/","mount_running_plugin_version":"v1.20.4+builtin.vault","mount_type":"system"},"time":"2025-12-05T13:47:02.231478912Z","type":"response"}

 

 

 

 

2. Vault 사용 on K8S

  • 목적 : Vault 에 시크릿 생성 및 애플리케이션에서 시크릿 가져와보기

 

Set a secret in Vault - Link

# Enable an instance of the kv-v2 secrets engine at the path secret.
(⎈|kind-myk8s:N/A) gaji:~$ vault secrets enable -path=secret kv-v2
                                                                                                                                             ess    Options           Description
Success! Enabled the kv-v2 secrets engine at: secret/


# 확인
---    -------           -----------
(⎈|kind-myk8s:N/A) gaji:~$ vault secrets list -detailed
Path          Plugin       Accessor              Default TTL    Max TTL    Force No Cache    Replication    Seal Wrap    External Entropy Acc       map[]             per-token private secret storageess    Options           Description                                                UUID                                    Version    Running Version          Running SHA256    Deprecation Status                                                                                             map[]             identity store
----          ------       --------              -----------    -------    --------------    -----------    ---------    -----------------------    -------           -----------                                                ----                                    -------    ------       map[version:2]    n/a---------          --------------    ------------------
cubbyhole/    cubbyhole    cubbyhole_cb98f681    n/a            n/a        false             local          false        false                      map[]             system endpoints used for control, policy and debugging   
       map[]             per-token private secret storage                           d71ddfb3-03c3-b64e-a19d-b6caa90b4a48    n/a        v1.20.4+builtin.vault    n/a               n/a
identity/     identity     identity_6897d8b0     system         system     false             replicated     false        false
       map[]             identity store                                             d68bbf88-c7ef-ef6a-ba2c-cc530753ed34    n/a        v1.20.4+builtin.vault    n/a               n/a
secret/       kv           kv_dd23f185           system         system     false             replicated     false        false
       map[version:2]    n/a                                                        5d8a325d-31ef-c32b-d1bc-ad40b7219f42    n/a        v0.24.0+builtin          n/a               supported
sys/          system       system_e93b0826       n/a            n/a        false             replicated     true         false
       map[]             system endpoints used for control, policy and debugging    8894ceb1-1348-26e7-4d8d-9d48118fcfa1    n/a        v1.20.4+builtin.vault    n/a               n/a



(⎈|kind-myk8s:N/A) gaji:~$ vault secrets list
Path          Type         Accessor              Description
----          ----         --------              -----------
cubbyhole/    cubbyhole    cubbyhole_cb98f681    per-token private secret storage
identity/     identity     identity_6897d8b0     identity store
secret/       kv           kv_dd23f185           n/a
sys/          system       system_e93b0826       system endpoints used for control, policy and debugging



# Create a secret at path secret/webapp/config with a username and password.
(⎈|kind-myk8s:N/A) gaji:~$ 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-05T14:25:40.484465797Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1


# Verify that the secret is defined at the path secret/webapp/config.
(⎈|kind-myk8s:N/A) gaji:~$ vault kv get secret/webapp/config
====== Secret Path ======
secret/data/webapp/config

======= Metadata =======
Key                Value
---                -----
created_time       2025-12-05T14:25:40.484465797Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

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


# Verify that the secret is defined at the path secret/webapp/config.
(⎈|kind-myk8s:N/A) gaji:~$ export VAULT_ROOT_TOKEN=hvs.LxcSqLEIxLOjIutLIyDVZE1N

# 포트포워딩한 8200으로 확인
(⎈|kind-myk8s:N/A) gaji:~$ curl -s --header "X-Vault-Token: $VAULT_ROOT_TOKEN" --request GET \
://127.>   http://127.0.0.1:8200/v1/secret/data/webapp/config | jq
{
  "request_id": "d60e69ef-ee67-6dc1-6899-7d8c5399d676",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": {
    "data": {
      "password": "static-password",
      "username": "static-user"
    },
    "metadata": {
      "created_time": "2025-12-05T14:25:40.484465797Z",
      "custom_metadata": null,
      "deletion_time": "",
      "destroyed": false,
      "version": 1
    }
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null,
  "mount_type": "kv"
}

 

 

  • 웹에서 시크릿 정보 확인

 

 

 

Configure K8S Authentication in Vault - Link

  • Vault 설정 관계도 : [Auth] k8s policy(webapp) ← [Policy] path (secret/data/webapp/config ‘read’) ← [Secret] username , password
  • 볼트는 고객이 Kubernetes 서비스 계정 토큰으로 인증할 수 있는 방법을 제공합니다.
    • Vault provides a Kubernetes authentication method that enables clients to authenticate with a Kubernetes Service Account Token.
  • 볼트는 Kubernetes 클러스터 내의 모든 클라이언트로부터 이 서비스 토큰을 받습니다.
    • Vault accepts this service token from any client within the Kubernetes cluster.
  • 인증 중에 볼트는 구성된 Kubernetes 엔드포인트를 조회하여 서비스 계정 토큰이 유효한지 확인합니다.
    • During authentication, Vault verifies that the service account token is valid by querying a configured Kubernetes endpoint.
# vault 서버가 가지고 있는 Role 확인 : 이를 통해 K8S Service Account Token 유효 여부 확인
## subjectaccessreviews : 쿠버네티스 환경에서 사용자 또는 그룹의 액션 수행 가능 여부 확인
## tokenreviews : 쿠버네티스 API 서버가 제시된 토큰의 유효성을 확인하고, 그 토큰과 관련된 사용자 정보를 얻기 위해 사용
(⎈|kind-myk8s:N/A) gaji:~$ kubectl rbac-tool lookup vault
  SUBJECT | SUBJECT TYPE   | SCOPE       | NAMESPACE | ROLE                  | BINDING
----------+----------------+-------------+-----------+-----------------------+-----------------------
  vault   | ServiceAccount | ClusterRole |           | system:auth-delegator | vault-server-binding
  

(⎈|kind-myk8s:N/A) gaji:~$ 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         [*]     [-]     [-]   ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖
  
  
# Enable the Kubernetes authentication method.
(⎈|kind-myk8s:N/A) gaji:~$ vault auth enable kubernetes
Success! Enabled kubernetes auth method at: kubernetes/

 

 

# 쿠버네티스라는 인증이 추가됨
(⎈|kind-myk8s:N/A) gaji:~$ 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_6e9a6db2    system         system     default-service    replicated     false        false      
                map[]      n/a                        5115a297-9300-cc03-8cc2-0c31e3e7d7e2    n/a        v0.22.2+builtin          n/a        
       supported
token/         token         auth_token_cd0c9d8e         system         system     default-service    replicated     false        false      
                map[]      token based credentials    656703aa-e3fd-3d1b-4407-347c677c9ec1    n/a        v1.20.4+builtin.vault    n/a        
       n/a


(⎈|kind-myk8s:N/A) gaji:~$ vault auth list
Path           Type          Accessor                    Description                Version
----           ----          --------                    -----------                -------
kubernetes/    kubernetes    auth_kubernetes_6e9a6db2    n/a                        n/a
token/         token         auth_token_cd0c9d8e         token based credentials    n/a


# K8S API 서버 정보 설정 : 현재 vault 가 k8s 에 설치되어 있으므로, 아래처럼 서비스명 주소 입력 가능
(⎈|kind-myk8s:N/A) gaji:~$ vault write auth/kubernetes/config \
>     kubernetes_host="https://kubernetes.default.svc"
Success! Data written to: auth/kubernetes/config


(⎈|kind-myk8s:N/A) gaji:~$ k get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   88m


# 설정 정보 확인
(⎈|kind-myk8s:N/A) gaji:~$ 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

 

 

 

  • 클라이언트가 secret/webapp/config에서 정의된 비밀 데이터에 접근하려면, path secret/data/webapp/config에 대한 읽기 기능이 부여되어야 합니다.
    • For a client to access the secret data defined, at secret/webapp/config, requires that the read capability be granted for the path secret/data/webapp/config.
  • 이것은 정책의 한 예입니다. 정책은 일련의 기능을 정의합니다. This is an example of a policy. A policy defines a set of capabilities.
# 읽기가 가능한 정책(webapp) 생성!
# Write out the policy named webapp that enables the read capability for secrets at path secret/data/webapp/config.
vault policy write webapp - <<EOF
path "secret/data/webapp/config" {
  capabilities = ["read"]
}
EOF
Success! Uploaded policy: webapp

 

 

  • webapp policy 을 사용하는 인증 메서드 역할을 정의합니다. Define an auth method role that uses the webapp policy.
  • 역할정책과 환경 매개변수를 결합하여 웹 애플리케이션의 로그인을 생성합니다.
    • A role binds policies and environment parameters together to create a login for the web application.
  • default 네임스페이스에 vault라는 SA계정이 Role을 사용할 건데, 해당 Role(webapp)에는 webapp이라는 정책이 연결되어 있음
# Kubernetes 서비스 계정 이름과 웹앱 정책을 연결하는 웹앱이라는 이름의 Kubernetes 인증 역할을 만듭니다.
# Create a Kubernetes authentication role, named webapp, that connects the Kubernetes service account name and webapp policy.
(⎈|kind-myk8s:N/A) gaji:~$ 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 인증 방식에 webapp이라는 Role 확인

 

 


 

K8S 파드의 애플리케이션이 사용할 수 있는 인증 관련 정보

  • 서비스 어카운트 Service Account
    • 서비스어카운트(ServiceAccount) 는 파드에서 실행되는 애플리케이션 프로세스에 대한 식별자를 제공한다.
    • 파드 내부의 애플리케이션 프로세스는, 자신에게 부여된 서비스 어카운트의 식별자를 사용하여 클러스터의 API 서버에 인증할 수 있다.
  • 서비스 어카운트 토큰 serviceAccountToken
    • 서비스어카운트토큰(serviceAccountToken) 정보는 kubelet이 kube-apiserver로부터 취득한 토큰을 포함한다.
    • kubelet은 TokenRequest API를 통해 일정 시간 동안 사용할 수 있는 토큰을 발급 받는다.
    • 이렇게 취득한 토큰은 파드가 삭제되거나 지정된 수명 주기 이후에 만료된다(기본값은 1시간이다).
    • 이 토큰은 특정한 파드에 바인딩되며 kube-apiserver를 그 대상으로 한다.
  • 토큰 컨트롤러 token Controller
    • kube-controller-manager 의 일부로써 실행되며, 비동기적으로 동작한다.
    • 서비스어카운트에 대한 삭제를 감시하고, 해당하는 모든 서비스어카운트 토큰 시크릿을 같이 삭제한다.
    • 서비스어카운트 토큰 시크릿에 대한 추가를 감시하고, 참조된 서비스어카운트가 존재하는지 확인하며, 필요한 경우 시크릿에 토큰을 추가한다.
    • 시크릿에 대한 삭제를 감시하고, 필요한 경우 해당 서비스어카운트에서 참조 중인 항목들을 제거한다.
  • 서비스 어카운트 어드미션 컨트롤러 Service Account Admission Controller
    1. 파드에 .spce.serviceAccountName 항목이 지정되지 않았다면, 어드미션 컨트롤러는 실행하려는 파드의 서비스어카운트 이름을 default로 설정한다.
    2. 어드미션 컨트롤러는 실행되는 파드가 참조하는 서비스어카운트가 존재하는지 확인한다.
      • 만약 해당하는 이름의 서비스어카운트가 존재하지 않는 경우, 어드미션 컨트롤러는 파드를 실행시키지 않는다.
      • 이는 default 서비스어카운트에 대해서도 동일하게 적용된다.
    3. 서비스어카운트의 automountServiceAccountToken 또는 파드의 automountServiceAccountToken 중 어느 것도 false 로 설정되어 있지 않다면,
      • 어드미션 컨트롤러는 실행하려는 파드에 API에 접근할 수 있는 토큰을 포함하는 볼륨 을 추가한다.
      • 어드미션 컨트롤러는 파드의 각 컨테이너에 volumeMount를 추가한다.
      • 이미 /var/run/secrets/kubernetes.io/serviceaccount 경로에 볼륨이 마운트 되어있는 컨테이너에 대해서는 추가하지 않는다.
      • 리눅스 컨테이너의 경우, 해당 볼륨은 /var/run/secrets/kubernetes.io/serviceaccount 위치에 마운트된다
    4. 파드의 spec에 imagePullSecrets 이 없는 경우, 어드미션 컨트롤러는 ServiceAccount의 imagePullSecrets을 복사하여 추가된다.
  • 어드미션 컨트롤러는 파드의 생성 시점에 다음 작업들을 수행한다.
  • TokenRequest API
    • 서비스어카운트의 하위 리소스인 TokenRequest를 사용하여 일정 시간 동안 해당 서비스어카운트에서 사용할 수 있는 토큰을 가져올 수 있다.
    • 컨테이너 내에서 사용하기 위한 API 토큰을 얻기 위해 이 요청을 직접 호출할 필요는 없는데, kubelet이 프로젝티드 볼륨 을 사용하여 이를 설정하기 때문이다.

 


 

 

 

Launch a web application*

  • 웹 애플리케이션을 생성하여 DockerHub에 게시하고 기존 클러스터에서 애플리케이션을 실행할 Kubernetes 배포를 만들었습니다.
    • You have created a web application, published it to DockerHub, and created a Kubernetes deployment that will run the application in your existing cluster.
  • 예시 웹 애플리케이션은 HTTP 요청을 청취하는 단일 기능을 수행합니다.
    • The example web application performs the single function of listening for HTTP requests.
  • 요청 시 Kubernetes 서비스 토큰을 읽고 볼트로그인한 다음 비밀을 요청합니다.
    • During a request it reads the Kubernetes service token, logs into Vault, and then requests the secret.
# vault 서비스 어카운트 생성 (default 네임스페이스 생성)
(⎈|kind-myk8s:N/A) gaji:~$ kubectl create sa vault
serviceaccount/vault created


# 웹 애플리케이션 디플로이먼트 + 서비스(NodePort) 배포
# JWT_PATH sets the path of the JSON web token (JWT) issued by Kubernetes. This token is used by the web application to authenticate with Vault.
# VAULT_ADDR sets the address of the Vault service. The Helm chart defined a Kubernetes service named vault that forwards requests to its endpoints (i.e. The pods named vault-0, vault-1, and vault-2).
# SERVICE_PORT sets the port that the service listens for incoming HTTP requests.
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 # SA를 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


# 서비스 어카운트 토큰 확인 : 600초(10분)마다 갱신됨
(⎈|kind-myk8s:N/A) gaji:~$ kubectl exec -it deploy/webapp -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
eyJhbGciOiJSUzI1NiIsImtpZCI6IlBBT1RGSnRmdTZZT3NobHNROHFUVVdyS0pzM1FQN0RWVjhRUEh0WF9CRU0ifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzY0OTQ3ODc3LCJpYXQiOjE3NjQ5NDcyNzcsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiZjM2YjUxMzItY2E0My00NWI5LWIxNTAtZmVkZjZkM2U0ZWNiIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwibm9kZSI6eyJuYW1lIjoibXlrOHMtY29udHJvbC1wbGFuZSIsInVpZCI6IjA3ZGQyNmI3LTRkOTEtNGFiNi05Y2QyLTgyYTA4ODY1YzgzZCJ9LCJwb2QiOnsibmFtZSI6IndlYmFwcC02ODZjYzc4ODU4LXI3MnRyIiwidWlkIjoiMjYxZjIyNGItMzYwZC00ZDFhLWE3NjQtMGYzNWM4OGMwNGRiIn0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJ2YXVsdCIsInVpZCI6Ijg1YjQ5OGIzLTY4NWUtNDNlNi1hMjA4LWUzMWMzMzUxYTZkYiJ9fSwibmJmIjoxNzY0OTQ3Mjc3LCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDp2YXVsdCJ9.8BmcT2X_4FLrtpFOeKhNxxZUWKpkAYrCpkIv9JyP1q0mD7CSGQlbCP1-Q0Kjc1ivmQO4fry7xH7aNOBEtonGZdt436WkCwGKObJcy3-z99fsAjUbpe9yiaB-rEDce6xeqS6ZZo--G9iAcsH0CnpIfohjYsSWAPFqXLI4Y1I7a1Cr88daLgjicQhY3gkObMJS2hEBJQkBFo0uKbfw0er39hSP-ceNf1dAjnBnDEUW-EqAfU-8eTs21XyCquf2ePgRfSow3iwasCmCcWm7aZbNKEIMVf6H6-ojMUobyhV5CnCWRpFPCL67mJvYR07g01mdlkfzE1tRRjDk14CNOe2ofg(⎈|kind-myk8s:N/A) gaji:~$ 

(⎈|kind-myk8s:N/A) gaji:~$ 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":1764947877,"iat":1764947277,"iss":"https://kubernetes.default.svc.cluster.local","jti":"f36b5132-ca43-45b9-b150-fedf6d3e4ecb","kubernetes.io":{"namespace":"default","node":{"name":"myk8s-control-plane","uid":"07dd26b7-4d91-4ab6-9cd2-82a08865c83d"},"pod":{"name":"webapp-686cc78858-r72tr","uid":"261f224b-360d-4d1a-a764-0f35c88c04db"},"serviceaccount":{"name":"vault"

 

  • (참고) jwt.io : 서비스 어카운트 토큰(JWT) 정보 확인

 

 

 

Vault Secrets Operaor (VSO)

https://www.hashicorp.com/en/blog/kubernetes-vault-integration-via-sidecar-agent-injector-vs-csi-provider

  • 기존에 Vault 사용을 위해 Vault Login, Vault Secret Read 등에 대한 동작을 애플리케이션에서 구현할 필요 없이, VSO가 대신 수행.
  • VSO는 Vault 의 Secret 를 k8S Native Secret 에 동기화.
    • Deployment, ReplicaSet, StatefulSet, Argo Rollout Kubernetes 리소스 유형에 대한 Rollout 으로 자동 시크릿 교체 적용 가능
      • 물론 Rollout 하지 않고, 애플리케이션에서 변경된 값을 반영하게 구성 가능함.
  • VSO는 ‘kv-v1, kv-2’, ‘TLS 인증서 in PKI’ - 고정/동적 Secret 지원.

 

 

  • k8s(kind) 설치
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 ps
kubectl get node

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

 

 

# 공식 문서 버전 정보
helm search repo hashicorp/vault
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

# Clone the repository
git clone https://github.com/hashicorp-education/learn-vault-secrets-operator
cd learn-vault-secrets-operator

# 테스트 용도(server.dev.enabled=true) 설정 파일 작성 : 직접 Unseal 하지 않아도됨. RootToken 직접 설정
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

# 확인
kubectl get pods -n vault
NAME      READY   STATUS    RESTARTS   AGE
vault-0   1/1     Running   0          14m

 

 

  • Vault 설정
# Vault 로그인 : 토큰(root)
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"

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

# Create a JSON file with a Vault policy.
tee webapp.json <<EOF
path "kvv2/data/webapp/config" {
   capabilities = ["read", "list"]
}
EOF
vault policy write webapp webapp.json

# Create a role in Vault to enable access to secrets within the kv v2 secrets engine.
## Notice that the bound_service_account_namespaces is app, limiting which namespace the secret is synced to.
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

# Create a secret.
vault kv put kvv2/webapp/config username="static-user" password="static-password"
===== Secret Path =====
kvv2/data/webapp/config

======= Metadata =======
Key                Value
---                -----
created_time       2025-04-16T12:09:42.364538501Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

 

 

  • Vault Secrets Operator (VSO) 설치 - HelmChart
# 공식 문서 버전 정보
helm search repo hashicorp/vault
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

# 파일 확인
cat vault/vault-operator-values.yaml
...
          
# VSO 설치 : Helm v4 실패
helm version
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
Error: INSTALLATION FAILED: VaultAuth.secrets.hashicorp.com "vault-secrets-operator-default-transit-auth" is invalid: spec.namespace: Invalid value: "null": spec.namespace in body must be of type string: "null"

helm list -A
helm uninstall -n vault-secrets-operator-system vault-secrets-operator

# Helm v3 으로 설치
docker exec -it myk8s-control-plane bash
------------------------------------
# Helm v3 설치
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update

#
cat << EOF > 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: k8s-auth-mount
        keyName: vso-client-cache
        transitMount: demo-transit
        kubernetes:
          role: auth-role-operator
          serviceAccount: vault-secrets-operator-controller-manager
          tokenAudiences: ["vault"]
EOF

helm install vault-secrets-operator hashicorp/vault-secrets-operator -n vault-secrets-operator-system --create-namespace --values vault-operator-values.yaml --version 0.10.0
helm list -A
exit
------------------------------------

# 설치 확인
kubectl get-all -n vault-secrets-operator-system
kubectl get crd | grep secrets.hashicorp.com
...
secrettransformations.secrets.hashicorp.com   2025-04-16T12:10:53Z
vaultauthglobals.secrets.hashicorp.com        2025-04-16T12:10:53Z
vaultauths.secrets.hashicorp.com              2025-04-16T12:10:53Z
vaultconnections.secrets.hashicorp.com        2025-04-16T12:10:53Z
vaultdynamicsecrets.secrets.hashicorp.com     2025-04-16T12:10:53Z
vaultpkisecrets.secrets.hashicorp.com         2025-04-16T12:10:53Z
vaultstaticsecrets.secrets.hashicorp.com      2025-04-16T12:10:53Z

# vso 파드 상세 정보 확인 : 2개의 컨테이너로 구성
kubectl describe pod -n vault-secrets-operator-system
...
Service Account:  vault-secrets-operator-controller-manager
...
Containers:
  kube-rbac-proxy:
    Container ID:  containerd://db3eae7b836fb4f1b4236c494c8fa96ada94769a6c602e1a150c75293a6a4162
    Image:         quay.io/brancz/kube-rbac-proxy:v0.18.1
  ...
  manager:
    Container ID:  containerd://1ab1545fb4bd86ac52d6c7609a3e962cd2d1a81daa9bbd9c82f79d9a0d8b6466
    Image:         hashicorp/vault-secrets-operator:0.10.0
...

# CRD 확인
kubectl get vaultconnections,vaultauths -n vault-secrets-operator-system
NAME                                            AGE
vaultconnection.secrets.hashicorp.com/default   3m21s

NAME                                                                          AGE
vaultauth.secrets.hashicorp.com/vault-secrets-operator-default-transit-auth   3m21s

# vaultauth CRD 확인
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"
}

# vaultconnection CRD 확인
kubectl get vaultconnection -n vault-secrets-operator-system default -o jsonpath='{.spec}' | jq

{
  "address": "http://vault.vault.svc.cluster.local:8200",
  "skipTLSVerify": false
}

# 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-manager-role         | vault-secrets-operator-manager-rolebinding          
  vault-secrets-operator-controller-manager | ServiceAccount | ClusterRole |                               | vault-secrets-operator-proxy-role           | vault-secrets-operator-proxy-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는 deployment 등에 Secret 적용을 위한 rollout(G W P U) 필요, 특히 vault 서버로 부터 암호 값을 가져와서 secret 에 업데이트 및 관리 필요함.
## kubectl rollout restart(GET, PATCH), rollout status(GET, WATCH), rollout undo(GET, UPDATE)
## G (Get), L(List), W(Watch), P(Patch), C(Create), U(Update), D(Delete), DC(DeleteCollection)
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                                                  [*]     [-]     [-]   ✖ ✖ ✖ ✔ ✖ ✔ ✖ ✖   
  ...
  rollouts.argoproj.io                                    [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✔ ✖ ✖   
  secrets                                                 [*]     [-]     [-]   ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔   
  secrettransformations.secrets.hashicorp.com             [*]     [-]     [-]   ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖   
  secrettransformations.secrets.hashicorp.com/finalizers  [*]     [-]     [-]   ✖ ✖ ✖ ✖ ✔ ✖ ✖ ✖   
  secrettransformations.secrets.hashicorp.com/status      [*]     [-]     [-]   ✔ ✖ ✖ ✖ ✔ ✔ ✖ ✖   
  serviceaccounts                                         [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖   
  serviceaccounts/token                                   [*]     [-]     [-]   ✔ ✔ ✔ ✔ ✖ ✖ ✖ ✖   
  statefulsets.apps                                       [*]     [-]     [-]   ✔ ✔ ✔ ✖ ✖ ✔ ✖ ✖   
  vaultauthglobals.secrets.hashicorp.com                  [*]     [-]     [-]   ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖   
  vaultauthglobals.secrets.hashicorp.com/finalizers       [*]     [-]     [-]   ✖ ✖ ✖ ✖ ✔ ✖ ✖ ✖   
  vaultauthglobals.secrets.hashicorp.com/status           [*]     [-]     [-]   ✔ ✖ ✖ ✖ ✔ ✔ ✖ ✖   
  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         [*]     [-]     [-]   ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖

 

 

 

 

static secret 고정 암호 실습 시나리오

  1. Vault 에 Secret/Policy/Role 생성
  2. VSO 가 → Vault 에 로그인 후 Token 받음 ← VSO 에 대한 확인 역시 K8S API 를 통해 확인
    • 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
#
kubectl create ns app

# CRD 확인 : vaultauths
kubectl explain vaultauths
kubectl explain vaultauths.spec

# Set up Kubernetes authentication for the secret.
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
      
kubectl apply -f vault/vault-auth-static.yaml

#
kubectl get sa,vaultauth -n app
NAME                             SECRETS   AGE
serviceaccount/default           0         23m
serviceaccount/demo-static-app   0         21m

NAME                                          AGE
vaultauth.secrets.hashicorp.com/static-auth   21m


# CRD 확인 : vaultstaticsecrets
kubectl explain vaultstaticsecrets
kubectl explain vaultstaticsecrets.spec

# Create the secret names secretkv in the app namespace.
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
  
kubectl apply -f vault/static-secret.yaml

#
kubectl get vaultstaticsecret -n app
NAME           AGE
vault-kv-app   11s

 

 

  • Rotate the static secret
# K8S Secret 확인
kubectl get secret -n app
NAME       TYPE     DATA   AGE
secretkv   Opaque   3      3m10s

# 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-04-16T12:09:42.364538501Z","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"
===== Secret Path =====
kvv2/data/webapp/config

======= Metadata =======
Key                Value
---                -----
created_time       2025-04-16T13:03:15.530981752Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            2

# K8S Secret 값 확인 >> 이후 VSO 는 설정된 주기(현재 30초) 마다 Vault 서버에 GET 요청으로 시크릿 값을 받음
kubectl view-secret -n app secretkv --all
_raw='{"data":{"password":"static-password2","username":"static-user2"},"metadata":{"created_time":"2025-04-16T13:03:15.530981752Z","custom_metadata":null,"deletion_time":"","destroyed":false,"version":2}}'
password='static-password2'
username='static-user2'

# secretkv 리소스의 AGE를 보면 재성성되지 않았고, 리소스 data 의 값만 바꿈
kubectl get secret -n app
NAME       TYPE     DATA   AGE
secretkv   Opaque   3      8m

→ 키 값이 업데이트 됨

 
 
 

dynamic secret 동적 암호 주기 관리 실습 시나리오

  • 동적 암호 주기 관리는 Vault 가 자동으로 암호를 갱신(삭제/재생성)하고, VSO가 해당 암호를 K8S Secret 에 동기화
    • Dynamic secrets lifecycle is managed by Vault and will be automatically rotated
    • The lifecycle management includes deleting and recreating the secret
  • 주요 사용 CRD
    • VSO가 Vault 에 로그인 과정은 VaultAuth CRD를 통해서 작동
    • VSO가 Vault 에 Dynamic Secret 요청 과정은 VaultDynamicSecret CRD를 통해서 작동

 

 

  • PostgreSQL 파드 배포 및 Vault Database Secret Engine 설정 - Docs , ClientCache
# 네임스페이스 생성
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl create ns postgres
namespace/postgres created


# Add the Bitnami repository to your local Helm.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ helm repo add bitnami https://charts.bitnami.com/bitnami
"bitnami" already exists with the same configuration, skipping


# Install PostgreSQL : 암호 secret-pass
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ 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 11:38:23 2025
NAMESPACE: postgres
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: postgresql
CHART VERSION: 18.1.13
APP VERSION: 18.1.0

⚠ WARNING: Since August 28th, 2025, only a limited subset of images/charts are available for free.
    Subscribe to Bitnami Secure Images to receive continued support and security updates.
    More info at https://bitnami.com and https://github.com/bitnami/containers/issues/83267

** Please be patient while the chart is being deployed **

PostgreSQL can be accessed via port 5432 on the following DNS names from within your cluster:

    postgres-postgresql.postgres.svc.cluster.local - Read/Write connection

To get the password for "postgres" run:

    export POSTGRES_PASSWORD=$(kubectl get secret --namespace postgres postgres-postgresql -o jsonpath="{.data.postgres-password}" | base64 -d)

To connect to your database run the following command:

    kubectl run postgres-postgresql-client --rm --tty -i --restart='Never' --namespace postgres --image registry-1.docker.io/bitnami/postgresql:latest --env="PGPASSWORD=$POSTGRES_PASSWORD" \
      --command -- psql --host postgres-postgresql -U postgres -d postgres -p 5432

    > NOTE: If you access the container using bash, make sure that you execute "/opt/bitnami/scripts/postgresql/entrypoint.sh /bin/bash" in order to avoid the error "psql: local user with ID 1001} does not exist"

To connect to your database from outside the cluster execute the following commands:

    kubectl port-forward --namespace postgres svc/postgres-postgresql 5432:5432 &
    PGPASSWORD="$POSTGRES_PASSWORD" psql --host 127.0.0.1 -U postgres -d postgres -p 5432

WARNING: The configured password will be ignored on new installation in case when previous PostgreSQL release was deleted through the helm command. In that case, old PVC will have an old password, and setting it through helm won't take effect. Deleting persistent volumes (PVs) will solve the issue.
WARNING: Rolling tag detected (bitnami/postgresql:latest), please note that it is strongly recommended to avoid using rolling tags in a production environment.
+info https://techdocs.broadcom.com/us/en/vmware-tanzu/application-catalog/tanzu-application-catalog/services/tac-doc/apps-tutorials-understand-rolling-tags-containers-index.html
WARNING: Rolling tag detected (bitnami/os-shell:latest), please note that it is strongly recommended to avoid using rolling tags in a production environment.
+info https://techdocs.broadcom.com/us/en/vmware-tanzu/application-catalog/tanzu-application-catalog/services/tac-doc/apps-tutorials-understand-rolling-tags-containers-index.html

WARNING: There are "resources" sections in the chart not set. Using "resourcesPreset" is not recommended for production. For production installations, please set the following values according to your workload needs:
  - primary.resources
  - readReplicas.resources
+info https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/



# 확인
^[\(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get sts,pod,svc,ep,pvc,secret -n postgres
NAME                                   READY   AGE
statefulset.apps/postgres-postgresql   1/1     112s

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

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

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

NAME                                               STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS 
  VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/data-postgres-postgresql-0   Bound    pvc-eb8a3232-23a1-4a25-a9ef-96a428df3eff   8Gi        RWO            standard     
  <unset>                 112s

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


(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl view-secret -n postgres postgres-postgresql --all
secret-pass



# psql 로그인 확인
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl exec -it -n postgres postgres-postgresql-0 -- sh -c 'PGPASSWORD=secret-pass psql -U postgres -h localhost'
psql (18.1)
Type "help" for help.

postgres=# 


(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ 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)

 

 

 

 

 

  • PostgreSQL 관련 설정

# Enable an instance of the Database Secrets Engine
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault secrets enable -path=demo-db database
Success! Enabled the database secrets engine at: demo-db/


# Configure the Database Secrets Engine : vault 에 DB에 대한 정보 설정 (DB 사용자 이름, 암호)
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault write demo-db/config/demo-db \
plug>    plugin_name=postgresql-database-plugin \
 allowed>    allowed_roles="dev-postgres" \
>    connection_url="postgresql://{{username}}:{{password}}@postgres-postgresql.postgres.svc.cluster.local:5432/postgres?sslmode=disable" \
rname=">    username="postgres" \
>    password="secret-pass"
Success! Data written to: demo-db/config/demo-db



# 확인 : user,pw는 조금 더 안전하게 변수 처리
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault read demo-db/config/demo-db
Key                                   Value
---                                   -----
allowed_roles                         [dev-postgres]
connection_details                    map[connection_url:postgresql://{{username}}:{{password}}@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



# DB 사용자 동적 생성 Role 등록
# Create a role for the PostgreSQL pod : default_ttl="10m"(인증 생성 후 10분 유효), max_ttl="10m"(연장 요청 해도 20분 못넘음)
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault write demo-db/roles/dev-postgres \
>    db_name=demo-db \
eation_>    creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
      G>       GRANT ALL PRIVILEGES ON DATABASE postgres TO \"{{name}}\";" \
>    revocation_statements="REVOKE ALL ON DATABASE postgres FROM  \"{{name}}\";" \
emo-d>    backend=demo-db \
postgres>    name=dev-postgres \
>    default_ttl="10m" \
>    max_ttl="20m"
Success! Data written to: demo-db/roles/dev-postgres



# creation_statements="..." : Vault가 동적으로 사용자 생성 시 실행할 SQL 문을 정의
## {{name}}, {{password}}, {{expiration}}은 Vault가 자동으로 치환하는 템플릿 변수
## 새로운 PostgreSQL 사용자 생성 (CREATE ROLE) , 비밀번호와 만료시간 설정 , 해당 사용자에게 postgres DB에 대한 모든 권한 부여

# revocation_statements="..." : Vault가 사용자 자격을 취소(revoke)할 때 실행할 SQL
## 해당 사용자로부터 postgres DB의 모든 권한을 제거합니다.
## 사용자를 아예 DROP하지 않는 경우도 많음 → 보안 정책에 따라 추가 가능.

# 확인
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault read demo-db/roles/dev-postgres
Key                      Value
---                      -----
creation_statements      [CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';       GRANT ALL PRIVILEGES ON DATABASE postgres TO "{{name}}";]
credential_type          password
db_name                  demo-db
default_ttl              10m
max_ttl                  20m
renew_statements         []
revocation_statements    [REVOKE ALL ON DATABASE postgres FROM  "{{name}}";]
rollback_statements      []



# Create the demo-auth-policy-db policy.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault policy write demo-auth-policy-db - <<EOF
h "demo> path "demo-db/creds/dev-postgres" {
>    capabilities = ["read"]
F> }
> EOF
Success! Uploaded policy: demo-auth-policy-db



# psql 확인
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ 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

 

 

  • Setup dynamic secrets : Vault's dynamic secrets engine for PostgreSQL to generate temporary client credentials to the PostgreSQL
# Create a new role for the dynamic secret.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault write auth/demo-auth-mount/role/auth-role \
 bound_>    bound_service_account_names=demo-dynamic-app \
service_>    bound_service_account_namespaces=demo-ns \
0 \
   t>    token_ttl=0 \
oken_pe>    token_period=120 \
ken_>    token_policies=demo-auth-policy-db \
ce=vault
>    audience=vault
Success! Data written to: auth/demo-auth-mount/role/auth-role



# 확인
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ 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

 

 

  • Create the application : demo-ns 네임스페이스에 vso-db-demo 파드가 동적 암호를 사용할 수 있게 해보기
# 네임스페이스 생성
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl create ns demo-ns
namespace/demo-ns created


# Create the app, Vault connection, authentication, service account and corresponding secrets.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ 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

(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ 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


# 확인
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get pod -n demo-ns
NAME                          READY   STATUS    RESTARTS   AGE
vso-db-demo-674bff7fd-6qc72   1/1     Running   0          19s
vso-db-demo-674bff7fd-b4cvw   1/1     Running   0          19s
vso-db-demo-674bff7fd-jsnkb   1/1     Running   0          7s



# 파드에 /etc/secrets 마운트는 K8S Secret vso-db-demo 를 사용
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl describe pod -n demo-ns
Name:             vso-db-demo-674bff7fd-6qc72
Namespace:        demo-ns
Priority:         0
Service Account:  default
Node:             myk8s-control-plane/172.18.0.2
Start Time:       Sat, 13 Dec 2025 11:48:22 +0900
Labels:           pod-template-hash=674bff7fd
                  test=vso-db-demo
Annotations:      vso.secrets.hashicorp.com/restartedAt: 2025-12-13T02:48:22Z
Status:           Running
IP:               10.244.0.11
IPs:
  IP:           10.244.0.11
Controlled By:  ReplicaSet/vso-db-demo-674bff7fd
Containers:
  example:
    Container ID:   containerd://8d0cabc3e89d35159ae037007335d3f53386b036cb477a2b907a1a7182fe3f4d
    Image:          nginx:latest
    Image ID:       docker.io/library/nginx@sha256:fb01117203ff38c2f9af91db1a7409459182a37c87cced5cb442d1d8fcc66d19
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Sat, 13 Dec 2025 11:48:34 +0900
    Ready:          True
    Restart Count:  0
    Limits:
      cpu:     500m
      memory:  512Mi
    Requests:
      cpu:     250m
      memory:  50Mi
    Liveness:  http-get http://:80/ delay=3s timeout=1s period=3s #success=1 #failure=3
    Environment:
      DB_PASSWORD:  <set to the key 'password' in secret 'vso-db-demo'>  Optional: false
      DB_USERNAME:  <set to the key 'username' in secret 'vso-db-demo'>  Optional: false
    Mounts:
      /etc/secrets from secrets (ro)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-lzf6h (ro)
Conditions:
  Type                        Status
  PodReadyToStartContainers   True
  Initialized                 True
  Ready                       True
  ContainersReady             True
  PodScheduled                True
Volumes:
  secrets:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  vso-db-demo
    Optional:    false
  kube-api-access-lzf6h:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    Optional:                false
    DownwardAPI:             true
QoS Class:                   Burstable
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  24s   default-scheduler  Successfully assigned demo-ns/vso-db-demo-674bff7fd-6qc72 to myk8s-control-plane
  Normal  Pulling    24s   kubelet            Pulling image "nginx:latest"
  Normal  Pulled     12s   kubelet            Successfully pulled image "nginx:latest" in 1.571s (11.481s including waiting). Image size: 59795293 bytes.
  Normal  Created    12s   kubelet            Created container: example
  Normal  Started    12s   kubelet            Started container example


Name:             vso-db-demo-674bff7fd-b4cvw
Namespace:        demo-ns
Priority:         0
Service Account:  default
Node:             myk8s-control-plane/172.18.0.2
Start Time:       Sat, 13 Dec 2025 11:48:22 +0900
Labels:           pod-template-hash=674bff7fd
                  test=vso-db-demo
Annotations:      vso.secrets.hashicorp.com/restartedAt: 2025-12-13T02:48:22Z
Status:           Running
IP:               10.244.0.12
IPs:
  IP:           10.244.0.12
Controlled By:  ReplicaSet/vso-db-demo-674bff7fd
Containers:
  example:
    Container ID:   containerd://51a6088f78b7297ff5d47596faf9358a0add8123c458f9f2f48a3724d00cacfd
    Image:          nginx:latest
    Image ID:       docker.io/library/nginx@sha256:fb01117203ff38c2f9af91db1a7409459182a37c87cced5cb442d1d8fcc66d19
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Sat, 13 Dec 2025 11:48:36 +0900
    Ready:          True
    Restart Count:  0
    Limits:
      cpu:     500m
      memory:  512Mi
    Requests:
      cpu:     250m
      memory:  50Mi
    Liveness:  http-get http://:80/ delay=3s timeout=1s period=3s #success=1 #failure=3
    Environment:
      DB_PASSWORD:  <set to the key 'password' in secret 'vso-db-demo'>  Optional: false
      DB_USERNAME:  <set to the key 'username' in secret 'vso-db-demo'>  Optional: false
    Mounts:
      /etc/secrets from secrets (ro)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-nbr8f (ro)
Conditions:
  Type                        Status
  PodReadyToStartContainers   True
  Initialized                 True
  Ready                       True
  ContainersReady             True
  PodScheduled                True
Volumes:
  secrets:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  vso-db-demo
    Optional:    false
  kube-api-access-nbr8f:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    Optional:                false
    DownwardAPI:             true
QoS Class:                   Burstable
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  24s   default-scheduler  Successfully assigned demo-ns/vso-db-demo-674bff7fd-b4cvw to myk8s-control-plane
  Normal  Pulling    24s   kubelet            Pulling image "nginx:latest"
  Normal  Pulled     11s   kubelet            Successfully pulled image "nginx:latest" in 1.614s (13.081s including waiting). Image size: 59795293 bytes.
  Normal  Created    11s   kubelet            Created container: example
  Normal  Started    10s   kubelet            Started container example


Name:             vso-db-demo-674bff7fd-jsnkb
Namespace:        demo-ns
Priority:         0
Service Account:  default
Node:             myk8s-control-plane/172.18.0.2
Start Time:       Sat, 13 Dec 2025 11:48:34 +0900
Labels:           pod-template-hash=674bff7fd
                  test=vso-db-demo
Annotations:      vso.secrets.hashicorp.com/restartedAt: 2025-12-13T02:48:22Z
Status:           Running
IP:               10.244.0.13
IPs:
  IP:           10.244.0.13
Controlled By:  ReplicaSet/vso-db-demo-674bff7fd
Containers:
  example:
    Container ID:   containerd://b33a687680201ed2d3470e483578c429825d8bd06353a4ca152dd167cc1574f1
    Image:          nginx:latest
    Image ID:       docker.io/library/nginx@sha256:fb01117203ff38c2f9af91db1a7409459182a37c87cced5cb442d1d8fcc66d19
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Sat, 13 Dec 2025 11:48:37 +0900
    Ready:          True
    Restart Count:  0
    Limits:
      cpu:     500m
      memory:  512Mi
    Requests:
      cpu:     250m
      memory:  50Mi
    Liveness:  http-get http://:80/ delay=3s timeout=1s period=3s #success=1 #failure=3
    Environment:
      DB_PASSWORD:  <set to the key 'password' in secret 'vso-db-demo'>  Optional: false
      DB_USERNAME:  <set to the key 'username' in secret 'vso-db-demo'>  Optional: false
    Mounts:
      /etc/secrets from secrets (ro)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-rm4m8 (ro)
Conditions:
  Type                        Status
  PodReadyToStartContainers   True
  Initialized                 True
  Ready                       True
  ContainersReady             True
  PodScheduled                True
Volumes:
  secrets:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  vso-db-demo
    Optional:    false
  kube-api-access-rm4m8:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    Optional:                false
    DownwardAPI:             true
QoS Class:                   Burstable
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  12s   default-scheduler  Successfully assigned demo-ns/vso-db-demo-674bff7fd-jsnkb to myk8s-control-plane
  Normal  Pulling    12s   kubelet            Pulling image "nginx:latest"
  Normal  Pulled     9s    kubelet            Successfully pulled image "nginx:latest" in 1.613s (2.614s including waiting). Image size: 59795293 bytes.
  Normal  Created    9s    kubelet            Created container: example
  Normal  Started    9s    kubelet            Started container example




# 파드에 /etc/secrets 정보 확인
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl exec -it deploy/vso-db-demo -n demo-ns -- ls -al /etc/secrets
total 8
drwxrwxrwt 3 root root  140 Dec 13 02:48 .
drwxr-xr-x 1 root root 4096 Dec 13 02:48 ..
drwxr-xr-x 2 root root  100 Dec 13 02:48 ..2025_12_13_02_48_22.3530996573
lrwxrwxrwx 1 root root   32 Dec 13 02:48 ..data -> ..2025_12_13_02_48_22.3530996573
lrwxrwxrwx 1 root root   11 Dec 13 02:48 _raw -> ..data/_raw
lrwxrwxrwx 1 root root   15 Dec 13 02:48 password -> ..data/password
lrwxrwxrwx 1 root root   15 Dec 13 02:48 username -> ..data/username


(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl exec -it deploy/vso-db-demo -n demo-ns -- cat /etc/secrets/username ; echo
v-demo-aut-dev-post-Ht0Ytb3HMaLb43IJvkLb-1765594102


(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl exec -it deploy/vso-db-demo -n demo-ns -- cat /etc/secrets/password ; echo
ZFxY4qUMkCxp-sLW9aGD


# K8S Secret 확인
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get secret -n demo-ns
NAME                  TYPE     DATA   AGE
vso-db-demo           Opaque   3      60s
vso-db-demo-created   Opaque   3      60s


(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl view-secret -n demo-ns vso-db-demo --all
_raw='{"password":"ZFxY4qUMkCxp-sLW9aGD","username":"v-demo-aut-dev-post-Ht0Ytb3HMaLb43IJvkLb-1765594102"}'
password='ZFxY4qUMkCxp-sLW9aGD'
username='v-demo-aut-dev-post-Ht0Ytb3HMaLb43IJvkLb-1765594102'



# VaultAuth 확인
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get vaultauth -n demo-ns dynamic-auth -o yaml
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"secrets.hashicorp.com/v1beta1","kind":"VaultAuth","metadata":{"annotations":{},"name":"dynamic-auth","namespace":"demo-ns"},"spec":{"kubernetes":{"audiences":["vault"],"role":"auth-role","serviceAccount":"demo-dynamic-app"},"method":"kubernetes","mount":"demo-auth-mount"}}
  creationTimestamp: "2025-12-13T02:48:22Z"
  finalizers:
  - vaultauth.secrets.hashicorp.com/finalizer
  generation: 1
  name: dynamic-auth
  namespace: demo-ns
  resourceVersion: "10056"
  uid: 7da68f4b-eb6b-41c2-907b-df372204791e
spec:
  kubernetes:
    audiences:
    - vault
    role: auth-role
    serviceAccount: demo-dynamic-app
    tokenExpirationSeconds: 600
  method: kubernetes
  mount: demo-auth-mount
status:
  error: ""
  valid: true



# VaultDynamicSecret 확인 : vso-db-demo
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get vaultdynamicsecret -n demo-ns vso-db-demo -o yaml
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultDynamicSecret
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"secrets.hashicorp.com/v1beta1","kind":"VaultDynamicSecret","metadata":{"annotations":{},"name":"vso-db-demo","namespace":"demo-ns"},"spec":{"destination":{"create":false,"name":"vso-db-demo"},"mount":"demo-db","path":"creds/dev-postgres","rolloutRestartTargets":[{"kind":"Deployment","name":"vso-db-demo"}],"vaultAuthRef":"dynamic-auth"}}
  creationTimestamp: "2025-12-13T02:48:22Z"
  finalizers:
  - vaultdynamicsecret.secrets.hashicorp.com/finalizer
  generation: 2
  name: vso-db-demo
  namespace: demo-ns
  resourceVersion: "10105"
  uid: 0140bff9-ad31-444d-9f20-1e4237ceb801
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: 1765594102
  lastRuntimePodUID: 0e97c8b3-cbe8-4ba2-a01e-640457982336
  secretLease:
    duration: 600
    id: demo-db/creds/dev-postgres/ah9EHlxbfcRzXgdhPx4ZcecH
    renewable: true
    requestID: b9bb888b-3e02-a255-92d9-5e4207517c5e
  staticCredsMetaData:
    lastVaultRotation: 0
    rotationPeriod: 0
    ttl: 0
  vaultClientMeta:
    cacheKey: kubernetes-1f645800d40a97dbe89409
    id: cc8b13de9b1c35d8c46749c9dcbe823936a8b6b26ed3667cc9813b0dc47dc7ae

 

 

  • Vault 가 Psql 암호를 동적으로 변경하고 VSO가 해당 암호를 K8S Secret 동기화 관련 상세 확인
# psql 확인
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ 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-DzKM6Sd0Jy6QQHNptTE9-1765594102 | Password valid until 2025-12-13 02:58:27+00
 v-demo-aut-dev-post-Ht0Ytb3HMaLb43IJvkLb-1765594102 | Password valid until 2025-12-13 02:58:27+00
 v-demo-aut-dev-post-LwiRB37RfcNZZiV4WPRE-1765594102 | Password valid until 2025-12-13 02:58:27+00
 v-demo-aut-dev-post-VqfXbDlkfoTzB5XN1crh-1765594102 | Password valid until 2025-12-13 02:58:27+00



# 1차 K8S Secret 확인
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl view-secret -n demo-ns vso-db-demo --all
_raw='{"password":"ZFxY4qUMkCxp-sLW9aGD","username":"v-demo-aut-dev-post-Ht0Ytb3HMaLb43IJvkLb-1765594102"}'
password='ZFxY4qUMkCxp-sLW9aGD'
username='v-demo-aut-dev-post-Ht0Ytb3HMaLb43IJvkLb-1765594102'



# (10분 정도 이후) 2차 K8S Secret 확인
# secret 리소스가 재생성되지는 않았고, Data 값만 바뀌었다
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl view-secret -n demo-ns vso-db-demo --all
_raw='{"password":"-PPTICp0hmKDTroV2VZH","username":"v-demo-aut-dev-post-m2DR5ZUKFkWoxHGBTevc-1765594934"}'
password='-PPTICp0hmKDTroV2VZH'
username='v-demo-aut-dev-post-m2DR5ZUKFkWoxHGBTevc-1765594934'



# AGE를 보면 파드가 rollout 되었음을 알 수 있다.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get pod -n demo-ns
NAME                           READY   STATUS    RESTARTS   AGE
vso-db-demo-5c578dd8dc-6z4bs   1/1     Running   0          17s
vso-db-demo-5c578dd8dc-cpp99   1/1     Running   0          17s
vso-db-demo-5c578dd8dc-txccl   1/1     Running   0          14s



# deployment Events 확인 : 10분 간격으로 파드를 비중에 따라 Rollout 동작
## vaultdynamicsecret 에 'renewalPercent: 67' 설정으로 secret's TTL(10분)의 67% 정도에 신규 시크릿을 생성 후 rolloutRestartTargets 에 의해 동작
## RenewalPercent is the percent out of 100 of a dynamic secret's TTL when new secrets are generated. Defaults to 67 percent plus up to 10% jitter.
^[\(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl describe deploy -n demo-ns
Name:                   vso-db-demo
Namespace:              demo-ns
CreationTimestamp:      Sat, 13 Dec 2025 11:48:22 +0900
Labels:                 test=vso-db-demo
Annotations:            deployment.kubernetes.io/revision: 3
Selector:               test=vso-db-demo
Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  1 max unavailable, 25% max surge
Pod Template:
  Labels:       test=vso-db-demo
  Annotations:  vso.secrets.hashicorp.com/restartedAt: 2025-12-13T03:02:14Z
  Containers:
   example:
    Image:      nginx:latest
    Port:       <none>
    Host Port:  <none>
    Limits:
      cpu:     500m
      memory:  512Mi
    Requests:
      cpu:     250m
      memory:  50Mi
    Liveness:  http-get http://:80/ delay=3s timeout=1s period=3s #success=1 #failure=3
    Environment:
      DB_PASSWORD:  <set to the key 'password' in secret 'vso-db-demo'>  Optional: false
      DB_USERNAME:  <set to the key 'username' in secret 'vso-db-demo'>  Optional: false
    Mounts:
      /etc/secrets from secrets (ro)
  Volumes:
   secrets:
    Type:          Secret (a volume populated by a Secret)
    SecretName:    vso-db-demo
    Optional:      false
  Node-Selectors:  <none>
  Tolerations:     <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  vso-db-demo-674bff7fd (0/0 replicas created), vso-db-demo-69848c8d56 (0/0 replicas created)
NewReplicaSet:   vso-db-demo-b7568b5fd (3/3 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  14m   deployment-controller  Scaled up replica set vso-db-demo-69848c8d56 from 0 to 3
  Normal  ScalingReplicaSet  14m   deployment-controller  Scaled up replica set vso-db-demo-674bff7fd from 0 to 1
  Normal  ScalingReplicaSet  14m   deployment-controller  Scaled down replica set vso-db-demo-69848c8d56 from 3 to 2
  Normal  ScalingReplicaSet  14m   deployment-controller  Scaled up replica set vso-db-demo-674bff7fd from 1 to 2
  Normal  ScalingReplicaSet  14m   deployment-controller  Scaled down replica set vso-db-demo-69848c8d56 from 2 to 1
  Normal  ScalingReplicaSet  14m   deployment-controller  Scaled up replica set vso-db-demo-674bff7fd from 2 to 3
  Normal  ScalingReplicaSet  14m   deployment-controller  Scaled down replica set vso-db-demo-69848c8d56 from 1 to 0
  Normal  ScalingReplicaSet  32s   deployment-controller  Scaled up replica set vso-db-demo-b7568b5fd from 0 to 1
  Normal  ScalingReplicaSet  32s   deployment-controller  Scaled down replica set vso-db-demo-674bff7fd from 3 to 2
  Normal  ScalingReplicaSet  32s   deployment-controller  Scaled up replica set vso-db-demo-b7568b5fd from 1 to 2
  Normal  ScalingReplicaSet  29s   deployment-controller  Scaled down replica set vso-db-demo-674bff7fd from 2 to 1
  Normal  ScalingReplicaSet  29s   deployment-controller  Scaled up replica set vso-db-demo-b7568b5fd from 2 to 3
  Normal  ScalingReplicaSet  27s   deployment-controller  Scaled down replica set vso-db-demo-674bff7fd from 1 to 0



# 실제 postgresql 에 사용자 정보 확인 : 계속 추가되고 있음..
# 각 줄이 접속 계정을 뜻함
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ 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-DzKM6Sd0Jy6QQHNptTE9-1765594102 | Password valid until 2025-12-13 02:58:27+00
 v-demo-aut-dev-post-Ht0Ytb3HMaLb43IJvkLb-1765594102 | Password valid until 2025-12-13 03:08:27+00
 v-demo-aut-dev-post-LwiRB37RfcNZZiV4WPRE-1765594102 | Password valid until 2025-12-13 03:08:27+00
 v-demo-aut-dev-post-TJzZ8PrAIJbFlUlOrR2D-1765594966 | Password valid until 2025-12-13 03:12:51+00
 v-demo-aut-dev-post-VqfXbDlkfoTzB5XN1crh-1765594102 | Password valid until 2025-12-13 02:58:27+00
 v-demo-aut-dev-post-m2DR5ZUKFkWoxHGBTevc-1765594934 | Password valid until 2025-12-13 03:12:19+00


# vault 에서 lease 조회
# 위에서 조회한 각 줄에 매칭되는 KEY 정보
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault list sys/leases/lookup/demo-db/creds/dev-postgres
Keys
----
GUmnsxVtfz7fmajWGPlvUq8s
HTZPYHnoHUblecMrTUSnC6Qo
Tv1Fh9cz8Baj2GcK5kdtsZ4g
ah9EHlxbfcRzXgdhPx4ZcecH



# 특정 lease 삭제(Revoke)
# revocation_statements="REVOKE ALL ON DATABASE postgres FROM  \"{{name}}\";" \
vault lease revoke demo-db/creds/dev-postgres/<LEASE_ID>
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault lease revoke demo-db/creds/dev-postgres/GUmnsxVtfz7fmajWGPlvUq8s
All revocation operations queued successfully!

# 기존에 있던 "GUmnsxVtfz7fmajWGPlvUq8s" 값이 제거됨
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault list sys/leases/lookup/demo-db/creds/dev-postgres
Keys
----
HTZPYHnoHUblecMrTUSnC6Qo
Tv1Fh9cz8Baj2GcK5kdtsZ4g
ah9EHlxbfcRzXgdhPx4ZcecH



# 동적 계정 생성 : Authentication Methods(token)
## creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
##   GRANT ALL PRIVILEGES ON DATABASE postgres TO \"{{name}}\";" \
### 키 동적으로 3개 만들기
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault read demo-db/creds/dev-postgres
Key                Value
---                -----
lease_id           demo-db/creds/dev-postgres/ekvgYkfrtafztFnp3ZWQ5h5I
lease_duration     10m
lease_renewable    true
password           TqU9ptNrdQmsGtV6-v4Z
username           v-token-dev-post-M0cmMw0f9B5K7ZzyvrqL-1765595168

(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault read demo-db/creds/dev-postgres
Key                Value
---                -----
lease_id           demo-db/creds/dev-postgres/9Ko7MVOsiwzXsPWzRn37KvXv
lease_duration     10m
lease_renewable    true
password           03Nyib3hIT3olRnl2Cg-
username           v-token-dev-post-rbq5XlzRUoSp8fvPE9d4-1765595169

(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault read demo-db/creds/dev-postgres
Key                Value
---                -----
lease_id           demo-db/creds/dev-postgres/TQ4K70nL0PNqYZBVFVhv0l5r
lease_duration     10m
lease_renewable    true
password           UY86lZdNdmy9pq4IvQL-
username           v-token-dev-post-u0Q3xAbHgG4YMprIx9P7-1765595170



# 새로운 키까지 생성되어 값이 증가됨
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ 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-DzKM6Sd0Jy6QQHNptTE9-1765594102 | Password valid until 2025-12-13 02:58:27+00
 v-demo-aut-dev-post-Ht0Ytb3HMaLb43IJvkLb-1765594102 | Password valid until 2025-12-13 03:08:27+00
 v-demo-aut-dev-post-LwiRB37RfcNZZiV4WPRE-1765594102 | Password valid until 2025-12-13 03:08:27+00
 v-demo-aut-dev-post-TJzZ8PrAIJbFlUlOrR2D-1765594966 | Password valid until 2025-12-13 03:12:51+00
 v-demo-aut-dev-post-VqfXbDlkfoTzB5XN1crh-1765594102 | Password valid until 2025-12-13 02:58:27+00
 v-demo-aut-dev-post-m2DR5ZUKFkWoxHGBTevc-1765594934 | Password valid until 2025-12-13 03:12:19+00
 v-token-dev-post-M0cmMw0f9B5K7ZzyvrqL-1765595168    | Password valid until 2025-12-13 03:16:13+00
 v-token-dev-post-rbq5XlzRUoSp8fvPE9d4-1765595169    | Password valid until 2025-12-13 03:16:14+00
 v-token-dev-post-u0Q3xAbHgG4YMprIx9P7-1765595170    | Password valid until 2025-12-13 03:16:15+00



# vault 로그
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl stern -n vault -l app.kubernetes.io/name=vault
+ vault-0 › vault
vault-0 vault 2025-12-13T02:19:06.980Z [DEBUG] secrets.identity.identity_15a1209a: rotating OIDC key: key=default
vault-0 vault 2025-12-13T02:19:07.187Z [DEBUG] secrets.identity.identity_15a1209a: generated OIDC public key for future use: key_id=9d391746-490b-1877-b13c-ebad9077a788
vault-0 vault 2025-12-13T02:19:07.187Z [DEBUG] secrets.identity.identity_15a1209a: rotated OIDC public key, now using: key_id=97d1e437-6956-858a-a76e-03521e9af991
vault-0 vault 2025-12-13T02:42:02.678Z [INFO]  secrets.database.database_718ff9de: initializing database rotation queue
vault-0 vault 2025-12-13T02:42:02.678Z [INFO]  core: successful mount: namespace="" path=demo-db/ type=database version="v1.19.0+builtin.vault"
vault-0 vault 2025-12-13T02:42:02.689Z [INFO]  secrets.database.database_718ff9de: populating role rotation queue
vault-0 vault 2025-12-13T02:42:02.689Z [DEBUG] secrets.database.database_718ff9de: deleting WAL with nil role or static account: WAL ID=d47bea8a-4606-1521-f005-81b44b1a6045
vault-0 vault 2025-12-13T02:42:02.689Z [INFO]  secrets.database.database_718ff9de: starting periodic ticker
vault-0 vault 2025-12-13T02:42:29.561Z [DEBUG] secrets.database.database_718ff9de: got database plugin instance: type=pgx
vault-0 vault 2025-12-13T02:42:29.574Z [DEBUG] secrets.database.database_718ff9de: created database object: name=demo-db plugin_name=postgresql-database-plugin
vault-0 vault 2025-12-13T02:42:29.574Z [DEBUG] secrets.database.database_718ff9de: Deregistering rotation job: mount=demo-db/config/demo-db   
vault-0 vault 2025-12-13T02:48:22.307Z [DEBUG] identity: creating a new entity: alias="id:\"c230b599-514a-3864-5302-856ad1acc622\" canonical_id:\"7db9f3fc-fa79-baf3-1555-fe9eeb75a245\" mount_type:\"kubernetes\" mount_accessor:\"auth_kubernetes_1c29cbdf\" mount_path:\"auth/demo-auth-mount/\" metadata:{key:\"service_account_name\" value:\"demo-dynamic-app\"} metadata:{key:\"service_account_namespace\" value:\"demo-ns\"} metadata:{key:\"service_account_secret_name\" value:\"\"} metadata:{key:\"service_account_uid\" value:\"5d6acef7-c301-4dab-ab58-05aead65421a\"} name:\"5d6acef7-c301-4dab-ab58-05aead65421a\" creation_time:{seconds:1765594102 nanos:307596585} last_update_time:{seconds:1765594102 nanos:307596585} namespace_id:\"root\" local_bucket_key:\"packer/local-aliases/buckets/44\""
vault-0 vault 2025-12-13T02:58:22.349Z [INFO]  expiration: revoked lease: lease_id=demo-db/creds/dev-postgres/11xFNBUlaL2rCZUqjKTDX3wA        
vault-0 vault 2025-12-13T02:58:22.378Z [INFO]  expiration: revoked lease: lease_id=demo-db/creds/dev-postgres/gcDJq8yj2d91SCmaPd1zAHDN        
vault-0 vault 2025-12-13T03:04:54.286Z [INFO]  expiration: revoked lease: lease_id=demo-db/creds/dev-postgres/GUmnsxVtfz7fmajWGPlvUq8s

 

 

 

 

PKI secret

  • PKI 인증서를 사용하는 애플리케이션에서 동적으로 변경된 PKI 인증서를 사용할 수 있도록 지원

 

  • 기본 설정 - Docs
# Enable the PKI secrets engine at its default path.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault secrets enable pki
Success! Enabled the pki secrets engine at: pki/


# By default the KPI secrets engine sets the time-to-live (TTL) to 30 days
# Configure the max lease time-to-live (TTL) to 8760h.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault secrets tune -max-lease-ttl=8760h pki
Success! Tuned the secrets engine at: pki/


# Vault can accept an existing key pair, or it can generate its own self-signed root. 
# In general, we recommend maintaining your root CA outside of Vault and providing Vault a signed intermediate CA.
# Generate a self-signed certificate valid for 8760h
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault write pki/root/generate/internal \
>     common_name=example.com \
ttl=8760>     ttl=8760h
WARNING! The following warnings were returned from Vault:

  * This mount hasn't configured any authority information access (AIA)
  fields; this may make it harder for systems to find missing certificates
  in the chain or to validate revocation status of certificates. Consider
  updating /config/urls or the newly generated issuer with this information.

Key              Value
---              -----
certificate      -----BEGIN CERTIFICATE-----
MIIDNTCCAh2gAwIBAgIUaVbYRPOhcRBRd77L5PTbLC7xhJgwDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMjUxMjEzMDMxMDU5WhcNMjYx
MjEzMDMxMTI5WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAK8GE/hMmJolg5uaHt18Clr10kenHQHu/OqDIpbO
NaP7By+/w+MTf4LvDD68V8Tg5rT4bc+RloVi3yrZSLmqTtFdaqHwWXS4/pXT5VOE
tFIzNvSk0ZT/Yx3326kaf1dAkj4/T2gqvhgniLso4Ba32N8Adj68lzW1779BklC4
ziMUPS84eROyUXGigwzrRqhxJdgQqUb137aMjEVN81R4/M/IuMThmyShAV6I+p2C
/jC41q6+Qim2Bk0pZNJGb09Gn8HHGHLydTkE+t14nPbOvK4frKPKjW309MeSrpxf
2exPtCJf67j7KFZyZKtuAWFKGFlFbg1MPAt2+vjYUQMAkGcCAwEAAaN7MHkwDgYD
VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFPgUUFrTpCxx
fdX5i2DbyDZ/qupDMB8GA1UdIwQYMBaAFPgUUFrTpCxxfdX5i2DbyDZ/qupDMBYG
A1UdEQQPMA2CC2V4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQAheGwJLVY2
5Rw38x1CymGGDWKyczOxHTCYA6NZchKns5bWcVmGRRBpNyrXvi4Brz9j7z9/lDrI
tSAkHA3cf8wI4SjtxPuCJ/6fvIfAY6/O190wy5USeOnVoXtQ48C4VwOl1Mf3d/65
DJD+75bIczbots90BvYXPGhaAadmYXGgHLzuYHYaJ5NVFAPIiH7YF4I6BI75lxud
2I3Y4UVOEqqACf5aCDaoQ11L18u1l0sO3Yq77JefOI4KIfYe9y5GFezm6PuFn/YC
22g5/xNGPEbUelmx/WZWW9CmGrhmpehjtuz1gVO7nTyxHX8oHP01rbw7lLFRA1Eb
7VBJfORWE9gX
-----END CERTIFICATE-----
expiration       1797131489
issuer_id        f6ce8e27-0695-893b-83a3-d46de7ca4e27
issuer_name      n/a
issuing_ca       -----BEGIN CERTIFICATE-----
MIIDNTCCAh2gAwIBAgIUaVbYRPOhcRBRd77L5PTbLC7xhJgwDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMjUxMjEzMDMxMDU5WhcNMjYx
MjEzMDMxMTI5WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAK8GE/hMmJolg5uaHt18Clr10kenHQHu/OqDIpbO
NaP7By+/w+MTf4LvDD68V8Tg5rT4bc+RloVi3yrZSLmqTtFdaqHwWXS4/pXT5VOE
tFIzNvSk0ZT/Yx3326kaf1dAkj4/T2gqvhgniLso4Ba32N8Adj68lzW1779BklC4
ziMUPS84eROyUXGigwzrRqhxJdgQqUb137aMjEVN81R4/M/IuMThmyShAV6I+p2C
/jC41q6+Qim2Bk0pZNJGb09Gn8HHGHLydTkE+t14nPbOvK4frKPKjW309MeSrpxf
2exPtCJf67j7KFZyZKtuAWFKGFlFbg1MPAt2+vjYUQMAkGcCAwEAAaN7MHkwDgYD
VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFPgUUFrTpCxx
fdX5i2DbyDZ/qupDMB8GA1UdIwQYMBaAFPgUUFrTpCxxfdX5i2DbyDZ/qupDMBYG
A1UdEQQPMA2CC2V4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQAheGwJLVY2
5Rw38x1CymGGDWKyczOxHTCYA6NZchKns5bWcVmGRRBpNyrXvi4Brz9j7z9/lDrI
tSAkHA3cf8wI4SjtxPuCJ/6fvIfAY6/O190wy5USeOnVoXtQ48C4VwOl1Mf3d/65
DJD+75bIczbots90BvYXPGhaAadmYXGgHLzuYHYaJ5NVFAPIiH7YF4I6BI75lxud
2I3Y4UVOEqqACf5aCDaoQ11L18u1l0sO3Yq77JefOI4KIfYe9y5GFezm6PuFn/YC
22g5/xNGPEbUelmx/WZWW9CmGrhmpehjtuz1gVO7nTyxHX8oHP01rbw7lLFRA1Eb
7VBJfORWE9gX
-----END CERTIFICATE-----
key_id           986c7d3d-0980-6668-363b-890448fd23dd
key_name         n/a
serial_number    69:56:d8:44:f3:a1:71:10:51:77:be:cb:e4:f4:db:2c:2e:f1:84:98





(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ echo "-----BEGIN CERTIFICATE-----
DNTC> MIIDNTCCAh2gAwIBAgIUaVbYRPOhcRBRd77L5PTbLC7xhJgwDQYJKoZIhvcNAQEL
> BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMjUxMjEzMDMxMDU5WhcNMjYx
jEzMD> MjEzMDMxMTI5WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN
> AQEBBQADggEPADCCAQoCggEBAK8GE/hMmJolg5uaHt18Clr10kenHQHu/OqDIpbO
> NaP7By+/w+MTf4LvDD68V8Tg5rT4bc+RloVi3yrZSLmqTtFdaqHwWXS4/pXT5VOE
tFIzN> tFIzNvSk0ZT/Yx3326kaf1dAkj4/T2gqvhgniLso4Ba32N8Adj68lzW1779BklC4
4eROyU> ziMUPS84eROyUXGigwzrRqhxJdgQqUb137aMjEVN81R4/M/IuMThmyShAV6I+p2C
> /jC41q6+Qim2Bk0pZNJGb09Gn8HHGHLydTkE+t14nPbOvK4frKPKjW309MeSrpxf
> 2exPtCJf67j7KFZyZKtuAWFKGFlFbg1MPAt2+vjYUQMAkGcCAwEAAaN7MHkwDgYD
> VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFPgUUFrTpCxx
i2DbyD> fdX5i2DbyDZ/qupDMB8GA1UdIwQYMBaAFPgUUFrTpCxxfdX5i2DbyDZ/qupDMBYG
QQPM> A1UdEQQPMA2CC2V4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQAheGwJLVY2
1CymGG> 5Rw38x1CymGGDWKyczOxHTCYA6NZchKns5bWcVmGRRBpNyrXvi4Brz9j7z9/lDrI
3cf8wI> tSAkHA3cf8wI4SjtxPuCJ/6fvIfAY6/O190wy5USeOnVoXtQ48C4VwOl1Mf3d/65
> DJD+75bIczbots90BvYXPGhaAadmYXGgHLzuYHYaJ5NVFAPIiH7YF4I6BI75lxud
EqqAC> 2I3Y4UVOEqqACf5aCDaoQ11L18u1l0sO3Yq77JefOI4KIfYe9y5GFezm6PuFn/YC
> 22g5/xNGPEbUelmx/WZWW9CmGrhmpehjtuz1gVO7nTyxHX8oHP01rbw7lLFRA1Eb
Jf> 7VBJfORWE9gX
--END> -----END CERTIFICATE-----" | openssl x509 -noout -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            69:56:d8:44:f3:a1:71:10:51:77:be:cb:e4:f4:db:2c:2e:f1:84:98
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = example.com                 ### CA 인증서라서 Issuer/Subject 정보 동일
        Validity
            Not Before: Dec 13 03:10:59 2025 GMT
            Not After : Dec 13 03:11:29 2026 GMT
        Subject: CN = example.com                ### CA 인증서라서 Issuer/Subject 정보 동일
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:af:06:13:f8:4c:98:9a:25:83:9b:9a:1e:dd:7c:
                    0a:5a:f5:d2:47:a7:1d:01:ee:fc:ea:83:22:96:ce:
                    35:a3:fb:07:2f:bf:c3:e3:13:7f:82:ef:0c:3e:bc:
                    57:c4:e0:e6:b4:f8:6d:cf:91:96:85:62:df:2a:d9:
                    48:b9:aa:4e:d1:5d:6a:a1:f0:59:74:b8:fe:95:d3:
                    e5:53:84:b4:52:33:36:f4:a4:d1:94:ff:63:1d:f7:
                    db:a9:1a:7f:57:40:92:3e:3f:4f:68:2a:be:18:27:
                    88:bb:28:e0:16:b7:d8:df:00:76:3e:bc:97:35:b5:
                    ef:bf:41:92:50:b8:ce:23:14:3d:2f:38:79:13:b2:
                    51:71:a2:83:0c:eb:46:a8:71:25:d8:10:a9:46:f5:
                    df:b6:8c:8c:45:4d:f3:54:78:fc:cf:c8:b8:c4:e1:
                    9b:24:a1:01:5e:88:fa:9d:82:fe:30:b8:d6:ae:be:
                    42:29:b6:06:4d:29:64:d2:46:6f:4f:46:9f:c1:c7:
                    18:72:f2:75:39:04:fa:dd:78:9c:f6:ce:bc:ae:1f:
                    ac:a3:ca:8d:6d:f4:f4:c7:92:ae:9c:5f:d9:ec:4f:
                    b4:22:5f:eb:b8:fb:28:56:72:64:ab:6e:01:61:4a:
                    18:59:45:6e:0d:4c:3c:0b:76:fa:f8:d8:51:03:00:
                    90:67
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign, CRL Sign        ### CRL Sign도 들어감
            X509v3 Basic Constraints: critical
                CA:TRUE                           ### CA 인증서 확인
            X509v3 Subject Key Identifier:
                F8:14:50:5A:D3:A4:2C:71:7D:D5:F9:8B:60:DB:C8:36:7F:AA:EA:43
            X509v3 Authority Key Identifier:
                F8:14:50:5A:D3:A4:2C:71:7D:D5:F9:8B:60:DB:C8:36:7F:AA:EA:43
            X509v3 Subject Alternative Name:
                DNS:example.com                   ### 도메인 확인
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        21:78:6c:09:2d:56:36:e5:1c:37:f3:1d:42:ca:61:86:0d:62:
        b2:73:33:b1:1d:30:98:03:a3:59:72:12:a7:b3:96:d6:71:59:
        86:45:10:69:37:2a:d7:be:2e:01:af:3f:63:ef:3f:7f:94:3a:
        c8:b5:20:24:1c:0d:dc:7f:cc:08:e1:28:ed:c4:fb:82:27:fe:
        9f:bc:87:c0:63:af:ce:d7:dd:30:cb:95:12:78:e9:d5:a1:7b:
        50:e3:c0:b8:57:03:a5:d4:c7:f7:77:fe:b9:0c:90:fe:ef:96:
        c8:73:36:e8:b6:cf:74:06:f6:17:3c:68:5a:01:a7:66:61:71:
        a0:1c:bc:ee:60:76:1a:27:93:55:14:03:c8:88:7e:d8:17:82:
        3a:04:8e:f9:97:1b:9d:d8:8d:d8:e1:45:4e:12:aa:80:09:fe:
        5a:08:36:a8:43:5d:4b:d7:cb:b5:97:4b:0e:dd:8a:bb:ec:97:
        9f:38:8e:0a:21:f6:1e:f7:2e:46:15:ec:e6:e8:fb:85:9f:f6:
        02:db:68:39:ff:13:46:3c:46:d4:7a:59:b1:fd:66:56:5b:d0:
        a6:1a:b8:66:a5:e8:63:b6:ec:f5:81:53:bb:9d:3c:b1:1d:7f:
        28:1c:fd:35:ad:bc:3b:94:b1:51:03:51:1b:ed:50:49:7c:e4:
        56:13:d8:17
        
        

# Vault의 PKI secrets engine(pki 마운트)에 대해 인증서 발급자(CA) 접근 URL과 CRL(인증서 폐기 목록) 접근 URL을 설정.
# 이 설정 값들은 Vault가 새로 발급하는 X.509 인증서의 확장 필드(AIA / CDP)에 포함되어 클라이언트와 인증서 검증자에게 전달됨.
# Configure the PKI secrets engine certificate issuing and certificate revocation list (CRL) endpoints to use the Vault service in the default namespace.
## issuing_certificates="http://vault.vault.svc:8200/v1/pki/ca"     # 발급자(issuer) 인증서(또는 CA 번들)를 가져오는 URL
## crl_distribution_points="http://vault.vault.svc:8200/v1/pki/crl" # CRL(Certificate Revocation List)을 내려받을 수 있는 URL
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault write pki/config/urls \
  issuin>     issuing_certificates="http://vault.vault.svc:8200/v1/pki/ca" \
ribution>     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               []



# Vault가 이제 발급하는 모든 인증서에 다음과 같은 확장(extension)을 포함할 수 있습니다:
## Authority Information Access (AIA) 또는 Issuing Certificate 관련 extension → issuing_certificates URL 포함
## CRL Distribution Points (CDP) → crl_distribution_points URL 포함
# 클라이언트(또는 CA 체인 검증 라이브러리)는 인증서의 AIA/CDP를 보고 해당 URL로 접속하여 CA 인증서나 CRL을 내려받아 검증에 사용합니다.
# 즉, 이 설정으로 인해 인증서의 메타정보가 외부(또는 내부) URL을 통해 자동으로 유효성/폐기 여부를 확인할 수 있게 됩니다.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ 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               []

 

 

    • ca 파일 다운로드 후 확인 해보자
    • http://127.0.0.1:8200/v1/pki/ca
    • http://127.0.0.1:30000/v1/pki/crl

 

 

 

# Configure a role named example-dot-com that enables the creation of certificates example.com domain with any subdomains.
## The role, example-dot-com, is a logical name that maps to a policy used to generate credentials. 
## This generates a number of endpoints that are used by the Kubernetes service account to issue and sign these certificates. 
## A policy must be created that enables these paths.(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault write pki/roles/example-dot-com \
>     allowed_domains=example.com \
   allow>     allow_subdomains=true \
ttl=72h>     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



# Create a policy named pki that enables read access to the PKI secrets engine paths.
## These paths enable the token to view all the roles created for this PKI secrets engine and 
## access the sign and issues operations for the example-dot-com role.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault policy write pki - <<EOF
> path "pki*"                        { capabilities = ["read", "list"] }
> path "pki/sign/example-dot-com"    { capabilities = ["create", "update"] }
"pki/is> path "pki/issue/example-dot-com"   { capabilities = ["create"] }
> EOF
Success! Uploaded policy: pki

 

 

  • 동작 설정 및 확인 : Cert Manager 활용
# kubernetes 인증 활성화
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault auth enable kubernetes
Success! Enabled kubernetes auth method at: kubernetes/

(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault write auth/kubernetes/config kubernetes_host="https://kubernetes.default.svc"
Success! Data written to: auth/kubernetes/config


# Create a Kubernetes authentication role named issuer that binds the pki policy with a Kubernetes service account named issuer.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ vault write auth/kubernetes/role/issuer \
>     bound_service_account_names=issuer \
und_s>     bound_service_account_namespaces=default \
es=p>     policies=pki \

>     ttl=20m
Success! Data written to: auth/kubernetes/role/issuer



# Deploy Cert Manager
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ 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


(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get crd | grep cert-manager
certificaterequests.cert-manager.io           2025-12-13T03:24:19Z
certificates.cert-manager.io                  2025-12-13T03:24:19Z
challenges.acme.cert-manager.io               2025-12-13T03:24:19Z
clusterissuers.cert-manager.io                2025-12-13T03:24:19Z
issuers.cert-manager.io                       2025-12-13T03:24:19Z
orders.acme.cert-manager.io                   2025-12-13T03:24:19Z


(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl create namespace cert-manager
namespace/cert-manager created


(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ helm repo add jetstack https://charts.jetstack.io
"jetstack" has been added to your repositories


(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "jetstack" chart repository
...Successfully got an update from the "hashicorp" chart repository
...Successfully got an update from the "argo" chart repository
...Successfully got an update from the "prometheus-community" chart repository
...Successfully got an update from the "geek-cookbook" chart repository
...Successfully got an update from the "bitnami" chart repository
Update Complete. ⎈Happy Helming!⎈


(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ helm install cert-manager --namespace cert-manager --version v1.12.3 jetstack/cert-manager
NAME: cert-manager
LAST DEPLOYED: Sat Dec 13 12:24:34 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/


(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get pods --namespace cert-manager
NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-cainjector-74bf899bd6-vwf7f   1/1     Running   0          104s
cert-manager-fb6f6945f-89qss               1/1     Running   0          104s
cert-manager-webhook-8fc69bc68-zpprs       1/1     Running   0          104s


# Configure an issuer and generate a certificate

# Create a service account named issuer within the default namespace.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl create serviceaccount issuer
serviceaccount/issuer created


# The service account generated a secret that is required by the Issuer automatically in Kubernetes 1.23. 
# In Kubernetes 1.24+, you need to create the secret explicitly.
# Create an issuer secret.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ cat >> issuer-secret.yaml <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: issuer-token-lmzpj
  annotations:
    kubernetes.io/service-account.name: issuer ### 위에서 방금 만든 ServiceAccount 설정
type: kubernetes.io/service-account-token
EOF



(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl apply -f issuer-secret.yaml
secret/issuer-token-lmzpj created

(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get secrets
NAME                 TYPE                                  DATA   AGE
issuer-token-lmzpj   kubernetes.io/service-account-token   3      27s



# Create a variable named ISSUER_SECRET_REF to capture the secret name.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ ISSUER_SECRET_REF=$(kubectl get secrets --output=json | jq -r '.items[].metadata | select(.name|startswith("issuer-token-")).name')

(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ echo $ISSUER_SECRET_REF
issuer-token-lmzpj

(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl view-secret $ISSUER_SECRET_REF --all
ca.crt='-----BEGIN CERTIFICATE-----
MIIDBTCCAe2gAwIBAgIIEal0zNtU7B0wDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKa3ViZXJuZXRlczAeFw0yNTEyMDYwNzE3NTlaFw0zNTEyMDQwNzIyNTlaMBUx
EzARBgNVBAMTCmt1YmVybmV0ZXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDFOmYBd0sjfR1rruRRih2vLXb92gTZoDLzMvnMQGKKWyOPxOGV4n47X3/H
p8D18ySSUcyrj/CKCGgrChCu7gPQGCVCJkQ4MAc2tNXxcxtGQPXVQph8nfUKCLfx
sIMFtZb7zMUZdxweTfP53i1DkkfzYLvzbv7U+mc/fdy/8J4lq3v5d/SfgQsqzPrp
xR++W/aryTVGKmZHMZotGXUAZioMNxbeAv+uqGHDhMuzBR9FULpz7NuXn01prjpW
Tm8xy0AtutPwnlttSJaXvu15FhmYRomq/ekBdxajsRTZDmOBCWvRO+JBj5jmiFeK
vo4Ud2YbeZ9P4nW1tS1JZtqpsT67AgMBAAGjWTBXMA4GA1UdDwEB/wQEAwICpDAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS0bW0TIiNhfenFT4sy0X91sudaGDAV
BgNVHREEDjAMggprdWJlcm5ldGVzMA0GCSqGSIb3DQEBCwUAA4IBAQCaT67QyMZL
V5Re0pctqzvPWMi4Vgyb4Yv/10Y0tgmPxlsD9n0nwCjG4atEEzCaZ4aBbcznwNCF
9gl3vRfsJGGBPeRF77eqnWk1liG25NW0BIFcrMvA4rrO65x5UYxivPuzVIScNKLU
3himfxzgfk4eOSldQW34T0Y8XovTB21Tq1GNjjObs7zQ6BMrP9AWMZTzJnCc1wef
LzRjzIzI6qLB9rSsEP06EhU97fnxs3Zn3oM1Q4oVSTQbb+kk9ny+TGRZaw6S17aD
yc6O62qCtpA4l0dGKCYE9CNL9BsN65y/YeEv87FNys3X98xsJ/VUNpZZZmne/mbb
7UsbO8AGhXau
-----END CERTIFICATE-----
'
namespace='default'
token='eyJhbGciOiJSUzI1NiIsImtpZCI6InROeDJzUy1LUlpYeHo1MklVWDRlTlRsVzNGUkMxZ0lpSHZ3aUVYZUkwb0UifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6Imlzc3Vlci10b2tlbi1sbXpwaiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJpc3N1ZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJiMDk2OGJlMy02MGE2LTQwYTQtYWZhZi1mM2ViNjBiZmRhZWIiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDppc3N1ZXIifQ.YaAlcOH0Xl-5swOoJrsopXvZW3J5ANieHACbsaYEGPbOsamEeEyOqDzUxbzWaIeBfiOHOMI18gq17FfkFsMFe4LrWKQT0S0lMpVgq6QVhzwCu3Smb6GheyZvWOn_HN_OmgPwHHzSal6-madaOkqMHy-N_GvA1HoR2UMRLGNMptzGt-E7LD6jqMIDOjG35SnULF24wFci8IeQZGedHxignwpBhUpUnGI2s-tR0aPxj4Q_KQ4PhiRDurn8Z2wglf6XTN5-j5U7hMG2vroPP_7eSSkIAoQzPPqhnDvZURE6blNY97mpSMUO0CN9020P1N35HbdGlipTZRLJzkGfFMKvTw'



# Define an Issuer, named vault-issuer, that sets Vault as a certificate issuer.
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



# Create the vault-issuer Issuer.
# The specification defines the signing endpoint and the authentication endpoint and credentials.
## metadata.name sets the name of the Issuer to vault-issuer
## spec.vault.server sets the server address to the Kubernetes service created in the default namespace
## spec.vault.path is the signing endpoint created by Vault PKI example-dot-com role
## spec.vault.auth.kubernetes.mountPath sets the Vault authentication endpoint
## spec.vault.auth.kubernetes.role sets the Vault Kubernetes role to issuer
## spec.vault.auth.kubernetes/secretRef.name sets the secret for the Kubernetes service account
## spec.vault.auth.kubernetes/secretRef.key sets the type to token.
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl apply --filename vault-issuer.yaml
issuer.cert-manager.io/vault-issuer created

(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get issuer.cert-manager.io/vault-issuer
NAME           READY   AGE
vault-issuer   True    2s



# Define a certificate named example-com.
# The Certificate, named example-com, requests from Vault the certificate through the Issuer, named vault-issuer.
# The common name and DNS names are names within the allowed domains for the configured Vault endpoint.
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

(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl apply --filename example-com-cert.yaml # Create the example-com certificate.
certificate.cert-manager.io/example-com created

(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ 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   3s




# View the details of the example-com certificate.
# cert-manager가 Certificate 리소스를 처리하며 인증서를 발급하는 전체 흐름을 단계별로 확인.
## Certificate 리소스를 생성하면 cert-manager는:
1. Secret 에 기존 private key 있는지 확인
2. 없으면 새 private key 생성
3. CertificateRequest(CSR) 생성
4. Issuer/ClusterIssuer 에게 서명 요청
5. CA 또는 ACME 등에서 서명 받아 certificate 생성
6. 최종 Secret에 private key + cert chain 저장
7. Certificate 상태를 Issued 로 변경
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl describe certificate.cert-manager example-com
Name:         example-com
Namespace:    default
Labels:       <none>
Annotations:  <none>
API Version:  cert-manager.io/v1
Kind:         Certificate
Metadata:
  Creation Timestamp:  2025-12-13T03:29:42Z
  Generation:          1
  Resource Version:    15460
  UID:                 4cdbc493-28ae-4362-98f0-486138eb841d
Spec:
  Common Name:  www.example.com
  Dns Names:
    www.example.com
  Issuer Ref:
    Name:       vault-issuer
  Secret Name:  issuer-token-lmzpj
Status:
  Conditions:
    Last Transition Time:  2025-12-13T03:29:43Z
    Message:               Certificate is up to date and has not expired
    Observed Generation:   1
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Not After:               2025-12-16T03:29:43Z
  Not Before:              2025-12-13T03:29:13Z
  Renewal Time:            2025-12-15T03:29:33Z
  Revision:                1
Events:
  Type    Reason     Age   From                                       Message
  ----    ------     ----  ----                                       -------
  Normal  Issuing    37s   cert-manager-certificates-trigger          Issuing certificate as Secret does not contain a private key
  Normal  Generated  36s   cert-manager-certificates-key-manager      Stored new private key in temporary Secret resource "example-com-2sk8g" 
  Normal  Requested  36s   cert-manager-certificates-request-manager  Created new CertificateRequest resource "example-com-sbkzs"
  Normal  Issuing    36s   cert-manager-certificates-issuing          The certificate has been successfully issued
  
  
  
  ### 설명
  ...
Events:
  Type    Reason     Age   From                                       Message
  ----    ------     ----  ----                                       -------
  # cert-manager가 Certificate 를 살펴보니 Secret(example-com) 안에 private key(비밀키)가 존재하지 않기 때문에 새로 인증서를 발급해야 한다는 뜻.
  # cert-manager가 하는 일 : Secret 조회, key 없음 → “새 key 필요 → 새 certificate 필요” 판단   
  Normal  Issuing    18s   cert-manager-certificates-trigger          Issuing certificate as Secret does not contain a private key

  # cert-manager가 새로운 private key를 생성하고, 임시 Secret에 저장했다는 뜻.
  # cert-manager는 다음 CSR(CertificateRequest)을 만들기 위해 key를 잠시 임시 Secret에 저장한다: 
  # 안전하게 key를 먼저 준비 → 이후 최종 Secret에 병합하는 방식
  Normal  Generated  17s   cert-manager-certificates-key-manager      Stored new private key in temporary Secret resource "example-com-54lj7"

  # cert-manager가 CertificateRequest(CSR) 를 생성했다는 의미.
  # CSR은: 방금 만든 private key 기반으로 Issuer/ClusterIssuer 에게 "서명해 주세요" 요청하는 리소스
  # 이 단계 후 보통 다음이 내부적으로 수행됨, issuer가 CSR을 확인하고 서명 처리 (예: Vault, ACME, CA, SelfSigned 등)
  Normal  Requested  17s   cert-manager-certificates-request-manager  Created new CertificateRequest resource "example-com-lmvtn"

  # Issuer(예: Vault issuer, CA issuer, ACME issuer)가 CSR에 서명했고, cert-manager가 정상적으로 인증서를 발급받아 Secret에 저장했다는 의미.
  # cert-manager는 수행 : CA response(인증서 체인) 수신 -> 최종 Secret(example-com)에 아래 내용 저장 -> CertificateReady = True
  ## private key (임시 Secret → 이동) , certificate (CRT), ca.crt (chain)
  Normal  Issuing    17s   cert-manager-certificates-issuing          The certificate has been successfully issued



#
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl get certificaterequests.cert-manager.io -owide
NAME                APPROVED   DENIED   READY   ISSUER         REQUESTOR                                         STATUS
                  AGE
example-com-sbkzs   True                True    vault-issuer   system:serviceaccount:cert-manager:cert-manager   Certificate fetched from issuer successfully   2m18s




# 결과적으로 Secret/example-com 은 다음을 포함: tls.key , tls.crt . ca.crt (if issuer provides)
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ kubectl view-secret $ISSUER_SECRET_REF --all
ca.crt='-----BEGIN CERTIFICATE-----
MIIDBTCCAe2gAwIBAgIIEal0zNtU7B0wDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKa3ViZXJuZXRlczAeFw0yNTEyMDYwNzE3NTlaFw0zNTEyMDQwNzIyNTlaMBUx
EzARBgNVBAMTCmt1YmVybmV0ZXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDFOmYBd0sjfR1rruRRih2vLXb92gTZoDLzMvnMQGKKWyOPxOGV4n47X3/H
p8D18ySSUcyrj/CKCGgrChCu7gPQGCVCJkQ4MAc2tNXxcxtGQPXVQph8nfUKCLfx
sIMFtZb7zMUZdxweTfP53i1DkkfzYLvzbv7U+mc/fdy/8J4lq3v5d/SfgQsqzPrp
xR++W/aryTVGKmZHMZotGXUAZioMNxbeAv+uqGHDhMuzBR9FULpz7NuXn01prjpW
Tm8xy0AtutPwnlttSJaXvu15FhmYRomq/ekBdxajsRTZDmOBCWvRO+JBj5jmiFeK
vo4Ud2YbeZ9P4nW1tS1JZtqpsT67AgMBAAGjWTBXMA4GA1UdDwEB/wQEAwICpDAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS0bW0TIiNhfenFT4sy0X91sudaGDAV
BgNVHREEDjAMggprdWJlcm5ldGVzMA0GCSqGSIb3DQEBCwUAA4IBAQCaT67QyMZL
V5Re0pctqzvPWMi4Vgyb4Yv/10Y0tgmPxlsD9n0nwCjG4atEEzCaZ4aBbcznwNCF
9gl3vRfsJGGBPeRF77eqnWk1liG25NW0BIFcrMvA4rrO65x5UYxivPuzVIScNKLU
3himfxzgfk4eOSldQW34T0Y8XovTB21Tq1GNjjObs7zQ6BMrP9AWMZTzJnCc1wef
LzRjzIzI6qLB9rSsEP06EhU97fnxs3Zn3oM1Q4oVSTQbb+kk9ny+TGRZaw6S17aD
yc6O62qCtpA4l0dGKCYE9CNL9BsN65y/YeEv87FNys3X98xsJ/VUNpZZZmne/mbb
7UsbO8AGhXau
-----END CERTIFICATE-----
'
namespace='default'
tls.crt='-----BEGIN CERTIFICATE-----
MIIDyzCCArOgAwIBAgIUKurA248sB6Z1XjsWPfDM88iY0I0wDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMjUxMjEzMDMyOTEzWhcNMjUx
MjE2MDMyOTQzWjAaMRgwFgYDVQQDEw93d3cuZXhhbXBsZS5jb20wggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDm/EjvG7cA6BmqOD69UlFGGgrozpErx7lL
o7L5C3pODFdMvSLei65zAOo28V8VKgP4UVjqsyyd7/1Q75tAURQkGO9UcwmaLP5W
OYxVsTGPb1zJPyrpotNatXkXeKfLopaxXhhy8yDt+T78xWEU9RbP3qPxz46Mh/07
XJepQCfE+Er45GJlc94bNJzoDmcEBOXrXcUK0ekBdhs7rTtkHSHDH/Yr0zen580O
P3VO5s5oiAXsa6ApzVULzCp9NSeXxXU4f41c8pBwSrLMHTSvoxF4HBGysFz7s1eX
NMAArKwUea8mpg0xgefF0HNT9wDZG/4MaBbSbL4B7LAlKB6NDxQ/AgMBAAGjggEL
MIIBBzAOBgNVHQ8BAf8EBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF
BwMCMB0GA1UdDgQWBBSsozi5Hrvd4yyy00zb7VeD5sQA1jAfBgNVHSMEGDAWgBT4
FFBa06QscX3V+Ytg28g2f6rqQzBBBggrBgEFBQcBAQQ1MDMwMQYIKwYBBQUHMAKG
JWh0dHA6Ly92YXVsdC52YXVsdC5zdmM6ODIwMC92MS9wa2kvY2EwGgYDVR0RBBMw
EYIPd3d3LmV4YW1wbGUuY29tMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly92YXVs
dC52YXVsdC5zdmM6ODIwMC92MS9wa2kvY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQAN
xk5Q/P3ofEDU9lAid+/34pgSywfWPejADoTmlhIGVxRdJaKyohJ6Lj3+arhWmaax
E3wQteo/SEVySiVaL/xhxp3Clq0uUZGT4B1di6T1oJl38+z2NjOWUj0lvLdE/m+L
JwZEbXmFstgTH+QbIbhLkHTXDDhtS8EWTV62lITG87Ap5UL5K2JoV1MGGnkhXtXp
1rU2UUlR7FlJ5+INSnkJZUROpyFAmVMrXsxENDEPoZqLI/e0fjTvuv64W0hZfqMq
qmHuZD87CA4hxoEsxyIfJcOLLd74Kj9l9Z9QTLbtXV4cnqQq1QLvFUhsGAINJibr
yA26y10ZrX330GFNXCq6
-----END CERTIFICATE-----
'
tls.key='-----BEGIN RSA PRIVATE KEY-----
MIIEpgIBAAKCAQEA5vxI7xu3AOgZqjg+vVJRRhoK6M6RK8e5S6Oy+Qt6TgxXTL0i
3ouucwDqNvFfFSoD+FFY6rMsne/9UO+bQFEUJBjvVHMJmiz+VjmMVbExj29cyT8q
6aLTWrV5F3iny6KWsV4YcvMg7fk+/MVhFPUWz96j8c+OjIf9O1yXqUAnxPhK+ORi
ZXPeGzSc6A5nBATl613FCtHpAXYbO607ZB0hwx/2K9M3p+fNDj91TubOaIgF7Gug
Kc1VC8wqfTUnl8V1OH+NXPKQcEqyzB00r6MReBwRsrBc+7NXlzTAAKysFHmvJqYN
MYHnxdBzU/cA2Rv+DGgW0my+AeywJSgejQ8UPwIDAQABAoIBAQDKkUFdAE8zaljn
oL/UxFYmRjx/AnjpjhQAM6WHJvuxar08vTnRNWpdzNWaLX+XTeuBX8W8vjlIoOjk
i9z5QKGLLprk0qX/IapC7+EUXXr7MUyL6Ou3TWZRTIjPfq6YtRO2pLCITpP4XvdQ
McD74hyJW9UnAUEgGTqJTqBqe6rk3rg5xrK3QUSeWQo6uv30UvN2I20sKUsC2Bi3
fw0pFmz9d267mUTKMN0gVd0owc6SCcK2SooUSJ497qardGHbCUrXlvZmXtJGRYf6
eZABLV/ltqufza2w2iMLUu6kjaTGnQ93sj9YtU/JPUuyAT1aroqzDLDkTaYL69I9
wsYon7axAoGBAO6aRQUOmF3Nf7BDksFWMKr9nAFT+El6d/JOV2C7N0cIGXpSzQmt
IjsroeD0O0eFGOswL12OwGOm4Iaav6Q1mJg41TPpmV4uEicBAMowAO24BYthTB8j
69cxXQvF8UFqsPGpqjLAt/tdibW8Eyg1fP+LeSaqs5sO8BvusFeGzOtrAoGBAPfT
1bZg82BObgmxmX8AfF4qWQOUirdo+b5HH7lS8VrTGAhK6KfQHHBAMZFU24KqofoR
a62FNEDWL731fdYyxw8T4SCHbm5XPtoiNdIQZbEIR2hrWEMWajRgSvOrEwiUsuDw
r+MM/DxbiFQsuh0FKxJ8qPjT1upo3OQhtPOdEaN9AoGBAK3YTQ2AMte1kKFWuqiP
KeqL2YzGJ5Mx3g73sYZTIdVpO1b62VWBhf1irxF+IWrcuOkzNG+QQPDad6DbQ2Jb
gpD2Z7DpNMt/+c3dVzv5edO6Tp/dBl9yBrXFy6t2T2+AUufg3JcZ/3LwFhQJslOL
lUWD04OuwCnr2lofsPA00T19AoGBAKXUZKO1+gSOVok8Arb9zzp/YbLImY2iu8J7
+xlaC9A3glRCM63ezri567EQtBWaMeqP75pbkJx19dpJQ5upvJM1PSY0GUvSK2dx
DsxyVmmAXa/cbGHvxL8pU936sjDCt3NW+oqWbM3CfdW9XAgBJlIngjWGIsAVzQEG
IPwGNQBNAoGBANePL4hUcf9iRA7CWNfjKGpRZuO+kotzG3H7GSCCM6g5KNvXtxQs
FjfgH4mv0sTaAVpiiDNYg4MBk+yPN8WHcwOMy8ILMliYdxTfiB43JYQhB8v8je29
C69UjKx7Ig5yJFO+yg4to8xTvghZ1mw2a55YvEeHIuBC3AKPyButLQT+
-----END RSA PRIVATE KEY-----
'
token='eyJhbGciOiJSUzI1NiIsImtpZCI6InROeDJzUy1LUlpYeHo1MklVWDRlTlRsVzNGUkMxZ0lpSHZ3aUVYZUkwb0UifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6Imlzc3Vlci10b2tlbi1sbXpwaiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJpc3N1ZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJiMDk2OGJlMy02MGE2LTQwYTQtYWZhZi1mM2ViNjBiZmRhZWIiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDppc3N1ZXIifQ.YaAlcOH0Xl-5swOoJrsopXvZW3J5ANieHACbsaYEGPbOsamEeEyOqDzUxbzWaIeBfiOHOMI18gq17FfkFsMFe4LrWKQT0S0lMpVgq6QVhzwCu3Smb6GheyZvWOn_HN_OmgPwHHzSal6-madaOkqMHy-N_GvA1HoR2UMRLGNMptzGt-E7LD6jqMIDOjG35SnULF24wFci8IeQZGedHxignwpBhUpUnGI2s-tR0aPxj4Q_KQ4PhiRDurn8Z2wglf6XTN5-j5U7hMG2vroPP_7eSSkIAoQzPPqhnDvZURE6blNY97mpSMUO0CN9020P1N35HbdGlipTZRLJzkGfFMKvTw'
  
  
  
# tls.crt의 키 값 조회
(⎈|kind-myk8s:N/A) gaji:~/learn-vault-secrets-operator$ echo "-----BEGIN CERTIFICATE-----
> MIIDyzCCArOgAwIBAgIUKurA248sB6Z1XjsWPfDM88iY0I0wDQYJKoZIhvcNAQEL
FjEUM> BQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMjUxMjEzMDMyOTEzWhcNMjUx
QzWjA> MjE2MDMyOTQzWjAaMRgwFgYDVQQDEw93d3cuZXhhbXBsZS5jb20wggEiMA0GCSqG
SIb3D> SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDm/EjvG7cA6BmqOD69UlFGGgrozpErx7lL
> o7L5C3pODFdMvSLei65zAOo28V8VKgP4UVjqsyyd7/1Q75tAURQkGO9UcwmaLP5W
YxVsT> OYxVsTGPb1zJPyrpotNatXkXeKfLopaxXhhy8yDt+T78xWEU9RbP3qPxz46Mh/07
XJepQ> XJepQCfE+Er45GJlc94bNJzoDmcEBOXrXcUK0ekBdhs7rTtkHSHDH/Yr0zen580O
P3VO5> P3VO5s5oiAXsa6ApzVULzCp9NSeXxXU4f41c8pBwSrLMHTSvoxF4HBGysFz7s1eX
AArKw> NMAArKwUea8mpg0xgefF0HNT9wDZG/4MaBbSbL4B7LAlKB6NDxQ/AgMBAAGjggEL
> MIIBBzAOBgNVHQ8BAf8EBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF
wMCMB> BwMCMB0GA1UdDgQWBBSsozi5Hrvd4yyy00zb7VeD5sQA1jAfBgNVHSMEGDAWgBT4
> FFBa06QscX3V+Ytg28g2f6rqQzBBBggrBgEFBQcBAQQ1MDMwMQYIKwYBBQUHMAKG
h0dHA> JWh0dHA6Ly92YXVsdC52YXVsdC5zdmM6ODIwMC92MS9wa2kvY2EwGgYDVR0RBBMw
YIPd3> EYIPd3d3LmV4YW1wbGUuY29tMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly92YXVs
> dC52YXVsdC5zdmM6ODIwMC92MS9wa2kvY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQAN
/P3ofEDU9lA> xk5Q/P3ofEDU9lAid+/34pgSywfWPejADoTmlhIGVxRdJaKyohJ6Lj3+arhWmaax
> E3wQteo/SEVySiVaL/xhxp3Clq0uUZGT4B1di6T1oJl38+z2NjOWUj0lvLdE/m+L
> JwZEbXmFstgTH+QbIbhLkHTXDDhtS8EWTV62lITG87Ap5UL5K2JoV1MGGnkhXtXp
U2UUlR7FlJ> 1rU2UUlR7FlJ5+INSnkJZUROpyFAmVMrXsxENDEPoZqLI/e0fjTvuv64W0hZfqMq
87CA4> qmHuZD87CA4hxoEsxyIfJcOLLd74Kj9l9Z9QTLbtXV4cnqQq1QLvFUhsGAINJibr
0ZrX> yA26y10ZrX330GFNXCq6
-----EN> -----END CERTIFICATE-----" | openssl x509 -noout -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            2a:ea:c0:db:8f:2c:07:a6:75:5e:3b:16:3d:f0:cc:f3:c8:98:d0:8d
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = example.com
        Validity
            Not Before: Dec 13 03:29:13 2025 GMT
            Not After : Dec 16 03:29:43 2025 GMT
        Subject: CN = www.example.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:e6:fc:48:ef:1b:b7:00:e8:19:aa:38:3e:bd:52:
                    51:46:1a:0a:e8:ce:91:2b:c7:b9:4b:a3:b2:f9:0b:
                    7a:4e:0c:57:4c:bd:22:de:8b:ae:73:00:ea:36:f1:
                    5f:15:2a:03:f8:51:58:ea:b3:2c:9d:ef:fd:50:ef:
                    9b:40:51:14:24:18:ef:54:73:09:9a:2c:fe:56:39:
                    8c:55:b1:31:8f:6f:5c:c9:3f:2a:e9:a2:d3:5a:b5:
                    79:17:78:a7:cb:a2:96:b1:5e:18:72:f3:20:ed:f9:
                    3e:fc:c5:61:14:f5:16:cf:de:a3:f1:cf:8e:8c:87:
                    fd:3b:5c:97:a9:40:27:c4:f8:4a:f8:e4:62:65:73:
                    de:1b:34:9c:e8:0e:67:04:04:e5:eb:5d:c5:0a:d1:
                    e9:01:76:1b:3b:ad:3b:64:1d:21:c3:1f:f6:2b:d3:
                    37:a7:e7:cd:0e:3f:75:4e:e6:ce:68:88:05:ec:6b:
                    a0:29:cd:55:0b:cc:2a:7d:35:27:97:c5:75:38:7f:
                    8d:5c:f2:90:70:4a:b2:cc:1d:34:af:a3:11:78:1c:
                    11:b2:b0:5c:fb:b3:57:97:34:c0:00:ac:ac:14:79:
                    af:26:a6:0d:31:81:e7:c5:d0:73:53:f7:00:d9:1b:
                    fe:0c:68:16:d2:6c:be:01:ec:b0:25:28:1e:8d:0f:
                    14:3f
                Exponent: 65537 (0x10001)
        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:
                AC:A3:38:B9:1E:BB:DD:E3:2C:B2:D3:4C:DB:ED:57:83:E6:C4:00:D6
            X509v3 Authority Key Identifier:
                F8:14:50:5A:D3:A4:2C:71:7D:D5:F9:8B:60:DB:C8:36:7F:AA:EA:43
            Authority Information Access:
                CA Issuers - URI:http://vault.vault.svc:8200/v1/pki/ca   ### CA도 들어감
            X509v3 Subject Alternative Name:   ### 웹 서버 정보 들어감
                DNS:www.example.com
            X509v3 CRL Distribution Points:    ### 폐기를 위한 CRL도 추가됨
                Full Name:
                  URI:http://vault.vault.svc:8200/v1/pki/crl
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        0d:c6:4e:50:fc:fd:e8:7c:40:d4:f6:50:22:77:ef:f7:e2:98:
        12:cb:07:d6:3d:e8:c0:0e:84:e6:96:12:06:57:14:5d:25:a2:
        b2:a2:12:7a:2e:3d:fe:6a:b8:56:99:a6:b1:13:7c:10:b5:ea:
        3f:48:45:72:4a:25:5a:2f:fc:61:c6:9d:c2:96:ad:2e:51:91:
        93:e0:1d:5d:8b:a4:f5:a0:99:77:f3:ec:f6:36:33:96:52:3d:
        25:bc:b7:44:fe:6f:8b:27:06:44:6d:79:85:b2:d8:13:1f:e4:
        1b:21:b8:4b:90:74:d7:0c:38:6d:4b:c1:16:4d:5e:b6:94:84:
        c6:f3:b0:29:e5:42:f9:2b:62:68:57:53:06:1a:79:21:5e:d5:
        e9:d6:b5:36:51:49:51:ec:59:49:e7:e2:0d:4a:79:09:65:44:
        4e:a7:21:40:99:53:2b:5e:cc:44:34:31:0f:a1:9a:8b:23:f7:
        b4:7e:34:ef:ba:fe:b8:5b:48:59:7e:a3:2a:aa:61:ee:64:3f:
        3b:08:0e:21:c6:81:2c:c7:22:1f:25:c3:8b:2d:de:f8:2a:3f:
        65:f5:9f:50:4c:b6:ed:5d:5e:1c:9e:a4:2a:d5:02:ef:15:48:
        6c:18:02:0d:26:26:eb:c8:0d:ba:cb:5d:19:ad:7d:f7:d0:61:
        4d:5c:2a:ba

 

 

 

  • 웹 서버용 인증서의 Serial Number가 일치함

→ 해당 정보는 동적으로 자동 갱신됨

'STUDY - CICD' 카테고리의 다른 글

8주차 (2) - Vault Production  (0) 2025.12.13
7주차 - HashiCorp Vault  (0) 2025.11.29
6주차 - Argo CD 3/3  (1) 2025.11.20
5주차 - Argo CD 2/3  (0) 2025.11.15
4주차 - Argo CD 1/3  (0) 2025.11.08