diff --git a/.gitignore b/.gitignore index 0c268de..c28c8ed 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ /dist localhost.key localhost.crt + +.vscode \ No newline at end of file diff --git a/Makefile b/Makefile index a0d63a0..5f82378 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ clean: ## fmt: Go Format fmt: @echo "Gofmt..." - if [ -n "$(gofmt -l ./...)" ]; then echo "Go code is not formatted"; exit 1; fi + @if [ -n "$(gofmt -l ./...)" ]; then echo "Go code is not formatted"; exit 1; fi ## help: prints this help message help: diff --git a/README.md b/README.md index 8ab084a..351bd40 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ $ torproxy start --domain mywebsite.com --registry '[{"endpoint": "http://somewh $ torproxy start --domain mywebsite.com --registry https://raw.githubusercontent.com/tdex-network/tdex-registry/master/registry.json ``` +With a URL, the proxy will refetch the registry every 12 hours in order to auto-update the set of endpoints to redirects. * Load registry from local path to file diff --git a/cmd/start.go b/cmd/start.go index b7f4900..2d6e81c 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -4,11 +4,12 @@ import ( "errors" "fmt" "log" - "net/url" "os" "os/signal" "syscall" + "time" + registrypkg "github.com/tdex-network/tor-proxy/pkg/registry" "github.com/tdex-network/tor-proxy/pkg/torproxy" "github.com/urfave/cli/v2" "github.com/weppos/publicsuffix-go/publicsuffix" @@ -64,25 +65,19 @@ var start = cli.Command{ Usage: "the socks5 port exposed by the tor client", Value: 9050, }, + &cli.IntFlag{ + Name: "auto-update-period", + Usage: "period in hours to check for new endpoints", + Value: 12, + }, }, Action: startAction, } func startAction(ctx *cli.Context) error { - - // load registry json - registryBytes, err := getRegistryJSON(ctx.String("registry")) - if err != nil { - return fmt.Errorf("laoding json: %w", err) - } - - // parse registry json - redirects, err := registryJSONToRedirects(registryBytes) - if err != nil { - return fmt.Errorf("validating json: %w", err) - } - var proxy *torproxy.TorProxy + var err error + if ctx.Bool("use-tor") { // use the embedded tor client and expose it on :9050 proxy, err = torproxy.NewTorProxy() @@ -97,8 +92,27 @@ func startAction(ctx *cli.Context) error { return fmt.Errorf("creating tor instance: %w", err) } - // Add redirects to the proxy - proxy.WithRedirects(redirects) + // create registry + registry, err := registrypkg.NewRegistry(ctx.String("registry")) + if err != nil { + return fmt.Errorf("loading json: %w", err) + } + + // Add registry to the proxy + // this will init the set of redirects + // in case of remote registry (an URL): start auto-updater + proxy.WithRegistry(registry) + + if proxy.Registry.RegistryType() == registrypkg.RemoteRegistryType { + errorHandler := func (err error) { + log.Println("registry auto update error: %w", err) + } + + period := ctx.Int("auto-update-period") + autoUpdatePeriod := time.Duration(period) * time.Hour + log.Printf("starting registry auto update every %s", autoUpdatePeriod) + proxy.WithAutoUpdater(autoUpdatePeriod, errorHandler) + } // check if insecure flag, otherwise either domain or key & cert paths MUST be present to serve with TLS var address string @@ -136,7 +150,8 @@ func startAction(ctx *cli.Context) error { if err := proxy.Serve(address, tlsOptions); err != nil { return fmt.Errorf("serving proxy: %w", err) } - defer proxy.Listener.Close() + // close the proxy when the process is interrupted + defer proxy.Close() // close the auto-updater in case of remote registry // Catch SIGTERM and SIGINT signals sigChan := make(chan os.Signal, 1) @@ -148,41 +163,7 @@ func startAction(ctx *cli.Context) error { return nil } -func isValidURL(s string) bool { - _, err := url.ParseRequestURI(s) - if err != nil { - return false - } - - return true -} - func isValidDomain(d string) bool { _, err := publicsuffix.Parse(d) - if err != nil { - return false - } - - return true -} - -// getRegistryJSON will check if the given string is a) a JSON by itself b) if is a path to a file c) remote url -func getRegistryJSON(source string) ([]byte, error) { - - // check if it is a json the given source already - if isArrayOfObjectsJSON(source) { - return []byte(source), nil - } - - // check if is a valid URL - if isValidURL(source) { - return fetchFromRemoteURL(source) - } - - // in the end check if is a path to a file. If it exists try to read - if _, err := os.Stat(source); !os.IsNotExist(err) { - return fetchFromFilePath(source) - } - - return nil, errors.New("source must be either a valid JSON string, a remote URL or a valid path to a JSON file") + return err == nil } diff --git a/go.mod b/go.mod index 269950f..0933623 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,17 @@ module github.com/tdex-network/tor-proxy go 1.15 require ( - github.com/caddyserver/certmagic v0.12.0 - github.com/cretz/bine v0.1.0 - github.com/ipsn/go-libtor v1.0.366 + github.com/caddyserver/certmagic v0.15.2 + github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect + github.com/cretz/bine v0.2.0 + github.com/ipsn/go-libtor v1.0.380 github.com/urfave/cli/v2 v2.3.0 - github.com/weppos/publicsuffix-go v0.13.0 - golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect - golang.org/x/net v0.0.0-20210119194325-5f4716e94777 - golang.org/x/sys v0.0.0-20210217105451-b926d437f341 // indirect + github.com/weppos/publicsuffix-go v0.15.0 + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.7.0 // indirect + go.uber.org/zap v1.19.1 // indirect + golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 // indirect + golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9 + golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 // indirect + golang.org/x/text v0.3.7 // indirect ) diff --git a/go.sum b/go.sum index bb480f1..871a7e7 100644 --- a/go.sum +++ b/go.sum @@ -1,111 +1,134 @@ -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/caddyserver/certmagic v0.12.0 h1:1f7kxykaJkOVVpXJ8ZrC6RAO5F6+kKm9U7dBFbLNeug= -github.com/caddyserver/certmagic v0.12.0/go.mod h1:tr26xh+9fY5dN0J6IPAlMj07qpog22PJKa7Nw7j835U= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/caddyserver/certmagic v0.15.2 h1:OMTakTsLM1ZfzMDjwvYprfUgFzpVPh3u87oxMPwmeBc= +github.com/caddyserver/certmagic v0.15.2/go.mod h1:qhkAOthf72ufAcp3Y5jF2RaGE96oip3UbEQRIzwe3/8= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cretz/bine v0.1.0 h1:1/fvhLE+fk0bPzjdO5Ci+0ComYxEMuB1JhM4X5skT3g= +github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= +github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/ipsn/go-libtor v1.0.366 h1:2CDA7H1iR9TcOAl+sibbvVei+JMghf4YfRNSwEraC+o= -github.com/ipsn/go-libtor v1.0.366/go.mod h1:6rIeHU7irp8ZH8E/JqaEOKlD6s4vSSUh4ngHelhlSMw= +github.com/ipsn/go-libtor v1.0.380 h1:hCmALDBe3bPpgwMunonMLArrG41MxzpE91Bk8KQYnYM= +github.com/ipsn/go-libtor v1.0.380/go.mod h1:6rIeHU7irp8ZH8E/JqaEOKlD6s4vSSUh4ngHelhlSMw= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/cpuid v1.2.5 h1:VBd9MyVIiJHzzgnrLQG5Bcv75H4YaWrlKqWHjurxCGo= -github.com/klauspost/cpuid v1.2.5/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/libdns/libdns v0.1.0 h1:0ctCOrVJsVzj53mop1angHp/pE3hmAhP7KiHvR0HD04= -github.com/libdns/libdns v0.1.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= -github.com/mholt/acmez v0.1.1 h1:KQODCqk+hBn3O7qfCRPj6L96uG65T5BSS95FKNEqtdA= -github.com/mholt/acmez v0.1.1/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM= -github.com/miekg/dns v1.1.30 h1:Qww6FseFn8PRfw07jueqIXqodm0JKiiKuK0DeXSqfyo= -github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis= +github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= +github.com/mholt/acmez v1.0.1 h1:J7uquHOKEmo71UDnVApy1sSLA0oF/r+NtVrNzMKKA9I= +github.com/mholt/acmez v1.0.1/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM= +github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/weppos/publicsuffix-go v0.13.0 h1:0Tu1uzLBd1jPn4k6OnMmOPZH/l/9bj9kUOMMkoRs6Gg= -github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= -go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= +github.com/weppos/publicsuffix-go v0.15.0 h1:2uQCwDczZ8YZe5uD0mM3sXRoZYA74xxPuiKK8LdPcGQ= +github.com/weppos/publicsuffix-go v0.15.0/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5 h1:bselrhR0Or1vomJZC8ZIjWtbDmn9OYFLX5Ik9alpJpE= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI= +golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9 h1:0qxwC5n+ttVOINCBeRHO0nq9X7uy8SDsPoi5OaCdIEI= +golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210217105451-b926d437f341 h1:2/QtM1mL37YmcsT8HaDNHDgTqqFVw+zr8UzMiBVLzYU= -golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 h1:TyHqChC80pFkXWraUUf6RuB5IqFdQieMLwwCJokV2pc= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA= -golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go new file mode 100644 index 0000000..44b34ac --- /dev/null +++ b/pkg/registry/registry.go @@ -0,0 +1,120 @@ +package registry + +import ( + "errors" + "os" + "time" +) + +// RegistryType is an enum-like type that represents the type of registry +// useful to conditionally observe the registry according to its type +type RegistryType int + +const ( + ConstantRegistryType RegistryType = iota + RemoteRegistryType +) + +// Registry is the interface for the JSON registry +// It is used to get the JSON bytes representing a set of endpoints to redirects +type Registry interface { + RegistryType() RegistryType + GetJSON() ([]byte, error) +} + +type ObserveRegistryResult struct { + Json []byte + Err error +} + +// Observe the registry for changes +// returns json bytes and errors via channels +func Observe(registry Registry, period time.Duration) (<-chan ObserveRegistryResult, func()) { + resultChan := make(chan ObserveRegistryResult) + + ticker := time.NewTicker(period) + done := make(chan struct{}) + + go func() { + for { + select { + case <-done: + return + + case <-ticker.C: + json, err := registry.GetJSON() + resultChan <- ObserveRegistryResult{json, err} + } + } + }() + + return resultChan, func() { + ticker.Stop() + done <- struct{}{} + close(resultChan) + } +} + +// ConstantRegistry is a registry that always returns the same JSON value +// it can be created from a JSON string or from a file path +type ConstantRegistry struct { + json []byte +} + +func (c *ConstantRegistry) RegistryType() RegistryType { + return ConstantRegistryType +} + +func (c *ConstantRegistry) GetJSON() ([]byte, error) { + return c.json, nil +} + +func newConstantRegistry(json []byte) *ConstantRegistry { + return &ConstantRegistry{json} +} + +func newConstantRegistryFromFilePath(source string) (*ConstantRegistry, error) { + json, err := fetchFromFilePath(source) + if err != nil { + return nil, err + } + return newConstantRegistry(json), nil +} + +// RemoteRegistry represents a registry that is fetched from a remote URL +// it can be created from a valid URL returning the JSON value on http GET request +type RemoteRegistry struct { + url string +} + +func (r *RemoteRegistry) RegistryType() RegistryType { + return RemoteRegistryType +} + +func (r *RemoteRegistry) GetJSON() ([]byte, error) { + return fetchFromRemoteURL(r.url) +} + +func newRemoteRegistryFromURL(url string) *RemoteRegistry { + return &RemoteRegistry{url} +} + +// getRegistry will check if the given string is a) a JSON by itself b) if is a path to a file c) remote url +func NewRegistry(source string) (Registry, error) { + // check if it is a json the given source already + if isArrayOfObjectsJSON(source) { + return newConstantRegistry([]byte(source)), nil + } + + // check if is a valid URL + if isValidURL(source) { + return newRemoteRegistryFromURL(source), nil + } + + // in the end check if is a path to a file. If it exists try to read + if _, err := os.Stat(source); !os.IsNotExist(err) { + return newConstantRegistryFromFilePath(source) + } + + return nil, errors.New("source must be either a valid JSON string, a remote URL or a valid path to a JSON file") +} \ No newline at end of file diff --git a/cmd/registryutil.go b/pkg/registry/registryutil.go similarity index 68% rename from cmd/registryutil.go rename to pkg/registry/registryutil.go index 335dc76..b817cc5 100644 --- a/cmd/registryutil.go +++ b/pkg/registry/registryutil.go @@ -1,4 +1,4 @@ -package main +package registry import ( "encoding/json" @@ -7,7 +7,7 @@ import ( "io/ioutil" "log" "net/http" - "strings" + "net/url" "time" ) @@ -17,6 +17,11 @@ func isArrayOfObjectsJSON(s string) bool { } +func isValidURL(s string) bool { + _, err := url.ParseRequestURI(s) + return err == nil +} + func fetchFromFilePath(source string) ([]byte, error) { data, err := ioutil.ReadFile(source) if err != nil { @@ -58,21 +63,3 @@ func fetchFromRemoteURL(source string) ([]byte, error) { return body, nil } -func registryJSONToRedirects(registryJSON []byte) ([]string, error) { - var data []map[string]string - err := json.Unmarshal(registryJSON, &data) - if err != nil { - return nil, fmt.Errorf("invalid JSON: %w", err) - } - redirects := make([]string, 0) - for _, v := range data { - if strings.Contains(v["endpoint"], "onion") { - redirects = append(redirects, v["endpoint"]) - } - } - if len(redirects) == 0 { - return nil, errors.New("no valid onion endpoints found") - } - - return redirects, nil -} diff --git a/pkg/torproxy/tor.go b/pkg/torproxy/tor.go index 22e7a4d..5f2db27 100644 --- a/pkg/torproxy/tor.go +++ b/pkg/torproxy/tor.go @@ -1,6 +1,7 @@ package torproxy import ( + "context" "fmt" "os" @@ -21,7 +22,7 @@ type TorClient struct { // NewTorEmbedded starts the embedded tor client func NewTorEmbedded() (*TorClient, error) { // Starting tor please wait a bit... - torInstance, err := tor.Start(nil, &tor.StartConf{ + torInstance, err := tor.Start(context.Background(), &tor.StartConf{ NoAutoSocksPort: true, ProcessCreator: libtor.Creator, DebugWriter: os.Stderr, @@ -29,7 +30,7 @@ func NewTorEmbedded() (*TorClient, error) { DataDir: "/tmp/tordir", }) if err != nil { - return nil, fmt.Errorf("Failed to start tor: %v", err) + return nil, fmt.Errorf("failed to start tor: %v", err) } return &TorClient{ diff --git a/pkg/torproxy/torproxy.go b/pkg/torproxy/torproxy.go index 9c6410f..477c192 100644 --- a/pkg/torproxy/torproxy.go +++ b/pkg/torproxy/torproxy.go @@ -2,14 +2,18 @@ package torproxy import ( "crypto/tls" + "encoding/json" + "errors" "fmt" "log" "net" "net/http" "net/url" "strings" + "time" "github.com/caddyserver/certmagic" + "github.com/tdex-network/tor-proxy/pkg/registry" "golang.org/x/net/http2" "golang.org/x/net/proxy" ) @@ -19,10 +23,12 @@ type TorProxy struct { Address string Domains []string Client *TorClient + Registry registry.Registry Redirects []*url.URL Listener net.Listener useTLS bool + closeAutoUpdaterFunc func() } // NewTorProxyFromHostAndPort returns a *TorProxy with givnen host and port @@ -61,7 +67,7 @@ func NewTorProxyFromHostAndPort(torHost string, torPort int) (*TorProxy, error) func NewTorProxy() (*TorProxy, error) { torClient, err := NewTorEmbedded() if err != nil { - return nil, fmt.Errorf("Couldn't start tor client: %w", err) + return nil, fmt.Errorf("couldn't start tor client: %w", err) } return &TorProxy{ @@ -69,22 +75,72 @@ func NewTorProxy() (*TorProxy, error) { }, nil } +func (tp *TorProxy) WithRegistry(regis registry.Registry) error { + tp.Registry = regis + registryJSON, err := tp.Registry.GetJSON() + if err != nil { + return err + } + + return tp.setRedirectsFromRegistry(registryJSON) +} + // WithRedirects modify the TorProxy struct with givend from -> to map -func (tp *TorProxy) WithRedirects(redirects []string) error { - var err error +// add the redirect URL if and only if the tor proxy doesn't know the new origin +func (tp *TorProxy) setRedirectsFromRegistry(registryJSON []byte) error { + redirects, err := parseRegistryJSONtoRedirects(registryJSON) + if err != nil { + return err + } + for _, to := range redirects { // we parse the destination upstram which should be on *.onion address origin, err := url.Parse(to) if err != nil { - err = fmt.Errorf("Failed to parse address : %v", err) - break + return fmt.Errorf("failed to parse address : %v", err) + } + + if !tp.includesRedirect(origin) { + tp.Redirects = append(tp.Redirects, origin) } - tp.Redirects = append(tp.Redirects, origin) } return err } +func (tp TorProxy) includesRedirect(redirect *url.URL) bool { + for _, proxyRedirect := range tp.Redirects { + if proxyRedirect.Host == redirect.Host { + return true + } + } + + return false +} + +// WithAutoUpdater starts a go-routine selecting results of registry.Observe +// set up a stop function in TorProxy to stop the go-routine in Close method +func (tp *TorProxy) WithAutoUpdater(period time.Duration, errorHandler func (err error)) { + observeRegistryChan, stop := registry.Observe(tp.Registry, period) + + go func() { + for newGetJSONResult := range observeRegistryChan { + if newGetJSONResult.Err != nil { + errorHandler(newGetJSONResult.Err) + continue + } + + err := tp.setRedirectsFromRegistry(newGetJSONResult.Json) + if err != nil { + errorHandler(err) + } + } + }() + + tp.closeAutoUpdaterFunc = stop +} + + // TLSOptions defines the domains we need to obtain and renew a TLS cerficate type TLSOptions struct { Domains []string @@ -122,7 +178,6 @@ func (tp *TorProxy) Serve(address string, options *TLSOptions) error { CipherSuites: []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, @@ -191,6 +246,19 @@ func (tp *TorProxy) Serve(address string, options *TLSOptions) error { return err } +func (tp *TorProxy) Close() error { + err := tp.Listener.Close() + if err != nil { + return err + } + + if tp.closeAutoUpdaterFunc != nil { + tp.closeAutoUpdaterFunc() + } + + return nil +} + // reverseProxy takes an address where to listen, a dialer with SOCKS5 proxy and a list of redirects as a list of URLs // the incoming request should match the pattern host:port//./ func reverseProxy(redirects []*url.URL, lis net.Listener, dialer proxy.Dialer) error { @@ -237,5 +305,23 @@ func addCorsHeader(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") w.Header().Set("Access-Control-Allow-Headers", "*") w.WriteHeader(http.StatusNoContent) - return +} + +func parseRegistryJSONtoRedirects(registryJSON []byte) ([]string, error) { + var data []map[string]string + err := json.Unmarshal(registryJSON, &data) + if err != nil { + return nil, fmt.Errorf("invalid JSON: %w", err) + } + redirects := make([]string, 0) + for _, v := range data { + if strings.Contains(v["endpoint"], "onion") { + redirects = append(redirects, v["endpoint"]) + } + } + if len(redirects) == 0 { + return nil, errors.New("no valid onion endpoints found") + } + + return redirects, nil }