K8s Secret 외부 연동 심화

K8s Secret의 한계

Kubernetes Secret은 민감 데이터를 관리하는 기본 리소스이지만, 실제로는 Base64 인코딩일 뿐 암호화가 아닙니다. etcd에 평문으로 저장되고, RBAC만으로 접근을 통제합니다. 프로덕션에서는 AWS Secrets Manager, HashiCorp Vault 같은 외부 비밀 저장소와 연동하는 것이 보안 표준입니다. External Secrets Operator(ESO)는 이 연동을 자동화하는 K8s 네이티브 솔루션입니다.

기본 Secret 유형과 생성

K8s Secret의 기본 유형을 먼저 이해해야 합니다.

타입 용도 자동 생성
Opaque 범용 키-값 아니오
kubernetes.io/tls TLS 인증서 cert-manager
kubernetes.io/dockerconfigjson 레지스트리 인증 아니오
kubernetes.io/service-account-token SA 토큰
# Opaque Secret 생성
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
  namespace: production
type: Opaque
stringData:          # stringData는 평문으로 작성 가능
  DB_HOST: postgres.internal
  DB_USER: app_user
  DB_PASSWORD: "s3cret!Pa$$w0rd"
---
# TLS Secret
apiVersion: v1
kind: Secret
metadata:
  name: tls-secret
type: kubernetes.io/tls
data:
  tls.crt: LS0tLS1CRU...  # Base64 인코딩
  tls.key: LS0tLS1CRU...

stringData는 평문으로 작성하면 K8s가 자동으로 Base64 인코딩합니다. 하지만 이 YAML을 Git에 커밋하면 비밀이 노출됩니다. 이것이 외부 비밀 관리가 필요한 근본 이유입니다.

etcd 암호화 설정

Secret을 etcd에 저장할 때 암호화하는 기본 보안 조치입니다.

# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: dGhpcyBpcyBhIDMyIGJ5dGUga2V5...  # 32바이트 Base64 키
      - identity: {}  # 폴백: 암호화 없이 읽기

# kube-apiserver 설정에 추가
# --encryption-provider-config=/etc/kubernetes/encryption-config.yaml

설정 후 기존 Secret을 다시 쓰기해야 암호화가 적용됩니다:

kubectl get secrets -A -o json | kubectl replace -f -

External Secrets Operator 아키텍처

ESO는 외부 비밀 저장소의 값을 K8s Secret으로 자동 동기화하는 오퍼레이터입니다.

# 1. 설치
helm install external-secrets external-secrets/external-secrets 
  -n external-secrets --create-namespace

# 2. SecretStore 정의 (AWS Secrets Manager 예시)
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: aws-secretsmanager
  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

# 3. 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"

SecretStore는 네임스페이스 범위, ClusterSecretStore는 클러스터 전역입니다. 프로덕션에서는 환경별로 SecretStore를 분리하고, ClusterSecretStore는 공통 인프라 비밀에 사용합니다.

ExternalSecret으로 동기화

ExternalSecret 리소스가 외부 저장소의 값을 K8s Secret으로 매핑합니다.

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-credentials
  namespace: production
spec:
  refreshInterval: 1h          # 동기화 주기
  secretStoreRef:
    name: aws-secretsmanager
    kind: SecretStore
  target:
    name: db-credentials        # 생성될 K8s Secret 이름
    creationPolicy: Owner       # ExternalSecret 삭제 시 Secret도 삭제
    template:                   # Secret 데이터 커스텀 포맷
      type: Opaque
      data:
        DATABASE_URL: "postgresql://{{ .DB_USER }}:{{ .DB_PASSWORD }}@{{ .DB_HOST }}:5432/{{ .DB_NAME }}"
  data:
    - secretKey: DB_USER
      remoteRef:
        key: production/database  # AWS Secrets Manager 키
        property: username        # JSON 내 필드
    - secretKey: DB_PASSWORD
      remoteRef:
        key: production/database
        property: password
    - secretKey: DB_HOST
      remoteRef:
        key: production/database
        property: host
    - secretKey: DB_NAME
      remoteRef:
        key: production/database
        property: dbname

