Post

External Secrets와 AWS Secrets Manager, SSM Parameter Store 연동하기

External Secrets와 AWS Secrets Manager, SSM Parameter Store 연동하기

External Secrets는 쿠버네티스 클러스터에서 외부 시크릿 관리 시스템과 통합해 시크릿을 관리하는 오픈소스 도구입니다. AWS Secrets Manager, AWS SSM Parameter Store, HashiCorp Vault, Google Secret Manager 같은 외부 시크릿 관리 서비스에서 값을 가져와 Kubernetes Secret에 주입하고, 주기적으로 외부 API를 호출해 시크릿 값을 다시 읽어 동기화합니다.

Application에서 사용하는 API URL이나 DB 정보가 바뀌어도 코드를 고치지 않고 외부에 저장된 시크릿을 가져와 환경변수나 볼륨으로 씁니다. 운영 우수성과 보안성이 올라가고 휴먼 에러 가능성도 줄어듭니다.

TL;DR

  • External Secrets Operator는 AWS Secrets Manager나 SSM Parameter Store 같은 외부 저장소의 값을 주기적으로 읽어 Kubernetes Secret으로 동기화합니다.
  • CRD는 범위로 나뉩니다. SecretStore와 ExternalSecret은 namespace 범위이고, ClusterSecretStore와 ClusterExternalSecret은 클러스터 범위입니다.
  • 실무에서는 Access Key 대신 IRSA를 쓰는 편이 낫고, 이때 auth.jwtserviceAccountRef를 빠뜨리면 인증이 실패합니다. 키가 많으면 .data[] 대신 .dataFrom이나 .template으로 통째로 가져올 수 있습니다.

1. External Secrets CRDs

External Secrets는 외부 시크릿 관리 시스템에 접근하기 위해 CRD(Custom Resource Definition)를 사용합니다. 주요 CRD로는 네임스페이스 범위에서 동작하는 SecretStore, ExternalSecret과 클러스터 범위로 동작하는 ClusterSecretStore, ClusterExternalSecret이 있습니다.

external-secret-diagram

1.1. SecretStore, ClusterSecretStore

SecretStore - 외부 시크릿 서비스에 접근하기 위한 정보를 저장하는 CRD입니다. 어떤 제공자(provider)를 사용할지, 인증 방식은 어떻게 할지 정의합니다. SecretStore는 네임스페이스 범위에서 동작하므로 동일한 네임스페이스의 ExternalSecret만 참조할 수 있습니다.

ClusterSecretStore - SecretStore와 유사하지만 클러스터 전역에서 사용할 수 있는 ClusterStore입니다. 하나만 만들어 두면 모든 네임스페이스의 ExternalSecret에서 참조할 수 있습니다.

SecretStore는 개별 팀이나 애플리케이션마다 자격증명을 격리할 때 씁니다. 모든 네임스페이스에서 공통으로 접근해야 하는 중앙 시크릿 관리 창구가 필요하면 ClusterSecretStore를 만듭니다.

예를 들어 여러 네임스페이스에서 같은 AWS Secrets Manager 자격 증명을 써야 한다면 ClusterSecretStore로 한 번 정의해두고 재사용합니다. 팀별로 네임스페이스와 AWS IAM 자격 증명을 나눠 써야 한다면 각 팀 네임스페이스에 SecretStore를 생성해 사용합니다.

1.2. ExternalSecret, ClusterExternalSecret

ExternalSecret - 외부 시크릿으로부터 실제 쿠버네티스 Secret을 동기화하기 위한 설정을 담은 CRD입니다. 어떤 SecretStore를 참고할지, 외부 시크릿 관리 시스템에서 가져올 시크릿의 키/경로는 무엇인지, 생성될 Kubernetes Secret의 이름은 무엇인지 등을 정의합니다. ExternalSecret은 네임스페이스 범위에서 동작하며 자신이 속한 네임스페이스에만 Secret을 생성합니다.

