在docker上搭配Trafik使用SSL服务器证书的完整指南

Traefik Labs在圣诞前后发布了2.4.0-rc1 这个版本。这个版本终于添加了我期待已久的一个ACME的增强功能 External Account Binding. 在做了一些测试之后,我终于可以提笔开始写这篇我早就想写的文章,寄望于本文可以给读者提供一个全方位的在Docker上搭配Traefik使用SSL的指南。尽管本文仅仅提到了在Docker这一个使用场景的说明,但是你应该可以把本文提及的内容适用在其他平台上面。

TL; DR

使用Traefik来部署SSL证书真的非常的方便。你需要做的就是在静态配置里面定义certResolver,然后告诉Traefik你想要哪些服务使用SSL证书以及你想要什么样的证书。Traefik就会自动完成你交给它的任务。

以下是 certResover 的模板 :

1
2
3
4
5
6
7
8
9
10
# Static configuration
certificatesResolvers:
<NAME_YOUR_PROVIDER>:
acme:
caServer: <CA_ACME_DIRECTORY>
email: <YOUR_VALID_EMAIL>
storage: acme.json
<EXTERNAL_ACCOUNT_BINDING>
keyType: <PREFERRED_KEY_ALGORITHM>
<VALIDATION_METHOD>
  • NAME_YOUR_PROVIDER

    你需要给你的certResolver命名方便你自己记忆。比如说名字是LE-http代表这个provider会通过http-01的验证方式从Let’s Encrypt申请证书。你可以创建多个 certificatesResolvers 然后根据需要在router里面设置不同的certResolver。

  • YOUR_VALID_EMAIL :

    你需要提供一个真实的Email地址来接收证书更新通知。如果你用假邮箱申请证书,有一些证书签发机构会拒绝你的申请请求。

  • CA_ACME_DIRECTORY

    有一些商业证书签发机构没有一个固定的ACME链接。客户需要登录到账户里面生成这个链接才行。具体的操作请参考各大商业CA的手册。

  • PREFERRED_KEY_ALGORITHM

    虽然CAB Forum允许使用521 bit的ECC私钥生成的证书,大部分证书签发机构只支持签发256和384位的ECC证书。

  • VALIDATION_METHOD

  • EXTERNAL_ACCOUNT_BINDING (EAB)

    商业证书签发机构(例如Sectigo,Digicert等)要求用户在账户里生成ACME的链接以及EAB凭证。虽然Zerossl提供免费的证书,但是使用它的ACME依然需要创建账户以及生成EAB凭证因为它证书的根是Sectigo的。

SSL Basics

再过去的几年中整个PKI行业都进行了大规模的改变。比如说证书的最大有效期从3年降到2年然后在2020年降到了1年。特别是在Let’s Encrypt开始提供免费的SSL证书之后,开启HTTPS已经成为所有网站或者服务的基础功能。同时各大浏览器也因此早在两年前就开始把http链接标记为不安全

在今天的文章中我不会讲特别寄出的SSL内容,比如说SSL是什么以及SSL原理之类的。我会跟大家聊聊我对于使用SSL证书的几个设置以及如何在Traefik上实现这些设置。

Key Algorithm

CAB Forum的baseline requirement目前的规定是TLS证书必须至少使用2048位的RSA私钥结合SHA-256,SHA-284或者SHA-512的签名算法或者P-256,P-384或者P-521的ECC私钥。大多数的SSL证书目前都是基于2048位的RSA私钥生成,那么什么事ECC私钥呢?

from Wikipedia:

椭圆曲线密码学(英語:Elliptic Curve Cryptography,缩写:ECC)是一種基于椭圆曲线数学的公开密钥加密演算法。椭圆曲线在密码学中的使用是在1985年由Neal Koblitz和Victor Miller分别独立提出的。ECC的主要优势是它相比RSA加密演算法使用較小的密鑰長度并提供相当等级的安全性[1]。ECC的另一个优势是可以定义群之间的双线性映射,基于Weil对或是Tate对;双线性映射已经在密码学中发现了大量的应用,例如基于身份的加密。

ECC密匙长度 RSA 密匙长度
256-bit 3072-bit
384-bit 7680-bit
521-bit 15360-bit

