利用 NGINX Ingress Controller 和 ACM PCA 在使用 Amazon Fargate 的 Amazon EKS 中实现 mTLS

在 Kubernetes 工作负载之间实现端到端安全通信
发布时间:2024 年 1 月 19 日
EKS 集群设置
EKS
Kubernetes
教程
亚马逊云科技
Olawale Olaleye
亚马逊云科技使用经验
200 - 中级
完成所需时间
30 分钟
前提条件

注册 / 登录 亚马逊云科技账户

上次更新时间
2024 年 1 月 19 日

云的灵活性在产生大量应用程序流量的同时,有助于各组织尽快扩展。然而,即使这些企业构建了许多解决方案,如微服务架构、企业对企业应用程序编程接口 (B2B API)、用户身份验证、内容分发、物联网 (IoT) 传感器等,仍必须防止“中间人攻击”或其他形式的网络攻击。通过向网络连接两端的各方证明彼此都能诚实行事,双向传输层安全 (mTLS) 可帮助这些企业提升其应用程序的安全性,或者遵守行业标准或特定的合规性要求。在微服务架构中,服务网格主要用于提供所需基础设施,以安全实现服务间的 mTLS。配置服务网格需要用到包括 Sidecar 代理和控制面板组件在内的更多组件,增加了复杂性,而且需要管理配置和策略。

在本教程中,您将学习如何在不通过服务网格的前提下使用 NGINX Ingress Controller 和 Amazon Certificate Manager (ACM) 私有证书颁发机构 (PCA),为运行在使用 Amazon Fargate 的 Amazon Elastic Kubernetes Service (Amazon EKS) 中的应用程序设置 mTLS。双向传输层安全 (mTLS) 可额外添加层级,保障 Kubernetes 工作负载流量的安全。此外也可用于企业对企业应用程序或标准,如开放银行 (Open Banking)

本教程会解决两项难题:

  1. 在使用 Amazon Fargate 的 Amazon EKS 上设置 NGINX Ingress Controller。
  2. 使用 ACM 私有 CA 颁发的证书,通过 NGINX Ingress Controller 配置 mTLS。

需要注意以下限制:

  • 如果您想在 Type=LoadBalancer 服务的后端使用 NGINX Ingress Controller 实现 mTLS 支持,则需要使用网络负载均衡器创建传输控制协议 (TCP) 侦听器并在目标上实现 mTLS。
  • Fargate 不支持特权容器。

前提条件

概述

本教程属于使用 Amazon EKS 管理对安全性要求较高的工作负载这一系列的一部分,致力于在使用 Amazon Fargate 的 Amazon EKS 中利用 mTLS 实现 Kubernetes 工作负载之间的安全通信。在开始按照本教程操作之前,您需要有使用 Fargate 的 现有 Amazon EKS 集群,且已针对运行财务工作负载这一目标进行配置。本教程将涉及以下组件:

  • Ingress-Nginx Controller:这是 Kubernetes 的入口控制器,使用 NGINX 作为反向代理和负载均衡器。它能利用入口规则的附加注解开启客户端证书验证,以实现 mTLS。
  • Amazon Certificate Manager 私有 CA:有助于创建私有 CA 层次结构,包括根 CA 和从属 CA,而无需运营本地 CA 的投资和维护成本。
  • Amazon Private CA Issuer这是 cert-manager 的附加组件,可使用 Amazon 私有 CA 签发证书申请。
  • Amazon Load Balancer Controller:Kubernetes 控制器,有助于为 Kubernetes 集群管理弹性负载均衡器
  • Cert-manager:Kubernetes 的附加组件,用于自动管理和颁发各种颁发来源的 TLS 证书。
  • ExternalDNS:有助于使用 Amazon Route 53 自动管理应用程序的域名系统 (DNS) 路由。
  • 示例应用程序部署:为已启用 mTLS 的服务部署示例工作负载。此工作负载包括在 Kubernetes 环境中为双向 TLS (mTLS) 配置的示例应用程序的部署,这里需要特别关注 Amazon EKS。

使用私有 CA 会产生费用。若使用通用模式,则每个私有 CA 400 美元一个月。若使用短期证书模式,则每个私有 CA 50 美元一个月。对于非整月使用的情况,此费用会根据创建和删除 CA 的时间按比例收取。

步骤 1:配置集群

在此部分,您只会使用 Fargate 配置文件配置 Amazon EKS 集群。若需详细的操作步骤,请参阅构建为财务工作负载预配置的 Amazon EKS 集群中的教程。

