본문 바로가기
카테고리 없음

[AEWS-3기][6주차-도전과제-3][EKS Workshop] EKS Pod Identity Managing Secrets with AWS Secrets Manager

by james_janghun 2025. 3. 15.

이번에는 쿠버네티스 Secrets를 AWS Secrets Manager와 연동하여 사용하는 작업을 학습해봅니다.

 

https://www.eksworkshop.com/docs/security/secrets-management/secrets-manager/

 

Managing Secrets with AWS Secrets Manager | EKS Workshop

Provide sensitive configuration like credentials to applications running on Amazon Elastic Kubernetes Service with AWS Secrets Manager.

www.eksworkshop.com

 

핵심 개념

Kubernetes 시크릿은 클러스터 운영자가 비밀번호, OAuth 토큰, SSH 키 등의 민감한 정보를 관리할 수 있게 도와주는 리소스입니다. 이러한 시크릿은 Pod 내 컨테이너에 데이터 볼륨으로 마운트되거나 환경 변수로 노출될 수 있어, Pod 배포와 민감한 데이터 관리를 분리할 수 있습니다.

시크릿 관리의 문제점

최근 트랜드에서는 GitOps를 중심으로 Kubernetes 리소스의 YAML 매니페스트를 관리하고 Git 저장소를 사용해 버전 관리하는 경우가 많아졌습니다. 그런데 Kubernetes에서 Secrets의 경우 암호화된다고 하지만 base64로 인코딩되어, 누구든지 base64로 디코딩하면 값을 취득할 수 있습니다. 이것을 Git에 올리는 것은 매우 위험한 행위죠. 이런 이유로 Kubernetes 시크릿을 위한 YAML 매니페스트를 클러스터 외부에서 관리하기는 어렵기 때문에 AWS Secrets Manager를 통해서 관리하고자 하는 것입니다.

시크릿 관리 접근 방식

일반적으로 시크릿을  외부저장소에 두는 방법은 다음 두 가지가 있으며, 이번 워크샵은 AWS Secrets Manager를 이용하고자 합니다.

  1. Sealed Secrets for Kubernetes: 시크릿을 암호화하여 안전하게 저장하고 관리하는 방법
  2. AWS Secrets Manager: AWS의 관리형 서비스를 사용하여 시크릿을 안전하게 저장하고 관리하는 방법

 

환경 준비 (EKS 클러스터에 다음과 같은 애드온이 필요합니다)

- kubernetes Secrets Store CSI Driver

- AWS Secrets and Configuration Provider(ASCP)

- External Secrets Operator

 

Kubernetes Secrets Store CSI Driver와 ASCP를 통해 Secrets Manager에 저장된 시크릿을 Kubernetes Pod의 볼륨으로 마운트 할 수 있습니다.

 

작동 방식

AWS Secrets and Configuration Provider(ASCP)를 사용하면 Amazon EKS에서 실행되는 워크로드가 IAM 역할과 정책을 통한 세밀한 접근 제어를 사용하여 Secrets Manager에 저장된 시크릿에 접근할 수 있습니다. Pod가 시크릿에 접근을 요청하면 ASCP는 다음과 같은 과정을 거칩니다:

  1. Pod의 ID를 검색
  2. 이 ID를 IAM 역할로 교환
  3. 해당 역할을 맡음
  4. Secrets Manager에서 해당 역할에 허용된 시크릿만 검색

 

External Secrets 접근 방식

Kubernetes와 AWS Secrets Manager를 통합하는 또 다른 방법은 External Secrets Operator를 사용하는 것입니다. 이 운영자는 AWS Secrets Manager의 시크릿을 Kubernetes 시크릿으로 동기화하며, 추상화 레이어를 통해 전체 수명 주기를 관리합니다. Secrets Manager의 값을 자동으로 Kubernetes 시크릿에 주입합니다.

https://external-secrets.io/latest/

 

Introduction - External Secrets Operator

Introduction External Secrets Operator is a Kubernetes operator that integrates external secret management systems like AWS Secrets Manager, HashiCorp Vault, Google Secrets Manager, Azure Key Vault, IBM Cloud Secrets Manager, CyberArk Conjur, Pulumi ESC an

external-secrets.io

자동 시크릿 교체 지원

두 접근 방식 모두 Secrets Manager를 통한 자동 시크릿 교체를 지원합니다.

  • External Secrets를 사용할 때는 업데이트를 위한 새로고침 간격을 구성할 수 있습니다.
  • Secrets Store CSI Driver는 Pod가 항상 최신 시크릿 값을 갖도록 하는 rotation reconciler 기능을 제공합니다.

 

실습

