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:
parent
63a446d7e5
commit
671958e123
24 changed files with 442 additions and 203 deletions
8
Makefile
8
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 \
|
||||
|
|
75
config.yaml
75
config.yaml
|
@ -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
1
config.yaml
Symbolic link
|
@ -0,0 +1 @@
|
|||
mock_config.yaml
|
|
@ -1,9 +1,4 @@
|
|||
endpoints:
|
||||
plainHttp:
|
||||
handler: http_mock
|
||||
listenAddress: 0.0.0.0
|
||||
port: 80
|
||||
options:
|
||||
x-response-rules: &httpResponseRules
|
||||
rules:
|
||||
- pattern: ".*\\.(?i)exe"
|
||||
response: ./assets/fakeFiles/sample.exe
|
||||
|
@ -19,31 +14,8 @@ endpoints:
|
|||
response: ./assets/fakeFiles/default.txt
|
||||
- pattern: ".*"
|
||||
response: ./assets/fakeFiles/default.html
|
||||
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
|
||||
httpsDowngrade:
|
||||
handler: tls_interceptor
|
||||
listenAddress: 0.0.0.0
|
||||
port: 443
|
||||
options:
|
||||
|
||||
x-tls-options: &tlsOptions
|
||||
ecdsaCurve: P256
|
||||
validity:
|
||||
ca:
|
||||
|
@ -56,6 +28,27 @@ endpoints:
|
|||
publicKey: ./ca.pem
|
||||
privateKey: ./ca.key
|
||||
certCachePath: /tmp/inetmock/
|
||||
|
||||
endpoints:
|
||||
plainHttp:
|
||||
handler: http_mock
|
||||
listenAddress: 0.0.0.0
|
||||
port: 80
|
||||
options:
|
||||
<<: *httpResponseRules
|
||||
proxy:
|
||||
handler: http_proxy
|
||||
listenAddress: 0.0.0.0
|
||||
port: 3128
|
||||
options:
|
||||
fallback: notfound
|
||||
<<: *httpResponseRules
|
||||
httpsDowngrade:
|
||||
handler: tls_interceptor
|
||||
listenAddress: 0.0.0.0
|
||||
port: 443
|
||||
options:
|
||||
<<: *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
|
|
@ -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)
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
package main
|
||||
|
||||
func main() {
|
||||
panic("should not every be called")
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
package main
|
||||
|
||||
func main() {
|
||||
panic("should not every be called")
|
||||
}
|
37
plugins/http_mock/http_handler.go
Normal file
37
plugins/http_mock/http_handler.go
Normal 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)
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
package main
|
||||
|
||||
func main() {
|
||||
panic("should not every be called")
|
||||
}
|
53
plugins/http_proxy/fallback.go
Normal file
53
plugins/http_proxy/fallback.go
Normal 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: ¬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
|
||||
}
|
|
@ -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 => ../../
|
||||
|
|
|
@ -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=
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
52
plugins/http_proxy/protocol_options.go
Normal file
52
plugins/http_proxy/protocol_options.go
Normal 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
|
||||
}
|
99
plugins/http_proxy/proxy_handler.go
Normal file
99
plugins/http_proxy/proxy_handler.go
Normal 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
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
package main
|
||||
|
||||
func main() {
|
||||
panic("should not every be called")
|
||||
}
|
|
@ -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
77
proxy_config.yaml
Normal 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
|
Loading…
Reference in a new issue