diff --git a/backend/app/api/v1/website_ca.go b/backend/app/api/v1/website_ca.go index e3b2ee301..c457f1361 100644 --- a/backend/app/api/v1/website_ca.go +++ b/backend/app/api/v1/website_ca.go @@ -110,7 +110,7 @@ func (b *BaseApi) ObtainWebsiteCA(c *gin.Context) { if err := helper.CheckBindAndValidate(&req, c); err != nil { return } - if err := websiteCAService.ObtainSSL(req); err != nil { + if _, err := websiteCAService.ObtainSSL(req); err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) return } @@ -131,7 +131,7 @@ func (b *BaseApi) RenewWebsiteCA(c *gin.Context) { if err := helper.CheckBindAndValidate(&req, c); err != nil { return } - if err := websiteCAService.ObtainSSL(request.WebsiteCAObtain{ + if _, err := websiteCAService.ObtainSSL(request.WebsiteCAObtain{ SSLID: req.SSLID, Renew: true, Unit: "year", diff --git a/backend/app/service/setting.go b/backend/app/service/setting.go index d50dfc42d..b8e88d4ea 100644 --- a/backend/app/service/setting.go +++ b/backend/app/service/setting.go @@ -6,6 +6,7 @@ import ( "encoding/json" "encoding/pem" "fmt" + "github.com/1Panel-dev/1Panel/backend/app/dto/request" "net" "os" "path" @@ -21,7 +22,6 @@ import ( "github.com/1Panel-dev/1Panel/backend/utils/common" "github.com/1Panel-dev/1Panel/backend/utils/encrypt" "github.com/1Panel-dev/1Panel/backend/utils/files" - "github.com/1Panel-dev/1Panel/backend/utils/ssl" "github.com/gin-gonic/gin" "github.com/robfig/cron/v3" ) @@ -204,7 +204,6 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error { }() return nil } - if _, err := os.Stat(secretDir); err != nil && os.IsNotExist(err) { if err = os.MkdirAll(secretDir, os.ModePerm); err != nil { return err @@ -213,49 +212,61 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error { if err := settingRepo.Update("SSLType", req.SSLType); err != nil { return err } - if req.SSLType == "self" { + var ( + secret string + key string + ) + + switch req.SSLType { + case "self": if len(req.Domain) == 0 { return fmt.Errorf("load domain failed") } - if err := ssl.GenerateSSL(req.Domain); err != nil { - return err - } - } - if req.SSLType == "select" { - sslInfo, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(req.SSLID)) + defaultCA, err := websiteCARepo.GetFirst(commonRepo.WithByName("1Panel")) if err != nil { return err } - req.Cert = sslInfo.Pem - req.Key = sslInfo.PrivateKey - req.SSLType = "import" + websiteSSL, err := NewIWebsiteCAService().ObtainSSL(request.WebsiteCAObtain{ + ID: defaultCA.ID, + KeyType: "P256", + Domains: req.Domain, + Time: 1, + Unit: "year", + AutoRenew: true, + }) + if err != nil { + return err + } + secret = websiteSSL.Pem + key = websiteSSL.PrivateKey + if err := settingRepo.Update("SSLID", strconv.Itoa(int(websiteSSL.ID))); err != nil { + return err + } + case "select": + websiteSSL, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(req.SSLID)) + if err != nil { + return err + } + secret = websiteSSL.Pem + key = websiteSSL.PrivateKey if err := settingRepo.Update("SSLID", strconv.Itoa(int(req.SSLID))); err != nil { return err } + case "import": + secret = req.Cert + key = req.Key } - if req.SSLType == "import" { - cert, err := os.OpenFile(path.Join(secretDir, "server.crt.tmp"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) - if err != nil { - return err - } - defer cert.Close() - if _, err := cert.WriteString(req.Cert); err != nil { - return err - } - key, err := os.OpenFile(path.Join(secretDir, "server.key.tmp"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) - if err != nil { - return err - } - if _, err := key.WriteString(req.Key); err != nil { - return err - } - defer key.Close() + + fileOp := files.NewFileOp() + if err := fileOp.WriteFile(path.Join(secretDir, "server.crt.tmp"), strings.NewReader(secret), 0600); err != nil { + return err + } + if err := fileOp.WriteFile(path.Join(secretDir, "server.key.tmp"), strings.NewReader(key), 0600); err != nil { + return err } if err := checkCertValid(req.Domain); err != nil { return err } - - fileOp := files.NewFileOp() if err := fileOp.Rename(path.Join(secretDir, "server.crt.tmp"), path.Join(secretDir, "server.crt")); err != nil { return err } diff --git a/backend/app/service/website_ca.go b/backend/app/service/website_ca.go index f3001063c..d72b5d447 100644 --- a/backend/app/service/website_ca.go +++ b/backend/app/service/website_ca.go @@ -37,7 +37,7 @@ type IWebsiteCAService interface { Create(create request.WebsiteCACreate) (*request.WebsiteCACreate, error) GetCA(id uint) (*response.WebsiteCADTO, error) Delete(id uint) error - ObtainSSL(req request.WebsiteCAObtain) error + ObtainSSL(req request.WebsiteCAObtain) (*model.WebsiteSSL, error) } func NewIWebsiteCAService() IWebsiteCAService { @@ -169,10 +169,17 @@ func (w WebsiteCAService) Delete(id uint) error { if len(ssls) > 0 { return buserr.New("ErrDeleteCAWithSSL") } + exist, err := websiteCARepo.GetFirst(commonRepo.WithByID(id)) + if err != nil { + return err + } + if exist.Name == "1Panel" { + return buserr.New("ErrDefaultCA") + } return websiteCARepo.DeleteBy(commonRepo.WithByID(id)) } -func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) error { +func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.WebsiteSSL, error) { var ( domains []string ips []net.IP @@ -183,11 +190,11 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) error { if req.Renew { websiteSSL, err = websiteSSLRepo.GetFirst(commonRepo.WithByID(req.SSLID)) if err != nil { - return err + return nil, err } ca, err = websiteCARepo.GetFirst(commonRepo.WithByID(websiteSSL.CaID)) if err != nil { - return err + return nil, err } existDomains := []string{websiteSSL.PrimaryDomain} if websiteSSL.Domains != "" { @@ -203,7 +210,7 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) error { } else { ca, err = websiteCARepo.GetFirst(commonRepo.WithByID(req.ID)) if err != nil { - return err + return nil, err } websiteSSL = &model.WebsiteSSL{ Provider: constant.SelfSigned, @@ -214,7 +221,7 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) error { } if req.PushDir { if !files.NewFileOp().Stat(req.Dir) { - return buserr.New(constant.ErrLinkPathNotFound) + return nil, buserr.New(constant.ErrLinkPathNotFound) } websiteSSL.Dir = req.Dir } @@ -223,7 +230,7 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) error { for _, domain := range domainArray { if !common.IsValidDomain(domain) { err = buserr.WithName("ErrDomainFormat", domain) - return err + return nil, err } else { if ipAddress := net.ParseIP(domain); ipAddress == nil { domains = append(domains, domain) @@ -241,32 +248,32 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) error { rootCertBlock, _ := pem.Decode([]byte(ca.CSR)) if rootCertBlock == nil { - return buserr.New("ErrSSLCertificateFormat") + return nil, buserr.New("ErrSSLCertificateFormat") } rootCsr, err := x509.ParseCertificate(rootCertBlock.Bytes) if err != nil { - return err + return nil, err } rootPrivateKeyBlock, _ := pem.Decode([]byte(ca.PrivateKey)) if rootPrivateKeyBlock == nil { - return buserr.New("ErrSSLCertificateFormat") + return nil, buserr.New("ErrSSLCertificateFormat") } var rootPrivateKey any if ssl.KeyType(websiteSSL.KeyType) == certcrypto.EC256 || ssl.KeyType(websiteSSL.KeyType) == certcrypto.EC384 { rootPrivateKey, err = x509.ParseECPrivateKey(rootPrivateKeyBlock.Bytes) if err != nil { - return err + return nil, err } } else { rootPrivateKey, err = x509.ParsePKCS1PrivateKey(rootPrivateKeyBlock.Bytes) if err != nil { - return err + return nil, err } } interPrivateKey, interPublicKey, _, err := createPrivateKey(websiteSSL.KeyType) if err != nil { - return err + return nil, err } notAfter := time.Now() if req.Unit == "year" { @@ -287,16 +294,16 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) error { } interDer, err := x509.CreateCertificate(rand.Reader, interCsr, rootCsr, interPublicKey, rootPrivateKey) if err != nil { - return err + return nil, err } interCert, err := x509.ParseCertificate(interDer) if err != nil { - return err + return nil, err } _, publicKey, privateKeyBytes, err := createPrivateKey(websiteSSL.KeyType) if err != nil { - return err + return nil, err } csr := &x509.Certificate{ @@ -314,11 +321,11 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) error { der, err := x509.CreateCertificate(rand.Reader, csr, interCert, publicKey, interPrivateKey) if err != nil { - return err + return nil, err } cert, err := x509.ParseCertificate(der) if err != nil { - return err + return nil, err } certBlock := &pem.Block{ @@ -335,11 +342,11 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) error { if req.Renew { if err := websiteSSLRepo.Save(websiteSSL); err != nil { - return err + return nil, err } } else { if err := websiteSSLRepo.Create(context.Background(), websiteSSL); err != nil { - return err + return nil, err } } @@ -348,7 +355,7 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) error { logger := log.New(logFile, "", log.LstdFlags) logger.Println(i18n.GetMsgWithMap("ApplySSLSuccess", map[string]interface{}{"domain": strings.Join(domains, ",")})) saveCertificateFile(websiteSSL, logger) - return nil + return websiteSSL, nil } func createPrivateKey(keyType string) (privateKey any, publicKey any, privateKeyBytes []byte, err error) { diff --git a/backend/cron/job/ssl.go b/backend/cron/job/ssl.go index 6c9efefde..3d4cc2199 100644 --- a/backend/cron/job/ssl.go +++ b/backend/cron/job/ssl.go @@ -7,6 +7,10 @@ import ( "github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/utils/common" + "github.com/1Panel-dev/1Panel/backend/utils/files" + "path" + "strconv" + "strings" "time" ) @@ -17,7 +21,25 @@ func NewSSLJob() *ssl { return &ssl{} } +func getSystemSSL() (bool, uint) { + settingRepo := repo.NewISettingRepo() + sslSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL")) + if err != nil { + global.LOG.Errorf("load service ssl from setting failed, err: %v", err) + return false, 0 + } + if sslSetting.Value == "enable" { + sslID, _ := settingRepo.Get(settingRepo.WithByKey("SSLID")) + idValue, _ := strconv.Atoi(sslID.Value) + if idValue > 0 { + return true, uint(idValue) + } + } + return false, 0 +} + func (ssl *ssl) Run() { + systemSSLEnable, sslID := getSystemSSL() sslRepo := repo.NewISSLRepo() sslService := service.NewIWebsiteSSLService() sslList, _ := sslRepo.List() @@ -34,7 +56,7 @@ func (ssl *ssl) Run() { global.LOG.Errorf("Update the SSL certificate for the [%s] domain", s.PrimaryDomain) if s.Provider == constant.SelfSigned { caService := service.NewIWebsiteCAService() - if err := caService.ObtainSSL(request.WebsiteCAObtain{ + if _, err := caService.ObtainSSL(request.WebsiteCAObtain{ ID: s.CaID, SSLID: s.ID, Renew: true, @@ -52,6 +74,19 @@ func (ssl *ssl) Run() { continue } } + if systemSSLEnable && sslID == s.ID { + websiteSSL, _ := sslRepo.GetFirst(repo.NewCommonRepo().WithByID(s.ID)) + fileOp := files.NewFileOp() + secretDir := path.Join(global.CONF.System.BaseDir, "1panel/secret") + if err := fileOp.WriteFile(path.Join(secretDir, "server.crt"), strings.NewReader(websiteSSL.Pem), 0600); err != nil { + global.LOG.Errorf("Failed to update the SSL certificate File for 1Panel System domain [%s] , err:%s", s.PrimaryDomain, err.Error()) + continue + } + if err := fileOp.WriteFile(path.Join(secretDir, "server.key"), strings.NewReader(websiteSSL.PrivateKey), 0600); err != nil { + global.LOG.Errorf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", s.PrimaryDomain, err.Error()) + continue + } + } global.LOG.Errorf("The SSL certificate for the [%s] domain has been successfully updated", s.PrimaryDomain) } } diff --git a/backend/init/migration/migrate.go b/backend/init/migration/migrate.go index 79aaaa87d..7e7940ed7 100644 --- a/backend/init/migration/migrate.go +++ b/backend/init/migration/migrate.go @@ -58,6 +58,7 @@ func Init() { migrations.AddWebsiteCA, migrations.AddDockerSockPath, migrations.AddDatabaseSSL, + migrations.AddDefaultCA, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/backend/init/migration/migrations/v_1_9.go b/backend/init/migration/migrations/v_1_9.go index 408bce1f1..53c898ad3 100644 --- a/backend/init/migration/migrations/v_1_9.go +++ b/backend/init/migration/migrations/v_1_9.go @@ -1,7 +1,9 @@ package migrations import ( + "github.com/1Panel-dev/1Panel/backend/app/dto/request" "github.com/1Panel-dev/1Panel/backend/app/model" + "github.com/1Panel-dev/1Panel/backend/app/service" "github.com/go-gormigrate/gormigrate/v2" "gorm.io/gorm" ) @@ -55,3 +57,23 @@ var AddDatabaseSSL = &gormigrate.Migration{ return nil }, } + +var AddDefaultCA = &gormigrate.Migration{ + ID: "20231129-add-default-ca", + Migrate: func(tx *gorm.DB) error { + caService := service.NewIWebsiteCAService() + if _, err := caService.Create(request.WebsiteCACreate{ + CommonName: "1Panel-CA", + Country: "CN", + KeyType: "P256", + Name: "1Panel", + Organization: "FIT2CLOUD", + OrganizationUint: "1Panel", + Province: "Beijing", + City: "Beijing", + }); err != nil { + return err + } + return nil + }, +} diff --git a/backend/utils/ssl/ssl.go b/backend/utils/ssl/ssl.go deleted file mode 100644 index 1f0a9916d..000000000 --- a/backend/utils/ssl/ssl.go +++ /dev/null @@ -1,113 +0,0 @@ -package ssl - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "math/big" - "net" - "os" - "path" - "time" - - "github.com/1Panel-dev/1Panel/backend/global" -) - -func GenerateSSL(domain string) error { - rootPrivateKey, _ := rsa.GenerateKey(rand.Reader, 2048) - ipItem := net.ParseIP(domain) - isIP := false - if len(ipItem) != 0 { - isIP = true - } - - rootTemplate := x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{CommonName: "1Panel Root CA"}, - NotBefore: time.Now(), - NotAfter: time.Now().AddDate(10, 0, 0), - BasicConstraintsValid: true, - IsCA: true, - KeyUsage: x509.KeyUsageCertSign, - } - if isIP { - rootTemplate.IPAddresses = []net.IP{ipItem} - } else { - rootTemplate.DNSNames = []string{domain} - } - - rootCertBytes, _ := x509.CreateCertificate(rand.Reader, &rootTemplate, &rootTemplate, &rootPrivateKey.PublicKey, rootPrivateKey) - rootCertBlock := &pem.Block{ - Type: "CERTIFICATE", - Bytes: rootCertBytes, - } - - interPrivateKey, _ := rsa.GenerateKey(rand.Reader, 2048) - interTemplate := x509.Certificate{ - SerialNumber: big.NewInt(2), - Subject: pkix.Name{CommonName: "1Panel Intermediate CA"}, - NotBefore: time.Now(), - NotAfter: time.Now().AddDate(10, 0, 0), - BasicConstraintsValid: true, - IsCA: true, - KeyUsage: x509.KeyUsageCertSign, - } - if isIP { - interTemplate.IPAddresses = []net.IP{ipItem} - } else { - interTemplate.DNSNames = []string{domain} - } - - interCertBytes, _ := x509.CreateCertificate(rand.Reader, &interTemplate, &rootTemplate, &interPrivateKey.PublicKey, rootPrivateKey) - interCertBlock := &pem.Block{ - Type: "CERTIFICATE", - Bytes: interCertBytes, - } - - clientPrivateKey, _ := rsa.GenerateKey(rand.Reader, 2048) - clientTemplate := x509.Certificate{ - SerialNumber: big.NewInt(3), - Subject: pkix.Name{CommonName: domain}, - NotBefore: time.Now(), - NotAfter: time.Now().AddDate(10, 0, 0), - KeyUsage: x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, - } - if isIP { - clientTemplate.IPAddresses = []net.IP{ipItem} - } else { - clientTemplate.DNSNames = []string{domain} - } - - clientCertBytes, _ := x509.CreateCertificate(rand.Reader, &clientTemplate, &interTemplate, &clientPrivateKey.PublicKey, interPrivateKey) - clientCertBlock := &pem.Block{ - Type: "CERTIFICATE", - Bytes: clientCertBytes, - } - - pemBytes := []byte{} - pemBytes = append(pemBytes, pem.EncodeToMemory(clientCertBlock)...) - pemBytes = append(pemBytes, pem.EncodeToMemory(interCertBlock)...) - pemBytes = append(pemBytes, pem.EncodeToMemory(rootCertBlock)...) - certOut, err := os.OpenFile(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt.tmp"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) - if err != nil { - return err - } - defer certOut.Close() - if _, err := certOut.Write(pemBytes); err != nil { - return err - } - - keyOut, err := os.OpenFile(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.key.tmp"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) - if err != nil { - return err - } - defer keyOut.Close() - if err := pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(clientPrivateKey)}); err != nil { - return err - } - - return nil -}