Merge pull request #3 from baez90/feature/add-http_proxy

http_proxy
This commit is contained in:
Peter 2020-04-26 17:58:11 +02:00 committed by GitHub
commit 127d40e9a6
98 changed files with 4055 additions and 1366 deletions

View file

@ -3,16 +3,22 @@
###############
.git/
plugins/
.idea/
.vscode/
deploy/
dist/
doc/
#########
# Files #
#########
*.so
*.out
main
inetmock
README.md
LICENSE
.dockerignore
.gitignore
Dockerfile
Dockerfile

3
.gitignore vendored
View file

@ -15,4 +15,5 @@ main
###############
.idea/
dist/
dist/
out/

View file

@ -22,7 +22,7 @@ archives:
wrap_in_directory: true
files:
- config.yaml
- plugins/*.so
- "*.so"
checksum:
name_template: 'checksums.txt'
snapshot:

View file

@ -38,8 +38,8 @@ WORKDIR /app
COPY --from=build /etc/passwd /etc/group /etc/
COPY --from=build --chown=$USER /work/inetmock ./
COPY --from=build --chown=$USER /work/plugins/ ./plugins/
COPY --from=build --chown=$USER /work/*.so ./plugins/
USER $USER:$USER
ENTRYPOINT ["/app/inetmock"]
ENTRYPOINT ["/app/inetmock"]

View file

@ -2,33 +2,36 @@ VERSION = $(shell git describe --dirty --tags --always)
DIR = $(dir $(realpath $(firstword $(MAKEFILE_LIST))))
BUILD_PATH = $(DIR)/main.go
PKGS = $(shell go list ./...)
TEST_PKGS = $(shell find . -type f -name "*_test.go" -not -path "./pkg/plugins/*" -printf '%h\n' | sort -u)
TEST_PKGS = $(shell find . -type f -name "*_test.go" -not -path "./plugins/*" -not -path "*/mock/*" -printf '%h\n' | sort -u)
GOARGS = GOOS=linux GOARCH=amd64
GO_BUILD_ARGS = -ldflags="-w -s"
GO_CONTAINER_BUILD_ARGS = -ldflags="-w -s" -a -installsuffix cgo
GO_DEBUG_BUILD_ARGS = -gcflags "all=-N -l"
BINARY_NAME = inetmock
PLUGINS = $(wildcard $(DIR)pkg/plugins/*/.)
PLUGINS = $(wildcard $(DIR)plugins/*/.)
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)
CONTAINER_BUILDER ?= podman
DOCKER_IMAGE ?= inetmock
.PHONY: clean all format deps update-deps compile debug generate snapshot-release test cli-cover-report html-cover-report plugins $(PLUGINS) $(GO_GEN_FILES)
all: clean format compile test plugins
clean:
@find $(DIR) -type f \( -name "*.out" -or -name "*.so" \) -exec rm -f {} \;
@rm -rf $(DIR)plugins
@rm -rf $(DIR)*.so
@rm -f $(DIR)$(BINARY_NAME) $(DIR)main
format:
@go fmt $(PKGS)
deps:
@go mod tidy
@go build -v $(BUILD_PATH)
update-deps:
@go mod tidy
@go get -u
compile: deps
ifdef DEBUG
@echo 'Compiling for debugging...'
@ -41,28 +44,33 @@ else
@$(GOARGS) go build $(GO_BUILD_ARGS) -o $(DIR)$(BINARY_NAME) $(BUILD_PATH)
endif
debug: export INETMOCK_PLUGINS_DIRECTORY = $(DIR)
debug:
@export INETMOCK_PLUGINS_DIRECTORY
@dlv exec $(DIR)$(BINARY_NAME) \
dlv debug $(DIR) \
--headless \
--listen=:2345 \
--api-version=2 \
--accept-multiclient \
-- $(DEBUG_ARGS)
generate:
@go generate ./...
snapshot-release:
@goreleaser release --snapshot --skip-publish --rm-dist
container:
@$(CONTAINER_BUILDER) build -t $(DOCKER_IMAGE):latest -f $(DIR)Dockerfile $(DIR)
test:
@go test -coverprofile=./cov-raw.out -v $(TEST_PKGS)
@cat ./cov-raw.out | grep -v "generated" > ./cov.out
cli-cover-report:
cli-cover-report: test
@go tool cover -func=cov.out
html-cover-report:
html-cover-report: test
@go tool cover -html=cov.out -o .coverage.html
plugins: $(PLUGINS)
$(PLUGINS):
$(MAKE) -C $@
$(MAKE) -C $@

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

1
config.yaml Symbolic link
View file

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

2
deploy/inetmock.default Normal file
View file

@ -0,0 +1,2 @@
INETMOCK_PLUGINS_DIRECTORY=/usr/lib/inetmock/plugins
OPTIONS="--config=/etc/inetmock/config.yaml"

15
deploy/inetmock.service Normal file
View file

@ -0,0 +1,15 @@
[Unit]
Description=INetMock is a simple service to simulate a valid internet connection
[Service]
Type=simple
User=inetmock
AmbientCapabilities=CAP_NET_BIND_SERVICE
MemoryMax=50M
CPUQuota=20%
EnvironmentFile=/etc/default/inetmock
ExecStart=/usr/bin/inetmock $OPTIONS
WorkingDirectory=/var/lib/inetmock
[Install]
WantedBy=multi-user.target

18
go.mod
View file

@ -3,11 +3,19 @@ module github.com/baez90/inetmock
go 1.13
require (
github.com/spf13/cobra v0.0.6
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/golang/mock v1.4.3
github.com/mitchellh/mapstructure v1.2.2 // indirect
github.com/pelletier/go-toml v1.7.0 // indirect
github.com/spf13/afero v1.2.2 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/cobra v1.0.0
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.4.0
go.uber.org/zap v1.14.1
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe // indirect
github.com/spf13/viper v1.6.3
go.uber.org/zap v1.15.0
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f // indirect
golang.org/x/text v0.3.2 // indirect
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
gopkg.in/ini.v1 v1.55.0 // indirect
)

78
go.sum
View file

@ -11,6 +11,7 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
@ -20,8 +21,9 @@ 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/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
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=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
@ -31,12 +33,18 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
@ -46,10 +54,12 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
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=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@ -57,16 +67,22 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4=
github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -85,30 +101,38 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
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=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.6 h1:breEStsVwemnKh2/s6gMvSdMEkwW0sK8vGStnlVBMCs=
github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
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/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs=
github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
@ -123,13 +147,11 @@ go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKY
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo=
go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
@ -148,27 +170,29 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8=
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@ -183,12 +207,18 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ=
gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

144
internal/cmd/ca.go Normal file
View file

@ -0,0 +1,144 @@
package cmd
import (
"crypto/tls"
"crypto/x509"
"github.com/baez90/inetmock/pkg/cert"
"github.com/baez90/inetmock/pkg/logging"
"github.com/spf13/cobra"
"go.uber.org/zap"
"time"
)
const (
generateCACommonName = "cn"
generateCaOrganizationName = "o"
generateCaOrganizationalUnitName = "ou"
generateCaCountryName = "c"
generateCaLocalityName = "l"
generateCaStateName = "st"
generateCaStreetAddressName = "street-address"
generateCaPostalCodeName = "postal-code"
generateCACertOutPath = "out-dir"
generateCACurveName = "curve"
generateCANotBeforeRelative = "not-before"
generateCANotAfterRelative = "not-after"
)
var (
generateCaCmd *cobra.Command
caCertOptions cert.GenerationOptions
)
func init() {
generateCaCmd = &cobra.Command{
Use: "generate-ca",
Short: "Generate a new CA certificate and corresponding key",
Long: ``,
Run: runGenerateCA,
}
generateCaCmd.Flags().StringVar(&caCertOptions.CommonName, generateCACommonName, "INetMock", "Certificate Common Name that will also be used as file name during generation.")
generateCaCmd.Flags().StringSliceVar(&caCertOptions.Organization, generateCaOrganizationName, nil, "Organization information to append to certificate")
generateCaCmd.Flags().StringSliceVar(&caCertOptions.OrganizationalUnit, generateCaOrganizationalUnitName, nil, "Organizational unit information to append to certificate")
generateCaCmd.Flags().StringSliceVar(&caCertOptions.Country, generateCaCountryName, nil, "Country information to append to certificate")
generateCaCmd.Flags().StringSliceVar(&caCertOptions.Province, generateCaStateName, nil, "State information to append to certificate")
generateCaCmd.Flags().StringSliceVar(&caCertOptions.Locality, generateCaLocalityName, nil, "Locality information to append to certificate")
generateCaCmd.Flags().StringSliceVar(&caCertOptions.StreetAddress, generateCaStreetAddressName, nil, "Street address information to append to certificate")
generateCaCmd.Flags().StringSliceVar(&caCertOptions.PostalCode, generateCaPostalCodeName, nil, "Postal code information to append to certificate")
generateCaCmd.Flags().String(generateCACertOutPath, "", "Path where CA files should be stored")
generateCaCmd.Flags().String(generateCACurveName, "", "Name of the curve to use, if empty ED25519 is used, other valid values are [P224, P256,P384,P521]")
generateCaCmd.Flags().Duration(generateCANotBeforeRelative, 17520*time.Hour, "Relative time value since when in the past the CA certificate should be valid. The value has a time unit, the greatest time unit is h for hour.")
generateCaCmd.Flags().Duration(generateCANotAfterRelative, 17520*time.Hour, "Relative time value until when in the future the CA certificate should be valid. The value has a time unit, the greatest time unit is h for hour.")
}
func runGenerateCA(cmd *cobra.Command, args []string) {
var certOutPath, curveName string
var notBefore, notAfter time.Duration
var err error
if certOutPath, err = getStringFlag(generateCaCmd, generateCACertOutPath, logger); err != nil {
return
}
if curveName, err = getStringFlag(generateCaCmd, generateCACurveName, logger); err != nil {
return
}
if notBefore, err = getDurationFlag(generateCaCmd, generateCANotBeforeRelative, logger); err != nil {
return
}
if notAfter, err = getDurationFlag(generateCaCmd, generateCANotAfterRelative, logger); err != nil {
return
}
logger, _ := logging.CreateLogger()
logger = logger.With(
zap.String(generateCACurveName, curveName),
zap.String(generateCACertOutPath, certOutPath),
)
generator := cert.NewDefaultGenerator(cert.Options{
CertCachePath: certOutPath,
Curve: cert.CurveType(curveName),
Validity: cert.ValidityByPurpose{
CA: cert.ValidityDuration{
NotAfterRelative: notAfter,
NotBeforeRelative: notBefore,
},
},
})
var caCrt *tls.Certificate
if caCrt, err = generator.CACert(caCertOptions); err != nil {
logger.Error(
"failed to generate CA certificate",
zap.Error(err),
)
return
}
if len(caCrt.Certificate) < 1 {
logger.Error("no public key given for generated CA certificate")
return
}
var pubKey *x509.Certificate
if pubKey, err = x509.ParseCertificate(caCrt.Certificate[0]); err != nil {
logger.Error(
"failed to parse public key from generated CA",
zap.Error(err),
)
return
}
pemCrt := cert.NewPEM(caCrt)
if err = pemCrt.Write(pubKey.Subject.CommonName, certOutPath); err != nil {
logger.Error(
"failed to write Ca files",
zap.Error(err),
)
}
logger.Info("completed certificate generation")
}
func getDurationFlag(cmd *cobra.Command, flagName string, logger logging.Logger) (val time.Duration, err error) {
if val, err = cmd.Flags().GetDuration(flagName); err != nil {
logger.Error(
"failed to parse parse flag",
zap.String("flag", flagName),
zap.Error(err),
)
}
return
}
func getStringFlag(cmd *cobra.Command, flagName string, logger logging.Logger) (val string, err error) {
if val, err = cmd.Flags().GetString(flagName); err != nil {
logger.Error(
"failed to parse parse flag",
zap.String("flag", flagName),
zap.Error(err),
)
}
return
}

View file

@ -1,11 +1,15 @@
package cmd
import (
"github.com/baez90/inetmock/internal/endpoints"
"github.com/baez90/inetmock/internal/plugins"
"github.com/baez90/inetmock/pkg/api"
"github.com/baez90/inetmock/pkg/logging"
"github.com/baez90/inetmock/pkg/path"
"github.com/spf13/viper"
"go.uber.org/zap"
"os"
"time"
)
var (
@ -24,6 +28,11 @@ func initApp() (err error) {
)
logger, _ = logging.CreateLogger()
registry := plugins.Registry()
endpointManager = endpoints.NewEndpointManager(logger)
rootCmd.Flags().ParseErrorsWhitelist.UnknownFlags = false
_ = rootCmd.ParseFlags(os.Args)
if err = appConfig.ReadConfig(configFilePath); err != nil {
logger.Error(
"unrecoverable error occurred during reading the config file",
@ -34,12 +43,27 @@ func initApp() (err error) {
viperInst := viper.GetViper()
pluginDir := viperInst.GetString("plugins-directory")
if err = registry.LoadPlugins(pluginDir); err != nil {
logger.Error("Failed to load plugins",
if err = api.InitServices(viperInst, logger); err != nil {
logger.Error(
"failed to initialize app services",
zap.Error(err),
)
}
pluginLoadStartTime := time.Now()
if err = registry.LoadPlugins(pluginDir); err != nil {
logger.Error("Failed to load plugins",
zap.String("pluginsDirectory", pluginDir),
zap.Error(err),
)
}
pluginLoadDuration := time.Since(pluginLoadStartTime)
logger.Info(
"loading plugins completed",
zap.Duration("pluginLoadDuration", pluginLoadDuration),
)
pluginsCmd.AddCommand(registry.PluginCommands()...)
return

View file

@ -18,5 +18,5 @@ The easiest way to explore what commands are available is to start with 'inetmoc
This help page contains a list of available sub-commands starting with the name of the plugin as a prefix.
`,
}
rootCmd.AddCommand(pluginsCmd)
rootCmd.AddCommand(pluginsCmd, generateCaCmd)
}

View file

@ -2,20 +2,20 @@ package cmd
import (
"github.com/baez90/inetmock/internal/config"
"github.com/baez90/inetmock/internal/plugins"
"github.com/baez90/inetmock/internal/endpoints"
"github.com/baez90/inetmock/pkg/api"
"github.com/baez90/inetmock/pkg/logging"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.uber.org/zap"
"os"
"os/signal"
"strings"
"sync"
"syscall"
)
var (
logger *zap.Logger
logger logging.Logger
rootCmd = cobra.Command{
Use: "",
Short: "INetMock is lightweight internet mock",
@ -27,6 +27,7 @@ var (
developmentLogs bool
handlers []api.ProtocolHandler
appConfig = config.CreateConfig()
endpointManager endpoints.EndpointManager
)
func init() {
@ -39,28 +40,21 @@ func init() {
}
func startInetMock(cmd *cobra.Command, args []string) {
registry := plugins.Registry()
var wg sync.WaitGroup
//todo introduce endpoint type and move startup and shutdown to this type
for key, val := range viper.GetStringMap(config.EndpointsKey) {
handlerSubConfig := viper.Sub(strings.Join([]string{config.EndpointsKey, key, config.OptionsKey}, "."))
pluginConfig := config.CreateHandlerConfig(val, handlerSubConfig)
logger.Info(key, zap.Any("value", pluginConfig))
if handler, ok := registry.HandlerForName(pluginConfig.HandlerName()); ok {
handlers = append(handlers, handler)
go startEndpoint(handler, pluginConfig, logger)
wg.Add(1)
} else {
for endpointName := range viper.GetStringMap(config.EndpointsKey) {
handlerSubConfig := viper.Sub(strings.Join([]string{config.EndpointsKey, endpointName}, "."))
handlerConfig := config.CreateMultiHandlerConfig(handlerSubConfig)
if err := endpointManager.CreateEndpoint(endpointName, handlerConfig); err != nil {
logger.Warn(
"no matching handler registered",
zap.String("handler", pluginConfig.HandlerName()),
"error occurred while creating endpoint",
zap.String("endpointName", endpointName),
zap.String("handlerName", handlerConfig.HandlerName()),
zap.Error(err),
)
}
}
endpointManager.StartEndpoints()
signalChannel := make(chan os.Signal, 1)
signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
@ -72,35 +66,7 @@ func startInetMock(cmd *cobra.Command, args []string) {
zap.String("signal", s.String()),
)
for _, handler := range handlers {
go shutdownEndpoint(handler, &wg, logger)
}
wg.Wait()
}
func startEndpoint(handler api.ProtocolHandler, config config.HandlerConfig, logger *zap.Logger) {
defer func() {
if r := recover(); r != nil {
logger.Fatal(
"recovered panic during startup of endpoint",
zap.Any("recovered", r),
)
}
}()
handler.Run(config)
}
func shutdownEndpoint(handler api.ProtocolHandler, wg *sync.WaitGroup, logger *zap.Logger) {
defer func() {
if r := recover(); r != nil {
logger.Fatal(
"recovered panic during shutdown of endpoint",
zap.Any("recovered", r),
)
}
}()
handler.Shutdown(wg)
endpointManager.ShutdownEndpoints()
}
func ExecuteRootCommand() error {

View file

@ -22,7 +22,7 @@ type Config interface {
}
type config struct {
logger *zap.Logger
logger logging.Logger
}
func (c config) InitConfig(flags *pflag.FlagSet) {
@ -39,6 +39,10 @@ func (c config) InitConfig(flags *pflag.FlagSet) {
func (c *config) ReadConfig(configFilePath string) (err error) {
if configFilePath != "" && path.FileExists(configFilePath) {
c.logger.Info(
"loading config from passed config file path",
zap.String("configFilePath", configFilePath),
)
viper.SetConfigFile(configFilePath)
}
if err = viper.ReadInConfig(); err != nil {

View file

@ -0,0 +1,49 @@
package config
import (
"github.com/baez90/inetmock/pkg/api"
"github.com/spf13/viper"
)
type MultiHandlerConfig interface {
HandlerName() string
ListenAddress() string
Ports() []uint16
Options() *viper.Viper
HandlerConfigs() []api.HandlerConfig
}
type multiHandlerConfig struct {
handlerName string
ports []uint16
listenAddress string
options *viper.Viper
}
func NewMultiHandlerConfig(handlerName string, ports []uint16, listenAddress string, options *viper.Viper) MultiHandlerConfig {
return &multiHandlerConfig{handlerName: handlerName, ports: ports, listenAddress: listenAddress, options: options}
}
func (m multiHandlerConfig) HandlerName() string {
return m.handlerName
}
func (m multiHandlerConfig) ListenAddress() string {
return m.listenAddress
}
func (m multiHandlerConfig) Ports() []uint16 {
return m.ports
}
func (m multiHandlerConfig) Options() *viper.Viper {
return m.options
}
func (m multiHandlerConfig) HandlerConfigs() []api.HandlerConfig {
configs := make([]api.HandlerConfig, 0)
for _, port := range m.ports {
configs = append(configs, api.NewHandlerConfig(m.handlerName, port, m.listenAddress, m.options))
}
return configs
}

View file

@ -0,0 +1,226 @@
package config
import (
"github.com/baez90/inetmock/pkg/api"
"github.com/spf13/viper"
"reflect"
"testing"
)
func Test_multiHandlerConfig_HandlerConfigs(t *testing.T) {
type fields struct {
handlerName string
ports []uint16
listenAddress string
options *viper.Viper
}
tests := []struct {
name string
fields fields
want []api.HandlerConfig
}{
{
name: "Get empty array if no ports are set",
fields: fields{},
want: make([]api.HandlerConfig, 0),
},
{
name: "Get a single handler config if only one port is set",
fields: fields{
handlerName: "sampleHandler",
ports: []uint16{80},
listenAddress: "0.0.0.0",
options: nil,
},
want: []api.HandlerConfig{
api.NewHandlerConfig("sampleHandler", 80, "0.0.0.0", nil),
},
},
{
name: "Get multiple handler configs if only one port is set",
fields: fields{
handlerName: "sampleHandler",
ports: []uint16{80, 8080},
listenAddress: "0.0.0.0",
options: nil,
},
want: []api.HandlerConfig{
api.NewHandlerConfig("sampleHandler", 80, "0.0.0.0", nil),
api.NewHandlerConfig("sampleHandler", 8080, "0.0.0.0", nil),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := multiHandlerConfig{
handlerName: tt.fields.handlerName,
ports: tt.fields.ports,
listenAddress: tt.fields.listenAddress,
options: tt.fields.options,
}
if got := m.HandlerConfigs(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("HandlerConfigs() = %v, want %v", got, tt.want)
}
})
}
}
func Test_multiHandlerConfig_HandlerName(t *testing.T) {
type fields struct {
handlerName string
ports []uint16
listenAddress string
options *viper.Viper
}
tests := []struct {
name string
fields fields
want string
}{
{
name: "Get empty handler name for uninitialized struct",
fields: fields{},
want: "",
},
{
name: "Get expected handler name for initialized struct",
fields: fields{
handlerName: "sampleHandler",
},
want: "sampleHandler",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := multiHandlerConfig{
handlerName: tt.fields.handlerName,
ports: tt.fields.ports,
listenAddress: tt.fields.listenAddress,
options: tt.fields.options,
}
if got := m.HandlerName(); got != tt.want {
t.Errorf("HandlerName() = %v, want %v", got, tt.want)
}
})
}
}
func Test_multiHandlerConfig_ListenAddress(t *testing.T) {
type fields struct {
handlerName string
ports []uint16
listenAddress string
options *viper.Viper
}
tests := []struct {
name string
fields fields
want string
}{
{
name: "Get empty ListenAddress for uninitialized struct",
fields: fields{},
want: "",
},
{
name: "Get expected ListenAddress for initialized struct",
fields: fields{
listenAddress: "0.0.0.0",
},
want: "0.0.0.0",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := multiHandlerConfig{
handlerName: tt.fields.handlerName,
ports: tt.fields.ports,
listenAddress: tt.fields.listenAddress,
options: tt.fields.options,
}
if got := m.ListenAddress(); got != tt.want {
t.Errorf("ListenAddress() = %v, want %v", got, tt.want)
}
})
}
}
func Test_multiHandlerConfig_Options(t *testing.T) {
type fields struct {
handlerName string
ports []uint16
listenAddress string
options *viper.Viper
}
tests := []struct {
name string
fields fields
want *viper.Viper
}{
{
name: "Get nil Options for uninitialized struct",
fields: fields{},
want: nil,
},
{
name: "Get expected Options for initialized struct",
fields: fields{
options: viper.New(),
},
want: viper.New(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := multiHandlerConfig{
handlerName: tt.fields.handlerName,
ports: tt.fields.ports,
listenAddress: tt.fields.listenAddress,
options: tt.fields.options,
}
if got := m.Options(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("Options() = %v, want %v", got, tt.want)
}
})
}
}
func Test_multiHandlerConfig_Ports(t *testing.T) {
type fields struct {
handlerName string
ports []uint16
listenAddress string
options *viper.Viper
}
tests := []struct {
name string
fields fields
want []uint16
}{
{
name: "Get empty Ports for uninitialized struct",
fields: fields{},
want: nil,
},
{
name: "Get expected Ports for initialized struct",
fields: fields{
ports: []uint16{80, 8080},
},
want: []uint16{80, 8080},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := multiHandlerConfig{
handlerName: tt.fields.handlerName,
ports: tt.fields.ports,
listenAddress: tt.fields.listenAddress,
options: tt.fields.options,
}
if got := m.Ports(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("Ports() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -0,0 +1,35 @@
package config
import (
"github.com/spf13/viper"
)
const (
pluginConfigKey = "handler"
listenAddressConfigKey = "listenAddress"
portConfigKey = "port"
portsConfigKey = "ports"
)
func CreateMultiHandlerConfig(handlerConfig *viper.Viper) MultiHandlerConfig {
return NewMultiHandlerConfig(
handlerConfig.GetString(pluginConfigKey),
portsFromConfig(handlerConfig),
handlerConfig.GetString(listenAddressConfigKey),
handlerConfig.Sub(OptionsKey),
)
}
func portsFromConfig(handlerConfig *viper.Viper) (ports []uint16) {
if portsInt := handlerConfig.GetIntSlice(portsConfigKey); len(portsInt) > 0 {
for _, port := range portsInt {
ports = append(ports, uint16(port))
}
return
}
if portInt := handlerConfig.GetInt(portConfigKey); portInt > 0 {
ports = append(ports, uint16(portInt))
}
return
}

View file

@ -0,0 +1,145 @@
package config
import (
"bytes"
"github.com/spf13/viper"
"reflect"
"testing"
)
func TestCreateMultiHandlerConfig(t *testing.T) {
type args struct {
handlerConfig *viper.Viper
}
tests := []struct {
name string
args args
want MultiHandlerConfig
}{
{
name: "Get simple multiHandlerConfig from config",
args: args{
handlerConfig: configFromString(`
handler: sampleHandler
listenAddress: 0.0.0.0
ports:
- 80
- 8080
options: {}
`),
},
want: &multiHandlerConfig{
handlerName: "sampleHandler",
ports: []uint16{80, 8080},
listenAddress: "0.0.0.0",
options: viper.New(),
},
},
{
name: "Get more complex multiHandlerConfig from config",
args: args{
handlerConfig: configFromString(`
handler: sampleHandler
listenAddress: 0.0.0.0
ports:
- 80
- 8080
options:
optionA: asdf
optionB: as1234
`),
},
want: &multiHandlerConfig{
handlerName: "sampleHandler",
ports: []uint16{80, 8080},
listenAddress: "0.0.0.0",
options: configFromString(`
nesting:
optionA: asdf
optionB: as1234
`).Sub("nesting"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := CreateMultiHandlerConfig(tt.args.handlerConfig); !reflect.DeepEqual(got, tt.want) {
t.Errorf("CreateMultiHandlerConfig() = %v, want %v", got, tt.want)
}
})
}
}
func Test_portsFromConfig(t *testing.T) {
type args struct {
handlerConfig *viper.Viper
}
tests := []struct {
name string
args args
wantPorts []uint16
}{
{
name: "Empty array if config value is not set",
args: args{
handlerConfig: viper.New(),
},
wantPorts: nil,
},
{
name: "Array of one if `port` is set",
args: args{
handlerConfig: configFromString(`
port: 80
`),
},
wantPorts: []uint16{80},
},
{
name: "Array of one if `ports` is set as array",
args: args{
handlerConfig: configFromString(`
ports:
- 80
`),
},
wantPorts: []uint16{80},
},
{
name: "Array of two if `ports` is set as array",
args: args{
handlerConfig: configFromString(`
ports:
- 80
- 8080
`),
},
wantPorts: []uint16{80, 8080},
},
{
name: "Array of two if `port` is set as array",
args: args{
handlerConfig: configFromString(`
ports:
- 80
- 8080
`),
},
wantPorts: []uint16{80, 8080},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if gotPorts := portsFromConfig(tt.args.handlerConfig); !reflect.DeepEqual(gotPorts, tt.wantPorts) {
t.Errorf("portsFromConfig() = %v, want %v", gotPorts, tt.wantPorts)
}
})
}
}
func configFromString(yaml string) (config *viper.Viper) {
config = viper.New()
config.SetConfigType("yaml")
_ = config.ReadConfig(bytes.NewBufferString(yaml))
return
}

View file

@ -0,0 +1,30 @@
//go:generate mockgen -source=endpoint.go -destination=./../../internal/mock/endpoints/endpoint_mock.go -package=endpoints_mock
package endpoints
import (
"github.com/baez90/inetmock/pkg/api"
)
type Endpoint interface {
Start() error
Shutdown() error
Name() string
}
type endpoint struct {
name string
handler api.ProtocolHandler
config api.HandlerConfig
}
func (e endpoint) Name() string {
return e.name
}
func (e *endpoint) Start() (err error) {
return e.handler.Start(e.config)
}
func (e *endpoint) Shutdown() (err error) {
return e.handler.Shutdown()
}

View file

@ -0,0 +1,131 @@
package endpoints
import (
"fmt"
"github.com/baez90/inetmock/internal/config"
"github.com/baez90/inetmock/internal/plugins"
"github.com/baez90/inetmock/pkg/logging"
"go.uber.org/zap"
"sync"
"time"
)
type EndpointManager interface {
RegisteredEndpoints() []Endpoint
StartedEndpoints() []Endpoint
CreateEndpoint(name string, multiHandlerConfig config.MultiHandlerConfig) error
StartEndpoints()
ShutdownEndpoints()
}
func NewEndpointManager(logger logging.Logger) EndpointManager {
return &endpointManager{
logger: logger,
registry: plugins.Registry(),
}
}
type endpointManager struct {
logger logging.Logger
registeredEndpoints []Endpoint
properlyStartedEndpoints []Endpoint
registry plugins.HandlerRegistry
}
func (e endpointManager) RegisteredEndpoints() []Endpoint {
return e.registeredEndpoints
}
func (e endpointManager) StartedEndpoints() []Endpoint {
return e.properlyStartedEndpoints
}
func (e *endpointManager) CreateEndpoint(name string, multiHandlerConfig config.MultiHandlerConfig) error {
for _, handlerConfig := range multiHandlerConfig.HandlerConfigs() {
if handler, ok := e.registry.HandlerForName(multiHandlerConfig.HandlerName()); ok {
e.registeredEndpoints = append(e.registeredEndpoints, &endpoint{
name: name,
handler: handler,
config: handlerConfig,
})
} else {
return fmt.Errorf("no matching handler registered for names %s", multiHandlerConfig.HandlerName())
}
}
return nil
}
func (e *endpointManager) StartEndpoints() {
startTime := time.Now()
for _, endpoint := range e.registeredEndpoints {
endpointLogger := e.logger.With(
zap.String("endpoint", endpoint.Name()),
)
endpointLogger.Info("Starting endpoint")
if ok := startEndpoint(endpoint, endpointLogger); ok {
e.properlyStartedEndpoints = append(e.properlyStartedEndpoints, endpoint)
endpointLogger.Info("successfully started endpoint")
} else {
endpointLogger.Error("error occurred during endpoint startup - will be skipped for now")
}
}
endpointStartupDuration := time.Since(startTime)
e.logger.Info(
"Startup of all endpoints completed",
zap.Duration("startupTime", endpointStartupDuration),
)
}
func (e *endpointManager) ShutdownEndpoints() {
var waitGroup sync.WaitGroup
waitGroup.Add(len(e.properlyStartedEndpoints))
for _, endpoint := range e.properlyStartedEndpoints {
endpointLogger := e.logger.With(
zap.String("endpoint", endpoint.Name()),
)
endpointLogger.Info("Triggering shutdown of endpoint")
go shutdownEndpoint(endpoint, endpointLogger, &waitGroup)
}
waitGroup.Wait()
}
func startEndpoint(ep Endpoint, logger logging.Logger) (success bool) {
defer func() {
if r := recover(); r != nil {
logger.Fatal(
"recovered panic during startup of endpoint",
zap.Any("recovered", r),
)
}
}()
if err := ep.Start(); err != nil {
logger.Error(
"failed to start endpoint",
zap.Error(err),
)
} else {
success = true
}
return
}
func shutdownEndpoint(ep Endpoint, logger logging.Logger, wg *sync.WaitGroup) {
defer func() {
if r := recover(); r != nil {
logger.Fatal(
"recovered panic during shutdown of endpoint",
zap.Any("recovered", r),
)
}
wg.Done()
}()
if err := ep.Shutdown(); err != nil {
logger.Error(
"Failed to shutdown endpoint",
zap.Error(err),
)
}
}

View file

@ -0,0 +1,118 @@
package endpoints
import (
"github.com/baez90/inetmock/internal/config"
api_mock "github.com/baez90/inetmock/internal/mock/api"
logging_mock "github.com/baez90/inetmock/internal/mock/logging"
plugins_mock "github.com/baez90/inetmock/internal/mock/plugins"
"github.com/baez90/inetmock/internal/plugins"
"github.com/baez90/inetmock/pkg/logging"
"github.com/golang/mock/gomock"
"testing"
)
func Test_endpointManager_CreateEndpoint(t *testing.T) {
type fields struct {
logger logging.Logger
registeredEndpoints []Endpoint
properlyStartedEndpoints []Endpoint
registry plugins.HandlerRegistry
}
type args struct {
name string
multiHandlerConfig config.MultiHandlerConfig
}
tests := []struct {
name string
fields fields
args args
wantErr bool
wantEndpoints int
}{
{
name: "Test add endpoint",
wantErr: false,
wantEndpoints: 1,
fields: fields{
logger: func() logging.Logger {
return logging_mock.NewMockLogger(gomock.NewController(t))
}(),
registeredEndpoints: nil,
properlyStartedEndpoints: nil,
registry: func() plugins.HandlerRegistry {
registry := plugins_mock.NewMockHandlerRegistry(gomock.NewController(t))
registry.
EXPECT().
HandlerForName("sampleHandler").
MinTimes(1).
MaxTimes(1).
Return(api_mock.NewMockProtocolHandler(gomock.NewController(t)), true)
return registry
}(),
},
args: args{
name: "sampleEndpoint",
multiHandlerConfig: config.NewMultiHandlerConfig(
"sampleHandler",
[]uint16{80},
"0.0.0.0",
nil,
),
},
},
{
name: "Test add unknown handler",
wantErr: true,
wantEndpoints: 0,
fields: fields{
logger: func() logging.Logger {
return logging_mock.NewMockLogger(gomock.NewController(t))
}(),
registeredEndpoints: nil,
properlyStartedEndpoints: nil,
registry: func() plugins.HandlerRegistry {
registry := plugins_mock.NewMockHandlerRegistry(gomock.NewController(t))
registry.
EXPECT().
HandlerForName("sampleHandler").
MinTimes(1).
MaxTimes(1).
Return(nil, false)
return registry
}(),
},
args: args{
name: "sampleEndpoint",
multiHandlerConfig: config.NewMultiHandlerConfig(
"sampleHandler",
[]uint16{80},
"0.0.0.0",
nil,
),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e := &endpointManager{
logger: tt.fields.logger,
registeredEndpoints: tt.fields.registeredEndpoints,
properlyStartedEndpoints: tt.fields.properlyStartedEndpoints,
registry: tt.fields.registry,
}
if err := e.CreateEndpoint(tt.args.name, tt.args.multiHandlerConfig); (err != nil) != tt.wantErr {
t.Errorf("CreateEndpoint() error = %v, wantErr %v", err, tt.wantErr)
}
if len(e.RegisteredEndpoints()) != tt.wantEndpoints {
t.Errorf("RegisteredEndpoints() = %d, want = 1", len(e.RegisteredEndpoints()))
return
}
if len(e.RegisteredEndpoints()) > 0 && e.RegisteredEndpoints()[0].Name() != tt.args.name {
t.Errorf("Name() = %s, want = %s", e.RegisteredEndpoints()[0].Name(), tt.args.name)
}
})
}
}

View file

@ -0,0 +1,178 @@
package endpoints
import (
"fmt"
api_mock "github.com/baez90/inetmock/internal/mock/api"
"github.com/baez90/inetmock/pkg/api"
"github.com/golang/mock/gomock"
"testing"
)
func Test_endpoint_Name(t *testing.T) {
type fields struct {
name string
handler api.ProtocolHandler
config api.HandlerConfig
}
tests := []struct {
name string
fields fields
want string
}{
{
name: "Empty Name if struct is uninitialized",
fields: fields{},
want: "",
},
{
name: "Expected Name if struct is initialized",
fields: fields{
name: "sampleHandler",
},
want: "sampleHandler",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e := endpoint{
name: tt.fields.name,
handler: tt.fields.handler,
config: tt.fields.config,
}
if got := e.Name(); got != tt.want {
t.Errorf("Name() = %v, want %v", got, tt.want)
}
})
}
}
func Test_endpoint_Shutdown(t *testing.T) {
type fields struct {
name string
handler api.ProtocolHandler
config api.HandlerConfig
}
tests := []struct {
name string
fields fields
wantErr bool
}{
{
name: "Expect no error if mocked handler does not return one",
fields: fields{
handler: func() api.ProtocolHandler {
handler := api_mock.NewMockProtocolHandler(gomock.NewController(t))
handler.EXPECT().
Shutdown().
MaxTimes(1).
Return(nil)
return handler
}(),
},
wantErr: false,
},
{
name: "Expect error if mocked handler returns one",
fields: fields{
handler: func() api.ProtocolHandler {
handler := api_mock.NewMockProtocolHandler(gomock.NewController(t))
handler.EXPECT().
Shutdown().
MaxTimes(1).
Return(fmt.Errorf(""))
return handler
}(),
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e := &endpoint{
name: tt.fields.name,
handler: tt.fields.handler,
config: tt.fields.config,
}
if err := e.Shutdown(); (err != nil) != tt.wantErr {
t.Errorf("Shutdown() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_endpoint_Start(t *testing.T) {
demoHandlerConfig := api.NewHandlerConfig(
"sampleHandler",
80,
"0.0.0.0",
nil,
)
type fields struct {
name string
handler api.ProtocolHandler
config api.HandlerConfig
}
tests := []struct {
name string
fields fields
wantErr bool
}{
{
name: "Expect no error if mocked handler does not return one",
fields: fields{
handler: func() api.ProtocolHandler {
handler := api_mock.NewMockProtocolHandler(gomock.NewController(t))
handler.EXPECT().
Start(nil).
MaxTimes(1).
Return(nil)
return handler
}(),
},
wantErr: false,
},
{
name: "Expect error if mocked handler returns one",
fields: fields{
handler: func() api.ProtocolHandler {
handler := api_mock.NewMockProtocolHandler(gomock.NewController(t))
handler.EXPECT().
Start(nil).
MaxTimes(1).
Return(fmt.Errorf(""))
return handler
}(),
},
wantErr: true,
},
{
name: "Expect config to be passed to Start call",
fields: fields{
config: demoHandlerConfig,
handler: func() api.ProtocolHandler {
handler := api_mock.NewMockProtocolHandler(gomock.NewController(t))
handler.EXPECT().
Start(demoHandlerConfig).
MaxTimes(1).
Return(fmt.Errorf(""))
return handler
}(),
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e := &endpoint{
name: tt.fields.name,
handler: tt.fields.handler,
config: tt.fields.config,
}
if err := e.Start(); (err != nil) != tt.wantErr {
t.Errorf("Start() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View file

@ -1,3 +1,4 @@
//go:generate mockgen -source=loading.go -destination=./../../internal/mock/plugins/handler_registry_mock.go -package=plugins_mock
package plugins
import (
@ -42,7 +43,7 @@ func (h *handlerRegistry) HandlerForName(handlerName string) (instance api.Proto
func (h *handlerRegistry) RegisterHandler(handlerName string, handlerProvider api.PluginInstanceFactory, subCommands ...*cobra.Command) {
if _, exists := h.handlers[handlerName]; exists {
panic(fmt.Sprintf("plugin %s already registered - there's something strange...in the neighborhood"))
panic(fmt.Sprintf("handler with name %s is already registered - there's something strange...in the neighborhood", handlerName))
}
h.handlers[handlerName] = handlerProvider
@ -56,7 +57,6 @@ func (h *handlerRegistry) RegisterHandler(handlerName string, handlerProvider ap
}
func (h *handlerRegistry) LoadPlugins(pluginsPath string) (err error) {
if !path.DirExists(pluginsPath) {
err = fmt.Errorf("plugins path %s does not exist or is not accessible", pluginsPath)
return

View file

@ -0,0 +1,109 @@
package plugins
import (
"github.com/baez90/inetmock/pkg/api"
"github.com/spf13/cobra"
"reflect"
"testing"
)
func Test_handlerRegistry_PluginCommands(t *testing.T) {
type fields struct {
handlers map[string]api.PluginInstanceFactory
pluginCommands []*cobra.Command
}
tests := []struct {
name string
fields fields
want []*cobra.Command
}{
{
name: "Default is an nil array of commands",
fields: fields{},
want: nil,
},
{
name: "Returns a copy of the given array of commands",
fields: fields{
pluginCommands: []*cobra.Command{
{
Use: "my-super-command",
Short: "bla bla bla, description",
},
},
},
want: []*cobra.Command{
{
Use: "my-super-command",
Short: "bla bla bla, description",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := handlerRegistry{
handlers: tt.fields.handlers,
pluginCommands: tt.fields.pluginCommands,
}
if got := h.PluginCommands(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("PluginCommands() = %v, want %v", got, tt.want)
}
})
}
}
func Test_handlerRegistry_HandlerForName(t *testing.T) {
type fields struct {
handlers map[string]api.PluginInstanceFactory
pluginCommands []*cobra.Command
}
type args struct {
handlerName string
}
tests := []struct {
name string
fields fields
args args
wantInstance api.ProtocolHandler
wantOk bool
}{
{
name: "No instance if nothing is registered",
fields: fields{},
args: args{},
wantInstance: nil,
wantOk: false,
},
{
name: "Nil instance from pseudo factory",
fields: fields{
handlers: map[string]api.PluginInstanceFactory{
"pseudo": func() api.ProtocolHandler {
return nil
},
},
},
args: args{
handlerName: "pseudo",
},
wantInstance: nil,
wantOk: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := &handlerRegistry{
handlers: tt.fields.handlers,
pluginCommands: tt.fields.pluginCommands,
}
gotInstance, gotOk := h.HandlerForName(tt.args.handlerName)
if !reflect.DeepEqual(gotInstance, tt.wantInstance) {
t.Errorf("HandlerForName() gotInstance = %v, want %v", gotInstance, tt.wantInstance)
}
if gotOk != tt.wantOk {
t.Errorf("HandlerForName() gotOk = %v, want %v", gotOk, tt.wantOk)
}
})
}
}

80
mock_config.yaml Normal file
View file

@ -0,0 +1,80 @@
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
tls:
ecdsaCurve: P256
validity:
ca:
notBeforeRelative: 17520h
notAfterRelative: 17520h
server:
NotBeforeRelative: 168h
NotAfterRelative: 168h
rootCaCert:
publicKey: ./ca.pem
privateKey: ./ca.key
certCachePath: /tmp/inetmock/
endpoints:
plainHttp:
handler: http_mock
listenAddress: 0.0.0.0
ports:
- 80
- 8080
options:
<<: *httpResponseRules
proxy:
handler: http_proxy
listenAddress: 0.0.0.0
port: 3128
options:
target:
ipAddress: 127.0.0.1
port: 80
httpsDowngrade:
handler: tls_interceptor
listenAddress: 0.0.0.0
ports:
- 443
- 8443
options:
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:
target:
ipAddress: 127.0.0.1
port: 53

View file

@ -1,22 +1,32 @@
package config
package api
import "github.com/spf13/viper"
const (
pluginConfigKey = "handler"
listenAddressConfigKey = "listenaddress"
portConfigKey = "port"
)
type HandlerConfig interface {
HandlerName() string
ListenAddress() string
Port() uint16
Options() *viper.Viper
}
func NewHandlerConfig(handlerName string, port uint16, listenAddress string, options *viper.Viper) HandlerConfig {
return &handlerConfig{
handlerName: handlerName,
port: port,
listenAddress: listenAddress,
options: options,
}
}
type handlerConfig struct {
pluginName string
handlerName string
port uint16
listenAddress string
options *viper.Viper
}
func (h handlerConfig) HandlerName() string {
return h.pluginName
return h.handlerName
}
func (h handlerConfig) ListenAddress() string {
@ -30,20 +40,3 @@ func (h handlerConfig) Port() uint16 {
func (h handlerConfig) Options() *viper.Viper {
return h.options
}
type HandlerConfig interface {
HandlerName() string
ListenAddress() string
Port() uint16
Options() *viper.Viper
}
func CreateHandlerConfig(configMap interface{}, subConfig *viper.Viper) HandlerConfig {
underlyingMap := configMap.(map[string]interface{})
return &handlerConfig{
pluginName: underlyingMap[pluginConfigKey].(string),
listenAddress: underlyingMap[listenAddressConfigKey].(string),
port: uint16(underlyingMap[portConfigKey].(int)),
options: subConfig,
}
}

View file

@ -0,0 +1,167 @@
package api
import (
"github.com/spf13/viper"
"reflect"
"testing"
)
func Test_handlerConfig_HandlerName(t *testing.T) {
type fields struct {
handlerName string
port uint16
listenAddress string
options *viper.Viper
}
tests := []struct {
name string
fields fields
want string
}{
{
name: "Get empty HandlerName for uninitialized struct",
fields: fields{},
want: "",
},
{
name: "Get expected HandlerName for initialized struct",
fields: fields{
handlerName: "sampleHandler",
},
want: "sampleHandler",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := handlerConfig{
handlerName: tt.fields.handlerName,
port: tt.fields.port,
listenAddress: tt.fields.listenAddress,
options: tt.fields.options,
}
if got := h.HandlerName(); got != tt.want {
t.Errorf("HandlerName() = %v, want %v", got, tt.want)
}
})
}
}
func Test_handlerConfig_ListenAddress(t *testing.T) {
type fields struct {
handlerName string
port uint16
listenAddress string
options *viper.Viper
}
tests := []struct {
name string
fields fields
want string
}{
{
name: "Get empty ListenAddress for uninitialized struct",
fields: fields{},
want: "",
},
{
name: "Get expected ListenAddress for initialized struct",
fields: fields{
listenAddress: "0.0.0.0",
},
want: "0.0.0.0",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := handlerConfig{
handlerName: tt.fields.handlerName,
port: tt.fields.port,
listenAddress: tt.fields.listenAddress,
options: tt.fields.options,
}
if got := h.ListenAddress(); got != tt.want {
t.Errorf("ListenAddress() = %v, want %v", got, tt.want)
}
})
}
}
func Test_handlerConfig_Options(t *testing.T) {
type fields struct {
handlerName string
port uint16
listenAddress string
options *viper.Viper
}
tests := []struct {
name string
fields fields
want *viper.Viper
}{
{
name: "Get nil Options for uninitialized struct",
fields: fields{},
want: nil,
},
{
name: "Get expected Options for initialized struct",
fields: fields{
options: viper.New(),
},
want: viper.New(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := handlerConfig{
handlerName: tt.fields.handlerName,
port: tt.fields.port,
listenAddress: tt.fields.listenAddress,
options: tt.fields.options,
}
if got := h.Options(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("Options() = %v, want %v", got, tt.want)
}
})
}
}
func Test_handlerConfig_Port(t *testing.T) {
type fields struct {
handlerName string
port uint16
listenAddress string
options *viper.Viper
}
tests := []struct {
name string
fields fields
want uint16
}{
{
name: "Get empty Port for uninitialized struct",
fields: fields{},
want: 0,
},
{
name: "Get expected Port for initialized struct",
fields: fields{
port: 80,
},
want: 80,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := handlerConfig{
handlerName: tt.fields.handlerName,
port: tt.fields.port,
listenAddress: tt.fields.listenAddress,
options: tt.fields.options,
}
if got := h.Port(); got != tt.want {
t.Errorf("Port() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -1,9 +1,8 @@
//go:generate mockgen -source=protocol_handler.go -destination=./../../internal/mock/api/protocol_handler_mock.go -package=api_mock
package api
import (
"github.com/baez90/inetmock/internal/config"
"go.uber.org/zap"
"sync"
)
type PluginInstanceFactory func() ProtocolHandler
@ -11,6 +10,6 @@ type PluginInstanceFactory func() ProtocolHandler
type LoggingFactory func() (*zap.Logger, error)
type ProtocolHandler interface {
Run(config config.HandlerConfig)
Shutdown(wg *sync.WaitGroup)
Start(config HandlerConfig) error
Shutdown() error
}

41
pkg/api/services.go Normal file
View file

@ -0,0 +1,41 @@
package api
import (
"github.com/baez90/inetmock/pkg/cert"
"github.com/baez90/inetmock/pkg/logging"
"github.com/spf13/viper"
)
var (
svcs Services
)
type Services interface {
CertStore() cert.Store
}
type services struct {
certStore cert.Store
}
func InitServices(
config *viper.Viper,
logger logging.Logger,
) error {
certStore, err := cert.NewDefaultStore(config, logger)
if err != nil {
return err
}
svcs = &services{
certStore: certStore,
}
return nil
}
func ServicesInstance() Services {
return svcs
}
func (s *services) CertStore() cert.Store {
return s.certStore
}

22
pkg/cert/addr_utils.go Normal file
View file

@ -0,0 +1,22 @@
package cert
import (
"fmt"
"strings"
)
func extractIPFromAddress(addr string) (ip string, err error) {
if idx := strings.LastIndex(addr, ":"); idx < 0 {
err = fmt.Errorf("addr %s does not match expected scheme <ip>:<port>", addr)
} else {
/* get IP part of address */
ip = addr[0:idx]
/* trim [ ] for IPv6 addresses */
if ip[0] == '[' {
ip = ip[1 : len(ip)-1]
}
}
return
}