일단 AWS Secrets Manager에 시크릿 정보를 생성해보겠습니다. 사용자 이름과 비밀번호를 포함하는 JSON 형식의 자격증명을 담은 시크릿을 만듭니다.

export SECRET_SUFFIX=$(openssl rand -hex 4)
export SECRET_NAME="$EKS_CLUSTER_NAME-catalog-secret-${SECRET_SUFFIX}"

aws secretsmanager create-secret --name "$SECRET_NAME" \
 --secret-string '{"username":"catalog_user", "password":"default_password"}' --region $AWS_REGION

 

잘 생성되었는지 시크릿정보를 확인해봅니다.

username과 password 필드가 정상적으로 저장되었는지 확인해보고, catalog_user와 default_password 값을 확인해봅니다.

aws secretsmanager describe-secret --secret-id "$SECRET_NAME"

 

콘솔에서 실제 정보를 확인할 수 있었습니다.

AWS Secrets and Configuration Provider (ASCP) 설정하기

먼저 Secret Store CSI Driver DaemonSet과 Pod들을 확인합니다.

kubectl -n secrets-store-csi-driver get pods,daemonsets -l app=secrets-store-csi-driver

 

 

다음으로, AWS용 CSI Secrets Store Provider DaemonSet과 Pod들을 확인합니다:

kubectl -n kube-system get pods,daemonset -l "app=secrets-store-csi-driver-provider-aws"

 

 

SecretProviderClass 설정하기

CSI 드라이버를 통해 AWS Secrets Manager에 저장된 시크릿에 접근하기 위해 SecretProviderClass가 필요합니다. 이것은 AWS Secrets Manager의 정보와 일치하는 드라이버 구성과 특정 매개변수를 제공하는 네임스페이스가 지정된 CRD 입니다.

 

워크샵에서는 다음과 같이 명령어 한 줄로 작업을 진행했습니다.

cat ~/environment/eks-workshop/modules/security/secrets-manager/secret-provider-class.yaml \
  | envsubst | kubectl apply -f -

 

실질적으로 진행되는 작업은 다음과같이 SPC라는 CRD를 생성하는 것입니다.

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: catalog-spc
  namespace: catalog
spec:
  provider: aws
  parameters:
    objects: |
      - objectName: "$SECRET_NAME"
        objectType: "secretsmanager"
        jmesPath:
          - path: username
            objectAlias: username
          - path: password
            objectAlias: password
  secretObjects:
    - secretName: catalog-secret
      type: Opaque
      data:
        - objectName: username
          key: username
        - objectName: password
          key: password

 

해당 내용을 조금 더 자세히 살펴보겠습니다.

  parameters:
    objects: |
      - objectName: "$SECRET_NAME"
        objectType: "secretsmanager"
        jmesPath:
          - path: username
            objectAlias: username
          - path: password
            objectAlias: password

 

먼저 Objects 매개변수는 Secrets Manager에 저장할 eks-workshop/catalog-secrets이라는 시크릿을 의미합니다. jmesPath를 사용해 JSON 형식의 키:값을 추출합니다.

  secretObjects:
    - secretName: catalog-secret
      type: Opaque
      data:
        - objectName: username
          key: username
        - objectName: password
          key: password

 

secretObjects에서는 해당 Secrets Manager 데이터를 Kubernetes 시크릿으로 생성하고 동기화하는 방법을 나타냅니다.

Pod에 마운트되면 SecretProviderClass라는 CRD를 통해서 Kubernetes의 시크릿을 생성하고 동기화 합니다.

 

즉 해당 CRD를 통해서 AWS Secrets Manager의 값을 추출하고, 쿠버네티스 Secret과 동기화시키는 작업을 진행합니다.

 

생성확인

실제 쿠버네티스에서 secret이 잘 생성되었는지 확인해보겠습니다.

kubectl get secretproviderclass -n catalog catalog-spc -o yaml | yq '.spec.secretObjects'

 

 

Secrets Manager 값을 Kubernetes Pod에 마운트 하기

이제 pod에 실제 값을 마운트해보겠습니다. 먼저 catalog 배포와 catalog 네임스페이스의 기존 시크릿을 살펴보겠습니다.

kubectl -n catalog get deployment catalog -o yaml | yq '.spec.template.spec.containers[] | .env'

 

현재 catalog 배포에는 /tmp에 마운트된 emptyDir외에 추가 볼륨이나 볼륨마운트가 없습니다.

kubectl -n catalog get deployment catalog -o yaml | yq '.spec.template.spec.volumes'

kubectl -n catalog get deployment catalog -o yaml | yq '.spec.template.spec.containers[] | .volumeMounts'

 

AWS Secrets Manager 시크릿 마운트하기