步骤 2:安装和配置集群组件

使用 Helm 选项在 Amazon Fargate 上安装 ALB Controller

检索为集群自动创建的虚拟私有云 (VPC),并将以下值替换为实际值:

export AWS_DEFAULT_REGION="us-east-2"
export vpcid=$(aws eks describe-cluster --name fg-security-quickstart --query 'cluster.resourcesVpcConfig.vpcId' --output text)
export mycluster=fg-security-quickstart
export region="us-east-2"

使用以下命令安装 Amazon Load Balancer (ALB) Controller:

helm repo add eks https://aws.github.io/eks-charts

helm repo update eks

helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
 -n kube-system \
 --set clusterName=$mycluster \
 --set serviceAccount.create=false \
 --set serviceAccount.name=aws-load-balancer-controller \
 --set region=$region \
 --set vpcId=$vpcid

下载 NGINX Ingress Controller 清单并为使用 Amazon Fargate 的 Amazon EKS 进行自定义

Fargate 不支持特权容器。默认 NGINX Ingress Controller Pod 需要升级权限才能运行。我们将下载原始的 NGINX Ingress Controller 清单,然后按照以下步骤进行自定义:

1. 从 https://kubernetes.github.io/ingress-nginx/deploy/#aws 下载 deploy.yaml 模板。

curl -O https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/aws/deploy.yaml

2. 编辑下载好的“deploy.yaml”文件,进行以下更改:

找到以下这行文本,将负载均衡器类型改为 nlb-ip,如下所示:

service.beta.kubernetes.io/aws-load-balancer-type: nlb # to nlb-ip

更改 Ingress-Nginx Controller 服务的默认端口,如下所示:

kind: Service
 name: ingress-nginx-controller
 namespace: ingress-nginx
...
spec:
 ports:
 - appProtocol: http
 targetPort: http # to 8080
 - appProtocol: https
 targetPort: https # to 8081

(可选)如果想公开应用程序,那么可以将以下额外注解添加到服务部分:

service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"

将 http-port=8080 和 https-port=8081 作为额外参数添加到部署清单中,如下所示:

kind: Deployment
...
 spec:
 containers:
 - args:
 ...
 - --http-port=8080 
 - --https-port=8081 

更改部署清单中的控制器容器端口,如下所示:

name: controller
 ports:
 - containerPort: 80 # to 8080
 name: http
 protocol: TCP
 - containerPort:443 # to 8081
 name: https
 protocol: TCP

将权限升级设为 false:

securityContext:
 allowPrivilegeEscalation: true # to false

3. 部署修改后的清单。

kubectl apply -f deploy.yaml

4. 等待 60 秒,让 Fargate 安排到入口控制器 Pod。使用此命令验证安装情况:

kubectl get pods -n ingress-nginx

输出结果应如下所示:

NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create-xfv5j 0/1 Completed 0 52s
ingress-nginx-admission-patch-5szks 0/1 Completed 2 52s
ingress-nginx-controller-84c9764964-mwxfc 1/1 Running 0 52s

使用 Helm 安装 cert-manager

kubelet 默认侦听 10250 端口,与 cert-manager webhook 的默认端口冲突。在以下命令中,webhook 的侦听端口已设为 10260,防止在使用 Fargate 的 EKS 中出现此问题。

helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install \
 cert-manager jetstack/cert-manager \
 --namespace cert-manager \
 --version v1.12.2 \
 --set installCRDs=true \
 --set serviceAccount.create=false \
 --set serviceAccount.name=cert-manager \
 --set webhook.securePort=10260

在集群中安装和设置 ExternalDNS(可选)

您可以在 Amazon Route53 中为应用程序手动创建 DNS 记录。参阅这些 文档,在 Route 53 中设置和管理指向已部署控制器的 ALB 的记录。

1. 下载示例 external-dns 清单。

wget https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/main/docs/examples/external-dns.yaml

2. 打开下载好的 external-dns.yaml 文件,编辑 --domain-filter 标志,以纳入托管区域。以下示例适用于托管区域 example.com:

args:
- --source=service
- --source=ingress
- --domain-filter=example.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
- --provider=aws
- --policy=upsert-only 
- --aws-zone-type=public 
- --registry=txt
- --txt-owner-id=my-identifier # Your Route53 Zone ID

3. 部署下载好的 external-dns.yaml 文件。

kubectl apply -f external-dns.yaml 

4. 验证该文件是否已成功部署。

