CI/CD Study 7주차 Vault (2/2)
가시다님이 운영하시는 CI/CD Study 7주차 내용과 유형욱님의 Vault 내용을 정리한 게시글 입니다.
1. Jenkins + Vault (AppRole) - CI
Vault KV Store에 저장한 username, password을 Jenkins을 활용해서 획득해보겠습니다.
기존 실습환경 에서 추가적으로 진행하겠습니다.
Jenkins + KV 시크릿 실습 환경 구성
kubectl create ns jenkins
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jenkins-pvc
namespace: jenkins
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: jenkins
namespace: jenkins
spec:
replicas: 1
selector:
matchLabels:
app: jenkins
template:
metadata:
labels:
app: jenkins
spec:
securityContext:
fsGroup: 1000
containers:
- name: jenkins
image: jenkins/jenkins:lts
ports:
- name: http
containerPort: 8080
- name: agent
containerPort: 50000
livenessProbe:
httpGet:
path: "/login"
port: 8080
initialDelaySeconds: 90
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 5
readinessProbe:
httpGet:
path: "/login"
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
volumeMounts:
- name: jenkins-home
mountPath: /var/jenkins_home
volumes:
- name: jenkins-home
persistentVolumeClaim:
claimName: jenkins-pvc
---
apiVersion: v1
kind: Service
metadata:
name: jenkins-svc
namespace: jenkins
spec:
type: NodePort
selector:
app: jenkins
ports:
- nodePort: 30001
port: 8080
targetPort: http
protocol: TCP
name: http
- port: 50000
targetPort: agent
protocol: TCP
name: agent
EOF
# 초기 암호 확인
kubectl exec -it -n jenkins deploy/jenkins -- cat /var/jenkins_home/secrets/initialAdminPassword
# a8cb7535dc874d3b839da1928e83037d
cd approle-creds
# role_id 확인
cat role_id.txt
b8654b70-7b97-2ed7-99cd-48ff94ca937d
# secret_id 확인
cat secret_id.txt
630e9122-a9a2-cbe0-8643-6564fffac384
Jenkins 초기 설정은 해당 링크에서 확인 하시면 됩니다. 링크
초기 설정 후 Vault Plugin을 설치 합니다.

Figure 1.1 Vault Plugin 설치
Vault URL: http://vault.vault.svc:8200 입럭 후 add를 클릭 하여 추가합니다.

Figure 1.2 Vault Plugin 설치
- Kind:
Vault AppRole Credential - Role ID & Secret ID 입력
- ID
vault-approle-creds
로 지정 후 Add 버튼을 클릭하여 추가합니다.

Figure 1.3 Vault Plugin 설치
Jenkins Pipeline 구성
Jenkinsfile을 이용하여 pipeline을 구성해보겠습니다.

Figure 1.4 pipeline 구성
pipeline {
agent any
environment {
VAULT_ADDR = http://vault.vault.svc:8200
}
stages {
stage('Read Vault Secret') {
steps {
withVault([
vaultSecrets: [
[
path: 'secret/sampleapp/config',
engineVersion: 2,
secretValues: [
[envVar: 'USERNAME', vaultKey: 'username'],
[envVar: 'PASSWORD', vaultKey: 'password']
]
]
],
configuration: [
vaultUrl: "${VAULT_ADDR}",
vaultCredentialId: 'vault-approle-creds'
]
]) {
sh '''
echo "Username from Vault: $USERNAME"
echo "Password from Vault: $PASSWORD"
'''
script {
echo "Username (env): ${env.USERNAME}"
echo "Password (env): ${env.PASSWORD}"
}
}
}
}
}
}

