10주차 - K8S Secret 관리 (HashiCorp Vault)

bocopile·2025년 4월 10일

AEWS 3기 스터디 

목록 보기
16/18
post-thumbnail

금일 작성하는 포스팅은 8주차 K8S CI/CD 실습시 사용했던 환경을 그대로 사용할 예정입니다.

Vault 개요

1) HashiCorp Vault??

HashiCorp Vault는 신원 기반의 시크릿 및 암호화 관리 시스템으로, 토큰, 비밀번호, 인증서, 암호화 키 등 민감한 정보를 안전하게 저장하고 관리합니다.

주요 기능

  • 인증 및 인가를 통해 권한 있는 클라이언트만 시크릿에 접근 가능
  • 시크릿의 안전한 저장 및 자동 키 롤링(교체) 지원
  • 상세한 감사 로그로 추적 가능성 확보
missing 출처 : AEWS 3기 스터디

2) 시크릿의 종류

  • API 키: 외부 서비스와의 통신을 위한 인증 키
  • 비밀번호: 시스템 또는 애플리케이션의 로그인 자격 증명
  • 인증서: TLS/SSL 인증서 등 보안 통신을 위한 인증서
  • 암호화 키: 데이터 암호화 및 복호화를 위한 키
  • 토큰: OAuth와 같은 인증 시스템에서 사용되는 액세스 토큰

3) Vault 동작 원리

HashiCorp Vault는 토큰 기반으로 작동하며, 각 토큰은 클라이언트의 정책(Policy)과 연결되어 있어 접근 권한을 제어합니다. 정책은 경로(Path) 기반으로 설정되며, 클라이언트가 어떤 작업을 할 수 있는지 정의합니다.

Vault의 핵심 워크플로우는 다음과 같이 구성됩니다

missing 출처 : AEWS 3기 스터디
  1. 인증 (Authenticate): 클라이언트가 인증 메서드(GitHub, LDAP 등)를 통해 Vault에 신원을 증명하면, 정책이 포함된 토큰이 발급됩니다.
  2. 검증 (Validation): 외부 신뢰 소스를 통해 클라이언트의 신원을 확인합니다.
  3. 인가 (Authorize): 토큰에 연결된 정책을 기반으로 클라이언트의 권한을 판단합니다.
  4. 접근 (Access): 정책에 따라 시크릿이나 암호화 기능에 대한 접근이 허용됩니다.

4) Vault의 필요성

현대 기업들이 겪는 자격 증명 관리의 보안 위험과 복잡성을 해결하기 위해, HashiCorp Vault는 모든 시크릿을 중앙 집중화하여 안전하게 관리하고, 인증·인가·감사 로그를 통해 접근을 제어합니다.

Vault의 주요 기능은 다음과 같습니다

  • 안전한 시크릿 저장: 키/값 형태의 데이터를 암호화하여 안전하게 저장
  • 동적 시크릿: AWS, DB 등과 연동해 요청 시 생성되며, 자동 폐기 지원
  • 데이터 암호화: 저장 없이도 암호화/복호화 수행, 외부 저장소와 연계 가능
  • 임대 및 갱신: 시크릿마다 임대 기간을 설정하고 갱신 가능
  • 시크릿 폐기: 특정 비밀 또는 관련된 전체 시크릿을 빠르게 폐기 가능

5) Vault 기본 구조의 이해

  • 개별 ID 인증/인가를 통해 필요한 자격 증명을 동적을 발급

6) Vault 아키텍처

(1) HTTP/S API

  • 외부 요청을 받아들이는 진입점 (REST API).

(2) Barrier

  • 데이터를 암호화 상태로 보호하는 보안 장벽.
  • Unseal 되기 전까지 내부 동작 차단.

(3) Core (핵심 로직)

  • Vault의 중심 기능 처리: 인증, 정책 평가 등.

