[도입전략] Git 시크릿 관리와 Vault 도입으로 보안 강화하기

Tech Story/Data Center & Security

[도입전략] Git 시크릿 관리와 Vault 도입으로 보안 강화하기

 

 
[ kt cloud Foundation플랫폼팀 이초환 님 ]

📋 요약

이 글에서는 Git 시크릿 관리 개선과 Kubernetes Secret 오브젝트 제거를 위한 Vault 도입 전략을 다룹니다.

시크릿 노출 위험을 줄이고 운영 환경의 보안 기준을 명확히 하는 방향을 정리합니다.

#Vault #GitSecret #Kubernetes #CSIProvider #시크릿관리

 


왜 Sealed Secrets도 SOPS도 아닌 Vault였나 — 도입 배경과 전략

요건은 두 가지였다

회사에서 시크릿 관리 개선 요청을 받았다. 요건을 정리하니 두 가지로 요약됐다.

  1. Git 레포에 평문으로 관리되는 시크릿을 제거할 것
  2. Kubernetes 클러스터 위에 Secret 오브젝트로 민감정보가 떠 있는 것을 없앨 것

두 번째 요건이 핵심이었다. 단순히 "시크릿을 Git 밖으로 꺼내는 것"이 아니라, K8s Secret 오브젝트 자체를 클러스터에서 제거하는 것이 목표였다. K8s Secret 오브젝트는 기본적으로 etcd에 base64 인코딩된 상태로 저장된다. 암호화가 아닌 단순 인코딩이기 때문에, etcd에 접근 권한이 있는 주체라면 누구든 값을 읽을 수 있다. 이 구조 자체를 없애는 것이 목표였고, 그 차이가 도구 선택을 완전히 갈랐다.


왜 Vault인가 — 대안 비교

도구 Git 평문 제거 K8s Secret 제거
Sealed Secrets
SOPS
Vault + CSI Provider
 

Sealed Secrets는 암호화된 시크릿을 Git에 커밋하고, 클러스터에서 복호화해 K8s Secret 오브젝트를 생성한다. SOPS는 파일 자체를 암호화해 Git에서 관리하고, CI/CD 파이프라인에서 복호화해 K8s Secret 오브젝트로 배포한다. 두 방식 모두 Git 평문 문제는 해결하지만, 최종적으로 복호화된 값이 K8s Secret 오브젝트로 클러스터에 올라간다는 점에서 두 번째 요건을 충족하지 못한다.

 

Vault는 시크릿을 외부 저장소에서 관리하고, 필요한 시점에 Pod로 직접 주입하는 방식이라 두 가지를 동시에 해결할 수 있는 유일한 현실적 선택지였다. 물론 별도 인프라를 직접 운영해야 한다는 트레이드오프가 있지만, 두 요건을 모두 만족하려면 감수해야 했다.

[도입전략] Git 시크릿 관리와 Vault 도입으로 보안 강화하기

Vault 도입 전 설계해야 할 것들

설치하고 바로 쓸 수 있는 도구가 아니다. 아래 항목들을 먼저 설계해야 한다.

 

스토리지 백엔드: Vault 내부 데이터는 암호화되어 스토리지 백엔드에 저장된다. 운영 환경에서는 외부 의존성 없이 Vault 클러스터 자체적으로 합의 알고리즘을 운영하는 Integrated Storage (Raft)가 권장된다.

 

Seal / Unseal 전략: Vault는 재시작 시 항상 Sealed 상태로 올라온다. 이 상태에서는 모든 요청이 거부되며, Unseal 키를 입력해야만 정상 동작을 시작한다. 자동화된 환경에서는 AWS KMS, GCP Cloud KMS 같은 클라우드 KMS를 연동하거나 내부적인 별도 프로세스를 연동하여 Auto Unseal 설정이 필수다. 수동 Unseal에 의존하면, 예상치 못한 재시작 상황에서 서비스 전체가 멈출 수 있다.

 

인증 방법 (Auth Method): Vault에 접근하는 모든 주체는 신원을 증명해야 한다. K8s 환경에서는 Pod의 ServiceAccount JWT를 Vault가 검증하는 Kubernetes Auth가 기본이다. K8s 외부의 CI/CD 파이프라인이나 VM에는 AppRole 방식이 적합하다. 사용자 접근에 관해서는 OKTA, LDAP 과 같은 통합 사용자 인증도구를 Auth method로 연동 할 수 있다.

 

