티스토리 뷰

Cloud/Kubernetes

Kubernetes Sealed Secrets

Jacob_baek 2022. 3. 21. 22:22

Why Sealed-Secrets is needed

Kubernetes 환경에서 대체로 Secret을 제외한 모든 설정정보를 Git으로 관리한다.
Secret의 경우 사실 비밀정보이고 노출되면 문제가 되기에 이를 따로 보관하거나
별도의 secret management 서비스를 사용하는등의 고민이 되기 마련이다.

SealedSecrets

Sealed-Secrets는 앞선 어려움을 보안적으로 취약하지 않은 상태의 암호화된 Secret으로
Git 저장소에 저장되게 해주어 GitOps 환경상에 Secret 보관에 어려움을 해소시켜준다.

테스트 하며 확인한 특징을 정리해보자면,

  • Kubernetes secret의 비밀정보를 암호화할 수 있다.
  • 암호화된 비밀정보가 포함된 상태로 Git 저장소에 저장할수 있다.
    (이는 제공기능이라기 보다 암호화된 비밀정보이기에 당연한 사실)
  • Sealing key를 갱신해준다.(기본 30일마다)
  • secret에 대한 scope을 통한 제어(이름변경과 같은)를 할 수 있다.
  • https://github.com/bitnami-labs/sealed-secrets

알아두어야할 것은 Kubernetes 상에서는 암호화된 상태가 아니다.
필자가 오해했던 부분이긴한데 Kubernetes에는 암호화된 비밀정보가 포함된 resource를 생성하게 되더라도
Secret은 base64로 decode 되는 평문으로 만들어진다.

how to install sealed-secrets

sealed-secrets controller command 사용시 help message이 출력되는 내용들이다.
(현재 최신버전인 0.17.3 기준)

 Usage of controller:                                                                                                                                                                │
│       --accept-deprecated-v1-data   Accept deprecated V1 data field. (default true)                                                                                                 │
│       --all-namespaces              Scan all namespaces or only the current namespace (default=true). (default true)                                                                │
│       --key-cutoff-time string      Create a new key if latest one is older than this cutoff time. RFC1123 format with numeric timezone expected.                                   │
│       --key-prefix string           Prefix used to name keys. (default "sealed-secrets-key")                                                                                        │
│       --key-renew-period duration   New key generation period (automatic rotation disabled if 0) (default 720h0m0s)                                                                 │
│       --key-size int                Size of encryption key. (default 4096)                                                                                                          │
│       --key-ttl duration            Duration that certificate is valid for. (default 87600h0m0s)                                                                                    │
│       --label-selector string       Label selector which can be used to filter sealed secrets.                                                                                      │
│       --listen-addr string          HTTP serving address. (default ":8080")                                                                                                         │
│       --my-cn string                Common name to be used as issuer/subject DN in generated certificate.                                                                           │
│       --old-gc-behaviour            Revert to old GC behavior where the controller deletes secrets instead of delegating that to k8s itself.                                        │
│       --read-timeout duration       HTTP request timeout. (default 2m0s)                                                                                                            │
│       --update-status               beta: if true, the controller will update the status subresource whenever it processes a sealed secret (default true)                           │
│       --version                     Print version information and exit                                                                                                              │
│       --write-timeout duration      HTTP response timeout. (default 2m0s)   

여기서 사용해볼 옵션은--key-renew-period이며 key를 지정한 주기마다 갱신하게 된다.

아래와 같이 deployment에 argument를 추가하여 renew 기간을 60초로 추가했다. (기본값은 30일이다.)


jacob@dubaek:~/workspace$ wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.17.3/controller.yaml -O sealedsecret-controller.yaml
jacob@dubaek:~/workspace$ cat sealedsecret-controller.yaml
...
    spec:
      containers:
      - command:
        - controller
        args:
        - --key-renew-period=60s
        image: quay.io/bitnami/sealed-secrets-controller:v0.17.3
        imagePullPolicy: Always
        livenessProbe:
          failureThreshold: 3
...

이후 아래와 같이 controller를 설치하게 되면

jacob@dubaek:~/workspace$ kubectl apply -f sealedsecret-controller.yaml
deployment.apps/sealed-secrets-controller configured
customresourcedefinition.apiextensions.k8s.io/sealedsecrets.bitnami.com unchanged
rolebinding.rbac.authorization.k8s.io/sealed-secrets-service-proxier unchanged
serviceaccount/sealed-secrets-controller unchanged
service/sealed-secrets-controller unchanged
role.rbac.authorization.k8s.io/sealed-secrets-service-proxier unchanged
rolebinding.rbac.authorization.k8s.io/sealed-secrets-controller unchanged
role.rbac.authorization.k8s.io/sealed-secrets-key-admin unchanged
clusterrolebinding.rbac.authorization.k8s.io/sealed-secrets-controller unchanged
clusterrole.rbac.authorization.k8s.io/secrets-unsealer unchanged

