通配符证书(Wildcard Certificate)
--server https://acme-v02.api.letsencrypt.org/directoryDNS TXT
在你申请证书时,Let's Encrypt 需要确保你拥有此域名,比如你不可能申请到包含 google.com 域名的证书。Certbot 支持如下 插件 来验证你是否拥有域名:
| Plugin | Authenticator | Installer | Notes | Challenge types (and port) |
|---|---|---|---|---|
| Y | Y | Automates obtaining and installing a certificate with Apache. | ||
| Y | Y | Automates obtaining and installing a certificate with Nginx. | ||
| Y | N | Obtains a certificate by writing to the webroot directory of an already running webserver. | ||
| Y | N | Uses a “standalone” webserver to obtain a certificate. Requires port 80 to be available. This is useful on systems with no webserver, or when direct integration with the local webserver is not supported or not desired. | ||
| Y | N | This category of plugins automates obtaining a certificate by modifying DNS records to prove you have control over a domain. Doing domain validation in this way is the only way to obtain wildcard certificates from Let’s Encrypt. | ||
| Y | N | Helps you obtain a certificate by giving you instructions to perform domain validation yourself. Additionally allows you to specify scripts to automate the validation task in a customized way. | http-01 (80) or dns-01 (53) |
manual--preferred-challenges dns-01
[root@CentOS ~]# certbot certonly \ --manual --preferred-challenges dns-01 \ -d *.madmalls.com -d madmalls.com \ --server https://acme-staging-v02.api.letsencrypt.org/directory Saving debug log to /var/log/letsencrypt/letsencrypt.log Plugins selected: Authenticator manual, Installer None Enter email address (used for urgent renewal and security notices) (Enter 'c' to cancel): wangy8961@163.com # 用于接收证书快过期的提醒邮件或安全邮件 Starting new HTTPS connection (1): acme-staging-v02.api.letsencrypt.org - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Please read the Terms of Service at https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must agree in order to register with the ACME server at https://acme-staging-v02.api.letsencrypt.org/directory - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (A)gree/(C)ancel: A # 同意服务条款 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Would you be willing to share your email address with the Electronic Frontier Foundation, a founding partner of the Let's Encrypt project and the non-profit organization that develops Certbot? We'd like to send you email about our work encrypting the web, EFF news, campaigns, and ways to support digital freedom. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (Y)es/(N)o: N # 不公开我的邮箱地址 Obtaining a new certificate Performing the following challenges: dns-01 challenge for madmalls.com dns-01 challenge for madmalls.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - NOTE: The IP of this machine will be publicly logged as having requested this certificate. If you're running certbot in manual mode on a machine that is not your server, please ensure you're okay with that. Are you OK with your IP being logged? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (Y)es/(N)o: Y - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Please deploy a DNS TXT record under the name _acme-challenge.madmalls.com with the following value: gVC175mlzBZU--D7yFmbEhoZOUbPX6JSE8Tjmz_1kN0 # 到你的域名下添加一条 DNS TXT 记录 Before continuing, verify the record is deployed. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Press Enter to Continue
切记: 要先到你的域名下添加 DNS TXT 记录,并等它生效后才能敲回车继续
再打开一个新的 Shell 会话,确认 DNS 记录已生效:
[root@CentOS ~]# dig -t txt _acme-challenge.madmalls.com ; <<>> DiG 9.9.4-RedHat-9.9.4-37.el7 <<>> -t txt _acme-challenge.madmalls.com ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26864 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;_acme-challenge.madmalls.com. IN TXT ;; ANSWER SECTION: _acme-challenge.madmalls.com. 600 IN TXT "gVC175mlzBZU--D7yFmbEhoZOUbPX6JSE8Tjmz_1kN0" # 注意返回值是否一致
-d
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Press Enter to Continue Waiting for verification... Cleaning up challenges Resetting dropped connection: acme-staging-v02.api.letsencrypt.org Starting new HTTPS connection (2): acme-staging-v02.api.letsencrypt.org IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/madmalls.com/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/madmalls.com/privkey.pem Your cert will expire on 2019-09-05. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run "certbot renew" - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le
2. 自动化申请申请完证书后,请删除对应的 DNS TXT 记录!
APIcertbot-dns-cloudflarecertbot-dns-google
国内的 Aliyun 和腾讯云 DNS 也提供了 API 接口,我的 DNS 解析是用的阿里云的(控制台: https://dns.console.aliyun.com/ ),我们只需要调用如下两个接口即可:
Golang
AccessKeyRAM
我写好的代码放在 Github 上了:
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"os"
"time"
"github.com/aliyun/alibaba-cloud-sdk-go/services/alidns"
)
// Config 保存 accesskey 的结构体
type Config struct {
AccessKeyID string `json:"accessKeyID"`
AccessKeySecret string `json:"accessKeySecret"`
}
// 从 JSON 配置文件中读取 accesskey
func readJSONFile(filename string) Config {
var config Config
f, err := os.Open(filename)
defer f.Close()
if err != nil {
log.Fatalf("Faild to open the JSON file: %s", err)
}
dec := json.NewDecoder(f)
if err = dec.Decode(&config); err != nil {
log.Fatalf("Faild to parse the JSON file: %s", err)
}
return config
}
// 通过阿里云的 SDK 添加一条 DNS TXT 解析记录,返回记录的 RecordId,后续删除时需要用到它
func addDomainRecord(client *alidns.Client, domainName string, value string) {
request := alidns.CreateAddDomainRecordRequest()
request.DomainName = domainName
request.Type = "TXT"
request.RR = "_acme-challenge"
request.Value = value
response, err := client.AddDomainRecord(request)
if err != nil {
fmt.Print(err.Error())
}
fmt.Printf("[%s] Response from 'addDomainRecord()' is %v\n", time.Now().Format("2006-01-02 15:04:05"), response)
}
// 列出所有记录类型为 TXT,且记录名包含 '_acme-challenge' 的所有记录,返回 recordID 组成的切片,后续删除它们
func listDomainRecords(client *alidns.Client, domainName string) []string {
request := alidns.CreateDescribeDomainRecordsRequest()
request.DomainName = domainName
request.TypeKeyWord = "TXT"
request.RRKeyWord = "_acme-challenge"
response, err := client.DescribeDomainRecords(request)
if err != nil {
fmt.Print(err.Error())
}
fmt.Printf("[%s] Response from 'listDomainRecords()' is %v\n", time.Now().Format("2006-01-02 15:04:05"), response)
var recordIds []string
for _, r := range response.DomainRecords.Record {
recordIds = append(recordIds, r.RecordId)
}
return recordIds
}
// 删除解析记录
func deleteDomainRecord(client *alidns.Client, recordID string) {
request := alidns.CreateDeleteDomainRecordRequest()
request.RecordId = recordID
response, err := client.DeleteDomainRecord(request)
if err != nil {
fmt.Print(err.Error())
}
fmt.Printf("[%s] Response from 'deleteDomainRecord()' is %v\n", time.Now().Format("2006-01-02 15:04:05"), response)
}
func main() {
// 提供 -c 选项,用户可以指定JSON配置文件。注意,cfg 是一个指针
cfg := flag.String("c", "config.json", "Assign the JSON config file")
// 操作类型,authenticator: 域名认证,添加 DNS TXT 记录; cleanup: 认证通过后,删除此 DNS TXT 记录
opt := flag.String("o", "authenticator", "Operate: authenticator or cleanup")
// 提供 -h 选项,查看命令行帮助信息
help := flag.Bool("h", false, "show help infomation")
flag.Parse()
if *help {
flag.Usage()
os.Exit(0)
}
// 解析JSON配置文件
config := readJSONFile(*cfg)
// Client for Aliyun DNS SDK
client, err := alidns.NewClientWithAccessKey("cn-hangzhou", config.AccessKeyID, config.AccessKeySecret)
if err != nil {
log.Fatal(err.Error())
}
// 判断操作类型
switch *opt {
case "authenticator":
// CERTBOT_DOMAIN 和 CERTBOT_VALIDATION 是 Certbot Hooks 传过来的环境变量
domainName := os.Getenv("CERTBOT_DOMAIN")
value := os.Getenv("CERTBOT_VALIDATION")
if domainName == "" || value == "" {
log.Fatal("Error: This plugin can only be used for 'certbot' (Let's Encrypt)")
}
addDomainRecord(client, domainName, value)
// Sleep to make sure the change has time to propagate over to DNS
time.Sleep(30 * time.Second)
/* 否则报错:
Attempting to renew cert (madmalls.com) from /etc/letsencrypt/renewal/madmalls.com.conf produced an unexpected error: Failed authorization procedure. madmalls.com (dns-01): urn:ietf:params:acme:error:dns :: DNS problem: NXDOMAIN looking up TXT for _acme-challenge.madmalls.com - check that a DNS record exists for this domain. Skipping.All renewal attempts failed. The following certs could not be renewed:
/etc/letsencrypt/live/madmalls.com/fullchain.pem (failure)
*/
case "cleanup":
// 先获取所有记录类型为 TXT,且记录名包含 '_acme-challenge' 的记录 ID
recordIds := listDomainRecords(client, os.Getenv("CERTBOT_DOMAIN"))
fmt.Printf("[%s] All record Ids that need to delete is: %v\n", time.Now().Format("2006-01-02 15:04:05"), recordIds)
// 循环,删除它们
for _, id := range recordIds {
deleteDomainRecord(client, id)
}
}
}
编译:
[root@CentOS ~]# go build -o certbot-dns-aliyun main.go
certbot-dns-aliyun/etc/letsencrypt/config.json
{
"accessKeyID": "你的AccessKeyID",
"accessKeySecret": "你的AccessKeySecret"
}
2.1 RSA 通配符证书
[root@CentOS ~]# certbot certonly \ --non-interactive \ --email wangy8961@163.com \ --agree-tos \ --manual-public-ip-logging-ok \ --manual --preferred-challenges dns-01 \ --manual-auth-hook "/etc/letsencrypt/certbot-dns-aliyun -o authenticator" \ --manual-cleanup-hook "/etc/letsencrypt/certbot-dns-aliyun -o cleanup" \ -d *.madmalls.com -d madmalls.com \ --server https://acme-v02.api.letsencrypt.org/directory
--email-d
--server https://acme-staging-v02.api.letsencrypt.org/directory
验证证书的内容:
[root@CentOS ~]# openssl x509 -text -in /etc/letsencrypt/live/madmalls.com/fullchain.pem -noout
Certificate:
Data:
Version: 3 (0x2)