Policy 설계: 최소 권한 원칙에 따라, 서비스별로 필요한 경로의 시크릿만 읽을 수 있도록 HCL 정책을 작성한다. 경로 규칙(secret/<환경>/<서비스>/<키>)도 초기에 반드시 잡아야 한다. 나중에 경로를 바꾸면 연관된 Policy, SecretProviderClass, 애플리케이션 설정을 전부 함께 수정해야 하는 상황이 온다.


아키텍처 구조

[도입전략] Git 시크릿 관리와 Vault 도입으로 보안 강화하기

시크릿 주입 방식 비교

Vault와 연동해 시크릿을 주입하는 방식은 크게 네 가지다. 각 방식의 K8s Secret 생성 여부와 주요 특징을 정리하면 다음과 같다.

 

방식 K8s Secret 생성 동작 시점 자동 갱신 주요 특징
CSI Provider 선택 (secretObjects 옵션) Pod 기동 시 마운트 △ (rotation 옵션) SecretProviderClass CRD로 GitOps 관리. K8s Secret 없이 파일로 직접 마운트가 기본
Vault Agent Injector ✗ (옵션 없음) Pod 기동 시 사이드카 주입 ✓ (사이드카가 상시 갱신) Mutating Webhook으로 어노테이션만 추가하면 자동 주입. tmpfs에 직접 쓰기
AVP (ArgoCD Vault Plugin) ✓ (항상 생성) ArgoCD 배포 시점 ✗ (재배포 필요) ArgoCD 필수. values.yaml 플레이스홀더를 치환해 K8s Secret 생성. GitOps 흐름에 자연스럽게 통합
ESO (External Secrets Operator) ✓ (항상 생성) ExternalSecret CR 동기화 주기 ✓ (주기적 폴링) K8s Secret 생성이 목적. Vault 외 다양한 외부 시크릿 소스(AWS SM, GCP SM 등) 지원
 

K8s Secret 오브젝트를 없애는 것이 목표라면 CSI ProviderVault Agent Injector 중에서 선택해야 한다. 반면 기존 앱이 K8s Secret을 직접 참조하는 구조라 마이그레이션 비용이 크다면, K8s Secret을 자동 동기화해주는 ESO가 현실적인 중간 단계가 될 수 있다.


CSI가 1순위인 이유, AVP가 보완인 이유

CSI Provider 방식에서는 SecretProviderClass라는 CRD를 GitOps로 관리한다. Pod가 기동될 때 CSI Driver가 Vault에 인증하고 시크릿을 가져와 직접 볼륨으로 마운트한다. 이 과정에서 K8s Secret 오브젝트는 생성되지 않는다. Git에는 시크릿 참조 경로만 존재하고, 클러스터에는 Secret 오브젝트가 없으며, 실제 값은 Vault에만 있다. 두 요건을 동시에 만족하는 구조다.

 

반면 AVP(ArgoCD Vault Plugin) 는 Helm values나 Kustomize 파일 내 플레이스홀더를 배포 시점에 Vault 값으로 치환해 K8s Secret 오브젝트를 생성한다. Git에 평문이 없다는 점은 좋지만, 결국 K8s Secret 오브젝트는 클러스터에 남는다.

 

두 방식의 흐름을 나란히 비교하면 차이가 명확해진다.

[도입전략] Git 시크릿 관리와 Vault 도입으로 보안 강화하기

AVP가 필요해진 건 OpenStack처럼 구조가 복잡한 컴포넌트 때문이었다. 내부적으로 K8s Secret 오브젝트를 직접 참조하도록 설계된 서비스는 CSI 마운트 방식으로 대응이 불가능하다. 이런 예외 케이스에 한해 AVP를 보완적으로 도입했다. 핵심은 AVP를 기본 수단으로 삼지 않는 것이다. 예외를 허용하되, 예외임을 명확히 인지하고 관리해야 체계가 흐트러지지 않는다.


운영에서 반드시 챙겨야 할 것들

Vault를 설치하는 것과 안전하게 운영하는 것은 다른 문제다. 도입 이후 반드시 챙겨야 하는 두 가지를 소개한다.

 

Audit Log + logrotate: Vault Audit Log는 모든 인증 요청과 시크릿 접근을 빠짐없이 기록한다. 활성화하지 않으면 누가 언제 어떤 시크릿에 접근했는지 사후 추적이 불가능하다. 특히 보안 감사나 인시던트 대응 시 Audit Log가 없으면 원인 파악 자체가 막힌다. 운영 환경에서는 로그가 빠르게 쌓이기 때문에 logrotate 설정을 함께 구성해 디스크 문제를 사전에 막아야 한다.

 

