mirror of
https://github.com/wyx2685/V2bX.git
synced 2026-02-04 12:40:11 +00:00
change project structure, performance optimization
This commit is contained in:
@@ -1,2 +0,0 @@
|
||||
// Package common contains common utilities that are shared among other packages.
|
||||
package common
|
||||
@@ -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,243 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/common/legoCmd/log"
|
||||
"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,205 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/common/legoCmd/log"
|
||||
"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/common/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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"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"
|
||||
"time"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/common/legoCmd/log"
|
||||
"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/common/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"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/common/legoCmd/log"
|
||||
"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"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/common/legoCmd/log"
|
||||
"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 (
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/common/legoCmd/log"
|
||||
"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"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/common/legoCmd/cmd"
|
||||
"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,82 +0,0 @@
|
||||
package legoCmd_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/common/legoCmd"
|
||||
)
|
||||
|
||||
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...)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package limiter
|
||||
|
||||
import "github.com/xtls/xray-core/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
// Package limiter is to control the links that go into the dispather
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/api"
|
||||
"github.com/juju/ratelimit"
|
||||
)
|
||||
|
||||
type UserInfo struct {
|
||||
UID int
|
||||
SpeedLimit uint64
|
||||
DeviceLimit int
|
||||
}
|
||||
|
||||
type InboundInfo struct {
|
||||
Tag string
|
||||
NodeSpeedLimit uint64
|
||||
UserInfo *sync.Map // Key: Uid value: UserInfo
|
||||
BucketHub *sync.Map // key: Uid, value: *ratelimit.Bucket
|
||||
UserOnlineIP *sync.Map // Key: Uid Value: *sync.Map: Key: IP, Value: bool
|
||||
}
|
||||
|
||||
type Limiter struct {
|
||||
InboundInfo *sync.Map // Key: Tag, Value: *InboundInfo
|
||||
}
|
||||
|
||||
func New() *Limiter {
|
||||
return &Limiter{
|
||||
InboundInfo: new(sync.Map),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Limiter) AddInboundLimiter(tag string, nodeInfo *api.NodeInfo, userList *[]api.UserInfo) error {
|
||||
inboundInfo := &InboundInfo{
|
||||
Tag: tag,
|
||||
NodeSpeedLimit: nodeInfo.SpeedLimit,
|
||||
BucketHub: new(sync.Map),
|
||||
UserOnlineIP: new(sync.Map),
|
||||
}
|
||||
userMap := new(sync.Map)
|
||||
for i := range *userList {
|
||||
/*if (*userList)[i].SpeedLimit == 0 {
|
||||
(*userList)[i].SpeedLimit = nodeInfo.SpeedLimit
|
||||
}
|
||||
if (*userList)[i].DeviceLimit == 0 {
|
||||
(*userList)[i].DeviceLimit = nodeInfo.DeviceLimit
|
||||
}*/
|
||||
userMap.Store(fmt.Sprintf("%s|%s|%d", tag, (*userList)[i].V2rayUser.Email, (*userList)[i].UID), UserInfo{
|
||||
UID: (*userList)[i].UID,
|
||||
SpeedLimit: nodeInfo.SpeedLimit,
|
||||
DeviceLimit: nodeInfo.DeviceLimit,
|
||||
})
|
||||
}
|
||||
inboundInfo.UserInfo = userMap
|
||||
l.InboundInfo.Store(tag, inboundInfo) // Replace the old inbound info
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Limiter) UpdateInboundLimiter(tag string, nodeInfo *api.NodeInfo, updatedUserList *[]api.UserInfo) error {
|
||||
if value, ok := l.InboundInfo.Load(tag); ok {
|
||||
inboundInfo := value.(*InboundInfo)
|
||||
// Update User info
|
||||
for i := range *updatedUserList {
|
||||
inboundInfo.UserInfo.Store(fmt.Sprintf("%s|%s|%d", tag,
|
||||
(*updatedUserList)[i].V2rayUser.Email, (*updatedUserList)[i].UID), UserInfo{
|
||||
UID: (*updatedUserList)[i].UID,
|
||||
SpeedLimit: nodeInfo.SpeedLimit,
|
||||
DeviceLimit: nodeInfo.DeviceLimit,
|
||||
})
|
||||
inboundInfo.BucketHub.Delete(fmt.Sprintf("%s|%s|%d", tag,
|
||||
(*updatedUserList)[i].V2rayUser.Email, (*updatedUserList)[i].UID)) // Delete old limiter bucket
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("no such inbound in limiter: %s", tag)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Limiter) DeleteInboundLimiter(tag string) error {
|
||||
l.InboundInfo.Delete(tag)
|
||||
return nil
|
||||
}
|
||||
|
||||
type UserIp struct {
|
||||
Uid int `json:"Uid"`
|
||||
IPs []string `json:"Ips"`
|
||||
}
|
||||
|
||||
func (l *Limiter) GetOnlineUserIp(tag string) (*[]UserIp, error) {
|
||||
if value, ok := l.InboundInfo.Load(tag); ok {
|
||||
inboundInfo := value.(*InboundInfo)
|
||||
// Clear Speed Limiter bucket for users who are not online
|
||||
inboundInfo.BucketHub.Range(func(key, value interface{}) bool {
|
||||
if _, exists := inboundInfo.UserOnlineIP.Load(key.(string)); !exists {
|
||||
inboundInfo.BucketHub.Delete(key.(string))
|
||||
}
|
||||
return true
|
||||
})
|
||||
onlineUser := make([]UserIp, 0)
|
||||
var ipMap *sync.Map
|
||||
inboundInfo.UserOnlineIP.Range(func(key, value interface{}) bool {
|
||||
ipMap = value.(*sync.Map)
|
||||
var ip []string
|
||||
ipMap.Range(func(key, v interface{}) bool {
|
||||
if v.(bool) {
|
||||
ip = append(ip, key.(string))
|
||||
}
|
||||
return true
|
||||
})
|
||||
if len(ip) > 0 {
|
||||
if u, ok := inboundInfo.UserInfo.Load(key.(string)); ok {
|
||||
onlineUser = append(onlineUser, UserIp{
|
||||
Uid: u.(UserInfo).UID,
|
||||
IPs: ip,
|
||||
})
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if len(onlineUser) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return &onlineUser, nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("no such inbound in limiter: %s", tag)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Limiter) UpdateOnlineUserIP(tag string, userIpList *[]UserIp) {
|
||||
if v, ok := l.InboundInfo.Load(tag); ok {
|
||||
inboundInfo := v.(*InboundInfo)
|
||||
//Clear old IP
|
||||
inboundInfo.UserOnlineIP.Range(func(key, value interface{}) bool {
|
||||
inboundInfo.UserOnlineIP.Delete(key)
|
||||
return true
|
||||
})
|
||||
// Update User Online IP
|
||||
for i := range *userIpList {
|
||||
ipMap := new(sync.Map)
|
||||
for _, userIp := range (*userIpList)[i].IPs {
|
||||
ipMap.Store(userIp, false)
|
||||
}
|
||||
inboundInfo.UserOnlineIP.Store((*userIpList)[i].Uid, ipMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Limiter) ClearOnlineUserIP(tag string) {
|
||||
if v, ok := l.InboundInfo.Load(tag); ok {
|
||||
inboundInfo := v.(*InboundInfo)
|
||||
inboundInfo.UserOnlineIP.Range(func(key, value interface{}) bool {
|
||||
inboundInfo.UserOnlineIP.Delete(key)
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Limiter) GetUserBucket(tag string, email string, ip string) (limiter *ratelimit.Bucket, SpeedLimit bool, Reject bool) {
|
||||
if value, ok := l.InboundInfo.Load(tag); ok {
|
||||
inboundInfo := value.(*InboundInfo)
|
||||
nodeLimit := inboundInfo.NodeSpeedLimit
|
||||
var userLimit uint64 = 0
|
||||
var deviceLimit = 0
|
||||
if v, ok := inboundInfo.UserInfo.Load(email); ok {
|
||||
u := v.(UserInfo)
|
||||
userLimit = u.SpeedLimit
|
||||
deviceLimit = u.DeviceLimit
|
||||
}
|
||||
ipMap := new(sync.Map)
|
||||
ipMap.Store(ip, true)
|
||||
// If any device is online
|
||||
if v, ok := inboundInfo.UserOnlineIP.LoadOrStore(email, ipMap); ok {
|
||||
ipMap := v.(*sync.Map)
|
||||
// If this ip is a new device
|
||||
if online, ok := ipMap.LoadOrStore(ip, true); !ok {
|
||||
counter := 0
|
||||
ipMap.Range(func(key, value interface{}) bool {
|
||||
counter++
|
||||
return true
|
||||
})
|
||||
if counter > deviceLimit && deviceLimit > 0 {
|
||||
ipMap.Delete(ip)
|
||||
return nil, false, true
|
||||
}
|
||||
} else {
|
||||
if !online.(bool) {
|
||||
ipMap.Store(ip, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
limit := determineRate(nodeLimit, userLimit) // If need the Speed limit
|
||||
if limit > 0 {
|
||||
limiter := ratelimit.NewBucketWithQuantum(time.Second, int64(limit), int64(limit)) // Byte/s
|
||||
if v, ok := inboundInfo.BucketHub.LoadOrStore(email, limiter); ok {
|
||||
bucket := v.(*ratelimit.Bucket)
|
||||
return bucket, true, false
|
||||
} else {
|
||||
return limiter, true, false
|
||||
}
|
||||
} else {
|
||||
return nil, false, false
|
||||
}
|
||||
} else {
|
||||
newError("Get Inbound Limiter information failed").AtDebug().WriteToLog()
|
||||
return nil, false, false
|
||||
}
|
||||
}
|
||||
|
||||
// determineRate returns the minimum non-zero rate
|
||||
func determineRate(nodeLimit, userLimit uint64) (limit uint64) {
|
||||
if nodeLimit == 0 || userLimit == 0 {
|
||||
if nodeLimit > userLimit {
|
||||
return nodeLimit
|
||||
} else if nodeLimit < userLimit {
|
||||
return userLimit
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
} else {
|
||||
if nodeLimit > userLimit {
|
||||
return userLimit
|
||||
} else if nodeLimit < userLimit {
|
||||
return nodeLimit
|
||||
} else {
|
||||
return nodeLimit
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/juju/ratelimit"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
)
|
||||
|
||||
type Writer struct {
|
||||
writer buf.Writer
|
||||
limiter *ratelimit.Bucket
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func (l *Limiter) RateWriter(writer buf.Writer, limiter *ratelimit.Bucket) buf.Writer {
|
||||
return &Writer{
|
||||
writer: writer,
|
||||
limiter: limiter,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Writer) Close() error {
|
||||
return common.Close(w.writer)
|
||||
}
|
||||
|
||||
func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
w.limiter.Wait(int64(mb.Len()))
|
||||
return w.writer.WriteMultiBuffer(mb)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package rule
|
||||
|
||||
import "github.com/xtls/xray-core/common/errors"
|
||||
|
||||
type errPathObjHolder struct{}
|
||||
|
||||
func newError(values ...interface{}) *errors.Error {
|
||||
return errors.New(values...).WithPathObj(errPathObjHolder{})
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
// Package rule is to control the audit rule behaviors
|
||||
package rule
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Yuzuki616/V2bX/api"
|
||||
mapset "github.com/deckarep/golang-set"
|
||||
)
|
||||
|
||||
type Rule struct {
|
||||
InboundRule *sync.Map // Key: Tag, Value: []api.DetectRule
|
||||
InboundProtocolRule *sync.Map // Key: Tag, Value: []string
|
||||
InboundDetectResult *sync.Map // key: Tag, Value: mapset.NewSet []api.DetectResult
|
||||
}
|
||||
|
||||
func New() *Rule {
|
||||
return &Rule{
|
||||
InboundRule: new(sync.Map),
|
||||
InboundProtocolRule: new(sync.Map),
|
||||
InboundDetectResult: new(sync.Map),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Rule) UpdateRule(tag string, newRuleList []api.DetectRule) error {
|
||||
if value, ok := r.InboundRule.LoadOrStore(tag, newRuleList); ok {
|
||||
oldRuleList := value.([]api.DetectRule)
|
||||
if !reflect.DeepEqual(oldRuleList, newRuleList) {
|
||||
r.InboundRule.Store(tag, newRuleList)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Rule) UpdateProtocolRule(tag string, ruleList []string) error {
|
||||
if value, ok := r.InboundProtocolRule.LoadOrStore(tag, ruleList); ok {
|
||||
old := value.([]string)
|
||||
if !reflect.DeepEqual(old, ruleList) {
|
||||
r.InboundProtocolRule.Store(tag, ruleList)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Rule) GetDetectResult(tag string) (*[]api.DetectResult, error) {
|
||||
detectResult := make([]api.DetectResult, 0)
|
||||
if value, ok := r.InboundDetectResult.LoadAndDelete(tag); ok {
|
||||
resultSet := value.(mapset.Set)
|
||||
it := resultSet.Iterator()
|
||||
for result := range it.C {
|
||||
detectResult = append(detectResult, result.(api.DetectResult))
|
||||
}
|
||||
}
|
||||
return &detectResult, nil
|
||||
}
|
||||
|
||||
func (r *Rule) Detect(tag string, destination string, email string) (reject bool) {
|
||||
reject = false
|
||||
var hitRuleID = -1
|
||||
// If we have some rule for this inbound
|
||||
if value, ok := r.InboundRule.Load(tag); ok {
|
||||
ruleList := value.([]api.DetectRule)
|
||||
for _, r := range ruleList {
|
||||
if r.Pattern.Match([]byte(destination)) {
|
||||
hitRuleID = r.ID
|
||||
reject = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// If we hit some rule
|
||||
if reject && hitRuleID != -1 {
|
||||
l := strings.Split(email, "|")
|
||||
uid, err := strconv.Atoi(l[len(l)-1])
|
||||
if err != nil {
|
||||
newError(fmt.Sprintf("Record illegal behavior failed! Cannot find user's uid: %s", email)).AtDebug().WriteToLog()
|
||||
return reject
|
||||
}
|
||||
newSet := mapset.NewSetWith(api.DetectResult{UID: uid, RuleID: hitRuleID})
|
||||
// If there are any hit history
|
||||
if v, ok := r.InboundDetectResult.LoadOrStore(tag, newSet); ok {
|
||||
resultSet := v.(mapset.Set)
|
||||
// If this is a new record
|
||||
if resultSet.Add(api.DetectResult{UID: uid, RuleID: hitRuleID}) {
|
||||
r.InboundDetectResult.Store(tag, resultSet)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return reject
|
||||
}
|
||||
func (r *Rule) ProtocolDetect(tag string, protocol string) bool {
|
||||
if value, ok := r.InboundProtocolRule.Load(tag); ok {
|
||||
ruleList := value.([]string)
|
||||
for _, r := range ruleList {
|
||||
if r == protocol {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user