CI/CD Study 7주차 Vault (1/2)
가시다님이 운영하시는 CI/CD Study 7주차 내용과 유형욱님의 Vault 내용을 정리한 게시글 입니다.
1. Vault 개요
Vault를 학습하기 위해 필요한 지식 안내 및 Vault에 대한 간단한 소개를 해보겠습니다.
✅ 정보보안의 3요소
정보보안의 3요소는 기밀성(Confidentiality) , 무결성(Integrity), 가용성(Availability) 으로 구성되어 있습니다.
기밀성(Confidentiality)
- 허가된 객체(사람, 시스템)만 정보에 접근 할 수 있어야합니다.
- Vault에서
Secret Engine을 통해 기밀성을 보장합니다.
무결성(Integrity)
- 정보가 허가 없이 변조 및 삭제되지 않아야 하며, 변경이 된 경우 반드시 알아챌 수 있어야합니다.
- Vault에서
Hashing을 통해 무결성을 보장합니다.
가용성(Availability)
- 허가받은 사용자가 원할 때 언제든지 정보 및 서비스를 제공할 수 있어야합니다.
- Vault에서
HA(High Availability) 구성을 통해 무결성을 보장합니다.
✅ 액세스 제어의 3단계
액세스 제어의 3단계는 인증(Who) , 인가(What), 계정관리(Accounting) 으로 구성되어 있습니다.
인증(Authentication)
- 접속하려는 대상이 누구인지 신원을 확인하는 절차입니다.
- 외부 서비스에서 Vault에 인증을 수행 시
Token을 발급하여 인증을 수행합니다.
인가(What)
인증된 사용자에게 권한을 부여하는 절차 입니다.- Vault에서
Policies을 통해 권한을 확인 합니다.
계정관리(Accounting)
- 사용자가 수행한 모든 활동들을 기록 및 추적하는 것을 말합니다.
- Vault에 접근한 기록들을
Audit Devices를 이용하여 관리합니다.
✅ 시크릿
시크릿이란 접근을 철저히 통제하고자 하는 모든 것을 의미 하며, 크게 사용자 및 시스템 접근 자격 증명 , 서비스 연동 및 자동화 키, 보안 통신 및 암호화 자산으로 구성됩니다.
사용자 및 시스템 접근 자격 증명(Credentials)
- 비밀번호(Password)
- SSH Key
- Database Credentials(ID/PW)
서비스 연동 및 자동화 키
- Cloud Credentials
- Token, API Key
보안 통신 및 암호화 자산
- 인증서 (PKI/TLS)
- 암호화 키
Vault를 사용해야하는 이유
현대 IT 인프라 및 아키텍처는 수십 수백개의 Micro Service로 이루어져있으며, 인프라가 동적으로 생성되고 사라지는 환경으로 구성되어 있습니다.
이런 환경에서는 시크릿의 갯수가 기하급수적으로 생성이 되며, 동적으로 생성되고 사라지는 환경에서 생성된 시크릿들을 관리하기가 너무 어렵고 시크릿 정보가 여러 곳에 하드코딩 되거나 방치되는 문제가 발생 합니다.
그리고 현대 IT 인프라 및 아키텍처에서는 내부, 외부의 경계가 모호해져서 보안을 강화 하기 위해 제로 트러스트를 기반으로 보안 모델을 구성합니다.
네트워크 위치를 신뢰의 기준으로 정하지 않고 모든 접근 요청을 의심하고 검증하기 때문에 사람과 시스템의 신원을 인증할 수 있고 인증된 신원에게만 정책에 따라 정확한 Secret 접근 권한을 부여해야하는 조건이 발생합니다.
Vault를 사용한다면 이런 문제들을 해결할 수 있습니다.
모든 시크릿을 Vault에 저장하여 시크릿 분산 문제를 해결할 수 있고, 생명 주기를 관리할 수 있습니다.
Vault이용 시 사람 및 시스템의 신원을 인증할 수 있고 인증된 신원에게만 정책에 따라 정확한 시크릿 접근 권한을 부여할 수 있어 Vault를 사용해야 합니다.
Vault란?
신원 기반(identity-based)의 시크릿 및 암호화 관리 시스템 이며 인증(authentication) 및 인가(authorization) 방법을 통해 암호화 서비스를 제공하여 비밀에 대한 안전을 보장 하고 감사 기능과 시크릿 접근 시 제한된 접근을 보장하는 오픈소스 솔루션 입니다.

Figure 1.1 Vault 소개
2. Vault 기본 구조와 동작 방식의 이해
Vault 동작 방식
일반적으로 개별 ID를 이용하여 Vault는 인증/인가를 통해 필요한 자격 증명을 동적으로 발급합니다.