이제 catalog Deployment를 수정해 시크릿을 사용하도록 하겠습니다.

kubectl kustomize ~/environment/eks-workshop/modules/security/secrets-manager/mounting-secrets/ \
  | envsubst | kubectl apply -f-
kubectl rollout status -n catalog deployment/catalog --timeout=120s

 

다음과 같이 마운트 됩니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/created-by: eks-workshop
    app.kubernetes.io/type: app
  name: catalog
  namespace: catalog
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/component: service
      app.kubernetes.io/instance: catalog
      app.kubernetes.io/name: catalog
  template:
    metadata:
      annotations:
        prometheus.io/path: /metrics
        prometheus.io/port: "8080"
        prometheus.io/scrape: "true"
      labels:
        app.kubernetes.io/component: service
        app.kubernetes.io/created-by: eks-workshop
        app.kubernetes.io/instance: catalog
        app.kubernetes.io/name: catalog
    spec:
      containers:
        - env:
            - name: DB_USER
              valueFrom:
                secretKeyRef:
                  key: username
                  name: catalog-secret
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  key: password
                  name: catalog-secret
          envFrom:
            - configMapRef:
                name: catalog
          image: public.ecr.aws/aws-containers/retail-store-sample-catalog:0.4.0
          imagePullPolicy: IfNotPresent
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 3
          name: catalog
          ports:
            - containerPort: 8080
              name: http
              protocol: TCP
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            periodSeconds: 5
            successThreshold: 3
          resources:
            limits:
              memory: 512Mi
            requests:
              cpu: 250m
              memory: 512Mi
          securityContext:
            capabilities:
              drop:
                - ALL
            readOnlyRootFilesystem: true
            runAsNonRoot: true
            runAsUser: 1000
          volumeMounts:
            - mountPath: /etc/catalog-secret
              name: catalog-secret
              readOnly: true
            - mountPath: /tmp
              name: tmp-volume
      securityContext:
        fsGroup: 1000
      serviceAccountName: catalog
      volumes:
        - csi:
            driver: secrets-store.csi.k8s.io
            readOnly: true
            volumeAttributes:
              secretProviderClass: catalog-spc
          name: catalog-secret
        - emptyDir:
            medium: Memory
          name: tmp-volume

 

해당 내용을 통해서 CSI 드라이버를 이용해 이전에 검증한 SecretProviderClass로 AWS Secret Manager의 시크릿을 Pod 내부에 /etc/catalog-secret 마운트 경로 마운트 합니다. 이를 통해서 Pod에 반영되게 됩니다.

 

변경사항 확인

이제 실제 Deployment 정보를 통해서 CSI Secret Store Driver를 사용하는 새로운 볼륨과 /etc/catalog-secrets에 마운트된 볼륨 마운트를 확인해봅니다.

kubectl -n catalog get deployment catalog -o yaml | yq '.spec.template.spec.volumes'
kubectl -n catalog get deployment catalog -o yaml | yq '.spec.template.spec.containers[] | .volumeMounts'

이렇게 Pod 컨테이너 내에 파일 시스템에서 파일로 민감 정보fi를 접근할 수 있도록합니다. 이 방식을 통해서 환경 변수로 시크릿 값을 노출하지 않고 소스 시크릿이 수정될 때 자동으로 업데이트 되는 등 여러 이점이 있습니다.

 

 

Pod 내부의 마운트된 시크릿 확인하기

마운트 경로에는 /etc/catalog-secret에 eks-workshop-catalog-secret, password, username 이렇게 3가지가 있습니다.

다음 명령어를 통해서 확인해보면 잘 마운트가 된것을 확인할 수 있습니다.

kubectl -n catalog exec deployment/catalog -- ls /etc/catalog-secret/
kubectl -n catalog exec deployment/catalog -- cat /etc/catalog-secret/${SECRET_NAME}
kubectl -n catalog exec deployment/catalog -- cat /etc/catalog-secret/username
kubectl -n catalog exec deployment/catalog -- cat /etc/catalog-secret/password

 

환경 변수는 이제 SecretProvierClass에 의해 CSI Secret Store 드라이버를 통해 자동으로 생성된 catalog-secret에서 가져옵니다.

kubectl -n catalog get deployment catalog -o yaml | yq '.spec.template.spec.containers[] | .env'
kubectl -n catalog get secrets

 

 

External Secrets Operator(ESO) 활용하기

해당 External Secrets Operator 설치 여부를 확인합니다. pod가 잘 떠있고 sa에 IRSA가 잘연결되어 있습니다.

kubectl -n external-secrets get pods
kubectl -n external-secrets get sa
kubectl -n external-secrets describe sa external-secrets-sa | grep Annotations

