일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- envoy
- kiali
- leastconnection
- traffic mirroring
- aws gateway api controller
- generative ai chat
- traffic cloning
- docker
- prometheus
- istio in action
- Ingress
- vagrant
- WSL
- grafana
- service mesh
- Istio
- Observability
- argocd
- Kind
- Kubernetes
- K8S
- CNI
- CICD
- loadbalancer
- vpc cni
- aws eks
- mistral-7b
- service entry
- Jenkins
- aws lattice
- Today
- Total
WellSpring
AEWS 10주차 - K8s 시크릿 관리 Update 본문
목차
※ 본 게재 글은 gasida님의 'AEWS' 스터디 중 유형욱님 특별 강의내용과 실습예제 및 AWS 공식 사이트와 관련 Blog 를 참고하여 작성하였습니다.
0. 실습환경 구성
☞ Kind (k8s cluster) 생성 및 docker 기반 Jenkins 서비스 연동을 통해 Vault를 통한 Secret 관리 자동화 기능을 검토해 보고자 한다.
A. kind 로 k8s 배포 : Windows (WSL2) 사용자 → 필수
▶ 기본 정보 확인
# 클러스터 배포 전 확인
docker ps
mkdir cicd-labs
cd ~/cicd-labs
# WSL2 Ubuntu eth0 IP를 지정
ifconfig eth0
MyIP=<각자 자신의 WSL2 Ubuntu eth0 IP>
MyIP=172.19.21.65
# cicd-labs 디렉터리에서 아래 파일 작성
cat > kind-3node.yaml <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
apiServerAddress: "$MyIP"
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30000
hostPort: 30000
- containerPort: 30001
hostPort: 30001
- containerPort: 30002
hostPort: 30002
- containerPort: 30003
hostPort: 30003
- containerPort: 30004
hostPort: 30004
- containerPort: 30005
hostPort: 30006
- role: worker
- role: worker
EOF
kind create cluster --config kind-3node.yaml --name myk8s --image kindest/node:v1.32.2
# 확인
kind get nodes --name myk8s
kubens default
# kind 는 별도 도커 네트워크 생성 후 사용 : 기본값 172.18.0.0/16
docker network ls
docker inspect kind | jq
# k8s api 주소 확인 : 어떻게 로컬에서 접속이 되는 걸까?
kubectl cluster-info
# 노드 정보 확인 : CRI 는 containerd 사용
kubectl get node -o wide
# 파드 정보 확인 : CNI 는 kindnet 사용
kubectl get pod -A -o wide
# 네임스페이스 확인 >> 도커 컨테이너에서 배운 네임스페이스와 다릅니다!
kubectl get namespaces
# 컨트롤플레인/워커 노드(컨테이너) 확인 : 도커 컨테이너 이름은 myk8s-control-plane , myk8s-worker/worker-2 임을 확인
docker ps
docker images
# 디버그용 내용 출력에 ~/.kube/config 권한 인증 로드
kubectl get pod -v6
# kube config 파일 확인 : "server: https://172.19.21.65:35413" 부분에 접속 주소 잘 확인해두자!
cat ~/.kube/config
ls -l ~/.kube/config
▶ (참고) Cluster 자원 삭제
# 클러스터 삭제
kind delete cluster --name myk8s
docker ps
cat ~/.kube/config
[ 실행 결과 - 한 눈에 보기 ]



B. Jenkins 환경 구성
☞ 주의 : windows (WSL2 + Docker) 경우 아래 1번 과정에 kind(k8s) 설치를 먼저 진행하여 docker network(kind) 생성 후 아래 Jenkins 생성 할 것
Step1. Jenkins 컨테이너 기동
# 작업 디렉토리 생성 후 이동
mkdir cicd-labs
cd cicd-labs
# cicd-labs 작업 디렉토리 IDE(VSCODE 등)로 열어두기
# kind 설치를 먼저 진행하여 docker network(kind) 생성 후 아래 Jenkins,gogs 생성 할 것
# docker network 확인 : kind 를 사용
docker network ls
...
7e8925d46acb kind bridge local
...
#
cat <<EOT > docker-compose.yaml
services:
jenkins:
container_name: jenkins
image: jenkins/jenkins
restart: unless-stopped
networks:
- kind
ports:
- "8080:8080"
- "50000:50000"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- jenkins_home:/var/jenkins_home
volumes:
jenkins_home:
networks:
kind:
external: true
EOT
# 배포
docker compose up -d
docker compose ps
docker inspect kind
# 기본 정보 확인
for i in jenkins ; do echo ">> container : $i <<"; docker compose exec $i sh -c "whoami && pwd"; echo; done
# 도커를 이용하여 각 컨테이너로 접속
docker compose exec jenkins bash
exit
Step1. Jenkins 컨테이너 초기 설정
# Jenkins 초기 암호 확인
docker compose exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
09a21116f3ce4f27a0ede79372febfb1
# Jenkins 웹 접속 주소 확인 : 계정 / 암호 입력 >> admin / qwe123
웹 브라우저에서 http://127.0.0.1:8080 접속 # Windows
# (참고) 로그 확인 : 플러그인 설치 과정 확인
docker compose logs jenkins -f
Step3. Jenkins URL 설정 : 각자 http://<각자 자신의 WSL2 Ubuntu eth0 IP>:8080/ 를 입력
- Windows WSL2(Ubuntu) 에서 eth0 IP 확인 ifconfig eth0

[ 실행 결과 - 한 눈에 보기 ]
* Jenkins 초기화 > Unlock > 설치 진행 화면




C. Argo CD 설치 - helm_chart
Step1. Argo CD 설치
# 네임스페이스 생성 및 파라미터 파일 작성
cd cicd-labs
kubectl create ns argocd
cat <<EOF > argocd-values.yaml
dex:
enabled: false
server:
service:
type: NodePort
nodePortHttps: 30002
extraArgs:
- --insecure # HTTPS 대신 HTTP 사용
EOF
# 설치
helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd argo/argo-cd --version 7.8.13 -f argocd-values.yaml --namespace argocd
# 확인
kubectl get pod,svc,ep,secret,cm -n argocd
kubectl get crd | grep argo
kubectl get appproject -n argocd -o yaml
# configmap
kubectl get cm -n argocd argocd-cm -o yaml
kubectl get cm -n argocd argocd-rbac-cm -o yaml
...
data:
policy.csv: ""
policy.default: ""
policy.matchMode: glob
scopes: '[groups]'
# 최초 접속 암호 확인
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d ;echo
XxJMMJUv8MHZa-kk
# Argo CD 웹 접속 주소 확인 : 초기 암호 입력 (admin 계정)
open "http://127.0.0.1:30002" # macOS
# Windows OS경우 직접 웹 브라우저에서 http://127.0.0.1:30002 접속
Step2. Argo CD 웹 접속 확인
- User info → UPDATE PASSWORD 로 admin 계정 암호 변경 (qwe12345)
Step3. 기본 정보 확인 (Settings) : Clusters, Projects, Accounts

[ 실행 결과 - 한 눈에 보기 ]
1) ArgoCD 설치 후 확인

2) k9s 에서 설치된 argocd-repo-server 파드 및 컨테이너 정보 확인

3) 젠킨스에서 "HashiCopr Vault" 플러그 인 설치

( 참고 ) - K9S 설치 ( Ubuntu )
Step1. 파일 다운로드 및 압축 풀기 ( 명령어 권한 부여 )
$ wget https://github.com/derailed/k9s/releases/download/v0.26.7/k9s_Linux_x86_64.tar.gz // 다운로드
$ tar -zxvf ./k9s_Linux_x86_64.tar.gz // 압축 해제
$ mkdir -p ~/.local/bin
$ mv ./k9s ~/.local/bin && chmod +x ~/.local/bin/k9s // 바이너리 이동 및 실행권한 추가
$ rm ./k9s_Linux_x86_64.tar.gz LICENSE README.md // 불필요한 코드 제거
Step2. Path 설정 ( ~/.bashrc)
export PATH=$PATH:$HOME/.local/bin
Step3. 변경사항 온라인 적용 ( or 터미널 재시작 )
$ source ~/.bashrc // 변경의 적용
(참고) - docker compose 관리
▶ 실습 완료 후 해당 컨테이너 중지 상태로 둘 경우 → 재부팅 및 이후에 다시 실습을 위해 컨테이너 시작 시
# 실습 완료 후 해당 컨테이너 중지 상태로 둘 경우
docker compose stop
docker compose ps
docker compose ps -a
# mac 재부팅 및 이후에 다시 실습을 위해 컨테이너 시작 시
docker compose start
docker compose ps
▶ 특정 컨테이너만 삭제 후 다시 초기화 상태로 기동 시
# gogs : 볼륨까지 삭제
docker compose down gogs -v
docker compose up gogs -d
# jenkins : 볼륨까지 삭제
docker compose down jenkins -v
docker compose up jenkins -d
▶ 모든 실습 후 삭제 시
docker compose down --volumes --remove-orphans
1. Vault 개요
☞ HashiCorp Vault는 신원 기반(identity-based)의 시크릿 및 암호화 관리 시스템입니다. 이 시스템은 인증(authentication) 및 인가(authorization) 방법을 통해 암호화 서비스를 제공하여 비밀에 대한 안전하고 감사 가능하며 제한된 접근을 보장합니다.
시크릿(Secret)이란 접근을 철저히 통제하고자 하는 모든 것을 의미하며, 예를 들어 토큰, API 키, 비밀번호, 암호화 키 또는 인증서 등이 이에 해당합니다. Vault는 모든 시크릿에 대해 통합된 인터페이스를 제공하면서, 엄격한 접근 제어와 상세한 감사 로그 기록 기능을 제공합니다.
외부 서비스용 API 키, 서비스 지향 아키텍처 간 통신을 위한 자격 증명 등은 플랫폼에 따라 누가 어떤 비밀에 접근했는지를 파악하기 어려울 수 있습니다. 여기에 키 롤링(교체), 안전한 저장, 상세한 감사 로그까지 추가하려면 별도의 커스텀 솔루션 없이는 거의 불가능합니다. Vault는 바로 이 지점에서 해결책을 제공합니다.
Vault는 클라이언트(사용자, 기계, 애플리케이션 등)를 검증하고 인가한 후에만 비밀이나 저장된 민감한 데이터에 접근할 수 있도록 합니다.

[ Vault 요약 - 한 눈에 보기 ] - 출처 : 가시다님 강의자료 중
특징
|
HashiCorp Vault
|
주요 기능
|
다양한 환경과 플랫폼에서 secret을 저장, 액세스 및 배포하기 위한 포괄적인 secret 관리 솔루션
|
암호화
|
높은 유연성과 보안을 위해 여러 암호화 백엔드(예: AWS KMS, Azure Key Vault, GCP KMS)를 지원하는 고급 암호화 메커니즘을 제공합니다.
|
접근 제어
|
역할, 경로 및 작업을 기반으로 세분화된 권한을 허용하는 세부적인 액세스 제어 정책(ACL)을 구현합니다.
|
동적 secret
|
데이터베이스, 클라우드 자격 증명, SSH 키와 같은 리소스에 대한 일시적이고 주문형 액세스를 제공하여 동적 secret 생성을 지원합니다.
|
감사 로깅
|
모든 작업과 액세스 요청을 추적하여 책임과 추적성을 보장하기 위한 포괄적인 감사 로그를 제공합니다.
|
완성
|
Kubernetes, Terraform, Jenkins 및 클라우드 공급자와 같은 광범위한 도구 및 서비스와의 광범위한 통합을 통해 광범위한 애플리케이션 범위가 가능합니다.
|
설치 복잡성
|
서버 배포 및 구성을 포함한 더 많은 설정 및 인프라가 필요하며 학습 곡선이 더 가파를 수 있습니다.
|
secret rotation
|
자동화된 secret rotation을 지원하여 secret이 정기적으로 업데이트되고 노출 위험이 최소화되도록 합니다.
|
API 및 CLI
|
다양한 작업을 위한 vault와의 광범위한 프로그래밍 액세스 및 상호 작용을 허용하는 풍부한 API 및 CLI를 제공합니다.
|
사용 사례
|
동적 secret 생성, 서비스로서의 암호화, 다양한 애플리케이션 및 환경에서의 안전한 데이터 저장을 포함한 광범위한 사용 사례에 적합합니다.
|
전개
|
최적의 성능과 보안을 위해 전용 인프라와 리소스가 필요한 독립 실행형 서비스로 배포됨
|
확장성
|
분산 환경에서 대량의 secret과 여러 클라이언트를 처리할 수 있는 높은 확장성을 위해 설계되었습니다.
|
버전 관리
|
secret 버전 관리를 지원하여 사용자가 시간 경과에 따라 다양한 버전의 secret을 추적하고 관리할 수 있도록 합니다.
|
오픈소스
|
추가 기능과 지원을 제공하는 엔터프라이즈 버전과 함께 오픈 소스 도구로 사용 가능
|
☞ 대표적인 Secret 종류
- 비밀번호
- Cloud Credentials : AWS, GCP, Azure, NCP
- Database Credentials : MySQL,
- SSH Key
- Token, API Key : GitHub, Telegram, Slack, OpenAI, Claude
- 인증서(PKI, TLS 등)
▶ Vault의 동작방식? - Docs
Vault는 주로 토큰(Token)을 기반으로 작동하며, 이 토큰은 클라이언트의 정책(Policy)과 연결되어 있습니다. 각 정책은 경로(path) 기반으로 설정되며, 정책 규칙은 클라이언트가 해당 경로에서 수행할 수 있는 작업과 접근 가능성을 제한합니다.
Vault에서는 토큰을 수동으로 생성해 클라이언트에 할당할 수도 있고, 클라이언트가 로그인하여 토큰을 직접 획득할 수도 있습니다.
[ Vault 의 핵심 워크플로우 - 4 단계 ]

