/* @Author: 1usir @Description: @File: service_server_api @Version: 1.0.0 @Date: 2024/7/30 10:07 */ package services import ( "context" "encoding/json" "errors" "net" "strings" "github.com/TeaOSLab/EdgeAPI/internal/db/models" "github.com/TeaOSLab/EdgeAPI/internal/db/models/acme" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs" "github.com/iwind/TeaGo/lists" ) type ( FindServerNamesByUsernameRequest struct { Username string `json:"username"` } FindServerNamesByUsernameResponse struct { Servers []*Server `json:"servers"` } Server struct { Id uint32 `json:"id"` IsOn bool `json:"isOn"` Name string `json:"name"` FirstServerName string `json:"firstServerName"` } UpdateServerWebConfigRequest struct { ServerId int64 `json:"serverId"` Domains []string `json:"domains"` CertIds []int64 `json:"certIds"` Certs []*Cert `json:"certs"` AutoCreateCert bool `json:"autoCreateCert"` } UpdateServerWebConfigResponse struct { Result *Result `json:"result"` } Result struct { Domains map[string]*Status `json:"domains,omitempty"` CertIds map[int64]*Status `json:"certIds,omitempty"` Certs []string `json:"certs,omitempty"` AutoCreateCert string `json:"autoCreateCert,omitempty"` } Status struct { Status string `json:"status"` Reason string `json:"reason"` } Cert struct { IsCA bool `json:"isCA"` Key string `json:"key"` Cert string `json:"cert"` } ResetServerWebConfigRequest struct { ServerId int64 `json:"serverId"` Domains []string `json:"domains"` CertIds []int64 `json:"certIds"` } ResetServerWebConfigResponse struct { Result *Result `json:"result"` } ) // 定制API // FindServerNamesByUsername 查找服务的域名设置 func (this *ServerService) FindServerNamesByUsername(ctx context.Context, req *FindServerNamesByUsernameRequest) (*FindServerNamesByUsernameResponse, error) { _, err := this.ValidateAdmin(ctx) if err != nil { return nil, err } var tx = this.NullTx() uid, err := models.SharedUserDAO.FindEnabledUserIdWithUsername(tx, req.Username) if err != nil { return nil, err } if uid == 0 { return &FindServerNamesByUsernameResponse{}, nil } servers, err := models.SharedServerDAO.FindAllBasicServersWithUserId(tx, uid) if err != nil { return nil, err } list := make([]*Server, len(servers)) for k, v := range servers { list[k] = &Server{v.Id, v.IsOn, v.Name, v.FirstServerName()} } return &FindServerNamesByUsernameResponse{Servers: list}, nil } // UpdateServerWebConfig 修改服务域名/申请或绑定证书 func (this *ServerService) UpdateServerWebConfig(ctx context.Context, req *UpdateServerWebConfigRequest) (*UpdateServerWebConfigResponse, error) { _, userId, err := this.ValidateAdminAndUser(ctx, true) if err != nil { return nil, err } var tx = this.NullTx() // 如果是用户调用,需要验证权限 if userId > 0 { err = models.SharedServerDAO.CheckUserServer(tx, userId, req.ServerId) if err != nil { return nil, err } } server, err := models.SharedServerDAO.FindEnabledServer(tx, req.ServerId) if err != nil { return nil, err } if server == nil { return nil, errors.New("not found server") } // 判断是否开启了HTTPS // HTTPS var sslPolicyId int64 if models.IsNotNull(server.Https) { httpsConfig := &serverconfigs.HTTPSProtocolConfig{} err := json.Unmarshal(server.Https, httpsConfig) if err != nil { return nil, err } if httpsConfig.SSLPolicyRef != nil { sslPolicyId = httpsConfig.SSLPolicyRef.SSLPolicyId } } resp := &UpdateServerWebConfigResponse{Result: &Result{Domains: make(map[string]*Status, len(req.Domains)), CertIds: make(map[int64]*Status, len(req.CertIds)), Certs: make([]string, len(req.Certs))}} if len(req.Domains) != 0 { var serverNameConfigs = []*serverconfigs.ServerNameConfig{} err = json.Unmarshal(server.ServerNames, &serverNameConfigs) if err != nil { return nil, err } // 检查域名是否已经存在 for _, domain := range req.Domains { exist, err := models.SharedServerDAO.ExistServerNameInCluster(tx, int64(server.ClusterId), domain, req.ServerId, false) if err != nil { return nil, err } if exist { resp.Result.Domains[domain] = &Status{Status: "failed", Reason: "domain is exist"} } else { if !containDomain(serverNameConfigs, domain) { serverNameConfigs = append(serverNameConfigs, &serverconfigs.ServerNameConfig{Name: domain, Type: "full"}) } resp.Result.Domains[domain] = &Status{Status: "success", Reason: "ok"} } } // 设置域名 serverNamesJSON, _ := json.Marshal(serverNameConfigs) err = models.SharedServerDAO.UpdateServerNames(tx, req.ServerId, serverNamesJSON) if err != nil { return nil, err } } certErrorFn := func(status, reason string) { for _, v := range req.CertIds { if resp.Result.CertIds[v] == nil { resp.Result.CertIds[v] = &Status{Status: status, Reason: reason} } } for k, v := range resp.Result.Certs { if v == "" { if status != "success" { resp.Result.Certs[k] = reason } else { resp.Result.Certs[k] = status } } } } // 创建证书 var newCertIds []int64 var newCACertIds []int64 topDomains := getTopDomains(req.Domains) for req.AutoCreateCert && len(req.Domains) > 0 { // 自动申请免费证书 users, _ := acme.SharedACMEUserDAO.ListACMEUsers(tx, int64(server.AdminId), int64(server.UserId), 0, 1) var acmeUserId int64 if len(users) == 0 { // 创建一个默认acme user acmeUserId, err = acme.SharedACMEUserDAO.CreateACMEUser(tx, int64(server.AdminId), int64(server.UserId), "letsencrypt", 0, "admin@qq.com", "") if err != nil { resp.Result.AutoCreateCert = err.Error() break } } else { acmeUserId = int64(users[0].Id) } for _, subs := range topDomains { taskId, err := acme.SharedACMETaskDAO.CreateACMETask(tx, int64(server.AdminId), int64(server.UserId), "http", acmeUserId, 0, "", subs, true, "") if err != nil { resp.Result.AutoCreateCert = err.Error() break } isOK, errMsg, certId := acme.SharedACMETaskDAO.RunTask(tx, taskId) if isOK { req.CertIds = append(req.CertIds, certId) resp.Result.AutoCreateCert = "success" } else { resp.Result.AutoCreateCert = errMsg } } break } if len(req.Certs) > 0 { for k, cert := range req.Certs { // 校验 var certConfig = &sslconfigs.SSLCertConfig{ IsCA: cert.IsCA, CertData: []byte(cert.Cert), KeyData: []byte(cert.Key), } err := certConfig.Init(context.TODO()) if err != nil { resp.Result.Certs[k] = "证书校验失败:" + err.Error() } certId, err := models.SharedSSLCertDAO.CreateCert(tx, int64(server.AdminId), int64(server.UserId), true, server.Name+"(自动创建证书)", "自动创建证书", "", cert.IsCA, certConfig.CertData, certConfig.KeyData, certConfig.TimeBeginAt, certConfig.TimeEndAt, certConfig.DNSNames, certConfig.CommonNames) if err != nil { resp.Result.Certs[k] = "证书创建失败:" + err.Error() } else { if cert.IsCA { newCACertIds = append(newCACertIds, certId) } else { newCertIds = append(newCertIds, certId) } } } } // 设置证书 if len(req.CertIds) > 0 || len(newCertIds) > 0 || len(newCACertIds) > 0 { for _, v := range req.CertIds { // 区分CA证书 cert, err := models.SharedSSLCertDAO.FindEnabledSSLCert(tx, v) if err != nil { resp.Result.CertIds[v] = &Status{Status: "failed", Reason: err.Error()} continue } if cert.IsCA { newCACertIds = append(newCACertIds, v) } else { newCertIds = append(newCertIds, v) } } if sslPolicyId > 0 { // 判断旧证书 policy, err := models.SharedSSLPolicyDAO.FindEnabledSSLPolicy(tx, sslPolicyId) if err != nil { certErrorFn("failed", err.Error()) return resp, nil } // certs var certs = []*sslconfigs.SSLCertRef{} if models.IsNotNull(policy.Certs) { _ = json.Unmarshal(policy.Certs, &certs) } for _, v := range newCertIds { if !containCert(certs, v) { certs = append(certs, &sslconfigs.SSLCertRef{CertId: v, IsOn: true}) } } // client CA certs var certCAs = []*sslconfigs.SSLCertRef{} if models.IsNotNull(policy.ClientCACerts) { _ = json.Unmarshal(policy.ClientCACerts, &certCAs) } for _, v := range newCACertIds { if !containCert(certCAs, v) { certCAs = append(certCAs, &sslconfigs.SSLCertRef{CertId: v, IsOn: true}) } } certJSON, _ := json.Marshal(certs) certCAJSON, _ := json.Marshal(certCAs) cipherSuites := []string{} if models.IsNotNull(policy.CipherSuites) { _ = json.Unmarshal(policy.CipherSuites, &cipherSuites) } err = models.SharedSSLPolicyDAO.UpdatePolicy(tx, sslPolicyId, policy.Http2Enabled, policy.Http3Enabled, policy.MinVersion, certJSON, policy.Hsts, policy.OcspIsOn == 1, int32(policy.ClientAuthType), certCAJSON, policy.CipherSuitesIsOn == 1, cipherSuites) if err != nil { certErrorFn("failed", err.Error()) return resp, nil } } else { var certs = make([]*sslconfigs.SSLCertRef, len(newCertIds)) var certCAs = make([]*sslconfigs.SSLCertRef, len(newCACertIds)) for k, v := range newCertIds { certs[k] = &sslconfigs.SSLCertRef{CertId: v, IsOn: true} } for k, v := range newCACertIds { certCAs[k] = &sslconfigs.SSLCertRef{CertId: v, IsOn: true} } certJSON, _ := json.Marshal(certs) certCAJSON, _ := json.Marshal(certCAs) sslPolicyId, err = models.SharedSSLPolicyDAO.CreatePolicy(tx, int64(server.AdminId), int64(server.UserId), true, false, "TLS 1.1", certJSON, nil, false, 0, certCAJSON, false, nil) if err != nil { certErrorFn("failed", err.Error()) return resp, nil } var httpsConfig = &serverconfigs.HTTPSProtocolConfig{} httpsConfig.IsOn = true httpsConfig.SSLPolicy = nil httpsConfig.SSLPolicyRef = &sslconfigs.SSLPolicyRef{ IsOn: true, SSLPolicyId: sslPolicyId, } httpsConfig.Listen = []*serverconfigs.NetworkAddressConfig{ { Protocol: serverconfigs.ProtocolHTTPS, PortRange: "443", MinPort: 443, MaxPort: 443, }, } configData, _ := json.Marshal(httpsConfig) if err = models.SharedServerDAO.UpdateServerHTTPS(tx, req.ServerId, configData); err != nil { certErrorFn("failed", err.Error()) return resp, nil } } } certErrorFn("success", "") return resp, nil } // ResetServerWebConfig 删除服务域名/解绑证书 func (this *ServerService) ResetServerWebConfig(ctx context.Context, req *ResetServerWebConfigRequest) (*ResetServerWebConfigResponse, error) { _, userId, err := this.ValidateAdminAndUser(ctx, true) if err != nil { return nil, err } var tx = this.NullTx() // 如果是用户调用,需要验证权限 if userId > 0 { err = models.SharedServerDAO.CheckUserServer(tx, userId, req.ServerId) if err != nil { return nil, err } } server, err := models.SharedServerDAO.FindEnabledServer(tx, req.ServerId) if err != nil { return nil, err } if server == nil { return nil, errors.New("not found server") } var sslPolicyId int64 if models.IsNotNull(server.Https) { httpsConfig := &serverconfigs.HTTPSProtocolConfig{} err := json.Unmarshal(server.Https, httpsConfig) if err != nil { return nil, err } sslPolicyId = httpsConfig.SSLPolicyRef.SSLPolicyId } resp := &ResetServerWebConfigResponse{Result: &Result{Domains: make(map[string]*Status, len(req.Domains)), CertIds: make(map[int64]*Status, len(req.CertIds))}} domainErrorFn := func(status, reason string) { for _, v := range req.Domains { if resp.Result.Domains[v] == nil { resp.Result.Domains[v] = &Status{Status: status, Reason: reason} } } } certErrorFn := func(status, reason string) { for _, v := range req.CertIds { if resp.Result.CertIds[v] == nil { resp.Result.CertIds[v] = &Status{Status: status, Reason: reason} } } } respFormatFn := func(status, reason string) { domainErrorFn(status, reason) certErrorFn(status, reason) } if len(req.Domains) != 0 { var serverNameConfigs = []*serverconfigs.ServerNameConfig{} var newServerNameConfigs = []*serverconfigs.ServerNameConfig{} _ = json.Unmarshal(server.ServerNames, &serverNameConfigs) // 检查域名是否已经存在 for _, server := range serverNameConfigs { if !lists.Contains(req.Domains, server.Name) { newServerNameConfigs = append(newServerNameConfigs, server) } } // 设置域名 serverNamesJSON, _ := json.Marshal(newServerNameConfigs) err = models.SharedServerDAO.UpdateServerNames(tx, req.ServerId, serverNamesJSON) if err != nil { domainErrorFn("failed", err.Error()) return nil, err } // 找到对应域名证书 certIds, err := models.SharedSSLCertDAO.ListCertIds(tx, false, false, false, 0, "", 0, req.Domains, true, 0, int64(len(req.Domains))) if err != nil { domainErrorFn("failed", err.Error()) return nil, err } req.CertIds = append(req.CertIds, certIds...) } if len(req.CertIds) != 0 { var delCertIds []int64 var delCACertIds []int64 for _, v := range req.CertIds { // 区分CA证书 cert, err := models.SharedSSLCertDAO.FindEnabledSSLCert(tx, v) if err != nil { resp.Result.CertIds[v] = &Status{Status: "failed", Reason: err.Error()} continue } if !cert.IsCA { delCertIds = append(delCertIds, v) } else { delCACertIds = append(delCACertIds, v) } } policy, err := models.SharedSSLPolicyDAO.FindEnabledSSLPolicy(tx, sslPolicyId) if err != nil { certErrorFn("failed", err.Error()) return resp, nil } // certs var certs = []*sslconfigs.SSLCertRef{} var newCerts = []*sslconfigs.SSLCertRef{} if models.IsNotNull(policy.Certs) { _ = json.Unmarshal(policy.Certs, &certs) } for _, v := range certs { if !lists.ContainsInt64(delCertIds, v.CertId) { newCerts = append(newCerts, &sslconfigs.SSLCertRef{CertId: v.CertId, IsOn: true}) } } // client CA certs var certCAs = []*sslconfigs.SSLCertRef{} var newCertCAs = []*sslconfigs.SSLCertRef{} if models.IsNotNull(policy.ClientCACerts) { _ = json.Unmarshal(policy.ClientCACerts, &certCAs) } for _, v := range certCAs { if !lists.ContainsInt64(delCACertIds, v.CertId) { newCertCAs = append(newCertCAs, &sslconfigs.SSLCertRef{CertId: v.CertId, IsOn: true}) } } cipherSuites := []string{} certJSON, _ := json.Marshal(newCerts) certCAJSON, _ := json.Marshal(newCertCAs) if models.IsNotNull(policy.CipherSuites) { _ = json.Unmarshal(policy.CipherSuites, &cipherSuites) } err = models.SharedSSLPolicyDAO.UpdatePolicy(tx, sslPolicyId, policy.Http2Enabled, policy.Http3Enabled, policy.MinVersion, certJSON, policy.Hsts, policy.OcspIsOn == 1, int32(policy.ClientAuthType), certCAJSON, policy.CipherSuitesIsOn == 1, cipherSuites) if err != nil { certErrorFn("failed", err.Error()) return resp, nil } } respFormatFn("success", "") return resp, nil } func containCert(certs []*sslconfigs.SSLCertRef, certId int64) bool { for _, cert := range certs { if cert.CertId == certId { return true } } return false } func containDomain(serverNames []*serverconfigs.ServerNameConfig, domain string) bool { for _, server := range serverNames { if server.Name == domain { return true } } return false } func getTopDomains(domains []string) (topDomains map[string][]string) { topDomains = map[string][]string{} getTopDomain := func(url string) (topDomain string) { url = strings.Split(url, "?")[0] url = strings.TrimPrefix(url, "https://") url = strings.TrimPrefix(url, "http://") url = strings.TrimPrefix(url, "//") url = strings.Split(url, "/")[0] //是ip if ip := net.ParseIP(url); ip != nil { return url } /*if matched, err := regexp.MatchString(`^\d{1,}\.\d{1,}\.\d{1,}\.\d{1,}`, url); matched && err == nil { return url }*/ var compoundSuffix, interceptLen = []string{ ".com.cn", ".net.cn", ".org.cn", ".gov.cn", ".edu.cn", }, 2 //复合后缀和截取长度 mainAddr := strings.Split(url, ":")[0] //去掉端口的地址 for _, v := range compoundSuffix { if strings.HasSuffix(mainAddr, v) { interceptLen++ break } } s := strings.Split(url, ".") if len(s) <= interceptLen { topDomain = strings.Join(s, ".") return strings.Split(topDomain, ":")[0] } topDomain = strings.Join(s[len(s)-interceptLen:], ".") return strings.Split(topDomain, ":")[0] } for _, domain := range domains { top := getTopDomain(domain) topDomains[top] = append(topDomains[top], domain) } return } // UpdateServerWebConfigForUser 用户修改服务域名/申请或绑定证书(支持用户权限) func (this *ServerService) UpdateServerWebConfigForUser(ctx context.Context, req *UpdateServerWebConfigRequest) (*UpdateServerWebConfigResponse, error) { _, userId, err := this.ValidateAdminAndUser(ctx, true) if err != nil { return nil, err } var tx = this.NullTx() // 验证serverId属于当前用户 if userId > 0 { err = models.SharedServerDAO.CheckUserServer(tx, userId, req.ServerId) if err != nil { return nil, errors.New("server not found or permission denied") } } // 复用UpdateServerWebConfig的逻辑 return this.UpdateServerWebConfig(ctx, req) } // ResetServerWebConfigForUser 用户删除服务域名/解绑证书(支持用户权限) func (this *ServerService) ResetServerWebConfigForUser(ctx context.Context, req *ResetServerWebConfigRequest) (*ResetServerWebConfigResponse, error) { _, userId, err := this.ValidateAdminAndUser(ctx, true) if err != nil { return nil, err } var tx = this.NullTx() // 验证serverId属于当前用户 if userId > 0 { err = models.SharedServerDAO.CheckUserServer(tx, userId, req.ServerId) if err != nil { return nil, errors.New("server not found or permission denied") } } // 复用ResetServerWebConfig的逻辑 return this.ResetServerWebConfig(ctx, req) }