Golang实现ACME协议的DNS示例

Golang实现ACME协议的DNS示例 我正在寻找使用ACME协议进行DNS验证的实际示例。我找到了很多使用HTTP验证的例子,但一个DNS验证的都没有。有没有人实际可用的代码或者好的实践示例?

我已经阅读了这个包的GoDoc文档,但帮助不大。

2 回复

我从 Let’s Encrypt 论坛得到了回复:

Let's Encrypt Community Support

使用 DNS 验证的 Golang 示例

哪个包?/x/crypto/acme 只支持 ACME v1,不支持通配符。尝试使用 github.com/eggsampler/acmegithub.com/xenolf/lego 来获取 ACME v2 库。但即使使用 v1 包,使用 DNS 验证也只需要很小的改动…

更多关于Golang实现ACME协议的DNS示例的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


以下是一个使用Go语言实现ACME协议DNS验证的完整示例,基于golang.org/x/crypto/acme包。这个示例展示了如何通过DNS-01挑战完成证书颁发过程。

package main

import (
	"context"
	"crypto"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"fmt"
	"log"
	"time"

	"golang.org/x/crypto/acme"
)

// DNSProvider 接口定义了DNS记录操作的方法
type DNSProvider interface {
	// 设置TXT记录
	SetTXTRecord(ctx context.Context, domain, value string) error
	// 清理TXT记录
	CleanupTXTRecord(ctx context.Context, domain string) error
}

// ACMEClient 封装ACME客户端和DNS操作
type ACMEClient struct {
	client      *acme.Client
	privateKey  crypto.Signer
	dnsProvider DNSProvider
}

// NewACMEClient 创建新的ACME客户端
func NewACMEClient(dnsProvider DNSProvider) (*ACMEClient, error) {
	privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return nil, fmt.Errorf("生成私钥失败: %v", err)
	}

	client := &acme.Client{
		Key:          privateKey,
		DirectoryURL: "https://acme-staging-v02.api.letsencrypt.org/directory", // 测试环境
	}

	return &ACMEClient{
		client:      client,
		privateKey:  privateKey,
		dnsProvider: dnsProvider,
	}, nil
}

// RequestCertificate 请求证书
func (a *ACMEClient) RequestCertificate(ctx context.Context, domain string) error {
	// 创建ACME账户
	account := &acme.Account{
		Contact: []string{"mailto:admin@example.com"},
	}
	
	if _, err := a.client.Register(ctx, account, acme.AcceptTOS); err != nil {
		return fmt.Errorf("注册ACME账户失败: %v", err)
	}

	// 授权请求
	authz, err := a.client.Authorize(ctx, domain)
	if err != nil {
		return fmt.Errorf("获取授权失败: %v", err)
	}

	// 查找DNS-01挑战
	var challenge *acme.Challenge
	for _, c := range authz.Challenges {
		if c.Type == "dns-01" {
			challenge = c
			break
		}
	}
	if challenge == nil {
		return fmt.Errorf("未找到DNS挑战")
	}

	// 生成DNS挑战记录值
	keyAuth, err := a.client.DNS01ChallengeRecord(challenge.Token)
	if err != nil {
		return fmt.Errorf("生成挑战记录失败: %v", err)
	}

	// DNS记录名称
	dnsRecord := "_acme-challenge." + domain

	// 设置DNS TXT记录
	if err := a.dnsProvider.SetTXTRecord(ctx, dnsRecord, keyAuth); err != nil {
		return fmt.Errorf("设置DNS记录失败: %v", err)
	}
	defer a.dnsProvider.CleanupTXTRecord(ctx, dnsRecord)

	// 等待DNS传播
	time.Sleep(30 * time.Second)

	// 接受挑战
	if _, err := a.client.Accept(ctx, challenge); err != nil {
		return fmt.Errorf("接受挑战失败: %v", err)
	}

	// 等待授权完成
	if _, err := a.client.WaitAuthorization(ctx, authz.URI); err != nil {
		return fmt.Errorf("等待授权完成失败: %v", err)
	}

	// 创建证书请求
	csr, err := a.generateCSR(domain)
	if err != nil {
		return fmt.Errorf("生成CSR失败: %v", err)
	}

	// 请求证书
	cert, _, err := a.client.CreateCert(ctx, csr, 0, true)
	if err != nil {
		return fmt.Errorf("创建证书失败: %v", err)
	}

	fmt.Printf("证书获取成功! 证书数量: %d\n", len(cert))
	return nil
}

// generateCSR 生成证书签名请求
func (a *ACMEClient) generateCSR(domain string) ([]byte, error) {
	template := &x509.CertificateRequest{
		Subject:  pkix.Name{CommonName: domain},
		DNSNames: []string{domain},
	}
	return x509.CreateCertificateRequest(rand.Reader, template, a.privateKey)
}

// 示例DNS提供者实现
type ExampleDNSProvider struct{}

func (e *ExampleDNSProvider) SetTXTRecord(ctx context.Context, domain, value string) error {
	// 这里实现具体的DNS记录设置逻辑
	// 例如使用Cloudflare、AWS Route53等DNS服务的API
	fmt.Printf("设置DNS记录: %s -> %s\n", domain, value)
	return nil
}

func (e *ExampleDNSProvider) CleanupTXTRecord(ctx context.Context, domain string) error {
	// 清理DNS记录
	fmt.Printf("清理DNS记录: %s\n", domain)
	return nil
}

func main() {
	ctx := context.Background()
	dnsProvider := &ExampleDNSProvider{}
	
	client, err := NewACMEClient(dnsProvider)
	if err != nil {
		log.Fatalf("创建ACME客户端失败: %v", err)
	}

	domain := "example.com"
	if err := client.RequestCertificate(ctx, domain); err != nil {
		log.Fatalf("请求证书失败: %v", err)
	}
}

这个示例包含以下关键组件:

  1. DNSProvider接口:定义DNS记录操作的标准方法
  2. ACMEClient结构体:封装ACME客户端逻辑
  3. 完整的DNS-01挑战流程
    • 注册ACME账户
    • 获取域名授权
    • 设置DNS TXT记录
    • 接受挑战并等待验证
    • 获取证书

你需要根据实际使用的DNS服务商实现SetTXTRecordCleanupTXTRecord方法。对于生产环境,建议使用正式的ACME端点(移除-staging)。

对于具体的DNS服务商实现,可以参考相应的Go SDK,如:

  • Cloudflare: github.com/cloudflare/cloudflare-go
  • AWS Route53: github.com/aws/aws-sdk-go-v2/service/route53
  • Google Cloud DNS: cloud.google.com/dns/docs/reference/libraries
回到顶部