주기적 토큰 비활성화: Vault의 토큰과 리스(Lease)에는 TTL을 설정할 수 있지만, 장기 실행 시스템에서는 만료되지 않은 토큰이 조용히 누적될 수 있다. 특히 퇴사자 발생 시 해당 구성원의 토큰과 Policy를 즉시 회수하지 않으면, 시크릿 관리 체계가 갖춰져 있어도 실질적인 보안 구멍이 남는다. vault token lookup으로 주기적으로 활성 토큰을 감사하고, vault token revoke를 통한 명시적 회수 루틴을 운영 프로세스에 포함해야 한다.


Vault가 있으면 가능해지는 것 — OIDC

Vault를 도입하고 나면 시크릿 관리를 넘어 신원(Identity) 체계로 확장할 수 있다. Vault는 OIDC Identity Provider 역할도 지원하기 때문이다. 서비스 간 JWT 기반 신원 발급, Okta·Google Workspace 같은 외부 IdP와의 SSO 연동이 가능해진다. 개발자 개인 접근도 LDAP이나 GitHub 대신 Vault OIDC로 일원화하면, 인증 체계가 단일 지점에서 관리된다.

 

이 방향으로 확장하면 "누가 어떤 시크릿에 접근할 수 있는가"라는 Policy 기반 체계가 사람과 시스템 모두에 일관되게 적용된다. 처음에는 "Git 평문 제거"라는 소박한 목표로 시작했지만, 기반이 갖춰지면 Vault는 조직 전체의 신원 허브(Identity Hub) 로 역할이 자연스럽게 확장된다.

 

다만 Vault를 OIDC Provider로 운영할 경우, userpass Auth Method로 관리되는 사용자 엔티티의 주기적 비활성화도 운영 루틴에 포함해야 한다. Vault의 userpass는 별도의 만료 개념이 없기 때문에, 퇴사자나 역할이 변경된 구성원의 계정이 자동으로 정리되지 않는다. OIDC를 통해 내부 서비스나 대시보드에 접근하는 구조라면, 해당 사용자 엔티티가 살아 있는 한 인증 자체는 여전히 가능한 상태가 된다. HR 프로세스와 연동해 계정 비활성화(vault write auth/userpass/users/<username> password=... disable=true 또는 엔티티 삭제)를 퇴사 처리 절차에 명시적으로 포함시키는 것이 필요하다.


로드맵 요약

단계 목표 핵심 작업
Phase 1 평문 제거 + K8s Secret 오브젝트 제거 Vault KV + K8s Auth + CSI Provider
Phase 2 예외 케이스 대응 AVP (K8s Secret이 불가피한 경우 한정)
Phase 3 운영 안전화 Audit Log + logrotate + 토큰 주기 감사
Phase 4 신원 체계 확장 OIDC Provider 연동
Phase 5 동적 시크릿 + 자동 로테이션 Vault Agent Injector 도입
 

Phase 5의 Vault Agent Injector는 Mutating Webhook으로 동작한다. Pod에 어노테이션만 추가하면, 배포 시 자동으로 Vault Agent 사이드카가 주입된다. 이 사이드카는 Pod와 함께 살아있으면서 Vault 토큰을 직접 관리하고, 시크릿이 갱신되면 자동으로 다시 가져와 파일을 업데이트한다.

 

여기서 중요한 점은 K8s Secret 오브젝트를 거치는 경로가 구조적으로 없다는 것이다. Injector는 Vault에서 가져온 값을 Pod 내부의 공유 tmpfs 볼륨에 직접 파일로 쓴다. K8s Secret 생성 옵션 자체가 존재하지 않는다. CSI Provider가 기본은 Pod 직접 마운트이되 secretObjects 옵션으로 K8s Secret을 함께 생성할 수도 있는 구조라면, Injector는 애초에 그 선택지가 없다. K8s Secret 배제라는 요건에 가장 강하게 일치하는 구조다.

 

[도입전략] Git 시크릿 관리와 Vault 도입으로 보안 강화하기

CSI Provider가 "Pod 기동 시점에 한 번 가져오는" 구조라면, Injector는 "항상 Vault와 연결된 채로 동적으로 유지하는" 구조다. DB 비밀번호처럼 주기적으로 로테이션되는 동적 시크릿을 앱 코드 변경 없이 실시간 반영할 수 있다는 점에서, Vault의 기능을 가장 완전하게 활용하는 방식이다. 다만 모든 Pod에 사이드카가 붙는 만큼 리소스 오버헤드가 생기기 때문에, 충분한 운영 경험이 쌓인 뒤 도입하는 것이 현실적이다.

 

