From 681a0c91064cc0019f8c50ded6dbc4b89174ef09 Mon Sep 17 00:00:00 2001 From: zhengkunwang223 Date: Wed, 16 Nov 2022 10:31:35 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E8=AF=81=E4=B9=A6?= =?UTF-8?q?=E7=94=B3=E8=AF=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/nginx/1.23.1/docker-compose.yml | 1 + backend/app/api/v1/website_ssl.go | 96 +++++---- backend/app/dto/nginx.go | 2 + backend/app/dto/website.go | 4 + backend/app/dto/website_ssl.go | 32 ++- backend/app/model/website.go | 23 ++- backend/app/model/website_ssl.go | 18 +- backend/app/repo/website.go | 2 +- backend/app/repo/website_acme_account.go | 9 + backend/app/repo/website_dns_account.go | 9 + backend/app/repo/website_ssl.go | 25 ++- backend/app/service/website.go | 8 +- backend/app/service/website_ssl.go | 153 ++++++++++---- backend/app/service/website_utils.go | 83 +++++++- backend/init/router/router.go | 1 + backend/router/entry.go | 1 + backend/router/ro_website_dns_account.go | 4 +- backend/router/ro_website_ssl.go | 7 +- backend/utils/nginx/private.key | 50 ++--- backend/utils/ssl/client.go | 58 +++++- frontend/src/api/interface/website.ts | 37 ++++ frontend/src/api/modules/website.ts | 30 ++- frontend/src/hooks/use-delete-data.ts | 2 +- frontend/src/lang/modules/zh.ts | 9 +- .../views/website/project/config/index.vue | 12 +- .../config/ssl/account/create/index.vue | 2 +- .../website/project/config/ssl/index.vue | 2 +- .../project/config/ssl/ssl/create/index.vue | 190 ++++++++++++++++++ .../website/project/config/ssl/ssl/index.vue | 90 ++++++++- go.mod | 2 +- go.sum | 6 + 31 files changed, 795 insertions(+), 173 deletions(-) create mode 100644 frontend/src/views/website/project/config/ssl/ssl/create/index.vue diff --git a/apps/nginx/1.23.1/docker-compose.yml b/apps/nginx/1.23.1/docker-compose.yml index 6ed38ceed..1a3890191 100644 --- a/apps/nginx/1.23.1/docker-compose.yml +++ b/apps/nginx/1.23.1/docker-compose.yml @@ -12,6 +12,7 @@ services: - ./conf/nginx.conf:/etc/nginx/nginx.conf - ./log:/var/log/nginx - ./conf/conf.d:/etc/nginx/conf.d/ + - ./ssl:/etc/nginx/ssl networks: 1panel: diff --git a/backend/app/api/v1/website_ssl.go b/backend/app/api/v1/website_ssl.go index a8fd76fe4..be42f49f8 100644 --- a/backend/app/api/v1/website_ssl.go +++ b/backend/app/api/v1/website_ssl.go @@ -24,43 +24,59 @@ func (b *BaseApi) PageWebsiteSSL(c *gin.Context) { }) } -//func (b *BaseApi) CreateWebsiteSSL(c *gin.Context) { -// var req dto.WebsiteSSLCreate -// if err := c.ShouldBindJSON(&req); err != nil { -// helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) -// return -// } -// if _, err := WebsiteSSLService.Create(req); err != nil { -// helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) -// return -// } -// helper.SuccessWithData(c, nil) -//} -// -//func (b *BaseApi) UpdateWebsiteSSL(c *gin.Context) { -// var req dto.WebsiteSSLUpdate -// if err := c.ShouldBindJSON(&req); err != nil { -// helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) -// return -// } -// if _, err := WebsiteSSLService.Update(req); err != nil { -// helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) -// return -// } -// helper.SuccessWithData(c, nil) -//} -// -//func (b *BaseApi) DeleteWebsiteSSL(c *gin.Context) { -// -// id, err := helper.GetParamID(c) -// if err != nil { -// helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) -// return -// } -// -// if err := WebsiteSSLService.Delete(id); err != nil { -// helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) -// return -// } -// helper.SuccessWithData(c, nil) -//} +func (b *BaseApi) CreateWebsiteSSL(c *gin.Context) { + var req dto.WebsiteSSLCreate + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + res, err := websiteSSLService.Create(req) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, res) +} + +func (b *BaseApi) ApplyWebsiteSSL(c *gin.Context) { + var req dto.WebsiteSSLApply + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + res, err := websiteSSLService.Apply(req) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, res) +} + +func (b *BaseApi) GetDNSResolve(c *gin.Context) { + var req dto.WebsiteDNSReq + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + res, err := websiteSSLService.GetDNSResolve(req) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, res) +} + +func (b *BaseApi) DeleteWebsiteSSL(c *gin.Context) { + + id, err := helper.GetParamID(c) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + + if err := websiteSSLService.Delete(id); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} diff --git a/backend/app/dto/nginx.go b/backend/app/dto/nginx.go index 38544a598..7f9c3e492 100644 --- a/backend/app/dto/nginx.go +++ b/backend/app/dto/nginx.go @@ -21,6 +21,7 @@ type NginxScope string const ( Index NginxScope = "index" LimitConn NginxScope = "limit-conn" + SSL NginxScope = "ssl" ) type NginxOp string @@ -34,6 +35,7 @@ const ( var ScopeKeyMap = map[NginxScope][]string{ Index: {"index"}, LimitConn: {"limit_conn", "limit_rate", "limit_conn_zone"}, + SSL: {"ssl_certificate", "ssl_certificate_key"}, } var RepeatKeys = map[string]struct { diff --git a/backend/app/dto/website.go b/backend/app/dto/website.go index 7ba0a329b..59210c663 100644 --- a/backend/app/dto/website.go +++ b/backend/app/dto/website.go @@ -28,6 +28,10 @@ type WebSiteCreate struct { WebSiteGroupID uint `json:"webSiteGroupID" validate:"required"` } +type WebsiteDTO struct { + model.WebSite +} + type WebSiteUpdate struct { ID uint `json:"id" validate:"required"` PrimaryDomain string `json:"primaryDomain" validate:"required"` diff --git a/backend/app/dto/website_ssl.go b/backend/app/dto/website_ssl.go index d2e372522..ae3cc93df 100644 --- a/backend/app/dto/website_ssl.go +++ b/backend/app/dto/website_ssl.go @@ -6,8 +6,34 @@ type WebsiteSSLDTO struct { model.WebSiteSSL } +type SSLProvider string + +const ( + DNSAccount = "dnsAccount" + DnsCommon = "dnsCommon" + Http = "http" +) + type WebsiteSSLCreate struct { - Name string `json:"name" validate:"required"` - Type string `json:"type" validate:"required"` - Authorization map[string]string `json:"authorization" validate:"required"` + Domains []string `json:"domains" validate:"required"` + Provider SSLProvider `json:"provider" validate:"required"` + WebsiteID uint `json:"websiteId" validate:"required"` + AcmeAccountID uint `json:"acmeAccountId"` + DnsAccountID uint `json:"dnsAccountId"` +} + +type WebsiteSSLApply struct { + WebsiteID uint `json:"websiteId" validate:"required"` + SSLID uint `json:"SSLId" validate:"required"` +} + +type WebsiteDNSReq struct { + Domains []string `json:"domains" validate:"required"` + AcmeAccountID uint `json:"acmeAccountId" validate:"required"` +} + +type WebsiteDNSRes struct { + Key string `json:"resolve"` + Value string `json:"value"` + Type string `json:"type"` } diff --git a/backend/app/model/website.go b/backend/app/model/website.go index e51051ed0..b560d4e42 100644 --- a/backend/app/model/website.go +++ b/backend/app/model/website.go @@ -4,17 +4,18 @@ import "time" type WebSite struct { BaseModel - PrimaryDomain string `gorm:"type:varchar(128);not null" json:"primaryDomain"` - Type string `gorm:"type:varchar(64);not null" json:"type"` - Alias string `gorm:"type:varchar(128);not null" json:"alias"` - Remark string `gorm:"type:longtext;" json:"remark"` - Status string `gorm:"type:varchar(64);not null" json:"status"` - ExpireDate time.Time `json:"expireDate"` - AppInstallID uint `gorm:"type:integer" json:"appInstallId"` - WebSiteGroupID uint `gorm:"type:integer" json:"webSiteGroupId"` - WebSiteSSLID uint `gorm:"type:integer" json:"webSiteSSLId"` - WebsiteDnsAccountID uint `gorm:"type:integer" json:"websiteDnsAccountId"` - WebsiteAcmeAccountID uint `gorm:"type:integer" json:"websiteAcmeAccountId"` + PrimaryDomain string `gorm:"type:varchar(128);not null" json:"primaryDomain"` + Type string `gorm:"type:varchar(64);not null" json:"type"` + Alias string `gorm:"type:varchar(128);not null" json:"alias"` + Remark string `gorm:"type:longtext;" json:"remark"` + Status string `gorm:"type:varchar(64);not null" json:"status"` + ExpireDate time.Time `json:"expireDate"` + AppInstallID uint `gorm:"type:integer" json:"appInstallId"` + WebSiteGroupID uint `gorm:"type:integer" json:"webSiteGroupId"` + WebSiteSSLID uint `gorm:"type:integer" json:"webSiteSSLId"` + WebsiteDnsAccountID uint `gorm:"type:integer" json:"websiteDnsAccountId"` + WebsiteAcmeAccountID uint `gorm:"type:integer" json:"websiteAcmeAccountId"` + Domains []WebSiteDomain `json:"domains"` } func (w WebSite) TableName() string { diff --git a/backend/app/model/website_ssl.go b/backend/app/model/website_ssl.go index b8fdd8f89..1e17743ef 100644 --- a/backend/app/model/website_ssl.go +++ b/backend/app/model/website_ssl.go @@ -4,14 +4,16 @@ import "time" type WebSiteSSL struct { BaseModel - PrivateKey string `gorm:"type:longtext;not null" json:"privateKey"` - Pem string `gorm:"type:longtext;not null" json:"pem"` - Domain string `gorm:"type:varchar(256);not null" json:"domain"` - CertURL string `gorm:"type:varchar(256);not null" json:"certURL"` - Type string `gorm:"type:varchar(64);not null" json:"type"` - IssuerName string `gorm:"type:varchar(64);not null" json:"issuerName"` - ExpireDate time.Time `json:"expireDate"` - StartDate time.Time `json:"startDate"` + Alias string `gorm:"type:varchar(64);not null" json:"alias"` + PrivateKey string `gorm:"type:longtext;not null" json:"privateKey"` + Pem string `gorm:"type:longtext;not null" json:"pem"` + Domain string `gorm:"type:varchar(256);not null" json:"domain"` + CertURL string `gorm:"type:varchar(256);not null" json:"certURL"` + Type string `gorm:"type:varchar(64);not null" json:"type"` + IssuerName string `gorm:"type:varchar(64);not null" json:"issuerName"` + DnsAccountID uint `gorm:"type:integer;not null" json:"dnsAccountId"` + ExpireDate time.Time `json:"expireDate"` + StartDate time.Time `json:"startDate"` } func (w WebSiteSSL) TableName() string { diff --git a/backend/app/repo/website.go b/backend/app/repo/website.go index d60c8e7b1..7d7420545 100644 --- a/backend/app/repo/website.go +++ b/backend/app/repo/website.go @@ -28,7 +28,7 @@ func (w WebSiteRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebS func (w WebSiteRepo) GetFirst(opts ...DBOption) (model.WebSite, error) { var website model.WebSite db := getDb(opts...).Model(&model.WebSite{}) - if err := db.First(&website).Error; err != nil { + if err := db.Preload("Domains").First(&website).Error; err != nil { return website, err } return website, nil diff --git a/backend/app/repo/website_acme_account.go b/backend/app/repo/website_acme_account.go index 19bd212af..c5ce6a7ae 100644 --- a/backend/app/repo/website_acme_account.go +++ b/backend/app/repo/website_acme_account.go @@ -14,6 +14,15 @@ func (w WebsiteAcmeAccountRepo) Page(page, size int, opts ...DBOption) (int64, [ return count, accounts, err } +func (w WebsiteAcmeAccountRepo) GetFirst(opts ...DBOption) (model.WebsiteAcmeAccount, error) { + var account model.WebsiteAcmeAccount + db := getDb(opts...).Model(&model.WebsiteAcmeAccount{}) + if err := db.First(&account).Error; err != nil { + return account, err + } + return account, nil +} + func (w WebsiteAcmeAccountRepo) Create(account model.WebsiteAcmeAccount) error { return getDb().Create(&account).Error } diff --git a/backend/app/repo/website_dns_account.go b/backend/app/repo/website_dns_account.go index bd1d5ef49..51125e1ac 100644 --- a/backend/app/repo/website_dns_account.go +++ b/backend/app/repo/website_dns_account.go @@ -16,6 +16,15 @@ func (w WebsiteDnsAccountRepo) Page(page, size int, opts ...DBOption) (int64, [] return count, accounts, err } +func (w WebsiteDnsAccountRepo) GetFirst(opts ...DBOption) (model.WebsiteDnsAccount, error) { + var account model.WebsiteDnsAccount + db := getDb(opts...).Model(&model.WebsiteDnsAccount{}) + if err := db.First(&account).Error; err != nil { + return account, err + } + return account, nil +} + func (w WebsiteDnsAccountRepo) Create(account model.WebsiteDnsAccount) error { return getDb().Create(&account).Error } diff --git a/backend/app/repo/website_ssl.go b/backend/app/repo/website_ssl.go index f17233ffe..74c20f626 100644 --- a/backend/app/repo/website_ssl.go +++ b/backend/app/repo/website_ssl.go @@ -1,10 +1,20 @@ package repo -import "github.com/1Panel-dev/1Panel/backend/app/model" +import ( + "context" + "github.com/1Panel-dev/1Panel/backend/app/model" + "gorm.io/gorm" +) type WebsiteSSLRepo struct { } +func (w WebsiteSSLRepo) ByAlias(alias string) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("alias = ?", alias) + } +} + func (w WebsiteSSLRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebSiteSSL, error) { var sslList []model.WebSiteSSL db := getDb(opts...).Model(&model.WebSiteSSL{}) @@ -14,8 +24,17 @@ func (w WebsiteSSLRepo) Page(page, size int, opts ...DBOption) (int64, []model.W return count, sslList, err } -func (w WebsiteSSLRepo) Create(ssl model.WebSiteSSL) error { - return getDb().Create(&ssl).Error +func (w WebsiteSSLRepo) GetFirst(opts ...DBOption) (model.WebSiteSSL, error) { + var website model.WebSiteSSL + db := getDb(opts...).Model(&model.WebSiteSSL{}) + if err := db.First(&website).Error; err != nil { + return website, err + } + return website, nil +} + +func (w WebsiteSSLRepo) Create(ctx context.Context, ssl *model.WebSiteSSL) error { + return getTx(ctx).Create(ssl).Error } func (w WebsiteSSLRepo) Save(ssl model.WebSiteSSL) error { diff --git a/backend/app/service/website.go b/backend/app/service/website.go index dbec8b349..3a8c9ff1f 100644 --- a/backend/app/service/website.go +++ b/backend/app/service/website.go @@ -99,12 +99,14 @@ func (w WebsiteService) UpdateWebsite(req dto.WebSiteUpdate) error { return websiteRepo.Save(context.TODO(), &website) } -func (w WebsiteService) GetWebsite(id uint) (model.WebSite, error) { +func (w WebsiteService) GetWebsite(id uint) (dto.WebsiteDTO, error) { + var res dto.WebsiteDTO website, err := websiteRepo.GetFirst(commonRepo.WithByID(id)) if err != nil { - return website, err + return res, err } - return website, nil + res.WebSite = website + return res, nil } func (w WebsiteService) DeleteWebSite(req dto.WebSiteDel) error { diff --git a/backend/app/service/website_ssl.go b/backend/app/service/website_ssl.go index 9e6a4a745..8aac0a664 100644 --- a/backend/app/service/website_ssl.go +++ b/backend/app/service/website_ssl.go @@ -1,7 +1,13 @@ package service import ( + "context" + "crypto/x509" "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/1Panel-dev/1Panel/backend/app/model" + "github.com/1Panel-dev/1Panel/backend/utils/ssl" + "path" + "strings" ) type WebSiteSSLService struct { @@ -18,44 +24,115 @@ func (w WebSiteSSLService) Page(search dto.PageInfo) (int64, []dto.WebsiteSSLDTO return total, sslDTOs, err } -//func (w WebSiteSSLService) Create(create dto.WebsiteSSLCreate) (dto.WebsiteSSLCreate, error) { -// -// authorization, err := json.Marshal(create.Authorization) -// if err != nil { -// return dto.WebsiteSSLCreate{}, err -// } -// -// if err := websiteSSLRepo.Create(model.WebsiteDnsAccount{ -// Name: create.Name, -// Type: create.Type, -// Authorization: string(authorization), -// }); err != nil { -// return dto.WebsiteSSLCreate{}, err -// } -// -// return create, nil -//} -// -//func (w WebSiteSSLService) Update(update dto.WebsiteDnsAccountUpdate) (dto.WebsiteDnsAccountUpdate, error) { -// -// authorization, err := json.Marshal(update.Authorization) -// if err != nil { -// return dto.WebsiteDnsAccountUpdate{}, err -// } -// -// if err := websiteSSLRepo.Save(model.WebsiteDnsAccount{ -// BaseModel: model.BaseModel{ -// ID: update.ID, -// }, -// Name: update.Name, -// Type: update.Type, -// Authorization: string(authorization), -// }); err != nil { -// return dto.WebsiteDnsAccountUpdate{}, err -// } -// -// return update, nil -//} +func (w WebSiteSSLService) Create(create dto.WebsiteSSLCreate) (dto.WebsiteSSLCreate, error) { + + var res dto.WebsiteSSLCreate + acmeAccount, err := websiteAcmeRepo.GetFirst(commonRepo.WithByID(create.AcmeAccountID)) + if err != nil { + return res, err + } + dnsAccount, err := websiteDnsRepo.GetFirst(commonRepo.WithByID(create.DnsAccountID)) + if err != nil { + return res, err + } + client, err := ssl.NewPrivateKeyClient(acmeAccount.Email, acmeAccount.PrivateKey) + if err != nil { + return res, err + } + if create.Provider == dto.Http { + + } else { + if err := client.UseDns(ssl.DnsType(dnsAccount.Type), dnsAccount.Authorization); err != nil { + return res, err + } + } + + resource, err := client.GetSSL(create.Domains) + if err != nil { + return res, err + } + + var websiteSSL model.WebSiteSSL + + //TODO 判断同一个账号下的证书 + websiteSSL.Alias = create.Domains[0] + websiteSSL.Domain = strings.Join(create.Domains, ",") + websiteSSL.PrivateKey = string(resource.PrivateKey) + websiteSSL.Pem = string(resource.Certificate) + websiteSSL.CertURL = resource.CertURL + + cert, err := x509.ParseCertificate([]byte(websiteSSL.Pem)) + if err != nil { + return dto.WebsiteSSLCreate{}, err + } + websiteSSL.ExpireDate = cert.NotAfter + websiteSSL.StartDate = cert.NotBefore + websiteSSL.Type = cert.Issuer.CommonName + websiteSSL.IssuerName = cert.Issuer.Organization[0] + + if err := createPemFile(websiteSSL); err != nil { + return dto.WebsiteSSLCreate{}, err + } + + if err := websiteSSLRepo.Create(context.TODO(), &websiteSSL); err != nil { + return res, err + } + + return create, nil +} + +func (w WebSiteSSLService) Apply(apply dto.WebsiteSSLApply) (dto.WebsiteSSLApply, error) { + websiteSSL, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(apply.SSLID)) + if err != nil { + return dto.WebsiteSSLApply{}, err + } + website, err := websiteRepo.GetFirst(commonRepo.WithByID(apply.WebsiteID)) + if err != nil { + return dto.WebsiteSSLApply{}, err + } + if err := createPemFile(websiteSSL); err != nil { + return dto.WebsiteSSLApply{}, err + } + nginxParams := getNginxParamsFromStaticFile(dto.SSL) + for i, param := range nginxParams { + if param.Name == "ssl_certificate" { + nginxParams[i].Params = []string{path.Join("/etc/nginx/ssl", websiteSSL.Alias, "fullchain.pem")} + } + if param.Name == "ssl_certificate_key" { + nginxParams[i].Params = []string{path.Join("/etc/nginx/ssl", websiteSSL.Alias, "privkey.pem")} + } + } + if err := updateNginxConfig(website, nginxParams, dto.SSL); err != nil { + return dto.WebsiteSSLApply{}, err + } + website.WebSiteSSLID = websiteSSL.ID + if err := websiteRepo.Save(context.TODO(), &website); err != nil { + return dto.WebsiteSSLApply{}, err + } + + return apply, nil +} + +func (w WebSiteSSLService) GetDNSResolve(req dto.WebsiteDNSReq) (dto.WebsiteDNSRes, error) { + acmeAccount, err := websiteAcmeRepo.GetFirst(commonRepo.WithByID(req.AcmeAccountID)) + if err != nil { + return dto.WebsiteDNSRes{}, err + } + + client, err := ssl.NewPrivateKeyClient(acmeAccount.Email, acmeAccount.PrivateKey) + if err != nil { + return dto.WebsiteDNSRes{}, err + } + re, err := client.UseManualDns(req.Domains) + if err != nil { + return dto.WebsiteDNSRes{}, err + } + var res dto.WebsiteDNSRes + res.Key = re.Key + res.Value = re.Value + res.Type = "TXT" + return res, nil +} func (w WebSiteSSLService) Delete(id uint) error { return websiteSSLRepo.DeleteBy(commonRepo.WithByID(id)) diff --git a/backend/app/service/website_utils.go b/backend/app/service/website_utils.go index f01e81242..74dd5c572 100644 --- a/backend/app/service/website_utils.go +++ b/backend/app/service/website_utils.go @@ -15,6 +15,7 @@ import ( "gorm.io/gorm" "os" "path" + "reflect" "strconv" "strings" ) @@ -258,16 +259,38 @@ func updateNginxConfig(website model.WebSite, params []dto.NginxParam, scope dto } func updateConfig(config *components.Config, scope dto.NginxScope) { - if scope == dto.LimitConn { - limit := parser.NewStringParser(string(nginx_conf.Limit)).Parse() - for _, dir := range limit.GetDirectives() { - newDir := components.Directive{ - Name: dir.GetName(), - Parameters: dir.GetParameters(), - } - config.UpdateDirectiveBySecondKey(dir.GetName(), dir.GetParameters()[0], newDir) - } + newConfig := &components.Config{} + switch scope { + case dto.LimitConn: + newConfig = parser.NewStringParser(string(nginx_conf.Limit)).Parse() } + if reflect.DeepEqual(newConfig, &components.Config{}) { + return + } + + for _, dir := range newConfig.GetDirectives() { + newDir := components.Directive{ + Name: dir.GetName(), + Parameters: dir.GetParameters(), + } + config.UpdateDirectiveBySecondKey(dir.GetName(), dir.GetParameters()[0], newDir) + } +} + +func getNginxParamsFromStaticFile(scope dto.NginxScope) []dto.NginxParam { + var nginxParams []dto.NginxParam + newConfig := &components.Config{} + switch scope { + case dto.SSL: + newConfig = parser.NewStringParser(string(nginx_conf.SSL)).Parse() + } + for _, dir := range newConfig.GetDirectives() { + nginxParams = append(nginxParams, dto.NginxParam{ + Name: dir.GetName(), + Params: dir.GetParameters(), + }) + } + return nginxParams } func deleteNginxConfig(website model.WebSite, keys []string) error { @@ -285,6 +308,48 @@ func deleteNginxConfig(website model.WebSite, keys []string) error { return nginxCheckAndReload(nginxConfig.OldContent, nginxConfig.FilePath, nginxConfig.ContainerName) } +func createPemFile(websiteSSL model.WebSiteSSL) error { + nginxApp, err := appRepo.GetFirst(appRepo.WithKey("nginx")) + if err != nil { + return err + } + nginxInstall, err := appInstallRepo.GetFirst(appInstallRepo.WithAppId(nginxApp.ID)) + if err != nil { + return err + } + + configDir := path.Join(constant.AppInstallDir, "nginx", nginxInstall.Name, "ssl", websiteSSL.Alias) + fileOp := files.NewFileOp() + + if !fileOp.Stat(configDir) { + if err := fileOp.CreateDir(configDir, 0775); err != nil { + return err + } + } + + fullChainFile := path.Join(configDir, "fullchain.pem") + privatePemFile := path.Join(configDir, "privkey.pem") + + if !fileOp.Stat(fullChainFile) { + if err := fileOp.CreateFile(fullChainFile); err != nil { + return err + } + } + if !fileOp.Stat(privatePemFile) { + if err := fileOp.CreateFile(privatePemFile); err != nil { + return err + } + } + + if err := fileOp.WriteFile(fullChainFile, strings.NewReader(websiteSSL.Pem), 0644); err != nil { + return err + } + if err := fileOp.WriteFile(privatePemFile, strings.NewReader(websiteSSL.PrivateKey), 0644); err != nil { + return err + } + return nil +} + func getParamArray(key string, param interface{}) []string { var res []string switch param.(type) { diff --git a/backend/init/router/router.go b/backend/init/router/router.go index 5aa9cf101..bd4d9967a 100644 --- a/backend/init/router/router.go +++ b/backend/init/router/router.go @@ -83,6 +83,7 @@ func Routers() *gin.Engine { systemRouter.InitWebsiteGroupRouter(PrivateGroup) systemRouter.InitWebsiteDnsAccountRouter(PrivateGroup) systemRouter.InitDatabaseRouter(PrivateGroup) + systemRouter.InitWebsiteSSLRouter(PrivateGroup) systemRouter.InitWebsiteAcmeAccountRouter(PrivateGroup) } diff --git a/backend/router/entry.go b/backend/router/entry.go index e17d19831..333debe7a 100644 --- a/backend/router/entry.go +++ b/backend/router/entry.go @@ -18,6 +18,7 @@ type RouterGroup struct { WebsiteGroupRouter WebsiteDnsAccountRouter WebsiteAcmeAccountRouter + WebsiteSSLRouter DatabaseRouter } diff --git a/backend/router/ro_website_dns_account.go b/backend/router/ro_website_dns_account.go index 3130464a6..6e7673e0a 100644 --- a/backend/router/ro_website_dns_account.go +++ b/backend/router/ro_website_dns_account.go @@ -15,8 +15,8 @@ func (a *WebsiteDnsAccountRouter) InitWebsiteDnsAccountRouter(Router *gin.Router baseApi := v1.ApiGroupApp.BaseApi { - groupRouter.POST("", baseApi.PageWebsiteDnsAccount) - groupRouter.POST("/create", baseApi.CreateWebsiteDnsAccount) + groupRouter.POST("/search", baseApi.PageWebsiteDnsAccount) + groupRouter.POST("", baseApi.CreateWebsiteDnsAccount) groupRouter.POST("/update", baseApi.UpdateWebsiteDnsAccount) groupRouter.DELETE("/:id", baseApi.DeleteWebsiteDnsAccount) } diff --git a/backend/router/ro_website_ssl.go b/backend/router/ro_website_ssl.go index 610091a0c..d337ab955 100644 --- a/backend/router/ro_website_ssl.go +++ b/backend/router/ro_website_ssl.go @@ -15,7 +15,10 @@ func (a *WebsiteSSLRouter) InitWebsiteSSLRouter(Router *gin.RouterGroup) { baseApi := v1.ApiGroupApp.BaseApi { - groupRouter.POST("", baseApi.PageWebsiteSSL) - + groupRouter.POST("/search", baseApi.PageWebsiteSSL) + groupRouter.POST("", baseApi.CreateWebsiteSSL) + groupRouter.POST("/apply", baseApi.ApplyWebsiteSSL) + groupRouter.POST("/resolve", baseApi.GetDNSResolve) + groupRouter.POST("/:id", baseApi.DeleteWebsiteSSL) } } diff --git a/backend/utils/nginx/private.key b/backend/utils/nginx/private.key index 5205a689a..0827a95e3 100644 --- a/backend/utils/nginx/private.key +++ b/backend/utils/nginx/private.key @@ -1,27 +1,27 @@ -----BEGIN privateKey----- -MIIEpAIBAAKCAQEAuDkgWX1wvKWnJyQnzW4lXcmJyvHryfpBR4/Ri5/wmS20lwhD -9vA98SLVpmp8EZs0GQiLZrYady9dB+QAPoW0R3kh2E9gpwv+G1czGxU2gu4jR292 -1Tq7mHi+ix+6LkvHktQxoPMUhCr8uqCySrUXez/mq3Ver6YcuqfCPCmxQ9Ox57No -uLzYg904kOEwGlnET0ObzbdqqasY951cAw4FdXll8zYgyWieJQG2Hm+LLl7izH/C -LOGkcyFfnUtld4CN3NiIxx+BEsIOFJtzcXe8bu9/PKU10pJm2O6FL5oaLuF4iPUM -3MkU0YucGkc1OQI58Q2AV7GKEe6cq7Lv4fVjIwIDAQABAoIBAAgdhB5NF1VIGtfv -kM1M59p80VQeWhu4qX2EiV+UOR4WVFk+5PeQ17mypiTBlhuUcbQUm0d4CCxt6rQ5 -SAV5EFsBrAsCXCifr572tWqhAZi88tLnxx1XjAIId6RbTnFRp0YBkPodGy9DUYTL -JW+DELi+NOQitHwaXjOexkCuOX/aVU4J+8Z7yvR/eicZVE+BhUQCeKolPuh657BN -aJJEBUDitRArQy5dUbFP1cWvIZG8xMWfvsgyoxhNniBAoRdALY8kRW52qOznacIP -9Ln2sH/wjgmyLX2B22W2KZjq2BfcCEbDEOoPJEcYx9aq7j1EeBrsavRMm62W27gj -neYWUVkCgYEAx+dmknMUIE4tUcpC0Wb8HFJ2Y1MVIrlXwRi+6S1N773AEsKRFVFP -XiwnFDQQynTPudxWi/1w9dm4AePn/k/53ac07Vtw0yp1YYRtOunN2K9tp5iV3n09 -1b1qRj9DxKYjh1yZIfzsgKBaibbfjmT16kWPaUqn0Vk6ES9Xb+VoAk8CgYEA6+tC -TSREetJsXQPZY9u6KatsPaf0Nd+r6bhJk8L83mTRXCgxHh+lnWC7gCxwuyZrjoXi -ahB0vCvs61jYPnbGujOGphW/76cu6OhY5DVtMisQKkMe7l87IEO5gtsEOL3YnucT -JTXerVqvhpltbHazbW3oJ3sIIKESEWH924YQwO0CgYEAhKxnvzrxWJ+KJIaA4knf -eUyhljpGBM3OGDI8QrX2y+6707eeYu+cJXxYU2ha3IO6ejhqmG6U0ha1sUt5Zafe -xeV7kyzlLME5NoeVl0wlenKz7E+w6AFnULxuFEFY0OMTIXurhos+y/+hF1Vv+im3 -rMyN6evKhX8ast0gwvsWlLECgYAiRgZW0KsGMOW/SZzebgCIpzfNaUYIQZtnE/fU -eKJl6L2lps0j9DMKPxBeWZZzCezcQsUW5Zcf8z2zHzAjOvw59txb6pL8zQv6mC65 -0K0xeaIaka+/r6QWVuBvi0P8vk/nHejhIgdcpe0UH9wOwtvkTPBKNAyFOQE390V7 -C+oJLQKBgQDB/y9SdYJCiih7KsQa0o87csOwHalQS7R06ipxGQiS0YU+aBLUeZQb -3cKI84b1ECmUnCikjSv/HRhRp5GX1NNEEAVb8H2ZQr9UCeGsVvrVbRfwOdM2+oui -K9v94Xq6NKqreUtBDTXLaCvFhuSsFJoifDudLMbHm5h6+WcqU43iiA== +MIIEpAIBAAKCAQEAk+3c1uehn2/YRZI/GVUb0mM51OxGTyiGtaVp9rCCMx9ajvgN +eVkF+yBqd7C3B2doKUe4Nprl0j1w3mM1Ol0FisqBjOm7DNq212//CtyjCYrbmCDE +DNXDI+3k7SImPGxDoCRQl/4rcRSZGAJe/BdW3U70UZU5203B8AWf5c8basWaB4gU +3rGK08f6qqQRGkoEL5W+b5LHxJO1xNrFDdRh0Qi3hzl8c5fIcqCgSQikyGoCSSLh +deoiCxl2ASuJ9xgbr7MLP5oN68T5AXduhbo87bsuweKxwe2D0XlM1PbfUGismqjR +zD/rPa0QRnRis5e0qxyyi5I8lmODa0kn6tAFsQIDAQABAoIBABCj1Q+nhpq0rhNF +XCuxUyvbVYoJ+e61lFGihcTmHf86K6mhZYKc7PtOritAiZYfn6vlEWezDN8VYjjh +1/70r8bo+KGtOQk9IQwi4QGLyBsur3zxUpxO/2BvRi0Whk6Nrx24eAhg4uoZcw8s +VRruVSsX0ovKyXNNz978AvyKy367B8x3ZWQdjS35qerP23FCPlrEsdQpm531M6L7 +lihX4oV9EVyRxmZOohR2nPy5TBxE9oQOSGNzsExO6zaOw6uq0MvOhu3ih1foQVA0 +IilCvJMwzaeufF2u0LKcHmo7OHPNqDwOYWmgf//q3FBkOi05FVI/rO084ENJcJFz +5TDtSHECgYEAw9QqMr9p9su4VTFAG3+2+9FnL8Bo6xgUtkeKGJr4PapnVRn28SIS +Qv6SupcB/CBNuUX9NV1GbBjL9XJreTWW3FVWiwsBmIoJ+Z1RUn5/WD66jIVHfbzl +cpw/yECeoKOJL0QRqPneNBfAYbsw6+PFpybKSCZ3f7adAGhOI3aPk+MCgYEAwWHt +cgss3RyFKbG5Tpo87Pf7R/PNOIEhLrXgQr7E+9Iw6YYDy6DMw/3SrH1w5DEr4nc+ +Pim41TMytdhwiPZXSEppL1lavhU1VGJuiQT59h5bB+XJUVKRf91otat8ILS3n3L5 +l2Ob3BUEkLSjW4lIcxQxG3c8/rV9UH0zLoNx/FsCgYEAwIGL7hE/MK55ib39oEq/ +bfMfddC3Ewy8J6hR9/g3uh8Or5jzqX3t58/sG+Mgv2JeJZjI3rHP7am+ro2JW0E0 +CWsWxV7Pdc2VGr3s2KSjuPMJXeQTMGcGQ9GX3dqwVYgN7toCZlMjfaAvraNf5zQk +9DlsttqhtHmnA2SGE9SUNjMCgYBxZ91YkOcpcA1Diz7xso/iI/cPlhEWftuXyf8P +BVL9nqEigX3+T3llwpdmolWu7Isgzu8Ig20qUlD9xUURfO1oroKKyurlKAjTSLor +zmhMBjc6JW5vK226P3yldUBg6bn5XvKx7i873HOF7PkTuCltmzzFL6LseEBaEGIQ +d/NDmwKBgQCKWe1wwyeiqhmJXV/AGMNNECeLdH8G9kBKt6OAPMISxDHQOkP3GDkG +HwqE00jCweDdDUHEbZ0gW299KCe8u6kBD5UWKiuUARBpiudlOvoKazCnhNKrCDfz +nAQrIod4nus0mRhVTBwxzmBPsdQ/rgmtGUd+RutxPgJJzjCVMAsFeQ== -----END privateKey----- diff --git a/backend/utils/ssl/client.go b/backend/utils/ssl/client.go index 7ac3e5fd0..7822ce37e 100644 --- a/backend/utils/ssl/client.go +++ b/backend/utils/ssl/client.go @@ -7,6 +7,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/lego" + "github.com/go-acme/lego/v4/providers/dns/alidns" "github.com/go-acme/lego/v4/providers/dns/dnspod" "github.com/go-acme/lego/v4/registration" "github.com/pkg/errors" @@ -58,7 +59,8 @@ func NewAcmeClient(email, privateKey string) (*AcmeClient, error) { type DnsType string const ( - DnsPod DnsType = "dnsPod" + DnsPod DnsType = "DnsPod" + AliYun DnsType = "AliYun" ) type DNSParam struct { @@ -87,9 +89,37 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string) error { return err } } + if dnsType == AliYun { + alidnsConfig := alidns.NewDefaultConfig() + alidnsConfig.SecretKey = param.SecretKey + alidnsConfig.APIKey = param.AccessKey + p, err = alidns.NewDNSProviderConfig(alidnsConfig) + if err != nil { + return err + } + } return c.Client.Challenge.SetDNS01Provider(p, dns01.AddDNSTimeout(6*time.Minute)) } + +func (c *AcmeClient) UseManualDns(domains []string) (*Resolve, error) { + p := &manualDnsProvider{} + if err := c.Client.Challenge.SetDNS01Provider(p, dns01.AddDNSTimeout(6*time.Minute)); err != nil { + return nil, nil + } + + request := certificate.ObtainRequest{ + Domains: domains, + Bundle: true, + } + + _, err := c.Client.Certificate.Obtain(request) + if err != nil { + return nil, err + } + return p.Resolve, nil +} + func (c *AcmeClient) UseHTTP() { } @@ -108,3 +138,29 @@ func (c *AcmeClient) GetSSL(domains []string) (certificate.Resource, error) { return *certificates, nil } + +type Resolve struct { + Key string + Value string +} + +type manualDnsProvider struct { + Resolve *Resolve +} + +func (p *manualDnsProvider) Present(domain, token, keyAuth string) error { + fqdn, value := dns01.GetRecord(domain, keyAuth) + p.Resolve = &Resolve{ + Key: fqdn, + Value: value, + } + return nil +} + +func (p *manualDnsProvider) CleanUp(domain, token, keyAuth string) error { + return nil +} + +func (c *AcmeClient) GetDNSResolve() { + +} diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index 4b0462257..0808a2472 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -14,6 +14,19 @@ export namespace WebSite { appinstall?: NewAppInstall; } + export interface WebSiteDTO extends CommonModel { + primaryDomain: string; + type: string; + alias: string; + remark: string; + domains: WebSite.Domain[]; + appType: string; + appInstallId?: number; + webSiteGroupId: number; + otherDomains: string; + appinstall?: NewAppInstall; + } + export interface NewAppInstall { name: string; appDetailId: number; @@ -115,6 +128,19 @@ export namespace WebSite { startDate: string; } + export interface SSLCreate { + domains: string[]; + provider: string; + websiteId: number; + acmeAccountId: number; + dnsAccountId: number; + } + + export interface SSLApply { + websiteId: number; + SSLId: number; + } + export interface AcmeAccount extends CommonModel { email: string; url: string; @@ -123,4 +149,15 @@ export namespace WebSite { export interface AcmeAccountCreate { email: string; } + + export interface DNSResolveReq { + domains: string[]; + acmeAccountId: number; + } + + export interface DNSResolve { + key: string; + value: string; + type: string; + } } diff --git a/frontend/src/api/modules/website.ts b/frontend/src/api/modules/website.ts index e9f8d90a6..0a77e2904 100644 --- a/frontend/src/api/modules/website.ts +++ b/frontend/src/api/modules/website.ts @@ -15,7 +15,7 @@ export const UpdateWebsite = (req: WebSite.WebSiteUpdateReq) => { }; export const GetWebsite = (id: number) => { - return http.get(`/websites/${id}`); + return http.get(`/websites/${id}`); }; export const DeleteWebsite = (req: WebSite.WebSiteDel) => { @@ -59,11 +59,11 @@ export const UpdateNginxConfig = (req: WebSite.NginxConfigReq) => { }; export const SearchDnsAccount = (req: ReqPage) => { - return http.post>(`/websites/dns`, req); + return http.post>(`/websites/dns/search`, req); }; export const CreateDnsAccount = (req: WebSite.DnsAccountCreate) => { - return http.post(`/websites/dns/create`, req); + return http.post(`/websites/dns`, req); }; export const UpdateDnsAccount = (req: WebSite.DnsAccountUpdate) => { @@ -74,10 +74,6 @@ export const DeleteDnsAccount = (id: number) => { return http.delete(`/websites/dns/${id}`); }; -export const SearchSSL = (req: ReqPage) => { - return http.post>(`/websites/ssl`, req); -}; - export const SearchAcmeAccount = (req: ReqPage) => { return http.post>(`/websites/acme/search`, req); }; @@ -89,3 +85,23 @@ export const CreateAcmeAccount = (req: WebSite.AcmeAccountCreate) => { export const DeleteAcmeAccount = (id: number) => { return http.delete(`/websites/acme/${id}`); }; + +export const SearchSSL = (req: ReqPage) => { + return http.post>(`/websites/ssl/search`, req); +}; + +export const CreateSSL = (req: WebSite.SSLCreate) => { + return http.post(`/websites/ssl`, req); +}; + +export const DeleteSSL = (id: number) => { + return http.delete(`/websites/ssl/${id}`); +}; + +export const ApplySSL = (req: WebSite.SSLApply) => { + return http.post(`/websites/ssl/apply`, req); +}; + +export const GetDnsResolve = (req: WebSite.DNSResolveReq) => { + return http.post(`/websites/ssl/resolve`, req); +}; diff --git a/frontend/src/hooks/use-delete-data.ts b/frontend/src/hooks/use-delete-data.ts index f355ba2e5..f3fddb3a5 100644 --- a/frontend/src/hooks/use-delete-data.ts +++ b/frontend/src/hooks/use-delete-data.ts @@ -31,7 +31,7 @@ export const useDeleteData =

