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 (
|
import (
|
||||||
"github.com/Yuzuki616/V2bX/conf"
|
"github.com/Yuzuki616/V2bX/conf"
|
||||||
"github.com/Yuzuki616/V2bX/node/controller/legoCmd/log"
|
"log"
|
||||||
"testing"
|
"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/go-resty/resty/v2 v2.7.0
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/juju/ratelimit v1.0.2
|
github.com/juju/ratelimit v1.0.2
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.1 // indirect
|
||||||
github.com/urfave/cli v1.22.10
|
|
||||||
github.com/xtls/xray-core v1.7.2
|
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/appengine v1.6.7 // indirect
|
||||||
google.golang.org/protobuf v1.28.1
|
google.golang.org/protobuf v1.28.1
|
||||||
)
|
)
|
||||||
@ -47,7 +46,6 @@ require (
|
|||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||||
github.com/cloudflare/cloudflare-go v0.49.0 // indirect
|
github.com/cloudflare/cloudflare-go v0.49.0 // indirect
|
||||||
github.com/cpu/goacmedns v0.1.1 // 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/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/deepmap/oapi-codegen v1.10.1 // indirect
|
github.com/deepmap/oapi-codegen v1.10.1 // indirect
|
||||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // 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/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/pquerna/otp v1.3.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/refraction-networking/utls v1.2.0 // indirect
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // 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/sacloud/libsacloud v1.36.2 // indirect
|
||||||
github.com/sagernet/sing v0.1.2 // indirect
|
github.com/sagernet/sing v0.1.2 // indirect
|
||||||
github.com/sagernet/sing-shadowsocks v0.1.0 // 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/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 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4=
|
||||||
github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ=
|
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.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/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/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=
|
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.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
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/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 h1:U5f8wkij2NVinfLuJdFP3gCMwIHs+EzvhxmYdXgiapo=
|
||||||
github.com/refraction-networking/utls v1.2.0/go.mod h1:NPq+cVqzH7D1BeOkmOcb5O/8iVewAsiVt2x1/eO0hgQ=
|
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=
|
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/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 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.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/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 h1:aosI7clbQ9IU0Hj+3rpk3SKJop5nLPpLThnWCivPqjI=
|
||||||
github.com/sacloud/libsacloud v1.36.2/go.mod h1:P7YAOVmnIn3DKHqCZcUKYUXmSwGBm3yS7IBEjKVSrjg=
|
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 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.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
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 h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
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=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"github.com/Yuzuki616/V2bX/core"
|
"github.com/Yuzuki616/V2bX/core"
|
||||||
"github.com/xtls/xray-core/common/task"
|
"github.com/xtls/xray-core/common/task"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
@ -22,8 +21,9 @@ type Node struct {
|
|||||||
ipRecorder iprecoder.IpRecorder
|
ipRecorder iprecoder.IpRecorder
|
||||||
nodeInfoMonitorPeriodic *task.Periodic
|
nodeInfoMonitorPeriodic *task.Periodic
|
||||||
userReportPeriodic *task.Periodic
|
userReportPeriodic *task.Periodic
|
||||||
|
renewCertPeriodic *task.Periodic
|
||||||
|
dynamicSpeedLimitPeriodic *task.Periodic
|
||||||
onlineIpReportPeriodic *task.Periodic
|
onlineIpReportPeriodic *task.Periodic
|
||||||
DynamicSpeedLimitPeriodic *task.Periodic
|
|
||||||
*conf.ControllerConfig
|
*conf.ControllerConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,62 +73,7 @@ func (c *Node) Start() error {
|
|||||||
log.Printf("Update rule filed: %s", err)
|
log.Printf("Update rule filed: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// fetch node info task
|
c.initTask()
|
||||||
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)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,18 +91,24 @@ func (c *Node) Close() error {
|
|||||||
log.Panicf("user report periodic close failed: %s", err)
|
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 {
|
if c.onlineIpReportPeriodic != nil {
|
||||||
err := c.onlineIpReportPeriodic.Close()
|
err := c.onlineIpReportPeriodic.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("online ip report periodic close failed: %s", err)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,8 +6,9 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Yuzuki616/V2bX/api/panel"
|
"github.com/Yuzuki616/V2bX/api/panel"
|
||||||
|
"github.com/Yuzuki616/V2bX/common/file"
|
||||||
"github.com/Yuzuki616/V2bX/conf"
|
"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/goccy/go-json"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/core"
|
"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) {
|
func getCertFile(certConfig *conf.CertConfig) (certFile string, keyFile string, err error) {
|
||||||
if certConfig.CertMode == "file" {
|
if certConfig.CertFile == "" || certConfig.KeyFile == "" {
|
||||||
if certConfig.CertFile == "" || certConfig.KeyFile == "" {
|
return "", "", fmt.Errorf("cert file path or key file path not exist")
|
||||||
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
|
||||||
|
}
|
||||||
|
l, err := lego.New(certConfig)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("create lego object error: %s", err)
|
||||||
|
}
|
||||||
|
err = l.CreateCert()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("create cert error: %s", err)
|
||||||
}
|
}
|
||||||
return certConfig.CertFile, certConfig.KeyFile, nil
|
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)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
return certPath, keyPath, err
|
|
||||||
} else if certConfig.CertMode == "http" {
|
|
||||||
lego, err := legoCmd.New()
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
certPath, keyPath, err := lego.HTTPCert(certConfig.CertDomain, certConfig.Email)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
return certPath, keyPath, err
|
|
||||||
}
|
}
|
||||||
return "", "", fmt.Errorf("unsupported certmode: %s", certConfig.CertMode)
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/Yuzuki616/V2bX/api/iprecoder"
|
||||||
"github.com/Yuzuki616/V2bX/api/panel"
|
"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/protocol"
|
||||||
|
"github.com/xtls/xray-core/common/task"
|
||||||
"log"
|
"log"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"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) {
|
func (c *Node) nodeInfoMonitor() (err error) {
|
||||||
// First fetch Node Info
|
// First fetch Node Info
|
||||||
newNodeInfo, err := c.apiClient.GetNodeInfo()
|
newNodeInfo, err := c.apiClient.GetNodeInfo()
|
||||||
@ -46,20 +118,6 @@ func (c *Node) nodeInfoMonitor() (err error) {
|
|||||||
log.Print(err)
|
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
|
// Update User
|
||||||
newUserInfo, err := c.apiClient.GetUserList()
|
newUserInfo, err := c.apiClient.GetUserList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -280,3 +338,16 @@ func (c *Node) dynamicSpeedLimit() error {
|
|||||||
}
|
}
|
||||||
return nil
|
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