[AEWS] #10์ฃผ์ฐจ K8s Vault ์‹ค์Šต

25๋…„๋„ AWS EKS Hands-on Study ์Šคํ„ฐ๋”” ์ •๋ฆฌ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.
๋ณธ ์‹ค์Šต ์ฐธ๊ณ  ํŽ˜์ด์ง€ : https://aws-ia.github.io/terraform-aws-eks-blueprints/patterns/blue-green-upgrade/ 

 

์‹ค์Šต ํ™˜๊ฒฝ ๊ตฌ์„ฑ

์  ํ‚จ์Šค

cat <<EOT > docker-compose.yaml
services:

  jenkins:
    container_name: jenkins
    image: jenkins/jenkins
    restart: unless-stopped
    networks:
      - cicd-network
    ports:
      - "8080:8080"
      - "50000:50000"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - jenkins_home:/var/jenkins_home

volumes:
  jenkins_home:

networks:
  cicd-network:
    driver: bridge
EOT
docker compose up -d
docker compose ps

 

# Jenkins ์ดˆ๊ธฐ ์•”ํ˜ธ ํ™•์ธ
docker compose exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
09a21116f3ce4f27a0ede79372febfb1

# Jenkins ์›น ์ ‘์†
open "http://127.0.0.1:8080"

docker compose logs jenkins -f

 

์  ํ‚จ์Šค์— ์ ‘์†ํ•˜์—ฌ ์ดˆ๊ธฐ ์„ธํŒ…์„ ํ•ด์ค€ ํ›„ ์ดˆ๊ธฐ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์„ค์น˜ํ•œ๋‹ค.

์ดํ›„ ์ •์ƒ์ ์œผ๋กœ ์™„๋ฃŒ๋˜์—ˆ์œผ๋ฉด ๊ณ„์ • ์„ธํŒ…์„ ์ง„ํ–‰ํ•ด์ฃผ๊ณ  Jenkins ๊ด€๋ฆฌ > ํ”Œ๋Ÿฌ๊ทธ์ธ > available plugins์—์„œ hashicorp vault๋ฅผ ๋‹ค์šด๋ฐ›๋Š”๋‹ค.

kind  ํด๋Ÿฌ์Šคํ„ฐ ๊ตฌ์„ฑ

cat > kind-3node.yaml <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  apiServerAddress: "127.0.0.1" # $MyIP๋กœ ์„ค์ •ํ•˜์…”๋„ ๋ฉ๋‹ˆ๋‹ค.
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30000
  - containerPort: 30001
    hostPort: 30001
  - containerPort: 30002
    hostPort: 30002
  - containerPort: 30003
    hostPort: 30003
  - containerPort: 30004
    hostPort: 30004
  - containerPort: 30005
    hostPort: 30005
  - containerPort: 30006
    hostPort: 30006
- role: worker
- role: worker
EOF

kind create cluster --config kind-3node.yaml --name myk8s --image kindest/node:v1.32.2

argocd

kubectl create ns argocd
cat <<EOF > argocd-values.yaml
dex:
  enabled: false

server:
  service:
    type: NodePort
    nodePortHttps: 30002
  extraArgs:
    - --insecure
EOF

# ์„ค์น˜
helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd argo/argo-cd --version 7.8.13 -f argocd-values.yaml --namespace argocd

 

 

Vault

์ธ์ฆ๊ณผ ์ธ๊ฐ€

์ธ์ฆ (Authentication) : "์‚ฌ์šฉ์ž๊ฐ€ ๋ˆ„๊ตฌ์ธ์ง€ ์ฆ๋ช…ํ•˜๋Š” ์ ˆ์ฐจ"

  • ์‚ฌ์šฉ์ž๊ฐ€ ์ฃผ์žฅํ•˜๋Š” ์‹ ์›์„ ํ™•์ธํ•˜๋Š” ๊ณผ์ •์ด๋‹ค.
  • ์ผ๋ฐ˜์ ์œผ๋กœ ID์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ, ์ธ์ฆ์„œ, OTP, ์ƒ์ฒด ์ •๋ณด ๋“ฑ์„ ์‚ฌ์šฉํ•œ๋‹ค.
  • ์˜ˆ์‹œ
    • ๋กœ๊ทธ์ธํ•  ๋•Œ ID/PW๋ฅผ ์ž…๋ ฅํ•ด์„œ ๋‚ด๊ฐ€ ์ง„์งœ ํŠน์ • ์‚ฌ๋žŒ์ด๋ผ๋Š” ๊ฑธ ํ™•์ธ๋ฐ›๋Š” ๊ฒƒ
    • OAuth์—์„œ ๊ตฌ๊ธ€ ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธํ•˜๊ธฐ

