diff --git a/agent/app/api/v2/website_ca.go b/agent/app/api/v2/website_ca.go index 183517ba9..6206cd4f5 100644 --- a/agent/app/api/v2/website_ca.go +++ b/agent/app/api/v2/website_ca.go @@ -1,13 +1,16 @@ package v2 import ( + "io" "net/http" "net/url" "strconv" + "strings" "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/gin-gonic/gin" ) @@ -176,3 +179,64 @@ func (b *BaseApi) DownloadCAFile(c *gin.Context) { c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(info.Name())) http.ServeContent(c.Writer, c.Request, info.Name(), info.ModTime(), file) } + +// @Tags Website CA +// @Summary Upload website CA archive +// @Accept multipart/form-data +// @Param file formData file true "ZIP containing ca.crt and ca.key" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/ca/upload [post] +func (b *BaseApi) UploadWebsiteCA(c *gin.Context) { + c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 11<<20) + if err := c.Request.ParseMultipartForm(11 << 20); err != nil { + helper.InternalServer(c, buserr.New("ErrCAUpload")) + return + } + if c.Request.MultipartForm != nil { + defer c.Request.MultipartForm.RemoveAll() + } + file, err := c.FormFile("file") + if err != nil || file.Size > 10<<20 || !strings.HasSuffix(strings.ToLower(file.Filename), ".zip") { + helper.InternalServer(c, buserr.New("ErrCAUpload")) + return + } + stream, err := file.Open() + if err != nil { + helper.InternalServer(c, err) + return + } + defer stream.Close() + data, err := io.ReadAll(io.LimitReader(stream, (10<<20)+1)) + if err != nil { + helper.InternalServer(c, err) + return + } + if err = websiteCAService.Upload(file.Filename[:len(file.Filename)-len(".zip")], data); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website CA +// @Summary Import website CA from PEM or server files +// @Accept json +// @Param request body request.WebsiteCAImport true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/ca/import [post] +func (b *BaseApi) ImportWebsiteCA(c *gin.Context) { + c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 3<<20) + var req request.WebsiteCAImport + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteCAService.Import(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/dto/request/website_ssl.go b/agent/app/dto/request/website_ssl.go index ee0b64d03..6bf206a17 100644 --- a/agent/app/dto/request/website_ssl.go +++ b/agent/app/dto/request/website_ssl.go @@ -182,3 +182,12 @@ type WebsiteSSLFileUpload struct { PushNode bool `json:"pushNode"` Nodes string `json:"nodes"` } + +type WebsiteCAImport struct { + Name string `json:"name" validate:"required,max=128"` + Type string `json:"type" validate:"required,oneof=paste local"` + Certificate string `json:"certificate" validate:"required_if=Type paste"` + PrivateKey string `json:"privateKey" validate:"required_if=Type paste"` + CertificatePath string `json:"certificatePath" validate:"required_if=Type local"` + PrivateKeyPath string `json:"privateKeyPath" validate:"required_if=Type local"` +} diff --git a/agent/app/service/website_ca.go b/agent/app/service/website_ca.go index c7c7391a9..5a5901643 100644 --- a/agent/app/service/website_ca.go +++ b/agent/app/service/website_ca.go @@ -1,15 +1,19 @@ package service import ( + "archive/zip" "bytes" "context" "crypto/ecdsa" "crypto/rand" "crypto/rsa" + "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/pem" + "errors" "fmt" + "io" "log" "math/big" "net" @@ -18,25 +22,27 @@ import ( "strings" "time" - "github.com/1Panel-dev/1Panel/agent/app/repo" - "github.com/1Panel-dev/1Panel/agent/utils/common" - "github.com/1Panel-dev/1Panel/agent/app/dto/request" "github.com/1Panel-dev/1Panel/agent/app/dto/response" "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/common" "github.com/1Panel-dev/1Panel/agent/utils/files" "github.com/1Panel-dev/1Panel/agent/utils/ssl" "github.com/go-acme/lego/v5/certcrypto" + "gorm.io/gorm" ) type WebsiteCAService struct { } type IWebsiteCAService interface { + Import(req request.WebsiteCAImport) error + Upload(name string, archive []byte) error Page(search request.WebsiteCASearch) (int64, []response.WebsiteCADTO, error) Create(create request.WebsiteCACreate) (*request.WebsiteCACreate, error) GetCA(id uint) (*response.WebsiteCADTO, error) @@ -334,7 +340,7 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website websiteSSL.ExpireDate = cert.NotAfter websiteSSL.StartDate = cert.NotBefore websiteSSL.Type = cert.Issuer.CommonName - websiteSSL.Organization = rootCsr.Subject.Organization[0] + websiteSSL.Organization = strings.Join(rootCsr.Subject.Organization, ",") websiteSSL.Status = constant.SSLReady if req.Renew { @@ -442,3 +448,150 @@ func (w WebsiteCAService) DownloadFile(id uint) (*os.File, error) { } return os.Open(path.Join(dir, fileName)) } + +func (w WebsiteCAService) Upload(name string, archive []byte) error { + ca, err := parseCAArchive(archive) + if err != nil { + return err + } + return saveImportedCA(name, ca) +} + +func saveImportedCA(name string, ca *model.WebsiteCA) error { + name = strings.TrimSpace(name) + if name == "" || len(name) > 128 || name == "." || name == ".." || strings.ContainsAny(name, "/\\\x00") { + return buserr.WithDetail("ErrInvalidParams", "name", nil) + } + exist, err := websiteCARepo.GetFirst(repo.WithByName(name)) + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return err + } + if exist.ID > 0 { + return buserr.New("ErrNameIsExist") + } + ca.Name = name + return websiteCARepo.Create(context.Background(), ca) +} + +// Read only the certificate and key in memory; never extract user-supplied paths. +func parseCAArchive(archive []byte) (*model.WebsiteCA, error) { + invalid := func() (*model.WebsiteCA, error) { return nil, buserr.New("ErrCAUpload") } + if len(archive) > 10<<20 { + return invalid() + } + reader, err := zip.NewReader(bytes.NewReader(archive), int64(len(archive))) + if err != nil || len(reader.File) > 16 { + return invalid() + } + contents := make(map[string][]byte) + for _, file := range reader.File { + if file.FileInfo().IsDir() { + continue + } + name := path.Base(file.Name) + if (name != "ca.crt" && name != "ca.key") || contents[name] != nil || file.UncompressedSize64 > 1<<20 { + return invalid() + } + stream, err := file.Open() + if err != nil { + return invalid() + } + data, err := io.ReadAll(io.LimitReader(stream, (1<<20)+1)) + stream.Close() + if err != nil || len(data) > 1<<20 { + return invalid() + } + contents[name] = data + } + return parseAndValidateRootCA(contents["ca.crt"], contents["ca.key"]) +} + +func parseAndValidateRootCA(certificate, privateKey []byte) (*model.WebsiteCA, error) { + invalid := func() (*model.WebsiteCA, error) { return nil, buserr.New("ErrCAImport") } + if len(certificate) > 1<<20 || len(privateKey) > 1<<20 { + return invalid() + } + pair, err := tls.X509KeyPair(certificate, privateKey) + if err != nil || len(pair.Certificate) != 1 { + return invalid() + } + cert := pair.Leaf + now := time.Now() + if cert == nil || !cert.IsCA || !cert.BasicConstraintsValid || cert.KeyUsage&x509.KeyUsageCertSign == 0 || + !bytes.Equal(cert.RawIssuer, cert.RawSubject) || cert.CheckSignatureFrom(cert) != nil || + now.Before(cert.NotBefore) || !now.Before(cert.NotAfter) { + return invalid() + } + ca := &model.WebsiteCA{CSR: string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}))} + // Normalize keys to the formats used by ObtainSSL, including PKCS#8 uploads. + switch key := pair.PrivateKey.(type) { + case *ecdsa.PrivateKey: + bits := key.Curve.Params().BitSize + if bits != 256 && bits != 384 { + return invalid() + } + der, err := x509.MarshalECPrivateKey(key) + if err != nil { + return invalid() + } + ca.KeyType = fmt.Sprintf("EC%d", bits) + ca.PrivateKey = string(pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: der})) + case *rsa.PrivateKey: + bits := key.N.BitLen() + if bits != 2048 && bits != 3072 && bits != 4096 && bits != 8192 { + return invalid() + } + ca.KeyType = fmt.Sprintf("RSA%d", bits) + ca.PrivateKey = string(pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})) + default: + return invalid() + } + return ca, nil +} + +func (w WebsiteCAService) Import(req request.WebsiteCAImport) error { + certificate, privateKey := []byte(req.Certificate), []byte(req.PrivateKey) + switch req.Type { + case "local": + var err error + certificate, err = readCAImportFile(req.CertificatePath) + if err != nil { + return err + } + privateKey, err = readCAImportFile(req.PrivateKeyPath) + if err != nil { + return err + } + case "paste": + default: + return buserr.New("ErrCAImport") + } + ca, err := parseAndValidateRootCA(certificate, privateKey) + if err != nil { + return err + } + return saveImportedCA(req.Name, ca) +} + +func readCAImportFile(name string) ([]byte, error) { + info, err := os.Stat(name) + if err != nil { + return nil, err + } + if !info.Mode().IsRegular() || info.Size() > 1<<20 { + return nil, buserr.New("ErrCAImport") + } + file, err := os.Open(name) + if err != nil { + return nil, err + } + defer file.Close() + content, err := io.ReadAll(io.LimitReader(file, (1<<20)+1)) + if err != nil { + return nil, err + } + if len(content) > 1<<20 { + return nil, buserr.New("ErrCAImport") + } + return content, nil +} diff --git a/agent/i18n/lang/en.yaml b/agent/i18n/lang/en.yaml index 301f0bf1e..a3a47cfad 100644 --- a/agent/i18n/lang/en.yaml +++ b/agent/i18n/lang/en.yaml @@ -773,3 +773,7 @@ VMSnapshotRecoverTask: "Recover snapshot [{{ .snapshot }}] for virtual machine [ VMSnapshotRecoverStep: "Recover virtual machine snapshot [{{ .snapshot }}]" VMSnapshotDeleteTask: "Delete snapshot [{{ .snapshot }}] for virtual machine [{{ .vm }}]" VMSnapshotDeleteStep: "Delete virtual machine snapshot [{{ .snapshot }}]" + +ErrCAUpload: "Upload a ZIP (up to 10 MB) containing a valid self-signed CA certificate ca.crt and its matching private key ca.key, with a supported RSA or EC algorithm." + +ErrCAImport: "Provide a valid self-signed root CA certificate and matching private key (up to 1 MB each), using a supported RSA or EC algorithm." diff --git a/agent/i18n/lang/es-ES.yaml b/agent/i18n/lang/es-ES.yaml index bf35e7ea9..e9ddb5505 100644 --- a/agent/i18n/lang/es-ES.yaml +++ b/agent/i18n/lang/es-ES.yaml @@ -773,3 +773,7 @@ VMSnapshotRecoverTask: "Restaurar instantánea [{{ .snapshot }}] para la máquin VMSnapshotRecoverStep: "Restaurar instantánea de máquina virtual [{{ .snapshot }}]" VMSnapshotDeleteTask: "Eliminar instantánea [{{ .snapshot }}] de la máquina virtual [{{ .vm }}]" VMSnapshotDeleteStep: "Eliminar instantánea de máquina virtual [{{ .snapshot }}]" + +ErrCAUpload: "Upload a ZIP (up to 10 MB) containing a valid self-signed CA certificate ca.crt and its matching private key ca.key, with a supported RSA or EC algorithm." + +ErrCAImport: "Provide a valid self-signed root CA certificate and matching private key (up to 1 MB each), using a supported RSA or EC algorithm." diff --git a/agent/i18n/lang/fa.yaml b/agent/i18n/lang/fa.yaml index 28a77cb46..2fa82e5c8 100644 --- a/agent/i18n/lang/fa.yaml +++ b/agent/i18n/lang/fa.yaml @@ -774,3 +774,7 @@ VMSnapshotRecoverTask: "بازیابی اسنپ‌شات [{{ .snapshot }}] بر VMSnapshotRecoverStep: "بازیابی اسنپ‌شات ماشین مجازی [{{ .snapshot }}]" VMSnapshotDeleteTask: "حذف اسنپ‌شات [{{ .snapshot }}] برای ماشین مجازی [{{ .vm }}]" VMSnapshotDeleteStep: "حذف اسنپ‌شات ماشین مجازی [{{ .snapshot }}]" + +ErrCAUpload: "Upload a ZIP (up to 10 MB) containing a valid self-signed CA certificate ca.crt and its matching private key ca.key, with a supported RSA or EC algorithm." + +ErrCAImport: "Provide a valid self-signed root CA certificate and matching private key (up to 1 MB each), using a supported RSA or EC algorithm." diff --git a/agent/i18n/lang/ja.yaml b/agent/i18n/lang/ja.yaml index 307018490..fd0b6dfea 100644 --- a/agent/i18n/lang/ja.yaml +++ b/agent/i18n/lang/ja.yaml @@ -773,3 +773,7 @@ VMSnapshotRecoverTask: "仮想マシン [{{ .vm }}] のスナップショット VMSnapshotRecoverStep: "仮想マシンのスナップショット [{{ .snapshot }}] を復元" VMSnapshotDeleteTask: "仮想マシン [{{ .vm }}] のスナップショット [{{ .snapshot }}] を削除" VMSnapshotDeleteStep: "仮想マシンのスナップショット [{{ .snapshot }}] を削除" + +ErrCAUpload: "Upload a ZIP (up to 10 MB) containing a valid self-signed CA certificate ca.crt and its matching private key ca.key, with a supported RSA or EC algorithm." + +ErrCAImport: "Provide a valid self-signed root CA certificate and matching private key (up to 1 MB each), using a supported RSA or EC algorithm." diff --git a/agent/i18n/lang/ko.yaml b/agent/i18n/lang/ko.yaml index a914c840c..5c171955f 100644 --- a/agent/i18n/lang/ko.yaml +++ b/agent/i18n/lang/ko.yaml @@ -773,3 +773,7 @@ VMSnapshotRecoverTask: "가상 머신 [{{ .vm }}] 스냅샷 [{{ .snapshot }}] VMSnapshotRecoverStep: "가상 머신 스냅샷 [{{ .snapshot }}] 복구" VMSnapshotDeleteTask: "가상 머신 [{{ .vm }}] 스냅샷 [{{ .snapshot }}] 삭제" VMSnapshotDeleteStep: "가상 머신 스냅샷 [{{ .snapshot }}] 삭제" + +ErrCAUpload: "Upload a ZIP (up to 10 MB) containing a valid self-signed CA certificate ca.crt and its matching private key ca.key, with a supported RSA or EC algorithm." + +ErrCAImport: "Provide a valid self-signed root CA certificate and matching private key (up to 1 MB each), using a supported RSA or EC algorithm." diff --git a/agent/i18n/lang/lo.yaml b/agent/i18n/lang/lo.yaml index ee4001e61..b2c23b6dd 100644 --- a/agent/i18n/lang/lo.yaml +++ b/agent/i18n/lang/lo.yaml @@ -764,3 +764,7 @@ VMSnapshotRecoverTask: "ກູ້ຄືນສະແນັບຊັອດ [{{ .sn VMSnapshotRecoverStep: "ກູ້ຄືນສະແນັບຊັອດເຄື່ອງສະເໝືອນ [{{ .snapshot }}]" VMSnapshotDeleteTask: "ລຶບສະແນັບຊັອດ [{{ .snapshot }}] ຂອງເຄື່ອງສະເໝືອນ [{{ .vm }}]" VMSnapshotDeleteStep: "ລຶບສະແນັບຊັອດເຄື່ອງສະເໝືອນ [{{ .snapshot }}]" + +ErrCAUpload: "Upload a ZIP (up to 10 MB) containing a valid self-signed CA certificate ca.crt and its matching private key ca.key, with a supported RSA or EC algorithm." + +ErrCAImport: "Provide a valid self-signed root CA certificate and matching private key (up to 1 MB each), using a supported RSA or EC algorithm." diff --git a/agent/i18n/lang/ms.yaml b/agent/i18n/lang/ms.yaml index 201a5872a..54410f746 100644 --- a/agent/i18n/lang/ms.yaml +++ b/agent/i18n/lang/ms.yaml @@ -773,3 +773,7 @@ VMSnapshotRecoverTask: "Pulihkan syot kilat [{{ .snapshot }}] untuk mesin maya [ VMSnapshotRecoverStep: "Pulihkan syot kilat mesin maya [{{ .snapshot }}]" VMSnapshotDeleteTask: "Padam syot kilat [{{ .snapshot }}] untuk mesin maya [{{ .vm }}]" VMSnapshotDeleteStep: "Padam syot kilat mesin maya [{{ .snapshot }}]" + +ErrCAUpload: "Upload a ZIP (up to 10 MB) containing a valid self-signed CA certificate ca.crt and its matching private key ca.key, with a supported RSA or EC algorithm." + +ErrCAImport: "Provide a valid self-signed root CA certificate and matching private key (up to 1 MB each), using a supported RSA or EC algorithm." diff --git a/agent/i18n/lang/pt-BR.yaml b/agent/i18n/lang/pt-BR.yaml index 119baa113..52127cf9d 100644 --- a/agent/i18n/lang/pt-BR.yaml +++ b/agent/i18n/lang/pt-BR.yaml @@ -773,3 +773,7 @@ VMSnapshotRecoverTask: "Restaurar snapshot [{{ .snapshot }}] da máquina virtual VMSnapshotRecoverStep: "Restaurar snapshot da máquina virtual [{{ .snapshot }}]" VMSnapshotDeleteTask: "Excluir snapshot [{{ .snapshot }}] da máquina virtual [{{ .vm }}]" VMSnapshotDeleteStep: "Excluir snapshot da máquina virtual [{{ .snapshot }}]" + +ErrCAUpload: "Upload a ZIP (up to 10 MB) containing a valid self-signed CA certificate ca.crt and its matching private key ca.key, with a supported RSA or EC algorithm." + +ErrCAImport: "Provide a valid self-signed root CA certificate and matching private key (up to 1 MB each), using a supported RSA or EC algorithm." diff --git a/agent/i18n/lang/ru.yaml b/agent/i18n/lang/ru.yaml index e55e3e92d..424b100c6 100644 --- a/agent/i18n/lang/ru.yaml +++ b/agent/i18n/lang/ru.yaml @@ -773,3 +773,7 @@ VMSnapshotRecoverTask: "Восстановить снимок [{{ .snapshot }}] VMSnapshotRecoverStep: "Восстановить снимок виртуальной машины [{{ .snapshot }}]" VMSnapshotDeleteTask: "Удалить снимок [{{ .snapshot }}] виртуальной машины [{{ .vm }}]" VMSnapshotDeleteStep: "Удалить снимок виртуальной машины [{{ .snapshot }}]" + +ErrCAUpload: "Upload a ZIP (up to 10 MB) containing a valid self-signed CA certificate ca.crt and its matching private key ca.key, with a supported RSA or EC algorithm." + +ErrCAImport: "Provide a valid self-signed root CA certificate and matching private key (up to 1 MB each), using a supported RSA or EC algorithm." diff --git a/agent/i18n/lang/tr.yaml b/agent/i18n/lang/tr.yaml index 78a60751c..73dbcc12e 100644 --- a/agent/i18n/lang/tr.yaml +++ b/agent/i18n/lang/tr.yaml @@ -773,3 +773,7 @@ VMSnapshotRecoverTask: "Sanal makine [{{ .vm }}] anlık görüntüsü [{{ .snaps VMSnapshotRecoverStep: "Sanal makine anlık görüntüsü [{{ .snapshot }}] geri yükle" VMSnapshotDeleteTask: "Sanal makine [{{ .vm }}] anlık görüntüsü [{{ .snapshot }}] sil" VMSnapshotDeleteStep: "Sanal makine anlık görüntüsü [{{ .snapshot }}] sil" + +ErrCAUpload: "Upload a ZIP (up to 10 MB) containing a valid self-signed CA certificate ca.crt and its matching private key ca.key, with a supported RSA or EC algorithm." + +ErrCAImport: "Provide a valid self-signed root CA certificate and matching private key (up to 1 MB each), using a supported RSA or EC algorithm." diff --git a/agent/i18n/lang/zh-Hant.yaml b/agent/i18n/lang/zh-Hant.yaml index 796963bdb..9746d9909 100644 --- a/agent/i18n/lang/zh-Hant.yaml +++ b/agent/i18n/lang/zh-Hant.yaml @@ -773,3 +773,7 @@ VMSnapshotRecoverTask: "復原虛擬機 [{{ .vm }}] 快照 [{{ .snapshot }}]" VMSnapshotRecoverStep: "復原虛擬機快照 [{{ .snapshot }}]" VMSnapshotDeleteTask: "刪除虛擬機 [{{ .vm }}] 快照 [{{ .snapshot }}]" VMSnapshotDeleteStep: "刪除虛擬機快照 [{{ .snapshot }}]" + +ErrCAUpload: "請上傳包含有效自簽根憑證 ca.crt 及匹配私鑰 ca.key 的 ZIP 檔案(最大 10 MB),金鑰演算法須為支援的 RSA 或 EC。" + +ErrCAImport: "請提供有效的自簽根憑證及匹配私鑰(每個最大 1 MB),金鑰演算法須為支援的 RSA 或 EC。" diff --git a/agent/i18n/lang/zh.yaml b/agent/i18n/lang/zh.yaml index 13e830f7d..3f1a457a8 100644 --- a/agent/i18n/lang/zh.yaml +++ b/agent/i18n/lang/zh.yaml @@ -773,3 +773,7 @@ VMSnapshotRecoverTask: "恢复虚拟机 [{{ .vm }}] 快照 [{{ .snapshot }}]" VMSnapshotRecoverStep: "恢复虚拟机快照 [{{ .snapshot }}]" VMSnapshotDeleteTask: "删除虚拟机 [{{ .vm }}] 快照 [{{ .snapshot }}]" VMSnapshotDeleteStep: "删除虚拟机快照 [{{ .snapshot }}]" + +ErrCAUpload: "请上传包含有效自签根证书 ca.crt 及匹配私钥 ca.key 的 ZIP 文件(最大 10 MB),密钥算法须为支持的 RSA 或 EC。" + +ErrCAImport: "请提供有效的自签根证书及匹配私钥(每个最大 1 MB),密钥算法须为支持的 RSA 或 EC。" diff --git a/agent/router/ro_website_ca.go b/agent/router/ro_website_ca.go index e86585a3f..383bdc49b 100644 --- a/agent/router/ro_website_ca.go +++ b/agent/router/ro_website_ca.go @@ -13,6 +13,8 @@ func (a *WebsiteCARouter) InitRouter(Router *gin.RouterGroup) { baseApi := v2.ApiGroupApp.BaseApi { + groupRouter.POST("/import", baseApi.ImportWebsiteCA) + groupRouter.POST("/upload", baseApi.UploadWebsiteCA) groupRouter.POST("/search", baseApi.PageWebsiteCA) groupRouter.POST("", baseApi.CreateWebsiteCA) groupRouter.POST("/del", baseApi.DeleteWebsiteCA) diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index 5da5618f9..b8801dfe0 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -626,6 +626,9 @@ export namespace Website { } export interface SSLObtainByCA { + autoRenew: boolean; + execShell: boolean; + shell: string; id: number; domains: string; keyType: string; diff --git a/frontend/src/api/modules/website.ts b/frontend/src/api/modules/website.ts index 81742b657..3ad59d36f 100644 --- a/frontend/src/api/modules/website.ts +++ b/frontend/src/api/modules/website.ts @@ -451,3 +451,18 @@ export const deleteTemplateOutput = (params: { id: number }) => { export const getTemplateOutput = (id: number) => { return http.post(`/websites/templates/outputs/get`, { id }); }; + +export const uploadCA = (params: FormData) => { + return http.upload(`/websites/ca/upload`, params); +}; + +export const importCA = (params: { + name: string; + type: 'paste' | 'local'; + certificate?: string; + privateKey?: string; + certificatePath?: string; + privateKeyPath?: string; +}) => { + return http.post(`/websites/ca/import`, params); +}; diff --git a/frontend/src/components/del-dialog/index.vue b/frontend/src/components/del-dialog/index.vue index 702b4e4fb..b9be1f787 100644 --- a/frontend/src/components/del-dialog/index.vue +++ b/frontend/src/components/del-dialog/index.vue @@ -1,6 +1,11 @@ - + diff --git a/frontend/src/views/website/ssl/ca/create/index.vue b/frontend/src/views/website/ssl/ca/create/index.vue index 35ea352ab..6f88c7fc3 100644 --- a/frontend/src/views/website/ssl/ca/create/index.vue +++ b/frontend/src/views/website/ssl/ca/create/index.vue @@ -1,42 +1,38 @@ diff --git a/frontend/src/views/website/ssl/ca/obtain/index.vue b/frontend/src/views/website/ssl/ca/obtain/index.vue deleted file mode 100644 index fa2472dba..000000000 --- a/frontend/src/views/website/ssl/ca/obtain/index.vue +++ /dev/null @@ -1,194 +0,0 @@ - - - diff --git a/frontend/src/views/website/ssl/ca/upload/index.vue b/frontend/src/views/website/ssl/ca/upload/index.vue new file mode 100644 index 000000000..135fd7267 --- /dev/null +++ b/frontend/src/views/website/ssl/ca/upload/index.vue @@ -0,0 +1,163 @@ + + + diff --git a/frontend/src/views/website/ssl/create/index.vue b/frontend/src/views/website/ssl/create/index.vue index 938f58712..33bd41e90 100644 --- a/frontend/src/views/website/ssl/create/index.vue +++ b/frontend/src/views/website/ssl/create/index.vue @@ -1,7 +1,33 @@ - - - (); const pushOpen = ref(false); @@ -464,10 +456,10 @@ const updateConfig = (row: Website.SSLDTO) => { }; const openAcmeAccount = () => { - acmeAccountRef.value.acceptParams(); + routerToNameWithQuery('SSL-ACME', { uncached: 'true' }); }; const openDnsAccount = () => { - dnsAccountRef.value.acceptParams(); + routerToNameWithQuery('SSL-DNS', { uncached: 'true' }); }; const openSSL = () => { sslCreateRef.value.acceptParams('create'); @@ -542,7 +534,7 @@ const submitPush = async () => { }; const openCA = () => { - caRef.value.acceptParams(); + routerToNameWithQuery('SSL-CA', { uncached: 'true' }); }; const applySSL = (row: Website.SSLDTO) => {