클라우드 컴퓨팅 & NoSQL/도커 & 쿠버네티스

쿠버네티스 #12 - Secret

Terry Cho 2018. 7. 7. 14:54

쿠버네티스 #12

Secret


조대협 (http://bcho.tistory.com)


Secret

configMap이 일반적인 환경 설정 정보나 CONFIG정보를 저장하도록 디자인 되었다면, 보안이 중요한 패스워드나, API 키, 인증서 파일들은 secret에 저장할 수 있다. Secret은 안에 저장된 내용을 지키기 위해서 추가적인 보안 기능을 제공한다. 예를 들어 secret의 값들은 etcd에 저장될때 암호화된 형태로 저장되고 API server나 node의 파일에는 저장되지 않고, 항상 메모리에 저장되어 있기 때문에 상대적으로 접근이 어렵다.

하나의 secret의 사이즈는 최대 1M까지 지원되는데, 메모리에 지원되는 특성 때문에, secret을 여러개 저장하게 되면 API Server나 노드에서 이를 저장하는 kubelet의 메모리 사용량이 늘어나서 Out Of Memory와 같은 이슈를 유발할 수 있기 때문에, 보안적으로 꼭 필요한 정보만 secret에 저장하도록 하는게 좋다.


사용 방법에 있어서는 secret와 configmap은 기본적으로 거의 유사하다. 기본적으로 키/밸류 형태의 저장구조를 가지고 있으며, 사용시 환경 변수를 통해서 Pod에 그 값을 전달하거나, 또는 디스크 볼륨으로 마운트가 가능한데, secret은 정의하는 방법이 다소 차이가 있다.

예를 들어 language라는 키로 java라는 값을 저장하고자 할때, configmap의 경우에는 이를 language:java 식으로 일반 문자열로 저장했지만 secret의 경우에는 값에 해당하는 부분을 base64 포맷으로 인코딩해야 한다.

즉 java라는 문자열을 base64로 인코딩을 하면, amF2YQo= 가 된다.

문자열을 base64포맷으로 인코딩 하려면 맥이나 리눅스에서 다음과 같은 명령을 이용하면 된다.

%echo java | base64

이렇게 인코딩 된 문자열을 이용해서 secret을 정의해보면 다음과 같다.


hello-secret.yaml 파일

apiVersion: v1
kind: Secret
metadata:
 name: hello-secret
data:
 language: amF2YQo=

base64로 인코딩이 되어 있지만 이를 환경변수로 넘길때나 디스크볼륨으로 마운트해서 읽을 경우에는 디코딩이되서 읽어진다. base64는 단순 인코딩이지 암호화가 아닌데, 왜 궂이 base64로 인코딩을 하는 것일까? secret에 저장되는 내용은 패스워드와 같은 단순 문자열의 경우에는 바로 저장이 가능 하지만, SSL 인증서와 같은 바이너리 파일의 경우에는 문자열로 저장이 불가능하다. 그래서 이러한 바이너리 파일 저장을 지원하기 위해서 secret의 경우에는 저장되는 값을 base64로 인코딩을 하여 저장하도록 되어 있다.


그러면 앞에서 작성한 secret을 테스트하기 위해서 node.js로 간단한 server.js 애플리케이션을 만들어보자.


var os = require('os');


var http = require('http');

var handleRequest = function(request, response) {

 response.writeHead(200);

 response.end(" my prefered secret language is "+process.env.LANGUAGE+ "\n");


 //log

 console.log("["+

Date(Date.now()).toLocaleString()+

"] "+os.hostname());

}

var www = http.createServer(handleRequest);

www.listen(8080);


이 코드는 LANGUAGE라는 환경 변수에서 값을 읽어서 출력하는 코드이다. (앞의 configmap 코드와 동일)

이 파일을 도커 컨테이너 이미지로 만든후에 gcr.io/terrycho-sandbox/hello-secret:v1 이름으로 등록한 후에, 아래와 같이 Deployment 코드를 작성해보자


hello-secret-literal-deployment.yaml 파일


apiVersion: apps/v1beta2

kind: Deployment

metadata:

 name: hello-secret-deployment

spec:

 replicas: 3

 minReadySeconds: 5

 selector:

   matchLabels:

     app: hello-secret-literal

 template:

   metadata:

     name: hello-secret-literal-pod

     labels:

       app: hello-secret-literal

   spec:

     containers:

     - name: cm

       image: gcr.io/terrycho-sandbox/hello-secret:v1

       imagePullPolicy: Always

       ports:

       - containerPort: 8080

       env:

       - name: LANGUAGE

         valueFrom:

           secretKeyRef:

              name: hello-secret

              key: language


Deployment 파일은 configMap과 크게 다를 것이 없다. configMapKeyRef를 secrectKeyRef로 변경하였고, configMap과 마찬가지로 secret의 이름(hello-secret)을 정하고, 키 이름 (language)을 지정하였다. Deployment를 배포한후에, 서비스를 배포해서 웹으로 접속하면 아래와 같이 secret에 base64로 저장된 “java”라는 문자열이 디코딩되서 출력되는 것을 확인할 수 있다.



파일로 마운트 하기

secret도 configMap과 마찬가지로, 설정 값들을 환경변수 뿐만 아니라, 파일로도 넘길 수 있다. 환경변수로 넘기는 방법과 마찬가지로 파일을 base64로 인코딩해서 secret을 생성해야 하며, 인코딩된 secret을 Pod에 파일로 마운트될때는 디코딩된 상태로 마운트가 된다.


이번에는 secret을 파일에서 부터 만들어보자 사용자 ID를 저장한 user.property 파일과, 비밀 번호를 저장한 password.property 파일 두개가 있다고 하자.각 파일의 내용은 다음과 같다.



Filename : user.property

terry



Filename : password.property

mypassword


이 두개의 파일을 secret에 저장을 할것이다. 명령은 다음과 같다.

% kubectl create secret generic db-password --from-file=./user.property  --from-file=./password.property


db-password라는 secret을 생성하고, user.property, password.property에서 secret을 생성하게 된다. 생성된 secret은 user.property, password.property라는 파일명을 각각 키로하여 파일의 내용이 저장된다.

이때 파일을 통해서 secret을 만들경우에는 별도로 base64 인코딩을 하지 않더라도 자동으로 base64로 인코딩 되어 저장된다.


위의 명령을 보면 kubectl create secret 명령어 뒤에 generic 이라는 키워드를 붙였는데, 이는 secret을 generic이라는 타입으로 생성하기 위함이다. secret의 타입에 대해서는 뒤에서 설명하도록 한다.


이렇게 생성된 secret을 확인해보면 아래와 같이 user.property, password.property 두개의 키로 데이타를 저장하고 있는 것을 확인할 수 있다.




시크릿을 디스크로 마운트해서 읽는 것을 테스트해보기 위해서 간단하게 node.js로 server.js 라는 코드를 아래와 같이 작성한다. 아래 코드는 /tmp/db-password 디렉토리에서 user.property와 password.property 파일을 읽어서 화면에 출력하는 코드이다.


var os = require('os');

var fs = require('fs');

var http = require('http');


var handleRequest = function(request, response) {

 fs.readFile('/tmp/db-password/user.property',function(err,userid){

   response.writeHead(200);

   response.write("user id  is "+userid+" \n");

   fs.readFile('/tmp/db-password/password.property',function(err,password){

     response.end(" password is "+password+ "\n");

   })

 })


 //log

 console.log("["+

Date(Date.now()).toLocaleString()+

"] "+os.hostname());

}

var www = http.createServer(handleRequest);

www.listen(8080);


다음 이 코드에서 user.property와 password.property 를 /tmp/db-password 디렉토리에서 읽어올 수 있도록, 앞에서 만든 db-password 라는 시크릿을 /tmp/db-password  디렉토리에 마운트 하도록 deployment를 정의한다.

hello-secret-file-deployment.yaml


apiVersion: apps/v1beta2

kind: Deployment

metadata:

 name: hello-serect-file-deployment

spec:

 replicas: 3

 minReadySeconds: 5

 selector:

   matchLabels:

     app: hello-secret-file

 template:

   metadata:

     name: hello-secret-file

     labels:

       app: hello-secret-file

   spec:

     containers:

     - name: hello-secret-file

       image: gcr.io/terrycho-sandbox/hello-secret-file:v1

       imagePullPolicy: Always

       ports:

       - containerPort: 8080

       volumeMounts:

         - name: db-password

           mountPath: "/tmp/db-password"

           readOnly: true

     volumes:

     - name: db-password

       secret:

         secretName: db-password

         defaultMode: 0600


configMap과 차이가 거의 없다.  configMap이 secret으로만 바뀐건데, 이번에는 마운트 되는 파일의 퍼미션을 지정하였다. (configMap도 지정이 가능하다.) defaultMode로 파일의 퍼미션을 정의해놓으면, 파일 생성시, 해당 퍼미션으로 파일이 생성된다. 여기서는 0600으로 정의했기 때문에, rw-------으로 파일이 생성될것이다. 만약에 퍼미션을 지정하지 않았을 경우에는 디폴트로 0644 퍼미션으로 파일이 생성된다.


위의 스크립트로 생성한 Pod에 SSH로 들어가 보면 아래와 같이 /tmp/db-password에 user.property파일과 password.property 파일이 생성된것을 확인할 수 있다.




그런데 파일 퍼미션을 보면 우리가 지정한 0600이 아닌데, 잘 보면 user.property와 password.property는 링크로 ..data/user.property 와  ..data/password.property 파일로 연결이 되어 있다.




Deployment 배포가 끝났으면, 서비스를 배포해서 웹으로 접속해보자


위와 같이 마운트된 시크릿 파일에서 데이타를 읽어와서 제대로 출력한것을 확인할 수 있다.

시크릿 타입

시크릿은 configMap과는 다르게 타입



그리드형