본문 바로가기
IT & Tech 정보

CI 비밀 관리 끝판왕 가이드: Vault & Kubernetes External Secrets 동적 주입

by 지식과 지혜의 나무 2025. 5. 26.
반응형


1. 서론: 왜 동적 비밀 관리인가

대규모 마이크로서비스·클라우드 네이티브 환경에서는 비밀(Secrets)이 하드코딩·수동 관리될 때 유출·운영 사고 위험이 폭발적으로 증가합니다. 실리콘밸리·심천 톱티어 IT기업들은 다음 세 가지 요구사항을 만족시키기 위해 HashiCorp Vault와 Kubernetes External Secrets(CES)를 결합한 동적 비밀 관리 파이프라인을 구축합니다:
1. 제로 트러스트: 애플리케이션이나 CI/CD가 최소 권한(Least Privilege)만으로 비밀 접근
2. 동적 프로비저닝: DB 자격증명·클라우드 API 키를 요청 시점에 생성·회전
3. 자동 주입: Vault Agent · External Secrets가 Pod·CI 컨테이너에 안전하게 주입

이 가이드에서는 Vault의 고급 기능과 CES CRD 설계, 그리고 GitHub Actions·Jenkins 파이프라인에 이르는 끝판왕 구현을 다룹니다.



2. 아키텍처 개요

┌───────────────┐         ┌────────────────────────┐
│   CI/CD 파이프라인  │  GitHub Actions / Jenkins  │
└───────────────┘         └────────────────────────┘
          │                          │
          ▼                          ▼
┌──────────────────┐         ┌─────────────────┐
│ Vault Authentication │ ───▶ │ Kubernetes Auth │
│  (GitHub / AppRole)  │      │  (ServiceAccount) │
└──────────────────┘         └─────────────────┘
          │                          │
          ▼                          ▼
┌──────────────────┐         ┌────────────────────────┐
│ Vault SecretEngines │      │ ExternalSecrets Operator │
│ (KV v2, Database,   │      │  (ExternalSecret CRD)    │
│  AWS, PKI 등)      │      └────────────────────────┘
└──────────────────┘
          │                                      
          ▼                                      
┌──────────────────┐                              
│  Application Pod │                              
│  (Vault Agent    │                              
│   Sidecar or Init│                              
│   Container)     │                              
└──────────────────┘                              

• Vault Authentication: CI/CD와 K8s 각각 AppRole, Kubernetes Auth 방식으로 Vault에 로그인
• Secret Engines: KV v2 for static, Database/AWS for 동적 크리덴셜
• ExternalSecrets: Vault→K8s Secret 동기화, 자동 갱신



3. HashiCorp Vault 고급 활용

3.1 HA 설치 & 엔터프라이즈 기능

helm repo add hashicorp https://helm.releases.hashicorp.com
helm install vault hashicorp/vault \
  --set "server.ha.enabled=true" \
  --set "server.ha.raft.enabled=true" \
  --set "server.dataStorage.size=20Gi" \
  --set "injector.enabled=true"

• Raft 스토어를 이용한 HA 구성
• Vault Agent Injector 설치하여 Pod에 자동 주입

3.2 인증 메커니즘

Kubernetes Auth

vault auth enable kubernetes
vault write auth/kubernetes/config \
    token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
    kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
    kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
vault write auth/kubernetes/role/app-role \
    bound_service_account_names=vault-sa \
    bound_service_account_namespaces=prod \
    policies=app-policy \
    ttl=24h

AppRole for CI

vault auth enable approle
vault write auth/approle/role/ci-role \
    token_policies=ci-policy \
    secret_id_ttl=10m \
    token_ttl=30m \
    token_max_ttl=1h
# CI 서버에 RoleID, SecretID 안전 전달

3.3 Secret Engines
• KV v2:

vault kv enable-versioning kv
vault kv put kv/app/config username=foo password=bar


• Database (MySQL 동적 자격증명)

vault secrets enable database
vault write database/config/mysql \
    plugin_name=mysql-database-plugin \
    connection_url="{{username}}:{{password}}@tcp(db:3306)/" \
    username="vaultadmin" \
    password="vaultpass"
vault write database/roles/readonly-role \
    db_name=mysql \
    creation_statements="GRANT SELECT ON *.* TO '{{name}}'@'%' IDENTIFIED BY '{{password}}';" \
    default_ttl="1h" \
    max_ttl="24h"



3.4 정책 및 RBAC

# policies/app-policy.hcl
path "kv/data/app/*" {
  capabilities = ["read"]
}
path "database/creds/readonly-role" {
  capabilities = ["read"]
}

vault policy write app-policy policies/app-policy.hcl
vault policy write ci-policy policies/ci-policy.hcl




4. Kubernetes External Secrets 심화

4.1 설치 & SecretStore/ClusterSecretStore

# SecretStore 예시
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-secretstore
spec:
  provider:
    vault:
      server: "https://vault.vault.svc.cluster.local:8200"
      path: "kv/data/app"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "auth/kubernetes"
          role: "app-role"
---
# ExternalSecret 예시
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: app-config-secret
  namespace: prod
spec:
  refreshInterval: "1m"
  secretStoreRef:
    name: vault-secretstore
    kind: SecretStore
  target:
    name: app-config
    creationPolicy: Owner
  data:
    - secretKey: username
      remoteRef:
        key: app/config
        property: username
    - secretKey: password
      remoteRef:
        key: app/config
        property: password