Figure 2.1 Vault 동작 방식 소개
Vault의 인증 및 인가 동작 방식
Vault 사용 시 인증 및 인가 동작 방식은 다음과 같은 절차로 수행됩니다.
- 다양한 IDP(Identity Providers)와 통합하여 사용자의 인증 요청을 검증
- 자격 증명에 대해 검증
- 사전 정의된 정책(Policy)와 연결된 Token을 생성하여 제공
- Vault에 Access할 수 있는 Vault Token을 사용자에게 반환
Vault Token은 인증된 사용자에게 할당된 Access 대상 및 기능에 Vault 정책과 연결합니다.


Figure 2.2 Vault 인증 및 인가 동작 방식 설명
✅ SSS(Shamir Secret Sharing) 방식 이란?
암호학자 아디 샤미르(Adi Shamir)의 이름에서 따온 이 시크릿 공유 알고리즘은 개인이 특정 시크릿 정보를 보유하지 않고 결합 될 때에야 암호화 된 루트 키를 해제할 수 있는 단일 키로 조합되는 방식입니다.
이 알고리즘은 N개의 Unseal Key를 생성(Key Shares)하고, 그 중 M개의 Key를 제공(Key Threshold)하면 이를 토대로 Root Key를 획득 및 Unseal을 수행합니다.

Figure 2.3 SSS 방식 설명
3. k8s에 Vault 설치
Vault를 운영하는것이 목표가 아니라 학습이 목표이기 때문에 Dev Mode를 이용하여 Vault를 설치해보도록 하겠습니다.
Dev Mode는 데이터를 메모리에 저장(In-Memory)하며 Auto Unseal이 적용되어 있으며 Root Token이 고정되어 있습니다.
# kind k8s 배포
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
extraPortMappings:
- containerPort: 30000 # Vault UI
hostPort: 30000
- containerPort: 30001 # Jenkins UI
hostPort: 30001
- containerPort: 30002 # DB 배포(PostgreSQL 또는 MySQL)
hostPort: 30002
- containerPort: 30003 # # Sample App
hostPort: 30003
EOF
# 네임스페이스 생성
kubectl create namespace vault
# Helm repo 등록
helm repo add hashicorp https://helm.releases.hashicorp.com
# values.yaml 생성
cat <<EOF > values.yaml
global:
enabled: true
tlsDisable: true
injector:
enabled: true
# Sidecar Injection을 위해 필요한 설정
server:
dev:
enabled: true
devRootToken: "root" # 학습 편의를 위해 Root Token을 'root'로 고정
# 데이터 영구 저장이 필요 없으므로 비활성화 (Dev모드는 메모리 사용)
dataStorage:
enabled: false
# UI 활성화 및 NodePort 노출
service:
type: "NodePort"
nodePort: 30000
ui:
enabled: true
EOF
# Helm Install 실행
helm upgrade vault hashicorp/vault -n vault -f values.yaml --install
# Vault 초기화 및 잠금해제
kubectl exec -ti vault-0 -- vault status
# Key Value
# --- -----
# Seal Type shamir
# Initialized true
# Sealed false
# Total Shares 1
# Threshold 1
# Version 1.20.4
# Build Date 2025-09-23T13:22:38Z
# Storage Type inmem
# Cluster Name vault-cluster-02f37f88
# Cluster ID afc3b38b-859f-a5da-7b25-6426861a242b
# HA Enabled false
# 1) 필수 패키지 설치
sudo apt-get update
sudo apt-get install -y gpg curl lsb-release
# 2) HashiCorp 공식 APT Repository 등록
# HashiCorp GPG Key 등록
curl -fsSL https://apt.releases.hashicorp.com/gpg \
| sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
# HashiCorp Repo 추가
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
https://apt.releases.hashicorp.com $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/hashicorp.list
# 3) Vault 설치
sudo apt-get update
sudo apt-get install -y vault
# 5) Vault 주소 설정 (NodePort 30000 사용)
export VAULT_ADDR='http://localhost:30000'
# 7) Root Token으로 로그인
vault login
# root 입력
# Token (will be hidden):
# Success! You are now authenticated. The token information displayed below
# is already stored in the token helper. You do NOT need to run "vault login"
# again. Future Vault requests will automatically use this token.
# Key Value
# --- -----
# token root
# token_accessor SkmnYpxsv0qOzaNtDhRFCY8i
# token_duration ∞
# token_renewable false
# token_policies ["root"]
# identity_policies []
# policies ["root"]
Vault는 KV 정적 Secret 엔진으로 v1과 v2를 제공하는데 v1는 버전관리가 불가능하지만 v2는 버전관리가 가능합니다.
하지만 v2가 버전관리를 위해 시크릿뿐만 아니라 메타 데이터도 가져오기때문에 성능적으로 약간의 차이가 있습니다.
이번 실습에서는 v2를 사용하여 실습 하겠습니다.
# 활성화된 시크렛 엔진 목록 조회
vault secrets list
# Path Type Accessor Description
# ---- ---- -------- -----------
# cubbyhole/ cubbyhole cubbyhole_aa0ed01b per-token private secret storage
# identity/ identity identity_d77f77db identity store
# secret/ kv kv_07a4f5f4 key/value secret storage
# sys/ system system_ddd093c4 system endpoints used for control, policy and debugging
# 개발 모드가 아닌 경우 사용 개발모드인 경우 기본적으로 설정돼있음
vault secrets enable -path=secret kv-v2
# 샘플 시크릿 저장 (경로: secret/sampleapp/config)
vault kv put secret/sampleapp/config \
username="demo" \
password="p@ssw0rd"
# ======== Secret Path ========
# secret/data/sampleapp/config
# ======= Metadata =======
# Key Value
# --- -----
# created_time 2025-11-29T08:55:51.721486008Z
# custom_metadata <nil>
# deletion_time n/a
# destroyed false
# version 1
# 입력된 데이터 확인
vault kv get secret/sampleapp/config
4. Vault Agent와 SideCar Pattern
Vault Agent 소개 및 필요성
Vault Agent는 Application 코드를 수정하지 않고도 Application이 필요로 하는 시크릿이 포함된 템플릿을 렌더링할 수 있게 함으로써, Vault 도입의 초기 장벽을 제거하고 더 확장 가능하고 간편한 통합 방식을 제공하는 에이전트 데몬입니다.
Vault 도입 시 Application에서 Vault 연동과 관련된 문제가 항상 발생합니다.
이때 개발자가 Vault SDK를 사용하여 연동 시 개발자는 비즈니스 로직 외에 보안 인프라 로직 까지 모두 구현해야하고, 언어마다 구현 방식이 달라 부담스럽습니다.
Vault Agent는 이 문제를 SideCar 기반 표준 Pattern으로 해결합니다.
Vault Agent SideCar Pattern 작동원리
Vault Agent SideCar Pattern의 작동 원리는 다음과 같습니다.

