Docker使用 linuxserver/letsencrypt 生成SSL证书最全解析及实践

本文使用 HTTP 和 DNS 两种校验方式对 Docker 下 linuxserver/letsencrypt 项目进行了实践。生成SpringBoot可用证书,使用 Nginx 的 htpasswd 来对网站进行密码保护,并测试使用 fail2ban 防止 htpasswd 被暴力破解。全文基于 linuxserver/letsencrypt 文档及其他官方资料,根据作者实践进行详细解析和记录。

1. 介绍

1.0 linuxserver/letsencrypt

这个容器设置了一个 Nginx 服务器,支持 PHP 的反向代理和一个内置的 letsencrypt 客户端,可以自动化生成或更新 SSL 服务器证书。它还包含用于防御入侵的 fail2ban。

1.1 使用

docker create \

  --cap-add=NET_ADMIN \

  --name=letsencrypt \

  -v <path to data>:/config \

  -e PGID=<gid> -e PUID=<uid>  \

  -e EMAIL=<email> \

  -e URL=<url> \

  -e SUBDOMAINS=<subdomains> \

  -e VALIDATION=<method> \

  -p 80:80 -p 443:443 \

  -e TZ=<timezone> \

  linuxserver/letsencrypt

1.2 参数

–cap-add=NET_ADMIN cap-add:即 Add Linux capabilities 添加 Linux 内核能力。这里具体添加的能力是允许执行网络管理任务。这是因为 fail2ban 需要修改 iptables

-p 80 -p 443:端口

-v /config:包括 webroot 在内的所有配置文件都保存在此处

-e URL:顶级域名(完全拥有则如:“customdomain.com”,动态 DNS 则如 “customsubdomain.ddnsprovider.com” )

-e SUBDOMAINS:证书覆盖的子域名 (逗号分隔,无空格) .如 www,ftp,cloud.对于通配符证书, 请将此明确地设置为通配符 (通配符证书只允许通过dns方式验证)

-e VALIDATION:letsencrypt验证方法,选项是 http、tls-sni 或者 DNS

不同校验方式的区别:

HTTP 校验:需要使用到80端口,故宿主机80端口应该转发到容器的80端口

tls-sni 校验:需要使用到443端口,故宿主机443端口应该转发到容器的443端口(注意:由于安全漏洞,letsencrypt 禁用了 tls-sni 验证,使用该方式会报错:Client with the currently selected authenticator does not support any combination of challenges that will satisfy the CA)

DNS 验证:需要设置 DNSPLUGIN 变量(不是所有的DNS服务商都支持),并且需要在 /config/dns-conf 文件夹下输入凭据到相应的 ini 文件里,当无法通过端口验证时可使用这种方法验证

-e PGID 设置 GroupID

-e PUID 设置 UserID

-e TZ - 时区 如 America/New_York:上海时区为Asia/Shanghai

通过指定用户ID和所属群的ID来避免数据卷挂载(-v)时容器和宿主机直接可能产生的权限问题。最好让挂载的数据卷目录的拥有者和指定的用户统一。

另外,需要注意:不能指定root用户(即PGID=0,PUID=0),否则会一直报错(但不影响使用)。

#宿主机root用户环境下使用例子(非官方,仅供参考)


#创建要挂载的目录,此时该目录属root用户和root组

mkdir /opt/letsencrypt

#创建docker用户(默认会顺带新建同名Group)

useradd dockeruser

#修改文件夹归属(R代表递归操作,文件夹下的也一并修改)

chown -R dockeruser:dockeruser /opt/letsencrypt

#查看dockeruser的用户id和群id

id dockeruser

可选设置:

-e DNSPLUGIN:如果 VALIDATION 设置为 DNS 则此项必选。选项有 cloudflare、cloudxns、digitalocean、dnsimple、dnsmadeeasy、google、luadns、nsone、rfc2136 和 route53。还需要在 /config/dns-conf 文件夹下输入凭据到相应的 ini 文件里。这里推荐使用 cloudflare,免费而且好用.

使用 Cloudflare 服务的话应确保设置为 dns only 而非 dns + proxy(事实上 Cloudflare 的 proxy 已经提供免费自动 SSL 服务了,也就没有本文的必要)

Google DNS 插件的使用对象是企业付费产品“Google Cloud DNS”而非“Google Domains DNS”

-e EMAIL:您的证书注册和通知的电子邮件地址

-e DHLEVEL:dhparams 位值(默认值= 2048,可设置为1024或4096)

-p 80:VALIDATION设置为 http 而不是 dns 或 tls-sni 时需要80端口进行转发

-e ONLY_SUBDOMAINS:仅为子域名获取证书(主域名可能托管在另外一台计算机且无法验证)时请将此项设置为 true

-e EXTRA_DOMAINS:额外的完全限定域名(逗号分隔,无空格)如 extradomain.com,subdomain.anotherdomain.org

