AWS announces preview of AWS Interconnect - multicloud
AWS announces AWS Interconnect – multicloud (preview), providing simple, resilient, high-speed private connections to other cloud service providers. AWS Interconnect - multicloud is easy to configure and provides high-speed, resilient connectivity with dedicated bandwidth, enabling customers to interconnect AWS networking services such as AWS Transit Gateway, AWS Cloud WAN, and Amazon VPC to other cloud service providers with ease.
Amazon EKS 클러스터를 위한 cert-manager를 통한 인증서 발급 및 TLS 지원
본 기사에서는 AWS EKS 클러스터 내 cert-manager를 설치하여 Ingress에서 cert-manager로부터 인증서를 가져오고 Secret으로 저장하여, 클러스터에 배포한 웹에 연결한 Route53에 등록한 도메인에 HTTPS를 통해 접근할 수 있는 방법을 설명합니다.
Kubernetes 클러스터에는 인증서 관리를 위한 모듈인 cert-manager[1]가 존재합니다. cert-manager는 사용하는 워크로드에 대한 TLS 인증서를 생성하고 만료되기 전 갱신을 수행하게 됩니다. cert-manager의 Certificate 리소스를 사용하게 되면, Private Key와 인증서가 Kubernetes Secret 오브젝트에 저장됩니다.
인증 과정에서 가장 중요한 오브젝트는 Issuer입니다. Issuer[2]란 인증서에 서명을 할 수 있는 인증기관(CA : Certificate Authority)를 나타내는 리소스입니다. Issuer 및 ClusterIssuer로 구분되는데 이는 리소스의 적용 범위에 대한 차이고, 특정 Namespace로 국한하기 위해서는 Issuer로, 클러스터 전체에 대해 적용하기 위해서는 ClusterIssuer로 선택하여 사용할 수 있습니다.
cert-manager를 통한 인증서 및 Secret 발급 과정에 대해 간단히 설명해보도록 하겠습니다. 본 기사에서는 Issuer를 통한 Issued 프로세스를 확인하기 위해 Issuer의 배포는 과정 중간에 진행됩니다.
Prerequisite
-
cert-manager 설치
$ helm repo add jetstack https://charts.jetstack.io $ helm repo update $ helm install cert-manager jetstack/cert-manager —namespace cert-manager —create-namespace —set installCRDs=true -
ingress-nginx 설치
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/cloud/deploy.yaml -
Target Service, Deployment/Pod 배포
$ cat application.yaml --- apiVersion: v1 kind: Service metadata: name: nginx-service namespace: sandbox spec: selector: app: nginx-deployment ports: - protocol: TCP port: 80 targetPort: 80 --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: sandbox spec: replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: public.ecr.aws/nginx/nginx:latest ports: - name: tcp containerPort: 80 -
Public 도메인 준비 인증서 발급을 위하여 public 도메인을 준비합니다.
-
Route53 도메인 연결 및 CLB 연결 ingress-nginx를 설치하면 기본적으로 CLB(Classic Load Balancer)가 생성됩니다. 준비한 public 도메인의 CNAME에 CLB의 DNS name을 연결합니다. 본 실습에서는 Route53에 준비한 도메인을 등록하여 Nameserver를 변경한 상태에서 CNAME을 추가하여 CLB를 연결합니다.
Step 1. Ingress 배포
Ingress를 배포할 때 tls 설정 및 인증 기관인 Issuer를 함께 정의합니다.
$ cat ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
namespace: sandbox
annotations:
cert-manager.io/issuer: nginx-issuer
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
rules:
- host: <test-host.com>
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80
tls:
- hosts:
- <test-host.com>
secretName: nginx-app-tls-secret
이 때, Issuer가 없는 상태임에도 cert-manager.io/issuer로 nginx-issuer를 정의하였고, tls 통신을 위한 정보로 host, secret을 정의하였기에 내부적으로 다음과 같은 작업이 수행됩니다.
Certificate[3] 생성
아직 Issuer(nginx-issuer)가 없는 상태이기 때문에 Issuing 상태 및 Ready:False 상태를 가진 Certificate가 생성됩니다.
$ kubectl get certificate -n sandbox
NAME READY SECRET AGE
nginx-app-tls-secret False nginx-app-tls-secret 3h11m
$ kubectl get certificate nginx-app-tls-secret -n sandbox -o yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
generation: 1
name: name: nginx-app-tls-secret
...
spec:
dnsNames:
- <test-host.com>
issuerRef:
group: cert-manager.io
kind: Issuer
name: nginx-issuer
secretName: nginx-app-tls-secret
usages:
- digital signature
- key encipherment
status:
conditions:
- lastTransitionTime: "2024-03-20T05:23:37Z"
message: Issuing certificate as Secret does not exist
observedGeneration: 1
reason: DoesNotExist
status: "False"
type: Ready
- lastTransitionTime: "2024-03-20T05:23:37Z"
message: Issuing certificate as Secret does not exist
observedGeneration: 1
reason: DoesNotExist
status: "True"
type: Issuing
nextPrivateKeySecretName: nginx-app-tls-secret-2hh6m
Secret[7] 생성
기존에 존재하는 Secret이 없기 때문에 임시로 임시 Private Key를 생성합니다. 그 결과 아래와 같이 Secret이 생성됩니다.
$ kubectl get secret -n sandbox
NAME TYPE DATA AGE
nginx-app-tls-secret-2hh6m Opaque 1 3h12m
$ kubectl get secret nginx-app-tls-secret-2hh6m -n sandbox -o yaml
apiVersion: v1
items:
- apiVersion: v1
data:
tls.key: <Base64 Encoded Private Key>
kind: Secret
metadata:
generateName: acme-nginx-app-tls-secret-
labels:
cert-manager.io/next-private-key: "true"
controller.cert-manager.io/fao: "true"
name: nginx-app-tls-secret-2hh6m
namespace: sandbox ownerReferences:
- apiVersion: cert-manager.io/v1
blockOwnerDeletion: true
controller: true
kind: Certificate
name: nginx-app-tls-secret
uid: 9945bcf5-01bb-4352-8ae4-70817bcaf4e3
resourceVersion: "238823"
uid: adf8544b-991c-4c7e-8320-c317c768cae0
type: Opaque
kind: List
metadata:
resourceVersion: ""
CertificateRequest[4] 생성
또한 Certificate가 인증받기 위한 요청을 처리하기 위해 CertificateRequest 오브젝트를 자동으로 생성합니다. 이 때 Secret의 Private Key를 통한 PEM encoded 정보가 Request에 포함됩니다.
$ kubectl get certificaterequest -n sandbox
NAMESPACE NAME APPROVED DENIED READY ISSUER REQUESTOR AGE
sandbox nginx-app-tls-secret-1 True False nginx-issuer system:serviceaccount:cert-manager:cert-manager 3h13m
$ kubectl get certificaterequest nginx-app-tls-secret-1 -n sandbox -o yaml
apiVersion: cert-manager.io/v1
kind: CertificateRequest
metadata:
annotations:
cert-manager.io/certificate-name: nginx-app-tls-secret
cert-manager.io/certificate-revision: "1"
cert-manager.io/private-key-secret-name: nginx-app-tls-secret-2hh6m
...
spec:
...
issuerRef:
group: cert-manager.io
kind: Issuer
name: acme-nginx-app-issuer
request: <Encoded Request>
status:
conditions:
- lastTransitionTime: "2024-03-20T05:23:37Z"
message: Certificate request has been approved by cert-manager.io
reason: cert-manager.io
status: "True"
type: Approved
- lastTransitionTime: "2024-03-20T05:23:37Z"
message: 'Referenced "Issuer" not found: issuer.cert-manager.io "nginx-issuer"
not found'
reason: Pending
status: "False"
type: Ready
이 역시 아직 Issuer가 존재하지 않기 때문에, Pending 상태로 유지됩니다.
Step 2. Issuer 생성
이제 기존에 생성된 리소스들의 인증 과정을 진행하기 위해 Issuer를 생성합니다.
$ cat issuer.yaml
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: nginx-issuer
namespace: sandbox
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: <email address>
privateKeySecretRef:
name: nginx-app-private-key
solvers:
- http01:
ingress:
ingressClassName: nginx
인증을 위해 CA 서버로 Let’s Encrypt(https://letsencrypt.org/)를 사용합니다. Let’s Encrypt는 staging/production이 구분되어 있으며, 일정 시간 내 여러번 인증서를 요청하는 경우 일정 시간 내에는 인증서 발급이 제한되므로 테스트시에는 staging 경로를 사용할 수 있도록 합니다.
- staging : https://acme-staging-v02.api.letsencrypt.org/directory
- production : https://acme-v02.api.letsencrypt.org/directory
Issuer가 생성되고 나면 다음과 같은 과정이 진행됩니다.
Order[6] 생성
Order는 CertificateRequest를 처리하기 위해 Issuer가 생성하게 되며 이어 Domain마다 각각 Challenge[5]를 생성합니다.
$ kubectl get order -A --watch
nginx-app-tls-secret-1-310547795 0s
nginx-app-tls-secret-1-310547795 pending 0s
nginx-app-tls-secret-1-310547795 ready 22s
nginx-app-tls-secret-1-310547795 valid 22s
Challenge 생성
$ kubectl get challenge -A --watch
nginx-app-tls-secret-1-310547795-2104847025 <test-host.com> 0s
nginx-app-tls-secret-1-310547795-2104847025 pending <test-host.com> 2s
nginx-app-tls-secret-1-310547795-2104847025 valid <test-host.com> 21s
nginx-app-tls-secret-1-310547795-2104847025 valid <test-host.com> 22s
현재 샘플상으로는 Domain이 1개이기 때문에 하나의 Challenge가 생성되었고, 해당 Challenge는 Let’s Encrypt를 통해 해당 도메인의 소유자가 맞는지 확인하고, 확인이 완료되면 Order 내 인증서 정보를 업데이트하고 Order 역시 상태가 변경됩니다. 이 때, 확인을 위해 생성된 Challenge 오브젝트는 완료 후 자동으로 삭제됩니다.
CertificateRequest 변경
이제 Order 및 Challenge를 통해 도메인이 확인되었기 때문에 인증서를 발급받기 위한 CertificateRequest가 어떻게 변경되었는지 확인해보겠습니다.
$ kubectl get certificaterequest -n sandbox
NAMESPACE NAME APPROVED DENIED READY ISSUER REQUESTOR AGE
sandbox nginx-app-tls-secret-1 True True nginx-issuer system:serviceaccount:cert-manager:cert-manager 3h28m
Approve는 되었으나 Ready가 false인 상태에서, true로 변경된 것을 확인하실 수 있습니다. 상세 정보를 확인해보면 다음과 같습니다.
$ kubectl get certificaterequest nginx-app-tls-secret-1 -n sandbox -o yaml
apiVersion: cert-manager.io/v1
kind: CertificateRequest
metadata:
annotations:
cert-manager.io/certificate-name: nginx-app-tls-secret
cert-manager.io/certificate-revision: "1"
cert-manager.io/private-key-secret-name: nginx-app-tls-secret-2hh6m
...
spec:
...
issuerRef:
group: cert-manager.io
kind: Issuer
name: acme-nginx-app-issuer
request: <Encoded Request>
status:
certificate: <Encoded Certificate> # 신규 생성
conditions:
- lastTransitionTime: "2024-03-20T05:23:37Z"
message: Certificate request has been approved by cert-manager.io
reason: cert-manager.io
status: "True"
type: Approved
- lastTransitionTime: "2024-03-20T08:52:30Z" # 상태, 시간 등 변경
message: Certificate fetched from issuer successfully
reason: Issued
status: "True"
type: Ready
Certificate 변경
다른 정보는 모두 동일하나 Status 내 Ready 타입의 값이 Issued로 상태로 변경되었고, 그에 따라 certificate 정보가 추가되었습니다. 이제 인증서가 발급되었습니다.이제 Certificate가 어떻게 변경되었는지 확인해보겠습니다.
$ kubectl get certificate -n sandbox
NAME READY SECRET AGE
nginx-app-tls-secret True nginx-app-tls-secret 3h28m
$ kubectl get certificate nginx-app-tls-secret -n sandbox -o yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
generation: 1
name: name: nginx-app-tls-secret
...
spec:
dnsNames:
- <test-host.com>
issuerRef:
group: cert-manager.io
kind: Issuer
name: nginx-issuer
secretName: nginx-app-tls-secret
usages:
- digital signature
- key encipherment
status: # 인증서 정보 변경
conditions:
- lastTransitionTime: "2024-03-20T08:52:30Z"
message: Certificate is up to date and has not expired
observedGeneration: 1
reason: Ready
status: "True"
type: Ready
notAfter: "2024-06-18T07:52:29Z"
notBefore: "2024-03-20T07:52:30Z"
renewalTime: "2024-05-19T07:52:29Z"
revision: 1 # CertificateRequest revision과 일치
Certificate 역시 false 상태였던 Ready 값이 true로 바뀌고 issued 된 것을 확인하실 수 있습니다. 또한 CertificateRequest의 revision 정보가 포함되어 어떠한 Request를 통해 발급된 Certificate 인지 정보를 확인할 수 있습니다.
Secret 변경
Certificate가 Ready:True로 변경되는 과정에서 Secret 역시 변경되었습니다.
$ kubectl get secret -A --watch
nginx-app-private-key Opaque 1 0s
nginx-app-tls-secret kubernetes.io/tls 2 0s
nginx-app-tls-secret-2hh6m Opaque 1 3h28m
$ kubectl get secret -n sandbox
NAMESPACE NAME TYPE DATA AGE
sandbox nginx-app-private-key Opaque 1 8m8s
sandbox nginx-app-tls-secret kubernetes.io/tls 2 7m45s
기존의 nginx-app-els-secret-2hh6m은 임시로 생성되었던 Secret이기 때문에 삭제되었고, 실제 인증서 정보가 포함된 Secret인 nginx-app-tls-secret이 새로 생성된 것을 확인하실 수 있습니다.
$ kubectl get secret nginx-app-tls-secret -n sandbox -o yaml
apiVersion: v1
data:
tls.crt: <Encoded Certificate>
tls.key: <Encoded RSA Private Key>
kind: Secret
metadata:
annotations:
cert-manager.io/alt-names: <test-domain.com>
cert-manager.io/certificate-name: nginx-app-tls-secret
cert-manager.io/common-name: <test-domain.com>
cert-manager.io/ip-sans: ""
cert-manager.io/issuer-group: cert-manager.io
cert-manager.io/issuer-kind: Issuer
cert-manager.io/issuer-name: nginx-issuer
cert-manager.io/uri-sans: ""
creationTimestamp: "2024-03-20T08:52:30Z"
labels:
controller.cert-manager.io/fao: "true"
name: nginx-app-tls-secret
namespace: acme-ns
resourceVersion: "273468"
uid: 48e2ae2d-824a-46c1-a025-24899820b2f6
type: kubernetes.io/tls
이렇게 실제 Secret이 신규 생성되고 나서 Certificate는 Ready:True로 상태가 변경되게 됩니다.
이제 <test-domain.com>에 접속하면 신뢰할 수 있는 사이트라는 표식과 함께 HTTPS를 통해 접속이 가능합니다.
Issuer의 종류에는 본 기사에서 다룬 ACME(Automatic Certificate Management Environment)는 물론 Cluster 내에서 사용하기 위해 Self-Signed가 있으며 다수의 in-tree issuer가 존재하합니다. ACM Private CA를 이용하는 경우를 위해 aws-privateca-issuer Helm chart[9]를 제공하고 있으며 이에 대해 자세한 단계가 설명된 블로그[10]를 참고하실 수 있습니다.
References:
[1] https://cert-manager.io/docs/
[2] https://cert-manager.io/docs/configuration/
[3] https://cert-manager.io/docs/usage/certificate/
[4] https://cert-manager.io/docs/usage/certificaterequest/
[5] https://cert-manager.io/docs/reference/api-docs/#acme.cert-manager.io/v1.Challenge
[6] https://cert-manager.io/docs/reference/api-docs/#acme.cert-manager.io/v1.Order
[7] https://kubernetes.io/docs/concepts/configuration/secret/
[8] https://cert-manager.io/docs/configuration/issuers/
- 語言
- 한국어
