🏖 Cert-manager란?
Cert-manager는 Kubernetes 내부에서 HTTPS 통신을 위한 인증서를 생성하고, 또 인증서의 만료 기간이 되면 자동으로 인증서를 갱신해주는 역할을 하는 Certificate manager controller입니다.
쉽게 말해 Kubernetes 내에서 외부에 존재하는 Issuers를 활용하거나 selfsigned Issuer를 직접 생성해서 생성하여 Certificate를 생성하고, 이때 생성된 Certificate를 관리하며 인증서의 만료 시간이 가까워지면 인증서를 자동으로 갱신해줍니다.
Cert-manager가 사용하는 외부에 존재하는 Issuer는 아래의 이미지와 같은데, 대표적인 Issuer로 무료로 사용되고 있는 let's enscrypt를 많이 사용하고 있습니다. 하지만 let's enscrypt를 사용한 예제를 구글링을 하면 쉽게 검색할 수 있기 때문에 해당 포스팅에서는 self-signed Issuer를 생성하고 그를 이용해 Certificate를 생성 및 관리하는 방법에 대해서 살펴보도록 하겠습니다. 그리고 마지막으로 생성된 Certificate를 이용해서 MySQL에 HTTPS를 적용해보도록 하겠습니다.
해당 포스팅에선 아래와 같은 version을 사용하였습니다.
kubernetes version : v1.17.12
cert-manager version: v1.1.0
🏖 Cert-manager deploy
cert-manager는 오픈소스이며 여기에서 공식적인 릴리즈 버전을 확인할 수 있습니다.
위의 이미지와 같이 Release 버전을 클릭하고 밑으로 스크롤을 내리다 보면 현재 release 버전에 해당하는 cert-manager.yaml 파일을 찾을 수 있습니다. 우리는 아래의 yaml 파일을 이용해서 kubernetes 클러스터에 cert-manager를 배포할 것입니다.
먼저 cert-manager를 배포할 cert-manager라는 이름의 namespace를 생성하도록 하겠습니다.
# Create namespace
kubectl create namespace cert-manager
그 후 아래의 명령어로 kuberetes에 바로 cert-manager를 배포할 수 있습니다. 아래의 명령어에선 현재 기준으로 최신 release 버전인 v1.1.0 버전을 사용하였지만, 사용하기에 따라 다른 버전을 사용하시면 됩니다.
kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v1.1.0/cert-manager.yaml
저는 cert-manager를 선언적으로 또 다시 사용할 것을 염두해서 local에 cert-manager.yaml 파일을 받고, apply 명령어를 통해서 cert-manaer를 배포하겠습니다.
# cert-manager.yaml 파일 다운로드
curl -LO https://github.com/jetstack/cert-manager/releases/download/v1.1.0/cert-manager.yaml
# 버전 관리를 위해 cert-manager.yaml -> cert-manager.1.1.0.yaml로 이름 변경
mv cert-manager.yaml cert-manager.1.1.0.yaml
# cert-manager install
kubectl apply -f cert-manager.1.1.0.yaml
install이 완료됐다면 `kubectl get all` 명령어를 통해서 클러스터에 cert-manager가 정상적으로 설치되었는지 확인해봅니다.
🏖 Cert-manager를 이용해 selfsigned 인증서 생성하기
Cert-manager는 기본적으로 let's enscypt가 아닌 Cluster 내부에서 사용할 수 있는 자체적으로 서명된 self-signed issuer를 생성할 수 있습니다. 여기서 Issuer란 흔히 CA라고 칭하는 서명할 수 있는 주체를 지칭하는 단어로 Certificate(인증서)를 생성할 수 있는 대상입니다.
self-signed 인증서를 사용하게 될 경우 브라우저에서 아래의 이미지와 같이 좋지 않은 사용자 경험을 줄 수 있지만, cluster 내부에서 사용하거나 테스트 용도로 사용하는 것이라면 self-signed 인증서를 사용하는 것도 좋은 방법이 될 수 있습니다.
우리가 구성하게 될 대략적인 시스템 구성도는 아래의 이미지와 같습니다. 제일 먼저 cert-manager를 이용해서 클러스터 전역에서 사용할 수 있는 Cluster Issuer를 생성하고, 해당 Issuer를 이용해서 각각의 Namespace 별로 Certificate를 생성하도록 하겠습니다.
1. Issuer 생성
자, 이제 본격적으로 Cluster에 인증서를 만들어보도록 하겠습니다. 가장 먼저 할 일은 Self-signed Issuer를 만드는 일입니다. 조금 전에 이야기했듯이 Issuer는 Certificate를 만들 수 있는 주체인데, Issuer에는 그냥 Issuer와 ClusterIssuer가 있습니다.
여기서 Issuer는 Namespace의 리소스로 속해있는 Namespace 안에서만 사용할 수 있는 리소스입니다. 반면 ClusterIssuer는 Namespace를 가리지 않고 Cluster 전역에서 접근할 수 있는 리소스입니다.
저는 ClusterIssuer로 생성하도록 하겠습니다.
# selfsigned-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned-issuer
spec:
selfSigned: {}
2. Certificate 생성
이번에는 생성한 ClusterIssuer를 사용해서 self-signed Certificate를 생성하고 이를 이용해서 MySQL에 적용해보도록 하겠습니다. cert-manager가 Issuer를 통해 Certificate를 생성하게 되면 해당 Certificate는 속해있는 Namespace 내의 모든 서비스가 사용할 수 있는 인증서가 됩니다.
해당 인증서가 생성됨과 동시에 Certificate는 Kubernetes내에서 사용할 수 있도록 Public key, Secret key와 같은 데이터를 가진 secret 리소스가 생성하게 되는데, 이때 생성되는 secret 리소스를 이용해서 kubernetes 내에서 pod에 주입하는 등 다양하게 사용할 수 있습니다.
secret에 관한 내용은 아래의 여기를 참조합니다.
# selfsigned-cert.yaml
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: selfsigned-cert
namespace: default
spec:
secretName: selfsigned-cert-tls
duration: 2880h # 120d
renewBefore: 360h # 15d
commonName: ooeunz.tistory.com
isCA: false
keySize: 2048
keyAlgorithm: rsa
keyEncoding: pkcs1
usages:
- digital signature
- key encipherment
- server auth
issuerRef:
name: selfsigned-issuer
kind: ClusterIssuer
group: cert-manager.io
Certificate의 spec을 간략하게 살펴보겠습니다.
- secretName: Certificate와 동시에 함께 생성되는 secret의 이름을 지정합니다.
- duration: 인증서의 유효기간을 지정합니다.
- renewBefore: 자동으로 인증서를 갱신할 때를 지정합니다. 여기서 duration과 renewBefore는 go time을 사용하기 때문에 "ms", "s", "m", "h"만을 사용할 수 있습니다. 더 자세한 내용은 여기에서 확인 하실 수 있습니다.
- commonName: host name을 지정하는 필드입니다. dnsName 옵션과 함게 사용할 수 있으며 만약 commonName이 설정되지 않았을 경우엔 dnsName의 가장 첫번째 값을 commonName으로 사용하게 됩니다. 하지만 해당 예제에선 dnsName은 지정하지 않겠습니다.
- isCa: 이 인증서를 CA서명이 유효하도록 하는 옵션입니다. 해당 옵션을 true로 해줄 경우 `cert sign`값이 usages 리스트에 자동으로 추가됩니다. 자세한 내용은 공식 도큐먼트에서 확인하실 수 있습니다.
- keySize: 암호화할 key의 길이를 지정합니다. 길이가 길어질수록 암호화의 수준이 높아지며 지정하지 않을 시에 default로 2048 값을 가집니다. 지정할 수 있는 옵션으론 2048 이외에 4096, 8192가 있습니다. 보안강도에 대한 더 자세한 내용은 여기서 확인할 수 있습니다.
- keyAlgorithm: 사용할 암호화 알고리즘을 지정합니다.
- keyEncoding: 어떤 keyEncoding을 사용할 것인지에 관한 부분입니다. PKCS#1과 PKCS#8만을 사용할 수 있고 default로 PKCS#1을 지원합니다.
위의 yaml 파일을 apply 하면 말씀드린 것처럼 certificate와 secret 리소스가 함께 생성됩니다. secret에는 아래와 같은 data를 가지고 있습니다.
- ca.crt: public certificate file
- tls.crt: Public Key
- tls.key: Private Key
이와 같이 생성된 Secret을 MySQL에 주입시켜주고, 동시에 아래와 같이 my.cnf 파일에 secret안에 있는 키들을 넣어주도록 합니다.
# mysql-pvc.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv-volume
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pv-claim
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
# mysql-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
namespace: default
data:
my.cnf: |-
[mysqld]
ssl-ca=/etc/mysql/tls/ca.crt
ssl-cert=/etc/mysql/tls/tls.crt
ssl-key=/etc/mysql/tls/tls.key
require_secure_transport=ON ## This line is the only setting required to enforce secure connections
# mysql-server.yaml
apiVersion: v1
kind: Service
metadata:
name: mysql-http
spec:
ports:
- port: 3306
selector:
app: mysql
clusterIP: None
# mysql-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
serviceName: mysql
template:
metadata:
labels:
app: mysql
spec:
terminationGracePeriodSeconds: 10
containers:
- name: mysql
image: mysql:8.0.21
env:
- name: MYSQL_ROOT_PASSWORD
value: password
imagePullPolicy: Always
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
- name: mysql-cnf
mountPath: /etc/mysql/conf.d/my.cnf
subPath: my.cnf
- name: mysql-tls
mountPath: /etc/mysql/tls
readOnly: true
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pv-claim
- name: mysql-cnf
configMap:
name: mysql-config
- name: mysql-tls
secret:
secretName: selfsigned-cert-tls
이제 위와 같이 secret을 주입하고, mysql에 들어가서 아래의 명령어로 SSL이 정상적으로 적용됐는지 확인해 볼 수 있습니다.
SHOW VARIABLES LIKE '%ssl%';
have_openssl과 have_ssl 옵션이 YES로 되어있고, ssl_ca, ssl_cert, ssl_key에 방금 mount해준 key가 정상적으로 적용된 것을 확인할 수 있습니다.
🤔 Jdbc로 연결할 때 주의할 점
이제 Spring Boot에서 MySQL에 연결을 해볼 텐데요. jdbc로 연결을 할 경우에 주의할 점이 몇 가지가 있습니다. 현재 require_secure_transport=ON 옵션을 주었기 때문에 MySQL에 접속하기 위해선 필수적으로 SSL 접속을 하여야 합니다. 그런데 우리는 self-sigend 인증서를 사용하고 있기 때문에 tomcat에서 인증서를 vailation 하는 과정에서 커넥션을 끊어버리게 됩니다. 따라서 인증서에 대하여 validation 하지 않는 옵션을 주도록 하겠습니다. (또는 자바에도 인증서를 포함하여 양방향 인증하는 방법도 있지만, 여기선 단순히 validation을 하지 않도록 하겠습니다.)
여기서 MySQL 버전에 따라 validation 옵션을 주는 방법이 조금 차이가 납니다. 자세한 내용은 공식 도큐먼트를 참고합니다.
MySQL 8.0.12
MySQL 8.0.12 이하 버전에선 useSSL=true, verifyServerCertificate=true 일 경우 서버 인증서의 유효성 검증이 활성화 되게 됩니다. (다만 hostname은 검증하지 않습니다.) 따라서 8.0.12 버전 이하에선 verifyServerCertificate=false를 주어 유효성 검증을 하지 않도록 합니다.
# MySQL 8.0.12 버전 이하
jdbc:mysql://localhost:3306/cruise?useSSL=true&verifyServerCertificate=false
MySQL 8.0.13 이후
반면 MySQL 8.0.13 이후 버전에서는 SSLMode가 VERTIFY_CA이거나 VERIFY_IDENTITY일 때만 서버 인증서 유효성을 확인하게 됩니다. 또한 useSSL옵션 역시 true값이 default이므로 따로 옵션을 주지 않으면 useSSL=true& verifyServerCertificate=false의 기능을 하게 됩니다.
# MySQL 8.0.13 버전 이후
jdbc:mysql://localhost:3306/cruise?
포스팅에 사용한 코드는 아래의 URL에서 확인할 수 있습니다. 😄
'DevOps > Cert manager' 카테고리의 다른 글
[Cert manager] SpringBoot(tomcat) HTTPS 적용하기 (0) | 2021.02.06 |
---|