sslcert.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. package service
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "crypto/x509"
  6. "encoding/json"
  7. "fmt"
  8. v1 "github.com/go-nunu/nunu-layout-advanced/api/v1"
  9. "github.com/go-nunu/nunu-layout-advanced/internal/repository/api/waf"
  10. )
  11. type SslCertService interface {
  12. ParseCert(ctx context.Context, httpsCert string, httpKey string) (serverName string, commonName []string, DNSNames []string, before int64, after int64, isSelfSigned bool, err error)
  13. AddSSLCert(ctx context.Context, req v1.SSL) (int64, error)
  14. EditSSLCert(ctx context.Context, req v1.SSL) error
  15. AddSslPolicy(ctx context.Context, CertIds []int64) (sslPolicyId int64, err error)
  16. EditSslPolicy(ctx context.Context, sslPolicyId int64, CertIds []int64, action string) error
  17. }
  18. func NewSslCertService(
  19. service *Service,
  20. webForwardingRep waf.WebForwardingRepository,
  21. cdn CdnService,
  22. ) SslCertService {
  23. return &sslCertService{
  24. Service: service,
  25. webForwardingRep: webForwardingRep,
  26. cdn: cdn,
  27. }
  28. }
  29. type sslCertService struct {
  30. *Service
  31. webForwardingRep waf.WebForwardingRepository
  32. cdn CdnService
  33. }
  34. func (s *sslCertService) ParseCert(ctx context.Context, httpsCert string, httpKey string) (serverName string, commonName []string, DNSNames []string, before int64, after int64, isSelfSigned bool, err error) {
  35. cert, err := tls.X509KeyPair([]byte(httpsCert), []byte(httpKey))
  36. if err != nil {
  37. return "", nil, nil, 0, 0, false, fmt.Errorf("无法从字符串加载密钥对: %v", err)
  38. }
  39. if len(cert.Certificate) == 0 {
  40. return "", nil, nil, 0, 0, false, fmt.Errorf("提供的证书数据中没有找到证书。")
  41. }
  42. // 解析第一个证书(通常是叶子证书)
  43. x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
  44. if err != nil {
  45. return "", nil, nil, 0, 0, false, fmt.Errorf("无法解析证书: %v", err)
  46. }
  47. // 1. 获取 Common Name (通用名称)
  48. // Common Name 位于 Subject 字段内. [1]
  49. serverName = x509Cert.Subject.CommonName
  50. // 2. 获取 DNS Names (备用主题名称中的DNS条目)
  51. // DNS Names 直接是证书结构体的一个字段. [1]
  52. DNSNames = x509Cert.DNSNames
  53. // 检查证书是否为自签名
  54. // 判断条件:颁发者(Issuer)和主题(Subject)相同,并且证书的签名可以由其自身的公钥验证
  55. if err := x509Cert.CheckSignatureFrom(x509Cert); err == nil {
  56. isSelfSigned = true
  57. }
  58. // 将CommonName放入一个切片,以匹配[]string的类型要求
  59. var commonNames []string
  60. if x509Cert.Subject.CommonName != "" {
  61. commonNames = []string{x509Cert.Subject.CommonName}
  62. }
  63. return serverName, commonNames, DNSNames, x509Cert.NotBefore.Unix(), x509Cert.NotAfter.Unix(), isSelfSigned, nil
  64. }
  65. func (s *sslCertService) AddSslPolicy(ctx context.Context, CertIds []int64) (sslPolicyId int64, err error) {
  66. // 构造策略中引用的证书列表
  67. type sslCerts struct {
  68. IsOn bool `json:"isOn" form:"isOn"`
  69. CertId int64 `json:"certId" form:"certId"`
  70. }
  71. var sslCertsSlice []sslCerts
  72. for _, certId := range CertIds {
  73. sslCertsSlice = append(sslCertsSlice, sslCerts{
  74. IsOn: true,
  75. CertId: certId,
  76. })
  77. }
  78. sslCertsJson, err := json.Marshal(sslCertsSlice)
  79. if err != nil {
  80. return 0, fmt.Errorf("序列化SSL证书引用失败: %w", err)
  81. }
  82. // 调用CDN服务创建策略
  83. newSslPolicyId, err := s.cdn.AddSSLPolicy(ctx, v1.AddSSLPolicy{
  84. Http2Enabled: true,
  85. SslCertsJSON: sslCertsJson,
  86. MinVersion: "TLS 1.1", // 可根据安全要求调整
  87. })
  88. if err != nil {
  89. // 如果策略创建失败,需要考虑回滚或记录错误,这里直接返回错误
  90. return 0, fmt.Errorf("通过CDN添加SSL策略失败: %w", err)
  91. }
  92. return newSslPolicyId, nil
  93. }
  94. func (s *sslCertService) EditSslPolicy(ctx context.Context, sslPolicyId int64, CertIds []int64, action string) error {
  95. type sslCerts struct {
  96. IsOn bool `json:"isOn" form:"isOn"`
  97. CertId int64 `json:"certId" form:"certId"`
  98. }
  99. oldCertIds, err := s.webForwardingRep.GetSslCertId(ctx, sslPolicyId)
  100. if err != nil {
  101. return fmt.Errorf("获取SSL证书失败: %w", err)
  102. }
  103. var sslCertsSlice []sslCerts
  104. newCertIdSet := make(map[int64]struct{}, len(CertIds))
  105. for _, certId := range CertIds {
  106. newCertIdSet[certId] = struct{}{}
  107. }
  108. oldCertIdSet := make(map[int64]struct{}, len(oldCertIds))
  109. for _, oldCert := range oldCertIds {
  110. oldCertIdSet[oldCert.CertId] = struct{}{}
  111. }
  112. switch action {
  113. case "add":
  114. for _, certId := range CertIds {
  115. // 使用 oldCertIdSet 进行 O(1) 复杂度的查找。
  116. if _, found := oldCertIdSet[certId]; !found {
  117. // 如果在旧的集合中没找到,说明是新增的。
  118. sslCertsSlice = append(sslCertsSlice, sslCerts{
  119. IsOn: true,
  120. CertId: certId,
  121. })
  122. }
  123. }
  124. case "del":
  125. for _, oldCert := range oldCertIds {
  126. // 使用 newCertIdSet 进行 O(1) 复杂度的查找。
  127. if _, found := newCertIdSet[oldCert.CertId]; !found {
  128. // 如果在新的集合中没找到,说明被删除了。
  129. sslCertsSlice = append(sslCertsSlice, sslCerts{
  130. IsOn: false,
  131. CertId: oldCert.CertId,
  132. })
  133. }
  134. }
  135. }
  136. sslCertsJson, err := json.Marshal(sslCertsSlice)
  137. if err != nil {
  138. return fmt.Errorf("序列化SSL证书引用失败: %w", err)
  139. }
  140. // 调用CDN服务创建策略
  141. err = s.cdn.EditSSLPolicy(ctx, v1.SSLPolicy{
  142. SslPolicyId: sslPolicyId,
  143. Http2Enabled: true,
  144. SslCertsJSON: sslCertsJson,
  145. MinVersion: "TLS 1.1", // 可根据安全要求调整
  146. })
  147. if err != nil {
  148. // 如果策略创建失败,需要考虑回滚或记录错误,这里直接返回错误
  149. return fmt.Errorf("通过CDN添加SSL策略失败: %w", err)
  150. }
  151. return nil
  152. }
  153. func (s *sslCertService) AddSSLCert(ctx context.Context, req v1.SSL) (int64, error) {
  154. // 1. 解析证书文件,提取元数据
  155. serverName, commonNames, DNSNames, before, after, isSelfSigned, err := s.ParseCert(ctx, req.CertData, req.KeyData)
  156. if err != nil {
  157. return 0, fmt.Errorf("解析证书失败: %w", err)
  158. }
  159. // 2. 将证书添加到CDN提供商
  160. // 这是获取可以在策略中引用的 `sslCertId` 的前提
  161. newSslCertId, err := s.cdn.AddSSLCert(ctx, v1.SSlCert{
  162. IsOn: true,
  163. UserId: int64(req.CdnUserId),
  164. Name: req.Domain, // 使用域名作为证书名称
  165. ServerName: serverName,
  166. Description: req.Description,
  167. CertData: []byte(req.CertData),
  168. KeyData: []byte(req.KeyData),
  169. TimeBeginAt: before,
  170. TimeEndAt: after,
  171. DnsNames: DNSNames,
  172. CommonNames: commonNames,
  173. IsSelfSigned: isSelfSigned,
  174. })
  175. if err != nil {
  176. return 0, fmt.Errorf("添加SSL证书到CDN失败: %w", err)
  177. }
  178. return newSslCertId, nil
  179. }
  180. func (s *sslCertService) EditSSLCert(ctx context.Context, req v1.SSL) error {
  181. if req.CertData == "" && req.KeyData == "" {
  182. return nil
  183. }
  184. // 1. 解析证书文件,提取元数据
  185. serverName, commonNames, DNSNames, before, after, isSelfSigned, err := s.ParseCert(ctx, req.CertData, req.KeyData)
  186. if err != nil {
  187. return fmt.Errorf("解析证书失败: %w", err)
  188. }
  189. // 2. 将证书添加到CDN提供商
  190. err = s.cdn.EditSSLCert(ctx, v1.SSlCert{
  191. SslCertId: int64(req.CertId),
  192. IsOn: true,
  193. UserId: int64(req.CdnUserId),
  194. Name: req.Domain, // 使用域名作为证书名称
  195. ServerName: serverName,
  196. Description: req.Description,
  197. CertData: []byte(req.CertData),
  198. KeyData: []byte(req.KeyData),
  199. TimeBeginAt: before,
  200. TimeEndAt: after,
  201. DnsNames: DNSNames,
  202. CommonNames: commonNames,
  203. IsSelfSigned: isSelfSigned,
  204. })
  205. if err != nil {
  206. return fmt.Errorf("添加SSL证书到CDN失败: %w", err)
  207. }
  208. return nil
  209. }