- 인증 (Authenticate): Vault에서 인증은 클라이언트가 Vault에 자신이 누구인지 증명할 수 있는 정보를 제공하는 과정입니다. 클라이언트가 인증 메서드를 통해 인증되면, 토큰이 생성되고 정책과 연결됩니다.
- 검증 (Validation): Vault는 Github, LDAP, AppRole 등과 같은 신뢰할 수 있는 외부 소스를 통해 클라이언트를 검증합니다.
- 인가 (Authorize): 클라이언트는 Vault의 보안 정책과 비교됩니다. 이 정책은 Vault 토큰을 사용하여 클라이언트가 접근할 수 있는 API 엔드포인트를 정의하는 규칙의 집합입니다. 정책은 Vault 내 특정 경로나 작업에 대한 접근을 허용하거나 거부하는 선언적 방식으로 권한을 제어합니다.
- 접근 (Access): Vault는 클라이언트의 신원에 연관된 정책을 기반으로 토큰을 발급하여 비밀, 키, 암호화 기능 등에 대한 접근을 허용합니다. 클라이언트는 이후 작업에서 해당 Vault 토큰을 사용할 수 있습니다.
▶ Vault 의 주요기능
1. 안전한 비밀 저장 (Secure Secret Storage): → Static 시크릿
Vault는 임의의 key/value 형식의 시크릿을 저장할 수 있으며, 이 시크릿은 영구 저장소에 기록되기 전에 암호화됩니다. 따라서 저장소에 직접 접근하더라도 비밀을 열람할 수 없습니다. Vault는 Disk, Consul 등 다양한 저장소를 지원합니다.
2. 동적 비밀 (Dynamic Secrets):
Vault는 AWS나 SQL 데이터베이스와 같은 일부 시스템에 대해 요청 시 비밀을 동적으로 생성할 수 있습니다. 예를 들어, 애플리케이션이 S3 버킷에 접근해야 할 때 Vault에 자격 증명을 요청하면, Vault는 해당 권한을 가진 AWS 키쌍을 생성해줍니다. 이 동적 시크릿은 일정 시간이 지나면 자동으로 폐기됩니다.
3. 데이터 암호화 (Data Encryption):
Vault는 데이터를 저장하지 않고 암호화 및 복호화를 수행할 수 있습니다. 이를 통해 보안 팀은 암호화 매개변수를 정의하고, 개발자는 암호화된 데이터를 SQL 데이터베이스 등 외부 저장소에 안전하게 저장할 수 있습니다.
4. 임대 및 갱신 (Leasing and Renewal):
Vault에 저장된 모든 시크릿은 임대 기간(lease)이 설정되어 있으며, 이 기간이 끝나면 해당 비밀은 자동으로 폐기됩니다. 클라이언트는 내장된 갱신 API를 통해 임대를 연장할 수 있습니다.
5. 폐기 (Revocation):
Vault는 비밀 폐기를 기본적으로 지원합니다. 단일 비밀뿐만 아니라 특정 사용자에 의해 읽힌 모든 비밀, 또는 특정 유형의 모든 비밀 등 비밀의 계층 구조 전체를 폐기할 수 있습니다. 이 기능은 키 롤링이나 침입 발생 시 시스템을 신속하게 차단하는 데 유용합니다.
2. Vault 기본 구조의 이해
2-1. hasicorp Vault , Security LifeCycle Management, Secret change automation
☞ 개별 ID 인증/인가를 통해 필요한 자격 증명을 동적을 발급

☞ Work-Flow 이해

Step1. 사용자(App)가 권한을 얻기 위한 요청을 진행
Step2. 권한 관리주체(솔루션-Vault)가 요청 대상의 적합성 검토 후, Token (or Access key)를 사용자에게 발급함 (Authentication)
Step3. 권한 관리주체(솔루션-Vault)가 대상 서비스(CSP - 리소스)에 대한 권한 정책 부여 (Authorization)
Step4. 사용자(App)가 부여받은 Token (or Access key) 로 대상 서비스에 접근하여 작업을 진행함
☞ Vault 의 다양한 인증방식
- Vault는 다양한 IdPs(Identity Providers)와 통합하여 사용자의 인증 요청을 검증
- 사전 정의된 정책(Policy)과 연결된 Token 제공
a. 자격 증명에 대한 검증(Validation) 후 Vault에 액세스 할 수 있는 Vault Token 반환
b. Vault Token은 인증된 사용자(클라이언트)에게 할당된 액세스 대상 및 기능(Engine)에 Vault 정책과 연결
- 사용자 인증/인가를 위해 외부 IDP를 연동하고, 엔티티를 기준으로 인증 방법/정책/엔진 접근을 관리
☞ 사람 또는 애플리케이션(시스템)에 최적화된 인증방식

- Vault의 인증 및 권한 부여
- Vault는 사용자 인증이 아닌 권한 부여를 제공
- 실제 인증 프로세스는 관리하지 않음
- 다양한 인증 방법 권장
- 사람: LDAP, OIDC, SAML 등의 외부 IDP 활용
- 애플리케이션/시스템:
- AWS IAM, EC2 등
- Token/AppRole/Kuberntes/TLS 등
- 보안 및 신뢰성 확보
- 다양한 유형에 적합한 인증 방법을 사용
☞ Vault 내부 아키텍처 - Docs
- High Level ( 외부 인증 요청 API call > Policy 검토 > Path Routing > 필요한 Back system 및 인증 방법에 매핑 )

3. Kubernetes에 Vault 설치 - Docs1 Docs2
▶ 학습목표
- 실습용 K8s 환경(KinD)에 Vault를 Helm으로 설치
- 설치된 Vault 서버에 접속하고 UI/CLI로 기본 기능을 확인
- Vault의 동작 방식과 구조의 이해
▶ 실습 환경 구성
Step1. Helm을 사용한 Vault 배포 - Vault helm
- 네임스페이스 생성 및 Helm Repo 추가
# Create a Kubernetes namespace.
kubectl create namespace vault
# View all resources in a namespace.
kubectl get all --namespace vault
# Setup Helm repo
helm repo add hashicorp https://helm.releases.hashicorp.com
# Check that you have access to the chart.
helm search repo hashicorp/vault
# NAME CHART VERSION APP VERSION DESCRIPTION
# hashicorp/vault 0.30.0 1.19.0 Official HashiCorp Vault Chart
# hashicorp/vault-secrets-gateway 0.0.2 0.1.0 A Helm chart for Kubernetes
# hashicorp/vault-secrets-operator 0.10.0 0.10.0 Official Vault Secrets Operator Chart
cat <<EOF > override-values.yaml
global:
enabled: true
tlsDisable: true # Disable TLS for demo purposes
server:
image:
repository: "hashicorp/vault"
tag: "1.19.0"
standalone:
enabled: true
replicas: 1
config: |
ui = true
listener "tcp" {
address = "[::]:8200"
cluster_address = "[::]:8201"
tls_disable = 1
}
storage "file" {
path = "/vault/data"
}
service:
enabled: true
type: NodePort
port: 8200
targetPort: 8200
nodePort: 30000 # 🔥 Kind에서 열어둔 포트 중 하나 사용
injector:
enabled: true
EOF
# Helm Install 실행
helm upgrade vault hashicorp/vault -n vault -f override-values.yaml --install
# 네임스페이스 변경 : vault
kubens vault
Context "kind-myk8s" modified.
Active namespace is "vault".
# 배포확인
k get pods,svc,pvc
NAME READY STATUS RESTARTS AGE
pod/vault-0 0/1 ContainerCreating 0 11s
pod/vault-agent-injector-56459c7545-9n94t 0/1 ContainerCreating 0 11s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/vault NodePort 10.96.36.121 <none> 8200:30000/TCP,8201:31091/TCP 11s
service/vault-agent-injector-svc ClusterIP 10.96.240.81 <none> 443/TCP 11s
service/vault-internal ClusterIP None <none> 8200/TCP,8201/TCP 11s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
persistentvolumeclaim/data-vault-0 Bound pvc-6f5739c5-14e9-4a62-bed2-b98fd327bcb1 10Gi RWO standard <unset> 11s
[ 실행 결과 - 한 눈에 보기 ]
1) k8s 에 'vault' 네임스페이스 생성
2) helm chart 통한 vault repo 등록

3) vault 설치 내용 확인

4) 편의 실습을 위해 namespace 를 'vault' 변경 후, 리소스 확인

Step2. Vault 초기화 및 잠금해제
- 상태 확인
# Vault Status 명령으로 Sealed 상태확인
kubectl exec -ti vault-0 -- vault status
- init-unseal.sh 을 사용하여 Vault Unseal 자동화 - Shamir Secret Sharing 방식이란?
cat <<EOF > init-unseal.sh
#!/bin/bash
# Vault Pod 이름
VAULT_POD="vault-0"
# Vault 명령 실행
VAULT_CMD="kubectl exec -ti \$VAULT_POD -- vault"
# 출력 저장 파일
VAULT_KEYS_FILE="./vault-keys.txt"
UNSEAL_KEY_FILE="./vault-unseal-key.txt"
ROOT_TOKEN_FILE="./vault-root-token.txt"
# Vault 초기화 (Unseal Key 1개만 생성되도록 설정)
\$VAULT_CMD operator init -key-shares=1 -key-threshold=1 | sed \$'s/\\x1b\\[[0-9;]*m//g' | tr -d '\r' > "\$VAULT_KEYS_FILE"
# Unseal Key / Root Token 추출
grep 'Unseal Key 1:' "\$VAULT_KEYS_FILE" | awk -F': ' '{print \$2}' > "\$UNSEAL_KEY_FILE"
grep 'Initial Root Token:' "\$VAULT_KEYS_FILE" | awk -F': ' '{print \$2}' > "\$ROOT_TOKEN_FILE"
# Unseal 수행
UNSEAL_KEY=\$(cat "\$UNSEAL_KEY_FILE")
\$VAULT_CMD operator unseal "\$UNSEAL_KEY"
# 결과 출력
echo "[🔓] Vault Unsealed!"
echo "[🔐] Root Token: \$(cat \$ROOT_TOKEN_FILE)"
EOF
# 실행 권한 부여
chmod +x init-unseal.sh
# 실행
./init-unseal.sh
- vault status 명령을 사용하여 Unseal 되었는지 확인한다 → Sealed=false
kubectl exec -ti vault-0 -- vault status
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 1
Threshold 1
Version 1.19.0
Build Date 2025-03-04T12:36:40Z
Storage Type file
Cluster Name vault-cluster-26e23c75
Cluster ID 326975b8-4907-2d62-9da1-f3165d7cc0c6
HA Enabled false
(참고) UI에 접속해서 Unseal Key 입력도 가능

☞ 이제 Unseal이 마무리 되었으므로 Root Token 값을 사용하여 UI에 접속할 수 있습니다.
- Root Token 입력 후 Vault UI 화면 - vault-root-token.txt 파일에서 획득

[ 실행 결과 - 한 눈에 보기 ]





Step3. CLI 설정(MacOS) - Docs / 수동설치
brew tap hashicorp/tap
brew install hashicorp/tap/vault
vault --version # 설치 확인
# NodePort로 공개한 30000 Port로 설정
export VAULT_ADDR='http://localhost:30000'
# vault 상태확인
vault status
# Root Token으로 로그인
vault login
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 hvs.egeVgnhMKgnFqof6YLfiPtLa
token_accessor wWpBsO3TqQEybFF0le2YmNGX
token_duration ∞
token_renewable false
token_policies ["root"]
identity_policies []
policies ["root"]
Step4. CLI 설정(Windows) - Docs
1) GPG key를 추가합니다
$ curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
2) 리포지토리를 추가합니다
$ sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
3) apt(Advanced Packaging Tool)을 업데이트합니다
$ sudo apt-get update
4) vault를 설치합니다
$ sudo apt-get install vault

5) vault 개발서버를 시작합니다. (필요 시)
$ vault server -dev
[ 결과 ]
==> Vault server configuration:
Api Address: http://127.0.0.1:8200
Cgo: disabled
Cluster Address: https://127.0.0.1:8201
Go Version: go1.14.7
Listener 1: tcp (addr: "127.0.0.1:8200", cluster address: "127.0.0.1:8201", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled")
Log Level: info
Mlock: supported: true, enabled: false
Recovery Mode: false
Storage: inmem
Version: Vault v1.5.3
Version Sha: 9fcd81405feb320390b9d71e15a691c3bc1daeef
WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory
and starts unsealed with a single unseal key. The root token is already
authenticated to the CLI, so you can immediately begin using Vault.
You may need to set the following environment variable:
$ export VAULT_ADDR='http://127.0.0.1:8200'
The unseal key and root token are displayed below in case you want to
seal/unseal the Vault or re-authenticate.
Unseal Key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Root Token: s.xxxxxxxxxxxxxxxxxxxxxx
Development mode should NOT be used in production installations!
6) VAULT_ADDR 환경변수를 등록합니다.
$ vi ~/.bashrc
추가할 내용
export VAULT_ADDR='http://127.0.0.1:8200' ## 자신의 Local PC IP:Port로 대체할 것 !!
## export VAULT_ADDR='http://172.31.79.21:30000'
환경변수를 적용하고 확인해봅니다.
$ source ~/.bashrc $ echo $VAULT_ADDR // [결과] http://127.0.0.1:8200
[ 실행 결과 - 한 눈에 보기 ]



