mirror of
https://github.com/XrayR-project/XrayR.git
synced 2025-06-10 14:29:56 +00:00
update: rebuild the implementation of legocmd
This commit is contained in:
parent
3b96b352cb
commit
209f5a17d6
@ -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,24 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
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,226 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/x509"
|
|
||||||
"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"
|
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
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,63 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
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,187 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"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"
|
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
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,130 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"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"
|
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
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,127 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"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"
|
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
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,190 +0,0 @@
|
|||||||
// 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/urfave/cli"
|
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd/cmd"
|
|
||||||
)
|
|
||||||
|
|
||||||
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 path to configPath/cert
|
|
||||||
var path string = ""
|
|
||||||
configPath := os.Getenv("XRAY_LOCATION_CONFIG")
|
|
||||||
if configPath != "" {
|
|
||||||
path = configPath
|
|
||||||
} else if cwd, err := os.Getwd(); err == nil {
|
|
||||||
path = cwd
|
|
||||||
} else {
|
|
||||||
path = "."
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultPath = filepath.Join(path, "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(strings.ToUpper(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("unknow 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/XrayR-project/XrayR/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,4 +1,4 @@
|
|||||||
package cmd
|
package mylego
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
@ -1,4 +1,4 @@
|
|||||||
package cmd
|
package mylego
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
@ -6,8 +6,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"log"
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -16,9 +15,7 @@ import (
|
|||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/go-acme/lego/v4/registration"
|
"github.com/go-acme/lego/v4/registration"
|
||||||
"github.com/urfave/cli"
|
"golang.org/x/crypto/acme"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -66,20 +63,18 @@ type AccountsStorage struct {
|
|||||||
rootUserPath string
|
rootUserPath string
|
||||||
keysPath string
|
keysPath string
|
||||||
accountFilePath string
|
accountFilePath string
|
||||||
ctx *cli.Context
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAccountsStorage Creates a new AccountsStorage.
|
// NewAccountsStorage Creates a new AccountsStorage.
|
||||||
func NewAccountsStorage(ctx *cli.Context) *AccountsStorage {
|
func NewAccountsStorage(l *LegoCMD) *AccountsStorage {
|
||||||
// TODO: move to account struct? Currently MUST pass email.
|
email := l.C.Email
|
||||||
email := getEmail(ctx)
|
|
||||||
|
|
||||||
serverURL, err := url.Parse(ctx.GlobalString("server"))
|
serverURL, err := url.Parse(acme.LetsEncryptURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rootPath := filepath.Join(ctx.GlobalString("path"), baseAccountsRootFolderName)
|
rootPath := filepath.Join(l.path, baseAccountsRootFolderName)
|
||||||
serverPath := strings.NewReplacer(":", "_", "/", string(os.PathSeparator)).Replace(serverURL.Host)
|
serverPath := strings.NewReplacer(":", "_", "/", string(os.PathSeparator)).Replace(serverURL.Host)
|
||||||
accountsPath := filepath.Join(rootPath, serverPath)
|
accountsPath := filepath.Join(rootPath, serverPath)
|
||||||
rootUserPath := filepath.Join(accountsPath, email)
|
rootUserPath := filepath.Join(accountsPath, email)
|
||||||
@ -90,7 +85,6 @@ func NewAccountsStorage(ctx *cli.Context) *AccountsStorage {
|
|||||||
rootUserPath: rootUserPath,
|
rootUserPath: rootUserPath,
|
||||||
keysPath: filepath.Join(rootUserPath, baseKeysFolderName),
|
keysPath: filepath.Join(rootUserPath, baseKeysFolderName),
|
||||||
accountFilePath: filepath.Join(rootUserPath, accountFileName),
|
accountFilePath: filepath.Join(rootUserPath, accountFileName),
|
||||||
ctx: ctx,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,11 +116,11 @@ func (s *AccountsStorage) Save(account *Account) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ioutil.WriteFile(s.accountFilePath, jsonBytes, filePerm)
|
return os.WriteFile(s.accountFilePath, jsonBytes, filePerm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AccountsStorage) LoadAccount(privateKey crypto.PrivateKey) *Account {
|
func (s *AccountsStorage) LoadAccount(privateKey crypto.PrivateKey) *Account {
|
||||||
fileBytes, err := ioutil.ReadFile(s.accountFilePath)
|
fileBytes, err := os.ReadFile(s.accountFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("Could not load file for account %s: %v", s.userID, err)
|
log.Panicf("Could not load file for account %s: %v", s.userID, err)
|
||||||
}
|
}
|
||||||
@ -140,7 +134,7 @@ func (s *AccountsStorage) LoadAccount(privateKey crypto.PrivateKey) *Account {
|
|||||||
account.key = privateKey
|
account.key = privateKey
|
||||||
|
|
||||||
if account.Registration == nil || account.Registration.Body.Status == "" {
|
if account.Registration == nil || account.Registration.Body.Status == "" {
|
||||||
reg, err := tryRecoverRegistration(s.ctx, privateKey)
|
reg, err := tryRecoverRegistration(privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("Could not load account for %s. Registration is nil: %#v", s.userID, err)
|
log.Panicf("Could not load account for %s. Registration is nil: %#v", s.userID, err)
|
||||||
}
|
}
|
||||||
@ -207,7 +201,7 @@ func generatePrivateKey(file string, keyType certcrypto.KeyType) (crypto.Private
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadPrivateKey(file string) (crypto.PrivateKey, error) {
|
func loadPrivateKey(file string) (crypto.PrivateKey, error) {
|
||||||
keyBytes, err := ioutil.ReadFile(file)
|
keyBytes, err := os.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -224,11 +218,11 @@ func loadPrivateKey(file string) (crypto.PrivateKey, error) {
|
|||||||
return nil, errors.New("unknown private key type")
|
return nil, errors.New("unknown private key type")
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryRecoverRegistration(ctx *cli.Context, privateKey crypto.PrivateKey) (*registration.Resource, error) {
|
func tryRecoverRegistration(privateKey crypto.PrivateKey) (*registration.Resource, error) {
|
||||||
// couldn't load account but got a key. Try to look the account up.
|
// couldn't load account but got a key. Try to look the account up.
|
||||||
config := lego.NewConfig(&Account{key: privateKey})
|
config := lego.NewConfig(&Account{key: privateKey})
|
||||||
config.CADirURL = ctx.GlobalString("server")
|
config.CADirURL = acme.LetsEncryptURL
|
||||||
config.UserAgent = fmt.Sprintf("lego-cli/%s", ctx.App.Version)
|
config.UserAgent = "lego-cli/dev"
|
||||||
|
|
||||||
client, err := lego.NewClient(config)
|
client, err := lego.NewClient(config)
|
||||||
if err != nil {
|
if err != nil {
|
163
common/mylego/base.go
Normal file
163
common/mylego/base.go
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
package mylego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var defaultPath string
|
||||||
|
|
||||||
|
func New(certConf *CertConfig) (*LegoCMD, error) {
|
||||||
|
// Set default path to configPath/cert
|
||||||
|
var p = ""
|
||||||
|
configPath := os.Getenv("V2RAY_LOCATION_CONFIG")
|
||||||
|
if configPath != "" {
|
||||||
|
p = configPath
|
||||||
|
} else if cwd, err := os.Getwd(); err == nil {
|
||||||
|
p = cwd
|
||||||
|
} else {
|
||||||
|
p = "."
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultPath = filepath.Join(p, "cert")
|
||||||
|
lego := &LegoCMD{
|
||||||
|
C: certConf,
|
||||||
|
path: defaultPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
return lego, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LegoCMD) getPath() string {
|
||||||
|
return l.path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LegoCMD) getCertConfig() *CertConfig {
|
||||||
|
return l.C
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSCert cert a domain using DNS API
|
||||||
|
func (l *LegoCMD) DNSCert() (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("unknown panic")
|
||||||
|
}
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
return CertPath, KeyPath, nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Set Env for DNS configuration
|
||||||
|
for key, value := range l.C.DNSEnv {
|
||||||
|
os.Setenv(strings.ToUpper(key), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// First check if the certificate exists
|
||||||
|
CertPath, KeyPath, err = checkCertFile(l.C.CertDomain)
|
||||||
|
if err == nil {
|
||||||
|
return CertPath, KeyPath, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = l.Run()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
CertPath, KeyPath, err = checkCertFile(l.C.CertDomain)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
return CertPath, KeyPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPCert cert a domain using http methods
|
||||||
|
func (l *LegoCMD) HTTPCert() (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("unknown panic")
|
||||||
|
}
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
return CertPath, KeyPath, nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
// First check if the certificate exists
|
||||||
|
CertPath, KeyPath, err = checkCertFile(l.C.CertDomain)
|
||||||
|
if err == nil {
|
||||||
|
return CertPath, KeyPath, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = l.Run()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
CertPath, KeyPath, err = checkCertFile(l.C.CertDomain)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return CertPath, KeyPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewCert renew a domain cert
|
||||||
|
func (l *LegoCMD) RenewCert() (CertPath string, KeyPath string, ok bool, err error) {
|
||||||
|
defer func() (string, string, bool, 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 "", "", false, err
|
||||||
|
}
|
||||||
|
return CertPath, KeyPath, ok, nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
ok, err = l.Renew()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
CertPath, KeyPath, err = checkCertFile(l.C.CertDomain)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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,22 +1,17 @@
|
|||||||
package cmd
|
package mylego
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/go-acme/lego/v4/certificate"
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
"github.com/urfave/cli"
|
|
||||||
"golang.org/x/net/idna"
|
"golang.org/x/net/idna"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -24,7 +19,7 @@ const (
|
|||||||
baseArchivesFolderName = "archives"
|
baseArchivesFolderName = "archives"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CertificatesStorage a certificates storage.
|
// CertificatesStorage a certificates' storage.
|
||||||
//
|
//
|
||||||
// rootPath:
|
// rootPath:
|
||||||
//
|
//
|
||||||
@ -38,19 +33,14 @@ const (
|
|||||||
// │ └── archived certificates directory
|
// │ └── archived certificates directory
|
||||||
// └── "path" option
|
// └── "path" option
|
||||||
type CertificatesStorage struct {
|
type CertificatesStorage struct {
|
||||||
rootPath string
|
rootPath string
|
||||||
archivePath string
|
pem bool
|
||||||
pem bool
|
|
||||||
filename string // Deprecated
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCertificatesStorage create a new certificates storage.
|
// NewCertificatesStorage create a new certificates storage.
|
||||||
func NewCertificatesStorage(ctx *cli.Context) *CertificatesStorage {
|
func NewCertificatesStorage(path string) *CertificatesStorage {
|
||||||
return &CertificatesStorage{
|
return &CertificatesStorage{
|
||||||
rootPath: filepath.Join(ctx.GlobalString("path"), baseCertificatesFolderName),
|
rootPath: filepath.Join(path, baseCertificatesFolderName),
|
||||||
archivePath: filepath.Join(ctx.GlobalString("path"), baseArchivesFolderName),
|
|
||||||
pem: ctx.GlobalBool("pem"),
|
|
||||||
filename: ctx.GlobalString("filename"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,13 +51,6 @@ func (s *CertificatesStorage) CreateRootFolder() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
func (s *CertificatesStorage) GetRootPath() string {
|
||||||
return s.rootPath
|
return s.rootPath
|
||||||
}
|
}
|
||||||
@ -144,7 +127,7 @@ func (s *CertificatesStorage) ExistsFile(domain, extension string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *CertificatesStorage) ReadFile(domain, extension string) ([]byte, error) {
|
func (s *CertificatesStorage) ReadFile(domain, extension string) ([]byte, error) {
|
||||||
return ioutil.ReadFile(s.GetFileName(domain, extension))
|
return os.ReadFile(s.GetFileName(domain, extension))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CertificatesStorage) GetFileName(domain, extension string) string {
|
func (s *CertificatesStorage) GetFileName(domain, extension string) string {
|
||||||
@ -163,36 +146,11 @@ func (s *CertificatesStorage) ReadCertificate(domain, extension string) ([]*x509
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *CertificatesStorage) WriteFile(domain, extension string, data []byte) error {
|
func (s *CertificatesStorage) WriteFile(domain, extension string, data []byte) error {
|
||||||
var baseFileName string
|
var baseFileName = sanitizedDomain(domain)
|
||||||
if s.filename != "" {
|
|
||||||
baseFileName = s.filename
|
|
||||||
} else {
|
|
||||||
baseFileName = sanitizedDomain(domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
filePath := filepath.Join(s.rootPath, baseFileName+extension)
|
filePath := filepath.Join(s.rootPath, baseFileName+extension)
|
||||||
|
|
||||||
return ioutil.WriteFile(filePath, data, filePerm)
|
return os.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 ;)).
|
// sanitizedDomain Make sure no funny chars are in the cert names (like wildcards ;)).
|
76
common/mylego/cmd_renew.go
Normal file
76
common/mylego/cmd_renew.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package mylego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/x509"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
|
"github.com/go-acme/lego/v4/lego"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (l *LegoCMD) Renew() (bool, error) {
|
||||||
|
account, client := setup(NewAccountsStorage(l))
|
||||||
|
setupChallenges(l, client)
|
||||||
|
|
||||||
|
if account.Registration == nil {
|
||||||
|
log.Panicf("Account %s is not registered. Use 'run' to register a new account.\n", account.Email)
|
||||||
|
}
|
||||||
|
|
||||||
|
return renewForDomains(l.C.CertDomain, client, NewCertificatesStorage(l.path))
|
||||||
|
}
|
||||||
|
|
||||||
|
func renewForDomains(domain string, client *lego.Client, certsStorage *CertificatesStorage) (bool, error) {
|
||||||
|
// 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, 30) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is just meant to be informal for the user.
|
||||||
|
timeLeft := cert.NotAfter.Sub(time.Now().UTC())
|
||||||
|
log.Printf("[%s] acme: Trying renewal with %d hours remaining", domain, int(timeLeft.Hours()))
|
||||||
|
|
||||||
|
certDomains := certcrypto.ExtractDomains(cert)
|
||||||
|
|
||||||
|
var privateKey crypto.PrivateKey
|
||||||
|
request := certificate.ObtainRequest{
|
||||||
|
Domains: certDomains,
|
||||||
|
PrivateKey: privateKey,
|
||||||
|
}
|
||||||
|
certRes, err := client.Certificate.Obtain(request)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certsStorage.SaveResource(certRes)
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package cmd
|
package mylego
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
@ -116,3 +116,19 @@ func Test_needRenewal(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
67
common/mylego/cmd_run.go
Normal file
67
common/mylego/cmd_run.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package mylego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
|
"github.com/go-acme/lego/v4/lego"
|
||||||
|
"github.com/go-acme/lego/v4/registration"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 (l *LegoCMD) Run() error {
|
||||||
|
accountsStorage := NewAccountsStorage(l)
|
||||||
|
|
||||||
|
account, client := setup(accountsStorage)
|
||||||
|
setupChallenges(l, client)
|
||||||
|
|
||||||
|
if account.Registration == nil {
|
||||||
|
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
|
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(l.path)
|
||||||
|
certsStorage.CreateRootFolder()
|
||||||
|
|
||||||
|
cert, err := obtainCertificate([]string{l.C.CertDomain}, 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)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func obtainCertificate(domains []string, client *lego.Client) (*certificate.Resource, error) {
|
||||||
|
if len(domains) > 0 {
|
||||||
|
// obtain a certificate, generating a new private key
|
||||||
|
request := certificate.ObtainRequest{
|
||||||
|
Domains: domains,
|
||||||
|
}
|
||||||
|
return client.Certificate.Obtain(request)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("not a valid domain")
|
||||||
|
}
|
87
common/mylego/lego_test.go
Normal file
87
common/mylego/lego_test.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package mylego_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/thank243/v2rayS/common/mylego"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLegoClient(t *testing.T) {
|
||||||
|
_, err := mylego.New(&mylego.CertConfig{})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLegoDNSCert(t *testing.T) {
|
||||||
|
lego, err := mylego.New(&mylego.CertConfig{
|
||||||
|
CertDomain: "node1.test.com",
|
||||||
|
Provider: "alidns",
|
||||||
|
Email: "test@gmail.com",
|
||||||
|
DNSEnv: map[string]string{
|
||||||
|
"ALICLOUD_ACCESS_KEY": "aaa",
|
||||||
|
"ALICLOUD_SECRET_KEY": "bbb",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certPath, keyPath, err := lego.DNSCert()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(certPath)
|
||||||
|
t.Log(keyPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLegoHTTPCert(t *testing.T) {
|
||||||
|
lego, err := mylego.New(&mylego.CertConfig{
|
||||||
|
CertMode: "http",
|
||||||
|
CertDomain: "node1.test.com",
|
||||||
|
Email: "test@gmail.com",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certPath, keyPath, err := lego.HTTPCert()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(certPath)
|
||||||
|
t.Log(keyPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLegoRenewCert(t *testing.T) {
|
||||||
|
lego, err := mylego.New(&mylego.CertConfig{
|
||||||
|
CertDomain: "node1.test.com",
|
||||||
|
Email: "test@gmail.com",
|
||||||
|
Provider: "alidns",
|
||||||
|
DNSEnv: map[string]string{
|
||||||
|
"ALICLOUD_ACCESS_KEY": "aaa",
|
||||||
|
"ALICLOUD_SECRET_KEY": "bbb",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
lego.C.CertMode = "http"
|
||||||
|
certPath, keyPath, ok, err := lego.RenewCert()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(certPath)
|
||||||
|
t.Log(keyPath)
|
||||||
|
t.Log(ok)
|
||||||
|
|
||||||
|
lego.C.CertMode = "dns"
|
||||||
|
certPath, keyPath, ok, err = lego.RenewCert()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(certPath)
|
||||||
|
t.Log(keyPath)
|
||||||
|
t.Log(ok)
|
||||||
|
}
|
18
common/mylego/model.go
Normal file
18
common/mylego/model.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package mylego
|
||||||
|
|
||||||
|
type CertConfig struct {
|
||||||
|
CertMode string `mapstructure:"CertMode"` // none, file, http, dns
|
||||||
|
VerifyClientCertificate bool `mapstructure:"VerifyClientCertificate"`
|
||||||
|
CertDomain string `mapstructure:"CertDomain"`
|
||||||
|
CertFile string `mapstructure:"CertFile"`
|
||||||
|
KeyFile string `mapstructure:"KeyFile"`
|
||||||
|
Provider string `mapstructure:"Provider"` // alidns, cloudflare, gandi, godaddy....
|
||||||
|
Email string `mapstructure:"Email"`
|
||||||
|
DNSEnv map[string]string `mapstructure:"DNSEnv"`
|
||||||
|
RejectUnknownSni bool `mapstructure:"RejectUnknownSni"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LegoCMD struct {
|
||||||
|
C *CertConfig
|
||||||
|
path string
|
||||||
|
}
|
95
common/mylego/setup.go
Normal file
95
common/mylego/setup.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package mylego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
|
"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/registration"
|
||||||
|
"golang.org/x/crypto/acme"
|
||||||
|
)
|
||||||
|
|
||||||
|
const filePerm os.FileMode = 0o600
|
||||||
|
|
||||||
|
func setup(accountsStorage *AccountsStorage) (*Account, *lego.Client) {
|
||||||
|
keyType := certcrypto.EC256
|
||||||
|
privateKey := accountsStorage.GetPrivateKey(keyType)
|
||||||
|
|
||||||
|
var account *Account
|
||||||
|
if accountsStorage.ExistsAccountFilePath() {
|
||||||
|
account = accountsStorage.LoadAccount(privateKey)
|
||||||
|
} else {
|
||||||
|
account = &Account{Email: accountsStorage.GetUserID(), key: privateKey}
|
||||||
|
}
|
||||||
|
|
||||||
|
client := newClient(account, keyType)
|
||||||
|
|
||||||
|
return account, client
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClient(acc registration.User, keyType certcrypto.KeyType) *lego.Client {
|
||||||
|
config := lego.NewConfig(acc)
|
||||||
|
config.CADirURL = acme.LetsEncryptURL
|
||||||
|
|
||||||
|
config.Certificate = lego.CertificateConfig{
|
||||||
|
KeyType: keyType,
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
}
|
||||||
|
config.UserAgent = "lego-cli/dev"
|
||||||
|
|
||||||
|
client, err := lego.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicf("Could not create client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
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 setupChallenges(l *LegoCMD, client *lego.Client) {
|
||||||
|
switch l.C.CertMode {
|
||||||
|
case "http":
|
||||||
|
err := client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "5001"))
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
case "tls":
|
||||||
|
err := client.Challenge.SetTLSALPN01Provider(tlsalpn01.NewProviderServer("", "5002"))
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
case "dns":
|
||||||
|
setupDNS(l.C.Provider, client)
|
||||||
|
default:
|
||||||
|
log.Panic("No challenge selected. You must specify at least one challenge: `http`, `tls`, `dns`.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupDNS(p string, client *lego.Client) {
|
||||||
|
provider, err := dns.NewDNSChallengeProviderByName(p)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.Challenge.SetDNS01Provider(
|
||||||
|
provider,
|
||||||
|
dns01.CondOption(true, dns01.AddDNSTimeout(10*time.Second)),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
}
|
@ -6,13 +6,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
LogConfig *LogConfig `mapstructure:"Log"`
|
LogConfig *LogConfig `mapstructure:"Log"`
|
||||||
DnsConfigPath string `mapstructure:"DnsConfigPath"`
|
DnsConfigPath string `mapstructure:"DnsConfigPath"`
|
||||||
InboundConfigPath string `mapstructure:"InboundConfigPath"`
|
InboundConfigPath string `mapstructure:"InboundConfigPath"`
|
||||||
OutboundConfigPath string `mapstructure:"OutboundConfigPath"`
|
OutboundConfigPath string `mapstructure:"OutboundConfigPath"`
|
||||||
RouteConfigPath string `mapstructure:"RouteConfigPath"`
|
RouteConfigPath string `mapstructure:"RouteConfigPath"`
|
||||||
ConnetionConfig *ConnetionConfig `mapstructure:"ConnetionConfig"`
|
ConnectionConfig *ConnectionConfig `mapstructure:"ConnectionConfig"`
|
||||||
NodesConfig []*NodesConfig `mapstructure:"Nodes"`
|
NodesConfig []*NodesConfig `mapstructure:"Nodes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodesConfig struct {
|
type NodesConfig struct {
|
||||||
@ -27,7 +27,7 @@ type LogConfig struct {
|
|||||||
ErrorPath string `mapstructure:"ErrorPath"`
|
ErrorPath string `mapstructure:"ErrorPath"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConnetionConfig struct {
|
type ConnectionConfig struct {
|
||||||
Handshake uint32 `mapstructure:"handshake"`
|
Handshake uint32 `mapstructure:"handshake"`
|
||||||
ConnIdle uint32 `mapstructure:"connIdle"`
|
ConnIdle uint32 `mapstructure:"connIdle"`
|
||||||
UplinkOnly uint32 `mapstructure:"uplinkOnly"`
|
UplinkOnly uint32 `mapstructure:"uplinkOnly"`
|
||||||
|
@ -10,8 +10,8 @@ func getDefaultLogConfig() *LogConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDefaultConnetionConfig() *ConnetionConfig {
|
func getDefaultConnectionConfig() *ConnectionConfig {
|
||||||
return &ConnetionConfig{
|
return &ConnectionConfig{
|
||||||
Handshake: 4,
|
Handshake: 4,
|
||||||
ConnIdle: 30,
|
ConnIdle: 30,
|
||||||
UplinkOnly: 2,
|
UplinkOnly: 2,
|
||||||
|
@ -123,7 +123,7 @@ func (p *Panel) loadCore(panelConfig *Config) *core.Instance {
|
|||||||
outBoundConfig = append(outBoundConfig, oc)
|
outBoundConfig = append(outBoundConfig, oc)
|
||||||
}
|
}
|
||||||
// Policy config
|
// Policy config
|
||||||
levelPolicyConfig := parseConnectionConfig(panelConfig.ConnetionConfig)
|
levelPolicyConfig := parseConnectionConfig(panelConfig.ConnectionConfig)
|
||||||
corePolicyConfig := &conf.PolicyConfig{}
|
corePolicyConfig := &conf.PolicyConfig{}
|
||||||
corePolicyConfig.Levels = map[uint32]*conf.Policy{0: levelPolicyConfig}
|
corePolicyConfig.Levels = map[uint32]*conf.Policy{0: levelPolicyConfig}
|
||||||
policyConfig, _ := corePolicyConfig.Build()
|
policyConfig, _ := corePolicyConfig.Build()
|
||||||
@ -219,11 +219,11 @@ func (p *Panel) Close() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseConnectionConfig(c *ConnetionConfig) (policy *conf.Policy) {
|
func parseConnectionConfig(c *ConnectionConfig) (policy *conf.Policy) {
|
||||||
connetionConfig := getDefaultConnetionConfig()
|
connetionConfig := getDefaultConnectionConfig()
|
||||||
if c != nil {
|
if c != nil {
|
||||||
if _, err := diff.Merge(connetionConfig, c, connetionConfig); err != nil {
|
if _, err := diff.Merge(connetionConfig, c, connetionConfig); err != nil {
|
||||||
log.Panicf("Read ConnetionConfig failed: %s", err)
|
log.Panicf("Read ConnectionConfig failed: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
policy = &conf.Policy{
|
policy = &conf.Policy{
|
||||||
|
@ -2,13 +2,14 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/XrayR-project/XrayR/common/limiter"
|
"github.com/XrayR-project/XrayR/common/limiter"
|
||||||
|
"github.com/XrayR-project/XrayR/common/mylego"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
ListenIP string `mapstructure:"ListenIP"`
|
ListenIP string `mapstructure:"ListenIP"`
|
||||||
SendIP string `mapstructure:"SendIP"`
|
SendIP string `mapstructure:"SendIP"`
|
||||||
UpdatePeriodic int `mapstructure:"UpdatePeriodic"`
|
UpdatePeriodic int `mapstructure:"UpdatePeriodic"`
|
||||||
CertConfig *CertConfig `mapstructure:"CertConfig"`
|
CertConfig *mylego.CertConfig `mapstructure:"CertConfig"`
|
||||||
EnableDNS bool `mapstructure:"EnableDNS"`
|
EnableDNS bool `mapstructure:"EnableDNS"`
|
||||||
DNSType string `mapstructure:"DNSType"`
|
DNSType string `mapstructure:"DNSType"`
|
||||||
DisableUploadTraffic bool `mapstructure:"DisableUploadTraffic"`
|
DisableUploadTraffic bool `mapstructure:"DisableUploadTraffic"`
|
||||||
@ -29,17 +30,6 @@ type AutoSpeedLimitConfig struct {
|
|||||||
LimitDuration int `mapstructure:"LimitDuration"` // minute
|
LimitDuration int `mapstructure:"LimitDuration"` // minute
|
||||||
}
|
}
|
||||||
|
|
||||||
type CertConfig struct {
|
|
||||||
CertMode string `mapstructure:"CertMode"` // none, file, http, dns
|
|
||||||
RejectUnknownSni bool `mapstructure:"RejectUnknownSni"`
|
|
||||||
CertDomain string `mapstructure:"CertDomain"`
|
|
||||||
CertFile string `mapstructure:"CertFile"`
|
|
||||||
KeyFile string `mapstructure:"KeyFile"`
|
|
||||||
Provider string `mapstructure:"Provider"` // alidns, cloudflare, gandi, godaddy....
|
|
||||||
Email string `mapstructure:"Email"`
|
|
||||||
DNSEnv map[string]string `mapstructure:"DNSEnv"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FallBackConfig struct {
|
type FallBackConfig struct {
|
||||||
SNI string `mapstructure:"SNI"`
|
SNI string `mapstructure:"SNI"`
|
||||||
Alpn string `mapstructure:"Alpn"`
|
Alpn string `mapstructure:"Alpn"`
|
||||||
|
@ -16,8 +16,8 @@ import (
|
|||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
"github.com/XrayR-project/XrayR/api"
|
||||||
"github.com/XrayR-project/XrayR/app/mydispatcher"
|
"github.com/XrayR-project/XrayR/app/mydispatcher"
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd"
|
|
||||||
"github.com/XrayR-project/XrayR/common/limiter"
|
"github.com/XrayR-project/XrayR/common/limiter"
|
||||||
|
"github.com/XrayR-project/XrayR/common/mylego"
|
||||||
"github.com/XrayR-project/XrayR/common/serverstatus"
|
"github.com/XrayR-project/XrayR/common/serverstatus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -221,12 +221,12 @@ func (c *Controller) nodeInfoMonitor() (err error) {
|
|||||||
|
|
||||||
// Check Cert
|
// Check Cert
|
||||||
if c.nodeInfo.EnableTLS && (c.config.CertConfig.CertMode == "dns" || c.config.CertConfig.CertMode == "http") {
|
if c.nodeInfo.EnableTLS && (c.config.CertConfig.CertMode == "dns" || c.config.CertConfig.CertMode == "http") {
|
||||||
lego, err := legocmd.New()
|
lego, err := mylego.New(c.config.CertConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
}
|
}
|
||||||
// Xray-core supports the OcspStapling certification hot renew
|
// Xray-core supports the OcspStapling certification hot renew
|
||||||
_, _, err = lego.RenewCert(c.config.CertConfig.CertDomain, c.config.CertConfig.Email, c.config.CertConfig.CertMode, c.config.CertConfig.Provider, c.config.CertConfig.DNSEnv)
|
_, _, _, err = lego.RenewCert()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
"github.com/XrayR-project/XrayR/api"
|
||||||
"github.com/XrayR-project/XrayR/api/sspanel"
|
"github.com/XrayR-project/XrayR/api/sspanel"
|
||||||
|
"github.com/XrayR-project/XrayR/common/mylego"
|
||||||
_ "github.com/XrayR-project/XrayR/main/distro/all"
|
_ "github.com/XrayR-project/XrayR/main/distro/all"
|
||||||
. "github.com/XrayR-project/XrayR/service/controller"
|
. "github.com/XrayR-project/XrayR/service/controller"
|
||||||
)
|
)
|
||||||
@ -46,7 +47,7 @@ func TestController(t *testing.T) {
|
|||||||
if err = server.Start(); err != nil {
|
if err = server.Start(); err != nil {
|
||||||
t.Errorf("Failed to start instance: %s", err)
|
t.Errorf("Failed to start instance: %s", err)
|
||||||
}
|
}
|
||||||
certConfig := &CertConfig{
|
certConfig := &mylego.CertConfig{
|
||||||
CertMode: "http",
|
CertMode: "http",
|
||||||
CertDomain: "test.ss.tk",
|
CertDomain: "test.ss.tk",
|
||||||
Provider: "alidns",
|
Provider: "alidns",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Package generate the InbounderConfig used by add inbound
|
// Package controller Package generate the InboundConfig used by add inbound
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/infra/conf"
|
"github.com/xtls/xray-core/infra/conf"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
"github.com/XrayR-project/XrayR/api"
|
||||||
"github.com/XrayR-project/XrayR/common/legocmd"
|
"github.com/XrayR-project/XrayR/common/mylego"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InboundBuilder build Inbound config for different protocol
|
// InboundBuilder build Inbound config for different protocol
|
||||||
@ -20,10 +20,10 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
// Build Listen IP address
|
// Build Listen IP address
|
||||||
if nodeInfo.NodeType == "Shadowsocks-Plugin" {
|
if nodeInfo.NodeType == "Shadowsocks-Plugin" {
|
||||||
// Shdowsocks listen in 127.0.0.1 for safety
|
// Shdowsocks listen in 127.0.0.1 for safety
|
||||||
inboundDetourConfig.ListenOn = &conf.Address{net.ParseAddress("127.0.0.1")}
|
inboundDetourConfig.ListenOn = &conf.Address{Address: net.ParseAddress("127.0.0.1")}
|
||||||
} else if config.ListenIP != "" {
|
} else if config.ListenIP != "" {
|
||||||
ipAddress := net.ParseAddress(config.ListenIP)
|
ipAddress := net.ParseAddress(config.ListenIP)
|
||||||
inboundDetourConfig.ListenOn = &conf.Address{ipAddress}
|
inboundDetourConfig.ListenOn = &conf.Address{Address: ipAddress}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build Port
|
// Build Port
|
||||||
@ -115,12 +115,12 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
NetworkList: []string{"tcp", "udp"},
|
NetworkList: []string{"tcp", "udp"},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("Unsupported node type: %s, Only support: V2ray, Trojan, Shadowsocks, and Shadowsocks-Plugin", nodeInfo.NodeType)
|
return nil, fmt.Errorf("unsupported node type: %s, Only support: V2ray, Trojan, Shadowsocks, and Shadowsocks-Plugin", nodeInfo.NodeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
setting, err := json.Marshal(proxySetting)
|
setting, err := json.Marshal(proxySetting)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Marshal proxy %s config fialed: %s", nodeInfo.NodeType, err)
|
return nil, fmt.Errorf("marshal proxy %s config fialed: %s", nodeInfo.NodeType, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build streamSettings
|
// Build streamSettings
|
||||||
@ -196,53 +196,53 @@ func InboundBuilder(config *Config, nodeInfo *api.NodeInfo, tag string) (*core.I
|
|||||||
return inboundDetourConfig.Build()
|
return inboundDetourConfig.Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCertFile(certConfig *CertConfig) (certFile string, keyFile string, err error) {
|
func getCertFile(certConfig *mylego.CertConfig) (certFile string, keyFile string, err error) {
|
||||||
if certConfig.CertMode == "file" {
|
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")
|
||||||
}
|
}
|
||||||
return certConfig.CertFile, certConfig.KeyFile, nil
|
return certConfig.CertFile, certConfig.KeyFile, nil
|
||||||
} else if certConfig.CertMode == "dns" {
|
} else if certConfig.CertMode == "dns" {
|
||||||
lego, err := legocmd.New()
|
lego, err := mylego.New(certConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
certPath, keyPath, err := lego.DNSCert(certConfig.CertDomain, certConfig.Email, certConfig.Provider, certConfig.DNSEnv)
|
certPath, keyPath, err := lego.DNSCert()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
return certPath, keyPath, err
|
return certPath, keyPath, err
|
||||||
} else if certConfig.CertMode == "http" {
|
} else if certConfig.CertMode == "http" {
|
||||||
lego, err := legocmd.New()
|
lego, err := mylego.New(certConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
certPath, keyPath, err := lego.HTTPCert(certConfig.CertDomain, certConfig.Email)
|
certPath, keyPath, err := lego.HTTPCert()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
return certPath, keyPath, err
|
return certPath, keyPath, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", "", fmt.Errorf("Unsupported certmode: %s", certConfig.CertMode)
|
return "", "", fmt.Errorf("unsupported certmode: %s", certConfig.CertMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildVlessFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.VLessInboundFallback, error) {
|
func buildVlessFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.VLessInboundFallback, error) {
|
||||||
if fallbackConfigs == nil {
|
if fallbackConfigs == nil {
|
||||||
return nil, fmt.Errorf("You must provide FallBackConfigs")
|
return nil, fmt.Errorf("you must provide FallBackConfigs")
|
||||||
}
|
}
|
||||||
|
|
||||||
vlessFallBacks := make([]*conf.VLessInboundFallback, len(fallbackConfigs))
|
vlessFallBacks := make([]*conf.VLessInboundFallback, len(fallbackConfigs))
|
||||||
for i, c := range fallbackConfigs {
|
for i, c := range fallbackConfigs {
|
||||||
|
|
||||||
if c.Dest == "" {
|
if c.Dest == "" {
|
||||||
return nil, fmt.Errorf("Dest is required for fallback fialed")
|
return nil, fmt.Errorf("dest is required for fallback fialed")
|
||||||
}
|
}
|
||||||
|
|
||||||
var dest json.RawMessage
|
var dest json.RawMessage
|
||||||
dest, err := json.Marshal(c.Dest)
|
dest, err := json.Marshal(c.Dest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Marshal dest %s config fialed: %s", dest, err)
|
return nil, fmt.Errorf("marshal dest %s config fialed: %s", dest, err)
|
||||||
}
|
}
|
||||||
vlessFallBacks[i] = &conf.VLessInboundFallback{
|
vlessFallBacks[i] = &conf.VLessInboundFallback{
|
||||||
Name: c.SNI,
|
Name: c.SNI,
|
||||||
@ -257,20 +257,20 @@ func buildVlessFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.VLessInboun
|
|||||||
|
|
||||||
func buildTrojanFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.TrojanInboundFallback, error) {
|
func buildTrojanFallbacks(fallbackConfigs []*FallBackConfig) ([]*conf.TrojanInboundFallback, error) {
|
||||||
if fallbackConfigs == nil {
|
if fallbackConfigs == nil {
|
||||||
return nil, fmt.Errorf("You must provide FallBackConfigs")
|
return nil, fmt.Errorf("you must provide FallBackConfigs")
|
||||||
}
|
}
|
||||||
|
|
||||||
trojanFallBacks := make([]*conf.TrojanInboundFallback, len(fallbackConfigs))
|
trojanFallBacks := make([]*conf.TrojanInboundFallback, len(fallbackConfigs))
|
||||||
for i, c := range fallbackConfigs {
|
for i, c := range fallbackConfigs {
|
||||||
|
|
||||||
if c.Dest == "" {
|
if c.Dest == "" {
|
||||||
return nil, fmt.Errorf("Dest is required for fallback fialed")
|
return nil, fmt.Errorf("dest is required for fallback fialed")
|
||||||
}
|
}
|
||||||
|
|
||||||
var dest json.RawMessage
|
var dest json.RawMessage
|
||||||
dest, err := json.Marshal(c.Dest)
|
dest, err := json.Marshal(c.Dest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Marshal dest %s config fialed: %s", dest, err)
|
return nil, fmt.Errorf("marshal dest %s config fialed: %s", dest, err)
|
||||||
}
|
}
|
||||||
trojanFallBacks[i] = &conf.TrojanInboundFallback{
|
trojanFallBacks[i] = &conf.TrojanInboundFallback{
|
||||||
Name: c.SNI,
|
Name: c.SNI,
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/XrayR-project/XrayR/api"
|
"github.com/XrayR-project/XrayR/api"
|
||||||
|
"github.com/XrayR-project/XrayR/common/mylego"
|
||||||
. "github.com/XrayR-project/XrayR/service/controller"
|
. "github.com/XrayR-project/XrayR/service/controller"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ func TestBuildV2ray(t *testing.T) {
|
|||||||
EnableTLS: false,
|
EnableTLS: false,
|
||||||
TLSType: "tls",
|
TLSType: "tls",
|
||||||
}
|
}
|
||||||
certConfig := &CertConfig{
|
certConfig := &mylego.CertConfig{
|
||||||
CertMode: "http",
|
CertMode: "http",
|
||||||
CertDomain: "test.test.tk",
|
CertDomain: "test.test.tk",
|
||||||
Provider: "alidns",
|
Provider: "alidns",
|
||||||
@ -51,7 +52,7 @@ func TestBuildTrojan(t *testing.T) {
|
|||||||
DNSEnv := make(map[string]string)
|
DNSEnv := make(map[string]string)
|
||||||
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
|
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
|
||||||
DNSEnv["ALICLOUD_SECRET_KEY"] = "bbb"
|
DNSEnv["ALICLOUD_SECRET_KEY"] = "bbb"
|
||||||
certConfig := &CertConfig{
|
certConfig := &mylego.CertConfig{
|
||||||
CertMode: "dns",
|
CertMode: "dns",
|
||||||
CertDomain: "trojan.test.tk",
|
CertDomain: "trojan.test.tk",
|
||||||
Provider: "alidns",
|
Provider: "alidns",
|
||||||
@ -83,7 +84,7 @@ func TestBuildSS(t *testing.T) {
|
|||||||
DNSEnv := make(map[string]string)
|
DNSEnv := make(map[string]string)
|
||||||
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
|
DNSEnv["ALICLOUD_ACCESS_KEY"] = "aaa"
|
||||||
DNSEnv["ALICLOUD_SECRET_KEY"] = "bbb"
|
DNSEnv["ALICLOUD_SECRET_KEY"] = "bbb"
|
||||||
certConfig := &CertConfig{
|
certConfig := &mylego.CertConfig{
|
||||||
CertMode: "dns",
|
CertMode: "dns",
|
||||||
CertDomain: "trojan.test.tk",
|
CertDomain: "trojan.test.tk",
|
||||||
Provider: "alidns",
|
Provider: "alidns",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user