Secrets in Kubernetes
쿠버네티스를 사용하다 보면, 수 많은 YAML 파일들을 만들어 낼 것입니다. 그리고 이 YAML 파일들을 잘 관리하기 위해서 저장소를 찾게 됩니다. 일반적으로는 평소 즐겨 사용하던 git 을 저장소로 많이 사용합니다.
YAML 파일을 git 저장소에 올리는 것 자체는 별로 어렵지 않습니다. 하지만 파일에 정의된 쿠버네티스 리소스들을 하나씩 하나씩 관심있게 살펴보게 되면, 과연 이 리소스를 올리는게 맞는지 의문을 가지게 만드는 리소스가 있습니다. 바로 Secret
리소스 입니다.
쿠버네티스의 Secret
리소스는 키(key)-값(value)의 쌍으로 이루어져 있으며, 메타 데이터를 가지고 있습니다.
ConfigMap
과 비슷하게 생겼지만, Secret
에는 민감한 데이터를 저장하기 때문에 보호받아야하는 차이점이 존재합니다.
아래 예제는 Secret
를 YAML로 표현한것입니다.
apiVersion: v1 kind: Secret metadata: name: my-secret namespace: default type: Opaque data: username: YWRtaW4= password: aGVsbG8td3JvbGQ=
username
과 password
라는 키가 있고, Base64 로 인코딩된 값들이 저장되어 있습니다. 문제는 Base64 인코딩이 암호화가 아니라는 점입니다. 단순히 64진수로 변환된것 뿐이라서, 아주 쉽게 디코딩을 할 수 있습니다.
예제에 나와 있는 password
의 값을 단순히 디코딩하면, 원래 값을 알 수가 있는 것입니다.
$ echo aGVsbG8td3JvbGQ= | base64 --decode hello-world
이러한 Secret
을 git에 그냥 올리게 되면, 위험한 상황에 빠질 수도 있게 되는 것입니다.
그러면 어떻게 해야 할까요?
그것은 Secret
을 암호화 해서 git에 저장하는 것입니다. 다행히도 이러한 기능을 하는 도구들이 이미 나와 있습니다. 바로 Sealed Secrets 와 Kamus 입니다.
이 문서에는 Sealed Secrets 에 대해 간단히 다룰 것입니다.
Sealed Secrets
Sealed Secrets
는 두 개의 부분으로 이루어져 있습니다. 쿠버네티스 클러스터에 설치할 sealed-secrets-controller
와 암호화 유틸리티인 kubeseal
입니다.
Sealed Secrets
는 SealedSecret
라는 CR(Custom Resource) 사용해서 Secrets
을 보호합니다. 사용자는 Secrets
리소스를 직접 생성하지 않고, SealedSecret
라는 커스텀 리소스를 생성합니다. 이 SealedSecret
리소스에는 중요한 값들이 암호화 되어 저장됩니다. 이러한 중요한 값들을 암호화 할 때 사용하는 도구가 kubeseal
입니다. SealedSecret
리소스가 클러스터에 등록되면, sealed-secrets-controller
가 해당 리소스의 암호화된 값들을 복호하해서 Secrets
리소스를 생성해 주는 것입니다.

