K8s 시크릿 암호화 3가지

K8s 시크릿 관리의 문제

Kubernetes Secret은 Base64 인코딩일 뿐 암호화가 아닙니다. Git에 Secret 매니페스트를 커밋하면 민감 정보가 평문으로 노출됩니다. ArgoCD GitOps 환경에서는 이 문제가 더 심각합니다. 이 글에서는 Sealed Secrets, External Secrets Operator(ESO), SOPS 3가지 시크릿 관리 도구의 아키텍처, 설정, 실전 패턴을 비교합니다.

3가지 도구 비교

구분 Sealed Secrets External Secrets (ESO) SOPS + Age
원리 클러스터 키로 암호화 외부 저장소에서 런타임 동기화 파일 단위 암호화
시크릿 저장 위치 Git (암호화됨) AWS SM, Vault, GCP SM 등 Git (암호화됨)
외부 의존성 없음 (클러스터 자체) 외부 시크릿 매니저 필수 없음
자동 로테이션 수동 자동 (refreshInterval) 수동
복잡도 낮음 중간 낮음
적합 환경 소규모, 단일 클러스터 엔터프라이즈, 멀티 클러스터 소규모, Helm/Kustomize

Sealed Secrets

Bitnami의 Sealed Secrets는 클러스터에 설치된 컨트롤러의 공개키로 시크릿을 암호화합니다. 암호화된 SealedSecret 리소스를 Git에 안전하게 커밋할 수 있습니다.

# 설치
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets 
  --namespace kube-system

# kubeseal CLI 설치
brew install kubeseal  # macOS
# 또는
wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.25.0/kubeseal-0.25.0-linux-amd64.tar.gz

# 1. 일반 Secret 생성 (적용하지 않음)
kubectl create secret generic db-credentials 
  --namespace production 
  --from-literal=username=admin 
  --from-literal=password='S3cur3P@ss!' 
  --dry-run=client -o yaml > secret.yaml

# 2. SealedSecret으로 암호화
kubeseal --format yaml 
  --controller-name sealed-secrets 
  --controller-namespace kube-system 
  < secret.yaml > sealed-secret.yaml

# 3. 결과: Git에 커밋 가능한 암호화된 매니페스트
cat sealed-secret.yaml
# sealed-secret.yaml — Git에 커밋 가능
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: db-credentials
  namespace: production
spec:
  encryptedData:
    username: AgBY7k...암호화된_데이터...==
    password: AgCx9m...암호화된_데이터...==
  template:
    metadata:
      name: db-credentials
      namespace: production
    type: Opaque

# 클러스터에 적용하면 컨트롤러가 복호화 → 일반 Secret 생성
kubectl apply -f sealed-secret.yaml
kubectl get secret db-credentials -n production  # 자동 생성됨

Sealed Secrets 키 백업

# 키 분실 = 모든 SealedSecret 복호화 불가 → 반드시 백업
kubectl get secret -n kube-system 
  -l sealedsecrets.bitnami.com/sealed-secrets-key 
  -o yaml > sealed-secrets-master-key.yaml

# 키 복원 (클러스터 재구축 시)
kubectl apply -f sealed-secrets-master-key.yaml
kubectl delete pod -n kube-system -l name=sealed-secrets-controller

# 스코프 설정: strict (기본) vs namespace-wide vs cluster-wide
kubeseal --scope strict          # 같은 이름+네임스페이스에서만 복호화
kubeseal --scope namespace-wide  # 같은 네임스페이스의 다른 이름도 가능
kubeseal --scope cluster-wide    # 클러스터 어디서든 복호화

External Secrets Operator (ESO)

ESO는 AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager 등 외부 시크릿 저장소에서 값을 가져와 K8s Secret을 자동 생성·동기화합니다.

# 설치
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets 
  --namespace external-secrets 
  --create-namespace

# 1단계: SecretStore — 외부 저장소 연결 정보
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: aws-secrets-manager
  namespace: production
spec:
  provider:
    aws:
      service: SecretsManager
      region: ap-northeast-2
      auth:
        secretRef:
          accessKeyIDSecretRef:
            name: aws-credentials
            key: access-key-id
          secretAccessKeySecretRef:
            name: aws-credentials
            key: secret-access-key

# ClusterSecretStore: 모든 네임스페이스에서 공유
---
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: vault-store
spec:
  provider:
    vault:
      server: "https://vault.internal:8200"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "external-secrets"
# 2단계: ExternalSecret — 어떤 시크릿을 가져올지 정의
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-credentials
  namespace: production
spec:
  refreshInterval: 1h              # 자동 동기화 주기
  secretStoreRef:
    name: aws-secrets-manager
    kind: SecretStore

  target:
    name: db-credentials           # 생성될 K8s Secret 이름
    creationPolicy: Owner          # ExternalSecret 삭제 시 Secret도 삭제
    deletionPolicy: Retain         # ExternalSecret 삭제 시 Secret 유지

  # 개별 키 매핑
  data:
    - secretKey: username          # K8s Secret의 키
      remoteRef:
        key: production/db-creds   # AWS SM의 시크릿 이름
        property: username         # JSON 내 필드

    - secretKey: password
      remoteRef:
        key: production/db-creds
        property: password

  # 또는 전체 JSON을 한 번에 매핑
  dataFrom:
    - extract:
        key: production/db-creds   # JSON 전체를 K8s Secret 키로 매핑

ESO 템플릿과 변환

# 커넥션 스트링 조합 등 템플릿 활용
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: app-secrets
  namespace: production
