-
[AEWS 3기] 10주차 - K8S 시크릿 관리AWS 2025. 4. 12. 22:20
- 실습환경 - Kind k8s cluster & Jenkins & ArgoCD
0.1. kind 로 k8s 배포 : macOS 사용자 → 필수
- 기본 정보 확인
# 클러스터 배포 전 확인 docker ps # Create a cluster with kind MyIP=<각자 자신의 PC IP> MyIP=192.168.254.127 # cicd-labs 디렉터리에서 아래 파일 작성 cat > kind-3node.yaml <<EOF kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 networking: apiServerAddress: "127.0.0.1" # $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: 30005 - containerPort: 30006 hostPort: 30006 - role: worker - role: worker EOF KUBECONFIG=$PWD/kubeconfig 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 파일 확인 cat ~/.kube/config- (참고) 클러스터 삭제
# 클러스터 삭제 kind delete cluster --name myk8s docker ps cat ~/.kube/config0.2. 컨테이터 (Jenkins) : 호스트 OS 포트 노출(expose)로 접속 및 사용 : macOS 사용자 → 필수
- Jenkins 컨테이너 기동
# 작업 디렉토리 생성 후 이동 mkdir cicd-labs cd cicd-labs # cicd-labs 작업 디렉토리 IDE(VSCODE 등)로 열어두기 code . # cat <<EOT > docker-compose.yaml services: jenkins: container_name: jenkins image: jenkins/jenkins restart: unless-stopped networks: - cicd-network ports: - "8080:8080" - "50000:50000" volumes: - /var/run/docker.sock:/var/run/docker.sock - jenkins_home:/var/jenkins_home volumes: jenkins_home: networks: cicd-network: driver: bridge EOT # 배포 docker compose up -d docker compose ps # 기본 정보 확인 for i in jenkins ; do echo ">> container : $i <<"; docker compose exec $i sh -c "whoami && pwd"; echo; done # 도커를 이용하여 각 컨테이너로 접속 docker compose exec jenkins bash exit- Jenkins 컨테이너 초기 설정
# Jenkins 초기 암호 확인 docker compose exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword 5ecee51cb8694adeb1c77aee0af065df # Jenkins 웹 접속 주소 확인 : 계정 / 암호 입력 >> admin / qwe123 open "http://127.0.0.1:8080" # macOS 웹 브라우저에서 http://127.0.0.1:8080 접속 # Windows # (참고) 로그 확인 : 플러그인 설치 과정 확인 docker compose logs jenkins -f # Jenkins URL http://192.168.0.10:8080/- Jenkins URL 설정 : 각자
자신의 PC의 IP를 입력
(참고) 실습 완료 후 해당 컨테이너 중지 상태로 둘 경우 → 재부팅 및 이후에 다시 실습을 위해 컨테이너 시작 시‣(참고) 특정 컨테이너만 삭제 후 다시 초기화 상태로 기동 시‣- (참고) 모든 실습 후 삭제 시 :
docker compose down --volumes --remove-orphans
0.3. ArgoCD 설치
0.3.1. ArgoCD 이전 스터디 내용‣0.3.2. Argo CD 설치 및 기본 설정 - helm_chart‣- 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 접속- Argo CD 웹 접속 확인
- User info → UPDATE PASSWORD 로 admin 계정 암호 변경 (qwe12345)
- 기본 정보 확인 (Settings) : Clusters, Projects, Accounts
- Vault 개요
1.1. Vault란? - Docs
HashiCorp Vault는 신원 기반(identity-based)의 시크릿 및 암호화 관리 시스템입니다. 이 시스템은 인증(authentication) 및 인가(authorization) 방법을 통해 암호화 서비스를 제공하여 비밀에 대한 안전하고 감사 가능하며 제한된 접근을 보장합니다.
시크릿(Secret)이란 접근을 철저히 통제하고자 하는 모든 것을 의미하며, 예를 들어 토큰, API 키, 비밀번호, 암호화 키 또는 인증서 등이 이에 해당합니다. Vault는 모든 시크릿에 대해 통합된 인터페이스를 제공하면서, 엄격한 접근 제어와 상세한 감사 로그 기록 기능을 제공합니다.
외부 서비스용 API 키, 서비스 지향 아키텍처 간 통신을 위한 자격 증명 등은 플랫폼에 따라 누가 어떤 비밀에 접근했는지를 파악하기 어려울 수 있습니다. 여기에 키 롤링(교체), 안전한 저장, 상세한 감사 로그까지 추가하려면 별도의 커스텀 솔루션 없이는 거의 불가능합니다. Vault는 바로 이 지점에서 해결책을 제공합니다.
Vault는 클라이언트(사용자, 기계, 애플리케이션 등)를 검증하고 인가한 후에만 비밀이나 저장된 민감한 데이터에 접근할 수 있도록 합니다.
관련 이미지 및 영상‣1.2. 대표적인 시크릿의 종류
- 비밀번호
- Cloud Credentials : AWS, GCP, Azure, NCP
- Database Credentials : MySQL,
- SSH Key
- Token, API Key : GitHub, Telegram, Slack, OpenAI, Claude
- 인증서(PKI, TLS 등)
그 밖에 어떤 대표적인 Secret이 있을까요?🤔1.3. Vault의 동작방식? - Docs
Vault는 주로 토큰(Token)을 기반으로 작동하며, 이 토큰은 클라이언트의 정책(Policy)과 연결되어 있습니다. 각 정책은 경로(path) 기반으로 설정되며, 정책 규칙은 클라이언트가 해당 경로에서 수행할 수 있는 작업과 접근 가능성을 제한합니다.
Vault에서는 토큰을 수동으로 생성해 클라이언트에 할당할 수도 있고, 클라이언트가 로그인하여 토큰을 직접 획득할 수도 있습니다.
- 아래 그림은 Vault의 핵심 워크플로우를 보여줍니다.
Vault의 핵심 워크플로우는 다음 네 단계로 구성됩니다:
- 인증 (Authenticate): Vault에서 인증은 클라이언트가 Vault에 자신이 누구인지 증명할 수 있는 정보를 제공하는 과정입니다. 클라이언트가 인증 메서드를 통해 인증되면, 토큰이 생성되고 정책과 연결됩니다.
- 검증 (Validation): Vault는 Github, LDAP, AppRole 등과 같은 신뢰할 수 있는 외부 소스를 통해 클라이언트를 검증합니다.
- 인가 (Authorize): 클라이언트는 Vault의 보안 정책과 비교됩니다. 이 정책은 Vault 토큰을 사용하여 클라이언트가 접근할 수 있는 API 엔드포인트를 정의하는 규칙의 집합입니다. 정책은 Vault 내 특정 경로나 작업에 대한 접근을 허용하거나 거부하는 선언적 방식으로 권한을 제어합니다.
- 접근 (Access): Vault는 클라이언트의 신원에 연관된 정책을 기반으로 토큰을 발급하여 비밀, 키, 암호화 기능 등에 대한 접근을 허용합니다. 클라이언트는 이후 작업에서 해당 Vault 토큰을 사용할 수 있습니다.
1.4. 왜 Vault가 필요한가요?
오늘날 대부분의 기업은 자격 증명이 조직 전반에 걸쳐 무분별하게 퍼져 있습니다. 비밀번호, API 키, 자격 증명 등이 일반 텍스트로 앱 소스 코드, 설정 파일, 기타 여러 위치에 저장되어 있습니다. 자격 증명이 이처럼 여기저기 흩어져 있으면 누가 무엇에 접근하고 권한이 있는지를 명확히 파악하기 어렵고, 그로 인해 큰 부담이 따릅니다. 일반 텍스트로 자격 증명을 저장하면 내부 공격자든 외부 공격자든 악의적인 공격 가능성이 크게 증가합니다.
Vault는 이러한 문제를 해결하기 위해 설계되었습니다. 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는 비밀 폐기를 기본적으로 지원합니다. 단일 비밀뿐만 아니라 특정 사용자에 의해 읽힌 모든 비밀, 또는 특정 유형의 모든 비밀 등 비밀의 계층 구조 전체를 폐기할 수 있습니다. 이 기능은 키 롤링이나 침입 발생 시 시스템을 신속하게 차단하는 데 유용합니다.
1.5. 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을 추적하고 관리할 수 있도록 합니다.오픈소스추가 기능과 지원을 제공하는 엔터프라이즈 버전과 함께 오픈 소스 도구로 사용 가능1.6. 시크릿을 관리할 수 있는 여러 방법
- private 망에 있는 각 서버들의
.evn에 환경변수로 저장하여 관리 - server마다 환경변수 세팅하여 관리해야하는 번거로움
- FE에서는 server가 없으므로 server 환경변수로는 관리 어려움
- 정보의 파편화가 올라감
- Jenkins, Github, Dockerhub 등의 Credentials에 등록하여 관리
- github이나 dockerhub는 repository 종속적이고, 같은 정보를 repository마다 각각 세팅하여 관리해야 함
- 정보의 파편화가 올라감
- Jenkins Config file Provider 활용
- jenkins에서 build시에 주입할 수 있는 장점
- audit log를 관리하기 어려움
- Secret Management System 사용하여 관리
- 비용, 사용방법, 다양한 언어 제공 등의 학습 비용 발생
- 초기 진입 허들
1.7. Vault 동작 방식 이해하기
호텔 체크인 절차에 비유한 Vault의 동작방식 이해
- 개별 ID 인증/인가를 통해 필요한 자격 증명을 동적을 발급
2.1. 실습환경 구성
Step1. Helm을 사용한 Vault 배포 -‣- 네임스페이스 생성 및 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 Chartcat <<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 disable_mlock = true cluster_name = "vault-local" listener "tcp" { address = "[::]:8200" cluster_address = "[::]:8201" tls_disable = 1 } storage "raft" { # Raft 구성 권장 path = "/vault/data" node_id = "vault-dev-node-1" } service: enabled: true type: NodePort port: 8200 targetPort: 8200 nodePort: 30000 # Kind에서 열어둔 포트 중 하나 사용 injector: enabled: true ui: enabled: true serviceType: "NodePort" 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 14s pod/vault-agent-injector-56459c7545-sgtth 1/1 Running 0 15s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/vault NodePort 10.96.120.103 <none> 8200:30000/TCP,8201:32281/TCP 15s service/vault-agent-injector-svc ClusterIP 10.96.249.165 <none> 443/TCP 15s service/vault-internal ClusterIP None <none> 8200/TCP,8201/TCP 15s service/vault-ui NodePort 10.96.105.103 <none> 8200:31916/TCP 15s NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE persistentvolumeclaim/data-vault-0 Bound pvc-c6dcf24c-acdb-4b13-b1c2-064207a206fb 10Gi RWO standard <unset> 15sStep2. Vault 초기화 및 잠금해제‣- 상태확인
# Vault Status 명령으로 Sealed 상태확인 kubectl exec -ti vault-0 -- vault statusinit-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.shvault 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 raft Cluster Name vault-local Cluster ID 90fbefc2-3218-c047-43de-23a11223d869 Removed From Cluster false HA Enabled true HA Cluster https://vault-0.vault-internal:8201 HA Mode active Active Since 2025-04-12T05:19:35.118981221Z Raft Committed Index 37 Raft Applied Index 37(참고) UI에 접속해서 Unseal Key 입력도 가능
이제 Unseal이 마무리 되었으므로 Root Token 값을 사용하여 UI에 접속할 수 있습니다.
- Root Token 입력 후 Vault UI 화면 -
vault-root-token.txt파일에서 획득
open "http://$MyIP:30000" #token은 앞에서 unseal을 통해 획득한 root token을 입력!! hvs.GdQtl6yNEcSeeDSGXCXVQRzxbrew tap hashicorp/tap brew install hashicorp/tap/vault vault --version # 설치 확인 Vault v1.19.1 (aa75903ec499b2236da9e7bbbfeb7fd16fa4fd9d), built 2025-04-02T15:43:01Z # 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"]2.2. 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 demoStep2. Vault UI : [Secrets Engine] 탭에 접속 후 [sampleapp - config] 접속하여 실제 저장된 Key / Value 확인‣- username : demo
- password : p@ssw0rd
Step3. (참고) 경로 확인하는 명령 가이드‣# get kv secret with CLI vault kv get -mount="secret" "sampleapp/config" ======== Secret Path ======== secret/data/sampleapp/config ======= Metadata ======= Key Value --- ----- created_time 2025-04-12T05:28:41.258214793Z custom_metadata <nil> deletion_time n/a destroyed false version 1 ====== Data ====== Key Value --- ----- password p@ssw0rd username demo- Vault Agent Sidecar 패턴을 이해하고, 앱(Pod)이 시크릿을 볼륨으로 마운트하여 사용하도록 구성한다.
- Vault Agent Injector 구성을 위해 Helm Chart(
) 에서 활성화 해야하며, 을 활용한다.
3.1. 공식문서 요약(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"3.2.
Vault Kubernetes Sidecar 아키텍처 및 워크플로우이미지 출처:
이미지 출처:
Vault Agent 동작방식
Vault - Kubernetes 연동시 동작흐름(출처:
)3.3. 실습
Step1. Vault AppRole 방식 인증 구성 - AppRole 인증이란?‣- 인증 구성 및 정책 적용
# 1. AppRole 인증 방식 활성화 vault auth enable approle || echo "AppRole already enabled" vault auth list Path Type Accessor Description Version ---- ---- -------- ----------- ------- approle/ approle auth_approle_45429d98 n/a n/a token/ token auth_token_0e40cfed token based credentials n/a # 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" ROLE_ID: b49cb2f2-ff99-7e31-2bb7-a3c63ce7cd5d SECRET_ID: 2b650c0b-d68a-6f5e-931b-dca81ae26684 # 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) - HCL2. 샘플 애플리케이션 + Sidecar 배포(수동방식)‣3. SVC 생성‣4. 생성된 컨테이너 확인‣5. 실제 배포된 화면 확인‣6. KV 값 변경 후 확인‣(참고) Annotation을 활용한 Vault Sidecar Injection - Docs‣3.4. 📖 추가 학습자료
Vault Proxy는 애플리케이션이 Vault와 통합하는 초기 진입 장벽을 낮추고, 더 확장 가능하고 단순한 방식을 제공하기 위해 만들어졌습니다. Vault Proxy는 Vault의 API 프록시 역할을 하며, 클라이언트가 자동 인증된 토큰을 사용하도록 허용하거나 강제할 수 있습니다.
Vault Proxy는 클라이언트 데몬으로 다음과 같은 기능을 제공합니다:
- Auto-Auth: Vault에 자동으로 인증하고, 로컬에서 가져온 동적 비밀의 토큰 갱신 프로세스를 관리합니다.
- API Proxy: Vault의 API에 대한 프록시 역할을 하며, Auto-Auth 토큰을 선택적으로 또는 강제로 사용하게 할 수 있습니다.
- 캐싱(Caching): 새로 생성된 토큰이나 이 토큰으로부터 생성된 리스 비밀(leased secrets)에 대한 응답을 클라이언트 측에서 캐싱할 수 있도록 합니다. 또한, 에이전트는 캐시된 토큰과 리스의 갱신도 관리합니다.
CapabilityVault AgentVault ProxyAuto-auth✅✅Caching✅✅Templating✅❌API proxyWill be deprecated✅Run as a Windows Service✅❌✅❌- Jenkins + Vault(AppRole) - CI
- Vault KV Store에 저장한 username, password을 Jenkins을 활용해서 획득하는 방안
- CI 파이프라인에서 정적(Static) 시크릿을 외부에 저장하고 관리할 경우 사용할 수 있습니다.
4.1. 🔐 CI/CD 보안 고려사항
최근 CI/CD의 공격사례(CVE-2025-30066) : GitHub Action‣tj-actions/changed-files공격4.2.
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에서 시크릿을 가져옴
4.3. 실습
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.txtsecret_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-f6318843a514Step3. Jenkins에서 Vault 설정 및 Credentials 추가‣- Jenkins UI(
admi/qwe123) → Manage Jenkins → Configure System

- 스크롤 하단의 Vault Plugin Configuration 섹션으로 이동
- Vault URL 입력 후 [Add] 버튼 클릭 : IP입력할것!

- 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.10:30000' // 실제 Vault 주소로 변경!!! } stages { stage('Read Vault Secret') { steps { withVault([ vaultSecrets: [ [ path: 'secret/sampleapp/config', // version2는 data path가 없음!! 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 블록
상황사용 방식sh 블록$USERNAME, $PASSWORDscript 블록${env.USERNAME}, ${env.PASSWORD}- ArgoCD + Vault Plugin (Kubernetes Auth/AppRole) - CD
📖 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를 추가하여 플러그인을 다운로드하고 설정합니다.
- 또는 플러그인이 사전 설치된 커스텀 이미지를 생성하여 사용합니다.
- 사이드카 컨테이너를 통한 설치:
- 사이드카 컨테이너를 추가하여 플러그인과 필요한 도구들을 포함시킵니다.
- 또는 플러그인이 사전 설치된 커스텀 사이드카 이미지를 생성하여 사용합니다.
이 중 사이드카 컨테이너를 활용한 방법은 Argo CD v2.4.0부터 도입된 최신 방식으로, 보안성과 유지보수 측면에서 권장됩니다.
5.1. 실습
Step 1. ArgoCD Vault Plugin을 위한 Credentials 활성화 - AppRole 인증‣- 이전 실습에서 획득한 Role_ID, Secret_ID활용
echo "ROLE_ID: $ROLE_ID" echo "SECRET_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: b49cb2f2-ff99-7e31-2bb7-a3c63ce7cd5d #Role_ID AVP_SECRET_ID: 1114778c-014d-b61f-5afd-d137a1a48484 #Secret_ID EOFStep 2.‣ArgoCD Vault Plugin 설치- Blog참고사항‣- 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/argocdStep 3. 샘플 Application 배포하여 Vault와 동기화‣- (참고)
luafanti/spring-boot-debug-app:main이미지는 amd64 CPU만 가능 (arm CPU는 기동 실패) - Link
Step 3-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 EOFStep 3-2) Application 배포시 참조하는‣new-values.yaml확인 - GitHubserviceAccount: 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"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 application에서 deployment live manifest 정보 spec: containers: - env: - name: LOG_LEVEL_SERVICE value: info - name: LOG_LEVEL_SPRING value: info - name: JSON_LOGS_ENABLED value: 'false' - name: VAULT_SECRET_USER value: demo-1 - name: VAULT_SECRET_PASSWORD value: p@ssw0rd

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

6. Vault Secrets Operator
6.1. VSO란? - Docs
Vault Secrets Operator(VSO)는 Kubernetes Secrets에서 Vault secrets 및 HCP Vault Secrets Apps를 네이티브하게 사용할 수 있도록 Pods에 제공해줍니다.
개요‣Vault Secrets Operator는 지원하는 Custom Resource Definitions(CRD) 집합의 변경 사항을 감시하여 작동합니다. 각 CRD는 시크릿의 지원되는 소스 중 하나에서 Kubernetes Secret으로 동기화할 수 있도록 필요한 사양을 제공합니다.
오퍼레이터는 소스 시크릿 데이터를 대상 Kubernetes Secret에 직접 작성하며, 소스에 변경 사항이 발생할 경우 해당 내용을 대상에도 수명 주기 동안 지속적으로 반영합니다. 이렇게 함으로써 애플리케이션은 대상 시크릿에만 접근하면 그 안의 시크릿 데이터를 사용할 수 있습니다.
기능‣Vault Secrets Operator가 지원하는 주요 기능은 다음과 같습니다:
- 여러 시크릿 소스로부터의 동기화 지원
- 자동 시크릿 일탈 감지 및 수정
- Deployment, ReplicaSet, StatefulSet Kubernetes 리소스 유형에 대한 자동 시크릿 교체
- 오퍼레이터 모니터링을 위한 Prometheus 전용 계측 지원
- Helm 또는 Kustomize를 통한 설치 지원
- 시크릿 데이터 변환 지원
지원되는 Kubernetes 배포판‣Vault Secrets Operator는 다음과 같은 호스팅된 Kubernetes 환경에서 성공적으로 테스트되었습니다:
- Amazon Elastic Kubernetes Service (EKS)
- Google Kubernetes Engine (GKE)
- Microsoft Azure Kubernetes Service (AKS)
- Red Hat OpenShift (공식 인증됨)
유사한 오픈소스 프로젝트는? - ESO(External Secrets Operator)‣5.2. VSO 구성도 - 개발자(Developer) / 운영자(Operator)의 역할
Vault Secrets Operator(출처: )
5.3. VSO - Vault 매핑구조
5.4. VSO 주요 CRD 정보 및 예시
VSO CRD 관계도‣--- 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--- 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: pki15.6. VSO Dynamic Secrets
- Vault의 Dynamic Secret Engine을 활용하여 동적으로 변경되는 Secrets을 K8s Secrets에 동기화
- 지원되는 시크릿 엔진: DB Credentials, Cloud Credentials(AWS, Azure, GCP 등)
5.6.1. 시나리오
- 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"]Values 구성요소 분석‣defaultVaultConnection: 기본 Vault 연결 설정을 구성address: vault.vault.svc.cluster.local:8200 (Helm으로 설치된 Vault의 ClusterIP)skipTLSVerify: false: TLS 검증 비활성화 안 함 (Vault에 TLS 적용된 경우 주의 필요)- clientCache 설정
persistenceModel: direct-encrypted: 암호화된 데이터를 직접 저장storageEncryption.enabled: true: 암호화 기능 활성화transitMount: demo-transit: 앞에서 Vault에 만든 Transit 엔진 mount pathkeyName: vso-client-cache: Transit에서 사용하는 암호화 키mount: k8s-auth-mount: Kubernetes 인증 방식에서 사용할 auth pathkubernetes.role: auth-role-operator 라는 Role에 지정된 정책으로 암복호화 가능serviceAccount: VSO가 사용하는 ServiceAccount 이름tokenAudiences: Vault 서버에서 사용하는 audience 설정- 위와 같은 설정으로 VSO는 Vault에 인증된 후, 동적/정적 시크릿을 Kubernetes 리소스로 가져오고, 가져온 시크릿 데이터를 자체적으로 암호화해 캐시 가능
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-ds5jc 2/2 Running 0 11m 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: 5268734382662717808 (0x491e4e018e7f9170) Signature Algorithm: sha256WithRSAEncryption Issuer: CN=kubernetes ... # vault 서비스 어카운트의 token 확인 kubectl exec -it vault-0 -n vault -- cat /var/run/secrets/kubernetes.io/serviceaccount/token eyJhbGciOiJSUzI1NiIsImtpZCI6IlhQM2UxWmlFX2tqWEVzN3FqMXhscHJ1VFB2cVRUcDd0czliZTlsNlF4SWcifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzc1OTgyNTM5LCJpYXQiOjE3NDQ0NDY1MzksImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiNDU3NzE3ODMtOTE3YS00OTQyLWFmYTAtMWY1ZmFlN2YxOWVkIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJ2YXVsdCIsIm5vZGUiOnsibmFtZSI6Im15azhzLXdvcmtlcjIiLCJ1aWQiOiI0YjQ0ZDgyMS1hM2UzLTQ4NzQtOTFhYi01MDQ2NjYxNjdkMjAifSwicG9kIjp7Im5hbWUiOiJ2YXVsdC0wIiwidWlkIjoiMjYyZTJlNGQtMjAxYy00YWU3LWEyZDktZTZiMDdmMDNmM2Y1In0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJ2YXVsdCIsInVpZCI6ImQ5MGYzMWI3LTIwYjYtNGNiNy04NjFlLWI4MDhhYjg5YWY2MyJ9LCJ3YXJuYWZ0ZXIiOjE3NDQ0NTAxNDZ9LCJuYmYiOjE3NDQ0NDY1MzksInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDp2YXVsdDp2YXVsdCJ9.tSEOCNGIge6tVmoNotiRnu2A77Bu76udz3uxYNEEMqjwM0aa4UnTaPrjFj_QCFKZ67Ebwwmy32AFAWKxTrf4w-un05k_Lz6huUMi7HtzoKQVwVlt6polMfAytu77ZbQ9PBMtTG7m_BR8Vc-KX7Y4CTT3cTUAbgp3M5uEsK07KQ9Pp0PIyO88ZW8eR50NihlAgNZdPVe7xSIaazJorE8z0QZ7amXi71RYc-qthStgiMXS8nLGGdkd6NzywNuDGOvAtD-6krNT9WS1qWMnmw_oDHY0gGEEMFuLIkkjJDVYsNJmWOJo-5WzD_XXPhz808msKrBgivpyWt842oGyC_rL5w # https://jwt.io/ 에서 token 디코드 확인 시 PAYLOAD 부분 { "aud": [ "https://kubernetes.default.svc.cluster.local" ], "exp": 1775982539, "iat": 1744446539, "iss": "https://kubernetes.default.svc.cluster.local", "jti": "45771783-917a-4942-afa0-1f5fae7f19ed", "kubernetes.io": { "namespace": "vault", "node": { "name": "myk8s-worker2", "uid": "4b44d821-a3e3-4874-91ab-504666167d20" }, "pod": { "name": "vault-0", "uid": "262e2e4d-201c-4ae7-a2d9-e6b07f03f3f5" }, "serviceaccount": { "name": "vault", "uid": "d90f31b7-20b6-4cb7-861e-b808ab89af63" }, "warnafter": 1744450146 }, "nbf": 1744446539, "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_45429d98 n/a n/a k8s-auth-mount/ kubernetes auth_kubernetes_b6937d5c n/a n/a token/ token auth_token_0e40cfed 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----- MIIDBTCCAe2gAwIBAgIISR5OAY5/kXAwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE AxMKa3ViZXJuZXRlczAeFw0yNTA0MTIwNDM4MTBaFw0zNTA0MTAwNDQzMTBaMBUx EzARBgNVBAMTCmt1YmVybmV0ZXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK AoIBAQC1OHyP0oqdQ+aTC7WMaCx7BfWN8/elTzEq3PeFAwP8GdliFVJnCXHF7kfk 91YDTUZDsTO0Sg1Wg2N5B0KI9Bn2fzni5SK+WpCmtDHomeg3ejDRKxZlR6NN3yHe 2b8SBsks8iqOa4x1RQkYLekW0V4Rb39hdtddSAdwinIcRGZZ2npGTsxOFcBx1H1J Z1bP5XzBB3oT72FXvD0YVPLpovuSYM1gUhfNDp/ciiCmRqugwkz1e+BJy8PSvwPh UedHX1mXoQJE7f9b7JE0MOd0JqOnMVh6SgyP9Np2i51TB/kw4qS6RwQql2WTUyBw /UNCp+M1icctg+KdhpIr61eu6hdlAgMBAAGjWTBXMA4GA1UdDwEB/wQEAwICpDAP BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRGCrk8aIWMI0GhAjekQN28AZZNSzAV BgNVHREEDjAMggprdWJlcm5ldGVzMA0GCSqGSIb3DQEBCwUAA4IBAQBSKsX17U7M Tcle6jcpAD+10Tyan3gwuQRYEDx8PxI2cDcsnJKahRI3j94bRGQM0KgIabc9ngGp oZv8i0UICoZJzikP8Mo8f2J0iiCR3G+yicVeHxC5Imdfdpuki8iHvncT8knoD36x njpWF8BIIn5TwYkwZ8V9OA1vbRCjS/wo8K31MlSdUq0L6YIjkJB8Ng4no4lIhLgT asY8eMWr7WNF/uv1LPAonCix6zJn3uYOz8r0mf+6Le4JVS8RR4guXs+19Nk94ZuC 758ubiAYE77tCtPYIxje5YDnsLyuigQ1t+8eIHTDYAF4J/s0/lKzVK9arpqYqUER mivt2SPk260L -----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"] } EOFStep7. 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 Dynamic Secret을 사용하는 경우 클라이언트 캐시를 영구적으로 저장하고 암호화하는 것이 좋습니다. ## 이렇게 하면 재시작 및 업그레이드를 통해 Dynamic Secret 임대가 유지됩니다. 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-operatorStep8. 샘플 애플리케이션 YAML 작성 및 배포‣demo-ns 네임스페이스 생성‣kubectl create ns demo-ns mkdir vso-dynamic cd vso-dynamic‣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‣app-secret.yaml:- Spring App에서 PostgreSQL에 접속할 때 사용할 해당 시크릿에 username/password을 동적으로 생성
--- # app-secret.yaml apiVersion: v1 kind: Secret metadata: name: vso-db-demo namespace: demo-ns‣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‣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는postgresStep10. 동작확인(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-559494db6f-jw568 1/1 Running 0 42s _raw='{"password":"fv6wQbr7O-VpbVHAt2nO","username":"v-k8s-auth-dev-post-BfAGdVGRKRuriTjwnzVm-1744451233"}' password='fv6wQbr7O-VpbVHAt2nO' username='v-k8s-auth-dev-post-BfAGdVGRKRuriTjwnzVm-1744451233' Sat Apr 12 18:47:55 KST 2025 NAME READY STATUS RESTARTS AGE vaultdemo-559494db6f-jw568 1/1 Running 0 43s _raw='{"password":"fv6wQbr7O-VpbVHAt2nO","username":"v-k8s-auth-dev-post-BfAGdVGRKRuriTjwnzVm-1744451233"}' password='fv6wQbr7O-VpbVHAt2nO' username='v-k8s-auth-dev-post-BfAGdVGRKRuriTjwnzVm-1744451233' Sat Apr 12 18:47:56 KST 2025 NAME READY STATUS RESTARTS AGE vaultdemo-559494db6f-jw568 1/1 Running 0 45s vaultdemo-65f9fc7c7-vjrcg 0/1 ContainerCreating 0 1s _raw='{"password":"z--6WK-9OsWHxtEyxjl3","username":"v-k8s-auth-dev-post-jMSezyOc1CxcNag59yky-1744451277"}' password='z--6WK-9OsWHxtEyxjl3' username='v-k8s-auth-dev-post-jMSezyOc1CxcNag59yky-1744451277' Sat Apr 12 18:47:58 KST 2025 ... # 만약 위의 secret이 변경되고 적용을 위해서 pod가 재생성되는 주기를 길게 하고 싶다면, # DB 사용자 동적 생성 Role에서 ttl을 아래와 같이 수정하면 된다. # 해당 Role 사용 시 Vault가 동적으로 사용자 계정과 비밀번호를 생성 하고 TTL마다 자동적으로 갱신하기 떄문에!! # TTL은 생성된 자격증명의 유효 시간 (30초~10분) ## 아래와 같이 수정하면 ## default_ttl="1m" / max_ttl="5m" : 기본 1분만 유효합니다. 갱신을 안 하면 5분 후 자동 만료 (계정도 자동 삭제). 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="5m" # 아래 동작처럼 최대 4분 30초 정도까지 같은 secret을 사용한다. while true; do kubectl get pod -n demo-ns ; echo; kubectl view-secret -n demo-ns vso-db-demo --all; date; sleep 1 ; echo ; done NAME READY STATUS RESTARTS AGE vaultdemo-f7c5d6b7d-rwvqp 1/1 Running 0 4m21s _raw='{"password":"uZwegj2lw-HsIsyE-TeQ","username":"v-k8s-auth-dev-post-coEagFro4iP9WSJNp4Ek-1744450286"}' password='uZwegj2lw-HsIsyE-TeQ' username='v-k8s-auth-dev-post-coEagFro4iP9WSJNp4Ek-1744450286' Sat Apr 12 18:35:47 KST 2025 NAME READY STATUS RESTARTS AGE vaultdemo-f7c5d6b7d-rwvqp 1/1 Running 0 4m22s _raw='{"password":"uZwegj2lw-HsIsyE-TeQ","username":"v-k8s-auth-dev-post-coEagFro4iP9WSJNp4Ek-1744450286"}' password='uZwegj2lw-HsIsyE-TeQ' username='v-k8s-auth-dev-post-coEagFro4iP9WSJNp4Ek-1744450286' Sat Apr 12 18:35:48 KST 2025 NAME READY STATUS RESTARTS AGE vaultdemo-6bff8f9b74-7x5qf 0/1 ContainerCreating 0 1s vaultdemo-f7c5d6b7d-rwvqp 1/1 Running 0 4m23s ... # 실제 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-1kQf61zXMEn84Z1Bf8DX-1744449161 | Password valid until 2025-04-12 09:13:46+00 v-k8s-auth-dev-post-1qw3jyZIEwUzpsY1gNCP-1744449116 | Password valid until 2025-04-12 09:13:01+00 v-k8s-auth-dev-post-22iqnzn5QfoIB2Yybc6h-1744449290 | Password valid until 2025-04-12 09:15:55+00 v-k8s-auth-dev-post-26Ospg823o9u8pMGnkWE-1744449895 | Password valid until 2025-04-12 09:26:00+00 v-k8s-auth-dev-post-5l8roWsv9OFMl7aqNyuC-1744449072 | Password valid until 2025-04-12 09:12:17+00 v-k8s-auth-dev-post-6iEacSDeIhKS3C4EeRlr-1744449853 | Password valid until 2025-04-12 09:25:18+00 v-k8s-auth-dev-post-7rpNbjzsp9MucFjrJ7nI-1744449461 | Password valid until 2025-04-12 09:18:46+00 v-k8s-auth-dev-post-9YeTsR3jWICkc9v9Vl9l-1744449591 | Password valid until 2025-04-12 09:20:56+00 v-k8s-auth-dev-post-9ohPXw3YZlnkgpj827xH-1744450201 | Password valid until 2025-04-12 09:31:06+00 v-k8s-auth-dev-post-AcPWt7isKrKfXNFpaHvm-1744449376 | Password valid until 2025-04-12 09:17:21+00 v-k8s-auth-dev-post-BDdUDrT95AGy2BFfKWkW-1744449940 | Password valid until 2025-04-12 09:26:45+00 v-k8s-auth-dev-post-DkJAhqwOTJGNj8JJjcAO-1744449028 | Password valid until 2025-04-12 09:11:33+00 v-k8s-auth-dev-post-Dz4sZshOxk7ZVAHDhVw4-1744450548 | Password valid until 2025-04-12 09:40:53+00 v-k8s-auth-dev-post-EyTNBS9mEojaBnB0FIXf-1744450242 | Password valid until 2025-04-12 09:31:47+00 v-k8s-auth-dev-post-F2Wf5eQygtSRIdwFLCBA-1744449546 | Password valid until 2025-04-12 09:20:11+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-ds5jc manager {"level":"info","ts":"2025-04-12T08:25:16Z","logger":"initCachingClientFactory","msg":"Initializing the CachingClientFactory"} vault-secrets-operator-controller-manager-7f67cd89fd-ds5jc manager {"level":"info","ts":"2025-04-12T08:25:16Z","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-ds5jc manager {"level":"info","ts":"2025-04-12T09:10:28Z","logger":"lifetimeWatcher","msg":"Starting","id":"b65f02ff-d629-46f9-a86f-081a27fdc2c2","entityID":"80d85315-6023-0e81-a1af-7aa053a1ca44","clientID":"fcbea1e09f9cc64124845f2c898a39a3f6ec30619ede674dc01610ef1395cb05","cacheKey":"kubernetes-996f6ebc5943cecdc4a118"}... # 암호화된 캐시 저장소 확인 : 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-5aca8a85319d25f2b20f24 Opaque 2 31m kubectl get secret -n vault-secrets-operator-system -l app.kubernetes.io/component=client-cache-storage -o yaml ... data: messageMAC: px4SelUKuVElPzlhEFq8nUhm9LdIlyRbZpr0LL2VzVQ= secret: eyJjaXBoZXJ0ZXh0IjoidmF1bHQ6djE6YUFiSHh4Q0ZoTjJrLytRM0ZZUmE4VU5KQXFkUXArTnN1YjFCRFpiMisxUVNZR3IrR0dGdm5VUTVrbFpOTldOamRHL01sV1FFYUdrZExjMUpSdWJRS2tZV2RBb0I3aHdOUFkyaU54cUxIOEhJSGhuTWJGdHhYdXBmR0tnRko0bUlndVFQVXpyaXZpYys2aVVNK1YzZnhyMmtUa1UzZVZIYTZZMEVrbC9xVkR4cUZxMkdaVmVLNXkzRlNsS1h4alhia0JDRW1RQkdyRXY3VTVxTVRNdTlDUHEvSGg3NHNRNlZEOTRPNG5zZk9Jd2dNUVovTVd2RXRuck1RSzdNTW5ZTGxGdGd0VTdsSTdkVnVOd0hsRkNCN0lFdjROazJZeUc0cWFVODQ3VTNUMGY3KzJ6eFd3Y3FUajV1L2txd1c3TmlPbHBuVEduYmNtSVJSemZZekRRMzZzdjl0RG1FdWpFZzdwdExVWEVybXU4L3N1RHcwS08zSlFUTVQrLy9xalEwb1lGaWRPVU56cm1GSjl4L2dRRG03U0FqcXRHQUV1dFdYYTA3MDQzRHcvK1NRUFpiZm5CdjdBYzd2L2ZFSmo4MjloOFBlQnRHUlZPVkZWNXIxUFVsWFBRZVc3MVl3VlZUVzVPZktpdEhPQ3NnODdqTjJ1dGJycHU5M01uOFJILzZMYWZ5cVNQc2FUZlUwYjFJUVdoQmFBTGU5QjQyWHkrdHF5RWNrLzBoUUhuditEK0kxK0l3RHFBR1BvUGdqZEZ6bTZua0FrRWpqZi9paWtBbnpZMk52ZlJJQnQwZHpOTVl1UFpIdHVwRWpydGw2czVDSHpUYVRhZXEya1ArL0c4U2x4V3hTdHRGUnVWMzA1MTZWNEI5TGV3OHlUR0U5SkhvS0gxZkk2SFdURHdZZkRlajloUFZBS1REOXBKMjJ1YkxTc2NiKzBGMTZvdDBQOFh4Yk5YVmd0YzhrSmczZ0g1ZS9MaHdxb25oZkpDNGRQWDBMTmZuTVVncXBQakRodHpzVjFkaTBaT21CU2JIRDlVWkx5NFlwODJuOTBLZ2o4UzRTbEEvR1ZNRVdvVTlINXhOejlQeEw1eDBaYUZmSUs2bEhXUW5YMFNyQlcweDc2RmhKWERDaGhQYzBYRkQxTk5XREZmbUFrUjdmUVpJUmZub3RSbW50RTBwOXdlOWNlQktCM0x6bHV5eVhFMFBaY1VDSzNJeFlUckNNWTVLdWk4Q1VaZ2dPcFI1bEhuS2kwMVpva3ZmRThPYUFiRFQ4QkRvWERjeVF6dGdJUGxYNmRCVktNZENnT0dZSmUrd3F1eGM4K0dDVk1VRWM1NFlWUmg2K2NKQTE3bldEWHhpWTE3elBkQkJOWjdHRHFqakRGaklRYU1JQ1hwRGVOUTRaSDZnN3QwPSIsImtleV92ZXJzaW9uIjoxfQ== immutable: true ...5.7. PKI Secrets
- 운영자가 단일 Vault PKI 시크릿을 단일 Kubernetes 시크릿으로 동기화하는 데 필요한 구성입니다.
- Ingress, mTLS, Webapp 등 PKI 인증서를 사용하는 애플리케이션에서 동적으로 변경된 PKI 인증서를 사용할 수 있도록 지원합니다.
5.8. 📖 추가 학습자료
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: ...'AWS' 카테고리의 다른 글
[AEWS 3기] 12주차 - Amazon VPC Lattice for Amazon EKS (1) 2025.04.27 [AEWS 3기] 11주차 - ML Infra(GPU) on EKS (0) 2025.04.20 [AEWS 3기] 9주차 - EKS Upgrade (0) 2025.04.02 [AEWS 3기] 8주차 - K8S CI/CD (0) 2025.03.30 [AEWS 3기] 7주차 - EKS Mode/Nodes (0) 2025.03.23