설치하기
Controller 설치
SealedSecret
이라는 CRD를 생성하고, Controller
를 설치하면 됩니다.
아래 예제는 CRD를 생성하고, kube-system
네임스페이스에 Controller
를 설치하는 방법입니다.
$ kubectl apply -f <https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.9.6/controller.yaml>
controller.yaml
--- apiVersion: v1 kind: Service metadata: annotations: {} labels: name: sealed-secrets-controller name: sealed-secrets-controller namespace: kube-system spec: ports: - port: 8080 targetPort: 8080 selector: name: sealed-secrets-controller type: ClusterIP --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: RoleBinding metadata: annotations: {} labels: name: sealed-secrets-service-proxier name: sealed-secrets-service-proxier namespace: kube-system roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: sealed-secrets-service-proxier subjects: - apiGroup: rbac.authorization.k8s.io kind: Group name: system:authenticated --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: Role metadata: annotations: {} labels: name: sealed-secrets-key-admin name: sealed-secrets-key-admin namespace: kube-system rules: - apiGroups: - "" resources: - secrets verbs: - create - list --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: annotations: {} labels: name: sealed-secrets-controller name: sealed-secrets-controller roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: secrets-unsealer subjects: - kind: ServiceAccount name: sealed-secrets-controller namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: annotations: {} labels: name: secrets-unsealer name: secrets-unsealer rules: - apiGroups: - bitnami.com resources: - sealedsecrets verbs: - get - list - watch - update - apiGroups: - "" resources: - secrets verbs: - get - create - update - delete - apiGroups: - "" resources: - events verbs: - create - patch --- apiVersion: v1 kind: ServiceAccount metadata: annotations: {} labels: name: sealed-secrets-controller name: sealed-secrets-controller namespace: kube-system --- apiVersion: apps/v1 kind: Deployment metadata: annotations: {} labels: name: sealed-secrets-controller name: sealed-secrets-controller namespace: kube-system spec: minReadySeconds: 30 replicas: 1 revisionHistoryLimit: 10 selector: matchLabels: name: sealed-secrets-controller strategy: rollingUpdate: maxSurge: 25% maxUnavailable: 25% type: RollingUpdate template: metadata: annotations: {} labels: name: sealed-secrets-controller spec: containers: - args: [] command: - controller env: [] image: quay.io/bitnami/sealed-secrets-controller:v0.9.6 imagePullPolicy: Always livenessProbe: httpGet: path: /healthz port: http name: sealed-secrets-controller ports: - containerPort: 8080 name: http readinessProbe: httpGet: path: /healthz port: http securityContext: readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 1001 stdin: false tty: false volumeMounts: - mountPath: /tmp name: tmp imagePullSecrets: [] initContainers: [] serviceAccountName: sealed-secrets-controller terminationGracePeriodSeconds: 30 volumes: - emptyDir: {} name: tmp --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: sealedsecrets.bitnami.com spec: group: bitnami.com names: kind: SealedSecret listKind: SealedSecretList plural: sealedsecrets singular: sealedsecret scope: Namespaced version: v1alpha1 --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: Role metadata: annotations: {} labels: name: sealed-secrets-service-proxier name: sealed-secrets-service-proxier namespace: kube-system rules: - apiGroups: - "" resourceNames: - 'http:sealed-secrets-controller:' - sealed-secrets-controller resources: - services/proxy verbs: - create - get --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: RoleBinding metadata: annotations: {} labels: name: sealed-secrets-controller name: sealed-secrets-controller namespace: kube-system roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: sealed-secrets-key-admin subjects: - kind: ServiceAccount name: sealed-secrets-controller namespace: kube-system
kubeseal 설치
MacOS
brew install kubeseal
Linux x86-64
wget <https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.9.6/kubeseal-linux-amd64> -O kubeseal sudo install -m 755 kubeseal /usr/local/bin/kubeseal
SealedSecret 생성하기
우선 kubectl
의 --dry-run
을 사용해서 my-secret.yaml
파일을 생성합니다.
kubectl -n default create secret generic my-secret \\ --from-literal=username=admin \\ --from-literal=password=hello-wrold \\ --dry-run \\ -o yaml > my-secret.yaml
생성한 my-secret.yaml
파일의 내용은 다음과 같습니다.
apiVersion: v1 data: password: aGVsbG8td3JvbGQ= username: YWRtaW4= kind: Secret metadata: creationTimestamp: null name: my-secret namespace: default
kubeseal
을 실행해서, my-secret.yaml
파일을 my-sealed-secret.yaml
파일로 변환합니다. my-sealed-secret.yaml
파일이 생성될때, 값들은 암호화되어 저장됩니다.
kubeseal --format=yaml < my-secret.yaml > my-sealed-secret.yaml
생성한 my-sealed-secret.yaml
파일의 내용은 다음과 같습니다.
apiVersion: bitnami.com/v1alpha1 kind: SealedSecret metadata: creationTimestamp: null name: my-secret namespace: default spec: encryptedData: password: AgAkqnrtawbZwPiOuleW5VDHTy8DtnSRqpW8xfV+um6qtqpFVOurbEXAmGWZUhv0MhvppXdHokccRDyS/iaODZGTDXALqrpfS2KMbsX3ARhUXPM5/4G8x9MrjZYGGZNYEJnY78ZvzxlMv7EWsDHTa+9UnWPAorNXd/KDjBhXujohdzs7rPmrAFQWznLovcdqSSXkBMQyf4J19wKPit9H8wBIfHkB4kftI8106NOU6NK+y9s0T9mXIGalS0h5B8KbntvnGmR1/QmxEDJeD7pRSWjAx2m8eRFUsDxRif9uJP2tkwKtQlLJrmsW9YWeDWzeszjAkcniNZtRDkak2mHs3LQZl/vq/otj3V/JuRxVYbukFkyjkvQghbfQek8CTJ7s4kZzozEEwtKWugtSxW2dh7JCewvy18W+IYjxqNDok7sA67bGTycRRBY3+db9mirdE5PxhoHQAkvDu9cUUAgM/+SR2FkpJQ9lGPKWkRiEi3Nx4ZhIJ/4PE+3swZj95BBFLK50gI32YAPnop7SO99Gt9yU7oPK7zoV9r9TNDNvZinB37r7Y26ti3ASrKYH5AY0jAqNcnK2KuzSk44NBRmQ6Hmo0/NZiONBtSckhuzpSw0jmr8KTjp3eZPjk4KLUIW/lEYdAl3kfx3Kd8+QZz6ygALLJWxPPheZm3sd1VHV7F+Bnpo4zLC8pDh9uW0spMCMYQD5adRK+FkXEgnuTw== username: AgBbJRLu/+ke7Ze5EgZIRbQHNgwFP/oOz8w2XlkU3oyZczvK6AqkCFUZpHAO3r02LOPpNmiKzRZKOts508LtAFcYNawDeWOS2S29x+m8DQHiYuiKQ0UDBkoSquaEAiUXBBPJomOMS2uDuOLRq2vC4KaQ7N27eQgw1JFR/GvTH0ZPf3hTZ0if0PFMOPcbm4UmCI1Qeoo0ToiOhvgOcpdy6xbcEOyOFX5L+663P5GsZJmZ3F6IoMcSI7QN/iFQXytifW/9GXKSyv4un+dtIZAmjX+sxeUr22nsrBwSJxXy8jKfnO4tNG/XzlPk91ZVu37A5nYJj2+iNVaW0j+1fu80Kcto02rFaC3eFZ+D6q/RTD0cyhJlsNDKFk93il9K7hoAJ7nkyEW4vQ+hpVBsRvRWy2yGYXs/S5tjMtBbgWPYVwlFwR1zoWAd6/t4o+u3xNcJB/LWgBOhuW+Z7TPHGffMxPe3yqgqsLy16iDytjdjsZ83v8t/Fl1OBhWH4ssSHSMu1GA830wDPeCnCQL/JZHWOpqEeTpG+/FPGpzlt+n+oJ8jHdWeErSal3JfMSOLubKkGWVB7pdAYgzl8Pn47Q/GA5QehBqJiaOME/iL93ONhOmJFyI9ErQ9sqCT6YRsvRqxKy0BZgdud2Et1/W8oVZnR6Ev0e5hG/UbJ3SCiocYusWyKwFcmO0xEydxQA85miCvu78DeaJ1aA== template: metadata: creationTimestamp: null name: my-secret namespace: default status: {}
SealedSecret 를 클러스터에 등록하기
생성한 SealedSecret
리소스를 클러스터에 등록해 보겠습니다. 네임스페이스는 default
입니다.
kubectl
일 실행해서 secrets
과 sealedsecrets
을 조회해 보면 나오지 않습니다.
$ kubectl -n default get secrets NAME TYPE DATA AGE default-token-pz9rt kubernetes.io/service-account-token 3 20d istio.default istio.io/key-and-cert 3 20d $ kubectl -n default get sealedsecrets No resources found in default namespace.
생성한 SealedSecret
리소스를 등록합니다.
$ kubectl apply -f my-sealed-secret.yaml sealedsecret.bitnami.com/my-secret created
다시 secrets
과 sealedsecrets
을 조회해 보면 등록한 my-secret
가 보이는 것을 알 수 있습니다.
$ kubectl -n default get secrets NAME TYPE DATA AGE default-token-pz9rt kubernetes.io/service-account-token 3 20d istio.default istio.io/key-and-cert 3 20d my-secret Opaque 2 4s $ kubectl -n default get sealedsecrets NAME AGE my-secret 9s
자동으로 생성된 secret
의 내용을 확인해 보겠습니다.
$ kubectl -n default get secrets my-secret -o yaml apiVersion: v1 data: password: aGVsbG8td3JvbGQ= username: YWRtaW4= kind: Secret metadata: creationTimestamp: "2020-01-25T06:37:42Z" name: my-secret namespace: default ownerReferences: - apiVersion: bitnami.com/v1alpha1 controller: true kind: SealedSecret name: my-secret uid: 8ed4ea13-4f89-46e2-9149-b80fe01b46d2 resourceVersion: "6472553" selfLink: /api/v1/namespaces/default/secrets/my-secret uid: 466f7e72-9dc0-401b-9e2f-b073d08b5540 type: Opaque
정상적으로 복호화 되어 있는 것을 확인 할 수 있습니다.
기타
인증서를 별도로 사용하기
kubeseal
은 비대칭키 암호화 알고리즘을 사용해서 값들을 암호화 합니다. 별 다른 옵션을 주지 않으면 기본 설정 파일(~/.kube/config)을 이용해서 인증서를 가져온 후, 암호화 작업을 합니다. 상황이 여의치 않는 경우에는 이 인증서 정보를 파일로 저장한 후 직접 사용하실 수 있습니다.
kubeseal --fetch-cert > pub-cert.pem
혹시라도 컨트롤러를 설치한 네임스페이스가 다르거나, 이름이 다른 경우 다음과 같은 옵션을 사용할 수도 있습니다.
kubeseal --fetch-cert \\ --controller-namespace=kube-system \\ --controller-name=sealed-secrets-controller \\ > pub-cert.pem
그리고 SealedSecret
리소스를 생성할때 --cert=pub-cert.pem
플래그로 인증서를 지정해 줄 수 있습니다.
kubeseal --format=yaml --cert=pub-cert.pem < my-secret.yaml > my-sealed-secret.yaml
비대칭키 암호화 알고리즘을 사용하기 때문에, 복호화를 위해서 개인키를 사용합니다. 이 개인키를 잃어버리면, 복호화를 할 수가 없습니다. 그래서 이 개인키를 안전하게 잘 보관해야 합니다.
백업하기
kubectl get secret -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key -o yaml >master.key
복구하기
$ kubectl apply -f master.key $ kubectl delete pod -n kube-system -l name=sealed-secrets-controller