0%

在Kubernetes使用Cert-Manager签发免费证书

概述

在Kubernetes中使用Cert-Manager签发免费证书,可以让我们更方便地管理和使用证书。Cert-Manager是一个开源项目,用于管理和自动化证书。如果对安全级别和证书功能要求不高,可以利用Cert-Manager基于ACME协议与Let’s Encrypt进行证书签发,并自动续订证书。

前提条件

  • Kubernetes集群
  • 安装kubectl/helm

使用Helm安装Cert-Manager

1
2
3
4
5
6
7
8
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install \
cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version v1.13.2 \
# --set installCRDs=true

ACME

ACME有两种协议,分别是HTTP01和DNS01。

HTTP01

HTTP01是一种基于HTTP的证书签发协议,可以让用户免费使用Let’s Encrypt提供的免费证书。

DNS01

ACME是一种基于DNS的证书签发协议,可以让用户免费使用Let’s Encrypt提供的免费证书。

如果使用DNS01,需要在DNS服务器上配置一条TXT记录,并将其值设置为cert-manager-webhook-dns-solver。如果DNS供应商提供了API,可以通过API实现自动化配置。

由于我的DNS不提供API,所以我使用了HTTP01。

ACME Issuer

Issuer是Cert-Manager中最重要的资源,用于管理证书签发流程。有两种Issuer:

  • ClusterIssuer:用于管理集群范围内的证书签发流程。
  • Issuer:用于管理命名空间范围内的证书签发流程。

创建ClusterIssuer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
# You must replace this email address with your own.
# Let's Encrypt will use this to contact you about expiring
# certificates, and issues related to your account.
email: user@example.com
server: https://acme-staging-v02.api.letsencrypt.org/directory
privateKeySecretRef:
# Secret resource that will be used to store the account's private key.
name: example-issuer-account-key
# Add a single challenge solver, HTTP01 using nginx
solvers:
- http01:
ingress:
ingressClassName: nginx

注意:

  • email:用于接收Let’s Encrypt的通知邮件,如果不指定,会收到一封默认的邮件。
  • server:Let’s Encrypt的ACME服务器地址,这里的地址是Let’s Encrypt的测试服务器地址,https://acme-staging-v02.api.letsencrypt.org/directory为Staging环境,https://acme-v02.api.letsencrypt.org/directory为Production环境。Staging环境产生的证书是不会被浏览器信任的,生产环境需要使用正式的ACME服务器地址。
  • privateKeySecretRef:用于存储私钥的Secret资源名称。
  • solvers:用于指定ACME协议的验证方式,可以选择HTTP01或者DNS01。如果使用HTTP01,需要在Ingress中指定ingressClassName,否则会报错。
  • ingressClassName:Ingress的ingressClassName,用于指定Ingress的类型。

为kubernetes的Ingress创建证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
# add an annotation indicating the issuer to use.
cert-manager.io/cluster-issuer: nameOfClusterIssuer
name: myIngress
namespace: myIngress
spec:
rules:
- host: example.com
http:
paths:
- pathType: Prefix
path: /
backend:
service:
name: myservice
port:
number: 80
tls: # < placing a host in the TLS config will determine what ends up in the cert's subjectAltNames
- hosts:
- example.com
secretName: myingress-cert # < cert-manager will store the created certificate in this secret.

注意:

  • cert-manager.io/cluster-issuer:用于指定证书签发流程使用的Issuer名称。
  • hosts:用于指定证书的域名。
  • secretName:用于指定证书存储的Secret名称。

正常情况下,这时候,证书就会签发成功了,可以通过https://example.com访问到服务。只是我遇到了问题。

问题

在创建Ingress后,发现证书颁发失败,检查状态和日志:

1
2
3
4
5
6
7
8
9
10
11
12
kubectl get Issuers,ClusterIssuers,Certificates,CertificateRequests,Orders,Challenges -n partner 
NAME READY AGE
clusterissuer.cert-manager.io/letsencrypt-staging True 42h

NAME READY SECRET AGE
certificate.cert-manager.io/myingress-cert False myingress-cert 42h

NAME APPROVED DENIED READY ISSUER REQUESTOR AGE
certificaterequest.cert-manager.io/myingress-cert-1 True False letsencrypt-staging system:serviceaccount:cert-manager:cert-manager 11h

NAME STATE AGE
order.acme.cert-manager.io/myingress-cert-1-1904933461 invalid 11h

在看看Challenges细节:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
kubectl describe challenges.acme.cert-manager.io/myingress-cert-1-1904933461 -n partner
Name: myingress-cert-1-1904933461-2937188937
Namespace: partner
Labels: <none>
Annotations: <none>
API Version: acme.cert-manager.io/v1
Kind: Challenge
Metadata:
Creation Timestamp: 2023-12-05T13:39:58Z
Finalizers:
finalizer.acme.cert-manager.io
Generation: 1
Owner References:
API Version: acme.cert-manager.io/v1
Block Owner Deletion: true
Controller: true
Kind: Order
Name: myingress-cert-1-1904933461
UID: 1691de2d-1c32-4f10-bee7-81b1d4c9f7bc
Resource Version: 4440169
UID: 4092e71c-4028-47f9-8c0f-f0d5e95a4e81
Spec:
Authorization URL: https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/9874881604
Dns Name: example.com
Issuer Ref:
Group: cert-manager.io
Kind: ClusterIssuer
Name: letsencrypt-staging
Key: xxxxxxxxx.xxxxxxxxx
Solver:
http01:
Ingress:
Ingress Class Name: nginx
Token: xxxxxxxxx
Type: HTTP-01
URL: https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/9874881604/fKmvZw
Wildcard: false
Status:
Presented: false
Processing: false
Reason: Error accepting authorization: acme: authorization error for example.com: 400 urn:ietf:params:acme:error:connection: xxx.xx.xx.xxx: Fetching http://example.com/.well-known/acme-challenge/xxxxxxxxx: Timeout during connect (likely firewall problem)
State: invalid
Events: <none>

其中xxx.xx.xx.xxx是我的公网IP

检查Cert-Manager的日志,发现实际访问http://example.com/.well-known/acme-challenge/xxxxxxxxx时,开始有访问404,并不是400。怀疑是网络原因。

我的网络是在内网有一个DNS服务器,将域名直接解析为内网IP,而通过公网访问时使用公共DNS访问公网IP。在出口路由器上做了端口映射,将公网端口映射到内网端口,这样就可以通过公网访问内网服务了。并且有一个限制,就是从内网不允许通过公网访问映射端口在回到内网。

怀疑是Cert-Manager在校验token时,通过公网访问了example.com,导致超时。先解除从内网走公网再进入内网的限制,测试成功。

分析

Cert-Manager在做域名校验时,会从容器环境访问域名,而这个域名解析并没有使用内网DNS,使用的公网DNS。实际更好的方法应该是使用内网DNS,这样就不会有访问公网的限制,但目前我还没有找到这个配置方式,留到后面再研究。

坚持原创技术分享,您的支持将鼓励我继续创作!