mirror of
https://github.com/TeaOSLab/EdgeAPI.git
synced 2025-11-03 06:40:26 +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)
|
||||
}
|
||||
@@ -2,10 +2,15 @@ package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/acme"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/dbs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
)
|
||||
|
||||
@@ -131,7 +136,6 @@ func (this *ACMETaskDAO) CreateACMETask(adminId int64, userId int64, acmeUserId
|
||||
op.AutoRenew = autoRenew
|
||||
op.IsOn = true
|
||||
op.State = ACMETaskStateEnabled
|
||||
op.IsOk = false
|
||||
_, err := this.Save(op)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -173,3 +177,179 @@ func (this *ACMETaskDAO) CheckACMETask(adminId int64, userId int64, acmeTaskId i
|
||||
Pk(acmeTaskId).
|
||||
Exist()
|
||||
}
|
||||
|
||||
// 设置任务关联的证书
|
||||
func (this *ACMETaskDAO) UpdateACMETaskCert(taskId int64, certId int64) error {
|
||||
if taskId <= 0 {
|
||||
return errors.New("invalid taskId")
|
||||
}
|
||||
|
||||
op := NewACMETaskOperator()
|
||||
op.Id = taskId
|
||||
op.CertId = certId
|
||||
_, err := this.Save(op)
|
||||
return err
|
||||
}
|
||||
|
||||
// 执行任务并记录日志
|
||||
func (this *ACMETaskDAO) RunTask(taskId int64) (isOk bool, errMsg string, resultCertId int64) {
|
||||
isOk, errMsg, resultCertId = this.runTaskWithoutLog(taskId)
|
||||
|
||||
// 记录日志
|
||||
err := SharedACMETaskLogDAO.CreateACMELog(taskId, isOk, errMsg)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 执行任务但并不记录日志
|
||||
func (this *ACMETaskDAO) runTaskWithoutLog(taskId int64) (isOk bool, errMsg string, resultCertId int64) {
|
||||
task, err := this.FindEnabledACMETask(taskId)
|
||||
if err != nil {
|
||||
errMsg = "查询任务信息时出错:" + err.Error()
|
||||
return
|
||||
}
|
||||
if task == nil {
|
||||
errMsg = "找不到要执行的任务"
|
||||
return
|
||||
}
|
||||
if task.IsOn != 1 {
|
||||
errMsg = "任务没有启用"
|
||||
return
|
||||
}
|
||||
|
||||
// ACME用户
|
||||
user, err := SharedACMEUserDAO.FindEnabledACMEUser(int64(task.AcmeUserId))
|
||||
if err != nil {
|
||||
errMsg = "查询ACME用户时出错:" + err.Error()
|
||||
return
|
||||
}
|
||||
if user == nil {
|
||||
errMsg = "找不到ACME用户"
|
||||
return
|
||||
}
|
||||
|
||||
privateKey, err := acme.ParsePrivateKeyFromBase64(user.PrivateKey)
|
||||
if err != nil {
|
||||
errMsg = "解析私钥时出错:" + err.Error()
|
||||
return
|
||||
}
|
||||
|
||||
remoteUser := acme.NewUser(user.Email, privateKey, func(resource *registration.Resource) error {
|
||||
resourceJSON, err := json.Marshal(resource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = SharedACMEUserDAO.UpdateACMEUserRegistration(int64(user.Id), resourceJSON)
|
||||
return err
|
||||
})
|
||||
|
||||
if len(user.Registration) > 0 {
|
||||
err = remoteUser.SetRegistration([]byte(user.Registration))
|
||||
if err != nil {
|
||||
errMsg = "设置注册信息时出错:" + err.Error()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// DNS服务商
|
||||
dnsProvider, err := SharedDNSProviderDAO.FindEnabledDNSProvider(int64(task.DnsProviderId))
|
||||
if err != nil {
|
||||
errMsg = "查找DNS服务商账号信息时出错:" + err.Error()
|
||||
return
|
||||
}
|
||||
if dnsProvider == nil {
|
||||
errMsg = "找不到DNS服务商账号"
|
||||
return
|
||||
}
|
||||
providerInterface := dnsclients.FindProvider(dnsProvider.Type)
|
||||
if providerInterface == nil {
|
||||
errMsg = "暂不支持此类型的DNS服务商 '" + dnsProvider.Type + "'"
|
||||
return
|
||||
}
|
||||
apiParams, err := dnsProvider.DecodeAPIParams()
|
||||
if err != nil {
|
||||
errMsg = "解析DNS服务商API参数时出错:" + err.Error()
|
||||
return
|
||||
}
|
||||
err = providerInterface.Auth(apiParams)
|
||||
if err != nil {
|
||||
errMsg = "校验DNS服务商API参数时出错:" + err.Error()
|
||||
return
|
||||
}
|
||||
|
||||
acmeRequest := acme.NewRequest(&acme.Task{
|
||||
User: remoteUser,
|
||||
DNSProvider: providerInterface,
|
||||
DNSDomain: task.DnsDomain,
|
||||
Domains: task.DecodeDomains(),
|
||||
})
|
||||
certData, keyData, err := acmeRequest.Run()
|
||||
if err != nil {
|
||||
errMsg = "证书生成失败:" + err.Error()
|
||||
return
|
||||
}
|
||||
|
||||
// 分析证书
|
||||
sslConfig := &sslconfigs.SSLCertConfig{
|
||||
CertData: certData,
|
||||
KeyData: keyData,
|
||||
}
|
||||
err = sslConfig.Init()
|
||||
if err != nil {
|
||||
errMsg = "证书生成成功,但是分析证书信息时发生错误:" + err.Error()
|
||||
return
|
||||
}
|
||||
|
||||
// 保存证书
|
||||
resultCertId = int64(task.CertId)
|
||||
if resultCertId > 0 {
|
||||
cert, err := SharedSSLCertDAO.FindEnabledSSLCert(resultCertId)
|
||||
if err != nil {
|
||||
errMsg = "证书生成成功,但查询已绑定的证书时出错:" + err.Error()
|
||||
return
|
||||
}
|
||||
if cert == nil {
|
||||
errMsg = "证书已被管理员或用户删除"
|
||||
|
||||
// 禁用
|
||||
err = SharedACMETaskDAO.DisableACMETask(taskId)
|
||||
if err != nil {
|
||||
errMsg = "禁用失效的ACME任务出错:" + err.Error()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err = SharedSSLCertDAO.UpdateCert(resultCertId, cert.IsOn == 1, cert.Name, cert.Description, cert.ServerName, cert.IsCA == 1, certData, keyData, sslConfig.TimeBeginAt, sslConfig.TimeEndAt, sslConfig.DNSNames, sslConfig.CommonNames)
|
||||
if err != nil {
|
||||
errMsg = "证书生成成功,但是修改数据库中的证书信息时出错:" + err.Error()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
resultCertId, err = SharedSSLCertDAO.CreateCert(int64(task.AdminId), int64(task.UserId), true, task.DnsDomain+"免费证书", "免费申请的证书", "", false, certData, keyData, sslConfig.TimeBeginAt, sslConfig.TimeEndAt, sslConfig.DNSNames, sslConfig.CommonNames)
|
||||
if err != nil {
|
||||
errMsg = "证书生成成功,但是保存到数据库失败:" + err.Error()
|
||||
return
|
||||
}
|
||||
|
||||
err = SharedSSLCertDAO.UpdateCertACME(resultCertId, int64(task.Id))
|
||||
if err != nil {
|
||||
errMsg = "证书生成成功,修改证书ACME信息时出错:" + err.Error()
|
||||
return
|
||||
}
|
||||
|
||||
// 设置成功
|
||||
err = SharedACMETaskDAO.UpdateACMETaskCert(taskId, resultCertId)
|
||||
if err != nil {
|
||||
errMsg = "证书生成成功,设置任务关联的证书时出错:" + err.Error()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
isOk = true
|
||||
return
|
||||
}
|
||||
|
||||
@@ -26,3 +26,13 @@ func init() {
|
||||
SharedACMETaskLogDAO = NewACMETaskLogDAO()
|
||||
})
|
||||
}
|
||||
|
||||
// 生成日志
|
||||
func (this *ACMETaskLogDAO) CreateACMELog(taskId int64, isOk bool, errMsg string) error {
|
||||
op := NewACMETaskLogOperator()
|
||||
op.TaskId = taskId
|
||||
op.Error = errMsg
|
||||
op.IsOk = isOk
|
||||
_, err := this.Save(op)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ type ACMETask struct {
|
||||
Domains string `field:"domains"` // 证书域名
|
||||
CreatedAt uint64 `field:"createdAt"` // 创建时间
|
||||
State uint8 `field:"state"` // 状态
|
||||
IsOk uint8 `field:"isOk"` // 最后运行是否正常
|
||||
CertId uint64 `field:"certId"` // 生成的证书ID
|
||||
AutoRenew uint8 `field:"autoRenew"` // 是否自动更新
|
||||
}
|
||||
@@ -28,7 +27,6 @@ type ACMETaskOperator struct {
|
||||
Domains interface{} // 证书域名
|
||||
CreatedAt interface{} // 创建时间
|
||||
State interface{} // 状态
|
||||
IsOk interface{} // 最后运行是否正常
|
||||
CertId interface{} // 生成的证书ID
|
||||
AutoRenew interface{} // 是否自动更新
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ func (this *ACMEUserDAO) CreateACMEUser(adminId int64, userId int64, email strin
|
||||
return types.Int64(op.Id), nil
|
||||
}
|
||||
|
||||
// 查找用户列表
|
||||
// 修改用户信息
|
||||
func (this *ACMEUserDAO) UpdateACMEUser(acmeUserId int64, description string) error {
|
||||
if acmeUserId <= 0 {
|
||||
return errors.New("invalid acmeUserId")
|
||||
@@ -109,6 +109,18 @@ func (this *ACMEUserDAO) UpdateACMEUser(acmeUserId int64, description string) er
|
||||
return err
|
||||
}
|
||||
|
||||
// 修改用户ACME注册信息
|
||||
func (this *ACMEUserDAO) UpdateACMEUserRegistration(acmeUserId int64, registrationJSON []byte) error {
|
||||
if acmeUserId <= 0 {
|
||||
return errors.New("invalid acmeUserId")
|
||||
}
|
||||
op := NewACMEUserOperator()
|
||||
op.Id = acmeUserId
|
||||
op.Registration = registrationJSON
|
||||
_, err := this.Save(op)
|
||||
return err
|
||||
}
|
||||
|
||||
// 计算用户数量
|
||||
func (this *ACMEUserDAO) CountACMEUsersWithAdminId(adminId int64, userId int64) (int64, error) {
|
||||
query := this.Query()
|
||||
|
||||
@@ -2,25 +2,27 @@ package models
|
||||
|
||||
//
|
||||
type ACMEUser struct {
|
||||
Id uint64 `field:"id"` // ID
|
||||
AdminId uint32 `field:"adminId"` // 管理员ID
|
||||
UserId uint32 `field:"userId"` // 用户ID
|
||||
PrivateKey string `field:"privateKey"` // 私钥
|
||||
Email string `field:"email"` // E-mail
|
||||
CreatedAt uint64 `field:"createdAt"` // 创建时间
|
||||
State uint8 `field:"state"` // 状态
|
||||
Description string `field:"description"` // 备注介绍
|
||||
Id uint64 `field:"id"` // ID
|
||||
AdminId uint32 `field:"adminId"` // 管理员ID
|
||||
UserId uint32 `field:"userId"` // 用户ID
|
||||
PrivateKey string `field:"privateKey"` // 私钥
|
||||
Email string `field:"email"` // E-mail
|
||||
CreatedAt uint64 `field:"createdAt"` // 创建时间
|
||||
State uint8 `field:"state"` // 状态
|
||||
Description string `field:"description"` // 备注介绍
|
||||
Registration string `field:"registration"` // 注册信息
|
||||
}
|
||||
|
||||
type ACMEUserOperator struct {
|
||||
Id interface{} // ID
|
||||
AdminId interface{} // 管理员ID
|
||||
UserId interface{} // 用户ID
|
||||
PrivateKey interface{} // 私钥
|
||||
Email interface{} // E-mail
|
||||
CreatedAt interface{} // 创建时间
|
||||
State interface{} // 状态
|
||||
Description interface{} // 备注介绍
|
||||
Id interface{} // ID
|
||||
AdminId interface{} // 管理员ID
|
||||
UserId interface{} // 用户ID
|
||||
PrivateKey interface{} // 私钥
|
||||
Email interface{} // E-mail
|
||||
CreatedAt interface{} // 创建时间
|
||||
State interface{} // 状态
|
||||
Description interface{} // 备注介绍
|
||||
Registration interface{} // 注册信息
|
||||
}
|
||||
|
||||
func NewACMEUserOperator() *ACMEUserOperator {
|
||||
|
||||
@@ -178,6 +178,7 @@ func (this *SSLCertDAO) ComposeCertConfig(certId int64) (*sslconfigs.SSLCertConf
|
||||
config.Id = int64(cert.Id)
|
||||
config.IsOn = cert.IsOn == 1
|
||||
config.IsCA = cert.IsCA == 1
|
||||
config.IsACME = cert.IsACME == 1
|
||||
config.Name = cert.Name
|
||||
config.Description = cert.Description
|
||||
config.CertData = []byte(cert.CertData)
|
||||
@@ -269,3 +270,16 @@ func (this *SSLCertDAO) ListCertIds(isCA bool, isAvailable bool, isExpired bool,
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 设置证书的ACME信息
|
||||
func (this *SSLCertDAO) UpdateCertACME(certId int64, acmeTaskId int64) error {
|
||||
if certId <= 0 {
|
||||
return errors.New("invalid certId")
|
||||
}
|
||||
op := NewSSLCertOperator()
|
||||
op.Id = certId
|
||||
op.AcmeTaskId = acmeTaskId
|
||||
op.IsACME = true
|
||||
_, err := this.Save(op)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
|
||||
// 阿里云服务商
|
||||
type AliDNSProvider struct {
|
||||
BaseProvider
|
||||
|
||||
accessKeyId string
|
||||
accessKeySecret string
|
||||
}
|
||||
@@ -87,6 +89,20 @@ func (this *AliDNSProvider) GetRoutes(domain string) (routes []*Route, err error
|
||||
return
|
||||
}
|
||||
|
||||
// 查询单个记录
|
||||
func (this *AliDNSProvider) QueryRecord(domain string, name string, recordType RecordType) (*Record, error) {
|
||||
records, err := this.GetRecords(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, record := range records {
|
||||
if record.Name == name && record.Type == recordType {
|
||||
return record, nil
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 设置记录
|
||||
func (this *AliDNSProvider) AddRecord(domain string, newRecord *Record) error {
|
||||
req := alidns.CreateAddDomainRecordRequest()
|
||||
|
||||
4
internal/dnsclients/provider_base.go
Normal file
4
internal/dnsclients/provider_base.go
Normal file
@@ -0,0 +1,4 @@
|
||||
package dnsclients
|
||||
|
||||
type BaseProvider struct {
|
||||
}
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
|
||||
// DNSPod服务商
|
||||
type DNSPodProvider struct {
|
||||
BaseProvider
|
||||
|
||||
apiId string
|
||||
apiToken string
|
||||
}
|
||||
@@ -104,6 +106,20 @@ func (this *DNSPodProvider) GetRoutes(domain string) (routes []*Route, err error
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
// 查询单个记录
|
||||
func (this *DNSPodProvider) QueryRecord(domain string, name string, recordType RecordType) (*Record, error) {
|
||||
records, err := this.GetRecords(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, record := range records {
|
||||
if record.Name == name && record.Type == recordType {
|
||||
return record, nil
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 设置记录
|
||||
func (this *DNSPodProvider) AddRecord(domain string, newRecord *Record) error {
|
||||
if newRecord == nil {
|
||||
|
||||
@@ -7,12 +7,15 @@ type ProviderInterface interface {
|
||||
// 认证
|
||||
Auth(params maps.Map) error
|
||||
|
||||
// 获取域名列表
|
||||
// 获取域名解析记录列表
|
||||
GetRecords(domain string) (records []*Record, err error)
|
||||
|
||||
// 读取域名支持的线路数据
|
||||
GetRoutes(domain string) (routes []*Route, err error)
|
||||
|
||||
// 查询单个记录
|
||||
QueryRecord(domain string, name string, recordType RecordType) (*Record, error)
|
||||
|
||||
// 设置记录
|
||||
AddRecord(domain string, newRecord *Record) error
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
@@ -15,6 +16,11 @@ type errorObj struct {
|
||||
}
|
||||
|
||||
func (this *errorObj) Error() string {
|
||||
// 在非测试环境下,我们不提示详细的行数等信息
|
||||
if !Tea.IsTesting() {
|
||||
return this.err.Error()
|
||||
}
|
||||
|
||||
s := this.err.Error() + "\n " + this.file
|
||||
if len(this.funcName) > 0 {
|
||||
s += ":" + this.funcName + "()"
|
||||
|
||||
@@ -113,9 +113,11 @@ func (this *ACMETaskService) ListEnabledACMETasks(ctx context.Context, req *pb.L
|
||||
continue
|
||||
}
|
||||
pbCert = &pb.SSLCert{
|
||||
Id: int64(cert.Id),
|
||||
IsOn: cert.IsOn == 1,
|
||||
Name: cert.Name,
|
||||
Id: int64(cert.Id),
|
||||
IsOn: cert.IsOn == 1,
|
||||
Name: cert.Name,
|
||||
TimeBeginAt: int64(cert.TimeBeginAt),
|
||||
TimeEndAt: int64(cert.TimeEndAt),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +127,6 @@ func (this *ACMETaskService) ListEnabledACMETasks(ctx context.Context, req *pb.L
|
||||
DnsDomain: task.DnsDomain,
|
||||
Domains: task.DecodeDomains(),
|
||||
CreatedAt: int64(task.CreatedAt),
|
||||
IsOk: task.IsOk == 1,
|
||||
AutoRenew: task.AutoRenew == 1,
|
||||
AcmeUser: pbACMEUser,
|
||||
DnsProvider: pbProvider,
|
||||
@@ -208,9 +209,13 @@ func (this *ACMETaskService) RunACMETask(ctx context.Context, req *pb.RunACMETas
|
||||
return nil, this.PermissionError()
|
||||
}
|
||||
|
||||
// TODO
|
||||
isOk, msg, certId := models.SharedACMETaskDAO.RunTask(req.AcmeTaskId)
|
||||
|
||||
return &pb.RunACMETaskResponse{}, nil
|
||||
return &pb.RunACMETaskResponse{
|
||||
IsOk: isOk,
|
||||
Error: msg,
|
||||
SslCertId: certId,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 查找单个任务信息
|
||||
|
||||
Reference in New Issue
Block a user