ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 쿠버네티스 시크릿(kubernetes secret)
    Kubernetes 2018. 8. 3. 09:00
    시크릿(secret)은 비밀번호, OAuth 토큰, ssh 키 같은 민감한 정보들을 저장하는 용도로 사용합니다. 이런 정보들은 컨테이너 안에 저장해 두지 않고 별도로 보관해 두었다가 실제 포드가 실행할때 설정을 통해서 컨테이너에 제공해 줍니다.

    시크릿 종류
    시크릿은 내장 시크릿(built-in)과 사용자 시크릿 2가지 종류가 있습니다. 내장 시크릿은 쿠버네티스 클러스터 내부에서 API에 접근할때 사용됩니다. 클러스터 내부에서 사용되는 계정인 ServiceAccount를 생성하면 자동으로 관련 시크릿이 만들어 집니다. 이렇게 만들어진 시크릿을 이용해서 해당 ServiceAccount가 권한을 가지고 있는 API에 접근할 수 있습니다. 
    사용자 시크릿은 사용자가 만든 시크릿입니다. 시크릿은 kubectl create secret 명령으로도 만들 수 있고 다른 자원들 처럼 yaml파일을 이용해서 만들수도 있습니다.
    먼저 kubectl create 명령으로 만드는 방법은 알아보겠습니다.
    다음처럼 유저이름과 비밀번호를 가지고 있는 파일을 각각 만듭니다. 그 후 kubectl create secret 명령으로 user-pass-secret라는 이름의 시크릿을 만듭니다.
    echo -n ‘username' > ./username.txt
    echo -n ‘password' > ./password.txt
    kubectl create secret generic user-pass-secret --from-file=./username.txt --from-file=./password.txt

    만들어진 secret은 다음 명령으로 확인할 수 있습니다. password.txt와 username.txt라는 키를 가진 data가 보입니다. 여기서 보이는 건 값이 평문이 아니라 base64로 인코딩되어서 들어가 있다는 걸 확인할 수 있습니다. secret이 생성될때 자동으로 값들이 base64로 인코딩되어서 들어간겁니다.
    wonchon@jeong-woncheon-ui-MacBook-Pro ~/test $ kubectl get secret user-pass-secret -o yaml
    apiVersion: v1
    data:
      password.txt: cGFzc3dvcmQ=
      username.txt: dXNlcm5hbWU=
    kind: Secret
    metadata:
      creationTimestamp: 2018-08-01T10:37:30Z
      name: user-pass-secret
      namespace: default
      resourceVersion: "42669"
      selfLink: /api/v1/namespaces/default/secrets/user-pass-secret
      uid: e4a7fd41-9576-11e8-84cf-025000000001
    type: Opaque
    wonchon@jeong-woncheon-ui-MacBook-Pro ~/test $

    아래 명령으로 입력되어 있는 값들을 디코딩해보면 원래 값을 확인해 볼 수 있습니다.
    echo cGFzc3dvcmQ= | base64 --decode
    echo dXNlcm5hbWU= | base64 --decode

    yaml파일을 이용해서 시크릿을 만들수도 있습니다. 다음처럼 user-pass-yaml.yaml파일을 만든 다음에 클러스터에 kubectl apply -f user-pass-yaml.yaml로 시크릿을 생성할 수 있습니다. 이때 주의해야할 점은 username과 password에 평문이 아니라 base64로 인코딩된 값을 넣어 준다는 점입니다. 확인해 보면 타입은 Opaque입니다. 다른 타입들은 뒤에 더 살펴보겠습니다.
    apiVersion: v1
    kind: Secret
    metadata:
      name: user-pass-yaml
    type: Opaque
    data:
      username: dXNlcm5hbWU=
      password: cGFzc3dvcmQ=

    다음 명령으로 필요한 값들을 base64로 인코딩할 수 있습니다. 데이터의 값에 json이나 yaml같은 값을 넣을 때도 base64 문자열로 인코딩해서 넣어주어야 합니다. 
    echo -n "username" | base64
    echo -n "password" | base64

    시크릿 사용하기
    시크릿은 포드에 환경변수로 제공하거나 볼륨을 이용해서 파일 형식으로 제공할 수도 있습니다.

    환경변수로 포드에 시크릿 제공하기
     앞서 생성한 user-pass-yaml 시크릿을 포드에 환경변수로 제공해 보겠습니다. 다음 내용을 파일로 저장한 다음 클러스터에 적용합니다. 눈여겨 봐야할 부분의 포드 컨테이너의 env 부분입니다. SECRET_USERNAME라는 환경변수를 확인해 보면 valueFrom에 secretKeyRef을 명시해서 시크릿의 특정 값을 참조한다는걸 알수 있습니다. 그 하위의 name에 시크릿 이름인 user-pass-yaml을 명시하고 이 시크릿의 username이라는 키를 가진 값을 가져온다는걸 확인할 수 있습니다. 여기서 사용되는 시크릿은 미리 만들어져 있어야 합니다. 만약 오타 등의 실수로 없는 시크릿을 참조하게 되면 에러가 나면서 포드가 실행되지 못합니다. 좀 더 구체적으로는 apiserver에서는 통과가 되서 스케쥴링이 되지만 실제 kubelet이 포드를 실행하면서 시크릿을 가져올 때 에러가 발생하게 됩니다. 그러면 kubelet은 계속해서 시크릿을 가져오려고 재시도하게 됩니다. 이 때 시크릿을 제대로 만들어 주면 포드가 정상적으로 실행이 됩니다.
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: secretapp
      labels:
        app: secretapp
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: secretapp
      template:
        metadata:
          labels:
            app: secretapp
        spec:
          containers:
          - name: testapp
            image: arisu1000/simple-container-app:latest
            ports:
            - containerPort: 8080
            env:
              - name: SECRET_USERNAME
                valueFrom:
                  secretKeyRef:
                    name: user-pass-yaml
                    key: username
              - name: SECRET_PASSWORD
                valueFrom:
                  secretKeyRef:
                    name: user-pass-yaml
                    key: password
    ---
    apiVersion: v1
    kind: Service
    metadata:
      labels:
        app: secretapp
      name: secretapp-svc
      namespace: default
    spec:
      ports:
      - nodePort: 30900
        port: 8080
        protocol: TCP
        targetPort: 8080
      selector:
        app: secretapp
      type: NodePort

    컨테이너가 실행된 다음에 브라우저에서 localhost:30900/env을 확인해보면 SECRET_USERNAME과 SECRET_PASSWORD이 시크릿에서 가져와서 들어가 있는걸 확인할 수 있습니다.


    볼륨으로 포드에 시크릿 제공하기
    볼륨으로 마운트해서 파일 형식으로 포드에 시크릿을 제공하는 것도 가능합니다. 앞의 예제 파일에서 deployment부분을 다음처럼 변경하면 됩니다. 포드내 컨테이너의 env부분이 없어졌고 포드에 볼륨으로 시크릿을 선언한 다음에 그 볼륨을 다시 컨테이너의 /etc/volume-secret 경로에 마운트한걸 확인할 수 있습니다.
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: secretapp
      labels:
        app: secretapp
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: secretapp
      template:
        metadata:
          labels:
            app: secretapp
        spec:
          containers:
          - name: testapp
            image: arisu1000/simple-container-app:latest
            ports:
            - containerPort: 8080
            volumeMounts:
            - name: volume-secret
              mountPath: "/etc/volume-secret"
              readOnly: true
          volumes:
          - name: volume-secret
            secret:
              secretName: user-pass-yaml

    클러스터에 이 디플로이먼트를 배포한 다음에 브라우저에서 환경변수를 확인해 보면 SECRET_USERNAME과 SECRET_PASSWORD이 없어진걸 확인할 수 있습니다. 볼륨으로 마운트 됐는지 확인하기 위해 다음 주소로 접근해 보면 시크릿에 있던 값이 정상적으로 보이는걸 알 수 있습니다. http://localhost:30900/volume-config?path=/etc/volume-secret/username



    다음처럼 컨테이너에 직접 접근해서 시크릿의 데이터들이 컨테이너 내부에 파일로 마운트되어 있는 걸 확인할 수 있습니다.
    wonchon@jeong-woncheon-ui-MacBook-Pro ~/test $ kubectl get pods
    NAME                         READY     STATUS    RESTARTS   AGE
    secretapp-6b49b8789d-qsjqd   1/1       Running   0          5m
    wonchon@jeong-woncheon-ui-MacBook-Pro ~/test $ kubectl exec -it secretapp-6b49b8789d-qsjqd sh
    ~ # cd /etc/volume-secret/
    /etc/volume-secret # ls
    password  username
    /etc/volume-secret #

    두가지 모두 시크릿 내의 데이터가 포드내에서 사용될때는 base64로 인코딩되어 있는게 아니라 디코딩되어서 평문으로 들어가 있습니다. 

    컨테이너 이미지 pull할때 시크릿 사용하기
    일반적으로 컨테이너 이미지를 pull할때는 대부분 공개되어 있는 이미지를 사용합니다. 하지만 사설(private) 이미지를 이용할 때는 인증 정보가 필요합니다. 데스크탑에서 개인용으로 사용할때는 데스크탑에 사설 이미지에 대한 권한을 가진 사용자 정보를 저장해두고 사용하지만 쿠버네티스 클러스터에서는 그렇게 설정해서 사용하면 보안상의 위험이 있기 때문에 그렇게 설정하지 않습니다. 이때 사용자 정보를 시크릿으로 저장해두고 사용할 수 있습니다. 
    이를 위해서 create secret의 하위 명령으로 도커저장소용 시크릿을 만들 수 있는 docker-registry명령이 있습니다. 다음명령으로 시크릿을 만듭니다. 도커이미지 저장소를 직접 설치해서 사용하거나 공식 도커 허브가 아닌 다른 곳을 사용하고 있다면 그곳 주소로 docker-server 설정을 변경해 주어야 합니다. 그외 USERNAME, PASSWORD, EMAIL부분에는 본인의 정보를 넣어 주어야 합니다.
    kubectl create secret docker-registry dockersecret --docker-username=USERNAME --docker-password=PASSWORD --docker-email=EMAIL --docker-server=https://index.docker.io/v1/

    시크릿이 어떻게 들어갔는지 다음 명령으로 확인해 보겠습니다. 기존과는 다르게 data하위에 .dockerconfigjson이라는 키로 값이 들어가 있는걸 확인할 수 있습니다. 이 시크릿의 타입은 kubernetes.io/dockerconfigjson 입니다.
    wonchon@jeong-woncheon-ui-MacBook-Pro ~/test $ kubectl get secrets dockersecret -o yaml
    apiVersion: v1
    data:
      .dockerconfigjson: eyJhdXRocyI6eyJodHRwczovL2luZGV4Lm...
    kind: Secret
    metadata:
      creationTimestamp: 2018-08-02T10:55:42Z
      name: dockersecret
      namespace: default
      resourceVersion: "52745"
      selfLink: /api/v1/namespaces/default/secrets/dockersecret
      uid: 99e50010-9642-11e8-84cf-025000000001
    type: kubernetes.io/dockerconfigjson

    위에서 사용했던 디플로이먼트를 아래처럼 변경해 보겠습니다. 이미지를 사설 이미지로 변경했고, 컨테이너 설정의 하위에 imagePullSecrets로 dockersecret을 추가했습니다. 우선 사설 저장소가 맞는지 확인하기 위해 imagePullSecrets부분을 삭제한 채로 이 디플로이먼트내용을 배포하면 다음처럼 이미지를 가져올때 ErrImagePull 에러가 나는걸 볼 수 있습니다.
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: secretapp
      labels:
        app: secretapp
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: secretapp
      template:
        metadata:
          labels:
            app: secretapp
        spec:
          containers:
          - name: testapp
            image: arisu1000/private-test:latest
            ports:
            - containerPort: 8080
          imagePullSecrets:
          - name: dockersecret


    다시 imagePullSecrets 부분을 설정하고 배포하면 정상적으로 포드가 실행되는걸 확인할 수 있습니다.


    TLS 인증서를 시크릿으로 사용하기
    시크릿을 https인증서를 저장하는 용도로도 사용할 수 있습니다. 일반적으로 인증서는 공인된 기관에서 발급받아서 사용해야 하지만 여기서는 테스트 목적이기 때문에 사설인증서를 만들어서 사용해 보겠습니다. 아래 명령으로 인증서 키과 crt 파일을 만듭니다.
    openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=example.com

    이렇게 만든 인증서 파일을 이용해서 다음처럼 시크릿을 만듭니다.
    kubectl create secret tls tlssecret --key tls.key --cert tls.crt

    시크릿 내용을 다음처럼 확인할 수 있습니다. tls.key와 tls.crt값이 들어간걸 확인할 수 있고 타입은 kubernetes.io/tls인걸 확인할 수 있습니다. 이 시크릿을 인그레스와 연결해서 사용할 수 있습니다.
    wonchon@jeong-woncheon-ui-MacBook-Pro ~/test $ kubectl get secret tlssecret -o yaml
    apiVersion: v1
    data:
      tls.crt: LS0tLS1CRUdJTi...
      tls.key: LS0tLS1CRUdJTi...
    kind: Secret
    metadata:
      creationTimestamp: 2018-08-02T11:29:46Z
      name: tlssecret
      namespace: default
      resourceVersion: "55043"
      selfLink: /api/v1/namespaces/default/secrets/tlssecret
      uid: 5c29da17-9647-11e8-84cf-025000000001
    type: kubernetes.io/tls

    시크릿 제한
    시크릿 크기가 너무 커지면 쿠버네티스의 apiserver나 kubelet의 메모리를 많이 차지하게되기 때문에 개별 시크릿의 최대 크기는 1MB까지입니다. 크기가 작은 시크릿을 너무 많이 만들어도 같은 이슈가 있을 수 있기 때문에 전체 시크릿에 제한을 두는 기능도 도입될 예정입니다. 시크릿 데이터는 etcd에 암호화 되지 않은 평문으로 저장됩니다. 그래서 누군가 etcd에 직접 접근한다면 시크릿의 내용을 확인할 수 있습니다. etcd에는 이외에도 중요한 데이터가 많이 있기 때문에 중요한 서비스에 쿠버네티스를 사용중이라면 etcd에 대한 접근을 제한하는 것이 필요합니다. etcd에 저장되는 시크릿을 암호화해서 저장하는 것도 가능하지만 그건 쿠버네티스 클러스터를 직접 설치해서 사용할 때 옵션으로 별도로 지정을 해주어야 합니다.




    댓글

Designed by Tistory.