实现证书管理

This commit is contained in:
GoEdgeLab
2020-09-30 17:46:38 +08:00
parent 81f2982abc
commit 76575a9e47
35 changed files with 912 additions and 193 deletions

View File

@@ -16,56 +16,18 @@ import (
"time"
)
// RPC客户端
type RPCClient struct {
apiConfig *configs.APIConfig
adminClients []pb.AdminServiceClient
nodeClients []pb.NodeServiceClient
nodeGrantClients []pb.NodeGrantServiceClient
nodeClusterClients []pb.NodeClusterServiceClient
nodeIPAddressClients []pb.NodeIPAddressServiceClient
serverClients []pb.ServerServiceClient
apiNodeClients []pb.APINodeServiceClient
originClients []pb.OriginServiceClient
httpWebClients []pb.HTTPWebServiceClient
reverseProxyClients []pb.ReverseProxyServiceClient
httpGzipClients []pb.HTTPGzipServiceClient
httpHeaderPolicyClients []pb.HTTPHeaderPolicyServiceClient
httpHeaderClients []pb.HTTPHeaderServiceClient
httpPageClients []pb.HTTPPageServiceClient
httpAccessLogPolicyClients []pb.HTTPAccessLogPolicyServiceClient
httpCachePolicyClients []pb.HTTPCachePolicyServiceClient
httpFirewallPolicyClients []pb.HTTPFirewallPolicyServiceClient
httpLocationClients []pb.HTTPLocationServiceClient
httpWebsocketClients []pb.HTTPWebsocketServiceClient
httpRewriteRuleClients []pb.HTTPRewriteRuleServiceClient
apiConfig *configs.APIConfig
conns []*grpc.ClientConn
}
// 构造新的RPC客户端
func NewRPCClient(apiConfig *configs.APIConfig) (*RPCClient, error) {
if apiConfig == nil {
return nil, errors.New("api config should not be nil")
}
adminClients := []pb.AdminServiceClient{}
nodeClients := []pb.NodeServiceClient{}
nodeGrantClients := []pb.NodeGrantServiceClient{}
nodeClusterClients := []pb.NodeClusterServiceClient{}
nodeIPAddressClients := []pb.NodeIPAddressServiceClient{}
serverClients := []pb.ServerServiceClient{}
apiNodeClients := []pb.APINodeServiceClient{}
originClients := []pb.OriginServiceClient{}
httpWebClients := []pb.HTTPWebServiceClient{}
reverseProxyClients := []pb.ReverseProxyServiceClient{}
httpGzipClients := []pb.HTTPGzipServiceClient{}
httpHeaderPolicyClients := []pb.HTTPHeaderPolicyServiceClient{}
httpHeaderClients := []pb.HTTPHeaderServiceClient{}
httpPageClients := []pb.HTTPPageServiceClient{}
httpAccessLogPolicyClients := []pb.HTTPAccessLogPolicyServiceClient{}
httpCachePolicyClients := []pb.HTTPCachePolicyServiceClient{}
httpFirewallPolicyClients := []pb.HTTPFirewallPolicyServiceClient{}
httpLocationClients := []pb.HTTPLocationServiceClient{}
httpWebsocketClients := []pb.HTTPWebsocketServiceClient{}
httpRewriteRuleClients := []pb.HTTPRewriteRuleServiceClient{}
conns := []*grpc.ClientConn{}
for _, endpoint := range apiConfig.RPC.Endpoints {
conn, err := grpc.Dial(endpoint, grpc.WithInsecure())
@@ -78,195 +40,97 @@ func NewRPCClient(apiConfig *configs.APIConfig) (*RPCClient, error) {
return nil, errors.New("[RPC]no available endpoints")
}
// node clients
for _, conn := range conns {
adminClients = append(adminClients, pb.NewAdminServiceClient(conn))
nodeClients = append(nodeClients, pb.NewNodeServiceClient(conn))
nodeGrantClients = append(nodeGrantClients, pb.NewNodeGrantServiceClient(conn))
nodeClusterClients = append(nodeClusterClients, pb.NewNodeClusterServiceClient(conn))
nodeIPAddressClients = append(nodeIPAddressClients, pb.NewNodeIPAddressServiceClient(conn))
serverClients = append(serverClients, pb.NewServerServiceClient(conn))
apiNodeClients = append(apiNodeClients, pb.NewAPINodeServiceClient(conn))
originClients = append(originClients, pb.NewOriginServiceClient(conn))
httpWebClients = append(httpWebClients, pb.NewHTTPWebServiceClient(conn))
reverseProxyClients = append(reverseProxyClients, pb.NewReverseProxyServiceClient(conn))
httpGzipClients = append(httpGzipClients, pb.NewHTTPGzipServiceClient(conn))
httpHeaderPolicyClients = append(httpHeaderPolicyClients, pb.NewHTTPHeaderPolicyServiceClient(conn))
httpHeaderClients = append(httpHeaderClients, pb.NewHTTPHeaderServiceClient(conn))
httpPageClients = append(httpPageClients, pb.NewHTTPPageServiceClient(conn))
httpAccessLogPolicyClients = append(httpAccessLogPolicyClients, pb.NewHTTPAccessLogPolicyServiceClient(conn))
httpCachePolicyClients = append(httpCachePolicyClients, pb.NewHTTPCachePolicyServiceClient(conn))
httpFirewallPolicyClients = append(httpFirewallPolicyClients, pb.NewHTTPFirewallPolicyServiceClient(conn))
httpLocationClients = append(httpLocationClients, pb.NewHTTPLocationServiceClient(conn))
httpWebsocketClients = append(httpWebsocketClients, pb.NewHTTPWebsocketServiceClient(conn))
httpRewriteRuleClients = append(httpRewriteRuleClients, pb.NewHTTPRewriteRuleServiceClient(conn))
}
return &RPCClient{
apiConfig: apiConfig,
adminClients: adminClients,
nodeClients: nodeClients,
nodeGrantClients: nodeGrantClients,
nodeClusterClients: nodeClusterClients,
nodeIPAddressClients: nodeIPAddressClients,
serverClients: serverClients,
apiNodeClients: apiNodeClients,
originClients: originClients,
httpWebClients: httpWebClients,
reverseProxyClients: reverseProxyClients,
httpGzipClients: httpGzipClients,
httpHeaderPolicyClients: httpHeaderPolicyClients,
httpHeaderClients: httpHeaderClients,
httpPageClients: httpPageClients,
httpAccessLogPolicyClients: httpAccessLogPolicyClients,
httpCachePolicyClients: httpCachePolicyClients,
httpFirewallPolicyClients: httpFirewallPolicyClients,
httpLocationClients: httpLocationClients,
httpWebsocketClients: httpWebsocketClients,
httpRewriteRuleClients: httpRewriteRuleClients,
apiConfig: apiConfig,
conns: conns,
}, nil
}
func (this *RPCClient) AdminRPC() pb.AdminServiceClient {
if len(this.adminClients) > 0 {
return this.adminClients[rands.Int(0, len(this.adminClients)-1)]
}
return nil
return pb.NewAdminServiceClient(this.pickConn())
}
func (this *RPCClient) NodeRPC() pb.NodeServiceClient {
if len(this.nodeClients) > 0 {
return this.nodeClients[rands.Int(0, len(this.nodeClients)-1)]
}
return nil
return pb.NewNodeServiceClient(this.pickConn())
}
func (this *RPCClient) NodeGrantRPC() pb.NodeGrantServiceClient {
if len(this.nodeGrantClients) > 0 {
return this.nodeGrantClients[rands.Int(0, len(this.nodeGrantClients)-1)]
}
return nil
return pb.NewNodeGrantServiceClient(this.pickConn())
}
func (this *RPCClient) NodeClusterRPC() pb.NodeClusterServiceClient {
if len(this.nodeClusterClients) > 0 {
return this.nodeClusterClients[rands.Int(0, len(this.nodeClusterClients)-1)]
}
return nil
return pb.NewNodeClusterServiceClient(this.pickConn())
}
func (this *RPCClient) NodeIPAddressRPC() pb.NodeIPAddressServiceClient {
if len(this.nodeIPAddressClients) > 0 {
return this.nodeIPAddressClients[rands.Int(0, len(this.nodeIPAddressClients)-1)]
}
return nil
return pb.NewNodeIPAddressServiceClient(this.pickConn())
}
func (this *RPCClient) ServerRPC() pb.ServerServiceClient {
if len(this.serverClients) > 0 {
return this.serverClients[rands.Int(0, len(this.serverClients)-1)]
}
return nil
return pb.NewServerServiceClient(this.pickConn())
}
func (this *RPCClient) APINodeRPC() pb.APINodeServiceClient {
if len(this.apiNodeClients) > 0 {
return this.apiNodeClients[rands.Int(0, len(this.apiNodeClients)-1)]
}
return nil
return pb.NewAPINodeServiceClient(this.pickConn())
}
func (this *RPCClient) OriginRPC() pb.OriginServiceClient {
if len(this.originClients) > 0 {
return this.originClients[rands.Int(0, len(this.originClients)-1)]
}
return nil
return pb.NewOriginServiceClient(this.pickConn())
}
func (this *RPCClient) HTTPWebRPC() pb.HTTPWebServiceClient {
if len(this.httpWebClients) > 0 {
return this.httpWebClients[rands.Int(0, len(this.httpWebClients)-1)]
}
return nil
return pb.NewHTTPWebServiceClient(this.pickConn())
}
func (this *RPCClient) ReverseProxyRPC() pb.ReverseProxyServiceClient {
if len(this.reverseProxyClients) > 0 {
return this.reverseProxyClients[rands.Int(0, len(this.reverseProxyClients)-1)]
}
return nil
return pb.NewReverseProxyServiceClient(this.pickConn())
}
func (this *RPCClient) HTTPGzipRPC() pb.HTTPGzipServiceClient {
if len(this.httpGzipClients) > 0 {
return this.httpGzipClients[rands.Int(0, len(this.httpGzipClients)-1)]
}
return nil
return pb.NewHTTPGzipServiceClient(this.pickConn())
}
func (this *RPCClient) HTTPHeaderRPC() pb.HTTPHeaderServiceClient {
if len(this.httpHeaderClients) > 0 {
return this.httpHeaderClients[rands.Int(0, len(this.httpHeaderClients)-1)]
}
return nil
return pb.NewHTTPHeaderServiceClient(this.pickConn())
}
func (this *RPCClient) HTTPHeaderPolicyRPC() pb.HTTPHeaderPolicyServiceClient {
if len(this.httpHeaderPolicyClients) > 0 {
return this.httpHeaderPolicyClients[rands.Int(0, len(this.httpHeaderPolicyClients)-1)]
}
return nil
return pb.NewHTTPHeaderPolicyServiceClient(this.pickConn())
}
func (this *RPCClient) HTTPPageRPC() pb.HTTPPageServiceClient {
if len(this.httpPageClients) > 0 {
return this.httpPageClients[rands.Int(0, len(this.httpPageClients)-1)]
}
return nil
return pb.NewHTTPPageServiceClient(this.pickConn())
}
func (this *RPCClient) HTTPAccessLogPolicyRPC() pb.HTTPAccessLogPolicyServiceClient {
if len(this.httpAccessLogPolicyClients) > 0 {
return this.httpAccessLogPolicyClients[rands.Int(0, len(this.httpAccessLogPolicyClients)-1)]
}
return nil
return pb.NewHTTPAccessLogPolicyServiceClient(this.pickConn())
}
func (this *RPCClient) HTTPCachePolicyRPC() pb.HTTPCachePolicyServiceClient {
if len(this.httpCachePolicyClients) > 0 {
return this.httpCachePolicyClients[rands.Int(0, len(this.httpCachePolicyClients)-1)]
}
return nil
return pb.NewHTTPCachePolicyServiceClient(this.pickConn())
}
func (this *RPCClient) HTTPFirewallPolicyRPC() pb.HTTPFirewallPolicyServiceClient {
if len(this.httpFirewallPolicyClients) > 0 {
return this.httpFirewallPolicyClients[rands.Int(0, len(this.httpFirewallPolicyClients)-1)]
}
return nil
return pb.NewHTTPFirewallPolicyServiceClient(this.pickConn())
}
func (this *RPCClient) HTTPLocationRPC() pb.HTTPLocationServiceClient {
if len(this.httpLocationClients) > 0 {
return this.httpLocationClients[rands.Int(0, len(this.httpLocationClients)-1)]
}
return nil
return pb.NewHTTPLocationServiceClient(this.pickConn())
}
func (this *RPCClient) HTTPWebsocketRPC() pb.HTTPWebsocketServiceClient {
if len(this.httpWebsocketClients) > 0 {
return this.httpWebsocketClients[rands.Int(0, len(this.httpWebsocketClients)-1)]
}
return nil
return pb.NewHTTPWebsocketServiceClient(this.pickConn())
}
func (this *RPCClient) HTTPRewriteRuleRPC() pb.HTTPRewriteRuleServiceClient {
if len(this.httpRewriteRuleClients) > 0 {
return this.httpRewriteRuleClients[rands.Int(0, len(this.httpRewriteRuleClients)-1)]
}
return nil
return pb.NewHTTPRewriteRuleServiceClient(this.pickConn())
}
func (this *RPCClient) SSLCertRPC() pb.SSLCertServiceClient {
return pb.NewSSLCertServiceClient(this.pickConn())
}
// 构造上下文
func (this *RPCClient) Context(adminId int64) context.Context {
ctx := context.Background()
m := maps.Map{
@@ -288,3 +152,11 @@ func (this *RPCClient) Context(adminId int64) context.Context {
ctx = metadata.AppendToOutgoingContext(ctx, "nodeId", this.apiConfig.NodeId, "token", token)
return ctx
}
// 随机选择一个连接
func (this *RPCClient) pickConn() *grpc.ClientConn {
if len(this.conns) == 0 {
return nil
}
return this.conns[rands.Int(0, len(this.conns)-1)]
}

View File

@@ -18,7 +18,7 @@ func (this *ComponentHelper) BeforeAction(action *actions.ActionObject) {
if action.Request.Method != http.MethodGet {
return
}
action.Data["teaMenu"] = "server"
action.Data["teaMenu"] = "servers"
action.Data["mainTab"] = "component"
// 顶部标签栏

View File

@@ -0,0 +1,77 @@
package ssl
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type CertPopupAction struct {
actionutils.ParentAction
}
func (this *CertPopupAction) Init() {
}
func (this *CertPopupAction) RunGet(params struct {
CertId int64
}) {
certResp, err := this.RPC().SSLCertRPC().FindEnabledSSLCertConfig(this.AdminContext(), &pb.FindEnabledSSLCertConfigRequest{CertId: params.CertId})
if err != nil {
this.ErrorPage(err)
return
}
certConfig := &sslconfigs.SSLCertConfig{}
err = json.Unmarshal(certResp.CertJSON, certConfig)
if err != nil {
this.ErrorPage(err)
return
}
reverseCommonNames := []string{}
for i := len(certConfig.CommonNames) - 1; i >= 0; i-- {
reverseCommonNames = append(reverseCommonNames, certConfig.CommonNames[i])
}
this.Data["info"] = maps.Map{
"id": certConfig.Id,
"name": certConfig.Name,
"description": certConfig.Description,
"isOn": certConfig.IsOn,
"isAvailable": certConfig.TimeEndAt >= time.Now().Unix(),
"commonNames": reverseCommonNames,
"dnsNames": certConfig.DNSNames,
// TODO 检查是否为7天或30天内过期
"beginTime": timeutil.FormatTime("Y-m-d H:i:s", certConfig.TimeBeginAt),
"endTime": timeutil.FormatTime("Y-m-d H:i:s", certConfig.TimeEndAt),
"isCA": certConfig.IsCA,
"certString": string(certConfig.CertData),
"keyString": string(certConfig.KeyData),
}
// 引入的服务
serversResp, err := this.RPC().ServerRPC().FindAllServersWithSSLCertId(this.AdminContext(), &pb.FindAllServersWithSSLCertIdRequest{CertId: params.CertId})
if err != nil {
this.ErrorPage(err)
return
}
serverMaps := []maps.Map{}
for _, server := range serversResp.Servers {
serverMaps = append(serverMaps, maps.Map{
"id": server.Id,
"isOn": server.IsOn,
"name": server.Name,
"type": server.Type,
})
}
this.Data["servers"] = serverMaps
this.Show()
}

View File

@@ -0,0 +1,32 @@
package ssl
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
CertId int64
}) {
// 是否正在被使用
countResp, err := this.RPC().ServerRPC().CountServersWithSSLCertId(this.AdminContext(), &pb.CountServersWithSSLCertIdRequest{CertId: params.CertId})
if err != nil {
this.ErrorPage(err)
return
}
if countResp.Count > 0 {
this.Fail("此证书正在被某些服务引用,请先修改服务后再删除。")
}
_, err = this.RPC().SSLCertRPC().DeleteSSLCert(this.AdminContext(), &pb.DeleteSSLCertRequest{CertId: params.CertId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,37 @@
package ssl
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"strconv"
)
type DownloadCertAction struct {
actionutils.ParentAction
}
func (this *DownloadCertAction) Init() {
this.Nav("", "", "")
}
func (this *DownloadCertAction) RunGet(params struct {
CertId int64
}) {
certResp, err := this.RPC().SSLCertRPC().FindEnabledSSLCertConfig(this.AdminContext(), &pb.FindEnabledSSLCertConfigRequest{CertId: params.CertId})
if err != nil {
this.ErrorPage(err)
return
}
certConfig := &sslconfigs.SSLCertConfig{}
err = json.Unmarshal(certResp.CertJSON, certConfig)
if err != nil {
this.ErrorPage(err)
return
}
this.AddHeader("Content-Disposition", "attachment; filename=\"cert-"+strconv.FormatInt(params.CertId, 10)+".pem\";")
this.Write(certConfig.CertData)
}

View File

@@ -0,0 +1,37 @@
package ssl
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"strconv"
)
type DownloadKeyAction struct {
actionutils.ParentAction
}
func (this *DownloadKeyAction) Init() {
this.Nav("", "", "")
}
func (this *DownloadKeyAction) RunGet(params struct {
CertId int64
}) {
certResp, err := this.RPC().SSLCertRPC().FindEnabledSSLCertConfig(this.AdminContext(), &pb.FindEnabledSSLCertConfigRequest{CertId: params.CertId})
if err != nil {
this.ErrorPage(err)
return
}
certConfig := &sslconfigs.SSLCertConfig{}
err = json.Unmarshal(certResp.CertJSON, certConfig)
if err != nil {
this.ErrorPage(err)
return
}
this.AddHeader("Content-Disposition", "attachment; filename=\"key-"+strconv.FormatInt(params.CertId, 10)+".pem\";")
this.Write(certConfig.KeyData)
}

View File

@@ -0,0 +1,80 @@
package ssl
import (
"archive/zip"
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"strconv"
)
type DownloadZipAction struct {
actionutils.ParentAction
}
func (this *DownloadZipAction) Init() {
this.Nav("", "", "")
}
func (this *DownloadZipAction) RunGet(params struct {
CertId int64
}) {
certResp, err := this.RPC().SSLCertRPC().FindEnabledSSLCertConfig(this.AdminContext(), &pb.FindEnabledSSLCertConfigRequest{CertId: params.CertId})
if err != nil {
this.ErrorPage(err)
return
}
certConfig := &sslconfigs.SSLCertConfig{}
err = json.Unmarshal(certResp.CertJSON, certConfig)
if err != nil {
this.ErrorPage(err)
return
}
z := zip.NewWriter(this.ResponseWriter)
defer func() {
_ = z.Close()
}()
this.AddHeader("Content-Disposition", "attachment; filename=\"cert-"+strconv.FormatInt(params.CertId, 10)+".zip\";")
// cert
{
w, err := z.Create("cert.pem")
if err != nil {
this.ErrorPage(err)
return
}
_, err = w.Write(certConfig.CertData)
if err != nil {
this.ErrorPage(err)
return
}
err = z.Flush()
if err != nil {
this.ErrorPage(err)
return
}
}
// key
if !certConfig.IsCA {
w, err := z.Create("key.pem")
if err != nil {
this.ErrorPage(err)
return
}
_, err = w.Write(certConfig.KeyData)
if err != nil {
this.ErrorPage(err)
return
}
err = z.Flush()
if err != nil {
this.ErrorPage(err)
return
}
}
}

View File

@@ -1,7 +1,13 @@
package ssl
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type IndexAction struct {
@@ -12,7 +18,146 @@ func (this *IndexAction) Init() {
this.FirstMenu("index")
}
func (this *IndexAction) RunGet(params struct{}) {
func (this *IndexAction) RunGet(params struct {
Type string
}) {
this.Data["type"] = params.Type
countAll := int64(0)
countCA := int64(0)
countAvailable := int64(0)
countExpired := int64(0)
count7Days := int64(0)
count30Days := int64(0)
// 计算数量
{
// all
resp, err := this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{})
if err != nil {
this.ErrorPage(err)
return
}
countAll = resp.Count
// CA
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
IsCA: true,
})
if err != nil {
this.ErrorPage(err)
return
}
countCA = resp.Count
// available
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
IsAvailable: true,
})
if err != nil {
this.ErrorPage(err)
return
}
countAvailable = resp.Count
// expired
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
IsExpired: true,
})
if err != nil {
this.ErrorPage(err)
return
}
countExpired = resp.Count
// expire in 7 days
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
ExpiringDays: 7,
})
if err != nil {
this.ErrorPage(err)
return
}
count7Days = resp.Count
// expire in 30 days
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
ExpiringDays: 30,
})
if err != nil {
this.ErrorPage(err)
return
}
count30Days = resp.Count
}
this.Data["countAll"] = countAll
this.Data["countCA"] = countCA
this.Data["countAvailable"] = countAvailable
this.Data["countExpired"] = countExpired
this.Data["count7Days"] = count7Days
this.Data["count30Days"] = count30Days
// 分页
var page *actionutils.Page
var listResp *pb.ListSSLCertsResponse
var err error
switch params.Type {
case "":
page = this.NewPage(countAll)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{Offset: page.Offset, Size: page.Size})
case "ca":
page = this.NewPage(countCA)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{IsCA: true, Offset: page.Offset, Size: page.Size})
case "available":
page = this.NewPage(countAvailable)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{IsAvailable: true, Offset: page.Offset, Size: page.Size})
case "expired":
page = this.NewPage(countExpired)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{IsExpired: true, Offset: page.Offset, Size: page.Size})
case "7days":
page = this.NewPage(count7Days)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{ExpiringDays: 7, Offset: page.Offset, Size: page.Size})
case "30days":
page = this.NewPage(count30Days)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{ExpiringDays: 30, Offset: page.Offset, Size: page.Size})
default:
page = this.NewPage(countAll)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{})
}
if err != nil {
this.ErrorPage(err)
return
}
certConfigs := []*sslconfigs.SSLCertConfig{}
err = json.Unmarshal(listResp.CertsJSON, &certConfigs)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["certs"] = certConfigs
certMaps := []maps.Map{}
nowTime := time.Now().Unix()
for _, certConfig := range certConfigs {
countServersResp, err := this.RPC().ServerRPC().CountServersWithSSLCertId(this.AdminContext(), &pb.CountServersWithSSLCertIdRequest{CertId: certConfig.Id})
if err != nil {
this.ErrorPage(err)
return
}
certMaps = append(certMaps, maps.Map{
"beginDay": timeutil.FormatTime("Y-m-d", certConfig.TimeBeginAt),
"endDay": timeutil.FormatTime("Y-m-d", certConfig.TimeEndAt),
"isExpired": nowTime > certConfig.TimeEndAt,
"isAvailable": nowTime <= certConfig.TimeEndAt,
"countServers": countServersResp.Count,
})
}
this.Data["certInfos"] = certMaps
this.Data["page"] = page.AsHTML()
this.Show()
}

