Scert/backend/internal/httpapi/aliyun_scope_api.go

162 lines
5.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package httpapi
import (
"context"
"errors"
"fmt"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"git.pcux.cn/xuans/Scert/backend/internal/dnsprovider"
"git.pcux.cn/xuans/Scert/backend/internal/domain"
)
type aliyunScopeChecker interface {
Verify(ctx context.Context, requestedDomain string, credentials map[string]string) (dnsprovider.AuthorizationReport, error)
VerifyDomain(ctx context.Context, providerID, requestedDomain string, credentials map[string]string) error
}
type aliyunDomainCheckRequest struct {
Domain string `json:"domain"`
}
func (s *Server) checkAliyunDNSDomain(c *gin.Context) {
var request aliyunDomainCheckRequest
if err := bindJSON(c, &request, jsonRequestBodyLimit); err != nil {
s.writeError(c, http.StatusBadRequest, "请求参数无效", err)
return
}
targetDomain := strings.ToLower(strings.TrimSuffix(strings.TrimSpace(request.Domain), "."))
if !domain.IsValidName(targetDomain) {
s.writeError(c, http.StatusBadRequest, fmt.Sprintf("域名格式无效:%s", targetDomain), nil)
return
}
s.configMu.RLock()
provider := strings.ToLower(strings.TrimSpace(s.config.DNS.Provider))
credentialRef := strings.TrimSpace(s.config.DNS.CredentialRef)
s.configMu.RUnlock()
if provider != dnsprovider.Aliyun {
s.writeError(c, http.StatusBadRequest, "当前系统 DNS 服务商不是阿里云,无法进行阿里云 RAM 权限考证", nil)
return
}
// 必须先在本地拒绝受保护域,不能为无效请求读取凭据或触发云端探测。
if err := dnsprovider.ValidateAliyunTargetDomains(provider, []string{targetDomain}); err != nil {
c.JSON(http.StatusOK, gin.H{"verified": false, "domain": targetDomain, "message": err.Error()})
return
}
credentials, err := s.readDNSCredentials(c.Request.Context(), credentialRef)
if err != nil {
s.writeError(c, http.StatusInternalServerError, "读取阿里云 DNS 凭据失败", err)
return
}
credentials, err = dnsprovider.ValidateCredentials(dnsprovider.Aliyun, credentials)
if err != nil {
s.writeError(c, http.StatusBadRequest, "阿里云 DNS 凭据无效", err)
return
}
if s.aliyunChecker == nil {
s.writeError(c, http.StatusNotImplemented, "阿里云权限探测器未装载", nil)
return
}
report, verifyErr := s.aliyunChecker.Verify(c.Request.Context(), targetDomain, credentials)
report = sanitizeAliyunReport(report, targetDomain)
if verifyErr != nil {
diagnosis := dnsprovider.DiagnoseAliyunError(verifyErr, targetDomain)
report.Verified = false
report.Diagnosis = diagnosis
c.JSON(http.StatusOK, gin.H{
"verified": false,
"domain": targetDomain,
"matched_domain": report.MatchedDomain,
"diagnosis": report.Diagnosis,
"error": diagnosis,
"report": report,
})
return
}
c.JSON(http.StatusOK, gin.H{
"verified": true,
"domain": targetDomain,
"matched_domain": report.MatchedDomain,
"message": "阿里云 RAM 账号对该域名的 DNS TXT 记录写入与清理权限验证通过",
"report": report,
})
}
func (s *Server) validateDomainAliyunAuthorization(ctx context.Context, domainNames ...string) error {
if s == nil {
return errors.New("域名授权检查服务未初始化")
}
s.configMu.RLock()
setupReady := s.config.SetupReady
provider := strings.ToLower(strings.TrimSpace(s.config.DNS.Provider))
credentialRef := strings.TrimSpace(s.config.DNS.CredentialRef)
s.configMu.RUnlock()
if provider != dnsprovider.Aliyun {
return nil
}
if !setupReady {
return errors.New("系统初始化尚未完成,不能验证域名授权")
}
// 在读取凭据、构造阿里云客户端或发起任何云 API 请求前检查整张订单。
if err := dnsprovider.ValidateAliyunTargetDomains(provider, domainNames); err != nil {
return err
}
if s.aliyunChecker == nil {
return errors.New("阿里云权限探测器未装载,不能确认域名授权")
}
credentials, err := s.readDNSCredentials(ctx, credentialRef)
if err != nil {
return errors.New("读取阿里云 DNS 凭据失败,不能确认域名授权,请检查受保护的凭据存储")
}
if _, err := dnsprovider.ValidateCredentials(dnsprovider.Aliyun, credentials); err != nil {
return fmt.Errorf("阿里云 DNS 凭据无效,不能确认域名授权:%w", err)
}
for _, domainName := range domainNames {
if err := s.aliyunChecker.VerifyDomain(ctx, dnsprovider.Aliyun, domainName, credentials); err != nil {
diagnosis := dnsprovider.DiagnoseAliyunError(err, domainName)
return fmt.Errorf("阿里云 RAM 账号无法操作域名 %s:%s", domainName, diagnosis)
}
}
return nil
}
func sanitizeAliyunReport(report dnsprovider.AuthorizationReport, requestedDomain string) dnsprovider.AuthorizationReport {
if report.Diagnosis != "" {
report.Diagnosis = dnsprovider.DiagnoseAliyunError(errors.New(report.Diagnosis), requestedDomain)
}
report.Domains = append([]dnsprovider.AuthorizationDomain(nil), report.Domains...)
for index := range report.Domains {
item := &report.Domains[index]
if item.Error == "" {
continue
}
if item.Writable && !item.Cleanup {
item.Error = "测试记录写入成功但清理失败,请检查删除权限并核对残留测试记录"
} else {
item.Error = dnsprovider.DiagnoseAliyunError(errors.New(item.Error), item.Domain)
}
}
return report
}
func (s *Server) readDNSCredentials(ctx context.Context, credentialRef string) (map[string]string, error) {
if s == nil || strings.TrimSpace(credentialRef) == "" {
return nil, errors.New("DNS 凭据引用未配置")
}
reader, ok := s.secrets.(readinessSecretReader)
if !ok || reader == nil {
return nil, errors.New("凭据存储未就绪")
}
return reader.Get(ctx, credentialRef)
}