-e STAGING:设置为 true 可以提高速率限制,但证书不会通过浏览器的安全测试,仅用于测试

-e HTTPVAL:已弃用, 请用VALIDATION 代替

2. 实践

2.1 使用 HTTP 方式验证

首先,你应该先保证要获取证书的域名(子域名)能正确地访问到主机。注意:域名需要备案。

这里我映射的宿主机目录为/opt/letsencrypt1,PGID 和 PUID 由上文提到的方式获得。配置的域名为 my.com 和 www.my.com (实际上我配置的是另外一个我自己真正拥有的域名,这里不贴出来)

注意:使用 HTTP 方式验证的话开发80端口就可以了,这里443端口也进行映射。这是为了证书获取成功后可以通过使用 HTTPS 登录该容器提供的默认首页进行确认。

docker run -d \

--cap-add=NET_ADMIN \

--name=letsencrypt \

-v /opt/letsencrypt1:/config \

-e PGID=1002 -e PUID=1001  \

-e URL=my.com \

-e SUBDOMAINS=www \

-e VALIDATION=http \

-p 80:80 -p 443:443 \

-e TZ=Asia/Shanghai \

linuxserver/letsencrypt

容器会在后台运行,这个时候应该提供如下指令查看日志输出(CTRL + z退出)

1

docker logs -f letsencrypt

最后,我卡在 Cleaning up challenges 这一步。这是因为我域名没有备案,无法通过域名访问到我所在的主机。这个时候打开域名链接被重定向到云主机提供商的网页禁止访问,Let’s encrypt 没办法通过域名访问到本机,所以验证失败(事实上它也没有说失败,只是一直停在那里)。

毋庸置疑,我是因为这个原因被禁止访问的。

既然 HTTP(80端口)方式验证走不通,tls-sni本来就不行,那就只能用 dns 验证了。

2.2 使用dns方式验证

这里以CloudFlare为例

第一步 完成域名服务器配置

首先,要有一个 cloudflare 账号。然后,在域名提供商那里将域名的 DNS 服务器改成 cloudflare 提供的 DNS 服务器。然后,在cloudflare那里添加对应的解析记录。

注意:解析记录 Status 的图标应该是灰色的,表示 DNS only。如果图标亮了,表示 DNS and HTTP proay(CDN),要使用 let’s encrypt 的 DNS 校验的话就不要再开 HTTP 代理和 CDN 了。开了代理的话 cloudflare 会免费给你提供(及自动维护更新)SSL证书,就可以直接 HTTP 访问了。不需要本文再干嘛了,而且还有免费 CDN,可谓十分良心。

第二步 完成域名服务器 API-KEY 相关配置并启动

这一步先正常启动,会启动失败但会生成所有的配置文件。再根据相应的 ini 文件里的提示去域名服务器提供商那里找到相对应的凭证,修改 ini 文件,重新启动容器。

启动如下。这次我映射到宿主机目录 /opt/letsencrypt2下,把 VALIDATION 改为dns,增加 DNSPLUGIN 配置为 cloudflare。

docker run -d \

--cap-add=NET_ADMIN \

--name=letsencrypt \

-v /opt/letsencrypt2:/config \

-e PGID=1002 -e PUID=1001  \

-e URL=my.com \

-e SUBDOMAINS=www \

-e VALIDATION=dns \

-e DNSPLUGIN=cloudflare \

-p 80:80 -p 443:443 \

-e TZ=Asia/Shanghai \

linuxserver/letsencrypt

使用 docker logs -f letsencrypt 查看。

这次是在 Cleaning up challenges 之后报错… 错误提示也很明确,是 Unknown X-Auth-Key or X-Auth-Email 的问题,配置是在/config/dns-conf/cloudflare.ini这个文件里面。

Cleaning up challenges

Error determining zone_id: 9103 Unknown X-Auth-Key or X-Auth-Email. Please confirm that you have supplied valid Cloudflare API credentials. (Did you enter the correct email address?)

IMPORTANT NOTES:

 - Your account credentials have been saved in your Certbot

   configuration directory at /etc/letsencrypt. You should make a

   secure backup of this folder now. This configuration directory will

   also contain certificates and private keys obtained by Certbot so

   making regular backups of this folder is ideal.

ERROR: Cert does not exist! Please see the validation error above. Make sure you entered correct credentials into the /config/dns-conf/cloudflare.ini file.

打开/config/dns-conf/cloudflare.ini可以看到

# Instructions: https://github.com/certbot/certbot/blob/master/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py#L20

# Replace with your values

dns_cloudflare_email = cloudflare@example.com

dns_cloudflare_api_key = 0123456789abcdef0123456789abcdef01234567