▶ 실습
☞ KV 시크릿 엔진 활성화 및 샘플 구성 → Static Secret
→ Vault KV version 2 엔진을 활성화하고 샘플 데이터를 저장합니다.
→ Version1 : KV 버전관리 불가 / Version2 : KV 버전관리 가능
Step 1. KV 엔진 활성화 및 샘플 데이터 추가
# KV v2 형태로 엔진 활성화
vault secrets enable -path=secret kv-v2
# 샘플 시크릿 저장
vault kv put secret/sampleapp/config \
username="demo" \
password="p@ssw0rd"
# 입력된 데이터 확인
vault kv get secret/sampleapp/config
======== Secret Path ========
secret/data/sampleapp/config
======= Metadata =======
Key Value
--- -----
created_time 2025-03-30T05:18:47.797852422Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 1
====== Data ======
Key Value
--- -----
password p@ssw0rd
username demo
Step2. Vault UI : [Secrets Engine] 탭에 접속 후 [sampleapp - config] 접속하여 실제 저장된 Key / Value 확인
- username : demo
- password : p@ssw0rd

Step3. (참고) 경로 확인하는 명령 가이드

[ 실행 결과 - 한 눈에 보기 ]


▶ 추가 학습자료
4. Vault Sidecar 연동 (Vault Agent) - 링크1 링크2
▶ 학습목표
- Vault Agent Sidecar 패턴을 이해하고, 앱(Pod)이 시크릿을 볼륨으로 마운트하여 사용하도록 구성한다.
- Vault Agent Injector 구성을 위해 Helm Chart(https://github.com/hashicorp/vault-helm) 에서 활성화 해야하며, https://github.com/hashicorp/vault-k8s을 활용한다.
▶ 공식문서 요약(ChatGPT)
Vault Agent Injector는 Kubernetes Pod 내부에 Vault Agent를 자동으로 주입해주는 기능입니다. 이를 통해 어플리케이션이 Vault로부터 자동으로 비밀 정보를 받아올 수 있게 됩니다. 하지만 이를 사용하기 전에 몇 가지 사전 준비가 필요합니다.
1. Vault가 설치되어 있고, Kubernetes와 통합되어 있어야 합니다
- Vault가 실행 중이어야 하고, Kubernetes 클러스터에 접근 가능해야 합니다.
- Vault는 Kubernetes 인증 방식을 설정하고 있어야 하며, 이를 통해 서비스 어카운트를 기반으로 토큰을 발급받을 수 있습니다.
2. Vault Agent Injector가 클러스터에 배포되어 있어야 합니다
- Injector는 Kubernetes에 배포되는 별도의 구성 요소입니다.
- 일반적으로 Helm Chart를 통해 배포하며, 이 컴포넌트가 있어야 Pod에 Vault Agent가 자동으로 주입됩니다.
3. Kubernetes 인증 방식이 활성화되어야 합니다
- Vault에서 Kubernetes Auth Method를 활성화하고 구성해야 합니다.
- 이 설정을 통해 특정 서비스 어카운트에 Vault 접근 권한을 부여할 수 있습니다.
4. 정책과 역할이 정의되어 있어야 합니다
- Vault에 접근할 수 있도록 적절한 Policy와 Role이 설정되어야 합니다.
- 예를 들어, 특정 서비스 어카운트가 특정 경로의 시크릿에만 접근할 수 있도록 제한할 수 있습니다.
5. 애플리케이션 Pod에 주입할 주석(annotation)을 추가해야 합니다
- Vault Agent Injector는 특정 주석이 있는 Pod에 대해서만 Vault Agent를 주입합니다.
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "example-role"
▶ Vault Kubernetes Sidecar 아키텍처 및 워크플로우


▶ 실습
Step1. Vault AppRole 방식 인증 구성 - AppRole 인증이란?
- 인증 구성 및 정책 적용
# 1. AppRole 인증 방식 활성화
vault auth enable approle || echo "AppRole already enabled"
vault auth list
# 2. 정책 생성
vault policy write sampleapp-policy - <<EOF
path "secret/data/sampleapp/*" {
capabilities = ["read"]
}
EOF
# 3. AppRole Role 생성
vault write auth/approle/role/sampleapp-role \
token_policies="sampleapp-policy" \
secret_id_ttl="1h" \
token_ttl="1h" \
token_max_ttl="4h"
# 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)
echo "ROLE_ID: $ROLE_ID"
echo "SECRET_ID: $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으로 저장
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 -
[ 실행 결과 - 한 눈에 보기 ]

Step2. Vault Agent Sidecar 연동
☞ Vault Agent는 vault-agent-config.hcl 설정을 통해 연결할 Vault의 정보와, Template 구성, 렌더링 주기, 참조할 Vault KV 위치정보 등을 정의한다.
1. Vault Agent 설정 파일 작성 및 생성 (vault-agent-config.hcl) - HCL ( HashiCorp Configuration Language )
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
2. 샘플 애플리케이션 + Sidecar 배포(수동방식)
- Nginx + Vault Agent 생성
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: {}
EOF
3. SVC 생성
kubectl apply -f - <<EOF
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
4. 생성된 컨테이너 확인
# 파드 내에 사이드카 컨테이너 추가되어 2/2 확인
kubectl get pod -l app=nginx-vault-demo
NAME READY STATUS RESTARTS AGE
nginx-vault-demo-7776649597-tcnxd 2/2 Running 0 5m32s
kubectl describe pod -l app=nginx-vault-demo
...
Containers:
nginx:
Container ID: containerd://c160c2268ce6d7602b718336b9036ae89646b5fdb7ebf310bed3dcf497b8675e
Image: nginx:latest
...
vault-agent-sidecar:
Container ID: containerd://6d7e37e8925e681d163b06ae7d18f5d3a236164a60ebf422abd4229a2e337e4e
Image: hashicorp/vault:latest
Image ID: docker.io/hashicorp/vault@sha256:ee674e47dcf85849aadf255b5341f76c0e1a474bc5fa9be9cdfff2a2edf9a628
Port: <none>
Host Port: <none>
Args:
agent
-config=/etc/vault/agent-config.hcl
...
# 볼륨 마운트 확인
kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- ls -l /etc/vault-agent-token
total 4
-rw-r----- 1 vault vault 95 Apr 10 02:09 token
kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- cat /etc/vault-agent-token/token ; echo
hvs.CAESIEKGV7vnUk7UxQ4c-QQq4pSEDGdWuFNKjOESJ7WfyKHuGh4KHGh2cy5paXJCTHFMWlBKU0pFMXlQelpqaFJDYWU
kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- ls -al /etc/vault
...
drwxr-xr-x 2 root root 4096 Apr 10 02:09 ..2025_04_10_02_09_17.2775716426
lrwxrwxrwx 1 root root 32 Apr 10 02:09 ..data -> ..2025_04_10_02_09_17.2775716426
lrwxrwxrwx 1 root root 23 Apr 10 02:09 agent-config.hcl -> ..data/agent-config.hcl
drwxrwxrwt 3 root root 120 Apr 10 02:09 approle
kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- cat /etc/vault/agent-config.hcl
...
kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- ls -al /etc/vault/approle
...
lrwxrwxrwx 1 root root 14 Apr 10 02:09 role_id -> ..data/role_id
lrwxrwxrwx 1 root root 16 Apr 10 02:09 secret_id -> ..data/secret_id
kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- cat /etc/vault/approle/role_id ; echo
5bcefce3-e2ff-9ae3-39c1-19d380a1635e
kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- cat /etc/vault/approle/secret_id ; echo
d50a1232-926a-81da-b053-132ee65a0ef9
kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- ls -l /etc/secrets
total 4
-rw-r--r-- 1 vault vault 94 Apr 10 02:09 index.html
kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- cat /etc/secrets/index.html
<html>
<body>
<p>username: demo</p>
<p>password: p@ssw0rd</p>
</body>
</html>
kubectl exec -it deploy/nginx-vault-demo -c nginx -- cat /usr/share/nginx/html/index.html
<html>
<body>
<p>username: demo</p>
<p>password: p@ssw0rd</p>
</body>
</html>
# 로그 확인
kubectl stern -l app=nginx-vault-demo -c vault-agent-sidecar
# mutating admission
kubectl get mutatingwebhookconfigurations.admissionregistration.k8s.io
NAME WEBHOOKS AGE
vault-agent-injector-cfg 1 3h10m
5. 실제 배포된 화면 확인
6. KV 값 변경 후 확인
(참고) Annotation을 활용한 Vault Sidecar Injection - Docs
- Vault의 Kubernetes 인증 활성화 및 구성
# Kubernetes Auth Method 활성화
vault auth enable kubernetes
# Kubernetes Auth Config 설정
vault write auth/kubernetes/config \
token_reviewer_jwt="$(kubectl get secret $(kubectl get serviceaccount vault -n vault -o jsonpath='{.secrets[0].name}') -n vault -o jsonpath="{.data.token}" | base64 --decode)" \
kubernetes_host="$(kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[0].cluster.server}')" \
kubernetes_ca_cert="$(kubectl get secret $(kubectl get serviceaccount vault -n vault -o jsonpath='{.secrets[0].name}') -n vault -o jsonpath='{.data.ca\.crt}' | base64 --decode)"
# 필요한 Policy 작성 (앞선 과정에서 만들었으므로 생략가능)
vault policy write sampleapp-policy - <<EOF
path "secret/data/sampleapp/*" {
capabilities = ["read"]
}
EOF
# 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"
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
▶ 추가 학습자료
1. Retrieve secrets for Kubernetes workloads with Vault Agent - 실습

Vault Proxy는 애플리케이션이 Vault와 통합하는 초기 진입 장벽을 낮추고, 더 확장 가능하고 단순한 방식을 제공하기 위해 만들어졌습니다. Vault Proxy는 Vault의 API 프록시 역할을 하며, 클라이언트가 자동 인증된 토큰을 사용하도록 허용하거나 강제할 수 있습니다.
Vault Proxy는 클라이언트 데몬으로 다음과 같은 기능을 제공합니다:
- Auto-Auth: Vault에 자동으로 인증하고, 로컬에서 가져온 동적 비밀의 토큰 갱신 프로세스를 관리합니다.
- API Proxy: Vault의 API에 대한 프록시 역할을 하며, Auto-Auth 토큰을 선택적으로 또는 강제로 사용하게 할 수 있습니다.
- 캐싱(Caching): 새로 생성된 토큰이나 이 토큰으로부터 생성된 리스 비밀(leased secrets)에 대한 응답을 클라이언트 측에서 캐싱할 수 있도록 합니다. 또한, 에이전트는 캐시된 토큰과 리스의 갱신도 관리합니다.
3. Vault Agent and Vault Proxy quick start



5. Jenkins + Vault (AppRole) - CI
▶ 학습 목표
- Vault KV Store에 저장한 username, password을 Jenkins을 활용해서 획득하는 방안
- CI 파이프라인에서 정적(Static) 시크릿을 외부에 저장하고 관리할 경우 사용할 수 있습니다.
▶ 🔐 CI/CD 보안 고려사항

☞ 최근 CI/CD의 공격사례(CVE-2025-30066) : GitHub Action tj-actions/changed-files 공격
https://news.hada.io/topic?id=19770
`tj-actions/changed-files` GitHub Action 해킹됨 - 230 | GeekNews
각 브랜치의 변경사항을 추적하는데 사용하는 인기 GitHub Action으로, 해킹된 커밋을 통해 CI/CD 시크릿 유출 시도가 발생23,000개의 Repo가 영향받았고, GitHub는 이 액션을 삭제했으며 더 이상 사용 불
news.hada.io
https://www.wiz.io/blog/github-action-tj-actions-changed-files-supply-chain-attack-cve-2025-30066
GitHub Action tj-actions/changed-files supply chain attack | Wiz Blog
A supply chain attack on GitHub Action tj-actions/changed-files caused many repositories to leak their secrets.
www.wiz.io
Detecting and Mitigating the “tj-actions/changed-files” Supply Chain Attack (CVE-2025-30066)
On March 14, 2025, StepSecurity uncovered a compromise in the popular GitHub Action tj-actions/changed-files. Tens of thousands of repositories use this
sysdig.com
▶ Vault - Jenkins Plugin with AppRole 인증방식 워크플로우 - Docs

☞ 공식문서 요약(ChatGPT)
젠킨스는 Vault에 시크릿으로 분류된 데이터를 필요로 하는 작업(job)을 실행해야 합니다. 젠킨스는 마스터 노드와 워커 노드를 가지고 있으며, 워커 노드는 짧은 시간 동안 실행되는 컨테이너 러너에서 작업을 실행합니다.
- 프로세스는 다음과 같습니다:
- 젠킨스 워커가 Vault에 인증
- Vault는 토큰을 반환
- 워커는 이 토큰을 사용해 작업에 해당하는 역할의 Wrapped SecretID를 요청
- Vault는 Wrapped SecretID를 반환
- 워커는 작업 러너를 생성하고, Wrapped SecretID를 변수로 전달
- 러너 컨테이너는 Wrapped SecretID의 unwrap을 요청
- Vault는 SecretID를 반환
- 러너는 RoleID와 SecretID를 사용해 Vault에 인증
- Vault는 필요한 시크릿 정보를 읽을 수 있는 정책이 포함된 토큰을 반환
- 러너는 이 토큰을 사용해 Vault에서 시크릿을 가져옴
▶ 실습
Step1. Jenkins에서 Vault Plugin 설치
- Jenkins UI 접속
- 상단 메뉴에서 Manage Jenkins → Plugins
- Available 탭에서 Vault 검색
- HashiCorp Vault Plugin 설치 후 Jenkins 재시작

Step2. Vault AppRole 정보 확인 ⇒ Secret ID는 1시간 만료이므로, 그냥 다시 생성해서, 해당 값을 젠킨스에 설정하고 빌드 실습 하자.
- Vault에서 발급된 ROLE_ID, SECRET_ID는 이전에 생성한 role_id.txt secret_id.txt 값을 참고하여 사용할 수 있습니다.
# 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)
echo "ROLE_ID: $ROLE_ID"
echo "SECRET_ID: $SECRET_ID"
(참고) Role ID, Secret ID 획득방안
# Role ID
vault read auth/approle/role/<role-name>/role-id
# Secret ID
vault write -f auth/approle/role/<role-name>/secret-id
# 예시
vault read auth/approle/role/sampleapp-role/role-id
Key Value
--- -----
role_id 678c0c6e-57df-bb23-1427-f6318843a514
Step3. Jenkins에서 Vault 설정 및 Credentials 추가
1. Jenkins UI(admi/qwe123) → Manage Jenkins → Configure System

2. 스크롤 하단의 Vault Plugin Configuration 섹션으로 이동

3. Vault Credential 다음 값 입력:
- 종류: Vault AppRole Credential
- Role ID & Secret ID 입력 → 생성해놓은 변수 또는 파일참고
- ID는 기억하기 쉬운 이름으로 지정 (vault-approle-creds 등)

Step4. Jenkins Pipeline Job 생성
- Jenkins UI → New Item → Pipeline 선택
- jenkins-vault-kv 입력 후 생성
- Jenkinsfile 작성
▶ Jenkinsfile 예시
pipeline {
agent any
environment {
VAULT_ADDR = 'http://192.168.0.2:30000' // 실제 Vault 주소로 변경!!!
}
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}"
}
}
}
}
}
}
- Jenkins 실행결과 → 보안상 취약하므로 마스킹처리됨. 우회방안은 있음(파일로 저장)