์ธ๊ฐ€ (Authorization) : "์‚ฌ์šฉ์ž๊ฐ€ ๋ฌด์—‡์„ ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ฒฐ์ •ํ•˜๋Š” ์ ˆ์ฐจ"

  • ์ธ์ฆ์ด ๋๋‚œ ์‚ฌ์šฉ์ž์—๊ฒŒ ์–ด๋–ค ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€ ํŒ๋‹จํ•˜๋Š” ๊ณผ์ •์ด๋‹ค.
  • ์–ด๋–ค ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ์ง€, ์–ด๋–ค ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š”์ง€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค.
  • ์˜ˆ์‹œ
    • ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธ
    • ํŒŒ์ผ ์‹œ์Šคํ…œ์—์„œ ์ฝ๊ธฐ/์“ฐ๊ธฐ ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€ ์ฒดํฌ

 

vault ์‹ค์Šต ํ™˜๊ฒฝ ๊ตฌ์„ฑ

helm์œผ๋กœ ํŒจํ‚ค์ง€ ์„ค์น˜

helm repo add hashicorp https://helm.releases.hashicorp.com

helm search repo hashicorp/vault
NAME                                    CHART VERSION   APP VERSION     DESCRIPTION                          
hashicorp/vault                         0.30.0          1.19.0          Official HashiCorp Vault Chart       
hashicorp/vault-secrets-gateway         0.0.2           0.1.0           A Helm chart for Kubernetes          
hashicorp/vault-secrets-operator        0.10.0          0.10.0          Official Vault Secrets Operator Chart
# Create a Kubernetes namespace.
kubectl create namespace vault

# View all resources in a namespace.
kubectl get all --namespace vault

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

# Check that you have access to the chart.
helm search repo hashicorp/vault
# NAME                                    CHART VERSION   APP VERSION     DESCRIPTION                          
# hashicorp/vault                         0.30.0          1.19.0          Official HashiCorp Vault Chart       
# hashicorp/vault-secrets-gateway         0.0.2           0.1.0           A Helm chart for Kubernetes          
# hashicorp/vault-secrets-operator        0.10.0          0.10.0          Official Vault Secrets Operator Chart

 

cat <<EOF > override-values.yaml
global:
  enabled: true
  tlsDisable: true  # Disable TLS for demo purposes

server:
  image:
    repository: "hashicorp/vault"
    tag: "1.19.0"

  standalone:
    enabled: true
    replicas: 1  # ๋‹จ์ผ ๋…ธ๋“œ

    config: |
      ui = true
      disable_mlock = true
      cluster_name = "vault-local"

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

      storage "raft" {
        path = "/vault/data"
        node_id = "vault-dev-node-1"
      }
  service:
    enabled: true
    type: NodePort
    port: 8200
    targetPort: 8200
    nodePort: 30000

injector:
  enabled: true

ui:
  enabled: true
  serviceType: "NodePort"
EOF

 

์ด ํ™˜๊ฒฝ์€ ์‹ค์Šต์šฉ ๊ตฌ์„ฑ์ด๋ฏ€๋กœ ์Šคํƒ ๋“œ์–ผ๋ก ์˜ ๋‹จ์ผ ๋…ธ๋“œ ํ˜•ํƒœ๋กœ ๊ตฌ์„ฑ์ด ์ง„ํ–‰๋œ๋‹ค. ์‹ค์ œ ์šด์˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๊ถŒ์žฅ๋˜์ง€ ์•Š๋Š” ํ™˜๊ฒฝ ๊ตฌ์„ฑ์ธ ์ ์— ์ฃผ์˜ํ•œ๋‹ค.