(4) Token Store / Policy Store / Expiration Manager

  • Token Store: 인증 토큰 관리.
  • Policy Store: 접근 제어 정책 저장.
  • Expiration Manager: 토큰, 임시 비밀 등의 만료 처리.

(5) Rollback Manager

  • 실패한 작업을 안전하게 롤백.

(6) Path Routing

  • 요청 경로를 기반으로 처리 컴포넌트에 전달.

(7) Backends

  • System Backend: 시스템 관련 기능 처리.
  • Secret Engine: 비밀 생성 및 저장.
  • Auth Method: 다양한 인증 방식 제공 (LDAP, JWT 등).

(8) Audit Broker / Audit Device

  • 모든 요청/응답을 감사 로그로 기록.
  • 다양한 장치로 로그 출력 가능 (파일, syslog 등).

(9) Storage Backend

  • 비밀과 Vault 데이터를 실제로 저장하는 위치.
  • 암호화된 상태로 저장되며 Barrier로 보호됨.

K8S에 Vault 설치 진행

1) Helm을 사용하여 Vault 배포

  • 네임스페이스 생성 및 Helm Repo 추가
    kubectl create namespace vault
    kubectl get all --namespace vault
    helm repo add hashicorp https://helm.releases.hashicorp.com
    helm search repo hashicorp/vault
  • Helm Chart, Vault 설정 및 배포
    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
    
          listener "tcp" {
            address = "[::]:8200"
            cluster_address = "[::]:8201"
            tls_disable = 1
          }
    
          storage "file" {
            path = "/vault/data"
          }
    
      service:
        enabled: true
        type: NodePort
        port: 8200
        targetPort: 8200
        nodePort: 30000   # 🔥 Kind에서 열어둔 포트 중 하나 사용
    
    injector:
      enabled: true
    EOF
  • helm 배포
    helm upgrade vault hashicorp/vault -n vault -f override-values.yaml --install
  • 네임스페이스 변경 및 배포 확인
    kubens vault
    k get pods,svc,pvc

2) Vault 초기화 및 잠금해제

  • 상태 확인
    kubectl exec -ti vault-0 -- vault status

  • Vault Unseal 등록
    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
  • vault status 확인
    kubectl exec -ti vault-0 -- vault status
  • UI 사이트 접속 - http://127.0.0.1:30000
    • token : Unseal 등록시 생성되었던 Root Token을 입력한다.

    • vault-root-token.txt 에서도 확인이 가능

3) CLI 설정 - mac

  • 설치
    brew tap hashicorp/tap
    brew install hashicorp/tap/vault
  • version 확인
    vault --version
  • expert 설정
    export VAULT_ADDR='http://localhost:30000'
  • 로그인 - Root Token으로 로그인 진행
    vault login

4) 실습

(1) KV 엔진 활성화 및 샘플 데이터 추가

  • KV v2 형태로 엔진 활성화
    vault secrets enable -path=secret kv-v2
  • 샘플 시크릿 저장
    vault kv put secret/sampleapp/config \
      username="demo" \
      password="p@ssw0rd"
  • 샘플 시크릿 확인
    vault kv get secret/sampleapp/config

(2) Vault UI 확인

  • Secrets Engine 접속 → sampleapp/config 접속 → Secrets 탭 클릭 후Key/Value 확인

(3) 경로 확인하는 명령 가이드

  • Secrets Engine 접속 → sampleapp/config 접속 → Paths 탭 클릭

Vault SideCar 연동 (Vault Agent)

1) Vault Kubernetes Sidecar 아키텍처 및 워크플로우

(1) Vault Kubernetes Sidecar 아키텍처

missing 출처: HashiCorp 공식 블로그
요소설명
Sidecar InjectorVault Agent 컨테이너들을 Pod에 자동 삽입
Vault Agent Init Container최초 Vault 인증 및 토큰 발급 담당
Vault Agent Sidecar Container시크릿을 Vault에서 가져오고 공유
Kubernetes AuthVault가 서비스 어카운트로부터 받은 JWT로 인증 수행
Secret Injection시크릿이 /vault/secrets 등의 경로로 마운트됨

