Published on

Tạo chứng chỉ Let's Encrypt SSL cho Gateway API trên Kubernetes

Giới thiệu

Ở bài viết: Sử dụng Gateway API trên Kubernetes, mình đã giới thiệu các cấu hình Gateway thông qua HTTPRoute để truy cập vào ứng dụng được triển khai trên Kubernetes. Tuy nhiên ứng được truy cập thông qua giao thức HTTP có độ bảo mật kém, ở bài viết này mình sẽ hướng dẫn các bước để chúng ta có thể tạo chứng chi SSL cấp bởi Let's Encrypt cho Gateway. Từ đó cho phép truy cập vào ứng dụng thông qua giao thức HTTPS, và điều hướng mọi truy cập HTTP sang thành HTTPS.

Cấu hình Cert-manager.io

cert-manager là một controller mạnh mẽ và có khả năng mở rộng cho quản lý chứng chỉ X.509 trên các khối công việc Kubernetes và OpenShift. Nó sẽ thu thập chứng chỉ từ nhiều Issuer khác nhau, bao gồm cả Issuer công cộng phổ biến và Issuer riêng tư, đồng thời đảm bảo rằng các chứng chỉ này hợp lệ và cập nhật. Nó cũng sẽ tự động thử gia hạn chứng chỉ trước khi chúng hết hạn123.

Một số điểm nổi bật của cert-manager:

  • Tự động cấp phát và gia hạn chứng chỉ để bảo vệ Ingress với TLS.
  • Hỗ trợ Issuer từ các Cơ quan chứng chỉ công nhận cũng như PKI riêng tư.
  • Bảo mật giao tiếp pod-to-pod với mTLS sử dụng PKI riêng tư.
  • Hỗ trợ các trường hợp sử dụng chứng chỉ cho các khối công việc web-facing và internal.
  • Miễn phí và mã nguồn mở.

Đầu tiên chúng ta phải cài cert-manager.io trên Kubernetes cluster:

  • Tải về manifest
    curl -LJO https://github.com/cert-manager/cert-manager/releases/download/v1.14.4/cert-manager.yaml
    
  • Mặc định thì cert-manager chưa tích hợp sẵn với Gateway, chúng ta phải sửa file manifest vừa download về bằng cách thêm vào cert-manager Deployment: - --feature-gates=ExperimentalGatewayAPISupport=true
    cert-manager.yaml
      ...  
          containers:
            - name: cert-manager-controller
              image: "quay.io/jetstack/cert-manager-controller:v1.14.4"
              imagePullPolicy: IfNotPresent
              args:
              - --v=2
              - --cluster-resource-namespace=$(POD_NAMESPACE)
              - --leader-election-namespace=kube-system
              - --acme-http01-solver-image=quay.io/jetstack/cert-manager-acmesolver:v1.14.4
              - --max-concurrent-challenges=60
              - --feature-gates=ExperimentalGatewayAPISupport=true
              ports:
              - containerPort: 9402
                name: http-metrics
                protocol: TCP
        ...
    
  • Bây giờ chúng cài đặt nó trên cluster:
    kubectl apply -f cert-manager.yaml
    

Thiết lập Cluster issuer

Dưới đây mình sẽ giới thiệu cách tạo Let's Encrypt certificate cho một Gateway với thông tin. (Gateway này đã được triển khai theo bài viết trong phần giới thiệu)

  • Tên gatewway: istio-gateway
  • Namespace của gateway: istio-ingress

Để biết cách tạo Gateway, vui lòng xem trong bài viết được đề cập ở phần giới thiệu

certificate-issuers.yaml
# Dev ClusterIssue
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-dev
spec:
  selfSigned: {}
---
# Staging ClusterIssue
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    # The ACME server URL
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: you@email.com
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-staging
    # Enable the HTTP-01 challenge provider
    solvers:
      - http01:
          gatewayHTTPRoute:
            parentRefs:
              - name: istio-gateway
                namespace: istio-ingress
                kind: Gateway
# Production ClusterIssue
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    # The ACME server URL
    server: https://acme-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: you@email.com
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-prod
    # Enable the HTTP-01 challenge provider
    solvers:
      - http01:
          gatewayHTTPRoute:
            parentRefs:
              - name: istio-gateway
                namespace: istio-ingress
                kind: Gateway

Bây giờ chúng ta chỉ cần triển khai lên Kubernetes:

kubectl apply -f certificate-issuers.yaml