▶ 유의사항
- KV Version1은 경로에 data을 넣고 Version2는 경로에 data을 넣지 않습니다! - 참고링크
- Version 1 : secret/data/sampleapp/config
- Version 2 : secret/sampleapp/config
- sh 블록 vs script 블록

[ 실행 결과 - 한 눈에 보기 ]
* Token TTL 만료로 Secret ID 재발급 필요 시, 기존 과정 재수행 필요


6. ArgoCD + Vault Plugin (Kubernetes Auth/AppRole) - CD
▶ 사전준비 : Argo CD 설치 및 기본 설정 - helm_chart → 앞에서 진행했다면 생략!
☞ Vault + ArgoCD Plugin 패턴 - 링크1 링크2
▶ ArgoCD Vault Plugin 소개
- Argo CD에는 다양한 시크릿 관리 도구(HashiCorp Vault, IBM Cloud Secrets Manager, AWS Secrets Manager 등)플러그인을 통해 Kubernetes 리소스에 주입할 수 있도록 지원합니다.
- 플러그인을 통해 Operator 또는 CRD(Custom Resource Definition)에 의존하지 않고 GitOps와 Argo CD로 시크릿 관리 문제를 해결할 수 있습니다.
- 특히 Secret 뿐만 아니라, deployment, configMap 또는 기타 Kubernetes 리소스에도 사용할 수 있습니다.
▶ 설치/구성관련 내용 요약(ChatGPT) - Docs
Argo CD Vault Plugin의 공식 문서에서는 Argo CD에 플러그인을 설치하는 방법으로 네 가지를 제시하고 있습니다:
- argocd-cm ConfigMap을 통한 설치:
- argocd-repo-server에 InitContainer를 추가하여 플러그인을 다운로드하고 설정합니다.
- 또는 플러그인이 사전 설치된 커스텀 이미지를 생성하여 사용합니다.
- 사이드카 컨테이너를 통한 설치:
- 사이드카 컨테이너를 추가하여 플러그인과 필요한 도구들을 포함시킵니다.
- 또는 플러그인이 사전 설치된 커스텀 사이드카 이미지를 생성하여 사용합니다.
Step 1. ArgoCD Vault Plugin을 위한 Credentials 활성화 - AppRole 인증
- argocd 네임스페이스 활성화
kubens argocd
- 이전 실습에서 획득한 Role_ID, Secret_ID활용
kubectl apply -f - <<EOF
kind: Secret
apiVersion: v1
metadata:
name: argocd-vault-plugin-credentials
namespace: argocd
type: Opaque
stringData:
VAULT_ADDR: "http://vault.vault:8200"
AVP_TYPE: "vault"
AVP_AUTH_TYPE: "approle"
AVP_ROLE_ID: 7e0c2ccf-b973-14ad-8efe-942b0bb1a2c5 #Role_ID
AVP_SECRET_ID: a93f212d-886c-5611-7233-4f91eb85accd #Secret_ID
EOF
Step 2. ArgoCD Vault Plugin 설치 - Blog
▶ 참고사항
ArgoCD Vault Plugin 설치 방법은 2가지가 있으며 현재는 Installation via a sidecar container 방식을 사용하는 것을 권장합니다.
→ 이번 스터디에서는 편의상 Helm으로 배포한 ArgoCD에 Kustomize을 활용해 기존 YAML에 대한 Patch을 적용합니다.
https://github.com/hyungwook0221/argocd-vault-plugin/tree/main/manifests/cmp-sidecar
- ArgoCD Helm 설치시 Plugin 활성화 방안
git clone https://github.com/hyungwook0221/argocd-vault-plugin.git
cd argocd-vault-plugin/manifests/cmp-sidecar
# 예전 문법으로 적용된 부분을 edit fix 명령으로 현행화
# kustomize edit fix
# argocd 네임스페이스 설정
kubens argocd
# 생성될 메니페스트 파일에 대한 확인
kubectl kustomize .
# -k 옵션으로 kusomize 실행
kubectl apply -n argocd -k .

k exec -it -n vault vault-0 -- sh
# vault pod shell에 접속 후 root token으로 로그인
vault login
Token (will be hidden): <토큰입력>
# 확인명령
vault read auth/kubernetes/role/argocd
Step 3. 샘플 Application 배포하여 Vault와 동기화
Step3-1. Application.yaml 작성
- GitHub에 저장된 Helm Repo을 배포하며, Helm 메니페스트 내에 변수로 치환된 값(username/password)을 CD 단계에서 Vault 통해서 읽고 렌더링하여 배포
kubectl apply -n argocd -f - <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: demo
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
destination:
namespace: argocd
server: https://kubernetes.default.svc
project: default
source:
path: infra/helm
repoURL: https://github.com/hyungwook0221/spring-boot-debug-app
targetRevision: main
plugin:
name: argocd-vault-plugin-helm
env:
- name: HELM_ARGS
value: -f new-values.yaml
syncPolicy:
automated:
prune: true
selfHeal: true
EOF
Step3-2. Application 배포시 참조하는 new-values.yaml 확인 - GitHub
serviceAccount:
create: true
image:
repository: luafanti/spring-boot-debug-app
tag: main
pullPolicy: IfNotPresent
replicaCount: 1
resources:
memoryRequest: 256Mi
memoryLimit: 512Mi
cpuRequest: 500m
cpuLimit: 1
probes:
liveness:
initialDelaySeconds: 15
path: /actuator/health/liveness
failureThreshold: 3
successThreshold: 1
timeoutSeconds: 3
periodSeconds: 5
readiness:
initialDelaySeconds: 15
path: /actuator/health/readiness
failureThreshold: 3
successThreshold: 1
timeoutSeconds: 3
periodSeconds: 5
ports:
http:
name: http
value: 8080
management:
name: management
value: 8081
envs:
- name: VAULT_SECRET_USER
value: <path:secret/data/sampleapp/config#username>
- name: VAULT_SECRET_PASSWORD
value: <path:secret/data/sampleapp/config#password>
log:
level:
spring: "info"
service: "info"
[ 실행 결과 - 한 눈에 보기 ]

https://github.com/hyungwook0221/spring-boot-debug-app/blob/main/infra/helm/new-values.yaml
spring-boot-debug-app/infra/helm/new-values.yaml at main · hyungwook0221/spring-boot-debug-app
Spring Boot application for debugging purpose. Contribute to hyungwook0221/spring-boot-debug-app development by creating an account on GitHub.
github.com
Step 3-3. 실제 배포시 적용된 화면
- ArgoCD Vault Plugin 적용된 화면 : [DETAILS] - [PARAMETERS]

- Application 배포화면

- Deployment에 적용된 env 값 확인 :
envs:
- name: VAULT_SECRET_USER
value: <path:secret/data/sampleapp/config#username>
- name: VAULT_SECRET_PASSWORD
value: <path:secret/data/sampleapp/config#password>

☞ 실습 완료 후, ArgoCD App 삭제
kubectl delete applications demo
(참고) Unknown 에러 발생시 redis 파드 재배포 후 필요
- ArgoCD repo-server 재기동 → Redis 재기동 → Application 재생성

▶ 도전과제
1) ArgoCD Helm 설치시 Vault Plugin 한번에 설치하기
👉 이번 스티디에서 학습한 방식으로 ArgoCD을 구성할 경우에는 운영시 버전관리에 어려움이 생길 수 있으므로 ArgoCD을 Helm, Kustomize, Operator 방식 한 가지를 채택하여 Plugin 설치하는 것을 적용해보자.
2) AVP Helm대신 AVP Kustomize, AVP 방식으로 배포해보기 - Docs
3) 다른 타입의 리소스 배포해보기 - Docs
예시) K8s Secret
kind: Secret
apiVersion: v1
metadata:
name: example-secret
annotations:
avp.kubernetes.io/path: "path/to/secret"
type: Opaque
data:
password: <password-vault-key>
▶ 추가 학습자료
Exploring GitOps with Argo CD and HashiCorp Vault in Kubernetes - Link1, Link2
👉 Vault Plugin 패턴은 아니지만 위와 같은 방법도 좋아보이네요.
Argo CD and HashiCorp Vault: How to Secure Kubernetes Deployments | TeKanAid
Keep your Kubernetes deployments safe with Argo CD and HashiCorp Vault integration. Learn how with a real-world application demo.
tekanaid.com
7. Vault Secrets Operator (VSO)
▶ 학습 목표
- Vault Secrets Operator(이하, VSO)에 대한 주요 개념과 구성요소를 알아본다.
- VSO을 통해서 Vault을 통해 획득한 시크릿을 Kubernetes Secret을 자동으로 동기화하고 관리하는 방법을 알아본다.
▶ VSO란? - Docs
Vault Secrets Operator(VSO)는 Kubernetes Secrets에서 Vault secrets 및 HCP Vault Secrets Apps를 네이티브하게 사용할 수 있도록 Pods에 제공해줍니다.
1) 개요
Vault Secrets Operator는 지원하는 Custom Resource Definitions(CRD) 집합의 변경 사항을 감시하여 작동합니다. 각 CRD는 시크릿의 지원되는 소스 중 하나에서 Kubernetes Secret으로 동기화할 수 있도록 필요한 사양을 제공합니다.
오퍼레이터는 소스 시크릿 데이터를 대상 Kubernetes Secret에 직접 작성하며, 소스에 변경 사항이 발생할 경우 해당 내용을 대상에도 수명 주기 동안 지속적으로 반영합니다. 이렇게 함으로써 애플리케이션은 대상 시크릿에만 접근하면 그 안의 시크릿 데이터를 사용할 수 있습니다.
2) 기능
Vault Secrets Operator가 지원하는 주요 기능은 다음과 같습니다:
- 여러 시크릿 소스로부터의 동기화 지원
- 자동 시크릿 일탈 감지 및 수정
- Deployment, ReplicaSet, StatefulSet Kubernetes 리소스 유형에 대한 자동 시크릿 교체
- 오퍼레이터 모니터링을 위한 Prometheus 전용 계측 지원
- Helm 또는 Kustomize를 통한 설치 지원
- 시크릿 데이터 변환 지원
3) 지원되는 Kubernetes 배포판
Vault Secrets Operator는 다음과 같은 호스팅된 Kubernetes 환경에서 성공적으로 테스트되었습니다:
- Amazon Elastic Kubernetes Service (EKS)
- Google Kubernetes Engine (GKE)
- Microsoft Azure Kubernetes Service (AKS)
- Red Hat OpenShift (공식 인증됨)
4) 유사한 오픈소스 프로젝트는? - ESO(External Secrets Operator)

▶ VSO 구성도 - 개발자(Developer) / 운영자(Operator)의 역할
▶ Vault 매핑구조

