ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Kubernetes 민감정보 관리 완벽 가이드: Secret, 암호화, 그리고 실전 전략
    실제 경험과 인사이트를 AI와 함께 정리한 글 2025. 10. 22. 13:11

    Kubernetes 민감정보 관리 완벽 가이드: Secret, 암호화, 그리고 실전 전략

    작성일: 2025-10-22
    태그: Kubernetes, Security, Secret Management, DevOps, GitOps
    난이도: 중급~고급

    들어가며

    Kubernetes 환경에서 가장 신경 써야 할 부분 중 하나가 바로 민감정보(Sensitive Data) 관리입니다. 데이터베이스 비밀번호, API 키, OAuth 클라이언트 시크릿, JWT 토큰 등 수많은 민감정보가 클러스터 곳곳에 흩어져 있습니다.

    이 글에서는 imprun.dev 플랫폼을 구축하면서 겪은 실전 경험을 바탕으로, 프로덕션 수준의 민감정보 관리 전략을 소개합니다.

    우리가 관리해야 하는 민감정보

    imprun.dev에서 관리하는 민감정보 목록:

    민감정보 카테고리:
      인증/보안:
        - JWT Secret (64자 랜덤 문자열)
        - GitHub OAuth (Client ID + Secret)
        - Google OAuth (Client ID + Secret)
        - Admin 계정 초기 비밀번호
    
      데이터베이스:
        - MongoDB Root 비밀번호
        - MongoDB 연결 URI
        - Redis 비밀번호
    
      인프라:
        - TLS/SSL 인증서 (cert-manager)
        - Cloudflare API 토큰 (DNS Challenge)
        - Container Registry 인증정보
    
      런타임:
        - 사용자 앱 전용 DB 접근 토큰
        - Runtime Exporter 인증 토큰

    문제: 이 모든 정보를 안전하게 저장하면서도, 운영 편의성을 유지해야 합니다.


    Part 1: Kubernetes Secret 기초와 한계

    Kubernetes Secret이란?

    민감정보를 Base64로 인코딩하여 저장하는 Kubernetes 리소스입니다.

    apiVersion: v1
    kind: Secret
    metadata:
      name: mongodb-auth
      namespace: imprun-system
    type: Opaque
    data:
      username: cm9vdA==           # base64("root")
      password: bXlwYXNzd29yZA==   # base64("mypassword")

    Secret의 장점

    환경 변수 주입 간편:

    env:
      - name: DB_PASSWORD
        valueFrom:
          secretKeyRef:
            name: mongodb-auth
            key: password

    RBAC 연동: 네임스페이스/역할별 접근 제어
    자동 마운트: Pod에 파일로 자동 마운트 가능

    Secret의 한계와 위험

    Base64는 암호화가 아님 (디코딩만으로 원본 노출)
    etcd에 평문 저장 (etcd 암호화 미활성화 시)
    Git에 커밋 위험 (실수로 values-production.yaml 커밋 시 유출)
    Secret Sprawl: Secret이 여러 곳에 분산되면 관리 복잡도 폭발


    Part 2: imprun.dev의 Secret 관리 전략

    전략 1: 중앙 집중식 Secret 생성

    문제: 5개의 서브차트가 각각 Secret을 생성하면 관리 불가능

    해결: Umbrella Chart의 templates/secrets.yaml에서 모든 Secret을 생성

    k8s/imprun/templates/secrets.yaml:

    {{- if .Values.secrets.jwt.secret }}
    ---
    apiVersion: v1
    kind: Secret
    metadata:
      name: imprun-jwt-secret
      namespace: {{ .Release.Namespace }}
      annotations:
        helm.sh/resource-policy: keep  # ⭐ 핵심: Helm 업그레이드 시 유지
    type: Opaque
    data:
      JWT_SECRET: {{ .Values.secrets.jwt.secret | b64enc }}
    {{- end }}
    
    {{- if .Values.secrets.github.clientSecret }}
    ---
    apiVersion: v1
    kind: Secret
    metadata:
      name: imprun-github-oauth
      namespace: {{ .Release.Namespace }}
      annotations:
        helm.sh/resource-policy: keep
    type: Opaque
    data:
      GITHUB_CLIENT_ID: {{ .Values.secrets.github.clientId | b64enc }}
      GITHUB_CLIENT_SECRET: {{ .Values.secrets.github.clientSecret | b64enc }}
    {{- end }}

    핵심 포인트:

    1. helm.sh/resource-policy: keep: 업그레이드 시 Secret 재생성 방지
    2. 조건부 생성: 값이 제공된 경우만 생성 ({{- if }})
    3. 단일 진실 공급원: 모든 Secret이 한 파일에 정의됨

    전략 2: values-production.yaml 분리

    절대 Git에 커밋하지 않는 파일: values-production.yaml

    # k8s/imprun/values-production.yaml (⚠️ .gitignore 필수)
    
    secrets:
      jwt:
        secret: "abcd1234...64자 랜덤 문자열"  # openssl rand -base64 48
    
      github:
        clientId: "Ov23liOCvWeKTz6TO6Pn"
        clientSecret: "ghp_xxxxxxxxxxxxxxxxxxxx"
    
      google:
        clientId: "123456789-xxxxxxx.apps.googleusercontent.com"
        clientSecret: "GOCSPX-xxxxxxxxxxxxxxxx"
    
      adminSeed:
        username: "admin@imprun.dev"
        password: "초기_관리자_비밀번호"
    
    mongodb:
      auth:
        rootPassword: "몽고_루트_비밀번호"
    
    redis:
      auth:
        password: "레디스_비밀번호"

    보안 원칙:

    # .gitignore
    values-production.yaml
    values-*.secret.yaml
    *.pem
    *.key

    전략 3: 자동 생성 vs 명시적 제공

    선택지 제공:

    방법 장점 단점 사용 시기
    자동 생성 편리함, 충돌 없음 값 추적 어려움 개발 환경
    명시적 제공 예측 가능, 백업 용이 초기 설정 번거로움 프로덕션

    Helm Template 예시 (자동 생성):

    {{- if not .Values.secrets.jwt.secret }}
    ---
    apiVersion: v1
    kind: Secret
    metadata:
      name: imprun-jwt-secret
      annotations:
        helm.sh/resource-policy: keep
    data:
      JWT_SECRET: {{ randAlphaNum 64 | b64enc }}  # 64자 랜덤 생성
    {{- end }}

    주의: 자동 생성 시 helm.sh/resource-policy: keep 필수 (재설치 시 값 변경 방지)


    Part 3: KubeBlocks와 자동 생성 Secret

    문제: 외부 Operator가 생성하는 Secret

    MongoDB를 KubeBlocks로 관리하면, KubeBlocks Operator가 Secret을 자동 생성합니다.

    # k8s/imprun/templates/mongodb-cluster.yaml
    apiVersion: apps.kubeblocks.io/v1alpha1
    kind: Cluster
    metadata:
      name: mongodb
    spec:
      clusterDefinitionRef: mongodb
      terminationPolicy: DoNotTerminate
      componentSpecs:
        - name: mongodb
          componentDefRef: mongodb

    KubeBlocks가 생성하는 Secret:

    $ kubectl get secret mongodb-conn-credential -n imprun-system
    
    NAME                       TYPE     DATA   AGE
    mongodb-conn-credential    Opaque   4      2m

    포함 내용:

    username: root
    password: values에서 지정한 rootPassword
    endpoint: mongodb-mongodb:27017
    port: "27017"

    해결: Secret 이름 표준화

    문제: 우리가 mongodb-auth를 생성했지만, KubeBlocks는 mongodb-conn-credential 생성

    해결: KubeBlocks의 표준 이름을 사용하도록 모든 참조 변경

    # imprun-server Deployment
    env:
      - name: MONGODB_ROOT_USER
        valueFrom:
          secretKeyRef:
            name: mongodb-conn-credential  # ✅ KubeBlocks 표준 이름
            key: username
      - name: MONGODB_ROOT_PASSWORD
        valueFrom:
          secretKeyRef:
            name: mongodb-conn-credential
            key: password

    교훈: 외부 Operator 사용 시 해당 Operator의 Secret 네이밍 규칙 따르기


    Part 4: Secret 라이프사이클 관리

    helm install: 초기 생성

    # 1차: Secret이 존재하지 않으므로 생성
    helm install imprun ./k8s/imprun -n imprun-system \
      -f values-production.yaml

    helm upgrade: 기존 유지

    annotations:
      helm.sh/resource-policy: keep  # ⭐ 이 annotation 덕분에 유지됨
    # 2차: Secret은 그대로 유지, Deployment만 업데이트
    helm upgrade imprun ./k8s/imprun -n imprun-system \
      -f values-production.yaml

    장점:

    • 비밀번호가 변경되지 않음 (서비스 안정성)
    • 롤링 업데이트 중에도 인증 문제 없음

    helm uninstall: 삭제하지 않음

    helm uninstall imprun -n imprun-system
    
    # ✅ Secret은 여전히 존재
    kubectl get secrets -n imprun-system
    # NAME                      TYPE     DATA   AGE
    # imprun-jwt-secret         Opaque   1      10d
    # imprun-github-oauth       Opaque   2      10d

    재설치 시:

    # Secret이 이미 존재하므로 재생성하지 않음 ({{- if }} 조건)
    helm install imprun ./k8s/imprun -n imprun-system

    Secret 완전 삭제 (신중히!)

    # ⚠️ 프로덕션에서는 절대 실행 금지
    kubectl delete secret imprun-jwt-secret -n imprun-system
    
    # 재설치 시 새 값으로 생성됨
    helm upgrade imprun ./k8s/imprun -n imprun-system

    Part 5: 민감정보 노출 방지 전략

    1. .gitignore 철저히 관리

    # k8s/imprun/.gitignore
    values-production.yaml
    values-*.secret.yaml
    *.pem
    *.key
    *.crt
    secrets/
    
    # k8s/victoriametrics/.gitignore
    values-production.yaml

    검증:

    # Git에 커밋되지 않았는지 확인
    git status --ignored | grep values-production

    2. Pre-commit Hook으로 자동 검증

    # .git/hooks/pre-commit
    #!/bin/bash
    
    # 민감정보 패턴 검색
    if git diff --cached | grep -E "(password|secret|token|apiKey).*[:=].*['\"]" ; then
      echo "❌ 민감정보가 커밋에 포함되어 있습니다!"
      exit 1
    fi
    
    # values-production.yaml 커밋 차단
    if git diff --cached --name-only | grep -E "values-production\.yaml" ; then
      echo "❌ values-production.yaml은 커밋할 수 없습니다!"
      exit 1
    fi

    3. Secret 백업 전략

    안전한 백업:

    # 1. Secret을 YAML로 추출
    kubectl get secrets -n imprun-system -o yaml > secrets-backup.yaml
    
    # 2. GPG로 암호화
    gpg --encrypt --recipient admin@imprun.dev secrets-backup.yaml
    
    # 3. 암호화된 파일만 백업 스토리지에 저장
    aws s3 cp secrets-backup.yaml.gpg s3://imprun-backups/secrets/
    
    # 4. 원본 삭제
    rm secrets-backup.yaml

    복구:

    # 1. 다운로드 및 복호화
    aws s3 cp s3://imprun-backups/secrets/secrets-backup.yaml.gpg .
    gpg --decrypt secrets-backup.yaml.gpg > secrets-backup.yaml
    
    # 2. 복원
    kubectl apply -f secrets-backup.yaml
    
    # 3. 복호화된 파일 삭제
    rm secrets-backup.yaml*

    4. RBAC으로 접근 제한

    # k8s/imprun/templates/rbac.yaml
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: secret-reader
      namespace: imprun-system
    rules:
      - apiGroups: [""]
        resources: ["secrets"]
        resourceNames:
          - imprun-jwt-secret
          - imprun-github-oauth
        verbs: ["get", "list"]  # create, update, delete 제외
    
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: imprun-server-secret-access
      namespace: imprun-system
    subjects:
      - kind: ServiceAccount
        name: imprun-server
        namespace: imprun-system
    roleRef:
      kind: Role
      name: secret-reader
      apiGroup: rbac.authorization.k8s.io

    원칙:

    • Pod는 필요한 Secret만 접근 가능
    • get, list만 허용 (update, delete 금지)
    • ServiceAccount별로 세밀한 권한 분리

    Part 6: 고급 전략 - External Secret Operator

    현재의 한계

    현재 방식 (values-production.yaml):

    • ❌ 여전히 평문 파일로 존재
    • ❌ 개발자 로컬에 민감정보 저장
    • ❌ Secret Rotation 수동 작업

    해결: External Secrets Operator

    AWS Secrets Manager / HashiCorp Vault 연동:

    # k8s/imprun/templates/external-secret.yaml
    apiVersion: external-secrets.io/v1beta1
    kind: ExternalSecret
    metadata:
      name: imprun-secrets
      namespace: imprun-system
    spec:
      refreshInterval: 1h
      secretStoreRef:
        name: aws-secretsmanager
        kind: SecretStore
      target:
        name: imprun-jwt-secret
        creationPolicy: Owner
      data:
        - secretKey: JWT_SECRET
          remoteRef:
            key: imprun/production/jwt-secret

    장점:

    • ✅ 중앙 집중식 Secret 관리 (AWS Secrets Manager)
    • ✅ 자동 Rotation 지원
    • ✅ 감사 로그 (누가 언제 접근했는지 추적)
    • ✅ 다중 클러스터 Secret 동기화

    단점:

    • 추가 복잡도
    • 외부 서비스 의존성
    • 비용 발생 (AWS Secrets Manager: $0.40/secret/month)

    Part 7: 실전 체크리스트

    배포 전 검증

    # ✅ 1. .gitignore 확인
    git check-ignore -v k8s/imprun/values-production.yaml
    # .gitignore:10:values-production.yaml    k8s/imprun/values-production.yaml
    
    # ✅ 2. Secret annotation 확인
    kubectl get secret imprun-jwt-secret -n imprun-system -o yaml | grep "resource-policy"
    # helm.sh/resource-policy: keep
    
    # ✅ 3. Secret 값 검증 (Base64 디코딩)
    kubectl get secret imprun-jwt-secret -n imprun-system \
      -o jsonpath='{.data.JWT_SECRET}' | base64 -d
    # (64자 랜덤 문자열 출력 확인)
    
    # ✅ 4. RBAC 권한 확인
    kubectl auth can-i get secrets --as=system:serviceaccount:imprun-system:imprun-server -n imprun-system
    # yes
    
    kubectl auth can-i delete secrets --as=system:serviceaccount:imprun-system:imprun-server -n imprun-system
    # no

    보안 감사

    # 1. 모든 Secret 목록
    kubectl get secrets --all-namespaces
    
    # 2. 특정 Secret 사용처 추적
    kubectl get pods -n imprun-system -o json | \
      jq -r '.items[] | select(.spec.containers[].env[]?.valueFrom.secretKeyRef.name == "imprun-jwt-secret") | .metadata.name'
    
    # 3. Secret 생성 일자 확인 (오래된 Secret은 Rotation 고려)
    kubectl get secrets -n imprun-system -o json | \
      jq -r '.items[] | [.metadata.name, .metadata.creationTimestamp] | @tsv'

    Part 8: MongoDB Secret 특수 사례 - terminationPolicy

    문제: MongoDB 데이터 보존

    # KubeBlocks Cluster
    spec:
      terminationPolicy: DoNotTerminate  # ⭐ 핵심 설정

    시나리오:

    # 1. Helm Chart 삭제
    helm uninstall imprun -n imprun-system
    
    # 2. MongoDB Cluster는?
    kubectl get cluster -n imprun-system
    # NAME      STATUS    AGE
    # mongodb   Running   10d  ← 여전히 존재!
    
    # 3. Secret도 유지됨
    kubectl get secret mongodb-conn-credential -n imprun-system
    # NAME                       TYPE     DATA   AGE
    # mongodb-conn-credential    Opaque   4      10d

    장점:

    • ✅ 실수로 helm uninstall 해도 데이터 보존
    • ✅ Secret도 함께 유지되어 재설치 시 즉시 연결 가능

    완전 삭제 방법:

    # 방법 1: Cluster만 삭제 (권장)
    kubectl delete cluster mongodb -n imprun-system
    
    # 방법 2: 네임스페이스 전체 삭제
    kubectl delete namespace imprun-system

    결론: 민감정보 관리의 핵심 원칙

    1. 분리 (Separation)

    • 코드와 민감정보 분리 (values.yaml vs values-production.yaml)
    • 환경별 Secret 분리 (dev/staging/production)

    2. 최소 권한 (Least Privilege)

    • RBAC으로 Pod별 필요한 Secret만 접근
    • get, list만 허용 (update, delete 금지)

    3. 암호화 (Encryption)

    • etcd 암호화 활성화 (--encryption-provider-config)
    • 백업 시 GPG 암호화
    • 전송 중 TLS 필수

    4. 감사 (Auditing)

    • Secret 접근 로그 기록
    • 정기적인 Secret Rotation
    • 사용하지 않는 Secret 정리

    5. 자동화 (Automation)

    • Helm으로 Secret 생성 자동화
    • helm.sh/resource-policy: keep로 안전한 업그레이드
    • External Secrets Operator로 고급 관리

    다음 단계

    단계별 개선 로드맵

    Level 1 (현재): Helm + values-production.yaml

    • ✅ 구현 간단
    • ✅ 대부분의 스타트업/중소기업에 적합

    Level 2 (6개월 내): External Secrets Operator

    • 중앙 집중식 관리
    • 자동 Rotation

    Level 3 (1년 내): Vault + Dynamic Secrets

    • 동적 Secret 생성 (DB 계정 자동 생성/삭제)
    • 단기 유효 토큰
    • 완벽한 감사 로그

    참고 자료

    공식 문서

    imprun.dev 관련 문서

    추가 학습


    글쓴이: imprun.dev Team
    피드백: GitHub Issues 또는 이메일로 문의해주세요


    변경 이력

    • 2025-10-22: 초안 작성
      • Helm 기반 Secret 관리 전략
      • KubeBlocks Secret 처리 방법
      • terminationPolicy: DoNotTerminate 패턴
      • 보안 체크리스트 및 RBAC 설정
Designed by Tistory.