Thông thường chúng ta sẽ dùng cluster issuer letsencrypt-prod, tuy nhiên để tránh tình trạng rate limit của Let's Encrypt, chúng ta nên dùng letsencrypt-dev hoặc letsencrypt-staging để test, khi mọi thứ ok chúng ta chuyển qua dùng letsencrypt-prod.

Cấu hình Gateway

Bước tiếp theo chúng ta sẽ cập nhật manifest của Gateway istio-gateway

istio-gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: istio-gateway
  namespace: istio-ingress
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  gatewayClassName: istio
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      hostname: "httpbin.yourdomain.com"
      allowedRoutes:
        namespaces:
          from: All
    - name: httpbin
      hostname: httpbin.yourdomain.com
      port: 443
      protocol: HTTPS
      tls:
        mode: Terminate
        certificateRefs:
          - name: ssl-httpbin.yourdomain.com # Tên của certificate sẽ được lưu
      allowedRoutes:
        namespaces:
          from: All

Những thay đổi trên Gateway mình đã highlight, chúng ta giờ sẽ apply lại manifest

kubectl apply -f istio-gateway.yaml

Giờ là bước cuối cùng, chúng ta cập nhật lại HTTPRoute để điều hướng request sang giao thức HTTPS

Cập nhật HTTPRoute

httproute.yaml
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
  name: httpbin
  namespace: test-istio
spec:
  parentRefs:
  - group: gateway.networking.k8s.io	
    kind: Gateway	
    name: istio-gateway
    namespace: istio-ingress
    sectionName: httpbin
  hostnames: ["httpbin.yourdomain.com"]
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /headers
    backendRefs:
    - name: httpbin
      port: 8000
      group: ''	
      kind: Service
      weight: 1
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: tls-redirect
  namespace: test-istio
spec:
  parentRefs:
  - group: gateway.networking.k8s.io	
    kind: Gateway	
    name: istio-gateway
    namespace: istio-ingress
    sectionName: http
  hostnames:
  - httpbin.yourdomain.com
  rules:
  - filters:
    - type: RequestRedirect
      requestRedirect:
        scheme: https
        port: 443
        statusCode: 302
    matches:	
        - path:	
            type: PathPrefix	
            value: /headers

Tóm tắt một số thay đổi mình đã highlight ở trên

  • Line 13: trở đến listener httpbin port 443 trên Gateway
  • Thêm HTTPRoute dùng để redirect từ http sang https

Chúng ta sẽ apply lại httproute mainifest:

kubectl apply -f httproute.yaml

Chúng ta kiểm tra xem mọi thứ đã hoạt động OK hay chưa. Thử request thông qua HTTP

curl -v http://httpbin.yourdomain.com/headers
*   Trying 11.12.69.53:80...
* Connected to httpbin.yourdomain.com (11.12.69.53) port 80 (#0)
> GET /headers HTTP/1.1
> Host: httpbin.yourdomain.com
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 Found
< location: https://httpbin.yourdomain.com/headers
< date: Sun, 17 Mar 2024 06:08:31 GMT
< server: istio-envoy
< content-length: 0
<
* Connection #0 to host httpbin.nvtienanh.info left intact

Kiết quả lè nhận được code 302 (Redirect sang https://httpbin.yourdomain.com/headers)

Bây giờ chúng ta thử request thông qua HTTPS:

curl https://httpbin.yourdomain.com/headers
{
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin.yourdomain.com",
    "User-Agent": "curl/7.81.0",
    "X-B3-Sampled": "0",
    "X-B3-Spanid": "7b517be14b",
    "X-B3-Traceid": "c2b167b8ef72b9a40b517be14b",
    "X-Envoy-Attempt-Count": "1",
    "X-Envoy-Decorator-Operation": "httpbin.test-istio.svc.cluster.local:8000/*",
    "X-Envoy-Internal": "true",
    "X-Envoy-Peer-Metadata": "ChQKDkFQUF9DT05UQUlORVJTEgIaAA---aW8tZ2F0ZXdheS1pc3Rpbw==",
    "X-Envoy-Peer-Metadata-Id": "router~10.244.133.198~istio-gateway-istio-6cb5879dd6-wwtvw.istio-ingress~istio-ingress.svc.cluster.local"
  }
}

Như vậy mọi thứ đã hoạt động đúng như mong đợi.

Chúc thành công,

ANH NGUYỄN