Files
waf-platform/EdgeUser/internal/web/actions/default/servers/create.go
2026-02-14 17:28:12 +08:00

700 lines
17 KiB
Go

package servers
import (
"encoding/json"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ossconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/TeaOSLab/EdgeUser/internal/configloaders"
"github.com/TeaOSLab/EdgeUser/internal/utils/domainutils"
"github.com/TeaOSLab/EdgeUser/internal/web/actions/actionutils"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"net"
"strings"
)
type CreateAction struct {
actionutils.ParentAction
}
func (this *CreateAction) Init() {
this.Nav("", "", "create")
}
func (this *CreateAction) RunGet(params struct{}) {
// 检查用户状态
this.CheckUserStatus()
clusterIdResp, err := this.RPC().UserRPC().FindUserNodeClusterId(this.UserContext(), &pb.FindUserNodeClusterIdRequest{UserId: this.UserId()})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["clusterId"] = clusterIdResp.NodeClusterId
// 套餐
userPlansResp, err := this.RPC().UserPlanRPC().FindAllEnabledUserPlansForServer(this.UserContext(), &pb.FindAllEnabledUserPlansForServerRequest{
UserId: this.UserId(),
ServerId: 0,
})
if err != nil {
this.ErrorPage(err)
return
}
var userPlanMaps = []maps.Map{}
for _, userPlan := range userPlansResp.UserPlans {
if userPlan.Plan == nil {
continue
}
var name = userPlan.Plan.Name
if len(userPlan.Name) > 0 {
name += "-" + userPlan.Name
}
userPlanMaps = append(userPlanMaps, maps.Map{
"id": userPlan.Id,
"name": name,
"dayTo": userPlan.DayTo,
})
}
this.Data["userPlans"] = userPlanMaps
// 是否必须使用套餐
this.Data["requirePlan"] = false
userServerConfig, err := configloaders.LoadServerConfig()
if err == nil && userServerConfig.RequirePlan {
this.Data["requirePlan"] = true
}
// OSS
this.Data["ossTypes"] = ossconfigs.FindAllOSSTypes()
this.Data["ossBucketParams"] = ossconfigs.FindAllOSSBucketParamDefinitions()
// 默认开启的选择
this.Data["accessLogIsOn"] = true
this.Data["websocketIsOn"] = true
this.Data["cacheIsOn"] = true
this.Data["wafIsOn"] = true
this.Data["remoteAddrIsOn"] = true
this.Data["statIsOn"] = true
this.Show()
}
func (this *CreateAction) RunPost(params struct {
ServerNames []byte
Protocols []string
CertIdsJSON []byte
OriginsJSON []byte
RequestHostType int32
RequestHost string
CacheCondsJSON []byte
GroupIds []int64
UserPlanId int64
AccessLogIsOn bool
WebsocketIsOn bool
CacheIsOn bool
WafIsOn bool
RemoteAddrIsOn bool
StatIsOn bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
// 检查用户所在集群
clusterIdResp, err := this.RPC().UserRPC().FindUserNodeClusterId(this.UserContext(), &pb.FindUserNodeClusterIdRequest{UserId: this.UserId()})
if err != nil {
this.ErrorPage(err)
return
}
var clusterId = clusterIdResp.NodeClusterId
if len(params.ServerNames) == 0 {
this.Data["requireServerNames"] = true
this.Fail("请添加要加速的域名")
}
var serverNames = []*serverconfigs.ServerNameConfig{}
err = json.Unmarshal(params.ServerNames, &serverNames)
if err != nil {
this.Fail("域名参数解析错误:" + err.Error())
}
serverconfigs.NormalizeServerNames(serverNames)
var allDomainNames = serverconfigs.PlainServerNames(serverNames)
if len(allDomainNames) == 0 {
this.Data["requireServerNames"] = true
this.Fail("请添加要加速的域名")
}
for _, domainName := range allDomainNames {
if !domainutils.ValidateDomainFormat(strings.ReplaceAll(domainName, "*.", "") /** 支持泛域名 **/) {
this.Fail("域名'" + domainName + "'输入错误")
}
}
// 检查域名是否已经存在
dupResp, err := this.RPC().ServerRPC().CheckServerNameDuplicationInNodeCluster(this.UserContext(), &pb.CheckServerNameDuplicationInNodeClusterRequest{
ServerNames: allDomainNames,
NodeClusterId: clusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
if len(dupResp.DuplicatedServerNames) > 0 {
this.Fail("域名 " + strings.Join(dupResp.DuplicatedServerNames, ", ") + " 已经被其他网站所占用,不能重复使用")
}
serverNamesJSON, err := json.Marshal(serverNames)
if err != nil {
this.ErrorPage(err)
return
}
// 协议:未选择时默认 HTTP
if len(params.Protocols) == 0 {
params.Protocols = []string{"http"}
}
httpConfig := &serverconfigs.HTTPProtocolConfig{}
httpsConfig := &serverconfigs.HTTPSProtocolConfig{}
// HTTP
if lists.ContainsString(params.Protocols, "http") {
httpConfig.IsOn = true
httpConfig.Listen = []*serverconfigs.NetworkAddressConfig{
{
Protocol: serverconfigs.ProtocolHTTP,
Host: "",
PortRange: "80",
},
}
}
// HTTPS
if lists.ContainsString(params.Protocols, "https") {
httpsConfig.IsOn = true
httpsConfig.Listen = []*serverconfigs.NetworkAddressConfig{
{
Protocol: serverconfigs.ProtocolHTTPS,
Host: "",
PortRange: "443",
},
}
if len(params.CertIdsJSON) == 0 {
this.Fail("请选择或者上传HTTPS证书")
}
certIds := []int64{}
err := json.Unmarshal(params.CertIdsJSON, &certIds)
if err != nil {
this.ErrorPage(err)
return
}
if len(certIds) == 0 {
this.Fail("请选择或者上传HTTPS证书")
}
certRefs := []*sslconfigs.SSLCertRef{}
for _, certId := range certIds {
certRefs = append(certRefs, &sslconfigs.SSLCertRef{
IsOn: true,
CertId: certId,
})
}
certRefsJSON, err := json.Marshal(certRefs)
if err != nil {
this.ErrorPage(err)
return
}
// 创建策略
sslPolicyIdResp, err := this.RPC().SSLPolicyRPC().CreateSSLPolicy(this.UserContext(), &pb.CreateSSLPolicyRequest{
Http2Enabled: false,
Http3Enabled: false,
MinVersion: "TLS 1.1",
SslCertsJSON: certRefsJSON,
HstsJSON: nil,
OcspIsOn: false,
ClientAuthType: 0,
ClientCACertsJSON: nil,
CipherSuites: nil,
CipherSuitesIsOn: false,
})
if err != nil {
this.ErrorPage(err)
return
}
httpsConfig.SSLPolicyRef = &sslconfigs.SSLPolicyRef{
IsOn: true,
SSLPolicyId: sslPolicyIdResp.SslPolicyId,
}
}
// 源站信息
var originMaps = []maps.Map{}
if len(params.OriginsJSON) == 0 {
this.Fail("请输入源站信息")
}
err = json.Unmarshal(params.OriginsJSON, &originMaps)
if err != nil {
this.ErrorPage(err)
return
}
if len(originMaps) == 0 {
this.Fail("请输入源站信息")
}
var primaryOriginRefs = []*serverconfigs.OriginRef{}
var backupOriginRefs = []*serverconfigs.OriginRef{}
var hasOSS = false
for _, originMap := range originMaps {
var host = originMap.GetString("host")
var isPrimary = originMap.GetBool("isPrimary")
var scheme = originMap.GetString("scheme")
if ossconfigs.IsOSSProtocol(scheme) {
hasOSS = true
continue
}
if len(host) == 0 {
this.Fail("源站地址不能为空")
}
if strings.Index(host, ":") > 0 {
_, _, err := net.SplitHostPort(host)
if err != nil {
this.Fail("源站地址'" + host + "'格式错误")
}
} else if !domainutils.ValidateDomainFormat(host) {
this.Fail("源站地址'" + host + "'格式错误")
}
if scheme != "http" && scheme != "https" {
this.Fail("错误的源站协议")
}
addrHost, addrPort, err := net.SplitHostPort(host)
if err != nil {
addrHost = host
if scheme == "http" {
addrPort = "80"
} else if scheme == "https" {
addrPort = "443"
}
}
originIdResp, err := this.RPC().OriginRPC().CreateOrigin(this.UserContext(), &pb.CreateOriginRequest{
Name: "",
Addr: &pb.NetworkAddress{
Protocol: scheme,
Host: addrHost,
PortRange: addrPort,
},
Description: "",
Weight: 10,
IsOn: true,
})
if err != nil {
this.ErrorPage(err)
return
}
if isPrimary {
primaryOriginRefs = append(primaryOriginRefs, &serverconfigs.OriginRef{
IsOn: true,
OriginId: originIdResp.OriginId,
})
} else {
backupOriginRefs = append(backupOriginRefs, &serverconfigs.OriginRef{
IsOn: true,
OriginId: originIdResp.OriginId,
})
}
}
primaryOriginsJSON, err := json.Marshal(primaryOriginRefs)
if err != nil {
this.ErrorPage(err)
return
}
backupOriginsJSON, err := json.Marshal(backupOriginRefs)
if err != nil {
this.ErrorPage(err)
return
}
var scheduling = &serverconfigs.SchedulingConfig{
Code: "random",
Options: nil,
}
schedulingJSON, err := json.Marshal(scheduling)
if err != nil {
this.ErrorPage(err)
return
}
// 反向代理
reverseProxyResp, err := this.RPC().ReverseProxyRPC().CreateReverseProxy(this.UserContext(), &pb.CreateReverseProxyRequest{
SchedulingJSON: schedulingJSON,
PrimaryOriginsJSON: primaryOriginsJSON,
BackupOriginsJSON: backupOriginsJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
var reverseProxyId = reverseProxyResp.ReverseProxyId
var reverseProxyRef = &serverconfigs.ReverseProxyRef{
IsPrior: false,
IsOn: true,
ReverseProxyId: reverseProxyId,
}
reverseProxyRefJSON, err := json.Marshal(reverseProxyRef)
if err != nil {
this.ErrorPage(err)
return
}
if params.RequestHostType > 0 {
_, err = this.RPC().ReverseProxyRPC().UpdateReverseProxy(this.UserContext(), &pb.UpdateReverseProxyRequest{
ReverseProxyId: reverseProxyId,
RequestHostType: params.RequestHostType,
RequestHost: params.RequestHost,
RequestURI: "",
StripPrefix: "",
AutoFlush: false,
})
if err != nil {
this.ErrorPage(err)
}
}
// 缓存设置
var cacheCondMaps = []maps.Map{}
if len(params.CacheCondsJSON) > 0 {
err = json.Unmarshal(params.CacheCondsJSON, &cacheCondMaps)
if err != nil {
this.ErrorPage(err)
return
}
}
var cacheRefs = []*serverconfigs.HTTPCacheRef{}
if len(cacheCondMaps) > 0 {
for _, condMap := range cacheCondMaps {
var durationMap = condMap.GetMap("duration")
if durationMap == nil {
continue
}
var duration = &shared.TimeDuration{
Count: durationMap.GetInt64("count"),
Unit: durationMap.GetString("unit"),
}
var value string
var param string
var operator string
var condType = condMap.GetString("type")
switch condType {
case "url-extension":
param = "${requestPathExtension}"
operator = shared.RequestCondOperatorIn
result := []string{}
v := condMap.GetString("value")
if len(v) > 0 {
err = json.Unmarshal([]byte(v), &result)
if err != nil {
this.ErrorPage(err)
return
}
value = v
}
case "url-prefix":
param = "${requestPath}"
operator = shared.RequestCondOperatorHasPrefix
value = condMap.GetString("value")
default:
continue
}
var conds = &shared.HTTPRequestCondsConfig{
IsOn: true,
Connector: "or",
Groups: []*shared.HTTPRequestCondGroup{
{
IsOn: true,
Connector: "or",
Conds: []*shared.HTTPRequestCond{
{
IsRequest: true,
Type: condType,
Param: param,
Operator: operator,
Value: value,
},
},
},
},
}
var cacheRef = &serverconfigs.HTTPCacheRef{
IsOn: true,
CachePolicyId: 0,
Key: "${scheme}://${host}${requestURI}",
Life: duration,
Status: []int{200},
MaxSize: &shared.SizeCapacity{
Count: 128,
Unit: shared.SizeCapacityUnitMB,
},
SkipResponseCacheControlValues: nil,
SkipResponseSetCookie: true,
EnableRequestCachePragma: false,
Conds: conds,
CachePolicy: nil,
AllowChunkedEncoding: true,
AllowPartialContent: false,
}
cacheRefs = append(cacheRefs, cacheRef)
}
}
var cacheConfig = &serverconfigs.HTTPCacheConfig{
IsPrior: false,
IsOn: true,
CacheRefs: cacheRefs,
AddStatusHeader: true,
}
cacheConfigJSON, err := cacheConfig.AsJSON()
if err != nil {
this.ErrorPage(err)
return
}
// 检查是否必须使用套餐
userServerConfig, err := configloaders.LoadServerConfig()
if err == nil {
if params.UserPlanId <= 0 && userServerConfig.RequirePlan {
this.Fail("请选择套餐")
}
}
// 检查套餐
if params.UserPlanId > 0 {
userPlanResp, err := this.RPC().UserPlanRPC().FindEnabledUserPlan(this.UserContext(), &pb.FindEnabledUserPlanRequest{UserPlanId: params.UserPlanId})
if err != nil {
this.ErrorPage(err)
return
}
var userPlan = userPlanResp.UserPlan
if userPlan == nil || userPlan.UserId != this.UserId() {
this.Fail("找不到要使用的套餐")
}
}
// 开始保存
httpJSON, err := httpConfig.AsJSON()
if err != nil {
this.ErrorPage(err)
return
}
httpsJSON, err := httpsConfig.AsJSON()
if err != nil {
this.ErrorPage(err)
return
}
createResp, err := this.RPC().ServerRPC().CreateServer(this.UserContext(), &pb.CreateServerRequest{
UserId: this.UserId(),
AdminId: 0,
Type: serverconfigs.ServerTypeHTTPProxy,
Name: serverNames[0].Name,
Description: "",
ServerNamesJSON: serverNamesJSON,
HttpJSON: httpJSON,
HttpsJSON: httpsJSON,
TcpJSON: nil,
TlsJSON: nil,
UdpJSON: nil,
WebId: 0,
ReverseProxyJSON: reverseProxyRefJSON,
ServerGroupIds: params.GroupIds,
NodeClusterId: clusterId,
IncludeNodesJSON: nil,
ExcludeNodesJSON: nil,
})
if err != nil {
this.ErrorPage(err)
return
}
var serverId = createResp.ServerId
defer this.CreateLogInfo(codes.Server_LogCreateServer, serverId)
// 保存缓存设置
webIdResp, err := this.RPC().HTTPWebRPC().CreateHTTPWeb(this.UserContext(), &pb.CreateHTTPWebRequest{RootJSON: nil})
if err != nil {
this.ErrorPage(err)
return
}
var webId = webIdResp.HttpWebId
_, err = this.RPC().ServerRPC().UpdateServerWeb(this.UserContext(), &pb.UpdateServerWebRequest{
ServerId: serverId,
WebId: webId,
})
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().HTTPWebRPC().UpdateHTTPWebCache(this.UserContext(), &pb.UpdateHTTPWebCacheRequest{
HttpWebId: webId,
CacheJSON: cacheConfigJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
// ========== 默认开启的功能 ==========
// 1. 访问日志
if params.AccessLogIsOn {
_, err = this.RPC().HTTPWebRPC().UpdateHTTPWebAccessLog(this.UserContext(), &pb.UpdateHTTPWebAccessLogRequest{
HttpWebId: webId,
AccessLogJSON: []byte(`{
"isPrior": false,
"isOn": true,
"fields": [1, 2, 6, 7],
"status1": true,
"status2": true,
"status3": true,
"status4": true,
"status5": true,
"storageOnly": false,
"storagePolicies": [],
"firewallOnly": false
}`),
})
if err != nil {
this.ErrorPage(err)
return
}
}
// 2. WebSocket
if params.WebsocketIsOn {
createWebSocketResp, err := this.RPC().HTTPWebsocketRPC().CreateHTTPWebsocket(this.UserContext(), &pb.CreateHTTPWebsocketRequest{
HandshakeTimeoutJSON: []byte(`{"count": 30, "unit": "second"}`),
AllowAllOrigins: true,
AllowedOrigins: nil,
RequestSameOrigin: true,
RequestOrigin: "",
})
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().HTTPWebRPC().UpdateHTTPWebWebsocket(this.UserContext(), &pb.UpdateHTTPWebWebsocketRequest{
HttpWebId: webId,
WebsocketJSON: []byte(`{"isPrior": false, "isOn": true, "websocketId": ` + types.String(createWebSocketResp.WebsocketId) + `}`),
})
if err != nil {
this.ErrorPage(err)
return
}
}
// 3. WAF 防火墙
if params.WafIsOn {
var firewallRef = &firewallconfigs.HTTPFirewallRef{
IsPrior: false,
IsOn: true,
FirewallPolicyId: 0,
}
firewallRefJSON, err := json.Marshal(firewallRef)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().HTTPWebRPC().UpdateHTTPWebFirewall(this.UserContext(), &pb.UpdateHTTPWebFirewallRequest{
HttpWebId: webId,
FirewallJSON: firewallRefJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
}
// 4. 从上级代理中读取IP
if params.RemoteAddrIsOn {
var remoteAddrConfig = &serverconfigs.HTTPRemoteAddrConfig{
IsOn: true,
Value: "${remoteAddr}",
Type: serverconfigs.HTTPRemoteAddrTypeProxy,
}
remoteAddrConfigJSON, err := json.Marshal(remoteAddrConfig)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().HTTPWebRPC().UpdateHTTPWebRemoteAddr(this.UserContext(), &pb.UpdateHTTPWebRemoteAddrRequest{
HttpWebId: webId,
RemoteAddrJSON: remoteAddrConfigJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
}
// 5. 统计
if params.StatIsOn {
var statConfig = &serverconfigs.HTTPStatRef{
IsPrior: false,
IsOn: true,
}
statJSON, err := json.Marshal(statConfig)
if err != nil {
this.ErrorPage(err)
return
}
_, err = this.RPC().HTTPWebRPC().UpdateHTTPWebStat(this.UserContext(), &pb.UpdateHTTPWebStatRequest{
HttpWebId: webId,
StatJSON: statJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
}
// 绑定套餐
_, err = this.RPC().ServerRPC().UpdateServerUserPlan(this.UserContext(), &pb.UpdateServerUserPlanRequest{
ServerId: serverId,
UserPlanId: params.UserPlanId,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["serverId"] = serverId
this.Data["hasOSS"] = hasOSS
this.Success()
}