感兴趣的可以到介绍的页面去查看相关信息,也可以直接到对应域名解析服务提供商那里去看。cloudflare 查看的地址是 https://dash.cloudflare.com/profile,最上面是 Email,最下面是 API Keys。

将对应内容替换到/config/dns-conf/cloudflare.ini里面(即宿主机的/opt/letsencrypt2/dns-conf/cloudflare.ini里面)。然后,使用 docker rm -f letsencrypt 强制删掉原容器。再重新运行上面的 docker run 就可以成功启动了。

查看日志如下:

最终会停在Server ready这一行(如果用 root 用户的 uid 和 gid 的话,现在会一直报错,但仍可使用)。这个时候就可以用 HTTPS 打开了(内置的 Nginx 只监听443端口,所以不能用 HTTP 打开),显示如下界面即为正常。


3. 设置

3.1 安全和密码保护

可以使用 Nginx 的 htpasswd 来对网站进行密码保护。htpasswd 的相关用法可见 htpasswd命令。

添加第一个密码访问用户(-c 参数表示创建一个加密文件,如果原来有的话则把原来的删掉)

1

docker exec -it letsencrypt htpasswd -c /config/nginx/.htpasswd <username>

继续添加密码访问用户(把-c去掉即可)

1

docker exec -it letsencrypt htpasswd /config/nginx/.htpasswd <username>

如下为添加两个用户(lin 和 shen)

查看用户信息文件(/opt/letsencrypt2 是我映射到容器 /config 的目录)

然后,还需要在 Nginx 的配置文件(默认为/config/nginx/site-confs/default)里面开启 auth_basic,如下:

location / {

    try_files $uri $uri/ /index.html /index.php?$args =404;

    # 将下列两行放到location{}里面,**Restricted**是网站要求输入账号密码时的提示语,后面是指定的用户密码文件路径

    auth_basic "Restricted";

    auth_basic_user_file /config/nginx/.htpasswd;

}

最后要使用 docker restart letsencrypt 重新启动容器使配置生效。登录网站,提示如下(我用的是firefox,不同浏览器可能显示不一样)

3.2 站点配置和反向代理

3.2.1 默认配置文件

默认的站点配置文件位于/config/nginx/site-confs/default。可直接修改此文件完成配置,也可将其他的 conf 文件添加到此目录。如果将 default 文件删除的话,容器启动时对其重新创建。

3.2.2 拒绝搜索引擎抓取

如果不希望网站被搜索引擎抓取,可以将以下命令添加到 /config/nginx文件夹下的 ssl.conf文件中。

1

add_header X-Robots-Tag "noindex, nofollow, nosnippet, noarchive";

3.2.3 使用预设的配置文件

本容器已经为热门应用添加了预设的反向代理配置文件,具体可以查看/config/nginx/proxy_confs文件夹下的 _readme文件

/config/nginx/proxy_confs 文件夹下的预设反向代理配置文件有两类:

.subfolder.conf : 这类型的配置文件将允许通过 https://yourdomain.com/servicename 的方式访问配置文件对应的服务

.subdomain.conf : 这类型的配置文件将允许通过 https://servicename.yourdomain.com 的方式访问配置文件对应的服务

启用预设的配置文件:

第一步 确保在默认站点配置文件(default文件)的server项内包含以下命令:

include /config/nginx/proxy-confs/*.subfolder.conf;

include /config/nginx/proxy-confs/*.subdomain.conf;

第二步 重命名 conf 文件并删除结尾的 .sample 

第三步 重启 letsencrypt 容器

3.3 证书相关

3.3.1 证书种类

cert.pem、chain.pem、fullchain.pem 和 privkey.pem。通过 letsencrypt 生成并由 Nginx 和其它各种应用使用

privkey.pfx:Microsoft支持的格式,常用于 Embnet Server 等 dotnet 应用程序(无密码)

priv-fullchain-bundle.pem:一个捆绑私钥和全链的 pem 证书,由 ZNC 等应用程序使用

3.3.2 在其他容器中使用证书

证书在容器中的存放在 config/etc/letsencrypt文件夹下,又因为 /config 文件夹被映射到宿主机,故如果需要在其他容器中使用,可以再把宿主机对应目录下的 etc/letsencrypt文件夹映射到需要用到证书的容器。

3.3.3 在SpringBoot 下使用

1. 将 pem 证书转为 JKS 格式,在此过程需要输入密码。这里使用的是网上的 SSL证书格式转换工具(https://www.chinassl.net/ssltools/convert-ssl.html)

2. 在SpringBoot 里面配置。有了JKS证书和密码后配置就很简单了,这里不贴出来。

3.3.4 fail2ban

fail2ban 是一款实用软件,可以监视你的系统日志,然后匹配日志的错误信息(正则式匹配)执行相应的屏蔽动作。多用于防止暴力破解和 CC 攻击。

1. 文件结构

/config/fail2ban 目录下主要有一个 jail.local 文件和 filter.d、action.d 两个文件夹。另外,还有一个 fail2ban.sqlite3 的数据库文件,这个不用管

jail.local 文件:负责 fail2ban 的主要配置,统管所有 jail 的启用和禁用和监控规、日志路径等

filter.d 文件夹:存放各个 jail 的过滤器配置文件,如 nginx-http-auth.conf 文件等

action.d 文件夹:存放各种功能对应的配置文件,如 sendmail.conf 文件等

2. 使用说明

该容器内置的fail2ban默认包括(并开启)3个jail

nginx-http-auth

nginx-badbots

nginx-botsearch

可以通过修改文件 /config/fail2ban/jail.local去启用或禁用其他 jail

要修改 filter.d 文件夹或 action.d 文件夹下的配置文件时,不要直接编辑 .conf 文件而应该创建一个同名的但以 .local 结尾的文件(如想要修改 nginx-http-auth.conf 的话就创建一个 nginx-http-auth.local)。这是因为当 actions 和 fileter 更新时,.conf 文件会被覆写而使修改失效。而.local文件是追加到 .conf文件后面的,不受.conf文件的变动的影响(根据对 Dockerfile 文件的分析,这些 .conf 文件应该是在构建 Docker 镜像时下载的,所以更新镜像后即使复用原来的文件夹,.conf 文件也会被覆写)

查看哪些 jail 是启用的

1

docker exec -it letsencrypt fail2ban-client status

查看特定 jail 的状态

1

docker exec -it letsencrypt fail2ban-client status <jail name>

设置特定 jail 对特定 IP 放行(注意:linuxserver/letsencrypt 给的教程没有 set ,会报指令错误。根据下面 fail2ban 的官方命令,我发现要加set)

1

docker exec -it letsencrypt fail2ban-client set <jail name> unbanip <IP>

命令列表:https://www.fail2ban.org/wiki/index.php/Commands

3. 默认配置

查看 /config/fail2ban/jail.local文件,部分内容如下:

[DEFAULT]

# "bantime" is the number of seconds that a host is banned.

bantime  = 600

# A host is banned if it has generated "maxretry" during the last "findtime"

# seconds.

findtime  = 600

# "maxretry" is the number of failures before a host get banned.

maxretry = 5


[nginx-http-auth]


enabled  = true

filter   = nginx-http-auth

port     = http,https

logpath  = /config/log/nginx/error.log

上方 [DEFAULT] 的含义:若在600秒内失败5次,则禁止访问600秒。

 [nginx-http-auth]的内容是启用,使用 nginx-http-auth 过滤器监听 HTTP 和 HTTPS 端口并把日志写在 /config/log/nginx/error.log 文件里。配置的选项则同[DEFAULT]。更多配置信息请看官方指南:http://www.fail2ban.org/wiki/index.php/MANUAL_0_8#General_settings

4. 测试

接下来当然是来测试一波啦。

先确保网页 HTTP 密码保护打开,然后登陆。任意错误登陆(用户名不能为空)5次后网页就开始提示找不到服务器了。

这个时候可以查看一下 jail 的状态,内容如下:

Total banned 表示历史 ban 总记录。在 Banned IP list 中可以看到 IP 已经被封了。

接下来再把 IP 解禁,如下:

可以查看 /config/log/fail2ban/fail2ban.log文件:

这个是600秒后自动解禁的

2018-09-15 09:39:44,090 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 09:39:44

2018-09-15 09:39:48,295 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 09:39:48

2018-09-15 09:39:53,503 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 09:39:53

2018-09-15 09:39:56,709 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 09:39:56

2018-09-15 09:39:57,911 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 09:39:57

2018-09-15 09:39:58,107 fail2ban.actions        [343]: NOTICE  [nginx-http-auth] Ban 125.90.49.157

2018-09-15 09:49:58,920 fail2ban.actions        [343]: NOTICE  [nginx-http-auth] Unban 125.90.49.157


这个是使用命令解禁的

2018-09-15 11:18:10,728 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 11:18:10

2018-09-15 11:18:12,738 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 11:18:12

2018-09-15 11:18:13,940 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 11:18:13

2018-09-15 11:18:14,542 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 11:18:14

2018-09-15 11:18:15,143 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 11:18:14

2018-09-15 11:18:15,620 fail2ban.actions        [343]: NOTICE  [nginx-http-auth] Ban 125.90.49.157

2018-09-15 11:18:15,745 fail2ban.filter         [343]: INFO    [nginx-http-auth] Found 125.90.49.157 - 2018-09-15 11:18:15

2018-09-15 11:18:35,942 fail2ban.actions        [343]: NOTICE  [nginx-http-auth] Unban 125.90.49.157

欢迎工作一到五年的Java工程师朋友们加入Java程序员开发: 854393687

群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容