diff --git a/api/errno/error.go b/api/errno/error.go index 24bb5a3067..08b5adba77 100644 --- a/api/errno/error.go +++ b/api/errno/error.go @@ -22,65 +22,74 @@ import ( ) type Message struct { - Code string - Msg string + Code string + Msg string + Status int `json:"-"` } var ( //AA 01 api-manager-api - SystemSuccess = Message{"010000", "success"} - SystemError = Message{"010001", "system error"} - BadRequestError = Message{Code: "010002", Msg: "Request format error"} - NotFoundError = Message{Code: "010003", Msg: "No resources found"} - InvalidParam = Message{"010004", "Request parameter error"} - DBWriteError = Message{"010005", "Database save failed"} - DBReadError = Message{"010006", "Database query failed"} - DBDeleteError = Message{"010007", "Database delete failed"} - RecordNotExist = Message{"010009", "Record does not exist"} - - //BB 01 config module - ConfEnvError = Message{"010101", "Environment variable not found: %s"} - ConfFilePathError = Message{"010102", "Error loading configuration file: %s"} - - // BB 02 route module - RouteRequestError = Message{"010201", "Route request parameters are abnormal: %s"} - ApisixRouteCreateError = Message{"010202", "Failed to create APISIX route: %s"} - DBRouteCreateError = Message{"010203", "Route storage failure: %s"} - ApisixRouteUpdateError = Message{"010204", "Update APISIX routing failed: %s"} - ApisixRouteDeleteError = Message{"010205", "Failed to remove APISIX route: %s"} - DBRouteUpdateError = Message{"010206", "Route update failed: %s"} - DBRouteDeleteError = Message{"010207", "Route remove failed: %s"} - DBRouteReduplicateError = Message{"010208", "Route name is reduplicate : %s"} - - // 03 plugin module - ApisixPluginListError = Message{"010301", "List APISIX plugins failed: %s"} - ApisixPluginSchemaError = Message{"010301", "Find APISIX plugin schema failed: %s"} - - // 04 ssl模块 - SslParseError = Message{"010401", "Certificate resolution failed: %s"} - ApisixSslCreateError = Message{"010402", "Create APISIX SSL failed"} - ApisixSslUpdateError = Message{"010403", "Update APISIX SSL failed"} - ApisixSslDeleteError = Message{"010404", "Delete APISIX SSL failed"} + //BB 00 system + SystemSuccess = Message{"010000", "success", 200} + SystemError = Message{"010001", "system error", 500} + BadRequestError = Message{"010002", "Request format error", 400} + NotFoundError = Message{"010003", "No resources found", 404} + InvalidParam = Message{"010004", "Request parameter error", 400} + DBWriteError = Message{"010005", "Database save failed", 500} + DBReadError = Message{"010006", "Database query failed", 500} + DBDeleteError = Message{"010007", "Database delete failed", 500} + RecordNotExist = Message{"010009", "Record does not exist", 404} + InvalidParamDetail = Message{"010010", "Invalid request parameter: %s", 400} + AdminApiSaveError = Message{"010011", "Data save failed", 500} + SchemaCheckFailed = Message{"010012", "%s", 400} + + //BB 01 configuration + ConfEnvError = Message{"010101", "Environment variable not found: %s", 500} + ConfFilePathError = Message{"010102", "Error loading configuration file: %s", 500} + + // BB 02 route + RouteRequestError = Message{"010201", "Route request parameters are abnormal: %s", 400} + ApisixRouteCreateError = Message{"010202", "Failed to create APISIX route: %s", 500} + DBRouteCreateError = Message{"010203", "Route storage failure: %s", 500} + ApisixRouteUpdateError = Message{"010204", "Update APISIX routing failed: %s", 500} + ApisixRouteDeleteError = Message{"010205", "Failed to delete APISIX route: %s", 500} + DBRouteUpdateError = Message{"010206", "Route update failed: %s", 500} + DBRouteDeleteError = Message{"010207", "Route deletion failed: %s", 500} + DBRouteReduplicateError = Message{"010208", "Route name is reduplicate : %s", 400} + + // 03 plugins + ApisixPluginListError = Message{"010301", "find APISIX plugin list failed: %s", 500} + ApisixPluginSchemaError = Message{"010301", "find APISIX plugin schema failed: %s", 500} + + // 04 ssl + SslParseError = Message{"010401", "Certificate resolution failed: %s", 400} + ApisixSslCreateError = Message{"010402", "Failed to create APISIX SSL", 500} + ApisixSslUpdateError = Message{"010403", "Failed to update APISIX SSL", 500} + ApisixSslDeleteError = Message{"010404", "Failed to delete APISIX SSL", 500} + SslForSniNotExists = Message{"010407", "Ssl for sni not exists:%s", 400} + DuplicateSslCert = Message{"010408", "Duplicate ssl cert", 400} // 06 upstream - UpstreamRequestError = Message{"010601", "upstream request parameters are abnormal: %s"} - UpstreamTransError = Message{"010602", "upstream parameter conversion is abnormal: %s"} - DBUpstreamError = Message{"010603", "upstream storage failure: %s"} - ApisixUpstreamCreateError = Message{"010604", "apisix upstream create failure: %s"} - ApisixUpstreamUpdateError = Message{"010605", "apisix upstream update failure: %s"} - ApisixUpstreamDeleteError = Message{"010606", "apisix upstream delete failure: %s"} - DBUpstreamDeleteError = Message{"010607", "upstream delete failure: %s"} - DBUpstreamReduplicateError = Message{"010608", "Upstream name is reduplicate : %s"} - - ApisixConsumerCreateError = Message{"010702", "Create APISIX Consumer failed"} - ApisixConsumerUpdateError = Message{"010703", "Update APISIX Consumer failed"} - ApisixConsumerDeleteError = Message{"010704", "Delete APISIX Consumer failed"} - DuplicateUserName = Message{"010705", "Duplicate username"} + UpstreamRequestError = Message{"010601", "upstream request parameters exception: %s", 400} + UpstreamTransError = Message{"010602", "Abnormal upstream parameter conversion: %s", 400} + DBUpstreamError = Message{"010603", "upstream storage failure: %s", 500} + ApisixUpstreamCreateError = Message{"010604", "apisix upstream create failed: %s", 500} + ApisixUpstreamUpdateError = Message{"010605", "apisix upstream update failed: %s", 500} + ApisixUpstreamDeleteError = Message{"010606", "apisix upstream delete failed: %s", 500} + DBUpstreamDeleteError = Message{"010607", "upstream storage delete failed: %s", 500} + DBUpstreamReduplicateError = Message{"010608", "Upstream name is reduplicate : %s", 500} + + // 07 consumer + ApisixConsumerCreateError = Message{"010702", "APISIX Consumer create failed", 500} + ApisixConsumerUpdateError = Message{"010703", "APISIX Consumer update failed", 500} + ApisixConsumerDeleteError = Message{"010704", "APISIX Consumer delete failed", 500} + DuplicateUserName = Message{"010705", "Duplicate consumer username", 400} ) type ManagerError struct { TraceId string Code string + Status int Msg string Data interface{} Detail string @@ -96,11 +105,11 @@ func (e *ManagerError) ErrorDetail() string { } func FromMessage(m Message, args ...interface{}) *ManagerError { - return &ManagerError{TraceId: "", Code: m.Code, Msg: fmt.Sprintf(m.Msg, args...)} + return &ManagerError{TraceId: "", Code: m.Code, Status: m.Status, Msg: fmt.Sprintf(m.Msg, args...)} } func New(m Message, args ...interface{}) *ManagerError { - return &ManagerError{TraceId: "", Code: m.Code, Msg: m.Msg, Detail: fmt.Sprintf("%s", args...)} + return &ManagerError{TraceId: "", Code: m.Code, Msg: m.Msg, Status: m.Status, Detail: fmt.Sprintf("%s", args...)} } func (e *ManagerError) Response() map[string]interface{} { @@ -139,9 +148,9 @@ func Succeed() map[string]interface{} { type HttpError struct { Code int - Msg string + Msg Message } func (e *HttpError) Error() string { - return e.Msg + return e.Msg.Msg } diff --git a/api/script/db/schema.sql b/api/script/db/schema.sql index 83c4bdd611..95d4e60962 100644 --- a/api/script/db/schema.sql +++ b/api/script/db/schema.sql @@ -28,7 +28,9 @@ CREATE TABLE `ssls` ( `status` tinyint(1) unsigned NOT NULL DEFAULT '1', `create_time` bigint(20) unsigned NOT NULL, `update_time` bigint(20) unsigned NOT NULL, - PRIMARY KEY (`id`) + `public_key_hash` varchar(64) NOT NULL DEFAULT '', + PRIMARY KEY (`id`), + UNIQUE KEY `uni_public_key_hash` (`public_key_hash`) ) DEFAULT CHARSET=utf8; -- upstream diff --git a/api/service/consumer.go b/api/service/consumer.go index a29dde6fa5..98dee452fc 100644 --- a/api/service/consumer.go +++ b/api/service/consumer.go @@ -102,8 +102,8 @@ func ConsumerList(page, size int, search string) (int, []ConsumerDto, error) { db := conf.DB().Table("consumers") if search != "" { - db = db.Where("name like ? ", "%"+search+"%"). - Or("description like ? ", "%"+search+"%") + db = db.Where("username like ? ", "%"+search+"%"). + Or("`desc` like ? ", "%"+search+"%") } if err := db.Order("create_time desc").Offset((page - 1) * size).Limit(size).Find(&consumerList).Error; err != nil { @@ -128,6 +128,10 @@ func ConsumerList(page, size int, search string) (int, []ConsumerDto, error) { } func ConsumerItem(id string) (*ConsumerDto, error) { + if id == "" { + return nil, errno.New(errno.InvalidParam) + } + consumer := &Consumer{} if err := conf.DB().Table("consumers").Where("id = ?", id).First(consumer).Error; err != nil { e := errno.New(errno.DBReadError, err.Error()) @@ -144,6 +148,14 @@ func ConsumerCreate(param interface{}, id string) error { req := &ConsumerDto{} req.Parse(param) + if req.Username == "" { + return errno.New(errno.InvalidParamDetail, "username is required") + } + + if len(req.Desc) > 200 { + return errno.New(errno.InvalidParamDetail, "description is too long") + } + exists := Consumer{} conf.DB().Table("consumers").Where("username = ?", req.Username).First(&exists) if exists != (Consumer{}) { @@ -151,10 +163,29 @@ func ConsumerCreate(param interface{}, id string) error { return e } + // trans + tx := conf.DB().Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + consumer := &Consumer{} + consumer.Transfer(req) + + // update mysql + consumer.ID = uuid.FromStringOrNil(id) + if err := tx.Create(consumer).Error; err != nil { + tx.Rollback() + return errno.New(errno.DBWriteError, err.Error()) + } + apisixConsumer := &ApisixConsumer{} apisixConsumer.Transfer(req) if _, err := apisixConsumer.PutConsumerToApisix(req.Username); err != nil { + tx.Rollback() if _, ok := err.(*errno.HttpError); ok { return err } @@ -162,33 +193,56 @@ func ConsumerCreate(param interface{}, id string) error { return e } - consumer := &Consumer{} - consumer.Transfer(req) - - // update mysql - consumer.ID = uuid.FromStringOrNil(id) - if err := conf.DB().Create(consumer).Error; err != nil { - return errno.New(errno.DBWriteError, err.Error()) - } + tx.Commit() return nil } func ConsumerUpdate(param interface{}, id string) error { + if id == "" { + return errno.New(errno.InvalidParam) + } + req := &ConsumerDto{} req.Parse(param) + if req == nil { + return errno.New(errno.InvalidParam) + } - exists := Consumer{} - conf.DB().Table("consumers").Where("username = ?", req.Username).First(&exists) - if exists != (Consumer{}) && exists.ID != req.ID { - e := errno.New(errno.DuplicateUserName) - return e + req.ID = uuid.FromStringOrNil(id) + if req.Username != "" { + exists := Consumer{} + conf.DB().Table("consumers").Where("username = ?", req.Username).First(&exists) + if exists != (Consumer{}) && exists.ID != req.ID { + e := errno.New(errno.DuplicateUserName) + return e + } + } + // trans + tx := conf.DB().Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + // update mysql + consumer := &Consumer{} + consumer.Transfer(req) + data := Consumer{Desc: consumer.Desc, Plugins: consumer.Plugins} + if req.Username != "" { + data.Username = req.Username + } + if err := tx.Model(&consumer).Updates(data).Error; err != nil { + tx.Rollback() + return errno.New(errno.DBWriteError, err.Error()) } apisixConsumer := &ApisixConsumer{} apisixConsumer.Transfer(req) if _, err := apisixConsumer.PutConsumerToApisix(req.Username); err != nil { + tx.Rollback() if _, ok := err.(*errno.HttpError); ok { return err } @@ -196,18 +250,15 @@ func ConsumerUpdate(param interface{}, id string) error { return e } - // update mysql - consumer := &Consumer{} - consumer.Transfer(req) - consumer.ID = uuid.FromStringOrNil(id) - if err := conf.DB().Model(&consumer).Updates(consumer).Error; err != nil { - return errno.New(errno.DBWriteError, err.Error()) - } + tx.Commit() return nil } func ConsumerDelete(id string) error { + if id == "" { + return errno.New(errno.InvalidParam) + } // consumer := &Consumer{} if err := conf.DB().Table("consumers").Where("id = ?", id).First(consumer).Error; err != nil { @@ -215,17 +266,31 @@ func ConsumerDelete(id string) error { return e } + // trans + tx := conf.DB().Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + // delete from mysql + if err := conf.DB().Delete(consumer).Error; err != nil { + tx.Rollback() + return errno.New(errno.DBDeleteError, err.Error()) + } + + //delete from apisix if _, err := consumer.DeleteConsumerFromApisix(); err != nil { + tx.Rollback() if _, ok := err.(*errno.HttpError); ok { return err } e := errno.New(errno.ApisixConsumerDeleteError, err.Error()) return e } - // delete from mysql - if err := conf.DB().Delete(consumer).Error; err != nil { - return errno.New(errno.DBDeleteError, err.Error()) - } + + tx.Commit() return nil } @@ -269,3 +334,16 @@ func (req *Consumer) DeleteConsumerFromApisix() (*ApisixConsumerResponse, error) } } } + +func GetConsumerByUserName(name string) (*Consumer, error) { + if name == "" { + return nil, errno.New(errno.InvalidParam) + } + consumer := &Consumer{} + if err := conf.DB().Table("consumers").Where("username = ?", name).First(consumer).Error; err != nil { + e := errno.New(errno.NotFoundError, err.Error()) + return nil, e + } + + return consumer, nil +} diff --git a/api/service/ssl.go b/api/service/ssl.go index 08dea43d87..2fbfbfd948 100644 --- a/api/service/ssl.go +++ b/api/service/ssl.go @@ -17,12 +17,15 @@ package service import ( + "crypto/md5" "crypto/tls" "crypto/x509" "encoding/json" "encoding/pem" "errors" "fmt" + "regexp" + "strings" "github.com/apisix/manager-api/conf" "github.com/apisix/manager-api/errno" @@ -37,6 +40,7 @@ type Ssl struct { Snis string `json:"snis"` Status uint64 `json:"status"` PublicKey string `json:"public_key,omitempty"` + PublicKeyHash string `json:"public_key_hash,omitempty"` } type SslDto struct { @@ -69,8 +73,9 @@ type SslNode struct { func (req *SslRequest) Parse(body interface{}) { if err := json.Unmarshal(body.([]byte), req); err != nil { + logger.Info("req:") + logger.Info(req) req = nil - logger.Error(errno.FromMessage(errno.RouteRequestError, err.Error()).Msg) } } @@ -91,7 +96,7 @@ func (sslDto *SslDto) Parse(ssl *Ssl) error { return nil } -func SslList(page, size, status, expireStart, expireEnd int, sni string) (int, []SslDto, error) { +func SslList(page, size, status, expireStart, expireEnd int, sni, sortType string) (int, []SslDto, error) { var count int sslList := []Ssl{} db := conf.DB().Table("ssls") @@ -109,7 +114,12 @@ func SslList(page, size, status, expireStart, expireEnd int, sni string) (int, [ db = db.Where("validity_end <= ? ", expireEnd) } - if err := db.Order("validity_end desc").Offset((page - 1) * size).Limit(size).Find(&sslList).Error; err != nil { + sortType = strings.ToLower(sortType) + if sortType != "desc" { + sortType = "asc" + } + + if err := db.Order("validity_end " + sortType).Offset((page - 1) * size).Limit(size).Find(&sslList).Error; err != nil { e := errno.New(errno.DBReadError, err.Error()) return 0, nil, e } @@ -131,7 +141,11 @@ func SslList(page, size, status, expireStart, expireEnd int, sni string) (int, [ } func SslItem(id string) (*SslDto, error) { + if id == "" { + return nil, errno.New(errno.InvalidParam) + } ssl := &Ssl{} + if err := conf.DB().Table("ssls").Where("id = ?", id).First(ssl).Error; err != nil { e := errno.New(errno.DBReadError, err.Error()) return nil, e @@ -166,53 +180,126 @@ func SslCreate(param interface{}, id string) error { sslReq := &SslRequest{} sslReq.Parse(param) - ssl, err := ParseCert(sslReq.PublicKey, sslReq.PrivateKey) + if sslReq.PrivateKey == "" { + return errno.New(errno.InvalidParamDetail, "Key is required") + } + if sslReq.PublicKey == "" { + return errno.New(errno.InvalidParamDetail, "Cert is required") + } + sslReq.PublicKey = strings.TrimSpace(sslReq.PublicKey) + sslReq.PrivateKey = strings.TrimSpace(sslReq.PrivateKey) + + ssl, err := ParseCert(sslReq.PublicKey, sslReq.PrivateKey) if err != nil { e := errno.FromMessage(errno.SslParseError, err.Error()) return e } - // first admin api + ssl.ID = uuid.FromStringOrNil(id) + ssl.Status = 1 + data := []byte(ssl.PublicKey) + hash := md5.Sum(data) + ssl.PublicKeyHash = fmt.Sprintf("%x", hash) + + //check hash + exists := Ssl{} + conf.DB().Table("ssls").Where("public_key_hash = ?", ssl.PublicKeyHash).First(&exists) + if exists != (Ssl{}) { + e := errno.New(errno.DuplicateSslCert) + return e + } + + //check sni var snis []string _ = json.Unmarshal([]byte(ssl.Snis), &snis) sslReq.Snis = snis + // trans + tx := conf.DB().Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + // update mysql + if err := tx.Create(ssl).Error; err != nil { + tx.Rollback() + return errno.New(errno.DBWriteError, err.Error()) + } + + //admin api + if _, err := sslReq.PutToApisix(id); err != nil { + tx.Rollback() if _, ok := err.(*errno.HttpError); ok { return err } e := errno.New(errno.ApisixSslCreateError, err.Error()) return e } - // then mysql - ssl.ID = uuid.FromStringOrNil(id) - ssl.Status = 1 - if err := conf.DB().Create(ssl).Error; err != nil { - return errno.New(errno.DBWriteError, err.Error()) - } + tx.Commit() return nil } func SslUpdate(param interface{}, id string) error { + if id == "" { + return errno.New(errno.InvalidParam) + } + sslReq := &SslRequest{} sslReq.Parse(param) - ssl, err := ParseCert(sslReq.PublicKey, sslReq.PrivateKey) + if sslReq.PrivateKey == "" { + return errno.New(errno.InvalidParamDetail, "Key is required") + } + if sslReq.PublicKey == "" { + return errno.New(errno.InvalidParamDetail, "Cert is required") + } + ssl, err := ParseCert(sslReq.PublicKey, sslReq.PrivateKey) if err != nil { - e := errno.FromMessage(errno.SslParseError, err.Error()) + return errno.FromMessage(errno.SslParseError, err.Error()) + } + + hash := md5.Sum([]byte(ssl.PublicKey)) + ssl.ID = uuid.FromStringOrNil(id) + ssl.PublicKeyHash = fmt.Sprintf("%x", hash) + + //check hash + exists := Ssl{} + conf.DB().Table("ssls").Where("public_key_hash = ?", ssl.PublicKeyHash).First(&exists) + if exists != (Ssl{}) && exists.ID != ssl.ID { + e := errno.New(errno.DuplicateSslCert) return e } - // first admin api + // trans + tx := conf.DB().Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + //sni check var snis []string _ = json.Unmarshal([]byte(ssl.Snis), &snis) sslReq.Snis = snis + // update mysql + data := Ssl{PublicKey: ssl.PublicKey, Snis: ssl.Snis, ValidityStart: ssl.ValidityStart, ValidityEnd: ssl.ValidityEnd} + if err := tx.Model(&ssl).Updates(data).Error; err != nil { + tx.Rollback() + return errno.New(errno.DBWriteError, err.Error()) + } + + //admin api if _, err := sslReq.PutToApisix(id); err != nil { + tx.Rollback() if _, ok := err.(*errno.HttpError); ok { return err } @@ -220,21 +307,37 @@ func SslUpdate(param interface{}, id string) error { return e } - // then mysql - ssl.ID = uuid.FromStringOrNil(id) - data := Ssl{PublicKey: ssl.PublicKey, Snis: ssl.Snis, ValidityStart: ssl.ValidityStart, ValidityEnd: ssl.ValidityEnd} - if err := conf.DB().Model(&ssl).Updates(data).Error; err != nil { - return errno.New(errno.DBWriteError, err.Error()) - } + tx.Commit() return nil } func SslPatch(param interface{}, id string) error { + if id == "" { + return errno.New(errno.InvalidParam) + } + sslReq := &SslRequest{} sslReq.Parse(param) + // trans + tx := conf.DB().Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + // update mysql + ssl := Ssl{} + ssl.ID = uuid.FromStringOrNil(id) + if err := tx.Model(&ssl).Update("status", sslReq.Status).Error; err != nil { + tx.Rollback() + return errno.New(errno.DBWriteError, err.Error()) + } + if _, err := sslReq.PatchToApisix(id); err != nil { + tx.Rollback() if _, ok := err.(*errno.HttpError); ok { return err } @@ -242,32 +345,45 @@ func SslPatch(param interface{}, id string) error { return e } - ssl := Ssl{} - ssl.ID = uuid.FromStringOrNil(id) - if err := conf.DB().Model(&ssl).Update("status", sslReq.Status).Error; err != nil { - return errno.New(errno.DBWriteError, err.Error()) - } + tx.Commit() return nil } func SslDelete(id string) error { + if id == "" { + return errno.New(errno.InvalidParam) + } + + // trans + tx := conf.DB().Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + // delete from mysql + ssl := &Ssl{} + ssl.ID = uuid.FromStringOrNil(id) + if err := conf.DB().Delete(ssl).Error; err != nil { + tx.Rollback() + return errno.New(errno.DBDeleteError, err.Error()) + } + // delete from apisix request := &SslRequest{} request.ID = id if _, err := request.DeleteFromApisix(); err != nil { + tx.Rollback() if _, ok := err.(*errno.HttpError); ok { return err } e := errno.New(errno.ApisixSslDeleteError, err.Error()) return e } - // delete from mysql - ssl := &Ssl{} - ssl.ID = uuid.FromStringOrNil(id) - if err := conf.DB().Delete(ssl).Error; err != nil { - return errno.New(errno.DBDeleteError, err.Error()) - } + + tx.Commit() return nil } @@ -404,3 +520,70 @@ func ParseCert(crt, key string) (*Ssl, error) { return &ssl, nil } } + +func CheckSniExists(param interface{}) error { + var hosts []string + if err := json.Unmarshal(param.([]byte), &hosts); err != nil { + return errno.FromMessage(errno.InvalidParam) + } + + sslList := []Ssl{} + db := conf.DB().Table("ssls") + db = db.Where("`status` = ? ", 1) + + condition := "" + args := []interface{}{} + first := true + for _, host := range hosts { + idx := strings.Index(host, "*") + keyword := strings.Replace(host, "*.", "", -1) + if idx == -1 { + if j := strings.Index(host, "."); j != -1 { + keyword = host[j:] + //just one `.` + if j := strings.Index(host[(j+1):], "."); j == -1 { + keyword = host + } + } + } + if first { + condition = condition + "`snis` like ?" + } else { + condition = condition + " or `snis` like ?" + } + first = false + args = append(args, "%"+keyword+"%") + } + db = db.Where(condition, args...) + + if err := db.Find(&sslList).Error; err != nil { + return errno.FromMessage(errno.SslForSniNotExists, hosts[0]) + } + +hre: + for _, host := range hosts { + for _, ssl := range sslList { + sslDto := SslDto{} + sslDto.Parse(&ssl) + for _, sni := range sslDto.Snis { + if sni == host { + continue hre + } + regx := strings.Replace(sni, ".", `\.`, -1) + regx = strings.Replace(regx, "*", `([^\.]+)`, -1) + regx = "^" + regx + "$" + if isOk, _ := regexp.MatchString(regx, host); isOk { + continue hre + } + } + } + return errno.FromMessage(errno.SslForSniNotExists, host) + } + + return nil +} + +func DeleteTestSslData() { + db := conf.DB().Table("ssls") + db.Where("snis LIKE ? OR (snis LIKE ? AND snis LIKE ? )", "%*.route.com%", "%r.com%", "%s.com%").Delete(Ssl{}) +} diff --git a/api/service/ssl_test.go b/api/service/ssl_test.go index 46607e8258..46ecbb973a 100644 --- a/api/service/ssl_test.go +++ b/api/service/ssl_test.go @@ -218,6 +218,16 @@ func TestSslCurd(t *testing.T) { assert.Equal(true, strings.Contains(dm, "test3.com")) } + //test3.com duplicate + param = []byte(`{ + "cert": "-----BEGIN CERTIFICATE-----\nMIIEWjCCAsKgAwIBAgIRAMLLNCKEvgEQL22Hpox6E1kwDQYJKoZIhvcNAQELBQAw\nfzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMSowKAYDVQQLDCFqdW54\ndWNoZW5AanVueHVkZUFpciAoanVueHUgY2hlbikxMTAvBgNVBAMMKG1rY2VydCBq\ndW54dWNoZW5AanVueHVkZUFpciAoanVueHUgY2hlbikwHhcNMTkwNjAxMDAwMDAw\nWhcNMzAwNjA5MTA0MjA1WjBVMScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQg\nY2VydGlmaWNhdGUxKjAoBgNVBAsMIWp1bnh1Y2hlbkBqdW54dWRlQWlyIChqdW54\ndSBjaGVuKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL2lg+vzHkRI\nCs8PHv6UVxXFbrL6wlsdurOICkW5daKUJyzUZQZl11CK9SWOk8vAc7j3pQ7Mz15r\nhfQB558WHzI/XXbZ1NrZrTpLaL0fWW5n4hIE8EbYf3Hy/xM8gRUXsWMEexq2WC/R\nPfTCQIZ85vUSANS72E5rHdba3Y5IMr8bn/NUg1sm2LxZQmZV6tBOpYnibyj7bXxw\n8kxr4w+B/5jDBPmwL59bdoatEs1FjdHzz+fbW1K4NdHZZEotYqkQhCS09JnwGswd\nAriy+Is44kt/gtw7nVWmuV/eQaxPEHVE4Bwvdmv11IsPsj6hif23gXjXLIHx66CY\n/S4I/P0allkCAwEAAaN7MHkwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsG\nAQUFBwMBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUVPhGyY/h6etoAqzb298k\nJGJzx1wwIwYDVR0RBBwwGoIJdGVzdDMuY29tgg13d3cudGVzdDMuY29tMA0GCSqG\nSIb3DQEBCwUAA4IBgQCgb5wOMzkD1/tgrjwE7Cj1NvX7/p/JIQVVtvtnnypyXGNn\nVL+q4oB9WzvOvMcTCiKKHqg9jCiu/KFFHy7nRzj7KPhU/o9M7qLNwLJjfUOtPYUm\nrS62kEXlj5L5+UJjiGABGfLllxMwwTkAFbdSUSB1awzoafPn5+g+qABXgkF/EN2I\n+IJcJCqg3IO30n4MMhqNx3IbqIohD3p5GzjQqnuQSrC/HJEsUuIlMCHPJ1GVbbrd\nRnSySMcbv2jThP53JVIe+0HHvcujb2pDQ4RcCSaN3OXaZDYVqoSR04+amotGwiWO\nDY/4LTWFJkfoWnv1kg2/AllMpsXB+1u+O+x6qWzBw2hXP5AM+8KIoJ2/Mb13TsN9\nqhrGep+SfhjARH068ZjaS2zQC2Uvc+SrEGXfITPIkstRELxIV9Lmjl9lwpAOJrte\n4TDjYhBS20j6mt3dUyEBnPfkpcOeLYZNS1sK66MRfzJ9MowdTcWyvMzFHjSjfWPY\n+4scQdmkFkCulnzylak=\n-----END CERTIFICATE-----", + "key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC9pYPr8x5ESArP\nDx7+lFcVxW6y+sJbHbqziApFuXWilCcs1GUGZddQivUljpPLwHO496UOzM9ea4X0\nAeefFh8yP1122dTa2a06S2i9H1luZ+ISBPBG2H9x8v8TPIEVF7FjBHsatlgv0T30\nwkCGfOb1EgDUu9hOax3W2t2OSDK/G5/zVINbJti8WUJmVerQTqWJ4m8o+218cPJM\na+MPgf+YwwT5sC+fW3aGrRLNRY3R88/n21tSuDXR2WRKLWKpEIQktPSZ8BrMHQK4\nsviLOOJLf4LcO51Vprlf3kGsTxB1ROAcL3Zr9dSLD7I+oYn9t4F41yyB8eugmP0u\nCPz9GpZZAgMBAAECggEAZ2+8SVgb/QASLSdBL3d3HB/IJgSRNyM67qrXd3oVCCyo\nRVI/G8M2Me7okKh4QhxgwdUIiM76l7Qrpo/XZjSppT1cW/Opngg17GKu6OANZiNw\n8YUSDIIO2PbBWxuYCAoZLTmHb2VfKg2FLlc43GGJksdT/rPJ4dOYvdQ4HV+Rlhuo\ntLJznoTStoX+DXLaNU7+jK1ZjbjOKPWcTJt42a0Rvu34ghtbqhkHt9VAvqysgysq\n2GO6Idvu3CE3rgDyde4SBiZL5twtLX3/56daIOG/Gt9NtfvftIilgrPoMfY8WsJH\njY5AaJKaq88a+A2M3ICOAehquhaGqest9kXP5qWzZQKBgQDIrvROSvKxYw9nAksR\nQtvA8fDp6JYxViDqAK5O3kCs0eamCNGycqEOO8yBakWY3YlVxVZVUd/0kymhePEw\nwiJbIZppOaS7xDtdrPvk3QwIVCDYYfZ7SyY1n1FPynpWAYTqIm1YuhWDqXOcQZcy\nxaK86LOtAIAIXomefJYEvkya4wKBgQDx68EKTqxdETiUGUJVxgTLg96Y5zMGLgGO\n6lkqpbUQFfN4yZSHv97P9gugv9fKustGxCEvA9StC2Dq7AtpjOqfYabqiM7ywqzf\nmKazBcIch/qPijVHZO6bLRBUXZWhV7/qZzxN7luenr+U4XwtxBXApGdUmkQYwPnk\nc52J156ikwKBgGOmtLu37cF13i0Zb2s31uV9flK4YvRGv3tTMTsKk/T9GdoyoOZK\nk3z85rUQr1SUFWEY56DgUiQhe1eqNaIvlF3KVuGPdSSj8ZK3ljF0LkhodhLcukdI\n7sVLwlWrxom0oWqeA8w+QvapCzZ5P3o/t2q05puuluURBKdFWD0svd9fAoGANjnk\nAU11MT9E8V1gEx3ZwUyDvr5EH6R8UO6Sog6WsU5aTr7QfkUxymeaX6Pg2N5Z5jjc\nP0+agldEmCPkwvoFNUiMQ5H64UtluJDc/M/TnNWWAkq2epRTL5FAUcjQW2Px7rbJ\nO6ar/rgStWp9jTygq5euWbZigTHwUZbgvx8HveUCgYBsNapwmK2w66ve6rhCHR4W\naNSZyHq/hz84hwhUwSMFv0qU54oJchZWGPnBBIHWlV3J/yYTxQ3p9UWRlH3u2kpQ\nhEcGHWmVjKCc1IL/Qczw4U4koMNSOY+uqJmomOGkDpA2yWuOIDhFXebLSdcJNr0u\n2DYf16scbug3YbbqdD2WGg==\n-----END PRIVATE KEY-----" + }`) + + err = SslCreate(param, u2.String()) + assert.NotNil(err) + assert.Equal(errno.DuplicateSslCert.Code, err.(*errno.ManagerError).Code) + //a.com b.com fail param = []byte(`{ "cert": "-----BEGIN CERTIFICATE-----\nMIICcTCCAdoCCQDQoPEll/bQizANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJD\nTjEOMAwGA1UECAwFbXlrZXkxDjAMBgNVBAcMBW15a2V5MQ4wDAYDVQQKDAVteWtl\neTEOMAwGA1UECwwFbXlrZXkxDjAMBgNVBAMMBWEuY29tMQ4wDAYDVQQDDAViLmNv\nbTEOMAwGA1UEAwwFYy5jb20wHhcNMjAwNjE3MDk1MDA0WhcNMzAwNjE1MDk1MDA0\nWjB9MQswCQYDVQQGEwJDTjEOMAwGA1UECAwFbXlrZXkxDjAMBgNVBAcMBW15a2V5\nMQ4wDAYDVQQKDAVteWtleTEOMAwGA1UECwwFbXlrZXkxDjAMBgNVBAMMBWEuY29t\nMQ4wDAYDVQQDDAViLmNvbTEOMAwGA1UEAwwFYy5jb20wgZ8wDQYJKoZIhvcNAQEB\nBQADgY0AMIGJAoGBANHMrKlfFzJbyYuD0YveK2mOOXR9zXi+vC5lW6RaoyKjx5AL\nyIXQWXURGVnxw1+xbmxWN1MXZyAP7eJYFPa0PIJvW0kbyHkJt/TrCyBLVOqpTqvE\nkDAIde9Fx83556sXD43Oq93lyBraXmR+fXuoLxJQQLhALW1tOg1X3VrxKYXNAgMB\nAAEwDQYJKoZIhvcNAQELBQADgYEAwJ7qV0Tj6JXR035ySVSBG1KBF19DVmMYRKdO\nSAU1j437q+ktTcEWSA0CkH6rg53tP4V1h0tzdhCxisivYynngjtEcZfsrwdIrsSg\ncmOBZ+KTRyZ2fLgH4F8Naz5hBrwmR8ZIG46feVOV/swJzz4BNaXGj1oATWkLMA3c\nSf0G+aI=\n-----END CERTIFICATE-----", @@ -238,10 +248,29 @@ func TestSslCurd(t *testing.T) { assert.Nil(err) //list - count, list, err := SslList(2, 1, -1, 0, 0, "") + count, list, err := SslList(2, 1, -1, 0, 0, "", "asc") assert.Equal(true, count >= 2) assert.Equal(1, len(list)) + // check sni ssl exist + param = []byte(`[ + "test3.com", + "www.test3.com", + "a.com" + ]`) + + err = CheckSniExists(param) + assert.Nil(err) + + // check sni ssl exist + param = []byte(`[ + "test3.com", + "a.test3.com", + "b.com" + ]`) + err = CheckSniExists(param) + assert.NotNil(err) + // patch param = []byte(`{ "status": 0 @@ -252,23 +281,70 @@ func TestSslCurd(t *testing.T) { ssl, err = SslItem(u1.String()) assert.Equal(uint64(0), ssl.Status) + // check sni ssl exist --- disable test3 + param = []byte(`[ + "test3.com", + "www.test3.com", + "a.com" + ]`) + + err = CheckSniExists(param) + assert.NotNil(err) + + param = []byte(`[ + "a.com" + ]`) + err = CheckSniExists(param) + assert.Nil(err) + //update param = []byte(`{ - "cert": "-----BEGIN CERTIFICATE-----\nMIICcTCCAdoCCQDQoPEll/bQizANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJD\nTjEOMAwGA1UECAwFbXlrZXkxDjAMBgNVBAcMBW15a2V5MQ4wDAYDVQQKDAVteWtl\neTEOMAwGA1UECwwFbXlrZXkxDjAMBgNVBAMMBWEuY29tMQ4wDAYDVQQDDAViLmNv\nbTEOMAwGA1UEAwwFYy5jb20wHhcNMjAwNjE3MDk1MDA0WhcNMzAwNjE1MDk1MDA0\nWjB9MQswCQYDVQQGEwJDTjEOMAwGA1UECAwFbXlrZXkxDjAMBgNVBAcMBW15a2V5\nMQ4wDAYDVQQKDAVteWtleTEOMAwGA1UECwwFbXlrZXkxDjAMBgNVBAMMBWEuY29t\nMQ4wDAYDVQQDDAViLmNvbTEOMAwGA1UEAwwFYy5jb20wgZ8wDQYJKoZIhvcNAQEB\nBQADgY0AMIGJAoGBANHMrKlfFzJbyYuD0YveK2mOOXR9zXi+vC5lW6RaoyKjx5AL\nyIXQWXURGVnxw1+xbmxWN1MXZyAP7eJYFPa0PIJvW0kbyHkJt/TrCyBLVOqpTqvE\nkDAIde9Fx83556sXD43Oq93lyBraXmR+fXuoLxJQQLhALW1tOg1X3VrxKYXNAgMB\nAAEwDQYJKoZIhvcNAQELBQADgYEAwJ7qV0Tj6JXR035ySVSBG1KBF19DVmMYRKdO\nSAU1j437q+ktTcEWSA0CkH6rg53tP4V1h0tzdhCxisivYynngjtEcZfsrwdIrsSg\ncmOBZ+KTRyZ2fLgH4F8Naz5hBrwmR8ZIG46feVOV/swJzz4BNaXGj1oATWkLMA3c\nSf0G+aI=\n-----END CERTIFICATE-----", - "key": "-----BEGIN RSA PRIVATE KEY-----\nMIICXAIBAAKBgQDRzKypXxcyW8mLg9GL3itpjjl0fc14vrwuZVukWqMio8eQC8iF\n0Fl1ERlZ8cNfsW5sVjdTF2cgD+3iWBT2tDyCb1tJG8h5Cbf06wsgS1TqqU6rxJAw\nCHXvRcfN+eerFw+Nzqvd5cga2l5kfn17qC8SUEC4QC1tbToNV91a8SmFzQIDAQAB\nAoGBAJIL/y4wqf8+ckES1G6fjG0AuvJjGQQzEuDhYjg5eFMG3EdkTIUKkxuxeYpp\niG43H/1+zyiipAFn1Vu5oW5T7cJEgC1YA39dERT605S5BrNWWHoZsgH+qmLoq7X+\njXMlmCagwlgwhUWMU2M1/LUbAl42384dK9u3EwcCgS//sFuBAkEA6mK52/Z03PB3\n0sS14eN7xFl96yc/NcneJ7Vy5APT0KGLo0j2S8gpOVW9EYrrzDzWgQ8FLIeed2Zw\nZ4ATksgRXQJBAOUlh5VJkyMdMiDEeJgK9QKtJkuiLZFAzZiWAUqjvSG2j8tWX/iN\nveI1sXCPyQSKoWPN74+23KWL+nW+mUzkzzECQFf+UIB/+keoD5QVPaNcX+7LGjba\nOSTccIa/3C42MaM1wtK+ZZj1wGRCCAU5/mRiwrUZCnw5PgjdcH2q265TZhECQASY\nJgnGOd8AXNrvVYOm5JazJgtqKwO4iua+SzRV6Bre8C8hgjcXkHESpoYdO+iNZwL7\nRAxbnDzte44UzjoOdGECQGtkrBffiyMaQv6LM/6Fa5TXHb1kPtLGIjFSygR3eTYI\ngHG78R5ac0dzhbyKaOo6cbj7CJVkbBh4BNW94tBZE/I=\n-----END RSA PRIVATE KEY-----" + "cert": "-----BEGIN CERTIFICATE-----\nMIIEWzCCAsOgAwIBAgIQDYoN+el2w074sSGlyKVZFTANBgkqhkiG9w0BAQsFADB/\nMR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExKjAoBgNVBAsMIWp1bnh1\nY2hlbkBqdW54dWRlQWlyIChqdW54dSBjaGVuKTExMC8GA1UEAwwobWtjZXJ0IGp1\nbnh1Y2hlbkBqdW54dWRlQWlyIChqdW54dSBjaGVuKTAeFw0xOTA2MDEwMDAwMDBa\nFw0zMDA3MDQwNjA0MzNaMFUxJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9wbWVudCBj\nZXJ0aWZpY2F0ZTEqMCgGA1UECwwhanVueHVjaGVuQGp1bnh1ZGVBaXIgKGp1bnh1\nIGNoZW4pMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy21LYFmQYpXm\nMlUjybwbJ338mwKmxY+wTEJLBhw7tBcau9aFjjyO4aRph4rpdMQgCn4lwTME2lbF\ndRhHzU5+Sy9JsI1k+9/J8sZSaTIj9paOX2PYnEOoFjIx9wpJRpeLNBjy3ICS3HC4\nSzTbDVAk9LZILLv/81vt1KpQ1HoPpE+OZ1wX+CL0/6RnNmdaqgrmttPv0sul9yIe\nKz19Hr26px7g6UnoK0o8rSwCqVmjoVTJ+eY2zmmzShqFPTLFgvNZmMeL3dPmg6nG\ndjjsbkXS50thyTDb/h+YIvEsZfrDVgbV2g6P/KiyfKCyevMQxd1J5/UI4HltI3MX\ngrpoaiDiVwIDAQABo30wezAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYB\nBQUHAwEwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBRU+EbJj+Hp62gCrNvb3yQk\nYnPHXDAlBgNVHREEHjAcggtleGFtcGxlLmNvbYINKi5leGFtcGxlLmNvbTANBgkq\nhkiG9w0BAQsFAAOCAYEAuKgNfkAA6uKoAgtQhE4+MPnRmWnHrYaUcqxVYXJZfFyi\nScPaolku+0MsSr1dD2JrbqKMwa54C293e2jkz1EETYKT4bhETS7ttzO1WubLHOyT\nRWb26DVZQH+tPrvUYE4kYSdT3uGi3JNJse2Lpw014nkcwgxOI1Sn8hnfeE7rZU5B\nv79EoqjSwvFDf8aOJTh6mBoe134s3PqrmEjx0VrTlZCkSy9J9REQKkdWmTwW68C2\nrdhV9+/E+xS10WlmxsbGhPgcEhMP0EfGLZm0dh7XUIt06Y3V1iCVFu6/7wQbmO6a\nrvOf2wmoUuZCfZDsLFBc1RIM5AvPktbZJrrhDunum+Sh3Pg8ntDhGSFJM8lCyvo3\n1bQ2rTc0fQJd95ztesaCUdyGi07coLtr1kXPpcv9DLTthXoDltSWB2jTDOPRS902\nzlBfhOmp1H7Xh9OQzEGxJLrtUOntdOM1Ws/GOvopaHdNic7xsodKqlxlnafXTJKA\n7/0x3XEWbPHxrfqvBoax\n-----END CERTIFICATE-----", + "key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDLbUtgWZBileYy\nVSPJvBsnffybAqbFj7BMQksGHDu0Fxq71oWOPI7hpGmHiul0xCAKfiXBMwTaVsV1\nGEfNTn5LL0mwjWT738nyxlJpMiP2lo5fY9icQ6gWMjH3CklGl4s0GPLcgJLccLhL\nNNsNUCT0tkgsu//zW+3UqlDUeg+kT45nXBf4IvT/pGc2Z1qqCua20+/Sy6X3Ih4r\nPX0evbqnHuDpSegrSjytLAKpWaOhVMn55jbOabNKGoU9MsWC81mYx4vd0+aDqcZ2\nOOxuRdLnS2HJMNv+H5gi8Sxl+sNWBtXaDo/8qLJ8oLJ68xDF3Unn9QjgeW0jcxeC\numhqIOJXAgMBAAECggEARdPea9RSm4SY3+4ZusW3DHdSnmLqnCYWfhbDafWHCOpl\nYtTgQ1LGOO4Wy1ADkvE/jlp+2zKIF+pxHSCYhQDMmUJCKThf0ZWf3JX28+UiMyK6\n/ENptzoCGJxiSkpdnL2oKtnlg4se3kxS9n3OM2OvS9DGNZTS5tZHbRiJZmX/uIZ4\n5JsETflo/emPmH3NJNMmUr+uLtowqn3KT0tCm2nPZUgtSepztTK5ugumk+Apyhc8\n3bno20b+97IzzUmn584C3fIv776rOgyMQPi2CrdtCh1jsgqsO87DyJTWf/BdbqPU\nKFD9buv21vgyCVBUPUHL/RLRkG5iwx5713bExmLjAQKBgQD+UrYu135kqZzgjkQ6\nviC1xUcJgi9mVI1AI7YXUEqx3/IDCr+oum95zDMJW2I5TbWJNdXQ1JmWqX1p06G7\nLDel/lYe/GUjNu76U7eSPL2H29MqymeT+y5GB63U5BqgfVKj39gTlrLoMwz7KF/x\nXtH5z/ZTo82NII12zUX36bbIiwKBgQDMxKv/94T8OWoO067lQ07vVCruhmjojJFa\na8oyv73tS3V8bzP4b1MgUinDaksyfpSdBbd4Tnr3ImvQxEUOJVK0Mg43sTK8GXpF\nPn0VsY+rhynDv0KQZ4GHvE2Mb/QxycJdiaF7TowevNe12Ujc+NGr35CRJhgspgYM\nnNhCppA65QKBgQCztvEEYsTvDyhLSl0OgaINsK1FG9iw4Bi8dT/Mc7GExnJ3EdZj\nvfLeR5zdBNWBFtescP15x3INFBIKgUEtSc69Ht/un348hyoSfKwgy4lHAuDSwRq2\naG3HkM+Wu+XQ+R43rQs8tGYSTVjj9iDuKIoKlJlFe1/aVWGBzQafbGj8hwKBgDLO\nlbLEMo32nPci1OFzyvEdHC3k0cDpp+McnaXr528qavM+EFITJTf+yvf+trvHpo4z\nbet+5YnOU5wQJuY0oomtZdOxttnvJGRr9dNdJD22Ism7+gMke4I3WbJ/0MJNwlk9\nHgEfYyr5RjiLukWBw1x280Lghd0GMLgObqZS97R1AoGAZnB0oCBKrgR1i76jUoXu\nsiSrId5KvcbtuE0H8JMN3rl+77HEYAyJXGRU45e1kyX1HvErGz2Q3mvpFFJxNRf7\nZRwIJh/rEQ/G7svCu6k+5UGt88dbxgtR8C0WfMnmQH0ZW8XLgD6J+R7A7N2vtrd1\nzlzZL/qYUmm8QEK8UN9LbiM=\n-----END PRIVATE KEY-----" }`) err = SslUpdate(param, u1.String()) assert.Nil(err) ssl, _ = SslItem(u1.String()) - assert.Equal(3, len(ssl.Snis)) + assert.Equal(2, len(ssl.Snis)) + + // check sni ssl exist + param = []byte(`[ + "example.com", + "www.example.com", + "a.example.com", + "a.com", + "b.com" + ]`) + + err = CheckSniExists(param) + assert.NotNil(err) + assert.Equal(errno.SslForSniNotExists.Code, err.(*errno.ManagerError).Code) + + param = []byte(`{ + "status": 1 + }`) + err = SslPatch(param, u1.String()) + assert.Nil(err) + + // check sni ssl exist + param = []byte(`[ + "example.com", + "www.example.com", + "a.example.com", + "a.com", + "b.com" + ]`) + + err = CheckSniExists(param) + assert.Nil(err) //delete err = SslDelete(u1.String()) assert.Nil(err) - count2, _, err := SslList(2, 1, -1, 0, 0, "") + count2, _, err := SslList(2, 1, -1, 0, 0, "", "desc") assert.Equal(count2, count-1) err = SslDelete(u2.String())