target.template을 사용하면 여러 비밀 값을 조합하여 DATABASE_URL 같은 커넥션 스트링을 자동 생성할 수 있습니다. Go 템플릿 문법을 지원합니다.

Pod에서 Secret 사용 패턴

생성된 Secret을 Pod에서 사용하는 방법입니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  template:
    spec:
      containers:
        - name: api
          image: api-server:latest
          # 패턴 1: 환경 변수로 주입
          envFrom:
            - secretRef:
                name: db-credentials
          # 패턴 2: 개별 키 매핑
          env:
            - name: JWT_SECRET
              valueFrom:
                secretKeyRef:
                  name: app-secrets
                  key: jwt-secret
          # 패턴 3: 볼륨 마운트 (파일로)
          volumeMounts:
            - name: tls-certs
              mountPath: /etc/tls
              readOnly: true
      volumes:
        - name: tls-certs
          secret:
            secretName: tls-secret
            defaultMode: 0400  # 읽기 전용 권한

볼륨 마운트 방식의 장점: kubelet이 Secret 변경을 감지하면 파일을 자동 업데이트합니다(기본 60초 주기). 환경 변수 방식은 Pod 재시작 없이는 갱신되지 않습니다. 인증서 로테이션이 필요한 경우 볼륨 마운트가 필수입니다.

Sealed Secrets: GitOps 친화적 암호화

Secret을 Git에 안전하게 커밋하려면 Sealed Secrets를 사용합니다.

# 설치
helm install sealed-secrets sealed-secrets/sealed-secrets 
  -n kube-system

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

# 결과: Git에 안전하게 커밋 가능
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: db-credentials
  namespace: production
spec:
  encryptedData:
    DB_PASSWORD: AgBy3i4OJSWK+PiTySYZZA9rO...  # 공개키로 암호화됨
    DB_USER: AgCtr8HNQO+GMFj2zBNSGI...

SealedSecret은 컨트롤러의 공개키로 암호화되어 해당 클러스터에서만 복호화됩니다. K8s FluxCD GitOps와 결합하면 비밀 관리까지 완전한 GitOps 파이프라인을 구축할 수 있습니다.

Secret 로테이션 자동화

# ESO 자동 로테이션: refreshInterval로 주기적 동기화
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
spec:
  refreshInterval: 15m  # 15분마다 외부 저장소 확인

# Stakater Reloader로 Secret 변경 시 Pod 자동 재시작
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    reloader.stakater.com/auto: "true"  # Secret 변경 감지 시 롤링 재시작

Stakater Reloader는 Secret/ConfigMap 변경을 감지하여 관련 Deployment를 자동 롤링 재시작합니다. ESO의 refreshInterval과 함께 사용하면 비밀 로테이션이 완전 자동화됩니다. K8s ServiceAccount 토큰 보안과 결합하면 인증 체계 전체를 자동 로테이션할 수 있습니다.

보안 체크리스트

항목 조치
etcd 암호화 EncryptionConfiguration으로 aescbc/aesgcm 적용
RBAC Secret 접근 최소 권한, get/list 분리
Git 노출 방지 SealedSecrets 또는 ESO 사용
감사 로그 Secret 접근 audit logging 활성화
로테이션 ESO refreshInterval + Reloader 자동화
네트워크 NetworkPolicy로 Secret 접근 Pod 제한

정리

K8s Secret은 Base64 인코딩일 뿐 암호화가 아닙니다. 프로덕션에서는 etcd 암호화를 기본 적용하고, External Secrets Operator로 AWS Secrets Manager나 Vault의 비밀을 자동 동기화하세요. GitOps 환경에서는 Sealed Secrets로 암호화된 비밀을 안전하게 커밋하고, Stakater Reloader로 비밀 변경 시 Pod를 자동 재시작하는 것이 운영 표준입니다.

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