• refreshInterval으로 주기적 갱신
• creationPolicy: Owner로 ExternalSecret 삭제 시 Secret 자동 삭제

4.2 템플릿 & 데이터 변환

spec:
  dataFrom:
    - extract:
        key: app/config
  template:
    metadata:
      annotations:
        ttl: "{{ .Data.ttl }}"

• dataFrom.extract로 모든 필드 한 번에 읽기
• template으로 주입된 Secret에 annotation·label 동적으로 추가



5. CI/CD 파이프라인 연동

5.1 GitHub Actions 예시

name: CI with Vault Secrets

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Vault Login (AppRole)
      id: vault-login
      run: |
        ROLE_ID="${{ secrets.VAULT_ROLE_ID }}"
        SECRET_ID="${{ secrets.VAULT_SECRET_ID }}"
        export VAULT_TOKEN=$(vault write -field=token auth/approle/login role_id=$ROLE_ID secret_id=$SECRET_ID)
    - name: Fetch Secrets from Vault
      run: |
        vault kv get -format=json kv/data/app/config > config.json
        export USERNAME=$(jq -r .data.data.username config.json)
        export PASSWORD=$(jq -r .data.data.password config.json)
    - name: Build & Test
      run: |
        docker build -t my-app:$GITHUB_SHA .
        docker run -e DB_USER=$USERNAME -e DB_PASS=$PASSWORD my-app:$GITHUB_SHA npm test

5.2 Jenkins Pipeline 예시

pipeline {
  agent any
  environment {
    VAULT_ADDR = 'https://vault.example.com'
  }
  stages {
    stage('Vault Login') {
      steps {
        script {
          def vaultToken = sh(
            script: "vault write -field=token auth/approle/login role_id=${VAULT_ROLE_ID} secret_id=${VAULT_SECRET_ID}",
            returnStdout: true
          ).trim()
          env.VAULT_TOKEN = vaultToken
        }
      }
    }
    stage('Retrieve Secrets') {
      steps {
        sh 'vault kv get -field=username kv/data/app/config > db_user.txt'
        sh 'vault kv get -field=password kv/data/app/config > db_pass.txt'
        script {
          env.DB_USER = readFile('db_user.txt').trim()
          env.DB_PASS = readFile('db_pass.txt').trim()
        }
      }
    }
    stage('Build & Deploy') {
      steps {
        sh """
          helm upgrade --install my-app ./charts/my-app \
            --set db.user=$DB_USER,db.pass=$DB_PASS
        """
      }
    }
  }
}




6. 동적 시나리오: 비밀 회전 & 자동 갱신

6.1 Lease Renewal
• Vault Agent Sidecar

# pod spec snippet
volumeMounts:
  - name: vault-secret
    mountPath: /etc/secrets
containers:
  - name: vault-agent
    image: hashicorp/vault-k8s:latest
    args:
      - "agent"
      - "-config=/etc/vault/config.hcl"

# config.hcl
pid_file = "/run/vault/vault.pid"
auto_auth {
  method "kubernetes" {
    mount_path = "auth/kubernetes"
    config = {
      role = "app-role"
    }
  }
  sink "file" {
    config = { path = "/home/vault/token" }
  }
}
listener "tcp" { address = "127.0.0.1:8200" tls_disable = true }
template {
  destination = "/etc/secrets/db_creds.json"
  contents = <<EOH
  {{- with secret "kv/data/app/config" -}}
  {"username":"{{ .Data.data.username }}","password":"{{ .Data.data.password }}"}
  {{- end -}}
  EOH
}


• Agent가 Lease 만료 전 자동 갱신

6.2 Dynamic Secrets Rotation
• Database 동적 자격증명: TTL 만료 후 자동 회전
• ExternalSecrets 갱신: refreshInterval에 따라 주기 재요청



7. 보안 최적화 & 팁
• Vault Transit Engine: 민감 데이터 암호화·복호화를 Vault 내에서 처리
• Audit Logging: Vault의 Audit Device 설정 후 Elasticsearch·Splunk 연동
• Auto Unseal: AWS KMS·Azure Key Vault 연동
• Network Policy: Vault·CES Pod 간 네트워크 분리, mTLS 적용
• Policy Versioning: GitOps로 Vault 정책(HCL) 버전 관리



8. 트러블슈팅 & “아, 이런 방법도”
• Vault 로그인 오류: VAULT_SKIP_VERIFY=true 활용 vs proper CA cert 마운트
• K8s Auth 바인딩 실패: audience 파라미터 확인, ServiceAccount 토큰 리프레시
• ExternalSecrets CRD 미반영: kubectl get externalsecrets -n prod 확인 후 CRD 설치
• CI 환경에서 Vault CLI 미설치: hashicorp/vault-action@v2 GitHub Action 사용



9. 결론: 끝판왕 동적 비밀 관리
• Vault로 동적·안전 크리덴셜 관리 → 최소 권한·자동 회전
• Kubernetes External Secrets로 자동 동기화 → Pod 재배포 없이 비밀 갱신
• CI/CD 파이프라인에 AppRole·Vault Agent 연동 → 빌드·배포 시점 안전한 주입

“교과서론 절대 가르쳐주지 않는, 실리콘밸리·심천 IT기업 최정예 개발자 수준의 CI 비밀 관리”을 직접 구현해 보세요. 보안 사고 Zero, 운영 자동화 100%를 달성할 수 있습니다!

반응형