mirror of
https://github.com/TeaOSLab/EdgeAPI.git
synced 2025-11-03 15:00:27 +08:00
[SSL证书]实现基本的自动申请证书流程
This commit is contained in:
59
internal/acme/dns_provider.go
Normal file
59
internal/acme/dns_provider.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/errors"
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type DNSProvider struct {
|
||||
raw dnsclients.ProviderInterface
|
||||
}
|
||||
|
||||
func NewDNSProvider(raw dnsclients.ProviderInterface) *DNSProvider {
|
||||
return &DNSProvider{raw: raw}
|
||||
}
|
||||
|
||||
func (this *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// 设置记录
|
||||
index := strings.Index(fqdn, "."+domain)
|
||||
if index < 0 {
|
||||
return errors.New("invalid fqdn value")
|
||||
}
|
||||
recordName := fqdn[:index]
|
||||
record, err := this.raw.QueryRecord(domain, recordName, dnsclients.RecordTypeTXT)
|
||||
if err != nil {
|
||||
return errors.New("query DNS record failed: " + err.Error())
|
||||
}
|
||||
if record == nil {
|
||||
err = this.raw.AddRecord(domain, &dnsclients.Record{
|
||||
Id: "",
|
||||
Name: recordName,
|
||||
Type: dnsclients.RecordTypeTXT,
|
||||
Value: value,
|
||||
Route: this.raw.DefaultRoute(),
|
||||
})
|
||||
if err != nil {
|
||||
return errors.New("create DNS record failed: " + err.Error())
|
||||
}
|
||||
} else {
|
||||
err = this.raw.UpdateRecord(domain, record, &dnsclients.Record{
|
||||
Name: recordName,
|
||||
Type: dnsclients.RecordTypeTXT,
|
||||
Value: value,
|
||||
Route: this.raw.DefaultRoute(),
|
||||
})
|
||||
if err != nil {
|
||||
return errors.New("update DNS record failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
return nil
|
||||
}
|
||||
15
internal/acme/key.go
Normal file
15
internal/acme/key.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
func ParsePrivateKeyFromBase64(base64String string) (interface{}, error) {
|
||||
data, err := base64.StdEncoding.DecodeString(base64String)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return x509.ParsePKCS8PrivateKey(data)
|
||||
}
|
||||
94
internal/acme/request.go
Normal file
94
internal/acme/request.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/errors"
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
acmelog "github.com/go-acme/lego/v4/log"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
debug bool
|
||||
|
||||
task *Task
|
||||
}
|
||||
|
||||
func NewRequest(task *Task) *Request {
|
||||
return &Request{
|
||||
task: task,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Request) Debug() {
|
||||
this.debug = true
|
||||
}
|
||||
|
||||
func (this *Request) Run() (certData []byte, keyData []byte, err error) {
|
||||
if !this.debug {
|
||||
acmelog.Logger = log.New(ioutil.Discard, "", log.LstdFlags)
|
||||
}
|
||||
|
||||
if this.task.User == nil {
|
||||
err = errors.New("'user' must not be nil")
|
||||
return
|
||||
}
|
||||
if this.task.DNSProvider == nil {
|
||||
err = errors.New("'dnsProvider' must not be nil")
|
||||
return
|
||||
}
|
||||
if len(this.task.DNSDomain) == 0 {
|
||||
err = errors.New("'dnsDomain' must not be empty")
|
||||
return
|
||||
}
|
||||
if len(this.task.Domains) == 0 {
|
||||
err = errors.New("'domains' must not be empty")
|
||||
return
|
||||
}
|
||||
|
||||
config := lego.NewConfig(this.task.User)
|
||||
config.Certificate.KeyType = certcrypto.RSA2048
|
||||
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// 注册用户
|
||||
resource := this.task.User.GetRegistration()
|
||||
if resource != nil {
|
||||
resource, err = client.Registration.QueryRegistration()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
} else {
|
||||
resource, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = this.task.User.Register(resource)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = client.Challenge.SetDNS01Provider(NewDNSProvider(this.task.DNSProvider))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// 申请证书
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: this.task.Domains,
|
||||
Bundle: true,
|
||||
}
|
||||
certResource, err := client.Certificate.Obtain(request)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return certResource.Certificate, certResource.PrivateKey, nil
|
||||
}
|
||||
74
internal/acme/request_test.go
Normal file
74
internal/acme/request_test.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"github.com/iwind/TeaGo/dbs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewRequest(t *testing.T) {
|
||||
privateKey, err := ParsePrivateKeyFromBase64("MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgD3xxDXP4YVqHCfub21Yi3QL1Kvgow23J8CKJ7vU3L4+hRANCAARRl5ZKAlgGRc5RETSMYFCTXvjnePDgjALWgtgfClQGLB2rGyRecJvlesAM6Q7LQrDxVxvxdSQQmPGRqJGiBtjd")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
user := NewUser("19644627@qq.com", privateKey, func(resource *registration.Resource) error {
|
||||
resourceJSON, err := json.Marshal(resource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.Log(string(resourceJSON))
|
||||
return nil
|
||||
})
|
||||
|
||||
regResource := []byte(`{"body":{"status":"valid","contact":["mailto:19644627@qq.com"]},"uri":"https://acme-v02.api.letsencrypt.org/acme/acct/103672877"}`)
|
||||
err = user.SetRegistration(regResource)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dnsProvider, err := testDNSPodProvider()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req := NewRequest(&Task{
|
||||
User: user,
|
||||
DNSProvider: dnsProvider,
|
||||
DNSDomain: "yun4s.cn",
|
||||
Domains: []string{"yun4s.cn"},
|
||||
})
|
||||
certData, keyData, err := req.Run()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("cert:", string(certData))
|
||||
t.Log("key:", string(keyData))
|
||||
}
|
||||
|
||||
func testDNSPodProvider() (dnsclients.ProviderInterface, error) {
|
||||
db, err := dbs.Default()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
one, err := db.FindOne("SELECT * FROM edgeDNSProviders WHERE type='dnspod' ORDER BY id DESC")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apiParams := maps.Map{}
|
||||
err = json.Unmarshal([]byte(one.GetString("apiParams")), &apiParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
provider := &dnsclients.DNSPodProvider{}
|
||||
err = provider.Auth(apiParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return provider, nil
|
||||
}
|
||||
10
internal/acme/task.go
Normal file
10
internal/acme/task.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package acme
|
||||
|
||||
import "github.com/TeaOSLab/EdgeAPI/internal/dnsclients"
|
||||
|
||||
type Task struct {
|
||||
User *User
|
||||
DNSProvider dnsclients.ProviderInterface
|
||||
DNSDomain string
|
||||
Domains []string
|
||||
}
|
||||
49
internal/acme/user.go
Normal file
49
internal/acme/user.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"encoding/json"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
email string
|
||||
resource *registration.Resource
|
||||
key crypto.PrivateKey
|
||||
registerFunc func(resource *registration.Resource) error
|
||||
}
|
||||
|
||||
func NewUser(email string, key crypto.PrivateKey, registerFunc func(resource *registration.Resource) error) *User {
|
||||
return &User{
|
||||
email: email,
|
||||
key: key,
|
||||
registerFunc: registerFunc,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *User) GetEmail() string {
|
||||
return this.email
|
||||
}
|
||||
|
||||
func (this *User) GetRegistration() *registration.Resource {
|
||||
return this.resource
|
||||
}
|
||||
|
||||
func (this *User) SetRegistration(resourceData []byte) error {
|
||||
resource := ®istration.Resource{}
|
||||
err := json.Unmarshal(resourceData, resource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.resource = resource
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *User) GetPrivateKey() crypto.PrivateKey {
|
||||
return this.key
|
||||
}
|
||||
|
||||
func (this *User) Register(resource *registration.Resource) error {
|
||||
this.resource = resource
|
||||
return this.registerFunc(resource)
|
||||
}
|
||||
Reference in New Issue
Block a user