根据这篇文章的测试,对比起RSA证书,ECC证书在体积减小的情况下,安全性和效率都得到了提升。除非是需要支持特别老旧的系统,我认为大家应该尽可能的使用ECC证书。

Certificate coverage

除了EV证书之外,用户可以选择单域名,多域名或者通配符证书来保护他们的网站。单域名和多域名这个很好理解,通配符的意思就是一张证书可以用在某一级别的所有子域名上。例如*.example.test这张证书可以用来保护<EVERYTHING>.example.test。需要注意的是这张通配符并不保护example.test。如果你需要你的证书涵盖这个域名,你需要把它添加到证书的SAN里。

Why do we use different CAs?

我认为在选择SSL证书的时候,有以下5个方面用户需要考虑。

Reputation

这个选项看起来有点傻,毕竟谁会从一个没有信誉的证书签发机构获取证书呢?每个用户在申请证书的时候期待的都是在证书的生命周期不要发生任何幺蛾子直到证书过期位置。可惜的是天总不遂人愿,因此通过一个更加有信誉的机构获取证书可以有效的帮助用户避免各种证书相关的服务导致的服务暂停。Let’s Encrypt在2020年3月的时候因为查询CAA的bug不得不吊销300万张证书就是一个很好的例证。想了解更多跟关于Let’s Encrypt的这个事件,你可以在找到官方声明。

The root matters

证书之所以会被信任主要就是因为他们的根证书被预安装在了软件,设备,浏览器以及操作系统里面,因此我们可以放心的说根证书是证书的根本,也因此在选择SSL证书之前我们必须对它的根证书的情况有一点的了解。特别是该SSL证书的根证书是否在你的客户群环境中被信任这一点非常的重要。就让我再次使用Let’s Encrypt作为一个例子,他们目前的信任链被 ISRG Root X1(新root) 和 DST Root CA X3 交叉签发。如果你的用户的环境的设备比较老旧,你可以选择使用老的根证书来签发证书。

Clarity of workflow

用户真的需要清楚地知道具体需要哪些步骤才可以让他们在需要的时候获取到证书。有的时候甚至只是一个DV的证书也要花不少时间才能签发,原因就在于流程不够清晰。

Validation Method

虽然CAB Forum规定了应该如何来完成验证,不同的证书颁发机构对于这些规定都有着自己的解读。举个例子,对于域名的DNS验证,有些机构选择只能使用DNS CNAME来完成而另一些可以使用DNS TXT或者DNS CNAME来完成,因此选择提供最简单验证流程的证书颁发机构非常的重要。

OCSP response speed

这是常常被忽略但是又非常重要的一个数据。每一次你的客户连接你的网站或者提供的服务在后台都会进行某种程度的证书状态查询。比如说Chrome用的CRLset,Firefox和其他的一些浏览器用的OCSP。(有些用户觉得Chrome比其他浏览器快也有一部分这个原因)。其他的大部分软件和设备也通通都会有各式各样的证书状态查询,其中应用最广泛的就是OCSP查询。因此OCSP查询的快慢非常非常的重要,特别是在我国,据说有的时候某些OCSP服务器会被墙造成网络延迟剧增。如果你想知道更多有关于证书状态查询对于连接速度的影响,请参考这篇文章

接下来我们进入今天的主题。

SSL certificate provisioning

你可能已经看得出来我对于SSL证书的安全看的非常的重。在我看来,所有的面向公共的网站或者程序都必须默认使用https来连接。自从我发现Traefik之后,它很快就成为我使用docker部署任何服务的必备工具。我可以把所有的服务都放在后端,用Traefik来处理所有相关服务的SSL证书的生成和部署而不必逐个服务来设置。

Traditional Method

传统的证书部署大致可以分为五个步骤。

  1. 在服务器端生成私钥和CSR。
  2. 提交证书申请以及提供CSR给证书颁发机构。
  3. 完成验证。
  4. 证书颁发机构签发证书。
  5. 安装证书在步骤一的服务器。

如果你习惯这些步骤来申请和使用证书,你可以参考我这篇文章讲述如何在Traefik使用现有的SSL证书。

Automated Method with Traefik

如果你跟我一样,你可能也希望你的工具能尽可能的帮你完成所有的事情。通过ACME协议来部署证书就可以取代上述5个步骤。你可能听说过的例如certbotcert-manger这样的软件已经被广泛地使用。Traefik也是通过ACME协议来部署SSL证书。