(2) 워크플로우

missing 출처: AEWS 스터디 3기
  • Login with platform-specific VM identifier
    • VM은 클라우드 플랫폼에서 제공하는 식별 정보(예: AWS IAM Role, GCP Service Account, Azure MSI 등)를 사용해 Vault에 로그인 요청을 보냄.
  • Validate the integrity of the data
    • Vault는 해당 클라우드 플랫폼에 요청하여, 전달된 정보가 정상적인 인스턴스에서 온 것인지 검증함.
    • 예: AWS STS API를 호출해서 instance identity 문서 확인.
  • Returns a token
    • Vault는 검증에 성공하면, 요청한 VM에 대해 정의된 Policy (app_pol)을 적용한 Vault Token을 반환함.
missing 출처: HashiCorp 공식 블로그

2) 실습

(1) Vault AppRole 방식 인증 구현

[인증 구성 및 정책 적용]

  • AppRole 인증 방식 활성화
    vault auth enable approle || echo "AppRole already enabled"
    vault auth list
  • 정책 설정
    vault policy write sampleapp-policy - <<EOF
    path "secret/data/sampleapp/*" {
      capabilities = ["read"]
    }
    EOF
  • AppRole Role 생성
    vault write auth/approle/role/sampleapp-role \
      token_policies="sampleapp-policy" \
      secret_id_ttl="4h" \
      token_ttl="4h" \
      token_max_ttl="7h"
    스크린샷 2025-04-10 오후 12.51.08.png
  • Role ID , Secrets 추출하여 저장
    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
    
  • (옵션) k8s 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 -

(2) Vault Agent Sidecar 연동

[Vault Agent 설정파일 생성] - 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

[샘플 애플리케이션 + Sidecar 배포]

  • 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

[생성된 컨테이너 확인]

  • 파드 확인
    kubectl get pod -l app=nginx-vault-demo
  • 파드 상세 확인
    kubectl describe pod -l app=nginx-vault-demo
  • 볼륨 마운트 확인

    kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- ls -l /etc/vault-agent-token

    kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- cat /etc/vault-agent-token/token ; echo

    kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- ls -al /etc/vault

    kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- cat /etc/vault/agent-config.hcl

    kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- ls -al /etc/vault/approle
    • 해당 이미지를 캡처하지 못했습니다.
    kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- cat /etc/vault/approle/role_id ; echo
    • 해당 이미지를 캡처하지 못했습니다.
    kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- cat /etc/vault/approle/secret_id ; echo

    kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- ls -l /etc/secrets       

    kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- cat /etc/secrets/index.html
    

    kubectl exec -it deploy/nginx-vault-demo -c nginx -- cat /usr/share/nginx/html/index.html

  • mutating admission 확인
    kubectl get mutatingwebhookconfigurations.admissionregistration.k8s.io   

[KV 값 변경 후 확인]

  • create new version 클릭
  • password 변경 : new-p@ssw0rd
  • password 변경 확인
  • Nginx 접속 - Link

Jenkins + Vault(AppRole)

1) 인증 방식 워크플로우

missing 출처 : hashicorp developer
  • Vault에 인증
    • Jenkins Worker가 Vault에 인증합니다 (보통 Vault Token이나 인증된 중간 주체 사용).
  • Worker Token을 받아옴
  • AppRole을 위한 SID 요청 (Vault에 요청)
    • Worker는 RoleName을 알고 있고, 이를 기반으로 Vault에 SID를 요청합니다.
  • Wrapped SID 반환
    • Vault는 SID를 직접 주지 않고, 1회용 래핑된 토큰(Wrapped SID) 을 반환합니다.
  • Runner에 환경 변수로 전달
    • Jenkins가 새로운 Job을 실행하면서 Wrapped SID를 환경 변수로 넘겨줍니다.
  • Runner가 Wrapped SID 해제 (Unwrap)
    • Runner는 받은 Wrapped SID를 Vault에 넘겨 실제 SID를 얻습니다.
  • SID를 획득
  • AppRole 인증 수행 (RID + SID)
    • Runner는 미리 알고 있던 RID(Role ID)와 방금 얻은 SID로 Vault에 인증 요청을 합니다.
  • Vault에서 최종 클라이언트 토큰 발급
  • Runner가 토큰으로 Vault의 Secrets 접근