Vault는 한 번에 모든 것을 갖출 필요가 없다. “Phase 1”만 완료해도 보안 수준은 이전과 완전히 달라진다. 중요한 건 요건을 명확히 하고, 그 요건에 맞는 도구와 방식을 선택하는 것이다. "K8s Secret 오브젝트까지 없애야 한다"는 요건이 없었다면 SOPS로도 충분했을 수 있다. 요건이 도구를 결정하고, 도구가 아키텍처를 결정한다.

 

 

kt cloud 플랫폼 바로가기

❓ 자주 묻는 질문 (FAQ)

Q. etcd 암호화(Encryption at Rest)를 활성화하면 K8s Secret을 그냥 써도 되지 않나요?
A. etcd Encryption at Rest를 켜면 Secret 값이 etcd에 암호화되어 저장되는 건 맞습니다. 하지만 이 암호화는 etcd 파일 수준의 보호일 뿐, Kubernetes API를 통한 접근은 여전히 평문으로 이루어집니다. 즉, kubectl get secret -o yaml로 Secret 오브젝트에 접근할 수 있는 권한이 있다면 값은 그대로 노출됩니다. 접근 제어(RBAC)를 아무리 잘 설계해도, Secret 오브젝트가 존재하는 한 클러스터 내 권한 탈취 경로가 남아 있습니다. CSI Provider 방식은 그 오브젝트 자체를 없애는 접근이라 본질적으로 다릅니다.
Q. CSI Driver 방식에서 Vault가 다운되면 Pod 재시작 시 어떻게 되나요?
A. Vault가 Sealed 상태이거나 응답 불가 상태에서 Pod가 재시작을 시도하면, CSI Driver가 시크릿을 가져오지 못해 Pod가 기동되지 않습니다. 이미 실행 중인 Pod는 영향을 받지 않지만, 재시작이 필요한 순간 서비스가 올라오지 않는 상황이 발생합니다. 이 때문에 운영 환경에서 Vault HA 구성과 Auto Unseal은 선택이 아닌 필수입니다. CSI Driver의 secretObjects 옵션을 통해 마운트된 시크릿을 로컬에 캐시하는 방법도 있지만, 근본적인 해결은 Vault 자체의 가용성을 확보하는 것입니다.
Q. AVP는 ArgoCD가 없으면 사용할 수 없나요?
A. AVP는 ArgoCD의 플러그인 구조를 활용하는 방식이기 때문에, ArgoCD를 GitOps 도구로 사용하고 있다는 전제가 필요합니다. ArgoCD 없이 Helm이나 Kustomize만 사용하는 환경이라면 AVP는 선택지에 오르지 않습니다. 이 경우 시크릿 주입은 CSI Driver 방식이나, External Secrets Operator(ESO) 같은 별도 도구로 해결해야 합니다.
Q. CSI와 AVP를 혼용하면 관리가 복잡해지지 않나요?
A. 실제로 복잡해집니다. 어떤 컴포넌트가 CSI로 마운트받는지, 어떤 컴포넌트가 AVP를 통해 K8s Secret으로 주입받는지 추적이 어려워지고, 운영자가 바뀌었을 때 혼란이 생기기 쉽습니다. AVP 적용 범위를 특정 네임스페이스나 레이블로 명시적으로 구분하고, 배포 정의 내에 주입 방식을 주석으로 명시하는 것이 도움이 됩니다. 가장 중요한 건 AVP가 예외 수단임을 팀 내에서 명확히 공유하고, 신규 서비스가 아무 고민 없이 AVP를 택하는 일이 생기지 않도록 가이드라인을 세워두는 것입니다.
Q. Vault 도입의 가장 큰 트레이드오프는 무엇인가요?
A. 별도 인프라를 직접 운영해야 한다는 점입니다. Sealed Secrets나 SOPS는 별도 서버 없이 클러스터와 파이프라인 안에서 동작하지만, Vault는 HA 구성, Auto Unseal, 백업 전략, 모니터링까지 직접 설계하고 운영해야 합니다. 운영 성숙도가 낮은 팀에서는 Vault 자체가 새로운 단일 장애점이 될 수 있습니다. 다만 "K8s Secret 오브젝트 자체를 없애야 한다"는 요건이 명확하다면, 이 운영 비용을 감수하는 것 외에 현실적인 대안이 없습니다. 요건이 없다면 SOPS로 시작해서 필요할 때 Vault로 마이그레이션하는 경로도 충분히 유효합니다.

 


📚 관련/출처