我认为所有的证书颁发机构,特别是商业性的证书颁发机构现在都必须支持用户通过ACME来部署证书。如果你的证书提供商不提供ACME,你真的需要好好考虑一下是否应该换一家来购买你的证书。

Free SSL providers

下面是3个我所知的可以通过ACME协议免费获取SSL证书的证书提供方。

CA Name Rate Limit FQDN Limit preferredChain Wildcard Required EAB
Let’s Encrypt 50/week 100 Names/cert Yes Yes No
Buypass 20/week 5 Name/cert No No No
ZeroSSL No No No Yes Yes

下面列出了他们的根证书。

ZeroSSL的中级证书是Sectigo的根证书签出来的,因此通过ZeroSSL签发的证书的根都有USERTrust这个字眼。

A fun fact: 很多自称自有品牌的SSL提供商实际上只是一个附属的证书提供商,他们没有自己的根证书因此对于自己证书实际上没有多少控制权。在写此文的时候我很惊奇的发现Sectigo签了大量的中级证书,其中包括某度。😑

如果你对于Sectigo到底签了哪些中级证书感兴趣,你可以查看他们的这个网页

Name Valid From Valid To Signature Algorithm Serial
DST Root CA X3 2000-09-30 2021-09-30 SHA1-RSA 44afb080d6a327ba893039862ef8406b
ISRG Root X1 2015-06-04 2035-06-04 SHA256-RSA 8210cfb0d240e3594463e0bb63828b00
Buypass Class 2 Root CA 2010-10-26 2040-10-26 SHA256-RSA 2
USERTrust ECC Certification Authority 2010-02-01 2038-01-19 ECDSA-SHA384 5c8b99c55a94c5d27156decd8980cc26
USERTrust RSA Certification Authority 2010-02-01 2038-01-19 SHA384-RSA 01fd6d30fca3ca51a81bbc640e35032d

Providers’ ACME directory URLs

免费证书提供方的ACME directory链接。

  • Let’sEncrypt

    1
    https://acme-v02.api.letsencrypt.org/directory
  • Buypass

    1
    https://api.buypass.com/acme/directory 
  • ZeroSSL

    1
    https://acme.zerossl.com/v2/DV90

Validation Methods