2) 실습

(1) Jenkins에 Vault Plugin 설치

  1. Jenkins UI 접속

  2. 상단 메뉴에서 Manage Jenkins → Plugins

  3. Available 탭에서 Vault 검색

  4. HashiCorp Vault Plugin 설치 후 Jenkins 재시작

(2) Vault AppRole 확인

  • Secret ID 기존 설정이 1시간 이므로 다시 생성할것
    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)
    
    echo "ROLE_ID: $ROLE_ID"
    echo "SECRET_ID: $SECRET_ID"

(3) Jenkins 설정 및 Credentials 추가

  • Manage Jenkins → System 클릭
  • 하단에 Vault Plugin 으로 이동
    • Vault URL 입력후에 Add 버튼 클릭
  • Vault Credential 다음 값 입력
    • 종류: Vault AppRole Credential

    • Role ID & Secret ID 입력 → 생성해놓은 변수 또는 파일참고

    • ID는 기억하기 쉬운 이름으로 지정 (vault-approle-creds )

(4) Jenkins pipeline 생성

  1. Jenkins UI → New Item → Pipeline 선택

  2. jenkins-vault-kv 입력 후 생성

  1. Jenkinsfile 작성

    pipeline {
      agent any
    
      environment {
        VAULT_ADDR = 'http://192.168.35.185:30000' // 실제 Vault 주소로 변경!!!
      }
    
      stages {
        stage('Read Vault Secret') {
          steps {
            withVault([
              vaultSecrets: [
                [
                  path: 'secret/sampleapp/config',
                  engineVersion: 2,
                  secretValues: [
                    [envVar: 'USERNAME', vaultKey: 'username'],
                    [envVar: 'PASSWORD', vaultKey: 'password']
                  ]
                ]
              ],
              configuration: [
                vaultUrl: "${VAULT_ADDR}",
                vaultCredentialId: 'vault-approle-creds'
              ]
            ]) {
              sh '''
                echo "Username from Vault: $USERNAME"
                echo "Password from Vault: $PASSWORD"
              '''
              script {
                echo "Username (env): ${env.USERNAME}"
                echo "Password (env): ${env.PASSWORD}"
              }
            }
          }
        }
      }
    }
  1. 빌드 실행 : 마스킹 처리하여 결과를 보여줌

ArgoCD + Vault Plugin

  • ArgoCD 웹 사이트 접속 - Link

1) ArgoCD Vault Plugin을 위한 Credentials 활성화 - AppRole 인증

  • 이전 실습에서 획득한 Role_ID, Secret_ID 활용
    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: 0192cd70-2366-efbd-f219-7396342bbe9f #ROLE_ID
      AVP_SECRET_ID: 88cbb2bb-8a34-f8a1-56e0-2ac6d16a7cf5 #SECRET_ID
    EOF

2) ArgoCD Vault Plugin 설치

  • ArgoCD Helm 설치시 Plugin 활성화 방안
    git clone https://github.com/hyungwook0221/argocd-vault-plugin.git
    cd argocd-vault-plugin/manifests/cmp-sidecar
    
    # argocd 네임스페이스 설정
    kubens argocd
    
    # 생성될 메니페스트 파일에 대한 확인
    kubectl kustomize .
    
    # -k 옵션으로 kusomize 실행
    kubectl apply -n argocd -k .  

3) 샘플 Application 배포 하여 Vault와 동기화

