mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 09:13:49 +08:00
feat: 支持重签面板HTTPS证书
This commit is contained in:
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -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.
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -74,6 +74,11 @@ func Cli() []*cli.Command {
|
||||
Usage: "关闭HTTPS",
|
||||
Action: cliService.HTTPSOff,
|
||||
},
|
||||
{
|
||||
Name: "generate",
|
||||
Usage: "生成HTTPS证书",
|
||||
Action: cliService.HTTPSGenerate,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
22
pkg/cert/cert_test.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user