k get pods,svc,pvc
NAME                                        READY   STATUS              RESTARTS   AGE
pod/vault-0                                 0/1     ContainerCreating   0          14s
pod/vault-agent-injector-56459c7545-bqgk2   0/1     Running             0          14s

NAME                               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                         AGE
service/vault                      NodePort    10.96.228.133   <none>        8200:30000/TCP,8201:32730/TCP   14s
service/vault-agent-injector-svc   ClusterIP   10.96.147.40    <none>        443/TCP                         14s
service/vault-internal             ClusterIP   None            <none>        8200/TCP,8201/TCP               14s
service/vault-ui                   NodePort    10.96.51.20     <none>        8200:31005/TCP                  14s

NAME                                 STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/data-vault-0   Bound    pvc-6df6d70b-9815-4f4c-b8fc-8c05efe570a5   10Gi       RWO            standard       <unset>                 14s

 

kubectl exec -ti vault-0 -- vault status
Key                     Value
---                     -----
Seal Type               shamir
Initialized             false
Sealed                  true
Total Shares            0
Threshold               0
Unseal Progress         0/0
Unseal Nonce            n/a
Version                 1.19.0
Build Date              2025-03-04T12:36:40Z
Storage Type            raft
Removed From Cluster    false
HA Enabled              true
command terminated with exit code 2

 

vault unseal

Vault๋Š” '๊ธˆ๊ณ '๋ผ๋Š” ๋œป์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”๋งŒํผ ๊ธฐ๋ณธ ์ƒํƒœ์—์„œ๋Š” '์ž ๊ธด ์ฑ„'๋กœ ์‹œ์ž‘ํ•œ๋‹ค.

  • Initialize: Vault๊ฐ€ ์ƒํƒœ๋ฅผ ์ €์žฅํ•  backend(์˜ˆ: Consul, file ๋“ฑ)์— ์ตœ์ดˆ ๋ฐ์ดํ„ฐ๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ , ๋งˆ์Šคํ„ฐ ํ‚ค์™€ ํ‚ค ์‰์–ด๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
  • Unseal: Vault๋Š” ์ดˆ๊ธฐํ™” ์ดํ›„์—๋„ ์‹ค์ œ ๋น„๋ฐ€ ์ €์žฅ์†Œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋„๋ก ๋ด‰์ธ(seal)๋˜์–ด ์žˆ๋‹ค. ์ตœ์†Œ 3๊ฐœ ์ค‘ 2๊ฐœ ์ด์ƒ์˜ ํ‚ค ์‰์–ด๋ฅผ ์ž…๋ ฅํ•ด์•ผ ๋ด‰์ธ์„ ํ•ด์ œํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฆ‰, Vault๋Š” ์„ค์น˜ํ•˜๋ฉด ์ฆ‰์‹œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๋‹ค. ์ˆ˜๋™์œผ๋กœ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ๋ด‰์ธ์„ ํ•ด์ œํ•ด์•ผ ๋ณธ๊ฒฉ์ ์ธ ๋™์ž‘์„ ์‹œ์ž‘ํ•œ๋‹ค.

 

cat <<EOF > init-unseal.sh
#!/bin/bash

# Vault Pod ์ด๋ฆ„
VAULT_POD="vault-0"

# Vault ๋ช…๋ น ์‹คํ–‰
VAULT_CMD="kubectl exec -ti \$VAULT_POD -- vault"

# ์ถœ๋ ฅ ์ €์žฅ ํŒŒ์ผ
VAULT_KEYS_FILE="./vault-keys.txt"
UNSEAL_KEY_FILE="./vault-unseal-key.txt"
ROOT_TOKEN_FILE="./vault-root-token.txt"

