들어가며
Secret은 API 키, 비밀번호, 데이터베이스 자격 증명, OAuth/JWT 토큰, TLS 인증서처럼 민감한 정보를 의미합니다.
이러한 중요한 정보들을 GitHub 같은 공개 저장소에 평문으로 저장하는 것은 매우 위험합니다. 실수로 코드에 포함되거나 외부에 노출될 경우, 서비스 전체가 공격받을 수 있는 심각한 보안 사고로 이어질 수 있기 때문입니다.
그렇기 때문에 Secret을 안전하게 관리하기 위한 전용 도구가 필요하며, 그중 대표적인 솔루션이 바로 HashiCorp Vault입니다.
Vault를 사용하면 Secret을 중앙에서 안전하게 저장하고, 접근 권한을 세밀하게 제어할 수 있으며, Secret을 자동으로 생성하거나 일정 시간이 지나면 폐기되도록 설정할 수 있어, 수동으로 관리할 때보다 훨씬 안전하고 효율적입니다.
또한, CI/CD 파이프라인과도 쉽게 연동할 수 있기 때문에 Jenkins를 통해 CI Pipeline 빌드 중 Vault에 접근해 Secret 값을 받아와 애플리케이션에 주입할 수 있고, ArgoCD Vault Plugin을 사용하여 애플리케이션을 배포할 때 Vault에서 Secret을 읽어와 필요한 Kubernetes 리소스에 자동으로 반영할 수 있습니다.
Hashicorp Vault
Vault란?
Vaults의 사전적 정의
먼저 사전에서 vaults의 뜻을 찾아보면 금고라는 뜻인 것을 알 수 있습니다. 이와 같이 금고처럼 특별한 key를 이용하여 금고 안에 있는 물건을 얻을 수 있게 설계된 것이 vault입니다.
Hashicorp Vault 정의
Hashicop Vault는 identity-based의 secret 및 암호화 관리 도구입니다. 특히, 인증(authentication), 인가(authorization)을 통해 secret에 대해 안전하게 관리 및 접근을 통제할 수 있습니다.
또한 다양한 secret 데이터들에 대해 통합된 interface를 제공하면서, 엄격한 접근 제어와 상세한 audit log 기능 등을 제공합니다.
외부 서비스용 API Key나 통신을 위한 자격 증명 등은 플랫폼마다 분산되어 있어, 누가 어떤 시크릿에 접근했는지 파악하기 어렵습니다. 이럴 때 HashiCorp Vault를 활용하면 키 회전(Key Rotation), 감사 로그(Audit Logging) 등의 기능을 통해 secret을 중앙에서 안전하게 관리할 수 있습니다.
Vault의 동작 방식
주요 기능
Static Secrets
비밀번호, API 키 등 Key/Value 형식의 secret을 암호화하여 저장하기에 저장소에 직접 접근해도 내용을 볼 수 없습니다.
Dynamic Secrets
요청할 때마다 자동으로 자격 증명을 생성합니다. 이때 일저 시간이 지나면 자동으로 폐기됩니다.
Data Encryption
Vault가 데이터를 직접 저장하지 않아도, 암호화/복호화를 대신 수행합니다. 이를 통해 개발자는 민감한 데이터를 안전하게 저장할 수 있습니다.
Leasing and Renewal
Secret에는 사용기한(lease)가 설정되어 있어 시간이 지나면 자동으로 만료되고 삭제됩니다. (필요시 갱신도 가능합니다.)
Revocation
특정 Secret 하나만 폐기하거나 특정 사용자 혹은 특정 유형 전체를 한 번에 삭제할 수 있습니다. 이를 통해 문제가 발생했을 때 빠르게 대응할 수 있습니다.
핵심 개념
vault는 토근(Token)기반으로 동작하며, 이 토큰은 클라이언트의 정책(policy)과 연결되어 있습니다. 그리고 각 정책은 경로(path) 기반으로 설정되며, 정책 규칙을 통해 클라이언트가 해당 경로에서 수행할 수 있는 작업들과 접근하는 시크릿을 제한합니다.
인증(Authenticate)
Vault에서 인증은 클라이언트가 Vault에 자신이 누구인지 증명할 수 있는 정보를 제공하는 과정입니다. 클라이언트가 인증 method를 통해 인증이 완료되면, Token이 생성되고 Policy와 연결됩니다.
검증(Vaildation)
Vault는 Github, OIDC, LDAP, AppRole등과 같이 신뢰할 수 있는 외부 소스를 통해 클라이언트를 검증합니다.
인가(Authorize)
클라이언트가 Vault의 보안정책를 통해 Vault Token을 사용하여 클라이언트가 접근할 수 있는 API 엔드포인트를 할당합니다. 이때 정책은 Vault 내 특정 경로나 작업에 대한 접근을 허용하거나 거부하는 방식으로 제어합니다.
접근(Access)
클라이언트와 연결된 정책을 기반으로 Token을 발급하여 secret, key, 암호화 등에 대한 접근을 허용합니다.
Vault Workflow
Vault는 다양한 Identity Provider(IdP)와의 통합을 통해 사용자 인증을 수행합니다. 사용자가 인증되면, 미리 정의된 정책(Policy)과 연계된 토큰을 발급하여 자격 증명을 검증하고, 최종적으로 Vault에 접근할 수 있는 Vault Token을 반환합니다. 이 Token은 해당 사용자의 권한 범위를 Vault Policy와 연동하여 엔진(Engine) 및 리소스 접근 제어에 활용됩니다.
Vault의 인증 및 권한 부여
Vault는 사용자 인증이 아닌 권한 부여를 제공합니다. 그렇기 때문에 실제 인증 프로세스는 관리하지 않습니다.
Vault Agent
Vault Agent는 애플리케이션이 직접 Vault와 통신하지 않아도 대신 Vault에 인증 후 필요한 secret을 가져와 파일로 저장하거나 환경변수로 주입해 주는 중간 역할을 합니다. 이를 통해 애플리케이션 코드를 수정하지 않아도 secret을 안전하게 사용할 수 있고, Token 갱신이나 Secret 갱신 같은 번거로운 작업을 자동으로 처리할 수 있습니다.
특히 Kubernetes 환경에서는 Vault Agent Ingector를 이용하여 Pod에 Vault Agent를 sidecar 형식으로 자동으로 주입할 수 있습니다.
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "example-role"
vault에서 Kubernetes Auth Method를 활성화하고, 적절한 Policy와 Role이 설정되어있어야 하며 위와 같이 애플리케이션 Pod에 주석을 추가해야 Vault Agent Injector이 Vault Agent를 주입합니다.
Vault AppRole
Vault AppRole은 사람이 아닌 애플리케이션이나 자동화된 작업이 Vault에 인증할 수 있게 해주는 인증 방식입니다.
일반적으로 공개되어도 무방하고 누구인지 식별하는 값인 RoleID와 외부에 노출되면 안되고 인증을 완료하기 위한 일종의 비밀번호인 SecretID로 구성됩니다. 이 둘을 함께 사용하여 Vault에 로그인하면, 해당 Role에 연결된 Policy 권한이 부여된 Token을 얻게 됩니다.
AppRole의 인증 흐름 방식을 간단하게 나타내면 아래와 같습니다.
1. Admin이 Role을 Vault에 등록
2. Vault가 RoleID를 생성
3. Vault가 SecretID를 발급
4. Client가 RoleID + SecretID로 Vault에 로그인
5. Vault가 인증된 Token을 반환
즉, Vault AppRole은 CI/CD 파이프라인, 스크립트, 마이크로서비스 등 사람의 개입 없이 Vault인증이 필요한 경우에 적합한 기능입니다.
실습 환경 구성
Jenkins 구축
docker compose yaml 파일 생성
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
위와 같이 Jenkins를 호스트 OS 포트 노출(expose)로 접속하게끔 docker-compose 파일을 생성합니다.
Docker-Compose를 통해 Jenkins 구축
docker compose up -d
그 후 docker compose 명령어를 통해 배포합니다.
Jenkins 비밀번호 획득
docker compose exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
Jenkins 접속
open "http://127.0.0.1:8080"
위의 docker-compose.yaml 파일에서 port를 8080으로 지정했기 때문에 해당 port로 접속해보겠습니다.
위와 같이 접속이 되는 것을 확인하실 수 있고, 획득한 초기 비밀번호로 접속합니다.
초기 추천하는 Plugin을 설치하면서 Jenkins 구축은 완료됩니다.
Jenkins 구축 확인
Kubernetes Cluster 구축
IP를 환경 변수에 지정
MyIP=122.46.200.191
위와 같이 현재 작업 중인 PC의 IP를 환경 변수에 지정합니다.
kube config 파일 생성 및 지정
cat > ~/.kube/config-cicd <<EOF
apiVersion: v1
kind: Config
preferences: {}
EOF
실제 업무에서 사용하는 cluster와 충돌이 나지 않게 하기 위해 config-cicd라는 kube config 파일을 만들겠습니다.
export KUBECONFIG=~/.kube/config-tmp
그 후 해당 파일로 kubeconifg를 지정합니다.
kind yaml 파일 생성
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
그 후 url로 접속을 하기 위해 몇 개의 port를 매핑하여 cluster를 생성하는 yaml파일을 작성합니다.
Kind를 통해 local용 cluster 구축
kind create cluster --config kind-3node.yaml --name myk8s --image kindest/node:v1.32.2
위의 명세파일을 토대로 kind cluster를 배포합니다.
Cluster 정보 확인
kubectl cluster-info
현재 구축된 cluster 정보를 확인해 보겠습니다.
ArgoCD 구축
argocd namespace 생성
kubectl create ns argocd
argocd 관련 리소스가 배포될 namespace를 생성합니다.
argocd yaml 파일 생성
cat <<EOF > argocd-values.yaml
dex:
enabled: false
server:
service:
type: NodePort
nodePortHttps: 30002
extraArgs:
- --insecure
EOF
argocd를 웹에서 접근하기 위해 nodeport type으로 service를 생성하고 해당 port를 3002로 지정합니다. 그 후 insecure 옵션을 통해 HTTPS 대신 HTTP 사용하게끔 설정합니다.
Helm을 통한 ArgoCD 구축
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
웹으로 접근
open "http://127.0.0.1:30002"
설정 파일에서 명시한 대로 30002 port로 접근해 보겠습니다.
최초 접속 암호 확인
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
위 명령어를 통해 ArgoCD 웹에 접근하기 위해 초기 비밀번호를 획득합니다.
ArgoCD 웹 로그인
ID: admin / PW : 초기비밀번호 입력 후 접속합니다.
UPDATE PASSWORD 로 admin 계정 암호 변경
이후의 실습을 원활하게 진행하기 위해 User Info > UPDATE PASSWORD를 통해 비밀번호를 qwe12345로 변경합니다.
K8s에 Vault 설치
namespace 생성
kubectl create namespace vault
helm repo 추가 및 vault repo 확인
helm repo add hashicorp https://helm.releases.hashicorp.com
hashicorp의 helm repo를 추가합니다.
helm search repo hashicorp/vault
vault에 관한 repo만 검색해 보겠습니다.
helm yaml 파일 생성
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
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
데모 목적이기 때문에 TLS는 비활성화(운영에서는 절대 비활성화 금지!)하고, injector를 true로 함으로써 추후에 있을 Kubernetes Pod에 자동으로 Vault 인증 및 비밀 주입 가능하는 AppRole 실습을 위해 Vault Agent Injector 활성화합니다.
Helm을 통해 Vault 배포
helm upgrade vault hashicorp/vault -n vault -f override-values.yaml --install
기본 namespace 변경
kubectl config set-context --current --namespace=vault
실습을 원활하게 진행하기 위해 기본 namespace를 vault로 지정합니다.
Vault 리소스 검색
pod, svc, pvc 등을 조회했을 때 vault-0 pod는 Running 상태이지만 READY가 0/1인 것을 확인하실 수 있습니다.
kubectl describe pod vault-0
describe 명령어를 통해 vault-0 pod의 상세 내용을 확인해 보겠습니다.
Initialized가 false로 되어있습니다. 즉, 초기화에 실패했다는 건데요.
Vault는 기본적으로 '금고(Vault)'라는 이름답게 처음 배포하면 잠긴(sealed) 상태로 시작합니다. 이 상태에서는 정상적인 기능을 사용할 수 없으며, 먼저 초기화(init)를 통해 금고를 생성하고, 이어서 잠금 해제(unseal) 과정을 거쳐야만 본격적으로 동작하게 됩니다.
따라서 Initialized: false 상태는 아직 Vault가 초기화되지 않았다는 뜻이며, 이는 초기화 절차를 거치지 않았기 때문에 초기화를 수행한 후, unseal 명령어로 잠금을 해제해야 Vault가 정상적으로 작동합니다.
init-unseal.sh을 사용하여 Vault Unseal 자동화
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
위와 같은 unseal script를 생성하고 실행합니다.
vault status 명령을 사용하여 Unseal 되었는지 확인
kubectl exec -ti vault-0 -- vault status
vault pod 내부에 접속하여 vault status 명령을 통해 Unseal 되었는지 확인해 보겠습니다.
Initalzed가 true로, Sealed가 false로 변경된 것을 확인하실 수 있습니다.
vault-0 pod도 정상적으로 실행 중인 것을 확인하실 수 있습니다.
CLI 설정
brew tap hashicorp/tap
brew install hashicorp/tap/vault
vault 관련 실습을 진행하는데 cli를 통해 접근하거나 제어를 해야 할 수도 있기 때문에 vault cli를 설치합니다.
NodePort로 공개한 30000 Port로 설정
export VAULT_ADDR='http://localhost:30000'
vault 상태확인
vault status
Root Token으로 로그인
ls -l | grep vault-
위에서 실행한 unseal script의 결과로 root token의 정보가 담긴 txt 파일이 생성되었고, 해당 파일에서 root token 값을 획득합니다.
vault login
vault login 명령어를 실행하여 위에서 획득한 root token을 입력하여 로그인을 진행합니다.
실습 - Vault 기본 실습
KV Secret Engine 활성화 및 샘플 구성
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
Vault UI에서 확인
[Secrets Engine] 탭에 접속 후 [sampleapp - config] 접속하면 위와 같이 실제 저장된 Key / Value을 확인하실 수 있습니다.
Vault Sidecar 연동(Vault Agent)
Vault AppRole 인증 방식 활성화
vault auth enable approle || echo "AppRole already enabled"
정책 생성
vault policy write sampleapp-policy - <<EOF
path "secret/data/sampleapp/*" {
capabilities = ["read"]
}
EOF
AppRole Role 생성
vault write auth/approle/role/sampleapp-role \
token_policies="sampleapp-policy" \
secret_id_ttl="1h" \
token_ttl="1h" \
token_max_ttl="4h"
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)
파일로 저장
mkdir -p approle-creds
echo "$ROLE_ID" > approle-creds/role_id.txt
echo "$SECRET_ID" > approle-creds/secret_id.txt
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 -
Vault Agent Sidecar 연동
Vault Agent는 vault-agent-config.hcl 설정을 통해 연결할 Vault의 정보와, Template 구성, 렌더링 주기, 참조할 Vault KV 위치정보 등을 정의합니다.
Vault Agent 설정 파일 작성 및 생성 (feat.HCL)
cat <<EOF | kubectl create configmap vault-agent-config -n vault --from-file=agent-config.hcl=/dev/stdin --dry-run=client -o yaml | kubectl apply -f -
vault {
address = "http://vault.vault.svc:8200"
}
auto_auth {
method "approle" {
config = {
role_id_file_path = "/etc/vault/approle/role_id"
secret_id_file_path = "/etc/vault/approle/secret_id"
remove_secret_id_file_after_reading = false
}
}
sink "file" {
config = {
path = "/etc/vault-agent-token/token"
}
}
}
template_config {
static_secret_render_interval = "20s"
}
template {
destination = "/etc/secrets/index.html"
contents = <<EOH
<html>
<body>
<p>username: {{ with secret "secret/data/sampleapp/config" }}{{ .Data.data.username }}{{ end }}</p>
<p>password: {{ with secret "secret/data/sampleapp/config" }}{{ .Data.data.password }}{{ end }}</p>
</body>
</html>
EOH
}
EOF
샘플 애플리케이션 + Sidecar 배포
kubectl apply -n vault -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-vault-demo
spec:
replicas: 1
selector:
matchLabels:
app: nginx-vault-demo
template:
metadata:
labels:
app: nginx-vault-demo
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: html-volume
mountPath: /usr/share/nginx/html
- name: vault-agent-sidecar
image: hashicorp/vault:latest
args:
- "agent"
- "-config=/etc/vault/agent-config.hcl"
volumeMounts:
- name: vault-agent-config
mountPath: /etc/vault
- name: vault-approle
mountPath: /etc/vault/approle
- name: vault-token
mountPath: /etc/vault-agent-token
- name: html-volume
mountPath: /etc/secrets
volumes:
- name: vault-agent-config
configMap:
name: vault-agent-config
- name: vault-approle
secret:
secretName: vault-approle
- name: vault-token
emptyDir: {}
- name: html-volume
emptyDir: {}
EOF
위의 yaml 파일을 배포하여 Nginx + Vault Agent 생성합니다.
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
생성된 컨테이너 확인
kubectl get pod -l app=nginx-vault-demo
pod를 조회하면 위와 같이 2개의 container가 생성된 것을 확인하실 수 있습니다.
kubectl describe pod -l app=nginx-vault-demo
해당 pod를 자세히 살펴보면 vault-agent-sidecar container가 생성된 것을 확인하실 수 있습니다.
secret mount 확인
kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- ls -al /etc/vault/approle
해당 pod에 접근하여 vault/approle에 role_id와 secret_id에 대한 파일이 있는지 확인해 보겠습니다.
kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- cat /etc/vault/approle/role_id ; echo
kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- cat /etc/vault/approle/secret_id ; echo
kubectl exec -it deploy/nginx-vault-demo -c vault-agent-sidecar -- ls -l /etc/secrets
그 후 /etc/secrets 경로에 설정 파일에서 secret 값들을 매핑하여 html 파일을 생성하는데, 해당 파일이 있는지 조회해 보겠습니다.
index.html 파일이 존재하는 것을 확인하실 수 있습니다.
그 후 해당 nodeport인 30001로 접근해 보면 secret값이 보이는 것을 확인하실 수 있습니다.
이처럼 Vault Agent sidecar를 통해 vault 내에 있는 secret을 pod내에 주입시켜 사용하는 것을 확인하실 수 있습니다.
실습 - Jenkins + Vault
Jenkins를 통해 Vault KV Store에 저장한 username, password와 같은 secret을 획득하여 CI 파이프라인에서 static secret을 외부에 저장하고 관리하는 경우를 실습해 보겠습니다.
Workflow
jenkins와 Vault를 결합하여 CI 파이프라인을 구성하는 흐름을 정리하면 아래와 같습니다.
1. Jenkins Worker가 Vault에 인증
2. Vault가 Token 반환
3. Jenkins Worker는 해당 Token을 사용하여, 실행할 Job의 Role에 대한 Wrapping 된 SecretID를 검색
4. Vault가 Wrapping된 SecretID 반환
5. Worker는 Runner 생성 후 SecretID를 Job의 변수로 전달
6. Runner Container가 Vault에게 SecretID의 Wrapping 해제 요청
7. Vault가 실제 SecretID 반환
8. Runner는 RoleID와 SecretID를 사용하여 Vault에 인증
9. Vault는 권한이 부여된 Token 반환
10. Runenr는 해당 Token을 사용하여 Vault에서 필요한 Secret 획득
jenkins에서 Vault Plugin 설치
Jenkins UI 접속 -> Manage Jenkins → Plugins -> Available -> Jenkins 검색 -> HashiCorp Vault Plugin 설치 후 Jenkins 재시작
Vault 설정 및 Credential 추가
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)
Vault에서 발급된 ROLE_ID, SECRET_ID는 이전에 생성한 role_id.txt secret_id.txt 값을 참고하여 사용할 수 있습니다.
Jenkins에서 Vault 설정 및 Credentials 추가
Jenkins UI→ Manage Jenkins → System 경로로 이동합니다.
하단의 Vault Plugin Configuration 섹션으로 이동합니다.
vault의 URL을 입력하신 후 + Add 버튼을 클릭합니다.
Vault AppRole Credential을 선택하여 생성해 놓은 Role ID와 Secret ID를 입력합니다.
그 후 Vault Credential에 방금 생성한 vault-approle-creds를 선택하고 저장합니다.
Jenkins Pipeline Job 생성
Pipeline 생성
Jenkins UI → New Item → Pipeline을 생성합니다.
Jenkinsfile 작성
pipeline {
agent any
environment {
VAULT_ADDR = ${VAULT IP}
}
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}"
}
}
}
}
}
}
위와 같이 pipeline의 코드를 입력합니다.
그 후 Save 하고 빌드를 확인합니다.
빌드 확인
위와 같이 secret 값은 보안상 취약하므로 마스킹처리가 되는 것을 확인하실 수 있습니다.
실습 - ArgoCD + Vault
ArgoCD Vault Plugin이란?
ArgoCD에는 다양한 secret 관리 도구(Hashicorp Vault, IBM Cloud Secret Manager, AWS Secrets Manager 등) 플러그인을 통해 Kuberentes 리소스에 주입할 수 있도록 설정이 가능합니다. 특히, Secret 뿐만 아니라 Deloyment, ConfigMap 등 다양한 Kuberntes 리소스에도 사용할 수 있습니다.
Credential 활성화
Role_ID, Secret_ID을 secret으로 생성
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: $ROLE_ID
AVP_SECRET_ID: $SECRET_ID
EOF
ArgoCD Vault Plugin 설치
ArgoCD Vault Plugin 설치 방법은 2가지가 있으며 현재는 Installation via a sidecar container 방식을 사용하는 것을 권장합니다.
실습을 위한 git repo clone
git clone https://github.com/hyungwook0221/argocd-vault-plugin.git
cd argocd-vault-plugin/manifests/cmp-sidecar
namespace 변경
kubectl config set-context --current --namespace=argocd
실습을 원활하게 하기 위해 argocd namespace로 변경합니다.
생성될 매니페스트 파일에 대한 확인
kubectl kustomize .
kusomize 실행
kubectl apply -n argocd -k .
-k 옵션으로 kusomize 실행합니다.
pod에 접근하여 vault login
kubectl exec -it -n vault vault-0 -- sh
Sample App 배포
Application yaml 파일 작성
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
Application 배포 시 참조하는 new-values.yaml 확인
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"
실제 배포시 적용된 화면
ArgoCD에 접근하면 위와 같이 Demo Application이 생성된 것이 보입니다.
DETAILS > PARAMETERS 탭에 접근하면 위와 같이 argocd vault plugin helm이 적용되어 있는 것을 확인하실 수 있습니다.
위와 같이 vault에 저장되어 있던 secret 값이 Deployment의 env으로 설정된 것을 확인할 수 있습니다.
참고
https://developer.hashicorp.com/vault/docs/what-is-vault
Introduction | Vault | HashiCorp Developer
Welcome to the intro guide to Vault! This guide is the best place to start with Vault. We cover what Vault is, what problems it can solve, how it compares to existing software, and contains a quick start for using Vault.
developer.hashicorp.com
https://medium.com/@muppedaanvesh/a-hand-on-guide-to-vault-in-kubernetes-%EF%B8%8F-1daf73f331bd
⎈ A Hands-On Guide to Vault in Kubernetes ⚙️
⇢ Manage k8s Secrets Using HashiCorp Vault: With Practical Examples
medium.com
https://developer.hashicorp.com/vault/docs/auth/approle/approle-pattern#jenkins-ci-cd
Best practices for AppRole authentication | Vault | HashiCorp Developer
Follow best practices for AppRole authentication to secure access and validate application workload identity.
developer.hashicorp.com
ArgoCD Vault Plugin
ArgoCD Vault Plugin 연동방안
docmoa.github.io
'스터디 이야기 > 25' AWS EKS' 카테고리의 다른 글
AWS EKS 업그레이드 실습: Control Plane부터 Node Group까지 (0) | 2025.04.02 |
---|---|
ArgoCD Image Updater로 이미지 자동으로 감지하여 배포하기 (0) | 2025.03.28 |
AWS EKS의 AutoMode (0) | 2025.03.21 |
EKS에서 ConfigMap 없이 API로 접근 관리하기(Cluster Access Management) (2) | 2025.03.14 |