View file

@ -1,4 +1,4 @@
package main
package cert
import "testing"

78
pkg/cert/cache.go Normal file
View file

@ -0,0 +1,78 @@
//go:generate mockgen -source=cache.go -destination=./../../internal/mock/cert/cert_cache_mock.go -package=cert_mock
package cert
import (
"crypto/tls"
"crypto/x509"
"errors"
)
type Cache interface {
Put(cert *tls.Certificate) error
Get(cn string) (*tls.Certificate, bool)
}
func NewFileSystemCache(certCachePath string, source TimeSource) Cache {
return &fileSystemCache{
certCachePath: certCachePath,
inMemCache: make(map[string]*tls.Certificate),
timeSource: source,
}
}
type fileSystemCache struct {
certCachePath string
inMemCache map[string]*tls.Certificate
timeSource TimeSource
}
func (f *fileSystemCache) Put(cert *tls.Certificate) (err error) {
if cert == nil {
err = errors.New("cert may not be nil")
return
}
var cn string
if len(cert.Certificate) > 0 {
if pubKey, err := x509.ParseCertificate(cert.Certificate[0]); err != nil {
return err
} else {
cn = pubKey.Subject.CommonName
}
f.inMemCache[cn] = cert
pemCrt := NewPEM(cert)
err = pemCrt.Write(cn, f.certCachePath)
} else {
err = errors.New("no public key present for certificate")
}
return
}
func (f *fileSystemCache) Get(cn string) (*tls.Certificate, bool) {
if crt, ok := f.inMemCache[cn]; ok {
return crt, true
}
pemCrt := NewPEM(nil)
if err := pemCrt.Read(cn, f.certCachePath); err != nil || pemCrt.Cert() == nil {
return nil, false
}
x509Cert, err := x509.ParseCertificate(pemCrt.Cert().Certificate[0])
if err == nil && !certShouldBeRenewed(f.timeSource, x509Cert) {
return pemCrt.Cert(), true
}
return nil, false
}
func certShouldBeRenewed(timeSource TimeSource, cert *x509.Certificate) bool {
lifetime := cert.NotAfter.Sub(cert.NotBefore)
// if the cert is closer to the end of the lifetime than lifetime/2 it should be renewed
if cert.NotAfter.Sub(timeSource.UTCNow()) < lifetime/4 {
return true
}
return false
}