☞ vault policy 에 의해 key/value 값을 지정하고, 이에 대한 vault Role을 지정하여 application 배포 관련 Yaml 내 주요 정보들을 동적으로 rendoring 함으로써 보안성을 강화 할 수 있다. 또한, PKI 인증서와 같은 값들에 대해서도 vault Engine 활성화하여 내부 에 저장된 Secret 값들을 k8s 의 secret(static/dynamic) 연계하여 seamless 하게 사용할 수 있다.
▶ VSO 주요 CRD 정보 및 예시
1) VSO CRD 관계도
☞ Namespace에 국한된 분산된 secret 값들을 "vault Secret Operator"를 통하여 중앙 집중적으로 편리하게 관리할 수 있다.

2) VaultConnect
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultConnection
metadata:
namespace: vso-example
name: vault-connection
spec:
# required configuration
# address to the Vault server.
address: http://vault.vault.svc.cluster.local:8200
3) VaultAuth
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
namespace: vso-example
name: vault-auth
spec:
# required configuration
# VaultConnectionRef of the corresponding VaultConnection CustomResource.
# If no value is specified the Operator will default to the `default` VaultConnection,
# configured in its own Kubernetes namespace.
vaultConnectionRef: vault-connection
# Method to use when authenticating to Vault.
method: kubernetes
# Mount to use when authenticating to auth method.
mount: kubernetes
# Kubernetes specific auth configuration, requires that the Method be set to kubernetes.
kubernetes:
# role to use when authenticating to Vault
role: example
# ServiceAccount to use when authenticating to Vault
# it is recommended to always provide a unique serviceAccount per Pod/application
serviceAccount: default
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuthGlobal
metadata:
namespace: vso-example
name: vault-auth-global
spec:
defaultAuthMethod: kubernetes
kubernetes:
audiences:
- vault
mount: kubernetes
namespace: example-ns
role: auth-role
serviceAccount: default
tokenExpirationSeconds: 600
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
namespace: vso-example
name: vault-auth
spec:
vaultAuthGlobalRef:
name: vault-auth-global
kubernetes:
role: local-role
# KV Version 1
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
namespace: vso-example
name: vault-static-secret-v1
spec:
vaultAuthRef: vault-auth
mount: kvv1
type: kv-v1
path: eng/apikey/google
refreshAfter: 60s
destination:
create: true
name: static-secret1
---
# KV Version 2
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
namespace: vso-example
name: vault-static-secret-v2
spec:
vaultAuthRef: vault-auth
mount: kvv2
type: kv-v2
path: eng/apikey/google
version: 2
refreshAfter: 60s
destination:
create: true
name: static-secret2
# DB Secret 예시
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultDynamicSecret
metadata:
namespace: vso-example
name: vault-dynamic-secret-db
spec:
vaultAuthRef: vault-auth
mount: db
path: creds/my-postgresql-role
destination:
create: true
name: dynamic-db
---
# AWS Secret 예시
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultDynamicSecret
metadata:
namespace: vso-example
name: vault-dynamic-secret-aws-iam
spec:
vaultAuthRef: vault-auth
mount: aws
path: creds/my-iam-role
destination:
create: true
name: dynamic-aws-iam
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultPKISecret
metadata:
namespace: vso-example
name: vault-pki
spec:
vaultAuthRef: vault-auth
mount: pki
role: default
commonName: example.com
format: pem
expiryOffset: 1s
ttl: 60s
namespace: tenant-1
destination:
create: true
name: pki1
▶ 🔐 위협 모델 및 보안 고려사항 - Docs
https://github.com/hashicorp/vault-secrets-operator/blob/main/docs/threat-model/README.md
vault-secrets-operator/docs/threat-model/README.md at main · hashicorp/vault-secrets-operator
The Vault Secrets Operator (VSO) allows Pods to consume Vault secrets natively from Kubernetes Secrets. - hashicorp/vault-secrets-operator
github.com
7-1. Static Secrets (KV) - Docs
▶ 개요
- 운영자가 단일 Vault 정적 시크릿을 단일 Kubernetes 시크릿으로 동기화하는 데 필요한 구성입니다.
- 지원되는 시크릿 엔진: kv-v2, kv-v1
▶ 워크플로우 및 아키텍쳐
▶ 실습
관련 실습은 공식 문서에서 제공하는 실습으로 대체합니다.
https://developer.hashicorp.com/vault/tutorials/kubernetes/vault-secrets-operator
Manage Kubernetes native secrets with the Vault Secrets Operator | Vault | HashiCorp Developer
Set-up the Vault Secrets Operator to synchronise secrets between Vault and a Kubernetes Cluster. Retreieve native static and dynanic Kubernetes secrets.
developer.hashicorp.com
7-2. Dynamic Secrets
▶ 개요
- Vault의 Dynamic Secret Engine을 활용하여 동적으로 변경되는 Secrets을 K8s Secrets에 동기화
- 지원되는 시크릿 엔진: DB Credentials, Cloud Credentials(AWS, Azure, GCP 등)
▶ 시나리오
- Spring(Web Application) → DB 접근하기 위해서는 DB Credentials을 K8s Secrets으로 참조해야함
- 대상 DB에 접근하기 위한 DB Credentials을 Vault의 Dynamic Secrets 기능을 활용하여 주기적으로 변경하고 VSO을 통해 K8s Secets에 갱신(업데이트)
- Spring(Web Application)에서는 갱신된 DB Credentials 정보를 K8s Secret을 통해서 읽어오기 위해 재기동(rolloutRestartTargets 설정)

