diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0ce6031700..683687fd02 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,7 +53,7 @@ jobs: - name: Install Requirements shell: pwsh run: | - Invoke-WebRequest "https://github.com/goreleaser/goreleaser/releases/download/v1.8.3/goreleaser_Windows_x86_64.zip" -o goreleaser.zip + Invoke-WebRequest "https://github.com/goreleaser/goreleaser/releases/download/v1.8.3/goreleaser_Windows_x86_64.zip" -OutFile goreleaser.zip Expand-Archive goreleaser.zip choco install make - name: Releasing diff --git a/CHANGELOG.md b/CHANGELOG.md index 600d66f3dd..2b8d2aea09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. updates may be generated with `scripts/changelog.sh ` +## 1.3.17 +- Add http-proxy on skysocks-client [#1728](https://github.com/skycoin/skywire/pull/1728) +- Little Improve on skywire and setup-node [#1723](https://github.com/skycoin/skywire/pull/1723) +- Improve VPN and Proxy cli command [#1722](https://github.com/skycoin/skywire/pull/1722) +- Improve Survey and Log Collection [#1721](https://github.com/skycoin/skywire/pull/1721) +- Server list optimization [#1720](https://github.com/skycoin/skywire/pull/1720) +- Fix reward calc [#1719](https://github.com/skycoin/skywire/pull/1719) +- Fix reward calculation [#1716](https://github.com/skycoin/skywire/pull/1716) +- Fix log collection panic [#1711](https://github.com/skycoin/skywire/pull/1711) +- Fix win installer script [#1706](https://github.com/skycoin/skywire/pull/1706) + ## 1.3.16 - fix VPN issues on CI and Windows [#1703](https://github.com/skycoin/skywire/pull/1703) diff --git a/cmd/apps/skysocks-client/skysocks-client.go b/cmd/apps/skysocks-client/skysocks-client.go index 34cea6f822..aa0d499684 100644 --- a/cmd/apps/skysocks-client/skysocks-client.go +++ b/cmd/apps/skysocks-client/skysocks-client.go @@ -11,9 +11,13 @@ import ( "fmt" "io" "net" + "net/http" + "net/url" "os" "time" + "github.com/elazarl/goproxy" + "github.com/skycoin/skywire-utilities/pkg/buildinfo" "github.com/skycoin/skywire-utilities/pkg/cipher" "github.com/skycoin/skywire-utilities/pkg/netutil" @@ -64,6 +68,7 @@ func main() { var addr = flag.String("addr", visorconfig.SkysocksClientAddr, "Client address to listen on") var serverPK = flag.String("srv", "", "PubKey of the server to connect to") + var httpAddr = flag.String("http", "", "Starting http proxy or not") flag.Parse() if *serverPK == "" { @@ -99,11 +104,14 @@ func main() { fmt.Printf("Serving proxy client %v\n", *addr) setAppStatus(appCl, appserver.AppDetailedStatusRunning) - + httpCtx, httpCancel := context.WithCancel(ctx) + if *httpAddr != "" { + go httpProxy(httpCtx, httpAddr, addr) + } if err := client.ListenAndServe(*addr); err != nil { print(fmt.Sprintf("Error serving proxy client: %v\n", err)) } - + httpCancel() // need to filter this out, cause usually client failure means app conn is already closed if err := conn.Close(); err != nil && err != io.ErrClosedPipe { print(fmt.Sprintf("Error closing app conn: %v\n", err)) @@ -131,3 +139,28 @@ func setAppPort(appCl *app.Client, port routing.Port) { print(fmt.Sprintf("Failed to set port %v: %v\n", port, err)) } } + +func httpProxy(ctx context.Context, httpAddr, sockscAddr *string) { + proxy := goproxy.NewProxyHttpServer() + + proxyURL, err := url.Parse(fmt.Sprintf("socks5://127.0.0.1%s", *sockscAddr)) //nolint + if err != nil { + print(fmt.Sprintf("Failed to parse socks address: %v\n", err)) + return + } + + proxy.Tr.Proxy = http.ProxyURL(proxyURL) + + fmt.Printf("Serving http proxy %v\n", *httpAddr) + httpProxySrv := &http.Server{Addr: *httpAddr, Handler: proxy} //nolint + + go func() { + <-ctx.Done() + httpProxySrv.Close() //nolint + print("Stopping http proxy") + }() + + if err := httpProxySrv.ListenAndServe(); err != nil { //nolint + print(fmt.Sprintf("Error serving http proxy: %v\n", err)) + } +} diff --git a/cmd/skywire-cli/commands/proxy/proxy.go b/cmd/skywire-cli/commands/proxy/proxy.go index 258550f60d..73e20014f8 100644 --- a/cmd/skywire-cli/commands/proxy/proxy.go +++ b/cmd/skywire-cli/commands/proxy/proxy.go @@ -36,6 +36,7 @@ func init() { startCmd.Flags().StringVarP(&addr, "addr", "a", "", "address of proxy for use") startCmd.Flags().StringVarP(&clientName, "name", "n", "", "name of skysocks client") startCmd.Flags().IntVarP(&startingTimeout, "timeout", "t", 20, "starting timeout value in second") + startCmd.Flags().StringVarP(&httpProxy, "http-proxy", "p", "", "starting http-proxy based on skysocks") } var startCmd = &cobra.Command{ @@ -48,6 +49,17 @@ var startCmd = &cobra.Command{ internal.PrintFatalError(cmd.Flags(), fmt.Errorf("unable to create RPC client: %w", err)) } + arguments := map[string]string{} + if pk != "" { + arguments["srv"] = pk + } + if addr != "" { + arguments["addr"] = addr + } + if httpProxy != "" { + arguments["http"] = httpProxy + } + if clientName != "" && pk != "" && addr != "" { // add new app with -srv and -addr args, and if app was there just change -srv and -addr args and run it err := pubkey.Set(pk) @@ -62,10 +74,6 @@ var startCmd = &cobra.Command{ } } - arguments := map[string]string{} - arguments["srv"] = pubkey.String() - arguments["addr"] = addr - _, err = rpcClient.App(clientName) if err == nil { err = rpcClient.DoCustomSetting(clientName, arguments) @@ -85,6 +93,10 @@ var startCmd = &cobra.Command{ internal.Catch(cmd.Flags(), rpcClient.StartApp(clientName)) internal.PrintOutput(cmd.Flags(), nil, "Starting.") } else if clientName != "" && pk == "" && addr == "" { + err = rpcClient.DoCustomSetting(clientName, arguments) + if err != nil { + internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Error occurs during set args to custom skysocks client")) + } internal.Catch(cmd.Flags(), rpcClient.StartApp(clientName)) internal.PrintOutput(cmd.Flags(), nil, "Starting.") } else if pk != "" && clientName == "" && addr == "" { @@ -99,7 +111,11 @@ var startCmd = &cobra.Command{ internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Invalid or missing public key")) } } - internal.Catch(cmd.Flags(), rpcClient.StartSkysocksClient(pubkey.String())) + err = rpcClient.DoCustomSetting("skysocks-client", arguments) + if err != nil { + internal.PrintFatalError(cmd.Flags(), fmt.Errorf("Error occurs during set args to custom skysocks client")) + } + internal.Catch(cmd.Flags(), rpcClient.StartApp("skysocks-client")) internal.PrintOutput(cmd.Flags(), nil, "Starting.") clientName = "skysocks-client" // change defaul skysocks-proxy app -srv arg and run it diff --git a/cmd/skywire-cli/commands/proxy/root.go b/cmd/skywire-cli/commands/proxy/root.go index f81b2f8362..dedb878930 100644 --- a/cmd/skywire-cli/commands/proxy/root.go +++ b/cmd/skywire-cli/commands/proxy/root.go @@ -31,6 +31,7 @@ var ( clientName string addr string startingTimeout int + httpProxy string ) // RootCmd contains commands that interact with the skywire-visor diff --git a/dmsghttp-config.json b/dmsghttp-config.json index 56abbbdd6d..590a83ccf7 100644 --- a/dmsghttp-config.json +++ b/dmsghttp-config.json @@ -56,7 +56,7 @@ { "static": "0326978f5a53aff537dbb47fed58b1f123af3b00132d365f1309a14db4168dcff7", "server": { - "address": "173.172.1.120:9083" + "address": "70.121.23.42:9083" } }, { diff --git a/go.mod b/go.mod index 109d6633ca..897bf8860c 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/bitfield/script v0.22.0 github.com/blang/semver/v4 v4.0.0 github.com/ccding/go-stun/stun v0.0.0-20200514191101-4dc67bcdb029 + github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 github.com/gen2brain/dlgs v0.0.0-20220603100644-40c77870fa8d github.com/gin-gonic/gin v1.9.1 github.com/go-chi/chi/v5 v5.0.11 @@ -27,11 +28,11 @@ require ( github.com/orandin/lumberjackrus v1.0.1 github.com/pterm/pterm v0.12.66 github.com/sirupsen/logrus v1.9.3 - github.com/skycoin/dmsg v1.3.15-0.20240201143600-b1d1d1012348 + github.com/skycoin/dmsg v1.3.17 github.com/skycoin/skycoin v0.27.1 github.com/skycoin/skycoin-service-discovery v0.0.0-20231221001759-d1af6ec27db1 github.com/skycoin/skywire-services v0.0.0-20231221001820-3212895ddf12 - github.com/skycoin/skywire-utilities v1.3.14 + github.com/skycoin/skywire-utilities v1.3.17 github.com/skycoin/systray v1.10.0 github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 github.com/spf13/cobra v1.7.0 diff --git a/go.sum b/go.sum index 5b7a52a027..28a2efc000 100644 --- a/go.sum +++ b/go.sum @@ -93,6 +93,10 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 h1:m62nsMU279qRD9PQSWD1l66kmkXzuYcnVJqL4XLeV2M= +github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -406,6 +410,7 @@ github.com/quic-go/quic-go v0.38.1/go.mod h1:ijnZM7JsFIkp4cRyjxJNIzdSfCLmUMg9wdy github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= @@ -429,8 +434,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skycoin/dmsg v1.3.15-0.20240201143600-b1d1d1012348 h1:UYKHYIcwI/cHcdctJFkMXTR94vgRuYvNejU7vecLOi8= -github.com/skycoin/dmsg v1.3.15-0.20240201143600-b1d1d1012348/go.mod h1:I4Bgha3DPNOoimvUtQmAjGUSknw4o/jaBVMY+KNz9vY= +github.com/skycoin/dmsg v1.3.17 h1:CUq8u4ABDKFkE4ppscJ/R09hMtfPUE72Rleq078vQ28= +github.com/skycoin/dmsg v1.3.17/go.mod h1:INEDx+ECwCGQWw/Kd0QcLmSWMhbeRRcfkxj+xATQGFg= github.com/skycoin/noise v0.0.0-20180327030543-2492fe189ae6 h1:1Nc5EBY6pjfw1kwW0duwyG+7WliWz5u9kgk1h5MnLuA= github.com/skycoin/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:UXghlricA7J3aRD/k7p/zBObQfmBawwCxIVPVjz2Q3o= github.com/skycoin/skycoin v0.27.1 h1:HatxsRwVSPaV4qxH6290xPBmkH/HgiuAoY2qC+e8C9I= @@ -439,8 +444,8 @@ github.com/skycoin/skycoin-service-discovery v0.0.0-20231221001759-d1af6ec27db1 github.com/skycoin/skycoin-service-discovery v0.0.0-20231221001759-d1af6ec27db1/go.mod h1:6VwZDwW4aO6fucuZKiJQ4PnnVYi5CfN/eHcK8F0C05M= github.com/skycoin/skywire-services v0.0.0-20231221001820-3212895ddf12 h1:fic0WKltARs0JnbNFcKesijNgcPOeJh7CsFs/KLZoJw= github.com/skycoin/skywire-services v0.0.0-20231221001820-3212895ddf12/go.mod h1:HYqBsmgat3wTk1zwGLNj3q5N3iCpJOwYOuLexqKH02k= -github.com/skycoin/skywire-utilities v1.3.14 h1:AzTV3oiij7b2VgpiZHJj/oy4Tojf22I+r50Riza8Xt0= -github.com/skycoin/skywire-utilities v1.3.14/go.mod h1:yFKWpL1bDRPKU3uK+cTF4PnYUMe+eyIj5N2bk4sF5Cw= +github.com/skycoin/skywire-utilities v1.3.17 h1:89aPdViJxhMpjEJbByQ02W8anX6Oxt68OeSxAENBlFI= +github.com/skycoin/skywire-utilities v1.3.17/go.mod h1:yFKWpL1bDRPKU3uK+cTF4PnYUMe+eyIj5N2bk4sF5Cw= github.com/skycoin/systray v1.10.0 h1:fQZJHMylpVvfmOOTLvUssfyHVDoC8Idx6Ba2BlLEuGg= github.com/skycoin/systray v1.10.0/go.mod h1:/i17Eni5GxFiboIZceeamY5LktDSFFRCvd3fBMerQ+4= github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8= diff --git a/pkg/transport/log.go b/pkg/transport/log.go index 51d8686aaa..c16eea21a2 100644 --- a/pkg/transport/log.go +++ b/pkg/transport/log.go @@ -43,7 +43,7 @@ func MakeLogEntry(ls LogStore, tpID uuid.UUID, log *logging.Logger) *LogEntry { oldLogEntry, err := ls.Entry(tpID) if err != nil { log.Warn(err) - return &LogEntry{} + log.Warn(fmt.Errorf("new log entry will create for transport %s", tpID.String())) } newEntry := NewLogEntry() if oldLogEntry != nil { @@ -155,7 +155,7 @@ func (tls *inMemoryTransportLogStore) Entry(id uuid.UUID) (*LogEntry, error) { entry, ok := tls.entries[id] tls.mu.Unlock() if !ok { - return entry, errors.New("transport log entry not found") + return nil, errors.New("transport log entry not found") } return entry, nil diff --git a/scripts/win_installer/Product.wxs b/scripts/win_installer/Product.wxs index 09aeb88ac5..28d728bc06 100644 --- a/scripts/win_installer/Product.wxs +++ b/scripts/win_installer/Product.wxs @@ -11,7 +11,7 @@ diff --git a/scripts/win_installer/script.ps1 b/scripts/win_installer/script.ps1 index 62ad4f59a7..3b3b3a7975 100644 --- a/scripts/win_installer/script.ps1 +++ b/scripts/win_installer/script.ps1 @@ -18,7 +18,7 @@ function CleanStage function InstallWix { Set-Location .\scripts\win_installer - Invoke-WebRequest "https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip" -o wix.zip + Invoke-WebRequest "https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip" -OutFile wix.zip Expand-Archive wix.zip Set-Location ../../ } @@ -51,12 +51,12 @@ function BuildInstaller($arch) $fileName = "skywire-v$version-windows-$arch" $msiName = "skywire-installer-v$version-windows-$arch" $downloadURL = "https://github.com/skycoin/skywire/releases/download/v$version/$filename.zip" - Invoke-WebRequest $downloadURL -o archive.zip -ErrorAction Stop + Invoke-WebRequest $downloadURL -OutFile archive.zip -ErrorAction Stop } else { $fileName = "skywire-$version-windows-$arch" $msiName = "skywire-installer-$version-windows-$arch" $downloadURL = "https://github.com/skycoin/skywire/releases/download/$version/$filename.zip" - Invoke-WebRequest $downloadURL -o archive.zip + Invoke-WebRequest $downloadURL -OutFile archive.zip } Write-Output "# 3. Extracing Downloaded Archive File... #" @@ -74,7 +74,7 @@ function BuildInstaller($arch) Copy-Item skywire.bat .\build\skywire.bat New-Item new.update > $null Move-Item new.update .\build\new.update - Invoke-WebRequest "https://www.wintun.net/builds/wintun-0.14.1.zip" -o wintun.zip + Invoke-WebRequest "https://www.wintun.net/builds/wintun-0.14.1.zip" -OutFile wintun.zip Expand-Archive wintun.zip Copy-Item .\wintun\wintun\bin\$wintun_arch\wintun.dll .\build\wintun.dll diff --git a/vendor/github.com/elazarl/goproxy/.gitignore b/vendor/github.com/elazarl/goproxy/.gitignore new file mode 100644 index 0000000000..1005f6f1ec --- /dev/null +++ b/vendor/github.com/elazarl/goproxy/.gitignore @@ -0,0 +1,2 @@ +bin +*.swp diff --git a/vendor/github.com/elazarl/goproxy/LICENSE b/vendor/github.com/elazarl/goproxy/LICENSE new file mode 100644 index 0000000000..2067e567c9 --- /dev/null +++ b/vendor/github.com/elazarl/goproxy/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 Elazar Leibovich. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Elazar Leibovich. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/elazarl/goproxy/README.md b/vendor/github.com/elazarl/goproxy/README.md new file mode 100644 index 0000000000..495afc2d45 --- /dev/null +++ b/vendor/github.com/elazarl/goproxy/README.md @@ -0,0 +1,169 @@ +# Introduction + +[![GoDoc](https://godoc.org/github.com/elazarl/goproxy?status.svg)](https://godoc.org/github.com/elazarl/goproxy) +[![Join the chat at https://gitter.im/elazarl/goproxy](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/elazarl/goproxy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +![Status](https://github.com/elazarl/goproxy/workflows/Go/badge.svg) + +Package goproxy provides a customizable HTTP proxy library for Go (golang), + +It supports regular HTTP proxy, HTTPS through CONNECT, and "hijacking" HTTPS +connection using "Man in the Middle" style attack. + +The intent of the proxy is to be usable with reasonable amount of traffic, +yet customizable and programmable. + +The proxy itself is simply a `net/http` handler. + +In order to use goproxy, one should set their browser to use goproxy as an HTTP +proxy. Here is how you do that [in Chrome](https://support.google.com/chrome/answer/96815?hl=en) +and [in Firefox](http://www.wikihow.com/Enter-Proxy-Settings-in-Firefox). + +For example, the URL you should use as proxy when running `./bin/basic` is +`localhost:8080`, as this is the default binding for the basic proxy. + +## Mailing List + +New features will be discussed on the [mailing list](https://groups.google.com/forum/#!forum/goproxy-dev) +before their development. + +## Latest Stable Release + +Get the latest goproxy from `gopkg.in/elazarl/goproxy.v1`. + +# Why not Fiddler2? + +Fiddler is an excellent software with similar intent. However, Fiddler is not +as customizable as goproxy intends to be. The main difference is, Fiddler is not +intended to be used as a real proxy. + +A possible use case that suits goproxy but +not Fiddler, is gathering statistics on page load times for a certain website over a week. +With goproxy you could ask all your users to set their proxy to a dedicated machine running a +goproxy server. Fiddler is a GUI app not designed to be run like a server for multiple users. + +# A taste of goproxy + +To get a taste of `goproxy`, a basic HTTP/HTTPS transparent proxy + +```go +package main + +import ( + "github.com/elazarl/goproxy" + "log" + "net/http" +) + +func main() { + proxy := goproxy.NewProxyHttpServer() + proxy.Verbose = true + log.Fatal(http.ListenAndServe(":8080", proxy)) +} +``` + +This line will add `X-GoProxy: yxorPoG-X` header to all requests sent through the proxy + +```go +proxy.OnRequest().DoFunc( + func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) { + r.Header.Set("X-GoProxy","yxorPoG-X") + return r,nil + }) +``` + +`DoFunc` will process all incoming requests to the proxy. It will add a header to the request +and return it. The proxy will send the modified request. + +Note that we returned nil value as the response. Had we returned a response, goproxy would +have discarded the request and sent the new response to the client. + +In order to refuse connections to reddit at work time + +```go +proxy.OnRequest(goproxy.DstHostIs("www.reddit.com")).DoFunc( + func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) { + if h,_,_ := time.Now().Clock(); h >= 8 && h <= 17 { + return r,goproxy.NewResponse(r, + goproxy.ContentTypeText,http.StatusForbidden, + "Don't waste your time!") + } + return r,nil +}) +``` + +`DstHostIs` returns a `ReqCondition`, that is a function receiving a `Request` and returning a boolean. +We will only process requests that match the condition. `DstHostIs("www.reddit.com")` will return +a `ReqCondition` accepting only requests directed to "www.reddit.com". + +`DoFunc` will receive a function that will preprocess the request. We can change the request, or +return a response. If the time is between 8:00am and 17:00pm, we will reject the request, and +return a pre-canned text response saying "do not waste your time". + +See additional examples in the examples directory. + + +# Type of handlers for manipulating connect/req/resp behavior + +There are 3 kinds of useful handlers to manipulate the behavior, as follows: + +```go +// handler called after receiving HTTP CONNECT from the client, and before proxy establish connection +// with destination host +httpsHandlers []HttpsHandler + +// handler called before proxy send HTTP request to destination host +reqHandlers []ReqHandler + +// handler called after proxy receives HTTP Response from destination host, and before proxy forward +// the Response to the client. +respHandlers []RespHandler +``` + +Depending on what you want to manipulate, the ways to add handlers to each handler list are: + +```go +// Add handlers to httpsHandlers +proxy.OnRequest(Some ReqConditions).HandleConnect(YourHandlerFunc()) + +// Add handlers to reqHandlers +proxy.OnRequest(Some ReqConditions).Do(YourReqHandlerFunc()) + +// Add handlers to respHandlers +proxy.OnResponse(Some RespConditions).Do(YourRespHandlerFunc()) +``` + +For example: + +```go +// This rejects the HTTPS request to *.reddit.com during HTTP CONNECT phase +proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("reddit.*:443$"))).HandleConnect(goproxy.AlwaysReject) + +// This will NOT reject the HTTPS request with URL ending with gif, due to the fact that proxy +// only got the URL.Hostname and URL.Port during the HTTP CONNECT phase if the scheme is HTTPS, which is +// quiet common these days. +proxy.OnRequest(goproxy.UrlMatches(regexp.MustCompile(`.*gif$`))).HandleConnect(goproxy.AlwaysReject) + +// The correct way to manipulate the HTTP request using URL.Path as condition is: +proxy.OnRequest(goproxy.UrlMatches(regexp.MustCompile(`.*gif$`))).Do(YourReqHandlerFunc()) +``` + +# What's New + +1. Ability to `Hijack` CONNECT requests. See +[the eavesdropper example](https://github.com/elazarl/goproxy/blob/master/examples/goproxy-eavesdropper/main.go#L27) +2. Transparent proxy support for http/https including MITM certificate generation for TLS. See the [transparent example.](https://github.com/elazarl/goproxy/tree/master/examples/goproxy-transparent) + +# License + +I put the software temporarily under the Go-compatible BSD license. +If this prevents someone from using the software, do let me know and I'll consider changing it. + +At any rate, user feedback is very important for me, so I'll be delighted to know if you're using this package. + +# Beta Software + +I've received positive feedback from a few people who use goproxy in production settings. +I believe it is good enough for usage. + +I'll try to keep reasonable backwards compatibility. In case of a major API change, +I'll change the import path. diff --git a/vendor/github.com/elazarl/goproxy/actions.go b/vendor/github.com/elazarl/goproxy/actions.go new file mode 100644 index 0000000000..e1a3e7ff17 --- /dev/null +++ b/vendor/github.com/elazarl/goproxy/actions.go @@ -0,0 +1,57 @@ +package goproxy + +import "net/http" + +// ReqHandler will "tamper" with the request coming to the proxy server +// If Handle returns req,nil the proxy will send the returned request +// to the destination server. If it returns nil,resp the proxy will +// skip sending any requests, and will simply return the response `resp` +// to the client. +type ReqHandler interface { + Handle(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) +} + +// A wrapper that would convert a function to a ReqHandler interface type +type FuncReqHandler func(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) + +// FuncReqHandler.Handle(req,ctx) <=> FuncReqHandler(req,ctx) +func (f FuncReqHandler) Handle(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) { + return f(req, ctx) +} + +// after the proxy have sent the request to the destination server, it will +// "filter" the response through the RespHandlers it has. +// The proxy server will send to the client the response returned by the RespHandler. +// In case of error, resp will be nil, and ctx.RoundTrip.Error will contain the error +type RespHandler interface { + Handle(resp *http.Response, ctx *ProxyCtx) *http.Response +} + +// A wrapper that would convert a function to a RespHandler interface type +type FuncRespHandler func(resp *http.Response, ctx *ProxyCtx) *http.Response + +// FuncRespHandler.Handle(req,ctx) <=> FuncRespHandler(req,ctx) +func (f FuncRespHandler) Handle(resp *http.Response, ctx *ProxyCtx) *http.Response { + return f(resp, ctx) +} + +// When a client send a CONNECT request to a host, the request is filtered through +// all the HttpsHandlers the proxy has, and if one returns true, the connection is +// sniffed using Man in the Middle attack. +// That is, the proxy will create a TLS connection with the client, another TLS +// connection with the destination the client wished to connect to, and would +// send back and forth all messages from the server to the client and vice versa. +// The request and responses sent in this Man In the Middle channel are filtered +// through the usual flow (request and response filtered through the ReqHandlers +// and RespHandlers) +type HttpsHandler interface { + HandleConnect(req string, ctx *ProxyCtx) (*ConnectAction, string) +} + +// A wrapper that would convert a function to a HttpsHandler interface type +type FuncHttpsHandler func(host string, ctx *ProxyCtx) (*ConnectAction, string) + +// FuncHttpsHandler should implement the RespHandler interface +func (f FuncHttpsHandler) HandleConnect(host string, ctx *ProxyCtx) (*ConnectAction, string) { + return f(host, ctx) +} diff --git a/vendor/github.com/elazarl/goproxy/all.bash b/vendor/github.com/elazarl/goproxy/all.bash new file mode 100644 index 0000000000..6503e73dc9 --- /dev/null +++ b/vendor/github.com/elazarl/goproxy/all.bash @@ -0,0 +1,15 @@ +#!/bin/bash + +go test || exit +for action in $@; do go $action; done + +mkdir -p bin +find regretable examples/* ext/* -maxdepth 0 -type d | while read d; do + (cd $d + go build -o ../../bin/$(basename $d) + find *_test.go -maxdepth 0 2>/dev/null|while read f;do + for action in $@; do go $action; done + go test + break + done) +done diff --git a/vendor/github.com/elazarl/goproxy/certs.go b/vendor/github.com/elazarl/goproxy/certs.go new file mode 100644 index 0000000000..4731971e70 --- /dev/null +++ b/vendor/github.com/elazarl/goproxy/certs.go @@ -0,0 +1,111 @@ +package goproxy + +import ( + "crypto/tls" + "crypto/x509" +) + +func init() { + if goproxyCaErr != nil { + panic("Error parsing builtin CA " + goproxyCaErr.Error()) + } + var err error + if GoproxyCa.Leaf, err = x509.ParseCertificate(GoproxyCa.Certificate[0]); err != nil { + panic("Error parsing builtin CA " + err.Error()) + } +} + +var tlsClientSkipVerify = &tls.Config{InsecureSkipVerify: true} + +var defaultTLSConfig = &tls.Config{ + InsecureSkipVerify: true, +} + +var CA_CERT = []byte(`-----BEGIN CERTIFICATE----- +MIIF9DCCA9ygAwIBAgIJAODqYUwoVjJkMA0GCSqGSIb3DQEBCwUAMIGOMQswCQYD +VQQGEwJJTDEPMA0GA1UECAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoM +B0dvUHJveHkxEDAOBgNVBAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0 +aHViLmlvMSAwHgYJKoZIhvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTAeFw0xNzA0 +MDUyMDAwMTBaFw0zNzAzMzEyMDAwMTBaMIGOMQswCQYDVQQGEwJJTDEPMA0GA1UE +CAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoMB0dvUHJveHkxEDAOBgNV +BAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0aHViLmlvMSAwHgYJKoZI +hvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAJ4Qy+H6hhoY1s0QRcvIhxrjSHaO/RbaFj3rwqcnpOgFq07gRdI9 +3c0TFKQJHpgv6feLRhEvX/YllFYu4J35lM9ZcYY4qlKFuStcX8Jm8fqpgtmAMBzP +sqtqDi8M9RQGKENzU9IFOnCV7SAeh45scMuI3wz8wrjBcH7zquHkvqUSYZz035t9 +V6WTrHyTEvT4w+lFOVN2bA/6DAIxrjBiF6DhoJqnha0SZtDfv77XpwGG3EhA/qoh +hiYrDruYK7zJdESQL44LwzMPupVigqalfv+YHfQjbhT951IVurW2NJgRyBE62dLr +lHYdtT9tCTCrd+KJNMJ+jp9hAjdIu1Br/kifU4F4+4ZLMR9Ueji0GkkPKsYdyMnq +j0p0PogyvP1l4qmboPImMYtaoFuYmMYlebgC9LN10bL91K4+jLt0I1YntEzrqgJo +WsJztYDw543NzSy5W+/cq4XRYgtq1b0RWwuUiswezmMoeyHZ8BQJe2xMjAOllASD +fqa8OK3WABHJpy4zUrnUBiMuPITzD/FuDx4C5IwwlC68gHAZblNqpBZCX0nFCtKj +YOcI2So5HbQ2OC8QF+zGVuduHUSok4hSy2BBfZ1pfvziqBeetWJwFvapGB44nIHh +WKNKvqOxLNIy7e+TGRiWOomrAWM18VSR9LZbBxpJK7PLSzWqYJYTRCZHAgMBAAGj +UzBRMB0GA1UdDgQWBBR4uDD9Y6x7iUoHO+32ioOcw1ICZTAfBgNVHSMEGDAWgBR4 +uDD9Y6x7iUoHO+32ioOcw1ICZTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB +CwUAA4ICAQAaCEupzGGqcdh+L7BzhX7zyd7yzAKUoLxFrxaZY34Xyj3lcx1XoK6F +AqsH2JM25GixgadzhNt92JP7vzoWeHZtLfstrPS638Y1zZi6toy4E49viYjFk5J0 +C6ZcFC04VYWWx6z0HwJuAS08tZ37JuFXpJGfXJOjZCQyxse0Lg0tuKLMeXDCk2Y3 +Ba0noeuNyHRoWXXPyiUoeApkVCU5gIsyiJSWOjhJ5hpJG06rQNfNYexgKrrraEin +o0jmEMtJMx5TtD83hSnLCnFGBBq5lkE7jgXME1KsbIE3lJZzRX1mQwUK8CJDYxye +i6M/dzSvy0SsPvz8fTAlprXRtWWtJQmxgWENp3Dv+0Pmux/l+ilk7KA4sMXGhsfr +bvTOeWl1/uoFTPYiWR/ww7QEPLq23yDFY04Q7Un0qjIk8ExvaY8lCkXMgc8i7sGY +VfvOYb0zm67EfAQl3TW8Ky5fl5CcxpVCD360Bzi6hwjYixa3qEeBggOixFQBFWft +8wrkKTHpOQXjn4sDPtet8imm9UYEtzWrFX6T9MFYkBR0/yye0FIh9+YPiTA6WB86 +NCNwK5Yl6HuvF97CIH5CdgO+5C7KifUtqTOL8pQKbNwy0S3sNYvB+njGvRpR7pKV +BUnFpB/Atptqr4CUlTXrc5IPLAqAfmwk5IKcwy3EXUbruf9Dwz69YA== +-----END CERTIFICATE-----`) + +var CA_KEY = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEAnhDL4fqGGhjWzRBFy8iHGuNIdo79FtoWPevCpyek6AWrTuBF +0j3dzRMUpAkemC/p94tGES9f9iWUVi7gnfmUz1lxhjiqUoW5K1xfwmbx+qmC2YAw +HM+yq2oOLwz1FAYoQ3NT0gU6cJXtIB6Hjmxwy4jfDPzCuMFwfvOq4eS+pRJhnPTf +m31XpZOsfJMS9PjD6UU5U3ZsD/oMAjGuMGIXoOGgmqeFrRJm0N+/vtenAYbcSED+ +qiGGJisOu5grvMl0RJAvjgvDMw+6lWKCpqV+/5gd9CNuFP3nUhW6tbY0mBHIETrZ +0uuUdh21P20JMKt34ok0wn6On2ECN0i7UGv+SJ9TgXj7hksxH1R6OLQaSQ8qxh3I +yeqPSnQ+iDK8/WXiqZug8iYxi1qgW5iYxiV5uAL0s3XRsv3Urj6Mu3QjVie0TOuq +AmhawnO1gPDnjc3NLLlb79yrhdFiC2rVvRFbC5SKzB7OYyh7IdnwFAl7bEyMA6WU +BIN+prw4rdYAEcmnLjNSudQGIy48hPMP8W4PHgLkjDCULryAcBluU2qkFkJfScUK +0qNg5wjZKjkdtDY4LxAX7MZW524dRKiTiFLLYEF9nWl+/OKoF561YnAW9qkYHjic +geFYo0q+o7Es0jLt75MZGJY6iasBYzXxVJH0tlsHGkkrs8tLNapglhNEJkcCAwEA +AQKCAgAwSuNvxHHqUUJ3XoxkiXy1u1EtX9x1eeYnvvs2xMb+WJURQTYz2NEGUdkR +kPO2/ZSXHAcpQvcnpi2e8y2PNmy/uQ0VPATVt6NuWweqxncR5W5j82U/uDlXY8y3 +lVbfak4s5XRri0tikHvlP06dNgZ0OPok5qi7d+Zd8yZ3Y8LXfjkykiIrSG1Z2jdt +zCWTkNmSUKMGG/1CGFxI41Lb12xuq+C8v4f469Fb6bCUpyCQN9rffHQSGLH6wVb7 ++68JO+d49zCATpmx5RFViMZwEcouXxRvvc9pPHXLP3ZPBD8nYu9kTD220mEGgWcZ +3L9dDlZPcSocbjw295WMvHz2QjhrDrb8gXwdpoRyuyofqgCyNxSnEC5M13SjOxtf +pjGzjTqh0kDlKXg2/eTkd9xIHjVhFYiHIEeITM/lHCfWwBCYxViuuF7pSRPzTe8U +C440b62qZSPMjVoquaMg+qx0n9fKSo6n1FIKHypv3Kue2G0WhDeK6u0U288vQ1t4 +Ood3Qa13gZ+9hwDLbM/AoBfVBDlP/tpAwa7AIIU1ZRDNbZr7emFdctx9B6kLINv3 +4PDOGM2xrjOuACSGMq8Zcu7LBz35PpIZtviJOeKNwUd8/xHjWC6W0itgfJb5I1Nm +V6Vj368pGlJx6Se26lvXwyyrc9pSw6jSAwARBeU4YkNWpi4i6QKCAQEA0T7u3P/9 +jZJSnDN1o2PXymDrJulE61yguhc/QSmLccEPZe7or06/DmEhhKuCbv+1MswKDeag +/1JdFPGhL2+4G/f/9BK3BJPdcOZSz7K6Ty8AMMBf8AehKTcSBqwkJWcbEvpHpKJ6 +eDqn1B6brXTNKMT6fEEXCuZJGPBpNidyLv/xXDcN7kCOo3nGYKfB5OhFpNiL63tw ++LntU56WESZwEqr8Pf80uFvsyXQK3a5q5HhIQtxl6tqQuPlNjsDBvCqj0x72mmaJ +ZVsVWlv7khUrCwAXz7Y8K7mKKBd2ekF5hSbryfJsxFyvEaWUPhnJpTKV85lAS+tt +FQuIp9TvKYlRQwKCAQEAwWJN8jysapdhi67jO0HtYOEl9wwnF4w6XtiOYtllkMmC +06/e9h7RsRyWPMdu3qRDPUYFaVDy6+dpUDSQ0+E2Ot6AHtVyvjeUTIL651mFIo/7 +OSUCEc+HRo3SfPXdPhSQ2thNTxl6y9XcFacuvbthgr70KXbvC4k6IEmdpf/0Kgs9 +7QTZCG26HDrEZ2q9yMRlRaL2SRD+7Y2xra7gB+cQGFj6yn0Wd/07er49RqMXidQf +KR2oYfev2BDtHXoSZFfhFGHlOdLvWRh90D4qZf4vQ+g/EIMgcNSoxjvph1EShmKt +sjhTHtoHuu+XmEQvIewk2oCI+JvofBkcnpFrVvUUrQKCAQAaTIufETmgCo0BfuJB +N/JOSGIl0NnNryWwXe2gVgVltbsmt6FdL0uKFiEtWJUbOF5g1Q5Kcvs3O/XhBQGa +QbNlKIVt+tAv7hm97+Tmn/MUsraWagdk1sCluns0hXxBizT27KgGhDlaVRz05yfv +5CdJAYDuDwxDXXBAhy7iFJEgYSDH00+X61tCJrMNQOh4ycy/DEyBu1EWod+3S85W +t3sMjZsIe8P3i+4137Th6eMbdha2+JaCrxfTd9oMoCN5b+6JQXIDM/H+4DTN15PF +540yY7+aZrAnWrmHknNcqFAKsTqfdi2/fFqwoBwCtiEG91WreU6AfEWIiJuTZIru +sIibAoIBAAqIwlo5t+KukF+9jR9DPh0S5rCIdvCvcNaN0WPNF91FPN0vLWQW1bFi +L0TsUDvMkuUZlV3hTPpQxsnZszH3iK64RB5p3jBCcs+gKu7DT59MXJEGVRCHT4Um +YJryAbVKBYIGWl++sZO8+JotWzx2op8uq7o+glMMjKAJoo7SXIiVyC/LHc95urOi +9+PySphPKn0anXPpexmRqGYfqpCDo7rPzgmNutWac80B4/CfHb8iUPg6Z1u+1FNe +yKvcZHgW2Wn00znNJcCitufLGyAnMofudND/c5rx2qfBx7zZS7sKUQ/uRYjes6EZ +QBbJUA/2/yLv8YYpaAaqj4aLwV8hRpkCggEBAIh3e25tr3avCdGgtCxS7Y1blQ2c +ue4erZKmFP1u8wTNHQ03T6sECZbnIfEywRD/esHpclfF3kYAKDRqIP4K905Rb0iH +759ZWt2iCbqZznf50XTvptdmjm5KxvouJzScnQ52gIV6L+QrCKIPelLBEIqCJREh +pmcjjocD/UCCSuHgbAYNNnO/JdhnSylz1tIg26I+2iLNyeTKIepSNlsBxnkLmqM1 +cj/azKBaT04IOMLaN8xfSqitJYSraWMVNgGJM5vfcVaivZnNh0lZBv+qu6YkdM88 +4/avCJ8IutT+FcMM+GbGazOm5ALWqUyhrnbLGc4CQMPfe7Il6NxwcrOxT8w= +-----END RSA PRIVATE KEY-----`) + +var GoproxyCa, goproxyCaErr = tls.X509KeyPair(CA_CERT, CA_KEY) diff --git a/vendor/github.com/elazarl/goproxy/chunked.go b/vendor/github.com/elazarl/goproxy/chunked.go new file mode 100644 index 0000000000..83654f6586 --- /dev/null +++ b/vendor/github.com/elazarl/goproxy/chunked.go @@ -0,0 +1,59 @@ +// Taken from $GOROOT/src/pkg/net/http/chunked +// needed to write https responses to client. +package goproxy + +import ( + "io" + "strconv" +) + +// newChunkedWriter returns a new chunkedWriter that translates writes into HTTP +// "chunked" format before writing them to w. Closing the returned chunkedWriter +// sends the final 0-length chunk that marks the end of the stream. +// +// newChunkedWriter is not needed by normal applications. The http +// package adds chunking automatically if handlers don't set a +// Content-Length header. Using newChunkedWriter inside a handler +// would result in double chunking or chunking with a Content-Length +// length, both of which are wrong. +func newChunkedWriter(w io.Writer) io.WriteCloser { + return &chunkedWriter{w} +} + +// Writing to chunkedWriter translates to writing in HTTP chunked Transfer +// Encoding wire format to the underlying Wire chunkedWriter. +type chunkedWriter struct { + Wire io.Writer +} + +// Write the contents of data as one chunk to Wire. +// NOTE: Note that the corresponding chunk-writing procedure in Conn.Write has +// a bug since it does not check for success of io.WriteString +func (cw *chunkedWriter) Write(data []byte) (n int, err error) { + + // Don't send 0-length data. It looks like EOF for chunked encoding. + if len(data) == 0 { + return 0, nil + } + + head := strconv.FormatInt(int64(len(data)), 16) + "\r\n" + + if _, err = io.WriteString(cw.Wire, head); err != nil { + return 0, err + } + if n, err = cw.Wire.Write(data); err != nil { + return + } + if n != len(data) { + err = io.ErrShortWrite + return + } + _, err = io.WriteString(cw.Wire, "\r\n") + + return +} + +func (cw *chunkedWriter) Close() error { + _, err := io.WriteString(cw.Wire, "0\r\n") + return err +} diff --git a/vendor/github.com/elazarl/goproxy/counterecryptor.go b/vendor/github.com/elazarl/goproxy/counterecryptor.go new file mode 100644 index 0000000000..d1c39d23b0 --- /dev/null +++ b/vendor/github.com/elazarl/goproxy/counterecryptor.go @@ -0,0 +1,73 @@ +package goproxy + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/ecdsa" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "errors" +) + +type CounterEncryptorRand struct { + cipher cipher.Block + counter []byte + rand []byte + ix int +} + +func NewCounterEncryptorRandFromKey(key interface{}, seed []byte) (r CounterEncryptorRand, err error) { + var keyBytes []byte + switch key := key.(type) { + case *rsa.PrivateKey: + keyBytes = x509.MarshalPKCS1PrivateKey(key) + case *ecdsa.PrivateKey: + if keyBytes, err = x509.MarshalECPrivateKey(key); err != nil { + return + } + default: + err = errors.New("only RSA and ECDSA keys supported") + return + } + h := sha256.New() + if r.cipher, err = aes.NewCipher(h.Sum(keyBytes)[:aes.BlockSize]); err != nil { + return + } + r.counter = make([]byte, r.cipher.BlockSize()) + if seed != nil { + copy(r.counter, h.Sum(seed)[:r.cipher.BlockSize()]) + } + r.rand = make([]byte, r.cipher.BlockSize()) + r.ix = len(r.rand) + return +} + +func (c *CounterEncryptorRand) Seed(b []byte) { + if len(b) != len(c.counter) { + panic("SetCounter: wrong counter size") + } + copy(c.counter, b) +} + +func (c *CounterEncryptorRand) refill() { + c.cipher.Encrypt(c.rand, c.counter) + for i := 0; i < len(c.counter); i++ { + if c.counter[i]++; c.counter[i] != 0 { + break + } + } + c.ix = 0 +} + +func (c *CounterEncryptorRand) Read(b []byte) (n int, err error) { + if c.ix == len(c.rand) { + c.refill() + } + if n = len(c.rand) - c.ix; n > len(b) { + n = len(b) + } + copy(b, c.rand[c.ix:c.ix+n]) + c.ix += n + return +} diff --git a/vendor/github.com/elazarl/goproxy/ctx.go b/vendor/github.com/elazarl/goproxy/ctx.go new file mode 100644 index 0000000000..b372f7d4f9 --- /dev/null +++ b/vendor/github.com/elazarl/goproxy/ctx.go @@ -0,0 +1,93 @@ +package goproxy + +import ( + "crypto/tls" + "net/http" + "regexp" +) + +// ProxyCtx is the Proxy context, contains useful information about every request. It is passed to +// every user function. Also used as a logger. +type ProxyCtx struct { + // Will contain the client request from the proxy + Req *http.Request + // Will contain the remote server's response (if available. nil if the request wasn't send yet) + Resp *http.Response + RoundTripper RoundTripper + // will contain the recent error that occurred while trying to send receive or parse traffic + Error error + // A handle for the user to keep data in the context, from the call of ReqHandler to the + // call of RespHandler + UserData interface{} + // Will connect a request to a response + Session int64 + certStore CertStorage + Proxy *ProxyHttpServer +} + +type RoundTripper interface { + RoundTrip(req *http.Request, ctx *ProxyCtx) (*http.Response, error) +} + +type CertStorage interface { + Fetch(hostname string, gen func() (*tls.Certificate, error)) (*tls.Certificate, error) +} + +type RoundTripperFunc func(req *http.Request, ctx *ProxyCtx) (*http.Response, error) + +func (f RoundTripperFunc) RoundTrip(req *http.Request, ctx *ProxyCtx) (*http.Response, error) { + return f(req, ctx) +} + +func (ctx *ProxyCtx) RoundTrip(req *http.Request) (*http.Response, error) { + if ctx.RoundTripper != nil { + return ctx.RoundTripper.RoundTrip(req, ctx) + } + return ctx.Proxy.Tr.RoundTrip(req) +} + +func (ctx *ProxyCtx) printf(msg string, argv ...interface{}) { + ctx.Proxy.Logger.Printf("[%03d] "+msg+"\n", append([]interface{}{ctx.Session & 0xFF}, argv...)...) +} + +// Logf prints a message to the proxy's log. Should be used in a ProxyHttpServer's filter +// This message will be printed only if the Verbose field of the ProxyHttpServer is set to true +// +// proxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) (*http.Request, *http.Response){ +// nr := atomic.AddInt32(&counter,1) +// ctx.Printf("So far %d requests",nr) +// return r, nil +// }) +func (ctx *ProxyCtx) Logf(msg string, argv ...interface{}) { + if ctx.Proxy.Verbose { + ctx.printf("INFO: "+msg, argv...) + } +} + +// Warnf prints a message to the proxy's log. Should be used in a ProxyHttpServer's filter +// This message will always be printed. +// +// proxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) (*http.Request, *http.Response){ +// f,err := os.OpenFile(cachedContent) +// if err != nil { +// ctx.Warnf("error open file %v: %v",cachedContent,err) +// return r, nil +// } +// return r, nil +// }) +func (ctx *ProxyCtx) Warnf(msg string, argv ...interface{}) { + ctx.printf("WARN: "+msg, argv...) +} + +var charsetFinder = regexp.MustCompile("charset=([^ ;]*)") + +// Will try to infer the character set of the request from the headers. +// Returns the empty string if we don't know which character set it used. +// Currently it will look for charset= in the Content-Type header of the request. +func (ctx *ProxyCtx) Charset() string { + charsets := charsetFinder.FindStringSubmatch(ctx.Resp.Header.Get("Content-Type")) + if charsets == nil { + return "" + } + return charsets[1] +} diff --git a/vendor/github.com/elazarl/goproxy/dispatcher.go b/vendor/github.com/elazarl/goproxy/dispatcher.go new file mode 100644 index 0000000000..25c949c0de --- /dev/null +++ b/vendor/github.com/elazarl/goproxy/dispatcher.go @@ -0,0 +1,341 @@ +package goproxy + +import ( + "bytes" + "io/ioutil" + "net" + "net/http" + "regexp" + "strings" +) + +// ReqCondition.HandleReq will decide whether or not to use the ReqHandler on an HTTP request +// before sending it to the remote server +type ReqCondition interface { + RespCondition + HandleReq(req *http.Request, ctx *ProxyCtx) bool +} + +// RespCondition.HandleReq will decide whether or not to use the RespHandler on an HTTP response +// before sending it to the proxy client. Note that resp might be nil, in case there was an +// error sending the request. +type RespCondition interface { + HandleResp(resp *http.Response, ctx *ProxyCtx) bool +} + +// ReqConditionFunc.HandleReq(req,ctx) <=> ReqConditionFunc(req,ctx) +type ReqConditionFunc func(req *http.Request, ctx *ProxyCtx) bool + +// RespConditionFunc.HandleResp(resp,ctx) <=> RespConditionFunc(resp,ctx) +type RespConditionFunc func(resp *http.Response, ctx *ProxyCtx) bool + +func (c ReqConditionFunc) HandleReq(req *http.Request, ctx *ProxyCtx) bool { + return c(req, ctx) +} + +// ReqConditionFunc cannot test responses. It only satisfies RespCondition interface so that +// to be usable as RespCondition. +func (c ReqConditionFunc) HandleResp(resp *http.Response, ctx *ProxyCtx) bool { + return c(ctx.Req, ctx) +} + +func (c RespConditionFunc) HandleResp(resp *http.Response, ctx *ProxyCtx) bool { + return c(resp, ctx) +} + +// UrlHasPrefix returns a ReqCondition checking wether the destination URL the proxy client has requested +// has the given prefix, with or without the host. +// For example UrlHasPrefix("host/x") will match requests of the form 'GET host/x', and will match +// requests to url 'http://host/x' +func UrlHasPrefix(prefix string) ReqConditionFunc { + return func(req *http.Request, ctx *ProxyCtx) bool { + return strings.HasPrefix(req.URL.Path, prefix) || + strings.HasPrefix(req.URL.Host+req.URL.Path, prefix) || + strings.HasPrefix(req.URL.Scheme+req.URL.Host+req.URL.Path, prefix) + } +} + +// UrlIs returns a ReqCondition, testing whether or not the request URL is one of the given strings +// with or without the host prefix. +// UrlIs("google.com/","foo") will match requests 'GET /' to 'google.com', requests `'GET google.com/' to +// any host, and requests of the form 'GET foo'. +func UrlIs(urls ...string) ReqConditionFunc { + urlSet := make(map[string]bool) + for _, u := range urls { + urlSet[u] = true + } + return func(req *http.Request, ctx *ProxyCtx) bool { + _, pathOk := urlSet[req.URL.Path] + _, hostAndOk := urlSet[req.URL.Host+req.URL.Path] + return pathOk || hostAndOk + } +} + +// ReqHostMatches returns a ReqCondition, testing whether the host to which the request was directed to matches +// any of the given regular expressions. +func ReqHostMatches(regexps ...*regexp.Regexp) ReqConditionFunc { + return func(req *http.Request, ctx *ProxyCtx) bool { + for _, re := range regexps { + if re.MatchString(req.Host) { + return true + } + } + return false + } +} + +// ReqHostIs returns a ReqCondition, testing whether the host to which the request is directed to equal +// to one of the given strings +func ReqHostIs(hosts ...string) ReqConditionFunc { + hostSet := make(map[string]bool) + for _, h := range hosts { + hostSet[h] = true + } + return func(req *http.Request, ctx *ProxyCtx) bool { + _, ok := hostSet[req.URL.Host] + return ok + } +} + +var localHostIpv4 = regexp.MustCompile(`127\.0\.0\.\d+`) + +// IsLocalHost checks whether the destination host is explicitly local host +// (buggy, there can be IPv6 addresses it doesn't catch) +var IsLocalHost ReqConditionFunc = func(req *http.Request, ctx *ProxyCtx) bool { + return req.URL.Host == "::1" || + req.URL.Host == "0:0:0:0:0:0:0:1" || + localHostIpv4.MatchString(req.URL.Host) || + req.URL.Host == "localhost" +} + +// UrlMatches returns a ReqCondition testing whether the destination URL +// of the request matches the given regexp, with or without prefix +func UrlMatches(re *regexp.Regexp) ReqConditionFunc { + return func(req *http.Request, ctx *ProxyCtx) bool { + return re.MatchString(req.URL.Path) || + re.MatchString(req.URL.Host+req.URL.Path) + } +} + +// DstHostIs returns a ReqCondition testing wether the host in the request url is the given string +func DstHostIs(host string) ReqConditionFunc { + return func(req *http.Request, ctx *ProxyCtx) bool { + return req.URL.Host == host + } +} + +// SrcIpIs returns a ReqCondition testing whether the source IP of the request is one of the given strings +func SrcIpIs(ips ...string) ReqCondition { + return ReqConditionFunc(func(req *http.Request, ctx *ProxyCtx) bool { + for _, ip := range ips { + if strings.HasPrefix(req.RemoteAddr, ip+":") { + return true + } + } + return false + }) +} + +// Not returns a ReqCondition negating the given ReqCondition +func Not(r ReqCondition) ReqConditionFunc { + return func(req *http.Request, ctx *ProxyCtx) bool { + return !r.HandleReq(req, ctx) + } +} + +// ContentTypeIs returns a RespCondition testing whether the HTTP response has Content-Type header equal +// to one of the given strings. +func ContentTypeIs(typ string, types ...string) RespCondition { + types = append(types, typ) + return RespConditionFunc(func(resp *http.Response, ctx *ProxyCtx) bool { + if resp == nil { + return false + } + contentType := resp.Header.Get("Content-Type") + for _, typ := range types { + if contentType == typ || strings.HasPrefix(contentType, typ+";") { + return true + } + } + return false + }) +} + +// StatusCodeIs returns a RespCondition, testing whether or not the HTTP status +// code is one of the given ints +func StatusCodeIs(codes ...int) RespCondition { + codeSet := make(map[int]bool) + for _, c := range codes { + codeSet[c] = true + } + return RespConditionFunc(func(resp *http.Response, ctx *ProxyCtx) bool { + if resp == nil { + return false + } + _, codeMatch := codeSet[resp.StatusCode] + return codeMatch + }) +} + +// ProxyHttpServer.OnRequest Will return a temporary ReqProxyConds struct, aggregating the given condtions. +// You will use the ReqProxyConds struct to register a ReqHandler, that would filter +// the request, only if all the given ReqCondition matched. +// Typical usage: +// proxy.OnRequest(UrlIs("example.com/foo"),UrlMatches(regexp.MustParse(`.*\.exampl.\com\./.*`)).Do(...) +func (proxy *ProxyHttpServer) OnRequest(conds ...ReqCondition) *ReqProxyConds { + return &ReqProxyConds{proxy, conds} +} + +// ReqProxyConds aggregate ReqConditions for a ProxyHttpServer. Upon calling Do, it will register a ReqHandler that would +// handle the request if all conditions on the HTTP request are met. +type ReqProxyConds struct { + proxy *ProxyHttpServer + reqConds []ReqCondition +} + +// DoFunc is equivalent to proxy.OnRequest().Do(FuncReqHandler(f)) +func (pcond *ReqProxyConds) DoFunc(f func(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response)) { + pcond.Do(FuncReqHandler(f)) +} + +// ReqProxyConds.Do will register the ReqHandler on the proxy, +// the ReqHandler will handle the HTTP request if all the conditions +// aggregated in the ReqProxyConds are met. Typical usage: +// proxy.OnRequest().Do(handler) // will call handler.Handle(req,ctx) on every request to the proxy +// proxy.OnRequest(cond1,cond2).Do(handler) +// // given request to the proxy, will test if cond1.HandleReq(req,ctx) && cond2.HandleReq(req,ctx) are true +// // if they are, will call handler.Handle(req,ctx) +func (pcond *ReqProxyConds) Do(h ReqHandler) { + pcond.proxy.reqHandlers = append(pcond.proxy.reqHandlers, + FuncReqHandler(func(r *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response) { + for _, cond := range pcond.reqConds { + if !cond.HandleReq(r, ctx) { + return r, nil + } + } + return h.Handle(r, ctx) + })) +} + +// HandleConnect is used when proxy receives an HTTP CONNECT request, +// it'll then use the HttpsHandler to determine what should it +// do with this request. The handler returns a ConnectAction struct, the Action field in the ConnectAction +// struct returned will determine what to do with this request. ConnectAccept will simply accept the request +// forwarding all bytes from the client to the remote host, ConnectReject will close the connection with the +// client, and ConnectMitm, will assume the underlying connection is an HTTPS connection, and will use Man +// in the Middle attack to eavesdrop the connection. All regular handler will be active on this eavesdropped +// connection. +// The ConnectAction struct contains possible tlsConfig that will be used for eavesdropping. If nil, the proxy +// will use the default tls configuration. +// proxy.OnRequest().HandleConnect(goproxy.AlwaysReject) // rejects all CONNECT requests +func (pcond *ReqProxyConds) HandleConnect(h HttpsHandler) { + pcond.proxy.httpsHandlers = append(pcond.proxy.httpsHandlers, + FuncHttpsHandler(func(host string, ctx *ProxyCtx) (*ConnectAction, string) { + for _, cond := range pcond.reqConds { + if !cond.HandleReq(ctx.Req, ctx) { + return nil, "" + } + } + return h.HandleConnect(host, ctx) + })) +} + +// HandleConnectFunc is equivalent to HandleConnect, +// for example, accepting CONNECT request if they contain a password in header +// io.WriteString(h,password) +// passHash := h.Sum(nil) +// proxy.OnRequest().HandleConnectFunc(func(host string, ctx *ProxyCtx) (*ConnectAction, string) { +// c := sha1.New() +// io.WriteString(c,ctx.Req.Header.Get("X-GoProxy-Auth")) +// if c.Sum(nil) == passHash { +// return OkConnect, host +// } +// return RejectConnect, host +// }) +func (pcond *ReqProxyConds) HandleConnectFunc(f func(host string, ctx *ProxyCtx) (*ConnectAction, string)) { + pcond.HandleConnect(FuncHttpsHandler(f)) +} + +func (pcond *ReqProxyConds) HijackConnect(f func(req *http.Request, client net.Conn, ctx *ProxyCtx)) { + pcond.proxy.httpsHandlers = append(pcond.proxy.httpsHandlers, + FuncHttpsHandler(func(host string, ctx *ProxyCtx) (*ConnectAction, string) { + for _, cond := range pcond.reqConds { + if !cond.HandleReq(ctx.Req, ctx) { + return nil, "" + } + } + return &ConnectAction{Action: ConnectHijack, Hijack: f}, host + })) +} + +// ProxyConds is used to aggregate RespConditions for a ProxyHttpServer. +// Upon calling ProxyConds.Do, it will register a RespHandler that would +// handle the HTTP response from remote server if all conditions on the HTTP response are met. +type ProxyConds struct { + proxy *ProxyHttpServer + reqConds []ReqCondition + respCond []RespCondition +} + +// ProxyConds.DoFunc is equivalent to proxy.OnResponse().Do(FuncRespHandler(f)) +func (pcond *ProxyConds) DoFunc(f func(resp *http.Response, ctx *ProxyCtx) *http.Response) { + pcond.Do(FuncRespHandler(f)) +} + +// ProxyConds.Do will register the RespHandler on the proxy, h.Handle(resp,ctx) will be called on every +// request that matches the conditions aggregated in pcond. +func (pcond *ProxyConds) Do(h RespHandler) { + pcond.proxy.respHandlers = append(pcond.proxy.respHandlers, + FuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response { + for _, cond := range pcond.reqConds { + if !cond.HandleReq(ctx.Req, ctx) { + return resp + } + } + for _, cond := range pcond.respCond { + if !cond.HandleResp(resp, ctx) { + return resp + } + } + return h.Handle(resp, ctx) + })) +} + +// OnResponse is used when adding a response-filter to the HTTP proxy, usual pattern is +// proxy.OnResponse(cond1,cond2).Do(handler) // handler.Handle(resp,ctx) will be used +// // if cond1.HandleResp(resp) && cond2.HandleResp(resp) +func (proxy *ProxyHttpServer) OnResponse(conds ...RespCondition) *ProxyConds { + return &ProxyConds{proxy, make([]ReqCondition, 0), conds} +} + +// AlwaysMitm is a HttpsHandler that always eavesdrop https connections, for example to +// eavesdrop all https connections to www.google.com, we can use +// proxy.OnRequest(goproxy.ReqHostIs("www.google.com")).HandleConnect(goproxy.AlwaysMitm) +var AlwaysMitm FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) { + return MitmConnect, host +} + +// AlwaysReject is a HttpsHandler that drops any CONNECT request, for example, this code will disallow +// connections to hosts on any other port than 443 +// proxy.OnRequest(goproxy.Not(goproxy.ReqHostMatches(regexp.MustCompile(":443$"))). +// HandleConnect(goproxy.AlwaysReject) +var AlwaysReject FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) { + return RejectConnect, host +} + +// HandleBytes will return a RespHandler that read the entire body of the request +// to a byte array in memory, would run the user supplied f function on the byte arra, +// and will replace the body of the original response with the resulting byte array. +func HandleBytes(f func(b []byte, ctx *ProxyCtx) []byte) RespHandler { + return FuncRespHandler(func(resp *http.Response, ctx *ProxyCtx) *http.Response { + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + ctx.Warnf("Cannot read response %s", err) + return resp + } + resp.Body.Close() + + resp.Body = ioutil.NopCloser(bytes.NewBuffer(f(b, ctx))) + return resp + }) +} diff --git a/vendor/github.com/elazarl/goproxy/doc.go b/vendor/github.com/elazarl/goproxy/doc.go new file mode 100644 index 0000000000..6f44317b94 --- /dev/null +++ b/vendor/github.com/elazarl/goproxy/doc.go @@ -0,0 +1,100 @@ +/* +Package goproxy provides a customizable HTTP proxy, +supporting hijacking HTTPS connection. + +The intent of the proxy, is to be usable with reasonable amount of traffic +yet, customizable and programmable. + +The proxy itself is simply an `net/http` handler. + +Typical usage is + + proxy := goproxy.NewProxyHttpServer() + proxy.OnRequest(..conditions..).Do(..requesthandler..) + proxy.OnRequest(..conditions..).DoFunc(..requesthandlerFunction..) + proxy.OnResponse(..conditions..).Do(..responesHandler..) + proxy.OnResponse(..conditions..).DoFunc(..responesHandlerFunction..) + http.ListenAndServe(":8080", proxy) + +Adding a header to each request + + proxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) (*http.Request, *http.Response){ + r.Header.Set("X-GoProxy","1") + return r, nil + }) + +Note that the function is called before the proxy sends the request to the server + +For printing the content type of all incoming responses + + proxy.OnResponse().DoFunc(func(r *http.Response, ctx *goproxy.ProxyCtx)*http.Response{ + println(ctx.Req.Host,"->",r.Header.Get("Content-Type")) + return r + }) + +note that we used the ProxyCtx context variable here. It contains the request +and the response (Req and Resp, Resp is nil if unavailable) of this specific client +interaction with the proxy. + +To print the content type of all responses from a certain url, we'll add a +ReqCondition to the OnResponse function: + + proxy.OnResponse(goproxy.UrlIs("golang.org/pkg")).DoFunc(func(r *http.Response, ctx *goproxy.ProxyCtx)*http.Response{ + println(ctx.Req.Host,"->",r.Header.Get("Content-Type")) + return r + }) + +We can write the condition ourselves, conditions can be set on request and on response + + var random = ReqConditionFunc(func(r *http.Request) bool { + return rand.Intn(1) == 0 + }) + var hasGoProxyHeader = RespConditionFunc(func(resp *http.Response,req *http.Request)bool { + return resp.Header.Get("X-GoProxy") != "" + }) + +Caution! If you give a RespCondition to the OnRequest function, you'll get a run time panic! It doesn't +make sense to read the response, if you still haven't got it! + +Finally, we have convenience function to throw a quick response + + proxy.OnResponse(hasGoProxyHeader).DoFunc(func(r*http.Response,ctx *goproxy.ProxyCtx)*http.Response { + r.Body.Close() + return goproxy.NewResponse(ctx.Req, goproxy.ContentTypeText, http.StatusForbidden, "Can't see response with X-GoProxy header!") + }) + +we close the body of the original response, and return a new 403 response with a short message. + +Example use cases: + +1. https://github.com/elazarl/goproxy/tree/master/examples/goproxy-avgsize + +To measure the average size of an Html served in your site. One can ask +all the QA team to access the website by a proxy, and the proxy will +measure the average size of all text/html responses from your host. + +2. [not yet implemented] + +All requests to your web servers should be directed through the proxy, +when the proxy will detect html pieces sent as a response to AJAX +request, it'll send a warning email. + +3. https://github.com/elazarl/goproxy/blob/master/examples/goproxy-httpdump/ + +Generate a real traffic to your website by real users using through +proxy. Record the traffic, and try it again for more real load testing. + +4. https://github.com/elazarl/goproxy/tree/master/examples/goproxy-no-reddit-at-worktime + +Will allow browsing to reddit.com between 8:00am and 17:00pm + +5. https://github.com/elazarl/goproxy/tree/master/examples/goproxy-jquery-version + +Will warn if multiple versions of jquery are used in the same domain. + +6. https://github.com/elazarl/goproxy/blob/master/examples/goproxy-upside-down-ternet/ + +Modifies image files in an HTTP response via goproxy's image extension found in ext/. + +*/ +package goproxy diff --git a/vendor/github.com/elazarl/goproxy/https.go b/vendor/github.com/elazarl/goproxy/https.go new file mode 100644 index 0000000000..608863fad4 --- /dev/null +++ b/vendor/github.com/elazarl/goproxy/https.go @@ -0,0 +1,493 @@ +package goproxy + +import ( + "bufio" + "crypto/tls" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/url" + "os" + "regexp" + "strconv" + "strings" + "sync" + "sync/atomic" +) + +type ConnectActionLiteral int + +const ( + ConnectAccept = iota + ConnectReject + ConnectMitm + ConnectHijack + ConnectHTTPMitm + ConnectProxyAuthHijack +) + +var ( + OkConnect = &ConnectAction{Action: ConnectAccept, TLSConfig: TLSConfigFromCA(&GoproxyCa)} + MitmConnect = &ConnectAction{Action: ConnectMitm, TLSConfig: TLSConfigFromCA(&GoproxyCa)} + HTTPMitmConnect = &ConnectAction{Action: ConnectHTTPMitm, TLSConfig: TLSConfigFromCA(&GoproxyCa)} + RejectConnect = &ConnectAction{Action: ConnectReject, TLSConfig: TLSConfigFromCA(&GoproxyCa)} + httpsRegexp = regexp.MustCompile(`^https:\/\/`) +) + +// ConnectAction enables the caller to override the standard connect flow. +// When Action is ConnectHijack, it is up to the implementer to send the +// HTTP 200, or any other valid http response back to the client from within the +// Hijack func +type ConnectAction struct { + Action ConnectActionLiteral + Hijack func(req *http.Request, client net.Conn, ctx *ProxyCtx) + TLSConfig func(host string, ctx *ProxyCtx) (*tls.Config, error) +} + +func stripPort(s string) string { + var ix int + if strings.Contains(s, "[") && strings.Contains(s, "]") { + //ipv6 : for example : [2606:4700:4700::1111]:443 + + //strip '[' and ']' + s = strings.ReplaceAll(s, "[", "") + s = strings.ReplaceAll(s, "]", "") + + ix = strings.LastIndexAny(s, ":") + if ix == -1 { + return s + } + } else { + //ipv4 + ix = strings.IndexRune(s, ':') + if ix == -1 { + return s + } + + } + return s[:ix] +} + +func (proxy *ProxyHttpServer) dial(network, addr string) (c net.Conn, err error) { + if proxy.Tr.Dial != nil { + return proxy.Tr.Dial(network, addr) + } + return net.Dial(network, addr) +} + +func (proxy *ProxyHttpServer) connectDial(ctx *ProxyCtx, network, addr string) (c net.Conn, err error) { + if proxy.ConnectDialWithReq == nil && proxy.ConnectDial == nil { + return proxy.dial(network, addr) + } + + if proxy.ConnectDialWithReq != nil { + return proxy.ConnectDialWithReq(ctx.Req, network, addr) + } + + return proxy.ConnectDial(network, addr) +} + +type halfClosable interface { + net.Conn + CloseWrite() error + CloseRead() error +} + +var _ halfClosable = (*net.TCPConn)(nil) + +func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request) { + ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), Proxy: proxy, certStore: proxy.CertStore} + + hij, ok := w.(http.Hijacker) + if !ok { + panic("httpserver does not support hijacking") + } + + proxyClient, _, e := hij.Hijack() + if e != nil { + panic("Cannot hijack connection " + e.Error()) + } + + ctx.Logf("Running %d CONNECT handlers", len(proxy.httpsHandlers)) + todo, host := OkConnect, r.URL.Host + for i, h := range proxy.httpsHandlers { + newtodo, newhost := h.HandleConnect(host, ctx) + + // If found a result, break the loop immediately + if newtodo != nil { + todo, host = newtodo, newhost + ctx.Logf("on %dth handler: %v %s", i, todo, host) + break + } + } + switch todo.Action { + case ConnectAccept: + if !hasPort.MatchString(host) { + host += ":80" + } + targetSiteCon, err := proxy.connectDial(ctx, "tcp", host) + if err != nil { + ctx.Warnf("Error dialing to %s: %s", host, err.Error()) + httpError(proxyClient, ctx, err) + return + } + ctx.Logf("Accepting CONNECT to %s", host) + proxyClient.Write([]byte("HTTP/1.0 200 Connection established\r\n\r\n")) + + targetTCP, targetOK := targetSiteCon.(halfClosable) + proxyClientTCP, clientOK := proxyClient.(halfClosable) + if targetOK && clientOK { + go copyAndClose(ctx, targetTCP, proxyClientTCP) + go copyAndClose(ctx, proxyClientTCP, targetTCP) + } else { + go func() { + var wg sync.WaitGroup + wg.Add(2) + go copyOrWarn(ctx, targetSiteCon, proxyClient, &wg) + go copyOrWarn(ctx, proxyClient, targetSiteCon, &wg) + wg.Wait() + proxyClient.Close() + targetSiteCon.Close() + + }() + } + + case ConnectHijack: + todo.Hijack(r, proxyClient, ctx) + case ConnectHTTPMitm: + proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n")) + ctx.Logf("Assuming CONNECT is plain HTTP tunneling, mitm proxying it") + targetSiteCon, err := proxy.connectDial(ctx, "tcp", host) + if err != nil { + ctx.Warnf("Error dialing to %s: %s", host, err.Error()) + return + } + for { + client := bufio.NewReader(proxyClient) + remote := bufio.NewReader(targetSiteCon) + req, err := http.ReadRequest(client) + if err != nil && err != io.EOF { + ctx.Warnf("cannot read request of MITM HTTP client: %+#v", err) + } + if err != nil { + return + } + req, resp := proxy.filterRequest(req, ctx) + if resp == nil { + if err := req.Write(targetSiteCon); err != nil { + httpError(proxyClient, ctx, err) + return + } + resp, err = http.ReadResponse(remote, req) + if err != nil { + httpError(proxyClient, ctx, err) + return + } + defer resp.Body.Close() + } + resp = proxy.filterResponse(resp, ctx) + if err := resp.Write(proxyClient); err != nil { + httpError(proxyClient, ctx, err) + return + } + } + case ConnectMitm: + proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n")) + ctx.Logf("Assuming CONNECT is TLS, mitm proxying it") + // this goes in a separate goroutine, so that the net/http server won't think we're + // still handling the request even after hijacking the connection. Those HTTP CONNECT + // request can take forever, and the server will be stuck when "closed". + // TODO: Allow Server.Close() mechanism to shut down this connection as nicely as possible + tlsConfig := defaultTLSConfig + if todo.TLSConfig != nil { + var err error + tlsConfig, err = todo.TLSConfig(host, ctx) + if err != nil { + httpError(proxyClient, ctx, err) + return + } + } + go func() { + //TODO: cache connections to the remote website + rawClientTls := tls.Server(proxyClient, tlsConfig) + if err := rawClientTls.Handshake(); err != nil { + ctx.Warnf("Cannot handshake client %v %v", r.Host, err) + return + } + defer rawClientTls.Close() + clientTlsReader := bufio.NewReader(rawClientTls) + for !isEof(clientTlsReader) { + req, err := http.ReadRequest(clientTlsReader) + var ctx = &ProxyCtx{Req: req, Session: atomic.AddInt64(&proxy.sess, 1), Proxy: proxy, UserData: ctx.UserData} + if err != nil && err != io.EOF { + return + } + if err != nil { + ctx.Warnf("Cannot read TLS request from mitm'd client %v %v", r.Host, err) + return + } + req.RemoteAddr = r.RemoteAddr // since we're converting the request, need to carry over the original connecting IP as well + ctx.Logf("req %v", r.Host) + + if !httpsRegexp.MatchString(req.URL.String()) { + req.URL, err = url.Parse("https://" + r.Host + req.URL.String()) + } + + // Bug fix which goproxy fails to provide request + // information URL in the context when does HTTPS MITM + ctx.Req = req + + req, resp := proxy.filterRequest(req, ctx) + if resp == nil { + if isWebSocketRequest(req) { + ctx.Logf("Request looks like websocket upgrade.") + proxy.serveWebsocketTLS(ctx, w, req, tlsConfig, rawClientTls) + return + } + if err != nil { + if req.URL != nil { + ctx.Warnf("Illegal URL %s", "https://"+r.Host+req.URL.Path) + } else { + ctx.Warnf("Illegal URL %s", "https://"+r.Host) + } + return + } + removeProxyHeaders(ctx, req) + resp, err = func() (*http.Response, error) { + // explicitly discard request body to avoid data races in certain RoundTripper implementations + // see https://github.com/golang/go/issues/61596#issuecomment-1652345131 + defer req.Body.Close() + return ctx.RoundTrip(req) + }() + if err != nil { + ctx.Warnf("Cannot read TLS response from mitm'd server %v", err) + return + } + ctx.Logf("resp %v", resp.Status) + } + resp = proxy.filterResponse(resp, ctx) + defer resp.Body.Close() + + text := resp.Status + statusCode := strconv.Itoa(resp.StatusCode) + " " + if strings.HasPrefix(text, statusCode) { + text = text[len(statusCode):] + } + // always use 1.1 to support chunked encoding + if _, err := io.WriteString(rawClientTls, "HTTP/1.1"+" "+statusCode+text+"\r\n"); err != nil { + ctx.Warnf("Cannot write TLS response HTTP status from mitm'd client: %v", err) + return + } + + if resp.Request.Method == "HEAD" { + // don't change Content-Length for HEAD request + } else { + // Since we don't know the length of resp, return chunked encoded response + // TODO: use a more reasonable scheme + resp.Header.Del("Content-Length") + resp.Header.Set("Transfer-Encoding", "chunked") + } + // Force connection close otherwise chrome will keep CONNECT tunnel open forever + resp.Header.Set("Connection", "close") + if err := resp.Header.Write(rawClientTls); err != nil { + ctx.Warnf("Cannot write TLS response header from mitm'd client: %v", err) + return + } + if _, err = io.WriteString(rawClientTls, "\r\n"); err != nil { + ctx.Warnf("Cannot write TLS response header end from mitm'd client: %v", err) + return + } + + if resp.Request.Method == "HEAD" { + // Don't write out a response body for HEAD request + } else { + chunked := newChunkedWriter(rawClientTls) + if _, err := io.Copy(chunked, resp.Body); err != nil { + ctx.Warnf("Cannot write TLS response body from mitm'd client: %v", err) + return + } + if err := chunked.Close(); err != nil { + ctx.Warnf("Cannot write TLS chunked EOF from mitm'd client: %v", err) + return + } + if _, err = io.WriteString(rawClientTls, "\r\n"); err != nil { + ctx.Warnf("Cannot write TLS response chunked trailer from mitm'd client: %v", err) + return + } + } + } + ctx.Logf("Exiting on EOF") + }() + case ConnectProxyAuthHijack: + proxyClient.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\n")) + todo.Hijack(r, proxyClient, ctx) + case ConnectReject: + if ctx.Resp != nil { + if err := ctx.Resp.Write(proxyClient); err != nil { + ctx.Warnf("Cannot write response that reject http CONNECT: %v", err) + } + } + proxyClient.Close() + } +} + +func httpError(w io.WriteCloser, ctx *ProxyCtx, err error) { + errStr := fmt.Sprintf("HTTP/1.1 502 Bad Gateway\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s", len(err.Error()), err.Error()) + if _, err := io.WriteString(w, errStr); err != nil { + ctx.Warnf("Error responding to client: %s", err) + } + if err := w.Close(); err != nil { + ctx.Warnf("Error closing client connection: %s", err) + } +} + +func copyOrWarn(ctx *ProxyCtx, dst io.Writer, src io.Reader, wg *sync.WaitGroup) { + if _, err := io.Copy(dst, src); err != nil { + ctx.Warnf("Error copying to client: %s", err) + } + wg.Done() +} + +func copyAndClose(ctx *ProxyCtx, dst, src halfClosable) { + if _, err := io.Copy(dst, src); err != nil { + ctx.Warnf("Error copying to client: %s", err) + } + + dst.CloseWrite() + src.CloseRead() +} + +func dialerFromEnv(proxy *ProxyHttpServer) func(network, addr string) (net.Conn, error) { + https_proxy := os.Getenv("HTTPS_PROXY") + if https_proxy == "" { + https_proxy = os.Getenv("https_proxy") + } + if https_proxy == "" { + return nil + } + return proxy.NewConnectDialToProxy(https_proxy) +} + +func (proxy *ProxyHttpServer) NewConnectDialToProxy(https_proxy string) func(network, addr string) (net.Conn, error) { + return proxy.NewConnectDialToProxyWithHandler(https_proxy, nil) +} + +func (proxy *ProxyHttpServer) NewConnectDialToProxyWithHandler(https_proxy string, connectReqHandler func(req *http.Request)) func(network, addr string) (net.Conn, error) { + u, err := url.Parse(https_proxy) + if err != nil { + return nil + } + if u.Scheme == "" || u.Scheme == "http" { + if !strings.ContainsRune(u.Host, ':') { + u.Host += ":80" + } + return func(network, addr string) (net.Conn, error) { + connectReq := &http.Request{ + Method: "CONNECT", + URL: &url.URL{Opaque: addr}, + Host: addr, + Header: make(http.Header), + } + if connectReqHandler != nil { + connectReqHandler(connectReq) + } + c, err := proxy.dial(network, u.Host) + if err != nil { + return nil, err + } + connectReq.Write(c) + // Read response. + // Okay to use and discard buffered reader here, because + // TLS server will not speak until spoken to. + br := bufio.NewReader(c) + resp, err := http.ReadResponse(br, connectReq) + if err != nil { + c.Close() + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + resp, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + c.Close() + return nil, errors.New("proxy refused connection" + string(resp)) + } + return c, nil + } + } + if u.Scheme == "https" || u.Scheme == "wss" { + if !strings.ContainsRune(u.Host, ':') { + u.Host += ":443" + } + return func(network, addr string) (net.Conn, error) { + c, err := proxy.dial(network, u.Host) + if err != nil { + return nil, err + } + c = tls.Client(c, proxy.Tr.TLSClientConfig) + connectReq := &http.Request{ + Method: "CONNECT", + URL: &url.URL{Opaque: addr}, + Host: addr, + Header: make(http.Header), + } + if connectReqHandler != nil { + connectReqHandler(connectReq) + } + connectReq.Write(c) + // Read response. + // Okay to use and discard buffered reader here, because + // TLS server will not speak until spoken to. + br := bufio.NewReader(c) + resp, err := http.ReadResponse(br, connectReq) + if err != nil { + c.Close() + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 500)) + if err != nil { + return nil, err + } + c.Close() + return nil, errors.New("proxy refused connection" + string(body)) + } + return c, nil + } + } + return nil +} + +func TLSConfigFromCA(ca *tls.Certificate) func(host string, ctx *ProxyCtx) (*tls.Config, error) { + return func(host string, ctx *ProxyCtx) (*tls.Config, error) { + var err error + var cert *tls.Certificate + + hostname := stripPort(host) + config := defaultTLSConfig.Clone() + ctx.Logf("signing for %s", stripPort(host)) + + genCert := func() (*tls.Certificate, error) { + return signHost(*ca, []string{hostname}) + } + if ctx.certStore != nil { + cert, err = ctx.certStore.Fetch(hostname, genCert) + } else { + cert, err = genCert() + } + + if err != nil { + ctx.Warnf("Cannot sign host certificate with provided CA: %s", err) + return nil, err + } + + config.Certificates = append(config.Certificates, *cert) + return config, nil + } +} diff --git a/vendor/github.com/elazarl/goproxy/logger.go b/vendor/github.com/elazarl/goproxy/logger.go new file mode 100644 index 0000000000..939cf69ed6 --- /dev/null +++ b/vendor/github.com/elazarl/goproxy/logger.go @@ -0,0 +1,5 @@ +package goproxy + +type Logger interface { + Printf(format string, v ...interface{}) +} diff --git a/vendor/github.com/elazarl/goproxy/proxy.go b/vendor/github.com/elazarl/goproxy/proxy.go new file mode 100644 index 0000000000..fa5494c6ad --- /dev/null +++ b/vendor/github.com/elazarl/goproxy/proxy.go @@ -0,0 +1,225 @@ +package goproxy + +import ( + "bufio" + "io" + "log" + "net" + "net/http" + "os" + "regexp" + "sync/atomic" +) + +// The basic proxy type. Implements http.Handler. +type ProxyHttpServer struct { + // session variable must be aligned in i386 + // see http://golang.org/src/pkg/sync/atomic/doc.go#L41 + sess int64 + // KeepDestinationHeaders indicates the proxy should retain any headers present in the http.Response before proxying + KeepDestinationHeaders bool + // setting Verbose to true will log information on each request sent to the proxy + Verbose bool + Logger Logger + NonproxyHandler http.Handler + reqHandlers []ReqHandler + respHandlers []RespHandler + httpsHandlers []HttpsHandler + Tr *http.Transport + // ConnectDial will be used to create TCP connections for CONNECT requests + // if nil Tr.Dial will be used + ConnectDial func(network string, addr string) (net.Conn, error) + ConnectDialWithReq func(req *http.Request, network string, addr string) (net.Conn, error) + CertStore CertStorage + KeepHeader bool +} + +var hasPort = regexp.MustCompile(`:\d+$`) + +func copyHeaders(dst, src http.Header, keepDestHeaders bool) { + if !keepDestHeaders { + for k := range dst { + dst.Del(k) + } + } + for k, vs := range src { + for _, v := range vs { + dst.Add(k, v) + } + } +} + +func isEof(r *bufio.Reader) bool { + _, err := r.Peek(1) + if err == io.EOF { + return true + } + return false +} + +func (proxy *ProxyHttpServer) filterRequest(r *http.Request, ctx *ProxyCtx) (req *http.Request, resp *http.Response) { + req = r + for _, h := range proxy.reqHandlers { + req, resp = h.Handle(r, ctx) + // non-nil resp means the handler decided to skip sending the request + // and return canned response instead. + if resp != nil { + break + } + } + return +} +func (proxy *ProxyHttpServer) filterResponse(respOrig *http.Response, ctx *ProxyCtx) (resp *http.Response) { + resp = respOrig + for _, h := range proxy.respHandlers { + ctx.Resp = resp + resp = h.Handle(resp, ctx) + } + return +} + +func removeProxyHeaders(ctx *ProxyCtx, r *http.Request) { + r.RequestURI = "" // this must be reset when serving a request with the client + ctx.Logf("Sending request %v %v", r.Method, r.URL.String()) + // If no Accept-Encoding header exists, Transport will add the headers it can accept + // and would wrap the response body with the relevant reader. + r.Header.Del("Accept-Encoding") + // curl can add that, see + // https://jdebp.eu./FGA/web-proxy-connection-header.html + r.Header.Del("Proxy-Connection") + r.Header.Del("Proxy-Authenticate") + r.Header.Del("Proxy-Authorization") + // Connection, Authenticate and Authorization are single hop Header: + // http://www.w3.org/Protocols/rfc2616/rfc2616.txt + // 14.10 Connection + // The Connection general-header field allows the sender to specify + // options that are desired for that particular connection and MUST NOT + // be communicated by proxies over further connections. + + // When server reads http request it sets req.Close to true if + // "Connection" header contains "close". + // https://github.com/golang/go/blob/master/src/net/http/request.go#L1080 + // Later, transfer.go adds "Connection: close" back when req.Close is true + // https://github.com/golang/go/blob/master/src/net/http/transfer.go#L275 + // That's why tests that checks "Connection: close" removal fail + if r.Header.Get("Connection") == "close" { + r.Close = false + } + r.Header.Del("Connection") +} + +type flushWriter struct { + w io.Writer +} + +func (fw flushWriter) Write(p []byte) (int, error) { + n, err := fw.w.Write(p) + if f, ok := fw.w.(http.Flusher); ok { + // only flush if the Writer implements the Flusher interface. + f.Flush() + } + + return n, err +} + +// Standard net/http function. Shouldn't be used directly, http.Serve will use it. +func (proxy *ProxyHttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + //r.Header["X-Forwarded-For"] = w.RemoteAddr() + if r.Method == "CONNECT" { + proxy.handleHttps(w, r) + } else { + ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), Proxy: proxy} + + var err error + ctx.Logf("Got request %v %v %v %v", r.URL.Path, r.Host, r.Method, r.URL.String()) + if !r.URL.IsAbs() { + proxy.NonproxyHandler.ServeHTTP(w, r) + return + } + r, resp := proxy.filterRequest(r, ctx) + + if resp == nil { + if isWebSocketRequest(r) { + ctx.Logf("Request looks like websocket upgrade.") + proxy.serveWebsocket(ctx, w, r) + } + + if !proxy.KeepHeader { + removeProxyHeaders(ctx, r) + } + resp, err = ctx.RoundTrip(r) + if err != nil { + ctx.Error = err + resp = proxy.filterResponse(nil, ctx) + + } + if resp != nil { + ctx.Logf("Received response %v", resp.Status) + } + } + + var origBody io.ReadCloser + + if resp != nil { + origBody = resp.Body + defer origBody.Close() + } + + resp = proxy.filterResponse(resp, ctx) + + if resp == nil { + var errorString string + if ctx.Error != nil { + errorString = "error read response " + r.URL.Host + " : " + ctx.Error.Error() + ctx.Logf(errorString) + http.Error(w, ctx.Error.Error(), 500) + } else { + errorString = "error read response " + r.URL.Host + ctx.Logf(errorString) + http.Error(w, errorString, 500) + } + return + } + ctx.Logf("Copying response to client %v [%d]", resp.Status, resp.StatusCode) + // http.ResponseWriter will take care of filling the correct response length + // Setting it now, might impose wrong value, contradicting the actual new + // body the user returned. + // We keep the original body to remove the header only if things changed. + // This will prevent problems with HEAD requests where there's no body, yet, + // the Content-Length header should be set. + if origBody != resp.Body { + resp.Header.Del("Content-Length") + } + copyHeaders(w.Header(), resp.Header, proxy.KeepDestinationHeaders) + w.WriteHeader(resp.StatusCode) + var copyWriter io.Writer = w + if w.Header().Get("content-type") == "text/event-stream" { + // server-side events, flush the buffered data to the client. + copyWriter = &flushWriter{w: w} + } + + nr, err := io.Copy(copyWriter, resp.Body) + if err := resp.Body.Close(); err != nil { + ctx.Warnf("Can't close response body %v", err) + } + ctx.Logf("Copied %v bytes to client error=%v", nr, err) + } +} + +// NewProxyHttpServer creates and returns a proxy server, logging to stderr by default +func NewProxyHttpServer() *ProxyHttpServer { + proxy := ProxyHttpServer{ + Logger: log.New(os.Stderr, "", log.LstdFlags), + reqHandlers: []ReqHandler{}, + respHandlers: []RespHandler{}, + httpsHandlers: []HttpsHandler{}, + NonproxyHandler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + http.Error(w, "This is a proxy server. Does not respond to non-proxy requests.", 500) + }), + Tr: &http.Transport{TLSClientConfig: tlsClientSkipVerify, Proxy: http.ProxyFromEnvironment}, + } + + proxy.ConnectDial = dialerFromEnv(&proxy) + + return &proxy +} diff --git a/vendor/github.com/elazarl/goproxy/responses.go b/vendor/github.com/elazarl/goproxy/responses.go new file mode 100644 index 0000000000..e1bf28fc27 --- /dev/null +++ b/vendor/github.com/elazarl/goproxy/responses.go @@ -0,0 +1,39 @@ +package goproxy + +import ( + "bytes" + "io/ioutil" + "net/http" +) + +// Will generate a valid http response to the given request the response will have +// the given contentType, and http status. +// Typical usage, refuse to process requests to local addresses: +// +// proxy.OnRequest(IsLocalHost()).DoFunc(func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request,*http.Response) { +// return nil,NewResponse(r,goproxy.ContentTypeHtml,http.StatusUnauthorized, +// `Can't use proxy for local addresses`) +// }) +func NewResponse(r *http.Request, contentType string, status int, body string) *http.Response { + resp := &http.Response{} + resp.Request = r + resp.TransferEncoding = r.TransferEncoding + resp.Header = make(http.Header) + resp.Header.Add("Content-Type", contentType) + resp.StatusCode = status + resp.Status = http.StatusText(status) + buf := bytes.NewBufferString(body) + resp.ContentLength = int64(buf.Len()) + resp.Body = ioutil.NopCloser(buf) + return resp +} + +const ( + ContentTypeText = "text/plain" + ContentTypeHtml = "text/html" +) + +// Alias for NewResponse(r,ContentTypeText,http.StatusAccepted,text) +func TextResponse(r *http.Request, text string) *http.Response { + return NewResponse(r, ContentTypeText, http.StatusAccepted, text) +} diff --git a/vendor/github.com/elazarl/goproxy/signer.go b/vendor/github.com/elazarl/goproxy/signer.go new file mode 100644 index 0000000000..aa511ca9f2 --- /dev/null +++ b/vendor/github.com/elazarl/goproxy/signer.go @@ -0,0 +1,108 @@ +package goproxy + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rsa" + "crypto/sha1" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "fmt" + "math/big" + "math/rand" + "net" + "runtime" + "sort" + "time" +) + +func hashSorted(lst []string) []byte { + c := make([]string, len(lst)) + copy(c, lst) + sort.Strings(c) + h := sha1.New() + for _, s := range c { + h.Write([]byte(s + ",")) + } + return h.Sum(nil) +} + +func hashSortedBigInt(lst []string) *big.Int { + rv := new(big.Int) + rv.SetBytes(hashSorted(lst)) + return rv +} + +var goproxySignerVersion = ":goroxy1" + +func signHost(ca tls.Certificate, hosts []string) (cert *tls.Certificate, err error) { + var x509ca *x509.Certificate + + // Use the provided ca and not the global GoproxyCa for certificate generation. + if x509ca, err = x509.ParseCertificate(ca.Certificate[0]); err != nil { + return + } + + start := time.Unix(time.Now().Unix()-2592000, 0) // 2592000 = 30 day + end := time.Unix(time.Now().Unix()+31536000, 0) // 31536000 = 365 day + + serial := big.NewInt(rand.Int63()) + template := x509.Certificate{ + // TODO(elazar): instead of this ugly hack, just encode the certificate and hash the binary form. + SerialNumber: serial, + Issuer: x509ca.Subject, + Subject: pkix.Name{ + Organization: []string{"GoProxy untrusted MITM proxy Inc"}, + }, + NotBefore: start, + NotAfter: end, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + for _, h := range hosts { + if ip := net.ParseIP(h); ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + template.DNSNames = append(template.DNSNames, h) + template.Subject.CommonName = h + } + } + + hash := hashSorted(append(hosts, goproxySignerVersion, ":"+runtime.Version())) + var csprng CounterEncryptorRand + if csprng, err = NewCounterEncryptorRandFromKey(ca.PrivateKey, hash); err != nil { + return + } + + var certpriv crypto.Signer + switch ca.PrivateKey.(type) { + case *rsa.PrivateKey: + if certpriv, err = rsa.GenerateKey(&csprng, 2048); err != nil { + return + } + case *ecdsa.PrivateKey: + if certpriv, err = ecdsa.GenerateKey(elliptic.P256(), &csprng); err != nil { + return + } + default: + err = fmt.Errorf("unsupported key type %T", ca.PrivateKey) + } + + var derBytes []byte + if derBytes, err = x509.CreateCertificate(&csprng, &template, x509ca, certpriv.Public(), ca.PrivateKey); err != nil { + return + } + return &tls.Certificate{ + Certificate: [][]byte{derBytes, ca.Certificate[0]}, + PrivateKey: certpriv, + }, nil +} + +func init() { + // Avoid deterministic random numbers + rand.Seed(time.Now().UnixNano()) +} diff --git a/vendor/github.com/elazarl/goproxy/websocket.go b/vendor/github.com/elazarl/goproxy/websocket.go new file mode 100644 index 0000000000..522b88e328 --- /dev/null +++ b/vendor/github.com/elazarl/goproxy/websocket.go @@ -0,0 +1,121 @@ +package goproxy + +import ( + "bufio" + "crypto/tls" + "io" + "net/http" + "net/url" + "strings" +) + +func headerContains(header http.Header, name string, value string) bool { + for _, v := range header[name] { + for _, s := range strings.Split(v, ",") { + if strings.EqualFold(value, strings.TrimSpace(s)) { + return true + } + } + } + return false +} + +func isWebSocketRequest(r *http.Request) bool { + return headerContains(r.Header, "Connection", "upgrade") && + headerContains(r.Header, "Upgrade", "websocket") +} + +func (proxy *ProxyHttpServer) serveWebsocketTLS(ctx *ProxyCtx, w http.ResponseWriter, req *http.Request, tlsConfig *tls.Config, clientConn *tls.Conn) { + targetURL := url.URL{Scheme: "wss", Host: req.URL.Host, Path: req.URL.Path} + + // Connect to upstream + targetConn, err := tls.Dial("tcp", targetURL.Host, tlsConfig) + if err != nil { + ctx.Warnf("Error dialing target site: %v", err) + return + } + defer targetConn.Close() + + // Perform handshake + if err := proxy.websocketHandshake(ctx, req, targetConn, clientConn); err != nil { + ctx.Warnf("Websocket handshake error: %v", err) + return + } + + // Proxy wss connection + proxy.proxyWebsocket(ctx, targetConn, clientConn) +} + +func (proxy *ProxyHttpServer) serveWebsocket(ctx *ProxyCtx, w http.ResponseWriter, req *http.Request) { + targetURL := url.URL{Scheme: "ws", Host: req.URL.Host, Path: req.URL.Path} + + targetConn, err := proxy.connectDial(ctx, "tcp", targetURL.Host) + if err != nil { + ctx.Warnf("Error dialing target site: %v", err) + return + } + defer targetConn.Close() + + // Connect to Client + hj, ok := w.(http.Hijacker) + if !ok { + panic("httpserver does not support hijacking") + } + clientConn, _, err := hj.Hijack() + if err != nil { + ctx.Warnf("Hijack error: %v", err) + return + } + + // Perform handshake + if err := proxy.websocketHandshake(ctx, req, targetConn, clientConn); err != nil { + ctx.Warnf("Websocket handshake error: %v", err) + return + } + + // Proxy ws connection + proxy.proxyWebsocket(ctx, targetConn, clientConn) +} + +func (proxy *ProxyHttpServer) websocketHandshake(ctx *ProxyCtx, req *http.Request, targetSiteConn io.ReadWriter, clientConn io.ReadWriter) error { + // write handshake request to target + err := req.Write(targetSiteConn) + if err != nil { + ctx.Warnf("Error writing upgrade request: %v", err) + return err + } + + targetTLSReader := bufio.NewReader(targetSiteConn) + + // Read handshake response from target + resp, err := http.ReadResponse(targetTLSReader, req) + if err != nil { + ctx.Warnf("Error reading handhsake response %v", err) + return err + } + + // Run response through handlers + resp = proxy.filterResponse(resp, ctx) + + // Proxy handshake back to client + err = resp.Write(clientConn) + if err != nil { + ctx.Warnf("Error writing handshake response: %v", err) + return err + } + return nil +} + +func (proxy *ProxyHttpServer) proxyWebsocket(ctx *ProxyCtx, dest io.ReadWriter, source io.ReadWriter) { + errChan := make(chan error, 2) + cp := func(dst io.Writer, src io.Reader) { + _, err := io.Copy(dst, src) + ctx.Warnf("Websocket error: %v", err) + errChan <- err + } + + // Start proxying websocket data + go cp(dest, source) + go cp(source, dest) + <-errChan +} diff --git a/vendor/github.com/skycoin/skywire-utilities/pkg/skyenv/values.go b/vendor/github.com/skycoin/skywire-utilities/pkg/skyenv/values.go index f633fb084a..cce318600b 100644 --- a/vendor/github.com/skycoin/skywire-utilities/pkg/skyenv/values.go +++ b/vendor/github.com/skycoin/skywire-utilities/pkg/skyenv/values.go @@ -12,7 +12,7 @@ const ( AddressResolverAddr = "http://ar.skywire.skycoin.com" RouteSetupPKs = "0324579f003e6b4048bae2def4365e634d8e0e3054a20fc7af49daf2a179658557,024fbd3997d4260f731b01abcfce60b8967a6d4c6a11d1008812810ea1437ce438,03b87c282f6e9f70d97aeea90b07cf09864a235ef718725632d067873431dd1015" TPSetupPKs = "03530b786c670fc7f5ab9021478c7ec9cd06a03f3ea1416c50c4a8889ef5bba80e,03271c0de223b80400d9bd4b7722b536a245eb6c9c3176781ee41e7bac8f9bad21,03a792e6d960c88c6fb2184ee4f16714c58b55f0746840617a19f7dd6e021699d9,0313efedc579f57f05d4f5bc3fbf0261f31e51cdcfde7e568169acf92c78868926,025c7bbf23e3441a36d7e8a1e9d717921e2a49a2ce035680fec4808a048d244c8a,030eb6967f6e23e81db0d214f925fc5ce3371e1b059fb8379ae3eb1edfc95e0b46,02e582c0a5e5563aad47f561b272e4c3a9f7ac716258b58e58eb50afd83c286a7f,02ddc6c749d6ed067bb68df19c9bcb1a58b7587464043b1707398ffa26a9746b26,03aa0b1c4e23616872058c11c6efba777c130a85eaf909945d697399a1eb08426d,03adb2c924987d8deef04d02bd95236c5ae172fe5dfe7273e0461d96bf4bc220be" - NetworkMonitorPKs = "0380ea88f0ad0aa4d93c330ba5f97aabca1d892190b94db69eee140b549d2817dd,0283bddb4357e2c4de0d470032cd809966aec65ce57e1188143ab32c7b589b38b6,02f4e33b75307267229b0c3d679d08dd23374333f558288cfcb114311a52199358,02090f03cb26c71779b8327067e2e37314d2db3e31dfe4f8f3cdd8e088a98eb7ec,03ff8dc39ed8d84be17a15b6a243edbcef1a5fd425209243fd7a9a28f0d23ddbea" + NetworkMonitorPKs = "0380ea88f0ad0aa4d93c330ba5f97aabca1d892190b94db69eee140b549d2817dd,0283bddb4357e2c4de0d470032cd809966aec65ce57e1188143ab32c7b589b38b6,02f4e33b75307267229b0c3d679d08dd23374333f558288cfcb114311a52199358,02090f03cb26c71779b8327067e2e37314d2db3e31dfe4f8f3cdd8e088a98eb7ec,03ff8dc39ed8d84be17a15b6a243edbcef1a5fd425209243fd7a9a28f0d23ddbea,02b9aa8276907db6f6ea8626d5d26aa6e119dd89d88bb222ce868376c5367d7b4c" SurveyWhitelistPKs = "0327e2cf1d2e516ecbfdbd616a87489cc92a73af97335d5c8c29eafb5d8882264a,03abbb3eff140cf3dce468b3fa5a28c80fa02c6703d7b952be6faaf2050990ebf4,02b5ee5333aa6b7f5fc623b7d5f35f505cb7f974e98a70751cf41962f84c8c4637,03714c8bdaee0fb48f47babbc47c33e1880752b6620317c9d56b30f3b0ff58a9c3,020d35bbaf0a5abc8ec0ba33cde219fde734c63e7202098e1f9a6cf9daaeee55a9,027f7dec979482f418f01dfabddbd750ad036c579a16422125dd9a313eaa59c8e1,031d4cf1b7ab4c789b56c769f2888e4a61c778dfa5fe7e5cd0217fc41660b2eb65" ) diff --git a/vendor/modules.txt b/vendor/modules.txt index d51e7ad117..6ba716e072 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -113,6 +113,9 @@ github.com/dgryski/go-rendezvous # github.com/dustin/go-humanize v1.0.0 ## explicit github.com/dustin/go-humanize +# github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 +## explicit +github.com/elazarl/goproxy # github.com/fatih/color v1.15.0 ## explicit; go 1.17 github.com/fatih/color @@ -439,7 +442,7 @@ github.com/rs/cors ## explicit; go 1.13 github.com/sirupsen/logrus github.com/sirupsen/logrus/hooks/syslog -# github.com/skycoin/dmsg v1.3.15-0.20240201143600-b1d1d1012348 +# github.com/skycoin/dmsg v1.3.17 ## explicit; go 1.21 github.com/skycoin/dmsg/cmd/dmsg-discovery/commands github.com/skycoin/dmsg/cmd/dmsg-server/commands @@ -520,7 +523,7 @@ github.com/skycoin/skywire-services/pkg/transport-discovery/api github.com/skycoin/skywire-services/pkg/transport-discovery/store github.com/skycoin/skywire-services/pkg/transport-setup/api github.com/skycoin/skywire-services/pkg/transport-setup/config -# github.com/skycoin/skywire-utilities v1.3.14 +# github.com/skycoin/skywire-utilities v1.3.17 ## explicit; go 1.21 github.com/skycoin/skywire-utilities/pkg/buildinfo github.com/skycoin/skywire-utilities/pkg/cipher