Figure 4.1 Vault Agent SideCar Pattern 원리 설명
Authenticate with Vault- Vault Agent가 설정된 Auto-Auth 방식을 사용해 Vault에 인증을 시작
Verify the identity of the application- 신뢰할 수 있는 플랫폼을 통해 Application이 실제로 인증된 워크로드인지 확인
Verified- Vault가 Application의 신원을 검증 후 해당 워크로드에 어떤 Role과 Policy를 적용할지 결정
Return a client token- Vault는 Agent에게 Application 전용 Client 토큰을 반환
- Token에 적용되어 있는 권한은 Policy 기반으로 적용되어습니다.
Store the token in a sink- 발급받은 토큰을 로컬 Sink에 저장
Read secrets to render the template file- Application은 Vault를 직접 호출하지 않고 Agent가 생성한 파일만 읽어서 실행
App uses the rendered output file- 로그인, 토큰 갱신, 에러 처리, JSON 파싱, secret rotation 같은 작업은 모두 Agent가 담당
Vault Agent Injector
Vault Agent Injector란 k8s Pod 내부에 Vault Agent를 자동으로 주입해주는 기능 입니다.
Application이 Vault로 부터 자동으로 비밀 정보를 받아 올 수 있도록 해줍니다.
Vault Agent Injector를 사용하기 위한 조건은 다음과 같습니다.
- Vault가 실행 중이며, k8s cluster와 통신이 가능해야합니다.
- Vault Agent Injector가 k8s cluster에 배포되어 있어야 합니다.
- k8s 인증 방식이 활성화되어 있어야 합니다.
- Vault에 접근할 수 있도록 Policy와 Role이 설정 되어 있어야 합니다.
- Application Pod에 주입할 Annotation을 추가 해야합니다.

