티스토리 뷰
[CI/CD Study] 8주차 - Hashicorp Vault / Vault on K8S, Vault Secrets Operator (VSO)
Hayley Shim 2025. 12. 7. 08:37안녕하세요, CICD 학습을 위해 CloudNetaStudy 스터디 모임을 통해 진행한 내용을 정리하였습니다.
8주차 : [Hashicorp Vault, Vault on K8S, Vault Secrets Operator] 에 대해 중점적으로 학습합니다.
1. Vault 단독 설치 및 기본 설정 (K8S 인증 포함)
개념 설명
이 단계는 K8S 클러스터 내에 HashiCorp Vault를 독립 실행형(Standalone) 모드로 배포하고, 보안을 위해 필수적인 초기화(Initialization) 및 봉인 해제(Unseal) 과정을 수행합니다. 가장 중요한 것은 K8S의 서비스 계정(Service Account)을 활용하여 애플리케이션이 Vault에 안전하게 인증하고 필요한 시크릿을 읽을 수 있도록 Kubernetes Auth Method를 구성하는 것입니다. 이는 Vault가 K8S의 JWT 토큰을 신뢰하는 방식으로 동작합니다.
Kubernetes 인증 단계 상세 설명
| 단계 | 주체 | 동작 | 설명 |
| 1 | 애플리케이션 Pod | JWT 제출 | Pod 내의 애플리케이션은 마운트된 Service Account JWT (JSON Web Token)를 Vault의 Kubernetes 인증 경로(auth/kubernetes/login)로 전송하여 로그인을 요청합니다. |
| 2 | Vault | 토큰 유효성 검증 요청 | Vault는 수신한 JWT가 유효한지 확인하기 위해 Kubernetes API Server에 TokenReview 요청을 보냅니다. |
| 3 | K8S API Server | 유효성 응답 | K8S API Server는 토큰이 유효한지 확인하고, 해당 토큰과 연결된 Service Account 이름과 네임스페이스 정보를 Vault에 응답합니다. |
| 4 | Vault | 정책 매핑 및 토큰 발급 | Vault는 수신한 SA 이름/네임스페이스 정보를 사전에 정의된 **Kubernetes 인증 역할(Role)**과 매칭합니다. 매칭에 성공하면, 역할에 연결된 Vault 정책이 적용된 Vault Auth Token을 애플리케이션에 발급합니다. |
| 5 | 애플리케이션 Pod | 시크릿 요청 | 애플리케이션은 발급받은 Vault Auth Token을 사용하여 Vault에 저장된 시크릿(KV Secret)을 요청하고, Vault는 정책에 따라 시크릿 데이터를 반환합니다. |
설치 과정 코드 형식
### 1.1. K8S (Kind) 클러스터 및 Vault 설치 ###
# Kind 클러스터 생성 (포트 30000, 30001 매핑)
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: 30000 # Vault Web UI
hostPort: 30000
- containerPort: 30001 # Sample application
hostPort: 30001
EOF
# Helm 저장소 및 네임스페이스 설정
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
kubectl create namespace vault
# Vault Helm 설치 (Standalone, NodePort 30000 설정)
cat <<EOF > vault-values.yaml
global:
tlsDisable: true
server:
standalone:
enabled: true
config: |
ui = true
listener "tcp" {
address = "[::]:8200"
tls_disable = 1
}
storage "file" { path = "/vault/data" }
service:
type: NodePort
nodePort: 30000
ui:
enabled: true
injector:
enabled: false
EOF
helm upgrade vault hashicorp/vault -n vault -f vault-values.yaml --install --version 0.31.0
### 1.2. Vault 초기화 및 Unseal ###
# 초기화 및 키 저장
kubectl exec vault-0 -n vault -- vault operator init \
-key-shares=1 \
-key-threshold=1 \
-format=json > cluster-keys.json
# Unseal Key 추출 및 봉인 해제
export VAULT_UNSEAL_KEY=$(jq -r ".unseal_keys_b64[]" cluster-keys.json)
kubectl exec vault-0 -n vault -- vault operator unseal $VAULT_UNSEAL_KEY
### 1.3. K8S 인증 방법 설정 및 시크릿 저장 ###
# Vault 주소 및 Root Token 설정/로그인
export VAULT_ADDR='http://localhost:30000'
export VAULT_ROOT_TOKEN=$(jq -r ".root_token" cluster-keys.json)
vault login $VAULT_ROOT_TOKEN
# KV-v2 시크릿 엔진 활성화 및 시크릿 저장
vault secrets enable -path=secret kv-v2
vault kv put secret/webapp/config username="static-user" password="static-password"
# Kubernetes 인증 방법 활성화 및 K8S API 서버 정보 설정
vault auth enable kubernetes
vault write auth/kubernetes/config kubernetes_host="https://kubernetes.default.svc"
# Vault 정책 ('webapp') 및 역할 ('webapp') 생성
vault policy write webapp - <<EOF
path "secret/data/webapp/config" {
capabilities = ["read"]
}
EOF
vault write auth/kubernetes/role/webapp \
bound_service_account_names=vault \
bound_service_account_namespaces=default \
policies=webapp \
ttl=24h
### 1.4. 샘플 애플리케이션 배포 및 검증 ###
# K8S Service Account 생성
kubectl create sa vault
# 웹 애플리케이션 Deployment + Service (NodePort 30001) 배포
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
spec:
# ... (Deployment 상세 설정: ServiceAccountName: vault, VAULT_ADDR, JWT_PATH 환경변수 설정)
# ... (Service NodePort 30001 설정)
EOF
# 애플리케이션 동작 확인
curl 127.0.0.1:30001
결론
이 단계를 통해 K8S 클러스터에 안정적인 Vault 환경을 구축하고, 애플리케이션이 Root Token 없이 자신의 Service Account JWT를 사용하여 Vault에 안전하게 접근하고 필요한 시크릿(Secret/webapp/config)을 읽어올 수 있는 기반을 마련했습니다.
2. Vault Secrets Operator (VSO)를 사용한 시크릿 동기화
개념 설명
Vault Secrets Operator (VSO)는 Vault와 K8S Secret 사이의 자동 동기화(Synchronization)를 담당하는 컨트롤러입니다. VSO를 사용하면 애플리케이션 코드를 수정하지 않고도 일반 K8S Secret을 사용하는 것처럼 Vault의 시크릿을 활용할 수 있습니다.
- Static Secret: Vault에 저장된 값을 주기적으로(refreshAfter) K8S Secret으로 복사합니다.
- Dynamic Secret: Vault가 DB 등 백엔드에서 임시 계정을 생성하면, VSO가 이를 K8S Secret에 동기화하고, 계정 만료 시간이 다가오면 자동으로 계정을 갱신(Rotate)하며 관련 파드에 롤아웃 재시작(Rollout Restart)을 트리거합니다.
VSO가 Static Secret 및 Dynamic Secret을 처리하는 과정은 다음과 같은 핵심 주체와 단계로 구성됩니다.
| 주체 | 역할 | CRD (Custom Resource Definition) |
| User | 시크릿 동기화 정책 정의 및 적용 | VaultAuth, VaultStaticSecret, VaultDynamicSecret |
| VSO | Vault와 K8S API 간의 조정(Reconciliation) | Controller Manager |
| Vault | 시크릿 저장, 동적 계정 생성 및 갱신 | Secret Engines (KV, Database 등) |
| K8S Secret | 동기화된 시크릿 데이터 저장 | Secret |
| Application Pod | K8S Secret을 볼륨 마운트 또는 환경 변수로 사용 | - |
설치 과정 코드 형식
### 2.1. Dev 모드 Vault 설치 및 VSO 준비 (재사용을 위해 Dev 모드 사용) ###
# Dev 모드 Vault 설치 및 Root Token 'root' 설정 (이전 Vault 설치 삭제 후 진행)
# ... (Helm install vault --values vault-values.yaml)
# export VAULT_ADDR='http://localhost:30000'; vault login root
# K8S 인증 및 KV-v2 엔진 설정 (VSO가 사용할 경로)
vault auth enable -path demo-auth-mount kubernetes
vault secrets enable -path=kvv2 kv-v2
# VSO가 사용할 Vault 정책/역할 설정 (demo-static-app SA에 webapp 정책 연결)
# ... (webapp.json 및 vault policy write webapp 작성)
vault write auth/demo-auth-mount/role/role1 \
bound_service_account_names=demo-static-app \
bound_service_account_namespaces=app \
policies=webapp \
audience=vault
# 정적 시크릿 생성
vault kv put kvv2/webapp/config username="static-user" password="static-password"
### 2.2. Vault Secrets Operator (VSO) 설치 및 Static Secret 동기화 ###
# VSO 설치 (vault-secrets-operator-system 네임스페이스에 설치)
helm install vault-secrets-operator hashicorp/vault-secrets-operator -n vault-secrets-operator-system --create-namespace --version 0.10.0 --values vault-operator-values.yaml
# K8S ServiceAccount 생성 (app 네임스페이스)
kubectl create ns app
kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
namespace: app
name: demo-static-app
EOF
# VaultAuth CRD 정의 (VSO의 인증 방식)
cat <<EOF | kubectl apply -f -
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
EOF
# VaultStaticSecret CRD 정의 (30초마다 kvv2/webapp/config를 secretkv로 동기화)
cat <<EOF | kubectl apply -f -
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: vault-kv-app
namespace: app
spec:
mount: kvv2
path: webapp/config
destination:
name: secretkv
create: true
refreshAfter: 30s
vaultAuthRef: static-auth
EOF
# 동기화된 K8S Secret 확인
kubectl view-secret -n app secretkv --all
### 2.3. Dynamic Secret 동기화 (PostgreSQL 연동) ###
# PostgreSQL 설치 (DB 백엔드)
helm upgrade --install postgres bitnami/postgresql --namespace postgres --set auth.postgresPassword=secret-pass
# Vault Database Secrets Engine 설정 및 Role 정의 (10분 TTL)
vault secrets enable -path=demo-db database
vault write demo-db/config/demo-db \
plugin_name=postgresql-database-plugin \
connection_url="postgresql://{{username}}:{{password}}@postgres-postgresql.postgres.svc.cluster.local:5432/postgres?sslmode=disable" \
username="postgres" \
password="secret-pass"
vault write demo-db/roles/dev-postgres \
db_name=demo-db \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';" \
revocation_statements="REVOKE ALL ON DATABASE postgres FROM \"{{name}}\";" \
default_ttl="10m"
# VaultDynamicSecret CRD 정의 (10분 TTL 계정 자동 갱신 및 파드 롤아웃)
cat <<EOF | kubectl apply -f -
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultDynamicSecret
metadata:
name: vso-db-demo
namespace: demo-ns
spec:
mount: demo-db
path: creds/dev-postgres
destination:
name: vso-db-demo
create: true
rolloutRestartTargets:
- kind: Deployment
name: vso-db-demo # 갱신 시 해당 Deployment 재시작
vaultAuthRef: dynamic-auth
EOF
# 동적 Secret 확인 (계정 정보 및 TTL에 따른 Secret 값/파드 롤아웃 확인)
kubectl get vaultdynamicsecret -n demo-ns
kubectl get secret -n demo-ns vso-db-demo
결론
VSO를 사용하면 K8S 애플리케이션은 Vault의 복잡한 인증 절차 없이 일반 K8S Secret을 통해 정적 시크릿을 동기화 받고, 특히 동적 시크릿의 경우 Vault가 생성/갱신하는 임시 계정을 자동으로 받아볼 수 있습니다. 이는 운영 편의성과 보안 강화 (계정 자동 폐기)를 동시에 달성하게 합니다.
3. Vault 고급 설정 (HA 모드, LDAP 인증, TLS 설정)
개념 설명
이 단계는 Vault를 기업 환경에 적용하기 위한 필수적인 고급 설정을 다룹니다.
- HA (High Availability) 모드: Raft 스토리지를 사용하여 Vault 클러스터의 고가용성을 확보합니다. 마스터-슬레이브 구조가 아닌 Raft 합의 알고리즘으로 클러스터 노드 간 데이터 일관성을 유지합니다.
- LDAP 인증: 조직의 중앙 집중식 사용자 관리 시스템(LDAP)을 Vault에 연동하여 기존 LDAP 계정으로 Vault에 로그인하고 그룹 기반 정책을 부여합니다.
- TLS/Ingress 설정: 통신 채널 보안을 위해 HTTPS (TLS)를 적용하고, NGINX Ingress Controller의 SSL Passthrough 기능을 사용하여 TLS 트래픽을 Vault로 직접 전달하는 구성을 구축합니다.
1. 고가용성 (HA) 모드 - Raft 기반
| 구성 요소 | 역할 | 상세 설명 |
| Vault Cluster (3 Nodes) | 서비스 연속성 | Raft 합의 알고리즘을 사용하여 클러스터 노드 간의 데이터 일관성을 유지합니다. |
| Active Node | 쓰기/읽기 처리 | 클러스터 내에서 오직 하나의 노드만 Active 상태가 되어 모든 API 요청을 처리합니다. |
| Standby Nodes | 대기 및 복제 | Standby 상태로 Active Node의 데이터를 복제하며, Active Node에 장애 발생 시 새로운 Active Node로 승격할 준비를 합니다. |
| Raft Storage | 데이터 저장 | Vault 내부에 내장된 스토리지로, 고가용성을 제공하며 외부 데이터베이스 의존성을 줄여줍니다. |
2. LDAP 인증 및 그룹 정책 매핑
| 구성 요소 | 역할 | 상세 설명 |
| User (LDAP Account) | 로그인 주체 | LDAP 계정(ID/PW)을 사용하여 Vault에 인증을 시도합니다. |
| LDAP Server | ID/그룹 관리 | 사용자 계정 및 사용자-그룹 매핑 정보를 중앙에서 관리합니다. |
| Vault LDAP Auth Method | 인증 중계 | 사용자에게 받은 자격 증명을 LDAP 서버로 전달하여 인증을 검증하고, 사용자가 속한 LDAP 그룹 정보를 가져옵니다. |
| Group Policy Mapping | 권한 부여 | 가져온 LDAP 그룹(예: admins)을 사전에 정의된 Vault 정책 (예: admin Policy)에 연결하여 사용자에게 적절한 권한을 부여합니다. |
3. TLS/Ingress를 통한 보안 접속
| 구성 요소 | 역할 | 상세 설명 |
| Vault Client (Browser/CLI) | 접속 시도 | HTTPS(Port 443 또는 NodePort 30000)를 통해 Vault에 접근을 시도합니다. |
| Ingress Controller (NGINX) | 트래픽 라우팅 | 외부 트래픽을 K8S 클러스터 내부의 Vault Service로 라우팅합니다. |
| SSL Passthrough | TLS 터널링 | NGINX가 TLS 트래픽을 복호화하지 않고, 암호화된 상태 그대로 Vault Active Node의 HTTPS 리스너(8200)로 전달하여 종단 간 암호화를 유지합니다. |
| Vault TLS Listener (8200) | 암호화/복호화 | Vault의 서버 인증서를 사용하여 TLS 핸드셰이크를 완료하고 데이터 암호화/복호화를 처리합니다. |
설치 과정 코드 형식
### 3.1. Vault HA (고가용성) 모드 설정 (Raft 기반) ###
# HA 구성용 Helm 값 파일 (replicas: 3, Raft 활성화)
cat << EOF > values-ha.yaml
server:
replicas: 3
ha:
enabled: true
raft:
enabled: true
config: |
ui = true
listener "tcp" { tls_disable = 1; address = "[::]:8200" }
dataStorage:
enabled: true
size: 10Gi
ui:
serviceNodePort: 30000
injector:
enabled: false
EOF
# HA Vault 설치
helm install vault hashicorp/vault -n vault -f values-ha.yaml --version 0.31.0
# HA 초기화 및 Unseal (5 Shares, 3 Threshold)
# kubectl exec -it vault-0 -n vault -- vault operator init # Root Token 및 5개 키 생성
# kubectl exec -it vault-0 -n vault -- vault operator unseal # 3개 키를 사용하여 Unseal
# kubectl exec -it vault-0 -n vault -- vault status # Active/Standby 상태 확인
### 3.2. Vault Auth with LDAP 설정 ###
# OpenLDAP 서버 배포
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Namespacemetadata:
name: openldap
# ... (Deployment 및 Service 정의 생략)
EOF
# Vault LDAP 인증 활성화 및 구성 (Root Token으로 로그인된 상태에서 실행)
vault auth enable ldap
vault write auth/ldap/config \
url="ldap://openldap.openldap.svc:389" \
binddn="cn=admin,dc=example,dc=org" \
bindpass="admin" \
userdn="ou=people,dc=example,dc=org" \
groupdn="ou=groups,dc=example,dc=org" \
groupfilter="(member=uid={{.Username}},ou=people,dc=example,dc=org)"
# Vault 'admin' 정책 생성 및 LDAP 'admins' 그룹에 매핑
vault policy write admin - <<EOF
path "*" { capabilities = ["create", "read", "update", "delete", "list", "sudo"]}
EOF
vault write auth/ldap/groups/admins policies=admin
# LDAP 계정으로 로그인 테스트
# vault login -method=ldap username=alice password=alice123
### 3.3. Vault Server TLS 및 Ingress 설정 ###
# NGINX Ingress Controller 배포 및 SSL Passthrough 활성화
# kubectl apply -f .../ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
# kubectl get deployment ingress-nginx-controller -n ingress-nginx -o yaml | sed '/- --publish-status-address=localhost/a\
# - --enable-ssl-passthrough' | kubectl apply -f -
# TLS 인증서 생성 및 K8S Secret 등록 (kind 노드 내부에서 openssl 실행 후 Secret 생성)
# kubectl -n vault create secret tls vault-tls --cert=vault.crt --key=vault.key
# Vault Helm 값 파일 (TLS 활성화)
cat << EOF > values-tls.yaml
global:
tlsDisable: false
server:
volumes:
- name: vault-tls
secret: { secretName: vault-tls }
volumeMounts:
- name: vault-tls
mountPath: /vault/user-tls
standalone:
enabled: "true"
config: |-
listener "tcp" {
tls_disable = 0
address = "0.0.0.0:8200"
tls_cert_file = "/vault/user-tls/tls.crt"
tls_key_file = "/vault/user-tls/tls.key"
}
api_addr = "https://vault.vault.svc.cluster.local:8200"
EOF
# Vault 설치 (TLS 활성화)
helm install vault hashicorp/vault -n vault -f values-tls.yaml --version 0.29.0
# Ingress 리소스 정의 (SSL Passthrough 적용)
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: vault-https
namespace: vault
annotations:
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
rules:
- host: vault.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: vault
port: { number: 8200 }
tls:
- hosts:
- vault.example.com
secretName: vault-tls
EOF
결론
Vault HA 구성은 Raft 기반 클러스터링으로 무중단 서비스를 보장합니다. LDAP 연동은 사용자 계정 관리를 중앙 집중화하며, TLS/Ingress 구성은 모든 통신을 암호화하여 Vault로의 접근을 안전하게 보호하는 보안 인프라를 완성합니다.
'IT > Devops' 카테고리의 다른 글
| [CI/CD Study] 7주차 - HashiCorp Vault (1) | 2025.11.29 |
|---|---|
| [cicd] 6주차 - Argo CD (0) | 2025.11.22 |
| [cicd] 5주차 - Argo CD (0) | 2025.11.16 |
| [cicd] 4주차 - Argo CD (0) | 2025.11.09 |
| [cicd] 3주차 - Jenkins + ArgoCD (0) | 2025.11.02 |
- Total
- Today
- Yesterday
- AWS
- ai 엔지니어링
- PYTHON
- IaC
- cni
- GKE
- NFT
- 혼공파
- AI Engineering
- k8s cni
- CICD
- k8s calico
- AI
- k8s
- cloud
- operator
- autoscaling
- SDWAN
- 도서
- handson
- terraform
- S3
- 파이썬
- 혼공챌린지
- VPN
- security
- NW
- 혼공단
- GCP
- EKS
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