299
pkg/cert/cache_test.go Normal file
View file

@ -0,0 +1,299 @@
package cert
import (
"crypto/tls"
"crypto/x509"
"fmt"
certmock "github.com/baez90/inetmock/internal/mock/cert"
"github.com/golang/mock/gomock"
"os"
"path"
"reflect"
"testing"
"time"
)
const (
cnLocalhost = "localhost"
caCN = "UnitTests"
serverRelativeValidity = 24 * time.Hour
caRelativeValidity = 168 * time.Hour
)
var (
serverCN = fmt.Sprintf("%s-%d", cnLocalhost, time.Now().Unix())
)
func Test_certShouldBeRenewed(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
type args struct {
timeSource TimeSource
cert *x509.Certificate
}
tests := []struct {
name string
args args
want bool
}{
{
name: "Expect cert should not be renewed right after creation",
args: args{
timeSource: func() TimeSource {
tsMock := certmock.NewMockTimeSource(ctrl)
tsMock.
EXPECT().
UTCNow().
Return(time.Now().UTC()).
Times(1)
return tsMock
}(),
cert: &x509.Certificate{
NotAfter: time.Now().UTC().Add(serverRelativeValidity),
NotBefore: time.Now().UTC().Add(-serverRelativeValidity),
},
},
want: false,
},
{
name: "Expect cert should be renewed if the remaining lifetime is less than a quarter of the total lifetime",
args: args{
timeSource: func() TimeSource {
tsMock := certmock.NewMockTimeSource(ctrl)
tsMock.
EXPECT().
UTCNow().
Return(time.Now().UTC().Add(serverRelativeValidity/2 + 1*time.Hour)).
Times(1)
return tsMock
}(),
cert: &x509.Certificate{
NotAfter: time.Now().UTC().Add(serverRelativeValidity),
NotBefore: time.Now().UTC().Add(-serverRelativeValidity),
},
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := certShouldBeRenewed(tt.args.timeSource, tt.args.cert); got != tt.want {
t.Errorf("certShouldBeRenewed() = %v, want %v", got, tt.want)
}
})
}
}
func Test_fileSystemCache_Get(t *testing.T) {
type fields struct {
certCachePath string
inMemCache map[string]*tls.Certificate
timeSource TimeSource
}
type args struct {
cn string
}
tests := []struct {
name string
fields fields
args args
wantOk bool
}{
{
name: "Get a miss when no cert is present",
fields: fields{
certCachePath: os.TempDir(),
inMemCache: make(map[string]*tls.Certificate),
timeSource: NewTimeSource(),
},
args: args{
cnLocalhost,
},
wantOk: false,
},
{
name: "Get a prepared certificate from the memory cache",
fields: func() fields {
certGen := setupCertGen()
caCrt, _ := certGen.CACert(GenerationOptions{
CommonName: caCN,
})
srvCrt, _ := certGen.ServerCert(GenerationOptions{
CommonName: cnLocalhost,
}, caCrt)
return fields{
certCachePath: os.TempDir(),
inMemCache: map[string]*tls.Certificate{
cnLocalhost: srvCrt,
},
timeSource: NewTimeSource(),
}
}(),
args: args{
cn: cnLocalhost,
},
wantOk: true,
},
{
name: "Get a prepared certificate from the file system",
fields: func() fields {
certGen := setupCertGen()
caCrt, _ := certGen.CACert(GenerationOptions{
CommonName: "INetMock",
})
srvCrt, _ := certGen.ServerCert(GenerationOptions{
CommonName: serverCN,
}, caCrt)
pem := NewPEM(srvCrt)
if err := pem.Write(serverCN, os.TempDir()); err != nil {
panic(err)
}
t.Cleanup(func() {
for _, f := range []string{
path.Join(os.TempDir(), fmt.Sprintf("%s.pem", serverCN)),
path.Join(os.TempDir(), fmt.Sprintf("%s.key", serverCN)),
} {
_ = os.Remove(f)
}
})
return fields{
certCachePath: os.TempDir(),
inMemCache: make(map[string]*tls.Certificate),
timeSource: NewTimeSource(),
}
}(),
args: args{
cn: serverCN,
},
wantOk: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f := &fileSystemCache{
certCachePath: tt.fields.certCachePath,
inMemCache: tt.fields.inMemCache,
timeSource: tt.fields.timeSource,
}
gotCrt, gotOk := f.Get(tt.args.cn)
if gotOk && (gotCrt == nil || !reflect.DeepEqual(reflect.TypeOf(new(tls.Certificate)), reflect.TypeOf(gotCrt))) {
t.Errorf("Wanted propert certificate but got %v", gotCrt)
}
if gotOk != tt.wantOk {
t.Errorf("Get() gotOk = %v, want %v", gotOk, tt.wantOk)
}
})
}
}
func Test_fileSystemCache_Put(t *testing.T) {
type fields struct {
certCachePath string
inMemCache map[string]*tls.Certificate
timeSource TimeSource
}
type args struct {
cert *tls.Certificate
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "Want error if nil cert is passed to put",
fields: fields{
certCachePath: os.TempDir(),
inMemCache: make(map[string]*tls.Certificate),
timeSource: NewTimeSource(),
},
args: args{
cert: nil,
},
wantErr: true,
},
{
name: "Want error if empty cert is passed to put",
fields: fields{
certCachePath: os.TempDir(),
inMemCache: make(map[string]*tls.Certificate),
timeSource: NewTimeSource(),
},
args: args{
cert: &tls.Certificate{},
},
wantErr: true,
},
{
name: "No error if valid cert is passed",
fields: fields{
certCachePath: os.TempDir(),
inMemCache: make(map[string]*tls.Certificate),
timeSource: NewTimeSource(),
},
args: args{
cert: func() *tls.Certificate {
gen := setupCertGen()
ca, _ := gen.CACert(GenerationOptions{
CommonName: caCN,
})
srvCN := fmt.Sprintf("%s-%d", cnLocalhost, time.Now().Unix())
t.Cleanup(func() {
for _, f := range []string{
path.Join(os.TempDir(), fmt.Sprintf("%s.pem", srvCN)),
path.Join(os.TempDir(), fmt.Sprintf("%s.key", srvCN)),
} {
_ = os.Remove(f)
}
})
srvCrt, _ := gen.ServerCert(GenerationOptions{
CommonName: srvCN,
}, ca)
return srvCrt
}(),
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f := &fileSystemCache{
certCachePath: tt.fields.certCachePath,
inMemCache: tt.fields.inMemCache,
timeSource: tt.fields.timeSource,
}
if err := f.Put(tt.args.cert); (err != nil) != tt.wantErr {
t.Errorf("Put() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func setupCertGen() Generator {
return NewDefaultGenerator(Options{
Validity: ValidityByPurpose{
Server: ValidityDuration{
NotBeforeRelative: serverRelativeValidity,
NotAfterRelative: serverRelativeValidity,
},
CA: ValidityDuration{
NotBeforeRelative: caRelativeValidity,
NotAfterRelative: caRelativeValidity,
},
},
})
}

21
pkg/cert/constants.go Normal file
View file

@ -0,0 +1,21 @@
package cert
const (
CurveTypeP224 CurveType = "P224"
CurveTypeP256 CurveType = "P256"
CurveTypeP384 CurveType = "P384"
CurveTypeP521 CurveType = "P521"
CurveTypeED25519 CurveType = "ED25519"
defaultServerValidityDuration = "168h"
defaultCAValidityDuration = "17520h"
certCachePathConfigKey = "tls.certCachePath"
ecdsaCurveConfigKey = "tls.ecdsaCurve"
publicKeyConfigKey = "tls.rootCaCert.publicKey"
privateKeyPathConfigKey = "tls.rootCaCert.privateKey"
caCertValidityNotBeforeKey = "tls.validity.ca.notBeforeRelative"
caCertValidityNotAfterKey = "tls.validity.ca.notAfterRelative"
serverCertValidityNotBeforeKey = "tls.validity.server.notBeforeRelative"
serverCertValidityNotAfterKey = "tls.validity.server.notAfterRelative"
)

43
pkg/cert/defaults.go Normal file
View file

@ -0,0 +1,43 @@
package cert
import (
"github.com/baez90/inetmock/pkg/defaulting"
"reflect"
)
var (
certOptionsDefaulter defaulting.Defaulter = func(instance interface{}) {
switch o := instance.(type) {
case *GenerationOptions:
if len(o.Country) < 1 {
o.Country = []string{"US"}
}
if len(o.Locality) < 1 {
o.Locality = []string{"San Francisco"}
}
if len(o.Organization) < 1 {
o.Organization = []string{"INetMock"}
}
if len(o.StreetAddress) < 1 {
o.StreetAddress = []string{"Golden Gate Bridge"}
}
if len(o.PostalCode) < 1 {
o.PostalCode = []string{"94016"}
}
if len(o.Province) < 1 {
o.Province = []string{""}
}
}
}
)
func init() {
certOptionsType := reflect.TypeOf(GenerationOptions{})
defaulters.Register(certOptionsType, certOptionsDefaulter)
}

39
pkg/cert/defaults_test.go Normal file
View file

@ -0,0 +1,39 @@
package cert
import (
"reflect"
"testing"
)
func Test_certOptionsDefaulter(t *testing.T) {
tests := []struct {
name string
arg GenerationOptions
expected GenerationOptions
}{
{
name: "",
arg: GenerationOptions{
CommonName: "CA",
},
expected: GenerationOptions{
CommonName: "CA",
Country: []string{"US"},
Locality: []string{"San Francisco"},
Organization: []string{"INetMock"},
StreetAddress: []string{"Golden Gate Bridge"},
PostalCode: []string{"94016"},
Province: []string{""},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
certOptionsDefaulter(&tt.arg)
if !reflect.DeepEqual(tt.expected, tt.arg) {
t.Errorf("Apply defaulter expected=%v got=%v", tt.expected, tt.arg)
}
})
}
}

184
pkg/cert/generator.go Normal file
View file

@ -0,0 +1,184 @@
package cert
import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"github.com/baez90/inetmock/pkg/defaulting"
"math/big"
"net"
)
var (
defaulters = defaulting.New()
)
type GenerationOptions struct {
CommonName string
Organization []string
OrganizationalUnit []string
IPAddresses []net.IP
DNSNames []string
Country []string
Province []string
Locality []string
StreetAddress []string
PostalCode []string
}
type Generator interface {
CACert(options GenerationOptions) (*tls.Certificate, error)
ServerCert(options GenerationOptions, ca *tls.Certificate) (*tls.Certificate, error)
}
func NewDefaultGenerator(options Options) Generator {
return NewGenerator(options, NewTimeSource(), defaultKeyProvider(options))
}
func NewGenerator(options Options, source TimeSource, provider KeyProvider) Generator {
return &generator{
options: options,
provider: provider,
timeSource: source,
}
}
type generator struct {
options Options
provider KeyProvider
timeSource TimeSource
outDir string
}
func (g *generator) privateKey() (key interface{}, err error) {
if g.provider != nil {
return g.provider()
} else {
return defaultKeyProvider(g.options)()
}
}
func (g *generator) ServerCert(options GenerationOptions, ca *tls.Certificate) (cert *tls.Certificate, err error) {
defaulters.Apply(&options)
var serialNumber *big.Int
if serialNumber, err = generateSerialNumber(); err != nil {
return
}
var privateKey interface{}
if privateKey, err = g.privateKey(); err != nil {
return
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: options.CommonName,
Organization: options.Organization,
OrganizationalUnit: options.OrganizationalUnit,
Country: options.Country,
Province: options.Province,
Locality: options.Locality,
StreetAddress: options.StreetAddress,
PostalCode: options.PostalCode,
},
IPAddresses: options.IPAddresses,
DNSNames: options.DNSNames,
NotBefore: g.timeSource.UTCNow().Add(-g.options.Validity.Server.NotBeforeRelative),
NotAfter: g.timeSource.UTCNow().Add(g.options.Validity.Server.NotAfterRelative),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
}
var caCrt *x509.Certificate
caCrt, err = x509.ParseCertificate(ca.Certificate[0])
var derBytes []byte
if derBytes, err = x509.CreateCertificate(rand.Reader, &template, caCrt, publicKey(privateKey), ca.PrivateKey); err != nil {
return
}
var privateKeyBytes []byte
if privateKeyBytes, err = x509.MarshalPKCS8PrivateKey(privateKey); err != nil {
return
}
if cert, err = parseCert(derBytes, privateKeyBytes); err != nil {
return
}
return
}
func (g generator) CACert(options GenerationOptions) (crt *tls.Certificate, err error) {
defaulters.Apply(&options)
var privateKey interface{}
var serialNumber *big.Int
if serialNumber, err = generateSerialNumber(); err != nil {
return
}
if privateKey, err = g.privateKey(); err != nil {
return
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: options.CommonName,
Organization: options.Organization,
Country: options.Country,
Province: options.Province,
Locality: options.Locality,
StreetAddress: options.StreetAddress,
PostalCode: options.PostalCode,
},
IsCA: true,
NotBefore: g.timeSource.UTCNow().Add(-g.options.Validity.Server.NotBeforeRelative),
NotAfter: g.timeSource.UTCNow().Add(g.options.Validity.Server.NotAfterRelative),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
var derBytes []byte
if derBytes, err = x509.CreateCertificate(rand.Reader, &template, &template, publicKey(privateKey), privateKey); err != nil {
return
}
var privateKeyBytes []byte
if privateKeyBytes, err = x509.MarshalPKCS8PrivateKey(privateKey); err != nil {
return
}
crt, err = parseCert(derBytes, privateKeyBytes)
return
}
func generateSerialNumber() (*big.Int, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
return rand.Int(rand.Reader, serialNumberLimit)
}
func publicKey(privateKey interface{}) interface{} {
switch k := privateKey.(type) {
case *ecdsa.PrivateKey:
return &k.PublicKey
case ed25519.PrivateKey:
return k.Public().(ed25519.PublicKey)
default:
return nil
}
}
func parseCert(derBytes []byte, privateKeyBytes []byte) (*tls.Certificate, error) {
pemEncodedPublicKey := pem.EncodeToMemory(&pem.Block{Type: certificateBlockType, Bytes: derBytes})
pemEncodedPrivateKey := pem.EncodeToMemory(&pem.Block{Type: privateKeyBlockType, Bytes: privateKeyBytes})
cert, err := tls.X509KeyPair(pemEncodedPublicKey, pemEncodedPrivateKey)
return &cert, err
}

79
pkg/cert/options.go Normal file
View file

@ -0,0 +1,79 @@
package cert
import (
"fmt"
"github.com/spf13/viper"
"os"
"strings"
"time"
)
var (
requiredConfigKeys = []string{
publicKeyConfigKey,
privateKeyPathConfigKey,
}
)
type CurveType string
type File struct {
PublicKeyPath string
PrivateKeyPath string
}
type ValidityDuration struct {
NotBeforeRelative time.Duration
NotAfterRelative time.Duration
}
type ValidityByPurpose struct {
CA ValidityDuration
Server ValidityDuration
}
type Options struct {
RootCACert File
CertCachePath string
Curve CurveType
Validity ValidityByPurpose
}
func loadFromConfig(config *viper.Viper) (Options, error) {
missingKeys := make([]string, 0)
for _, requiredKey := range requiredConfigKeys {
if !config.IsSet(requiredKey) {
missingKeys = append(missingKeys, requiredKey)
}
}
if len(missingKeys) > 0 {
return Options{}, fmt.Errorf("config keys are missing: %s", strings.Join(missingKeys, ", "))
}
config.SetDefault(certCachePathConfigKey, os.TempDir())
config.SetDefault(ecdsaCurveConfigKey, string(CurveTypeED25519))
config.SetDefault(caCertValidityNotBeforeKey, defaultCAValidityDuration)
config.SetDefault(caCertValidityNotAfterKey, defaultCAValidityDuration)
config.SetDefault(serverCertValidityNotBeforeKey, defaultServerValidityDuration)
config.SetDefault(serverCertValidityNotAfterKey, defaultServerValidityDuration)
return Options{
CertCachePath: config.GetString(certCachePathConfigKey),
Curve: CurveType(config.GetString(ecdsaCurveConfigKey)),
Validity: ValidityByPurpose{
CA: ValidityDuration{
NotBeforeRelative: config.GetDuration(caCertValidityNotBeforeKey),
NotAfterRelative: config.GetDuration(caCertValidityNotAfterKey),
},
Server: ValidityDuration{
NotBeforeRelative: config.GetDuration(serverCertValidityNotBeforeKey),
NotAfterRelative: config.GetDuration(serverCertValidityNotAfterKey),
},
},
RootCACert: File{
PublicKeyPath: config.GetString(publicKeyConfigKey),
PrivateKeyPath: config.GetString(privateKeyPathConfigKey),
},
}, nil
}

137
pkg/cert/options_test.go Normal file
View file

@ -0,0 +1,137 @@
package cert
import (
"github.com/spf13/viper"
"os"
"reflect"
"strings"
"testing"
"time"
)
func readViper(cfg string) *viper.Viper {
vpr := viper.New()
vpr.SetConfigType("yaml")
if err := vpr.ReadConfig(strings.NewReader(cfg)); err != nil {
panic(err)
}
return vpr
}
func Test_loadFromConfig(t *testing.T) {
type args struct {
config *viper.Viper
}
tests := []struct {
name string
args args
want Options
wantErr bool
}{
{
name: "Parse valid TLS configuration",
wantErr: false,
args: args{
config: readViper(`
tls:
ecdsaCurve: P256
validity:
ca:
notBeforeRelative: 17520h
notAfterRelative: 17520h
server:
NotBeforeRelative: 168h
NotAfterRelative: 168h
rootCaCert:
publicKey: ./ca.pem
privateKey: ./ca.key
certCachePath: /tmp/inetmock/
`),
},
want: Options{
RootCACert: File{
PublicKeyPath: "./ca.pem",
PrivateKeyPath: "./ca.key",
},
CertCachePath: "/tmp/inetmock/",
Curve: CurveTypeP256,
Validity: ValidityByPurpose{
CA: ValidityDuration{
NotBeforeRelative: 17520 * time.Hour,
NotAfterRelative: 17520 * time.Hour,
},
Server: ValidityDuration{
NotBeforeRelative: 168 * time.Hour,
NotAfterRelative: 168 * time.Hour,
},
},
},
},
{
name: "Get an error if CA public key path is missing",
args: args{
readViper(`
tls:
rootCaCert:
privateKey: ./ca.key
`),
},
want: Options{},
wantErr: true,
},
{
name: "Get an error if CA private key path is missing",
args: args{
readViper(`
tls:
rootCaCert:
publicKey: ./ca.pem
`),
},
want: Options{},
wantErr: true,
},
{
name: "Get default options if all required fields are set",
args: args{
readViper(`
tls:
rootCaCert:
publicKey: ./ca.pem
privateKey: ./ca.key
`),
},
want: Options{
RootCACert: File{
PublicKeyPath: "./ca.pem",
PrivateKeyPath: "./ca.key",
},
CertCachePath: os.TempDir(),
Curve: CurveTypeED25519,
Validity: ValidityByPurpose{
CA: ValidityDuration{
NotBeforeRelative: 17520 * time.Hour,
NotAfterRelative: 17520 * time.Hour,
},
Server: ValidityDuration{
NotBeforeRelative: 168 * time.Hour,
NotAfterRelative: 168 * time.Hour,
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := loadFromConfig(tt.args.config)
if (err != nil) != tt.wantErr {
t.Errorf("loadFromConfig() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("loadFromConfig() got = %v, want %v", got, tt.want)
}
})
}
}

79
pkg/cert/pem.go Normal file
View file

@ -0,0 +1,79 @@
package cert
import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"github.com/baez90/inetmock/pkg/path"
"os"
"path/filepath"
)
const (
certificateBlockType = "CERTIFICATE"
privateKeyBlockType = "PRIVATE KEY"
)
type PEM interface {
Cert() *tls.Certificate
Write(cn string, outDir string) error
Read(cn string, inDir string) error
ReadFrom(pubKeyPath, privateKeyPath string) error
}
func NewPEM(crt *tls.Certificate) PEM {
return &pemCrt{
crt: crt,
}
}
type pemCrt struct {
crt *tls.Certificate
}
func (p *pemCrt) Cert() *tls.Certificate {
return p.crt
}
func (p pemCrt) Write(cn string, outDir string) (err error) {
var certOut *os.File
if certOut, err = os.Create(filepath.Join(outDir, fmt.Sprintf("%s.pem", cn))); err != nil {
return
}
defer certOut.Close()
if err = pem.Encode(certOut, &pem.Block{Type: certificateBlockType, Bytes: p.crt.Certificate[0]}); err != nil {
return
}
var keyOut *os.File
if keyOut, err = os.OpenFile(filepath.Join(outDir, fmt.Sprintf("%s.key", cn)), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600); err != nil {
return
}
var privKeyBytes []byte
privKeyBytes, err = x509.MarshalPKCS8PrivateKey(p.crt.PrivateKey)
err = pem.Encode(keyOut, &pem.Block{Type: privateKeyBlockType, Bytes: privKeyBytes})
return
}
func (p *pemCrt) Read(cn string, inDir string) error {
certPath := filepath.Join(inDir, fmt.Sprintf("%s.pem", cn))
keyPath := filepath.Join(inDir, fmt.Sprintf("%s.key", cn))
return p.ReadFrom(certPath, keyPath)
}
func (p *pemCrt) ReadFrom(pubKeyPath, privateKeyPath string) (err error) {
var tlsCrt tls.Certificate
if path.FileExists(pubKeyPath) && path.FileExists(privateKeyPath) {
if tlsCrt, err = tls.LoadX509KeyPair(pubKeyPath, privateKeyPath); err == nil {
p.crt = &tlsCrt
}
} else {
err = errors.New("either public or private key file do not exist")
}
return
}

153
pkg/cert/store.go Normal file
View file

@ -0,0 +1,153 @@
package cert
import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"github.com/baez90/inetmock/pkg/logging"
"github.com/spf13/viper"
"go.uber.org/zap"
"net"
)
const (
ipv4Loopback = "127.0.0.1"
)
var (
defaultKeyProvider = func(options Options) func() (key interface{}, err error) {
return func() (key interface{}, err error) {
return privateKeyForCurve(options)
}
}
)
type KeyProvider func() (key interface{}, err error)
type Store interface {
CACert() *tls.Certificate
GetCertificate(serverName string, ip string) (*tls.Certificate, error)
TLSConfig() *tls.Config
}
func NewDefaultStore(
config *viper.Viper,
logger logging.Logger,
) (Store, error) {
options, err := loadFromConfig(config)
if err != nil {
return nil, err
}
timeSource := NewTimeSource()
return NewStore(
options,
NewFileSystemCache(options.CertCachePath, timeSource),
NewDefaultGenerator(options),
logger,
)
}
func NewStore(
options Options,
cache Cache,
generator Generator,
logger logging.Logger,
) (Store, error) {
store := &store{
options: options,
cache: cache,
generator: generator,
logger: logger,
}
if err := store.loadCACert(); err != nil {
return nil, err
}
return store, nil
}
type store struct {
options Options
caCert *tls.Certificate
cache Cache
timeSource TimeSource
generator Generator
logger logging.Logger
}
func (s *store) TLSConfig() *tls.Config {
rootCaPool := x509.NewCertPool()
rootCaPubKey, _ := x509.ParseCertificate(s.caCert.Certificate[0])
rootCaPool.AddCert(rootCaPubKey)
return &tls.Config{
GetCertificate: func(info *tls.ClientHelloInfo) (cert *tls.Certificate, err error) {
var localIp string
if localIp, err = extractIPFromAddress(info.Conn.LocalAddr().String()); err != nil {
localIp = ipv4Loopback
}
if cert, err = s.GetCertificate(info.ServerName, localIp); err != nil {
s.logger.Error(
"error while resolving certificate",
zap.String("serverName", info.ServerName),
zap.String("localAddr", localIp),
zap.Error(err),
)
}
return
},
RootCAs: rootCaPool,
}
}
func (s *store) loadCACert() (err error) {
pemCrt := NewPEM(nil)
if err = pemCrt.ReadFrom(s.options.RootCACert.PublicKeyPath, s.options.RootCACert.PrivateKeyPath); err != nil {
return
}
s.caCert = pemCrt.Cert()
return
}
func (s *store) CACert() *tls.Certificate {
return s.caCert
}
func (s *store) GetCertificate(serverName string, ip string) (cert *tls.Certificate, err error) {
if crt, ok := s.cache.Get(serverName); ok {
return crt, nil
}
if cert, err = s.generator.ServerCert(GenerationOptions{
CommonName: serverName,
DNSNames: []string{serverName},
IPAddresses: []net.IP{net.ParseIP(ip)},
}, s.caCert); err == nil {
s.cache.Put(cert)
}
return
}
func privateKeyForCurve(options Options) (privateKey interface{}, err error) {
switch options.Curve {
case CurveTypeP224:
privateKey, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
case CurveTypeP256:
privateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
case CurveTypeP384:
privateKey, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
case CurveTypeP521:
privateKey, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
default:
_, privateKey, err = ed25519.GenerateKey(rand.Reader)
}
return
}

20
pkg/cert/time_source.go Normal file
View file

@ -0,0 +1,20 @@
//go:generate mockgen -source=time_source.go -destination=./../../internal/mock/cert/time_source_mock.go -package=cert_mock
package cert
import "time"
type TimeSource interface {
UTCNow() time.Time
}
func NewTimeSource() TimeSource {
return &defaultTimeSource{}
}
type defaultTimeSource struct {
}
func (d defaultTimeSource) UTCNow() time.Time {
return time.Now().UTC()
}

View file

@ -0,0 +1,43 @@
package defaulting
import (
"reflect"
)
type Defaulter func(instance interface{})
type Registry interface {
Register(t reflect.Type, defaulter ...Defaulter)
Apply(instance interface{})
}
func New() Registry {
return &registry{
defaulters: make(map[reflect.Type][]Defaulter),
}
}
type registry struct {
defaulters map[reflect.Type][]Defaulter
}
func (r *registry) Register(t reflect.Type, defaulter ...Defaulter) {
var given []Defaulter
if r, ok := r.defaulters[t]; ok {
given = r
}
for _, d := range defaulter {
given = append(given, d)
}
r.defaulters[t] = given
}
func (r *registry) Apply(instance interface{}) {
if defs, ok := r.defaulters[reflect.TypeOf(instance)]; ok {
for _, def := range defs {
def(instance)
}
}
}

View file

@ -0,0 +1,111 @@
package defaulting
import (
"reflect"
"testing"
)
func Test_registry_Apply(t *testing.T) {
type sample struct {
i int
}
type fields struct {
defaulters map[reflect.Type][]Defaulter
}
type args struct {
instance interface{}
}
type expect struct {
result interface{}
}
tests := []struct {
name string
fields fields
args args
expect expect
}{
{
name: "Expect setting a sample value",
fields: fields{
defaulters: map[reflect.Type][]Defaulter{
reflect.TypeOf(&sample{}): {func(instance interface{}) {
switch i := instance.(type) {
case *sample:
i.i = 42
}
}},
},
},
args: args{
instance: &sample{},
},
expect: expect{
result: &sample{
i: 42,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &registry{
defaulters: tt.fields.defaulters,
}
r.Apply(tt.args.instance)
if !reflect.DeepEqual(tt.expect.result, tt.args.instance) {
t.Errorf("Apply() expected = %v got %v", tt.args.instance, tt.expect.result)
}
})
}
}
func Test_registry_Register(t *testing.T) {
type sample struct {
}
type fields struct {
defaulters map[reflect.Type][]Defaulter
}
type args struct {
t reflect.Type
defaulter []Defaulter
}
type expect struct {
length int
}
tests := []struct {
name string
fields fields
args args
expect expect
}{
{
name: "",
fields: fields{
defaulters: make(map[reflect.Type][]Defaulter),
},
args: args{
t: reflect.TypeOf(sample{}),
defaulter: []Defaulter{func(instance interface{}) {
}},
},
expect: expect{
length: 1,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &registry{
defaulters: tt.fields.defaulters,
}
r.Register(tt.args.t, tt.args.defaulter...)
if length := len(r.defaulters); length != tt.expect.length {
t.Errorf("len(r.defaulters) expect %d got %d", tt.expect.length, length)
}
})
}
}

View file

@ -17,7 +17,9 @@ func ConfigureLogging(
) {
loggingConfig.Level = level
loggingConfig.Development = developmentLogging
loggingConfig.InitialFields = initialFields
if initialFields != nil {
loggingConfig.InitialFields = initialFields
}
}
func ParseLevel(levelString string) zap.AtomicLevel {
@ -37,6 +39,10 @@ func ParseLevel(levelString string) zap.AtomicLevel {
}
}
func CreateLogger() (*zap.Logger, error) {
return loggingConfig.Build()
func CreateLogger() (Logger, error) {
if zapLogger, err := loggingConfig.Build(); err != nil {
return nil, err
} else {
return NewLogger(zapLogger), nil
}
}

166
pkg/logging/factory_test.go Normal file
View file

@ -0,0 +1,166 @@
package logging
import (
"go.uber.org/zap"
"reflect"
"testing"
)
func TestParseLevel(t *testing.T) {
type args struct {
levelString string
}
tests := []struct {
name string
args args
want zap.AtomicLevel
}{
{
name: "Test parse DEBUG level",
args: args{
levelString: "DEBUG",
},
want: zap.NewAtomicLevelAt(zap.DebugLevel),
},
{
name: "Test parse DeBuG level",
args: args{
levelString: "DeBuG",
},
want: zap.NewAtomicLevelAt(zap.DebugLevel),
},
{
name: "Test parse INFO level",
args: args{
levelString: "INFO",
},
want: zap.NewAtomicLevelAt(zap.InfoLevel),
},
{
name: "Test parse InFo level",
args: args{
levelString: "InFo",
},
want: zap.NewAtomicLevelAt(zap.InfoLevel),
},
{
name: "Test parse WARN level",
args: args{
levelString: "WARN",
},
want: zap.NewAtomicLevelAt(zap.WarnLevel),
},
{
name: "Test parse WaRn level",
args: args{
levelString: "WaRn",
},
want: zap.NewAtomicLevelAt(zap.WarnLevel),
},
{
name: "Test parse ERROR level",
args: args{
levelString: "ERROR",
},
want: zap.NewAtomicLevelAt(zap.ErrorLevel),
},
{
name: "Test parse ErRoR level",
args: args{
levelString: "ErRoR",
},
want: zap.NewAtomicLevelAt(zap.ErrorLevel),
},
{
name: "Test parse FATAL level",
args: args{
levelString: "FATAL",
},
want: zap.NewAtomicLevelAt(zap.FatalLevel),
},
{
name: "Test parse FaTaL level",
args: args{
levelString: "FaTaL",
},
want: zap.NewAtomicLevelAt(zap.FatalLevel),
},
{
name: "Fallback to INFO level if unknown level",
args: args{
levelString: "asdf23423",
},
want: zap.NewAtomicLevelAt(zap.InfoLevel),
},
{
name: "Fallback to INFO level if no level",
args: args{
levelString: "",
},
want: zap.NewAtomicLevelAt(zap.InfoLevel),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ParseLevel(tt.args.levelString); !reflect.DeepEqual(got, tt.want) {
t.Errorf("ParseLevel() = %v, want %v", got, tt.want)
}
})
}
}
func TestConfigureLogging(t *testing.T) {
type args struct {
level zap.AtomicLevel
developmentLogging bool
initialFields map[string]interface{}
}
tests := []struct {
name string
args args
}{
{
name: "Test configure defaults",
args: args{},
},
{
name: "Test configure with initialFields",
args: args{
initialFields: map[string]interface{}{
"asdf": "hello, World",
},
},
},
{
name: "Test configure development logging enabled",
args: args{
developmentLogging: true,
},
},
{
name: "Test configure log level",
args: args{
level: zap.NewAtomicLevelAt(zap.FatalLevel),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ConfigureLogging(tt.args.level, tt.args.developmentLogging, tt.args.initialFields)
if loggingConfig.Development != tt.args.developmentLogging {
t.Errorf("loggingConfig.Development = %t, want %t", loggingConfig.Development, tt.args.developmentLogging)
return
}
if loggingConfig.Level != tt.args.level {
t.Errorf("loggingConfig.Level = %v, want %v", loggingConfig.Level, tt.args.level)
return
}
if tt.args.initialFields != nil && !reflect.DeepEqual(loggingConfig.InitialFields, tt.args.initialFields) {
t.Errorf("loggingConfig.InitialFields = %v, want %v", loggingConfig.InitialFields, tt.args.initialFields)
return
}
})
}
}

60
pkg/logging/logger.go Normal file
View file

@ -0,0 +1,60 @@
//go:generate mockgen -source=logger.go -destination=./../../internal/mock/logging/logger_mock.go -package=logging_mock
package logging
import "go.uber.org/zap"
type Logger interface {
Named(s string) Logger
With(fields ...zap.Field) Logger
Debug(msg string, fields ...zap.Field)
Info(msg string, fields ...zap.Field)
Warn(msg string, fields ...zap.Field)
Error(msg string, fields ...zap.Field)
Panic(msg string, fields ...zap.Field)
Fatal(msg string, fields ...zap.Field)
Sync() error
}
type logger struct {
underlyingLogger *zap.Logger
}
func NewLogger(underlyingLogger *zap.Logger) *logger {
return &logger{underlyingLogger: underlyingLogger}
}
func (l logger) Named(s string) Logger {
return NewLogger(l.underlyingLogger.Named(s))
}
func (l logger) With(fields ...zap.Field) Logger {
return NewLogger(l.underlyingLogger.With(fields...))
}
func (l logger) Debug(msg string, fields ...zap.Field) {
l.underlyingLogger.Debug(msg, fields...)
}
func (l logger) Info(msg string, fields ...zap.Field) {
l.underlyingLogger.Info(msg, fields...)
}
func (l logger) Warn(msg string, fields ...zap.Field) {
l.underlyingLogger.Warn(msg, fields...)
}
func (l logger) Error(msg string, fields ...zap.Field) {
l.underlyingLogger.Error(msg, fields...)
}
func (l logger) Panic(msg string, fields ...zap.Field) {
l.underlyingLogger.Panic(msg, fields...)
}
func (l logger) Fatal(msg string, fields ...zap.Field) {
l.underlyingLogger.Fatal(msg, fields...)
}
func (l logger) Sync() error {
return l.underlyingLogger.Sync()
}

View file

@ -2,11 +2,10 @@ package path
import (
"os"
"path/filepath"
)
func WorkingDirectory() (cwd string) {
cwd, _ = filepath.Abs(filepath.Dir(os.Args[0]))
cwd, _ = os.Getwd()
return
}

View file

@ -1,22 +0,0 @@
module github.com/baez90/inetmock/pkg/plugins/dns_mock
go 1.14
require (
github.com/baez90/inetmock v0.0.1
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/miekg/dns v1.1.29
github.com/mitchellh/mapstructure v1.2.2 // indirect
github.com/pelletier/go-toml v1.7.0 // indirect
github.com/spf13/afero v1.2.2 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/cobra v1.0.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.6.3
go.uber.org/zap v1.14.1
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa // indirect
golang.org/x/text v0.3.2 // indirect
gopkg.in/ini.v1 v1.55.0 // indirect
)
replace github.com/baez90/inetmock v0.0.1 => ../../../

View file

@ -1,3 +0,0 @@
module github.com/baez90/inetmock/pkg/plugins
go 1.14

View file

@ -1,21 +0,0 @@
module github.com/baez90/inetmock/pkg/plugins/http_mock
go 1.14
require (
github.com/baez90/inetmock v0.0.1
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/mitchellh/mapstructure v1.2.2 // indirect
github.com/pelletier/go-toml v1.7.0 // indirect
github.com/spf13/afero v1.2.2 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/cobra v1.0.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.6.3
go.uber.org/zap v1.14.1
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa // indirect
golang.org/x/text v0.3.2 // indirect
gopkg.in/ini.v1 v1.55.0 // indirect
)
replace github.com/baez90/inetmock v0.0.1 => ../../../

View file

@ -1,89 +0,0 @@
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"
)
const (
name = "http_mock"
)
type httpHandler struct {
logger *zap.Logger
router *RegexpHandler
server *http.Server
}
func (p *httpHandler) Run(config config.HandlerConfig) {
options := loadFromConfig(config.Options())
addr := fmt.Sprintf("%s:%d", config.ListenAddress(), config.Port())
p.server = &http.Server{Addr: addr, Handler: p.router}
p.logger = p.logger.With(
zap.String("address", addr),
)
for _, rule := range options.Rules {
p.setupRoute(rule)
}
go p.startServer()
}
func (p *httpHandler) Shutdown(wg *sync.WaitGroup) {
if err := p.server.Close(); err != nil {
p.logger.Error(
"failed to shutdown HTTP server",
zap.Error(err),
)
}
wg.Done()
}
func (p *httpHandler) startServer() {
if err := p.server.ListenAndServe(); err != nil {
p.logger.Error(
"failed to start http listener",
zap.Error(err),
)
}
}
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

@ -1,20 +0,0 @@
package main
import (
"fmt"
"regexp"
"strings"
)
var (
ipExtractionRegex = regexp.MustCompile(`(.+):\d{1,5}$`)
)
func extractIPFromAddress(addr string) (string, error) {
matches := ipExtractionRegex.FindAllStringSubmatch(addr, -1)
if len(matches) > 0 && len(matches[0]) >= 1 {
return strings.Trim(matches[0][1], "[]"), nil
} else {
return "", fmt.Errorf("failed to extract IP address from addr %s", addr)
}
}

View file

@ -1,203 +0,0 @@
package main
import (
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"github.com/baez90/inetmock/pkg/path"
"go.uber.org/zap"
"math/big"
"net"
"path/filepath"
)
type keyProvider func() (key interface{}, err error)
type certStore struct {
options *tlsOptions
keyProvider keyProvider
caCert *x509.Certificate
caPrivateKey interface{}
certCache map[string]*tls.Certificate
timeSourceInstance timeSource
logger *zap.Logger
}
func (cs *certStore) timeSource() timeSource {
if cs.timeSourceInstance == nil {
cs.timeSourceInstance = createTimeSource()
}
return cs.timeSourceInstance
}
func (cs *certStore) initCaCert() (err error) {
if path.FileExists(cs.options.rootCaCert.publicKeyPath) && path.FileExists(cs.options.rootCaCert.privateKeyPath) {
var tlsCert tls.Certificate
if tlsCert, err = tls.LoadX509KeyPair(cs.options.rootCaCert.publicKeyPath, cs.options.rootCaCert.privateKeyPath); err != nil {
return
} else if len(tlsCert.Certificate) > 0 {
cs.caPrivateKey = tlsCert.PrivateKey
cs.caCert, err = x509.ParseCertificate(tlsCert.Certificate[0])
}
} else {
cs.caCert, cs.caPrivateKey, err = cs.generateCaCert()
}
return
}
func (cs *certStore) initProvider() {
if cs.keyProvider == nil {
cs.keyProvider = func() (key interface{}, err error) {
return privateKeyForCurve(cs.options)
}
}
}
func (cs *certStore) generateCaCert() (pubKey *x509.Certificate, privateKey interface{}, err error) {
cs.initProvider()
timeSource := cs.timeSource()
var serialNumber *big.Int
if serialNumber, err = generateSerialNumber(); err != nil {
return
}
if privateKey, err = cs.keyProvider(); err != nil {
return
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"INetMock"},
Country: []string{"US"},
Province: []string{""},
Locality: []string{"San Francisco"},
StreetAddress: []string{"Golden Gate Bridge"},
PostalCode: []string{"94016"},
},
IsCA: true,
NotBefore: timeSource.UTCNow().Add(-cs.options.validity.ca.notBeforeRelative),
NotAfter: timeSource.UTCNow().Add(cs.options.validity.ca.notAfterRelative),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
var derBytes []byte
if derBytes, err = x509.CreateCertificate(rand.Reader, &template, &template, publicKey(privateKey), privateKey); err != nil {
return
}
pubKey, err = x509.ParseCertificate(derBytes)
if err = writePublicKey(cs.options.rootCaCert.publicKeyPath, derBytes); err != nil {
return
}
var privateKeyBytes []byte
if privateKeyBytes, err = x509.MarshalPKCS8PrivateKey(privateKey); err != nil {
return
}
if err = writePrivateKey(cs.options.rootCaCert.privateKeyPath, privateKeyBytes); err != nil {
return
}
return
}
func (cs *certStore) getCertificate(serverName string, ip string) (cert *tls.Certificate, err error) {
if cert, ok := cs.certCache[serverName]; ok {
return cert, nil
}
certPath := filepath.Join(cs.options.certCachePath, fmt.Sprintf("%s.pem", serverName))
keyPath := filepath.Join(cs.options.certCachePath, fmt.Sprintf("%s.key", serverName))
if path.FileExists(certPath) && path.FileExists(keyPath) {
if tlsCert, loadErr := tls.LoadX509KeyPair(certPath, keyPath); loadErr == nil {
cs.certCache[serverName] = &tlsCert
x509Cert, err := x509.ParseCertificate(tlsCert.Certificate[0])
if err == nil && !certShouldBeRenewed(cs.timeSource(), x509Cert) {
return &tlsCert, nil
}
}
}
if cert, err = cs.generateDomainCert(serverName, ip); err == nil {
cs.certCache[serverName] = cert
}
return
}
func (cs *certStore) generateDomainCert(
serverName string,
localIp string,
) (cert *tls.Certificate, err error) {
cs.initProvider()
if cs.caCert == nil {
if err = cs.initCaCert(); err != nil {
return
}
}
var serialNumber *big.Int
if serialNumber, err = generateSerialNumber(); err != nil {
return
}
var privateKey interface{}
if privateKey, err = cs.keyProvider(); err != nil {
return
}
notBefore := cs.timeSource().UTCNow().Add(-cs.options.validity.domain.notBeforeRelative)
notAfter := cs.timeSource().UTCNow().Add(cs.options.validity.domain.notAfterRelative)
cs.logger.Info(
"generate domain certificate",
zap.Time("notBefore", notBefore),
zap.Time("notAfter", notAfter),
)
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"INetMock"},
},
IPAddresses: []net.IP{net.ParseIP(localIp)},
DNSNames: []string{serverName},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
}
var derBytes []byte
if derBytes, err = x509.CreateCertificate(rand.Reader, &template, cs.caCert, publicKey(privateKey), cs.caPrivateKey); err != nil {
return
}
if err = writePublicKey(filepath.Join(cs.options.certCachePath, fmt.Sprintf("%s.pem", serverName)), derBytes); err != nil {
return
}
var privateKeyBytes []byte
if privateKeyBytes, err = x509.MarshalPKCS8PrivateKey(privateKey); err != nil {
return
}
if cert, err = parseCert(derBytes, privateKeyBytes); err != nil {
return
}
if err = writePrivateKey(filepath.Join(cs.options.certCachePath, fmt.Sprintf("%s.key", serverName)), privateKeyBytes); err != nil {
return
}
return
}

