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 <33000667+nic-c...@users.noreply.github.com> AuthorDate: Sun Sep 20 15:46:54 2020 +0800 feat: add SSL refactoring (#488) Co-authored-by: Vinci Xu <277040...@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 {