Secret에 base64만 씌우고 Git에 올리셨나요? GitOps 비밀값 관리 3가지 실전 선택법
혹시 Kubernetes Secret을 base64로 바꾼 뒤 Git에 올리고 있나요? 그렇다면 암호화한 것이 아니라, 누구나 되돌릴 수 있는 형태로 비밀번호를 공개한 것에 가깝습니다.
GitOps는 원하는 상태를 Git에 모아 변경 이력과 재현성을 확보합니다. 그런데 DB 비밀번호, API 토큰, TLS 개인 키까지 평문으로 올릴 수는 없습니다. 여기서 많은 팀이 멈춥니다. “Secret만 수동으로 넣자”는 예외가 생기고, 어느 순간 Git과 실제 클러스터의 상태가 달라집니다.
Kubernetes 공식 문서는 Secret의 base64 값이 암호화가 아니며, 기본 설정에서는 API 서버의 저장소인 etcd에도 암호화되지 않은 상태로 저장될 수 있다고 경고합니다. 즉
echo 'password' | base64는 보안이 아니라 인코딩입니다.
결론부터: 정답은 하나가 아니라 운영 모델에 따라 달라진다
현장에서 자주 선택하는 방법은 Sealed Secrets, External Secrets Operator(ESO), SOPS 세 가지입니다. 이름은 비슷하지만 비밀값의 원본이 어디에 존재하는지, 누가 복호화하는지가 완전히 다릅니다.
| 방식 | 비밀 원본 | 잘 맞는 환경 | 운영 포인트 |
|---|---|---|---|
| Sealed Secrets | Git의 암호문 | 작은 팀, 단순한 클러스터 | 컨트롤러 개인 키 백업 |
| External Secrets | AWS Secrets Manager·Vault 등 | 다중 서비스, 중앙 통제·회전 필요 | IAM 최소 권한과 외부 저장소 가용성 |
| SOPS | Git의 선택적 암호문 | 멀티 클러스터, IaC 저장소 | KMS·age 키와 복호화 주체 관리 |
1. Sealed Secrets: Git에는 암호문만, 복호화는 클러스터 안에서
kubeseal은 컨트롤러의 공개 키로 Secret을 암호화해 SealedSecret 리소스로 만듭니다. Git에는 암호화된 YAML만 들어가고, 대상 클러스터의 컨트롤러만 일반 Kubernetes Secret으로 복원합니다. 기본 strict 범위에서는 이름과 네임스페이스도 암호화 조건에 포함돼 다른 곳으로 복사해 사용할 수 없습니다.
kubectl create secret generic app-db \
--namespace production \
--from-literal=password='값은 셸 기록에도 남기지 마세요' \
--dry-run=client -o yaml \
| kubeseal --format yaml \
> app-db.sealed.yaml
장점: 별도의 외부 Secret 저장소 없이 GitOps 흐름을 완성하기 쉽습니다. 주의점: 컨트롤러의 개인 키를 잃으면 기존 암호문을 복원할 수 없습니다. 클러스터 재구축을 생각한다면 키 백업과 복구 훈련이 설치보다 먼저입니다.
2. External Secrets Operator: 비밀은 금고에, Git에는 주소만
ESO는 Git에 실제 값 대신 “어느 Secret 저장소의 어떤 키를 가져올지”만 선언합니다. 컨트롤러는 AWS Secrets Manager, HashiCorp Vault 같은 외부 저장소에서 값을 읽어 Kubernetes Secret을 생성합니다. 주기 동기화, 변경 시 동기화, 최초 1회 생성 같은 정책도 선택할 수 있습니다.
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: app-db
namespace: production
spec:
refreshPolicy: Periodic
refreshInterval: 1h
secretStoreRef:
name: aws-secretsmanager
kind: SecretStore
target:
name: app-db
data:
- secretKey: password
remoteRef:
key: prod/my-service/database
property: password
장점: 중앙 감사, 자동 회전, 접근 통제를 기존 클라우드 보안 체계와 연결하기 좋습니다. 주의점: Operator가 사용할 IAM 권한을 넓게 주면 중앙 금고가 오히려 대형 사고 지점이 됩니다. 서비스·네임스페이스별로 읽을 수 있는 경로를 좁혀야 합니다.
3. SOPS: YAML 구조는 읽고, 민감한 값만 암호화
SOPS는 YAML·JSON·ENV 등의 값을 AWS KMS, GCP KMS, Azure Key Vault, age, PGP 키로 암호화할 수 있습니다. 메타데이터와 키 이름은 리뷰할 수 있고 실제 값만 암호문으로 남길 수 있어 IaC 저장소와 궁합이 좋습니다.
sops encrypt \
--encrypted-regex '^(data|stringData)$' \
secret.yaml > secret.enc.yaml
# Argo CD 또는 Flux의 배포 경로에서만 복호화
장점: 변경 리뷰가 쉽고 멀티 클러스터·멀티 클라우드에서도 유연합니다. 주의점: “누가, 어디서, 어떤 키로 복호화하는가”가 설계의 핵심입니다. CI 로그나 임시 파일에 평문이 남지 않도록 배포 플러그인과 실행 환경까지 점검해야 합니다.
30초 선택법
다음 세 문장 중 우리 팀에 가장 가까운 것을 고르면 됩니다.
- 이미 AWS Secrets Manager나 Vault를 운영한다 → External Secrets Operator
- 작은 팀이고 외부 시스템 없이 GitOps를 빠르게 완성하고 싶다 → Sealed Secrets
- IaC 파일의 리뷰 가능성과 KMS·멀티 클러스터 유연성이 중요하다 → SOPS
한 가지 도구로 모든 문제를 해결하려 하지 않아도 됩니다. 예를 들어 운영 애플리케이션 비밀값은 ESO로 중앙 관리하고, 클러스터 부트스트랩에 필요한 최소 자격 증명만 SOPS 또는 Sealed Secrets로 관리하는 혼합 구조도 현실적입니다.
도구를 골랐다면 반드시 확인할 운영 체크리스트
- etcd Encryption at Rest를 활성화했는가?
- 사람과 컨트롤러의 Secret get·list·watch 권한을 최소화했는가?
- 개발·스테이징·운영의 키와 Secret 경로가 분리돼 있는가?
- 암호화 키 분실과 클러스터 재구축을 가정한 복구 훈련을 했는가?
- 비밀번호가 애플리케이션 로그, CI 로그, 셸 히스토리, 빌드 산출물에 남지 않는가?
- 회전 후 실행 중인 Pod가 새 값을 읽는 방식까지 검증했는가?
- Git push 전에 평문 Secret을 탐지하는 스캐너와 차단 정책이 있는가?
운영 Secret의 원본은 AWS Secrets Manager에 두고 ESO에는 필요한 경로만 읽는 최소 IAM 권한을 주는 구성이 관리하기 편합니다. Argo CD는 비밀값이 아니라 ExternalSecret 선언만 배포합니다. 여기에 etcd 저장 암호화, 네임스페이스별 RBAC, 회전·감사 정책을 더해야 비로소 “GitOps다운 Secret 관리”가 완성됩니다.
마무리: GitOps의 완성도는 Secret에서 드러난다
Deployment와 Service는 Git으로 관리하면서 Secret만 운영자 노트북에서 수동 입력한다면, 자동화 파이프라인에는 아직 보이지 않는 구멍이 있습니다. 반대로 비밀값의 저장 위치, 복호화 주체, 회전과 복구까지 선언하면 GitOps는 단순한 배포 도구가 아니라 신뢰할 수 있는 운영 체계가 됩니다.
지금 저장소에서 kind: Secret을 검색해 보세요. base64 문자열이 보인다면, 오늘 고쳐야 할 첫 번째 파이프라인이 바로 거기 있습니다.
참고 문서: Kubernetes Secrets 공식 문서 · Sealed Secrets 공식 README · External Secrets Operator 공식 문서 · SOPS 공식 저장소
댓글
댓글 쓰기