60초가 경과한 후에 새로운 인증서로 갱신을 수행한다.

2022/03/21 11:10:47 Starting sealed-secrets controller version: 0.17.3
2022/03/21 11:10:47 Searching for existing private keys
controller version: 0.17.3
2022/03/21 11:10:47 ----- sealed-secrets-keywl7pm
2022/03/21 11:10:47 HTTP server serving on :8080
2022/03/21 11:10:47 Updating default/sealedsecret-data
2022/03/21 11:10:47 Event(v1.ObjectReference{Kind:"SealedSecret", Namespace:"default", Name:"sealedsecret-data", UID:"7dd21fab-69a5-4a31-a509-4be72a2d7f48", APIVersion:"bitnami.com/v1
alpha1", ResourceVersion:"19721501", FieldPath:""}): type: 'Normal' reason: 'Unsealed' SealedSecret unsealed successfully
2022/03/21 11:10:53 New key written to kube-system/sealed-secrets-key7t9bj
2022/03/21 11:10:53 Certificate is
-----BEGIN CERTIFICATE-----
MIIEzDCCArSgAwIBAgIQOJ5EjDxQL88NT6piw+NiuDANBgkqhkiG9w0BAQsFADAA
......
xNQ17WyoKiSwjwbxBuZrbYWYy9YbwqK9l7kuixba78U=
-----END CERTIFICATE-----
2022/03/21 11:11:54 New key written to kube-system/sealed-secrets-keybz2mg
2022/03/21 11:11:54 Certificate is
-----BEGIN CERTIFICATE-----
MIIEzDCCArSgAwIBAgIQKp8u+R0ys+c6FBVBoZ0crTANBgkqhkiG9w0BAQsFADAA
......
q5JD1Lco3EFeW1OkHr15p5sGnmWyKP77DRBIDbPnT84=
-----END CERTIFICATE-----

다음과 같이 60초마다 새로 갱신된 secret을 확인할 수 있다.

jacob@dubaek:~/workspace$ kubectl get secrets --selector=sealedsecrets.bitnami.com/sealed-secrets-key=active -n kube-system
NAME                      TYPE                DATA   AGE
sealed-secrets-key7t9bj   kubernetes.io/tls   2      4m4s
sealed-secrets-keybz2mg   kubernetes.io/tls   2      3m3s
sealed-secrets-keyrnbr5   kubernetes.io/tls   2      2m9s
sealed-secrets-keywl7pm   kubernetes.io/tls   2      3h16m
sealed-secrets-keyxqqk9   kubernetes.io/tls   2      9s
sealed-secrets-keyzd87g   kubernetes.io/tls   2      69s

이를 통해 키가 유출되었을 때(그럴일은 거의 없겠지만)에도 보완이 일부 이루어질수 있다.

How to make and use sealed-secrets

들어가기전에 다음 순서를 기억하자.

  • 암호화
    1. secret 생성
    2. kubeseal로 암호화
    3. kubectl로 SealedSecret cluster에 생성
  • 복호화
    1. SealedSecret Resource Type으로 생성하게 되면
    2. SealedSecret Controller에 의해 data가 복호화되면서 secret이 cluster내에 생성된다.

실제 명령을 사용하여 secret 생성과정을 알아보자.

  1. kubeseal 을 이용한 secret 암호화

다음 명령을 통해 kubeseal을 다운로드 받아 실행가능한 path에 위치시킨다.

jacob@dubaek:~/workspace$ curl -sL https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.17.3/kubeseal-0.17.3-linux-amd64.tar.gz | tar xvzf - 
README.md
kubeseal 
jacob@dubaek:~/workspace$ sudo mv kubeseal /usr/local/bin/

kubeseal을 이용하여 secret내 비밀정보를 암호화하자.

예제는 testval이라는 문구를 secret 으로 만들었다.

jacob@dubaek:~/workspace$ echo "testval" | base64
dGVzdHZhbAo=
jacob@dubaek:~/workspace$ cat sealedsecret-example.yaml
apiVersion: v1
data:
  testsecret: dGVzdHZhbAo=
kind: Secret
metadata:
  name: sealedsecret-data

이제 만들어진 예제를 kubeseal로 암호화 시키는 과정이다.

jacob@dubaek:~/workspace$ kubeseal -o yaml < sealedsecret-example.yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  creationTimestamp: null
  name: sealedsecret-data
  namespace: default
spec:
  encryptedData:
    testsecret: AgCaKUt30SsLXBhZ0BeHqJDQ7e7uw9LmW6LQHWVfEtcq/ovfHYTcK8TtypQ8UaFzddLpl2uDK/kFKsqy8BP67NQ13FkpVZVphkXir2Dw6WGZtQiYQ/ic/CrtXPtXRL2UfYrI8btV1tV9cPuU3Lc8cCMPZbhP3vCSif3PobLPWx7uouMFh+LGD/U95QqyHJqRAwEKgvgX/m5HPcPv3a5wndraCcY380xMKvVGnSx8nubqiECCpUxvJtTWH/+a/fQIwT50MtauGLtqe+t61709H2iK1QC25077wEbER/24mMnr8PsSI3pWIV5ZA3BIfyQD9m87ExkHJRdF5TJ6BAv+GoMCzefMX0JE4CldN/Fgzw6HOY95VwKOAnQ7WgutkDpbbrl5cLmmlrc6uqlFvu5GHqrhy7OZ7TpNb3+U01fRSt58ZH4Ek23Zv4zQJS2pChYWA2JZadH1lF41RjWGJq5NW6m+jRm5oQ+wEz5V44gqwHDfI2YxgVv6cXlNUdII0B3EcKYn3nFWcALdXJw5HIhZ6LJ2dhWT2N0S335TSfBBkr7/s73Js+X4RhB/9/44BtMHEGToxSLGT3cmRpuws7w3fsQ9UFns9gyQJuBU4rqe+qNXJLWM8/4zXdc4ayZI2Wgk6w+VZq3f0hR6mAtoeut9zqNguW11NDohWV4ubqoNRJggEVYzrDI7mEDVLQK5A2fr12JGaS4K0tpU2Q==
  template:
    data: null
    metadata:
      creationTimestamp: null
      name: sealedsecret-data
      namespace: default

위 명령어는 kubeseal이 연결가능한 kubernetes cluster에서 동작중인 상태에서 사용가능하다.

참고로 kubeseal로 암호화할때마다 다른 결과가 나오며
controller가 미존재할 경우 다음과 같은 에러가 나온다.

jacob@dubaek:~/workspace$ kubeseal -o yaml < sealedsecret-example.yaml
error: cannot get sealed secret service: services "sealed-secrets-controller" not found
  1. cluster내에 secret 생성

이제 kubeseal로 만들어낸 yaml로 sealedsecret 을 생성하자.

jacob@dubaek:~/workspace$ kubectl apply -f sealedsecret-sealdata.yaml
sealedsecret.bitnami.com/sealedsecret-data created
jacob@dubaek:~/workspace$ kubectl apply -f sealedsecret-sealdata2.yaml
sealedsecret.bitnami.com/sealedsecret-data2 created

위 예제중 sealedsecret-sealdata2.yaml 예제는 다른 namespace를 사용한다.

실제 secret이 만들어질때 controller의 log를 확인해보면 생성요청을 한 sealedsecret resource type이
만들어지면서 지정한 namespace에 지정한 name으로 생성되는것을 볼수 있다.

jacob@dubaek:~/workspace$ kubectl logs sealed-secrets-controller-6fdc665cf7-l66xl -n kube-system
controller version: 0.17.3
2022/03/21 12:27:42 Starting sealed-secrets controller version: 0.17.3
2022/03/21 12:27:42 Searching for existing private keys

...

2022/03/21 12:27:42 Updating loki/sealedsecret-data2
2022/03/21 12:27:42 Event(v1.ObjectReference{Kind:"SealedSecret", Namespace:"loki", Name:"sealedsecret-data2", UID:"7421df4a-1e08-431e-a804-bfbb6c1efaf6", APIVersion:"bitnami.com/v1alpha1", ResourceVersion:"19746425", FieldPath:""}): type: 'Normal' reason: 'Unsealed' SealedSecret unsealed successfully
2022/03/21 12:27:42 Updating loki/sealedsecret-data2
2022/03/21 12:27:42 Event(v1.ObjectReference{Kind:"SealedSecret", Namespace:"loki", Name:"sealedsecret-data2", UID:"7421df4a-1e08-431e-a804-bfbb6c1efaf6", APIVersion:"bitnami.com/v1alpha1", ResourceVersion:"19746606", FieldPath:""}): type: 'Normal' reason: 'Unsealed' SealedSecret unsealed successfully
2022/03/21 12:28:26 Updating default/sealedsecret-data
2022/03/21 12:28:26 Event(v1.ObjectReference{Kind:"SealedSecret", Namespace:"default", Name:"sealedsecret-data", UID:"9cd18041-7d7d-4d36-baab-9f7ec630712f", APIVersion:"bitnami.com/v1alpha1", ResourceVersion:"19746725", FieldPath:""}): type: 'Normal' reason: 'Unsealed' SealedSecret unsealed successfully
2022/03/21 12:28:26 Updating default/sealedsecret-data
2022/03/21 12:28:26 Event(v1.ObjectReference{Kind:"SealedSecret", Namespace:"default", Name:"sealedsecret-data", UID:"9cd18041-7d7d-4d36-baab-9f7ec630712f", APIVersion:"bitnami.com/v1alpha1", ResourceVersion:"19746727", FieldPath:""}): type: 'Normal' reason: 'Unsealed' SealedSecret unsealed successfully
  1. secret 조회
    실제로 지정했던 namespace로 sealedsecret 이 생성된것을 볼수 있고
jacob@dubaek:~/workspace$ kubectl get sealedsecret -A
NAMESPACE   NAME                 AGE
default     sealedsecret-data    5m17s
loki        sealedsecret-data2   7m19s

실제 secret이 다음과 같이 확인되어진다.

jacob@dubaek:~/workspace$ kubectl get secret/sealedsecret-data2 -n loki
NAME                 TYPE     DATA   AGE
sealedsecret-data2   Opaque   1      5m14s
jacob@dubaek:~/workspace$ kubectl get secret/sealedsecret-data
NAME                TYPE     DATA   AGE
sealedsecret-data   Opaque   1      4m39s

이제 secret이 준비되었으니 각 app에서 mount 하여 사용하면 된다.

즉, sealedsecret resource type을 secret을 만들게되면 실제 secret이 kubernetes cluster에 생성되게 된다.
다만 github과 같은 git 저장소에는 yaml 형태의 암호화된 결과가 저장되게 되기에 gitops 환경에서는 유용하게 된다.

cert

앞에서는 비밀정보만 secret 형태로 만들어내는 방식이었는데
개인적으로 자주 사용할 certificate를 암호화된 상태로 만들어 저장하는 방법을 알아보자.
(사실 앞선 비밀정보 암호화 방식과 별반 다르지 않다.)

먼저 아래 명령으로 cert용 yaml 파일을 생성한다.

jacob@dubaek:~/workspace$ kubectl create secret tls jacobbaek-crt --key jacobbaek.key.pem  --cert jacobbaek.com.pem --dry-run=client -o yaml > jacobbaek-crt.yaml

생성된 jacobbaek-crt.yaml 파일을 kubeseal 명령을 이용해 암호화 시키자.

jacob@dubaek:~/workspace$ kubeseal -o yaml -f jacobbaek-crt.yaml -w jacobbaek-crt-sealed.yaml
jacob@dubaek:~/workspace$ cat jacobbaek-crt-sealed.yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  creationTimestamp: null
  name: jacobbaek-crt
  namespace: default
spec:
  encryptedData:
    tls.crt: Ag ... g=
    tls.key: Ag ... M=
  template:
    data: null
    metadata:
      creationTimestamp: null
      name: jacobbaek-crt
      namespace: default
    type: kubernetes.io/tls

이후 이를 확인해보면 복호화된 상태의 secret이 생성되었음을 확인할 수 있다.

jacob@dubaek:~/workspace$ kubectl apply -f jacobbaek-crt-sealed.yaml
sealedsecret.bitnami.com/jacobbaek-crt created
jacob@dubaek:~/workspace$ kubectl get secret jacobbaek-crt
NAME            TYPE                DATA   AGE
jacobbaek-crt   kubernetes.io/tls   2      16s
jacob@dubaek:~/workspace$ kubectl get secret jacobbaek-crt -o yaml
apiVersion: v1
data:
  tls.crt: LS ... 0K
  tls.key: LS ... 0K
kind: Secret
metadata:
  creationTimestamp: "2022-03-22T00:37:42Z"
  name: jacobbaek-crt
  namespace: default
  ownerReferences:
  - apiVersion: bitnami.com/v1alpha1
    controller: true
    kind: SealedSecret
    name: jacobbaek-crt
    uid: 75c4a1db-8d52-403a-9a92-68e507d2be29
  resourceVersion: "19838672"
  uid: b914a8bf-e923-4f91-9b6d-b6b675607ade
type: kubernetes.io/tls

scope 지정

kubeseal을 이용하여 다음 3가지 scope을 지정하여 sealed secret을 생성할 수 있다.

scope 분류 설명
strict name과 namespace 를 지정한대로만 사용가능하며 변경이 불가하다.
namespace-wide sealedsecret이 존재하는 namespace에서는 이름변경이 가능하다.
cluster-wide 모든 namespace와 원하는 어느 이름이든지 자유롭게 secret을 변경할수 있다.

다음 명령으로 clsuter-wide 혹은 namespace-wide를 지정할수 있다.
아무것도 설정하지 않을 경우 strict이 기본값이다.

jacob@dubaek:~/workspace$ kubeseal -o yaml --scope cluster-wide < sealedsecret-example.yaml > sealedsecret-sealdata3.yaml

사실 어디에 쓰일지는 아직 감이 안잡힌다. 좀더 사용해보면서 업데이트해볼 예정.

helm-operator를 이용한 sealedsecret 사용해보기

참고사이트

댓글
댓글쓰기 폼