diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index fcd1602e..9a13cbfa 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -56,7 +56,7 @@ body: - type: input id: version attributes: - label: 耗子面板版本 (HaoZi Panel Version) + label: 耗子面板版本 (Rat Panel Version) description: | 请提供面板的版本号。 Please provide the version number of the panel. diff --git a/internal/data/cert.go b/internal/data/cert.go index 861ee72c..3348bd27 100644 --- a/internal/data/cert.go +++ b/internal/data/cert.go @@ -75,7 +75,10 @@ func (r *certRepo) GetByWebsite(WebsiteID uint) (*biz.Cert, error) { func (r *certRepo) Upload(req *request.CertUpload) (*biz.Cert, error) { info, err := pkgcert.ParseCert(req.Cert) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to parse certificate: %v", err) + } + if _, err = pkgcert.ParseKey(req.Key); err != nil { + return nil, fmt.Errorf("failed to parse private key: %v", err) } cert := &biz.Cert{ diff --git a/internal/data/setting.go b/internal/data/setting.go index b800ee71..c36b85ec 100644 --- a/internal/data/setting.go +++ b/internal/data/setting.go @@ -15,6 +15,7 @@ import ( "github.com/TheTNB/panel/internal/app" "github.com/TheTNB/panel/internal/biz" "github.com/TheTNB/panel/internal/http/request" + "github.com/TheTNB/panel/pkg/cert" "github.com/TheTNB/panel/pkg/firewall" "github.com/TheTNB/panel/pkg/io" "github.com/TheTNB/panel/pkg/shell" @@ -178,6 +179,12 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P } restartFlag = true } + if _, err := cert.ParseCert(setting.Cert); err != nil { + return false, fmt.Errorf("failed to parse certificate: %w", err) + } + if _, err := cert.ParseKey(setting.Key); err != nil { + return false, fmt.Errorf("failed to parse private key: %w", err) + } if err := io.Write(filepath.Join(app.Root, "panel/storage/cert.pem"), setting.Cert, 0644); err != nil { return false, err } diff --git a/internal/route/cli.go b/internal/route/cli.go index 73dc0417..443ddd6c 100644 --- a/internal/route/cli.go +++ b/internal/route/cli.go @@ -74,6 +74,11 @@ func Cli() []*cli.Command { Usage: "关闭HTTPS", Action: cliService.HTTPSOff, }, + { + Name: "generate", + Usage: "生成HTTPS证书", + Action: cliService.HTTPSGenerate, + }, }, }, { diff --git a/internal/service/cli.go b/internal/service/cli.go index 5afb2665..34b5a684 100644 --- a/internal/service/cli.go +++ b/internal/service/cli.go @@ -18,6 +18,7 @@ import ( "github.com/TheTNB/panel/internal/data" "github.com/TheTNB/panel/internal/http/request" "github.com/TheTNB/panel/pkg/api" + "github.com/TheTNB/panel/pkg/cert" "github.com/TheTNB/panel/pkg/io" "github.com/TheTNB/panel/pkg/ntp" "github.com/TheTNB/panel/pkg/str" @@ -286,6 +287,37 @@ func (s *CliService) HTTPSOff(ctx context.Context, cmd *cli.Command) error { return s.Restart(ctx, cmd) } +func (s *CliService) HTTPSGenerate(ctx context.Context, cmd *cli.Command) error { + var names []string + if lv4, err := tools.GetLocalIPv4(); err == nil { + names = append(names, lv4) + } + if lv6, err := tools.GetLocalIPv6(); err == nil { + names = append(names, lv6) + } + if rv4, err := tools.GetPublicIPv4(); err == nil { + names = append(names, rv4) + } + if rv6, err := tools.GetPublicIPv6(); err == nil { + names = append(names, rv6) + } + + crt, key, err := cert.GenerateSelfSigned(names) + if err != nil { + return err + } + + if err = io.Write(filepath.Join(app.Root, "panel/storage/cert.pem"), string(crt), 0644); err != nil { + return err + } + if err = io.Write(filepath.Join(app.Root, "panel/storage/cert.key"), string(key), 0644); err != nil { + return err + } + + fmt.Println("已生成HTTPS证书") + return s.Restart(ctx, cmd) +} + func (s *CliService) EntranceOn(ctx context.Context, cmd *cli.Command) error { config := new(types.PanelConfig) cm := yaml.CommentMap{} @@ -739,6 +771,10 @@ func (s *CliService) Init(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("初始化失败:%v", err) } + if err = s.HTTPSGenerate(ctx, cmd); err != nil { + return fmt.Errorf("初始化失败:%v", err) + } + config := new(types.PanelConfig) cm := yaml.CommentMap{} raw, err := io.Read("/usr/local/etc/panel/config.yml") diff --git a/pkg/acme/ssl.go b/pkg/acme/ssl.go index e7fb919b..8d2a213b 100644 --- a/pkg/acme/ssl.go +++ b/pkg/acme/ssl.go @@ -1,98 +1 @@ package acme - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "math/big" - "net" - "time" -) - -// GenerateSelfSignedSSL 生成自签名证书 -func GenerateSelfSignedSSL(domains []string) ([]byte, []byte, error) { - rootPrivateKey, _ := rsa.GenerateKey(rand.Reader, 4096) - var ip []net.IP - isIP := false - for _, item := range domains { - ipItem := net.ParseIP(item) - if len(ipItem) != 0 { - isIP = true - ip = append(ip, ipItem) - } - } - - rootTemplate := x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{CommonName: "HaoZi Panel Root CA"}, - NotBefore: time.Now(), - NotAfter: time.Now().AddDate(20, 0, 0), - BasicConstraintsValid: true, - IsCA: true, - KeyUsage: x509.KeyUsageCertSign, - } - if isIP { - rootTemplate.IPAddresses = ip - } else { - rootTemplate.DNSNames = domains - } - - rootCertBytes, _ := x509.CreateCertificate(rand.Reader, &rootTemplate, &rootTemplate, &rootPrivateKey.PublicKey, rootPrivateKey) - rootCertBlock := &pem.Block{ - Type: "CERTIFICATE", - Bytes: rootCertBytes, - } - - interPrivateKey, _ := rsa.GenerateKey(rand.Reader, 4096) - interTemplate := x509.Certificate{ - SerialNumber: big.NewInt(2), - Subject: pkix.Name{CommonName: "HaoZi Panel Intermediate CA"}, - NotBefore: time.Now(), - NotAfter: time.Now().AddDate(20, 0, 0), - BasicConstraintsValid: true, - IsCA: true, - KeyUsage: x509.KeyUsageCertSign, - } - if isIP { - interTemplate.IPAddresses = ip - } else { - interTemplate.DNSNames = domains - } - - interCertBytes, _ := x509.CreateCertificate(rand.Reader, &interTemplate, &rootTemplate, &interPrivateKey.PublicKey, rootPrivateKey) - interCertBlock := &pem.Block{ - Type: "CERTIFICATE", - Bytes: interCertBytes, - } - - clientPrivateKey, _ := rsa.GenerateKey(rand.Reader, 4096) - clientTemplate := x509.Certificate{ - SerialNumber: big.NewInt(3), - Subject: pkix.Name{CommonName: "HaoZi Panel Client"}, - NotBefore: time.Now(), - NotAfter: time.Now().AddDate(20, 0, 0), - KeyUsage: x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, - } - if isIP { - clientTemplate.IPAddresses = ip - } else { - clientTemplate.DNSNames = domains - } - - 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)...) - keyBytes := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(clientPrivateKey)}) - - return pemBytes, keyBytes, nil -} diff --git a/pkg/acme/ssl_test.go b/pkg/acme/ssl_test.go deleted file mode 100644 index 4d4d3b26..00000000 --- a/pkg/acme/ssl_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package acme - -import ( - "testing" - - "github.com/stretchr/testify/suite" -) - -type SSLTestSuite struct { - suite.Suite -} - -func TestSSLTestSuite(t *testing.T) { - suite.Run(t, &SSLTestSuite{}) -} - -func (s *SSLTestSuite) TestGenerateSelfSignedSSL() { - pem, key, err := GenerateSelfSignedSSL([]string{"haozi.dev"}) - s.Nil(err) - s.NotNil(pem) - s.NotNil(key) -} diff --git a/pkg/cert/cert.go b/pkg/cert/cert.go index 3ba8c764..b488ff33 100644 --- a/pkg/cert/cert.go +++ b/pkg/cert/cert.go @@ -4,12 +4,17 @@ import ( "crypto" "crypto/ecdsa" "crypto/ed25519" + "crypto/rand" "crypto/rsa" "crypto/x509" + "crypto/x509/pkix" "encoding/pem" "errors" "fmt" + "math/big" + "net" "strings" + "time" ) func ParseCert(crt string) (x509.Certificate, error) { @@ -87,3 +92,82 @@ func EncodeKey(key crypto.Signer) ([]byte, error) { pemKey := pem.Block{Type: pemType + " PRIVATE KEY", Bytes: keyBytes} return pem.EncodeToMemory(&pemKey), nil } + +// GenerateSelfSigned 生成自签名证书 +func GenerateSelfSigned(names []string) (cert []byte, key []byte, err error) { + rootPrivateKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return nil, nil, err + } + var ips []net.IP + ip := false + for _, item := range names { + ipItem := net.ParseIP(item) + if ipItem != nil { + ip = true + ips = append(ips, ipItem) + } + } + + rootTemplate := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{CommonName: "Rat Panel Root CA"}, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(40, 0, 0), + BasicConstraintsValid: true, + IsCA: true, + KeyUsage: x509.KeyUsageCertSign, + } + + rootCertBytes, _ := x509.CreateCertificate(rand.Reader, &rootTemplate, &rootTemplate, &rootPrivateKey.PublicKey, rootPrivateKey) + rootCertBlock := &pem.Block{ + Type: "CERTIFICATE", + Bytes: rootCertBytes, + } + + interPrivateKey, _ := rsa.GenerateKey(rand.Reader, 4096) + interTemplate := x509.Certificate{ + SerialNumber: big.NewInt(2), + Subject: pkix.Name{CommonName: "Rat Panel CA"}, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(30, 0, 0), + BasicConstraintsValid: true, + IsCA: true, + KeyUsage: x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + } + + interCertBytes, _ := x509.CreateCertificate(rand.Reader, &interTemplate, &rootTemplate, &interPrivateKey.PublicKey, rootPrivateKey) + interCertBlock := &pem.Block{ + Type: "CERTIFICATE", + Bytes: interCertBytes, + } + + clientPrivateKey, _ := rsa.GenerateKey(rand.Reader, 4096) + clientTemplate := x509.Certificate{ + SerialNumber: big.NewInt(3), + Subject: pkix.Name{CommonName: "Rat Panel"}, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(20, 0, 0), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + } + if ip { + clientTemplate.IPAddresses = ips + } else { + clientTemplate.DNSNames = names + } + + clientCertBytes, _ := x509.CreateCertificate(rand.Reader, &clientTemplate, &interTemplate, &clientPrivateKey.PublicKey, interPrivateKey) + clientCertBlock := &pem.Block{ + Type: "CERTIFICATE", + Bytes: clientCertBytes, + } + + cert = append(cert, pem.EncodeToMemory(clientCertBlock)...) + cert = append(cert, pem.EncodeToMemory(interCertBlock)...) + cert = append(cert, pem.EncodeToMemory(rootCertBlock)...) + key = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(clientPrivateKey)}) + + return cert, key, nil +} diff --git a/pkg/cert/cert_test.go b/pkg/cert/cert_test.go new file mode 100644 index 00000000..e3761daf --- /dev/null +++ b/pkg/cert/cert_test.go @@ -0,0 +1,22 @@ +package cert + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +type CertTestSuite struct { + suite.Suite +} + +func TestCertTestSuite(t *testing.T) { + suite.Run(t, &CertTestSuite{}) +} + +func (s *CertTestSuite) TestGenerateSelfSigned() { + pem, key, err := GenerateSelfSigned([]string{"haozi.dev"}) + s.Nil(err) + s.NotNil(pem) + s.NotNil(key) +}