Figure 4.2 Vault Agent Injector 설명
실습
실습 시 AppRole을 이용하여 인증을 수행할 예정입니다.
AppRole이란 특정 정책(Policies)과 로그인 제약 조건(Constraints)의 집합입니다. 이 조건을 충족해야 해당 정책이 부여된 토큰을 발급받을 수 있습니다
# 1. AppRole 인증 방식 활성화
vault auth enable approle || echo "AppRole already enabled"
# Success! Enabled approle auth method at: approle/
# 2. 정책 생성
vault policy write sampleapp-policy - <<EOF
path "secret/data/sampleapp/*" {
capabilities = ["read"]
}
EOF
# Success! Uploaded policy: sampleapp-policy
# 3. AppRole Role 생성 - 앞서 생성한 정책(sampleapp-policy) 연결
vault write auth/approle/role/sampleapp-role \
token_policies="sampleapp-policy" \
secret_id_ttl="1h" \
token_ttl="1h" \
token_max_ttl="4h"
# Success! Data written to: auth/approle/role/sampleapp-role
# 4. Role ID 및 Secret ID 추출 및 저장
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)
# 5. 파일로 저장
mkdir -p approle-creds
echo "$ROLE_ID" > approle-creds/role_id.txt
echo "$SECRET_ID" > approle-creds/secret_id.txt
# 6. Kubernetes Secret으로 저장 (Agent 인증시 AppRole Role ID, Secret ID 사용)
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 -
# 7. 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
# 8. Sample Application + SideCar 수동 배포
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: {}
---
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
# vault agent 확인
kubectl exec -it -n vault deploy/nginx-vault-demo -c vault-agent-sidecar -- vault agent -h
# password 변경
vault kv patch secret/sampleapp/config password=new-"p@ssw0rd"
# 9. Sample Application + SideCar 자동 배포
# Kubernetes Auth Method 활성화
vault auth enable kubernetes
# Success! Enabled kubernetes auth method at: kubernetes/
# Kubernetes Auth Config 설정
TOKEN=$(kubectl create token vault -n vault)
CA_CERT=$(kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 --decode)
vault write auth/kubernetes/config \
token_reviewer_jwt="$TOKEN" \
kubernetes_host="https://kubernetes.default.svc.cluster.local" \
kubernetes_ca_cert="$CA_CERT" \
issuer="https://kubernetes.default.svc.cluster.local" \
disable_iss_validation=false
# Success! Data written to: auth/kubernetes/config
# Role 생성 (Injector 로그인)
vault write auth/kubernetes/role/sampleapp-role \
bound_service_account_names="vault-ui-sa" \
bound_service_account_namespaces="vault" \
policies="sampleapp-policy" \
ttl="24h" \
audience="https://kubernetes.default.svc.cluster.local"
# Success! Data written to: auth/kubernetes/role/sampleapp-role
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-ui-sa
namespace: vault
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: vault-injected-ui
namespace: vault
spec:
replicas: 1
selector:
matchLabels:
app: vault-injected-ui
template:
metadata:
labels:
app: vault-injected-ui
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "sampleapp-role"
vault.hashicorp.com/agent-inject-secret-config.json: "secret/data/sampleapp/config"
vault.hashicorp.com/agent-inject-template-config.json: |
{{- with secret "secret/data/sampleapp/config" -}}
{
"username": "{{ .Data.data.username }}",
"password": "{{ .Data.data.password }}"
}
{{- end }}
vault.hashicorp.com/agent-inject-output-path: "/vault/secrets"
spec:
serviceAccountName: vault-ui-sa
containers:
- name: app
image: python:3.10
ports:
- containerPort: 5000
command: ["sh", "-c"]
args:
- |
pip install flask && cat <<PYEOF > /app.py
import json, time
from flask import Flask, render_template_string
app = Flask(__name__)
while True:
try:
with open("/vault/secrets/config.json") as f:
secret = json.load(f)
break
except:
time.sleep(1)
@app.route("/")
def index():
return render_template_string("<h2>🔐 Vault Injected UI</h2><p>👤 사용자: {{username}}</p><p>🔑 비밀번호: {{password}}</p>", **secret)
app.run(host="0.0.0.0", port=5000)
PYEOF
python /app.py
---
apiVersion: v1
kind: Service
metadata:
name: vault-injected-ui
namespace: vault
spec:
type: NodePort
ports:
- port: 5000
targetPort: 5000
nodePort: 30002
selector:
app: vault-injected-ui
EOF
# 실습시 생성한 리소스 삭제
kubectl delete deployment/nginx-vault-demo \
deployment/vault-injected-ui \
service/nginx-service \
service/vault-injected-ui \
sa/vault-ui-sa \
secret/vault-approle -n vault



Figure 4.3 Vault Agent Sidecar 확인
'DevOps > Study' 카테고리의 다른 글
| CI/CD Study 8주차 Vault (1/2) (0) | 2025.12.13 |
|---|---|
| CI/CD Study 7주차 Vault (2/2) (0) | 2025.11.29 |
| CI/CD Study 6주차 ArgoCD 3/3 (1/2) (0) | 2025.11.23 |
| CI/CD Study 5주차 ArgoCD 2/3 (1) | 2025.11.16 |
| CI/CD Study 4주차 ArgoCD 1/3 (2) (0) | 2025.11.08 |