本文章介绍如何使用 go-acme/lego
的客户端或库完成证书的自动签发。
相关背景知识
Let's Encrypt
Let's Encrypt是一个于2015年三季度推出的数字证书认证机构,
旨在以自动化流程消除手动创建和安装证书的复杂流程,并推广使万维网服务器的加密连接无所不在,为安全网站提供免费的传输层安全性协议证书。
ACME
ACME,即自动自动证书管理环境(Automatic Certificate Management Environment),是一种协议,用于自动颁发和更新证书,无需人工干预。
互联网安全研究小组 (ISRG) 最初为自己的证书服务设计了ACME协议。证书颁发机构Let's Encrypt通过ACME协议免费提供DV证书。如今,各种其他的CA、PKI供应商和浏览器都支持ACME协议,支持不同类型的证书。
go-acme/lego
一个用go语言写的acme客户端和go语言库。下文简称为
lego
。
使用 lego 客户端
安装
- 如果有golang开发环境可以使用以下命令安装
lego
shell
go install github.com/go-acme/lego/v4/cmd/lego@latest
- 如果没有golang开发环境可前往 github.com/go-acme/leg... 下载最新cli客户端
签发证书
签发证书需要验证域名的所有权,acme
协议提供了两种方法来验证你的域名所有权,分别是http服务器验证和dns验证。
http服务器验证
使用该方法验证需要有一台服务器,该服务器要有公网ip。同时还需要拥有服务器的root权限,才能监听80和443端口。
假设需要部署证书的域名为 example.com ,我们需要先配置 example.com 指向该服务器的公网ip,然后在服务器上执行
shell
lego --email="you@example.com" --domains="example.com" --http run
该命令启动一个http服务器,监听80/443端口。
随后Let's Encrypt
会根据域名(example.com)访问服务器的80/443端口,验证域名的所有权。这个过程是自动化的,只需要等待即可。
验证通过后会下发证书到 ./.lego/certificates
里,证书的有效期为3个月。
dns验证
lego
强大的地方便是集成了几乎所有常用的云厂商,如CloudFlare、阿里云和腾讯云等的服务API。只需要设置API_KEY和API_SECRET便可以自动帮你完成DNS验证。
这边以腾讯云为例:
执行以下命令:
ini
TENCENTCLOUD_SECRET_ID=abcdefghijklmnopqrstuvwx \
TENCENTCLOUD_SECRET_KEY=your-secret-key \
lego --email you@example.com --dns tencentcloud --domains my.example.org run
该命令会调用腾讯云的后台API,为example.com
填写DNS验证信息;Let's Encrypt
验证通过之后,下发证书到./.lego/certificates
,同时也会自动帮你删除该DNS验证信息。
使用lego库
我们也可以使用lego
库来生成证书,或者做一些自动化证书生成然后部署的二次开发。lego
库同样也支持http服务器验证和dns验证,下面给出完整可用代码。
50-62行为腾讯云dns验证部分,64-73为http服务器验证部分,二选一即可。
go
package main
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"fmt"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/providers/dns/tencentcloud"
"github.com/go-acme/lego/v4/registration"
"log"
"os"
)
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 main() {
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Fatal(err)
}
myUser := MyUser{
Email: "you@example.com",
key: privateKey,
}
config := lego.NewConfig(&myUser)
config.Certificate.KeyType = certcrypto.RSA2048
client, err := lego.NewClient(config)
if err != nil {
log.Fatal(err)
}
// 使用dns验证的方式,这边以腾讯云为例子。
cfg := tencentcloud.NewDefaultConfig()
cfg.SecretID = "abcdefghijklmnopqrstuvwx"
cfg.SecretKey = "your-secret-key"
p, err := tencentcloud.NewDNSProviderConfig(cfg)
if err != nil {
log.Fatal(err)
}
err = client.Challenge.SetDNS01Provider(p)
if err != nil {
log.Fatal(err)
}
// ---------使用dns-腾讯云验证的方式结束-----------
// 使用http服务器验证的方式,注释上面dns验证的代码部分,取消下面http验证的代码注释部分
// err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "80"))
// if err != nil {
// log.Fatal(err)
// }
// err = client.Challenge.SetTLSALPN01Provider(tlsalpn01.NewProviderServer("", "443"))
// if err != nil {
// log.Fatal(err)
// }
//-----------使用http服务器验证的方式结束-----------
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
log.Fatal(err)
}
myUser.Registration = reg
request := certificate.ObtainRequest{
Domains: []string{"sd.pigudaxiang.cn"},
Bundle: true,
}
certificates, err := client.Certificate.Obtain(request)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%#v\n", certificates)
err = os.WriteFile("PrivateKey", certificates.PrivateKey, os.ModePerm)
if err != nil {
log.Print(err)
}
err = os.WriteFile("Certificate", certificates.Certificate, os.ModePerm)
if err != nil {
log.Print(err)
}
err = os.WriteFile("IssuerCertificate", certificates.IssuerCertificate, os.ModePerm)
if err != nil {
log.Print(err)
}
err = os.WriteFile("CSR", certificates.CSR, os.ModePerm)
if err != nil {
log.Print(err)
}
}