diff --git a/internal/acme/request_test.go b/internal/acme/request_test.go index e610d35b..f55beafd 100644 --- a/internal/acme/request_test.go +++ b/internal/acme/request_test.go @@ -39,7 +39,7 @@ func TestRequest_Run_DNS(t *testing.T) { req := NewRequest(&Task{ User: user, - Type: TaskTypeDNS, + AuthType: AuthTypeDNS, DNSProvider: dnsProvider, DNSDomain: "yun4s.cn", Domains: []string{"yun4s.cn"}, @@ -74,9 +74,9 @@ func TestRequest_Run_HTTP(t *testing.T) { } req := NewRequest(&Task{ - User: user, - Type: TaskTypeHTTP, - Domains: []string{"teaos.cn", "www.teaos.cn", "meloy.cn"}, + User: user, + AuthType: AuthTypeHTTP, + Domains: []string{"teaos.cn", "www.teaos.cn", "meloy.cn"}, }) certData, keyData, err := req.runHTTP() if err != nil { diff --git a/internal/db/models/acme/acme_task_dao.go b/internal/db/models/acme/acme_task_dao.go index 233c3a40..0260f6b4 100644 --- a/internal/db/models/acme/acme_task_dao.go +++ b/internal/db/models/acme/acme_task_dao.go @@ -1,6 +1,7 @@ package acme import ( + "bytes" "encoding/json" "github.com/TeaOSLab/EdgeAPI/internal/acme" "github.com/TeaOSLab/EdgeAPI/internal/db/models" @@ -8,13 +9,17 @@ import ( dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils" "github.com/TeaOSLab/EdgeAPI/internal/dnsclients" "github.com/TeaOSLab/EdgeAPI/internal/errors" + "github.com/TeaOSLab/EdgeAPI/internal/remotelogs" + "github.com/TeaOSLab/EdgeAPI/internal/utils" "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/maps" "github.com/iwind/TeaGo/types" + "net/http" "time" ) @@ -44,7 +49,7 @@ func init() { }) } -// 启用条目 +// EnableACMETask 启用条目 func (this *ACMETaskDAO) EnableACMETask(tx *dbs.Tx, id int64) error { _, err := this.Query(tx). Pk(id). @@ -53,7 +58,7 @@ func (this *ACMETaskDAO) EnableACMETask(tx *dbs.Tx, id int64) error { return err } -// 禁用条目 +// DisableACMETask 禁用条目 func (this *ACMETaskDAO) DisableACMETask(tx *dbs.Tx, id int64) error { _, err := this.Query(tx). Pk(id). @@ -62,7 +67,7 @@ func (this *ACMETaskDAO) DisableACMETask(tx *dbs.Tx, id int64) error { return err } -// 查找启用中的条目 +// FindEnabledACMETask 查找启用中的条目 func (this *ACMETaskDAO) FindEnabledACMETask(tx *dbs.Tx, id int64) (*ACMETask, error) { result, err := this.Query(tx). Pk(id). @@ -74,7 +79,7 @@ func (this *ACMETaskDAO) FindEnabledACMETask(tx *dbs.Tx, id int64) (*ACMETask, e return result.(*ACMETask), err } -// 计算某个ACME用户相关的任务数量 +// CountACMETasksWithACMEUserId 计算某个ACME用户相关的任务数量 func (this *ACMETaskDAO) CountACMETasksWithACMEUserId(tx *dbs.Tx, acmeUserId int64) (int64, error) { return this.Query(tx). State(ACMETaskStateEnabled). @@ -82,7 +87,7 @@ func (this *ACMETaskDAO) CountACMETasksWithACMEUserId(tx *dbs.Tx, acmeUserId int Count() } -// 计算某个DNS服务商相关的任务数量 +// CountACMETasksWithDNSProviderId 计算某个DNS服务商相关的任务数量 func (this *ACMETaskDAO) CountACMETasksWithDNSProviderId(tx *dbs.Tx, dnsProviderId int64) (int64, error) { return this.Query(tx). State(ACMETaskStateEnabled). @@ -90,7 +95,7 @@ func (this *ACMETaskDAO) CountACMETasksWithDNSProviderId(tx *dbs.Tx, dnsProvider Count() } -// 停止某个证书相关任务 +// DisableAllTasksWithCertId 停止某个证书相关任务 func (this *ACMETaskDAO) DisableAllTasksWithCertId(tx *dbs.Tx, certId int64) error { _, err := this.Query(tx). Attr("certId", certId). @@ -99,7 +104,7 @@ func (this *ACMETaskDAO) DisableAllTasksWithCertId(tx *dbs.Tx, certId int64) err return err } -// 计算所有任务数量 +// CountAllEnabledACMETasks 计算所有任务数量 func (this *ACMETaskDAO) CountAllEnabledACMETasks(tx *dbs.Tx, adminId int64, userId int64, isAvailable bool, isExpired bool, expiringDays int64, keyword string) (int64, error) { query := dbutils.NewQuery(tx, this, adminId, userId) if isAvailable || isExpired || expiringDays > 0 { @@ -130,7 +135,7 @@ func (this *ACMETaskDAO) CountAllEnabledACMETasks(tx *dbs.Tx, adminId int64, use Count() } -// 列出单页任务 +// ListEnabledACMETasks 列出单页任务 func (this *ACMETaskDAO) ListEnabledACMETasks(tx *dbs.Tx, adminId int64, userId int64, isAvailable bool, isExpired bool, expiringDays int64, keyword string, offset int64, size int64) (result []*ACMETask, err error) { query := dbutils.NewQuery(tx, this, adminId, userId) if isAvailable || isExpired || expiringDays > 0 { @@ -161,8 +166,8 @@ func (this *ACMETaskDAO) ListEnabledACMETasks(tx *dbs.Tx, adminId int64, userId return } -// 创建任务 -func (this *ACMETaskDAO) CreateACMETask(tx *dbs.Tx, adminId int64, userId int64, authType acme.AuthType, acmeUserId int64, dnsProviderId int64, dnsDomain string, domains []string, autoRenew bool) (int64, error) { +// CreateACMETask 创建任务 +func (this *ACMETaskDAO) CreateACMETask(tx *dbs.Tx, adminId int64, userId int64, authType acme.AuthType, acmeUserId int64, dnsProviderId int64, dnsDomain string, domains []string, autoRenew bool, authURL string) (int64, error) { op := NewACMETaskOperator() op.AdminId = adminId op.UserId = userId @@ -182,6 +187,7 @@ func (this *ACMETaskDAO) CreateACMETask(tx *dbs.Tx, adminId int64, userId int64, } op.AutoRenew = autoRenew + op.AuthURL = authURL op.IsOn = true op.State = ACMETaskStateEnabled err := this.Save(tx, op) @@ -191,8 +197,8 @@ func (this *ACMETaskDAO) CreateACMETask(tx *dbs.Tx, adminId int64, userId int64, return types.Int64(op.Id), nil } -// 修改任务 -func (this *ACMETaskDAO) UpdateACMETask(tx *dbs.Tx, acmeTaskId int64, acmeUserId int64, dnsProviderId int64, dnsDomain string, domains []string, autoRenew bool) error { +// UpdateACMETask 修改任务 +func (this *ACMETaskDAO) UpdateACMETask(tx *dbs.Tx, acmeTaskId int64, acmeUserId int64, dnsProviderId int64, dnsDomain string, domains []string, autoRenew bool, authURL string) error { if acmeTaskId <= 0 { return errors.New("invalid acmeTaskId") } @@ -214,11 +220,12 @@ func (this *ACMETaskDAO) UpdateACMETask(tx *dbs.Tx, acmeTaskId int64, acmeUserId } op.AutoRenew = autoRenew + op.AuthURL = authURL err := this.Save(tx, op) return err } -// 检查权限 +// CheckACMETask 检查权限 func (this *ACMETaskDAO) CheckACMETask(tx *dbs.Tx, adminId int64, userId int64, acmeTaskId int64) (bool, error) { return dbutils.NewQuery(tx, this, adminId, userId). State(ACMETaskStateEnabled). @@ -226,7 +233,7 @@ func (this *ACMETaskDAO) CheckACMETask(tx *dbs.Tx, adminId int64, userId int64, Exist() } -// 设置任务关联的证书 +// UpdateACMETaskCert 设置任务关联的证书 func (this *ACMETaskDAO) UpdateACMETaskCert(tx *dbs.Tx, taskId int64, certId int64) error { if taskId <= 0 { return errors.New("invalid taskId") @@ -239,7 +246,7 @@ func (this *ACMETaskDAO) UpdateACMETaskCert(tx *dbs.Tx, taskId int64, certId int return err } -// 执行任务并记录日志 +// RunTask 执行任务并记录日志 func (this *ACMETaskDAO) RunTask(tx *dbs.Tx, taskId int64) (isOk bool, errMsg string, resultCertId int64) { isOk, errMsg, resultCertId = this.runTaskWithoutLog(tx, taskId) @@ -350,7 +357,33 @@ func (this *ACMETaskDAO) runTaskWithoutLog(tx *dbs.Tx, taskId int64) (isOk bool, acmeRequest.OnAuth(func(domain, token, keyAuth string) { err := SharedACMEAuthenticationDAO.CreateAuth(tx, taskId, domain, token, keyAuth) if err != nil { - logs.Println("[ACME]write authentication to database error: " + err.Error()) + remotelogs.Error("ACME", "write authentication to database error: "+err.Error()) + } else { + // 调用校验URL + if len(task.AuthURL) > 0 { + authJSON, err := json.Marshal(maps.Map{ + "domain": domain, + "token": token, + "key": keyAuth, + }) + if err != nil { + remotelogs.Error("ACME", "encode auth data failed: '"+task.AuthURL+"'") + } else { + client := utils.SharedHttpClient(5 * time.Second) + req, err := http.NewRequest(http.MethodPost, task.AuthURL, bytes.NewReader(authJSON)) + req.Header.Set("Content-Type", "application/json") + if err != nil { + remotelogs.Error("ACME", "parse auth url failed '"+task.AuthURL+"': "+err.Error()) + } else { + resp, err := client.Do(req) + if err != nil { + remotelogs.Error("ACME", "call auth url failed '"+task.AuthURL+"': "+err.Error()) + } else { + _ = resp.Body.Close() + } + } + } + } } }) certData, keyData, err := acmeRequest.Run() diff --git a/internal/db/models/acme/acme_task_model.go b/internal/db/models/acme/acme_task_model.go index 43651131..930f780f 100644 --- a/internal/db/models/acme/acme_task_model.go +++ b/internal/db/models/acme/acme_task_model.go @@ -1,6 +1,6 @@ package acme -// ACME任务 +// ACMETask ACME任务 type ACMETask struct { Id uint64 `field:"id"` // ID AdminId uint32 `field:"adminId"` // 管理员ID @@ -15,6 +15,7 @@ type ACMETask struct { CertId uint64 `field:"certId"` // 生成的证书ID AutoRenew uint8 `field:"autoRenew"` // 是否自动更新 AuthType string `field:"authType"` // 认证类型 + AuthURL string `field:"authURL"` // 认证URL } type ACMETaskOperator struct { @@ -31,6 +32,7 @@ type ACMETaskOperator struct { CertId interface{} // 生成的证书ID AutoRenew interface{} // 是否自动更新 AuthType interface{} // 认证类型 + AuthURL interface{} // 认证URL } func NewACMETaskOperator() *ACMETaskOperator { diff --git a/internal/rpc/services/service_acme_task.go b/internal/rpc/services/service_acme_task.go index 5ce987b9..f5d73bcc 100644 --- a/internal/rpc/services/service_acme_task.go +++ b/internal/rpc/services/service_acme_task.go @@ -10,12 +10,12 @@ import ( "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" ) -// ACME任务相关服务 +// ACMETaskService ACME任务相关服务 type ACMETaskService struct { BaseService } -// 计算某个ACME用户相关的任务数量 +// CountAllEnabledACMETasksWithACMEUserId 计算某个ACME用户相关的任务数量 func (this *ACMETaskService) CountAllEnabledACMETasksWithACMEUserId(ctx context.Context, req *pb.CountAllEnabledACMETasksWithACMEUserIdRequest) (*pb.RPCCountResponse, error) { _, _, err := this.ValidateAdminAndUser(ctx, 0, 0) if err != nil { @@ -33,7 +33,7 @@ func (this *ACMETaskService) CountAllEnabledACMETasksWithACMEUserId(ctx context. return this.SuccessCount(count) } -// 计算跟某个DNS服务商相关的任务数量 +// CountEnabledACMETasksWithDNSProviderId 计算跟某个DNS服务商相关的任务数量 func (this *ACMETaskService) CountEnabledACMETasksWithDNSProviderId(ctx context.Context, req *pb.CountEnabledACMETasksWithDNSProviderIdRequest) (*pb.RPCCountResponse, error) { _, _, err := this.ValidateAdminAndUser(ctx, 0, 0) if err != nil { @@ -51,7 +51,7 @@ func (this *ACMETaskService) CountEnabledACMETasksWithDNSProviderId(ctx context. return this.SuccessCount(count) } -// 计算所有任务数量 +// CountAllEnabledACMETasks 计算所有任务数量 func (this *ACMETaskService) CountAllEnabledACMETasks(ctx context.Context, req *pb.CountAllEnabledACMETasksRequest) (*pb.RPCCountResponse, error) { _, _, err := this.ValidateAdminAndUser(ctx, 0, req.UserId) if err != nil { @@ -67,7 +67,7 @@ func (this *ACMETaskService) CountAllEnabledACMETasks(ctx context.Context, req * return this.SuccessCount(count) } -// 列出单页任务 +// ListEnabledACMETasks 列出单页任务 func (this *ACMETaskService) ListEnabledACMETasks(ctx context.Context, req *pb.ListEnabledACMETasksRequest) (*pb.ListEnabledACMETasksResponse, error) { _, _, err := this.ValidateAdminAndUser(ctx, 0, req.UserId) if err != nil { @@ -162,13 +162,14 @@ func (this *ACMETaskService) ListEnabledACMETasks(ctx context.Context, req *pb.L SslCert: pbCert, LatestACMETaskLog: pbTaskLog, AuthType: task.AuthType, + AuthURL: task.AuthURL, }) } return &pb.ListEnabledACMETasksResponse{AcmeTasks: result}, nil } -// 创建任务 +// CreateACMETask 创建任务 func (this *ACMETaskService) CreateACMETask(ctx context.Context, req *pb.CreateACMETaskRequest) (*pb.CreateACMETaskResponse, error) { adminId, userId, err := this.ValidateAdminAndUser(ctx, 0, 0) if err != nil { @@ -180,14 +181,14 @@ func (this *ACMETaskService) CreateACMETask(ctx context.Context, req *pb.CreateA } tx := this.NullTx() - taskId, err := acmemodels.SharedACMETaskDAO.CreateACMETask(tx, adminId, userId, req.AuthType, req.AcmeUserId, req.DnsProviderId, req.DnsDomain, req.Domains, req.AutoRenew) + taskId, err := acmemodels.SharedACMETaskDAO.CreateACMETask(tx, adminId, userId, req.AuthType, req.AcmeUserId, req.DnsProviderId, req.DnsDomain, req.Domains, req.AutoRenew, req.AuthURL) if err != nil { return nil, err } return &pb.CreateACMETaskResponse{AcmeTaskId: taskId}, nil } -// 修改任务 +// UpdateACMETask 修改任务 func (this *ACMETaskService) UpdateACMETask(ctx context.Context, req *pb.UpdateACMETaskRequest) (*pb.RPCSuccess, error) { adminId, userId, err := this.ValidateAdminAndUser(ctx, 0, 0) if err != nil { @@ -204,14 +205,14 @@ func (this *ACMETaskService) UpdateACMETask(ctx context.Context, req *pb.UpdateA return nil, this.PermissionError() } - err = acmemodels.SharedACMETaskDAO.UpdateACMETask(tx, req.AcmeTaskId, req.AcmeUserId, req.DnsProviderId, req.DnsDomain, req.Domains, req.AutoRenew) + err = acmemodels.SharedACMETaskDAO.UpdateACMETask(tx, req.AcmeTaskId, req.AcmeUserId, req.DnsProviderId, req.DnsDomain, req.Domains, req.AutoRenew, req.AuthURL) if err != nil { return nil, err } return this.Success() } -// 删除任务 +// DeleteACMETask 删除任务 func (this *ACMETaskService) DeleteACMETask(ctx context.Context, req *pb.DeleteACMETaskRequest) (*pb.RPCSuccess, error) { adminId, userId, err := this.ValidateAdminAndUser(ctx, 0, 0) if err != nil { @@ -235,7 +236,7 @@ func (this *ACMETaskService) DeleteACMETask(ctx context.Context, req *pb.DeleteA return this.Success() } -// 运行某个任务 +// RunACMETask 运行某个任务 func (this *ACMETaskService) RunACMETask(ctx context.Context, req *pb.RunACMETaskRequest) (*pb.RunACMETaskResponse, error) { adminId, userId, err := this.ValidateAdminAndUser(ctx, 0, 0) if err != nil { @@ -261,7 +262,7 @@ func (this *ACMETaskService) RunACMETask(ctx context.Context, req *pb.RunACMETas }, nil } -// 查找单个任务信息 +// FindEnabledACMETask 查找单个任务信息 func (this *ACMETaskService) FindEnabledACMETask(ctx context.Context, req *pb.FindEnabledACMETaskRequest) (*pb.FindEnabledACMETaskResponse, error) { adminId, userId, err := this.ValidateAdminAndUser(ctx, 0, 0) if err != nil { @@ -328,5 +329,6 @@ func (this *ACMETaskService) FindEnabledACMETask(ctx context.Context, req *pb.Fi DnsProvider: pbProvider, AcmeUser: pbACMEUser, AuthType: task.AuthType, + AuthURL: task.AuthURL, }}, nil } diff --git a/internal/utils/http.go b/internal/utils/http.go new file mode 100644 index 00000000..fea8d875 --- /dev/null +++ b/internal/utils/http.go @@ -0,0 +1,53 @@ +package utils + +import ( + "crypto/tls" + "io/ioutil" + "net/http" + "net/http/httputil" + "sync" + "time" +) + +// HTTP请求客户端管理 +var timeoutClientMap = map[time.Duration]*http.Client{} // timeout => Client +var timeoutClientLocker = sync.Mutex{} + +// DumpResponse 导出响应 +func DumpResponse(resp *http.Response) (header []byte, body []byte, err error) { + header, err = httputil.DumpResponse(resp, false) + body, err = ioutil.ReadAll(resp.Body) + return +} + +// NewHTTPClient 获取一个新的Client +func NewHTTPClient(timeout time.Duration) *http.Client { + return &http.Client{ + Timeout: timeout, + Transport: &http.Transport{ + MaxIdleConns: 4096, + MaxIdleConnsPerHost: 32, + MaxConnsPerHost: 32, + IdleConnTimeout: 2 * time.Minute, + ExpectContinueTimeout: 1 * time.Second, + TLSHandshakeTimeout: 0, + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + }, + } +} + +// SharedHttpClient 获取一个公用的Client +func SharedHttpClient(timeout time.Duration) *http.Client { + timeoutClientLocker.Lock() + defer timeoutClientLocker.Unlock() + + client, ok := timeoutClientMap[timeout] + if ok { + return client + } + client = NewHTTPClient(timeout) + timeoutClientMap[timeout] = client + return client +}