Files
waf-platform/EdgeAPI/internal/rpc/services/service_server_api.go
2026-02-04 20:27:13 +08:00

573 lines
18 KiB
Go

/*
@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)
}