View file

@ -1,211 +0,0 @@
package main
import (
"crypto/tls"
"crypto/x509"
"go.uber.org/zap"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"testing"
"time"
)
func Test_generateCaCert(t *testing.T) {
tmpDir, err := ioutil.TempDir(os.TempDir(), "*-inetmock")
if err != nil {
t.Errorf("failed to create temp dir %v", err)
return
}
options := &tlsOptions{
ecdsaCurve: "P256",
rootCaCert: cert{
publicKeyPath: filepath.Join(tmpDir, "localhost.pem"),
privateKeyPath: filepath.Join(tmpDir, "localhost.key"),
},
validity: validity{
ca: certValidity{
notBeforeRelative: time.Hour * 24 * 30,
notAfterRelative: time.Hour * 24 * 30,
},
},
}
certStore := certStore{
options: options,
}
defer func() {
_ = os.Remove(tmpDir)
}()
_, _, err = certStore.generateCaCert()
if err != nil {
t.Errorf("failed to generate CA cert %v", err)
}
if _, err = os.Stat(options.rootCaCert.publicKeyPath); err != nil {
t.Errorf("cert file was not created")
}
if _, err = os.Stat(options.rootCaCert.privateKeyPath); err != nil {
t.Errorf("cert file was not created")
}
}
func Test_generateDomainCert(t *testing.T) {
tmpDir, err := ioutil.TempDir(os.TempDir(), "*-inetmock")
if err != nil {
t.Errorf("failed to create temp dir %v", err)
return
}
defer func() {
_ = os.Remove(tmpDir)
}()
caTlsCert, _ := loadPEMCert(testCaCrt, testCaKey)
caCert, _ := x509.ParseCertificate(caTlsCert.Certificate[0])
options := &tlsOptions{
ecdsaCurve: "P256",
certCachePath: tmpDir,
validity: validity{
domain: certValidity{
notAfterRelative: time.Hour * 24 * 30,
notBeforeRelative: time.Hour * 24 * 30,
},
ca: certValidity{
notAfterRelative: time.Hour * 24 * 30,
notBeforeRelative: time.Hour * 24 * 30,
},
},
}
logger, _ := zap.NewDevelopment()
certStore := certStore{
options: options,
caCert: caCert,
logger: logger,
caPrivateKey: caTlsCert.PrivateKey,
}
type args struct {
domain string
ip string
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "Test create google.com cert",
args: args{
domain: "google.com",
ip: "127.0.0.1",
},
wantErr: false,
},
{
name: "Test create golem.de cert",
args: args{
domain: "golem.de",
ip: "127.0.0.1",
},
wantErr: false,
},
{
name: "Test create golem.de cert with any IP address",
args: args{
domain: "golem.de",
ip: "10.10.0.10",
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if domainCert, err := certStore.generateDomainCert(
tt.args.domain,
tt.args.ip,
); (err != nil) != tt.wantErr || reflect.DeepEqual(domainCert, tls.Certificate{}) {
t.Errorf("generateDomainCert() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_certStore_initCaCert(t *testing.T) {
tmpDir, err := ioutil.TempDir(os.TempDir(), "*-inetmock")
if err != nil {
t.Errorf("failed to create temp dir %v", err)
return
}
defer func() {
_ = os.Remove(tmpDir)
}()
caCertPath := filepath.Join(tmpDir, "cacert.pem")
caKeyPath := filepath.Join(tmpDir, "cacert.key")
if err := ioutil.WriteFile(caCertPath, testCaCrt, 0600); err != nil {
t.Errorf("failed to write cacert.pem %v", err)
return
}
if err := ioutil.WriteFile(caKeyPath, testCaKey, 0600); err != nil {
t.Errorf("failed to write cacert.key %v", err)
return
}
type fields struct {
options *tlsOptions
caCert *x509.Certificate
}
tests := []struct {
name string
fields fields
wantErr bool
}{
{
name: "Init CA cert from file",
wantErr: false,
fields: fields{
options: &tlsOptions{
rootCaCert: cert{
publicKeyPath: caCertPath,
privateKeyPath: caKeyPath,
},
},
},
},
{
name: "Init CA with new cert",
wantErr: false,
fields: fields{
options: &tlsOptions{
rootCaCert: cert{
publicKeyPath: filepath.Join(tmpDir, "nonexistent.pem"),
privateKeyPath: filepath.Join(tmpDir, "nonexistent.key"),
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cs := &certStore{
options: tt.fields.options,
caCert: tt.fields.caCert,
}
if err := cs.initCaCert(); (err != nil) != tt.wantErr || cs.caCert == nil {
t.Errorf("initCaCert() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View file

@ -1,106 +0,0 @@
package main
import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"math/big"
"os"
)
type curveType string
const (
certificateBlockType = "CERTIFICATE"
privateKeyBlockType = "PRIVATE KEY"
curveTypeP224 curveType = "P224"
curveTypeP256 curveType = "P256"
curveTypeP384 curveType = "P384"
curveTypeP521 curveType = "P521"
curveTypeED25519 curveType = "ED25519"
)
func loadPEMCert(certPEMBytes []byte, keyPEMBytes []byte) (*tls.Certificate, error) {
cert, err := tls.X509KeyPair(certPEMBytes, keyPEMBytes)
return &cert, err
}
func parseCert(derBytes []byte, privateKeyBytes []byte) (*tls.Certificate, error) {
pemEncodedPublicKey := pem.EncodeToMemory(&pem.Block{Type: certificateBlockType, Bytes: derBytes})
pemEncodedPrivateKey := pem.EncodeToMemory(&pem.Block{Type: privateKeyBlockType, Bytes: privateKeyBytes})
cert, err := tls.X509KeyPair(pemEncodedPublicKey, pemEncodedPrivateKey)
return &cert, err
}
func writePublicKey(crtOutPath string, derBytes []byte) (err error) {
var certOut *os.File
if certOut, err = os.Create(crtOutPath); err != nil {
return
}
if err = pem.Encode(certOut, &pem.Block{Type: certificateBlockType, Bytes: derBytes}); err != nil {
return
}
err = certOut.Close()
return
}
func writePrivateKey(keyOutPath string, privateKeyBytes []byte) (err error) {
var keyOut *os.File
if keyOut, err = os.OpenFile(keyOutPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600); err != nil {
return
}
if err = pem.Encode(keyOut, &pem.Block{Type: privateKeyBlockType, Bytes: privateKeyBytes}); err != nil {
return
}
err = keyOut.Close()
return
}
func certShouldBeRenewed(timeSource timeSource, cert *x509.Certificate) bool {
lifetime := cert.NotAfter.Sub(cert.NotBefore)
// if the cert is closer to the end of the lifetime than lifetime/2 it should be renewed
if cert.NotAfter.Sub(timeSource.UTCNow()) < lifetime/4 {
return true
}
return false
}
func generateSerialNumber() (*big.Int, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
return rand.Int(rand.Reader, serialNumberLimit)
}
func privateKeyForCurve(options *tlsOptions) (privateKey interface{}, err error) {
switch options.ecdsaCurve {
case "P224":
privateKey, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
case "P256":
privateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
case "P384":
privateKey, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
case "P521":
privateKey, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
default:
_, privateKey, err = ed25519.GenerateKey(rand.Reader)
}
return
}
func publicKey(privateKey interface{}) interface{} {
switch k := privateKey.(type) {
case *ecdsa.PrivateKey:
return &k.PublicKey
case ed25519.PrivateKey:
return k.Public().(ed25519.PublicKey)
default:
return nil
}
}

View file

@ -1,74 +0,0 @@
package main
import (
"crypto/x509"
"testing"
"time"
)
type testTimeSource struct {
nowValue time.Time
}
func (t testTimeSource) UTCNow() time.Time {
return t.nowValue
}
func Test_certShouldBeRenewed(t *testing.T) {
type args struct {
timeSource timeSource
cert *x509.Certificate
}
tests := []struct {
name string
args args
want bool
}{
{
name: "Detect cert is expired",
want: true,
args: args{
cert: &x509.Certificate{
NotAfter: time.Now().UTC().Add(1 * time.Hour),
NotBefore: time.Now().UTC().Add(-1 * time.Hour),
},
timeSource: testTimeSource{
nowValue: time.Now().UTC().Add(2 * time.Hour),
},
},
},
{
name: "Detect cert should be renewed",
want: true,
args: args{
cert: &x509.Certificate{
NotAfter: time.Now().UTC().Add(1 * time.Hour),
NotBefore: time.Now().UTC().Add(-1 * time.Hour),
},
timeSource: testTimeSource{
nowValue: time.Now().UTC().Add(45 * time.Minute),
},
},
},
{
name: "Detect cert shouldn't be renewed",
want: false,
args: args{
cert: &x509.Certificate{
NotAfter: time.Now().UTC().Add(1 * time.Hour),
NotBefore: time.Now().UTC().Add(-1 * time.Hour),
},
timeSource: testTimeSource{
nowValue: time.Now().UTC().Add(25 * time.Minute),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := certShouldBeRenewed(tt.args.timeSource, tt.args.cert); got != tt.want {
t.Errorf("certShouldBeRenewed() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -1,112 +0,0 @@
package main
import (
"github.com/spf13/cobra"
"go.uber.org/zap"
"time"
)
const (
generateCACertOutPath = "cert-out"
generateCAKeyOutPath = "key-out"
generateCACurveName = "curve"
generateCANotBeforeRelative = "not-before"
generateCANotAfterRelative = "not-after"
)
func generateCACmd(logger *zap.Logger) *cobra.Command {
cmd := &cobra.Command{
Use: "generate-ca",
Short: "Generate a new CA certificate and corresponding key",
Long: ``,
Run: runGenerateCA(logger),
}
cmd.Flags().String(generateCACertOutPath, "", "Path where CA cert file should be stored")
cmd.Flags().String(generateCAKeyOutPath, "", "Path where CA key file should be stored")
cmd.Flags().String(generateCACurveName, "", "Name of the curve to use, if empty ED25519 is used, other valid values are [P224, P256,P384,P521]")
cmd.Flags().Duration(generateCANotBeforeRelative, 17520*time.Hour, "Relative time value since when in the past the CA certificate should be valid. The value has a time unit, the greatest time unit is h for hour.")
cmd.Flags().Duration(generateCANotAfterRelative, 17520*time.Hour, "Relative time value until when in the future the CA certificate should be valid. The value has a time unit, the greatest time unit is h for hour.")
return cmd
}
func getDurationFlag(cmd *cobra.Command, flagName string, logger *zap.Logger) (val time.Duration, err error) {
if val, err = cmd.Flags().GetDuration(flagName); err != nil {
logger.Error(
"failed to parse parse flag",
zap.String("flag", flagName),
zap.Error(err),
)
}
return
}
func getStringFlag(cmd *cobra.Command, flagName string, logger *zap.Logger) (val string, err error) {
if val, err = cmd.Flags().GetString(flagName); err != nil {
logger.Error(
"failed to parse parse flag",
zap.String("flag", flagName),
zap.Error(err),
)
}
return
}
func runGenerateCA(logger *zap.Logger) func(cmd *cobra.Command, args []string) {
return func(cmd *cobra.Command, args []string) {
var certOutPath, keyOutPath, curveName string
var notBefore, notAfter time.Duration
var err error
if certOutPath, err = getStringFlag(cmd, generateCACertOutPath, logger); err != nil {
return
}
if keyOutPath, err = getStringFlag(cmd, generateCAKeyOutPath, logger); err != nil {
return
}
if curveName, err = getStringFlag(cmd, generateCACurveName, logger); err != nil {
return
}
if notBefore, err = getDurationFlag(cmd, generateCANotBeforeRelative, logger); err != nil {
return
}
if notAfter, err = getDurationFlag(cmd, generateCANotAfterRelative, logger); err != nil {
return
}
logger := logger.With(
zap.String(generateCACurveName, curveName),
zap.String(generateCACertOutPath, certOutPath),
zap.String(generateCAKeyOutPath, keyOutPath),
)
certStore := certStore{
options: &tlsOptions{
ecdsaCurve: curveType(curveName),
validity: validity{
ca: certValidity{
notAfterRelative: notAfter,
notBeforeRelative: notBefore,
},
},
rootCaCert: cert{
publicKeyPath: certOutPath,
privateKeyPath: keyOutPath,
},
},
}
if _, _, err := certStore.generateCaCert(); err != nil {
logger.Error(
"failed to generate CA cert",
zap.Error(err),
)
} else {
logger.Info("Successfully generated CA cert")
}
}
}

View file

@ -1,21 +0,0 @@
module github.com/baez90/inetmock/pkg/plugins/tls_interceptor
go 1.14
require (
github.com/baez90/inetmock v0.0.1
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/mitchellh/mapstructure v1.2.2 // indirect
github.com/pelletier/go-toml v1.7.0 // indirect
github.com/spf13/afero v1.2.2 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/cobra v1.0.0
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.6.3
go.uber.org/zap v1.14.1
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa // indirect
golang.org/x/text v0.3.2 // indirect
gopkg.in/ini.v1 v1.55.0 // indirect
)
replace github.com/baez90/inetmock v0.0.1 => ../../../

View file

@ -1,78 +0,0 @@
package main
import (
"fmt"
"github.com/spf13/viper"
"time"
)
const (
certCachePathConfigKey = "certCachePath"
ecdsaCurveConfigKey = "ecdsaCurve"
targetIpAddressConfigKey = "target.ipAddress"
targetPortConfigKey = "target.port"
publicKeyConfigKey = "rootCaCert.publicKey"
privateKeyPathConfigKey = "rootCaCert.privateKey"
caCertValidityNotBeforeKey = "validity.ca.notBeforeRelative"
caCertValidityNotAfterKey = "validity.ca.notAfterRelative"
domainCertValidityNotBeforeKey = "validity.domain.notBeforeRelative"
domainCertValidityNotAfterKey = "validity.domain.notAfterRelative"
)
type cert struct {
publicKeyPath string
privateKeyPath string
}
type certValidity struct {
notBeforeRelative time.Duration
notAfterRelative time.Duration
}
type validity struct {
ca certValidity
domain certValidity
}
type redirectionTarget struct {
ipAddress string
port uint16
}
func (rt redirectionTarget) address() string {
return fmt.Sprintf("%s:%d", rt.ipAddress, rt.port)
}
type tlsOptions struct {
rootCaCert cert
certCachePath string
redirectionTarget redirectionTarget
ecdsaCurve curveType
validity validity
}
func loadFromConfig(config *viper.Viper) *tlsOptions {
return &tlsOptions{
certCachePath: config.GetString(certCachePathConfigKey),
ecdsaCurve: curveType(config.GetString(ecdsaCurveConfigKey)),
redirectionTarget: redirectionTarget{
ipAddress: config.GetString(targetIpAddressConfigKey),
port: uint16(config.GetInt(targetPortConfigKey)),
},
validity: validity{
ca: certValidity{
notBeforeRelative: config.GetDuration(caCertValidityNotBeforeKey),
notAfterRelative: config.GetDuration(caCertValidityNotAfterKey),
},
domain: certValidity{
notBeforeRelative: config.GetDuration(domainCertValidityNotBeforeKey),
notAfterRelative: config.GetDuration(domainCertValidityNotAfterKey),
},
},
rootCaCert: cert{
publicKeyPath: config.GetString(publicKeyConfigKey),
privateKeyPath: config.GetString(privateKeyPathConfigKey),
},
}
}

View file

@ -1,55 +0,0 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"time"
)
var (
testCaCrt []byte
testCaKey []byte
)
func init() {
tmpDir, err := ioutil.TempDir(os.TempDir(), "*-inetmock")
if err != nil {
panic(fmt.Sprintf("failed to create temp dir %v", err))
}
options := &tlsOptions{
ecdsaCurve: "P256",
rootCaCert: cert{
publicKeyPath: filepath.Join(tmpDir, "localhost.pem"),
privateKeyPath: filepath.Join(tmpDir, "localhost.key"),
},
validity: validity{
ca: certValidity{
notBeforeRelative: time.Hour * 24 * 30,
notAfterRelative: time.Hour * 24 * 30,
},
},
}
certStore := certStore{
options: options,
keyProvider: func() (key interface{}, err error) {
return privateKeyForCurve(options)
},
}
defer func() {
_ = os.Remove(tmpDir)
}()
_, _, err = certStore.generateCaCert()
testCaCrt, _ = ioutil.ReadFile(options.rootCaCert.publicKeyPath)
testCaKey, _ = ioutil.ReadFile(options.rootCaCert.privateKeyPath)
if err != nil {
panic(fmt.Sprintf("failed to generate CA cert %v", err))
}
}

View file

@ -1,18 +0,0 @@
package main
import "time"
type timeSource interface {
UTCNow() time.Time
}
func createTimeSource() timeSource {
return &defaultTimeSource{}
}
type defaultTimeSource struct {
}
func (d defaultTimeSource) UTCNow() time.Time {
return time.Now().UTC()
}

View file

@ -7,17 +7,20 @@ GO_BUILD_ARGS = -buildmode=plugin -ldflags="-w -s"
GO_CONTAINER_BUILD_ARGS = -buildmode=plugin -ldflags="-w -s" -a -installsuffix cgo
GO_DEBUG_BUILD_ARGS = -buildmode=plugin -gcflags "all=-N -l"
PLUGIN_NAME = $(shell basename $(DIR)).so
OUT_DIR = $(DIR)../../../plugins
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 mod tidy
@go build -buildmode=plugin -v $(DIR)...
update-deps:
@go mod tidy
@go get -u
format:
@go fmt $(PKGS)

14
plugins/dns_mock/go.mod Normal file
View file

@ -0,0 +1,14 @@
module github.com/baez90/inetmock/plugins/dns_mock
go 1.14
require (
github.com/baez90/inetmock v0.0.1
github.com/miekg/dns v1.1.29
github.com/spf13/viper v1.6.3
go.uber.org/zap v1.15.0
golang.org/x/crypto v0.0.0-20200406173513-056763e48d71 // indirect
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect
)
replace github.com/baez90/inetmock v0.0.1 => ../../

View file

@ -21,7 +21,6 @@ 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/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
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=
@ -35,6 +34,7 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@ -72,7 +72,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4=
github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
@ -81,7 +80,6 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
@ -110,23 +108,17 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
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 v0.0.6 h1:breEStsVwemnKh2/s6gMvSdMEkwW0sK8vGStnlVBMCs=
github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
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 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@ -157,11 +149,14 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo=
go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200406173513-056763e48d71 h1:DOmugCavvUtnUD114C1Wh+UgTgQZ4pMLzXxi1pSt+/Y=
golang.org/x/crypto v0.0.0-20200406173513-056763e48d71/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
@ -175,8 +170,9 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -189,12 +185,14 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa h1:mQTN3ECqfsViCNBgq+A40vdwhkGykrrQlYe3mPj6BoU=
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8=
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@ -204,6 +202,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -220,7 +219,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ=
gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
@ -228,10 +226,11 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View file

@ -2,18 +2,18 @@ package main
import (
"fmt"
"github.com/baez90/inetmock/internal/config"
"github.com/baez90/inetmock/pkg/api"
"github.com/baez90/inetmock/pkg/logging"
"github.com/miekg/dns"
"go.uber.org/zap"
"sync"
)
type dnsHandler struct {
logger *zap.Logger
logger logging.Logger
dnsServer []*dns.Server
}
func (d *dnsHandler) Run(config config.HandlerConfig) {
func (d *dnsHandler) Start(config api.HandlerConfig) (err error) {
options := loadFromConfig(config.Options())
addr := fmt.Sprintf("%s:%d", config.ListenAddress(), config.Port())
@ -51,6 +51,7 @@ func (d *dnsHandler) Run(config config.HandlerConfig) {
for _, dnsServer := range d.dnsServer {
go d.startServer(dnsServer)
}
return
}
func (d *dnsHandler) startServer(dnsServer *dns.Server) {
@ -62,7 +63,8 @@ func (d *dnsHandler) startServer(dnsServer *dns.Server) {
}
}
func (d *dnsHandler) Shutdown(wg *sync.WaitGroup) {
func (d *dnsHandler) Shutdown() error {
d.logger.Info("shutting down DNS mock")
for _, dnsServer := range d.dnsServer {
if err := dnsServer.Shutdown(); err != nil {
d.logger.Error(
@ -71,5 +73,5 @@ func (d *dnsHandler) Shutdown(wg *sync.WaitGroup) {
)
}
}
wg.Done()
return nil
}

View file

@ -1,6 +1,7 @@
package main
import (
"github.com/baez90/inetmock/pkg/logging"
"github.com/miekg/dns"
"go.uber.org/zap"
)
@ -8,7 +9,7 @@ import (
type regexHandler struct {
routes []resolverRule
fallback ResolverFallback
logger *zap.Logger
logger logging.Logger
}
func (r2 *regexHandler) AddRule(rule resolverRule) {

3
plugins/go.mod Normal file
View file

@ -0,0 +1,3 @@
module github.com/baez90/inetmock/plugins
go 1.14

View file

@ -7,17 +7,20 @@ GO_BUILD_ARGS = -buildmode=plugin -ldflags="-w -s"
GO_CONTAINER_BUILD_ARGS = -buildmode=plugin -ldflags="-w -s" -a -installsuffix cgo
GO_DEBUG_BUILD_ARGS = -buildmode=plugin -gcflags "all=-N -l"
PLUGIN_NAME = $(shell basename $(DIR)).so
OUT_DIR = $(DIR)../../../plugins
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 mod tidy
@go build -buildmode=plugin -v $(DIR)...
update-deps:
@go mod tidy
@go get -u
format:
@go fmt $(PKGS)
@ -29,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:

11
plugins/http_mock/go.mod Normal file
View file

@ -0,0 +1,11 @@
module github.com/baez90/inetmock/plugins/http_mock
go 1.14
require (
github.com/baez90/inetmock v0.0.1
github.com/spf13/viper v1.6.3
go.uber.org/zap v1.15.0
)
replace github.com/baez90/inetmock v0.0.1 => ../../

View file

@ -21,7 +21,6 @@ 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/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
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=
@ -35,6 +34,7 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@ -70,7 +70,6 @@ github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzR
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4=
github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
@ -79,7 +78,6 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
@ -108,22 +106,17 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
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 v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
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 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@ -154,6 +147,8 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo=
go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -181,14 +176,13 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa h1:mQTN3ECqfsViCNBgq+A40vdwhkGykrrQlYe3mPj6BoU=
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8=
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@ -198,6 +192,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -214,7 +209,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ=
gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
@ -222,10 +216,11 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View file

@ -0,0 +1,38 @@
package main
import (
"bytes"
"github.com/baez90/inetmock/pkg/logging"
"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 logging.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)
})
}

59
plugins/http_mock/main.go Normal file
View file

@ -0,0 +1,59 @@
package main
import (
"fmt"
"github.com/baez90/inetmock/pkg/api"
"github.com/baez90/inetmock/pkg/logging"
"go.uber.org/zap"
"net/http"
)
const (
name = "http_mock"
)
type httpHandler struct {
logger logging.Logger
router *RegexpHandler
server *http.Server
}
func (p *httpHandler) Start(config api.HandlerConfig) (err error) {
options := loadFromConfig(config.Options())
addr := fmt.Sprintf("%s:%d", config.ListenAddress(), config.Port())
p.server = &http.Server{Addr: addr, Handler: p.router}
p.logger = p.logger.With(
zap.String("address", addr),
)
for _, rule := range options.Rules {
p.setupRoute(rule)
}
go p.startServer()
return
}
func (p *httpHandler) Shutdown() (err error) {
p.logger.Info("Shutting down HTTP mock")
if err = p.server.Close(); err != nil {
p.logger.Error(
"failed to shutdown HTTP server",
zap.Error(err),
)
err = fmt.Errorf(
"failed to shutdown HTTP server: %w",
err,
)
}
return
}
func (p *httpHandler) startServer() {
if err := p.server.ListenAndServe(); err != nil {
p.logger.Error(
"failed to start http listener",
zap.Error(err),
)
}
}

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

@ -7,17 +7,20 @@ GO_BUILD_ARGS = -buildmode=plugin -ldflags="-w -s"
GO_CONTAINER_BUILD_ARGS = -buildmode=plugin -ldflags="-w -s" -a -installsuffix cgo
GO_DEBUG_BUILD_ARGS = -buildmode=plugin -gcflags "all=-N -l"
PLUGIN_NAME = $(shell basename $(DIR)).so
OUT_DIR = $(DIR)../../../plugins
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 mod tidy
@go build -buildmode=plugin -v $(DIR)...
update-deps:
@go mod tidy
@go get -u
format:
@go fmt $(PKGS)
@ -29,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:

12
plugins/http_proxy/go.mod Normal file
View file

@ -0,0 +1,12 @@
module github.com/baez90/inetmock/plugins/http_proxy
go 1.14
require (
github.com/baez90/inetmock v0.0.1
github.com/spf13/viper v1.6.3
go.uber.org/zap v1.15.0
gopkg.in/elazarl/goproxy.v1 v1.0.0-20180725130230-947c36da3153
)
replace github.com/baez90/inetmock v0.0.1 => ../../

233
plugins/http_proxy/go.sum Normal file
View file

@ -0,0 +1,233 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/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=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
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=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4=
github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
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=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
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=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs=
github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo=
go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa h1:mQTN3ECqfsViCNBgq+A40vdwhkGykrrQlYe3mPj6BoU=
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8=
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
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=
gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View file

@ -0,0 +1,23 @@
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() {
logger, _ := logging.CreateLogger()
logger = logger.With(
zap.String("ProtocolHandler", name),
)
plugins.Registry().RegisterHandler(name, func() api.ProtocolHandler {
return &httpProxy{
logger: logger,
proxy: goproxy.NewProxyHttpServer(),
}
})
}

View file

@ -0,0 +1,71 @@
package main
import (
"fmt"
"github.com/baez90/inetmock/pkg/api"
"github.com/baez90/inetmock/pkg/logging"
"go.uber.org/zap"
"gopkg.in/elazarl/goproxy.v1"
"net/http"
)
const (
name = "http_proxy"
)
type httpProxy struct {
logger logging.Logger
proxy *goproxy.ProxyHttpServer
server *http.Server
}
func (h *httpProxy) Start(config api.HandlerConfig) (err error) {
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),
)
tlsConfig := api.ServicesInstance().CertStore().TLSConfig()
proxyHandler := &proxyHttpHandler{
options: options,
logger: h.logger,
}
proxyHttpsHandler := &proxyHttpsHandler{
tlsConfig: tlsConfig,
logger: h.logger,
}
h.proxy.OnRequest().Do(proxyHandler)
h.proxy.OnRequest().HandleConnect(proxyHttpsHandler)
go h.startProxy()
return
}
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() (err error) {
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),
)
err = fmt.Errorf(
"failed to shutdown proxy endpoint: %w",
err,
)
}
return
}

View file

@ -0,0 +1,42 @@
package main
import (
"fmt"
"github.com/spf13/viper"
)
const (
targetSchemeConfigKey = "target.scheme"
targetIpAddressConfigKey = "target.ipAddress"
targetPortConfigKey = "target.port"
)
type redirectionTarget struct {
scheme string
ipAddress string
port uint16
}
func (rt redirectionTarget) host() string {
return fmt.Sprintf("%s:%d", rt.ipAddress, rt.port)
}
type httpProxyOptions struct {
redirectionTarget redirectionTarget
}
func loadFromConfig(config *viper.Viper) (options httpProxyOptions) {
config.SetDefault(targetSchemeConfigKey, "http")
config.SetDefault(targetIpAddressConfigKey, "127.0.0.1")
config.SetDefault(targetPortConfigKey, "80")
options = httpProxyOptions{
redirectionTarget{
scheme: config.GetString(targetSchemeConfigKey),
ipAddress: config.GetString(targetIpAddressConfigKey),
port: uint16(config.GetInt(targetPortConfigKey)),
},
}
return
}

View file

@ -0,0 +1 @@
package main

View file

@ -0,0 +1,93 @@
package main
import (
"context"
"crypto/tls"
"github.com/baez90/inetmock/pkg/logging"
"go.uber.org/zap"
"gopkg.in/elazarl/goproxy.v1"
"net/http"
"net/url"
)
type proxyHttpHandler struct {
options httpProxyOptions
logger logging.Logger
}
type proxyHttpsHandler struct {
tlsConfig *tls.Config
logger logging.Logger
}
func (p *proxyHttpsHandler) HandleConnect(req string, _ *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
p.logger.Info(
"Intercepting HTTPS proxy request",
zap.String("request", req),
)
return &goproxy.ConnectAction{
Action: goproxy.ConnectMitm,
TLSConfig: func(host string, ctx *goproxy.ProxyCtx) (*tls.Config, error) {
return p.tlsConfig, nil
},
}, ""
}
func (p *proxyHttpHandler) Handle(req *http.Request, ctx *goproxy.ProxyCtx) (retReq *http.Request, resp *http.Response) {
retReq = req
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),
)
var err error
if resp, err = ctx.RoundTrip(p.redirectHTTPRequest(req)); err != nil {
p.logger.Error(
"error while doing roundtrip",
zap.Error(err),
)
return req, nil
}
return
}
func (p proxyHttpHandler) redirectHTTPRequest(originalRequest *http.Request) (redirectReq *http.Request) {
redirectReq = &http.Request{
Method: originalRequest.Method,
URL: &url.URL{
Host: p.options.redirectionTarget.host(),
Scheme: p.options.redirectionTarget.scheme,
Path: originalRequest.URL.Path,
ForceQuery: originalRequest.URL.ForceQuery,
Fragment: originalRequest.URL.Fragment,
Opaque: originalRequest.URL.Opaque,
RawPath: originalRequest.URL.RawPath,
RawQuery: originalRequest.URL.RawQuery,
User: originalRequest.URL.User,
},
Proto: originalRequest.Proto,
ProtoMajor: originalRequest.ProtoMajor,
ProtoMinor: originalRequest.ProtoMinor,
Header: originalRequest.Header,
Body: originalRequest.Body,
GetBody: originalRequest.GetBody,
ContentLength: originalRequest.ContentLength,
TransferEncoding: originalRequest.TransferEncoding,
Close: false,
Host: originalRequest.Host,
Form: originalRequest.Form,
PostForm: originalRequest.PostForm,
MultipartForm: originalRequest.MultipartForm,
Trailer: originalRequest.Trailer,
}
redirectReq = redirectReq.WithContext(context.Background())
return
}

View file

@ -0,0 +1,47 @@
VERSION = $(shell git describe --dirty --tags --always)
DIR = $(dir $(realpath $(firstword $(MAKEFILE_LIST))))
PKGS = $(shell go list ./...)
TEST_PKGS = $(shell find . -type f -name "*_test.go" -printf '%h\n' | sort -u)
GOARGS = GOOS=linux GOARCH=amd64
GO_BUILD_ARGS = -buildmode=plugin -ldflags="-w -s"
GO_CONTAINER_BUILD_ARGS = -buildmode=plugin -ldflags="-w -s" -a -installsuffix cgo
GO_DEBUG_BUILD_ARGS = -buildmode=plugin -gcflags "all=-N -l"
PLUGIN_NAME = $(shell basename $(DIR)).so
OUT_DIR = $(DIR)../../
DEBUG_PORT = 2345
.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
format:
@go fmt $(PKGS)
compile: deps
@mkdir -p $(OUT_DIR)
ifdef DEBUG
@echo 'Compiling for debugging...'
@$(GOARGS) go build $(GO_DEBUG_BUILD_ARGS) -o $(OUT_DIR)/$(PLUGIN_NAME) $(DIR)
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_NAME) $(DIR)
endif
test:
@go test -coverprofile=./cov-raw.out -v $(TEST_PKGS)
@cat ./cov-raw.out | grep -v "generated" > ./cov.out
@rm -f $(DIR)$(PLUGIN_NAME)
cli-cover-report:
@go tool cover -func=cov.out
html-cover-report:
@go tool cover -html=cov.out -o .coverage.html

View file

@ -0,0 +1,12 @@
module github.com/baez90/inetmock/plugins/tls_interceptor
go 1.14
require (
github.com/baez90/inetmock v0.0.1
github.com/google/uuid v1.1.1
github.com/spf13/viper v1.6.3
go.uber.org/zap v1.15.0
)
replace github.com/baez90/inetmock v0.0.1 => ../../

View file

@ -21,7 +21,6 @@ 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/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
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=
@ -35,12 +34,15 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
@ -70,7 +72,6 @@ github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzR
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4=
github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
@ -79,7 +80,6 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
@ -108,23 +108,17 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
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 v0.0.6 h1:breEStsVwemnKh2/s6gMvSdMEkwW0sK8vGStnlVBMCs=
github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
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 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@ -155,6 +149,8 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo=
go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -182,14 +178,13 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa h1:mQTN3ECqfsViCNBgq+A40vdwhkGykrrQlYe3mPj6BoU=
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8=
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@ -199,6 +194,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -215,7 +211,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ=
gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
@ -223,10 +218,11 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View file

@ -4,6 +4,7 @@ import (
"github.com/baez90/inetmock/internal/plugins"
"github.com/baez90/inetmock/pkg/api"
"github.com/baez90/inetmock/pkg/logging"
"github.com/google/uuid"
"go.uber.org/zap"
"sync"
)
@ -18,6 +19,7 @@ func init() {
return &tlsInterceptor{
logger: logger,
currentConnectionsCount: &sync.WaitGroup{},
currentConnections: make(map[uuid.UUID]*proxyConn),
}
}, generateCACmd(logger))
})
}

View file

@ -2,9 +2,11 @@ package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"github.com/baez90/inetmock/internal/config"
"github.com/baez90/inetmock/pkg/api"
"github.com/baez90/inetmock/pkg/cert"
"github.com/baez90/inetmock/pkg/logging"
"github.com/google/uuid"
"go.uber.org/zap"
"net"
"sync"
@ -16,17 +18,16 @@ const (
)
type tlsInterceptor struct {
logger *zap.Logger
options tlsOptions
logger logging.Logger
listener net.Listener
certStore *certStore
options *tlsOptions
certStore cert.Store
shutdownRequested bool
currentConnectionsCount *sync.WaitGroup
currentConnections []*proxyConn
currentConnections map[uuid.UUID]*proxyConn
}
func (t *tlsInterceptor) Run(config config.HandlerConfig) {
var err error
func (t *tlsInterceptor) Start(config api.HandlerConfig) (err error) {
t.options = loadFromConfig(config.Options())
addr := fmt.Sprintf("%s:%d", config.ListenAddress(), config.Port())
@ -35,39 +36,24 @@ func (t *tlsInterceptor) Run(config config.HandlerConfig) {
zap.String("target", t.options.redirectionTarget.address()),
)
t.certStore = &certStore{
options: t.options,
certCache: make(map[string]*tls.Certificate),
logger: t.logger,
}
if err = t.certStore.initCaCert(); err != nil {
t.logger.Error(
"failed to initialize CA cert",
zap.Error(err),
)
}
rootCaPool := x509.NewCertPool()
rootCaPool.AddCert(t.certStore.caCert)
tlsConfig := &tls.Config{
GetCertificate: t.getCert,
RootCAs: rootCaPool,
}
if t.listener, err = tls.Listen("tcp", addr, tlsConfig); err != nil {
if t.listener, err = tls.Listen("tcp", addr, api.ServicesInstance().CertStore().TLSConfig()); err != nil {
t.logger.Fatal(
"failed to create tls listener",
zap.Error(err),
)
err = fmt.Errorf(
"failed to create tls listener: %w",
err,
)
return
}
go t.startListener()
return
}
func (t *tlsInterceptor) Shutdown(wg *sync.WaitGroup) {
func (t *tlsInterceptor) Shutdown() (err error) {
t.logger.Info("Shutting down TLS interceptor")
t.shutdownRequested = true
done := make(chan struct{})
go func() {
@ -77,17 +63,21 @@ func (t *tlsInterceptor) Shutdown(wg *sync.WaitGroup) {
select {
case <-done:
wg.Done()
return
case <-time.After(5 * time.Second):
for _, proxyConn := range t.currentConnections {
if err := proxyConn.Close(); err != nil {
if err = proxyConn.Close(); err != nil {
t.logger.Error(
"error while closing remaining proxy connections",
zap.Error(err),
)
err = fmt.Errorf(
"error while closing remaining proxy connections: %w",
err,
)
}
}
wg.Done()
return
}
}
@ -107,25 +97,9 @@ func (t *tlsInterceptor) startListener() {
}
}
func (t *tlsInterceptor) getCert(info *tls.ClientHelloInfo) (cert *tls.Certificate, err error) {
var localIp string
if localIp, err = extractIPFromAddress(info.Conn.LocalAddr().String()); err != nil {
localIp = "127.0.0.1"
}
if cert, err = t.certStore.getCertificate(info.ServerName, localIp); err != nil {
t.logger.Error(
"error while resolving certificate",
zap.String("serverName", info.ServerName),
zap.String("localAddr", localIp),
zap.Error(err),
)
}
return
}
func (t *tlsInterceptor) proxyConn(conn net.Conn) {
defer conn.Close()
defer t.currentConnectionsCount.Done()
rAddr, err := net.ResolveTCPAddr("tcp", t.options.redirectionTarget.address())
if err != nil {
@ -145,14 +119,16 @@ func (t *tlsInterceptor) proxyConn(conn net.Conn) {
}
defer targetConn.Close()
t.currentConnections = append(t.currentConnections, &proxyConn{
proxyCon := &proxyConn{
source: conn,
target: targetConn,
})
}
conUID := uuid.New()
t.currentConnections[conUID] = proxyCon
Pipe(conn, targetConn)
delete(t.currentConnections, conUID)
t.currentConnectionsCount.Done()
t.logger.Info(
"connection closed",
zap.String("remoteAddr", conn.RemoteAddr().String()),

View file

@ -0,0 +1,33 @@
package main
import (
"fmt"
"github.com/spf13/viper"
)
const (
targetIpAddressConfigKey = "target.ipAddress"
targetPortConfigKey = "target.port"
)
type redirectionTarget struct {
ipAddress string
port uint16
}
func (rt redirectionTarget) address() string {
return fmt.Sprintf("%s:%d", rt.ipAddress, rt.port)
}
type tlsOptions struct {
redirectionTarget redirectionTarget
}
func loadFromConfig(config *viper.Viper) tlsOptions {
return tlsOptions{
redirectionTarget: redirectionTarget{
ipAddress: config.GetString(targetIpAddressConfigKey),
port: uint16(config.GetInt(targetPortConfigKey)),
},
}
}

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