ClusterExternalSecret - ExternalSecret과 유사하지만 클러스터 전역에서 사용할 수 있는 ExternalSecret입니다. 하나로 여러 네임스페이스에 동일한 ExternalSecret을 생성해 줍니다. spec.namespaceSelector 등으로 대상 네임스페이스를 지정하면, 해당 네임스페이스마다 ExternalSecret이 자동 생성되어 동일한 시크릿이 배포됩니다.

ExternalSecret은 특정 애플리케이션 네임스페이스에서만 필요한 DB 비밀번호, API 키를 동기화할 때 씁니다. 모든 네임스페이스에 공통으로 필요한 인증서나 공용 API 키는 ClusterExternalSecret으로 배포합니다.


2. External Secrets Operator Helm으로 배포하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# Helm Chart Repository 추가
❯ helm repo add external-secrets https://charts.external-secrets.io

# Helm Chart Repository 업데이트
❯ helm repo update

# External Secrets 설치
❯ helm install external-secrets external-secrets/external-secrets \
  -n external-secrets --create-namespace

# 설치 확인 (1) - Pod 정상 동작 확인
❯ kubectl get po -n external-secrets
NAME                                                READY   STATUS    RESTARTS   AGE
external-secrets-6cddc7bb75-8484n                   1/1     Running   0          67s
external-secrets-cert-controller-64f8978956-t77dk   1/1     Running   0          67s
external-secrets-webhook-868d7b5756-wdsw6           1/1     Running   0          67s

# 설치 확인 (2) - crd 확인
❯ kubectl get crds | grep external-secrets.io
acraccesstokens.generators.external-secrets.io          2025-03-02T05:13:57Z
clusterexternalsecrets.external-secrets.io              2025-03-02T05:13:57Z
clustergenerators.generators.external-secrets.io        2025-03-02T05:13:57Z
clustersecretstores.external-secrets.io                 2025-03-02T05:13:57Z
ecrauthorizationtokens.generators.external-secrets.io   2025-03-02T05:13:57Z
externalsecrets.external-secrets.io                     2025-03-02T05:13:57Z
fakes.generators.external-secrets.io                    2025-03-02T05:13:57Z
gcraccesstokens.generators.external-secrets.io          2025-03-02T05:13:57Z
generatorstates.generators.external-secrets.io          2025-03-02T05:13:57Z
githubaccesstokens.generators.external-secrets.io       2025-03-02T05:13:57Z
grafanas.generators.external-secrets.io                 2025-03-02T05:13:57Z
passwords.generators.external-secrets.io                2025-03-02T05:13:57Z
pushsecrets.external-secrets.io                         2025-03-02T05:13:57Z
quayaccesstokens.generators.external-secrets.io         2025-03-02T05:13:57Z
secretstores.external-secrets.io                        2025-03-02T05:13:57Z
stssessiontokens.generators.external-secrets.io         2025-03-02T05:13:57Z
uuids.generators.external-secrets.io                    2025-03-02T05:13:57Z
vaultdynamicsecrets.generators.external-secrets.io      2025-03-02T05:13:57Z
webhooks.generators.external-secrets.io                 2025-03-02T05:13:57Z

3. AWS 접근을 위한 Access Key, Secret Access Key 설정

간단한 테스트를 위해 external-secret이라는 IAM 유저를 생성하고 해당 유저에 AWS Secrets Manager, SSM Parameter Store에 접근할 수 있는 권한을 부여하여 사용했습니다. 실제 운영환경에서는 최소권한 IAM Role을 IRSA로 연결해 접근하는 편이 안전합니다.
[IRSA (IAM Role for Service Account)란? 사용 방법]

1
2
3
4
5
# AWS 액세스 키와 시크릿 키 값으로 쿠버네티스 Secret 생성 (namespace - 'external-secrets')
kubectl create secret generic aws-credentials \
  -n external-secrets \
  --from-literal=aws_access_key_id={Access Key} \
  --from-literal=aws_secret_access_key={Secret Access Key}

