sslcert.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  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"
  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 repository.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 repository.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. if len(oldCertIds) == 0 {
  104. return nil
  105. }
  106. var sslCertsSlice []sslCerts
  107. switch action {
  108. case "add":
  109. for _, certId := range CertIds {
  110. exist := false
  111. for _, oldCertId := range oldCertIds {
  112. if oldCertId.CertId == certId {
  113. exist = true
  114. break
  115. }
  116. }
  117. if !exist {
  118. sslCertsSlice = append(sslCertsSlice, sslCerts{
  119. IsOn: true,
  120. CertId: certId,
  121. })
  122. }
  123. }
  124. case "del":
  125. for _, oldCertId := range oldCertIds {
  126. exist := false
  127. for _, certId := range CertIds {
  128. if oldCertId.CertId == certId {
  129. exist = true
  130. break
  131. }
  132. }
  133. if !exist {
  134. sslCertsSlice = append(sslCertsSlice, sslCerts{
  135. IsOn: false,
  136. CertId: oldCertId.CertId,
  137. })
  138. }
  139. }
  140. }
  141. sslCertsJson, err := json.Marshal(sslCertsSlice)
  142. if err != nil {
  143. return fmt.Errorf("序列化SSL证书引用失败: %w", err)
  144. }
  145. // 调用CDN服务创建策略
  146. err = s.cdn.EditSSLPolicy(ctx, v1.SSLPolicy{
  147. SslPolicyId: sslPolicyId,
  148. Http2Enabled: true,
  149. SslCertsJSON: sslCertsJson,
  150. MinVersion: "TLS 1.1", // 可根据安全要求调整
  151. })
  152. if err != nil {
  153. // 如果策略创建失败,需要考虑回滚或记录错误,这里直接返回错误
  154. return fmt.Errorf("通过CDN添加SSL策略失败: %w", err)
  155. }
  156. return nil
  157. }
  158. func (s *sslCertService) AddSSLCert(ctx context.Context, req v1.SSL) (int64, error) {
  159. // 1. 解析证书文件,提取元数据
  160. serverName, commonNames, DNSNames, before, after, isSelfSigned, err := s.ParseCert(ctx, req.CertData, req.KeyData)
  161. if err != nil {
  162. return 0, fmt.Errorf("解析证书失败: %w", err)
  163. }
  164. // 2. 将证书添加到CDN提供商
  165. // 这是获取可以在策略中引用的 `sslCertId` 的前提
  166. newSslCertId, err := s.cdn.AddSSLCert(ctx, v1.SSlCert{
  167. IsOn: true,
  168. UserId: int64(req.CdnUserId),
  169. Name: req.Domain, // 使用域名作为证书名称
  170. ServerName: serverName,
  171. Description: req.Description,
  172. CertData: []byte(req.CertData),
  173. KeyData: []byte(req.KeyData),
  174. TimeBeginAt: before,
  175. TimeEndAt: after,
  176. DnsNames: DNSNames,
  177. CommonNames: commonNames,
  178. IsSelfSigned: isSelfSigned,
  179. })
  180. if err != nil {
  181. return 0, fmt.Errorf("添加SSL证书到CDN失败: %w", err)
  182. }
  183. return newSslCertId, nil
  184. }
  185. func (s *sslCertService) EditSSLCert(ctx context.Context, req v1.SSL) error {
  186. serverName, commonNames, DNSNames, before, after, isSelfSigned, err := s.ParseCert(ctx, req.CertData, req.KeyData)
  187. if err != nil {
  188. return fmt.Errorf("解析证书失败: %w", err)
  189. }
  190. // 2. 将证书添加到CDN提供商
  191. // 这是获取可以在策略中引用的 `sslCertId` 的前提
  192. err = s.cdn.EditSSLCert(ctx, v1.SSlCert{
  193. IsOn: true,
  194. UserId: int64(req.CdnUserId),
  195. Name: req.Domain, // 使用域名作为证书名称
  196. ServerName: serverName,
  197. Description: req.Description,
  198. CertData: []byte(req.CertData),
  199. KeyData: []byte(req.KeyData),
  200. TimeBeginAt: before,
  201. TimeEndAt: after,
  202. DnsNames: DNSNames,
  203. CommonNames: commonNames,
  204. IsSelfSigned: isSelfSigned,
  205. })
  206. if err != nil {
  207. return fmt.Errorf("添加SSL证书到CDN失败: %w", err)
  208. }
  209. return nil
  210. }