HashiCorp Vault란?
HashiCorp Vault는 시크릿(비밀번호, API 키, 인증서 등)을 중앙에서 안전하게 관리하는 시크릿 관리 플랫폼이다. 전통적인 환경 변수나 설정 파일에 시크릿을 하드코딩하면 유출 위험이 크다. Vault는 동적 시크릿 생성, 자동 만료, 감사 로그를 통해 이 문제를 근본적으로 해결한다.
이 글에서는 Vault의 핵심 아키텍처, Seal/Unseal 메커니즘, Secret Engine 종류별 활용법, 그리고 Spring Boot·Kubernetes 환경에서의 실전 통합 패턴을 심화하여 다룬다.
Vault 아키텍처 핵심 구조
Vault는 크게 Storage Backend, Barrier, Secret Engine, Auth Method, Audit Device로 구성된다.
┌─────────────────────────────────────┐
│ Vault Server │
│ ┌───────────┐ ┌────────────────┐ │
│ │ Auth │ │ Secret Engine │ │
│ │ Methods │ │ (KV, PKI, │ │
│ │ (Token, │ │ Database, │ │
│ │ AppRole, │ │ Transit) │ │
│ │ K8s) │ │ │ │
│ └─────┬─────┘ └───────┬───────┘ │
│ │ Barrier │ │
│ └────────┬────────┘ │
│ ┌───────┴───────┐ │
│ │ Storage │ │
│ │ (Consul, Raft,│ │
│ │ S3, etcd) │ │
│ └───────────────┘ │
│ ┌──────────────────────────────┐ │
│ │ Audit Device (File, Syslog) │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────┘
Storage Backend는 암호화된 데이터를 저장하는 영구 저장소다. Vault 자체는 데이터를 암호화한 후 Storage에 기록하므로 Storage 관리자도 원본 시크릿을 볼 수 없다. 프로덕션에서는 Integrated Raft Storage가 가장 많이 사용된다.
Seal/Unseal 메커니즘
Vault가 시작되면 Sealed 상태다. 이 상태에서는 Storage의 데이터를 복호화할 수 없어 어떤 요청도 처리하지 못한다.
# Vault 초기화: 5개 키 생성, 3개로 Unseal
vault operator init -key-shares=5 -key-threshold=3
# Unseal (3번 반복, 서로 다른 키 사용)
vault operator unseal <key-1>
vault operator unseal <key-2>
vault operator unseal <key-3>
# 상태 확인
vault status
# Sealed: false → 사용 가능
Vault는 Shamir’s Secret Sharing 알고리즘을 사용한다. Master Key를 N개 조각으로 나누고, 그 중 T개(threshold)를 모아야 Unseal할 수 있다. 이를 통해 단일 관리자가 혼자 Vault를 열 수 없게 한다.
Auto Unseal
프로덕션에서는 수동 Unseal이 비현실적이다. AWS KMS, GCP Cloud KMS, Azure Key Vault를 이용한 Auto Unseal을 설정한다.
# vault.hcl 설정
seal "awskms" {
region = "ap-northeast-2"
kms_key_id = "alias/vault-unseal-key"
}
storage "raft" {
path = "/opt/vault/data"
node_id = "vault-1"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_cert_file = "/opt/vault/tls/tls.crt"
tls_key_file = "/opt/vault/tls/tls.key"
}
Secret Engine 종류별 활용
1. KV (Key-Value) Secret Engine
가장 기본적인 시크릿 저장소다. v2에서는 버전 관리를 지원한다.
# KV v2 엔진 활성화
vault secrets enable -path=secret kv-v2
# 시크릿 저장
vault kv put secret/myapp/db
username="admin"
password="s3cret!"
host="db.internal:5432"
# 시크릿 조회
vault kv get secret/myapp/db
# 특정 버전 조회
vault kv get -version=2 secret/myapp/db
# 삭제 (soft delete, 복구 가능)
vault kv delete secret/myapp/db
# 영구 삭제
vault kv destroy -versions=1,2 secret/myapp/db
2. Database Secret Engine (동적 시크릿)
Vault의 가장 강력한 기능이다. DB 자격 증명을 요청 시 동적으로 생성하고, TTL 만료 시 자동 폐기한다. 유출되어도 수분 내 만료되므로 피해가 최소화된다.
# Database 엔진 활성화
vault secrets enable database
# PostgreSQL 연결 설정
vault write database/config/mydb
plugin_name=postgresql-database-plugin
connection_url="postgresql://{{username}}:{{password}}@db.internal:5432/myapp?sslmode=require"
allowed_roles="readonly","readwrite"
username="vault_admin"
password="vault_admin_pw"
# 읽기 전용 Role 정의 (TTL 1시간, 최대 24시간)
vault write database/roles/readonly
db_name=mydb
creation_statements="CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO "{{name}}";"
default_ttl="1h"
max_ttl="24h"
# 동적 자격 증명 발급
vault read database/creds/readonly
# username: v-token-readonly-abc123
# password: A1b2C3d4E5...
# lease_duration: 1h
발급된 자격 증명은 lease로 관리된다. TTL 만료 시 Vault가 자동으로 DB에서 해당 사용자를 삭제한다. 필요하면 vault lease renew로 갱신하거나 vault lease revoke로 즉시 폐기할 수 있다.
3. PKI Secret Engine (인증서 자동 발급)
내부 서비스 간 mTLS 인증서를 자동 발급한다.
# PKI 엔진 활성화 및 Root CA 생성
vault secrets enable pki
vault secrets tune -max-lease-ttl=87600h pki
vault write pki/root/generate/internal
common_name="internal-ca"
ttl=87600h
# Intermediate CA 설정
vault secrets enable -path=pki_int pki
vault write pki_int/intermediate/generate/internal
common_name="internal-ca-int"
# Role 정의
vault write pki_int/roles/internal-service
allowed_domains="svc.cluster.local"
allow_subdomains=true
max_ttl="72h"
# 인증서 발급
vault write pki_int/issue/internal-service
common_name="myapp.svc.cluster.local"
ttl="24h"
4. Transit Secret Engine (암호화 as a Service)
애플리케이션이 직접 암호화 로직을 구현하지 않고 Vault에 암호화/복호화를 위임한다.
# Transit 엔진 활성화 및 키 생성
vault secrets enable transit
vault write -f transit/keys/myapp-encryption
# 암호화
vault write transit/encrypt/myapp-encryption
plaintext=$(echo "민감한 데이터" | base64)
# ciphertext: vault:v1:abc123...
# 복호화
vault write transit/decrypt/myapp-encryption
ciphertext="vault:v1:abc123..."
# plaintext: 7ZuA6rCA7ZWc IOuNsOydtO2EsA== (base64)
Auth Method: 인증 방식
Vault에 접근하려면 먼저 인증해야 한다. 환경에 맞는 Auth Method를 선택한다.
| Auth Method | 용도 | 적합한 환경 |
|---|---|---|
| Token | 기본 인증, 모든 Auth의 최종 결과 | 개발/테스트 |
| AppRole | 애플리케이션 서버 인증 | VM, CI/CD |
| Kubernetes | Pod ServiceAccount 기반 인증 | K8s 클러스터 |
| AWS IAM | EC2/Lambda IAM Role 기반 | AWS 환경 |
| OIDC | SSO 연동 (Google, Okta) | 관리자 웹 UI |
AppRole 인증 설정
# AppRole 활성화
vault auth enable approle
# Role 생성 (Policy 연결)
vault write auth/approle/role/myapp
token_policies="myapp-policy"
token_ttl=1h
token_max_ttl=4h
secret_id_ttl=10m
secret_id_num_uses=1
# Role ID 조회 (배포 시 설정 파일에 포함)
vault read auth/approle/role/myapp/role-id
# Secret ID 발급 (배포 시 1회용으로 전달)
vault write -f auth/approle/role/myapp/secret-id
# 로그인
vault write auth/approle/login
role_id="abc-123"
secret_id="def-456"
# → Token 발급
Kubernetes Auth
# K8s Auth 활성화
vault auth enable kubernetes
vault write auth/kubernetes/config
kubernetes_host="https://kubernetes.default.svc:443"
# Role: 특정 namespace의 특정 SA만 허용
vault write auth/kubernetes/role/myapp
bound_service_account_names=myapp-sa
bound_service_account_namespaces=production
policies=myapp-policy
ttl=1h
Policy: 최소 권한 원칙
Vault Policy는 HCL(HashiCorp Configuration Language)로 작성하며, 경로 기반으로 권한을 제어한다.
# myapp-policy.hcl
# KV 시크릿 읽기만 허용
path "secret/data/myapp/*" {
capabilities = ["read", "list"]
}
# DB 동적 자격 증명 발급 허용
path "database/creds/readonly" {
capabilities = ["read"]
}
# Transit 암호화/복호화만 허용 (키 삭제 불가)
path "transit/encrypt/myapp-encryption" {
capabilities = ["update"]
}
path "transit/decrypt/myapp-encryption" {
capabilities = ["update"]
}
# 자신의 토큰 정보 조회
path "auth/token/lookup-self" {
capabilities = ["read"]
}
# Policy 적용
vault policy write myapp-policy myapp-policy.hcl
Spring Boot 통합: Spring Cloud Vault
Spring Boot 애플리케이션에서 Vault 시크릿을 PropertySource로 자동 로드할 수 있다.
// build.gradle.kts
dependencies {
implementation("org.springframework.cloud:spring-cloud-starter-vault-config")
implementation("org.springframework.vault:spring-vault-core")
}
# application.yml
spring:
cloud:
vault:
uri: https://vault.internal:8200
authentication: KUBERNETES # K8s 환경
kubernetes:
role: myapp
service-account-token-file: /var/run/secrets/kubernetes.io/serviceaccount/token
kv:
enabled: true
backend: secret
default-context: myapp
application-name: myapp
database:
enabled: true
role: readonly
backend: database
config:
import: vault://
// DB 자격 증명 자동 갱신 설정
@Configuration
public class VaultDatabaseConfig {
@Bean
public SecretLeaseContainer leaseContainer(
VaultOperations vaultOps,
TaskSchedulerRouter scheduler) {
SecretLeaseContainer container =
new SecretLeaseContainer(vaultOps, scheduler);
container.setMinRenewal(Duration.ofSeconds(30));
container.setExpiryThreshold(Duration.ofSeconds(60));
container.start();
return container;
}
@EventListener
public void onSecretLeaseExpired(SecretLeaseExpiredEvent event) {
// lease 만료 시 DataSource 갱신 로직
if (event.getSource().getPath()
.startsWith("database/creds/")) {
log.info("DB credential rotated, refreshing pool");
// HikariCP soft-evict 후 새 연결
}
}
}
Kubernetes 통합: Vault Agent Injector
Vault Agent Injector는 Mutating Webhook으로 Pod에 사이드카를 주입하여 시크릿을 파일로 마운트한다. 애플리케이션 코드 수정 없이 시크릿을 주입할 수 있다.
# Helm으로 Vault Agent Injector 설치
helm install vault hashicorp/vault
--set "injector.enabled=true"
--set "server.enabled=false" # 외부 Vault 사용 시
---
# Pod에 어노테이션 추가
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
template:
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "myapp"
vault.hashicorp.com/agent-inject-secret-db: "database/creds/readonly"
vault.hashicorp.com/agent-inject-template-db: |
{{- with secret "database/creds/readonly" -}}
export DB_USER="{{ .Data.username }}"
export DB_PASS="{{ .Data.password }}"
{{- end }}
spec:
serviceAccountName: myapp-sa
containers:
- name: myapp
image: myapp:latest
command: ["/bin/sh", "-c", "source /vault/secrets/db && exec java -jar app.jar"]
Vault CSI Provider (시크릿을 Volume으로)
Vault Agent 대신 Secrets Store CSI Driver를 사용하면 사이드카 없이 시크릿을 볼륨에 마운트할 수 있다.
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: vault-myapp
spec:
provider: vault
parameters:
vaultAddress: "https://vault.internal:8200"
roleName: "myapp"
objects: |
- objectName: "db-password"
secretPath: "secret/data/myapp/db"
secretKey: "password"
---
# Pod에서 CSI Volume 마운트
volumes:
- name: secrets
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "vault-myapp"
운영 Best Practices
1. Namespace로 멀티 테넌트 격리
# 팀별 Namespace 생성
vault namespace create team-backend
vault namespace create team-infra
# Namespace 내에서 독립적인 Policy, Auth, Secret Engine
VAULT_NAMESPACE=team-backend vault secrets enable -path=secret kv-v2
2. 감사 로그 필수 활성화
# 파일 감사 로그
vault audit enable file file_path=/var/log/vault/audit.log
# Syslog로 중앙 로그 시스템 연동
vault audit enable syslog tag="vault" facility="AUTH"
# 감사 로그 예시 (모든 요청/응답 기록)
# {
# "type": "request",
# "auth": {"token_type": "service", "policies": ["myapp-policy"]},
# "request": {
# "path": "secret/data/myapp/db",
# "operation": "read"
# }
# }
3. Response Wrapping (안전한 시크릿 전달)
# 시크릿을 1회용 Wrapping Token으로 감싸기
vault kv get -wrap-ttl=120 secret/myapp/db
# wrapping_token: hvs.ABCDEF123456
# 수신 측에서 1회만 unwrap 가능
VAULT_TOKEN=hvs.ABCDEF123456 vault unwrap
# → 실제 시크릿 반환 (2번째 시도 시 실패)
4. 시크릿 로테이션 자동화
# Root 자격 증명 자동 로테이션
vault write -f database/rotate-root/mydb
# 정적 Role (기존 DB 계정 비밀번호 자동 변경)
vault write database/static-roles/dba
db_name=mydb
username="app_user"
rotation_period=86400 # 24시간마다 자동 변경
HA 구성: Raft 클러스터
프로덕션에서는 최소 3노드 Raft 클러스터로 고가용성을 확보한다.
# vault-1.hcl
storage "raft" {
path = "/opt/vault/data"
node_id = "vault-1"
retry_join {
leader_api_addr = "https://vault-2.internal:8200"
}
retry_join {
leader_api_addr = "https://vault-3.internal:8200"
}
}
# Leader 선출은 Raft가 자동으로 처리
# vault operator raft list-peers로 클러스터 상태 확인
vault operator raft list-peers
# Node Address State
# vault-1 vault-1.internal:8201 leader
# vault-2 vault-2.internal:8201 follower
# vault-3 vault-3.internal:8201 follower
마무리
HashiCorp Vault는 단순한 시크릿 저장소가 아니라 시크릿 라이프사이클 전체를 관리하는 플랫폼이다. 동적 시크릿으로 유출 피해를 최소화하고, Transit Engine으로 암호화를 중앙화하며, Policy로 최소 권한 원칙을 강제한다.
실무에서 가장 중요한 포인트를 정리하면:
- Auto Unseal: 프로덕션에서는 반드시 KMS 기반 Auto Unseal 사용
- 동적 시크릿: DB 자격 증명은 정적 비밀번호 대신 동적 발급 활용
- 감사 로그: 누가 어떤 시크릿에 접근했는지 추적 필수
- Lease 관리: TTL과 자동 갱신을 적절히 설정하여 다운타임 방지
Vault를 도입하면 시크릿 관리가 코드 배포와 완전히 분리되어, 보안과 운영 편의성 모두를 잡을 수 있다. Spring Security 인증 아키텍처와 결합하면 인증부터 시크릿까지 일관된 보안 체계를 구축할 수 있으며, K8s 네이티브 사이드카 패턴을 활용하면 Vault Agent 통합이 더욱 안정적이 된다.