You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by vi...@apache.org on 2020/09/20 07:47:00 UTC

[apisix-dashboard] branch refactor updated: feat: add SSL refactoring (#488)

This is an automated email from the ASF dual-hosted git repository.

vinci pushed a commit to branch refactor
in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git


The following commit(s) were added to refs/heads/refactor by this push:
     new 075c1c3  feat: add SSL refactoring (#488)
075c1c3 is described below

commit 075c1c3d26caf45874514cb067535d65a2073b15
Author: nic-chen <33...@users.noreply.github.com>
AuthorDate: Sun Sep 20 15:46:54 2020 +0800

    feat: add SSL refactoring (#488)
    
    Co-authored-by: Vinci Xu <27...@qq.com>
---
 api/internal/core/entity/entity.go |   2 +-
 api/internal/handler/ssl/ssl.go    | 252 +++++++++++++++++++++++++++++++++++++
 api/route/base.go                  |   2 +
 3 files changed, 255 insertions(+), 1 deletion(-)

diff --git a/api/internal/core/entity/entity.go b/api/internal/core/entity/entity.go
index c81377b..f588feb 100644
--- a/api/internal/core/entity/entity.go
+++ b/api/internal/core/entity/entity.go
@@ -109,7 +109,7 @@ type Consumer struct {
 	Plugins  interface{} `json:"plugins,omitempty"`
 }
 
-type Ssl struct {
+type SSL struct {
 	ID      string   `json:"id"`
 	Cert    string   `json:"cert"`
 	Key     string   `json:"key"`
diff --git a/api/internal/handler/ssl/ssl.go b/api/internal/handler/ssl/ssl.go
new file mode 100644
index 0000000..a9a0882
--- /dev/null
+++ b/api/internal/handler/ssl/ssl.go
@@ -0,0 +1,252 @@
+package ssl
+
+import (
+	"crypto/tls"
+	"crypto/x509"
+	"encoding/pem"
+	"errors"
+	"fmt"
+	"reflect"
+	"strings"
+
+	"github.com/api7/go-jsonpatch"
+	"github.com/gin-gonic/gin"
+	"github.com/shiningrush/droplet"
+	"github.com/shiningrush/droplet/data"
+	"github.com/shiningrush/droplet/wrapper"
+	wgin "github.com/shiningrush/droplet/wrapper/gin"
+
+	"github.com/apisix/manager-api/internal/core/entity"
+	"github.com/apisix/manager-api/internal/core/store"
+	"github.com/apisix/manager-api/internal/handler"
+	"github.com/apisix/manager-api/internal/utils"
+)
+
+type Handler struct {
+	sslStore *store.GenericStore
+}
+
+func NewHandler() (handler.RouteRegister, error) {
+	s, err := store.NewGenericStore(store.GenericStoreOption{
+		BasePath: "/apisix/ssl",
+		ObjType:  reflect.TypeOf(entity.SSL{}),
+		KeyFunc: func(obj interface{}) string {
+			r := obj.(*entity.SSL)
+			return r.ID
+		},
+	})
+	if err != nil {
+		return nil, err
+	}
+	if err := s.Init(); err != nil {
+		return nil, err
+	}
+
+	utils.AppendToClosers(s.Close)
+	return &Handler{
+		sslStore: s,
+	}, nil
+}
+
+func (h *Handler) ApplyRoute(r *gin.Engine) {
+	r.GET("/apisix/admin/ssl/:id", wgin.Wraps(h.Get,
+		wrapper.InputType(reflect.TypeOf(GetInput{}))))
+	r.GET("/apisix/admin/ssl", wgin.Wraps(h.List,
+		wrapper.InputType(reflect.TypeOf(ListInput{}))))
+	r.POST("/apisix/admin/ssl", wgin.Wraps(h.Create,
+		wrapper.InputType(reflect.TypeOf(entity.SSL{}))))
+	r.POST("/apisix/admin/ssl/validate", wgin.Wraps(h.Validate,
+		wrapper.InputType(reflect.TypeOf(entity.SSL{}))))
+	r.PUT("/apisix/admin/ssl/:id", wgin.Wraps(h.Update,
+		wrapper.InputType(reflect.TypeOf(UpdateInput{}))))
+	r.PATCH("/apisix/admin/ssl/:id", wgin.Wraps(h.Patch,
+		wrapper.InputType(reflect.TypeOf(UpdateInput{}))))
+	r.DELETE("/apisix/admin/ssl", wgin.Wraps(h.BatchDelete,
+		wrapper.InputType(reflect.TypeOf(BatchDelete{}))))
+}
+
+type GetInput struct {
+	ID string `auto_read:"id,path" validate:"required"`
+}
+
+func (h *Handler) Get(c droplet.Context) (interface{}, error) {
+	input := c.Input().(*GetInput)
+
+	r, err := h.sslStore.Get(input.ID)
+	if err != nil {
+		return nil, err
+	}
+	return r, nil
+}
+
+type ListInput struct {
+	ID string `auto_read:"id,query"`
+	data.Pager
+}
+
+func (h *Handler) List(c droplet.Context) (interface{}, error) {
+	input := c.Input().(*ListInput)
+
+	ret, err := h.sslStore.List(store.ListInput{
+		Predicate: func(obj interface{}) bool {
+			if input.ID != "" {
+				return strings.Index(obj.(*entity.SSL).ID, input.ID) > 0
+			}
+			return true
+		},
+		PageSize:   input.PageSize,
+		PageNumber: input.PageNumber,
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return ret, nil
+}
+
+func (h *Handler) Create(c droplet.Context) (interface{}, error) {
+	input := c.Input().(*entity.SSL)
+
+	if err := h.sslStore.Create(c.Context(), input); err != nil {
+		return nil, err
+	}
+
+	return nil, nil
+}
+
+type UpdateInput struct {
+	ID string `auto_read:"id,path"`
+	entity.SSL
+}
+
+func (h *Handler) Update(c droplet.Context) (interface{}, error) {
+	input := c.Input().(*UpdateInput)
+	input.SSL.ID = input.ID
+
+	if err := h.sslStore.Update(c.Context(), &input.SSL); err != nil {
+		return nil, err
+	}
+
+	return nil, nil
+}
+
+func (h *Handler) Patch(c droplet.Context) (interface{}, error) {
+	input := c.Input().(*UpdateInput)
+	arr := strings.Split(input.ID, "/")
+	var subPath string
+	if len(arr) > 1 {
+		input.ID = arr[0]
+		subPath = arr[1]
+	}
+
+	stored, err := h.sslStore.Get(input.ID)
+	if err != nil {
+		return nil, err
+	}
+
+	var patch jsonpatch.Patch
+	if subPath != "" {
+		patch = jsonpatch.Patch{
+			Operations: []jsonpatch.PatchOperation{
+				{Op: jsonpatch.Replace, Path: subPath, Value: c.Input()},
+			},
+		}
+	} else {
+		patch, err = jsonpatch.MakePatch(stored, input.SSL)
+		if err != nil {
+			panic(err)
+		}
+	}
+
+	err = patch.Apply(&stored)
+	if err != nil {
+		panic(err)
+	}
+
+	if err := h.sslStore.Update(c.Context(), &stored); err != nil {
+		return nil, err
+	}
+
+	return nil, nil
+}
+
+type BatchDelete struct {
+	Ids string `auto_read:"ids,query"`
+}
+
+func (h *Handler) BatchDelete(c droplet.Context) (interface{}, error) {
+	input := c.Input().(*BatchDelete)
+
+	if err := h.sslStore.BatchDelete(c.Context(), strings.Split(input.Ids, ",")); err != nil {
+		return nil, err
+	}
+
+	return nil, nil
+}
+
+func ParseCert(crt, key string) (*entity.SSL, error) {
+	if crt == "" || key == "" {
+		return nil, errors.New("invalid certificate")
+	}
+
+	certDERBlock, _ := pem.Decode([]byte(crt))
+	if certDERBlock == nil {
+		return nil, errors.New("Certificate resolution failed")
+	}
+	// match
+	_, err := tls.X509KeyPair([]byte(crt), []byte(key))
+	if err != nil {
+		return nil, errors.New("key and cert don't match")
+	}
+
+	x509Cert, err := x509.ParseCertificate(certDERBlock.Bytes)
+
+	if err != nil {
+		return nil, errors.New("Certificate resolution failed")
+	} else {
+		ssl := entity.SSL{}
+		//domain
+		snis := []string{}
+		if x509Cert.DNSNames != nil && len(x509Cert.DNSNames) > 0 {
+			snis = x509Cert.DNSNames
+		} else if x509Cert.IPAddresses != nil && len(x509Cert.IPAddresses) > 0 {
+			for _, ip := range x509Cert.IPAddresses {
+				snis = append(snis, ip.String())
+			}
+		} else {
+			if x509Cert.Subject.Names != nil && len(x509Cert.Subject.Names) > 1 {
+				var attributeTypeNames = map[string]string{
+					"2.5.4.6":  "C",
+					"2.5.4.10": "O",
+					"2.5.4.11": "OU",
+					"2.5.4.3":  "CN",
+					"2.5.4.5":  "SERIALNUMBER",
+					"2.5.4.7":  "L",
+					"2.5.4.8":  "ST",
+					"2.5.4.9":  "STREET",
+					"2.5.4.17": "POSTALCODE",
+				}
+				for _, tv := range x509Cert.Subject.Names {
+					oidString := tv.Type.String()
+					typeName, ok := attributeTypeNames[oidString]
+					if ok && typeName == "CN" {
+						valueString := fmt.Sprint(tv.Value)
+						snis = append(snis, valueString)
+					}
+				}
+			}
+		}
+
+		return &ssl, nil
+	}
+}
+
+func (h *Handler) Validate(c droplet.Context) (interface{}, error) {
+	input := c.Input().(*entity.SSL)
+	ssl, err := ParseCert(input.Cert, input.Key)
+	if err != nil {
+		return nil, err
+	}
+
+	return ssl, nil
+}
diff --git a/api/route/base.go b/api/route/base.go
index b9fe141..2afd68e 100644
--- a/api/route/base.go
+++ b/api/route/base.go
@@ -27,6 +27,7 @@ import (
 	"github.com/apisix/manager-api/internal/handler"
 	"github.com/apisix/manager-api/internal/handler/consumer"
 	"github.com/apisix/manager-api/internal/handler/route"
+	"github.com/apisix/manager-api/internal/handler/ssl"
 )
 
 func SetUpRouter() *gin.Engine {
@@ -51,6 +52,7 @@ func SetUpRouter() *gin.Engine {
 
 	factories := []handler.RegisterFactory{
 		route.NewHandler,
+		ssl.NewHandler,
 		consumer.NewHandler,
 	}
 	for i := range factories {