style="text-align:center;"> Figure 1.5 pipeline 수행 결과
Jenkins + 동적(Dynamic) DB 시크릿 실습 환경 구성
동적 시크릿이란 요청 시 즉시 발급되고, TTL이 지나면 자동으로 사라지는 인증 정보입니다.
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: default
spec:
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:13
env:
- name: POSTGRES_PASSWORD
value: "rootpassword"
- name: POSTGRES_DB
value: "mydb"
ports:
- containerPort: 5432
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: default
spec:
type: NodePort
ports:
- port: 5432
targetPort: 5432
nodePort: 30002 # [External] Jenkins 접속용
selector:
app: postgres
EOF
# (로컬 터미널에서 수행)
export VAULT_ADDR=http://127.0.0.1:30000
export VAULT_TOKEN=root
# 1. Database Secret Engine 활성화
vault secrets enable database
vault secrets list
# 2. Vault -> Postgres 연결 설정
# (Vault와 DB는 같은 K8s 안에 있으므로 내부 DNS 사용)
vault write database/config/my-postgresql-database \
plugin_name=postgresql-database-plugin \
allowed_roles="jenkins-role" \
connection_url="postgresql://{{username}}:{{password}}@postgres.default.svc.cluster.local:5432/mydb?sslmode=disable" \
username="postgres" \
password="rootpassword"
# Success! Data written to: database/config/my-postgresql-database
# 3. "4시간짜리 임시 계정" 생성 규칙 정의 (DB Role)
# 주의: Vault 내부의 Role이 아니라, DB 엔진용 Role입니다.
vault write database/roles/jenkins-role \
db_name=my-postgresql-database \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl="4h" \
max_ttl="24h"
# Success! Data written to: database/roles/jenkins-role
# 기존 KV 권한 + 새로운 DB 권한(database/creds/jenkins-role) 병합
vault policy write sampleapp-policy - <<EOF
# 1. KV v2 데이터 읽기
path "secret/data/sampleapp/*" {
capabilities = ["read"]
}
# 2. KV v2 목록 조회 (플러그인 에러 방지용 필수!)
path "secret/metadata/sampleapp/*" {
capabilities = ["list", "read"]
}
# 3. DB Creds 발급
path "database/creds/jenkins-role" {
capabilities = ["read"]
}
EOF
# Success! Uploaded policy: sampleapp-policy
# 관리자 로그인 상태에서 실행. token polices에 default도 함께추가
vault write auth/approle/role/sampleapp-role \
token_policies="default,sampleapp-policy" \
secret_id_ttl="0" \
token_ttl="4h" \
token_max_ttl="4h"
# Success! Data written to: auth/approle/role/sampleapp-role
# Jenkinsfile
pipeline {
agent any
environment {
VAULT_ADDR = 'http://vault.vault.svc:8200'
DB_HOST = 'postgres.default.svc'
DB_PORT = '5432'
}
stages {
stage('Vault 통합 및 DB 접속 테스트') {
steps {
withVault([
configuration: [
vaultUrl: "${VAULT_ADDR}",
vaultCredentialId: 'vault-approle-creds',
skipSslVerification: true
],
vaultSecrets: [
// 1. KV Secret (정적 시크릿)
// KV v2 엔진을 사용하므로 engineVersion: 2를 명시합니다.
[
path: 'secret/sampleapp/config',
engineVersion: 2,
secretValues: [
[envVar: 'STATIC_USER', vaultKey: 'username']
]
],
// 2. Database Secret (동적 시크릿)
// DB 엔진은 기본 방식(v1)으로 통신해야 경로 에러가 없습니다.
[
path: 'database/creds/jenkins-role',
engineVersion: 1,
secretValues: [
[envVar: 'DB_USER', vaultKey: 'username'],
[envVar: 'DB_PASS', vaultKey: 'password']
]
]
]
]) {
script {
echo "=================================================="
echo " Vault 연동 테스트 시작 "
echo "=================================================="
sh '''
echo "[1] KV Secret (Static)"
echo " - 원본 값은 보안상 **** 로 표시됩니다."
echo " - 실제 값 확인: $(echo $STATIC_USER | sed "s/./& /g")"
'''
sh '''
echo "--------------------------------------------------"
echo "[2] Database Secret (Dynamic)"
echo " - Vault가 생성한 임시 계정 ID입니다."
echo " - 실제 값 확인: $(echo $DB_USER | sed "s/./& /g")"
echo "--------------------------------------------------"
'''
sh '''
echo "[3] DB Connection Simulation"
echo " - Connecting to: ${DB_HOST}:${DB_PORT}"
echo " - User: ${DB_USER}"
echo " - Password: (Hidden)"
echo " >> ✅ DB 접속 테스트 성공! (가상)"
'''
}
}
}
}
}
post {
success {
script {
echo "🎉 Pipeline 성공!"
echo " -> 확인된 DB 계정(${env.DB_USER})은 Vault의 TTL 설정에 따라 1시간 후 자동 삭제됩니다."
}
}
failure {
echo "💥 Pipeline 실패! Vault 로그나 네트워크 설정을 확인하세요."
}
}
}

style="text-align:center;"> Figure 1.5 pipeline 수행 결과
2. Vault Transit Engine
Vault가 제공하는 Encryption as a Service (EaaS) 기능을 Transit Engine을 중심으로 실습을 통해 알아보겠습니다.
✅ Transit Engine
Transit Engine이란 애플리케이션은 데이터를 갖고 있고, Vault는 암호화/복호화/서명 연산과 키 관리만 담당합니다.
Vault는 입력된 Plaintext를 절대 저장하지 않고 암호화 결과(ciphertext)만 반환하며, 키는 Vault 내부에서만 보호됩니다.
내부적으로 AES-256-GCM 기반 대칭키 암호화 사용합니다.