# Vault ์ดˆ๊ธฐํ™” (Unseal Key 1๊ฐœ๋งŒ ์ƒ์„ฑ๋˜๋„๋ก ์„ค์ •)
\$VAULT_CMD operator init -key-shares=1 -key-threshold=1 | sed \$'s/\\x1b\\[[0-9;]*m//g' | tr -d '\r' > "\$VAULT_KEYS_FILE"

# Unseal Key / Root Token ์ถ”์ถœ
grep 'Unseal Key 1:' "\$VAULT_KEYS_FILE" | awk -F': ' '{print \$2}' > "\$UNSEAL_KEY_FILE"
grep 'Initial Root Token:' "\$VAULT_KEYS_FILE" | awk -F': ' '{print \$2}' > "\$ROOT_TOKEN_FILE"

# Unseal ์ˆ˜ํ–‰
UNSEAL_KEY=\$(cat "\$UNSEAL_KEY_FILE")
\$VAULT_CMD operator unseal "\$UNSEAL_KEY"

# ๊ฒฐ๊ณผ ์ถœ๋ ฅ
echo "[๐Ÿ”“] Vault Unsealed!"
echo "[๐Ÿ”] Root Token: \$(cat \$ROOT_TOKEN_FILE)"
EOF


chmod +x init-unseal.sh
./init-unseal.sh
kubectl exec -ti vault-0 -- vault status
Key                     Value
---                     -----
Seal Type               shamir
Initialized             true

 

unseal ์ดํ›„ vault-root-token.txt๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ ‘์†ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

vault cli

brew tap hashicorp/tap
brew install hashicorp/tap/vault

export VAULT_ADDR='http://localhost:30000'

vault status
vault login

 

vault ์‹œํฌ๋ฆฟ์—”์ง„ ํ™œ์„ฑํ™”

vault secrets enable -path=secret kv-v2
Success! Enabled the kv-v2 secrets engine at: secret/

vault kv put secret/sampleapp/config \
  username="demo" \
  password="p@ssw0rd"
======== Secret Path ========
secret/data/sampleapp/config

======= Metadata =======
Key                Value
---                -----
created_time       2025-04-12T16:10:07.147938837Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

vault kv get secret/sampleapp/config
======== Secret Path ========
secret/data/sampleapp/config

======= Metadata =======
Key                Value
---                -----
created_time       2025-04-12T16:10:07.147938837Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

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

 

 

vault sidecar ์‹ค์Šต

AppRole ๋ฐฉ์‹ ์ธ์ฆ ๊ตฌ์„ฑ

AppRole ์ธ์ฆ ํ™œ์„ฑํ™”

vault auth enable approle || echo "AppRole already enabled"
vault auth list
Success! Enabled approle auth method at: approle/
Path        Type       Accessor                 Description                Version
----        ----       --------                 -----------                -------
approle/    approle    auth_approle_e9721a41    n/a                        n/a
token/      token      auth_token_363206bd      token based credentials    n/a

์ ‘๊ทผ ์ •์ฑ…(Policy) ์ž‘์„ฑ

vault policy write sampleapp-policy - <<EOF
path "secret/data/sampleapp/*" {
  capabilities = ["read"]
}
EOF
Success! Uploaded policy: sampleapp-policy

 

 

sampleapp-policy๋ผ๋Š” ์ด๋ฆ„์˜ ์ •์ฑ…์„ ์ž‘์„ฑํ•œ๋‹ค.