4. ClusterSecretStore 생성 - [AWS Secrets Manager]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# clustersecretstore-secretsmanager.yaml 
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: aws-secretsmanager-store
spec:
  provider:
    aws:
      service: SecretsManager # AWS Secrets Manager 사용
      region: ap-northeast-2 # 시크릿이 존재하는 리전 (서울 리전 예시)
      auth:
        secretRef: # 쿠버네티스 Secret 기반 인증
          accessKeyIDSecretRef:
            name: aws-credentials # AWS 액세스 키가 들어있는 Secret 이름
            namespace: external-secrets # Secret 존재 네임스페이스 (ClusterSecretStore이므로 필요)
            key: aws_access_key_id # Secret에서 액세스 키 ID에 해당하는 키 이름
          secretAccessKeySecretRef:
            name: aws-credentials
            namespace: external-secrets
            key: aws_secret_access_key
1
2
3
4
❯ kubectl apply -f clustersecretstore-secretsmanager.yaml
❯ kubectl get clustersecretstore
NAME                       AGE   STATUS   CAPABILITIES   READY
aws-secretsmanager-store   15s   Valid    ReadWrite      True

5. ExternalSecret 생성 및 Kubernetes Secret 동기화 - [AWS Secrets Manager]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# externalsecret-secretsmanager.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: sample-secretsmanager-secret
  namespace: default
spec:
  refreshInterval: 1h # 주기적 동기화 간격 (예: 1시간)
  secretStoreRef:
    name: aws-secretsmanager-store # 참조할 (Cluster)SecretStore 이름
    kind: ClusterSecretStore # ClusterSecretStore 사용 (전역 스토어)
  target:
    name: basic-auth-secret-manager # 생성될 Kubernetes Secret 이름
    creationPolicy: Owner # Secret 생성 정책 (Owner: 외부 변경 시 덮어씌움)
  data:
    - secretKey: auth # 만들 Kubernetes Secret의 키 이름
      remoteRef:
        key: basic-auth # AWS Secrets Manager 상의 시크릿 이름
        property: basic-auth # (선택사항) AWS 시크릿 JSON에서 가져올 필드
1
2
3
4
5
6
7
8
9
10
11
12
13
# ExternalSecret 생성
❯ kubectl apply -f externalsecret-secretsmanager.yaml                           
externalsecret.external-secrets.io/sample-secretsmanager-secret created

# ExternalSecret 확인
❯ k get externalsecrets                        
NAME                           STORETYPE            STORE                      REFRESH INTERVAL   STATUS         READY
sample-secretsmanager-secret   ClusterSecretStore   aws-secretsmanager-store   1h                 SecretSynced   True

# 생성된 Kubernetes Secret 확인
❯ k get secrets        
NAME                        TYPE     DATA   AGE
basic-auth-secret-manager   Opaque   1      13s

6. ClusterSecretStore 생성 - [AWS SSM Parameter Store]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# clustersecretstore-ssm.yaml
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: aws-ssm-store
spec:
  provider:
    aws:
      service: ParameterStore # AWS SSM Parameter Store 사용
      region: ap-northeast-2 # 파라미터가 존재하는 리전
      auth:
        secretRef: # AWS 액세스 키/시크릿 키로 인증
          accessKeyIDSecretRef:
            name: aws-credentials
            namespace: external-secrets
            key: aws_access_key_id
          secretAccessKeySecretRef:
            name: aws-credentials
            namespace: external-secrets
            key: aws_secret_access_key
1
2
3
4
❯ kubectl apply -f clustersecretstore-ssm.yaml
❯ kubectl get clustersecretstore
NAME                       AGE     STATUS   CAPABILITIES   READY
aws-ssm-store              8s      Valid    ReadWrite      True

7. ExternalSecret 생성 및 Kubernetes Secret 동기화 - [AWS SSM Parameter Store]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# externalsecret-ssm.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: sample-ssm-secret
  namespace: default
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-ssm-store # 참조할 ClusterSecretStore 이름
    kind: ClusterSecretStore 
  target:
    name: basic-auth-secret-ssm # 생성될 Kubernetes Secret 이름
    creationPolicy: Owner
  data:
  - secretKey: auth # Kubernetes Secrets의 Key
    remoteRef:
      key: basic-auth # SSM Parameter Store의 파라미터 경로 (키)