你需要在certResolver里定义你想使用的验证方法。ACME支持3种验证方式,http-01dns-01 和tls-01`。本文只涉及前两种验证方式。

DNS-01
1
2
3
4
5
dnsChallenge:
provider: <DNS_PROVIDER_NAME>
resolvers:
- "1.1.1.1:53"
- "1.0.0.1:53"

**DNS_PROVIDER_NAME **: 你需要填入你的DNS提供商的名字,例如Cloudflare。你可以通过官方文档来查看你的DNS提供商是否提供相关支持。

你也可以使用自定义的DNS服务器来解析你的域名。只需要添加resolvers这个参数然后填入对应的dns服务器的IP地址和端口即可。上面的例子使用的是Cloudflare来解析DNS记录。根据dnsperf这篇文章,Cloudflare的解析速度全球最快。(我国的国情不同,请各位自行斟酌)

HTTP-01

你需要告诉Traefik你的80端口的entryPoint叫什么名字即可。在官方文档里面, 80端口的entry point叫web.

1
2
3
4
5
6
7
8
entryPoints:
web:
address: ":80"

...
httpChallenge:
entryPoint: web
...

Preferred chain

如果你在阅读过我上述段落之后觉得需要使用Let’s Encrypt的老root来签你的证书,你可添加preferredChain这个参数到你的ACME resolver。官方文档.

1
preferredChain: 'DST Root CA X3'

External Account Binding

使用EAB实际上非常的简单。你的证书提供商会提供KID和hmac给你,你只需要如下填入相关的内容即可。官方文档.

1
2
3
eab:
kid: abc-keyID-xyz
hmacEncoded: abc-hmac-xyz

Usage Examples

在阅读过以上内容之后,我希望你对于自己希望使用什么样的证书或者通过那个CA来申请证书已经有了自己的想法。下面就让我们来几个实例并简单分析一下。

To define providers

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
44
45
46
47
48
49
50
51
52
53
54
55
56
entryPoints:
web:
address: ":80"

certificatesResolvers:
letsencrypt:
acme:
email: [email protected]
storage: acme.json
keyType: EC384
httpChallenge:
entryPoint: web

letsencrypt-legacy:
acme:
email: [email protected]
storage: acme.json
keyType: EC384
preferredChain: 'DST Root CA X3'
httpChallenge:
entryPoint: web

buypass:
acme:
email: [email protected]
storage: acme.json
caServer: https://api.buypass.com/acme/directory
keyType: EC256
httpChallenge:
entryPoint: web

le-dns:
acme:
email: [email protected]
storage: acme.json
keyType: EC384
dnsChallenge:
provider: acme-dns
resolvers:
- "1.1.1.1:53"
- "1.0.0.1:53"

zerossl:
acme:
email: [email protected]
storage: acme.json
caServer: https://acme.zerossl.com/v2/DV90
keyType: EC384
eab:
kid: <KEY_ID>
hmacEncoded: <HMAC_VALUE>
dnsChallenge:
provider: cloudflare
resolvers:
- "1.1.1.1:53"
- "1.0.0.1:53"

如你所见,前两个certificatesResolvers我使用的是http-01的验证方式并且entry point是web。如果你是通过Let’s Encrypt来申请证书,你可以不用填入 caServer这个参数。

也许你也注意到我所有的邮箱用的都是同一个。这是因为Traefik的一个bug(至少我觉得是个bug),该问题在 这个issue被简略的提及过。老实说我完全不明白为什么Traefik要求所有的certificatesResolvers使用同一个邮箱……🥱

下面的两个provider使用的是dns-01这个验证方式。其中前者用的是acme-dns作为dns提供方,另一个则是cloudflare。如果你的DNS提供商不在Traefik的支持列表里面,你可以通过acme-dns来使用dns-01的验证方式。详情请参考前文.

To use Providers

官方给的例子是在router上使用你想用的certificatesResolvers

Defining a certificates resolver does not result in all routers automatically using it. Each router that is supposed to use the resolver must reference it.

你也可以在静态配置文件中设置一个默认certificatesResolvers,这样你就不需要在每一个router下面都填入你想使用的provider。我的静态配置文件就做了这样的设置,如下所示,我使用的是 letsencypt作为默认的resolver。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
entryPoints:
...
websecure:
address: :443
http:
middlewares:
- secureHeaders@file
tls:
certResolver: letsencrypt

certificatesResolvers:
letsencrypt:
acme:
email: admin@yourdomain
storage: acme.json
keyType: EC384
httpChallenge:
entryPoint: web
...

Extra Settings

  • 实现entryPoint的http转接https

    这个是Traefik在2.2版本中加入的新功能。当时我特别的激动,甚至写了此文来介绍这个设置。更多详情,请参考 官方文档.

  • HSTS secure headers以及Mozilla SSL中级设置

    如果你之前看过我的文章/影片,你可能注意到在我的动态配置文件里面有以下两个设置。

    • 首先我使用Traefik的headers这个middleware并命名为 secureHeaders。使用这个middleware的主要目的就是为了HSTS 。请阅读 Wikipedia或者Chromium HSTS preload list website去了解这几个设置,特别是stsPreload的使用情况并决定你是否要开启这个设置。

      Preloading Should Be Opt-In

      If you maintain a project that provides HTTPS configuration advice or provides an option to enable HSTS, do not include the preload directive by default. We get regular emails from site operators who tried out HSTS this way, only to find themselves on the preload list by the time they find they need to remove HSTS to access certain subdomains. Removal tends to be slow and painful for those sites.

    • 我是通过Mozilla SSL Configuration Generator生成了TLS的一些设置。我这里选择了中级的设置是为了更好的兼容性(在我国目前TLS1.3好像不太能用)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # Dynamic configuration
    http:
    middlewares:
    secureHeaders:
    headers:
    sslRedirect: true
    forceSTSHeader: true
    stsIncludeSubdomains: true
    stsPreload: true
    stsSeconds: 31536000
    tls:
    options:
    default:
    cipherSuites:
    - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
    - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
    - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
    - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
    - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
    - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
    minVersion: VersionTLS12

以上就是我今天想聊的所有内容。再开始写本文之前我没有想过我会写这么长的一篇文章,不过这也许也意味着2021一个好的开始吧。

感谢你看到这里,咱们下次再见。