姊妹篇: 使用Let’s Encrypt 申请通配符证书
关于acme 协议
ACME是自动证书管理环境(Automatic Certificate Management Environment)的缩写,是一个由IETF(Internet Engineering Task Force)制定的协议标准,用于自动化证书颁发和管理。ACME协议的主要目的是使得证书颁发过程自动化、安全化和可扩展化,同时减少人工干预的成本和风险。
ACME协议的核心是证书颁发机构(CA)和客户端之间的交互过程。客户端可以是一个Web服务器、一个容器或者一个操作系统。客户端使用ACME协议与CA通信,申请证书、更新证书或者撤销证书。
ACME协议使用了基于HTTP的RESTful API协议,支持多种验证方式,包括HTTP验证、DNS验证和TLS-SNI验证。其中,HTTP验证是最常用的验证方式。
ACME协议的流程如下:
-
客户端向CA发送证书请求,并提供验证信息。
-
CA验证客户端提供的信息,如果验证通过,则向客户端颁发一个签名证书。
-
客户端使用签名证书进行加密通信。
-
客户端定期更新证书,以保证证书的有效性。
ACME协议的实现需要CA和客户端双方的支持,目前已经有很多主流的CA和客户端支持ACME协议,例如Let’s Encrypt、Certbot、ACME.sh等。
以Let’s Encr域名如何支持https访问ypt为例,Let’s Encrypt是一个免费的证书颁发机构,它支持ACME协议,并提供了Certbot客户端工具,可以自动化地申请、更新和管理SSL证书。Certbot客户端工具可以通过命令行工具或者Web界面进行操作。
Certbot客户端工具的实现原理如下:
-
客户端向Let’s Encrypt发送证书请求,并提供验证信息。
-
Let’s Encrypt验证客户端提供的信息,如果验证通过,则向客户端颁发一个签名证书。
-
Certbot客户端工具将签名证书部署到Web服务器上,并自动配置SSL证书。
-
Certbot客户端工具定期更新证书,以保证证书的有效性。
ACME协议使得SSL证书的颁发和管理变得自动化、安全化和可扩展化,让网站管理员不再需要手动申请和更新SSL证书,从而节省了时间和精力。
自动证书管理环境(ACME)
自动证书更新环境
更多可参考 acme自动证书管理系统
几种工具
基于ACME协议自动更新证书的工具 大致可以分成两类,一类是是独立的,脱离于项目,一般是一个命令行工具,或者shell脚本,如Certbot(python实现),acme.sh, mkcert(这个只能制作本地信任的证书,对localhost可用, 作者是给Go密码学库做了很多贡献的意大利开发者FiloSottile)
图片来源
另一类是和项目集成在一起,可以在项目中,依据逻辑等判断,进行相应操作,以Go语言为例, 比较知名的如 certmagic(Caddy下面的一个项目)
CertMagic是一个用于Go程序的自动HTTPS库,可完全管理TLS证书的签发和更新。通过添加一行代码,你的Go应用程序即可安全地通过TLS提供服务,无需处理证书。与其他Go的ACME客户端库相比,CertMagic支持完整的ACME功能集,并在成熟性和可靠性方面领先。该库具有全自动证书管理、一行代码实现HTTPS服务器、对系统几乎所有方面的完全控制等特点。它支持多个证书颁发机构,解决了HTTP、TLS-ALPN和DNS等常见ACME挑战,并具有强大的错误处理、分布式挑战解决等功能。 CertMagic可以与任何符合ACME规范RFC 8555的证书颁发机构一起使用,支持通配符证书、OCSP stapling等功能,适用于Mac、Windows、Linux、BSD、Android等多平台。
Caddy 源码阅读
CertMagic源码分析 » 1 -Cermagic整体结构
这篇内容介绍了一个名为CertMagic的Go语言库,用于实现自动化的HTTPS证书管理。CertMagic是Caddy Web服务器使用的库,它提供了简单而强大的TLS自动化功能。通过在Go应用程序中添加一行代码,即可实现安全的HTTPS服务,无需手动处理证书。与其他Go语言ACME客户端库相比,CertMagic是最成熟、稳健和强大的集成之一,支持完整的ACME功能。该库具有自动化证书管理、支持多个证书颁发机构、解决常见ACME挑战、高效的分布式管理等特点。CertMagic支持使用Let’s Encrypt进行自动HTTPS,并提供了丰富的功能和配置选项,适用于各种TLS需求。
关于 Lego
本篇重点介绍兼具二者功能Lego—既能作为工具使用,又可作为项目进行集成
通过 Lego 工具获取 HTTPS 证书
Lego和Let’s Encrypt 官方推荐的certbot,以及acme.sh都是差不多功用的东西, 只不过Lego是Go实现的,最终编译成一个二进制文件。除此还可以集成到Go项目中,可根据项目业务逻辑进行证书更新的操作
安装
从github上wget tar包,解压,得到二进制文件
wget -c https://github.com/go-acme/lego/releases/download/v4.14.2/lego_v4.14.2_linux_amd64.tar.gz
tar -zxvf lego_v4.14.2_linux_amd64.tar.gz
rm lego_v4.14.2_linux_amd64.tar.gz
chmod +x lego
// 把二进制文件移动到任意path路径下
lego -h
输出帮助信息
Lego作为命令行工具离线使用
lego 可以直接使用命令行,和cerebot,acme.sh一样
acme 协议一般有两种方式验证: http 和 dns 验证
其中 http方式需要侵入Nginx;
所以如果域名在自己手中,一般优先使用DNS方式。 需要在域名解析商那里新增TXT记录用来验证该域名属于你( 另外域名服务商一般提供key和secret,可以自动化验证)
这里有每一个DNS厂商获取凭证的说明
以阿里云为例:(阿里云提供的是全局的ALICLOUD_ACCESS_KEY和ALICLOUD_SECRET_KEY,如果授权了,能够操作全部资源)
访问控制 > 用户 > 新建用户
登陆名: dns
显示名: dns
访问方式: 编程式访问
创建后,添加权限: AliyunDNSFullAccess
最终得到 AccessKey ID、AccessKeySecret 两个字符串,务必保存好key和secret
用 Lego 实现 Let‘s Encrypt HTTPS 通配符证书
ALICLOUD_ACCESS_KEY=your_key \
ALICLOUD_SECRET_KEY=your_secret \
./lego --email your_mail@gmail.com --dns alidns --domains *.your_domain.com run
DNS Challenge 方式 手动更新:
# --renew-hook="sudo /usr/sbin/nginx -s reload",表示更新证书成功后执行的命令,即重启nginx
ALICLOUD_ACCESS_KEY=your_key \
ALICLOUD_SECRET_KEY=your_secret \
lego --email="your_mail@gmail.com" --domains="*.wode.tech" --dns="alidns" renew --days=30 --renew-hook="sudo /usr/sbin/nginx -s reload"
新申请的证书有效期为90天。 低于30天时才能更新证书
DNS Challenge 方式 自动更新:
可以配置定时任务,自动更新~
autorenew.sh:
#!/bin/sh
ALICLOUD_ACCESS_KEY=your_key \
ALICLOUD_SECRET_KEY=your_secret \
lego --email="your_mail@gmail.com" --domains="*.wode.tech" --dns="alidns" renew --days=30 --renew-hook="sudo /usr/sbin/nginx -s reload"
配置定时任务,每天执行一次
(如果DNS服务商是dnspod: 使用lego签发Let’s Encrypt的证书)
HTTP Challenge 方式 手动更新:
某些场景下,无法使用DNS Challenge的方式进行验证(比如这个域名aaa.com在别人手里,只是通过Cname指向了我们的一个域名bbb.com; 这时我们其实可以为aaa.com申请&维护证书,是不是有点颠覆之前的认知…), 就必须使用HTTP Challenge的方式, 会向Nginx里写入一段配置
(但是 泛域名证书只能通过DNS验证)
没有证书之前:
# 此命令会占用80端口
lego --email="your_email@163.com" --domains="www.your_domain.com" --http run
# 如果80端口已经占用请此命令;(还需要在nginx里新增配置,不然会如下图报错)
lego --email="your_email@163.com" --domains="www.your_domain.com" --http --http.port :8080 run
# 如果80端口被nginx占用了,可以在nginx.conf配置文件中的http块的server块中添加如下配置。
location /.well-known/acme-challenge {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
}
体验下来确实可以…都没有去碰 my_domain.com 这个域名的DNS解析
HTTP Challenge 方式 自动更新:
lego --email="your_mail@163.com" --domains="www.your_domain.com" --http --http.port :8080 renew --days 30
现在还没到能更新的时间
也可以写成脚本,配置为定时任务,每天执行一次~
Lego使用HTTP或DNS生成SSL通配符证书
Let’s Encrypt
HTTP验证的方式,不支持申请泛域名证书(例如 *.your_domain.tech
),而是一个特定的二级域名证书,可以一次申请多个
通配符证书只能通过 DNS challenge
生成,因为这样才能保证你是这个域名的完全拥有者;
如果是HTTP的方式,别人比如123.aaa.com cname过来,我就能申请其域名的通配符证书的话,把他整个aaa.com都能影响了,显然不合理
所谓的通配符,也只是通配同级, 比如 *.world.com
, 只是对任意的xxx.world.com
有效,对于其一级域名world.co, 三级域名a.b.world.com 都是无效的~
另外有可能使用或更新https证书后,浏览器还是显示不安全, 这很有可能是该网站有请求其他http的静态资源导致的…
部署SSL后,为何网站还是显示不安全?
这篇是把lego集成进脚本使用 使用 lego 申请 let’s encrypt 证书
Lego使用HTTP或DNS生成SSL通配符证书
使用lego申请Let’s Encrypt通配符证书
Lego集成到Go项目中使用
如果想集成进Go项目里,也提供了在Go代码中使用的方式
以下代码参考自 免费!让Https证书不再成为烦恼
package get_certificate_from_lego
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/challenge/http01"
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/registration"
)
const (
EmailStr = "your_mail@163.com" // 修改为自己的电子邮件
OneDomain = "www.your_domain.com" // 修改为自己的域名
)
type MyUser struct {
Email string
Registration *registration.Resource
key crypto.PrivateKey
}
func (u *MyUser) GetEmail() string {
return u.Email
}
func (u MyUser) GetRegistration() *registration.Resource {
return u.Registration
}
func (u *MyUser) GetPrivateKey() crypto.PrivateKey {
return u.key
}
func GetCertificateFromLego() (*certificate.Resource, error) {
// 创建myUser用户对象。新对象需要email和私钥才能启动,私钥需自己生成
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
myUser := MyUser{
Email: EmailStr,
key: privateKey,
}
config := lego.NewConfig(&myUser)
// 此处配置密钥的类型和密钥申请的地址,记得上线后替换成 lego.LEDirectoryProduction ,测试环境下就用 lego.LEDirectoryStaging
config.CADirURL = lego.LEDirectoryStaging
config.Certificate.KeyType = certcrypto.RSA2048
// 创建一个client与CA服务器通信
client, err := lego.NewClient(config)
if err != nil {
return nil, err
}
// 此处需要进行申请证书的chanlldge,必须监听80和443端口,这样才能让Let's Encrypt访问到我们的服务器
err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "80"))
if err != nil {
return nil, err
}
err = client.Challenge.SetTLSALPN01Provider(tlsalpn01.NewProviderServer("", "443"))
if err != nil {
return nil, err
}
// 把这个客户端注册,传递给myUser用户里
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
return nil, err
}
myUser.Registration = reg
request := certificate.ObtainRequest{
Domains: []string{OneDomain}, // 这里如果有多个,就写多个就好了,可以是多个域名
Bundle: true, // 这里如果是true,将把颁发者证书一起返回,也就是返回里面certificates.IssuerCertificate
}
// 开始申请证书
certificates, err := client.Certificate.Obtain(request)
if err != nil {
return nil, err
}
// 申请完了后,里面会带有证书的PrivateKey Certificate,都为[]byte格式,需要存储的自行转为string即可
return certificates, nil
}
// 如果要进行续订,可将certificates, err := client.Certificate.Obtain(request)替换为certificates, err := client.Certificate.Renew(request)
// renew里面的参数就很简单了,第一个参数就是第一次申请返回的指针的值certificates,第二个参数bundle上面已经讲过传true即可,后面两个参数一个传false,一个传空字符串""即可。开启PAC自动配置
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"log"
"server/get_certificate_from_lego"
)
func main(){
cs, err := get_certificate_from_lego.GetCertificateFromLego()
if err != nil {
log.Fatalln("obtains certificate:", err)
}
ca, err := tls.X509KeyPair(cs.Certificate, cs.PrivateKey)
if err != nil {
log.Fatalln(err)
}
if ca.Leaf, err = x509.ParseCertificate(ca.Certificate[0]); err != nil {
log.Fatalln(err)
}
fmt.Println(ca.Leaf)
}
更多相关资料:
go用lego获取ssl证书
go用lego获取ssl证书
dcert
lego-certmgr 一款使用 lego 生成域名证书的代理服务