spec:
  refreshInterval: 30m
  secretStoreRef:
    name: aws-secrets-manager
    kind: SecretStore
  target:
    name: app-secrets
    template:
      engineVersion: v2
      data:
        # 여러 시크릿 값을 조합하여 커넥션 스트링 생성
        DATABASE_URL: "postgresql://{{ .username }}:{{ .password }}@{{ .host }}:5432/{{ .dbname }}"
        REDIS_URL: "redis://:{{ .redis_password }}@redis:6379"
  data:
    - secretKey: username
      remoteRef:
        key: production/db-creds
        property: username
    - secretKey: password
      remoteRef:
        key: production/db-creds
        property: password
    - secretKey: host
      remoteRef:
        key: production/db-creds
        property: host
    - secretKey: dbname
      remoteRef:
        key: production/db-creds
        property: dbname
    - secretKey: redis_password
      remoteRef:
        key: production/redis
        property: password

SOPS + Age 암호화

Mozilla SOPS는 YAML/JSON 파일의 값만 선택적으로 암호화합니다. Age(현대적 암호화 도구)와 조합하면 가볍게 시크릿을 관리할 수 있습니다.

# Age 키 생성
age-keygen -o age-key.txt
# Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p

# .sops.yaml — 프로젝트 루트에 규칙 정의
creation_rules:
  - path_regex: secrets/production/.*.yaml$
    age: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
  - path_regex: secrets/staging/.*.yaml$
    age: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p

# 시크릿 파일 작성
cat > secrets/production/db.yaml << 'EOF'
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
  namespace: production
type: Opaque
stringData:
  username: admin
  password: S3cur3P@ss!
  host: db.internal
EOF

# SOPS로 암호화
sops --encrypt --in-place secrets/production/db.yaml
# 암호화된 파일 — Git에 안전하게 커밋
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
  namespace: production
type: Opaque
stringData:
  username: ENC[AES256_GCM,data:8dK3nQ==,iv:...,tag:...,type:str]
  password: ENC[AES256_GCM,data:Vk9x2mPqRw==,iv:...,tag:...,type:str]
  host: ENC[AES256_GCM,data:ZGIuaW50ZXJuYWw=,iv:...,tag:...,type:str]
sops:
  age:
    - recipient: age1ql3z7hjy54...
      enc: |
        -----BEGIN AGE ENCRYPTED FILE-----
        ...
        -----END AGE ENCRYPTED FILE-----

# 복호화하여 적용
sops --decrypt secrets/production/db.yaml | kubectl apply -f -

# 편집 (자동 복호화 → 편집 → 재암호화)
sops secrets/production/db.yaml

# ArgoCD + SOPS 연동: ksops 플러그인
# ArgoCD repo-server에 SOPS 복호화 플러그인 설치 필요

ArgoCD 연동 패턴

각 도구의 GitOps 워크플로우에서의 역할입니다.

# Sealed Secrets + ArgoCD: 가장 간단
# SealedSecret 매니페스트를 Git에 커밋 → ArgoCD가 적용 → 컨트롤러가 복호화
# 추가 설정 불필요

# External Secrets + ArgoCD: 권장 패턴
# ExternalSecret 매니페스트를 Git에 커밋 (시크릿 값 없음)
# ArgoCD가 ExternalSecret 적용 → ESO가 외부 저장소에서 동기화
# health check 설정 추가 권장:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
spec:
  # ... source, destination ...
  ignoreDifferences:
    - group: ""
      kind: Secret
      jsonPointers:
        - /data  # ESO가 관리하는 Secret의 data 변경 무시

# SOPS + ArgoCD: ksops 플러그인 필요
# ArgoCD repo-server ConfigMap에 플러그인 등록
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cm
  namespace: argocd
data:
  kustomize.buildOptions: "--enable-alpha-plugins"

시크릿 로테이션 전략

K8s 운영에서 시크릿 로테이션은 보안의 핵심입니다.

# ESO: refreshInterval로 자동 로테이션
spec:
  refreshInterval: 15m  # 15분마다 외부 저장소와 동기화
  # AWS SM에서 값을 변경하면 15분 내 K8s Secret에 반영

# Pod가 시크릿 변경을 감지하려면:
# 1. Volume mount: kubelet이 주기적으로 갱신 (기본 1분)
# 2. 환경 변수: Pod 재시작 필요 → Reloader 사용
# stakater/Reloader: Secret 변경 시 자동 롤아웃
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    reloader.stakater.com/auto: "true"  # 참조하는 Secret 변경 시 자동 재시작
spec:
  template:
    spec:
      containers:
        - name: app
          envFrom:
            - secretRef:
                name: db-credentials

선택 가이드

  • 소규모 팀, 단일 클러스터: Sealed Secrets — 설치 간단, 외부 의존성 없음
  • 이미 AWS SM/Vault 사용 중: External Secrets Operator — 기존 인프라 활용, 자동 로테이션
  • Helm/Kustomize 중심: SOPS + Age — 기존 워크플로우에 자연스럽게 통합
  • 엔터프라이즈, 멀티 클러스터: ESO + Vault — 중앙 집중 관리, 감사 로그, 동적 시크릿

정리

K8s Secret을 Git에 평문으로 커밋하는 것은 보안 사고의 시작입니다. Sealed Secrets로 클러스터 키 암호화, ESO로 외부 저장소 동기화, SOPS로 파일 단위 암호화 중 팀의 규모와 인프라에 맞는 도구를 선택하세요. GitOps 환경에서는 ESO가 자동 로테이션과 중앙 관리 면에서 가장 유연하지만, 간단한 프로젝트에서는 Sealed Secrets만으로도 충분합니다. 중요한 것은 어떤 도구든 사용하는 것입니다.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux