姊妹篇: 使用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协议的流程如下:

  1. 客户端向CA发送证书请求,并提供验证信息。
  2. CA验证客户端提供的信息,如果验证通过,则向客户端颁发一个签名证书。
  3. 客户端使用签名证书进行加密通信。
  4. 客户端定期更新证书,以保证证书的有效性。

ACME协议的实现需要CA和客户端双方的支持,目前已经有很多主流的CA和客户端支持ACME协议,例如Let’s Encrypt、Certbot、ACME.sh等。

以Let’s Encr域名如何支持https访问ypt为例,Let’s Encrypt是一个免费的证书颁发机构,它支持ACME协议,并提供了Certbot客户端工具,可以自动化地申请、更新和管理SSL证书。Certbot客户端工具可以通过命令行工具或者Web界面进行操作。

Certbot客户端工具的实现原理如下:

  1. 客户端向Let’s Encrypt发送证书请求,并提供验证信息。
  2. Let’s Encrypt验证客户端提供的信息,如果验证通过,则向客户端颁发一个签名证书。
  3. Certbot客户端工具将签名证书部署到Web服务器上,并自动配置SSL证书。
  4. Certbot客户端工具定期更新证书,以保证证书的有效性。

ACME协议使得SSL证书的颁发和管理变得自动化、安全化和可扩展化,让网站管理员不再需要手动申请和更新SSL证书,从而节省了时间和精力。

自动证书管理环境(ACME)

自动证书更新环境

更多可参考 acme自动证书管理系统

几种工具


基于ACME协议自动更新证书的工具 大致可以分成两类,一类是是独立的,脱离于项目,一般是一个命令行工具,或者shell脚本,如Certbot(python实现),acme.sh, mkcert(这个只能制作本地信任的证书,对localhost可用, 作者是给Go密码学库做了很多贡献的意大利开发者FiloSottile)


图片来源

另一类是和项目集成在一起,可以在项目中,依据逻辑等判断,进行相应操作,以Go语言为例, 比较知名的如 certmagic(Caddy下面的一个项目)

<font size=1>

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需求。

</font>


关于 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 生成域名证书的代理服务



好文收藏
38 声望6 粉丝

好文收集