mirror of
https://github.com/wyx2685/V2bX.git
synced 2025-01-22 09:58:14 -05:00
refactor lego
This commit is contained in:
parent
05ac7c19f6
commit
405cd246e1
@ -2,7 +2,7 @@ package panel
|
||||
|
||||
import (
|
||||
"github.com/Yuzuki616/V2bX/conf"
|
||||
"github.com/Yuzuki616/V2bX/node/controller/legoCmd/log"
|
||||
"log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
8
common/file/file.go
Normal file
8
common/file/file.go
Normal file
@ -0,0 +1,8 @@
|
||||
package file
|
||||
|
||||
import "os"
|
||||
|
||||
func IsExist(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil || !os.IsNotExist(err)
|
||||
}
|
8
go.mod
8
go.mod
@ -8,10 +8,9 @@ require (
|
||||
github.com/go-resty/resty/v2 v2.7.0
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/juju/ratelimit v1.0.2
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/urfave/cli v1.22.10
|
||||
github.com/stretchr/testify v1.8.1 // indirect
|
||||
github.com/xtls/xray-core v1.7.2
|
||||
golang.org/x/net v0.5.0
|
||||
golang.org/x/net v0.5.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.1
|
||||
)
|
||||
@ -47,7 +46,6 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/cloudflare/cloudflare-go v0.49.0 // indirect
|
||||
github.com/cpu/goacmedns v0.1.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/deepmap/oapi-codegen v1.10.1 // indirect
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
||||
@ -115,10 +113,8 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/pquerna/otp v1.3.0 // indirect
|
||||
github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2 // indirect
|
||||
github.com/refraction-networking/utls v1.2.0 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sacloud/libsacloud v1.36.2 // indirect
|
||||
github.com/sagernet/sing v0.1.2 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.1.0 // indirect
|
||||
|
9
go.sum
9
go.sum
@ -104,10 +104,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4=
|
||||
github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -546,8 +543,6 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2 h1:dq90+d51/hQRaHEqRAsQ1rE/pC1GUS4sc2rCbbFsAIY=
|
||||
github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA=
|
||||
github.com/refraction-networking/utls v1.2.0 h1:U5f8wkij2NVinfLuJdFP3gCMwIHs+EzvhxmYdXgiapo=
|
||||
github.com/refraction-networking/utls v1.2.0/go.mod h1:NPq+cVqzH7D1BeOkmOcb5O/8iVewAsiVt2x1/eO0hgQ=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||
@ -559,8 +554,6 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po
|
||||
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sacloud/libsacloud v1.36.2 h1:aosI7clbQ9IU0Hj+3rpk3SKJop5nLPpLThnWCivPqjI=
|
||||
github.com/sacloud/libsacloud v1.36.2/go.mod h1:P7YAOVmnIn3DKHqCZcUKYUXmSwGBm3yS7IBEjKVSrjg=
|
||||
@ -661,8 +654,6 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk=
|
||||
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"github.com/Yuzuki616/V2bX/core"
|
||||
"github.com/xtls/xray-core/common/task"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Node struct {
|
||||
@ -22,8 +21,9 @@ type Node struct {
|
||||
ipRecorder iprecoder.IpRecorder
|
||||
nodeInfoMonitorPeriodic *task.Periodic
|
||||
userReportPeriodic *task.Periodic
|
||||
renewCertPeriodic *task.Periodic
|
||||
dynamicSpeedLimitPeriodic *task.Periodic
|
||||
onlineIpReportPeriodic *task.Periodic
|
||||
DynamicSpeedLimitPeriodic *task.Periodic
|
||||
*conf.ControllerConfig
|
||||
}
|
||||
|
||||
@ -73,62 +73,7 @@ func (c *Node) Start() error {
|
||||
log.Printf("Update rule filed: %s", err)
|
||||
}
|
||||
}
|
||||
// fetch node info task
|
||||
c.nodeInfoMonitorPeriodic = &task.Periodic{
|
||||
Interval: time.Duration(c.nodeInfo.BaseConfig.PullInterval.(int)) * time.Second,
|
||||
Execute: c.nodeInfoMonitor,
|
||||
}
|
||||
// fetch user list task
|
||||
c.userReportPeriodic = &task.Periodic{
|
||||
Interval: time.Duration(c.nodeInfo.BaseConfig.PushInterval.(int)) * time.Second,
|
||||
Execute: c.reportUserTraffic,
|
||||
}
|
||||
log.Printf("[%s: %d] Start monitor node status", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
|
||||
// delay to start nodeInfoMonitor
|
||||
go func() {
|
||||
time.Sleep(time.Duration(c.nodeInfo.BaseConfig.PullInterval.(int)) * time.Second)
|
||||
_ = c.nodeInfoMonitorPeriodic.Start()
|
||||
}()
|
||||
|
||||
log.Printf("[%s: %d] Start report node status", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
|
||||
// delay to start userReport
|
||||
go func() {
|
||||
time.Sleep(time.Duration(c.nodeInfo.BaseConfig.PushInterval.(int)) * time.Second)
|
||||
_ = c.userReportPeriodic.Start()
|
||||
}()
|
||||
if c.EnableDynamicSpeedLimit {
|
||||
// Check dynamic speed limit task
|
||||
c.DynamicSpeedLimitPeriodic = &task.Periodic{
|
||||
Interval: time.Duration(c.DynamicSpeedLimitConfig.Periodic) * time.Second,
|
||||
Execute: c.dynamicSpeedLimit,
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(time.Duration(c.DynamicSpeedLimitConfig.Periodic) * time.Second)
|
||||
_ = c.DynamicSpeedLimitPeriodic.Start()
|
||||
}()
|
||||
log.Printf("[%s: %d] Start dynamic speed limit", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
|
||||
}
|
||||
if c.EnableIpRecorder {
|
||||
switch c.IpRecorderConfig.Type {
|
||||
case "Recorder":
|
||||
c.ipRecorder = iprecoder.NewRecorder(c.IpRecorderConfig.RecorderConfig)
|
||||
case "Redis":
|
||||
c.ipRecorder = iprecoder.NewRedis(c.IpRecorderConfig.RedisConfig)
|
||||
default:
|
||||
log.Printf("recorder type: %s is not vail, disable recorder", c.IpRecorderConfig.Type)
|
||||
return nil
|
||||
}
|
||||
// report and fetch online ip list task
|
||||
c.onlineIpReportPeriodic = &task.Periodic{
|
||||
Interval: time.Duration(c.IpRecorderConfig.Periodic) * time.Second,
|
||||
Execute: c.reportOnlineIp,
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(time.Duration(c.IpRecorderConfig.Periodic) * time.Second)
|
||||
_ = c.onlineIpReportPeriodic.Start()
|
||||
}()
|
||||
log.Printf("[%s: %d] Start report online ip", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
|
||||
}
|
||||
c.initTask()
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -146,18 +91,24 @@ func (c *Node) Close() error {
|
||||
log.Panicf("user report periodic close failed: %s", err)
|
||||
}
|
||||
}
|
||||
if c.renewCertPeriodic != nil {
|
||||
err := c.renewCertPeriodic.Close()
|
||||
if err != nil {
|
||||
log.Panicf("renew cert periodic close failed: %s", err)
|
||||
}
|
||||
}
|
||||
if c.dynamicSpeedLimitPeriodic != nil {
|
||||
err := c.dynamicSpeedLimitPeriodic.Close()
|
||||
if err != nil {
|
||||
log.Panicf("dynamic speed limit periodic close failed: %s", err)
|
||||
}
|
||||
}
|
||||
if c.onlineIpReportPeriodic != nil {
|
||||
err := c.onlineIpReportPeriodic.Close()
|
||||
if err != nil {
|
||||
log.Panicf("online ip report periodic close failed: %s", err)
|
||||
}
|
||||
}
|
||||
if c.DynamicSpeedLimitPeriodic != nil {
|
||||
err := c.DynamicSpeedLimitPeriodic.Close()
|
||||
if err != nil {
|
||||
log.Panicf("dynamic speed limit periodic close failed: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -6,8 +6,9 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/Yuzuki616/V2bX/api/panel"
|
||||
"github.com/Yuzuki616/V2bX/common/file"
|
||||
"github.com/Yuzuki616/V2bX/conf"
|
||||
"github.com/Yuzuki616/V2bX/node/controller/legoCmd"
|
||||
"github.com/Yuzuki616/V2bX/node/controller/lego"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/core"
|
||||
@ -220,31 +221,25 @@ func buildShadowsocks(config *conf.ControllerConfig, nodeInfo *panel.NodeInfo, i
|
||||
}
|
||||
|
||||
func getCertFile(certConfig *conf.CertConfig) (certFile string, keyFile string, err error) {
|
||||
if certConfig.CertMode == "file" {
|
||||
if certConfig.CertFile == "" || certConfig.KeyFile == "" {
|
||||
return "", "", fmt.Errorf("cert file path or key file path not exist")
|
||||
}
|
||||
switch certConfig.CertMode {
|
||||
case "file":
|
||||
return certConfig.CertFile, certConfig.KeyFile, nil
|
||||
case "dns", "http":
|
||||
if file.IsExist(certConfig.CertFile) && file.IsExist(certConfig.KeyFile) {
|
||||
return certConfig.CertFile, certConfig.KeyFile, nil
|
||||
} else if certConfig.CertMode == "dns" {
|
||||
lego, err := legoCmd.New()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
certPath, keyPath, err := lego.DNSCert(certConfig.CertDomain, certConfig.Email, certConfig.Provider, certConfig.DNSEnv)
|
||||
l, err := lego.New(certConfig)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return "", "", fmt.Errorf("create lego object error: %s", err)
|
||||
}
|
||||
return certPath, keyPath, err
|
||||
} else if certConfig.CertMode == "http" {
|
||||
lego, err := legoCmd.New()
|
||||
err = l.CreateCert()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return "", "", fmt.Errorf("create cert error: %s", err)
|
||||
}
|
||||
certPath, keyPath, err := lego.HTTPCert(certConfig.CertDomain, certConfig.Email)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return certPath, keyPath, err
|
||||
return certConfig.CertFile, certConfig.KeyFile, nil
|
||||
}
|
||||
return "", "", fmt.Errorf("unsupported certmode: %s", certConfig.CertMode)
|
||||
}
|
||||
|
110
node/controller/lego/cert.go
Normal file
110
node/controller/lego/cert.go
Normal file
@ -0,0 +1,110 @@
|
||||
package lego
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Yuzuki616/V2bX/common/file"
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/challenge/http01"
|
||||
"github.com/go-acme/lego/v4/providers/dns"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (l *Lego) SetProvider() error {
|
||||
switch l.config.CertMode {
|
||||
case "http":
|
||||
err := l.client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "5002"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "dns":
|
||||
for k, v := range l.config.DNSEnv {
|
||||
os.Setenv(k, v)
|
||||
}
|
||||
p, err := dns.NewDNSChallengeProviderByName(l.config.Provider)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create dns challenge provider error: %s", err)
|
||||
}
|
||||
err = l.client.Challenge.SetDNS01Provider(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("set dns provider error: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Lego) CreateCert() (err error) {
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: []string{l.config.CertDomain},
|
||||
}
|
||||
certificates, err := l.client.Certificate.Obtain(request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("obtain certificate error: %s", err)
|
||||
}
|
||||
err = l.writeCert(certificates)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Lego) RenewCert() error {
|
||||
file, err := os.ReadFile(l.config.CertFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read cert file error: %s", err)
|
||||
}
|
||||
if e, err := l.CheckCert(file); !e {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("check cert error: %s", err)
|
||||
}
|
||||
res, err := l.client.Certificate.Renew(certificate.Resource{
|
||||
Domain: l.config.CertDomain,
|
||||
Certificate: file,
|
||||
}, false, false, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = l.writeCert(res)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Lego) CheckCert(file []byte) (bool, error) {
|
||||
cert, err := certcrypto.ParsePEMCertificate(file)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
notAfter := int(time.Until(cert.NotAfter).Hours() / 24.0)
|
||||
if notAfter > 30 {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
func (l *Lego) parseParmas(path string) string {
|
||||
r := strings.NewReplacer("{domain}", l.config.CertDomain,
|
||||
"{email}", l.config.Email)
|
||||
return r.Replace(path)
|
||||
}
|
||||
func (l *Lego) writeCert(certificates *certificate.Resource) error {
|
||||
if !file.IsExist(path.Dir(l.config.CertFile)) {
|
||||
err := os.MkdirAll(path.Dir(l.config.CertFile), 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create dir error: %s", err)
|
||||
}
|
||||
}
|
||||
err := os.WriteFile(l.parseParmas(l.config.CertFile), certificates.Certificate, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !file.IsExist(path.Dir(l.config.KeyFile)) {
|
||||
err := os.MkdirAll(path.Dir(l.config.CertFile), 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create dir error: %s", err)
|
||||
}
|
||||
}
|
||||
err = os.WriteFile(l.parseParmas(l.config.KeyFile), certificates.PrivateKey, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
51
node/controller/lego/lego.go
Normal file
51
node/controller/lego/lego.go
Normal file
@ -0,0 +1,51 @@
|
||||
package lego
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Yuzuki616/V2bX/common/file"
|
||||
"github.com/Yuzuki616/V2bX/conf"
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
type Lego struct {
|
||||
client *lego.Client
|
||||
config *conf.CertConfig
|
||||
}
|
||||
|
||||
func New(config *conf.CertConfig) (*Lego, error) {
|
||||
user, err := NewUser(path.Join("/etc/V2bX/cert/user/",
|
||||
fmt.Sprintf("user-%s.json", config.Email)),
|
||||
config.Email)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create user error: %s", err)
|
||||
}
|
||||
c := lego.NewConfig(user)
|
||||
//c.CADirURL = "http://192.168.99.100:4000/directory"
|
||||
c.Certificate.KeyType = certcrypto.RSA2048
|
||||
client, err := lego.NewClient(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l := Lego{
|
||||
client: client,
|
||||
config: config,
|
||||
}
|
||||
err = l.SetProvider()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("set provider error: %s", err)
|
||||
}
|
||||
return &l, nil
|
||||
}
|
||||
|
||||
func checkPath(p string) error {
|
||||
if !file.IsExist(path.Dir(p)) {
|
||||
err := os.MkdirAll(path.Dir(p), 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create dir error: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
40
node/controller/lego/lego_test.go
Normal file
40
node/controller/lego/lego_test.go
Normal file
@ -0,0 +1,40 @@
|
||||
package lego
|
||||
|
||||
import (
|
||||
"github.com/Yuzuki616/V2bX/conf"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var l *Lego
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
l, err = New(&conf.CertConfig{
|
||||
CertMode: "dns",
|
||||
Email: "test@test.com",
|
||||
CertDomain: "test.test.com",
|
||||
Provider: "cloudflare",
|
||||
DNSEnv: map[string]string{
|
||||
"CF_DNS_API_TOKEN": "123",
|
||||
},
|
||||
CertFile: "./cert/1.pem",
|
||||
KeyFile: "./cert/1.key",
|
||||
})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLego_CreateCertByDns(t *testing.T) {
|
||||
err := l.CreateCert()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLego_RenewCert(t *testing.T) {
|
||||
log.Println(l.RenewCert())
|
||||
}
|
129
node/controller/lego/user.go
Normal file
129
node/controller/lego/user.go
Normal file
@ -0,0 +1,129 @@
|
||||
package lego
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"github.com/Yuzuki616/V2bX/common/file"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"github.com/goccy/go-json"
|
||||
"os"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Email string `json:"Email"`
|
||||
Registration *registration.Resource `json:"Registration"`
|
||||
key crypto.PrivateKey
|
||||
KeyEncoded string `json:"Key"`
|
||||
}
|
||||
|
||||
func (u *User) GetEmail() string {
|
||||
return u.Email
|
||||
}
|
||||
func (u *User) GetRegistration() *registration.Resource {
|
||||
return u.Registration
|
||||
}
|
||||
func (u *User) GetPrivateKey() crypto.PrivateKey {
|
||||
return u.key
|
||||
}
|
||||
|
||||
func NewUser(path string, email string) (*User, error) {
|
||||
var user User
|
||||
if file.IsExist(path) {
|
||||
err := user.Load(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user.Email != email {
|
||||
user.Registration = nil
|
||||
user.Email = email
|
||||
err := registerUser(&user, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
user.Email = email
|
||||
err := registerUser(&user, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func registerUser(user *User, path string) error {
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generate key error: %s", err)
|
||||
}
|
||||
user.key = privateKey
|
||||
c := lego.NewConfig(user)
|
||||
client, err := lego.NewClient(c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create lego client error: %s", err)
|
||||
}
|
||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user.Registration = reg
|
||||
err = user.Save(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("save user error: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func EncodePrivate(privKey *ecdsa.PrivateKey) (string, error) {
|
||||
encoded, err := x509.MarshalECPrivateKey(privKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pemEncoded := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: encoded})
|
||||
return string(pemEncoded), nil
|
||||
}
|
||||
func (u *User) Save(path string) error {
|
||||
err := checkPath(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check path error: %s", err)
|
||||
}
|
||||
u.KeyEncoded, _ = EncodePrivate(u.key.(*ecdsa.PrivateKey))
|
||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.NewEncoder(f).Encode(u)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal json error: %s", err)
|
||||
}
|
||||
u.KeyEncoded = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) DecodePrivate(pemEncodedPriv string) (*ecdsa.PrivateKey, error) {
|
||||
blockPriv, _ := pem.Decode([]byte(pemEncodedPriv))
|
||||
x509EncodedPriv := blockPriv.Bytes
|
||||
privateKey, err := x509.ParseECPrivateKey(x509EncodedPriv)
|
||||
return privateKey, err
|
||||
}
|
||||
func (u *User) Load(path string) error {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open file error: %s", err)
|
||||
}
|
||||
err = json.NewDecoder(f).Decode(u)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal json error: %s", err)
|
||||
}
|
||||
u.key, err = u.DecodePrivate(u.KeyEncoded)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode private key error: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
)
|
||||
|
||||
// Account represents a users local saved credentials.
|
||||
type Account struct {
|
||||
Email string `json:"email"`
|
||||
Registration *registration.Resource `json:"registration"`
|
||||
key crypto.PrivateKey
|
||||
}
|
||||
|
||||
/** Implementation of the registration.User interface **/
|
||||
|
||||
// GetEmail returns the email address for the account.
|
||||
func (a *Account) GetEmail() string {
|
||||
return a.Email
|
||||
}
|
||||
|
||||
// GetPrivateKey returns the private RSA account key.
|
||||
func (a *Account) GetPrivateKey() crypto.PrivateKey {
|
||||
return a.key
|
||||
}
|
||||
|
||||
// GetRegistration returns the server registration.
|
||||
func (a *Account) GetRegistration() *registration.Resource {
|
||||
return a.Registration
|
||||
}
|
||||
|
||||
/** End **/
|
@ -1,242 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Yuzuki616/V2bX/node/controller/legoCmd/log"
|
||||
"github.com/goccy/go-json"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const (
|
||||
baseAccountsRootFolderName = "accounts"
|
||||
baseKeysFolderName = "keys"
|
||||
accountFileName = "account.json"
|
||||
)
|
||||
|
||||
// AccountsStorage A storage for account data.
|
||||
//
|
||||
// rootPath:
|
||||
//
|
||||
// ./.lego/accounts/
|
||||
// │ └── root accounts directory
|
||||
// └── "path" option
|
||||
//
|
||||
// rootUserPath:
|
||||
//
|
||||
// ./.lego/accounts/localhost_14000/hubert@hubert.com/
|
||||
// │ │ │ └── userID ("email" option)
|
||||
// │ │ └── CA server ("server" option)
|
||||
// │ └── root accounts directory
|
||||
// └── "path" option
|
||||
//
|
||||
// keysPath:
|
||||
//
|
||||
// ./.lego/accounts/localhost_14000/hubert@hubert.com/keys/
|
||||
// │ │ │ │ └── root keys directory
|
||||
// │ │ │ └── userID ("email" option)
|
||||
// │ │ └── CA server ("server" option)
|
||||
// │ └── root accounts directory
|
||||
// └── "path" option
|
||||
//
|
||||
// accountFilePath:
|
||||
//
|
||||
// ./.lego/accounts/localhost_14000/hubert@hubert.com/account.json
|
||||
// │ │ │ │ └── account file
|
||||
// │ │ │ └── userID ("email" option)
|
||||
// │ │ └── CA server ("server" option)
|
||||
// │ └── root accounts directory
|
||||
// └── "path" option
|
||||
type AccountsStorage struct {
|
||||
userID string
|
||||
rootPath string
|
||||
rootUserPath string
|
||||
keysPath string
|
||||
accountFilePath string
|
||||
ctx *cli.Context
|
||||
}
|
||||
|
||||
// NewAccountsStorage Creates a new AccountsStorage.
|
||||
func NewAccountsStorage(ctx *cli.Context) *AccountsStorage {
|
||||
// TODO: move to account struct? Currently MUST pass email.
|
||||
email := getEmail(ctx)
|
||||
|
||||
serverURL, err := url.Parse(ctx.GlobalString("server"))
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
rootPath := filepath.Join(ctx.GlobalString("path"), baseAccountsRootFolderName)
|
||||
serverPath := strings.NewReplacer(":", "_", "/", string(os.PathSeparator)).Replace(serverURL.Host)
|
||||
accountsPath := filepath.Join(rootPath, serverPath)
|
||||
rootUserPath := filepath.Join(accountsPath, email)
|
||||
|
||||
return &AccountsStorage{
|
||||
userID: email,
|
||||
rootPath: rootPath,
|
||||
rootUserPath: rootUserPath,
|
||||
keysPath: filepath.Join(rootUserPath, baseKeysFolderName),
|
||||
accountFilePath: filepath.Join(rootUserPath, accountFileName),
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AccountsStorage) ExistsAccountFilePath() bool {
|
||||
accountFile := filepath.Join(s.rootUserPath, accountFileName)
|
||||
if _, err := os.Stat(accountFile); os.IsNotExist(err) {
|
||||
return false
|
||||
} else if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *AccountsStorage) GetRootPath() string {
|
||||
return s.rootPath
|
||||
}
|
||||
|
||||
func (s *AccountsStorage) GetRootUserPath() string {
|
||||
return s.rootUserPath
|
||||
}
|
||||
|
||||
func (s *AccountsStorage) GetUserID() string {
|
||||
return s.userID
|
||||
}
|
||||
|
||||
func (s *AccountsStorage) Save(account *Account) error {
|
||||
jsonBytes, err := json.MarshalIndent(account, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(s.accountFilePath, jsonBytes, filePerm)
|
||||
}
|
||||
|
||||
func (s *AccountsStorage) LoadAccount(privateKey crypto.PrivateKey) *Account {
|
||||
fileBytes, err := ioutil.ReadFile(s.accountFilePath)
|
||||
if err != nil {
|
||||
log.Panicf("Could not load file for account %s: %v", s.userID, err)
|
||||
}
|
||||
|
||||
var account Account
|
||||
err = json.Unmarshal(fileBytes, &account)
|
||||
if err != nil {
|
||||
log.Panicf("Could not parse file for account %s: %v", s.userID, err)
|
||||
}
|
||||
|
||||
account.key = privateKey
|
||||
|
||||
if account.Registration == nil || account.Registration.Body.Status == "" {
|
||||
reg, err := tryRecoverRegistration(s.ctx, privateKey)
|
||||
if err != nil {
|
||||
log.Panicf("Could not load account for %s. Registration is nil: %#v", s.userID, err)
|
||||
}
|
||||
|
||||
account.Registration = reg
|
||||
err = s.Save(&account)
|
||||
if err != nil {
|
||||
log.Panicf("Could not save account for %s. Registration is nil: %#v", s.userID, err)
|
||||
}
|
||||
}
|
||||
|
||||
return &account
|
||||
}
|
||||
|
||||
func (s *AccountsStorage) GetPrivateKey(keyType certcrypto.KeyType) crypto.PrivateKey {
|
||||
accKeyPath := filepath.Join(s.keysPath, s.userID+".key")
|
||||
|
||||
if _, err := os.Stat(accKeyPath); os.IsNotExist(err) {
|
||||
log.Printf("No key found for account %s. Generating a %s key.", s.userID, keyType)
|
||||
s.createKeysFolder()
|
||||
|
||||
privateKey, err := generatePrivateKey(accKeyPath, keyType)
|
||||
if err != nil {
|
||||
log.Panicf("Could not generate RSA private account key for account %s: %v", s.userID, err)
|
||||
}
|
||||
|
||||
log.Printf("Saved key to %s", accKeyPath)
|
||||
return privateKey
|
||||
}
|
||||
|
||||
privateKey, err := loadPrivateKey(accKeyPath)
|
||||
if err != nil {
|
||||
log.Panicf("Could not load RSA private key from file %s: %v", accKeyPath, err)
|
||||
}
|
||||
|
||||
return privateKey
|
||||
}
|
||||
|
||||
func (s *AccountsStorage) createKeysFolder() {
|
||||
if err := createNonExistingFolder(s.keysPath); err != nil {
|
||||
log.Panicf("Could not check/create directory for account %s: %v", s.userID, err)
|
||||
}
|
||||
}
|
||||
|
||||
func generatePrivateKey(file string, keyType certcrypto.KeyType) (crypto.PrivateKey, error) {
|
||||
privateKey, err := certcrypto.GeneratePrivateKey(keyType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certOut, err := os.Create(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer certOut.Close()
|
||||
|
||||
pemKey := certcrypto.PEMBlock(privateKey)
|
||||
err = pem.Encode(certOut, pemKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
func loadPrivateKey(file string) (crypto.PrivateKey, error) {
|
||||
keyBytes, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyBlock, _ := pem.Decode(keyBytes)
|
||||
|
||||
switch keyBlock.Type {
|
||||
case "RSA PRIVATE KEY":
|
||||
return x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
|
||||
case "EC PRIVATE KEY":
|
||||
return x509.ParseECPrivateKey(keyBlock.Bytes)
|
||||
}
|
||||
|
||||
return nil, errors.New("unknown private key type")
|
||||
}
|
||||
|
||||
func tryRecoverRegistration(ctx *cli.Context, privateKey crypto.PrivateKey) (*registration.Resource, error) {
|
||||
// couldn't load account but got a key. Try to look the account up.
|
||||
config := lego.NewConfig(&Account{key: privateKey})
|
||||
config.CADirURL = ctx.GlobalString("server")
|
||||
config.UserAgent = fmt.Sprintf("lego-cli/%s", ctx.App.Version)
|
||||
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reg, err := client.Registration.ResolveAccountByKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return reg, nil
|
||||
}
|
@ -1,204 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"github.com/Yuzuki616/V2bX/node/controller/legoCmd/log"
|
||||
"github.com/goccy/go-json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/urfave/cli"
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
const (
|
||||
baseCertificatesFolderName = "certificates"
|
||||
baseArchivesFolderName = "archives"
|
||||
)
|
||||
|
||||
// CertificatesStorage a certificates storage.
|
||||
//
|
||||
// rootPath:
|
||||
//
|
||||
// ./.lego/certificates/
|
||||
// │ └── root certificates directory
|
||||
// └── "path" option
|
||||
//
|
||||
// archivePath:
|
||||
//
|
||||
// ./.lego/archives/
|
||||
// │ └── archived certificates directory
|
||||
// └── "path" option
|
||||
type CertificatesStorage struct {
|
||||
rootPath string
|
||||
archivePath string
|
||||
pem bool
|
||||
filename string // Deprecated
|
||||
}
|
||||
|
||||
// NewCertificatesStorage create a new certificates storage.
|
||||
func NewCertificatesStorage(ctx *cli.Context) *CertificatesStorage {
|
||||
return &CertificatesStorage{
|
||||
rootPath: filepath.Join(ctx.GlobalString("path"), baseCertificatesFolderName),
|
||||
archivePath: filepath.Join(ctx.GlobalString("path"), baseArchivesFolderName),
|
||||
pem: ctx.GlobalBool("pem"),
|
||||
filename: ctx.GlobalString("filename"),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CertificatesStorage) CreateRootFolder() {
|
||||
err := createNonExistingFolder(s.rootPath)
|
||||
if err != nil {
|
||||
log.Panicf("Could not check/create path: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CertificatesStorage) CreateArchiveFolder() {
|
||||
err := createNonExistingFolder(s.archivePath)
|
||||
if err != nil {
|
||||
log.Panicf("Could not check/create path: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CertificatesStorage) GetRootPath() string {
|
||||
return s.rootPath
|
||||
}
|
||||
|
||||
func (s *CertificatesStorage) SaveResource(certRes *certificate.Resource) {
|
||||
domain := certRes.Domain
|
||||
|
||||
// We store the certificate, private key and metadata in different files
|
||||
// as web servers would not be able to work with a combined file.
|
||||
err := s.WriteFile(domain, ".crt", certRes.Certificate)
|
||||
if err != nil {
|
||||
log.Panicf("Unable to save Certificate for domain %s\n\t%v", domain, err)
|
||||
}
|
||||
|
||||
if certRes.IssuerCertificate != nil {
|
||||
err = s.WriteFile(domain, ".issuer.crt", certRes.IssuerCertificate)
|
||||
if err != nil {
|
||||
log.Panicf("Unable to save IssuerCertificate for domain %s\n\t%v", domain, err)
|
||||
}
|
||||
}
|
||||
|
||||
if certRes.PrivateKey != nil {
|
||||
// if we were given a CSR, we don't know the private key
|
||||
err = s.WriteFile(domain, ".key", certRes.PrivateKey)
|
||||
if err != nil {
|
||||
log.Panicf("Unable to save PrivateKey for domain %s\n\t%v", domain, err)
|
||||
}
|
||||
|
||||
if s.pem {
|
||||
err = s.WriteFile(domain, ".pem", bytes.Join([][]byte{certRes.Certificate, certRes.PrivateKey}, nil))
|
||||
if err != nil {
|
||||
log.Panicf("Unable to save Certificate and PrivateKey in .pem for domain %s\n\t%v", domain, err)
|
||||
}
|
||||
}
|
||||
} else if s.pem {
|
||||
// we don't have the private key; can't write the .pem file
|
||||
log.Panicf("Unable to save pem without private key for domain %s\n\t%v; are you using a CSR?", domain, err)
|
||||
}
|
||||
|
||||
jsonBytes, err := json.MarshalIndent(certRes, "", "\t")
|
||||
if err != nil {
|
||||
log.Panicf("Unable to marshal CertResource for domain %s\n\t%v", domain, err)
|
||||
}
|
||||
|
||||
err = s.WriteFile(domain, ".json", jsonBytes)
|
||||
if err != nil {
|
||||
log.Panicf("Unable to save CertResource for domain %s\n\t%v", domain, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CertificatesStorage) ReadResource(domain string) certificate.Resource {
|
||||
raw, err := s.ReadFile(domain, ".json")
|
||||
if err != nil {
|
||||
log.Panicf("Error while loading the meta data for domain %s\n\t%v", domain, err)
|
||||
}
|
||||
|
||||
var resource certificate.Resource
|
||||
if err = json.Unmarshal(raw, &resource); err != nil {
|
||||
log.Panicf("Error while marshaling the meta data for domain %s\n\t%v", domain, err)
|
||||
}
|
||||
|
||||
return resource
|
||||
}
|
||||
|
||||
func (s *CertificatesStorage) ExistsFile(domain, extension string) bool {
|
||||
filePath := s.GetFileName(domain, extension)
|
||||
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
return false
|
||||
} else if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *CertificatesStorage) ReadFile(domain, extension string) ([]byte, error) {
|
||||
return ioutil.ReadFile(s.GetFileName(domain, extension))
|
||||
}
|
||||
|
||||
func (s *CertificatesStorage) GetFileName(domain, extension string) string {
|
||||
filename := sanitizedDomain(domain) + extension
|
||||
return filepath.Join(s.rootPath, filename)
|
||||
}
|
||||
|
||||
func (s *CertificatesStorage) ReadCertificate(domain, extension string) ([]*x509.Certificate, error) {
|
||||
content, err := s.ReadFile(domain, extension)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The input may be a bundle or a single certificate.
|
||||
return certcrypto.ParsePEMBundle(content)
|
||||
}
|
||||
|
||||
func (s *CertificatesStorage) WriteFile(domain, extension string, data []byte) error {
|
||||
var baseFileName string
|
||||
if s.filename != "" {
|
||||
baseFileName = s.filename
|
||||
} else {
|
||||
baseFileName = sanitizedDomain(domain)
|
||||
}
|
||||
|
||||
filePath := filepath.Join(s.rootPath, baseFileName+extension)
|
||||
|
||||
return ioutil.WriteFile(filePath, data, filePerm)
|
||||
}
|
||||
|
||||
func (s *CertificatesStorage) MoveToArchive(domain string) error {
|
||||
matches, err := filepath.Glob(filepath.Join(s.rootPath, sanitizedDomain(domain)+".*"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, oldFile := range matches {
|
||||
date := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
filename := date + "." + filepath.Base(oldFile)
|
||||
newFile := filepath.Join(s.archivePath, filename)
|
||||
|
||||
err = os.Rename(oldFile, newFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sanitizedDomain Make sure no funny chars are in the cert names (like wildcards ;)).
|
||||
func sanitizedDomain(domain string) string {
|
||||
safe, err := idna.ToASCII(strings.ReplaceAll(domain, "*", "_"))
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
return safe
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
// CreateCommands Creates all CLI commands.
|
||||
func CreateCommands() []cli.Command {
|
||||
return []cli.Command{
|
||||
createRun(),
|
||||
createRevoke(),
|
||||
createRenew(),
|
||||
createDNSHelp(),
|
||||
createList(),
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/Yuzuki616/V2bX/node/controller/legoCmd/log"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func Before(ctx *cli.Context) error {
|
||||
if ctx.GlobalString("path") == "" {
|
||||
log.Panic("Could not determine current working directory. Please pass --path.")
|
||||
}
|
||||
|
||||
err := createNonExistingFolder(ctx.GlobalString("path"))
|
||||
if err != nil {
|
||||
log.Panicf("Could not check/create path: %v", err)
|
||||
}
|
||||
|
||||
if ctx.GlobalString("server") == "" {
|
||||
log.Panic("Could not determine current working server. Please pass --server.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func createDNSHelp() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "dnshelp",
|
||||
Usage: "Shows additional help for the '--dns' global option",
|
||||
Action: dnsHelp,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "code, c",
|
||||
Usage: fmt.Sprintf("DNS code: %s", allDNSCodes()),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func dnsHelp(ctx *cli.Context) error {
|
||||
code := ctx.String("code")
|
||||
if code == "" {
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
ew := &errWriter{w: w}
|
||||
|
||||
ew.writeln(`Credentials for DNS providers must be passed through environment variables.`)
|
||||
ew.writeln()
|
||||
ew.writeln(`To display the documentation for a DNS providers:`)
|
||||
ew.writeln()
|
||||
ew.writeln("\t$ lego dnshelp -c code")
|
||||
ew.writeln()
|
||||
ew.writeln("All DNS codes:")
|
||||
ew.writef("\t%s\n", allDNSCodes())
|
||||
ew.writeln()
|
||||
ew.writeln("More information: https://go-acme.github.io/lego/dns")
|
||||
|
||||
if ew.err != nil {
|
||||
return ew.err
|
||||
}
|
||||
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
return displayDNSHelp(strings.ToLower(code))
|
||||
}
|
||||
|
||||
type errWriter struct {
|
||||
w io.Writer
|
||||
err error
|
||||
}
|
||||
|
||||
func (ew *errWriter) writeln(a ...interface{}) {
|
||||
if ew.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, ew.err = fmt.Fprintln(ew.w, a...)
|
||||
}
|
||||
|
||||
func (ew *errWriter) writef(format string, a ...interface{}) {
|
||||
if ew.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, ew.err = fmt.Fprintf(ew.w, format, a...)
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/goccy/go-json"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func createList() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "list",
|
||||
Usage: "Display certificates and accounts information.",
|
||||
Action: list,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "accounts, a",
|
||||
Usage: "Display accounts.",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "names, n",
|
||||
Usage: "Display certificate common names only.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func list(ctx *cli.Context) error {
|
||||
if ctx.Bool("accounts") && !ctx.Bool("names") {
|
||||
if err := listAccount(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return listCertificates(ctx)
|
||||
}
|
||||
|
||||
func listCertificates(ctx *cli.Context) error {
|
||||
certsStorage := NewCertificatesStorage(ctx)
|
||||
|
||||
matches, err := filepath.Glob(filepath.Join(certsStorage.GetRootPath(), "*.crt"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
names := ctx.Bool("names")
|
||||
|
||||
if len(matches) == 0 {
|
||||
if !names {
|
||||
fmt.Println("No certificates found.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if !names {
|
||||
fmt.Println("Found the following certs:")
|
||||
}
|
||||
|
||||
for _, filename := range matches {
|
||||
if strings.HasSuffix(filename, ".issuer.crt") {
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pCert, err := certcrypto.ParsePEMCertificate(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if names {
|
||||
fmt.Println(pCert.Subject.CommonName)
|
||||
} else {
|
||||
fmt.Println(" Certificate Name:", pCert.Subject.CommonName)
|
||||
fmt.Println(" Domains:", strings.Join(pCert.DNSNames, ", "))
|
||||
fmt.Println(" Expiry Date:", pCert.NotAfter)
|
||||
fmt.Println(" Certificate Path:", filename)
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func listAccount(ctx *cli.Context) error {
|
||||
// fake email, needed by NewAccountsStorage
|
||||
if err := ctx.GlobalSet("email", "unknown"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accountsStorage := NewAccountsStorage(ctx)
|
||||
|
||||
matches, err := filepath.Glob(filepath.Join(accountsStorage.GetRootPath(), "*", "*", "*.json"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(matches) == 0 {
|
||||
fmt.Println("No accounts found.")
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println("Found the following accounts:")
|
||||
for _, filename := range matches {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var account Account
|
||||
err = json.Unmarshal(data, &account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uri, err := url.Parse(account.Registration.URI)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(" Email:", account.Email)
|
||||
fmt.Println(" Server:", uri.Host)
|
||||
fmt.Println(" Path:", filepath.Dir(filename))
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,225 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"github.com/Yuzuki616/V2bX/node/controller/legoCmd/log"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const (
|
||||
renewEnvAccountEmail = "LEGO_ACCOUNT_EMAIL"
|
||||
renewEnvCertDomain = "LEGO_CERT_DOMAIN"
|
||||
renewEnvCertPath = "LEGO_CERT_PATH"
|
||||
renewEnvCertKeyPath = "LEGO_CERT_KEY_PATH"
|
||||
)
|
||||
|
||||
func createRenew() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "renew",
|
||||
Usage: "Renew a certificate",
|
||||
Action: renew,
|
||||
Before: func(ctx *cli.Context) error {
|
||||
// we require either domains or csr, but not both
|
||||
hasDomains := len(ctx.GlobalStringSlice("domains")) > 0
|
||||
hasCsr := len(ctx.GlobalString("csr")) > 0
|
||||
if hasDomains && hasCsr {
|
||||
log.Panic("Please specify either --domains/-d or --csr/-c, but not both")
|
||||
}
|
||||
if !hasDomains && !hasCsr {
|
||||
log.Panic("Please specify --domains/-d (or --csr/-c if you already have a CSR)")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
cli.IntFlag{
|
||||
Name: "days",
|
||||
Value: 30,
|
||||
Usage: "The number of days left on a certificate to renew it.",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "reuse-key",
|
||||
Usage: "Used to indicate you want to reuse your current private key for the new certificate.",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-bundle",
|
||||
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "must-staple",
|
||||
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "renew-hook",
|
||||
Usage: "Define a hook. The hook is executed only when the certificates are effectively renewed.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "preferred-chain",
|
||||
Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func renew(ctx *cli.Context) error {
|
||||
account, client := setup(ctx, NewAccountsStorage(ctx))
|
||||
setupChallenges(ctx, client)
|
||||
|
||||
if account.Registration == nil {
|
||||
log.Panicf("Account %s is not registered. Use 'run' to register a new account.\n", account.Email)
|
||||
}
|
||||
|
||||
certsStorage := NewCertificatesStorage(ctx)
|
||||
|
||||
bundle := !ctx.Bool("no-bundle")
|
||||
|
||||
meta := map[string]string{renewEnvAccountEmail: account.Email}
|
||||
|
||||
// CSR
|
||||
if ctx.GlobalIsSet("csr") {
|
||||
return renewForCSR(ctx, client, certsStorage, bundle, meta)
|
||||
}
|
||||
|
||||
// Domains
|
||||
return renewForDomains(ctx, client, certsStorage, bundle, meta)
|
||||
}
|
||||
|
||||
func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error {
|
||||
domains := ctx.GlobalStringSlice("domains")
|
||||
domain := domains[0]
|
||||
|
||||
// load the cert resource from files.
|
||||
// We store the certificate, private key and metadata in different files
|
||||
// as web servers would not be able to work with a combined file.
|
||||
certificates, err := certsStorage.ReadCertificate(domain, ".crt")
|
||||
if err != nil {
|
||||
log.Panicf("Error while loading the certificate for domain %s\n\t%v", domain, err)
|
||||
}
|
||||
|
||||
cert := certificates[0]
|
||||
|
||||
if !needRenewal(cert, domain, ctx.Int("days")) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This is just meant to be informal for the user.
|
||||
timeLeft := cert.NotAfter.Sub(time.Now().UTC())
|
||||
log.Infof("[%s] acme: Trying renewal with %d hours remaining", domain, int(timeLeft.Hours()))
|
||||
|
||||
certDomains := certcrypto.ExtractDomains(cert)
|
||||
|
||||
var privateKey crypto.PrivateKey
|
||||
if ctx.Bool("reuse-key") {
|
||||
keyBytes, errR := certsStorage.ReadFile(domain, ".key")
|
||||
if errR != nil {
|
||||
log.Panicf("Error while loading the private key for domain %s\n\t%v", domain, errR)
|
||||
}
|
||||
|
||||
privateKey, errR = certcrypto.ParsePEMPrivateKey(keyBytes)
|
||||
if errR != nil {
|
||||
return errR
|
||||
}
|
||||
}
|
||||
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: merge(certDomains, domains),
|
||||
Bundle: bundle,
|
||||
PrivateKey: privateKey,
|
||||
MustStaple: ctx.Bool("must-staple"),
|
||||
PreferredChain: ctx.String("preferred-chain"),
|
||||
}
|
||||
certRes, err := client.Certificate.Obtain(request)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
certsStorage.SaveResource(certRes)
|
||||
|
||||
meta[renewEnvCertDomain] = domain
|
||||
meta[renewEnvCertPath] = certsStorage.GetFileName(domain, ".crt")
|
||||
meta[renewEnvCertKeyPath] = certsStorage.GetFileName(domain, ".key")
|
||||
|
||||
return launchHook(ctx.String("renew-hook"), meta)
|
||||
}
|
||||
|
||||
func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error {
|
||||
csr, err := readCSRFile(ctx.GlobalString("csr"))
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
domain := csr.Subject.CommonName
|
||||
|
||||
// load the cert resource from files.
|
||||
// We store the certificate, private key and metadata in different files
|
||||
// as web servers would not be able to work with a combined file.
|
||||
certificates, err := certsStorage.ReadCertificate(domain, ".crt")
|
||||
if err != nil {
|
||||
log.Panicf("Error while loading the certificate for domain %s\n\t%v", domain, err)
|
||||
}
|
||||
|
||||
cert := certificates[0]
|
||||
|
||||
if !needRenewal(cert, domain, ctx.Int("days")) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This is just meant to be informal for the user.
|
||||
timeLeft := cert.NotAfter.Sub(time.Now().UTC())
|
||||
log.Infof("[%s] acme: Trying renewal with %d hours remaining", domain, int(timeLeft.Hours()))
|
||||
|
||||
certRes, err := client.Certificate.ObtainForCSR(certificate.ObtainForCSRRequest{
|
||||
CSR: csr,
|
||||
Bundle: bundle,
|
||||
PreferredChain: ctx.String("preferred-chain"),
|
||||
})
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
certsStorage.SaveResource(certRes)
|
||||
|
||||
meta[renewEnvCertDomain] = domain
|
||||
meta[renewEnvCertPath] = certsStorage.GetFileName(domain, ".crt")
|
||||
meta[renewEnvCertKeyPath] = certsStorage.GetFileName(domain, ".key")
|
||||
|
||||
return launchHook(ctx.String("renew-hook"), meta)
|
||||
}
|
||||
|
||||
func needRenewal(x509Cert *x509.Certificate, domain string, days int) bool {
|
||||
if x509Cert.IsCA {
|
||||
log.Panicf("[%s] Certificate bundle starts with a CA certificate", domain)
|
||||
}
|
||||
|
||||
if days >= 0 {
|
||||
notAfter := int(time.Until(x509Cert.NotAfter).Hours() / 24.0)
|
||||
if notAfter > days {
|
||||
log.Printf("[%s] The certificate expires in %d days, the number of days defined to perform the renewal is %d: no renewal.",
|
||||
domain, notAfter, days)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func merge(prevDomains, nextDomains []string) []string {
|
||||
for _, next := range nextDomains {
|
||||
var found bool
|
||||
for _, prev := range prevDomains {
|
||||
if prev == next {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
prevDomains = append(prevDomains, next)
|
||||
}
|
||||
}
|
||||
return prevDomains
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_merge(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
prevDomains []string
|
||||
nextDomains []string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
desc: "all empty",
|
||||
prevDomains: []string{},
|
||||
nextDomains: []string{},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
desc: "next empty",
|
||||
prevDomains: []string{"a", "b", "c"},
|
||||
nextDomains: []string{},
|
||||
expected: []string{"a", "b", "c"},
|
||||
},
|
||||
{
|
||||
desc: "prev empty",
|
||||
prevDomains: []string{},
|
||||
nextDomains: []string{"a", "b", "c"},
|
||||
expected: []string{"a", "b", "c"},
|
||||
},
|
||||
{
|
||||
desc: "merge append",
|
||||
prevDomains: []string{"a", "b", "c"},
|
||||
nextDomains: []string{"a", "c", "d"},
|
||||
expected: []string{"a", "b", "c", "d"},
|
||||
},
|
||||
{
|
||||
desc: "merge same",
|
||||
prevDomains: []string{"a", "b", "c"},
|
||||
nextDomains: []string{"a", "b", "c"},
|
||||
expected: []string{"a", "b", "c"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := merge(test.prevDomains, test.nextDomains)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_needRenewal(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
x509Cert *x509.Certificate
|
||||
days int
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
desc: "30 days, NotAfter now",
|
||||
x509Cert: &x509.Certificate{
|
||||
NotAfter: time.Now(),
|
||||
},
|
||||
days: 30,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
desc: "30 days, NotAfter 31 days",
|
||||
x509Cert: &x509.Certificate{
|
||||
NotAfter: time.Now().Add(31*24*time.Hour + 1*time.Second),
|
||||
},
|
||||
days: 30,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
desc: "30 days, NotAfter 30 days",
|
||||
x509Cert: &x509.Certificate{
|
||||
NotAfter: time.Now().Add(30 * 24 * time.Hour),
|
||||
},
|
||||
days: 30,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
desc: "0 days, NotAfter 30 days: only the day of the expiration",
|
||||
x509Cert: &x509.Certificate{
|
||||
NotAfter: time.Now().Add(30 * 24 * time.Hour),
|
||||
},
|
||||
days: 0,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
desc: "-1 days, NotAfter 30 days: always renew",
|
||||
x509Cert: &x509.Certificate{
|
||||
NotAfter: time.Now().Add(30 * 24 * time.Hour),
|
||||
},
|
||||
days: -1,
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
actual := needRenewal(test.x509Cert, "foo.com", test.days)
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/Yuzuki616/V2bX/node/controller/legoCmd/log"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func createRevoke() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "revoke",
|
||||
Usage: "Revoke a certificate",
|
||||
Action: revoke,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "keep, k",
|
||||
Usage: "Keep the certificates after the revocation instead of archiving them.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func revoke(ctx *cli.Context) error {
|
||||
acc, client := setup(ctx, NewAccountsStorage(ctx))
|
||||
|
||||
if acc.Registration == nil {
|
||||
log.Panicf("Account %s is not registered. Use 'run' to register a new account.\n", acc.Email)
|
||||
}
|
||||
|
||||
certsStorage := NewCertificatesStorage(ctx)
|
||||
certsStorage.CreateRootFolder()
|
||||
|
||||
for _, domain := range ctx.GlobalStringSlice("domains") {
|
||||
log.Printf("Trying to revoke certificate for domain %s", domain)
|
||||
|
||||
certBytes, err := certsStorage.ReadFile(domain, ".crt")
|
||||
if err != nil {
|
||||
log.Panicf("Error while revoking the certificate for domain %s\n\t%v", domain, err)
|
||||
}
|
||||
|
||||
err = client.Certificate.Revoke(certBytes)
|
||||
if err != nil {
|
||||
log.Panicf("Error while revoking the certificate for domain %s\n\t%v", domain, err)
|
||||
}
|
||||
|
||||
log.Println("Certificate was revoked.")
|
||||
|
||||
if ctx.Bool("keep") {
|
||||
return nil
|
||||
}
|
||||
|
||||
certsStorage.CreateArchiveFolder()
|
||||
|
||||
err = certsStorage.MoveToArchive(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println("Certificate was archived for domain:", domain)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,186 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/Yuzuki616/V2bX/node/controller/legoCmd/log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func createRun() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "run",
|
||||
Usage: "Register an account, then create and install a certificate",
|
||||
Before: func(ctx *cli.Context) error {
|
||||
// we require either domains or csr, but not both
|
||||
hasDomains := len(ctx.GlobalStringSlice("domains")) > 0
|
||||
hasCsr := len(ctx.GlobalString("csr")) > 0
|
||||
if hasDomains && hasCsr {
|
||||
log.Panic("Please specify either --domains/-d or --csr/-c, but not both")
|
||||
}
|
||||
if !hasDomains && !hasCsr {
|
||||
log.Panic("Please specify --domains/-d (or --csr/-c if you already have a CSR)")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Action: run,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "no-bundle",
|
||||
Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "must-staple",
|
||||
Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "run-hook",
|
||||
Usage: "Define a hook. The hook is executed when the certificates are effectively created.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "preferred-chain",
|
||||
Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const rootPathWarningMessage = `!!!! HEADS UP !!!!
|
||||
|
||||
Your account credentials have been saved in your Let's Encrypt
|
||||
configuration directory at "%s".
|
||||
|
||||
You should make a secure backup of this folder now. This
|
||||
configuration directory will also contain certificates and
|
||||
private keys obtained from Let's Encrypt so making regular
|
||||
backups of this folder is ideal.
|
||||
`
|
||||
|
||||
func run(ctx *cli.Context) error {
|
||||
accountsStorage := NewAccountsStorage(ctx)
|
||||
|
||||
account, client := setup(ctx, accountsStorage)
|
||||
setupChallenges(ctx, client)
|
||||
|
||||
if account.Registration == nil {
|
||||
reg, err := register(ctx, client)
|
||||
if err != nil {
|
||||
log.Panicf("Could not complete registration\n\t%v", err)
|
||||
}
|
||||
|
||||
account.Registration = reg
|
||||
if err = accountsStorage.Save(account); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf(rootPathWarningMessage, accountsStorage.GetRootPath())
|
||||
}
|
||||
|
||||
certsStorage := NewCertificatesStorage(ctx)
|
||||
certsStorage.CreateRootFolder()
|
||||
|
||||
cert, err := obtainCertificate(ctx, client)
|
||||
if err != nil {
|
||||
// Make sure to return a non-zero exit code if ObtainSANCertificate returned at least one error.
|
||||
// Due to us not returning partial certificate we can just exit here instead of at the end.
|
||||
log.Panicf("Could not obtain certificates:\n\t%v", err)
|
||||
}
|
||||
|
||||
certsStorage.SaveResource(cert)
|
||||
|
||||
meta := map[string]string{
|
||||
renewEnvAccountEmail: account.Email,
|
||||
renewEnvCertDomain: cert.Domain,
|
||||
renewEnvCertPath: certsStorage.GetFileName(cert.Domain, ".crt"),
|
||||
renewEnvCertKeyPath: certsStorage.GetFileName(cert.Domain, ".key"),
|
||||
}
|
||||
|
||||
return launchHook(ctx.String("run-hook"), meta)
|
||||
}
|
||||
|
||||
func handleTOS(ctx *cli.Context, client *lego.Client) bool {
|
||||
// Check for a global accept override
|
||||
if ctx.GlobalBool("accept-tos") {
|
||||
return true
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
log.Printf("Please review the TOS at %s", client.GetToSURL())
|
||||
|
||||
for {
|
||||
fmt.Println("Do you accept the TOS? Y/n")
|
||||
text, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Panicf("Could not read from console: %v", err)
|
||||
}
|
||||
|
||||
text = strings.Trim(text, "\r\n")
|
||||
switch text {
|
||||
case "", "y", "Y":
|
||||
return true
|
||||
case "n", "N":
|
||||
return false
|
||||
default:
|
||||
fmt.Println("Your input was invalid. Please answer with one of Y/y, n/N or by pressing enter.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func register(ctx *cli.Context, client *lego.Client) (*registration.Resource, error) {
|
||||
accepted := handleTOS(ctx, client)
|
||||
if !accepted {
|
||||
log.Panic("You did not accept the TOS. Unable to proceed.")
|
||||
}
|
||||
|
||||
if ctx.GlobalBool("eab") {
|
||||
kid := ctx.GlobalString("kid")
|
||||
hmacEncoded := ctx.GlobalString("hmac")
|
||||
|
||||
if kid == "" || hmacEncoded == "" {
|
||||
log.Panicf("Requires arguments --kid and --hmac.")
|
||||
}
|
||||
|
||||
return client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
||||
TermsOfServiceAgreed: accepted,
|
||||
Kid: kid,
|
||||
HmacEncoded: hmacEncoded,
|
||||
})
|
||||
}
|
||||
|
||||
return client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
}
|
||||
|
||||
func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Resource, error) {
|
||||
bundle := !ctx.Bool("no-bundle")
|
||||
|
||||
domains := ctx.GlobalStringSlice("domains")
|
||||
if len(domains) > 0 {
|
||||
// obtain a certificate, generating a new private key
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: domains,
|
||||
Bundle: bundle,
|
||||
MustStaple: ctx.Bool("must-staple"),
|
||||
PreferredChain: ctx.String("preferred-chain"),
|
||||
}
|
||||
return client.Certificate.Obtain(request)
|
||||
}
|
||||
|
||||
// read the CSR
|
||||
csr, err := readCSRFile(ctx.GlobalString("csr"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// obtain a certificate for this CSR
|
||||
return client.Certificate.ObtainForCSR(certificate.ObtainForCSRRequest{
|
||||
CSR: csr,
|
||||
Bundle: bundle,
|
||||
PreferredChain: ctx.String("preferred-chain"),
|
||||
})
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func CreateFlags(defaultPath string) []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringSliceFlag{
|
||||
Name: "domains, d",
|
||||
Usage: "Add a domain to the process. Can be specified multiple times.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "server, s",
|
||||
Usage: "CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client.",
|
||||
Value: lego.LEDirectoryProduction,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "accept-tos, a",
|
||||
Usage: "By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "email, m",
|
||||
Usage: "Email used for registration and recovery contact.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "csr, c",
|
||||
Usage: "Certificate signing request filename, if an external CSR is to be used.",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "eab",
|
||||
Usage: "Use External Account Binding for account registration. Requires --kid and --hmac.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "kid",
|
||||
Usage: "Key identifier from External CA. Used for External Account Binding.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "hmac",
|
||||
Usage: "MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "key-type, k",
|
||||
Value: "ec256",
|
||||
Usage: "Key type to use for private keys. Supported: rsa2048, rsa4096, rsa8192, ec256, ec384.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "filename",
|
||||
Usage: "(deprecated) Filename of the generated certificate.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "path",
|
||||
EnvVar: "LEGO_PATH",
|
||||
Usage: "Directory to use for storing the data.",
|
||||
Value: defaultPath,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "http",
|
||||
Usage: "Use the HTTP challenge to solve challenges. Can be mixed with other types of challenges.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "http.port",
|
||||
Usage: "Set the port and interface to use for HTTP based challenges to listen on.Supported: interface:port or :port.",
|
||||
Value: ":80",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "http.proxy-header",
|
||||
Usage: "Validate against this HTTP header when solving HTTP based challenges behind a reverse proxy.",
|
||||
Value: "Host",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "http.webroot",
|
||||
Usage: "Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge. This disables the built-in server and expects the given directory to be publicly served with access to .well-known/acme-challenge",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "http.memcached-host",
|
||||
Usage: "Set the memcached host(s) to use for HTTP based challenges. Challenges will be written to all specified hosts.",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "tls",
|
||||
Usage: "Use the TLS challenge to solve challenges. Can be mixed with other types of challenges.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "tls.port",
|
||||
Usage: "Set the port and interface to use for TLS based challenges to listen on. Supported: interface:port or :port.",
|
||||
Value: ":443",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "dns",
|
||||
Usage: "Solve a DNS challenge using the specified provider. Can be mixed with other types of challenges. Run 'lego dnshelp' for help on usage.",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "dns.disable-cp",
|
||||
Usage: "By setting this flag to true, disables the need to wait the propagation of the TXT record to all authoritative name servers.",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "dns.resolvers",
|
||||
Usage: "Set the resolvers to use for performing recursive DNS queries. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "http-timeout",
|
||||
Usage: "Set the HTTP timeout value to a specific value in seconds.",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "dns-timeout",
|
||||
Usage: "Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name servers queries.",
|
||||
Value: 10,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "pem",
|
||||
Usage: "Generate a .pem file by concatenating the .key and .crt files together.",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "cert.timeout",
|
||||
Usage: "Set the certificate timeout value to a specific value in seconds. Only used when obtaining certificates.",
|
||||
Value: 30,
|
||||
},
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func launchHook(hook string, meta map[string]string) error {
|
||||
if hook == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ctxCmd, cancel := context.WithTimeout(context.Background(), 120*time.Second)
|
||||
defer cancel()
|
||||
|
||||
parts := strings.Fields(hook)
|
||||
|
||||
cmdCtx := exec.CommandContext(ctxCmd, parts[0], parts[1:]...)
|
||||
cmdCtx.Env = append(os.Environ(), metaToEnv(meta)...)
|
||||
|
||||
output, err := cmdCtx.CombinedOutput()
|
||||
|
||||
if len(output) > 0 {
|
||||
fmt.Println(string(output))
|
||||
}
|
||||
|
||||
if errors.Is(ctxCmd.Err(), context.DeadlineExceeded) {
|
||||
return errors.New("hook timed out")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func metaToEnv(meta map[string]string) []string {
|
||||
var envs []string
|
||||
|
||||
for k, v := range meta {
|
||||
envs = append(envs, k+"="+v)
|
||||
}
|
||||
|
||||
return envs
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"github.com/Yuzuki616/V2bX/node/controller/legoCmd/log"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const filePerm os.FileMode = 0o600
|
||||
|
||||
func setup(ctx *cli.Context, accountsStorage *AccountsStorage) (*Account, *lego.Client) {
|
||||
keyType := getKeyType(ctx)
|
||||
privateKey := accountsStorage.GetPrivateKey(keyType)
|
||||
|
||||
var account *Account
|
||||
if accountsStorage.ExistsAccountFilePath() {
|
||||
account = accountsStorage.LoadAccount(privateKey)
|
||||
} else {
|
||||
account = &Account{Email: accountsStorage.GetUserID(), key: privateKey}
|
||||
}
|
||||
|
||||
client := newClient(ctx, account, keyType)
|
||||
|
||||
return account, client
|
||||
}
|
||||
|
||||
func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyType) *lego.Client {
|
||||
config := lego.NewConfig(acc)
|
||||
config.CADirURL = ctx.GlobalString("server")
|
||||
|
||||
config.Certificate = lego.CertificateConfig{
|
||||
KeyType: keyType,
|
||||
Timeout: time.Duration(ctx.GlobalInt("cert.timeout")) * time.Second,
|
||||
}
|
||||
config.UserAgent = fmt.Sprintf("lego-cli/%s", ctx.App.Version)
|
||||
|
||||
if ctx.GlobalIsSet("http-timeout") {
|
||||
config.HTTPClient.Timeout = time.Duration(ctx.GlobalInt("http-timeout")) * time.Second
|
||||
}
|
||||
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
log.Panicf("Could not create client: %v", err)
|
||||
}
|
||||
|
||||
if client.GetExternalAccountRequired() && !ctx.GlobalIsSet("eab") {
|
||||
log.Panic("Server requires External Account Binding. Use --eab with --kid and --hmac.")
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
// getKeyType the type from which private keys should be generated.
|
||||
func getKeyType(ctx *cli.Context) certcrypto.KeyType {
|
||||
keyType := ctx.GlobalString("key-type")
|
||||
switch strings.ToUpper(keyType) {
|
||||
case "RSA2048":
|
||||
return certcrypto.RSA2048
|
||||
case "RSA4096":
|
||||
return certcrypto.RSA4096
|
||||
case "RSA8192":
|
||||
return certcrypto.RSA8192
|
||||
case "EC256":
|
||||
return certcrypto.EC256
|
||||
case "EC384":
|
||||
return certcrypto.EC384
|
||||
}
|
||||
|
||||
log.Panicf("Unsupported KeyType: %s", keyType)
|
||||
return ""
|
||||
}
|
||||
|
||||
func getEmail(ctx *cli.Context) string {
|
||||
email := ctx.GlobalString("email")
|
||||
if email == "" {
|
||||
log.Panic("You have to pass an account (email address) to the program using --email or -m")
|
||||
}
|
||||
return email
|
||||
}
|
||||
|
||||
func createNonExistingFolder(path string) error {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return os.MkdirAll(path, 0o700)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readCSRFile(filename string) (*x509.CertificateRequest, error) {
|
||||
bytes, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
raw := bytes
|
||||
|
||||
// see if we can find a PEM-encoded CSR
|
||||
var p *pem.Block
|
||||
rest := bytes
|
||||
for {
|
||||
// decode a PEM block
|
||||
p, rest = pem.Decode(rest)
|
||||
|
||||
// did we fail?
|
||||
if p == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// did we get a CSR?
|
||||
if p.Type == "CERTIFICATE REQUEST" {
|
||||
raw = p.Bytes
|
||||
}
|
||||
}
|
||||
|
||||
// no PEM-encoded CSR
|
||||
// assume we were given a DER-encoded ASN.1 CSR
|
||||
// (if this assumption is wrong, parsing these bytes will fail)
|
||||
return x509.ParseCertificateRequest(raw)
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/Yuzuki616/V2bX/node/controller/legoCmd/log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
"github.com/go-acme/lego/v4/challenge/http01"
|
||||
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/providers/dns"
|
||||
"github.com/go-acme/lego/v4/providers/http/memcached"
|
||||
"github.com/go-acme/lego/v4/providers/http/webroot"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func setupChallenges(ctx *cli.Context, client *lego.Client) {
|
||||
if !ctx.GlobalBool("http") && !ctx.GlobalBool("tls") && !ctx.GlobalIsSet("dns") {
|
||||
log.Panic("No challenge selected. You must specify at least one challenge: `--http`, `--tls`, `--dns`.")
|
||||
}
|
||||
|
||||
if ctx.GlobalBool("http") {
|
||||
err := client.Challenge.SetHTTP01Provider(setupHTTPProvider(ctx))
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.GlobalBool("tls") {
|
||||
err := client.Challenge.SetTLSALPN01Provider(setupTLSProvider(ctx))
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.GlobalIsSet("dns") {
|
||||
setupDNS(ctx, client)
|
||||
}
|
||||
}
|
||||
|
||||
func setupHTTPProvider(ctx *cli.Context) challenge.Provider {
|
||||
switch {
|
||||
case ctx.GlobalIsSet("http.webroot"):
|
||||
ps, err := webroot.NewHTTPProvider(ctx.GlobalString("http.webroot"))
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
return ps
|
||||
case ctx.GlobalIsSet("http.memcached-host"):
|
||||
ps, err := memcached.NewMemcachedProvider(ctx.GlobalStringSlice("http.memcached-host"))
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
return ps
|
||||
case ctx.GlobalIsSet("http.port"):
|
||||
iface := ctx.GlobalString("http.port")
|
||||
if !strings.Contains(iface, ":") {
|
||||
log.Panicf("The --http switch only accepts interface:port or :port for its argument.")
|
||||
}
|
||||
|
||||
host, port, err := net.SplitHostPort(iface)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
srv := http01.NewProviderServer(host, port)
|
||||
if header := ctx.GlobalString("http.proxy-header"); header != "" {
|
||||
srv.SetProxyHeader(header)
|
||||
}
|
||||
return srv
|
||||
case ctx.GlobalBool("http"):
|
||||
srv := http01.NewProviderServer("", "")
|
||||
if header := ctx.GlobalString("http.proxy-header"); header != "" {
|
||||
srv.SetProxyHeader(header)
|
||||
}
|
||||
return srv
|
||||
default:
|
||||
log.Panic("Invalid HTTP challenge options.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func setupTLSProvider(ctx *cli.Context) challenge.Provider {
|
||||
switch {
|
||||
case ctx.GlobalIsSet("tls.port"):
|
||||
iface := ctx.GlobalString("tls.port")
|
||||
if !strings.Contains(iface, ":") {
|
||||
log.Panicf("The --tls switch only accepts interface:port or :port for its argument.")
|
||||
}
|
||||
|
||||
host, port, err := net.SplitHostPort(iface)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
return tlsalpn01.NewProviderServer(host, port)
|
||||
case ctx.GlobalBool("tls"):
|
||||
return tlsalpn01.NewProviderServer("", "")
|
||||
default:
|
||||
log.Panic("Invalid HTTP challenge options.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func setupDNS(ctx *cli.Context, client *lego.Client) {
|
||||
provider, err := dns.NewDNSChallengeProviderByName(ctx.GlobalString("dns"))
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
servers := ctx.GlobalStringSlice("dns.resolvers")
|
||||
err = client.Challenge.SetDNS01Provider(provider,
|
||||
dns01.CondOption(len(servers) > 0,
|
||||
dns01.AddRecursiveNameservers(dns01.ParseNameservers(ctx.GlobalStringSlice("dns.resolvers")))),
|
||||
dns01.CondOption(ctx.GlobalBool("dns.disable-cp"),
|
||||
dns01.DisableCompletePropagationRequirement()),
|
||||
dns01.CondOption(ctx.GlobalIsSet("dns-timeout"),
|
||||
dns01.AddDNSTimeout(time.Duration(ctx.GlobalInt("dns-timeout"))*time.Second)),
|
||||
)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,189 +0,0 @@
|
||||
// Package legoCmd Let's Encrypt client to go!
|
||||
// CLI application for generating Let's Encrypt certificates using the ACME package.
|
||||
package legoCmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Yuzuki616/V2bX/node/controller/legoCmd/cmd"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var version = "dev"
|
||||
var defaultPath string
|
||||
|
||||
type LegoCMD struct {
|
||||
cmdClient *cli.App
|
||||
}
|
||||
|
||||
func New() (*LegoCMD, error) {
|
||||
app := cli.NewApp()
|
||||
app.Name = "lego"
|
||||
app.HelpName = "lego"
|
||||
app.Usage = "Let's Encrypt client written in Go"
|
||||
app.EnableBashCompletion = true
|
||||
|
||||
app.Version = version
|
||||
cli.VersionPrinter = func(c *cli.Context) {
|
||||
fmt.Printf("lego version %s %s/%s\n", c.App.Version, runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
// Set default pathTemp to configPath/cert
|
||||
var pathTemp = ""
|
||||
configPath := os.Getenv("XRAY_LOCATION_CONFIG")
|
||||
if configPath != "" {
|
||||
pathTemp = configPath
|
||||
} else if cwd, err := os.Getwd(); err == nil {
|
||||
pathTemp = cwd
|
||||
} else {
|
||||
pathTemp = "."
|
||||
}
|
||||
|
||||
defaultPath = filepath.Join(pathTemp, "cert")
|
||||
|
||||
app.Flags = cmd.CreateFlags(defaultPath)
|
||||
|
||||
app.Before = cmd.Before
|
||||
|
||||
app.Commands = cmd.CreateCommands()
|
||||
|
||||
lego := &LegoCMD{
|
||||
cmdClient: app,
|
||||
}
|
||||
|
||||
return lego, nil
|
||||
}
|
||||
|
||||
// DNSCert cert a domain using DNS API
|
||||
func (l *LegoCMD) DNSCert(domain, email, provider string, DNSEnv map[string]string) (CertPath string, KeyPath string, err error) {
|
||||
defer func() (string, string, error) {
|
||||
// Handle any error
|
||||
if r := recover(); r != nil {
|
||||
switch x := r.(type) {
|
||||
case string:
|
||||
err = errors.New(x)
|
||||
case error:
|
||||
err = x
|
||||
default:
|
||||
err = errors.New("unknow panic")
|
||||
}
|
||||
return "", "", err
|
||||
}
|
||||
return CertPath, KeyPath, nil
|
||||
}()
|
||||
// Set Env for DNS configuration
|
||||
for key, value := range DNSEnv {
|
||||
os.Setenv(key, value)
|
||||
}
|
||||
// First check if the certificate exists
|
||||
CertPath, KeyPath, err = checkCertfile(domain)
|
||||
if err == nil {
|
||||
return CertPath, KeyPath, err
|
||||
}
|
||||
|
||||
argstring := fmt.Sprintf("lego -a -d %s -m %s --dns %s run", domain, email, provider)
|
||||
err = l.cmdClient.Run(strings.Split(argstring, " "))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
CertPath, KeyPath, err = checkCertfile(domain)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return CertPath, KeyPath, nil
|
||||
}
|
||||
|
||||
// HTTPCert cert a domain using http methods
|
||||
func (l *LegoCMD) HTTPCert(domain, email string) (CertPath string, KeyPath string, err error) {
|
||||
defer func() (string, string, error) {
|
||||
// Handle any error
|
||||
if r := recover(); r != nil {
|
||||
switch x := r.(type) {
|
||||
case string:
|
||||
err = errors.New(x)
|
||||
case error:
|
||||
err = x
|
||||
default:
|
||||
err = errors.New("unknow panic")
|
||||
}
|
||||
return "", "", err
|
||||
}
|
||||
return CertPath, KeyPath, nil
|
||||
}()
|
||||
// First check if the certificate exists
|
||||
CertPath, KeyPath, err = checkCertfile(domain)
|
||||
if err == nil {
|
||||
return CertPath, KeyPath, err
|
||||
}
|
||||
argString := fmt.Sprintf("lego -a -d %s -m %s --http run", domain, email)
|
||||
err = l.cmdClient.Run(strings.Split(argString, " "))
|
||||
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
CertPath, KeyPath, err = checkCertfile(domain)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return CertPath, KeyPath, nil
|
||||
}
|
||||
|
||||
// RenewCert renew a domain cert
|
||||
func (l *LegoCMD) RenewCert(domain, email, certMode, provider string, DNSEnv map[string]string) (CertPath string, KeyPath string, err error) {
|
||||
var argstring string
|
||||
defer func() (string, string, error) {
|
||||
// Handle any error
|
||||
if r := recover(); r != nil {
|
||||
switch x := r.(type) {
|
||||
case string:
|
||||
err = errors.New(x)
|
||||
case error:
|
||||
err = x
|
||||
default:
|
||||
err = errors.New("unknown panic")
|
||||
}
|
||||
return "", "", err
|
||||
}
|
||||
return CertPath, KeyPath, nil
|
||||
}()
|
||||
if certMode == "http" {
|
||||
argstring = fmt.Sprintf("lego -a -d %s -m %s --http renew --days 30", domain, email)
|
||||
} else if certMode == "dns" {
|
||||
// Set Env for DNS configuration
|
||||
for key, value := range DNSEnv {
|
||||
os.Setenv(key, value)
|
||||
}
|
||||
argstring = fmt.Sprintf("lego -a -d %s -m %s --dns %s renew --days 30", domain, email, provider)
|
||||
} else {
|
||||
return "", "", fmt.Errorf("unsupport cert mode: %s", certMode)
|
||||
}
|
||||
err = l.cmdClient.Run(strings.Split(argstring, " "))
|
||||
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
CertPath, KeyPath, err = checkCertfile(domain)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return CertPath, KeyPath, nil
|
||||
}
|
||||
func checkCertfile(domain string) (string, string, error) {
|
||||
keyPath := path.Join(defaultPath, "certificates", fmt.Sprintf("%s.key", domain))
|
||||
certPath := path.Join(defaultPath, "certificates", fmt.Sprintf("%s.crt", domain))
|
||||
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
|
||||
return "", "", fmt.Errorf("cert key failed: %s", domain)
|
||||
}
|
||||
if _, err := os.Stat(certPath); os.IsNotExist(err) {
|
||||
return "", "", fmt.Errorf("cert cert failed: %s", domain)
|
||||
}
|
||||
absKeyPath, _ := filepath.Abs(keyPath)
|
||||
absCertPath, _ := filepath.Abs(certPath)
|
||||
return absCertPath, absKeyPath, nil
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
package legoCmd_test
|
||||
|
||||
import (
|
||||
"github.com/Yuzuki616/V2bX/node/controller/legoCmd"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLegoClient(t *testing.T) {
|
||||
_, err := legoCmd.New()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLegoDNSCert(t *testing.T) {
|
||||
lego, err := legoCmd.New()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
var (
|
||||
domain string = "node1.test.com"
|
||||
email string = "test@gmail.com"
|
||||
provider string = "alidns"
|
||||
DNSEnv map[string]string
|
||||
)
|
||||
DNSEnv = make(map[string]string)
|
||||
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
|
||||
DNSEnv["ALICLOUD_SECRET_KEY"] = "bbb"
|
||||
certPath, keyPath, err := lego.DNSCert(domain, email, provider, DNSEnv)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Log(certPath)
|
||||
t.Log(keyPath)
|
||||
}
|
||||
|
||||
func TestLegoHTTPCert(t *testing.T) {
|
||||
lego, err := legoCmd.New()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
var (
|
||||
domain string = "node1.test.com"
|
||||
email string = "test@gmail.com"
|
||||
)
|
||||
certPath, keyPath, err := lego.HTTPCert(domain, email)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Log(certPath)
|
||||
t.Log(keyPath)
|
||||
}
|
||||
|
||||
func TestLegoRenewCert(t *testing.T) {
|
||||
lego, err := legoCmd.New()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
var (
|
||||
domain string = "node1.test.com"
|
||||
email string = "test@gmail.com"
|
||||
provider string = "alidns"
|
||||
DNSEnv map[string]string
|
||||
)
|
||||
DNSEnv = make(map[string]string)
|
||||
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
|
||||
DNSEnv["ALICLOUD_SECRET_KEY"] = "bbb"
|
||||
certPath, keyPath, err := lego.RenewCert(domain, email, "dns", provider, DNSEnv)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Log(certPath)
|
||||
t.Log(keyPath)
|
||||
|
||||
certPath, keyPath, err = lego.RenewCert(domain, email, "http", provider, DNSEnv)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Log(certPath)
|
||||
t.Log(keyPath)
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Logger is an optional custom logger.
|
||||
var Logger StdLogger = log.New(os.Stdout, "", log.LstdFlags)
|
||||
|
||||
// StdLogger interface for Standard Logger.
|
||||
type StdLogger interface {
|
||||
Panic(args ...interface{})
|
||||
Fatalln(args ...interface{})
|
||||
Panicf(format string, args ...interface{})
|
||||
Print(args ...interface{})
|
||||
Println(args ...interface{})
|
||||
Printf(format string, args ...interface{})
|
||||
}
|
||||
|
||||
// Panic writes a log entry.
|
||||
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
||||
func Panic(args ...interface{}) {
|
||||
Logger.Panic(args...)
|
||||
}
|
||||
|
||||
// Panicf writes a log entry.
|
||||
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
||||
func Panicf(format string, args ...interface{}) {
|
||||
Logger.Panicf(format, args...)
|
||||
}
|
||||
|
||||
// Print writes a log entry.
|
||||
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
||||
func Print(args ...interface{}) {
|
||||
Logger.Print(args...)
|
||||
}
|
||||
|
||||
// Println writes a log entry.
|
||||
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
||||
func Println(args ...interface{}) {
|
||||
Logger.Println(args...)
|
||||
}
|
||||
|
||||
// Printf writes a log entry.
|
||||
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
||||
func Printf(format string, args ...interface{}) {
|
||||
Logger.Printf(format, args...)
|
||||
}
|
||||
|
||||
// Warnf writes a log entry.
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
Printf("[WARN] "+format, args...)
|
||||
}
|
||||
|
||||
// Infof writes a log entry.
|
||||
func Infof(format string, args ...interface{}) {
|
||||
Printf("[INFO] "+format, args...)
|
||||
}
|
@ -2,15 +2,87 @@ package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Yuzuki616/V2bX/api/iprecoder"
|
||||
"github.com/Yuzuki616/V2bX/api/panel"
|
||||
"github.com/Yuzuki616/V2bX/node/controller/legoCmd"
|
||||
"github.com/Yuzuki616/V2bX/node/controller/lego"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/common/task"
|
||||
"log"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (c *Node) initTask() {
|
||||
// fetch node info task
|
||||
c.nodeInfoMonitorPeriodic = &task.Periodic{
|
||||
Interval: time.Duration(c.nodeInfo.BaseConfig.PullInterval.(int)) * time.Second,
|
||||
Execute: c.nodeInfoMonitor,
|
||||
}
|
||||
// fetch user list task
|
||||
c.userReportPeriodic = &task.Periodic{
|
||||
Interval: time.Duration(c.nodeInfo.BaseConfig.PushInterval.(int)) * time.Second,
|
||||
Execute: c.reportUserTraffic,
|
||||
}
|
||||
log.Printf("[%s: %d] Start monitor node status", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
|
||||
// delay to start nodeInfoMonitor
|
||||
go func() {
|
||||
time.Sleep(time.Duration(c.nodeInfo.BaseConfig.PullInterval.(int)) * time.Second)
|
||||
_ = c.nodeInfoMonitorPeriodic.Start()
|
||||
}()
|
||||
log.Printf("[%s: %d] Start report node status", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
|
||||
// delay to start userReport
|
||||
go func() {
|
||||
time.Sleep(time.Duration(c.nodeInfo.BaseConfig.PushInterval.(int)) * time.Second)
|
||||
_ = c.userReportPeriodic.Start()
|
||||
}()
|
||||
if c.nodeInfo.EnableTls && c.CertConfig.CertMode != "none" &&
|
||||
(c.CertConfig.CertMode == "dns" || c.CertConfig.CertMode == "http") {
|
||||
c.renewCertPeriodic = &task.Periodic{
|
||||
Interval: time.Hour * 24,
|
||||
Execute: c.reportUserTraffic,
|
||||
}
|
||||
log.Printf("[%s: %d] Start renew cert", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
|
||||
// delay to start renewCert
|
||||
go func() {
|
||||
_ = c.renewCertPeriodic.Start()
|
||||
}()
|
||||
}
|
||||
if c.EnableDynamicSpeedLimit {
|
||||
// Check dynamic speed limit task
|
||||
c.dynamicSpeedLimitPeriodic = &task.Periodic{
|
||||
Interval: time.Duration(c.DynamicSpeedLimitConfig.Periodic) * time.Second,
|
||||
Execute: c.dynamicSpeedLimit,
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(time.Duration(c.DynamicSpeedLimitConfig.Periodic) * time.Second)
|
||||
_ = c.dynamicSpeedLimitPeriodic.Start()
|
||||
}()
|
||||
log.Printf("[%s: %d] Start dynamic speed limit", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
|
||||
}
|
||||
if c.EnableIpRecorder {
|
||||
switch c.IpRecorderConfig.Type {
|
||||
case "Recorder":
|
||||
c.ipRecorder = iprecoder.NewRecorder(c.IpRecorderConfig.RecorderConfig)
|
||||
case "Redis":
|
||||
c.ipRecorder = iprecoder.NewRedis(c.IpRecorderConfig.RedisConfig)
|
||||
default:
|
||||
log.Printf("recorder type: %s is not vail, disable recorder", c.IpRecorderConfig.Type)
|
||||
return
|
||||
}
|
||||
// report and fetch online ip list task
|
||||
c.onlineIpReportPeriodic = &task.Periodic{
|
||||
Interval: time.Duration(c.IpRecorderConfig.Periodic) * time.Second,
|
||||
Execute: c.reportOnlineIp,
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(time.Duration(c.IpRecorderConfig.Periodic) * time.Second)
|
||||
_ = c.onlineIpReportPeriodic.Start()
|
||||
}()
|
||||
log.Printf("[%s: %d] Start report online ip", c.nodeInfo.NodeType, c.nodeInfo.NodeId)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Node) nodeInfoMonitor() (err error) {
|
||||
// First fetch Node Info
|
||||
newNodeInfo, err := c.apiClient.GetNodeInfo()
|
||||
@ -46,20 +118,6 @@ func (c *Node) nodeInfoMonitor() (err error) {
|
||||
log.Print(err)
|
||||
}
|
||||
}
|
||||
// Check Cert
|
||||
if c.nodeInfo.EnableTls && c.CertConfig.CertMode != "none" &&
|
||||
(c.CertConfig.CertMode == "dns" || c.CertConfig.CertMode == "http") {
|
||||
lego, err := legoCmd.New()
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
// Core-core supports the OcspStapling certification hot renew
|
||||
_, _, err = lego.RenewCert(c.CertConfig.CertDomain, c.CertConfig.Email,
|
||||
c.CertConfig.CertMode, c.CertConfig.Provider, c.CertConfig.DNSEnv)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
}
|
||||
// Update User
|
||||
newUserInfo, err := c.apiClient.GetUserList()
|
||||
if err != nil {
|
||||
@ -280,3 +338,16 @@ func (c *Node) dynamicSpeedLimit() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Node) RenewCert() {
|
||||
l, err := lego.New(c.CertConfig)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return
|
||||
}
|
||||
err = l.RenewCert()
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user