External Secrets Operator를 통한 Kubernetes Secret 관리 (feat. AWS Secrets Manager)
Kubernetes에는 Secrets라는 리소스가 존재한다. Secrets에는 Application에서 사용하는 Credential과 같은 민감한 정보를 저장하여 관리하도록 한다.
하지만 Secrets은 이름과는 다르게 정말 완벽한 비밀은 아니다. 이러한 Secrets는 Kubernetes 클러스터 Control plane에 위치한 etcd에 저장되는데, base64로 인코딩된 값이 평문으로 그대로 저장되기 때문이다.
Secrets를 정말 Secret답게 사용하기 위해 보통 크게 3가지 방법을 사용하는 것이 제시된다.
- RBAC를 활용해 Secrets에 접근할 수 있는 User를 제한하는 방법
- 외부 Secret Management 서비스를 이용해 저장 후, 클러스터로 가져와서 사용하는 방법
- Secrets가 저장되는 클러스터의 ETCD에 저장될때 암호화하는 방법 (ETCD 암호화)
이번에는 Secrets 리소스를 AWS Secrets Manager와 같은 외부 서비스와 연동시켜 사용해보도록 한다.
External Secrets Operator
ESO(External Secrets Operator)는 AWS의 Secrets Manager, GCP의 Secret Manager, Azure의 Key Vault와 같은 퍼블릭 클라우드 서비스가 제공하는 민감정보들을 관리할 수 있는 서비스에서 값을 가져와 Kubernetes의 Secrets로 주입할 수 있도록 도와주는 오픈소스 툴이다.
ESO의 목적은 외부 Secret Management 서비스 API를 이용해 Secret들을 Kubernetes 클러스터의 Secret과 동기화하는 것이다.
ESO는 CustomResourceDefinitions(CRD)을 통해 SecretStore와 ExternalSecrets 리소스를 정의한다.
SecretStore로는 접근할 secret provider의 정보를 정의하고, ExternalSecrets로는 SecretStore로 부터 가져온 Secret을 관리한다.
또한 위 두가지 리소스(SecretStore, ExternalSecret)을 클러스터 단위로 선언해 클러스터 전체에 적용할 수 있는 ClusterSecretStore와 ClusterExternalSecret 리소스도 존재한다.
클러스터에 ESO 설치
먼저, 외부 Secrets Management 서비스와 Kubernetes의 Secret을 연동하기 위해 ESO를 클러스터에 설치해야한다.
helm 차트를 이용해 클러스터에 ESO를 설치한다.
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets \
external-secrets/external-secrets \
-n external-secrets \
--create-namespace \
ESO를 설치하면 클러스터에 SecretStore와 ExternalSecrets CRD를 비롯해 Cert controller와 같은 ESO 관련 pod 리소스가 구축되는것을 확인할 수 있다.
AWS Secrets Manager에 Secrets 등록
ESO 연동 테스트를 위해 간단하게 AWS Secrets Manager에 Secret을 하나 등록해보도록 한다.
ServiceAccount & IRSA 생성
kubernetes 클러스터에서 AWS 서비스인 Secrets Manager에 접근해야 하기에 권한이 필요하다. 이 권한은 ServiceAccount에 IRSA를 연결함으로써 할당받도록 한다.
EKS 클러스터에 대한 IRSA 설정은 아래 글을 참고하도록 한다.
IRSA로 연결할 IAM Role은 Secrets Manager에 접근가능해야하기에 다음과 같은 정책을 연결한다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetResourcePolicy",
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
"secretsmanager:ListSecretVersionIds"
],
"Resource": [
"arn:aws:secretsmanager:ap-northeast-2:[Account ID]:secret:*"
]
}
]
}
IAM Role과 Policy를 연결한 뒤, 클러스터에서 eso-irsa 이름의 SA를 생성한다.
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
eks.amazonaws.com/role-arn: [IRSA 연결할 Role]
name: eso-irsa
외부에 보관된 Secret 획득 및 저장
AWS Secrets Manager에 저장된 Secret을 가져오기 위해 SecretStore와 ExternalSecret 리소스를 생성한다.
클러스터가 Secret을 fetch해올 외부 Management 서비스를 선언한 SecretStore를 다음과 같이 생성해준다.
(클러스터 전체에서 접근가능한 ClusterSecretStore를 생성하는 경우, kind만 변경해주면 된다.)
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secretmanager
spec:
provider:
aws:
service: SecretsManager
region: ap-northeast-2
auth:
jwt:
serviceAccountRef: # IRSA 연결한 ServiceAccount
name: eso-irsa
그리고 이 SecretStore에서 가져올 Secret 데이터를 선언한 ExternalSecret을 다음과 같이 생성해준다.
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: eso-secret
spec:
secretStoreRef: # Secret을 가져올 SecretStore 작성
name: aws-secretmanager
kind: SecretStore
refreshInterval: 1h
target:
name: secret-from-eso # 가져온 값으로 클러스터에 생성할 Secret object의 이름
data:
- secretKey: api-key
remoteRef: # Secrets Manager에 저장된 Secret
key: eso-test
property: APIKey
ExternalSecret 리소스까지 생성하면, 클러스터의 Secrets 리소스를 확인하면 SecretStore에서 가져온 Secret이 하나 생성된 것을 확인할 수 있다.
이 Secret을 사용하는 Pod를 생성하고 내용을 확인할 수 있다.
apiVersion: v1
kind: Pod
metadata:
name: my-pod
namespace: default
spec:
containers:
- name: my-container
image: nginx
env:
- name: API_KEY
valueFrom:
secretKeyRef: # 클러스터의 Secret object를 참조
name: secret-from-eso
key: api-key
Pod에 접속 후 환경변수로 가져온 Secret object의 내용을 확인할 수 있다.
이렇게 클러스터와 외부 Secret Management 서비스와 연동해 Management 서비스에 저장된 Secret을 클러스터로 가져오고 갱신하는 과정을 알아보았다.
하지만 이렇게 가져온 Secret도 클러스터상에서는 base64 인코딩된 평문으로 저장되기에 여전히 노출에 대한 문제는 존재한다.
다음에는 클러스터의 Secret 리소스를 암호화할 수 있는 저장소 ETCD의 암호화를 알아보도록 한다.