kubectl apply -n argocd -f - <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: demo
  namespace: argocd
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

4) 샘플 배포앱 확인

  • ArgoCD Vault Plugin 확인
  • Application 배포 확인
  • Deployment env 확인

Vault Secrets Operator (VSO)

1) VSO??

missing 출처 : HashiCorp Blog

(1) 개요

Vault Secrets Operator는 CRD의 변화를 감지해 Vault 등에서 Kubernetes Secret으로 데이터를 동기화하며, 변경 사항이 생길 때마다 이를 지속적으로 반영해 애플리케이션이 최신 비밀 데이터에 접근할 수 있도록 합니다.

(2) 주요 기능

  • 여러 시크릿 소스를 지원하고 지속적으로 동기화.
  • 시크릿 변경 사항을 자동 감지하고 수정.
  • Kubernetes 리소스에 자동으로 새로운 시크릿 적용.
  • Prometheus 기반 모니터링 지원.
  • Helm 및 Kustomize로 간편하게 설치 가능.
  • 시크릿 데이터를 변환하는 기능 제공.

2) VSO 주요 CRD 정보

missing 출처 : AEWS 스터디 3기

VSO - Dynamic Secrets 실습

1) VSO 배포를 위한 Chart Vaules 파일 작성

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

cat vault-operator-values.yaml

2) VSO 배포

helm install vault-secrets-operator hashicorp/vault-secrets-operator \
  -n vault-secrets-operator-system \
  --create-namespace \
  --values vault-operator-values.yaml

3) PostgreSQL 설치

kubectl create ns postgres

helm repo add bitnami https://charts.bitnami.com/bitnami
helm upgrade --install postgres bitnami/postgresql \
  --namespace postgres \
  --set auth.audit.logConnections=true \
  --set auth.postgresPassword=secret-pass

4) K8S Auth Method 설정

  • vault-0 container 접속
    kubectl exec --stdin=true --tty=true vault-0 -n vault -- /bin/sh
  • vault root token 적용
    vault login
  • K8S 인증 메서드 활성화
    vault auth enable -path k8s-auth-mount kubernetes
  • K8S 클러스터 정보 구성
    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 read auth/k8s-auth-mount/config

5) Vault K8S Auth Role 설정

실행중인 애플리케이션이 Vault로부터 DB 자격증명을 받아올수 있도록 권한 연결

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

6) Vault Database Secret Engine 설정

  • Database Secret Engine 활성화 (demo-db 경로)
    vault secrets enable -path=demo-db database
  • PostgreSQL 연결 정보 등록
    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 등록
    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"
  • 정책 설정 (DB 자격증명 읽기 권한)
    • demo-db/creds/dev-postgres 경로에 대한 read 권한 부여
    • 추후 K8S SA(demo-dynamic-app)에 이 정책을 연결해서 자격증명 요청 가능
      vault policy write demo-auth-policy-db - <<EOF
      path "demo-db/creds/dev-postgres" {
         capabilities = ["read"]
      }
      EOF
      

7) Transit Secret Engine 설정 + VSO 연동 Role 구성

캐싱시 암호화 처리를 위하여 반드시 구성 필요

  • Transit Secret Engine 활성화
    vault secrets enable -path=demo-transit transit
  • vso-client-cache 키 생성 : VSO 암복호화 시 사용할 암호화 키 역활
    vault write -force demo-transit/keys/vso-client-cache
  • vso-client-cache 에 대한 암호화/복호화 허용하는 정책 생성
    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