kubectl logs -f $(kubectl get po | egrep -o 'external-dns[A-Za-z0-9-]+')

步骤 3:创建私有 CA

遵照以下步骤,创建 Amazon Certificate Manager 私有 CA,在与集群相同的区域中将 RSA 2048 选为主要算法。

1. 若要创建私有 CA,请在终端执行以下内容,将 example.com 替换为实际值:

export AWS_DEFAULT_REGION="us-east-2"
export SERVICES_DOMAIN="example.com"

export ROOT_CA_ARN=`aws acm-pca create-certificate-authority \
 --certificate-authority-type ROOT \
 --certificate-authority-configuration \
 "KeyAlgorithm=RSA_2048,
 SigningAlgorithm=SHA256WITHRSA,
 Subject={
 Country=US,
 State=WA,
 Locality=Seattle,
 Organization=Build on EKS,
 OrganizationalUnit=mTLS Example,
 CommonName=${SERVICES_DOMAIN}}" \
 --query CertificateAuthorityArn --output text` 

2. 创建和安装私有 CA 证书。

ROOT_CA_CSR=`aws acm-pca get-certificate-authority-csr \
 --certificate-authority-arn ${ROOT_CA_ARN} \
 --query Csr --output text`

3. 使用上一步的证书签名请求 (CSR) 文件颁发根证书。请注意,如果您使用的是 Amazon CLI 第 2 版,那么您需要在调用 issue-certificate 命令前通过编码传递 CSR 数据。

AWS_CLI_VERSION=$(aws --version 2>&1 | cut -d/ -f2 | cut -d. -f1)
[[ ${AWS_CLI_VERSION} -gt 1 ]] && ROOT_CA_CSR="$(echo ${ROOT_CA_CSR} | base64)"

ROOT_CA_CERT_ARN=`aws acm-pca issue-certificate \
 --certificate-authority-arn ${ROOT_CA_ARN} \
 --template-arn arn:aws:acm-pca:::template/RootCACertificate/V1 \
 --signing-algorithm SHA256WITHRSA \
 --validity Value=10,Type=YEARS \
 --csr "${ROOT_CA_CSR}" \
 --query CertificateArn --output text`

4. 导入签名证书作为根 CA。

ROOT_CA_CERT=`aws acm-pca get-certificate \
 --certificate-arn ${ROOT_CA_CERT_ARN} \
 --certificate-authority-arn ${ROOT_CA_ARN} \
 --query Certificate --output text`

5. 导入根 CA 证书,将其安装在 CA 中。Amazon CLI 第 2 版需要通过编码传递证书数据。执行以下命令:

[[ ${AWS_CLI_VERSION} -gt 1 ]] && ROOT_CA_CERT="$(echo ${ROOT_CA_CERT} | base64)"

aws acm-pca import-certificate-authority-certificate \
 --certificate-authority-arn $ROOT_CA_ARN \
 --certificate "${ROOT_CA_CERT}"

6. 检查 CA 的状态,确认 CA 处于启用状态。如果 CA 处于启用状态,则可以使用。

aws acm-pca describe-certificate-authority \
 --certificate-authority-arn "$ROOT_CA_ARN" \
 --output json

7. 获取 CA 的 ARN:

echo $ROOT_CA_ARN

步骤 4:安装 Amazon-PCA-ISSUER

Amazon Private CA Issuer 插件充当 cert-manager 的插件,使用 ACM 私有 CA 签发证书申请。定义环境变量:

export ROOT_CA_ARN="YOUR_PRIVATE_CA_ARN"
export SERVICES_DOMAIN="example.com"

记得将 YOUR_PRIVATE_CA_ARN 和 example.com 替换为实际值。Amazon PCA Issuer 插件充当 cert-manager 的插件,使用 Amazon Certificate Manager 私有 CA 签发证书申请。

1. 复制以下命令并将其粘贴到您的终端上,创建 pca-iam-policy.json 文件:

cat<<EOF > pca-iam-policy.json
{
 "Version": "2012-10-17",
 "Statement": [
 {
 "Sid": "awspcaissuer",
 "Action": [
 "acm-pca:DescribeCertificateAuthority",
 "acm-pca:GetCertificate",
 "acm-pca:IssueCertificate"
 ],
 "Effect": "Allow",
 "Resource": "${ROOT_CA_ARN}"
 }
 ]
 }
EOF

2. 使用以下命令创建名为 AWSPCAIssuerIAMPolicy 的 Amazon Identity and Access Management (IAM) 策略:

aws iam create-policy --policy-name AWSPCAIssuerIAMPolicy --policy-document file://pca-iam-policy.json

请注意命令输出结果中的 IAM 策略 ARN,在下一步会用到。

3. 使用以下命令为 Amazon PCA Issuer 插件创建服务账户:

export mycluster=fg-security-quickstart
export ARN="IAM_POLICY_ARN"

eksctl create iamserviceaccount \
--cluster=$mycluster \
--namespace=default \
--name=aws-pca-issuer \
--attach-policy-arn=$ARN \
--override-existing-serviceaccounts \
--approve \
--region us-east-2

将 IAM_POLICY_ARN 替换为之前检索的 ARN 值。

4. 添加 Amazon PCA Issuer Helm 存储库并运行 Helm 安装命令:

helm repo add awspca https://cert-manager.github.io/aws-privateca-issuer

helm install aws-pca-issuer awspca/aws-privateca-issuer -n default \
--set serviceAccount.create=false \
--set serviceAccount.name=aws-pca-issuer

5. 在约 60 秒后运行以下命令,验证 Amazon PCA Issuer 是否正确配置:

% kubectl get pods
NAME READY STATUS RESTARTS AGE
aws-pca-issuer-aws-privateca-issuer-6cf57c44bf-t9qgb 1/1 Running 0 53s

6. 复制以下命令并将其粘贴到您的终端上,创建集群级别证书颁发机构 (ClusterIssuer) 和证书文件:

cat << EOF > cluster-issuer.yaml
apiVersion: awspca.cert-manager.io/v1beta1
kind: AWSPCAClusterIssuer
metadata:
 name: demo-test-root-ca
spec:
 arn: ${ROOT_CA_ARN}
 region: us-east-2
EOF

7. 复制以下命令并将其粘贴到您的终端上,创建 mtls-cert.yaml 文件。该文件会创建包含 CA 证书的密钥,以及可用于 TLS 和客户端身份验证的服务器证书。此外,该文件还会创建客户端应用程序将使用的其他密钥,或者您可以只创建一张证书,使用相同的密钥。

cat << EOF > mtls-cert.yaml
kind: Certificate
apiVersion: cert-manager.io/v1
metadata:
 name: mtls-cert-acm
spec:
 commonName: mtls.${SERVICES_DOMAIN}
 dnsNames:
 - www.mtls.${SERVICES_DOMAIN}
 - mtls.${SERVICES_DOMAIN}
 duration: 2160h0m0s
 issuerRef:
 group: awspca.cert-manager.io
 kind: AWSPCAClusterIssuer
 name: demo-test-root-ca
 renewBefore: 360h0m0s
 secretName: mtls-cert
 usages:
 - server auth
 - client auth
 privateKey:
 algorithm: "RSA"
 size: 2048
---

kind: Certificate
apiVersion: cert-manager.io/v1
metadata:
 name: mtls-cert-acm-client
spec:
 commonName: mtls.${SERVICES_DOMAIN}
 dnsNames:
 - www.mtls.${SERVICES_DOMAIN}
 - mtls.${SERVICES_DOMAIN}
 duration: 2160h0m0s
 issuerRef:
 group: awspca.cert-manager.io
 kind: AWSPCAClusterIssuer
 name: demo-test-root-ca
 renewBefore: 360h0m0s
 secretName: mtls-cert-client
 usages:
 - server auth
 - client auth
 privateKey:
 algorithm: "RSA"
 size: 2048 
EOF 

8. 针对后端应用程序,在 Amazon EKS 集群中创建证书颁发机构并生成 TLS 证书。

kubectl apply -f cluster-issuer.yaml 

kubectl create namespace mtls

kubectl apply -f mtls-cert.yaml -n mtls

步骤 5:部署示例应用程序

1. 复制以下命令并将其粘贴到您的终端上,创建一个示例工作负载:

cat << EOF > mtls.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
 name: mtls-app
 labels:
 app: mtls
spec:
 replicas: 1
 selector:
 matchLabels:
 app: mtls
 template:
 metadata:
 labels:
 app: mtls
 spec:
 containers:
 - name: mtls-app
 image: hashicorp/http-echo
 args:
 - "-text=mTLS in Amazon EKS Fargate with NGINX Ingress Controller"

---
kind: Service
apiVersion: v1
metadata:
 name: mtls-service
spec:
 selector:
 app: mtls
 ports:
 - port: 5678 # Default port for image
EOF 

2. 运行以下命令创建工作负载:

kubectl create namespace mtls

kubectl create -f mtls.yaml -n mtls

3. 复制以下命令并将其粘贴到您的终端上,为此工作负载创建 ingress.yaml 入口清单文件:

cat << EOF > ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
 # annotations:
 # # Enable client certificate authentication
 # nginx.ingress.kubernetes.io/auth-tls-verify-client: "on"
 # # The secret containing the trusted ca certificates. Namespace\secretname
 # nginx.ingress.kubernetes.io/auth-tls-secret: mtls/mtls-cert
 name: mtls-ingress
spec:
 ingressClassName: nginx
 rules:
 - host: "mtls.${SERVICES_DOMAIN}"
 http:
 paths:
 - path: /
 pathType: Prefix
 backend:
 service:
 name: mtls-service
 port:
 number: 5678
 tls:
 - hosts:
 - "mtls.${SERVICES_DOMAIN}"
 secretName: mtls-cert
EOF

部署清单:

kubectl create -f ingress.yaml -n mtls

约 2 分钟后,运行以下命令验证是否已创建入口。

kubectl get ingress -n mtls

输出结果应如下所示:

NAME CLASS HOSTS ADDRESS PORTS AGE
mtls-ingress nginx mtls.example.com k8s-ingressn-ingressn-f661079efc-a1c19cd4348edbec.elb.us-east-2.amazonaws.com 80, 443 96s

4. 复制以下命令并将其粘贴到您的终端上,创建测试客户端 Pod,其中必须包含与应用程序交互所需的客户端证书。

cat << EOF > mtls-cert-client.yaml
apiVersion: v1
kind: Pod
metadata:
 name: mtls-test-pod
spec:
 containers:
 - name: mtls-container
 image: nginx
 args:
 - /bin/sh
 - -c
 - >
 while true;
 do
 curl -v -sk "https://mtls.${SERVICES_DOMAIN}" --cert /etc/secret-volume/tls.crt --key /etc/secret-volume/tls.key;
 sleep 60
 done
 volumeMounts:
 - name: secret-volume
 mountPath: /etc/secret-volume
 readOnly: true
 volumes:
 - name: secret-volume
 secret:
 secretName: mtls-cert-client
EOF
kubectl apply -f mtls-cert-client.yaml -n mtls

5. 验证是否能从测试 Pod 访问应用程序。在 Pod 中执行以下命令:

kubectl exec -it mtls-test-pod -n mtls -- sh

6. 运行 curl 命令测试应用程序的连通性。

curl -sk -v https://mtls.example.com

若请求成功,就会看到 HTTP 响应状态码 200

7. 在之前创建的入口清单 ingress.yaml 中启用 mTLS。在 ingress.yaml 中取消以下注释,如下所示:

metadata:
 annotations:
 # Enable client certificate authentication
 nginx.ingress.kubernetes.io/auth-tls-verify-client: "on"
 # The secret containing the trusted ca certificates. Namespace\secretname
 nginx.ingress.kubernetes.io/auth-tls-secret: mtls/mtls-cert
 name: mtls-ingress

执行以下命令,应用更改:

kubectl apply -f ingress.yaml -n mtls

8. 使用双向证书密钥文件验证测试 Pod 是否能与应用程序连接。在 Pod 中执行以下命令:

kubectl exec -it mtls-test-pod -n mtls -- sh

9. 运行 curl 命令测试应用程序的连通性。

curl -v -sk "https://mtls.example.com" --cert /etc/secret-volume/tls.crt --key /etc/secret-volume/tls.key 

仅在此 curl 命令中指定证书和密钥时,才会看到 HTTP 响应状态码 200

清理资源

为避免持续产生费用,建议您删除在学习本教程的过程中创建的资源。可以使用以下命令删除这些资源:

kubectl delete namespace mtls

helm delete aws-pca-issuer 

helm delete cert-manager --namespace cert-manager

eksctl delete cluster -f cluster.yaml

# delete the certificates
aws acm delete-certificate --certificate-arn $CERTIFICATE_ARN
aws acm-pca update-certificate-authority --certificate-authority-arn $ROOT_CA_ARN --status DISABLED
aws acm-pca delete-certificate-authority --certificate-authority-arn $ROOT_CA_ARN

总结

在本教程中,您已通过安装自定义 NGINX Ingress Controller,为在使用 Amazon Fargate 的 Amazon EKS 中运行的应用程序成功设置了 mTLS,进而部署了由 Amazon Load Balancer Controller 预配置的网络负载均衡器