▶ 실습
Step1. VSO 배포를 위한 Chart Values 파일 작성
# vault-operator-values.yaml
defaultVaultConnection:
enabled: true
address: "http://vault.vault.svc.cluster.local:8200"
skipTLSVerify: false
controller:
manager:
clientCache:
persistenceModel: direct-encrypted
storageEncryption:
enabled: true
mount: k8s-auth-mount
keyName: vso-client-cache
transitMount: demo-transit
kubernetes:
role: auth-role-operator
serviceAccount: vault-secrets-operator-controller-manager
tokenAudiences: ["vault"]
Step2. VSO 배포
helm install vault-secrets-operator hashicorp/vault-secrets-operator \
-n vault-secrets-operator-system \
--create-namespace \
--values vault-operator-values.yaml
#
kubectl get-all -n vault-secrets-operator-system
#
kubectl get pod -n vault-secrets-operator-system
NAME READY STATUS RESTARTS AGE
vault-secrets-operator-controller-manager-7f67cd89fd-d2t2k 2/2 Running 0 53s
kubectl describe pod -n vault-secrets-operator-system
...
Service Account: vault-secrets-operator-controller-manager
...
Containers:
kube-rbac-proxy:
Container ID: containerd://db3eae7b836fb4f1b4236c494c8fa96ada94769a6c602e1a150c75293a6a4162
Image: quay.io/brancz/kube-rbac-proxy:v0.18.1
...
manager:
Container ID: containerd://1ab1545fb4bd86ac52d6c7609a3e962cd2d1a81daa9bbd9c82f79d9a0d8b6466
Image: hashicorp/vault-secrets-operator:0.10.0
...
#
kubectl rbac-tool lookup vault-secrets-operator-controller-manager
SUBJECT | SUBJECT TYPE | SCOPE | NAMESPACE | ROLE | BINDING
--------------------------------------------+----------------+-------------+-------------------------------+---------------------------------------------+-----------------------------------------------------
vault-secrets-operator-controller-manager | ServiceAccount | ClusterRole | | vault-secrets-operator-proxy-role | vault-secrets-operator-proxy-rolebinding
vault-secrets-operator-controller-manager | ServiceAccount | ClusterRole | | vault-secrets-operator-manager-role | vault-secrets-operator-manager-rolebinding
vault-secrets-operator-controller-manager | ServiceAccount | Role | vault-secrets-operator-system | vault-secrets-operator-leader-election-role | vault-secrets-operator-leader-election-rolebinding
#
kubectl rolesum vault-secrets-operator-controller-manager -n vault-secrets-operator-system
ServiceAccount: vault-secrets-operator-system/vault-secrets-operator-controller-manager
Secrets:
Policies:
• [RB] vault-secrets-operator-system/vault-secrets-operator-leader-election-rolebinding ⟶ [R] vault-secrets-operator-system/vault-secrets-operator-leader-election-role
Resource Name Exclude Verbs G L W C U P D DC
configmaps [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
events [*] [-] [-] ✖ ✖ ✖ ✔ ✖ ✔ ✖ ✖
leases.coordination.k8s.io [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
• [CRB] */vault-secrets-operator-manager-rolebinding ⟶ [CR] */vault-secrets-operator-manager-role
Resource Name Exclude Verbs G L W C U P D DC
configmaps [*] [-] [-] ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖
daemonsets.apps [*] [-] [-] ✔ ✔ ✔ ✖ ✖ ✔ ✖ ✖
deployments.apps [*] [-] [-] ✔ ✔ ✔ ✖ ✖ ✔ ✖ ✖
events [*] [-] [-] ✖ ✖ ✖ ✔ ✖ ✔ ✖ ✖
hcpauths.secrets.hashicorp.com [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
hcpauths.secrets.hashicorp.com/finalizers [*] [-] [-] ✖ ✖ ✖ ✖ ✔ ✖ ✖ ✖
hcpauths.secrets.hashicorp.com/status [*] [-] [-] ✔ ✖ ✖ ✖ ✔ ✔ ✖ ✖
hcpvaultsecretsapps.secrets.hashicorp.com [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
hcpvaultsecretsapps.secrets.hashicorp.com/finalizers [*] [-] [-] ✖ ✖ ✖ ✖ ✔ ✖ ✖ ✖
hcpvaultsecretsapps.secrets.hashicorp.com/status [*] [-] [-] ✔ ✖ ✖ ✖ ✔ ✔ ✖ ✖
rollouts.argoproj.io [*] [-] [-] ✔ ✔ ✔ ✖ ✖ ✔ ✖ ✖
secrets [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔
secrettransformations.secrets.hashicorp.com [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
secrettransformations.secrets.hashicorp.com/finalizers [*] [-] [-] ✖ ✖ ✖ ✖ ✔ ✖ ✖ ✖
secrettransformations.secrets.hashicorp.com/status [*] [-] [-] ✔ ✖ ✖ ✖ ✔ ✔ ✖ ✖
serviceaccounts [*] [-] [-] ✔ ✔ ✔ ✖ ✖ ✖ ✖ ✖
serviceaccounts/token [*] [-] [-] ✔ ✔ ✔ ✔ ✖ ✖ ✖ ✖
statefulsets.apps [*] [-] [-] ✔ ✔ ✔ ✖ ✖ ✔ ✖ ✖
vaultauthglobals.secrets.hashicorp.com [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
vaultauthglobals.secrets.hashicorp.com/finalizers [*] [-] [-] ✖ ✖ ✖ ✖ ✔ ✖ ✖ ✖
vaultauthglobals.secrets.hashicorp.com/status [*] [-] [-] ✔ ✖ ✖ ✖ ✔ ✔ ✖ ✖
vaultauths.secrets.hashicorp.com [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
vaultauths.secrets.hashicorp.com/finalizers [*] [-] [-] ✖ ✖ ✖ ✖ ✔ ✖ ✖ ✖
vaultauths.secrets.hashicorp.com/status [*] [-] [-] ✔ ✖ ✖ ✖ ✔ ✔ ✖ ✖
vaultconnections.secrets.hashicorp.com [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
vaultconnections.secrets.hashicorp.com/finalizers [*] [-] [-] ✖ ✖ ✖ ✖ ✔ ✖ ✖ ✖
vaultconnections.secrets.hashicorp.com/status [*] [-] [-] ✔ ✖ ✖ ✖ ✔ ✔ ✖ ✖
vaultdynamicsecrets.secrets.hashicorp.com [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
vaultdynamicsecrets.secrets.hashicorp.com/finalizers [*] [-] [-] ✖ ✖ ✖ ✖ ✔ ✖ ✖ ✖
vaultdynamicsecrets.secrets.hashicorp.com/status [*] [-] [-] ✔ ✖ ✖ ✖ ✔ ✔ ✖ ✖
vaultpkisecrets.secrets.hashicorp.com [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
vaultpkisecrets.secrets.hashicorp.com/finalizers [*] [-] [-] ✖ ✖ ✖ ✖ ✔ ✖ ✖ ✖
vaultpkisecrets.secrets.hashicorp.com/status [*] [-] [-] ✔ ✖ ✖ ✖ ✔ ✔ ✖ ✖
vaultstaticsecrets.secrets.hashicorp.com [*] [-] [-] ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
vaultstaticsecrets.secrets.hashicorp.com/finalizers [*] [-] [-] ✖ ✖ ✖ ✖ ✔ ✖ ✖ ✖
vaultstaticsecrets.secrets.hashicorp.com/status [*] [-] [-] ✔ ✖ ✖ ✖ ✔ ✔ ✖ ✖
• [CRB] */vault-secrets-operator-proxy-rolebinding ⟶ [CR] */vault-secrets-operator-proxy-role
Resource Name Exclude Verbs G L W C U P D DC
subjectaccessreviews.authorization.k8s.io [*] [-] [-] ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖
tokenreviews.authentication.k8s.io [*] [-] [-] ✖ ✖ ✖ ✔ ✖ ✖ ✖ ✖
#
kubectl get vaultauth -n vault-secrets-operator-system vault-secrets-operator-default-transit-auth -o jsonpath='{.spec}' | jq
{
"kubernetes": {
"audiences": [
"vault"
],
"role": "auth-role-operator",
"serviceAccount": "vault-secrets-operator-controller-manager",
"tokenExpirationSeconds": 600
},
"method": "kubernetes",
"mount": "k8s-auth-mount",
"storageEncryption": {
"keyName": "vso-client-cache",
"mount": "demo-transit"
},
"vaultConnectionRef": "default"
}
kubectl get vaultconnection -n vault-secrets-operator-system default -o jsonpath='{.spec}' | jq
{
"address": "http://vault.vault.svc.cluster.local:8200",
"skipTLSVerify": false
}

Step3. PostgreSQL 설치 (Bitnami Helm Chart)
kubectl create ns postgres
helm repo add bitnami https://charts.bitnami.com/bitnami
helm upgrade --install postgres bitnami/postgresql \
--namespace postgres \
--set auth.audit.logConnections=true \
--set auth.postgresPassword=secret-pass
# psql 로그인 확인
kubectl exec -it -n postgres postgres-postgresql-0 -- sh -c 'PGPASSWORD=secret-pass psql -U postgres -h localhost'
kubectl exec -it -n postgres postgres-postgresql-0 -- sh -c "PGPASSWORD=secret-pass psql -U postgres -h localhost -c '\l'"

Step4. Kubernetes Auth Method 설정
#
kubectl exec -it vault-0 -n vault -- cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
-----BEGIN CERTIFICATE-----
MIIDBTCCAe2gAwIBAgIIfA5oV86RmOswDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKa3ViZXJuZXRlczAeFw0yNTA0MTAwMDUzMTdaFw0zNTA0MDgwMDU4MTdaMBUx
EzARBgNVBAMTCmt1YmVybmV0ZXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDSUCHyBXVhBb91uZKdqwFAussfSxqPadYhrj659fp/XuJQrRrOMLZAh5eP
6MCkeYbHeMY2stwBzuwzSzyaxkJdKiR5yKhtIHT7vpYn4XT0bNdyWWKAHkjyqjsk
DYkAbwQ1xksL3DwArRfUSdZPCVsDLhu+G3wbGKiW64q0Rb1pEk/gK2AhxnSSqoeR
YNzh10FA/CxS2wiLFfsm8NAn4KHG134AKY0Vxb/EkXnL9b2jRK+BfSh/sSpuEzN3
16Xy1fC5UX/xCdTvwx+57xOoFhymaF0aVISSavzrkaYd8kaPwAKtoAlxvDmDrVhv
YKRfCTerHUCOL3IPQqChIZQIUvoVAgMBAAGjWTBXMA4GA1UdDwEB/wQEAwICpDAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSDK0FYEL+FNdgIpMhCnj9g2CkAIjAV
BgNVHREEDjAMggprdWJlcm5ldGVzMA0GCSqGSIb3DQEBCwUAA4IBAQC5/EdWTRD6
SoghjPe/bKKfQzFxPmEZVTb0JI0CWVUZorGzFcHCMCXMOf/XuECkK5tqn7QR/Nw8
yy3XvmT6DY+J2GshrusJXfMEOJ9RQIwDBJKHoZqbcyI/w294Ecp1Mpivb8WeI2FW
wMsaDoswQ3H/0zrEBwhhpUAWcmjm6w4/7Tsid9t1DsQrp9xH37wU/uP07HyEIKPS
TCQ2tUueHSVihNhCg7RejjbiGy8EUZV6aGIrtFgss9Mzk+5m+1KXm8o+Lj/YNYFx
JFeII2h3ZptO4I9vHrohXKVw0esHNzdHQPeGEXCWHTd+K38jIJPfggA8Yu1htbUa
MKEIbcT53UHH
-----END CERTIFICATE-----
# 해당 ca.crt 는 k8s 의 루트인증서 정보
kubectl exec -it vault-0 -n vault -- cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt > vso-ca.crt
openssl x509 -in vso-ca.crt -noout -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 8939197036714105067 (0x7c0e6857ce9198eb)
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=kubernetes
...
# vault 서비스 어카운트의 token 확인
kubectl exec -it vault-0 -n vault -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
eyJhbGciOiJSUzI1NiIsImtpZCI6InJBT1dvdDVZME1wMjZ4NmJuZkVvNk9nc1BGY1FIVDgyaUllUy1VN05oejgifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzc1ODAzNjMyLCJpYXQiOjE3NDQyNjc2MzIsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiZTJkOTdiYTItNTMzMi00ZDFlLWJjYWMtYzg3NDdmNGQ2MWIwIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJ2YXVsdCIsIm5vZGUiOnsibmFtZSI6Im15azhzLWNvbnRyb2wtcGxhbmUiLCJ1aWQiOiI5ZGZkYTg5YS1hYzZkLTRmNTYtYjlhOC03MTg2YTI3MWVjZjMifSwicG9kIjp7Im5hbWUiOiJ2YXVsdC0wIiwidWlkIjoiNGFlMTVhZTctOTI0NS00OTZkLWE0ZDItYWI3ZTk4NDA1Yjg1In0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJ2YXVsdCIsInVpZCI6ImNjYjgwNjJkLTQ4OTctNDI1MC1hYzg3LTYxMTAxYWQ0YjI4NyJ9LCJ3YXJuYWZ0ZXIiOjE3NDQyNzEyMzl9LCJuYmYiOjE3NDQyNjc2MzIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDp2YXVsdDp2YXVsdCJ9.dekUKWseyWNgYO4uUi1MHI16BvHOAmeO61gxO-LIEK7m5ahG72wLwUnl5G22eRlA8T9X2DnvB8tIPyGDawzkCJWve7PTq-_76QOXZO8a3-KUY9kJ3gI-GymN9jwV9eUxXoFGqp73WxZCK-derP6aj5WZirSCHb1YQnioKmxQ4frhQVYi-GDLLz6A2OH-MnZIM68LvFPxK06Id2McMlqOFnV3JCDSzxvyEtnANQgdNWXIit-o6FFzDvKhlfwN6Yrjs9AO7JEyFeQy7M-C2_mQq_Xqht-cO28b8tA4kEJLIdWSIfu1LAWP06YZYeEbQM-8qZ9HoXR1L-QFtonMLkJ1pg
# https://jwt.io/ 에서 token 디코드 확인 시 PAYLOAD 부분
{
"aud": [
"https://kubernetes.default.svc.cluster.local"
],
"exp": 1775803632,
"iat": 1744267632,
"iss": "https://kubernetes.default.svc.cluster.local",
"jti": "e2d97ba2-5332-4d1e-bcac-c8747f4d61b0",
"kubernetes.io": {
"namespace": "vault",
"node": {
"name": "myk8s-control-plane",
"uid": "9dfda89a-ac6d-4f56-b9a8-7186a271ecf3"
},
"pod": {
"name": "vault-0",
"uid": "4ae15ae7-9245-496d-a4d2-ab7e98405b85"
},
"serviceaccount": {
"name": "vault",
"uid": "ccb8062d-4897-4250-ac87-61101ad4b287"
},
"warnafter": 1744271239
},
"nbf": 1744267632,
"sub": "system:serviceaccount:vault:vault"
}
# vault SA 토큰 만료 기간 : 대략 1시간(3600초)+7초
k get pod -n vault vault-0 -o yaml
...
- name: kube-api-access-9rqgv
projected:
defaultMode: 420
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
- configMap:
items:
- key: ca.crt
path: ca.crt
name: kube-root-ca.crt
kubectl exec --stdin=true --tty=true vault-0 -n vault -- /bin/sh
----------------------------------------------------------------
# 최초 설치시 획득한 vault root token 사용.
# 예시) hvs.hnlFKWjE10FOyrwLMeK9PrCC
vault login
# Kubernetes 인증 메서드 활성화
vault auth enable -path k8s-auth-mount kubernetes
#
vault auth list
Path Type Accessor Description Version
---- ---- -------- ----------- -------
approle/ approle auth_approle_03723e90 n/a n/a
k8s-auth-mount/ kubernetes auth_kubernetes_a2ee4150 n/a n/a
token/ token auth_token_de4f4e43 token based credentials n/a
# Kubernetes 클러스터 정보 구성
vault write auth/k8s-auth-mount/config \
kubernetes_host="https://kubernetes.default.svc:443" \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"
# 설정 확인
vault read auth/k8s-auth-mount/config
Key Value
--- -----
disable_iss_validation true
disable_local_ca_jwt false
issuer n/a
kubernetes_ca_cert -----BEGIN CERTIFICATE-----
MIIDBTCCAe2gAwIBAgIIfA5oV86RmOswDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
AxMKa3ViZXJuZXRlczAeFw0yNTA0MTAwMDUzMTdaFw0zNTA0MDgwMDU4MTdaMBUx
EzARBgNVBAMTCmt1YmVybmV0ZXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDSUCHyBXVhBb91uZKdqwFAussfSxqPadYhrj659fp/XuJQrRrOMLZAh5eP
6MCkeYbHeMY2stwBzuwzSzyaxkJdKiR5yKhtIHT7vpYn4XT0bNdyWWKAHkjyqjsk
DYkAbwQ1xksL3DwArRfUSdZPCVsDLhu+G3wbGKiW64q0Rb1pEk/gK2AhxnSSqoeR
YNzh10FA/CxS2wiLFfsm8NAn4KHG134AKY0Vxb/EkXnL9b2jRK+BfSh/sSpuEzN3
16Xy1fC5UX/xCdTvwx+57xOoFhymaF0aVISSavzrkaYd8kaPwAKtoAlxvDmDrVhv
YKRfCTerHUCOL3IPQqChIZQIUvoVAgMBAAGjWTBXMA4GA1UdDwEB/wQEAwICpDAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSDK0FYEL+FNdgIpMhCnj9g2CkAIjAV
BgNVHREEDjAMggprdWJlcm5ldGVzMA0GCSqGSIb3DQEBCwUAA4IBAQC5/EdWTRD6
SoghjPe/bKKfQzFxPmEZVTb0JI0CWVUZorGzFcHCMCXMOf/XuECkK5tqn7QR/Nw8
yy3XvmT6DY+J2GshrusJXfMEOJ9RQIwDBJKHoZqbcyI/w294Ecp1Mpivb8WeI2FW
wMsaDoswQ3H/0zrEBwhhpUAWcmjm6w4/7Tsid9t1DsQrp9xH37wU/uP07HyEIKPS
TCQ2tUueHSVihNhCg7RejjbiGy8EUZV6aGIrtFgss9Mzk+5m+1KXm8o+Lj/YNYFx
JFeII2h3ZptO4I9vHrohXKVw0esHNzdHQPeGEXCWHTd+K38jIJPfggA8Yu1htbUa
MKEIbcT53UHH
-----END CERTIFICATE-----
kubernetes_host https://kubernetes.default.svc:443
pem_keys []
token_reviewer_jwt_set true
use_annotations_as_alias_metadata false
----------------------------------------------------------------
▶ vault write 명령 분석
- Vault가 K8s API 서버에 인증 요청을 보낼 수 있도록 설정
- kubernetes_host: API 서버 주소
- kubernetes_ca_cert: API 서버의 CA 인증서 (Pod 내 ServiceAccount에서 자동 주입됨)
- token_reviewer_jwt: TokenReviewer 권한이 있는 토큰으로 Vault가 요청자의 토큰을 검증할 수 있도록 설정
[ 실행 결과 - 한 눈에 보기 ]

Step5. Vault Kubernetes Auth Role 설정
실행 중인 애플리케이션이 Vault로부터 DB 자격증명(동적 사용자)을 받아올 수 있도록 권한을 연결을 위한 설정
vault write auth/k8s-auth-mount/role/auth-role \
bound_service_account_names=demo-dynamic-app \
bound_service_account_namespaces=demo-ns \
token_ttl=0 \
token_period=120 \
token_policies=demo-auth-policy-db \
audience=vault
[ 상세 설명 ]
# auth-role 생성
vault write auth/k8s-auth-mount/role/auth-role \
bound_service_account_names=demo-dynamic-app \ # demo-dynamic-app 서비스 어카운트를 사용하는 Pod에서만 인증 허용, Pod 내부에서 Vault에 로그인하려면 이 서비스 어카운트를 써야 함
bound_service_account_namespaces=demo-ns \ # 이 Role은 오직 demo-ns 네임스페이스에서 오는 요청만 허용
token_ttl=0 \ # 발급되는 Vault 토큰의 기본 TTL 없음(0). 일반적으로 token_ttl=0이면 token_period가 사용됨
token_period=120 \ # Lease 기간이 120초인 Renewable Token 발급. TTL이 없는 대신, 2분마다 갱신 가능한 토큰을 생성. 보통 이 설정은 "periodic token"이라 불리며, long-lived session에 적합.
token_policies=demo-auth-policy-db \ # 인증에 성공하면 발급되는 토큰에 demo-auth-policy-db 정책이 적용
audience=vault # JWT 토큰의 aud (Audience) claim 검증에 사용.
Step6. Vault Database Secret Engine 설정
# demo-db라는 경로로 Database Secret Engine을 활성화
vault secrets enable -path=demo-db database
# PostgreSQL 연결 정보 등록
# 해당 과정은 postgres가 정상적으로 동작 시 적용 가능
# allowed_roles: 이후 설정할 Role 이름 지정
vault write demo-db/config/demo-db \
plugin_name=postgresql-database-plugin \
allowed_roles="dev-postgres" \
connection_url="postgresql://{{username}}:{{password}}@postgres-postgresql.postgres.svc.cluster.local:5432/postgres?sslmode=disable" \
username="postgres" \
password="secret-pass"
# DB 사용자 동적 생성 Role 등록
# 해당 Role 사용 시 Vault가 동적으로 사용자 계정과 비밀번호를 생성 가능
# TTL은 생성된 자격증명의 유효 시간 (30초~10분)
## creation_statements=... : Vault가 계정을 자동 생성할 때 실행할 SQL 문. {{name}}, {{password}}, {{expiration}}은 Vault가 자동으로 채워줌.
## revocation_statements=... : Vault가 동적 사용자 계정을 폐기(revoke)할 때 실행할 SQL 문. 권한을 모두 회수.
## default_ttl="1m" / max_ttl="1m" : 기본 1분만 유효합니다. 갱신을 안 하면 1분 후 자동 만료 (계정도 자동 삭제). 최대 TTL도 1분이므로 연장도 최대 1분까지만.
vault write demo-db/roles/dev-postgres \
db_name=demo-db \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
GRANT ALL PRIVILEGES ON DATABASE postgres TO \"{{name}}\";" \
revocation_statements="REVOKE ALL ON DATABASE postgres FROM \"{{name}}\";" \
backend=demo-db \
name=dev-postgres \
default_ttl="1m" \
max_ttl="1m"
# 정책 설정: DB 자격증명 읽기 권한
# demo-db/creds/dev-postgres 경로에 대한 read 권한 부여
# 추후 Kubernetes 서비스 어카운트(demo-dynamic-app)에 이 정책을 연결해서 자격증명 요청 가능
vault policy write demo-auth-policy-db - <<EOF
path "demo-db/creds/dev-postgres" {
capabilities = ["read"]
}
EOF


Step7. Transit Secret Engine 설정 + VSO 연동 Role 구성 - Docs , Client-cache
kubectl exec --stdin=true --tty=true vault-0 -n vault -- /bin/sh
----------------------------------------------------------------
# Transit Secret Engine 활성화
# transit 엔진을 demo-transit 경로로 활성화.
# 데이터를 저장하지 않고 암복호화 기능만 제공하는 Vault의 기능
# 클라이언트 캐시는 리더십 변경 시에도 Vault 토큰 및 동적 비밀 임대를 계속 추적하고 갱신할 수 있으므로 원활한 업그레이드를 지원합니다
## Vault 서버에 클라이언트 캐시를 저장하고 암호화할 수 있습니다.
## Vault 동적 비밀을 사용하는 경우 클라이언트 캐시를 영구적으로 저장하고 암호화하는 것이 좋습니다.
## 이렇게 하면 재시작 및 업그레이드를 통해 동적 비밀 임대가 유지됩니다.
vault secrets enable -path=demo-transit transit
vault secrets list -detailed
# vso-client-cache라는 키를 생성
# 이 키는 VSO가 암복호화 시 사용할 암호화 키 역할
vault write -force demo-transit/keys/vso-client-cache
# vso-client-cache 키에 대해 암호화(encrypt), 복호화(decrypt)를 허용하는 정책 생성
vault policy write demo-auth-policy-operator - <<EOF
path "demo-transit/encrypt/vso-client-cache" {
capabilities = ["create", "update"]
}
path "demo-transit/decrypt/vso-client-cache" {
capabilities = ["create", "update"]
}
EOF
# Vault Secrets Operator가 사용하는 ServiceAccount에 위 정책을 바인딩
# vso가 Vault에 로그인할 때 사용할 수 있는 JWT 기반 Role 설정
# 해당 Role을 통해 Operator는 Transit 엔진을 이용한 암복호화 API 호출 가능
vault write auth/k8s-auth-mount/role/auth-role-operator \
bound_service_account_names=vault-secrets-operator-controller-manager \
bound_service_account_namespaces=vault-secrets-operator-system \
token_ttl=0 \
token_period=120 \
token_policies=demo-auth-policy-operator \
audience=vault
vault read auth/k8s-auth-mount/role/auth-role-operator

Step8. 샘플 애플리케이션 YAML 작성 및 배포
1) demo-ns 네임스페이스 생성
kubectl create ns demo-ns
mkdir vso-dynamic
cd vso-dynamic
2) vault-auth-dynamic.yaml :
- 앱이 Vault에 인증하기 위한 ServiceAccount 및 VaultAuth 리소스
- Vault에서 발급한 동적 PostgreSQL 크레덴셜을 얻기 위해 반드시 필요
---
# vault-auth-dynamic.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
namespace: demo-ns
name: demo-dynamic-app
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: dynamic-auth
namespace: demo-ns
spec:
method: kubernetes
mount: k8s-auth-mount
kubernetes:
role: auth-role
serviceAccount: demo-dynamic-app
audiences:
- vault
3) app-secret.yaml :
- Spring App에서 PostgreSQL에 접속할 때 사용할 해당 시크릿에 username/password을 동적으로 생성
---
# app-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: vso-db-demo
namespace: demo-ns
4) vault-dynamic-secret.yaml
- Vault에서 동적으로 PostgreSQL 접속정보 생성하고 K8s Secret에 저장
- 생성된 Secret(vso-db-demo)은 앱에서 환경 변수(env)로 사용
- 애플리케이션에서 Dynamic Reloading을 지원하지 않을 경우 rolloutRestartTargets 을 사용하여 애플리케이션을 재배포하여 새로 업데이트된 시크릿을 사용하도록 할 수 있음
- refreshAfter 설정을 통해 VSO가 소스 시크릿 데이터를 동기화 하는 주기 설정가능 - Docs → Event Notification 기능을 통해 변경이 있을 경우 즉시 반영할 수도 있음 - Docs
---
# vault-dynamic-secret.yaml
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultDynamicSecret
metadata:
name: vso-db-demo
namespace: demo-ns
spec:
refreshAfter: 25s
mount: demo-db
path: creds/dev-postgres
destination:
name: vso-db-demo
create: true
overwrite: true
vaultAuthRef: dynamic-auth
rolloutRestartTargets:
- kind: Deployment
name: vaultdemo
5) app-spring-deploy.yaml :
- DB 접속 테스트를 위한 Spring App →
---
# app-spring-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: vaultdemo
namespace: demo-ns
labels:
app: vaultdemo
spec:
replicas: 1
selector:
matchLabels:
app: vaultdemo
template:
metadata:
labels:
app: vaultdemo
spec:
volumes:
- name: secrets
secret:
secretName: "vso-db-demo"
containers:
- name: vaultdemo
image: hyungwookhub/vso-spring-demo:v5
imagePullPolicy: IfNotPresent
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: "vso-db-demo"
key: password
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: "vso-db-demo"
key: username
- name: DB_HOST
value: "postgres-postgresql.postgres.svc.cluster.local"
- name: DB_PORT
value: "5432"
- name: DB_NAME
value: "postgres"
ports:
- containerPort: 8088
volumeMounts:
- name: secrets
mountPath: /etc/secrets
readOnly: true
---
apiVersion: v1
kind: Service
metadata:
name: vaultdemo
namespace: demo-ns
spec:
ports:
- name: vaultdemo
port: 8088
targetPort: 8088
nodePort: 30003
selector:
app: vaultdemo
type: NodePort
- 애플리케이션 배포 : kubectl apply -f .
Step9. 동작확인(UI) : postgres-postgresql.postgres.svc , db는 postgres
Step9-1. 동작확인(CLI)
# 스크릿이 변경되고, 적용을 위해서 파드가 42~45초 사이로 재생성됨
while true; do kubectl get pod -n demo-ns ; echo; kubectl view-secret -n demo-ns vso-db-demo --all; date; sleep 30 ; echo ; done
NAME READY STATUS RESTARTS AGE
vaultdemo-7779bcc8c6-tn74g 1/1 Running 0 27s
_raw='{"password":"igZwq-mC04usPQ7QOVDM","username":"v-k8s-auth-dev-post-5mYscZjhGgMhqcWIRpjo-1744273869"}'
password='igZwq-mC04usPQ7QOVDM'
username='v-k8s-auth-dev-post-5mYscZjhGgMhqcWIRpjo-1744273869'
Thu Apr 10 17:31:36 KST 2025
NAME READY STATUS RESTARTS AGE
vaultdemo-59784754f8-d85bm 1/1 Running 0 12s
_raw='{"password":"V2-a1wJQk7MhDACwJYfb","username":"v-k8s-auth-dev-post-HYgtwKnrMqBML4831xiO-1744273914"}'
password='V2-a1wJQk7MhDACwJYfb'
username='v-k8s-auth-dev-post-HYgtwKnrMqBML4831xiO-1744273914'
Thu Apr 10 17:32:06 KST 2025
NAME READY STATUS RESTARTS AGE
vaultdemo-59784754f8-d85bm 1/1 Running 0 42s
vaultdemo-79576986c7-cb5h9 0/1 ContainerCreating 0 0s
_raw='{"password":"-YZ2ebxyEskpl1ZP1Mcm","username":"v-k8s-auth-dev-post-LGpcqVAfuYdWNNWBkbKA-1744273956"}'
password='-YZ2ebxyEskpl1ZP1Mcm'
username='v-k8s-auth-dev-post-LGpcqVAfuYdWNNWBkbKA-1744273956'
Thu Apr 10 17:32:36 KST 2025
# 실제 postgresql 에 사용자 정보 확인 : 계속 추가되고 있음..
kubectl exec -it -n postgres postgres-postgresql-0 -- sh -c "PGPASSWORD=secret-pass psql -U postgres -h localhost -c '\du'"
List of roles
Role name | Attributes
-----------------------------------------------------+------------------------------------------------------------
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS
v-k8s-auth-dev-post-23yTKlIlbnInwyQAaO55-1744274290 | Password valid until 2025-04-10 08:39:15+00
v-k8s-auth-dev-post-3Vtbg8lmlyxRRzHfM7Zt-1744272836 | Password valid until 2025-04-10 08:15:01+00
v-k8s-auth-dev-post-5I5y27xxG61G1m1MxPWp-1744273652 | Password valid until 2025-04-10 08:28:37+00
v-k8s-auth-dev-post-5mYscZjhGgMhqcWIRpjo-1744273869 | Password valid until 2025-04-10 08:32:14+00
v-k8s-auth-dev-post-6x0KoTXiUn0XcattYd5A-1744273218 | Password valid until 2025-04-10 08:21:23+00
v-k8s-auth-dev-post-7EzuV4eRnE3sRLaGsM2h-1744273475 | Password valid until 2025-04-10 08:25:40+00
v-k8s-auth-dev-post-8SRaQap9V1Rcd4ZQ9k73-1744273607 | Password valid until 2025-04-10 08:27:52+00
v-k8s-auth-dev-post-9AHIb55XgqeDFqwphmby-1744274501 | Password valid until 2025-04-10 08:42:46+00
...
# 로그 확인
kubectl stern -n demo-ns -l app=vaultdemo
...
kubectl stern -n vault vault-0
...
vault-0 vault 2025-04-10T08:32:09.484Z [INFO] expiration: revoked lease: lease_id=demo-db/creds/dev-postgres/Ph1sg8efnqssOU5FVIuAqhMW
vault-0 vault 2025-04-10T08:32:54.229Z [INFO] expiration: revoked lease: lease_id=demo-db/creds/dev-postgres/XKkHZ65ZYLeILvo8GqWfE7F3
vault-0 vault 2025-04-10T08:33:36.804Z [INFO] expiration: revoked lease: lease_id=demo-db/creds/dev-postgres/rk1KUFsV7SjZPNFsCqCHGZVx
...
# vault-secrets-operator 가 clientCachePersistenceModel 설정 적용 정보 확인 : 참고로 해당 파드가 재시작되지는 않음.
kubectl stern -n vault-secrets-operator-system -l app.kubernetes.io/name=vault-secrets-operator
...
vault-secrets-operator-controller-manager-7f67cd89fd-d2t2k manager {"level":"info","ts":"2025-04-10T06:50:14Z","logger":"initCachingClientFactory","msg":"Initializing the CachingClientFactory"}
vault-secrets-operator-controller-manager-7f67cd89fd-d2t2k manager {"level":"info","ts":"2025-04-10T06:50:14Z","logger":"setup","msg":"Starting manager","gitVersion":"0.10.0","gitCommit":"aebf0c1c59485059a9ea6c58340fd406afe4cbef","gitTreeState":"clean","buildDate":"2025-03-04T22:22:24+0000","goVersion":"go1.23.6","platform":"linux/arm64","clientCachePersistenceModel":"direct-encrypted","clientCacheSize":10000,"backoffMultiplier":1.5,"backoffMaxInterval":60,"backoffMaxElapsedTime":0,"backoffInitialInterval":5,"backoffRandomizationFactor":0.5,"globalTransformationOptions":"","globalVaultAuthOptions":"allow-default-globals"}
...
vault-secrets-operator-controller-manager-7f67cd89fd-d2t2k manager {"level":"info","ts":"2025-04-10T08:11:00Z","logger":"lifetimeWatcher","msg":"Starting","id":"aee67272-40e9-41ba-a380-b9f948acea7e","entityID":"77fb779f-c79e-567e-1c72-e83c0a0552a7","clientID":"024b5ed8d389816c9a4a99f2968ffc4ba66282d7f39d34b465aba1ebe3a8bb67","cacheKey":"kubernetes-5530fb1481fb1695773196"}
...
# 암호화된 캐시 저장소 확인 : vso-cc-<auth method>
kubectl get secret -n vault-secrets-operator-system -l app.kubernetes.io/component=client-cache-storage
NAME TYPE DATA AGE
vso-cc-kubernetes-5530fb1481fb1695773196 Opaque 2 5m21s
kubectl get secret -n vault-secrets-operator-system -l app.kubernetes.io/component=client-cache-storage -o yaml
...
data:
messageMAC: AZ2WpecD1Ecc4fu6TQY8qtqrIGuZEBfBIIecuOrLMgY=
secret: eyJjaXBoZXJ0ZXh0IjoidmF1bHQ6djE6c1U3RkVjZ2NldW1SSDR4K3hjRGF1eTNTRzNyejBnMUgxZlY4TmozRUJ5cCtuZnN6WWxMTmwrSDdtVy96ZnAvcVNUVDMxeU5ORlNTTjNWOFh6Zkx6TlJnMGt3QXZaeTBwbUZXclFlUGVBVmdLU3RSSllDSVA0YjRkTnk1cWgxcFA4Tnh0cDVvV1QvRnRpNWptZWpBN2FxWkxXRjJ3L1Y1K2FLZ0tTN1hZcG9pRnJpUTc3QWRvaEltTjlweUdpVHZVNFF4OTVSOW94Y3N6cW1JaTB6MUVCZ2l4U1AxWmNqUDZkSUN0QTREK3hTMWsxNmtneFV2elpUZHN5R01QS01manBzVGRCbmQ1NnFJYlQ1OTZ4bEh3WmJvSnU4dUhaY1pKaGVtWnc2eUJXekV4VHdYTXIxV3ZqbVdZWXNwL0dkTzRXTktpZE5nQjB3QnBqM0o3VFd5UEgreWVJTXFkREdmZWFCWkxhRkJQN1VtNnNpNEI1TUZSRFQrdElEZVhER3dLcjdCbWRSMVVpT0M5WU9hN1MxRlpacGFGTis2QXJRUUJDMGFicHZNSlN1blQ0R1o4L1lGUUpPcnFYaTlwNWJrYUhWUFU5S1dXQ2VtTFRqRDJHdnN5ZkZkMmkyZlhJNHR5Ly8vczZIYndGbDZZT0EyRWw1c1QxbjNkbkF6NXFXOUtuVUdYYVVTY0NhWTRDOG5rU3dLeVlRejY2WHU2QkJvYXkvT3k0QjRBUy83bS9seXpHRCtQZUFNQU84cjE1YmlUZTgvdWtmZWIxTHl1cXVFbG5saGdHMmxYTjFSd1laSElwVnpuZXlkZWlYd2RsdW5leFVXeFk3QUg2QjNEVmVTWWlWczE5dEIxRGt2Mm1KbkJLRThtL09QcVliSENYaS9zTXFEMGpoYjFQUWJ4QzdiQkY3ODRIQnByUDFDNmo0Szd5VVpKdGtqVUpsVTBxclRpMXBNQS9nbnovVXV2RHZSb3RUTkE5RTk1U0I1c1pVbmZsZHlsZGNCNjZ3RG5oaXJDNkluZnlmcFN2ZUFMNTErNUc3bDZzOXlVbUQ4aDNpQW5nWTlEemt1bGlmakxkYyt4VE9sY2ZjUWp2RVJ1UVA0b2psTkxvY3BuOVZ4b3E4ZUZDZllsaEVENlNJTzZCdHc4SWZWWndZa0JNWHVieVFtMGl1b3VXaVFZcXdnNEhSL2l1SVZyMVB2S3ZZcUJ3LzhHQ1g4QjhyVzM0SWFHeHlIRGN4UG9kUHJuKy95QnFYSVphMm1IZDBkempOQjB4UytVYU5WZS9GamdLM1hnbE1aSkdjWldyT0NxbDQrYmpCa1NJTkVXUHV5Y3BoL1hxRFVmVStMTDJFTEsxRUo5OUprTUd3ZEdzRDBOR0FjZTIrclpPQTVNNUk4aDE4R040aEt1MUdZPSIsImtleV92ZXJzaW9uIjoxfQ==
immutable: true
...
7-3. PKI Secrets(수정중)
▶ 개요
- 운영자가 단일 Vault PKI 시크릿을 단일 Kubernetes 시크릿으로 동기화하는 데 필요한 구성입니다.
- Ingress, mTLS, Webapp 등 PKI 인증서를 사용하는 애플리케이션에서 동적으로 변경된 PKI 인증서를 사용할 수 있도록 지원합니다.
▶ 실습
Step1. Vault PKI 시크릿엔진 사용을 위한 설정
1. PKI 엔진 활성화(마운트)
kubectl exec --stdin=true --tty=true vault-0 -n vault -- /bin/sh
vault secrets enable -path=pki pki
- Vault에 PKI Secrets Engine을 pki라는 사용자 정의 경로로 마운트합니다.
2. Root CA(Common Name) 인증서 생성(발급)
vault write pki/root/generate/internal \
common_name="hashicorp.local" \
ttl=87600h
- Vault 자체가 루트 인증기관(CA)이 됨
- Root CA 인증서와 개인키를 Vault 내부에 생성·보관 - 10년 TTL
- PEM 형식으로 루트 인증서 반환됨 (출력 포함)
- 공개 인증서/CRL URL도 함께 설정해야 함 (config/urls 경로 설정 생략됨 – 권장 추가)
3. Issuing Cert & CRL Distribution URL 설정
vault write pki/config/urls \
issuing_certificates="http://vault.vault.svc:8200/v1/pki/ca" \
crl_distribution_points="http://vault.vault.svc:8200/v1/pki/crl"
- Vault가 발급하는 인증서에 포함될 CA/CRL 메타데이터
4. 인증서 발급용 Role 생성
vault write pki/roles/cert-role \
allowed_domains="hashicorp-app.svc.cluster.local" \
allow_subdomains=true \
allow_bare_domains=true \
allow_any_name=true \
enforce_hostnames=false \
max_ttl="24h"
5. 인증서 발급용 Policy 정의
vault policy write pki-policy - <<EOF
path "pki/issue/cert-role" {
capabilities = ["update", "read", "list"]
}
EOF
6. Role 생성과 함께 policy 바인딩
vault write auth/approle/role/cert-role \
token_policies="pki-policy" \
token_ttl="30m" \
token_max_ttl="1h"
7. Role ID / Secret ID 발급 및 K8s Secret에 저장
# exit로 Vault 파드에서 빠져나오기
exit
cd ..
mkdir dynamic-secret
cd dynamic-secret
# Local에서 실행
vault read -field=role_id auth/approle/role/cert-role/role-id > role_id.txt
vault write -f -field=secret_id auth/approle/role/cert-role/secret-id > secret_id.txt
# 확인
cat role_id.txt
cat secret_id.txt
Step2. VSO의 CR(Custom Resource) 배포
1. webapp 네임스페이스 생성
kubectl create ns webapp
2. AppRole Secret 생성
kubectl create secret generic vso-cert-role \
-n webapp \
--from-file=role_id=role_id.txt \
--from-file=id=secret_id.txt
3. VaultAuth CR 생성
kubectl create -f - <<EOF
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: pki-auth
namespace: webapp
spec:
method: appRole
mount: approle
appRole:
roleId: <Role ID>
secretRef: vso-cert-role
EOF
4. VaultPKISecret CR 생성
# 1. vault agent 역할. 사용자를 대행하여 vault에 주기적 요청을 통해 인증서를 갱신
# 2. 인증서를 사용하는 devployment에 대해 롤 아웃 수행하여 바뀐 인증서 정보 반영
kubectl create -f - <<EOF
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultPKISecret
metadata:
name: hashicorp-tls
namespace: webapp
spec:
vaultAuthRef: pki-auth
mount: pki
role: cert-role
commonName: hashicorp-app.svc.cluster.local
ttl: 1m
destination:
create: true
name: vso-pki-cert
rolloutRestartTargets:
- kind: Deployment
name: nginx-vault-ssl
EOF
5. 인증서 갱신 동작 로직의 이해
☞ VSO는 VaultPKISecret 리소스를 watch 하면서 아래 조건을 기준으로 자동 갱신 시점을 판단합니다:
- 인증서의 NotAfter (만료일) 확인
- 현재 시간과 비교해서 남은 시간이 ⅓ 미만일 때부터 재발급 트리거
- 단, 실제 갱신은 Controller의 reconcile 주기에 따라 수 초 단위로 오차가 있을 수 있음

Step3. 생성된 인증서 확인
kubectl get secret vso-pki-cert -n webapp -o json | jq -r .data._raw | base64 -d
# view-secret 플러그인
kubectl view-secret -n webapp vso-pki-cert --all
# 만료기간 확인 Tip (MacOS 기준)
kubectl view-secret -n webapp vso-pki-cert expiration
1743932886
# date 명령으로 확인
date -r 1743948834
Sun Apr 6 18:45:08 KST 2025
Step4. 검증을 위한 서비스(Nginx) 배포
기능 검증 수행용 nginx 배포
- 구성파일 상세
- ConfigMap : nginx의 config 저장
- Deployment : TLS가 enable된 Nginx deployment
- initContainer : 인증서 갱신정보(만료일)을 화면에 표기할 웰컴페이지 수정(index.html)
- secret mount : CR이 생성한 인증서가 담긴 secret 서비스내에 마운트
- Service : nginx 서비스의 추상화된 진입점
kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-ssl-config
namespace: webapp
data:
nginx.conf: |
events {}
http {
server {
listen 443 ssl;
server_name localhost;
ssl_certificate /etc/nginx/tls/certificate;
ssl_certificate_key /etc/nginx/tls/private_key;
location / {
root /usr/share/nginx/html;
index index.html;
}
}
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-vault-ssl
namespace: webapp
spec:
replicas: 1
minReadySeconds: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
selector:
matchLabels:
app: nginx-vault
template:
metadata:
labels:
app: nginx-vault
spec:
initContainers:
- name: generate-expiration-html
image: alpine:latest
command: ["/bin/sh", "-c"]
args:
- |
apk add --no-cache openssl > /dev/null
expiry=\$(openssl x509 -enddate -noout -in /certs/certificate | cut -d= -f2)
echo "<html><head><meta http-equiv='refresh' content='10'></head><body><h1>Certificate Expiration: \$expiry</h1></body></html>" > /output/index.html
volumeMounts:
- name: tls
mountPath: /certs
readOnly: true
- name: html
mountPath: /output
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 443
volumeMounts:
- name: tls
mountPath: /etc/nginx/tls
readOnly: true
- name: html
mountPath: /usr/share/nginx/html
- name: nginx-conf
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
volumes:
- name: tls
secret:
secretName: vso-pki-cert
- name: html
emptyDir: {}
- name: nginx-conf
configMap:
name: nginx-ssl-config
---
apiVersion: v1
kind: Service
metadata:
name: nginx-vault-service
namespace: webapp
spec:
selector:
app: nginx-vault
ports:
- protocol: TCP
port: 443
targetPort: 443
nodePort: 30004
type: NodePort
EOF
Step5. 서비스 배포확인
Step6. Ingress 연동을 통한 TCP Passthrough → 추후 업데이트 예정
▶ 추가 학습 자료
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: vault-issuer
namespace: sandbox
spec:
vault:
path: pki_int/sign/example-dot-com
server: https://vault.local
caBundle: <base64 encoded CA Bundle PEM file>
auth:
...




[ 마무리 ]
Hashcorp Vault 라는 솔루션을 통해서 k8s 에서 사용하는 secret 을 중앙집중식으로 동적으로 관리할 수 있는 방법에 대해 체험해 볼 수 있는 좋은 기회였던 것 같다. 중간 중간 환경설정이나 진행 상 오류가 있어 애를 먹었던 것 같다. 보안 인증의 관리 효율화 측면에서 Vault 같은 자동화 관리 툴의 도입을 검토해 보면 좋을 것 같다.
[ 참고 링크 모음 ]
[ 사전 지식 ]
☞ 암호(대칭키/비대칭키), 전자서명, PKI(X.509) 등 이해 필요**(출처: 가시다님 6주차)**
- 정보 보안 3요소 : 기밀성(암호화, 액세스 제어..), 무결성(무단 변조 방지), 가용성 - Link
- 액세스 제어(AAA) : 인증(Who?), 인가(What?), 감사 - Link
[ 하시코프 Vault 관련 영상 ]
☞ Introduction to HashiCorp Vault with Armon Dadgar : Link
☞ Vault 통합 및 활용 사례 : Youtube
[ 환경 설치 관련 ]
☞ Ubuntu 에서 hashicorp vault-CLI 설치하기 : Blog
'AEWS' 카테고리의 다른 글
AEWS 12주차 - Amazon VPC Lattice for Amazon EKS (0) | 2025.04.21 |
---|---|
AEWS 11주차 - ML Infra(GPU) on EKS (0) | 2025.04.18 |
AEWS 9주차 - EKS Upgrade [AWS Workshop] (0) | 2025.04.01 |
AEWS 8주차 - K8S CI/CD (0) | 2025.03.25 |
AEWS 7주차 - EKS Mode/Nodes (Fargate, Auto-mode) (0) | 2025.03.17 |