8) 샘플 애플리케이션 배포

  • demo-ns namespace 생성
    kubectl create ns demo-ns
  • yaml 를 위한 폴더 생성
    mkdir vso-dynamic
    cd vso-dynamic
  • vault-auth-dynamic.yaml 생성
    • 앱이 Vault에 인증하기 위한 ServiceAccount 및 VaultAuth 리소스
    • Vault에서 발급한 동적 PostgreSQL Credentials를 얻기 위해 반드시 필요
      cat <<EOF > 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
            
      EOF
  • app-secret.yaml 생성
    • Spring App에서 PostgreSQL에 접속할 때 사용할 해당 시크릿에 username/password을 동적으로 생성
      cat <<EOF > app-secret.yaml
      apiVersion: v1
      kind: Secret
      metadata:
        name: vso-db-demo
        namespace: demo-ns
      EOF
  • vault-dynamic-secret.yaml 생성
    • Vault에서 동적으로 PostgreSQL 접속 정보를 생성하고 이를 K8S Secret에 저장
    • 생성된 Secret(vso-db-demo)은 앱에서 환경 변수(env)로 사용
    • 애플리케이션에서 Dynamic Reloading을 지원하지 않을 경우 rolloutRestartTargets 을 사용하여 애플리케이션을 재배포하여 새로 업데이트된 시크릿을 사용하도록 할 수 있음
      cat <<EOF > 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
          
      EOF
  • app-spring-deploy.yaml
    • DB 접속 테스트를 위한 Spring App
      cat <<EOF > 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: 30005
        selector:
          app: vaultdemo
        type: NodePort
      
      EOF
  • 애플리케이션 배포
    kubectl apply -f vault-auth-dynamic.yaml
    kubectl apply -f app-secret.yaml
    kubectl apply -f vault-dynamic-secret.yaml
    kubectl apply -f app-spring-deploy.yaml

9) 동작 확인

  • k8s Secrets에 생성된 username, password로 연결 테스트 진행
    • DB Host : postgres-postgresql.postgres.svc.cluster.local

    • DB Port : 5432

    • DB name : postgres

    • Username, Password 아래 DB_USERNAME, DB_PASSWORD 참고

VSO - PKI Secrets 실습

1) Vault PKI Secret Engine 사용을 위한 설정

(1) PKI 엔진 활성화

vault secrets enable -path=pki pki

(2) Root CA 인증서 생성

  • Vault 자체가 루트 인증기관이 됨
  • Root CA 인증서와 개인키를 Vault 내부에 생성·보관
    vault write pki/root/generate/internal \
      common_name="hashicorp.local" \
      ttl=87600h

(3) Issuing Cert & CRL Distribution URL 설정

  • Vault가 발급하는 인증서에 포함될 CA/CRL 메타데이터
    vault write pki/config/urls \
      issuing_certificates="http://vault.vault.svc:8200/v1/pki/ca" \
      crl_distribution_points="http://vault.vault.svc:8200/v1/pki/crl"

(4) 인증서 발급용 Role 생성

  • hashicorp-app.svc.cluster.local 도메인 네임에 대해 PKI 엔진에 요청 허용
    vault write pki/roles/cert-role \
      allowed_domains="hashicorp-app.svc.cluster.local" \
      allow_subdomains=true \
      allow_bare_domains=true \
      allow_any_name=true \
      enforce_hostnames=false \
      max_ttl="24h"

(5) 인증서 발급용 Policy 정의

vault policy write pki-policy - <<EOF
path "pki/issue/cert-role" {
  capabilities = ["update", "read", "list"]
}
EOF

(6) 인증서 발급용 Policy 정의

vault write auth/approle/role/cert-role \
  token_policies="pki-policy" \
  token_ttl="30m" \
  token_max_ttl="1h"

(7) Role ID / Secret ID 발급 및 K8S Secret에 저장

vault read -field=role_id auth/approle/role/cert-role/role-id > role_id.txt
vault write -f -field=secret_id auth/approle/role/cert-role/secret-id > secret_id.txt

# 확인
cat role_id.txt
cat secret_id.txt

2) VSO의 CR(Custom Resource) 배포

(1) webapp 네임스페이스 생성

kubectl create ns webapp

(2) AppRole Secret 생성