ClusterSecretStore 생성하기

모든 네임스페이스의 ExternalSecrets가 참조할 수 있는 클러스터 전체의 SecretStroe인 ClusterSecretStore리소스를 생성해야 합니다.

cat ~/environment/eks-workshop/modules/security/secrets-manager/cluster-secret-store.yaml \
  | envsubst | kubectl apply -f -

 

작업내용은 다음과 같습니다.

apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: "cluster-secret-store"
spec:
  provider:
    aws:
      service: SecretsManager
      region: $AWS_REGION
      auth:
        jwt:
          serviceAccountRef:
            name: "external-secrets-sa"
            namespace: "external-secrets"

 

ClusterSecretStore는 AWS Secrets Manager와 인증하기 위해 Service Account에 연결된 JWT 토큰을 사용합니다.

 

ExternalSecret 생성 및 배포

kubectl kustomize ~/environment/eks-workshop/modules/security/secrets-manager/external-secrets/ \
  | envsubst | kubectl apply -f-
kubectl rollout status -n catalog deployment/catalog --timeout=120s
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/created-by: eks-workshop
    app.kubernetes.io/type: app
  name: catalog
  namespace: catalog
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/component: service
      app.kubernetes.io/instance: catalog
      app.kubernetes.io/name: catalog
  template:
    metadata:
      annotations:
        prometheus.io/path: /metrics
        prometheus.io/port: "8080"
        prometheus.io/scrape: "true"
      labels:
        app.kubernetes.io/component: service
        app.kubernetes.io/created-by: eks-workshop
        app.kubernetes.io/instance: catalog
        app.kubernetes.io/name: catalog
    spec:
      containers:
        - env:
            - name: DB_USER
              valueFrom:
                secretKeyRef:
                  key: username
                  name: catalog-external-secret
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  key: password
                  name: catalog-external-secret
          envFrom:
            - configMapRef:
                name: catalog
          image: public.ecr.aws/aws-containers/retail-store-sample-catalog:0.4.0
          imagePullPolicy: IfNotPresent
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 3
          name: catalog
          ports:
            - containerPort: 8080
              name: http
              protocol: TCP
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            periodSeconds: 5
            successThreshold: 3
          resources:
            limits:
              memory: 512Mi
            requests:
              cpu: 250m
              memory: 512Mi
          securityContext:
            capabilities:
              drop:
                - ALL
            readOnlyRootFilesystem: true
            runAsNonRoot: true
            runAsUser: 1000
          volumeMounts:
            - mountPath: /tmp
              name: tmp-volume
      securityContext:
        fsGroup: 1000
      serviceAccountName: catalog
      volumes:
        - emptyDir:
            medium: Memory
          name: tmp-volume

 

리소스를 수정한 뒤 적용을 위해 재시작 합니다.

kubectl rollout status -n catalog deployment/catalog --timeout=120s

 

생성된 External Secret 리소스를 살펴보겠습니다.

kubectl -n catalog get externalsecrets.external-secrets.io catalog-external-secret -o yaml | yq '.spec'

시크릿을 동기화하는 전략을 살펴볼 수 있습니다. refreshInterval은 1시간이므로 1시간 단위로 기본 동기화를 진행합니다.

 

다음 명령어를 통해서 시크릿 동기화 현황을 볼 수도 있습니다.

kubectl -n catalog get externalsecrets.external-secrets.io

SecretSynced 상태는 AWS Secrets Manager에서 성공적으로 동기화되었다는 것입니다. 이 구성은 key 매개변수를 통해 AWS Secrets Manager 시크릿을 참조하고, 앞서 생성한 ClusterSecretStore를 참조합니다. 

 

ExternalSecret을 생성하면 자동으로 해당 Kubernetes 시크릿이 생성됩니다. 이 시크릿은 External Secrets Operator에 의해 소유됩니다.

kubectl -n catalog get secrets

 

catalog Pod가 새 시크릿 값을 사용하고 있는지 확인해봅시다.

kubectl -n catalog get pods
kubectl -n catalog get deployment catalog -o yaml | yq '.spec.template.spec.containers[] | .env'

 

이렇게 AWS Secrets and Configuration Provider(ASCP)와 External Secrets Operator(ESO)를 둘 다 사용해봤습니다.

 

 

- ASCP는 시크릿을 환경변수로 노출하지 않고도 AWS Secrets Manager에서 직접 볼륨으로 마운트할 수 있지만, 볼륨 관리가 필요한 것은 단점입니다.

- ESO는 쿠버네티스의 시크릿 수명 주기 관리를 단순화하고 클러스터 전체 SecretStore 기능을 제공하지만 볼륨마운트는 불가능하다는 단점이 있습니다.