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%를 달성할 수 있습니다!
'IT & Tech 정보' 카테고리의 다른 글
ChatOps 워크플로우 끝판왕 가이드 Slack · Hubot · GitHub Webhook 자동화 (0) | 2025.05.26 |
---|---|
DB 마이그레이션 자동화 끝판왕 가이드 (0) | 2025.05.26 |
통합 보안 스캔: SonarQube · OWASP ZAP · Snyk CI 파이프라인 연동 끝판왕 가이드 (0) | 2025.05.26 |
모노레포 의존성 자동화: Nx & Lerna를 활용한 빌드 그래프 관리 끝판왕 가이드 (0) | 2025.05.26 |
동적 파이프라인 생성: Jenkins Shared Library & Groovy 스크립팅 끝판왕 가이드 (0) | 2025.05.26 |