保存Lego注册信息,避免重复注册导致失败

优化动态加载TLS证书性能
This commit is contained in:
wsczx 2023-04-04 22:35:40 +08:00
parent 061f6f222b
commit 748adadd1e
6 changed files with 214 additions and 128 deletions

View File

@ -1,18 +1,14 @@
package admin package admin
import ( import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"os" "os"
"sync"
"time" "time"
"github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/base"
@ -21,32 +17,15 @@ import (
"github.com/go-acme/lego/v4/certificate" "github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/lego" "github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/providers/dns/alidns"
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
"github.com/go-acme/lego/v4/providers/dns/tencentcloud"
"github.com/go-acme/lego/v4/registration" "github.com/go-acme/lego/v4/registration"
"github.com/xenolf/lego/challenge"
"golang.org/x/crypto/scrypt"
) )
type LegoUser struct {
Email string
Registration *registration.Resource
key crypto.PrivateKey
}
type LeGoClient struct { type LeGoClient struct {
mutex sync.Mutex
Client *lego.Client Client *lego.Client
dbdata.LegoUserData
} }
func (u *LegoUser) GetEmail() string {
return u.Email
}
func (u LegoUser) GetRegistration() *registration.Resource {
return u.Registration
}
func (u *LegoUser) GetPrivateKey() crypto.PrivateKey {
return u.key
}
func CustomCert(w http.ResponseWriter, r *http.Request) { func CustomCert(w http.ResponseWriter, r *http.Request) {
cert, _, err := r.FormFile("cert") cert, _, err := r.FormFile("cert")
if err != nil { if err != nil {
@ -78,18 +57,18 @@ func CustomCert(w http.ResponseWriter, r *http.Request) {
RespError(w, RespInternalErr, err) RespError(w, RespInternalErr, err)
return return
} }
if tlscert, _, err := ParseCert(); err != nil {
return
} else {
dbdata.TLSCert = tlscert
}
RespSucess(w, "上传成功") RespSucess(w, "上传成功")
} }
func GetCertSetting(w http.ResponseWriter, r *http.Request) { func GetCertSetting(w http.ResponseWriter, r *http.Request) {
data := &dbdata.SettingDnsProvider{} data := &dbdata.SettingLetsEncrypt{}
if err := dbdata.SettingGet(data); err != nil { if err := dbdata.SettingGet(data); err != nil {
RespError(w, RespInternalErr, err) RespError(w, RespInternalErr, err)
} }
data.AliYun.APIKey = Scrypt(data.AliYun.APIKey)
data.AliYun.SecretKey = Scrypt(data.AliYun.SecretKey)
data.TXCloud.SecretID = Scrypt(data.TXCloud.SecretID)
data.TXCloud.SecretKey = Scrypt(data.TXCloud.SecretKey)
data.CfCloud.AuthKey = Scrypt(data.CfCloud.AuthKey)
RespSucess(w, data) RespSucess(w, data)
} }
func CreatCert(w http.ResponseWriter, r *http.Request) { func CreatCert(w http.ResponseWriter, r *http.Request) {
@ -103,7 +82,7 @@ func CreatCert(w http.ResponseWriter, r *http.Request) {
return return
} }
defer r.Body.Close() defer r.Body.Close()
config := &dbdata.SettingDnsProvider{} config := &dbdata.SettingLetsEncrypt{}
err = json.Unmarshal(body, config) err = json.Unmarshal(body, config)
if err != nil { if err != nil {
RespError(w, RespInternalErr, err) RespError(w, RespInternalErr, err)
@ -113,8 +92,8 @@ func CreatCert(w http.ResponseWriter, r *http.Request) {
RespError(w, RespInternalErr, err) RespError(w, RespInternalErr, err)
return return
} }
client, err := NewLeGoClient(config) client := LeGoClient{}
if err != nil { if err := client.NewClient(config); err != nil {
base.Error(err) base.Error(err)
RespError(w, RespInternalErr, fmt.Sprintf("获取证书失败:%v", err)) RespError(w, RespInternalErr, fmt.Sprintf("获取证书失败:%v", err))
return return
@ -128,13 +107,13 @@ func CreatCert(w http.ResponseWriter, r *http.Request) {
} }
func ReNewCert() { func ReNewCert() {
certtime, err := GetCerttime() _, certtime, err := ParseCert()
if err != nil { if err != nil {
base.Error(err) base.Error(err)
return return
} }
if certtime.AddDate(0, 0, -7).Before(time.Now()) { if certtime.AddDate(0, 0, -7).Before(time.Now()) {
config := &dbdata.SettingDnsProvider{} config := &dbdata.SettingLetsEncrypt{}
if err := dbdata.SettingGet(config); err != nil { if err := dbdata.SettingGet(config); err != nil {
base.Error(err) base.Error(err)
return return
@ -143,8 +122,8 @@ func ReNewCert() {
return return
} }
if config.Renew { if config.Renew {
client, err := NewLeGoClient(config) client := &LeGoClient{}
if err != nil { if err := client.NewClient(config); err != nil {
base.Error(err) base.Error(err)
return return
} }
@ -158,51 +137,38 @@ func ReNewCert() {
base.Info(fmt.Sprintf("证书过期时间:%s", certtime.Local().Format("2006-1-2 15:04:05"))) base.Info(fmt.Sprintf("证书过期时间:%s", certtime.Local().Format("2006-1-2 15:04:05")))
} }
func NewLeGoClient(d *dbdata.SettingDnsProvider) (*LeGoClient, error) { func (c *LeGoClient) NewClient(l *dbdata.SettingLetsEncrypt) error {
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) c.mutex.Lock()
defer c.mutex.Unlock()
legouser, err := c.GetUserData(l)
if err != nil { if err != nil {
return nil, err return err
} }
legoUser := LegoUser{ config := lego.NewConfig(legouser)
Email: d.Legomail,
key: privateKey,
}
config := lego.NewConfig(&legoUser)
config.CADirURL = lego.LEDirectoryProduction config.CADirURL = lego.LEDirectoryProduction
config.Certificate.KeyType = certcrypto.RSA2048 config.Certificate.KeyType = certcrypto.RSA2048
client, err := lego.NewClient(config) client, err := lego.NewClient(config)
if err != nil { if err != nil {
return nil, err return err
} }
if _, err := client.Registration.ResolveAccountByKey(); err != nil { Provider, err := dbdata.GetDNSProvider(l)
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) if err != nil {
if err != nil { return err
return nil, err
}
legoUser.Registration = reg
}
var Provider challenge.Provider
switch d.Name {
case "aliyun":
if Provider, err = alidns.NewDNSProviderConfig(&alidns.Config{APIKey: d.AliYun.APIKey, SecretKey: d.AliYun.SecretKey, TTL: 600}); err != nil {
return nil, err
}
case "txcloud":
if Provider, err = tencentcloud.NewDNSProviderConfig(&tencentcloud.Config{SecretID: d.TXCloud.SecretID, SecretKey: d.TXCloud.SecretKey, TTL: 600}); err != nil {
return nil, err
}
case "cloudflare":
if Provider, err = cloudflare.NewDNSProviderConfig(&cloudflare.Config{AuthEmail: d.CfCloud.AuthEmail, AuthKey: d.CfCloud.AuthKey, TTL: 600}); err != nil {
return nil, err
}
} }
if err := client.Challenge.SetDNS01Provider(Provider, dns01.AddRecursiveNameservers([]string{"114.114.114.114", "114.114.115.115"})); err != nil { if err := client.Challenge.SetDNS01Provider(Provider, dns01.AddRecursiveNameservers([]string{"114.114.114.114", "114.114.115.115"})); err != nil {
return nil, err return err
} }
return &LeGoClient{ if legouser.Registration == nil {
Client: client, reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
}, nil if err != nil {
return err
}
legouser.Registration = reg
c.SaveUserData(legouser)
}
c.Client = client
return nil
} }
func (c *LeGoClient) GetCertificate(domain string) error { func (c *LeGoClient) GetCertificate(domain string) error {
// 申请证书 // 申请证书
@ -250,6 +216,11 @@ func SaveCertificate(cert *certificate.Resource) error {
if err != nil { if err != nil {
return err return err
} }
if tlscert, _, err := ParseCert(); err != nil {
return err
} else {
dbdata.TLSCert = tlscert
}
return nil return nil
} }
@ -268,24 +239,24 @@ func LoadCertResource(certFile, keyFile string) (*certificate.Resource, error) {
}, nil }, nil
} }
func GetCerttime() (*time.Time, error) { func ParseCert() (*tls.Certificate, *time.Time, error) {
cert, err := tls.LoadX509KeyPair(base.Cfg.CertFile, base.Cfg.CertKey) cert, err := tls.LoadX509KeyPair(base.Cfg.CertFile, base.Cfg.CertKey)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
parseCert, err := x509.ParseCertificate(cert.Certificate[0]) parseCert, err := x509.ParseCertificate(cert.Certificate[0])
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
certtime := parseCert.NotAfter certtime := parseCert.NotAfter
return &certtime, nil return &cert, &certtime, nil
} }
func Scrypt(passwd string) string { // func Scrypt(passwd string) string {
salt := []byte{0xc8, 0x28, 0xf2, 0x58, 0xa7, 0x6a, 0xad, 0x7b} // salt := []byte{0xc8, 0x28, 0xf2, 0x58, 0xa7, 0x6a, 0xad, 0x7b}
hashPasswd, err := scrypt.Key([]byte(passwd), salt, 1<<15, 8, 1, 32) // hashPasswd, err := scrypt.Key([]byte(passwd), salt, 1<<15, 8, 1, 32)
if err != nil { // if err != nil {
return err.Error() // return err.Error()
} // }
return base64.StdEncoding.EncodeToString(hashPasswd) // return base64.StdEncoding.EncodeToString(hashPasswd)
} // }

View File

@ -9,6 +9,7 @@ import (
"github.com/arl/statsviz" "github.com/arl/statsviz"
"github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/dbdata"
"github.com/gorilla/handlers" "github.com/gorilla/handlers"
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
@ -99,17 +100,25 @@ func StartAdmin() {
for _, s := range cipherSuites { for _, s := range cipherSuites {
selectedCipherSuites = append(selectedCipherSuites, s.ID) selectedCipherSuites = append(selectedCipherSuites, s.ID)
} }
if tlscert, _, err := ParseCert(); err != nil {
base.Error(err)
return
} else {
dbdata.TLSCert = tlscert
}
// 设置tls信息 // 设置tls信息
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
NextProtos: []string{"http/1.1"}, NextProtos: []string{"http/1.1"},
MinVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12,
CipherSuites: selectedCipherSuites, CipherSuites: selectedCipherSuites,
GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) { GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
cert, err := tls.LoadX509KeyPair(base.Cfg.CertFile, base.Cfg.CertKey) // cert, err := tls.LoadX509KeyPair(base.Cfg.CertFile, base.Cfg.CertKey)
if err != nil { // if err != nil {
return nil, err // return nil, err
} // }
return &cert, nil return dbdata.TLSCert, nil
}, },
} }
srv := &http.Server{ srv := &http.Server{

119
server/dbdata/cert.go Executable file
View File

@ -0,0 +1,119 @@
package dbdata
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"github.com/go-acme/lego/v4/providers/dns/alidns"
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
"github.com/go-acme/lego/v4/providers/dns/tencentcloud"
"github.com/go-acme/lego/v4/registration"
"github.com/xenolf/lego/challenge"
)
var TLSCert *tls.Certificate
type SettingLetsEncrypt struct {
// LegoUser LegoUser
Domain string `json:"domain"`
Legomail string `json:"legomail"`
Name string `json:"name"`
Renew bool `json:"renew"`
DNSProvider
}
type DNSProvider struct {
AliYun struct {
APIKey string `json:"apiKey"`
SecretKey string `json:"secretKey"`
} `json:"aliyun"`
TXCloud struct {
SecretID string `json:"secretId"`
SecretKey string `json:"secretKey"`
} `json:"txcloud"`
CfCloud struct {
AuthEmail string `json:"authEmail"`
AuthKey string `json:"authKey"`
} `json:"cfcloud"`
}
type LegoUserData struct {
Email string `json:"email"`
Registration *registration.Resource `json:"registration"`
Key []byte `json:"key"`
}
type LegoUser struct {
Email string
Registration *registration.Resource
Key *ecdsa.PrivateKey
}
func GetDNSProvider(l *SettingLetsEncrypt) (Provider challenge.Provider, err error) {
switch l.Name {
case "aliyun":
if Provider, err = alidns.NewDNSProviderConfig(&alidns.Config{APIKey: l.DNSProvider.AliYun.APIKey, SecretKey: l.DNSProvider.AliYun.SecretKey, TTL: 600}); err != nil {
return
}
case "txcloud":
if Provider, err = tencentcloud.NewDNSProviderConfig(&tencentcloud.Config{SecretID: l.DNSProvider.TXCloud.SecretID, SecretKey: l.DNSProvider.TXCloud.SecretKey, TTL: 600}); err != nil {
return
}
case "cloudflare":
if Provider, err = cloudflare.NewDNSProviderConfig(&cloudflare.Config{AuthEmail: l.DNSProvider.CfCloud.AuthEmail, AuthKey: l.DNSProvider.CfCloud.AuthKey, TTL: 600}); err != nil {
return
}
}
return
}
func (u *LegoUser) GetEmail() string {
return u.Email
}
func (u LegoUser) GetRegistration() *registration.Resource {
return u.Registration
}
func (u *LegoUser) GetPrivateKey() crypto.PrivateKey {
return u.Key
}
func (l *LegoUserData) SaveUserData(u *LegoUser) error {
key, err := x509.MarshalECPrivateKey(u.Key)
if err != nil {
return err
}
l.Email = u.Email
l.Registration = u.Registration
l.Key = key
if err := SettingSet(l); err != nil {
return err
}
return nil
}
func (l *LegoUserData) GetUserData(d *SettingLetsEncrypt) (*LegoUser, error) {
if err := SettingGet(l); err != nil {
return nil, err
}
if l.Email != "" {
key, err := x509.ParseECPrivateKey(l.Key)
if err != nil {
return nil, err
}
return &LegoUser{
Email: l.Email,
Registration: l.Registration,
Key: key,
}, nil
}
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
return &LegoUser{
Email: d.Legomail,
Key: privateKey,
}, nil
}

View File

@ -100,28 +100,35 @@ func addInitData() error {
} }
// SettingDnsProvider // SettingDnsProvider
provider := &SettingDnsProvider{ provider := &SettingLetsEncrypt{
Domain: "vpn.xxx.com", Domain: "vpn.xxx.com",
Legomail: "legomail", Legomail: "legomail",
Name: "", Name: "aliyun",
Renew: false, Renew: false,
AliYun: struct { DNSProvider: DNSProvider{
APIKey string `json:"apiKey"` AliYun: struct {
SecretKey string `json:"secretKey"` APIKey string `json:"apiKey"`
}{APIKey: "", SecretKey: ""}, SecretKey string `json:"secretKey"`
TXCloud: struct { }{APIKey: "", SecretKey: ""},
SecretID string `json:"secretId"` TXCloud: struct {
SecretKey string `json:"secretKey"` SecretID string `json:"secretId"`
}{SecretID: "", SecretKey: ""}, SecretKey string `json:"secretKey"`
CfCloud: struct { }{SecretID: "", SecretKey: ""},
AuthEmail string `json:"authEmail"` CfCloud: struct {
AuthKey string `json:"authKey"` AuthEmail string `json:"authEmail"`
}{AuthEmail: "", AuthKey: ""}, AuthKey string `json:"authKey"`
}{AuthEmail: "", AuthKey: ""}},
} }
err = SettingSessAdd(sess, provider) err = SettingSessAdd(sess, provider)
if err != nil { if err != nil {
return err return err
} }
// LegoUser
legouser := &LegoUserData{}
err = SettingSessAdd(sess, legouser)
if err != nil {
return err
}
// SettingOther // SettingOther
other := &SettingOther{ other := &SettingOther{
LinkAddr: "vpn.xx.com", LinkAddr: "vpn.xx.com",

View File

@ -33,26 +33,6 @@ type SettingOther struct {
AccountMail string `json:"account_mail"` AccountMail string `json:"account_mail"`
} }
type SettingDnsProvider struct {
Domain string `json:"domain"`
Legomail string `json:"legomail"`
Name string `json:"name"`
Renew bool `json:"renew"`
AliYun struct {
APIKey string `json:"apiKey"`
SecretKey string `json:"secretKey"`
} `json:"aliyun"`
TXCloud struct {
SecretID string `json:"secretId"`
SecretKey string `json:"secretKey"`
} `json:"txcloud"`
CfCloud struct {
AuthEmail string `json:"authEmail"`
AuthKey string `json:"authKey"`
} `json:"cfcloud"`
}
func StructName(data interface{}) string { func StructName(data interface{}) string {
ref := reflect.ValueOf(data) ref := reflect.ValueOf(data)
s := &ref s := &ref
@ -69,7 +49,6 @@ func SettingSessAdd(sess *xorm.Session, data interface{}) error {
v, _ := json.Marshal(data) v, _ := json.Marshal(data)
s := &Setting{Name: name, Data: v} s := &Setting{Name: name, Data: v}
_, err := sess.InsertOne(s) _, err := sess.InsertOne(s)
return err return err
} }

View File

@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/base"
"github.com/bjdgyc/anylink/dbdata"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/pires/go-proxyproto" "github.com/pires/go-proxyproto"
) )
@ -49,11 +50,11 @@ func startTls() {
MinVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12,
CipherSuites: selectedCipherSuites, CipherSuites: selectedCipherSuites,
GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) { GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
cert, err := tls.LoadX509KeyPair(base.Cfg.CertFile, base.Cfg.CertKey) // cert, err := tls.LoadX509KeyPair(base.Cfg.CertFile, base.Cfg.CertKey)
if err != nil { // if err != nil {
return nil, err // return nil, err
} // }
return &cert, nil return dbdata.TLSCert, nil
}, },
// InsecureSkipVerify: true, // InsecureSkipVerify: true,
} }