mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-03 07:06:36 +08:00
[优化] 重构 ACME 客户端逻辑,简化证书获取和 DNS 提供者设置
This commit is contained in:
@@ -1,222 +1,13 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"openflare/model"
|
"openflare/model"
|
||||||
|
"openflare/utils/acme"
|
||||||
"github.com/go-acme/lego/v4/acme"
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
|
||||||
"github.com/go-acme/lego/v4/certificate"
|
|
||||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
|
||||||
"github.com/go-acme/lego/v4/lego"
|
|
||||||
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
|
|
||||||
"github.com/go-acme/lego/v4/registration"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AcmeUser struct {
|
|
||||||
Email string
|
|
||||||
Registration *registration.Resource
|
|
||||||
key crypto.PrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *AcmeUser) GetEmail() string {
|
|
||||||
return u.Email
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *AcmeUser) GetRegistration() *registration.Resource {
|
|
||||||
return u.Registration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *AcmeUser) GetPrivateKey() crypto.PrivateKey {
|
|
||||||
return u.key
|
|
||||||
}
|
|
||||||
|
|
||||||
func parsePrivateKey(pemData string) (crypto.PrivateKey, error) {
|
|
||||||
block, _ := pem.Decode([]byte(pemData))
|
|
||||||
if block == nil {
|
|
||||||
return nil, errors.New("failed to parse PEM block containing the key")
|
|
||||||
}
|
|
||||||
|
|
||||||
if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil {
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
if key, err := x509.ParseECPrivateKey(block.Bytes); err == nil {
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("failed to parse private key")
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodePrivateKey(key crypto.PrivateKey) (string, error) {
|
|
||||||
var pemBlock *pem.Block
|
|
||||||
switch k := key.(type) {
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
pemBlock = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
b, err := x509.MarshalECPrivateKey(k)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
pemBlock = &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
|
|
||||||
default:
|
|
||||||
return "", errors.New("unsupported key type")
|
|
||||||
}
|
|
||||||
return string(pem.EncodeToMemory(pemBlock)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetOrCreateLegoClient(account *model.AcmeAccount, keyAlgorithm string) (*lego.Client, *AcmeUser, error) {
|
|
||||||
var privateKey crypto.PrivateKey
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if account.PrivateKey == "" {
|
|
||||||
privateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
pemStr, err := encodePrivateKey(privateKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
account.PrivateKey = pemStr
|
|
||||||
// Don't save it to DB yet, wait for successful registration.
|
|
||||||
} else {
|
|
||||||
privateKey, err = parsePrivateKey(account.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
user := &AcmeUser{
|
|
||||||
Email: account.Email,
|
|
||||||
key: privateKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
if account.URL != "" {
|
|
||||||
user.Registration = ®istration.Resource{
|
|
||||||
Body: acme.Account{
|
|
||||||
Status: "valid",
|
|
||||||
Contact: []string{"mailto:" + account.Email},
|
|
||||||
},
|
|
||||||
URI: account.URL,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
config := lego.NewConfig(user)
|
|
||||||
// Use Let's Encrypt production environment by default
|
|
||||||
config.CADirURL = lego.LEDirectoryProduction
|
|
||||||
|
|
||||||
switch keyAlgorithm {
|
|
||||||
case "RSA2048":
|
|
||||||
config.Certificate.KeyType = certcrypto.RSA2048
|
|
||||||
case "RSA4096":
|
|
||||||
config.Certificate.KeyType = certcrypto.RSA4096
|
|
||||||
case "EC256":
|
|
||||||
config.Certificate.KeyType = certcrypto.EC256
|
|
||||||
case "EC384":
|
|
||||||
config.Certificate.KeyType = certcrypto.EC384
|
|
||||||
default:
|
|
||||||
config.Certificate.KeyType = certcrypto.RSA2048
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := lego.NewClient(config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if account.URL == "" {
|
|
||||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
user.Registration = reg
|
|
||||||
account.URL = reg.URI
|
|
||||||
if account.ID == 0 {
|
|
||||||
err = model.DB.Create(account).Error
|
|
||||||
} else {
|
|
||||||
err = model.DB.Save(account).Error
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetupDNSProvider(client *lego.Client, dnsAccount *model.DnsAccount, dns1, dns2 string, disableCNAME, skipDNS bool) error {
|
|
||||||
var provider challengeProvider
|
|
||||||
|
|
||||||
switch dnsAccount.Type {
|
|
||||||
case "cloudflare":
|
|
||||||
var creds map[string]string
|
|
||||||
if err := json.Unmarshal([]byte(dnsAccount.Authorization), &creds); err != nil {
|
|
||||||
return fmt.Errorf("failed to parse cloudflare credentials: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config := cloudflare.NewDefaultConfig()
|
|
||||||
config.AuthToken = creds["api_token"]
|
|
||||||
|
|
||||||
p, err := cloudflare.NewDNSProviderConfig(config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
provider = p
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported DNS provider: %s", dnsAccount.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can use custom DNS servers to verify challenges if provided
|
|
||||||
var resolvers []string
|
|
||||||
if dns1 != "" {
|
|
||||||
resolvers = append(resolvers, dns1+":53")
|
|
||||||
}
|
|
||||||
if dns2 != "" {
|
|
||||||
resolvers = append(resolvers, dns2+":53")
|
|
||||||
}
|
|
||||||
|
|
||||||
var opts []dns01.ChallengeOption
|
|
||||||
|
|
||||||
if len(resolvers) > 0 {
|
|
||||||
opts = append(opts, dns01.AddRecursiveNameservers(resolvers))
|
|
||||||
}
|
|
||||||
|
|
||||||
if disableCNAME {
|
|
||||||
opts = append(opts, dns01.DisableCompletePropagationRequirement())
|
|
||||||
}
|
|
||||||
|
|
||||||
if skipDNS {
|
|
||||||
opts = append(opts, dns01.WrapPreCheck(func(domain, fqdn, value string, check dns01.PreCheckFunc) (bool, error) {
|
|
||||||
// If we skip the local DNS check entirely, we might trigger Let's Encrypt to verify
|
|
||||||
// BEFORE Cloudflare's edge servers have actually synced the TXT record (which takes 5-15 seconds).
|
|
||||||
// So we add a safe 20-second artificial delay before forcing the true return.
|
|
||||||
time.Sleep(20 * time.Second)
|
|
||||||
return true, nil
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.Challenge.SetDNS01Provider(provider, opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// challengeProvider interface helps to bypass the strict type definition of SetDNS01Provider
|
|
||||||
type challengeProvider interface {
|
|
||||||
Present(domain, token, keyAuth string) error
|
|
||||||
CleanUp(domain, token, keyAuth string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func ObtainSSL(cert *model.TLSCertificate) error {
|
func ObtainSSL(cert *model.TLSCertificate) error {
|
||||||
cert.ApplyStatus = "applying"
|
cert.ApplyStatus = "applying"
|
||||||
model.DB.Save(cert)
|
model.DB.Save(cert)
|
||||||
@@ -240,18 +31,6 @@ func ObtainSSL(cert *model.TLSCertificate) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
client, _, err := GetOrCreateLegoClient(acmeAccount, cert.KeyAlgorithm)
|
|
||||||
if err != nil {
|
|
||||||
updateCertError(cert, fmt.Sprintf("Failed to create ACME client: %v", err))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = SetupDNSProvider(client, dnsAccount, cert.DNS1, cert.DNS2, cert.DisableCNAME, cert.SkipDNS)
|
|
||||||
if err != nil {
|
|
||||||
updateCertError(cert, fmt.Sprintf("Failed to setup DNS provider: %v", err))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
domains := []string{cert.PrimaryDomain}
|
domains := []string{cert.PrimaryDomain}
|
||||||
if cert.OtherDomains != "" {
|
if cert.OtherDomains != "" {
|
||||||
for _, d := range strings.Split(cert.OtherDomains, "\n") {
|
for _, d := range strings.Split(cert.OtherDomains, "\n") {
|
||||||
@@ -262,32 +41,56 @@ func ObtainSSL(cert *model.TLSCertificate) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
request := certificate.ObtainRequest{
|
newAccountURL, newPrivateKeyPEM, result, err := acme.ObtainSSL(
|
||||||
Domains: domains,
|
acmeAccount.Email,
|
||||||
Bundle: true,
|
acmeAccount.PrivateKey,
|
||||||
|
acmeAccount.URL,
|
||||||
|
dnsAccount.Type,
|
||||||
|
dnsAccount.Authorization,
|
||||||
|
cert.DNS1,
|
||||||
|
cert.DNS2,
|
||||||
|
cert.DisableCNAME,
|
||||||
|
cert.SkipDNS,
|
||||||
|
cert.KeyAlgorithm,
|
||||||
|
domains,
|
||||||
|
)
|
||||||
|
|
||||||
|
// If new key or URL was generated, save them to the DB
|
||||||
|
if (newPrivateKeyPEM != "" && acmeAccount.PrivateKey != newPrivateKeyPEM) || (newAccountURL != "" && acmeAccount.URL != newAccountURL) {
|
||||||
|
if newPrivateKeyPEM != "" {
|
||||||
|
acmeAccount.PrivateKey = newPrivateKeyPEM
|
||||||
|
}
|
||||||
|
if newAccountURL != "" {
|
||||||
|
acmeAccount.URL = newAccountURL
|
||||||
|
}
|
||||||
|
if acmeAccount.ID == 0 {
|
||||||
|
if dbErr := model.DB.Create(acmeAccount).Error; dbErr != nil {
|
||||||
|
updateCertError(cert, fmt.Sprintf("Failed to create ACME account: %v", dbErr))
|
||||||
|
return dbErr
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if dbErr := model.DB.Save(acmeAccount).Error; dbErr != nil {
|
||||||
|
updateCertError(cert, fmt.Sprintf("Failed to save ACME account: %v", dbErr))
|
||||||
|
return dbErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Self-heal the cert
|
||||||
|
cert.AcmeAccountID = acmeAccount.ID
|
||||||
|
model.DB.Save(cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
certificates, err := client.Certificate.Obtain(request)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
updateCertError(cert, fmt.Sprintf("Failed to obtain certificate: %v", err))
|
updateCertError(cert, err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cert.CertPEM = string(certificates.Certificate)
|
cert.CertPEM = result.CertPEM
|
||||||
cert.KeyPEM = string(certificates.PrivateKey)
|
cert.KeyPEM = result.KeyPEM
|
||||||
|
cert.NotBefore = result.NotBefore
|
||||||
// Parse validity dates
|
cert.NotAfter = result.NotAfter
|
||||||
certBlock, _ := pem.Decode(certificates.Certificate)
|
|
||||||
if certBlock != nil {
|
|
||||||
parsedCert, err := x509.ParseCertificate(certBlock.Bytes)
|
|
||||||
if err == nil {
|
|
||||||
cert.NotBefore = parsedCert.NotBefore
|
|
||||||
cert.NotAfter = parsedCert.NotAfter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cert.ApplyStatus = "ready"
|
cert.ApplyStatus = "ready"
|
||||||
cert.ApplyMessage = ""
|
cert.ApplyMessage = ""
|
||||||
|
|
||||||
return model.DB.Save(cert).Error
|
return model.DB.Save(cert).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,256 @@
|
|||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/acme"
|
||||||
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
|
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||||
|
"github.com/go-acme/lego/v4/lego"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
|
||||||
|
"github.com/go-acme/lego/v4/registration"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AcmeUser struct {
|
||||||
|
Email string
|
||||||
|
Registration *registration.Resource
|
||||||
|
key crypto.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *AcmeUser) GetEmail() string {
|
||||||
|
return u.Email
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *AcmeUser) GetRegistration() *registration.Resource {
|
||||||
|
return u.Registration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *AcmeUser) GetPrivateKey() crypto.PrivateKey {
|
||||||
|
return u.key
|
||||||
|
}
|
||||||
|
|
||||||
|
type CertificateResult struct {
|
||||||
|
CertPEM string
|
||||||
|
KeyPEM string
|
||||||
|
NotBefore time.Time
|
||||||
|
NotAfter time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePrivateKey(pemData string) (crypto.PrivateKey, error) {
|
||||||
|
block, _ := pem.Decode([]byte(pemData))
|
||||||
|
if block == nil {
|
||||||
|
return nil, errors.New("failed to parse PEM block containing the key")
|
||||||
|
}
|
||||||
|
|
||||||
|
if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
if key, err := x509.ParseECPrivateKey(block.Bytes); err == nil {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("failed to parse private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodePrivateKey(key crypto.PrivateKey) (string, error) {
|
||||||
|
var pemBlock *pem.Block
|
||||||
|
switch k := key.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
pemBlock = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
b, err := x509.MarshalECPrivateKey(k)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
pemBlock = &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
|
||||||
|
default:
|
||||||
|
return "", errors.New("unsupported key type")
|
||||||
|
}
|
||||||
|
return string(pem.EncodeToMemory(pemBlock)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetOrCreateLegoClient(acmeEmail, privateKeyPEM, accountURL string, keyAlgorithm string) (*lego.Client, *AcmeUser, string, string, error) {
|
||||||
|
var privateKey crypto.PrivateKey
|
||||||
|
var err error
|
||||||
|
var newPrivateKeyPEM string
|
||||||
|
var newAccountURL string
|
||||||
|
|
||||||
|
if privateKeyPEM == "" {
|
||||||
|
privateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, "", "", err
|
||||||
|
}
|
||||||
|
pemStr, err := encodePrivateKey(privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, "", "", err
|
||||||
|
}
|
||||||
|
newPrivateKeyPEM = pemStr
|
||||||
|
} else {
|
||||||
|
privateKey, err = parsePrivateKey(privateKeyPEM)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, "", "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &AcmeUser{
|
||||||
|
Email: acmeEmail,
|
||||||
|
key: privateKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
if accountURL != "" {
|
||||||
|
user.Registration = ®istration.Resource{
|
||||||
|
Body: acme.Account{
|
||||||
|
Status: "valid",
|
||||||
|
Contact: []string{"mailto:" + acmeEmail},
|
||||||
|
},
|
||||||
|
URI: accountURL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config := lego.NewConfig(user)
|
||||||
|
// Use Let's Encrypt production environment by default
|
||||||
|
config.CADirURL = lego.LEDirectoryProduction
|
||||||
|
|
||||||
|
switch keyAlgorithm {
|
||||||
|
case "RSA2048":
|
||||||
|
config.Certificate.KeyType = certcrypto.RSA2048
|
||||||
|
case "RSA4096":
|
||||||
|
config.Certificate.KeyType = certcrypto.RSA4096
|
||||||
|
case "EC256":
|
||||||
|
config.Certificate.KeyType = certcrypto.EC256
|
||||||
|
case "EC384":
|
||||||
|
config.Certificate.KeyType = certcrypto.EC384
|
||||||
|
default:
|
||||||
|
config.Certificate.KeyType = certcrypto.RSA2048
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := lego.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if accountURL == "" {
|
||||||
|
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, "", "", err
|
||||||
|
}
|
||||||
|
user.Registration = reg
|
||||||
|
newAccountURL = reg.URI
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, user, newPrivateKeyPEM, newAccountURL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetupDNSProvider(client *lego.Client, dnsType, dnsAuth string, dns1, dns2 string, disableCNAME, skipDNS bool) error {
|
||||||
|
var provider challengeProvider
|
||||||
|
|
||||||
|
switch dnsType {
|
||||||
|
case "cloudflare":
|
||||||
|
var creds map[string]string
|
||||||
|
if err := json.Unmarshal([]byte(dnsAuth), &creds); err != nil {
|
||||||
|
return fmt.Errorf("failed to parse cloudflare credentials: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := cloudflare.NewDefaultConfig()
|
||||||
|
config.AuthToken = creds["api_token"]
|
||||||
|
|
||||||
|
p, err := cloudflare.NewDNSProviderConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
provider = p
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported DNS provider: %s", dnsType)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolvers []string
|
||||||
|
if dns1 != "" {
|
||||||
|
resolvers = append(resolvers, dns1+":53")
|
||||||
|
}
|
||||||
|
if dns2 != "" {
|
||||||
|
resolvers = append(resolvers, dns2+":53")
|
||||||
|
}
|
||||||
|
|
||||||
|
var opts []dns01.ChallengeOption
|
||||||
|
|
||||||
|
if len(resolvers) > 0 {
|
||||||
|
opts = append(opts, dns01.AddRecursiveNameservers(resolvers))
|
||||||
|
}
|
||||||
|
|
||||||
|
if disableCNAME {
|
||||||
|
opts = append(opts, dns01.DisableCompletePropagationRequirement())
|
||||||
|
}
|
||||||
|
|
||||||
|
if skipDNS {
|
||||||
|
opts = append(opts, dns01.WrapPreCheck(func(domain, fqdn, value string, check dns01.PreCheckFunc) (bool, error) {
|
||||||
|
time.Sleep(20 * time.Second)
|
||||||
|
return true, nil
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.Challenge.SetDNS01Provider(provider, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type challengeProvider interface {
|
||||||
|
Present(domain, token, keyAuth string) error
|
||||||
|
CleanUp(domain, token, keyAuth string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func ObtainSSL(
|
||||||
|
acmeEmail, acmePrivateKeyPEM, acmeURL string,
|
||||||
|
dnsType, dnsAuth string,
|
||||||
|
dns1, dns2 string,
|
||||||
|
disableCNAME, skipDNS bool,
|
||||||
|
keyAlgorithm string,
|
||||||
|
domains []string,
|
||||||
|
) (string, string, *CertificateResult, error) {
|
||||||
|
client, _, newPrivateKeyPEM, newAccountURL, err := GetOrCreateLegoClient(acmeEmail, acmePrivateKeyPEM, acmeURL, keyAlgorithm)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", nil, fmt.Errorf("failed to create ACME client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = SetupDNSProvider(client, dnsType, dnsAuth, dns1, dns2, disableCNAME, skipDNS)
|
||||||
|
if err != nil {
|
||||||
|
return newAccountURL, newPrivateKeyPEM, nil, fmt.Errorf("failed to setup DNS provider: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
request := certificate.ObtainRequest{
|
||||||
|
Domains: domains,
|
||||||
|
Bundle: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
certificates, err := client.Certificate.Obtain(request)
|
||||||
|
if err != nil {
|
||||||
|
return newAccountURL, newPrivateKeyPEM, nil, fmt.Errorf("failed to obtain certificate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &CertificateResult{
|
||||||
|
CertPEM: string(certificates.Certificate),
|
||||||
|
KeyPEM: string(certificates.PrivateKey),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse validity dates
|
||||||
|
certBlock, _ := pem.Decode(certificates.Certificate)
|
||||||
|
if certBlock != nil {
|
||||||
|
parsedCert, err := x509.ParseCertificate(certBlock.Bytes)
|
||||||
|
if err == nil {
|
||||||
|
result.NotBefore = parsedCert.NotBefore
|
||||||
|
result.NotAfter = parsedCert.NotAfter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newAccountURL, newPrivateKeyPEM, result, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user