Skip to content

Commit

Permalink
feat: 支持重签面板HTTPS证书
Browse files Browse the repository at this point in the history
  • Loading branch information
devhaozi committed Oct 26, 2024
1 parent f40ad8b commit 9179543
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 121 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 4 additions & 1 deletion internal/data/cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
7 changes: 7 additions & 0 deletions internal/data/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
Expand Down
5 changes: 5 additions & 0 deletions internal/route/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ func Cli() []*cli.Command {
Usage: "关闭HTTPS",
Action: cliService.HTTPSOff,
},
{
Name: "generate",
Usage: "生成HTTPS证书",
Action: cliService.HTTPSGenerate,
},
},
},
{
Expand Down
36 changes: 36 additions & 0 deletions internal/service/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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{}
Expand Down Expand Up @@ -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")
Expand Down
97 changes: 0 additions & 97 deletions pkg/acme/ssl.go
Original file line number Diff line number Diff line change
@@ -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
}
22 changes: 0 additions & 22 deletions pkg/acme/ssl_test.go

This file was deleted.

84 changes: 84 additions & 0 deletions pkg/cert/cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}
22 changes: 22 additions & 0 deletions pkg/cert/cert_test.go
Original file line number Diff line number Diff line change
@@ -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)
}

0 comments on commit 9179543

Please sign in to comment.