style="text-align:center;"> Figure 2.1 Transit의 기본 동작구조
실습
export NS=vault-demo
export IMAGE=hyungwookhub/vault-transit-demo:v1
export VAULT_ADDR=http://localhost:30000
export VAULT_TOKEN=root
# Vault 서버 현재 상태 확인 (Seal 여부, 클러스터 상태 등)
vault status
# Transit 엔진 활성화 (암복호화 전용 엔진)
vault secrets enable transit
# Success! Enabled the transit secrets engine at: transit/
# ds-poc 이름의 암호화 키 생성 (AES-256-GCM 모드 사용)
vault write -f transit/keys/ds-poc type=aes256-gcm96
# Transit 엔진 내에 생성된 키 목록 확인
vault list transit/keys
# Keys
# ----
# ds-poc
# 암/복호 테스트 (복붙)
PLAINTEXT="My Data"
# 평문을 Base64 → Vault Encrypt API 호출 → ciphertext 출력값 저장
CIPHERTEXT=$(vault write -field=ciphertext transit/encrypt/ds-poc \
plaintext=$(echo -n "$PLAINTEXT" | base64))
# 암호문을 Vault Decrypt API에 전달 → base64-decoding → 원문 출력
vault write -field=plaintext transit/decrypt/ds-poc \
ciphertext="$CIPHERTEXT" | base64 -d && echo
# My Data
kubectl create ns $NS
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
name: ${NS}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
namespace: ${NS}
spec:
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0.31
env:
- name: MYSQL_ROOT_PASSWORD
value: "rootpassword"
ports:
- containerPort: 3306
---
apiVersion: v1
kind: Service
metadata:
name: mysql
namespace: ${NS}
spec:
type: NodePort
selector:
app: mysql
ports:
- name: mysql
port: 3306
targetPort: 3306
nodePort: 30002
---
EOF
# DB 생성/계정 (root 사용; 필요 시 app/password 생성)
kubectl -n ${NS} exec -it deploy/mysql -- \
mysql -uroot -prootpassword -e "CREATE DATABASE IF NOT EXISTS VaultData;"
# Database 생성확인
kubectl -n ${NS} exec -it deploy/mysql -- \
mysql -uroot -prootpassword -e "SHOW DATABASES LIKE 'VaultData';"
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: vault-transit-demo
namespace: ${NS}
spec:
replicas: 1
selector:
matchLabels:
app: vault-transit-demo
template:
metadata:
labels:
app: vault-transit-demo
spec:
containers:
- name: app
image: ${IMAGE}
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
env:
- name: MYSQL_HOST
value: mysql.${NS}.svc.cluster.local
- name: MYSQL_PORT
value: "3306"
- name: MYSQL_DB_NAME
value: VaultData
- name: MYSQL_USERNAME
value: root
- name: MYSQL_USERPW
value: rootpassword
- name: VAULT_HOST
value: vault.vault.svc.cluster.local # 필요 시 노드 IP/NodePort로 교체
- name: VAULT_PORT
value: "8200" # NodePort로 붙을 땐 30000 등으로 교체
- name: VAULT_SCHEME
value: http
- name: VAULT_TOKEN
value: root
- name: VAULT_TRANSIT_KEY_NAME
value: ds-poc
- name: SERVER_PORT
value: "8080"
- name: AWS_REGION
value: "ap-northeast-2"
---
apiVersion: v1
kind: Service
metadata:
name: vault-transit-demo
namespace: ${NS}
spec:
type: NodePort
selector:
app: vault-transit-demo
ports:
- port: 8080
targetPort: 8080
nodePort: 30003
name: http
EOF
# 암호문 저장 여부 확인
kubectl -n ${NS} exec -it deploy/mysql -- \
mysql -uroot -prootpassword -e "USE VaultData; SHOW TABLES; SELECT id,data,date_created FROM vault_data;"
mysql: [Warning] Using a password on the command line interface can be insecure.
+---------------------+
| Tables_in_VaultData |
+---------------------+
| vault_data |
+---------------------+
+----+-----------------------------------------------------------+----------------------------+
| id | data | date_created |
+----+-----------------------------------------------------------+----------------------------+
| 1 | vault:v1:AQy+Z4N430yv9xXLFELfB9+JJqCvc/ZwuQv9zwi7ttL7iHh+ | 2025-11-29 22:47:52.685000 |
+----+-----------------------------------------------------------+----------------------------+
kubectl delete ns ${NS}


style="text-align:center;"> Figure 2.2 평문,암호화 데이터 확인
'DevOps > Study' 카테고리의 다른 글
| CI/CD Study 8주차 Vault (2/2) (0) | 2025.12.13 |
|---|---|
| CI/CD Study 8주차 Vault (1/2) (0) | 2025.12.13 |
| CI/CD Study 7주차 Vault (1/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 |