View File

@@ -14,6 +14,15 @@ func init() {
Helper(componentutils.NewComponentHelper()).
Prefix("/servers/components/ssl").
Get("", new(IndexAction)).
GetPost("/uploadPopup", new(UploadPopupAction)).
Post("/delete", new(DeleteAction)).
GetPost("/updatePopup", new(UpdatePopupAction)).
Get("/certPopup", new(CertPopupAction)).
Get("/viewKey", new(ViewKeyAction)).
Get("/viewCert", new(ViewCertAction)).
Get("/downloadKey", new(DownloadKeyAction)).
Get("/downloadCert", new(DownloadCertAction)).
Get("/downloadZip", new(DownloadZipAction)).
EndAll()
})
}

View File

@@ -0,0 +1,15 @@
package ssl
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
type UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct{}) {
this.Show()
}

View File

@@ -0,0 +1,95 @@
package ssl
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/actions"
)
type UploadPopupAction struct {
actionutils.ParentAction
}
func (this *UploadPopupAction) Init() {
this.Nav("", "", "")
}
func (this *UploadPopupAction) RunGet(params struct{}) {
this.Show()
}
func (this *UploadPopupAction) RunPost(params struct {
Name string
IsCA bool
Description string
IsOn bool
CertFile *actions.File
KeyFile *actions.File
Must *actions.Must
}) {
params.Must.
Field("name", params.Name).
Require("请输入证书说明")
certData := []byte{}
keyData := []byte{}
if params.CertFile == nil {
this.Fail("请选择要上传的证书文件")
}
var err error
certData, err = params.CertFile.Read()
if err != nil {
this.Fail("读取证书文件内容错误,请重新上传")
}
if !params.IsCA {
if params.KeyFile == nil {
this.Fail("请选择要上传的私钥文件")
} else {
keyData, err = params.KeyFile.Read()
if err != nil {
this.Fail("读取密钥文件内容错误,请重新上传")
}
}
}
// 校验
sslConfig := &sslconfigs.SSLCertConfig{
IsCA: params.IsCA,
CertData: certData,
KeyData: keyData,
}
err = sslConfig.Init()
if err != nil {
if params.IsCA {
this.Fail("证书校验错误:" + err.Error())
} else {
this.Fail("证书或密钥校验错误:" + err.Error())
}
}
// 保存
_, err = this.RPC().SSLCertRPC().CreateSSLCert(this.AdminContext(), &pb.CreateSSLCertRequest{
IsOn: params.IsOn,
Name: params.Name,
Description: params.Description,
ServerName: "",
IsCA: params.IsCA,
CertData: certData,
KeyData: keyData,
TimeBeginAt: sslConfig.TimeBeginAt,
TimeEndAt: sslConfig.TimeEndAt,
DnsNames: sslConfig.DNSNames,
CommonNames: sslConfig.CommonNames,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,34 @@
package ssl
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
)
type ViewCertAction struct {
actionutils.ParentAction
}
func (this *ViewCertAction) Init() {
this.Nav("", "", "")
}
func (this *ViewCertAction) RunGet(params struct {
CertId int64
}) {
certResp, err := this.RPC().SSLCertRPC().FindEnabledSSLCertConfig(this.AdminContext(), &pb.FindEnabledSSLCertConfigRequest{CertId: params.CertId})
if err != nil {
this.ErrorPage(err)
return
}
certConfig := &sslconfigs.SSLCertConfig{}
err = json.Unmarshal(certResp.CertJSON, certConfig)
if err != nil {
this.ErrorPage(err)
return
}
this.Write(certConfig.CertData)
}

View File

@@ -0,0 +1,34 @@
package ssl
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
)
type ViewKeyAction struct {
actionutils.ParentAction
}
func (this *ViewKeyAction) Init() {
this.Nav("", "", "")
}
func (this *ViewKeyAction) RunGet(params struct {
CertId int64
}) {
certResp, err := this.RPC().SSLCertRPC().FindEnabledSSLCertConfig(this.AdminContext(), &pb.FindEnabledSSLCertConfigRequest{CertId: params.CertId})
if err != nil {
this.ErrorPage(err)
return
}
certConfig := &sslconfigs.SSLCertConfig{}
err = json.Unmarshal(certResp.CertJSON, certConfig)
if err != nil {
this.ErrorPage(err)
return
}
this.Write(certConfig.KeyData)
}

View File

@@ -1,4 +1,4 @@
Vue.component("label-on", {
props: ["v-is-on"],
template: '<div><span v-if="vIsOn" class="ui label tiny green">已启用</span><span v-if="!vIsOn" class="ui label tiny red">已关闭</span></div>'
template: '<div><span v-if="vIsOn" class="ui label tiny green basic">已启用</span><span v-if="!vIsOn" class="ui label tiny red basic">已关闭</span></div>'
})

View File

@@ -52,7 +52,7 @@ Vue.component("http-gzip-box", {
</td>
</tr>
</tbody>
<more-options-tbody @change="changeAdvancedVisible"></more-options-tbody>
<more-options-tbody @change="changeAdvancedVisible" v-if="isOn()"></more-options-tbody>
<tbody v-show="isOn() && advancedVisible">
<tr>
<td>Gzip内容最小长度</td>

View File

@@ -621,11 +621,6 @@ var.olive {
var.dash {
border-bottom: 1px dashed grey;
}
/** Message **/
.message .gopher {
width: 30px;
margin-right: 10px;
}
/** checkbox **/
.checkbox label a,
.checkbox label {

File diff suppressed because one or more lines are too long

View File

@@ -667,12 +667,6 @@ var.dash {
border-bottom: 1px dashed grey;
}
/** Message **/
.message .gopher {
width: 30px;
margin-right: 10px;
}
/** checkbox **/
.checkbox label a, .checkbox label {
font-size: 0.8em !important;

View File

@@ -6,4 +6,7 @@
.ui.toggle.checkbox input:checked ~ label:before {
background-color: #21ba45 !important;
}
.ui.label.basic {
background-color: white !important;
}
/*# sourceMappingURL=@layout_override.css.map */

View File

@@ -1 +1 @@
{"version":3,"sources":["@layout_override.less"],"names":[],"mappings":"AACA,GAAG,OAAO,SAAU,MAAK,MAAM,QAAS,OAAM;AAAS,GAAG,OAAO,SAAU,MAAK,MAAM,QAAS,QAAO;EACrG,oCAAA;;AAGD,GAAG,OAAO,SAAU,MAAK,QAAS,OAAM;AAAS,GAAG,OAAO,SAAU,MAAK,QAAS,QAAO;EACzF,oCAAA","file":"@layout_override.css"}
{"version":3,"sources":["@layout_override.less"],"names":[],"mappings":"AACA,GAAG,OAAO,SAAU,MAAK,MAAM,QAAS,OAAM;AAAS,GAAG,OAAO,SAAU,MAAK,MAAM,QAAS,QAAO;EACrG,oCAAA;;AAGD,GAAG,OAAO,SAAU,MAAK,QAAS,OAAM;AAAS,GAAG,OAAO,SAAU,MAAK,QAAS,QAAO;EACzF,oCAAA;;AAGD,GAAG,MAAM;EACR,kCAAA","file":"@layout_override.css"}

View File

@@ -5,4 +5,8 @@
.ui.toggle.checkbox input:checked ~ .box:before, .ui.toggle.checkbox input:checked ~ label:before {
background-color: #21ba45 !important;
}
.ui.label.basic {
background-color: white !important;
}

View File

@@ -211,9 +211,6 @@ td .label.tiny {
padding: 2px;
font-size: 0.9em;
}
td .label.small {
margin-bottom: 0.6em;
}
/** Menu **/
.first-menu .menu.text {
margin-top: 0 !important;

View File

@@ -1 +1 @@
{"version":3,"sources":["@layout_popup.less"],"names":[],"mappings":"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","file":"@layout_popup.css"}
{"version":3,"sources":["@layout_popup.less"],"names":[],"mappings":"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","file":"@layout_popup.css"}

View File

@@ -9,6 +9,7 @@
<link rel="stylesheet" type="text/css" href="/css/semantic.iframe.min.css?v=bRafhK" media="all"/>
{$TEA.VUE}
<link rel="stylesheet" type="text/css" href="/_/@default/@layout_override.css" media="all"/>
{$echo "header"}
<script type="text/javascript" src="/_/@default/@layout.js"></script>
<script type="text/javascript" src="/ui/components.js"></script>

View File

@@ -255,10 +255,6 @@ td .label.tiny {
font-size: 0.9em;
}
td .label.small {
margin-bottom: 0.6em;
}
/** Menu **/
.first-menu .menu.text {
margin-top: 0 !important;

View File

@@ -0,0 +1,15 @@
.pre-box {
padding: 1em;
margin: 0;
line-height: 1.7;
-ms-word-break: break-all;
word-break: break-all;
font-size: 0.9em;
background: rgba(0, 0, 0, 0.05);
overflow-y: auto;
max-height: 20em;
}
.pre-box::-webkit-scrollbar {
width: 6px;
}
/*# sourceMappingURL=certPopup.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["certPopup.less"],"names":[],"mappings":"AAAA;EACC,YAAA;EACA,SAAA;EACA,gBAAA;EACA,yBAAA;EACA,qBAAA;EACA,gBAAA;EACA,+BAAA;EACA,gBAAA;EACA,gBAAA;;AAGD,QAAQ;EACP,UAAA","file":"certPopup.css"}

View File

@@ -0,0 +1,74 @@
{$layout "layout_popup"}
<h3>证书详情</h3>
<table class="ui table definition selectable">
<tr>
<td class="title">证书说明</td>
<td>{{info.name}}</td>
</tr>
<tr v-if="info.description.length > 0">
<td>详细说明</td>
<td>{{info.decription}}</td>
</tr>
<tr>
<td>证书状态</td>
<td>
<span class="ui label small green basic" v-if="info.isAvailable">有效中</span>
<span class="ui label small red basic" v-else>已过期</span>
</td>
</tr>
<tr>
<td>发行信息</td>
<td>
<div v-if="info.commonNames != null">
<div v-for="(commonName, index) in info.commonNames">
<span v-html="indent(index)"></span>{{commonName}}
</div>
</div>
</td>
</tr>
<tr>
<td>域名</td>
<td>
<span class="ui label small" v-for="dnsName in info.dnsNames">{{dnsName}}</span>
</td>
</tr>
<tr>
<td>有效期</td>
<td>{{info.beginTime}} - {{info.endTime}}</td>
</tr>
<tr>
<td>引用服务</td>
<td>
<span class="disabled" v-if="servers.length == 0">暂时没有引用此证书的服务。</span>
<div v-if="servers.length > 0">
<a v-for="server in servers" :href="'/servers/server?serverId=' + server.id" target="_blank" class="ui label small">{{server.name}}</a>
</div>
</td>
</tr>
<tr>
<td>证书文件下载</td>
<td>
<a :href="'/servers/components/ssl/downloadZip?certId=' + info.id" target="_blank">[ZIP下载]</a> &nbsp;
<a :href="'/servers/components/ssl/downloadCert?certId=' + info.id" target="_blank">[证书下载]</a> &nbsp;
<a :href="'/servers/components/ssl/downloadKey?certId=' + info.id" v-if="!info.isCA" target="_blank">[私钥下载]</a>
</td>
</tr>
<tr>
<td>证书预览</td>
<td>
<pre class="pre-box" style="font-family: Menlo, Monaco, 'Courier New', monospace !important">{{info.certString}}</pre>
<div style="margin-top:1em">
<a :href="'/servers/components/ssl/viewCert?certId=' + info.id" target="_blank">[浏览器新窗口打开]</a>
</div>
</td>
</tr>
<tr v-if="!info.isCA">
<td>私钥预览</td>
<td><pre class="pre-box" style="font-family: Menlo, Monaco, 'Courier New', monospace !important">{{info.keyString}}</pre>
<div style="margin-top: 1em">
<a :href="'/servers/components/ssl/viewKey?certId=' + info.id" target="_blank">[浏览器新窗口打开]</a>
</div>
</td>
</tr>
</table>

View File

@@ -0,0 +1,10 @@
Tea.context(function () {
// 打印缩进
this.indent = function (index) {
let indent = ""
for (let i = 0; i < index; i++) {
indent += " &nbsp; &nbsp; "
}
return indent
}
})

View File

@@ -0,0 +1,15 @@
.pre-box {
padding: 1em;
margin: 0;
line-height: 1.7;
-ms-word-break: break-all;
word-break: break-all;
font-size: 0.9em;
background: rgba(0, 0, 0, 0.05);
overflow-y: auto;
max-height: 20em;
}
.pre-box::-webkit-scrollbar {
width: 6px;
}

View File

@@ -2,5 +2,55 @@
{$template "/left_menu"}
<div class="right-box">
<p class="ui message">此功能暂未开放敬请期待。</p>
<second-menu>
<menu-item href="/servers/components/ssl" :active="type == ''">所有证书({{countAll}})</menu-item>
<menu-item href="/servers/components/ssl?type=ca" :active="type == 'ca'">CA证书({{countCA}})</menu-item>
<menu-item href="/servers/components/ssl?type=available" :active="type == 'available'">有效证书({{countAvailable}})</menu-item>
<menu-item href="/servers/components/ssl?type=expired" :active="type == 'expired'">过期证书<span :class="{red: countExpired > 0}">({{countExpired}})</span></menu-item>
<menu-item href="/servers/components/ssl?type=7days" :active="type == '7days'">7天内过期<span :class="{red: count7Days > 0}">({{count7Days}})</span></menu-item>
<menu-item href="/servers/components/ssl?type=30days" :active="type == '30days'">30天过期({{count30Days}})</menu-item>
<span class="item">|</span>
<a href="" class="item" @click.prevent="uploadCert">[上传证书]</a>
</second-menu>
<p class="comment" v-if="certs.length == 0">暂时还没有相关的证书。</p>
<table class="ui table selectable" v-if="certs.length > 0">
<thead>
<tr>
<th>证书说明</th>
<th>顶级发行组织</th>
<th>域名</th>
<th>生效日期</th>
<th>过期日期</th>
<th>引用服务</th>
<th>状态</th>
<th class="three op">操作</th>
</tr>
</thead>
<tr v-for="(cert, index) in certs">
<td>{{cert.name}}</td>
<td>
<span v-if="cert.commonNames != null && cert.commonNames.length > 0">{{cert.commonNames[cert.commonNames.length-1]}}</span>
</td>
<td>
<div v-for="dnsName in cert.dnsNames" style="margin-bottom:0.4em">
<span class="ui label tiny">{{dnsName}}</span>
</div>
</td>
<td>{{certInfos[index].beginDay}}</td>
<td>{{certInfos[index].endDay}}</td>
<td>{{certInfos[index].countServers}}</td>
<td nowrap="">
<span class="ui label red tiny basic" v-if="certInfos[index].isExpired">已过期</span>
<span class="ui label green tiny basic" v-else>有效中</span>
</td>
<td>
<a href="" @click.prevent="viewCert(cert.id)">详情</a> &nbsp;
<a href="" @click.prevent="updateCert(cert.id)">修改</a> &nbsp;
<a href="" @click.prevent="deleteCert(cert.id)">删除</a>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>
</div>

View File

@@ -0,0 +1,33 @@
Tea.context(function () {
// 上传证书
this.uploadCert = function () {
teaweb.popup("/servers/components/ssl/uploadPopup", {
height: "28em",
callback: function () {
teaweb.success("上传成功", function () {
window.location.reload()
})
}
})
}
// 删除证书
this.deleteCert = function (certId) {
let that = this
teaweb.confirm("确定要删除此证书吗?", function () {
that.$post("/servers/components/ssl/delete")
.params({
certId: certId
})
.refresh()
})
}
// 查看证书
this.viewCert = function (certId) {
teaweb.popup("/servers/components/ssl/certPopup?certId=" + certId, {
height: "28em",
width: "48em"
})
}
})

View File

@@ -0,0 +1,59 @@
{$layout "layout_popup"}
<h3>上传证书</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<table class="ui table definition selectable">
<tr>
<td class="title">证书说明 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus"/>
<p class="comment">可以简单说明证书的用途。</p>
</td>
</tr>
<tr>
<td>证书类型</td>
<td>
<select class="ui dropdown auto-width" name="isCA" v-model="isCA">
<option value="0">加密证书</option>
<option value="1">CA证书</option>
</select>
</td>
</tr>
<tr>
<td>选择证书文件 *</td>
<td>
<input type="file" name="certFile" accept="application/x-pem-file, application/pkcs10, application/x-pkcs12, application/x-x509-user-cert, application/x-x509-ca-cert, application/pkix-cert"/>
<p class="comment">内容中通常含有"-----BEGIN CERTIFICATE-----"类似的信息。</p
</td>
</tr>
<tr v-show="isCA == 0">
<td>选择私钥文件 *</td>
<td>
<input type="file" name="keyFile" accept="application/pkcs8"/>
<p class="comment">内容中通常含有"-----BEGIN RSA PRIVATE KEY-----"类似的信息。</p>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>详细说明</td>
<td>
<textarea rows="3" name="description" maxlength="200"></textarea>
</td>
</tr>
<tr>
<td>是否启用</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" checked="checked"/>
<label></label>
</div>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -0,0 +1,4 @@
Tea.context(function () {
this.success = NotifyPopup
this.isCA = 0
})

View File

@@ -3,6 +3,7 @@
{$template "/left_menu"}
<div class="right-box">
<p class="comment">提醒HTTP2、证书等信息修改后可能需要清空浏览器缓存后才能浏览效果。</p>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="serverId" :value="serverId"/>
<input type="hidden" name="serverType" :value="serverType"/>