From 671958e123e69e4f2e25e1dcd1c06e23083a3190 Mon Sep 17 00:00:00 2001 From: Peter Kurfer Date: Sun, 12 Apr 2020 03:51:41 +0200 Subject: [PATCH] Complete first naive HTTP proxy implementation - HTTPS configuration is till missing - fix a few minor things in other plugins - cleanup of config to reduce repeating of the same values multiple times --- Makefile | 8 ++- config.yaml | 76 +------------------- mock_config.yaml | 90 ++++++++++------------- plugins/dns_mock/Makefile | 6 +- plugins/dns_mock/fake_main.go | 5 -- plugins/dns_mock/main.go | 1 + plugins/http_mock/Makefile | 8 ++- plugins/http_mock/fake_main.go | 5 -- plugins/http_mock/http_handler.go | 37 ++++++++++ plugins/http_mock/main.go | 36 +--------- plugins/http_mock/protocol_options.go | 5 +- plugins/http_proxy/Makefile | 6 +- plugins/http_proxy/fake_main.go | 5 -- plugins/http_proxy/fallback.go | 53 ++++++++++++++ plugins/http_proxy/go.mod | 3 + plugins/http_proxy/go.sum | 9 +++ plugins/http_proxy/init.go | 10 +++ plugins/http_proxy/main.go | 42 +++++++++-- plugins/http_proxy/protocol_options.go | 52 ++++++++++++++ plugins/http_proxy/proxy_handler.go | 99 ++++++++++++++++++++++++++ plugins/tls_interceptor/Makefile | 6 +- plugins/tls_interceptor/fake_main.go | 5 -- plugins/tls_interceptor/main.go | 1 + proxy_config.yaml | 77 ++++++++++++++++++++ 24 files changed, 442 insertions(+), 203 deletions(-) mode change 100644 => 120000 config.yaml delete mode 100644 plugins/dns_mock/fake_main.go delete mode 100644 plugins/http_mock/fake_main.go create mode 100644 plugins/http_mock/http_handler.go delete mode 100644 plugins/http_proxy/fake_main.go create mode 100644 plugins/http_proxy/fallback.go create mode 100644 plugins/http_proxy/protocol_options.go create mode 100644 plugins/http_proxy/proxy_handler.go delete mode 100644 plugins/tls_interceptor/fake_main.go create mode 100644 proxy_config.yaml diff --git a/Makefile b/Makefile index b8ec306..66a0630 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ DEBUG_PORT = 2345 DEBUG_ARGS?= --development-logs=true INETMOCK_PLUGINS_DIRECTORY = $(DIR) -.PHONY: clean all format deps compile debug snapshot-release test cli-cover-report html-cover-report plugins $(PLUGINS) +.PHONY: clean all format deps update-deps compile debug snapshot-release test cli-cover-report html-cover-report plugins $(PLUGINS) all: clean format compile test plugins @@ -26,9 +26,11 @@ format: @go fmt $(PKGS) deps: + @go build -v $(BUILD_PATH) + +update-deps: @go mod tidy @go get -u - @go build -v $(BUILD_PATH) compile: deps ifdef DEBUG @@ -44,7 +46,7 @@ endif debug: @export INETMOCK_PLUGINS_DIRECTORY - @dlv exec $(DIR)$(BINARY_NAME) \ + dlv exec $(DIR)$(BINARY_NAME) \ --headless \ --listen=:2345 \ --api-version=2 \ diff --git a/config.yaml b/config.yaml deleted file mode 100644 index 2f94e96..0000000 --- a/config.yaml +++ /dev/null @@ -1,75 +0,0 @@ -endpoints: - plainHttp: - handler: http_mock - listenAddress: 0.0.0.0 - port: 80 - options: - rules: - - pattern: ".*\\.(?i)exe" - response: ./assets/fakeFiles/sample.exe - - pattern: ".*\\.(?i)(jpg|jpeg)" - response: ./assets/fakeFiles/default.jpg - - pattern: ".*\\.(?i)png" - response: ./assets/fakeFiles/default.png - - pattern: ".*\\.(?i)gif" - response: ./assets/fakeFiles/default.gif - - pattern: ".*\\.(?i)ico" - response: ./assets/fakeFiles/default.ico - - pattern: ".*\\.(?i)txt" - response: ./assets/fakeFiles/default.txt - - pattern: ".*" - response: ./assets/fakeFiles/default.html - httpsDowngrade: - handler: tls_interceptor - listenAddress: 0.0.0.0 - port: 443 - options: - ecdsaCurve: P256 - validity: - ca: - notBeforeRelative: 17520h - notAfterRelative: 17520h - domain: - notBeforeRelative: 168h - notAfterRelative: 168h - rootCaCert: - publicKey: ./ca.pem - privateKey: ./ca.key - certCachePath: /tmp/inetmock/ - target: - ipAddress: 127.0.0.1 - port: 80 - plainDns: - handler: dns_mock - listenAddress: 0.0.0.0 - port: 53 - options: - rules: - - pattern: ".*\\.google\\.com" - response: 1.1.1.1 - - pattern: ".*\\.reddit\\.com" - response: 2.2.2.2 - fallback: - strategy: incremental - args: - startIP: 10.0.10.0 - dnsOverTlsDowngrade: - handler: tls_interceptor - listenAddress: 0.0.0.0 - port: 853 - options: - ecdsaCurve: P256 - validity: - ca: - notBeforeRelative: 17520h - notAfterRelative: 17520h - domain: - notBeforeRelative: 168h - notAfterRelative: 168h - rootCaCert: - publicKey: ./ca.pem - privateKey: ./ca.key - certCachePath: /tmp/inetmock/ - target: - ipAddress: 127.0.0.1 - port: 53 \ No newline at end of file diff --git a/config.yaml b/config.yaml new file mode 120000 index 0000000..54eeef2 --- /dev/null +++ b/config.yaml @@ -0,0 +1 @@ +mock_config.yaml \ No newline at end of file diff --git a/mock_config.yaml b/mock_config.yaml index deb5295..450facc 100644 --- a/mock_config.yaml +++ b/mock_config.yaml @@ -1,61 +1,54 @@ +x-response-rules: &httpResponseRules + rules: + - pattern: ".*\\.(?i)exe" + response: ./assets/fakeFiles/sample.exe + - pattern: ".*\\.(?i)(jpg|jpeg)" + response: ./assets/fakeFiles/default.jpg + - pattern: ".*\\.(?i)png" + response: ./assets/fakeFiles/default.png + - pattern: ".*\\.(?i)gif" + response: ./assets/fakeFiles/default.gif + - pattern: ".*\\.(?i)ico" + response: ./assets/fakeFiles/default.ico + - pattern: ".*\\.(?i)txt" + response: ./assets/fakeFiles/default.txt + - pattern: ".*" + response: ./assets/fakeFiles/default.html + +x-tls-options: &tlsOptions + ecdsaCurve: P256 + validity: + ca: + notBeforeRelative: 17520h + notAfterRelative: 17520h + domain: + notBeforeRelative: 168h + notAfterRelative: 168h + rootCaCert: + publicKey: ./ca.pem + privateKey: ./ca.key + certCachePath: /tmp/inetmock/ + endpoints: plainHttp: handler: http_mock listenAddress: 0.0.0.0 port: 80 options: - rules: - - pattern: ".*\\.(?i)exe" - response: ./assets/fakeFiles/sample.exe - - pattern: ".*\\.(?i)(jpg|jpeg)" - response: ./assets/fakeFiles/default.jpg - - pattern: ".*\\.(?i)png" - response: ./assets/fakeFiles/default.png - - pattern: ".*\\.(?i)gif" - response: ./assets/fakeFiles/default.gif - - pattern: ".*\\.(?i)ico" - response: ./assets/fakeFiles/default.ico - - pattern: ".*\\.(?i)txt" - response: ./assets/fakeFiles/default.txt - - pattern: ".*" - response: ./assets/fakeFiles/default.html + <<: *httpResponseRules proxy: handler: http_proxy listenAddress: 0.0.0.0 port: 3128 options: - rules: - - pattern: ".*\\.(?i)exe" - response: ./assets/fakeFiles/sample.exe - - pattern: ".*\\.(?i)(jpg|jpeg)" - response: ./assets/fakeFiles/default.jpg - - pattern: ".*\\.(?i)png" - response: ./assets/fakeFiles/default.png - - pattern: ".*\\.(?i)gif" - response: ./assets/fakeFiles/default.gif - - pattern: ".*\\.(?i)ico" - response: ./assets/fakeFiles/default.ico - - pattern: ".*\\.(?i)txt" - response: ./assets/fakeFiles/default.txt - - pattern: ".*" - response: ./assets/fakeFiles/default.html + fallback: notfound + <<: *httpResponseRules httpsDowngrade: handler: tls_interceptor listenAddress: 0.0.0.0 port: 443 options: - ecdsaCurve: P256 - validity: - ca: - notBeforeRelative: 17520h - notAfterRelative: 17520h - domain: - notBeforeRelative: 168h - notAfterRelative: 168h - rootCaCert: - publicKey: ./ca.pem - privateKey: ./ca.key - certCachePath: /tmp/inetmock/ + <<: *tlsOptions target: ipAddress: 127.0.0.1 port: 80 @@ -78,18 +71,7 @@ endpoints: listenAddress: 0.0.0.0 port: 853 options: - ecdsaCurve: P256 - validity: - ca: - notBeforeRelative: 17520h - notAfterRelative: 17520h - domain: - notBeforeRelative: 168h - notAfterRelative: 168h - rootCaCert: - publicKey: ./ca.pem - privateKey: ./ca.key - certCachePath: /tmp/inetmock/ + <<: *tlsOptions target: ipAddress: 127.0.0.1 port: 53 \ No newline at end of file diff --git a/plugins/dns_mock/Makefile b/plugins/dns_mock/Makefile index 0086189..b9ad011 100644 --- a/plugins/dns_mock/Makefile +++ b/plugins/dns_mock/Makefile @@ -10,14 +10,16 @@ PLUGIN_NAME = $(shell basename $(DIR)).so OUT_DIR = $(DIR)../../ DEBUG_PORT = 2345 -.PHONY: deps format compile test cli-cover-report html-cover-report +.PHONY: deps update-deps format compile test cli-cover-report html-cover-report all: format compile test deps: + @go build -buildmode=plugin -v $(DIR)... + +update-deps: @go mod tidy @go get -u - @go build -buildmode=plugin -v $(DIR)... format: @go fmt $(PKGS) diff --git a/plugins/dns_mock/fake_main.go b/plugins/dns_mock/fake_main.go deleted file mode 100644 index fa0b8bf..0000000 --- a/plugins/dns_mock/fake_main.go +++ /dev/null @@ -1,5 +0,0 @@ -package main - -func main() { - panic("should not every be called") -} diff --git a/plugins/dns_mock/main.go b/plugins/dns_mock/main.go index 7171ee6..6e604e6 100644 --- a/plugins/dns_mock/main.go +++ b/plugins/dns_mock/main.go @@ -63,6 +63,7 @@ func (d *dnsHandler) startServer(dnsServer *dns.Server) { } func (d *dnsHandler) Shutdown(wg *sync.WaitGroup) { + d.logger.Info("shutting down DNS mock") for _, dnsServer := range d.dnsServer { if err := dnsServer.Shutdown(); err != nil { d.logger.Error( diff --git a/plugins/http_mock/Makefile b/plugins/http_mock/Makefile index ea47347..b9ad011 100644 --- a/plugins/http_mock/Makefile +++ b/plugins/http_mock/Makefile @@ -10,14 +10,16 @@ PLUGIN_NAME = $(shell basename $(DIR)).so OUT_DIR = $(DIR)../../ DEBUG_PORT = 2345 -.PHONY: deps format compile test cli-cover-report html-cover-report +.PHONY: deps update-deps format compile test cli-cover-report html-cover-report all: format compile test deps: + @go build -buildmode=plugin -v $(DIR)... + +update-deps: @go mod tidy @go get -u - @go build -buildmode=plugin -v $(DIR)... format: @go fmt $(PKGS) @@ -30,7 +32,7 @@ ifdef DEBUG else ifdef CONTAINER @$(GOARGS) go build $(GO_CONTAINER_BUILD_ARGS) -o $(OUT_DIR)/$(PLUGIN_NAME) $(DIR) else - @$(GOARGS) go build $(GO_BUILD_ARGS) -o $(OUT_DIR)/$(PLUGIN_NAMEs) $(DIR) + @$(GOARGS) go build $(GO_BUILD_ARGS) -o $(OUT_DIR)/$(PLUGIN_NAME) $(DIR) endif test: diff --git a/plugins/http_mock/fake_main.go b/plugins/http_mock/fake_main.go deleted file mode 100644 index fa0b8bf..0000000 --- a/plugins/http_mock/fake_main.go +++ /dev/null @@ -1,5 +0,0 @@ -package main - -func main() { - panic("should not every be called") -} diff --git a/plugins/http_mock/http_handler.go b/plugins/http_mock/http_handler.go new file mode 100644 index 0000000..987c4d7 --- /dev/null +++ b/plugins/http_mock/http_handler.go @@ -0,0 +1,37 @@ +package main + +import ( + "bytes" + "go.uber.org/zap" + "net/http" +) + +func (p *httpHandler) setupRoute(rule targetRule) { + p.logger.Info( + "setup routing", + zap.String("route", rule.Pattern().String()), + zap.String("response", rule.Response()), + ) + + p.router.Handler(rule.Pattern(), createHandlerForTarget(p.logger, rule.response)) +} + +func createHandlerForTarget(logger *zap.Logger, targetPath string) http.Handler { + return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + headerWriter := &bytes.Buffer{} + request.Header.Write(headerWriter) + + logger.Info( + "Handling request", + zap.String("source", request.RemoteAddr), + zap.String("host", request.Host), + zap.String("method", request.Method), + zap.String("protocol", request.Proto), + zap.String("path", request.RequestURI), + zap.String("response", targetPath), + zap.Reflect("headers", request.Header), + ) + + http.ServeFile(writer, request, targetPath) + }) +} diff --git a/plugins/http_mock/main.go b/plugins/http_mock/main.go index 9ef13f9..5a5c1e1 100644 --- a/plugins/http_mock/main.go +++ b/plugins/http_mock/main.go @@ -1,13 +1,10 @@ package main import ( - "bytes" "fmt" "github.com/baez90/inetmock/internal/config" - "github.com/baez90/inetmock/pkg/path" "go.uber.org/zap" "net/http" - "path/filepath" "sync" ) @@ -37,6 +34,7 @@ func (p *httpHandler) Run(config config.HandlerConfig) { } func (p *httpHandler) Shutdown(wg *sync.WaitGroup) { + p.logger.Info("Shutting down HTTP mock") if err := p.server.Close(); err != nil { p.logger.Error( "failed to shutdown HTTP server", @@ -55,35 +53,3 @@ func (p *httpHandler) startServer() { ) } } - -func (p *httpHandler) setupRoute(rule targetRule) { - p.logger.Info( - "setup routing", - zap.String("route", rule.Pattern().String()), - zap.String("response", rule.Response()), - ) - - p.router.Handler(rule.Pattern(), createHandlerForTarget(p.logger, rule.response)) -} - -func createHandlerForTarget(logger *zap.Logger, targetPath string) http.Handler { - return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { - targetFilePath := filepath.Join(path.WorkingDirectory(), targetPath) - - headerWriter := &bytes.Buffer{} - request.Header.Write(headerWriter) - - logger.Info( - "Handling request", - zap.String("source", request.RemoteAddr), - zap.String("host", request.Host), - zap.String("method", request.Method), - zap.String("protocol", request.Proto), - zap.String("path", request.RequestURI), - zap.String("response", targetFilePath), - zap.Reflect("headers", request.Header), - ) - - http.ServeFile(writer, request, targetFilePath) - }) -} diff --git a/plugins/http_mock/protocol_options.go b/plugins/http_mock/protocol_options.go index 0c9f955..f4a5189 100644 --- a/plugins/http_mock/protocol_options.go +++ b/plugins/http_mock/protocol_options.go @@ -28,8 +28,7 @@ type httpOptions struct { Rules []targetRule } -func loadFromConfig(config *viper.Viper) httpOptions { - options := httpOptions{} +func loadFromConfig(config *viper.Viper) (options httpOptions) { anonRules := config.Get(rulesConfigKey).([]interface{}) for _, i := range anonRules { @@ -45,5 +44,5 @@ func loadFromConfig(config *viper.Viper) httpOptions { } } - return options + return } diff --git a/plugins/http_proxy/Makefile b/plugins/http_proxy/Makefile index 0086189..b9ad011 100644 --- a/plugins/http_proxy/Makefile +++ b/plugins/http_proxy/Makefile @@ -10,14 +10,16 @@ PLUGIN_NAME = $(shell basename $(DIR)).so OUT_DIR = $(DIR)../../ DEBUG_PORT = 2345 -.PHONY: deps format compile test cli-cover-report html-cover-report +.PHONY: deps update-deps format compile test cli-cover-report html-cover-report all: format compile test deps: + @go build -buildmode=plugin -v $(DIR)... + +update-deps: @go mod tidy @go get -u - @go build -buildmode=plugin -v $(DIR)... format: @go fmt $(PKGS) diff --git a/plugins/http_proxy/fake_main.go b/plugins/http_proxy/fake_main.go deleted file mode 100644 index fa0b8bf..0000000 --- a/plugins/http_proxy/fake_main.go +++ /dev/null @@ -1,5 +0,0 @@ -package main - -func main() { - panic("should not every be called") -} diff --git a/plugins/http_proxy/fallback.go b/plugins/http_proxy/fallback.go new file mode 100644 index 0000000..1a72564 --- /dev/null +++ b/plugins/http_proxy/fallback.go @@ -0,0 +1,53 @@ +package main + +import ( + "gopkg.in/elazarl/goproxy.v1" + "net/http" +) + +const ( + passthroughStrategyName = "passthrough" + notFoundStrategyName = "notfound" +) + +var ( + fallbackStrategies map[string]ProxyFallbackStrategy +) + +func init() { + fallbackStrategies = map[string]ProxyFallbackStrategy{ + passthroughStrategyName: &passthroughFallbackStrategy{}, + notFoundStrategyName: ¬FoundFallbackStrategy{}, + } +} + +func StrategyForName(name string) ProxyFallbackStrategy { + if strategy, ok := fallbackStrategies[name]; ok { + return strategy + } + return fallbackStrategies[notFoundStrategyName] +} + +type ProxyFallbackStrategy interface { + Apply(request *http.Request) (*http.Response, error) +} + +type passthroughFallbackStrategy struct { +} + +func (p passthroughFallbackStrategy) Apply(request *http.Request) (*http.Response, error) { + return nil, nil +} + +type notFoundFallbackStrategy struct { +} + +func (n notFoundFallbackStrategy) Apply(request *http.Request) (response *http.Response, err error) { + response = goproxy.NewResponse( + request, + goproxy.ContentTypeText, + http.StatusNotFound, + "The requested resource was not found", + ) + return +} diff --git a/plugins/http_proxy/go.mod b/plugins/http_proxy/go.mod index dc5e230..c592bcb 100644 --- a/plugins/http_proxy/go.mod +++ b/plugins/http_proxy/go.mod @@ -4,7 +4,10 @@ go 1.14 require ( github.com/baez90/inetmock v0.0.1 + github.com/elazarl/goproxy v0.0.0-20200315184450-1f3cb6622dad + github.com/spf13/viper v1.6.3 go.uber.org/zap v1.14.1 + gopkg.in/elazarl/goproxy.v1 v1.0.0-20180725130230-947c36da3153 ) replace github.com/baez90/inetmock v0.0.1 => ../../ diff --git a/plugins/http_proxy/go.sum b/plugins/http_proxy/go.sum index 5b68430..6397fa2 100644 --- a/plugins/http_proxy/go.sum +++ b/plugins/http_proxy/go.sum @@ -21,6 +21,10 @@ 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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/elazarl/goproxy v0.0.0-20200315184450-1f3cb6622dad h1:zPs0fNF2Io1Qytf92EI2CDJ9oCXZr+NmjEVexrUEdq4= +github.com/elazarl/goproxy v0.0.0-20200315184450-1f3cb6622dad/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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -48,6 +52,7 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -94,6 +99,7 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +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/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -110,6 +116,7 @@ github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= @@ -199,6 +206,8 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks 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/elazarl/goproxy.v1 v1.0.0-20180725130230-947c36da3153 h1:i2sumy6EgvN2dbX7HPhoDc7hLyoym3OYdU5HlvUUrpE= +gopkg.in/elazarl/goproxy.v1 v1.0.0-20180725130230-947c36da3153/go.mod h1:xzjpkyedLMz3EXUTBbkRuuGPsxfsBX3Sy7J6kC9Gvoc= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ= diff --git a/plugins/http_proxy/init.go b/plugins/http_proxy/init.go index 22ef15e..ea0d660 100644 --- a/plugins/http_proxy/init.go +++ b/plugins/http_proxy/init.go @@ -1,8 +1,11 @@ package main import ( + "github.com/baez90/inetmock/internal/plugins" + "github.com/baez90/inetmock/pkg/api" "github.com/baez90/inetmock/pkg/logging" "go.uber.org/zap" + "gopkg.in/elazarl/goproxy.v1" ) func init() { @@ -10,4 +13,11 @@ func init() { logger = logger.With( zap.String("ProtocolHandler", name), ) + + plugins.Registry().RegisterHandler(name, func() api.ProtocolHandler { + return &httpProxy{ + logger: logger, + proxy: goproxy.NewProxyHttpServer(), + } + }) } diff --git a/plugins/http_proxy/main.go b/plugins/http_proxy/main.go index 9b644a0..78bbab3 100644 --- a/plugins/http_proxy/main.go +++ b/plugins/http_proxy/main.go @@ -1,8 +1,11 @@ package main import ( + "fmt" "github.com/baez90/inetmock/internal/config" "go.uber.org/zap" + "gopkg.in/elazarl/goproxy.v1" + "net/http" "sync" ) @@ -12,12 +15,43 @@ const ( type httpProxy struct { logger *zap.Logger + proxy *goproxy.ProxyHttpServer + server *http.Server } -func (h httpProxy) Run(config config.HandlerConfig) { - panic("implement me") +func (h *httpProxy) Run(config config.HandlerConfig) { + options := loadFromConfig(config.Options()) + addr := fmt.Sprintf("%s:%d", config.ListenAddress(), config.Port()) + h.server = &http.Server{Addr: addr, Handler: h.proxy} + h.logger = h.logger.With( + zap.String("address", addr), + ) + + proxyHandler := &proxyHttpHandler{ + options: options, + logger: h.logger, + } + h.proxy.OnRequest().Do(proxyHandler) + h.proxy.OnRequest().HandleConnect() + go h.startProxy() } -func (h httpProxy) Shutdown(wg *sync.WaitGroup) { - panic("implement me") +func (h *httpProxy) startProxy() { + if err := h.server.ListenAndServe(); err != nil { + h.logger.Error( + "failed to start proxy server", + zap.Error(err), + ) + } +} + +func (h *httpProxy) Shutdown(wg *sync.WaitGroup) { + defer wg.Done() + h.logger.Info("Shutting down HTTP proxy") + if err := h.server.Close(); err != nil { + h.logger.Error( + "failed to shutdown proxy endpoint", + zap.Error(err), + ) + } } diff --git a/plugins/http_proxy/protocol_options.go b/plugins/http_proxy/protocol_options.go new file mode 100644 index 0000000..751c1ba --- /dev/null +++ b/plugins/http_proxy/protocol_options.go @@ -0,0 +1,52 @@ +package main + +import ( + "github.com/spf13/viper" + "regexp" +) + +const ( + rulesConfigKey = "rules" + patternConfigKey = "pattern" + responseConfigKey = "response" + fallbackStrategyConfigKey = "fallback" +) + +type targetRule struct { + pattern *regexp.Regexp + response string +} + +func (tr targetRule) Pattern() *regexp.Regexp { + return tr.pattern +} + +func (tr targetRule) Response() string { + return tr.response +} + +type httpProxyOptions struct { + Rules []targetRule + FallbackStrategy ProxyFallbackStrategy +} + +func loadFromConfig(config *viper.Viper) (options httpProxyOptions) { + options.FallbackStrategy = StrategyForName(config.GetString(fallbackStrategyConfigKey)) + + anonRules := config.Get(rulesConfigKey).([]interface{}) + + for _, i := range anonRules { + innerData := i.(map[interface{}]interface{}) + + if rulePattern, err := regexp.Compile(innerData[patternConfigKey].(string)); err == nil { + options.Rules = append(options.Rules, targetRule{ + pattern: rulePattern, + response: innerData[responseConfigKey].(string), + }) + } else { + panic(err) + } + } + + return +} diff --git a/plugins/http_proxy/proxy_handler.go b/plugins/http_proxy/proxy_handler.go new file mode 100644 index 0000000..b0a3798 --- /dev/null +++ b/plugins/http_proxy/proxy_handler.go @@ -0,0 +1,99 @@ +package main + +import ( + "go.uber.org/zap" + "gopkg.in/elazarl/goproxy.v1" + "io" + "mime" + "net/http" + "os" + "path/filepath" +) + +type proxyHttpHandler struct { + options httpProxyOptions + logger *zap.Logger +} + +/* +TODO implement HTTPS proxy like in TLS interceptor +func (p *proxyHttpHandler) HandleConnect(req string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) { + return &goproxy.ConnectAction{ + Action: goproxy.OkConnect, + + }, "" +}*/ + +func (p *proxyHttpHandler) Handle(req *http.Request, _ *goproxy.ProxyCtx) (retReq *http.Request, resp *http.Response) { + + retReq = req + resp = &http.Response{ + Request: req, + TransferEncoding: req.TransferEncoding, + Header: make(http.Header), + StatusCode: http.StatusOK, + } + + p.logger.Info( + "Handling request", + zap.String("source", req.RemoteAddr), + zap.String("host", req.Host), + zap.String("method", req.Method), + zap.String("protocol", req.Proto), + zap.String("path", req.RequestURI), + zap.Reflect("headers", req.Header), + ) + + for _, rule := range p.options.Rules { + if rule.pattern.MatchString(req.URL.Path) { + if file, err := os.Open(rule.response); err != nil { + p.logger.Error( + "failed to open response target file", + zap.String("resonse", rule.response), + zap.Error(err), + ) + continue + } else { + resp.Body = file + + if stat, err := file.Stat(); err == nil { + resp.ContentLength = stat.Size() + } + + if contentType, err := GetContentType(rule, file); err == nil { + resp.Header["Content-Type"] = []string{contentType} + } + + p.logger.Info("returning fake response from rules") + return req, resp + } + } + } + + if resp, err := p.options.FallbackStrategy.Apply(req); err != nil { + p.logger.Error( + "failed to apply fallback strategy", + zap.Error(err), + ) + } else { + p.logger.Info("returning fake response from fallback strategy") + return req, resp + } + + p.logger.Info("falling back to proxying request through") + return req, nil +} + +func GetContentType(rule targetRule, file *os.File) (contentType string, err error) { + if contentType = mime.TypeByExtension(filepath.Ext(rule.response)); contentType != "" { + return + } + + var buf [512]byte + + n, _ := io.ReadFull(file, buf[:]) + + contentType = http.DetectContentType(buf[:n]) + _, err = file.Seek(0, io.SeekStart) + return +} diff --git a/plugins/tls_interceptor/Makefile b/plugins/tls_interceptor/Makefile index 0086189..b9ad011 100644 --- a/plugins/tls_interceptor/Makefile +++ b/plugins/tls_interceptor/Makefile @@ -10,14 +10,16 @@ PLUGIN_NAME = $(shell basename $(DIR)).so OUT_DIR = $(DIR)../../ DEBUG_PORT = 2345 -.PHONY: deps format compile test cli-cover-report html-cover-report +.PHONY: deps update-deps format compile test cli-cover-report html-cover-report all: format compile test deps: + @go build -buildmode=plugin -v $(DIR)... + +update-deps: @go mod tidy @go get -u - @go build -buildmode=plugin -v $(DIR)... format: @go fmt $(PKGS) diff --git a/plugins/tls_interceptor/fake_main.go b/plugins/tls_interceptor/fake_main.go deleted file mode 100644 index fa0b8bf..0000000 --- a/plugins/tls_interceptor/fake_main.go +++ /dev/null @@ -1,5 +0,0 @@ -package main - -func main() { - panic("should not every be called") -} diff --git a/plugins/tls_interceptor/main.go b/plugins/tls_interceptor/main.go index 0ec98d9..3d6cf40 100644 --- a/plugins/tls_interceptor/main.go +++ b/plugins/tls_interceptor/main.go @@ -68,6 +68,7 @@ func (t *tlsInterceptor) Run(config config.HandlerConfig) { } func (t *tlsInterceptor) Shutdown(wg *sync.WaitGroup) { + t.logger.Info("Shutting down TLS interceptor") t.shutdownRequested = true done := make(chan struct{}) go func() { diff --git a/proxy_config.yaml b/proxy_config.yaml new file mode 100644 index 0000000..7c3b15d --- /dev/null +++ b/proxy_config.yaml @@ -0,0 +1,77 @@ +endpoints: + plainHttp: + handler: http_proxy + listenAddress: 0.0.0.0 + port: 80 + options: + rules: + - pattern: ".*\\.(?i)exe" + response: ./assets/fakeFiles/sample.exe + - pattern: ".*\\.(?i)(jpg|jpeg)" + response: ./assets/fakeFiles/default.jpg + - pattern: ".*\\.(?i)png" + response: ./assets/fakeFiles/default.png + - pattern: ".*\\.(?i)gif" + response: ./assets/fakeFiles/default.gif + - pattern: ".*\\.(?i)ico" + response: ./assets/fakeFiles/default.ico + - pattern: ".*\\.(?i)txt" + response: ./assets/fakeFiles/default.txt + - pattern: ".*" + response: ./assets/fakeFiles/default.html + httpsDowngrade: + handler: tls_interceptor + listenAddress: 0.0.0.0 + port: 443 + options: + ecdsaCurve: P256 + validity: + ca: + notBeforeRelative: 17520h + notAfterRelative: 17520h + domain: + notBeforeRelative: 168h + notAfterRelative: 168h + rootCaCert: + publicKey: ./ca.pem + privateKey: ./ca.key + certCachePath: /tmp/inetmock/ + target: + ipAddress: 127.0.0.1 + port: 80 + plainDns: + handler: dns_mock + listenAddress: 0.0.0.0 + port: 53 + options: + rules: + - pattern: "www.golem.de" + response: 77.247.84.129 + - pattern: ".*\\.google\\.com" + response: 1.1.1.1 + - pattern: ".*\\.reddit\\.com" + response: 2.2.2.2 + fallback: + strategy: incremental + args: + startIP: 10.0.0.0 + dnsOverTlsDowngrade: + handler: tls_interceptor + listenAddress: 0.0.0.0 + port: 853 + options: + ecdsaCurve: P256 + validity: + ca: + notBeforeRelative: 17520h + notAfterRelative: 17520h + domain: + notBeforeRelative: 168h + notAfterRelative: 168h + rootCaCert: + publicKey: ./ca.pem + privateKey: ./ca.key + certCachePath: /tmp/inetmock/ + target: + ipAddress: 127.0.0.1 + port: 53 \ No newline at end of file