# JSON 값: 만약 파라미터 값이 JSON 객체라면, property 필드를 사용해 특정 속성만 추출할 수 있음
# https://external-secrets.io/latest/provider/aws-parameter-store/#json-secret-values
1
2
3
4
5
6
7
8
9
10
11
12
# ExternalSecret 생성
❯ kubectl apply -f externalsecret-ssm.yaml

# ExternalSecret 확인
❯ kubectl get externalsecrets
NAME                           STORETYPE            STORE                      REFRESH INTERVAL   STATUS         READY
sample-ssm-secret              ClusterSecretStore   aws-ssm-store              1h                 SecretSynced   True

# 생성된 Kubernetes Secret 확인
❯ kubectl get secrets           
NAME                        TYPE     DATA   AGE
basic-auth-secret-ssm       Opaque   1      2m53s

8. ClusterExternalSecret으로 여러 네임스페이스에 Secret 배포하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# clusterexternalsecret-ssm.yaml
apiVersion: external-secrets.io/v1beta1
kind: ClusterExternalSecret
metadata:
  name: secretsmanager-clusterexternalsecret
spec:
  externalSecretName: clusterexternalsecret # 생성될 ExternalSecret 이름 (각 네임스페이스에서 이 이름으로 생성됨)
  namespaceSelector:
    # matchLabels:
    #   kubernetes.io/metadata.name: kube-system # kubernetes.io/metadata.name=kube-system 라벨을 가진 네임스페이스에만 배포
    matchExpressions:
      - key: kubernetes.io/metadata.name
        operator: In
        values:
          - kube-system
          - monitoring
  refreshTime: 1h # ClusterExternalSecret 자체의 재평가 주기
  externalSecretSpec: # 생성할 ExternalSecret의 사양 정의 (ExternalSecret spec과 동일)
    secretStoreRef:
      name: aws-secretsmanager-store
      kind: ClusterSecretStore
    target:
      name: clusterexternalsecret-secret # 생성될 Secret 이름
      creationPolicy: Owner
    refreshInterval: 1h # 각 ExternalSecret의 동기화 주기
    data:
      - secretKey: auth
        remoteRef:
          key: basic-auth # AWS Secrets Manager 시크릿 이름 (예시)
          property: basic-auth # 특정 필드명 (예를 들어 JSON 형태일 경우)
1
2
3
4
5
6
7
8
9
10
11
❯ kubectl apply -f clusterexternalsecret-ssm.yaml
clusterexternalsecret.external-secrets.io/secretsmanager-clusterexternalsecret created

❯ kubectl get clusterexternalsecrets                   
NAME                                   STORE                      REFRESH INTERVAL   READY
secretsmanager-clusterexternalsecret   aws-secretsmanager-store   1h                 True

❯ kubectl get secrets -A            
NAMESPACE          NAME                                     TYPE                 DATA   AGE
kube-system        clusterexternalsecret-secret             Opaque               1      15s
monitoring         clusterexternalsecret-secret             Opaque               1      15s

9. 마무리

External Secrets Operator로 쿠버네티스에서 AWS Secrets Manager와 SSM Parameter Store의 시크릿을 동기화하는 방법을 다뤄보았습니다. Application에서 사용하는 민감한 값을 Git에 직접 올리지 않고도 참조하게 되면서 보안성과 편의성이 함께 올라갑니다.

실무에서는 IRSA를 쓰시길 권하고, 이때 auth.jwt 필드나 serviceAccountRef를 빼먹으면 인증이 실패합니다. 키가 많거나 구조가 복잡한 시크릿은 .data[] 대신 .dataFrom이나 .template으로 통째로 가져올 수 있습니다.


10. 참고 자료


궁금하신 점이나 추가해야 할 부분은 댓글이나 아래의 링크를 통해 문의해주세요.
Written with KKamJi

This post is licensed under CC BY 4.0 by the author.