( if (!res) return reject(false); ElMessage({ type: 'success', - message: i18n.global.t('commons.msg.deleteSuccess'), + message: i18n.global.t('commons.msg.operationSuccess'), }); resolve(true); }) diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 027ac307c..e86a5f364 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -692,7 +692,7 @@ export default { perip: '单IP限制', peripHelper: '限制单个IP访问最大并发数', rate: '流量限制', - rateHelper: '限制每个请求的流量上限(单位:KB)', + rateHelper: '限制每个请求的流量上(单位:KB)', limtHelper: '启用流量控制', other: '其他', currentSSL: '当前证书', @@ -707,5 +707,12 @@ export default { accountManage: '账户管理', email: '邮箱', addAccount: '新增账户', + acmeAccount: 'Acme 账户', + provider: '验证方式', + dnsCommon: '手动解析', + expireDate: '到期时间', + brand: '品牌', + deploySSL: '部署', + deploySSLHelper: '确定部署证书?', }, }; diff --git a/frontend/src/views/website/project/config/index.vue b/frontend/src/views/website/project/config/index.vue index 0baa60621..e78b76fab 100644 --- a/frontend/src/views/website/project/config/index.vue +++ b/frontend/src/views/website/project/config/index.vue @@ -16,15 +16,15 @@ diff --git a/frontend/src/views/website/project/config/ssl/ssl/index.vue b/frontend/src/views/website/project/config/ssl/ssl/index.vue index 933f2d896..482c11c01 100644 --- a/frontend/src/views/website/project/config/ssl/ssl/index.vue +++ b/frontend/src/views/website/project/config/ssl/ssl/index.vue @@ -2,20 +2,51 @@

- + + + + +