AEWS 10주차 - K8S Secret Update(vault sidecar & jenkins(AppRole) CI)
본 글은 가시다님이 진행하시는 AEWS(AWS EKS Workshop Study)를 참여하여 정리한 글입니다. 모르는 부분이 많아서 틀린 내용이 있다면 말씀 부탁드리겠습니다! |
vault sidecar
🔐 Vault Agent Sidecar란?
Vault Agent Sidecar는 Kubernetes Pod 안에 함께 배포되는 컨테이너로, 애플리케이션이 직접 Vault와 통신하지 않고도 자동으로 Secret을 안전하게 받아 쓸 수 있게 해주는 구성 방식입니다.
쉽게 말해, 내 앱 컨테이너 옆에서 "Vault 비서(agent)"가 같이 살면서 대신 Vault에서 자격증명(Secrets)을 가져오고 관리해주는 거예요.
🧐 왜 필요한가요?
Kubernetes 환경에서 애플리케이션이 Vault에서 직접 비밀 정보를 가져오려면:
- Vault의 주소를 알아야 하고
- 인증을 직접 수행해야 하며
- Token을 안전하게 관리해야 합니다
이 모든 걸 애플리케이션에 넣는 건 보안상 위험하고, 구현도 복잡하죠.
그래서 Vault Agent Sidecar를 사용하면:
- 내 앱은 Vault를 몰라도 되고
- Sidecar가 알아서 인증하고
- Secret을 미리 정해둔 경로에 파일 형태로 저장해주기 때문에
- 앱은 그냥 파일만 읽으면 됩니다!
🧬 Vault Agent Injector란?
Vault Agent Injector는 Kubernetes에서 Vault Agent Sidecar를 자동으로 내 Pod에 주입해주는 Mutating Admission Webhook이에요.
즉, 내가 따로 Vault Agent를 명시하지 않아도, Kubernetes가 알아서 내 Pod 템플릿에 Vault Agent Sidecar + 필요한 설정을 추가해주는 시스템입니다.
어떻게 작동하나요?
- vault.hashicorp.com/agent-inject: "true" 라는 주석(annotation)을 Pod에 달면
- Injector가 그것을 감지하고
- 내 Pod Spec에 Vault Agent Sidecar와 필요한 Volume, Init Container 등을 자동으로 넣어줍니다
- Agent는 Vault로부터 Secret을 가져와 /vault/secrets/ 같은 경로에 저장합니다
🔄 구성 흐름 요약
[애플리케이션 컨테이너]
🔽 파일 접근
[/vault/secrets/my-secret]
[Vault Agent Sidecar]
🔁 인증 (예: Kubernetes 인증)
🔁 Vault에서 Secret 읽기
🔁 Secret을 파일로 저장
[Vault Agent Injector]
✏️ Pod에 Sidecar 자동 주입
🔑 Vault AppRole이란?
AppRole은 Vault에서 제공하는 인증 방법 중 하나로,
사람이 아닌 시스템(애플리케이션, 서버, 자동화 스크립트 등) 이 Vault에 접근할 수 있도록 해줍니다.
간단히 말해, 특정 역할(Role)을 가진 시스템이 Role ID와 Secret ID라는 두 가지 자격증명을 이용해 Vault에 로그인하는 방식입니다.
🧬 AppRole의 핵심 개념
Role ID | 공개 키처럼, 정적인 식별자입니다. 이 Role이 누구인지 식별할 때 사용해요. |
Secret ID | 비밀 키처럼, 1회성 또는 짧은 수명의 비밀 자격증명입니다. Role ID와 함께 Vault에 인증할 때 사용합니다. |
Role | 인증 정책, Secret ID 수명, 토큰 TTL 등을 정의한 권한 집합이에요. |
🔐 왜 AppRole을 사용할까?
AppRole은 CI/CD 파이프라인, 서버, 백엔드 애플리케이션 등 Vault와 통신해야 하지만 사람처럼 로그인을 못하는 시스템에 적합해요.
특히 다음 같은 이유로 인기 있어요:
- Kubernetes 외 환경에서도 사용 가능 (예: GitLab Runner, EC2 등)
- Secret ID 주기적 갱신 가능 → 보안성 향상
- 정책별 Role 생성 가능 → 최소 권한 원칙 적용
- Secret ID에 IP 제한 등 설정 가능
Vault 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 -
Approle 방식을 구성했으면 이용해서 sidecar을 연동해봅니다.
Vault Agent Sidecar 연동
Vault Agent 설정 파일 작성 및 생성
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 배포(수동방식)
# 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
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
확인
# 파드 내에 사이드카 컨테이너 추가되어 2/2 확인
kubectl get pod -l app=nginx-vault-demo
NAME READY STATUS RESTARTS AGE
nginx-vault-demo-84526491123-sdnfxc 2/2 Running 0 3m17s
kv값 변경 후 확인
Jenkins
이번에는 jenkins에서 vault의 secret들을 들고와서 build하는 과정을 실습합니다.
jenkins에서 vault plugin 설치
Vault AppRole 정보 확인
# 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"
vault 설정 및 credentials 추가
System -> Vault Plugin Configuration -> Add클릭
- Vault Credential 다음 값 입력:
- 종류: Vault AppRole Credential
- Role ID & Secret ID 입력 → 생성해놓은 변수 또는 파일참고
- ID는 기억하기 쉬운 이름으로 지정 (vault-approle-creds 등)
pipeline 확인
- Jenkins UI → New Item → Pipeline 선택
- jenkins-vault-kv 입력 후 생성
- 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}"
}
}
}
}
}
}
값들은 마스킹되어서 나타내집니다.
이렇게 jenkinis에서 vault의 값들도 받아와서 build(pipeline)을 성공했습니다.