kubectl create secret generic vso-cert-role \
  -n webapp \
  --from-file=role_id=role_id.txt \
  --from-file=id=secret_id.txt 

(3) VaultAuth CR 생성

kubectl create -f - <<EOF
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
  name: pki-auth
  namespace: webapp
spec:
  method: appRole
  mount: approle
  appRole:
    roleId: ff2a2786-b998-c61e-ee94-a203dedc70c1 # RoleID
    secretRef: vso-cert-role
EOF

(4) Vault PKI Secret CR 생성

  • vault agent 역할 : 사용자를 대행하여 vault에 주가적 요청을 통하여 인증서를 갱신
  • 인증서를 사용하는 deployment에 대해 롤 아웃을 수행하여 바뀐 인증서 정보 반영
    kubectl create -f - <<EOF
    apiVersion: secrets.hashicorp.com/v1beta1
    kind: VaultPKISecret
    metadata:
      name: hashicorp-tls
      namespace: webapp
    spec:
      vaultAuthRef: pki-auth
      mount: pki
      role: cert-role
      commonName: hashicorp-app.svc.cluster.local
      ttl: 1m
      destination:
        create: true
        name: vso-pki-cert
      rolloutRestartTargets:
        - kind: Deployment
          name: nginx-vault-ssl
    EOF

3) 생성된 인증서 확인

  • secret 확인
    kubectl get secret vso-pki-cert -n webapp -o json | jq -r .data._raw | base64 -d
  • view-secret 플러그인 배포 - Link
    kubectl krew install view-secret
  • vso-pki-cert 확인
    kubectl view-secret -n webapp vso-pki-cert --all
  • 만료 시간 확인
    kubectl view-secret -n webapp vso-pki-cert expiration
  • date로 시간 변환하여 만료 시간 확인
    date -r 1744267831

4) 검증을 위한 서비스(Nginx) 배포

kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-ssl-config
  namespace: webapp
data:
  nginx.conf: |
    events {}
    http {
      server {
        listen 443 ssl;
        server_name localhost;

        ssl_certificate     /etc/nginx/tls/certificate;
        ssl_certificate_key /etc/nginx/tls/private_key;

        location / {
          root /usr/share/nginx/html;
          index index.html;
        }
      }
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-vault-ssl
  namespace: webapp
spec:
  replicas: 1
  minReadySeconds: 10
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  selector:
    matchLabels:
      app: nginx-vault
  template:
    metadata:
      labels:
        app: nginx-vault
    spec:
      initContainers:
        - name: generate-expiration-html
          image: alpine:latest
          command: ["/bin/sh", "-c"]
          args:
            - |
              apk add --no-cache openssl > /dev/null
              expiry=\$(openssl x509 -enddate -noout -in /certs/certificate | cut -d= -f2)
              echo "<html><head><meta http-equiv='refresh' content='10'></head><body><h1>Certificate Expiration: \$expiry</h1></body></html>" > /output/index.html
          volumeMounts:
            - name: tls
              mountPath: /certs
              readOnly: true
            - name: html
              mountPath: /output
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 443
          volumeMounts:
            - name: tls
              mountPath: /etc/nginx/tls
              readOnly: true
            - name: html
              mountPath: /usr/share/nginx/html
            - name: nginx-conf
              mountPath: /etc/nginx/nginx.conf
              subPath: nginx.conf
      volumes:
        - name: tls
          secret:
            secretName: vso-pki-cert
        - name: html
          emptyDir: {}
        - name: nginx-conf
          configMap:
            name: nginx-ssl-config
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-vault-service
  namespace: webapp
spec:
  selector:
    app: nginx-vault
  ports:
    - protocol: TCP
      port: 443
      targetPort: 443
      nodePort: 30004
  type: NodePort
EOF

서비스를 확인하면 다음과 같이 인증서가 배포되어 있습니다.
단 공인된 인증서가 아니므로 주의요함 이 나타남

profile
DevOps Engineer

0개의 댓글