Kubernetes 는 애플리케이션이 민감 정보를 안전하게 사용할 수 있도록 Secret 리소스를 제공합니다. 하지만 기본 제공되는 Secret 기능은 실제 운영 환경에서 보안과 관리 효율성 측면에서 여러 가지 한계를 드러냅니다.
1. 저장소 암호화 부족
Kubernetes Secret 은 etcd 에 저장될 때 base64 인코딩만 적용됩니다.
이는 암호화가 아니기 때문에 etcd 저장소가 유출되거나 내부 관리자가 접근하는 경우 Secret 이 그대로 노출될 수 있습니다.
etcd 저장소 자체를 암호화할 수는 있지만, 기본적으로 활성화되어 있지 않고 관리자의 추가 작업이 필요합니다.
2. 세밀한 접근 제어의 어려움
Kubernetes RBAC 으로 Secret 에 대한 접근을 통제할 수 있으나, Secret 단위로 정밀하게 권한을 구분하는 데에는 한계가 있습니다.
대부분 namespace 수준에서 권한을 설정하는 방식이기 때문에, 특정 Secret 에만 제한적으로 접근하도록 제어하기가 복잡하고 비효율적입니다.
3. Secret 수명 주기 관리의 부재
기본 Kubernetes Secret 은 한 번 생성되면 수명 주기 관리 기능이 없습니다.
만료, 자동 갱신, 자동 폐기 같은 기능이 기본적으로 제공되지 않으며, 주기적인 키 교체 역시 관리자의 수작업이나 별도의 자동화 도구에 의존해야 합니다.
4. 감사 및 추적 기능의 부족
Secret 이 어떻게 사용되었는지, 누가 조회했는지 등의 기록을 Kubernetes 는 기본적으로 남기지 않습니다.
Audit 로그 기능이 존재하지만 별도로 활성화해야 하고, 로그 분석 시스템과 연동하지 않으면 실효성이 떨어집니다.
이로 인해 보안 사고 발생 시 원인 파악과 대응이 늦어질 수 있습니다.
5. 자동화된 Secret 생성 및 배포의 부족
Pod 이나 애플리케이션이 필요한 시점에 Secret 을 동적으로 생성하거나 받을 수 있는 기능이 부족합니다.
대부분의 경우 Secret 은 사전에 수작업으로 생성하거나 배포 파이프라인을 통해 전달되어야 하므로, 운영 자동화 측면에서도 제약이 있습니다.
이런 이유로 Kubernetes Secret 만으로는 충분하지 않은 경우가 많습니다. 보안이 중요한 환경이라면 시크릿을 더 안전하게 관리할 수 있는 방법을 고민할 필요가 있습니다. Vault 는 그 대안이 될 수 있습니다. Secret 을 저장할 때 자동으로 암호화하는 것은 물론 접근 권한을 세밀하게 설정할 수 있고 필요할 때마다 시크릿을 발급하고 사용이 끝나면 자동으로 폐기하는 기능까지 갖추고 있습니다. 무엇보다도 모든 작업이 로그로 남아 추적이 가능하므로 보안 요구 사항이 높은 환경에서도 안심하고 사용할 수 있습니다.
운영 환경이 복잡하거나 민감한 정보를 다루는 경우라면,Vault 를 활용해 시크릿 관리를 한층 더 강화하는 것을 고려해 보시는 것이 좋겠습니다.
HashiCorp Vault 는 조직이 보유한 민감한 정보를 안전하게 보호하고 효율적으로 관리하기 위해 설계된 플랫폼입니다. 단순히 시크릿을 저장하는 데 그치지 않고 누가 어떤 정보에 접근할 수 있는지 정교하게 제어하며 모든 사용 이력을 남겨 민감 정보의 사용 내역을 투명하게 추적할 수 있도록 지원합니다.
보안이 요구되는 시스템에서는 비밀번호나 API 키, 데이터베이스 자격 증명 같은 민감한 정보가 필수적으로 사용되지만 이를 안전하게 관리하는 일은 생각보다 복잡합니다. 환경 변수나 Kubernetes Secret 과 같은 기본적인 방법은 설정이 간편하다는 장점이 있지만 저장소 암호화가 미흡하거나 세밀한 접근 제어가 어렵다는 한계가 있습니다. 또한, 수명 주기 관리나 접근 이력 기록이 부족하여 민감 정보가 오래 방치되거나 사용 내역을 추적하기 어려운 문제가 발생할 수 있습니다.
Vault 는 이러한 문제를 해결하는 역할을 합니다. 정보를 저장하는 것에 그치지 않고, 필요한 순간마다 시크릿을 동적으로 생성하고 사용이 끝나면 자동으로 폐기하여 민감 정보가 불필요하게 남지 않도록 관리합니다. 무엇보다도 Vault 는 정책 기반 접근 제어를 통해 사용자나 애플리케이션이 반드시 인증과 검증을 거친 후에만 시크릿에 접근하도록 강제하며 이 모든 과정을 감사 로그로 남겨 이후에도 투명하게 관리할 수 있습니다.
결국 Vault 는 민감 정보를 저장하고 보호하는 것에서 더 나아가 정보의 수명 주기를 체계적으로 관리하며 조직이 요구하는 보안 수준과 컴플라이언스 요구 사항을 충족하는 데 필수적인 역할을 수행합니다.
단계 | 설명 |
1. 인증 (Authenticate) | Vault 에 접근하려는 클라이언트(사람, 시스템, 애플리케이션 등)가 자신이 누구인지 증명하는 단계입니다. 클라이언트는 Kubernetes, AppRole, GitHub, LDAP 등 Vault 가 지원하는 다양한 인증 방법 중 하나를 사용하여 Vault 에 로그인합니다. 인증에 성공하면 Vault 가 클라이언트에게 토큰을 발급합니다. 이 토큰은 이후 요청에서 신원을 증명하는 수단으로 사용됩니다. |
2. 검증 (Validation) | Vault 는 외부 신뢰 소스 또는 내부 메커니즘을 통해 클라이언트의 신원을 검증합니다. 클라이언트가 제출한 인증 정보가 유효한지 확인하고 이를 기반으로 Vault 내부에서 신뢰할 수 있는 사용자 또는 시스템인지 판단합니다. |
3. 인가 (Authorize) | 검증이 완료된 후, Vault 는 발급한 토큰이 어떤 정책(Policy)과 연결되어 있는지를 확인합니다. 정책은 클라이언트가 Vault 내 특정 경로나 기능에 대해 어떤 작업을 수행할 수 있는지를 정의하며 이를 바탕으로 요청을 허용하거나 거부합니다. |
4. 접근 (Access) | Vault 는 정책에 따라 요청을 허용하고 클라이언트가 접근을 요청한 시크릿을 반환하거나 관련 기능을 실행합니다. 클라이언트는 이후 작업에서 토큰을 사용하여 Vault 와 지속적으로 상호작용할 수 있습니다. 모든 접근 내역은 Vault 에 의해 기록됩니다. |
구성 요소 | 설명 |
Storage Backend | Vault 가 시크릿 데이터를 저장하는 저장소. 저장 전 데이터는 Vault 가 자체적으로 암호화하며, Consul, 파일 시스템, S3 등 다양한 백엔드를 사용할 수 있습니다. |
Seal / Unseal |
Vault 의 데이터 보호 메커니즘. Vault 는 초기화 시 봉인(Seal) 상태이며, 키 조각을 모아 복호화(Unseal) 후에 정상 작동합니다. Shamir’s Secret Sharing 방식 사용. |
Authentication Method |
Vault 접근 시 사용자의 신원을 확인하는 인증 방식. Kubernetes, AppRole, LDAP, GitHub, OIDC 등 다양한 방식이 지원됩니다. |
Policy |
인증된 사용자가 Vault 내에서 수행할 수 있는 작업을 정의하는 규칙. 접근 경로, 허용된 작업 등을 선언적으로 설정하여 권한을 관리합니다. |
Secret Engine |
인증된 사용자가 Vault 내에서 수행할 수 있는 작업을 정의하는 규칙. 접근 경로, 허용된 작업 등을 선언적으로 설정하여 권한을 관리합니다. |
Lease |
발급된 시크릿에 임대 기간을 부여하여 일정 기간 후 자동으로 만료 또는 폐기. 관리자는 필요 시 임대를 갱신할 수 있습니다. |
Audit Device |
Vault 의 모든 작업을 기록하는 감사 시스템. 시크릿 생성, 조회, 삭제 등 작업 이력을 남겨 보안 사고 대응과 규정 준수를 지원합니다. |
High Availability & Replication |
고가용성 및 멀티 데이터센터 복제 기능. 활성/대기(Active/Standby) 구성과 DR(재해 복구), 성능 복제를 통해 서비스 연속성을 보장합니다. |
※ 목표
① Helm을 사용한 Vault 배포
# 네임스페이스 생성
kubectl create namespace vault
# Helm Chart 저장소 추가
helm repo add hashicorp https://helm.releases.hashicorp.com
# Helm Chart에서 Vault Chart 검색
helm search repo hashicorp/vault
# Helm Chart 배포를 위한 Values 파일 생성
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
# 배포 확인
kubectl get pods,svc,pvc
# Vault 서버의 현재 상태 확인
kubectl exec -ti vault-0 -- vault status
상태 확인 결과 Vault 는 현재 봉인(Sealed) 상태이며 아직 초기화되지 않았습니다. Vault 가 기동되면 기본적으로 봉인된 상태로 시작하며, 이 상태에서는 저장소에 접근하거나 시크릿을 발급하는 등의 기능이 모두 차단됩니다.
② 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
# 실행 권한 부여
chmod +x init-unseal.sh
# 실행
./init-unseal.sh
Vault 는 저장된 데이터를 암호화하기 위해 Master Key 를 사용합니다.
이 Master Key 는 하나로 보관하지 않고 Shamir’s Secret Sharing 방식으로 여러 개의 Unseal Key 로 나누어 관리하는데 Vault 가 시작되면 봉인 상태로 유지되며 운영자가 Threshold 이상 Unseal Key 를 입력해야 Master Key 가 복원되고 Vault 가 정상적으로 동작합니다.
Shamir’s Secret Sharing (샤미르 비밀 분산 기법) 은 하나의 중요한 비밀 값을 여러 조각(Share) 으로 나누고 그 중 일부만 모아도 원래 비밀을 복원할 수 있도록 만든 수학적 방법입니다.
Unseal이 진행되었으므로 Root Token값으로 Vault UI에 접속 할 수 있습니다.
Root Token값은 스크립트 실행 후 생성 된 vault-root-token.txt 파일에서 확인 가능합니다.
③ CLI 설정 (Mac OS 기준)
# Homebrew에서 HashiCorp 공식 소프트웨어 저장소(Tap)를 추가
brew tap hashicorp/tap
# 설치
brew install hashicorp/tap/vault
# 설치 확인
vault --version
Vault KV Secret Engine 의 Version 2 를 활성화하여 시크릿의 버전 관리를 지원하도록 구성하고 샘플 데이터를 저장합니다.
참고로 Version 1 은 시크릿 버전 관리 기능이 없고 Version 2 는 시크릿 변경 이력 관리 및 복원 기능이 제공됩니다.
# KV v2 형태로 엔진 활성화
vault secrets enable -path=secret kv-v2
Success! Enabled the kv-v2 secrets engine at: secret/
# 샘플 시크릿 저장
vault kv put secret/sampleapp/config username="demo" password="p@ssw0rd"
# 데이터 확인
vault kv get secret/sampleapp/config
Vault UI → Sample Engines → Secret → sampleapp → config/Secret 에서 확인 가능
■ Vault Sidecar 연동이란?
Vault Sidecar 연동은 Kubernetes 파드에 Vault Agent 컨테이너를 함께 배치해서 애플리케이션이 시크릿을 안전하게 사용할 수 있도록 하는 방식입니다.
애플리케이션이 Vault 서버와 직접 통신하지 않고도 필요한 시크릿을 사용할 수 있게 도와주며, Vault Agent 가 Vault 서버에서 시크릿을 가져와 Pod 내부 공유 볼륨 에 저장하고 애플리케이션 컨테이너는 이 파일을 읽어서 시크릿을 사용합니다.
즉, Vault Agent 가 중간 다리 역할 을 하면서 시크릿을 안전하고 효율적으로 전달하는 구조입니다.
■ 목적
구분 | 설명 |
애플리케이션 코드 수정 불필요 | 앱이 Vault API 를 몰라도 시크릿을 파일로 받아 사용 가능 |
보안성 강화 | 앱에 Vault Token 등을 직접 전달하지 않아도 됨 |
자동 갱신 (Hot Reload) | Vault Agent 가 시크릿 변경 시 자동으로 파일 갱신 |
운영 편의성 | 시크릿 관리를 Vault Agent 가 대신 처리하므로 관리가 간편 |
■ 동작 방식
단계 | 설명 | 비고 |
1 | k8s Pod가 생성될 때 Vault Agent Sidecar 컨테이너가 함께 실행 | Injector 사용 시 자동으로, 수동 방식이면 YAML 에서 직접 정의 |
2 | Vault Agent 가 k8s ServiceAccount 토큰을 사용하여 Vault 서버에 인증 | Vault 의 Kubernetes Auth Method 사용 |
3 | Vault Agent 가 지정된 시크릿 경로에서 시크릿을 요청 | 예: secret/data/myapp |
4 | Vault Agent 가 Vault 서버로부터 시크릿을 가져와 Pod 내 공유 볼륨에 파일로 저장 | 애플리케이션이 읽을 수 있도록 지정된 경로에 저장 (/vault/secrets/...) |
5 | 애플리케이션 컨테이너는 Vault 와 직접 통신하지 않고, 공유 볼륨에 저장된 시크릿 파일을 사용 | 코드 수정 없이 환경 변수 또는 파일 참조 방식으로 사용 |
6 | 시크릿이 갱신되면 Vault Agent 가 변경 사항을 감지하고 파일을 자동으로 업데이트 | Hot reload 지원 (앱은 파일 변경을 감지하여 반영 필요) |
7 | 필요 시 Vault Agent 가 토큰 갱신, 시크릿 재요청을 자동으로 처리 | 장기 실행 파드에서도 시크릿 관리 자동화 |
■구성 방법
구분 | Injector 사용 (자동 방식) | 수동 Sidecar 구성 |
특징 | k8s 어노테이션 추가로 Vault Agent Sidecar 가 자동 주입됨 | Pod YAML 에 Vault Agent 를 수동으로 정의 |
편의성 | 관리가 쉽고 빠름 | 동작 원리를 깊이 있게 이해할 때 적합 |
사용 예시 | 운영 환경에서 주로 사용 | 테스트 환경, 교육 목적 등 |
# Injector 예시
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "my-role"
vault.hashicorp.com/agent-inject-secret-credentials.txt: "secret/data/myapp"
# 수동 sidecar 구성 예시
containers:
- name: my-app
image: my-app-image
volumeMounts:
- name: vault-secrets
mountPath: /etc/secrets
- name: vault-agent
image: hashicorp/vault:latest
args:
- agent
- -config=/etc/vault/agent-config.hcl
volumeMounts:
- name: vault-config
mountPath: /etc/vault
- name: vault-secrets
mountPath: /etc/secrets
♣ 실습
① Vault AppRole 인증 방식 구성
Vault Agent 가 시크릿을 가져오기 위해서는 Vault 서버에 인증해야 합니다.
인증 방식은 다양하게 있는데 그 중 AppRole 은 Role ID 와 Secret ID 를 사용합니다. Sidecar 구성에서는 Kubernetes Auth Method 가 더 일반적으로 사용되지만, 이번 실습에서는 AppRole 인증 방식으로 진행해 보겠습니다.
# 1. AppRole 인증 방식 활성화
vault auth enable approle || echo "AppRole already enabled"
# 결과
Success! Enabled approle auth method at: approle/
# 2. 정책 생성
vault policy write sampleapp-policy - <<EOF
path "secret/data/sampleapp/*" {
capabilities = ["read"]
}
EOF
#결과
Success! Uploaded policy: sampleapp-policy
# 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"
# 결과
Success! Data written to: auth/approle/role/sampleapp-role
# 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" > role_id.txt
echo "$SECRET_ID" > 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 -
② Vault Agent Sidecar 연동
Vault Agent 는 vault-agent-config.hcl 설정 파일을 통해 연결할 Vault 서버 정보, 사용할 인증 방법, 시크릿을 가져올 Vault KV 경로, 시크릿을 파일로 렌더링할 템플릿 구성, 그리고 시크릿을 주기적으로 갱신하고 렌더링하는 주기 등을 정의합니다.
⊙ 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: 30003 # Kind에서 설정한 Port
EOF
⊙ 생성된 컨테이너 확인
# Kubernetes Mutating Admission Webhook 설정 목록 조회
kubectl get mutatingwebhookconfigurations.admissionregistration.k8s.io
KV값 변경 후 확인
11주차 - ML Infra(GPU) on EKS (1) | 2025.04.20 |
---|---|
10주차 - K8S 시크릿 관리 (2) (0) | 2025.04.12 |
9주차 - EKS Upgrade (0) | 2025.04.03 |
8주차 - K8S CI/CD (3) (0) | 2025.03.30 |
8주차 - K8S CI/CD (1) (1) | 2025.03.30 |