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
This commit is contained in:
Peter 2020-04-12 03:51:41 +02:00
parent 63a446d7e5
commit 671958e123
Signed by: prskr
GPG key ID: C1DB5D2E8DB512F9
24 changed files with 442 additions and 203 deletions

View file

@ -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 \

View file

@ -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

1
config.yaml Symbolic link
View file

@ -0,0 +1 @@
mock_config.yaml

View file

@ -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

View file

@ -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)

View file

@ -1,5 +0,0 @@
package main
func main() {
panic("should not every be called")
}

View file

@ -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(

View file

@ -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:

View file

@ -1,5 +0,0 @@
package main
func main() {
panic("should not every be called")
}

View file

@ -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)
})
}

View file

@ -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)
})
}

View file

@ -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
}

View file

@ -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)

View file

@ -1,5 +0,0 @@
package main
func main() {
panic("should not every be called")
}

View file

@ -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: &notFoundFallbackStrategy{},
}
}
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
}

View file

@ -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 => ../../

View file

@ -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=

View file

@ -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(),
}
})
}

View file

@ -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),
)
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)

View file

@ -1,5 +0,0 @@
package main
func main() {
panic("should not every be called")
}

View file

@ -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() {

77
proxy_config.yaml Normal file
View file

@ -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