From d70ba748f5992587a98efd30fdf0da390fb39aaa Mon Sep 17 00:00:00 2001 From: Peter Kurfer Date: Wed, 10 Feb 2021 20:26:45 +0000 Subject: [PATCH] Introduce Lifecycle for every endpoint and manage listeners in the renamed Orchestrator - merge packages to get a more concise layout because plugins are no more and therefore there's not a lot to be exported - fix test logger - rework config parsing to be easier and more transparent - remove unnecessary APIs because dynamic endpoint handling is rather a won't implement --- .gitignore | 3 +- .gitlab-ci.yml | 22 ++- README.md | 23 +-- Taskfile.yml | 17 +- api/proto/internal/rpc/endpoints.proto | 28 --- api/proto/internal/rpc/handlers.proto | 20 -- cmd/imctl/endpoints.go | 67 ------- cmd/imctl/handlers.go | 57 ------ cmd/imctl/main.go | 4 +- cmd/inetmock/ca.go | 9 +- cmd/inetmock/serve.go | 50 ++--- config-container.yaml | 162 ++++++++++------ config.yaml | 162 ++++++++++------ go.mod | 12 +- go.sum | 168 ++++++++-------- internal/app/app.go | 46 ++--- {pkg/config => internal/app}/config.go | 41 ++-- internal/app/config_test.go | 70 +++++++ internal/app/constants.go | 6 + {pkg/config => internal/app}/defaults.go | 2 +- {pkg/config => internal/app}/rpc.go | 2 +- {pkg/config => internal/app}/rpc_test.go | 2 +- internal/endpoint/api.go | 31 +++ internal/endpoint/endpoint.go | 69 +++---- internal/endpoint/endpoint_manager.go | 179 ------------------ internal/endpoint/endpoint_manager_test.go | 139 -------------- .../endpoint/handler/dns/mock/fallback.go | 45 +++-- internal/endpoint/handler/dns/mock/handler.go | 72 +++---- .../handler/dns/mock/handler_bench_test.go | 77 ++++++++ .../handler/dns/mock/protocol_options.go | 13 +- .../endpoint/handler/dns/mock/register.go | 6 +- internal/endpoint/handler/http/audit.go | 8 +- .../endpoint/handler/http/conn_context.go | 24 +++ .../endpoint/handler/http/mock/handler.go | 58 +++--- .../handler/http/mock/handler_bench_test.go | 155 +++++++++++++++ .../handler/http/mock/handler_test.go | 147 -------------- .../handler/http/mock/protocol_options.go | 10 +- .../http/mock/protocol_options_test.go | 103 ++++++---- .../endpoint/handler/http/mock/register.go | 6 +- .../endpoint/handler/http/proxy/handler.go | 52 ++--- .../handler/http/proxy/handler_bench_test.go | 167 ++++++++++++++++ .../handler/http/proxy/proxy_handler.go | 82 +++----- .../endpoint/handler/http/proxy/register.go | 16 +- internal/endpoint/handler/metrics/handler.go | 28 +-- internal/endpoint/handler/metrics/register.go | 6 +- .../handler/tls/interceptor/handler.go | 39 ++-- .../handler/tls/interceptor/register.go | 6 +- internal/endpoint/lifecycle.go | 69 +++++++ internal/endpoint/listener.go | 143 ++++++++++++++ internal/endpoint/orchestrator.go | 102 ++++++++++ internal/endpoint/registration.go | 47 +++++ internal/endpoint/uplink.go | 33 ++++ internal/rpc/endpoints_server.go | 37 ---- internal/rpc/grpc_api.go | 13 +- internal/rpc/handlers_server.go | 18 -- internal/test/integration/testcontainer.go | 64 +++++++ pkg/api/protocol_handler.go | 22 --- pkg/api/registration.go | 54 ------ pkg/api/registration_test.go | 59 ------ pkg/audit/event.go | 2 +- pkg/cert/cache_test.go | 9 +- pkg/{config => cert}/certs.go | 2 +- pkg/cert/constants.go | 17 +- pkg/cert/defaults.go | 44 +---- pkg/cert/defaults_test.go | 53 +++++- pkg/cert/generator.go | 17 +- pkg/cert/options.go | 12 +- pkg/cert/options_test.go | 31 ++- pkg/cert/store.go | 25 ++- pkg/config/config_test.go | 68 ------- pkg/config/constants.go | 17 -- pkg/config/handler_config.go | 18 -- pkg/config/handler_config_test.go | 168 ---------------- pkg/config/multi_handler_config.go | 25 --- pkg/defaulting/defaulter.go | 41 ---- pkg/defaulting/defaulter_test.go | 110 ----------- pkg/logging/factory.go | 10 +- pkg/logging/test_logger.go | 14 +- testdata/integration.dockerfile | 20 ++ 79 files changed, 1839 insertions(+), 2036 deletions(-) delete mode 100644 api/proto/internal/rpc/endpoints.proto delete mode 100644 api/proto/internal/rpc/handlers.proto delete mode 100644 cmd/imctl/endpoints.go delete mode 100644 cmd/imctl/handlers.go rename {pkg/config => internal/app}/config.go (65%) create mode 100644 internal/app/config_test.go create mode 100644 internal/app/constants.go rename {pkg/config => internal/app}/defaults.go (94%) rename {pkg/config => internal/app}/rpc.go (93%) rename {pkg/config => internal/app}/rpc_test.go (98%) create mode 100644 internal/endpoint/api.go delete mode 100644 internal/endpoint/endpoint_manager.go delete mode 100644 internal/endpoint/endpoint_manager_test.go create mode 100644 internal/endpoint/handler/dns/mock/handler_bench_test.go create mode 100644 internal/endpoint/handler/http/mock/handler_bench_test.go delete mode 100644 internal/endpoint/handler/http/mock/handler_test.go create mode 100644 internal/endpoint/handler/http/proxy/handler_bench_test.go create mode 100644 internal/endpoint/lifecycle.go create mode 100644 internal/endpoint/listener.go create mode 100644 internal/endpoint/orchestrator.go create mode 100644 internal/endpoint/registration.go create mode 100644 internal/endpoint/uplink.go delete mode 100644 internal/rpc/endpoints_server.go delete mode 100644 internal/rpc/handlers_server.go create mode 100644 internal/test/integration/testcontainer.go delete mode 100644 pkg/api/protocol_handler.go delete mode 100644 pkg/api/registration.go delete mode 100644 pkg/api/registration_test.go rename pkg/{config => cert}/certs.go (98%) delete mode 100644 pkg/config/config_test.go delete mode 100644 pkg/config/constants.go delete mode 100644 pkg/config/handler_config.go delete mode 100644 pkg/config/handler_config_test.go delete mode 100644 pkg/config/multi_handler_config.go delete mode 100644 pkg/defaulting/defaulter.go delete mode 100644 pkg/defaulting/defaulter_test.go create mode 100644 testdata/integration.dockerfile diff --git a/.gitignore b/.gitignore index 374b413..9d05b35 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ .vscode/ dist/ out/ -.task/ \ No newline at end of file +.task/ +public/ \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ec52ec8..dc8fe77 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: registry.gitlab.com/inetmock/ci-image +image: registry.gitlab.com/inetmock/ci-image/go stages: - test @@ -14,6 +14,13 @@ test: junit: out/report.xml cobertura: out/coverage.xml +integration-test: + stage: test + services: + - docker:dind + script: + - task integration-test + lint: stage: test script: @@ -42,3 +49,16 @@ release: script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - goreleaser release --rm-dist + +docs: + stage: release + image: registry.gitlab.com/inetmock/ci-image/mdbook + only: + refs: + - master + - tags + script: + - mdbook build -d ./../public ./docs + artifacts: + paths: + - public \ No newline at end of file diff --git a/README.md b/README.md index 6aba043..6051412 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,9 @@ # INetMock -![Go](https://gitlab.com/inetmock/inetmock/workflows/Go/badge.svg) + +[![pipeline status](https://gitlab.com/inetmock/inetmock/badges/master/pipeline.svg)](https://gitlab.com/inetmock/inetmock/-/commits/master) +[![coverage report](https://gitlab.com/inetmock/inetmock/badges/master/coverage.svg)](https://gitlab.com/inetmock/inetmock/-/commits/master) [![Go Report Card](https://goreportcard.com/badge/gitlab.com/inetmock/inetmock)](https://goreportcard.com/report/gitlab.com/inetmock/inetmock) -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=baez90_inetmock&metric=alert_status)](https://sonarcloud.io/dashboard?id=baez90_inetmock) -[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=baez90_inetmock&metric=ncloc)](https://sonarcloud.io/dashboard?id=baez90_inetmock) -[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=baez90_inetmock&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=baez90_inetmock) -[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=baez90_inetmock&metric=security_rating)](https://sonarcloud.io/dashboard?id=baez90_inetmock) -[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=baez90_inetmock&metric=bugs)](https://sonarcloud.io/dashboard?id=baez90_inetmock) -[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=baez90_inetmock&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=baez90_inetmock) INetMock is kind of a fork of [INetSim](https://www.inetsim.org/). "Kind of" in terms that both applications overlap in their functionality to serve as "fake internet" routers. @@ -17,18 +13,15 @@ HTTPS, DNS, DNS-over-TLS (DoT) requests and to act as an HTTP proxy. The most no is that it issues proper TLS certificates on the fly signed by a CA certificate that can be deployed to client systems to achieve "proper" TLS encryption - as long as the client does not use certificate pinning or something similar. -A second advantage is that INetMock is a complete rewrite in Go, based on a plugin system that allows dynamic -configuration while it has a way smaller memory footprint and far better startup and shutdown times. It also does not -enforce `root` privileges as it is also possible to run the application with the required capabilities to open ports -e.g. with SystemD (a sample unit file can be found in the `deploy/` directory). +A second advantage is that INetMock is a complete rewrite in Go. +It has a way smaller memory footprint and far better startup and shutdown times. +It also does not enforce `root` privileges as it is also possible to run the application with the required capabilities to open ports e.g. with SystemD (a sample unit file can be found in the `deploy/` directory). -_This project is still heavy work-in-progress. There may be breaking changes at any time. There's no guarantee for -anything except no kittens will be harmed!_ +_This project is still heavy work-in-progress. There may be breaking changes at any time. There's no guarantee for anything except no kittens will be harmed!_ ## Docs -Docs are available either in the [`docs/`](./docs/) directory or as rendered markdown book at -the [GitHub pages](https://baez90.github.io/inetmock/). +Docs are available either in the [`docs/`](./docs/) directory or as rendered markdown book at the [GitHub pages](https://baez90.github.io/inetmock/). ## Contribution/feature requests diff --git a/Taskfile.yml b/Taskfile.yml index 169d953..c2d70ed 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -6,6 +6,8 @@ vars: IMCTL_PKG: gitlab.com/inetmock/inetmock/cmd/imctl PROTO_FILES: sh: find ./api/ -type f -name "*.proto" -printf "%p " + BENCHMARKS: + sh: find . -type f -name "*_bench_test.go" env: GOOS: linux @@ -53,6 +55,15 @@ tasks: - gocover-cobertura < {{ .OUT_DIR }}/cov.out > {{ .OUT_DIR }}/coverage.xml - rm -f {{ .OUT_DIR }}/cov-raw.out + integration-test: + deps: + - generate + cmds: + - | + {{ range .BENCHMARKS | splitLines -}} + go test -bench=. {{ . }} + {{ end }} + cli-cover-report: deps: - test @@ -98,4 +109,8 @@ tasks: deps: - test cmds: - - goreleaser release \ No newline at end of file + - goreleaser release + + docs: + cmds: + - mdbook build -d ./../public ./docs \ No newline at end of file diff --git a/api/proto/internal/rpc/endpoints.proto b/api/proto/internal/rpc/endpoints.proto deleted file mode 100644 index db21768..0000000 --- a/api/proto/internal/rpc/endpoints.proto +++ /dev/null @@ -1,28 +0,0 @@ -syntax = "proto3"; - -option go_package = "gitlab.com/inetmock/inetmock/internal/rpc"; -option java_multiple_files = true; -option java_package = "com.github.baez90.inetmock.rpc"; -option java_outer_classname = "EndpointsProto"; - -package inetmock.rpc; - -message GetEndpointsRequest { -} - -message GetEndpointsResponse { - repeated Endpoint endpoints = 1; -} - -message Endpoint { - string id = 1; - string name = 2; - string handler = 3; - string listenAddress = 4; - int32 port = 5; -} - -service Endpoints { - rpc GetEndpoints (GetEndpointsRequest) returns (GetEndpointsResponse) { - } -} \ No newline at end of file diff --git a/api/proto/internal/rpc/handlers.proto b/api/proto/internal/rpc/handlers.proto deleted file mode 100644 index 44a3ff9..0000000 --- a/api/proto/internal/rpc/handlers.proto +++ /dev/null @@ -1,20 +0,0 @@ -syntax = "proto3"; - -option go_package = "gitlab.com/inetmock/inetmock/internal/rpc"; -option java_multiple_files = true; -option java_package = "com.github.baez90.inetmock.rpc"; -option java_outer_classname = "HandlersProto"; - -package inetmock.rpc; - -service Handlers { - rpc GetHandlers (GetHandlersRequest) returns (GetHandlersResponse) { - } -} - -message GetHandlersRequest { -} - -message GetHandlersResponse { - repeated string handlers = 1; -} \ No newline at end of file diff --git a/cmd/imctl/endpoints.go b/cmd/imctl/endpoints.go deleted file mode 100644 index 622dd4d..0000000 --- a/cmd/imctl/endpoints.go +++ /dev/null @@ -1,67 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - - "github.com/spf13/cobra" - "gitlab.com/inetmock/inetmock/internal/format" - "gitlab.com/inetmock/inetmock/internal/rpc" -) - -var ( - getEndpoints = &cobra.Command{ - Use: "get", - Short: "Get all running endpoints", - RunE: runGetEndpoints, - } - - endpointsCmd = &cobra.Command{ - Use: "endpoints", - Short: "endpoints is the entrypoint to all kind of commands to interact with endpoints", - Aliases: []string{"ep", "endpoint"}, - } -) - -type printableEndpoint struct { - ID string - Name string - Handler string - ListenAddress string - Port int -} - -func fromEndpoint(ep *rpc.Endpoint) *printableEndpoint { - return &printableEndpoint{ - ID: ep.Id, - Name: ep.Name, - Handler: ep.Handler, - ListenAddress: ep.ListenAddress, - Port: int(ep.Port), - } -} - -func fromEndpoints(eps []*rpc.Endpoint) (out []*printableEndpoint) { - for idx := range eps { - out = append(out, fromEndpoint(eps[idx])) - } - return -} - -func runGetEndpoints(_ *cobra.Command, _ []string) (err error) { - endpointsClient := rpc.NewEndpointsClient(conn) - ctx, cancel := context.WithTimeout(cliApp.Context(), grpcTimeout) - defer cancel() - var endpointsResp *rpc.GetEndpointsResponse - if endpointsResp, err = endpointsClient.GetEndpoints(ctx, &rpc.GetEndpointsRequest{}); err != nil { - fmt.Printf("Failed to get the endpoints: %v", err) - os.Exit(11) - } - - writer := format.Writer(outputFormat, os.Stdout) - if err = writer.Write(fromEndpoints(endpointsResp.Endpoints)); err != nil { - fmt.Printf("Error occurred during writing response values: %v\n", err) - } - return -} diff --git a/cmd/imctl/handlers.go b/cmd/imctl/handlers.go deleted file mode 100644 index 06822da..0000000 --- a/cmd/imctl/handlers.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - - "github.com/spf13/cobra" - "gitlab.com/inetmock/inetmock/internal/format" - "gitlab.com/inetmock/inetmock/internal/rpc" -) - -var ( - getHandlersCmd = &cobra.Command{ - Use: "get", - Short: "Get all registered handlers", - Run: runGetHandlers, - } - - handlerCmd = &cobra.Command{ - Use: "handlers", - Short: "handlers is the entrypoint to all kind of commands to interact with handlers", - Aliases: []string{"handler"}, - } -) - -type printableHandler struct { - Handler string -} - -func fromHandlers(hs []string) (handlers []*printableHandler) { - for idx := range hs { - handlers = append(handlers, &printableHandler{ - Handler: hs[idx], - }) - } - return -} - -func runGetHandlers(_ *cobra.Command, _ []string) { - handlersClient := rpc.NewHandlersClient(conn) - - ctx, cancel := context.WithTimeout(cliApp.Context(), grpcTimeout) - defer cancel() - var err error - var handlersResp *rpc.GetHandlersResponse - - if handlersResp, err = handlersClient.GetHandlers(ctx, &rpc.GetHandlersRequest{}); err != nil { - fmt.Printf("Failed to get the endpoints: %v", err) - os.Exit(11) - } - - writer := format.Writer(outputFormat, os.Stdout) - if err = writer.Write(fromHandlers(handlersResp.Handlers)); err != nil { - fmt.Printf("Error occurred during writing response values: %v\n", err) - } -} diff --git a/cmd/imctl/main.go b/cmd/imctl/main.go index 93a0803..70ab252 100644 --- a/cmd/imctl/main.go +++ b/cmd/imctl/main.go @@ -22,12 +22,10 @@ var ( ) func main() { - endpointsCmd.AddCommand(getEndpoints) - handlerCmd.AddCommand(getHandlersCmd) healthCmd.AddCommand(generalHealthCmd, containerHealthCmd) cliApp = app.NewApp("imctl", "IMCTL is the CLI app to interact with an INetMock server"). - WithCommands(endpointsCmd, handlerCmd, healthCmd, auditCmd). + WithCommands(healthCmd, auditCmd). WithInitTasks(func(_ *cobra.Command, _ []string) (err error) { return initGRPCConnection() }). diff --git a/cmd/inetmock/ca.go b/cmd/inetmock/ca.go index b28ad0f..9b7062c 100644 --- a/cmd/inetmock/ca.go +++ b/cmd/inetmock/ca.go @@ -7,7 +7,6 @@ import ( "github.com/spf13/cobra" "gitlab.com/inetmock/inetmock/pkg/cert" - "gitlab.com/inetmock/inetmock/pkg/config" "go.uber.org/zap" ) @@ -64,11 +63,11 @@ func runGenerateCA(_ *cobra.Command, _ []string) { zap.String(generateCACertOutPath, certOutPath), ) - generator := cert.NewDefaultGenerator(config.CertOptions{ + generator := cert.NewDefaultGenerator(cert.CertOptions{ CertCachePath: certOutPath, - Curve: config.CurveType(curveName), - Validity: config.ValidityByPurpose{ - CA: config.ValidityDuration{ + Curve: cert.CurveType(curveName), + Validity: cert.ValidityByPurpose{ + CA: cert.ValidityDuration{ NotAfterRelative: notAfter, NotBeforeRelative: notBefore, }, diff --git a/cmd/inetmock/serve.go b/cmd/inetmock/serve.go index fd5dc5b..71485b5 100644 --- a/cmd/inetmock/serve.go +++ b/cmd/inetmock/serve.go @@ -1,11 +1,8 @@ package main import ( - "strings" - "github.com/spf13/cobra" "gitlab.com/inetmock/inetmock/internal/rpc" - "gitlab.com/inetmock/inetmock/pkg/config" "go.uber.org/zap" ) @@ -14,41 +11,48 @@ var ( Use: "serve", Short: "Starts the INetMock server", Long: ``, - Run: startINetMock, + RunE: startINetMock, } ) -func startINetMock(_ *cobra.Command, _ []string) { +func startINetMock(_ *cobra.Command, _ []string) (err error) { rpcAPI := rpc.NewINetMockAPI(serverApp) - logger := serverApp.Logger().Named("inetmock").With(zap.String("command", "serve")) + logger := serverApp.Logger() - for endpointName, endpointHandler := range serverApp.Config().EndpointConfigs() { - handlerSubConfig := serverApp.Config().Viper().Sub(strings.Join([]string{config.EndpointsKey, endpointName, config.OptionsKey}, ".")) - endpointHandler.Options = handlerSubConfig - if err := serverApp.EndpointManager().CreateEndpoint(endpointName, endpointHandler); err != nil { - logger.Warn( - "error occurred while creating endpoint", - zap.String("endpointName", endpointName), - zap.String("handlerName", endpointHandler.Handler), - zap.Error(err), - ) + cfg := serverApp.Config() + endpointOrchestrator := serverApp.EndpointManager() + + for name, spec := range cfg.ListenerSpecs() { + if spec.Name == "" { + spec.Name = name + } + if err = endpointOrchestrator.RegisterListener(spec); err != nil { + logger.Error("Failed to register listener", zap.Error(err)) + return } } - serverApp.EndpointManager().StartEndpoints() - if err := rpcAPI.StartServer(); err != nil { + errChan := serverApp.EndpointManager().StartEndpoints() + if err = rpcAPI.StartServer(); err != nil { + serverApp.Shutdown() logger.Error( "failed to start gRPC API", zap.Error(err), ) } - <-serverApp.Context().Done() +loop: + for { + select { + case err := <-errChan: + logger.Error("got error from endpoint", zap.Error(err)) + case <-serverApp.Context().Done(): + break loop + } + } - logger.Info( - "App context canceled - shutting down", - ) + logger.Info("App context canceled - shutting down") rpcAPI.StopServer() - serverApp.EndpointManager().ShutdownEndpoints() + return } diff --git a/config-container.yaml b/config-container.yaml index 636f536..19ce48a 100644 --- a/config-container.yaml +++ b/config-container.yaml @@ -41,6 +41,19 @@ x-response-rules: &httpResponseRules matcher: Path response: /var/lib/inetmock/fakeFiles/default.html +x-http-handlers: &httpHandlers + endpoints: + plainHttp: + handler: http_mock + tls: false + options: + <<: *httpResponseRules + https: + handler: http_mock + tls: true + options: + <<: *httpResponseRules + api: listen: unix:///var/run/inetmock/inetmock.sock @@ -60,61 +73,94 @@ tls: privateKeyPath: /var/lib/inetmock/ca/ca.key certCachePath: /var/lib/inetmock/certs -endpoints: - plainHttp: - handler: http_mock - listenAddress: 0.0.0.0 - ports: - - 80 - - 8080 - options: - <<: *httpResponseRules - https: - handler: http_mock - listenAddress: 0.0.0.0 - ports: - - 443 - - 8443 - options: - tls: true - <<: *httpResponseRules - proxy: - handler: http_proxy - listenAddress: 0.0.0.0 - ports: - - 3128 - options: - target: - ipAddress: 127.0.0.1 - port: 80 - plainDns: - handler: dns_mock - listenAddress: 0.0.0.0 - ports: - - 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 - ports: - - 853 - options: - target: - ipAddress: 127.0.0.1 - port: 53 - metrics: - handler: metrics_exporter - listenAddress: 0.0.0.0 - ports: - - 9110 - options: - route: /metrics \ No newline at end of file +listeners: + udp_53: + name: '' + protocol: udp + listenAddress: '' + port: 53 + endpoints: + plainDns: + handler: dns_mock + 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 + tcp_80: + name: '' + protocol: tcp + listenAddress: '' + port: 80 + <<: *httpHandlers + tcp_443: + name: '' + protocol: tcp + listenAddress: '' + port: 443 + <<: *httpHandlers + tcp_853: + name: '' + protocol: tcp + listenAddress: '' + port: 853 + endpoints: + DoT: + handler: dns_mock + tls: true + 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 + tcp_3128: + name: '' + protocol: tcp + listenAddress: '' + port: 3128 + endpoints: + proxyPlain: + handler: http_proxy + options: + target: + ipAddress: 127.0.0.1 + port: 80 + proxyTls: + handler: http_proxy + tls: true + options: + target: + ipAddress: 127.0.0.1 + port: 443 + tcp_8080: + name: '' + protocol: tcp + listenAddress: '' + port: 8080 + <<: *httpHandlers + tcp_8443: + name: '' + protocol: tcp + listenAddress: '' + port: 8443 + <<: *httpHandlers + tcp_9110: + name: '' + protocol: tcp + listenAddress: '' + port: 9110 + endpoints: + metrics: + handler: metrics_exporter + options: + route: /metrics \ No newline at end of file diff --git a/config.yaml b/config.yaml index 7ce1cdc..f9e88d8 100644 --- a/config.yaml +++ b/config.yaml @@ -41,6 +41,19 @@ x-response-rules: &httpResponseRules matcher: Path response: ./assets/fakeFiles/default.html +x-http-handlers: &httpHandlers + endpoints: + plainHttp: + handler: http_mock + tls: false + options: + <<: *httpResponseRules + https: + handler: http_mock + tls: true + options: + <<: *httpResponseRules + api: listen: unix:///var/run/inetmock.sock @@ -60,61 +73,94 @@ tls: privateKeyPath: ./assets/demoCA/ca.key certCachePath: /tmp/inetmock/ -endpoints: - plainHttp: - handler: http_mock - listenAddress: 0.0.0.0 - ports: - - 80 - - 8080 - options: - <<: *httpResponseRules - https: - handler: http_mock - listenAddress: 0.0.0.0 - ports: - - 443 - - 8443 - options: - tls: true - <<: *httpResponseRules - proxy: - handler: http_proxy - listenAddress: 0.0.0.0 - ports: - - 3128 - options: - target: - ipAddress: 127.0.0.1 - port: 80 - plainDns: - handler: dns_mock - listenAddress: 0.0.0.0 - ports: - - 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 - ports: - - 853 - options: - target: - ipAddress: 127.0.0.1 - port: 53 - metrics: - handler: metrics_exporter - listenAddress: 0.0.0.0 - ports: - - 9110 - options: - route: /metrics \ No newline at end of file +listeners: + udp_53: + name: '' + protocol: udp + listenAddress: '' + port: 1053 + endpoints: + plainDns: + handler: dns_mock + 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 + tcp_80: + name: '' + protocol: tcp + listenAddress: '' + port: 80 + <<: *httpHandlers + tcp_443: + name: '' + protocol: tcp + listenAddress: '' + port: 443 + <<: *httpHandlers + tcp_853: + name: '' + protocol: tcp + listenAddress: '' + port: 853 + endpoints: + DoT: + handler: dns_mock + tls: true + 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 + tcp_3128: + name: '' + protocol: tcp + listenAddress: '' + port: 3128 + endpoints: + proxyPlain: + handler: http_proxy + options: + target: + ipAddress: 127.0.0.1 + port: 80 + proxyTls: + handler: http_proxy + tls: true + options: + target: + ipAddress: 127.0.0.1 + port: 443 + tcp_8080: + name: '' + protocol: tcp + listenAddress: '' + port: 8080 + <<: *httpHandlers + tcp_8443: + name: '' + protocol: tcp + listenAddress: '' + port: 8443 + <<: *httpHandlers + tcp_9110: + name: '' + protocol: tcp + listenAddress: '' + port: 9110 + endpoints: + metrics: + handler: metrics_exporter + options: + route: /metrics \ No newline at end of file diff --git a/go.mod b/go.mod index bd4d26f..5a22ac5 100644 --- a/go.mod +++ b/go.mod @@ -4,16 +4,20 @@ go 1.15 require ( github.com/bwmarrin/snowflake v0.3.0 + github.com/docker/go-connections v0.4.0 github.com/golang/mock v1.4.4 github.com/golang/protobuf v1.4.3 github.com/google/uuid v1.2.0 github.com/imdario/mergo v0.3.11 - github.com/miekg/dns v1.1.35 - github.com/olekukonko/tablewriter v0.0.4 + github.com/jinzhu/copier v0.2.4 + github.com/miekg/dns v1.1.38 + github.com/mitchellh/mapstructure v1.4.1 + github.com/olekukonko/tablewriter v0.0.5 github.com/prometheus/client_golang v1.9.0 - github.com/spf13/cobra v1.1.1 - github.com/spf13/pflag v1.0.5 + github.com/soheilhy/cmux v0.1.4 + github.com/spf13/cobra v1.1.2 github.com/spf13/viper v1.7.1 + github.com/testcontainers/testcontainers-go v0.9.0 go.uber.org/multierr v1.6.0 go.uber.org/zap v1.16.0 google.golang.org/grpc v1.35.0 diff --git a/go.sum b/go.sum index df50c82..bbd9a6e 100644 --- a/go.sum +++ b/go.sum @@ -11,10 +11,16 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA= +github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= @@ -28,7 +34,6 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= @@ -45,6 +50,7 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= @@ -53,12 +59,15 @@ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/containerd/containerd v1.4.1 h1:pASeJT3R3YyVn+94qEPk0SnU1OQ20Jd/T+SPKy9xehY= +github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= 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-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -74,20 +83,28 @@ 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/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible h1:dvc1KSkIYTVjZgHf/CTC2diTYC8PzhaA5sFISRfNVrE= +github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible h1:SiUATuP//KecDjpOK2tvZJgeScYAklvyjfK8JZlU6fo= +github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/elazarl/goproxy v0.0.0-20201021153353-00ad82a08272 h1:Am81SElhR3XCQBunTisljzNkNese2T1FiV8jP79+dqg= -github.com/elazarl/goproxy v0.0.0-20201021153353-00ad82a08272/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy v0.0.0-20210110162100-a92cc753f88e h1:/cwV7t2xezilMljIftb7WlFtzGANRCnoOhPjtl2ifcs= +github.com/elazarl/goproxy v0.0.0-20210110162100-a92cc753f88e/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= -github.com/elazarl/goproxy/ext v0.0.0-20201021153353-00ad82a08272 h1:xOHQWEGsftkjjFV2KIrC9vOz+iOinvZ7H6EAjSznqUk= -github.com/elazarl/goproxy/ext v0.0.0-20201021153353-00ad82a08272/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= +github.com/elazarl/goproxy/ext v0.0.0-20210110162100-a92cc753f88e h1:CQn2/8fi3kmpT9BTiHEELgdxAOQNVZc9GoPA4qnQzrs= +github.com/elazarl/goproxy/ext v0.0.0-20210110162100-a92cc753f88e/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -95,9 +112,9 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= 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/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -105,15 +122,25 @@ github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgO 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-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= +github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= 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-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -123,6 +150,7 @@ github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71 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/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -159,9 +187,9 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -192,6 +220,7 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= @@ -199,12 +228,15 @@ github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jinzhu/copier v0.2.4 h1:dT3tI+8GzU8DjJFCj9mLYtjfRtUmK7edauduQdcZCpI= +github.com/jinzhu/copier v0.2.4/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -214,38 +246,33 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8 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/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 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/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= -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/magiconair/properties v1.8.4 h1:8KGKTcQQGm0Kv7vEbKFErAoAOFyyacLStRtQSeYtvkY= -github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= -github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= -github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo= -github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= -github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs= -github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.38 h1:MtIY+fmHUVVgv1AXzmKMWcwdCYxTRPG1EDjpqF4RCEw= +github.com/miekg/dns v1.1.38/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -255,14 +282,14 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 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.4.0 h1:7ks8ZkOP5/ujthUsT07rNv+nkLXCQWKNHuwzOAesEks= -github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/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/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE= +github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= @@ -276,12 +303,22 @@ github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtb github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= -github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -295,17 +332,15 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= 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.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= -github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -347,14 +382,8 @@ github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFB github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.3.0 h1:Uehi/mxLK0eiUc0H0++5tpMGTexB8wZ598MIgU8VpDM= -github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 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= @@ -365,36 +394,29 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg 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/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 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 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= 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.5.1 h1:VHu76Lk0LSP1x254maIu2bplkWpfBWI+B+6fdoZprcg= -github.com/spf13/afero v1.5.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= 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.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -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/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= -github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/cobra v1.1.2 h1:frHO75w/dH7kEc+e2KYZZKY4+PLrp39OqI77oB8m0KQ= +github.com/spf13/cobra v1.1.2/go.mod h1:ZjwqWkCg0LnXvLRIfTLdB4Y/MCO3gMHHJ2KFxQZy4xE= 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.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 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.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= @@ -409,15 +431,19 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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/testcontainers/testcontainers-go v0.9.0 h1:ZyftCfROjGrKlxk3MOUn2DAzWrUtzY/mj17iAkdUIvI= +github.com/testcontainers/testcontainers-go v0.9.0/go.mod h1:b22BFXhRbg4PJmeMVWh6ftqjyZHgiIl3w274e9r3C2E= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 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/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 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.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= @@ -425,6 +451,7 @@ go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -452,13 +479,10 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/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-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -493,7 +517,6 @@ 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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/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-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= @@ -503,11 +526,10 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7 golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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= @@ -536,37 +558,26 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e h1:AyodaIpKjppX+cBfTASF2E1US3H2JFBj920Ot3rtDjs= golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo= -golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/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-20180810170437-e96c4e24768d/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/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= @@ -590,6 +601,7 @@ golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktyp golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/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/tools v0.0.0-20200103221440-774c71fcf114 h1:DnSr2mCsxyCE6ZgIkmcWUQY2R5cH/6wL7eIxEmQOMSE= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/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= @@ -619,8 +631,8 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgX google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506 h1:uLBY0yHDCj2PMQ98KWDSIDFwn9zK2zh+tgWtbvPPBjI= -google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98 h1:LCO0fg4kb6WwkXQXRQQgUYsFeFb5taTX5WAx5O/Vt28= +google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= @@ -633,10 +645,11 @@ google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc/examples v0.0.0-20210210171350-ce29c77c5fa1 h1:dtHfS+TF/MZg8X+DaxrDGiq+5z8OrIJJc1yX+8cQ0cU= +google.golang.org/grpc/examples v0.0.0-20210210171350-ce29c77c5fa1/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -659,13 +672,13 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS 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/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= 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.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= -gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= @@ -680,10 +693,11 @@ gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v0.0.0-20181223230014-1083505acf35 h1:zpdCK+REwbk+rqjJmHhiCN6iBIigrZ39glqSF0P3KF0= +gotest.tools v0.0.0-20181223230014-1083505acf35/go.mod h1:R//lfYlUuTOTfblYI3lGoAAAebUdzjvbmQsuB7Ykd90= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/app/app.go b/internal/app/app.go index aeb0494..6a16601 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -10,15 +10,12 @@ import ( "github.com/spf13/cobra" "gitlab.com/inetmock/inetmock/internal/endpoint" - "gitlab.com/inetmock/inetmock/pkg/api" "gitlab.com/inetmock/inetmock/pkg/audit" "gitlab.com/inetmock/inetmock/pkg/audit/sink" "gitlab.com/inetmock/inetmock/pkg/cert" - "gitlab.com/inetmock/inetmock/pkg/config" "gitlab.com/inetmock/inetmock/pkg/health" "gitlab.com/inetmock/inetmock/pkg/logging" "gitlab.com/inetmock/inetmock/pkg/path" - "go.uber.org/multierr" "go.uber.org/zap" ) @@ -41,12 +38,12 @@ const ( ) type App interface { - api.PluginContext EventStream() audit.EventStream - Config() config.Config + Config() Config Checker() health.Checker - EndpointManager() endpoint.EndpointManager - HandlerRegistry() api.HandlerRegistry + Logger() logging.Logger + EndpointManager() endpoint.Orchestrator + HandlerRegistry() endpoint.HandlerRegistry Context() context.Context RootCommand() *cobra.Command MustRun() @@ -58,7 +55,7 @@ type App interface { // WithHandlerRegistry builds up the handler registry // requires nothing - WithHandlerRegistry(registrations ...api.Registration) App + WithHandlerRegistry(registrations ...endpoint.Registration) App // WithHealthChecker adds the health checker mechanism // requires nothing @@ -115,12 +112,12 @@ func (a *app) Logger() logging.Logger { return val.(logging.Logger) } -func (a *app) Config() config.Config { +func (a *app) Config() Config { val := a.ctx.Value(configKey) if val == nil { return nil } - return val.(config.Config) + return val.(Config) } func (a *app) CertStore() cert.Store { @@ -139,12 +136,12 @@ func (a *app) Checker() health.Checker { return val.(health.Checker) } -func (a *app) EndpointManager() endpoint.EndpointManager { +func (a *app) EndpointManager() endpoint.Orchestrator { val := a.ctx.Value(endpointManagerKey) if val == nil { return nil } - return val.(endpoint.EndpointManager) + return val.(endpoint.Orchestrator) } func (a *app) Audit() audit.Emitter { @@ -163,12 +160,12 @@ func (a *app) EventStream() audit.EventStream { return val.(audit.EventStream) } -func (a *app) HandlerRegistry() api.HandlerRegistry { +func (a *app) HandlerRegistry() endpoint.HandlerRegistry { val := a.ctx.Value(handlerRegistryKey) if val == nil { return nil } - return val.(api.HandlerRegistry) + return val.(endpoint.HandlerRegistry) } func (a *app) Context() context.Context { @@ -192,8 +189,8 @@ func (a *app) WithCommands(cmds ...*cobra.Command) App { // WithHandlerRegistry builds up the handler registry // requires nothing -func (a *app) WithHandlerRegistry(registrations ...api.Registration) App { - registry := api.NewHandlerRegistry() +func (a *app) WithHandlerRegistry(registrations ...endpoint.Registration) App { + registry := endpoint.NewHandlerRegistry() for _, registration := range registrations { if err := registration(registry); err != nil { @@ -242,11 +239,12 @@ func (a *app) WithLogger() App { // requires WithHandlerRegistry, WithHealthChecker and WithLogger func (a *app) WithEndpointManager() App { a.lateInitTasks = append(a.lateInitTasks, func(_ *cobra.Command, _ []string) (err error) { - epMgr := endpoint.NewEndpointManager( + epMgr := endpoint.NewOrchestrator( + a.Context(), + a.CertStore(), a.HandlerRegistry(), - a.Logger().Named("EndpointManager"), - a.Checker(), - a, + a.Audit(), + a.Logger().Named("Orchestrator"), ) a.ctx = context.WithValue(a.ctx, endpointManagerKey, epMgr) @@ -261,7 +259,7 @@ func (a *app) WithCertStore() App { a.lateInitTasks = append(a.lateInitTasks, func(cmd *cobra.Command, args []string) (err error) { var certStore cert.Store if certStore, err = cert.NewDefaultStore( - a.Config(), + a.Config().TLSConfig(), a.Logger().Named("CertStore"), ); err != nil { return @@ -310,7 +308,7 @@ func (a *app) WithEventStream() App { // requires nothing func (a *app) WithConfig() App { a.lateInitTasks = append(a.lateInitTasks, func(cmd *cobra.Command, _ []string) (err error) { - cfg := config.CreateConfig(cmd.Flags()) + cfg := CreateConfig() if err = cfg.ReadConfig(configFilePath); err != nil { return } @@ -343,7 +341,9 @@ func NewApp(name, short string) App { a.rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) (err error) { for _, initTask := range a.lateInitTasks { - err = multierr.Append(err, initTask(cmd, args)) + if err = initTask(cmd, args); err != nil { + return + } } return } diff --git a/pkg/config/config.go b/internal/app/config.go similarity index 65% rename from pkg/config/config.go rename to internal/app/config.go index 705af5d..e0e828d 100644 --- a/pkg/config/config.go +++ b/internal/app/config.go @@ -1,20 +1,17 @@ -package config +package app import ( "strings" - "github.com/spf13/pflag" "github.com/spf13/viper" - "gitlab.com/inetmock/inetmock/pkg/logging" + "gitlab.com/inetmock/inetmock/internal/endpoint" + "gitlab.com/inetmock/inetmock/pkg/cert" "gitlab.com/inetmock/inetmock/pkg/path" - "go.uber.org/zap" ) -func CreateConfig(flags *pflag.FlagSet) Config { - logger, _ := logging.CreateLogger() +func CreateConfig() Config { configInstance := &config{ - logger: logger.Named("Config"), - cfg: viper.New(), + cfg: viper.New(), } configInstance.cfg.SetConfigName("config") @@ -23,7 +20,6 @@ func CreateConfig(flags *pflag.FlagSet) Config { configInstance.cfg.AddConfigPath("$HOME/.inetmock") configInstance.cfg.AddConfigPath(".") configInstance.cfg.SetEnvPrefix("INetMock") - _ = configInstance.cfg.BindPFlags(flags) configInstance.cfg.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) configInstance.cfg.AutomaticEnv() @@ -41,17 +37,15 @@ func CreateConfig(flags *pflag.FlagSet) Config { type Config interface { ReadConfig(configFilePath string) error ReadConfigString(config, format string) error - Viper() *viper.Viper - TLSConfig() CertOptions + TLSConfig() cert.CertOptions APIConfig() RPC - EndpointConfigs() map[string]EndpointConfig + ListenerSpecs() map[string]endpoint.ListenerSpec } type config struct { cfg *viper.Viper - logger logging.Logger - TLS CertOptions - Endpoints map[string]EndpointConfig + TLS cert.CertOptions + Listeners map[string]endpoint.ListenerSpec API RPC } @@ -69,30 +63,23 @@ func (c *config) ReadConfigString(config, format string) (err error) { return } -func (c *config) EndpointConfigs() map[string]EndpointConfig { - return c.Endpoints +func (c config) ListenerSpecs() map[string]endpoint.ListenerSpec { + return c.Listeners } -func (c *config) TLSConfig() CertOptions { +func (c config) TLSConfig() cert.CertOptions { return c.TLS } -func (c *config) Viper() *viper.Viper { - return c.cfg -} - 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), - ) c.cfg.SetConfigFile(configFilePath) } if err = c.cfg.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { err = nil - c.logger.Warn("failed to load config") + } else { + return } } diff --git a/internal/app/config_test.go b/internal/app/config_test.go new file mode 100644 index 0000000..ca491c2 --- /dev/null +++ b/internal/app/config_test.go @@ -0,0 +1,70 @@ +package app + +import ( + "reflect" + "testing" + + "gitlab.com/inetmock/inetmock/internal/endpoint" +) + +func Test_config_ReadConfig(t *testing.T) { + type args struct { + config string + } + tests := []struct { + name string + args args + wantListeners map[string]endpoint.ListenerSpec + wantErr bool + }{ + { + name: "Test endpoints config", + args: args{ + //language=yaml + config: ` +listeners: + tcp_80: + name: '' + protocol: tcp + listenAddress: '' + port: 80 + tcp_443: + name: '' + protocol: tcp + listenAddress: '' + port: 443 +`, + }, + wantListeners: map[string]endpoint.ListenerSpec{ + "tcp_80": { + Name: "", + Protocol: "tcp", + Address: "", + Port: 80, + Endpoints: nil, + }, + "tcp_443": { + Name: "", + Protocol: "tcp", + Address: "", + Port: 443, + Endpoints: nil, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := CreateConfig() + if err := cfg.ReadConfigString(tt.args.config, "yaml"); (err != nil) != tt.wantErr { + t.Errorf("ReadConfig() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !reflect.DeepEqual(tt.wantListeners, cfg.ListenerSpecs()) { + t.Errorf("want = %v, got = %v", tt.wantListeners, cfg.ListenerSpecs()) + } + }) + } +} diff --git a/internal/app/constants.go b/internal/app/constants.go new file mode 100644 index 0000000..6e55d7d --- /dev/null +++ b/internal/app/constants.go @@ -0,0 +1,6 @@ +package app + +const ( + EndpointsKey = "endpoints" + OptionsKey = "options" +) diff --git a/pkg/config/defaults.go b/internal/app/defaults.go similarity index 94% rename from pkg/config/defaults.go rename to internal/app/defaults.go index d856839..0346ece 100644 --- a/pkg/config/defaults.go +++ b/internal/app/defaults.go @@ -1,4 +1,4 @@ -package config +package app var ( registeredDefaults = make(map[string]interface{}) diff --git a/pkg/config/rpc.go b/internal/app/rpc.go similarity index 93% rename from pkg/config/rpc.go rename to internal/app/rpc.go index be698a0..538efe0 100644 --- a/pkg/config/rpc.go +++ b/internal/app/rpc.go @@ -1,4 +1,4 @@ -package config +package app import "net/url" diff --git a/pkg/config/rpc_test.go b/internal/app/rpc_test.go similarity index 98% rename from pkg/config/rpc_test.go rename to internal/app/rpc_test.go index bdf8b6f..a5fea12 100644 --- a/pkg/config/rpc_test.go +++ b/internal/app/rpc_test.go @@ -1,4 +1,4 @@ -package config +package app import ( "net/url" diff --git a/internal/endpoint/api.go b/internal/endpoint/api.go new file mode 100644 index 0000000..895c302 --- /dev/null +++ b/internal/endpoint/api.go @@ -0,0 +1,31 @@ +//go:generate mockgen -source=$GOFILE -destination=./../../internal/mock/endpoint/protocol_handler.mock.go -package=endpoint_mock + +package endpoint + +import ( + "context" + + "github.com/soheilhy/cmux" + "gitlab.com/inetmock/inetmock/pkg/audit" + "gitlab.com/inetmock/inetmock/pkg/cert" + "gitlab.com/inetmock/inetmock/pkg/logging" +) + +type Lifecycle interface { + Name() string + Logger() logging.Logger + CertStore() cert.Store + Audit() audit.Emitter + Context() context.Context + Uplink() Uplink + UnmarshalOptions(cfg interface{}) error +} + +type ProtocolHandler interface { + Start(ctx Lifecycle) error +} + +type MultiplexHandler interface { + ProtocolHandler + Matchers() []cmux.Matcher +} diff --git a/internal/endpoint/endpoint.go b/internal/endpoint/endpoint.go index 9dd314a..73405ea 100644 --- a/internal/endpoint/endpoint.go +++ b/internal/endpoint/endpoint.go @@ -1,55 +1,42 @@ -//go:generate mockgen -source=$GOFILE -destination=./../../internal/mock/endpoints/endpoint.mock.go -package=endpoints_mock package endpoint import ( "context" + "time" - "github.com/google/uuid" - "gitlab.com/inetmock/inetmock/pkg/api" - "gitlab.com/inetmock/inetmock/pkg/config" + "go.uber.org/zap" ) -type Endpoint interface { - Id() uuid.UUID - Start(ctx api.PluginContext) error - Shutdown(ctx context.Context) error - Name() string - Handler() string - Listen() string - Port() uint16 +const ( + startupTimeoutDuration = 100 * time.Millisecond +) + +type Endpoint struct { + Spec + name string + uplink Uplink } -type endpoint struct { - id uuid.UUID - name string - handler api.ProtocolHandler - config config.HandlerConfig -} +func (e Endpoint) Start(lifecycle Lifecycle) (err error) { + startupResult := make(chan error) + ctx, cancel := context.WithTimeout(lifecycle.Context(), startupTimeoutDuration) + defer cancel() -func (e endpoint) Id() uuid.UUID { - return e.id -} + go func() { + defer func() { + if r := recover(); r != nil { + lifecycle.Logger().Fatal("Startup error recovered", zap.Any("recovered", r)) + } + }() -func (e endpoint) Name() string { - return e.name -} + startupResult <- e.Handler.Start(lifecycle) + }() -func (e endpoint) Handler() string { - return e.config.HandlerName -} + select { + case err = <-startupResult: + case <-ctx.Done(): + err = ErrStartupTimeout + } -func (e endpoint) Listen() string { - return e.config.ListenAddress -} - -func (e endpoint) Port() uint16 { - return e.config.Port -} - -func (e *endpoint) Start(ctx api.PluginContext) (err error) { - return e.handler.Start(ctx, e.config) -} - -func (e *endpoint) Shutdown(ctx context.Context) (err error) { - return e.handler.Shutdown(ctx) + return } diff --git a/internal/endpoint/endpoint_manager.go b/internal/endpoint/endpoint_manager.go deleted file mode 100644 index 174a9d3..0000000 --- a/internal/endpoint/endpoint_manager.go +++ /dev/null @@ -1,179 +0,0 @@ -package endpoint - -import ( - "context" - "fmt" - "sync" - "time" - - "github.com/google/uuid" - "gitlab.com/inetmock/inetmock/pkg/api" - "gitlab.com/inetmock/inetmock/pkg/config" - "gitlab.com/inetmock/inetmock/pkg/health" - "gitlab.com/inetmock/inetmock/pkg/logging" - "go.uber.org/zap" -) - -const ( - startupTimeoutDuration = 100 * time.Millisecond -) - -type EndpointManager interface { - RegisteredEndpoints() []Endpoint - StartedEndpoints() []Endpoint - CreateEndpoint(name string, multiHandlerConfig config.EndpointConfig) error - StartEndpoints() - ShutdownEndpoints() -} - -func NewEndpointManager(registry api.HandlerRegistry, logging logging.Logger, checker health.Checker, pluginContext api.PluginContext) EndpointManager { - return &endpointManager{ - registry: registry, - logger: logging, - checker: checker, - pluginContext: pluginContext, - } -} - -type endpointManager struct { - registry api.HandlerRegistry - logger logging.Logger - checker health.Checker - pluginContext api.PluginContext - registeredEndpoints []Endpoint - properlyStartedEndpoints []Endpoint -} - -func (e endpointManager) RegisteredEndpoints() []Endpoint { - return e.registeredEndpoints -} - -func (e endpointManager) StartedEndpoints() []Endpoint { - return e.properlyStartedEndpoints -} - -func (e *endpointManager) CreateEndpoint(name string, endpointConfig config.EndpointConfig) error { - for _, handlerConfig := range endpointConfig.HandlerConfigs() { - if handler, ok := e.registry.HandlerForName(endpointConfig.Handler); ok { - e.registeredEndpoints = append(e.registeredEndpoints, &endpoint{ - id: uuid.New(), - name: name, - handler: handler, - config: handlerConfig, - }) - } else { - return fmt.Errorf("no matching handler registered for names %s", endpointConfig.Handler) - } - } - - 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, e.pluginContext, endpointLogger); ok { - _ = e.checker.RegisterCheck( - endpointComponentName(endpoint), - health.StaticResultCheckWithMessage(health.HEALTHY, "Successfully started"), - ) - e.properlyStartedEndpoints = append(e.properlyStartedEndpoints, endpoint) - endpointLogger.Info("successfully started endpoint") - } else { - _ = e.checker.RegisterCheck( - endpointComponentName(endpoint), - health.StaticResultCheckWithMessage(health.UNHEALTHY, "failed to start"), - ) - 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() { - waitGroup := new(sync.WaitGroup) - waitGroup.Add(len(e.properlyStartedEndpoints)) - - parentCtx, _ := context.WithTimeout(context.Background(), shutdownTimeout) - - perHandlerTimeout := e.shutdownTimePerEndpoint() - - for _, endpoint := range e.properlyStartedEndpoints { - ctx, _ := context.WithTimeout(parentCtx, perHandlerTimeout) - endpointLogger := e.logger.With( - zap.String("endpoint", endpoint.Name()), - ) - endpointLogger.Info("Triggering shutdown of endpoint") - go shutdownEndpoint(ctx, endpoint, endpointLogger, waitGroup) - } - - waitGroup.Wait() -} - -func startEndpoint(ep Endpoint, ctx api.PluginContext, logger logging.Logger) (success bool) { - startSuccessful := make(chan bool) - - go func() { - defer func() { - if r := recover(); r != nil { - logger.Fatal( - "recovered panic during startup of endpoint", - zap.Any("recovered", r), - ) - startSuccessful <- false - } - }() - - if err := ep.Start(ctx); err != nil { - logger.Error( - "failed to start endpoint", - zap.Error(err), - ) - startSuccessful <- false - } else { - startSuccessful <- true - } - }() - - select { - case success = <-startSuccessful: - case <-time.After(startupTimeoutDuration): - success = false - } - - return -} - -func shutdownEndpoint(ctx context.Context, 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(ctx); err != nil { - logger.Error( - "Failed to shutdown endpoint", - zap.Error(err), - ) - } -} - -func (e *endpointManager) shutdownTimePerEndpoint() time.Duration { - return time.Duration((float64(shutdownTimeout) * 0.9) / float64(len(e.properlyStartedEndpoints))) -} - -func endpointComponentName(ep Endpoint) string { - return fmt.Sprintf("endpoint_%s", ep.Name()) -} diff --git a/internal/endpoint/endpoint_manager_test.go b/internal/endpoint/endpoint_manager_test.go deleted file mode 100644 index 0c71cf4..0000000 --- a/internal/endpoint/endpoint_manager_test.go +++ /dev/null @@ -1,139 +0,0 @@ -package endpoint - -import ( - "reflect" - "testing" - - "github.com/golang/mock/gomock" - api_mock "gitlab.com/inetmock/inetmock/internal/mock/api" - logging_mock "gitlab.com/inetmock/inetmock/internal/mock/logging" - plugins_mock "gitlab.com/inetmock/inetmock/internal/mock/plugins" - "gitlab.com/inetmock/inetmock/pkg/api" - "gitlab.com/inetmock/inetmock/pkg/config" - "gitlab.com/inetmock/inetmock/pkg/health" - "gitlab.com/inetmock/inetmock/pkg/logging" -) - -func Test_endpointManager_CreateEndpoint(t *testing.T) { - type fields struct { - logger logging.Logger - registry api.HandlerRegistry - } - type args struct { - name string - multiHandlerConfig config.EndpointConfig - } - 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)) - }(), - registry: func() api.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.EndpointConfig{ - Handler: "sampleHandler", - Ports: []uint16{80}, - ListenAddress: "0.0.0.0", - }, - }, - }, - { - name: "Test add unknown handler", - wantErr: true, - wantEndpoints: 0, - fields: fields{ - logger: func() logging.Logger { - return logging_mock.NewMockLogger(gomock.NewController(t)) - }(), - registry: func() api.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.EndpointConfig{ - Handler: "sampleHandler", - Ports: []uint16{80}, - ListenAddress: "0.0.0.0", - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - e := NewEndpointManager(tt.fields.registry, tt.fields.logger, health.New(), nil) - 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) - } - }) - } -} - -func Test_endpointManager_StartedEndpoints(t *testing.T) { - type fields struct { - logger logging.Logger - registeredEndpoints []Endpoint - properlyStartedEndpoints []Endpoint - registry api.HandlerRegistry - } - tests := []struct { - name string - fields fields - want []Endpoint - }{ - { - name: "", - fields: fields{}, - want: 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, - } - if got := e.StartedEndpoints(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("StartedEndpoints() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/internal/endpoint/handler/dns/mock/fallback.go b/internal/endpoint/handler/dns/mock/fallback.go index 8fb39a8..3aa790a 100644 --- a/internal/endpoint/handler/dns/mock/fallback.go +++ b/internal/endpoint/handler/dns/mock/fallback.go @@ -7,38 +7,45 @@ import ( "net" "unsafe" - "github.com/spf13/viper" + "github.com/mitchellh/mapstructure" ) const ( randomIPStrategyName = "random" incrementalIPStrategyName = "incremental" - startIPConfigKey = "startIP" ) var ( - fallbackStrategies map[string]ResolverFactory + defaultStartIPIncrementalStrategy = net.ParseIP("10.10.0.1") + fallbackStrategies = map[string]ResolverFactory{ + incrementalIPStrategyName: func(args map[string]interface{}) ResolverFallback { + tmp := struct { + StartIP string + }{} + var startIp net.IP + if err := mapstructure.Decode(args, &tmp); err == nil { + startIp = net.ParseIP(tmp.StartIP) + } + if startIp == nil || len(startIp) == 0 { + startIp = defaultStartIPIncrementalStrategy + } + return &incrementalIPFallback{ + latestIp: ipToInt32(startIp), + } + }, + randomIPStrategyName: func(map[string]interface{}) ResolverFallback { + return &randomIPFallback{} + }, + } ) -type ResolverFactory func(conf *viper.Viper) ResolverFallback +type ResolverFactory func(args map[string]interface{}) ResolverFallback -func init() { - fallbackStrategies = make(map[string]ResolverFactory) - fallbackStrategies[incrementalIPStrategyName] = func(conf *viper.Viper) ResolverFallback { - return &incrementalIPFallback{ - latestIp: ipToInt32(net.ParseIP(conf.GetString(startIPConfigKey))), - } - } - fallbackStrategies[randomIPStrategyName] = func(conf *viper.Viper) ResolverFallback { - return &randomIPFallback{} - } -} - -func CreateResolverFallback(name string, config *viper.Viper) ResolverFallback { +func CreateResolverFallback(name string, args map[string]interface{}) ResolverFallback { if factory, ok := fallbackStrategies[name]; ok { - return factory(config) + return factory(args) } else { - return fallbackStrategies[randomIPStrategyName](config) + return fallbackStrategies[randomIPStrategyName](args) } } diff --git a/internal/endpoint/handler/dns/mock/handler.go b/internal/endpoint/handler/dns/mock/handler.go index 5e05432..0958ae3 100644 --- a/internal/endpoint/handler/dns/mock/handler.go +++ b/internal/endpoint/handler/dns/mock/handler.go @@ -2,36 +2,35 @@ package mock import ( "context" + "time" "github.com/miekg/dns" - "gitlab.com/inetmock/inetmock/pkg/api" - "gitlab.com/inetmock/inetmock/pkg/config" + "gitlab.com/inetmock/inetmock/internal/endpoint" "gitlab.com/inetmock/inetmock/pkg/logging" "go.uber.org/zap" ) type dnsHandler struct { logger logging.Logger - dnsServer []*dns.Server + dnsServer *dns.Server } -func (d *dnsHandler) Start(pluginCtx api.PluginContext, config config.HandlerConfig) (err error) { +func (d *dnsHandler) Start(lifecycle endpoint.Lifecycle) (err error) { var options dnsOptions - if options, err = loadFromConfig(config.Options); err != nil { + if options, err = loadFromConfig(lifecycle); err != nil { return } - listenAddr := config.ListenAddr() - d.logger = pluginCtx.Logger().With( - zap.String("handler_name", config.HandlerName), - zap.String("address", listenAddr), + d.logger = lifecycle.Logger().With( + zap.String("handler_name", lifecycle.Name()), + zap.String("address", lifecycle.Uplink().Addr().String()), ) handler := ®exHandler{ - handlerName: config.HandlerName, + handlerName: lifecycle.Name(), fallback: options.Fallback, - logger: pluginCtx.Logger(), - auditEmitter: pluginCtx.Audit(), + logger: lifecycle.Logger(), + auditEmitter: lifecycle.Audit(), } for _, rule := range options.Rules { @@ -43,31 +42,24 @@ func (d *dnsHandler) Start(pluginCtx api.PluginContext, config config.HandlerCon handler.AddRule(rule) } - d.logger = d.logger.With( - zap.String("address", listenAddr), - ) - - d.dnsServer = []*dns.Server{ - { - Addr: listenAddr, - Net: "udp", - Handler: handler, - }, - { - Addr: listenAddr, - Net: "tcp", - Handler: handler, - }, + if lifecycle.Uplink().Listener != nil { + d.dnsServer = &dns.Server{ + Listener: lifecycle.Uplink().Listener, + Handler: handler, + } + } else { + d.dnsServer = &dns.Server{ + PacketConn: lifecycle.Uplink().PacketConn, + Handler: handler, + } } - for _, dnsServer := range d.dnsServer { - go d.startServer(dnsServer) - } + go d.startServer() return } -func (d *dnsHandler) startServer(dnsServer *dns.Server) { - if err := dnsServer.ListenAndServe(); err != nil { +func (d *dnsHandler) startServer() { + if err := d.dnsServer.ActivateAndServe(); err != nil { d.logger.Error( "failed to start DNS server listener", zap.Error(err), @@ -75,15 +67,11 @@ func (d *dnsHandler) startServer(dnsServer *dns.Server) { } } -func (d *dnsHandler) Shutdown(ctx context.Context) error { - d.logger.Info("shutting down DNS mock") - for _, dnsServer := range d.dnsServer { - if err := dnsServer.ShutdownContext(ctx); err != nil { - d.logger.Error( - "failed to shutdown server", - zap.Error(err), - ) - } +func (d *dnsHandler) shutdownOnEnd(ctx context.Context) { + <-ctx.Done() + shutdownCtx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + if err := d.dnsServer.ShutdownContext(shutdownCtx); err != nil { + d.logger.Error("failed to shutdown DNS server", zap.Error(err)) } - return nil } diff --git a/internal/endpoint/handler/dns/mock/handler_bench_test.go b/internal/endpoint/handler/dns/mock/handler_bench_test.go new file mode 100644 index 0000000..bb10ab0 --- /dev/null +++ b/internal/endpoint/handler/dns/mock/handler_bench_test.go @@ -0,0 +1,77 @@ +package mock + +import ( + "context" + "fmt" + "math/rand" + "net" + "strings" + "testing" + "time" + + "github.com/docker/go-connections/nat" + "github.com/testcontainers/testcontainers-go" + "gitlab.com/inetmock/inetmock/internal/test/integration" +) + +const ( + charSet = "abcdedfghijklmnopqrstABCDEFGHIJKLMNOP" +) + +func init() { + rand.Seed(time.Now().Unix()) +} + +func Benchmark_dnsHandler(b *testing.B) { + var err error + var endpoint string + if endpoint, err = setupContainer(b, "53/udp"); err != nil { + b.Errorf("setupContainer() error = %v", err) + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + resolv := resolver(endpoint) + for pb.Next() { + lookupCtx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + _, err := resolv.LookupHost(lookupCtx, fmt.Sprintf("www.%s.com", randomString(8))) + cancel() + if err != nil { + b.Errorf("LookupHost() error = %v", err) + } + } + }) + +} + +func randomString(length int) (result string) { + buffer := strings.Builder{} + for i := 0; i < length; i++ { + buffer.WriteByte(charSet[rand.Intn(len(charSet))]) + } + return buffer.String() +} + +func setupContainer(b *testing.B, port string) (httpEndpoint string, err error) { + b.Helper() + + startupCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + var inetMockContainer testcontainers.Container + if inetMockContainer, err = integration.SetupINetMockContainer(startupCtx, b, port); err != nil { + return + } + + httpEndpoint, err = inetMockContainer.PortEndpoint(startupCtx, nat.Port(port), "") + return +} + +func resolver(endpoint string) net.Resolver { + return net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (conn net.Conn, err error) { + dialer := net.Dialer{} + return dialer.DialContext(ctx, "udp", endpoint) + }, + } +} diff --git a/internal/endpoint/handler/dns/mock/protocol_options.go b/internal/endpoint/handler/dns/mock/protocol_options.go index ea74351..754602a 100644 --- a/internal/endpoint/handler/dns/mock/protocol_options.go +++ b/internal/endpoint/handler/dns/mock/protocol_options.go @@ -4,11 +4,7 @@ import ( "net" "regexp" - "github.com/spf13/viper" -) - -const ( - fallbackArgsConfigKey = "fallback.args" + "gitlab.com/inetmock/inetmock/internal/endpoint" ) type resolverRule struct { @@ -21,7 +17,7 @@ type dnsOptions struct { Fallback ResolverFallback } -func loadFromConfig(config *viper.Viper) (options dnsOptions, err error) { +func loadFromConfig(lifecycle endpoint.Lifecycle) (options dnsOptions, err error) { type rule struct { Pattern string Response string @@ -29,6 +25,7 @@ func loadFromConfig(config *viper.Viper) (options dnsOptions, err error) { type fallback struct { Strategy string + Args map[string]interface{} } opts := struct { @@ -36,7 +33,7 @@ func loadFromConfig(config *viper.Viper) (options dnsOptions, err error) { Fallback fallback }{} - err = config.Unmarshal(&opts) + err = lifecycle.UnmarshalOptions(&opts) for _, rule := range opts.Rules { var err error @@ -53,7 +50,7 @@ func loadFromConfig(config *viper.Viper) (options dnsOptions, err error) { options.Fallback = CreateResolverFallback( opts.Fallback.Strategy, - config.Sub(fallbackArgsConfigKey), + opts.Fallback.Args, ) return diff --git a/internal/endpoint/handler/dns/mock/register.go b/internal/endpoint/handler/dns/mock/register.go index 312f3e6..a7f3e22 100644 --- a/internal/endpoint/handler/dns/mock/register.go +++ b/internal/endpoint/handler/dns/mock/register.go @@ -2,7 +2,7 @@ package mock import ( "github.com/prometheus/client_golang/prometheus" - "gitlab.com/inetmock/inetmock/pkg/api" + "gitlab.com/inetmock/inetmock/internal/endpoint" "gitlab.com/inetmock/inetmock/pkg/metrics" ) @@ -17,7 +17,7 @@ var ( requestDurationHistogram *prometheus.HistogramVec ) -func AddDNSMock(registry api.HandlerRegistry) (err error) { +func AddDNSMock(registry endpoint.HandlerRegistry) (err error) { if totalHandledRequestsCounter, err = metrics.Counter( name, "handled_requests_total", @@ -46,7 +46,7 @@ func AddDNSMock(registry api.HandlerRegistry) (err error) { return } - registry.RegisterHandler(name, func() api.ProtocolHandler { + registry.RegisterHandler(name, func() endpoint.ProtocolHandler { return &dnsHandler{} }) diff --git a/internal/endpoint/handler/http/audit.go b/internal/endpoint/handler/http/audit.go index 3a8d993..deb8bcf 100644 --- a/internal/endpoint/handler/http/audit.go +++ b/internal/endpoint/handler/http/audit.go @@ -23,11 +23,11 @@ func EventFromRequest(request *http.Request, app audit.AppProtocol) audit.Event ProtocolDetails: httpDetails, } - if request.TLS != nil { + if state, ok := tlsConnectionState(request.Context()); ok { ev.TLS = &audit.TLSDetails{ - Version: audit.TLSVersionToEntity(request.TLS.Version).String(), - CipherSuite: tls.CipherSuiteName(request.TLS.CipherSuite), - ServerName: request.TLS.ServerName, + Version: audit.TLSVersionToEntity(state.Version).String(), + CipherSuite: tls.CipherSuiteName(state.CipherSuite), + ServerName: state.ServerName, } } diff --git a/internal/endpoint/handler/http/conn_context.go b/internal/endpoint/handler/http/conn_context.go index 3f9c6da..85c29be 100644 --- a/internal/endpoint/handler/http/conn_context.go +++ b/internal/endpoint/handler/http/conn_context.go @@ -2,7 +2,10 @@ package http import ( "context" + "crypto/tls" "net" + + "github.com/soheilhy/cmux" ) type httpContextKey string @@ -10,14 +13,35 @@ type httpContextKey string const ( remoteAddrKey httpContextKey = "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/context/remoteAddr" localAddrKey httpContextKey = "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/context/localAddr" + tlsStateKey httpContextKey = "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/context/tlsState" ) func StoreConnPropertiesInContext(ctx context.Context, c net.Conn) context.Context { ctx = context.WithValue(ctx, remoteAddrKey, c.RemoteAddr()) ctx = context.WithValue(ctx, localAddrKey, c.LocalAddr()) + ctx = addTLSConnectionStateToContext(ctx, c) return ctx } +func addTLSConnectionStateToContext(ctx context.Context, c net.Conn) context.Context { + switch subConn := c.(type) { + case *tls.Conn: + return context.WithValue(ctx, tlsStateKey, subConn.ConnectionState()) + case *cmux.MuxConn: + return addTLSConnectionStateToContext(ctx, subConn.Conn) + default: + return ctx + } +} + +func tlsConnectionState(ctx context.Context) (tls.ConnectionState, bool) { + val := ctx.Value(tlsStateKey) + if val == nil { + return tls.ConnectionState{}, false + } + return val.(tls.ConnectionState), true +} + func localAddr(ctx context.Context) net.Addr { val := ctx.Value(localAddrKey) if val == nil { diff --git a/internal/endpoint/handler/http/mock/handler.go b/internal/endpoint/handler/http/mock/handler.go index a4a8805..5a1467b 100644 --- a/internal/endpoint/handler/http/mock/handler.go +++ b/internal/endpoint/handler/http/mock/handler.go @@ -2,14 +2,13 @@ package mock import ( "context" - "crypto/tls" "errors" - "fmt" + "net" "net/http" + "github.com/soheilhy/cmux" + "gitlab.com/inetmock/inetmock/internal/endpoint" imHttp "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http" - "gitlab.com/inetmock/inetmock/pkg/api" - "gitlab.com/inetmock/inetmock/pkg/config" "gitlab.com/inetmock/inetmock/pkg/logging" "go.uber.org/zap" ) @@ -25,71 +24,62 @@ type httpHandler struct { server *http.Server } -func (p *httpHandler) Start(ctx api.PluginContext, config config.HandlerConfig) (err error) { - p.logger = ctx.Logger().With( +func (p *httpHandler) Matchers() []cmux.Matcher { + return []cmux.Matcher{cmux.HTTP1()} +} + +func (p *httpHandler) Start(lifecycle endpoint.Lifecycle) (err error) { + p.logger = lifecycle.Logger().With( zap.String("protocol_handler", name), ) var options httpOptions - if options, err = loadFromConfig(config.Options); err != nil { + if options, err = loadFromConfig(lifecycle); err != nil { return } p.logger = p.logger.With( - zap.String("handler_name", config.HandlerName), - zap.String("address", config.ListenAddr()), + zap.String("address", lifecycle.Uplink().Addr().String()), ) router := &RegexpHandler{ logger: p.logger, - emitter: ctx.Audit(), - handlerName: config.HandlerName, + emitter: lifecycle.Audit(), + handlerName: lifecycle.Name(), } p.server = &http.Server{ - Addr: config.ListenAddr(), Handler: router, ConnContext: imHttp.StoreConnPropertiesInContext, } - if options.TLS { - p.server.TLSConfig = ctx.CertStore().TLSConfig() - p.server.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler)) - } - for _, rule := range options.Rules { router.setupRoute(rule) } - go p.startServer(options.TLS) + go p.startServer(lifecycle.Uplink().Listener) + go p.shutdownOnCancel(lifecycle.Context()) return } -func (p *httpHandler) Shutdown(ctx context.Context) (err error) { +func (p *httpHandler) shutdownOnCancel(ctx context.Context) { + <-ctx.Done() p.logger.Info("Shutting down HTTP mock") - if err = p.server.Shutdown(ctx); err != nil { + 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(tls bool) { - var listen func() error - if tls { - listen = func() error { - return p.server.ListenAndServeTLS("", "") +func (p *httpHandler) startServer(listener net.Listener) { + defer func() { + if err := listener.Close(); err != nil { + p.logger.Warn("failed to close listener", zap.Error(err)) } - } else { - listen = p.server.ListenAndServe - } - - if err := listen(); err != nil && !errors.Is(err, http.ErrServerClosed) { + }() + if err := p.server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) { p.logger.Error( "failed to start http listener", zap.Error(err), diff --git a/internal/endpoint/handler/http/mock/handler_bench_test.go b/internal/endpoint/handler/http/mock/handler_bench_test.go new file mode 100644 index 0000000..9fc285c --- /dev/null +++ b/internal/endpoint/handler/http/mock/handler_bench_test.go @@ -0,0 +1,155 @@ +package mock_test + +import ( + "context" + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "io/ioutil" + "math/rand" + "net" + "net/http" + "net/url" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + "github.com/docker/go-connections/nat" + "github.com/testcontainers/testcontainers-go" + "gitlab.com/inetmock/inetmock/internal/test/integration" +) + +const ( + charSet = "abcdedfghijklmnopqrstABCDEFGHIJKLMNOP" +) + +var ( + availableExtensions = []string{"gif", "html", "ico", "jpg", "png", "txt"} +) + +func init() { + rand.Seed(time.Now().Unix()) +} + +func Benchmark_httpHandler(b *testing.B) { + type benchmark struct { + name string + port string + scheme string + } + benchmarks := []benchmark{ + { + name: "HTTP", + port: "80/tcp", + scheme: "http", + }, + { + name: "HTTPS", + port: "443/tcp", + scheme: "https", + }, + } + scenario := func(bm benchmark) func(bm *testing.B) { + return func(b *testing.B) { + var err error + var endpoint string + if endpoint, err = setupContainer(b, bm.scheme, bm.port); err != nil { + b.Errorf("setupContainer() error = %v", err) + } + + var httpClient *http.Client + if httpClient, err = setupHTTPClient(); err != nil { + return + } + + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + extension := availableExtensions[rand.Intn(len(availableExtensions))] + + reqUrl, _ := url.Parse(fmt.Sprintf("%s/%s.%s", endpoint, randomString(15), extension)) + + req := &http.Request{ + Method: http.MethodGet, + URL: reqUrl, + Close: false, + Host: "www.inetmock.com", + } + if resp, err := httpClient.Do(req); err != nil { + b.Error(err) + } else if resp.StatusCode != 200 { + b.Errorf("Got status code %d", resp.StatusCode) + } + } + }) + } + } + for _, bm := range benchmarks { + b.Run(bm.name, scenario(bm)) + } +} + +func randomString(length int) (result string) { + buffer := strings.Builder{} + for i := 0; i < length; i++ { + buffer.WriteByte(charSet[rand.Intn(len(charSet))]) + } + return buffer.String() +} + +func setupContainer(b *testing.B, scheme, port string) (httpEndpoint string, err error) { + b.Helper() + + startupCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + var inetMockContainer testcontainers.Container + if inetMockContainer, err = integration.SetupINetMockContainer(startupCtx, b, port); err != nil { + return + } + + httpEndpoint, err = inetMockContainer.PortEndpoint(startupCtx, nat.Port(port), scheme) + return +} + +func setupHTTPClient() (client *http.Client, err error) { + _, fileName, _, _ := runtime.Caller(0) + + var repoRoot string + if repoRoot, err = filepath.Abs(filepath.Join(filepath.Dir(fileName), "..", "..", "..", "..", "..")); err != nil { + return + } + + var demoCABytes []byte + if demoCABytes, err = ioutil.ReadFile(filepath.Join(repoRoot, "assets", "demoCA", "ca.pem")); err != nil { + return + } + + rootCaPool := x509.NewCertPool() + if !rootCaPool.AppendCertsFromPEM(demoCABytes) { + err = errors.New("failed to add CA key") + return + } + + client = &http.Client{ + Transport: &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + TLSClientConfig: &tls.Config{ + RootCAs: rootCaPool, + }, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + }, + } + + return +} diff --git a/internal/endpoint/handler/http/mock/handler_test.go b/internal/endpoint/handler/http/mock/handler_test.go deleted file mode 100644 index e23bbf7..0000000 --- a/internal/endpoint/handler/http/mock/handler_test.go +++ /dev/null @@ -1,147 +0,0 @@ -package mock_test - -import ( - "context" - "fmt" - "math/rand" - "net" - "net/http" - "strconv" - "strings" - "testing" - "time" - - "github.com/golang/mock/gomock" - "github.com/spf13/viper" - "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/mock" - api_mock "gitlab.com/inetmock/inetmock/internal/mock/api" - audit_mock "gitlab.com/inetmock/inetmock/internal/mock/audit" - "gitlab.com/inetmock/inetmock/pkg/api" - "gitlab.com/inetmock/inetmock/pkg/config" - "gitlab.com/inetmock/inetmock/pkg/logging" - "go.uber.org/zap" -) - -var ( - testLogger = logging.NewLogger(zap.NewNop()) - availableExtensions = []string{"gif", "html", "ico", "jpg", "png", "txt"} - charSet = "abcdedfghijklmnopqrstABCDEFGHIJKLMNOP" -) - -func init() { - rand.Seed(time.Now().Unix()) -} - -func Benchmark_httpHandler(b *testing.B) { - ctrl := gomock.NewController(b) - defer ctrl.Finish() - - listenPort := randomHighPort() - _, handler := setupHandler(b, ctrl, listenPort) - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - extension := availableExtensions[rand.Intn(len(availableExtensions))] - if resp, err := http.Get(fmt.Sprintf("http://localhost:%d/%s.%s", listenPort, randomString(15), extension)); err != nil { - b.Error(err) - } else if resp.StatusCode != 200 { - b.Error("") - } - } - - defer handler.Shutdown(context.Background()) -} - -func randomString(length int) (result string) { - buffer := strings.Builder{} - for i := 0; i < length; i++ { - buffer.WriteByte(charSet[rand.Intn(len(charSet))]) - } - return buffer.String() -} - -func setupHandler(b *testing.B, ctrl *gomock.Controller, listenPort uint16) (api.HandlerRegistry, api.ProtocolHandler) { - b.Helper() - - registry := api.NewHandlerRegistry() - if err := mock.AddHTTPMock(registry); err != nil { - b.Errorf("AddHTTPMock() error = %v", err) - } - handler, ok := registry.HandlerForName("http_mock") - if !ok { - b.Error("handler not registered") - } - - emitter := audit_mock.NewMockEmitter(ctrl) - emitter.EXPECT().Emit(gomock.Any()).AnyTimes() - - mockApp := api_mock.NewMockPluginContext(ctrl) - mockApp.EXPECT(). - Logger(). - Return(testLogger) - - mockApp.EXPECT(). - Audit(). - Return(emitter) - - v := viper.New() - v.Set("rules", []map[string]string{ - { - "pattern": ".*\\.(?i)gif", - "response": "./../../assets/fakeFiles/default.gif", - }, - { - "pattern": ".*\\.(?i)html", - "response": "./../../assets/fakeFiles/default.html", - }, - { - "pattern": ".*\\.(?i)ico", - "response": "./../../assets/fakeFiles/default.ico", - }, - { - "pattern": ".*\\.(?i)jpg", - "response": "./../../assets/fakeFiles/default.jpg", - }, - { - "pattern": ".*\\.(?i)png", - "response": "./../../assets/fakeFiles/default.png", - }, - { - "pattern": ".*\\.(?i)txt", - "response": "./../../assets/fakeFiles/default.txt", - }, - }) - - handlerConfig := config.HandlerConfig{ - HandlerName: "http_test", - Port: listenPort, - ListenAddress: "localhost", - Options: v, - } - - if err := handler.Start(mockApp, handlerConfig); err != nil { - b.Error(err) - b.FailNow() - } - - return registry, handler -} - -func randomHighPort() uint16 { - var err error - var listener net.Listener - defer func() { - if listener != nil { - _ = listener.Close() - } - }() - - for { - if listener, err = net.Listen("tcp", ":0"); err == nil { - parts := strings.Split(listener.Addr().String(), ":") - port, _ := strconv.Atoi(parts[len(parts)-1]) - return uint16(port) - } - } -} diff --git a/internal/endpoint/handler/http/mock/protocol_options.go b/internal/endpoint/handler/http/mock/protocol_options.go index 0a6099a..0d688a6 100644 --- a/internal/endpoint/handler/http/mock/protocol_options.go +++ b/internal/endpoint/handler/http/mock/protocol_options.go @@ -6,7 +6,7 @@ import ( "path/filepath" "regexp" - "github.com/spf13/viper" + "gitlab.com/inetmock/inetmock/internal/endpoint" ) var ( @@ -50,11 +50,10 @@ func (tr targetRule) Response() string { } type httpOptions struct { - TLS bool Rules []targetRule } -func loadFromConfig(config *viper.Viper) (options httpOptions, err error) { +func loadFromConfig(lifecycle endpoint.Lifecycle) (options httpOptions, err error) { type tmpCfg struct { Pattern string Response string @@ -63,16 +62,13 @@ func loadFromConfig(config *viper.Viper) (options httpOptions, err error) { } tmpRules := struct { - TLS bool Rules []tmpCfg }{} - if err = config.Unmarshal(&tmpRules); err != nil { + if err = lifecycle.UnmarshalOptions(&tmpRules); err != nil { return } - options.TLS = tmpRules.TLS - for _, i := range tmpRules.Rules { var rulePattern *regexp.Regexp var matchTargetValue RequestMatchTarget diff --git a/internal/endpoint/handler/http/mock/protocol_options_test.go b/internal/endpoint/handler/http/mock/protocol_options_test.go index abad022..31510fc 100644 --- a/internal/endpoint/handler/http/mock/protocol_options_test.go +++ b/internal/endpoint/handler/http/mock/protocol_options_test.go @@ -4,15 +4,16 @@ import ( "path/filepath" "reflect" "regexp" - "strings" "testing" - "github.com/spf13/viper" + "github.com/golang/mock/gomock" + "github.com/mitchellh/mapstructure" + endpoint_mock "gitlab.com/inetmock/inetmock/internal/mock/endpoint" ) func Test_loadFromConfig(t *testing.T) { type args struct { - config string + config map[string]interface{} } tests := []struct { name string @@ -23,11 +24,18 @@ func Test_loadFromConfig(t *testing.T) { { name: "Parse default config", args: args{ - config: ` -rules: -- pattern: ".*\\.(?i)exe" - response: ./assets/fakeFiles/sample.exe -`, + config: map[string]interface{}{ + "rules": []struct { + Pattern string + Matcher string + Response string + }{ + { + Pattern: ".*\\.(?i)exe", + Response: "./assets/fakeFiles/sample.exe", + }, + }, + }, }, wantOptions: httpOptions{ Rules: []targetRule{ @@ -47,12 +55,19 @@ rules: { name: "Parse config with path matcher", args: args{ - config: ` -rules: -- pattern: ".*\\.(?i)exe" - matcher: Path - response: ./assets/fakeFiles/sample.exe -`, + config: map[string]interface{}{ + "rules": []struct { + Pattern string + Matcher string + Response string + }{ + { + Pattern: ".*\\.(?i)exe", + Response: "./assets/fakeFiles/sample.exe", + Matcher: "Path", + }, + }, + }, }, wantOptions: httpOptions{ Rules: []targetRule{ @@ -72,13 +87,21 @@ rules: { name: "Parse config with header matcher", args: args{ - config: ` -rules: -- pattern: "^application/octet-stream$" - target: Content-Type - matcher: Header - response: ./assets/fakeFiles/sample.exe -`, + config: map[string]interface{}{ + "rules": []struct { + Pattern string + Matcher string + Target string + Response string + }{ + { + Pattern: "^application/octet-stream$", + Response: "./assets/fakeFiles/sample.exe", + Target: "Content-Type", + Matcher: "Header", + }, + }, + }, }, wantOptions: httpOptions{ Rules: []targetRule{ @@ -98,17 +121,24 @@ rules: { name: "Parse config with header matcher and TLS true", args: args{ - config: ` -tls: true -rules: -- pattern: "^application/octet-stream$" - target: Content-Type - matcher: Header - response: ./assets/fakeFiles/sample.exe -`, + config: map[string]interface{}{ + "tls": true, + "rules": []struct { + Pattern string + Matcher string + Target string + Response string + }{ + { + Pattern: "^application/octet-stream$", + Response: "./assets/fakeFiles/sample.exe", + Target: "Content-Type", + Matcher: "Header", + }, + }, + }, }, wantOptions: httpOptions{ - TLS: true, Rules: []targetRule{ { pattern: regexp.MustCompile("^application/octet-stream$"), @@ -126,10 +156,15 @@ rules: } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - v := viper.New() - v.SetConfigType("yaml") - _ = v.ReadConfig(strings.NewReader(tt.args.config)) - gotOptions, err := loadFromConfig(v) + ctrl := gomock.NewController(t) + t.Cleanup(ctrl.Finish) + lcMock := endpoint_mock.NewMockLifecycle(ctrl) + + lcMock.EXPECT().UnmarshalOptions(gomock.Any()).Do(func(cfg interface{}) { + _ = mapstructure.Decode(tt.args.config, cfg) + }) + + gotOptions, err := loadFromConfig(lcMock) if (err != nil) != tt.wantErr { t.Errorf("loadFromConfig() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/internal/endpoint/handler/http/mock/register.go b/internal/endpoint/handler/http/mock/register.go index b84738f..ff00eb0 100644 --- a/internal/endpoint/handler/http/mock/register.go +++ b/internal/endpoint/handler/http/mock/register.go @@ -2,7 +2,7 @@ package mock import ( "github.com/prometheus/client_golang/prometheus" - "gitlab.com/inetmock/inetmock/pkg/api" + "gitlab.com/inetmock/inetmock/internal/endpoint" "gitlab.com/inetmock/inetmock/pkg/metrics" ) @@ -11,7 +11,7 @@ var ( requestDurationHistogram *prometheus.HistogramVec ) -func AddHTTPMock(registry api.HandlerRegistry) (err error) { +func AddHTTPMock(registry endpoint.HandlerRegistry) (err error) { if totalRequestCounter == nil { if totalRequestCounter, err = metrics.Counter( name, @@ -36,7 +36,7 @@ func AddHTTPMock(registry api.HandlerRegistry) (err error) { } } - registry.RegisterHandler(name, func() api.ProtocolHandler { + registry.RegisterHandler(name, func() endpoint.ProtocolHandler { return &httpHandler{} }) diff --git a/internal/endpoint/handler/http/proxy/handler.go b/internal/endpoint/handler/http/proxy/handler.go index 08c12d5..360ee0f 100644 --- a/internal/endpoint/handler/http/proxy/handler.go +++ b/internal/endpoint/handler/http/proxy/handler.go @@ -3,12 +3,12 @@ package proxy import ( "context" "errors" - "fmt" + "net" "net/http" + "github.com/soheilhy/cmux" + "gitlab.com/inetmock/inetmock/internal/endpoint" imHttp "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http" - "gitlab.com/inetmock/inetmock/pkg/api" - "gitlab.com/inetmock/inetmock/pkg/config" "gitlab.com/inetmock/inetmock/pkg/logging" "go.uber.org/zap" "gopkg.in/elazarl/goproxy.v1" @@ -24,46 +24,49 @@ type httpProxy struct { server *http.Server } -func (h *httpProxy) Start(ctx api.PluginContext, cfg config.HandlerConfig) (err error) { +func (h *httpProxy) Matchers() []cmux.Matcher { + return []cmux.Matcher{cmux.HTTP1()} +} + +func (h *httpProxy) Start(lifecycle endpoint.Lifecycle) (err error) { var opts httpProxyOptions - if err = cfg.Options.Unmarshal(&opts); err != nil { + if err = lifecycle.UnmarshalOptions(&opts); err != nil { return } - listenAddr := cfg.ListenAddr() + h.server = &http.Server{ - Addr: listenAddr, Handler: h.proxy, ConnContext: imHttp.StoreConnPropertiesInContext, } h.logger = h.logger.With( - zap.String("handler_name", cfg.HandlerName), - zap.String("address", listenAddr), + zap.String("handler_name", lifecycle.Name()), + zap.String("address", lifecycle.Uplink().Addr().String()), ) - tlsConfig := ctx.CertStore().TLSConfig() + tlsConfig := lifecycle.CertStore().TLSConfig() proxyHandler := &proxyHttpHandler{ - handlerName: cfg.HandlerName, + handlerName: lifecycle.Name(), options: opts, logger: h.logger, - emitter: ctx.Audit(), + emitter: lifecycle.Audit(), } proxyHTTPSHandler := &proxyHttpsHandler{ - handlerName: cfg.HandlerName, - tlsConfig: tlsConfig, - logger: h.logger, - emitter: ctx.Audit(), + options: opts, + tlsConfig: tlsConfig, + emitter: lifecycle.Audit(), } h.proxy.OnRequest().Do(proxyHandler) h.proxy.OnRequest().HandleConnect(proxyHTTPSHandler) - go h.startProxy() + go h.startProxy(lifecycle.Uplink().Listener) + go h.shutdownOnContextDone(lifecycle.Context()) return } -func (h *httpProxy) startProxy() { - if err := h.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { +func (h *httpProxy) startProxy(listener net.Listener) { + if err := h.server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) { h.logger.Error( "failed to start proxy server", zap.Error(err), @@ -71,18 +74,15 @@ func (h *httpProxy) startProxy() { } } -func (h *httpProxy) Shutdown(ctx context.Context) (err error) { +func (h *httpProxy) shutdownOnContextDone(ctx context.Context) { + <-ctx.Done() + var err error h.logger.Info("Shutting down HTTP proxy") - if err = h.server.Shutdown(ctx); err != nil { + 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 } diff --git a/internal/endpoint/handler/http/proxy/handler_bench_test.go b/internal/endpoint/handler/http/proxy/handler_bench_test.go new file mode 100644 index 0000000..d256f13 --- /dev/null +++ b/internal/endpoint/handler/http/proxy/handler_bench_test.go @@ -0,0 +1,167 @@ +package proxy_test + +import ( + "context" + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "io/ioutil" + "math/rand" + "net" + "net/http" + "net/url" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + "github.com/docker/go-connections/nat" + "github.com/testcontainers/testcontainers-go" + "gitlab.com/inetmock/inetmock/internal/test/integration" +) + +const ( + charSet = "abcdedfghijklmnopqrstABCDEFGHIJKLMNOP" +) + +var ( + availableExtensions = []string{"gif", "html", "ico", "jpg", "png", "txt"} +) + +func init() { + rand.Seed(time.Now().Unix()) +} + +func Benchmark_httpProxy(b *testing.B) { + type benchmark struct { + name string + port string + scheme string + } + benchmarks := []benchmark{ + { + name: "HTTP", + port: "3128/tcp", + scheme: "http", + }, + { + name: "HTTPS", + port: "3128/tcp", + scheme: "https", + }, + } + scenario := func(bm benchmark) func(bm *testing.B) { + return func(b *testing.B) { + var err error + var endpoint string + if endpoint, err = setupContainer(b, bm.port); err != nil { + b.Errorf("setupContainer() error = %v", err) + } + + var httpClient *http.Client + if httpClient, err = setupHTTPClient(fmt.Sprintf("http://%s", endpoint), fmt.Sprintf("https://%s", endpoint)); err != nil { + return + } + + time.Sleep(500 * time.Millisecond) + + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + extension := availableExtensions[rand.Intn(len(availableExtensions))] + + reqUrl, _ := url.Parse(fmt.Sprintf("%s://%s/%s.%s", bm.scheme, endpoint, randomString(15), extension)) + + req := &http.Request{ + Method: http.MethodGet, + URL: reqUrl, + Close: false, + Host: "www.inetmock.com", + } + if resp, err := httpClient.Do(req); err != nil { + b.Error(err) + } else if resp.StatusCode != 200 { + b.Errorf("Got status code %d", resp.StatusCode) + } + } + }) + } + } + for _, bm := range benchmarks { + b.Run(bm.name, scenario(bm)) + } +} + +func randomString(length int) (result string) { + buffer := strings.Builder{} + for i := 0; i < length; i++ { + buffer.WriteByte(charSet[rand.Intn(len(charSet))]) + } + return buffer.String() +} + +func setupContainer(b *testing.B, port string) (httpEndpoint string, err error) { + b.Helper() + + startupCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + var inetMockContainer testcontainers.Container + if inetMockContainer, err = integration.SetupINetMockContainer(startupCtx, b, port); err != nil { + return + } + + httpEndpoint, err = inetMockContainer.PortEndpoint(startupCtx, nat.Port(port), "") + return +} + +func setupHTTPClient(httpEndpoint, httpsEndpoint string) (client *http.Client, err error) { + _, fileName, _, _ := runtime.Caller(0) + + var repoRoot string + if repoRoot, err = filepath.Abs(filepath.Join(filepath.Dir(fileName), "..", "..", "..", "..", "..")); err != nil { + return + } + + var demoCABytes []byte + if demoCABytes, err = ioutil.ReadFile(filepath.Join(repoRoot, "assets", "demoCA", "ca.pem")); err != nil { + return + } + + rootCaPool := x509.NewCertPool() + if !rootCaPool.AppendCertsFromPEM(demoCABytes) { + err = errors.New("failed to add CA key") + return + } + + client = &http.Client{ + Transport: &http.Transport{ + Proxy: func(req *http.Request) (*url.URL, error) { + switch req.URL.Scheme { + case "http": + return url.Parse(httpEndpoint) + case "https": + return url.Parse(httpsEndpoint) + default: + return nil, errors.New("unknown scheme") + } + }, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + TLSClientConfig: &tls.Config{ + RootCAs: rootCaPool, + }, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + }, + } + + return +} diff --git a/internal/endpoint/handler/http/proxy/proxy_handler.go b/internal/endpoint/handler/http/proxy/proxy_handler.go index 6bdf26e..3b94ae0 100644 --- a/internal/endpoint/handler/http/proxy/proxy_handler.go +++ b/internal/endpoint/handler/http/proxy/proxy_handler.go @@ -3,8 +3,8 @@ package proxy import ( "crypto/tls" "net/http" - "net/url" + "github.com/jinzhu/copier" "github.com/prometheus/client_golang/prometheus" imHttp "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http" "gitlab.com/inetmock/inetmock/pkg/audit" @@ -13,6 +13,23 @@ import ( "gopkg.in/elazarl/goproxy.v1" ) +type proxyHttpsHandler struct { + options httpProxyOptions + tlsConfig *tls.Config + emitter audit.Emitter +} + +func (p *proxyHttpsHandler) HandleConnect(_ string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) { + p.emitter.Emit(imHttp.EventFromRequest(ctx.Req, audit.AppProtocol_HTTP_PROXY)) + + return &goproxy.ConnectAction{ + Action: goproxy.ConnectAccept, + TLSConfig: func(host string, ctx *goproxy.ProxyCtx) (*tls.Config, error) { + return p.tlsConfig, nil + }, + }, p.options.Target.host() +} + type proxyHttpHandler struct { handlerName string options httpProxyOptions @@ -20,38 +37,19 @@ type proxyHttpHandler struct { emitter audit.Emitter } -type proxyHttpsHandler struct { - handlerName string - tlsConfig *tls.Config - logger logging.Logger - emitter audit.Emitter -} - -func (p *proxyHttpsHandler) HandleConnect(req string, _ *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) { - totalHttpsRequestCounter.WithLabelValues(p.handlerName).Inc() - 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) { timer := prometheus.NewTimer(requestDurationHistogram.WithLabelValues(p.handlerName)) defer timer.ObserveDuration() - totalRequestCounter.WithLabelValues(p.handlerName).Inc() retReq = req p.emitter.Emit(imHttp.EventFromRequest(req, audit.AppProtocol_HTTP_PROXY)) var err error - if resp, err = ctx.RoundTrip(p.redirectHTTPRequest(req)); err != nil { + var redirectReq *http.Request + if redirectReq, err = redirectHTTPRequest(p.options.Target.host(), req); err != nil { + return req, nil + } + if resp, err = ctx.RoundTrip(redirectReq); err != nil { p.logger.Error( "error while doing roundtrip", zap.Error(err), @@ -62,35 +60,11 @@ func (p *proxyHttpHandler) Handle(req *http.Request, ctx *goproxy.ProxyCtx) (ret return } -func (p proxyHttpHandler) redirectHTTPRequest(originalRequest *http.Request) (redirectReq *http.Request) { - redirectReq = &http.Request{ - Method: originalRequest.Method, - URL: &url.URL{ - Host: p.options.Target.host(), - 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, +func redirectHTTPRequest(targetHost string, originalRequest *http.Request) (redirectReq *http.Request, err error) { + redirectReq = new(http.Request) + if err = copier.Copy(redirectReq, originalRequest); err != nil { + return } - redirectReq = redirectReq.WithContext(originalRequest.Context()) - + originalRequest.URL.Host = targetHost return } diff --git a/internal/endpoint/handler/http/proxy/register.go b/internal/endpoint/handler/http/proxy/register.go index 4470caf..2814e19 100644 --- a/internal/endpoint/handler/http/proxy/register.go +++ b/internal/endpoint/handler/http/proxy/register.go @@ -2,7 +2,7 @@ package proxy import ( "github.com/prometheus/client_golang/prometheus" - "gitlab.com/inetmock/inetmock/pkg/api" + "gitlab.com/inetmock/inetmock/internal/endpoint" "gitlab.com/inetmock/inetmock/pkg/logging" "gitlab.com/inetmock/inetmock/pkg/metrics" "go.uber.org/zap" @@ -11,12 +11,10 @@ import ( var ( handlerNameLblName = "handler_name" - totalRequestCounter *prometheus.CounterVec - totalHttpsRequestCounter *prometheus.CounterVec requestDurationHistogram *prometheus.HistogramVec ) -func AddHTTPProxy(registry api.HandlerRegistry) (err error) { +func AddHTTPProxy(registry endpoint.HandlerRegistry) (err error) { var logger logging.Logger if logger, err = logging.CreateLogger(); err != nil { return @@ -25,19 +23,11 @@ func AddHTTPProxy(registry api.HandlerRegistry) (err error) { zap.String("protocol_handler", name), ) - if totalRequestCounter, err = metrics.Counter(name, "total_requests", "", handlerNameLblName); err != nil { - return - } - if requestDurationHistogram, err = metrics.Histogram(name, "request_duration", "", nil, handlerNameLblName); err != nil { return } - if totalHttpsRequestCounter, err = metrics.Counter(name, "total_https_requests", "", handlerNameLblName); err != nil { - return - } - - registry.RegisterHandler(name, func() api.ProtocolHandler { + registry.RegisterHandler(name, func() endpoint.ProtocolHandler { return &httpProxy{ logger: logger, proxy: goproxy.NewProxyHttpServer(), diff --git a/internal/endpoint/handler/metrics/handler.go b/internal/endpoint/handler/metrics/handler.go index 0519892..f0120de 100644 --- a/internal/endpoint/handler/metrics/handler.go +++ b/internal/endpoint/handler/metrics/handler.go @@ -1,13 +1,11 @@ package metrics import ( - "context" "errors" "net/http" "github.com/prometheus/client_golang/prometheus/promhttp" - "gitlab.com/inetmock/inetmock/pkg/api" - "gitlab.com/inetmock/inetmock/pkg/config" + "gitlab.com/inetmock/inetmock/internal/endpoint" "gitlab.com/inetmock/inetmock/pkg/logging" "go.uber.org/zap" ) @@ -21,35 +19,37 @@ type metricsExporter struct { server *http.Server } -func (m *metricsExporter) Start(_ api.PluginContext, config config.HandlerConfig) (err error) { - exporterOptions := metricsExporterOptions{} - if err = config.Options.Unmarshal(&exporterOptions); err != nil { +func (m *metricsExporter) Start(lifecycle endpoint.Lifecycle) (err error) { + var exporterOptions metricsExporterOptions + if err = lifecycle.UnmarshalOptions(&exporterOptions); err != nil { return } m.logger = m.logger.With( - zap.String("handler_name", config.HandlerName), - zap.String("address", config.ListenAddr()), + zap.String("handler_name", lifecycle.Name()), + zap.String("address", lifecycle.Uplink().Addr().String()), ) mux := http.NewServeMux() mux.Handle(exporterOptions.Route, promhttp.Handler()) m.server = &http.Server{ - Addr: config.ListenAddr(), Handler: mux, } go func() { - if err := m.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + if err := m.server.Serve(lifecycle.Uplink().Listener); err != nil && !errors.Is(err, http.ErrServerClosed) { m.logger.Error( "Error occurred while serving metrics", zap.Error(err), ) } }() + + go func() { + <-lifecycle.Context().Done() + if err := m.server.Close(); err != nil && !errors.Is(err, http.ErrServerClosed) { + m.logger.Error("failed to stop metrics server", zap.Error(err)) + } + }() return } - -func (m *metricsExporter) Shutdown(ctx context.Context) error { - return m.server.Shutdown(ctx) -} diff --git a/internal/endpoint/handler/metrics/register.go b/internal/endpoint/handler/metrics/register.go index 4fa3043..6991111 100644 --- a/internal/endpoint/handler/metrics/register.go +++ b/internal/endpoint/handler/metrics/register.go @@ -1,12 +1,12 @@ package metrics import ( - "gitlab.com/inetmock/inetmock/pkg/api" + "gitlab.com/inetmock/inetmock/internal/endpoint" "gitlab.com/inetmock/inetmock/pkg/logging" "go.uber.org/zap" ) -func AddMetricsExporter(registry api.HandlerRegistry) (err error) { +func AddMetricsExporter(registry endpoint.HandlerRegistry) (err error) { var logger logging.Logger if logger, err = logging.CreateLogger(); err != nil { return @@ -15,7 +15,7 @@ func AddMetricsExporter(registry api.HandlerRegistry) (err error) { zap.String("protocol_handler", name), ) - registry.RegisterHandler(name, func() api.ProtocolHandler { + registry.RegisterHandler(name, func() endpoint.ProtocolHandler { return &metricsExporter{ logger: logger, } diff --git a/internal/endpoint/handler/tls/interceptor/handler.go b/internal/endpoint/handler/tls/interceptor/handler.go index b180eb2..268ba30 100644 --- a/internal/endpoint/handler/tls/interceptor/handler.go +++ b/internal/endpoint/handler/tls/interceptor/handler.go @@ -3,14 +3,13 @@ package interceptor import ( "context" "crypto/tls" - "fmt" "net" "sync" + "time" "github.com/google/uuid" "github.com/prometheus/client_golang/prometheus" - "gitlab.com/inetmock/inetmock/pkg/api" - "gitlab.com/inetmock/inetmock/pkg/config" + "gitlab.com/inetmock/inetmock/internal/endpoint" "gitlab.com/inetmock/inetmock/pkg/logging" "go.uber.org/zap" ) @@ -30,36 +29,28 @@ type tlsInterceptor struct { connectionsMutex *sync.Mutex } -func (t *tlsInterceptor) Start(ctx api.PluginContext, config config.HandlerConfig) (err error) { - t.name = config.HandlerName +func (t *tlsInterceptor) Start(ctx endpoint.Lifecycle) (err error) { + t.name = ctx.Name() - if err = config.Options.Unmarshal(&t.options); err != nil { + if err = ctx.UnmarshalOptions(&t.options); err != nil { return } t.logger = t.logger.With( - zap.String("handler_name", config.HandlerName), - zap.String("address", config.ListenAddr()), + zap.String("handler_name", ctx.Name()), + zap.String("address", ctx.Uplink().Addr().String()), zap.String("Target", t.options.Target.address()), ) - if t.listener, err = tls.Listen("tcp", config.ListenAddr(), ctx.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 - } + t.listener = tls.NewListener(ctx.Uplink().Listener, ctx.CertStore().TLSConfig()) go t.startListener() + go t.shutdownOnContextDone(ctx.Context()) return } -func (t *tlsInterceptor) Shutdown(ctx context.Context) (err error) { +func (t *tlsInterceptor) shutdownOnContextDone(ctx context.Context) { + <-ctx.Done() t.logger.Info("Shutting down TLS interceptor") t.shutdownRequested = true done := make(chan struct{}) @@ -71,17 +62,13 @@ func (t *tlsInterceptor) Shutdown(ctx context.Context) (err error) { select { case <-done: return - case <-ctx.Done(): + case <-time.After(100 * time.Millisecond): 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, - ) } } return diff --git a/internal/endpoint/handler/tls/interceptor/register.go b/internal/endpoint/handler/tls/interceptor/register.go index 6fcf3fc..9d61045 100644 --- a/internal/endpoint/handler/tls/interceptor/register.go +++ b/internal/endpoint/handler/tls/interceptor/register.go @@ -5,7 +5,7 @@ import ( "github.com/google/uuid" "github.com/prometheus/client_golang/prometheus" - "gitlab.com/inetmock/inetmock/pkg/api" + "gitlab.com/inetmock/inetmock/internal/endpoint" "gitlab.com/inetmock/inetmock/pkg/logging" "gitlab.com/inetmock/inetmock/pkg/metrics" "go.uber.org/zap" @@ -18,7 +18,7 @@ var ( requestDurationHistogram *prometheus.HistogramVec ) -func AddTLSInterceptor(registry api.HandlerRegistry) (err error) { +func AddTLSInterceptor(registry endpoint.HandlerRegistry) (err error) { var logger logging.Logger if logger, err = logging.CreateLogger(); err != nil { panic(err) @@ -37,7 +37,7 @@ func AddTLSInterceptor(registry api.HandlerRegistry) (err error) { } - registry.RegisterHandler(name, func() api.ProtocolHandler { + registry.RegisterHandler(name, func() endpoint.ProtocolHandler { return &tlsInterceptor{ logger: logger, currentConnectionsCount: new(sync.WaitGroup), diff --git a/internal/endpoint/lifecycle.go b/internal/endpoint/lifecycle.go new file mode 100644 index 0000000..af1fba3 --- /dev/null +++ b/internal/endpoint/lifecycle.go @@ -0,0 +1,69 @@ +package endpoint + +import ( + "context" + + "github.com/mitchellh/mapstructure" + "gitlab.com/inetmock/inetmock/pkg/audit" + "gitlab.com/inetmock/inetmock/pkg/cert" + "gitlab.com/inetmock/inetmock/pkg/logging" +) + +type endpointLifecycle struct { + endpointName string + ctx context.Context + logger logging.Logger + certStore cert.Store + emitter audit.Emitter + uplink Uplink + tls bool + opts map[string]interface{} +} + +func NewEndpointLifecycleFromContext( + endpointName string, + ctx context.Context, + logger logging.Logger, + certStore cert.Store, + emitter audit.Emitter, + uplink Uplink, + opts map[string]interface{}, +) Lifecycle { + return &endpointLifecycle{ + endpointName: endpointName, + ctx: ctx, + logger: logger, + certStore: certStore, + emitter: emitter, + uplink: uplink, + opts: opts, + } +} + +func (e endpointLifecycle) Name() string { + return e.endpointName +} + +func (e endpointLifecycle) Uplink() Uplink { + return e.uplink +} + +func (e endpointLifecycle) Logger() logging.Logger { + return e.logger +} + +func (e endpointLifecycle) CertStore() cert.Store { + return e.certStore +} + +func (e endpointLifecycle) Audit() audit.Emitter { + return e.emitter +} + +func (e endpointLifecycle) Context() context.Context { + return e.ctx +} + +func (e endpointLifecycle) UnmarshalOptions(cfg interface{}) error { + return mapstructure.Decode(e.opts, cfg) +} diff --git a/internal/endpoint/listener.go b/internal/endpoint/listener.go new file mode 100644 index 0000000..115d5a7 --- /dev/null +++ b/internal/endpoint/listener.go @@ -0,0 +1,143 @@ +//go:generate go-enum -f $GOFILE --lower --marshal --names + +package endpoint + +import ( + "crypto/tls" + "errors" + "fmt" + "net" + "sort" + "strings" + + "github.com/soheilhy/cmux" +) + +var ( + ErrUDPMultiplexer = errors.New("UDP listeners don't support multiplexing") + ErrMultiplexingNotSupported = errors.New("not all handlers do support multiplexing") +) + +/* ENUM( +UDP, +TCP +) +*/ +type NetProto int + +type HandlerReference string + +func (h HandlerReference) ToLower() HandlerReference { + return HandlerReference(strings.ToLower(string(h))) +} + +type ListenerSpec struct { + Name string + Protocol string + Address string `mapstructure:"listenAddress"` + Port uint16 + Endpoints map[string]Spec + Uplink *Uplink `mapstructure:"-"` +} + +type Spec struct { + HandlerRef HandlerReference `mapstructure:"handler"` + TLS bool + Handler ProtocolHandler `mapstructure:"-"` + Options map[string]interface{} +} + +func (l *ListenerSpec) ConfigureMultiplexing(tlsConfig *tls.Config) (endpoints []Endpoint, muxes []cmux.CMux, err error) { + if l.Uplink == nil { + if err = l.setupUplink(); err != nil { + return + } + } + + if len(l.Endpoints) <= 1 { + for name, s := range l.Endpoints { + endpoints = append(endpoints, Endpoint{ + name: fmt.Sprintf("%s:%s", l.Name, name), + uplink: *l.Uplink, + Spec: s, + }) + return + } + } + + if l.Uplink.Proto == NetProtoUDP { + err = ErrUDPMultiplexer + return + } + + var epNames []string + var multiplexEndpoints = make(map[string]MultiplexHandler) + for name, spec := range l.Endpoints { + epNames = append(epNames, name) + if ep, ok := spec.Handler.(MultiplexHandler); !ok { + err = fmt.Errorf("handler %s %w", spec.HandlerRef, ErrMultiplexingNotSupported) + return + } else { + multiplexEndpoints[name] = ep + } + } + + sort.Strings(epNames) + + plainMux := cmux.New(l.Uplink.Listener) + tlsListener := plainMux.Match(cmux.TLS()) + tlsListener = tls.NewListener(tlsListener, tlsConfig) + tlsMux := cmux.New(tlsListener) + + var tlsRequired = false + + for _, epName := range epNames { + epSpec := l.Endpoints[epName] + var epMux = plainMux + if epSpec.TLS { + epMux = tlsMux + tlsRequired = true + } + epListener := Endpoint{ + name: fmt.Sprintf("%s:%s", l.Name, epName), + uplink: Uplink{ + Proto: NetProtoTCP, + Listener: epMux.Match(multiplexEndpoints[epName].Matchers()...), + }, + Spec: epSpec, + } + + endpoints = append(endpoints, epListener) + } + + muxes = append(muxes, plainMux) + + if tlsRequired { + muxes = append(muxes, tlsMux) + } else { + _ = tlsListener.Close() + } + + return +} + +func (l *ListenerSpec) setupUplink() (err error) { + l.Uplink = new(Uplink) + switch l.Protocol { + case "udp", "udp4", "udp6": + l.Uplink.Proto = NetProtoUDP + l.Uplink.PacketConn, err = net.ListenUDP(l.Protocol, &net.UDPAddr{ + IP: net.ParseIP(l.Address), + Port: int(l.Port), + }) + case "tcp", "tcp4", "tcp6": + l.Uplink.Proto = NetProtoTCP + l.Uplink.Listener, err = net.ListenTCP(l.Protocol, &net.TCPAddr{ + IP: net.ParseIP(l.Address), + Port: int(l.Port), + }) + default: + err = errors.New("protocol not supported") + } + return +} diff --git a/internal/endpoint/orchestrator.go b/internal/endpoint/orchestrator.go new file mode 100644 index 0000000..da2473d --- /dev/null +++ b/internal/endpoint/orchestrator.go @@ -0,0 +1,102 @@ +package endpoint + +import ( + "context" + "errors" + + "github.com/soheilhy/cmux" + "gitlab.com/inetmock/inetmock/pkg/audit" + "gitlab.com/inetmock/inetmock/pkg/cert" + "gitlab.com/inetmock/inetmock/pkg/logging" + "go.uber.org/zap" +) + +var ( + ErrStartupTimeout = errors.New("endpoint did not start in time") +) + +type Orchestrator interface { + RegisterListener(spec ListenerSpec) error + StartEndpoints() (errChan chan error) +} + +func NewOrchestrator(appCtx context.Context, certStore cert.Store, registry HandlerRegistry, emitter audit.Emitter, logging logging.Logger) Orchestrator { + return &orchestrator{ + appCtx: appCtx, + registry: registry, + logger: logging, + certStore: certStore, + emitter: emitter, + } +} + +type orchestrator struct { + appCtx context.Context + registry HandlerRegistry + logger logging.Logger + certStore cert.Store + emitter audit.Emitter + + endpointListeners []Endpoint + muxes []cmux.CMux +} + +func (e *orchestrator) RegisterListener(spec ListenerSpec) (err error) { + for name, s := range spec.Endpoints { + if handler, registered := e.registry.HandlerForName(s.HandlerRef); registered { + s.Handler = handler + spec.Endpoints[name] = s + } + } + + var endpoints []Endpoint + var muxes []cmux.CMux + if endpoints, muxes, err = spec.ConfigureMultiplexing(e.certStore.TLSConfig()); err != nil { + return + } + + e.endpointListeners = append(e.endpointListeners, endpoints...) + e.muxes = append(e.muxes, muxes...) + + return +} + +func (e *orchestrator) StartEndpoints() (errChan chan error) { + errChan = make(chan error) + for _, epListener := range e.endpointListeners { + endpointLogger := e.logger.With( + zap.String("epListener", epListener.name), + ) + endpointLogger.Info("Starting epListener") + lifecycle := NewEndpointLifecycleFromContext( + epListener.name, + e.appCtx, + e.logger.With(zap.String("epListener", epListener.name)), + e.certStore, + e.emitter, + epListener.uplink, + epListener.Options, + ) + + if err := epListener.Start(lifecycle); err == nil { + endpointLogger.Info("successfully started epListener") + } else { + endpointLogger.Error("error occurred during epListener startup - will be skipped for now") + } + } + e.logger.Info("Startup of all endpoints completed") + + for _, mux := range e.muxes { + go func(mux cmux.CMux) { + mux.HandleError(func(err error) bool { + errChan <- err + return true + }) + if err := mux.Serve(); err != nil && !errors.Is(err, cmux.ErrListenerClosed) { + errChan <- err + } + }(mux) + } + + return +} diff --git a/internal/endpoint/registration.go b/internal/endpoint/registration.go new file mode 100644 index 0000000..d2cf3b5 --- /dev/null +++ b/internal/endpoint/registration.go @@ -0,0 +1,47 @@ +//go:generate mockgen -source=$GOFILE -destination=./../../internal/mock/endpoint/handler_registry.mock.go -package=endpoint_mock +package endpoint + +import ( + "fmt" +) + +type Registration func(registry HandlerRegistry) error + +type HandlerRegistry interface { + RegisterHandler(handlerRef HandlerReference, handlerProvider func() ProtocolHandler) + AvailableHandlers() []HandlerReference + HandlerForName(handlerRef HandlerReference) (ProtocolHandler, bool) +} + +func NewHandlerRegistry() HandlerRegistry { + return &handlerRegistry{ + handlers: make(map[HandlerReference]func() ProtocolHandler), + } +} + +type handlerRegistry struct { + handlers map[HandlerReference]func() ProtocolHandler +} + +func (h handlerRegistry) AvailableHandlers() (availableHandlers []HandlerReference) { + for s := range h.handlers { + availableHandlers = append(availableHandlers, s) + } + return +} + +func (h *handlerRegistry) HandlerForName(handlerRef HandlerReference) (instance ProtocolHandler, ok bool) { + var provider func() ProtocolHandler + if provider, ok = h.handlers[handlerRef.ToLower()]; ok { + instance = provider() + } + return +} + +func (h *handlerRegistry) RegisterHandler(handlerRef HandlerReference, handlerProvider func() ProtocolHandler) { + handlerRef = handlerRef.ToLower() + if _, exists := h.handlers[handlerRef]; exists { + panic(fmt.Sprintf("handler with name %s is already registered - there's something strange...in the neighborhood", handlerRef)) + } + h.handlers[handlerRef] = handlerProvider +} diff --git a/internal/endpoint/uplink.go b/internal/endpoint/uplink.go new file mode 100644 index 0000000..177ec79 --- /dev/null +++ b/internal/endpoint/uplink.go @@ -0,0 +1,33 @@ +package endpoint + +import ( + "net" + + "go.uber.org/multierr" +) + +type Uplink struct { + Proto NetProto + Listener net.Listener + PacketConn net.PacketConn +} + +func (u Uplink) Addr() net.Addr { + if u.Listener != nil { + return u.Listener.Addr() + } + if u.PacketConn != nil { + return u.PacketConn.LocalAddr() + } + return nil +} + +func (u Uplink) Close() (err error) { + if u.Listener != nil { + err = multierr.Append(err, u.Listener.Close()) + } + if u.PacketConn != nil { + err = multierr.Append(err, u.PacketConn.Close()) + } + return +} diff --git a/internal/rpc/endpoints_server.go b/internal/rpc/endpoints_server.go deleted file mode 100644 index ff7fd7a..0000000 --- a/internal/rpc/endpoints_server.go +++ /dev/null @@ -1,37 +0,0 @@ -package rpc - -import ( - "context" - - "gitlab.com/inetmock/inetmock/internal/endpoint" -) - -type endpointsServer struct { - UnimplementedEndpointsServer - endpointsManager endpoint.EndpointManager -} - -func (e endpointsServer) GetEndpoints(_ context.Context, _ *GetEndpointsRequest) (*GetEndpointsResponse, error) { - eps := rpcEndpointsFromEndpoints(e.endpointsManager.StartedEndpoints()) - return &GetEndpointsResponse{ - Endpoints: *eps, - }, nil -} - -func rpcEndpointsFromEndpoints(eps []endpoint.Endpoint) *[]*Endpoint { - out := make([]*Endpoint, 0) - for _, ep := range eps { - out = append(out, rpcEndpointFromEndpoint(ep)) - } - return &out -} - -func rpcEndpointFromEndpoint(ep endpoint.Endpoint) *Endpoint { - return &Endpoint{ - Id: ep.Id().String(), - Name: ep.Name(), - Handler: ep.Handler(), - ListenAddress: ep.Listen(), - Port: int32(ep.Port()), - } -} diff --git a/internal/rpc/grpc_api.go b/internal/rpc/grpc_api.go index b523d7f..a666dc5 100644 --- a/internal/rpc/grpc_api.go +++ b/internal/rpc/grpc_api.go @@ -3,6 +3,7 @@ package rpc import ( "net" "net/url" + "os" "time" app2 "gitlab.com/inetmock/inetmock/internal/app" @@ -42,13 +43,6 @@ func (i *inetmockAPI) StartServer() (err error) { } i.server = grpc.NewServer() - RegisterHandlersServer(i.server, &handlersServer{ - registry: i.app.HandlerRegistry(), - }) - RegisterEndpointsServer(i.server, &endpointsServer{ - endpointsManager: i.app.EndpointManager(), - }) - RegisterHealthServer(i.server, &healthServer{ app: i.app, }) @@ -98,6 +92,11 @@ func (i *inetmockAPI) startServerAsync(listener net.Listener) { func createListenerFromURL(url *url.URL) (lis net.Listener, err error) { switch url.Scheme { case "unix": + if _, err = os.Stat(url.Path); err == nil { + if err = os.Remove(url.Path); err != nil { + return + } + } lis, err = net.Listen(url.Scheme, url.Path) default: lis, err = net.Listen(url.Scheme, url.Host) diff --git a/internal/rpc/handlers_server.go b/internal/rpc/handlers_server.go deleted file mode 100644 index 0af6e56..0000000 --- a/internal/rpc/handlers_server.go +++ /dev/null @@ -1,18 +0,0 @@ -package rpc - -import ( - "context" - - "gitlab.com/inetmock/inetmock/pkg/api" -) - -type handlersServer struct { - UnimplementedHandlersServer - registry api.HandlerRegistry -} - -func (h *handlersServer) GetHandlers(_ context.Context, _ *GetHandlersRequest) (*GetHandlersResponse, error) { - return &GetHandlersResponse{ - Handlers: h.registry.AvailableHandlers(), - }, nil -} diff --git a/internal/test/integration/testcontainer.go b/internal/test/integration/testcontainer.go new file mode 100644 index 0000000..77ff8b2 --- /dev/null +++ b/internal/test/integration/testcontainer.go @@ -0,0 +1,64 @@ +package integration + +import ( + "context" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + "github.com/docker/go-connections/nat" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +func SetupINetMockContainer(ctx context.Context, tb testing.TB, exposedPorts ...string) (imContainer testcontainers.Container, err error) { + _, fileName, _, _ := runtime.Caller(0) + + var repoRoot string + if repoRoot, err = filepath.Abs(filepath.Join(filepath.Dir(fileName), "..", "..", "..")); err != nil { + return + } + + var waitStrategies []wait.Strategy + + var tcpPortPresent = false + for _, port := range exposedPorts { + if strings.Contains(port, "tcp") { + tcpPortPresent = true + waitStrategies = append(waitStrategies, wait.ForListeningPort(nat.Port(port))) + } + } + + if !tcpPortPresent { + exposedPorts = append(exposedPorts, "80/tcp") + waitStrategies = append(waitStrategies, wait.ForListeningPort("80/tcp")) + } + + req := testcontainers.ContainerRequest{ + FromDockerfile: testcontainers.FromDockerfile{ + Context: repoRoot, + Dockerfile: filepath.Join("./", "testdata", "integration.dockerfile"), + }, + ExposedPorts: exposedPorts, + WaitingFor: wait.ForAll(waitStrategies...), + } + + imContainer, err = testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) + + if err != nil { + return + } + + tb.Cleanup(func() { + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + _ = imContainer.Terminate(shutdownCtx) + }) + + return +} diff --git a/pkg/api/protocol_handler.go b/pkg/api/protocol_handler.go deleted file mode 100644 index f927570..0000000 --- a/pkg/api/protocol_handler.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:generate mockgen -source=$GOFILE -destination=./../../internal/mock/api/protocol_handler.mock.go -package=api_mock -package api - -import ( - "context" - - "gitlab.com/inetmock/inetmock/pkg/audit" - "gitlab.com/inetmock/inetmock/pkg/cert" - "gitlab.com/inetmock/inetmock/pkg/config" - "gitlab.com/inetmock/inetmock/pkg/logging" -) - -type PluginContext interface { - Logger() logging.Logger - CertStore() cert.Store - Audit() audit.Emitter -} - -type ProtocolHandler interface { - Start(ctx PluginContext, config config.HandlerConfig) error - Shutdown(ctx context.Context) error -} diff --git a/pkg/api/registration.go b/pkg/api/registration.go deleted file mode 100644 index 1f8662c..0000000 --- a/pkg/api/registration.go +++ /dev/null @@ -1,54 +0,0 @@ -//go:generate mockgen -source=$GOFILE -destination=./../../internal/mock/plugins/handler_registry.mock.go -package=plugins_mock -package api - -import ( - "fmt" - "regexp" - "strings" -) - -var ( - pluginFileNamePattern = regexp.MustCompile(`[\w\-]+\.so$`) -) - -type Registration func(registry HandlerRegistry) error - -type HandlerRegistry interface { - RegisterHandler(handlerName string, handlerProvider func() ProtocolHandler) - AvailableHandlers() []string - HandlerForName(handlerName string) (ProtocolHandler, bool) -} - -func NewHandlerRegistry() HandlerRegistry { - return &handlerRegistry{ - handlers: make(map[string]func() ProtocolHandler), - } -} - -type handlerRegistry struct { - handlers map[string]func() ProtocolHandler -} - -func (h handlerRegistry) AvailableHandlers() (availableHandlers []string) { - for s := range h.handlers { - availableHandlers = append(availableHandlers, s) - } - return -} - -func (h *handlerRegistry) HandlerForName(handlerName string) (instance ProtocolHandler, ok bool) { - handlerName = strings.ToLower(handlerName) - var provider func() ProtocolHandler - if provider, ok = h.handlers[handlerName]; ok { - instance = provider() - } - return -} - -func (h *handlerRegistry) RegisterHandler(handlerName string, handlerProvider func() ProtocolHandler) { - handlerName = strings.ToLower(handlerName) - if _, exists := h.handlers[handlerName]; exists { - panic(fmt.Sprintf("handler with name %s is already registered - there's something strange...in the neighborhood", handlerName)) - } - h.handlers[handlerName] = handlerProvider -} diff --git a/pkg/api/registration_test.go b/pkg/api/registration_test.go deleted file mode 100644 index 561d12f..0000000 --- a/pkg/api/registration_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package api - -import ( - "reflect" - "testing" -) - -func Test_handlerRegistry_HandlerForName(t *testing.T) { - type fields struct { - handlers map[string]func() ProtocolHandler - } - type args struct { - handlerName string - } - tests := []struct { - name string - fields fields - args args - wantInstance 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]func() ProtocolHandler{ - "pseudo": func() 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, - } - 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) - } - }) - } -} diff --git a/pkg/audit/event.go b/pkg/audit/event.go index dc9f696..969ba57 100644 --- a/pkg/audit/event.go +++ b/pkg/audit/event.go @@ -29,7 +29,7 @@ type Event struct { TLS *TLSDetails } -func (e *Event) ProtoMessage() *EventEntity { +func (e Event) ProtoMessage() *EventEntity { var tlsDetails *TLSDetailsEntity = nil if e.TLS != nil { tlsDetails = e.TLS.ProtoMessage() diff --git a/pkg/cert/cache_test.go b/pkg/cert/cache_test.go index 8d911ba..71fa798 100644 --- a/pkg/cert/cache_test.go +++ b/pkg/cert/cache_test.go @@ -12,7 +12,6 @@ import ( "github.com/golang/mock/gomock" certmock "gitlab.com/inetmock/inetmock/internal/mock/cert" - "gitlab.com/inetmock/inetmock/pkg/config" ) const ( @@ -286,13 +285,13 @@ func Test_fileSystemCache_Put(t *testing.T) { } func setupCertGen() Generator { - return NewDefaultGenerator(config.CertOptions{ - Validity: config.ValidityByPurpose{ - Server: config.ValidityDuration{ + return NewDefaultGenerator(CertOptions{ + Validity: ValidityByPurpose{ + Server: ValidityDuration{ NotBeforeRelative: serverRelativeValidity, NotAfterRelative: serverRelativeValidity, }, - CA: config.ValidityDuration{ + CA: ValidityDuration{ NotBeforeRelative: caRelativeValidity, NotAfterRelative: caRelativeValidity, }, diff --git a/pkg/config/certs.go b/pkg/cert/certs.go similarity index 98% rename from pkg/config/certs.go rename to pkg/cert/certs.go index ef614d1..65e744b 100644 --- a/pkg/config/certs.go +++ b/pkg/cert/certs.go @@ -1,4 +1,4 @@ -package config +package cert import ( "crypto/tls" diff --git a/pkg/cert/constants.go b/pkg/cert/constants.go index 8960242..204c415 100644 --- a/pkg/cert/constants.go +++ b/pkg/cert/constants.go @@ -1,13 +1,14 @@ package cert const ( - defaultServerValidityDuration = "168h" - defaultCAValidityDuration = "17520h" + CurveTypeP224 CurveType = "P224" + CurveTypeP256 CurveType = "P256" + CurveTypeP384 CurveType = "P384" + CurveTypeP521 CurveType = "P521" + CurveTypeED25519 CurveType = "ED25519" - certCachePathConfigKey = "tls.certCachePath" - ecdsaCurveConfigKey = "tls.ecdsaCurve" - caCertValidityNotBeforeKey = "tls.validity.ca.notBeforeRelative" - caCertValidityNotAfterKey = "tls.validity.ca.notAfterRelative" - serverCertValidityNotBeforeKey = "tls.validity.server.notBeforeRelative" - serverCertValidityNotAfterKey = "tls.validity.server.notAfterRelative" + TLSVersionSSL3 TLSVersion = "SSL3" + TLSVersionTLS10 TLSVersion = "TLS10" + TLSVersionTLS11 TLSVersion = "TLS11" + TLSVersionTLS12 TLSVersion = "TLS12" ) diff --git a/pkg/cert/defaults.go b/pkg/cert/defaults.go index 6ea10be..b7e0fda 100644 --- a/pkg/cert/defaults.go +++ b/pkg/cert/defaults.go @@ -1,44 +1,20 @@ package cert import ( - "reflect" - - "gitlab.com/inetmock/inetmock/pkg/defaulting" + "github.com/imdario/mergo" ) 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{""} - } - } + defaultOptions = &GenerationOptions{ + Country: []string{"US"}, + Locality: []string{"San Francisco"}, + Organization: []string{"INetMock"}, + StreetAddress: []string{"Golden Gate Bridge"}, + PostalCode: []string{"94016"}, + Province: []string{""}, } ) -func init() { - certOptionsType := reflect.TypeOf(GenerationOptions{}) - defaulters.Register(certOptionsType, certOptionsDefaulter) +func applyDefaultGenerationOptions(opts *GenerationOptions) error { + return mergo.Merge(opts, defaultOptions) } diff --git a/pkg/cert/defaults_test.go b/pkg/cert/defaults_test.go index e5cf2f5..403db98 100644 --- a/pkg/cert/defaults_test.go +++ b/pkg/cert/defaults_test.go @@ -6,13 +6,14 @@ import ( ) func Test_certOptionsDefaulter(t *testing.T) { - tests := []struct { + type testCase struct { name string arg GenerationOptions expected GenerationOptions - }{ + } + tests := []testCase{ { - name: "", + name: "Empty options", arg: GenerationOptions{ CommonName: "CA", }, @@ -26,14 +27,50 @@ func Test_certOptionsDefaulter(t *testing.T) { Province: []string{""}, }, }, + { + name: "Options with country set", + arg: GenerationOptions{ + CommonName: "CA", + Country: []string{"DE"}, + }, + expected: GenerationOptions{ + CommonName: "CA", + Country: []string{"DE"}, + Locality: []string{"San Francisco"}, + Organization: []string{"INetMock"}, + StreetAddress: []string{"Golden Gate Bridge"}, + PostalCode: []string{"94016"}, + Province: []string{""}, + }, + }, + { + name: "Options with organization set set", + arg: GenerationOptions{ + CommonName: "CA", + Organization: []string{"inetmock"}, + }, + 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) + scenario := func(tt testCase) func(t *testing.T) { + return func(t *testing.T) { + if err := applyDefaultGenerationOptions(&tt.arg); err != nil { + t.Errorf("applyDefaultGenerationOptions() error = %v", err) + } if !reflect.DeepEqual(tt.expected, tt.arg) { t.Errorf("Apply defaulter expected=%v got=%v", tt.expected, tt.arg) } - }) + } + } + for _, tt := range tests { + t.Run(tt.name, scenario(tt)) } } diff --git a/pkg/cert/generator.go b/pkg/cert/generator.go index a59eadb..d57f872 100644 --- a/pkg/cert/generator.go +++ b/pkg/cert/generator.go @@ -10,13 +10,6 @@ import ( "encoding/pem" "math/big" "net" - - "gitlab.com/inetmock/inetmock/pkg/config" - "gitlab.com/inetmock/inetmock/pkg/defaulting" -) - -var ( - defaulters = defaulting.New() ) type GenerationOptions struct { @@ -37,11 +30,11 @@ type Generator interface { ServerCert(options GenerationOptions, ca *tls.Certificate) (*tls.Certificate, error) } -func NewDefaultGenerator(options config.CertOptions) Generator { +func NewDefaultGenerator(options CertOptions) Generator { return NewGenerator(options, NewTimeSource(), defaultKeyProvider(options)) } -func NewGenerator(options config.CertOptions, source TimeSource, provider KeyProvider) Generator { +func NewGenerator(options CertOptions, source TimeSource, provider KeyProvider) Generator { return &generator{ options: options, provider: provider, @@ -50,7 +43,7 @@ func NewGenerator(options config.CertOptions, source TimeSource, provider KeyPro } type generator struct { - options config.CertOptions + options CertOptions provider KeyProvider timeSource TimeSource outDir string @@ -65,7 +58,7 @@ func (g *generator) privateKey() (key interface{}, err error) { } func (g *generator) ServerCert(options GenerationOptions, ca *tls.Certificate) (cert *tls.Certificate, err error) { - defaulters.Apply(&options) + applyDefaultGenerationOptions(&options) var serialNumber *big.Int if serialNumber, err = generateSerialNumber(); err != nil { return @@ -116,7 +109,7 @@ func (g *generator) ServerCert(options GenerationOptions, ca *tls.Certificate) ( } func (g generator) CACert(options GenerationOptions) (crt *tls.Certificate, err error) { - defaulters.Apply(&options) + applyDefaultGenerationOptions(&options) var privateKey interface{} var serialNumber *big.Int diff --git a/pkg/cert/options.go b/pkg/cert/options.go index 65da2f6..ad59aa4 100644 --- a/pkg/cert/options.go +++ b/pkg/cert/options.go @@ -1,16 +1,10 @@ package cert -import ( - "os" - - "gitlab.com/inetmock/inetmock/pkg/config" -) - -func init() { +/*func init() { config.AddDefaultValue(certCachePathConfigKey, os.TempDir()) - config.AddDefaultValue(ecdsaCurveConfigKey, string(config.CurveTypeED25519)) + config.AddDefaultValue(ecdsaCurveConfigKey, string(CurveTypeED25519)) config.AddDefaultValue(caCertValidityNotBeforeKey, defaultCAValidityDuration) config.AddDefaultValue(caCertValidityNotAfterKey, defaultCAValidityDuration) config.AddDefaultValue(serverCertValidityNotBeforeKey, defaultServerValidityDuration) config.AddDefaultValue(serverCertValidityNotAfterKey, defaultServerValidityDuration) -} +}*/ diff --git a/pkg/cert/options_test.go b/pkg/cert/options_test.go index 9956c6b..cecc945 100644 --- a/pkg/cert/options_test.go +++ b/pkg/cert/options_test.go @@ -7,7 +7,6 @@ import ( "time" "github.com/spf13/viper" - "gitlab.com/inetmock/inetmock/pkg/config" ) func readViper(cfg string) *viper.Viper { @@ -26,7 +25,7 @@ func Test_loadFromConfig(t *testing.T) { tests := []struct { name string args args - want config.CertOptions + want CertOptions wantErr bool }{ { @@ -49,19 +48,19 @@ tls: certCachePath: /tmp/inetmock/ `), }, - want: config.CertOptions{ - RootCACert: config.File{ + want: CertOptions{ + RootCACert: File{ PublicKeyPath: "./ca.pem", PrivateKeyPath: "./ca.key", }, CertCachePath: "/tmp/inetmock/", - Curve: config.CurveTypeP256, - Validity: config.ValidityByPurpose{ - CA: config.ValidityDuration{ + Curve: CurveTypeP256, + Validity: ValidityByPurpose{ + CA: ValidityDuration{ NotBeforeRelative: 17520 * time.Hour, NotAfterRelative: 17520 * time.Hour, }, - Server: config.ValidityDuration{ + Server: ValidityDuration{ NotBeforeRelative: 168 * time.Hour, NotAfterRelative: 168 * time.Hour, }, @@ -77,7 +76,7 @@ tls: privateKey: ./ca.key `), }, - want: config.CertOptions{}, + want: CertOptions{}, wantErr: true, }, { @@ -89,7 +88,7 @@ tls: publicKey: ./ca.pem `), }, - want: config.CertOptions{}, + want: CertOptions{}, wantErr: true, }, { @@ -102,19 +101,19 @@ tls: privateKey: ./ca.key `), }, - want: config.CertOptions{ - RootCACert: config.File{ + want: CertOptions{ + RootCACert: File{ PublicKeyPath: "./ca.pem", PrivateKeyPath: "./ca.key", }, CertCachePath: os.TempDir(), - Curve: config.CurveTypeED25519, - Validity: config.ValidityByPurpose{ - CA: config.ValidityDuration{ + Curve: CurveTypeED25519, + Validity: ValidityByPurpose{ + CA: ValidityDuration{ NotBeforeRelative: 17520 * time.Hour, NotAfterRelative: 17520 * time.Hour, }, - Server: config.ValidityDuration{ + Server: ValidityDuration{ NotBeforeRelative: 168 * time.Hour, NotAfterRelative: 168 * time.Hour, }, diff --git a/pkg/cert/store.go b/pkg/cert/store.go index 0dbe78e..a9a8391 100644 --- a/pkg/cert/store.go +++ b/pkg/cert/store.go @@ -8,7 +8,6 @@ import ( "crypto/x509" "net" - "gitlab.com/inetmock/inetmock/pkg/config" "gitlab.com/inetmock/inetmock/pkg/logging" "go.uber.org/zap" ) @@ -18,7 +17,7 @@ const ( ) var ( - defaultKeyProvider = func(options config.CertOptions) func() (key interface{}, err error) { + defaultKeyProvider = func(options CertOptions) func() (key interface{}, err error) { return func() (key interface{}, err error) { return privateKeyForCurve(options) } @@ -34,20 +33,20 @@ type Store interface { } func NewDefaultStore( - config config.Config, + options CertOptions, logger logging.Logger, ) (Store, error) { timeSource := NewTimeSource() return NewStore( - config.TLSConfig(), - NewFileSystemCache(config.TLSConfig().CertCachePath, timeSource), - NewDefaultGenerator(config.TLSConfig()), + options, + NewFileSystemCache(options.CertCachePath, timeSource), + NewDefaultGenerator(options), logger, ) } func NewStore( - options config.CertOptions, + options CertOptions, cache Cache, generator Generator, logger logging.Logger, @@ -67,7 +66,7 @@ func NewStore( } type store struct { - options config.CertOptions + options CertOptions caCert *tls.Certificate cache Cache timeSource TimeSource @@ -147,15 +146,15 @@ func (s *store) GetCertificate(serverName string, ip string) (cert *tls.Certific return } -func privateKeyForCurve(options config.CertOptions) (privateKey interface{}, err error) { +func privateKeyForCurve(options CertOptions) (privateKey interface{}, err error) { switch options.Curve { - case config.CurveTypeP224: + case CurveTypeP224: privateKey, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader) - case config.CurveTypeP256: + case CurveTypeP256: privateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - case config.CurveTypeP384: + case CurveTypeP384: privateKey, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) - case config.CurveTypeP521: + case CurveTypeP521: privateKey, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader) default: privateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go deleted file mode 100644 index daf977b..0000000 --- a/pkg/config/config_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package config - -import ( - "testing" - - "github.com/spf13/pflag" -) - -func Test_config_ReadConfig(t *testing.T) { - type args struct { - flags *pflag.FlagSet - config string - } - tests := []struct { - name string - args args - matcher func(Config) bool - wantErr bool - }{ - { - name: "Test endpoints config", - args: args{ - flags: pflag.NewFlagSet("", pflag.ContinueOnError), - config: ` -endpoints: - plainHttp: - handler: http_mock - listenAddress: 0.0.0.0 - ports: - - 80 - - 8080 - options: {} - proxy: - handler: http_proxy - listenAddress: 0.0.0.0 - ports: - - 3128 - options: - target: - ipAddress: 127.0.0.1 - port: 80 -`, - }, - matcher: func(c Config) bool { - if len(c.EndpointConfigs()) < 1 { - t.Error("Expected EndpointConfigs to be set but is empty") - return false - } - - return true - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cfg := CreateConfig(tt.args.flags) - if err := cfg.ReadConfigString(tt.args.config, "yaml"); (err != nil) != tt.wantErr { - t.Errorf("ReadConfig() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if !tt.matcher(cfg) { - t.Error("matcher error") - } - }) - } -} diff --git a/pkg/config/constants.go b/pkg/config/constants.go deleted file mode 100644 index 66f47f0..0000000 --- a/pkg/config/constants.go +++ /dev/null @@ -1,17 +0,0 @@ -package config - -const ( - EndpointsKey = "endpoints" - OptionsKey = "options" - - CurveTypeP224 CurveType = "P224" - CurveTypeP256 CurveType = "P256" - CurveTypeP384 CurveType = "P384" - CurveTypeP521 CurveType = "P521" - CurveTypeED25519 CurveType = "ED25519" - - TLSVersionSSL3 TLSVersion = "SSL3" - TLSVersionTLS10 TLSVersion = "TLS10" - TLSVersionTLS11 TLSVersion = "TLS11" - TLSVersionTLS12 TLSVersion = "TLS12" -) diff --git a/pkg/config/handler_config.go b/pkg/config/handler_config.go deleted file mode 100644 index b7fae34..0000000 --- a/pkg/config/handler_config.go +++ /dev/null @@ -1,18 +0,0 @@ -package config - -import ( - "fmt" - - "github.com/spf13/viper" -) - -type HandlerConfig struct { - HandlerName string - Port uint16 - ListenAddress string - Options *viper.Viper -} - -func (h HandlerConfig) ListenAddr() string { - return fmt.Sprintf("%s:%d", h.ListenAddress, h.Port) -} diff --git a/pkg/config/handler_config_test.go b/pkg/config/handler_config_test.go deleted file mode 100644 index be66f85..0000000 --- a/pkg/config/handler_config_test.go +++ /dev/null @@ -1,168 +0,0 @@ -package config - -import ( - "reflect" - "testing" - - "github.com/spf13/viper" -) - -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 Handler for uninitialized struct", - fields: fields{}, - want: "", - }, - { - name: "Get expected Handler 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("Handler() = %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) - } - }) - } -} diff --git a/pkg/config/multi_handler_config.go b/pkg/config/multi_handler_config.go deleted file mode 100644 index 6a821ec..0000000 --- a/pkg/config/multi_handler_config.go +++ /dev/null @@ -1,25 +0,0 @@ -package config - -import ( - "github.com/spf13/viper" -) - -type EndpointConfig struct { - Handler string - Ports []uint16 - ListenAddress string - Options *viper.Viper -} - -func (m EndpointConfig) HandlerConfigs() []HandlerConfig { - configs := make([]HandlerConfig, 0) - for _, port := range m.Ports { - configs = append(configs, HandlerConfig{ - HandlerName: m.Handler, - Port: port, - ListenAddress: m.ListenAddress, - Options: m.Options, - }) - } - return configs -} diff --git a/pkg/defaulting/defaulter.go b/pkg/defaulting/defaulter.go deleted file mode 100644 index 10c75d9..0000000 --- a/pkg/defaulting/defaulter.go +++ /dev/null @@ -1,41 +0,0 @@ -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 ®istry{ - 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 - } - - given = append(given, defaulter...) - - 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) - } - } -} diff --git a/pkg/defaulting/defaulter_test.go b/pkg/defaulting/defaulter_test.go deleted file mode 100644 index 73caaba..0000000 --- a/pkg/defaulting/defaulter_test.go +++ /dev/null @@ -1,110 +0,0 @@ -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{}) { - if i, ok := instance.(*sample); ok { - 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 := ®istry{ - 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 := ®istry{ - 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) - } - }) - } -} diff --git a/pkg/logging/factory.go b/pkg/logging/factory.go index 961ee55..b42a286 100644 --- a/pkg/logging/factory.go +++ b/pkg/logging/factory.go @@ -42,7 +42,7 @@ func ParseLevel(levelString string) zap.AtomicLevel { } func CreateLogger() (Logger, error) { - if zapLogger, err := loggingConfig.Build(); err != nil { + if zapLogger, err := loggingConfig.Build(zap.AddCallerSkip(2)); err != nil { return nil, err } else { return NewLogger(zapLogger), nil @@ -55,11 +55,3 @@ func CreateTestLogger(tb testing.TB) Logger { encoder: zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), } } - -func MustCreateLogger() Logger { - if logger, err := CreateLogger(); err != nil { - panic(err) - } else { - return logger - } -} diff --git a/pkg/logging/test_logger.go b/pkg/logging/test_logger.go index 3ae9a93..263f0c5 100644 --- a/pkg/logging/test_logger.go +++ b/pkg/logging/test_logger.go @@ -19,17 +19,19 @@ type testLogger struct { func (t testLogger) Named(s string) Logger { return testLogger{ - name: s, - tb: t.tb, - fields: t.fields, + encoder: t.encoder, + name: s, + tb: t.tb, + fields: t.fields, } } func (t testLogger) With(fields ...zap.Field) Logger { return &testLogger{ - name: t.name, - fields: append(t.fields, fields...), - tb: t.tb, + encoder: t.encoder, + name: t.name, + fields: append(t.fields, fields...), + tb: t.tb, } } diff --git a/testdata/integration.dockerfile b/testdata/integration.dockerfile new file mode 100644 index 0000000..c058cef --- /dev/null +++ b/testdata/integration.dockerfile @@ -0,0 +1,20 @@ +FROM golang:1.15-alpine as build + +WORKDIR /app + +COPY ./ ./ + +RUN go build -o inetmock ./cmd/inetmock + +FROM alpine:3.13 + +WORKDIR /app + +COPY --from=build /app/inetmock ./ +COPY --from=build /app/config-container.yaml /etc/inetmock/config.yaml +COPY --from=build /app/assets/fakeFiles /var/lib/inetmock/fakeFiles +COPY --from=build /app/assets/demoCA /var/lib/inetmock/ca + +RUN mkdir -p /var/run/inetmock + +CMD /app/inetmock serve \ No newline at end of file