시크릿 관리 심화 — etcd 암호화부터 External Secrets Operator까지
Kubernetes Secret이 base64로 인코딩된다는 건 알겠는데, 그게 정말 "안전한" 건가요?
결론부터 말하면, base64는 암호화가 아닙니다. echo 'cEBzc3cwcmQ=' | base64 -d를 실행하면 즉시 원문이 보입니다. Kubernetes Secret의 기본 보안은 RBAC로 접근을 제한하는 것뿐이며, 실제 암호화를 위해서는 추가적인 설정이 필요합니다.
Secret의 기본 보안 수준
Kubernetes Secret의 기본 상태를 정리하면 다음과 같습니다.
| 항목 | 상태 | 위험도 |
|---|---|---|
| 저장 형태 (etcd) | base64 인코딩 (평문) | 높음 |
| 네트워크 전송 | TLS 암호화 (API Server ↔ etcd) | 낮음 |
| API 접근 | RBAC로 제어 | 설정에 따라 |
| Git 저장 | 평문 노출 위험 | 매우 높음 |
# Secret 값 쉽게 확인 가능
kubectl get secret db-credentials -o jsonpath='{.data.password}' | base64 -d
# p@ssw0rd
etcd Encryption at Rest
etcd에 저장되는 Secret을 실제로 암호화하는 설정입니다.
EncryptionConfiguration 설정
# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
# 암호화 프로바이더 (우선순위 순)
- aescbc:
keys:
- name: key1
secret: <base64로 인코딩된 32바이트 키>
# identity는 암호화 안 함 — 기존 데이터 읽기용
- identity: {}
# 암호화 키 생성
head -c 32 /dev/urandom | base64
# API Server 설정에 추가
# kube-apiserver 실행 옵션:
# --encryption-provider-config=/etc/kubernetes/encryption-config.yaml
암호화 알고리즘 비교
| 알고리즘 | 특징 | 권장 |
|---|---|---|
aescbc | AES-CBC, 안정적 | 일반적 사용 |
aesgcm | AES-GCM, 인증된 암호화 | 키 순환 필수 |
secretbox | XSalsa20+Poly1305, 빠름 | 권장 |
kms | 외부 KMS 연동 (AWS KMS 등) | 프로덕션 권장 |
KMS 연동 (AWS KMS 예제)
providers:
- kms:
apiVersion: v2
name: aws-kms
endpoint: unix:///var/run/kmsplugin/socket.sock
AWS KMS를 사용하면 암호화 키를 Kubernetes 외부에서 관리할 수 있어 보안이 한 단계 더 강화됩니다.
기존 Secret 재암호화
EncryptionConfiguration을 적용해도 기존에 저장된 Secret은 업데이트하기 전까지 평문입니다.
# 모든 Secret 재암호화
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
External Secrets Operator (ESO)
외부 시크릿 관리 서비스의 값을 Kubernetes Secret으로 자동 동기화합니다.
# ESO 설치
helm install external-secrets external-secrets/external-secrets \
--namespace external-secrets \
--create-namespace
AWS Secrets Manager 연동
# SecretStore — 외부 소스 연결 설정
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets
namespace: production
spec:
provider:
aws:
service: SecretsManager
region: ap-northeast-2
auth:
jwt:
serviceAccountRef:
name: eso-sa
---
# ExternalSecret — 동기화 규칙
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: production
spec:
refreshInterval: 1h # 1시간마다 동기화
secretStoreRef:
name: aws-secrets
kind: SecretStore
target:
name: db-credentials # 생성될 Kubernetes Secret 이름
creationPolicy: Owner
data:
- secretKey: username # Kubernetes Secret의 키
remoteRef:
key: production/db # AWS Secrets Manager의 시크릿 이름
property: username # JSON 속성
- secretKey: password
remoteRef:
key: production/db
property: password
ESO가 AWS Secrets Manager에서 값을 가져와 Kubernetes Secret을 자동으로 생성하고, refreshInterval마다 동기화합니다.
HashiCorp Vault 연동
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "app-role"
serviceAccountRef:
name: vault-sa
Sealed Secrets — GitOps 친화적
Sealed Secrets는 Git에 안전하게 저장할 수 있는 암호화된 Secret을 제공합니다.
# 컨트롤러 설치
helm install sealed-secrets sealed-secrets/sealed-secrets \
--namespace kube-system
# kubeseal CLI로 Secret 암호화
kubectl create secret generic db-secret \
--from-literal=password=p@ssw0rd \
--dry-run=client -o yaml | \
kubeseal --format yaml > sealed-secret.yaml
# sealed-secret.yaml — Git에 커밋해도 안전
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: db-secret
namespace: production
spec:
encryptedData:
password: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq...
클러스터의 Sealed Secrets 컨트롤러만이 이를 복호화하여 일반 Secret으로 변환합니다.
시크릿 관리 전략 비교
| 방식 | 복잡도 | 보안 수준 | GitOps | 키 관리 |
|---|---|---|---|---|
| 기본 Secret + RBAC | 낮음 | 기본 | 불가 | 수동 |
| etcd 암호화 | 중간 | 중간 | 불가 | 수동/KMS |
| Sealed Secrets | 중간 | 높음 | 가능 | 자동 |
| ESO + Vault/AWS SM | 높음 | 매우 높음 | 가능 | 외부 관리 |
시크릿 관리 모범 사례
- 최소한: etcd 암호화 + RBAC 제한
- GitOps: Sealed Secrets 사용
- 엔터프라이즈: ESO + Vault/AWS Secrets Manager
추가 보안 조치
# Secret 접근 감사 로그 활성화
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
resources:
- group: ""
resources: ["secrets"]
# Secret을 사용하는 Pod 확인
kubectl get pods --all-namespaces -o json | \
jq '.items[] | select(.spec.volumes[]?.secret) | .metadata.name'
# 불필요한 Secret 정리
kubectl get secrets --all-namespaces --no-headers | wc -l
실무에서 주의할 점
- Secret을 Git에 절대 커밋하지 마세요: .gitignore에 추가하거나 Sealed Secrets를 사용
- 환경변수보다 볼륨 마운트를 권장합니다: 환경변수는 프로세스 목록이나 로그에 노출될 수 있습니다
- 시크릿 교체(rotation) 자동화: ESO의 refreshInterval이나 Vault의 동적 시크릿 활용
- 감사 로그를 활성화하세요: 누가 언제 Secret에 접근했는지 추적
정리
Kubernetes Secret의 base64는 암호화가 아닙니다. 최소한 etcd 암호화와 RBAC 제한을 적용하고, GitOps 환경에서는 Sealed Secrets, 엔터프라이즈 환경에서는 External Secrets Operator와 Vault/AWS Secrets Manager를 조합하여 시크릿을 안전하게 관리해야 합니다.