ํ•ด๋‹น ์ •์ฑ…์€ secret/data/sampleapp/* ๊ฒฝ๋กœ์˜ KV ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ธฐ(read) ํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•œ๋‹ค.

 

 

AppRole Role ์ƒ์„ฑ

vault write auth/approle/role/sampleapp-role \
  token_policies="sampleapp-policy" \
  secret_id_ttl="1h" \
  token_ttl="1h" \
  token_max_ttl="4h"

Success! Data written to: auth/approle/role/sampleapp-role

 

vault policy write๋Š” ์ •์ฑ… ์ด๋ฆ„๊ณผ ๋‚ด์šฉ์„ Vault์— ๋“ฑ๋กํ•œ๋‹ค.

์ด Role์„ ์‚ฌ์šฉํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ sampleapp-policy์— ์ •์˜๋œ ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ํ† ํฐ์„ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

 

ROLE_ID ๋ฐ SECRET_ID ์ƒ์„ฑ

ROLE_ID=$(vault read -field=role_id auth/approle/role/sampleapp-role/role-id)
SECRET_ID=$(vault write -f -field=secret_id auth/approle/role/sampleapp-role/secret-id)

mkdir -p approle-creds
echo "$ROLE_ID" > approle-creds/role_id.txt
echo "$SECRET_ID" > approle-creds/secret_id.txt

 

ROLE_ID๋Š” AppRole ์ž์ฒด์˜ ์‹๋ณ„์ž๋กœ ๊ณ ์ •๊ฐ’์ด๋ฉฐ ๊ณต๊ฐœ์ ์œผ๋กœ ์ „๋‹ฌํ•ด๋„ ์•ˆ์ „ํ•˜๋‹ค.

SECRET_ID๋Š” ์ผํšŒ์šฉ ์‹œํฌ๋ฆฟ ํ‚ค๋กœ, ์‹ค์ œ ์ธ์ฆ ์‹œ Role ID์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ๋˜์–ด Vault ์ ‘๊ทผ์„ ํ—ˆ์šฉํ•œ๋‹ค. 

์ด ๋‘ ๊ฐ’์„ ์กฐํ•ฉํ•ด์„œ AppRole ์ธ์ฆ ํ† ํฐ์„ ๋ฐœ๊ธ‰๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

Kubernetes Secret ์ƒ์„ฑ

kubectl create secret generic vault-approle -n vault \
  --from-literal=role_id="${ROLE_ID}" \
  --from-literal=secret_id="${SECRET_ID}" \
  --save-config \
  --dry-run=client -o yaml | kubectl apply -f -
secret/vault-approle created

 

ํŒŒ๋“œ๊ฐ€ Vault์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•œ ์ž๊ฒฉ์ฆ๋ช…์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

vault agent sidecar ์—ฐ๋™

vault-agent-config.hcl

cat <<EOF | kubectl create configmap vault-agent-config -n vault --from-file=agent-config.hcl=/dev/stdin --dry-run=client -o yaml | kubectl apply -f -
vault {
  address = "http://vault.vault.svc:8200"
}

auto_auth {
  method "approle" {
    config = {
      role_id_file_path = "/etc/vault/approle/role_id"
      secret_id_file_path = "/etc/vault/approle/secret_id"
      remove_secret_id_file_after_reading = false
    }
  }

  sink "file" {
    config = {
      path = "/etc/vault-agent-token/token"
    }
  }
}

template_config {
  static_secret_render_interval = "20s"
}

template {
  destination = "/etc/secrets/index.html"
  contents = <<EOH
  <html>
  <body>
    <p>username: {{ with secret "secret/data/sampleapp/config" }}{{ .Data.data.username }}{{ end }}</p>
    <p>password: {{ with secret "secret/data/sampleapp/config" }}{{ .Data.data.password }}{{ end }}</p>
  </body>
  </html>
EOH
}
EOF

 

Vault Agent๋ฅผ Kubernetes ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ConfigMap ์ƒ์„ฑ ๊ณผ์ •์„ ๋‹ด๊ณ ์žˆ์œผ๋ฉฐ, Vault Agent๋Š” Vault์™€์˜ ์ธ์ฆ, ํ† ํฐ ๊ด€๋ฆฌ, ์‹œํฌ๋ฆฟ ํ…œํ”Œ๋ฆฟ ๋ Œ๋”๋ง์„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์ฃผ๋Š” ๋„๊ตฌ์ด๋‹ค.

 

Nginx + Vault Agent ๋ฐฐํฌ

kubectl apply -n vault -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-vault-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-vault-demo
  template:
    metadata:
      labels:
        app: nginx-vault-demo
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
        volumeMounts:
        - name: html-volume
          mountPath: /usr/share/nginx/html
      - name: vault-agent-sidecar
        image: hashicorp/vault:latest
        args:
          - "agent"
          - "-config=/etc/vault/agent-config.hcl"
        volumeMounts:
        - name: vault-agent-config
          mountPath: /etc/vault
        - name: vault-approle
          mountPath: /etc/vault/approle
        - name: vault-token
          mountPath: /etc/vault-agent-token
        - name: html-volume
          mountPath: /etc/secrets
      volumes:
      - name: vault-agent-config
        configMap:
          name: vault-agent-config
      - name: vault-approle
        secret:
          secretName: vault-approle
      - name: vault-token
        emptyDir: {}
      - name: html-volume
        emptyDir: {}
EOF

 

svc ์ƒ์„ฑ

kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: NodePort
  selector:
    app: nginx-vault-demo
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
      nodePort: 30001 # Kind์—์„œ ์„ค์ •ํ•œ Port
EOF
service/nginx-service created

 

kubectl get pod -l app=nginx-vault-demo
NAME                                READY   STATUS    RESTARTS   AGE
nginx-vault-demo-7776649597-tcm4l   2/2     Running   0          3m14s

 

์‹ ๊ทœ ํŒจ์Šค์›Œ๋“œ

 

 

vault + jenkins

 

 

ArgoCD + vault plugin

kubectl apply -f - <<EOF
kind: Secret
apiVersion: v1
metadata:
  name: argocd-vault-plugin-credentials
  namespace: argocd
type: Opaque
stringData:
  VAULT_ADDR: "http://vault.vault:8200"
  AVP_TYPE: "vault"
  AVP_AUTH_TYPE: "approle"
  AVP_ROLE_ID: ๋กค
  AVP_SECRET_ID: ์‹œํฌ๋ฆฟ
EOF
secret/argocd-vault-plugin-credentials created

ํ”Œ๋Ÿฌ๊ทธ์ธ ํ™œ์„ฑํ™”

git clone https://github.com/hyungwook0221/argocd-vault-plugin.git
cd argocd-vault-plugin/manifests/cmp-sidecar

# kustomize edit fix

kubens argocd

kubectl kustomize .
kubectl apply -n argocd -k .

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฐฐํฌ

kubectl apply -n argocd -f - <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: demo
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
spec:
  destination:
    namespace: argocd
    server: https://kubernetes.default.svc
  project: default
  source:
    path: infra/helm
    repoURL: https://github.com/hyungwook0221/spring-boot-debug-app
    targetRevision: main
    plugin:
      name: argocd-vault-plugin-helm
      env:
        - name: HELM_ARGS
          value: -f new-values.yaml
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
EOF

 

 

 

๋””ํ”Œ๋กœ์ด๋จผํŠธ์— ์ ์šฉ๋œ env๊ฐ’์ด ๋ณ€๊ฒฝ๋œ ํŒจ์Šค์›Œ๋“œ๋กœ ์ ์šฉ๋ผ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

Vault Secret Operator

# 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"]

 

๊ถŒํ•œ ์„ค์ •

kubectl exec --stdin=true --tty=true vault-0 -n vault -- /bin/sh

vault login

# Kubernetes ์ธ์ฆ ๋ฉ”์„œ๋“œ ํ™œ์„ฑํ™”
vault auth enable -path k8s-auth-mount kubernetes

vault write auth/k8s-auth-mount/config \
  kubernetes_host="https://kubernetes.default.svc:443" \
  kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
  token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"

# vault k8s auth role 
vault write auth/k8s-auth-mount/role/auth-role \
   bound_service_account_names=demo-dynamic-app \
   bound_service_account_namespaces=demo-ns \
   token_ttl=0 \
   token_period=120 \
   token_policies=demo-auth-policy-db \
   audience=vault

vault secrets enable -path=demo-db database

# PostgreSQL ์—ฐ๊ฒฐ ์ •๋ณด ๋“ฑ๋ก
# ํ•ด๋‹น ๊ณผ์ •์€ postgres๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ ์‹œ ์ ์šฉ ๊ฐ€๋Šฅ
# allowed_roles: ์ดํ›„ ์„ค์ •ํ•  Role ์ด๋ฆ„ ์ง€์ •
vault write demo-db/config/demo-db \
   plugin_name=postgresql-database-plugin \
   allowed_roles="dev-postgres" \
   connection_url="postgresql://{{username}}:{{password}}@postgres-postgresql.postgres.svc.cluster.local:5432/postgres?sslmode=disable" \
   username="postgres" \
   password="secret-pass"
  
# DB ์‚ฌ์šฉ์ž ๋™์  ์ƒ์„ฑ Role ๋“ฑ๋ก
# ํ•ด๋‹น Role ์‚ฌ์šฉ ์‹œ Vault๊ฐ€ ๋™์ ์œผ๋กœ ์‚ฌ์šฉ์ž ๊ณ„์ •๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ƒ์„ฑ ๊ฐ€๋Šฅ
# TTL์€ ์ƒ์„ฑ๋œ ์ž๊ฒฉ์ฆ๋ช…์˜ ์œ ํšจ ์‹œ๊ฐ„ (30์ดˆ~10๋ถ„)
vault write demo-db/roles/dev-postgres \
   db_name=demo-db \
   creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
      GRANT ALL PRIVILEGES ON DATABASE postgres TO \"{{name}}\";" \
   revocation_statements="REVOKE ALL ON DATABASE postgres FROM  \"{{name}}\";" \
   backend=demo-db \
   name=dev-postgres \
   default_ttl="1m" \
   max_ttl="1m"
   
# demo-db/creds/dev-postgres ๊ฒฝ๋กœ์— ๋Œ€ํ•œ read ๊ถŒํ•œ ๋ถ€์—ฌ
# ์ถ”ํ›„ Kubernetes ์„œ๋น„์Šค ์–ด์นด์šดํŠธ(demo-dynamic-app)์— ์ด ์ •์ฑ…์„ ์—ฐ๊ฒฐํ•ด์„œ ์ž๊ฒฉ์ฆ๋ช… ์š”์ฒญ ๊ฐ€๋Šฅ
vault policy write demo-auth-policy-db - <<EOF
path "demo-db/creds/dev-postgres" {
   capabilities = ["read"]
}
EOF

vault secrets enable -path=demo-transit transit

# vso-client-cache๋ผ๋Š” ํ‚ค๋ฅผ ์ƒ์„ฑ
# ์ด ํ‚ค๋Š” VSO๊ฐ€ ์•”๋ณตํ˜ธํ™” ์‹œ ์‚ฌ์šฉํ•  ์•”ํ˜ธํ™” ํ‚ค ์—ญํ• 
vault write -force demo-transit/keys/vso-client-cache

# vso-client-cache ํ‚ค์— ๋Œ€ํ•ด ์•”ํ˜ธํ™”(encrypt), ๋ณตํ˜ธํ™”(decrypt)๋ฅผ ํ—ˆ์šฉํ•˜๋Š” ์ •์ฑ… ์ƒ์„ฑ
vault policy write demo-auth-policy-operator - <<EOF
path "demo-transit/encrypt/vso-client-cache" {
   capabilities = ["create", "update"]
}
path "demo-transit/decrypt/vso-client-cache" {
   capabilities = ["create", "update"]
}
EOF

# Vault Secrets Operator๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ServiceAccount์— ์œ„ ์ •์ฑ…์„ ๋ฐ”์ธ๋”ฉ
# vso๊ฐ€ Vault์— ๋กœ๊ทธ์ธํ•  ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” JWT ๊ธฐ๋ฐ˜ Role ์„ค์ •
# ํ•ด๋‹น Role์„ ํ†ตํ•ด Operator๋Š” Transit ์—”์ง„์„ ์ด์šฉํ•œ ์•”๋ณตํ˜ธํ™” API ํ˜ธ์ถœ ๊ฐ€๋Šฅ
vault write auth/k8s-auth-mount/role/auth-role-operator \
   bound_service_account_names=vault-secrets-operator-controller-manager \
   bound_service_account_namespaces=vault-secrets-operator-system \
   token_ttl=0 \
   token_period=120 \
   token_policies=demo-auth-policy-operator \
   audience=vault
   
vault read auth/k8s-auth-mount/role/auth-role-operator

 

helm install vault-secrets-operator hashicorp/vault-secrets-operator \
  -n vault-secrets-operator-system \
  --create-namespace \
  --values vault-operator-values.yaml
NAME: vault-secrets-operator
LAST DEPLOYED: Sun Apr 13 02:35:06 2025
NAMESPACE: vault-secrets-operator-system
STATUS: deployed
REVISION: 1

 

vault read auth/k8s-auth-mount/role/auth-role-operator
Key                                         Value
---                                         -----
alias_name_source                           serviceaccount_uid
audience                                    vault
bound_service_account_names                 [vault-secrets-operator-controller-manager]
bound_service_account_namespace_selector    n/a
bound_service_account_namespaces            [vault-secrets-operator-system]
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-operator]
token_ttl                                   0s
token_type                                  default

 

vault-auth-dynamic.yaml

---
# vault-auth-dynamic.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  namespace: demo-ns
  name: demo-dynamic-app
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
  name: dynamic-auth
  namespace: demo-ns
spec:
  method: kubernetes
  mount: k8s-auth-mount
  kubernetes:
    role: auth-role
    serviceAccount: demo-dynamic-app
    audiences:
      - vault

 

vault-dynamic-secret.yaml

---
# vault-dynamic-secret.yaml
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultDynamicSecret
metadata:
  name: vso-db-demo
  namespace: demo-ns
spec:
  refreshAfter: 25s
  mount: demo-db
  path: creds/dev-postgres
  destination:
    name: vso-db-demo
    create: true
    overwrite: true
  vaultAuthRef: dynamic-auth
  rolloutRestartTargets:
  - kind: Deployment
    name: vaultdemo

app-secret.yaml

---
# app-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: vso-db-demo
  namespace: demo-ns

app-secret-deploy.yaml

---
# app-spring-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vaultdemo
  namespace: demo-ns
  labels:
    app: vaultdemo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: vaultdemo
  template:
    metadata:
      labels:
        app: vaultdemo
    spec:
      volumes:
        - name: secrets
          secret:
            secretName: "vso-db-demo"
      containers:
        - name: vaultdemo
          image: hyungwookhub/vso-spring-demo:v5
          imagePullPolicy: IfNotPresent
          env:
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: "vso-db-demo"
                  key: password
            - name: DB_USERNAME
              valueFrom:
                secretKeyRef:
                  name: "vso-db-demo"
                  key: username
            - name: DB_HOST
              value: "postgres-postgresql.postgres.svc.cluster.local"
            - name: DB_PORT
              value: "5432"
            - name: DB_NAME
              value: "postgres"
          ports:
            - containerPort: 8088
          volumeMounts:
            - name: secrets
              mountPath: /etc/secrets
              readOnly: true
---
apiVersion: v1
kind: Service
metadata:
  name: vaultdemo
  namespace: demo-ns
spec:
  ports:
    - name: vaultdemo
      port: 8088         
      targetPort: 8088 
      nodePort: 30003
  selector:
    app: vaultdemo
  type: NodePort

 

k apply -f .
secret/vso-db-demo created
deployment.apps/vaultdemo created
service/vaultdemo created
serviceaccount/demo-dynamic-app created
vaultauth.secrets.hashicorp.com/dynamic-auth created
vaultdynamicsecret.secrets.hashicorp.com/vso-db-demo created

 

 

 

 

์‹ค์Šต ๋ฆฌ์†Œ์Šค ์‚ญ์ œ

docker compose down --volumes --remove-orphans
[+] Running 3/3
 โœ” Container jenkins               Removed                                                     0.5s 
 โœ” Volume cicd-labs_jenkins_home   Removed                                                     0.2s 
 โœ” Network cicd-labs_cicd-network  Removed                                                     0.2s 

kind delete cluster --name myk8s
Deleting cluster "myk8s" ...
Deleted nodes: ["myk8s-worker2" "myk8s-worker" "myk8s-control-plane"]