2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 09:13:49 +08:00

feat: 支持重签面板HTTPS证书

This commit is contained in:
耗子
2024-10-27 04:09:33 +08:00
parent f40ad8bdf9
commit 9179543b7f
9 changed files with 159 additions and 121 deletions

View File

@@ -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.

View File

@@ -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{

View File

@@ -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
}

View File

@@ -74,6 +74,11 @@ func Cli() []*cli.Command {
Usage: "关闭HTTPS",
Action: cliService.HTTPSOff,
},
{
Name: "generate",
Usage: "生成HTTPS证书",
Action: cliService.HTTPSGenerate,
},
},
},
{

View File

@@ -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")

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}

22
pkg/cert/cert_test.go Normal file
View File

@@ -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)
}