diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3af8f37..ec52ec8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,6 +9,10 @@ test: stage: test script: - task cli-cover-report + artifacts: + reports: + junit: out/report.xml + cobertura: out/coverage.xml lint: stage: test diff --git a/.goreleaser.yml b/.goreleaser.yml index e426e71..be5c7a6 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -7,7 +7,7 @@ before: builds: - id: "inetmock" binary: inetmock - main: ./cmd/inetmock/main.go + main: ./cmd/inetmock/ ldflags: - -w -s env: @@ -19,7 +19,7 @@ builds: - amd64 - id: "imctl" binary: imctl - main: ./cmd/imctl/main.go + main: ./cmd/imctl/ ldflags: - -w -s goos: @@ -65,7 +65,7 @@ release: name: inetmock dockers: - - binaries: + - ids: - inetmock - imctl image_templates: @@ -81,4 +81,4 @@ dockers: - "--label=org.opencontainers.image.version={{.Version}}" extra_files: - config-container.yaml - - assets/fakeFiles/ + - assets/ diff --git a/Taskfile.yml b/Taskfile.yml index bada179..169d953 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -5,7 +5,7 @@ vars: INETMOCK_PKG: gitlab.com/inetmock/inetmock/cmd/inetmock IMCTL_PKG: gitlab.com/inetmock/inetmock/cmd/imctl PROTO_FILES: - sh: find ./api/ -type f -name "*.proto" -printf "%f " + sh: find ./api/ -type f -name "*.proto" -printf "%p " env: GOOS: linux @@ -26,7 +26,7 @@ tasks: sources: - "**/*.proto" cmds: - - protoc --proto_path ./api/ --go_out=./internal/rpc --go_opt=paths=source_relative --go-grpc_out=./internal/rpc --go-grpc_opt=paths=source_relative {{ .PROTO_FILES }} + - protoc --proto_path ./api/proto/ --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative {{ .PROTO_FILES }} go-generate: sources: @@ -46,8 +46,11 @@ tasks: - generate cmds: - mkdir -p {{ .OUT_DIR }} - - go test -coverprofile={{ .OUT_DIR }}/cov-raw.out -v ./... + - cmd: go test -coverprofile={{ .OUT_DIR }}/cov-raw.out -covermode count -v ./... 2>&1 | tee {{ .OUT_DIR }}/test_output + ignore_error: true + - cat {{ .OUT_DIR }}/test_output | go-junit-report -set-exit-code > {{ .OUT_DIR }}/report.xml - grep -v "generated" {{ .OUT_DIR }}/cov-raw.out > {{ .OUT_DIR }}/cov.out + - gocover-cobertura < {{ .OUT_DIR }}/cov.out > {{ .OUT_DIR }}/coverage.xml - rm -f {{ .OUT_DIR }}/cov-raw.out cli-cover-report: diff --git a/api/proto/internal/rpc/audit.proto b/api/proto/internal/rpc/audit.proto new file mode 100644 index 0000000..3a1aab6 --- /dev/null +++ b/api/proto/internal/rpc/audit.proto @@ -0,0 +1,52 @@ +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 = "AuditProto"; + +import 'pkg/audit/event_entity.proto'; + +package inetmock.rpc; + +enum FileOpenMode { + TRUNCATE = 0; + APPEND = 1; +} + +message WatchEventsRequest { + string watcherName = 1; +} + +message RegisterFileSinkRequest { + string targetPath = 1; + FileOpenMode openMode = 2; + uint32 permissions = 3; +} + +message RegisterFileSinkResponse { + +} + +message RemoveFileSinkRequest { + string targetPath = 1; +} + +message RemoveFileSinkResponse { + bool SinkGotRemoved = 1; +} + +message ListSinksRequest { + +} + +message ListSinksResponse { + repeated string sinks = 1; +} + +service Audit { + rpc WatchEvents (WatchEventsRequest) returns (stream inetmock.audit.EventEntity); + rpc RegisterFileSink (RegisterFileSinkRequest) returns (RegisterFileSinkResponse); + rpc RemoveFileSink (RemoveFileSinkRequest) returns (RemoveFileSinkResponse); + rpc ListSinks(ListSinksRequest) returns (ListSinksResponse); +} \ No newline at end of file diff --git a/api/endpoints.proto b/api/proto/internal/rpc/endpoints.proto similarity index 96% rename from api/endpoints.proto rename to api/proto/internal/rpc/endpoints.proto index 3981ff5..db21768 100644 --- a/api/endpoints.proto +++ b/api/proto/internal/rpc/endpoints.proto @@ -5,12 +5,7 @@ option java_multiple_files = true; option java_package = "com.github.baez90.inetmock.rpc"; option java_outer_classname = "EndpointsProto"; -package inetmock; - -service Endpoints { - rpc GetEndpoints (GetEndpointsRequest) returns (GetEndpointsResponse) { - } -} +package inetmock.rpc; message GetEndpointsRequest { } @@ -25,4 +20,9 @@ message Endpoint { 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/handlers.proto b/api/proto/internal/rpc/handlers.proto similarity index 95% rename from api/handlers.proto rename to api/proto/internal/rpc/handlers.proto index 6440a99..44a3ff9 100644 --- a/api/handlers.proto +++ b/api/proto/internal/rpc/handlers.proto @@ -5,7 +5,7 @@ option java_multiple_files = true; option java_package = "com.github.baez90.inetmock.rpc"; option java_outer_classname = "HandlersProto"; -package inetmock; +package inetmock.rpc; service Handlers { rpc GetHandlers (GetHandlersRequest) returns (GetHandlersResponse) { diff --git a/api/health.proto b/api/proto/internal/rpc/health.proto similarity index 96% rename from api/health.proto rename to api/proto/internal/rpc/health.proto index 8c9b6b9..3ff1aee 100644 --- a/api/health.proto +++ b/api/proto/internal/rpc/health.proto @@ -5,7 +5,7 @@ option java_multiple_files = true; option java_package = "com.github.baez90.inetmock.rpc"; option java_outer_classname = "HealthProto"; -package inetmock; +package inetmock.rpc; service Health { rpc GetHealth (HealthRequest) returns (HealthResponse) { diff --git a/api/proto/pkg/audit/details/dns_details.proto b/api/proto/pkg/audit/details/dns_details.proto new file mode 100644 index 0000000..1575e32 --- /dev/null +++ b/api/proto/pkg/audit/details/dns_details.proto @@ -0,0 +1,42 @@ +syntax = "proto3"; + +option go_package = "gitlab.com/inetmock/inetmock/pkg/audit/details"; +option java_multiple_files = true; +option java_package = "com.github.baez90.inetmock.audit.details"; +option java_outer_classname = "HandlerEventProto"; + +package inetmock.audit; + +enum DNSOpCode { + Query = 0; + Status = 2; + Notify = 4; + Update = 5; +} + +enum ResourceRecordType { + UnknownRR = 0; + A = 1; + NS = 2; + CNAME = 5; + SOA = 6; + PTR = 12; + HINFO = 13; + MINFO = 14; + MX = 15; + TXT = 16; + RP = 17; + AAAA = 28; + SRV = 33; + NAPTR = 35; +} + +message DNSQuestionEntity { + ResourceRecordType type = 1; + string name = 2; +} + +message DNSDetailsEntity { + DNSOpCode opcode = 1; + repeated DNSQuestionEntity questions = 2; +} \ No newline at end of file diff --git a/api/proto/pkg/audit/details/http_details.proto b/api/proto/pkg/audit/details/http_details.proto new file mode 100644 index 0000000..ffd5ac0 --- /dev/null +++ b/api/proto/pkg/audit/details/http_details.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +option go_package = "gitlab.com/inetmock/inetmock/pkg/audit/details"; +option java_multiple_files = true; +option java_package = "com.github.baez90.inetmock.audit.details"; +option java_outer_classname = "HandlerEventProto"; + +package inetmock.audit; + +enum HTTPMethod { + GET = 0; + HEAD = 1; + POST = 2; + PUT = 3; + DELETE = 4; + CONNECT = 5; + OPTIONS = 6; + TRACE = 7; + PATCH = 8; +} + +message HTTPHeaderValue { + repeated string values = 1; +} + +message HTTPDetailsEntity { + HTTPMethod method = 1; + string host = 2; + string uri = 3; + string proto = 4; + map headers = 5; +} \ No newline at end of file diff --git a/api/proto/pkg/audit/event_entity.proto b/api/proto/pkg/audit/event_entity.proto new file mode 100644 index 0000000..00492af --- /dev/null +++ b/api/proto/pkg/audit/event_entity.proto @@ -0,0 +1,51 @@ +syntax = "proto3"; + +option go_package = "gitlab.com/inetmock/inetmock/pkg/audit"; +option java_multiple_files = true; +option java_package = "com.github.baez90.inetmock.audit"; +option java_outer_classname = "HandlerEventProto"; + +package inetmock.audit; + +import "google/protobuf/any.proto"; +import "google/protobuf/timestamp.proto"; + +enum TransportProtocol { + UNKNOWN_TRANSPORT = 0; + TCP = 1; + UDP = 2; +} + +enum AppProtocol { + UNKNOWN_APPLICATION = 0; + DNS = 1; + HTTP = 2; + HTTP_PROXY = 3; +} + +enum TLSVersion { + SSLv30 = 0; + TLS10 = 1; + TLS11 = 2; + TLS12 = 3; + TLS13 = 4; +} + +message TLSDetailsEntity { + TLSVersion version = 1; + string cipherSuite = 2; + string serverName = 3; +} + +message EventEntity { + int64 id = 1; + google.protobuf.Timestamp timestamp = 2; + TransportProtocol transport = 3; + AppProtocol application = 4; + bytes sourceIP = 5; + bytes destinationIP = 6; + uint32 sourcePort = 7; + uint32 destinationPort = 8; + TLSDetailsEntity tls = 9; + google.protobuf.Any protocolDetails = 10; +} \ No newline at end of file diff --git a/assets/demoCA/ca.key b/assets/demoCA/ca.key index b3aa03e..c77e1ea 100644 --- a/assets/demoCA/ca.key +++ b/assets/demoCA/ca.key @@ -1,5 +1,5 @@ -----BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgTTz25fFLS2WO4hXD -162B059HEe+MAQtV4iGXf7HfKCihRANCAAT3D181Tzrz6i9Mx75pmyAsg+itojO9 -sHXZSswmfsh46IVK46m0hXNHgPvD2WYW5m1PHvRl3B0vDo/2Y6sOU/Q9 +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEk8KFe6Vl3xefH82 +Na91uPQkBz4D/s1xE+59+kaRM1+hRANCAASnjLNiOc4UNsRBIQN+FOv8CEd6ftDH +Egg2dWUgGCFsgE2VaEG+jOwamBzTjCWbr0azIc+Brupd0CAChq8hVeaM -----END PRIVATE KEY----- diff --git a/assets/demoCA/ca.pem b/assets/demoCA/ca.pem index c603b9c..a815780 100644 --- a/assets/demoCA/ca.pem +++ b/assets/demoCA/ca.pem @@ -1,12 +1,14 @@ -----BEGIN CERTIFICATE----- -MIIB3DCCAYKgAwIBAgIQHQIFIEcNZjsDP+wDtGPMXzAKBggqhkjOPQQDAjBOMRAw -DgYDVQQGEwdnZXJtYW55MREwDwYDVQQHEwhEb3J0bXVuZDERMA8GA1UEChMISU5l -dE1vY2sxFDASBgNVBAMTC0lOZXRNb2NrIENBMB4XDTIwMDYxNTEwNTEzNloXDTIw -MDYxNTEwNTEzNlowTjEQMA4GA1UEBhMHZ2VybWFueTERMA8GA1UEBxMIRG9ydG11 -bmQxETAPBgNVBAoTCElOZXRNb2NrMRQwEgYDVQQDEwtJTmV0TW9jayBDQTBZMBMG -ByqGSM49AgEGCCqGSM49AwEHA0IABPcPXzVPOvPqL0zHvmmbICyD6K2iM72wddlK -zCZ+yHjohUrjqbSFc0eA+8PZZhbmbU8e9GXcHS8Oj/Zjqw5T9D2jQjBAMA4GA1Ud -DwEB/wQEAwIChDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDwYDVR0T -AQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiBecJsOL7ej0kCkWOnoQJpW3JuY -KQIxQBT+XXPKEJj14AIhANG4twTloC3amz8Y7Zn3DVtvjXlTgg8YwjBFG+JioQOe +MIICGjCCAb+gAwIBAgIQZutydXTVGWPa32GhLwn4tDAKBggqhkjOPQQDAjBcMRAw +DgYDVQQGEwdnZXJtYW55MQwwCgYDVQQIEwNOUlcxETAPBgNVBAcTCERvcnRtdW5k +MREwDwYDVQQKEwhJTmV0TW9jazEUMBIGA1UEAxMLSU5ldE1vY2sgQ0EwIBcNMDEw +MjAxMDgyODA5WhgPMjA1MTAxMjAwODI4MDlaMFwxEDAOBgNVBAYTB2dlcm1hbnkx +DDAKBgNVBAgTA05SVzERMA8GA1UEBxMIRG9ydG11bmQxETAPBgNVBAoTCElOZXRN +b2NrMRQwEgYDVQQDEwtJTmV0TW9jayBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEH +A0IABKeMs2I5zhQ2xEEhA34U6/wIR3p+0McSCDZ1ZSAYIWyATZVoQb6M7BqYHNOM +JZuvRrMhz4Gu6l3QIAKGryFV5oyjYTBfMA4GA1UdDwEB/wQEAwIChDAdBgNVHSUE +FjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQUkD5REm4Z34O8em3l5cXmHgd3S4swCgYIKoZIzj0EAwIDSQAwRgIhAJ5xWTz/ +/51Jyo7sO4hi3FhyDYJXeC0koRSQCvMggWuPAiEAjpgtX/UFukTAfTFsDp8q3AXZ +Kn0ejVWjmkh4K7nEJ5s= -----END CERTIFICATE----- diff --git a/build/docker/inetmock.dockerfile b/build/docker/inetmock.dockerfile index 33a2e3f..0fc09fe 100644 --- a/build/docker/inetmock.dockerfile +++ b/build/docker/inetmock.dockerfile @@ -1,5 +1,5 @@ # Runtime layer -FROM alpine:3.12 +FROM alpine:3.13 # Create appuser and group. ARG USER=inetmock @@ -20,6 +20,7 @@ RUN addgroup -S -g "${GROUP_ID}" "${GROUP}" && \ COPY --chown=$USER:$GROUP inetmock imctl /usr/lib/inetmock/bin/ COPY --chown=$USER:$GROUP assets/fakeFiles /var/lib/inetmock/fakeFiles/ +COPY --chown=$USER:$GROUP assets/demoCA /var/lib/inetmock/ca COPY config-container.yaml /etc/inetmock/config.yaml RUN mkdir -p /var/run/inetmock /var/lib/inetmock/certs /usr/lib/inetmock && \ diff --git a/cmd/imctl/audit_sinks.go b/cmd/imctl/audit_sinks.go new file mode 100644 index 0000000..75aefb5 --- /dev/null +++ b/cmd/imctl/audit_sinks.go @@ -0,0 +1,118 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "os" + + "github.com/spf13/cobra" + "gitlab.com/inetmock/inetmock/internal/format" + "gitlab.com/inetmock/inetmock/internal/rpc" + "gitlab.com/inetmock/inetmock/pkg/audit" +) + +var ( + listSinksCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls", "dir"}, + Short: "List all subscribed sinks", + RunE: runListSinks, + } + addFileCmd = &cobra.Command{ + Use: "add-file", + Aliases: []string{"add"}, + Short: "subscribe events to a file", + Args: cobra.ExactArgs(1), + RunE: runAddFile, + } + + removeFileCmd = &cobra.Command{ + Use: "remove-file", + Aliases: []string{"rm", "del"}, + Short: "remove file subscription", + Args: cobra.ExactArgs(1), + RunE: runRemoveFile, + } + + readFileCmd = &cobra.Command{ + Use: "read-file", + Aliases: []string{"cat"}, + Short: "reads an audit file and prints the events", + Args: cobra.ExactArgs(1), + RunE: runReadFile, + } +) + +type printableSink struct { + Name string +} + +func runListSinks(*cobra.Command, []string) (err error) { + auditClient := rpc.NewAuditClient(conn) + ctx, cancel := context.WithTimeout(cliApp.Context(), grpcTimeout) + defer cancel() + + var resp *rpc.ListSinksResponse + if resp, err = auditClient.ListSinks(ctx, &rpc.ListSinksRequest{}); err != nil { + return + } + + var sinks []printableSink + for _, s := range resp.Sinks { + sinks = append(sinks, printableSink{Name: s}) + } + + writer := format.Writer(outputFormat, os.Stdout) + err = writer.Write(sinks) + return +} + +func runAddFile(_ *cobra.Command, args []string) (err error) { + auditClient := rpc.NewAuditClient(conn) + ctx, cancel := context.WithTimeout(cliApp.Context(), grpcTimeout) + defer cancel() + + _, err = auditClient.RegisterFileSink(ctx, &rpc.RegisterFileSinkRequest{TargetPath: args[0]}) + return +} + +func runRemoveFile(_ *cobra.Command, args []string) (err error) { + auditClient := rpc.NewAuditClient(conn) + ctx, cancel := context.WithTimeout(cliApp.Context(), grpcTimeout) + defer cancel() + + _, err = auditClient.RemoveFileSink(ctx, &rpc.RemoveFileSinkRequest{TargetPath: args[0]}) + return +} + +func runReadFile(_ *cobra.Command, args []string) (err error) { + if len(args) != 1 { + return errors.New("expected only 1 argument") + } + + var reader io.ReadCloser + if reader, err = os.Open(args[0]); err != nil { + return + } + + eventReader := audit.NewEventReader(reader) + var ev audit.Event + + for err == nil { + if ev, err = eventReader.Read(); err == nil { + var jsonBytes []byte + if jsonBytes, err = json.Marshal(ev); err == nil { + fmt.Println(string(jsonBytes)) + } + } + } + + if errors.Is(err, io.EOF) { + err = nil + } + + return +} diff --git a/cmd/imctl/audit_watch.go b/cmd/imctl/audit_watch.go new file mode 100644 index 0000000..42f4345 --- /dev/null +++ b/cmd/imctl/audit_watch.go @@ -0,0 +1,54 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/spf13/cobra" + "gitlab.com/inetmock/inetmock/internal/rpc" + "gitlab.com/inetmock/inetmock/pkg/audit" +) + +var ( + watchEventsCmd = &cobra.Command{ + Use: "watch", + Short: "Watch all audit events", + RunE: watchAuditEvents, + } + + auditCmd = &cobra.Command{ + Use: "audit", + Short: "Interact with the audit stream", + } + + listenerName string +) + +func watchAuditEvents(_ *cobra.Command, _ []string) (err error) { + auditClient := rpc.NewAuditClient(conn) + + var watchClient rpc.Audit_WatchEventsClient + if watchClient, err = auditClient.WatchEvents(cliApp.Context(), &rpc.WatchEventsRequest{WatcherName: listenerName}); err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + + go func() { + var protoEv *audit.EventEntity + for protoEv, err = watchClient.Recv(); err == nil; protoEv, err = watchClient.Recv() { + ev := audit.NewEventFromProto(protoEv) + var out []byte + out, err = json.Marshal(ev) + if err != nil { + continue + } + fmt.Println(string(out)) + } + }() + + <-cliApp.Context().Done() + err = watchClient.CloseSend() + + return +} diff --git a/internal/cmd/endpoints.go b/cmd/imctl/endpoints.go similarity index 76% rename from internal/cmd/endpoints.go rename to cmd/imctl/endpoints.go index 3cd2749..622dd4d 100644 --- a/internal/cmd/endpoints.go +++ b/cmd/imctl/endpoints.go @@ -1,4 +1,4 @@ -package cmd +package main import ( "context" @@ -8,14 +8,13 @@ import ( "github.com/spf13/cobra" "gitlab.com/inetmock/inetmock/internal/format" "gitlab.com/inetmock/inetmock/internal/rpc" - "google.golang.org/grpc" ) var ( getEndpoints = &cobra.Command{ Use: "get", Short: "Get all running endpoints", - Run: runGetEndpoints, + RunE: runGetEndpoints, } endpointsCmd = &cobra.Command{ @@ -26,7 +25,7 @@ var ( ) type printableEndpoint struct { - Id string + ID string Name string Handler string ListenAddress string @@ -35,7 +34,7 @@ type printableEndpoint struct { func fromEndpoint(ep *rpc.Endpoint) *printableEndpoint { return &printableEndpoint{ - Id: ep.Id, + ID: ep.Id, Name: ep.Name, Handler: ep.Handler, ListenAddress: ep.ListenAddress, @@ -50,16 +49,10 @@ func fromEndpoints(eps []*rpc.Endpoint) (out []*printableEndpoint) { return } -func runGetEndpoints(_ *cobra.Command, _ []string) { - var err error - var conn *grpc.ClientConn - - if conn, err = grpc.Dial(inetMockSocketPath, grpc.WithInsecure()); err != nil { - fmt.Printf("Failed to connecto INetMock socket: %v\n", err) - os.Exit(10) - } +func runGetEndpoints(_ *cobra.Command, _ []string) (err error) { endpointsClient := rpc.NewEndpointsClient(conn) - ctx, _ := context.WithTimeout(context.Background(), grpcTimeout) + 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) @@ -70,4 +63,5 @@ func runGetEndpoints(_ *cobra.Command, _ []string) { if err = writer.Write(fromEndpoints(endpointsResp.Endpoints)); err != nil { fmt.Printf("Error occurred during writing response values: %v\n", err) } + return } diff --git a/internal/cmd/handlers.go b/cmd/imctl/handlers.go similarity index 80% rename from internal/cmd/handlers.go rename to cmd/imctl/handlers.go index 136829c..06822da 100644 --- a/internal/cmd/handlers.go +++ b/cmd/imctl/handlers.go @@ -1,4 +1,4 @@ -package cmd +package main import ( "context" @@ -8,7 +8,6 @@ import ( "github.com/spf13/cobra" "gitlab.com/inetmock/inetmock/internal/format" "gitlab.com/inetmock/inetmock/internal/rpc" - "google.golang.org/grpc" ) var ( @@ -39,15 +38,11 @@ func fromHandlers(hs []string) (handlers []*printableHandler) { } func runGetHandlers(_ *cobra.Command, _ []string) { - var err error - var conn *grpc.ClientConn - - if conn, err = grpc.Dial(inetMockSocketPath, grpc.WithInsecure()); err != nil { - fmt.Printf("Failed to connecto INetMock socket: %v\n", err) - os.Exit(10) - } handlersClient := rpc.NewHandlersClient(conn) - ctx, _ := context.WithTimeout(context.Background(), grpcTimeout) + + 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 { diff --git a/internal/cmd/health.go b/cmd/imctl/health.go similarity index 91% rename from internal/cmd/health.go rename to cmd/imctl/health.go index 2b17d8f..e19b1c8 100644 --- a/internal/cmd/health.go +++ b/cmd/imctl/health.go @@ -1,4 +1,4 @@ -package cmd +package main import ( "context" @@ -8,7 +8,6 @@ import ( "github.com/spf13/cobra" "gitlab.com/inetmock/inetmock/internal/format" "gitlab.com/inetmock/inetmock/internal/rpc" - "google.golang.org/grpc" ) var ( @@ -55,15 +54,10 @@ func fromComponentsHealth(componentsHealth map[string]*rpc.ComponentHealth) (com } func getHealthResult() (healthResp *rpc.HealthResponse, err error) { - var conn *grpc.ClientConn - - if conn, err = grpc.Dial(inetMockSocketPath, grpc.WithInsecure()); err != nil { - return - } - var healthClient = rpc.NewHealthClient(conn) - ctx, _ := context.WithTimeout(context.Background(), grpcTimeout) + ctx, cancel := context.WithTimeout(context.Background(), grpcTimeout) healthResp, err = healthClient.GetHealth(ctx, &rpc.HealthRequest{}) + cancel() return } diff --git a/cmd/imctl/main.go b/cmd/imctl/main.go index 1771ede..93a0803 100644 --- a/cmd/imctl/main.go +++ b/cmd/imctl/main.go @@ -1,9 +1,64 @@ package main -import "gitlab.com/inetmock/inetmock/internal/cmd" +import ( + "context" + "fmt" + "os" + "os/user" + "time" + + "github.com/google/uuid" + "github.com/spf13/cobra" + "gitlab.com/inetmock/inetmock/internal/app" + "google.golang.org/grpc" +) + +var ( + inetMockSocketPath string + outputFormat string + grpcTimeout time.Duration + cliApp app.App + conn *grpc.ClientConn +) func main() { - if err := cmd.ExecuteClientCommand(); err != nil { - panic(err) + 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). + WithInitTasks(func(_ *cobra.Command, _ []string) (err error) { + return initGRPCConnection() + }). + WithLogger() + + cliApp.RootCommand().PersistentFlags().StringVar(&inetMockSocketPath, "socket-path", "unix:///var/run/inetmock.sock", "Path to the INetMock socket file") + cliApp.RootCommand().PersistentFlags().StringVarP(&outputFormat, "format", "f", "table", "Output format to use. Possible values: table, json, yaml") + cliApp.RootCommand().PersistentFlags().DurationVar(&grpcTimeout, "grpc-timeout", 5*time.Second, "Timeout to connect to the gRPC API") + + currentUser := "" + if usr, err := user.Current(); err == nil { + currentUser = usr.Username + } else { + currentUser = uuid.New().String() } + + hostname := "." + if hn, err := os.Hostname(); err == nil { + hostname = hn + } + + watchEventsCmd.PersistentFlags().StringVar(&listenerName, "listener-name", fmt.Sprintf("%s\\%s is watching", hostname, currentUser), "set listener name - defaults to the current username, if the user cannot be determined a random UUID will be used") + auditCmd.AddCommand(listSinksCmd, watchEventsCmd, addFileCmd, removeFileCmd, readFileCmd) + + cliApp.MustRun() +} + +func initGRPCConnection() (err error) { + dialCtx, cancel := context.WithTimeout(cliApp.Context(), grpcTimeout) + conn, err = grpc.DialContext(dialCtx, inetMockSocketPath, grpc.WithInsecure()) + cancel() + + return } diff --git a/internal/cmd/ca.go b/cmd/inetmock/ca.go similarity index 83% rename from internal/cmd/ca.go rename to cmd/inetmock/ca.go index df84505..b28ad0f 100644 --- a/internal/cmd/ca.go +++ b/cmd/inetmock/ca.go @@ -1,4 +1,4 @@ -package cmd +package main import ( "crypto/tls" @@ -8,7 +8,6 @@ import ( "github.com/spf13/cobra" "gitlab.com/inetmock/inetmock/pkg/cert" "gitlab.com/inetmock/inetmock/pkg/config" - "gitlab.com/inetmock/inetmock/pkg/logging" "go.uber.org/zap" ) @@ -34,6 +33,7 @@ var ( certOutPath, curveName string ) +//nolint:lll func init() { generateCaCmd = &cobra.Command{ Use: "generate-ca", @@ -50,14 +50,14 @@ func init() { generateCaCmd.Flags().StringSliceVar(&caCertOptions.Locality, generateCaLocalityName, nil, "Locality information to append to certificate") generateCaCmd.Flags().StringSliceVar(&caCertOptions.StreetAddress, generateCaStreetAddressName, nil, "Street address information to append to certificate") generateCaCmd.Flags().StringSliceVar(&caCertOptions.PostalCode, generateCaPostalCodeName, nil, "Postal code information to append to certificate") - generateCaCmd.Flags().StringVar(&certOutPath, generateCACertOutPath, "", "Path where CA files should be stored") + generateCaCmd.Flags().StringVar(&certOutPath, generateCACertOutPath, "", "Path where CA files should be stored") generateCaCmd.Flags().StringVar(&curveName, generateCACurveName, "", "Name of the curve to use, if empty ED25519 is used, other valid values are [P224, P256,P384,P521]") generateCaCmd.Flags().DurationVar(¬Before, generateCANotBeforeRelative, 17520*time.Hour, "Relative time value since when in the past the CA certificate should be valid. The value has a time unit, the greatest time unit is h for hour.") generateCaCmd.Flags().DurationVar(¬After, generateCANotAfterRelative, 17520*time.Hour, "Relative time value until when in the future the CA certificate should be valid. The value has a time unit, the greatest time unit is h for hour.") } func runGenerateCA(_ *cobra.Command, _ []string) { - logger := server.Logger().Named("generate-ca") + logger := serverApp.Logger().Named("generate-ca") logger = logger.With( zap.String(generateCACurveName, curveName), @@ -108,25 +108,3 @@ func runGenerateCA(_ *cobra.Command, _ []string) { } logger.Info("completed certificate generation") } - -func getDurationFlag(cmd *cobra.Command, flagName string, logger logging.Logger) (val time.Duration, err error) { - if val, err = cmd.Flags().GetDuration(flagName); err != nil { - logger.Error( - "failed to parse parse flag", - zap.String("flag", flagName), - zap.Error(err), - ) - } - return -} - -func getStringFlag(cmd *cobra.Command, flagName string, logger logging.Logger) (val string, err error) { - if val, err = cmd.Flags().GetString(flagName); err != nil { - logger.Error( - "failed to parse parse flag", - zap.String("flag", flagName), - zap.Error(err), - ) - } - return -} diff --git a/cmd/inetmock/main.go b/cmd/inetmock/main.go index f478e04..cab14d1 100644 --- a/cmd/inetmock/main.go +++ b/cmd/inetmock/main.go @@ -1,14 +1,33 @@ package main import ( - "gitlab.com/inetmock/inetmock/internal/cmd" - _ "gitlab.com/inetmock/inetmock/plugins/dns_mock" - _ "gitlab.com/inetmock/inetmock/plugins/http_mock" - _ "gitlab.com/inetmock/inetmock/plugins/http_proxy" - _ "gitlab.com/inetmock/inetmock/plugins/metrics_exporter" - _ "gitlab.com/inetmock/inetmock/plugins/tls_interceptor" + "gitlab.com/inetmock/inetmock/internal/app" + dns "gitlab.com/inetmock/inetmock/internal/endpoint/handler/dns/mock" + http "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/mock" + "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/proxy" + "gitlab.com/inetmock/inetmock/internal/endpoint/handler/metrics" + "gitlab.com/inetmock/inetmock/internal/endpoint/handler/tls/interceptor" +) + +var ( + serverApp app.App ) func main() { - cmd.ExecuteServerCommand() + serverApp = app.NewApp("inetmock", "INetMock is lightweight internet mock"). + WithHandlerRegistry( + http.AddHTTPMock, + dns.AddDNSMock, + interceptor.AddTLSInterceptor, + proxy.AddHTTPProxy, + metrics.AddMetricsExporter). + WithCommands(serveCmd, generateCaCmd). + WithConfig(). + WithLogger(). + WithHealthChecker(). + WithCertStore(). + WithEventStream(). + WithEndpointManager() + + serverApp.MustRun() } diff --git a/internal/cmd/serve.go b/cmd/inetmock/serve.go similarity index 57% rename from internal/cmd/serve.go rename to cmd/inetmock/serve.go index 97ce54f..fd5dc5b 100644 --- a/internal/cmd/serve.go +++ b/cmd/inetmock/serve.go @@ -1,4 +1,4 @@ -package cmd +package main import ( "strings" @@ -19,13 +19,13 @@ var ( ) func startINetMock(_ *cobra.Command, _ []string) { - rpcAPI := rpc.NewINetMockAPI(server) - logger := server.Logger().Named("inetmock").With(zap.String("command", "serve")) + rpcAPI := rpc.NewINetMockAPI(serverApp) + logger := serverApp.Logger().Named("inetmock").With(zap.String("command", "serve")) - for endpointName, endpointHandler := range server.Config().EndpointConfigs() { - handlerSubConfig := server.Config().Viper().Sub(strings.Join([]string{config.EndpointsKey, endpointName, config.OptionsKey}, ".")) + 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 := server.EndpointManager().CreateEndpoint(endpointName, endpointHandler); err != nil { + if err := serverApp.EndpointManager().CreateEndpoint(endpointName, endpointHandler); err != nil { logger.Warn( "error occurred while creating endpoint", zap.String("endpointName", endpointName), @@ -35,7 +35,7 @@ func startINetMock(_ *cobra.Command, _ []string) { } } - server.EndpointManager().StartEndpoints() + serverApp.EndpointManager().StartEndpoints() if err := rpcAPI.StartServer(); err != nil { logger.Error( "failed to start gRPC API", @@ -43,12 +43,12 @@ func startINetMock(_ *cobra.Command, _ []string) { ) } - <-server.Context().Done() + <-serverApp.Context().Done() logger.Info( "App context canceled - shutting down", ) rpcAPI.StopServer() - server.EndpointManager().ShutdownEndpoints() + serverApp.EndpointManager().ShutdownEndpoints() } diff --git a/config-container.yaml b/config-container.yaml index f449288..636f536 100644 --- a/config-container.yaml +++ b/config-container.yaml @@ -1,18 +1,44 @@ x-response-rules: &httpResponseRules rules: - pattern: ".*\\.(?i)exe" + matcher: Path + - pattern: "^application/octet-stream$" + target: Accept + matcher: Header response: /var/lib/inetmock/fakeFiles/sample.exe - - pattern: ".*\\.(?i)(jpg|jpeg)" + - pattern: "^image/jpeg$" + target: Accept + matcher: Header response: /var/lib/inetmock/fakeFiles/default.jpg + - pattern: ".*\\.(?i)(jpg|jpeg)" + matcher: Path + response: /var/lib/inetmock/fakeFiles/default.jpg + - pattern: "^image/png$" + target: Accept + matcher: Header + response: /var/lib/inetmock/fakeFiles/default.png - pattern: ".*\\.(?i)png" + matcher: Path response: /var/lib/inetmock/fakeFiles/default.png - pattern: ".*\\.(?i)gif" + matcher: Path response: /var/lib/inetmock/fakeFiles/default.gif - pattern: ".*\\.(?i)ico" + matcher: Path response: /var/lib/inetmock/fakeFiles/default.ico - - pattern: ".*\\.(?i)txt" + - pattern: "^text/plain$" + target: Accept + matcher: Header response: /var/lib/inetmock/fakeFiles/default.txt + - pattern: ".*\\.(?i)txt" + matcher: Path + response: /var/lib/inetmock/fakeFiles/default.txt + - pattern: "^text/html$" + target: Accept + matcher: Header + response: /var/lib/inetmock/fakeFiles/default.html - pattern: ".*" + matcher: Path response: /var/lib/inetmock/fakeFiles/default.html api: @@ -43,6 +69,15 @@ endpoints: - 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 @@ -52,16 +87,6 @@ endpoints: target: ipAddress: 127.0.0.1 port: 80 - httpsDowngrade: - handler: tls_interceptor - listenAddress: 0.0.0.0 - ports: - - 443 - - 8443 - options: - target: - ipAddress: 127.0.0.1 - port: 80 plainDns: handler: dns_mock listenAddress: 0.0.0.0 @@ -85,4 +110,11 @@ endpoints: options: target: ipAddress: 127.0.0.1 - port: 53 \ No newline at end of file + port: 53 + metrics: + handler: metrics_exporter + listenAddress: 0.0.0.0 + ports: + - 9110 + options: + route: /metrics \ No newline at end of file diff --git a/config.yaml b/config.yaml index c71558e..7ce1cdc 100644 --- a/config.yaml +++ b/config.yaml @@ -69,6 +69,15 @@ endpoints: - 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 @@ -78,16 +87,6 @@ endpoints: target: ipAddress: 127.0.0.1 port: 80 - httpsDowngrade: - handler: tls_interceptor - listenAddress: 0.0.0.0 - ports: - - 443 - - 8443 - options: - target: - ipAddress: 127.0.0.1 - port: 80 plainDns: handler: dns_mock listenAddress: 0.0.0.0 diff --git a/go.mod b/go.mod index e17d974..bd4d26f 100644 --- a/go.mod +++ b/go.mod @@ -3,18 +3,21 @@ module gitlab.com/inetmock/inetmock go 1.15 require ( + github.com/bwmarrin/snowflake v0.3.0 github.com/golang/mock v1.4.4 - github.com/golang/protobuf v1.4.2 - github.com/google/uuid v1.1.2 - github.com/miekg/dns v1.1.31 + 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/prometheus/client_golang v1.7.1 - github.com/spf13/cobra v1.0.0 + github.com/prometheus/client_golang v1.9.0 + github.com/spf13/cobra v1.1.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.7.1 + go.uber.org/multierr v1.6.0 go.uber.org/zap v1.16.0 - google.golang.org/grpc v1.34.0 + google.golang.org/grpc v1.35.0 google.golang.org/protobuf v1.25.0 gopkg.in/elazarl/goproxy.v1 v1.0.0-20180725130230-947c36da3153 - gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ) diff --git a/go.sum b/go.sum index fec03c6..df50c82 100644 --- a/go.sum +++ b/go.sum @@ -14,15 +14,27 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 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/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= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +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= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -30,51 +42,79 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +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/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= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= 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-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +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/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= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/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/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/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.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +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= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +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/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= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= 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-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 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/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/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= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -83,7 +123,6 @@ 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= @@ -94,6 +133,7 @@ github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -108,21 +148,30 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/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/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= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -133,6 +182,7 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -142,37 +192,60 @@ 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/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= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 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/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.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= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 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/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/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-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/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/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= @@ -182,102 +255,194 @@ 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/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= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +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/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/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +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= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +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/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= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.9.0 h1:Rrch9mh17XcxvEu9D9DEpb4isxjGBtcevQjKvxPRQIU= +github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tyspaKM= +github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= 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= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 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/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/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/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= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tmc/grpc-websocket-proxy v0.0.0-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/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= +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/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= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -286,8 +451,14 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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= @@ -310,11 +481,13 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -325,10 +498,13 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR 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= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +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= @@ -343,9 +519,11 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -355,13 +533,25 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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-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= @@ -370,9 +560,14 @@ 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/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-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= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -395,16 +590,19 @@ 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/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= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= @@ -413,6 +611,7 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -420,21 +619,24 @@ 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-20201214200347-8c77b98c765d h1:HV9Z9qMhQEsdlvxNFELgQ11RkMzO3CMkjEySjCtuLes= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +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/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= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 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.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 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/cmd/protoc-gen-go-grpc v1.0.1 h1:M8spwkmx0pHrPq+uMdl22w5CvJ/Y+oAJTIs9oGoCpOE= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.1/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +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/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= @@ -453,12 +655,19 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= 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/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/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= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -466,8 +675,16 @@ gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +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-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -475,3 +692,5 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/internal/app/app.go b/internal/app/app.go index ce8d1ff..aeb0494 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -9,13 +9,16 @@ import ( "syscall" "github.com/spf13/cobra" - "gitlab.com/inetmock/inetmock/internal/endpoints" + "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" ) @@ -25,34 +28,76 @@ var ( developmentLogs bool ) +type contextKey string + +const ( + loggerKey contextKey = "gitlab.com/inetmock/inetmock/app/context/logger" + configKey contextKey = "gitlab.com/inetmock/inetmock/app/context/config" + handlerRegistryKey contextKey = "gitlab.com/inetmock/inetmock/app/context/handlerRegistry" + healthCheckerKey contextKey = "gitlab.com/inetmock/inetmock/app/context/healthChecker" + endpointManagerKey contextKey = "gitlab.com/inetmock/inetmock/app/context/endpointManager" + certStoreKey contextKey = "gitlab.com/inetmock/inetmock/app/context/certStore" + eventStreamKey contextKey = "gitlab.com/inetmock/inetmock/app/context/eventStream" +) + type App interface { api.PluginContext + EventStream() audit.EventStream Config() config.Config Checker() health.Checker - EndpointManager() endpoints.EndpointManager + EndpointManager() endpoint.EndpointManager HandlerRegistry() api.HandlerRegistry Context() context.Context + RootCommand() *cobra.Command MustRun() Shutdown() + + // WithCommands adds subcommands to the root command + // requires nothing WithCommands(cmds ...*cobra.Command) App + + // WithHandlerRegistry builds up the handler registry + // requires nothing + WithHandlerRegistry(registrations ...api.Registration) App + + // WithHealthChecker adds the health checker mechanism + // requires nothing + WithHealthChecker() App + + // WithLogger configures the logging system + // requires nothing + WithLogger() App + + // WithEndpointManager creates an endpoint manager instance and adds it to the context + // requires WithHandlerRegistry, WithHealthChecker and WithLogger + WithEndpointManager() App + + // WithCertStore initializes the cert store + // requires WithLogger and WithConfig + WithCertStore() App + + // WithEventStream adds the audit event stream + // requires WithLogger + WithEventStream() App + + // WithConfig loads the config + // requires nothing + WithConfig() App + + WithInitTasks(task ...func(cmd *cobra.Command, args []string) (err error)) App } type app struct { - cfg config.Config - rootCmd *cobra.Command - rootLogger logging.Logger - certStore cert.Store - checker health.Checker - endpointManager endpoints.EndpointManager - registry api.HandlerRegistry - ctx context.Context - cancel context.CancelFunc + rootCmd *cobra.Command + ctx context.Context + cancel context.CancelFunc + lateInitTasks []func(cmd *cobra.Command, args []string) (err error) } func (a *app) MustRun() { if err := a.rootCmd.Execute(); err != nil { - if a.rootLogger != nil { - a.rootLogger.Error( + if a.Logger() != nil { + a.Logger().Error( "Failed to run inetmock", zap.Error(err), ) @@ -62,62 +107,234 @@ func (a *app) MustRun() { } } -func (a app) Logger() logging.Logger { - return a.rootLogger +func (a *app) Logger() logging.Logger { + val := a.ctx.Value(loggerKey) + if val == nil { + return nil + } + return val.(logging.Logger) } -func (a app) Config() config.Config { - return a.cfg +func (a *app) Config() config.Config { + val := a.ctx.Value(configKey) + if val == nil { + return nil + } + return val.(config.Config) } -func (a app) CertStore() cert.Store { - return a.certStore +func (a *app) CertStore() cert.Store { + val := a.ctx.Value(certStoreKey) + if val == nil { + return nil + } + return val.(cert.Store) } -func (a app) Checker() health.Checker { - return a.checker +func (a *app) Checker() health.Checker { + val := a.ctx.Value(healthCheckerKey) + if val == nil { + return nil + } + return val.(health.Checker) } -func (a app) EndpointManager() endpoints.EndpointManager { - return a.endpointManager +func (a *app) EndpointManager() endpoint.EndpointManager { + val := a.ctx.Value(endpointManagerKey) + if val == nil { + return nil + } + return val.(endpoint.EndpointManager) } -func (a app) HandlerRegistry() api.HandlerRegistry { - return a.registry +func (a *app) Audit() audit.Emitter { + val := a.ctx.Value(eventStreamKey) + if val == nil { + return nil + } + return val.(audit.Emitter) } -func (a app) Context() context.Context { +func (a *app) EventStream() audit.EventStream { + val := a.ctx.Value(eventStreamKey) + if val == nil { + return nil + } + return val.(audit.EventStream) +} + +func (a *app) HandlerRegistry() api.HandlerRegistry { + val := a.ctx.Value(handlerRegistryKey) + if val == nil { + return nil + } + return val.(api.HandlerRegistry) +} + +func (a *app) Context() context.Context { return a.ctx } -func (a app) Shutdown() { +func (a *app) RootCommand() *cobra.Command { + return a.rootCmd +} + +func (a *app) Shutdown() { a.cancel() } +// WithCommands adds subcommands to the root command +// requires nothing func (a *app) WithCommands(cmds ...*cobra.Command) App { a.rootCmd.AddCommand(cmds...) return a } -func NewApp(registrations ...api.Registration) (inetmockApp App, err error) { +// WithHandlerRegistry builds up the handler registry +// requires nothing +func (a *app) WithHandlerRegistry(registrations ...api.Registration) App { registry := api.NewHandlerRegistry() for _, registration := range registrations { - if err = registration(registry); err != nil { - return + if err := registration(registry); err != nil { + panic(err) } } - ctx, cancel := initAppContext() + a.ctx = context.WithValue(a.ctx, handlerRegistryKey, registry) + return a +} + +// WithHealthChecker adds the health checker mechanism +// requires nothing +func (a *app) WithHealthChecker() App { + checker := health.New() + a.ctx = context.WithValue(a.ctx, healthCheckerKey, checker) + return a +} + +// WithLogger configures the logging system +// requires nothing +func (a *app) WithLogger() App { + a.lateInitTasks = append(a.lateInitTasks, func(cmd *cobra.Command, args []string) (err error) { + logging.ConfigureLogging( + logging.ParseLevel(logLevel), + developmentLogs, + map[string]interface{}{ + "cwd": path.WorkingDirectory(), + "cmd": cmd.Name(), + "args": args, + }, + ) + + var logger logging.Logger + if logger, err = logging.CreateLogger(); err != nil { + return + } + a.ctx = context.WithValue(a.ctx, loggerKey, logger) + return + }) + return a +} + +// WithEndpointManager creates an endpoint manager instance and adds it to the context +// requires WithHandlerRegistry, WithHealthChecker and WithLogger +func (a *app) WithEndpointManager() App { + a.lateInitTasks = append(a.lateInitTasks, func(_ *cobra.Command, _ []string) (err error) { + epMgr := endpoint.NewEndpointManager( + a.HandlerRegistry(), + a.Logger().Named("EndpointManager"), + a.Checker(), + a, + ) + + a.ctx = context.WithValue(a.ctx, endpointManagerKey, epMgr) + return + }) + return a +} + +// WithCertStore initializes the cert store +// requires WithLogger and WithConfig +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.Logger().Named("CertStore"), + ); err != nil { + return + } + + a.ctx = context.WithValue(a.ctx, certStoreKey, certStore) + return + }) + return a +} + +// WithEventStream adds the audit event stream +// requires WithLogger +func (a *app) WithEventStream() App { + a.lateInitTasks = append(a.lateInitTasks, func(_ *cobra.Command, _ []string) (err error) { + var eventStream audit.EventStream + eventStream, err = audit.NewEventStream( + a.Logger().Named("EventStream"), + audit.WithSinkBufferSize(10), + ) + if err != nil { + return + } + + if err = eventStream.RegisterSink(a.ctx, sink.NewLogSink(a.Logger().Named("LogSink"))); err != nil { + return + } + + var metricSink audit.Sink + if metricSink, err = sink.NewMetricSink(); err != nil { + return + } + + if err = eventStream.RegisterSink(a.ctx, metricSink); err != nil { + return + } + + a.ctx = context.WithValue(a.ctx, eventStreamKey, eventStream) + return + }) + + return a +} + +// WithConfig loads the config +// requires nothing +func (a *app) WithConfig() App { + a.lateInitTasks = append(a.lateInitTasks, func(cmd *cobra.Command, _ []string) (err error) { + cfg := config.CreateConfig(cmd.Flags()) + if err = cfg.ReadConfig(configFilePath); err != nil { + return + } + a.ctx = context.WithValue(a.ctx, configKey, cfg) + return + }) + + return a +} + +func (a *app) WithInitTasks(task ...func(cmd *cobra.Command, args []string) (err error)) App { + a.lateInitTasks = append(a.lateInitTasks, task...) + return a +} + +func NewApp(name, short string) App { + ctx, cancel := initAppContext() a := &app{ rootCmd: &cobra.Command{ - Short: "INetMock is lightweight internet mock", + Use: name, + Short: short, }, - checker: health.New(), - registry: registry, - ctx: ctx, - cancel: cancel, + ctx: ctx, + cancel: cancel, } a.rootCmd.PersistentFlags().StringVar(&configFilePath, "config", "", "Path to config file that should be used") @@ -125,31 +342,13 @@ func NewApp(registrations ...api.Registration) (inetmockApp App, err error) { a.rootCmd.PersistentFlags().BoolVar(&developmentLogs, "development-logs", false, "Enable development mode logs") a.rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) (err error) { - logging.ConfigureLogging( - logging.ParseLevel(logLevel), - developmentLogs, - map[string]interface{}{ - "cwd": path.WorkingDirectory(), - }, - ) - - if a.rootLogger, err = logging.CreateLogger(); err != nil { - return + for _, initTask := range a.lateInitTasks { + err = multierr.Append(err, initTask(cmd, args)) } - - a.cfg = config.CreateConfig(cmd.Flags()) - - if err = a.cfg.ReadConfig(configFilePath); err != nil { - return - } - - a.certStore, err = cert.NewDefaultStore(a.cfg, a.rootLogger) - a.endpointManager = endpoints.NewEndpointManager(a.registry, a.Logger().Named("EndpointManager"), a.checker, a) - return } - return a, nil + return a } func initAppContext() (context.Context, context.CancelFunc) { diff --git a/internal/cmd/cli.go b/internal/cmd/cli.go deleted file mode 100644 index 68f5491..0000000 --- a/internal/cmd/cli.go +++ /dev/null @@ -1,34 +0,0 @@ -package cmd - -import ( - "time" - - "github.com/spf13/cobra" -) - -var ( - cliCmd = &cobra.Command{ - Use: "", - Short: "IMCTL is the CLI app to interact with an INetMock server", - } - - inetMockSocketPath string - outputFormat string - grpcTimeout time.Duration -) - -func init() { - cliCmd.PersistentFlags().StringVar(&inetMockSocketPath, "socket-path", "./inetmock.sock", "Path to the INetMock socket file") - cliCmd.PersistentFlags().StringVarP(&outputFormat, "format", "f", "table", "Output format to use. Possible values: table, json, yaml") - cliCmd.PersistentFlags().DurationVar(&grpcTimeout, "grpc-timeout", 5*time.Second, "Timeout to connect to the gRPC API") - - cliCmd.AddCommand(endpointsCmd, handlerCmd, healthCmd) - endpointsCmd.AddCommand(getEndpoints) - handlerCmd.AddCommand(getHandlersCmd) - healthCmd.AddCommand(generalHealthCmd) - healthCmd.AddCommand(containerHealthCmd) -} - -func ExecuteClientCommand() error { - return cliCmd.Execute() -} diff --git a/internal/cmd/server.go b/internal/cmd/server.go deleted file mode 100644 index b182b88..0000000 --- a/internal/cmd/server.go +++ /dev/null @@ -1,34 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - - "gitlab.com/inetmock/inetmock/internal/app" - "gitlab.com/inetmock/inetmock/plugins/dns_mock" - "gitlab.com/inetmock/inetmock/plugins/http_mock" - "gitlab.com/inetmock/inetmock/plugins/http_proxy" - "gitlab.com/inetmock/inetmock/plugins/metrics_exporter" - "gitlab.com/inetmock/inetmock/plugins/tls_interceptor" -) - -var ( - server app.App -) - -func ExecuteServerCommand() { - var err error - if server, err = app.NewApp( - http_mock.AddHTTPMock, - dns_mock.AddDNSMock, - tls_interceptor.AddTLSInterceptor, - http_proxy.AddHTTPProxy, - metrics_exporter.AddMetricsExporter, - ); err != nil { - fmt.Println(err.Error()) - os.Exit(1) - } - server. - WithCommands(serveCmd, generateCaCmd). - MustRun() -} diff --git a/internal/endpoints/constants.go b/internal/endpoint/constants.go similarity index 77% rename from internal/endpoints/constants.go rename to internal/endpoint/constants.go index 1e9f681..b7379eb 100644 --- a/internal/endpoints/constants.go +++ b/internal/endpoint/constants.go @@ -1,4 +1,4 @@ -package endpoints +package endpoint import "time" diff --git a/internal/endpoints/endpoint.go b/internal/endpoint/endpoint.go similarity index 98% rename from internal/endpoints/endpoint.go rename to internal/endpoint/endpoint.go index f27068b..9dd314a 100644 --- a/internal/endpoints/endpoint.go +++ b/internal/endpoint/endpoint.go @@ -1,5 +1,5 @@ //go:generate mockgen -source=$GOFILE -destination=./../../internal/mock/endpoints/endpoint.mock.go -package=endpoints_mock -package endpoints +package endpoint import ( "context" diff --git a/internal/endpoints/endpoint_manager.go b/internal/endpoint/endpoint_manager.go similarity index 97% rename from internal/endpoints/endpoint_manager.go rename to internal/endpoint/endpoint_manager.go index afc13e2..174a9d3 100644 --- a/internal/endpoints/endpoint_manager.go +++ b/internal/endpoint/endpoint_manager.go @@ -1,4 +1,4 @@ -package endpoints +package endpoint import ( "context" @@ -99,7 +99,7 @@ func (e *endpointManager) StartEndpoints() { } func (e *endpointManager) ShutdownEndpoints() { - var waitGroup sync.WaitGroup + waitGroup := new(sync.WaitGroup) waitGroup.Add(len(e.properlyStartedEndpoints)) parentCtx, _ := context.WithTimeout(context.Background(), shutdownTimeout) @@ -112,7 +112,7 @@ func (e *endpointManager) ShutdownEndpoints() { zap.String("endpoint", endpoint.Name()), ) endpointLogger.Info("Triggering shutdown of endpoint") - go shutdownEndpoint(ctx, endpoint, endpointLogger, &waitGroup) + go shutdownEndpoint(ctx, endpoint, endpointLogger, waitGroup) } waitGroup.Wait() @@ -149,8 +149,6 @@ func startEndpoint(ep Endpoint, ctx api.PluginContext, logger logging.Logger) (s success = false } - close(startSuccessful) - return } diff --git a/internal/endpoints/endpoint_manager_test.go b/internal/endpoint/endpoint_manager_test.go similarity index 99% rename from internal/endpoints/endpoint_manager_test.go rename to internal/endpoint/endpoint_manager_test.go index 0e49092..0c71cf4 100644 --- a/internal/endpoints/endpoint_manager_test.go +++ b/internal/endpoint/endpoint_manager_test.go @@ -1,4 +1,4 @@ -package endpoints +package endpoint import ( "reflect" diff --git a/plugins/dns_mock/fallback.go b/internal/endpoint/handler/dns/mock/fallback.go similarity index 98% rename from plugins/dns_mock/fallback.go rename to internal/endpoint/handler/dns/mock/fallback.go index 43697b1..8fb39a8 100644 --- a/plugins/dns_mock/fallback.go +++ b/internal/endpoint/handler/dns/mock/fallback.go @@ -1,4 +1,4 @@ -package dns_mock +package mock import ( "encoding/binary" diff --git a/plugins/dns_mock/fallback_test.go b/internal/endpoint/handler/dns/mock/fallback_test.go similarity index 99% rename from plugins/dns_mock/fallback_test.go rename to internal/endpoint/handler/dns/mock/fallback_test.go index a1996b3..33622be 100644 --- a/plugins/dns_mock/fallback_test.go +++ b/internal/endpoint/handler/dns/mock/fallback_test.go @@ -1,4 +1,4 @@ -package dns_mock +package mock import ( "net" diff --git a/plugins/dns_mock/handler.go b/internal/endpoint/handler/dns/mock/handler.go similarity index 84% rename from plugins/dns_mock/handler.go rename to internal/endpoint/handler/dns/mock/handler.go index d07f7e0..5e05432 100644 --- a/plugins/dns_mock/handler.go +++ b/internal/endpoint/handler/dns/mock/handler.go @@ -1,4 +1,4 @@ -package dns_mock +package mock import ( "context" @@ -15,22 +15,23 @@ type dnsHandler struct { dnsServer []*dns.Server } -func (d *dnsHandler) Start(_ api.PluginContext, config config.HandlerConfig) (err error) { +func (d *dnsHandler) Start(pluginCtx api.PluginContext, config config.HandlerConfig) (err error) { var options dnsOptions if options, err = loadFromConfig(config.Options); err != nil { return } listenAddr := config.ListenAddr() - d.logger = d.logger.With( + d.logger = pluginCtx.Logger().With( zap.String("handler_name", config.HandlerName), zap.String("address", listenAddr), ) handler := ®exHandler{ - handlerName: config.HandlerName, - fallback: options.Fallback, - logger: d.logger, + handlerName: config.HandlerName, + fallback: options.Fallback, + logger: pluginCtx.Logger(), + auditEmitter: pluginCtx.Audit(), } for _, rule := range options.Rules { diff --git a/plugins/dns_mock/protocol_options.go b/internal/endpoint/handler/dns/mock/protocol_options.go similarity index 98% rename from plugins/dns_mock/protocol_options.go rename to internal/endpoint/handler/dns/mock/protocol_options.go index f3aabc6..ea74351 100644 --- a/plugins/dns_mock/protocol_options.go +++ b/internal/endpoint/handler/dns/mock/protocol_options.go @@ -1,4 +1,4 @@ -package dns_mock +package mock import ( "net" diff --git a/internal/endpoint/handler/dns/mock/regex_handler.go b/internal/endpoint/handler/dns/mock/regex_handler.go new file mode 100644 index 0000000..64905b0 --- /dev/null +++ b/internal/endpoint/handler/dns/mock/regex_handler.go @@ -0,0 +1,139 @@ +package mock + +import ( + "net" + + "github.com/miekg/dns" + "github.com/prometheus/client_golang/prometheus" + "gitlab.com/inetmock/inetmock/pkg/audit" + "gitlab.com/inetmock/inetmock/pkg/audit/details" + "gitlab.com/inetmock/inetmock/pkg/logging" + "go.uber.org/zap" +) + +type regexHandler struct { + handlerName string + routes []resolverRule + fallback ResolverFallback + auditEmitter audit.Emitter + logger logging.Logger +} + +func (rh *regexHandler) AddRule(rule resolverRule) { + rh.routes = append(rh.routes, rule) +} + +func (rh *regexHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { + timer := prometheus.NewTimer(requestDurationHistogram.WithLabelValues(rh.handlerName)) + defer func() { + timer.ObserveDuration() + }() + + rh.recordRequest(r, w.LocalAddr(), w.RemoteAddr()) + + m := new(dns.Msg) + m.Compress = false + m.SetReply(r) + + if r.Opcode == dns.OpcodeQuery { + rh.handleQuery(m) + } + if err := w.WriteMsg(m); err != nil { + rh.logger.Error( + "Failed to write DNS response message", + zap.Error(err), + ) + } +} + +func (rh *regexHandler) handleQuery(m *dns.Msg) { + for _, q := range m.Question { + switch q.Qtype { + case dns.TypeA: + totalHandledRequestsCounter.WithLabelValues(rh.handlerName).Inc() + for _, rule := range rh.routes { + if !rule.pattern.MatchString(q.Name) { + continue + } + m.Authoritative = true + answer := &dns.A{ + Hdr: dns.RR_Header{ + Name: q.Name, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: 60, + }, + A: rule.response, + } + m.Answer = append(m.Answer, answer) + rh.logger.Info( + "matched DNS rule", + zap.String("pattern", rule.pattern.String()), + zap.String("response", rule.response.String()), + ) + return + } + rh.handleFallbackForMessage(m, q) + default: + unhandledRequestsCounter.WithLabelValues(rh.handlerName).Inc() + rh.logger.Warn( + "Unhandled DNS question type - no response will be sent", + zap.Uint16("question_type", q.Qtype), + ) + } + } +} + +func (rh *regexHandler) handleFallbackForMessage(m *dns.Msg, q dns.Question) { + fallbackIP := rh.fallback.GetIP() + answer := &dns.A{ + Hdr: dns.RR_Header{ + Name: q.Name, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: 60, + }, + A: fallbackIP, + } + rh.logger.Info( + "Falling back to generated IP", + zap.String("response", fallbackIP.String()), + ) + m.Authoritative = true + m.Answer = append(m.Answer, answer) +} + +func (rh *regexHandler) recordRequest(m *dns.Msg, localAddr, remoteAddr net.Addr) { + dnsDetails := &details.DNS{ + OPCode: details.DNSOpCode(m.Opcode), + } + + for _, q := range m.Question { + dnsDetails.Questions = append(dnsDetails.Questions, details.DNSQuestion{ + RRType: details.ResourceRecordType(q.Qtype), + Name: q.Name, + }) + } + + ev := audit.Event{ + Transport: guessTransportFromAddr(localAddr), + Application: audit.AppProtocol_DNS, + ProtocolDetails: dnsDetails, + } + + ev.SetSourceIPFromAddr(remoteAddr) + ev.SetDestinationIPFromAddr(localAddr) + + rh.auditEmitter.Emit(ev) +} + +func guessTransportFromAddr(addr net.Addr) audit.TransportProtocol { + switch addr.(type) { + case *net.TCPAddr: + return audit.TransportProtocol_TCP + case *net.UDPAddr: + return audit.TransportProtocol_UDP + default: + return audit.TransportProtocol_UNKNOWN_TRANSPORT + } +} diff --git a/plugins/dns_mock/register.go b/internal/endpoint/handler/dns/mock/register.go similarity index 77% rename from plugins/dns_mock/register.go rename to internal/endpoint/handler/dns/mock/register.go index ed32569..312f3e6 100644 --- a/plugins/dns_mock/register.go +++ b/internal/endpoint/handler/dns/mock/register.go @@ -1,11 +1,9 @@ -package dns_mock +package mock import ( "github.com/prometheus/client_golang/prometheus" "gitlab.com/inetmock/inetmock/pkg/api" - "gitlab.com/inetmock/inetmock/pkg/logging" "gitlab.com/inetmock/inetmock/pkg/metrics" - "go.uber.org/zap" ) const ( @@ -20,14 +18,6 @@ var ( ) func AddDNSMock(registry api.HandlerRegistry) (err error) { - var logger logging.Logger - if logger, err = logging.CreateLogger(); err != nil { - return - } - logger = logger.With( - zap.String("protocol_handler", name), - ) - if totalHandledRequestsCounter, err = metrics.Counter( name, "handled_requests_total", @@ -57,9 +47,7 @@ func AddDNSMock(registry api.HandlerRegistry) (err error) { } registry.RegisterHandler(name, func() api.ProtocolHandler { - return &dnsHandler{ - logger: logger, - } + return &dnsHandler{} }) return diff --git a/internal/endpoint/handler/http/audit.go b/internal/endpoint/handler/http/audit.go new file mode 100644 index 0000000..3a8d993 --- /dev/null +++ b/internal/endpoint/handler/http/audit.go @@ -0,0 +1,38 @@ +package http + +import ( + "crypto/tls" + "net/http" + + "gitlab.com/inetmock/inetmock/pkg/audit" + "gitlab.com/inetmock/inetmock/pkg/audit/details" +) + +func EventFromRequest(request *http.Request, app audit.AppProtocol) audit.Event { + httpDetails := details.HTTP{ + Method: request.Method, + Host: request.Host, + URI: request.RequestURI, + Proto: request.Proto, + Headers: request.Header, + } + + ev := audit.Event{ + Transport: audit.TransportProtocol_TCP, + Application: app, + ProtocolDetails: httpDetails, + } + + if request.TLS != nil { + ev.TLS = &audit.TLSDetails{ + Version: audit.TLSVersionToEntity(request.TLS.Version).String(), + CipherSuite: tls.CipherSuiteName(request.TLS.CipherSuite), + ServerName: request.TLS.ServerName, + } + } + + ev.SetDestinationIPFromAddr(localAddr(request.Context())) + ev.SetSourceIPFromAddr(remoteAddr(request.Context())) + + return ev +} diff --git a/internal/endpoint/handler/http/conn_context.go b/internal/endpoint/handler/http/conn_context.go new file mode 100644 index 0000000..3f9c6da --- /dev/null +++ b/internal/endpoint/handler/http/conn_context.go @@ -0,0 +1,35 @@ +package http + +import ( + "context" + "net" +) + +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" +) + +func StoreConnPropertiesInContext(ctx context.Context, c net.Conn) context.Context { + ctx = context.WithValue(ctx, remoteAddrKey, c.RemoteAddr()) + ctx = context.WithValue(ctx, localAddrKey, c.LocalAddr()) + return ctx +} + +func localAddr(ctx context.Context) net.Addr { + val := ctx.Value(localAddrKey) + if val == nil { + return nil + } + return val.(net.Addr) +} + +func remoteAddr(ctx context.Context) net.Addr { + val := ctx.Value(remoteAddrKey) + if val == nil { + return nil + } + return val.(net.Addr) +} diff --git a/plugins/http_mock/handler.go b/internal/endpoint/handler/http/mock/handler.go similarity index 64% rename from plugins/http_mock/handler.go rename to internal/endpoint/handler/http/mock/handler.go index c87bd2a..a4a8805 100644 --- a/plugins/http_mock/handler.go +++ b/internal/endpoint/handler/http/mock/handler.go @@ -1,11 +1,13 @@ -package http_mock +package mock import ( "context" + "crypto/tls" "errors" "fmt" "net/http" + 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" @@ -40,15 +42,25 @@ func (p *httpHandler) Start(ctx api.PluginContext, config config.HandlerConfig) router := &RegexpHandler{ logger: p.logger, + emitter: ctx.Audit(), handlerName: config.HandlerName, } - p.server = &http.Server{Addr: config.ListenAddr(), Handler: router} + 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() + go p.startServer(options.TLS) return } @@ -67,8 +79,17 @@ func (p *httpHandler) Shutdown(ctx context.Context) (err error) { return } -func (p *httpHandler) startServer() { - if err := p.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { +func (p *httpHandler) startServer(tls bool) { + var listen func() error + if tls { + listen = func() error { + return p.server.ListenAndServeTLS("", "") + } + } else { + listen = p.server.ListenAndServe + } + + if err := listen(); err != nil && !errors.Is(err, http.ErrServerClosed) { p.logger.Error( "failed to start http listener", zap.Error(err), diff --git a/plugins/http_mock/handler_test.go b/internal/endpoint/handler/http/mock/handler_test.go similarity index 89% rename from plugins/http_mock/handler_test.go rename to internal/endpoint/handler/http/mock/handler_test.go index f60d031..e23bbf7 100644 --- a/plugins/http_mock/handler_test.go +++ b/internal/endpoint/handler/http/mock/handler_test.go @@ -1,4 +1,4 @@ -package http_mock_test +package mock_test import ( "context" @@ -13,11 +13,12 @@ import ( "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" - "gitlab.com/inetmock/inetmock/plugins/http_mock" "go.uber.org/zap" ) @@ -64,7 +65,7 @@ func setupHandler(b *testing.B, ctrl *gomock.Controller, listenPort uint16) (api b.Helper() registry := api.NewHandlerRegistry() - if err := http_mock.AddHTTPMock(registry); err != nil { + if err := mock.AddHTTPMock(registry); err != nil { b.Errorf("AddHTTPMock() error = %v", err) } handler, ok := registry.HandlerForName("http_mock") @@ -72,11 +73,18 @@ func setupHandler(b *testing.B, ctrl *gomock.Controller, listenPort uint16) (api 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{ { diff --git a/plugins/http_mock/protocol_options.go b/internal/endpoint/handler/http/mock/protocol_options.go similarity index 96% rename from plugins/http_mock/protocol_options.go rename to internal/endpoint/handler/http/mock/protocol_options.go index 600cfb8..0a6099a 100644 --- a/plugins/http_mock/protocol_options.go +++ b/internal/endpoint/handler/http/mock/protocol_options.go @@ -1,5 +1,5 @@ //go:generate go-enum -f $GOFILE --lower --marshal --names -package http_mock +package mock import ( "net/http" @@ -50,6 +50,7 @@ func (tr targetRule) Response() string { } type httpOptions struct { + TLS bool Rules []targetRule } @@ -62,6 +63,7 @@ func loadFromConfig(config *viper.Viper) (options httpOptions, err error) { } tmpRules := struct { + TLS bool Rules []tmpCfg }{} @@ -69,6 +71,8 @@ func loadFromConfig(config *viper.Viper) (options httpOptions, err error) { return } + options.TLS = tmpRules.TLS + for _, i := range tmpRules.Rules { var rulePattern *regexp.Regexp var matchTargetValue RequestMatchTarget @@ -82,7 +86,7 @@ func loadFromConfig(config *viper.Viper) (options httpOptions, err error) { } if absoluteResponsePath, parseErr = filepath.Abs(i.Response); parseErr != nil { - + continue } options.Rules = append(options.Rules, targetRule{ diff --git a/plugins/http_mock/protocol_options_test.go b/internal/endpoint/handler/http/mock/protocol_options_test.go similarity index 78% rename from plugins/http_mock/protocol_options_test.go rename to internal/endpoint/handler/http/mock/protocol_options_test.go index e992c2f..abad022 100644 --- a/plugins/http_mock/protocol_options_test.go +++ b/internal/endpoint/handler/http/mock/protocol_options_test.go @@ -1,4 +1,4 @@ -package http_mock +package mock import ( "path/filepath" @@ -95,6 +95,34 @@ rules: }, wantErr: false, }, + { + 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 +`, + }, + wantOptions: httpOptions{ + TLS: true, + Rules: []targetRule{ + { + pattern: regexp.MustCompile("^application/octet-stream$"), + response: func() string { + p, _ := filepath.Abs("./assets/fakeFiles/sample.exe") + return p + }(), + requestMatchTarget: RequestMatchTargetHeader, + targetKey: "Content-Type", + }, + }, + }, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/plugins/http_mock/regex_router.go b/internal/endpoint/handler/http/mock/regex_router.go similarity index 58% rename from plugins/http_mock/regex_router.go rename to internal/endpoint/handler/http/mock/regex_router.go index 3de7ea4..28f9d8b 100644 --- a/plugins/http_mock/regex_router.go +++ b/internal/endpoint/handler/http/mock/regex_router.go @@ -1,11 +1,12 @@ -package http_mock +package mock import ( - "bytes" "net/http" "strconv" "github.com/prometheus/client_golang/prometheus" + imHttp "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http" + "gitlab.com/inetmock/inetmock/pkg/audit" "gitlab.com/inetmock/inetmock/pkg/logging" "go.uber.org/zap" ) @@ -19,16 +20,13 @@ type RegexpHandler struct { handlerName string logger logging.Logger routes []*route + emitter audit.Emitter } func (h *RegexpHandler) Handler(rule targetRule, handler http.Handler) { h.routes = append(h.routes, &route{rule, handler}) } -func (h *RegexpHandler) HandleFunc(rule targetRule, handler func(http.ResponseWriter, *http.Request)) { - h.routes = append(h.routes, &route{rule, http.HandlerFunc(handler)}) -} - func (h *RegexpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { timer := prometheus.NewTimer(requestDurationHistogram.WithLabelValues(h.handlerName)) defer timer.ObserveDuration() @@ -53,25 +51,18 @@ func (h *RegexpHandler) setupRoute(rule targetRule) { zap.String("response", rule.Response()), ) - h.Handler(rule, createHandlerForTarget(h.logger, rule.response)) -} - -func createHandlerForTarget(logger logging.Logger, targetPath string) http.Handler { - return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { - headerWriter := &bytes.Buffer{} - request.Header.Write(headerWriter) - - logger.Info( - "Handling request", - zap.String("source", request.RemoteAddr), - zap.String("host", request.Host), - zap.String("method", request.Method), - zap.String("protocol", request.Proto), - zap.String("path", request.RequestURI), - zap.String("response", targetPath), - zap.Reflect("headers", request.Header), - ) - - http.ServeFile(writer, request, targetPath) + h.Handler(rule, emittingFileHandler{ + emitter: h.emitter, + targetPath: rule.response, }) } + +type emittingFileHandler struct { + emitter audit.Emitter + targetPath string +} + +func (f emittingFileHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + f.emitter.Emit(imHttp.EventFromRequest(request, audit.AppProtocol_HTTP)) + http.ServeFile(writer, request, f.targetPath) +} diff --git a/plugins/http_mock/register.go b/internal/endpoint/handler/http/mock/register.go similarity index 97% rename from plugins/http_mock/register.go rename to internal/endpoint/handler/http/mock/register.go index d81e055..b84738f 100644 --- a/plugins/http_mock/register.go +++ b/internal/endpoint/handler/http/mock/register.go @@ -1,4 +1,4 @@ -package http_mock +package mock import ( "github.com/prometheus/client_golang/prometheus" diff --git a/plugins/http_proxy/handler.go b/internal/endpoint/handler/http/proxy/handler.go similarity index 80% rename from plugins/http_proxy/handler.go rename to internal/endpoint/handler/http/proxy/handler.go index 251a43e..08c12d5 100644 --- a/plugins/http_proxy/handler.go +++ b/internal/endpoint/handler/http/proxy/handler.go @@ -1,4 +1,4 @@ -package http_proxy +package proxy import ( "context" @@ -6,6 +6,7 @@ import ( "fmt" "net/http" + 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" @@ -29,7 +30,11 @@ func (h *httpProxy) Start(ctx api.PluginContext, cfg config.HandlerConfig) (err return } listenAddr := cfg.ListenAddr() - h.server = &http.Server{Addr: listenAddr, Handler: h.proxy} + 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), @@ -41,16 +46,18 @@ func (h *httpProxy) Start(ctx api.PluginContext, cfg config.HandlerConfig) (err handlerName: cfg.HandlerName, options: opts, logger: h.logger, + emitter: ctx.Audit(), } - proxyHttpsHandler := &proxyHttpsHandler{ + proxyHTTPSHandler := &proxyHttpsHandler{ handlerName: cfg.HandlerName, tlsConfig: tlsConfig, logger: h.logger, + emitter: ctx.Audit(), } h.proxy.OnRequest().Do(proxyHandler) - h.proxy.OnRequest().HandleConnect(proxyHttpsHandler) + h.proxy.OnRequest().HandleConnect(proxyHTTPSHandler) go h.startProxy() return } diff --git a/plugins/http_proxy/protocol_options.go b/internal/endpoint/handler/http/proxy/protocol_options.go similarity index 92% rename from plugins/http_proxy/protocol_options.go rename to internal/endpoint/handler/http/proxy/protocol_options.go index 5ec23eb..3dbf34c 100644 --- a/plugins/http_proxy/protocol_options.go +++ b/internal/endpoint/handler/http/proxy/protocol_options.go @@ -1,4 +1,4 @@ -package http_proxy +package proxy import ( "fmt" diff --git a/plugins/http_proxy/proxy_handler.go b/internal/endpoint/handler/http/proxy/proxy_handler.go similarity index 87% rename from plugins/http_proxy/proxy_handler.go rename to internal/endpoint/handler/http/proxy/proxy_handler.go index 9bf6ccb..6bdf26e 100644 --- a/plugins/http_proxy/proxy_handler.go +++ b/internal/endpoint/handler/http/proxy/proxy_handler.go @@ -1,12 +1,13 @@ -package http_proxy +package proxy import ( - "context" "crypto/tls" "net/http" "net/url" "github.com/prometheus/client_golang/prometheus" + imHttp "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http" + "gitlab.com/inetmock/inetmock/pkg/audit" "gitlab.com/inetmock/inetmock/pkg/logging" "go.uber.org/zap" "gopkg.in/elazarl/goproxy.v1" @@ -16,12 +17,14 @@ type proxyHttpHandler struct { handlerName string options httpProxyOptions logger logging.Logger + 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) { @@ -45,15 +48,7 @@ func (p *proxyHttpHandler) Handle(req *http.Request, ctx *goproxy.ProxyCtx) (ret totalRequestCounter.WithLabelValues(p.handlerName).Inc() retReq = req - p.logger.Info( - "Handling request", - zap.String("source", req.RemoteAddr), - zap.String("host", req.Host), - zap.String("method", req.Method), - zap.String("protocol", req.Proto), - zap.String("path", req.RequestURI), - zap.Reflect("headers", req.Header), - ) + p.emitter.Emit(imHttp.EventFromRequest(req, audit.AppProtocol_HTTP_PROXY)) var err error if resp, err = ctx.RoundTrip(p.redirectHTTPRequest(req)); err != nil { @@ -95,7 +90,7 @@ func (p proxyHttpHandler) redirectHTTPRequest(originalRequest *http.Request) (re MultipartForm: originalRequest.MultipartForm, Trailer: originalRequest.Trailer, } - redirectReq = redirectReq.WithContext(context.Background()) + redirectReq = redirectReq.WithContext(originalRequest.Context()) return } diff --git a/plugins/http_proxy/register.go b/internal/endpoint/handler/http/proxy/register.go similarity index 98% rename from plugins/http_proxy/register.go rename to internal/endpoint/handler/http/proxy/register.go index cd743e5..4470caf 100644 --- a/plugins/http_proxy/register.go +++ b/internal/endpoint/handler/http/proxy/register.go @@ -1,4 +1,4 @@ -package http_proxy +package proxy import ( "github.com/prometheus/client_golang/prometheus" diff --git a/plugins/metrics_exporter/handler.go b/internal/endpoint/handler/metrics/handler.go similarity index 97% rename from plugins/metrics_exporter/handler.go rename to internal/endpoint/handler/metrics/handler.go index 31aaac5..0519892 100644 --- a/plugins/metrics_exporter/handler.go +++ b/internal/endpoint/handler/metrics/handler.go @@ -1,4 +1,4 @@ -package metrics_exporter +package metrics import ( "context" diff --git a/plugins/metrics_exporter/protocol_options.go b/internal/endpoint/handler/metrics/protocol_options.go similarity index 68% rename from plugins/metrics_exporter/protocol_options.go rename to internal/endpoint/handler/metrics/protocol_options.go index 89de17c..b7b1aed 100644 --- a/plugins/metrics_exporter/protocol_options.go +++ b/internal/endpoint/handler/metrics/protocol_options.go @@ -1,4 +1,4 @@ -package metrics_exporter +package metrics type metricsExporterOptions struct { Route string diff --git a/plugins/metrics_exporter/register.go b/internal/endpoint/handler/metrics/register.go similarity index 94% rename from plugins/metrics_exporter/register.go rename to internal/endpoint/handler/metrics/register.go index f21fcf8..4fa3043 100644 --- a/plugins/metrics_exporter/register.go +++ b/internal/endpoint/handler/metrics/register.go @@ -1,4 +1,4 @@ -package metrics_exporter +package metrics import ( "gitlab.com/inetmock/inetmock/pkg/api" diff --git a/plugins/tls_interceptor/handler.go b/internal/endpoint/handler/tls/interceptor/handler.go similarity index 99% rename from plugins/tls_interceptor/handler.go rename to internal/endpoint/handler/tls/interceptor/handler.go index f5107a7..b180eb2 100644 --- a/plugins/tls_interceptor/handler.go +++ b/internal/endpoint/handler/tls/interceptor/handler.go @@ -1,4 +1,4 @@ -package tls_interceptor +package interceptor import ( "context" diff --git a/plugins/tls_interceptor/protocol_options.go b/internal/endpoint/handler/tls/interceptor/protocol_options.go similarity index 91% rename from plugins/tls_interceptor/protocol_options.go rename to internal/endpoint/handler/tls/interceptor/protocol_options.go index 097ec28..c0de849 100644 --- a/plugins/tls_interceptor/protocol_options.go +++ b/internal/endpoint/handler/tls/interceptor/protocol_options.go @@ -1,4 +1,4 @@ -package tls_interceptor +package interceptor import ( "fmt" diff --git a/plugins/tls_interceptor/proxy.go b/internal/endpoint/handler/tls/interceptor/proxy.go similarity index 97% rename from plugins/tls_interceptor/proxy.go rename to internal/endpoint/handler/tls/interceptor/proxy.go index 5017044..be5af4c 100644 --- a/plugins/tls_interceptor/proxy.go +++ b/internal/endpoint/handler/tls/interceptor/proxy.go @@ -1,4 +1,4 @@ -package tls_interceptor +package interceptor import ( "net" diff --git a/plugins/tls_interceptor/proxy_conn.go b/internal/endpoint/handler/tls/interceptor/proxy_conn.go similarity index 94% rename from plugins/tls_interceptor/proxy_conn.go rename to internal/endpoint/handler/tls/interceptor/proxy_conn.go index f7529d9..396d80f 100644 --- a/plugins/tls_interceptor/proxy_conn.go +++ b/internal/endpoint/handler/tls/interceptor/proxy_conn.go @@ -1,4 +1,4 @@ -package tls_interceptor +package interceptor import ( "fmt" diff --git a/plugins/tls_interceptor/register.go b/internal/endpoint/handler/tls/interceptor/register.go similarity index 94% rename from plugins/tls_interceptor/register.go rename to internal/endpoint/handler/tls/interceptor/register.go index d354da5..6fcf3fc 100644 --- a/plugins/tls_interceptor/register.go +++ b/internal/endpoint/handler/tls/interceptor/register.go @@ -1,4 +1,4 @@ -package tls_interceptor +package interceptor import ( "sync" @@ -40,7 +40,7 @@ func AddTLSInterceptor(registry api.HandlerRegistry) (err error) { registry.RegisterHandler(name, func() api.ProtocolHandler { return &tlsInterceptor{ logger: logger, - currentConnectionsCount: &sync.WaitGroup{}, + currentConnectionsCount: new(sync.WaitGroup), currentConnections: make(map[uuid.UUID]*proxyConn), connectionsMutex: &sync.Mutex{}, } diff --git a/internal/format/table_writer.go b/internal/format/table_writer.go index feb3383..da75f16 100644 --- a/internal/format/table_writer.go +++ b/internal/format/table_writer.go @@ -75,6 +75,15 @@ func (t *tblWriter) getData(val reflect.Value, numberOfFields int) (data []strin } func value(val reflect.Value) string { + + if val.IsZero() { + return "" + } + + if stringer, isStringer := val.Interface().(fmt.Stringer); isStringer { + return stringer.String() + } + switch val.Kind() { case reflect.Ptr: return value(val.Elem()) @@ -84,6 +93,8 @@ func value(val reflect.Value) string { return strconv.FormatBool(val.Bool()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return strconv.FormatInt(val.Int(), 10) + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return strconv.FormatUint(val.Uint(), 10) case reflect.Float32, reflect.Float64: return strconv.FormatFloat(val.Float(), 'f', 6, 64) default: diff --git a/internal/rpc/audit_server.go b/internal/rpc/audit_server.go new file mode 100644 index 0000000..a593179 --- /dev/null +++ b/internal/rpc/audit_server.go @@ -0,0 +1,74 @@ +package rpc + +import ( + "context" + "io" + "os" + + "gitlab.com/inetmock/inetmock/pkg/audit" + "gitlab.com/inetmock/inetmock/pkg/audit/sink" + "gitlab.com/inetmock/inetmock/pkg/logging" + "go.uber.org/zap" +) + +type auditServer struct { + UnimplementedAuditServer + logger logging.Logger + eventStream audit.EventStream +} + +func (a *auditServer) ListSinks(context.Context, *ListSinksRequest) (*ListSinksResponse, error) { + return &ListSinksResponse{ + Sinks: a.eventStream.Sinks(), + }, nil +} + +func (a *auditServer) WatchEvents(req *WatchEventsRequest, srv Audit_WatchEventsServer) (err error) { + a.logger.Info("watcher attached", zap.String("name", req.WatcherName)) + err = a.eventStream.RegisterSink(srv.Context(), sink.NewGenericSink(req.WatcherName, func(ev audit.Event) { + if err = srv.Send(ev.ProtoMessage()); err != nil { + return + } + })) + + if err != nil { + return + } + + <-srv.Context().Done() + a.logger.Info("Watcher detached", zap.String("name", req.WatcherName)) + return +} + +func (a *auditServer) RegisterFileSink(_ context.Context, req *RegisterFileSinkRequest) (resp *RegisterFileSinkResponse, err error) { + var writer io.WriteCloser + var flags int + + switch req.OpenMode { + case FileOpenMode_APPEND: + flags = os.O_CREATE | os.O_WRONLY | os.O_APPEND + default: + flags = os.O_CREATE | os.O_WRONLY | os.O_TRUNC + } + + var permissions = os.FileMode(req.Permissions) + if permissions == 0 { + permissions = 644 + } + + if writer, err = os.OpenFile(req.TargetPath, flags, permissions); err != nil { + return + } + if err = a.eventStream.RegisterSink(context.Background(), sink.NewWriterSink(req.TargetPath, audit.NewEventWriter(writer))); err != nil { + return + } + resp = &RegisterFileSinkResponse{} + return +} + +func (a *auditServer) RemoveFileSink(_ context.Context, req *RemoveFileSinkRequest) (*RemoveFileSinkResponse, error) { + gotRemoved := a.eventStream.RemoveSink(req.TargetPath) + return &RemoveFileSinkResponse{ + SinkGotRemoved: gotRemoved, + }, nil +} diff --git a/internal/rpc/endpoints_server.go b/internal/rpc/endpoints_server.go index 7fada0d..ff7fd7a 100644 --- a/internal/rpc/endpoints_server.go +++ b/internal/rpc/endpoints_server.go @@ -3,12 +3,12 @@ package rpc import ( "context" - "gitlab.com/inetmock/inetmock/internal/endpoints" + "gitlab.com/inetmock/inetmock/internal/endpoint" ) type endpointsServer struct { UnimplementedEndpointsServer - endpointsManager endpoints.EndpointManager + endpointsManager endpoint.EndpointManager } func (e endpointsServer) GetEndpoints(_ context.Context, _ *GetEndpointsRequest) (*GetEndpointsResponse, error) { @@ -18,7 +18,7 @@ func (e endpointsServer) GetEndpoints(_ context.Context, _ *GetEndpointsRequest) }, nil } -func rpcEndpointsFromEndpoints(eps []endpoints.Endpoint) *[]*Endpoint { +func rpcEndpointsFromEndpoints(eps []endpoint.Endpoint) *[]*Endpoint { out := make([]*Endpoint, 0) for _, ep := range eps { out = append(out, rpcEndpointFromEndpoint(ep)) @@ -26,7 +26,7 @@ func rpcEndpointsFromEndpoints(eps []endpoints.Endpoint) *[]*Endpoint { return &out } -func rpcEndpointFromEndpoint(ep endpoints.Endpoint) *Endpoint { +func rpcEndpointFromEndpoint(ep endpoint.Endpoint) *Endpoint { return &Endpoint{ Id: ep.Id().String(), Name: ep.Name(), diff --git a/internal/rpc/grpc_api.go b/internal/rpc/grpc_api.go index cbcc87c..b523d7f 100644 --- a/internal/rpc/grpc_api.go +++ b/internal/rpc/grpc_api.go @@ -9,6 +9,7 @@ import ( "gitlab.com/inetmock/inetmock/pkg/logging" "go.uber.org/zap" "google.golang.org/grpc" + "google.golang.org/grpc/reflection" ) type INetMockAPI interface { @@ -52,6 +53,13 @@ func (i *inetmockAPI) StartServer() (err error) { app: i.app, }) + RegisterAuditServer(i.server, &auditServer{ + logger: i.app.Logger(), + eventStream: i.app.EventStream(), + }) + + reflection.Register(i.server) + go i.startServerAsync(lis) return } diff --git a/internal/rpc/handlers_server.go b/internal/rpc/handlers_server.go index bd5491e..0af6e56 100644 --- a/internal/rpc/handlers_server.go +++ b/internal/rpc/handlers_server.go @@ -11,9 +11,6 @@ type handlersServer struct { registry api.HandlerRegistry } -func (h *handlersServer) mustEmbedUnimplementedHandlersServer() { -} - func (h *handlersServer) GetHandlers(_ context.Context, _ *GetHandlersRequest) (*GetHandlersResponse, error) { return &GetHandlersResponse{ Handlers: h.registry.AvailableHandlers(), diff --git a/internal/rpc/health_server.go b/internal/rpc/health_server.go index d92f344..17b0af7 100644 --- a/internal/rpc/health_server.go +++ b/internal/rpc/health_server.go @@ -3,12 +3,12 @@ package rpc import ( "context" - app2 "gitlab.com/inetmock/inetmock/internal/app" + "gitlab.com/inetmock/inetmock/internal/app" ) type healthServer struct { UnimplementedHealthServer - app app2.App + app app.App } func (h healthServer) GetHealth(_ context.Context, _ *HealthRequest) (resp *HealthResponse, err error) { diff --git a/pkg/api/protocol_handler.go b/pkg/api/protocol_handler.go index 540ae77..f927570 100644 --- a/pkg/api/protocol_handler.go +++ b/pkg/api/protocol_handler.go @@ -4,6 +4,7 @@ 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" @@ -12,6 +13,7 @@ import ( type PluginContext interface { Logger() logging.Logger CertStore() cert.Store + Audit() audit.Emitter } type ProtocolHandler interface { diff --git a/pkg/audit/api.go b/pkg/audit/api.go new file mode 100644 index 0000000..b3a789f --- /dev/null +++ b/pkg/audit/api.go @@ -0,0 +1,30 @@ +//go:generate mockgen -source=$GOFILE -destination=./../../internal/mock/audit/audit.mock.go -package=audit_mock + +package audit + +import ( + "context" + "errors" + "io" +) + +var ( + ErrSinkAlreadyRegistered = errors.New("sink with same name already registered") +) + +type Emitter interface { + Emit(ev Event) +} + +type Sink interface { + Name() string + OnSubscribe(evs <-chan Event) +} + +type EventStream interface { + io.Closer + Emitter + RegisterSink(ctx context.Context, s Sink) error + Sinks() []string + RemoveSink(name string) (exists bool) +} diff --git a/pkg/audit/details/dns_details.go b/pkg/audit/details/dns_details.go new file mode 100644 index 0000000..91397dd --- /dev/null +++ b/pkg/audit/details/dns_details.go @@ -0,0 +1,47 @@ +package details + +import ( + "google.golang.org/protobuf/types/known/anypb" +) + +func NewDNSFromWireFormat(entity *DNSDetailsEntity) DNS { + d := DNS{ + OPCode: entity.Opcode, + } + + for _, q := range entity.Questions { + d.Questions = append(d.Questions, DNSQuestion{ + RRType: q.Type, + Name: q.Name, + }) + } + + return d +} + +type DNSQuestion struct { + RRType ResourceRecordType + Name string +} + +type DNS struct { + OPCode DNSOpCode + Questions []DNSQuestion +} + +func (d DNS) MarshalToWireFormat() (any *anypb.Any, err error) { + detailsEntity := &DNSDetailsEntity{ + Opcode: d.OPCode, + } + + for _, q := range d.Questions { + detailsEntity.Questions = append(detailsEntity.Questions, &DNSQuestionEntity{ + Type: q.RRType, + Name: q.Name, + }) + } + + any, err = anypb.New(detailsEntity) + + return +} diff --git a/pkg/audit/details/http_details.go b/pkg/audit/details/http_details.go new file mode 100644 index 0000000..bee3928 --- /dev/null +++ b/pkg/audit/details/http_details.go @@ -0,0 +1,59 @@ +package details + +import ( + "net/http" + "strings" + + "google.golang.org/protobuf/types/known/anypb" +) + +type HTTP struct { + Method string + Host string + URI string + Proto string + Headers http.Header +} + +func NewHTTPFromWireFormat(entity *HTTPDetailsEntity) HTTP { + headers := http.Header{} + for name, values := range entity.Headers { + for idx := range values.Values { + headers.Add(name, values.Values[idx]) + } + } + + return HTTP{ + Method: entity.Method.String(), + Host: entity.Host, + URI: entity.Uri, + Proto: entity.Proto, + Headers: headers, + } +} + +func (d HTTP) MarshalToWireFormat() (any *anypb.Any, err error) { + var method = HTTPMethod_GET + if methodValue, known := HTTPMethod_value[strings.ToUpper(d.Method)]; known { + method = HTTPMethod(methodValue) + } + + headers := make(map[string]*HTTPHeaderValue) + + for k, v := range d.Headers { + headers[k] = &HTTPHeaderValue{ + Values: v, + } + } + + protoDetails := &HTTPDetailsEntity{ + Method: method, + Host: d.Host, + Uri: d.URI, + Proto: d.Proto, + Headers: headers, + } + + any, err = anypb.New(protoDetails) + return +} diff --git a/pkg/audit/event.go b/pkg/audit/event.go new file mode 100644 index 0000000..dc9f696 --- /dev/null +++ b/pkg/audit/event.go @@ -0,0 +1,134 @@ +package audit + +import ( + "net" + "strconv" + "strings" + "time" + + "gitlab.com/inetmock/inetmock/pkg/audit/details" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type Details interface { + MarshalToWireFormat() (*anypb.Any, error) +} + +type Event struct { + ID int64 + Timestamp time.Time + Transport TransportProtocol + Application AppProtocol + SourceIP net.IP + DestinationIP net.IP + SourcePort uint16 + DestinationPort uint16 + ProtocolDetails Details + TLS *TLSDetails +} + +func (e *Event) ProtoMessage() *EventEntity { + var tlsDetails *TLSDetailsEntity = nil + if e.TLS != nil { + tlsDetails = e.TLS.ProtoMessage() + } + + var detailsEntity *anypb.Any = nil + if e.ProtocolDetails != nil { + if any, err := e.ProtocolDetails.MarshalToWireFormat(); err == nil { + detailsEntity = any + } + } + + return &EventEntity{ + Id: e.ID, + Timestamp: timestamppb.New(e.Timestamp), + Transport: e.Transport, + Application: e.Application, + SourceIP: e.SourceIP, + DestinationIP: e.DestinationIP, + SourcePort: uint32(e.SourcePort), + DestinationPort: uint32(e.DestinationPort), + Tls: tlsDetails, + ProtocolDetails: detailsEntity, + } +} + +func (e *Event) ApplyDefaults(id int64) { + e.ID = id + emptyTime := time.Time{} + if e.Timestamp == emptyTime { + e.Timestamp = time.Now().UTC() + } +} + +func (e *Event) SetSourceIPFromAddr(remoteAddr net.Addr) { + ip, port := parseIPPortFromAddr(remoteAddr) + e.SourceIP = ip + e.SourcePort = port +} + +func (e *Event) SetDestinationIPFromAddr(localAddr net.Addr) { + ip, port := parseIPPortFromAddr(localAddr) + e.DestinationIP = ip + e.DestinationPort = port +} + +func NewEventFromProto(msg *EventEntity) (ev Event) { + ev = Event{ + ID: msg.GetId(), + Timestamp: msg.GetTimestamp().AsTime(), + Transport: msg.GetTransport(), + Application: msg.GetApplication(), + SourceIP: msg.SourceIP, + DestinationIP: msg.DestinationIP, + SourcePort: uint16(msg.GetSourcePort()), + DestinationPort: uint16(msg.GetDestinationPort()), + ProtocolDetails: guessDetailsFromApp(msg.GetProtocolDetails()), + TLS: NewTLSDetailsFromProto(msg.GetTls()), + } + return +} + +func parseIPPortFromAddr(addr net.Addr) (ip net.IP, port uint16) { + if addr == nil { + return + } + switch a := addr.(type) { + case *net.TCPAddr: + return a.IP, uint16(a.Port) + case *net.UDPAddr: + return a.IP, uint16(a.Port) + case *net.UnixAddr: + return + default: + ipPortSplit := strings.Split(addr.String(), ":") + if len(ipPortSplit) != 2 { + return + } + + ip = net.ParseIP(ipPortSplit[0]) + if p, err := strconv.Atoi(ipPortSplit[1]); err == nil { + port = uint16(p) + } + return + } +} + +func guessDetailsFromApp(any *anypb.Any) Details { + var detailsProto proto.Message + var err error + if detailsProto, err = any.UnmarshalNew(); err != nil { + return nil + } + switch any.TypeUrl { + case "type.googleapis.com/inetmock.audit.HTTPDetailsEntity": + return details.NewHTTPFromWireFormat(detailsProto.(*details.HTTPDetailsEntity)) + case "type.googleapis.com/inetmock.audit.DNSDetailsEntity": + return details.NewDNSFromWireFormat(detailsProto.(*details.DNSDetailsEntity)) + default: + return nil + } +} diff --git a/pkg/audit/event_stream.go b/pkg/audit/event_stream.go new file mode 100644 index 0000000..262a29d --- /dev/null +++ b/pkg/audit/event_stream.go @@ -0,0 +1,145 @@ +package audit + +import ( + "context" + "sync" + "time" + + "github.com/bwmarrin/snowflake" + "gitlab.com/inetmock/inetmock/pkg/logging" + "go.uber.org/zap" +) + +func init() { + snowflake.Epoch = time.Unix(0, 0).Unix() +} + +type eventStream struct { + logger logging.Logger + buffer chan *Event + sinks map[string]*registeredSink + lock sync.Locker + idGenerator *snowflake.Node + sinkBufferSize int + sinkConsumptionTimeout time.Duration +} + +type registeredSink struct { + downstream chan Event + lock sync.Locker +} + +func NewEventStream(logger logging.Logger, options ...EventStreamOption) (es EventStream, err error) { + cfg := newEventStreamCfg() + + for _, opt := range options { + opt(&cfg) + } + + var node *snowflake.Node + node, err = snowflake.NewNode(cfg.generatorIndex) + if err != nil { + return + } + + generatorIdx++ + underlying := &eventStream{ + logger: logger, + sinks: make(map[string]*registeredSink), + buffer: make(chan *Event, cfg.bufferSize), + sinkBufferSize: cfg.sinkBuffersize, + sinkConsumptionTimeout: cfg.sinkConsumptionTimeout, + idGenerator: node, + lock: &sync.Mutex{}, + } + + // start distribute workers + for i := 0; i < cfg.distributeParallelization; i++ { + go underlying.distribute() + } + + es = underlying + + return +} + +func (e *eventStream) Emit(ev Event) { + ev.ApplyDefaults(e.idGenerator.Generate().Int64()) + select { + case e.buffer <- &ev: + e.logger.Debug("pushed event to distribute loop") + default: + e.logger.Warn("buffer is full") + } +} + +func (e *eventStream) RemoveSink(name string) (exists bool) { + e.lock.Lock() + defer e.lock.Unlock() + + var sink *registeredSink + sink, exists = e.sinks[name] + if !exists { + return + } + sink.lock.Lock() + defer sink.lock.Unlock() + delete(e.sinks, name) + close(sink.downstream) + + return +} + +func (e *eventStream) RegisterSink(ctx context.Context, s Sink) error { + name := s.Name() + + if _, exists := e.sinks[name]; exists { + return ErrSinkAlreadyRegistered + } + + rs := ®isteredSink{ + downstream: make(chan Event, e.sinkBufferSize), + lock: new(sync.Mutex), + } + + s.OnSubscribe(rs.downstream) + + go func() { + <-ctx.Done() + e.RemoveSink(name) + }() + + e.sinks[name] = rs + return nil +} + +func (e eventStream) Sinks() (sinks []string) { + for name := range e.sinks { + sinks = append(sinks, name) + } + return +} + +func (e *eventStream) Close() error { + close(e.buffer) + for _, rs := range e.sinks { + close(rs.downstream) + } + return nil +} + +func (e *eventStream) distribute() { + for ev := range e.buffer { + for name, rs := range e.sinks { + rs.lock.Lock() + e.logger.Debug("notify sink", zap.String("sink", name)) + select { + case rs.downstream <- *ev: + e.logger.Debug("pushed event to sink channel") + case <-time.After(e.sinkConsumptionTimeout): + e.logger.Warn("sink consummation timed out") + } + rs.lock.Unlock() + } + } +} diff --git a/pkg/audit/event_stream_test.go b/pkg/audit/event_stream_test.go new file mode 100644 index 0000000..bd15ea8 --- /dev/null +++ b/pkg/audit/event_stream_test.go @@ -0,0 +1,216 @@ +package audit_test + +import ( + "context" + "crypto/tls" + "net" + "net/http" + "sync" + "testing" + "time" + + "gitlab.com/inetmock/inetmock/pkg/audit" + "gitlab.com/inetmock/inetmock/pkg/audit/details" + "gitlab.com/inetmock/inetmock/pkg/audit/sink" + "gitlab.com/inetmock/inetmock/pkg/logging" + "gitlab.com/inetmock/inetmock/pkg/wait" +) + +var ( + noOpSink = sink.NewNoOpSink("test defaultSink") + testEvents = []*audit.Event{ + { + Transport: audit.TransportProtocol_TCP, + Application: audit.AppProtocol_HTTP, + SourceIP: net.ParseIP("127.0.0.1").To4(), + DestinationIP: net.ParseIP("127.0.0.1").To4(), + SourcePort: 32344, + DestinationPort: 80, + TLS: &audit.TLSDetails{ + Version: audit.TLSVersionToEntity(tls.VersionTLS13).String(), + CipherSuite: tls.CipherSuiteName(tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA), + ServerName: "localhost", + }, + ProtocolDetails: details.HTTP{ + Method: "GET", + Host: "localhost", + URI: "http://localhost/asdf", + Proto: "HTTP 1.1", + Headers: http.Header{ + "Accept": []string{"application/json"}, + }, + }, + }, + { + Transport: audit.TransportProtocol_TCP, + Application: audit.AppProtocol_DNS, + SourceIP: net.ParseIP("::1").To16(), + DestinationIP: net.ParseIP("::1").To16(), + SourcePort: 32344, + DestinationPort: 80, + }, + } +) + +func wgMockSink(t testing.TB, wg *sync.WaitGroup) audit.Sink { + return sink.NewGenericSink( + "WG mock sink", + func(event audit.Event) { + t.Logf("Got event = %v", event) + wg.Done() + }, + ) +} + +func Test_eventStream_RegisterSink(t *testing.T) { + type args struct { + s audit.Sink + } + type testCase struct { + name string + args args + setup func(e audit.EventStream) + wantErr bool + } + tests := []testCase{ + { + name: "Register test defaultSink", + args: args{ + s: noOpSink, + }, + wantErr: false, + }, + { + name: "Fail due to already registered defaultSink", + args: args{ + s: noOpSink, + }, + setup: func(e audit.EventStream) { + _ = e.RegisterSink(context.Background(), noOpSink) + }, + wantErr: true, + }, + } + scenario := func(tt testCase) func(t *testing.T) { + return func(t *testing.T) { + var err error + var e audit.EventStream + if e, err = audit.NewEventStream(logging.CreateTestLogger(t)); err != nil { + t.Errorf("NewEventStream() error = %v", err) + } + + t.Cleanup(func() { + _ = e.Close() + }) + + if tt.setup != nil { + tt.setup(e) + } + + if err := e.RegisterSink(context.Background(), tt.args.s); (err != nil) != tt.wantErr { + t.Errorf("RegisterSink() error = %v, wantErr %v", err, tt.wantErr) + } + + found := false + for _, s := range e.Sinks() { + if found = s == tt.args.s.Name(); found { + break + } + } + if !found { + t.Errorf("expected defaultSink name %s not found in registered sinks %v", tt.args.s.Name(), e.Sinks()) + } + } + } + + for _, tt := range tests { + t.Run(tt.name, scenario(tt)) + } +} + +func Test_eventStream_Emit(t *testing.T) { + type args struct { + evs []*audit.Event + opts []audit.EventStreamOption + } + type testCase struct { + name string + args args + subscribe bool + } + tests := []testCase{ + { + name: "Expect to get a single event", + subscribe: true, + args: args{ + opts: []audit.EventStreamOption{}, + evs: testEvents[:1], + }, + }, + { + name: "Expect to get multiple events", + subscribe: true, + args: args{ + opts: []audit.EventStreamOption{}, + evs: testEvents, + }, + }, + { + name: "Emit without subscribe sink", + args: args{ + opts: []audit.EventStreamOption{audit.WithBufferSize(0)}, + evs: testEvents[:1], + }, + subscribe: false, + }, + } + + scenario := func(tt testCase) func(t *testing.T) { + return func(t *testing.T) { + var err error + var e audit.EventStream + if e, err = audit.NewEventStream(logging.CreateTestLogger(t), tt.args.opts...); err != nil { + t.Errorf("NewEventStream() error = %v", err) + } + + t.Cleanup(func() { + _ = e.Close() + }) + + emittedWaitGroup := new(sync.WaitGroup) + receivedWaitGroup := new(sync.WaitGroup) + + emittedWaitGroup.Add(len(tt.args.evs)) + + if tt.subscribe { + receivedWaitGroup.Add(len(tt.args.evs)) + if err := e.RegisterSink(context.Background(), wgMockSink(t, receivedWaitGroup)); err != nil { + t.Errorf("RegisterSink() error = %v", err) + } + } + + go func(evs []*audit.Event, wg *sync.WaitGroup) { + for _, ev := range evs { + e.Emit(*ev) + wg.Done() + } + }(tt.args.evs, emittedWaitGroup) + + select { + case <-wait.ForWaitGroupDone(emittedWaitGroup): + case <-time.After(100 * time.Millisecond): + t.Errorf("not all events emitted in time") + } + + select { + case <-wait.ForWaitGroupDone(receivedWaitGroup): + case <-time.After(5 * time.Second): + t.Errorf("did not get all expected events in time") + } + } + } + + for _, tt := range tests { + t.Run(tt.name, scenario(tt)) + } +} diff --git a/pkg/audit/event_test.go b/pkg/audit/event_test.go new file mode 100644 index 0000000..b1aaebc --- /dev/null +++ b/pkg/audit/event_test.go @@ -0,0 +1,61 @@ +package audit + +import ( + "net/http" + "reflect" + "testing" + + "gitlab.com/inetmock/inetmock/pkg/audit/details" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" +) + +func Test_guessDetailsFromApp(t *testing.T) { + + mustAny := func(msg proto.Message) *anypb.Any { + a, err := anypb.New(msg) + if err != nil { + panic(a) + } + return a + } + + type args struct { + any *anypb.Any + } + type testCase struct { + name string + args args + want Details + } + tests := []testCase{ + { + name: "HTTP etails", + args: args{ + any: mustAny(&details.HTTPDetailsEntity{ + Method: details.HTTPMethod_GET, + Host: "localhost", + Uri: "http://localhost/asdf", + Proto: "HTTP", + }), + }, + want: details.HTTP{ + Method: "GET", + Host: "localhost", + URI: "http://localhost/asdf", + Proto: "HTTP", + Headers: http.Header{}, + }, + }, + } + scenario := func(tt testCase) func(t *testing.T) { + return func(t *testing.T) { + if got := guessDetailsFromApp(tt.args.any); !reflect.DeepEqual(got, tt.want) { + t.Errorf("guessDetailsFromApp() = %v, want %v", got, tt.want) + } + } + } + for _, tt := range tests { + t.Run(tt.name, scenario(tt)) + } +} diff --git a/pkg/audit/options.go b/pkg/audit/options.go new file mode 100644 index 0000000..46868e9 --- /dev/null +++ b/pkg/audit/options.go @@ -0,0 +1,74 @@ +package audit + +import ( + "runtime" + "time" +) + +const ( + defaultEventStreamBufferSize = 100 + defaultSinkBufferSize = 0 + defaultSinkConsumptionTimeout = 50 * time.Millisecond +) + +var ( + defaultDistributeParallelization int + generatorIdx int64 = 1 + WithBufferSize = func(bufferSize int) EventStreamOption { + return func(cfg *eventStreamCfg) { + cfg.bufferSize = bufferSize + } + } + WithGeneratorIndex = func(generatorIndex int64) EventStreamOption { + return func(cfg *eventStreamCfg) { + cfg.generatorIndex = generatorIndex + } + } + WithSinkBufferSize = func(bufferSize int) EventStreamOption { + return func(cfg *eventStreamCfg) { + cfg.sinkBuffersize = bufferSize + } + } + WithSinkConsumptionTimeout = func(timeout time.Duration) EventStreamOption { + return func(cfg *eventStreamCfg) { + cfg.sinkConsumptionTimeout = timeout + } + } + WithDistributeParallelization = func(parallelization int) EventStreamOption { + return func(cfg *eventStreamCfg) { + if parallelization <= 0 || parallelization > runtime.NumCPU() { + return + } + cfg.distributeParallelization = parallelization + } + } +) + +func init() { + if defaultDistributeParallelization = runtime.NumCPU() / 2; defaultDistributeParallelization < 2 { + defaultDistributeParallelization = 2 + } +} + +type EventStreamOption func(cfg *eventStreamCfg) + +type eventStreamCfg struct { + bufferSize int + sinkBuffersize int + generatorIndex int64 + distributeParallelization int + sinkConsumptionTimeout time.Duration +} + +func newEventStreamCfg() eventStreamCfg { + cfg := eventStreamCfg{ + generatorIndex: generatorIdx, + sinkBuffersize: defaultSinkBufferSize, + bufferSize: defaultEventStreamBufferSize, + sinkConsumptionTimeout: defaultSinkConsumptionTimeout, + distributeParallelization: defaultDistributeParallelization, + } + generatorIdx++ + + return cfg +} diff --git a/pkg/audit/reader.go b/pkg/audit/reader.go new file mode 100644 index 0000000..d679565 --- /dev/null +++ b/pkg/audit/reader.go @@ -0,0 +1,87 @@ +package audit + +import ( + "encoding/binary" + "io" + "sync" + + "google.golang.org/protobuf/proto" +) + +const ( + lengthBufferSize = 4 + defaultPayloadBufferSize = 4096 +) + +type Reader interface { + Read() (Event, error) +} + +type EventReaderOption func(reader *eventReader) + +var ( + WithReaderByteOrder = func(order binary.ByteOrder) EventReaderOption { + return func(reader *eventReader) { + reader.byteOrder = order + } + } +) + +func NewEventReader(source io.Reader, opts ...EventReaderOption) Reader { + reader := &eventReader{ + source: source, + byteOrder: defaultOrder, + lengthPool: &sync.Pool{ + New: func() interface{} { + return make([]byte, lengthBufferSize) + }, + }, + payloadPool: &sync.Pool{ + New: func() interface{} { + return make([]byte, defaultPayloadBufferSize) + }, + }, + } + + for _, opt := range opts { + opt(reader) + } + + return reader +} + +type eventReader struct { + lengthPool *sync.Pool + payloadPool *sync.Pool + byteOrder binary.ByteOrder + source io.Reader +} + +func (e eventReader) Read() (ev Event, err error) { + lengthBuf := e.lengthPool.Get().([]byte) + if _, err = e.source.Read(lengthBuf); err != nil { + return + } + + length := e.byteOrder.Uint32(lengthBuf) + var msgBuf []byte + if length <= defaultPayloadBufferSize { + msgBuf = e.payloadPool.Get().([]byte)[:length] + } else { + msgBuf = make([]byte, length) + } + + if _, err = e.source.Read(msgBuf); err != nil { + return + } + + protoEv := new(EventEntity) + + if err = proto.Unmarshal(msgBuf, protoEv); err != nil { + return + } + + ev = NewEventFromProto(protoEv) + + return +} diff --git a/pkg/audit/reader_test.go b/pkg/audit/reader_test.go new file mode 100644 index 0000000..94c2e32 --- /dev/null +++ b/pkg/audit/reader_test.go @@ -0,0 +1,99 @@ +package audit_test + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "io" + "reflect" + "testing" + + "gitlab.com/inetmock/inetmock/pkg/audit" +) + +var ( + //nolint:lll + httpPayloadBytesLittleEndian = `dd000000120b088092b8c398feffffff01180120022a047f00000132047f00000138d8fc0140504a3308041224544c535f45434448455f45434453415f574954485f4145535f3235365f4342435f5348411a096c6f63616c686f73745282010a34747970652e676f6f676c65617069732e636f6d2f696e65746d6f636b2e61756469742e4854545044657461696c73456e74697479124a12096c6f63616c686f73741a15687474703a2f2f6c6f63616c686f73742f6173646622084854545020312e312a1c0a0641636365707412120a106170706c69636174696f6e2f6a736f6e` + //nolint:lll + httpPayloadBytesBigEndian = `000000dd120b088092b8c398feffffff01180120022a047f00000132047f00000138d8fc0140504a3308041224544c535f45434448455f45434453415f574954485f4145535f3235365f4342435f5348411a096c6f63616c686f73745282010a34747970652e676f6f676c65617069732e636f6d2f696e65746d6f636b2e61756469742e4854545044657461696c73456e74697479124a12096c6f63616c686f73741a15687474703a2f2f6c6f63616c686f73742f6173646622084854545020312e312a1c0a0641636365707412120a106170706c69636174696f6e2f6a736f6e` + //nolint:lll + dnsPayloadBytesLittleEndian = `3b000000120b088092b8c398feffffff01180120012a100000000000000000000000000000000132100000000000000000000000000000000138d8fc014050` + //nolint:lll + dnsPayloadBytesBigEndian = `0000003b120b088092b8c398feffffff01180120012a100000000000000000000000000000000132100000000000000000000000000000000138d8fc014050` +) + +func mustDecodeHex(hexBytes string) io.Reader { + b, err := hex.DecodeString(hexBytes) + if err != nil { + panic(err) + } + return bytes.NewReader(b) +} + +func Test_eventReader_Read(t *testing.T) { + type fields struct { + source io.Reader + order binary.ByteOrder + } + type testCase struct { + name string + fields fields + wantEv *audit.Event + wantErr bool + } + tests := []testCase{ + { + name: "Read HTTP payload - little endian", + fields: fields{ + source: mustDecodeHex(httpPayloadBytesLittleEndian), + order: binary.LittleEndian, + }, + wantEv: testEvents[0], + wantErr: false, + }, + { + name: "Read HTTP payload - big endian", + fields: fields{ + source: mustDecodeHex(httpPayloadBytesBigEndian), + order: binary.BigEndian, + }, + wantEv: testEvents[0], + wantErr: false, + }, + { + name: "Read DNS payload - little endian", + fields: fields{ + source: mustDecodeHex(dnsPayloadBytesLittleEndian), + order: binary.LittleEndian, + }, + wantEv: testEvents[1], + wantErr: false, + }, + { + name: "Read DNS payload - big endian", + fields: fields{ + source: mustDecodeHex(dnsPayloadBytesBigEndian), + order: binary.BigEndian, + }, + wantEv: testEvents[1], + wantErr: false, + }, + } + scenario := func(tt testCase) func(t *testing.T) { + return func(t *testing.T) { + e := audit.NewEventReader(tt.fields.source, audit.WithReaderByteOrder(tt.fields.order)) + gotEv, err := e.Read() + if (err != nil) != tt.wantErr { + t.Errorf("Read() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if err == nil && !reflect.DeepEqual(gotEv, *tt.wantEv) { + t.Errorf("Read() gotEv = %v, want %v", gotEv, tt.wantEv) + } + } + } + for _, tt := range tests { + t.Run(tt.name, scenario(tt)) + } +} diff --git a/pkg/audit/sink/generic_sink.go b/pkg/audit/sink/generic_sink.go new file mode 100644 index 0000000..4c10e84 --- /dev/null +++ b/pkg/audit/sink/generic_sink.go @@ -0,0 +1,33 @@ +package sink + +import ( + "gitlab.com/inetmock/inetmock/pkg/audit" +) + +func NewNoOpSink(name string) audit.Sink { + return NewGenericSink(name, func(_ audit.Event) {}) +} + +func NewGenericSink(name string, consumer func(ev audit.Event)) audit.Sink { + return &genericSink{ + name: name, + consumer: consumer, + } +} + +type genericSink struct { + name string + consumer func(ev audit.Event) +} + +func (g genericSink) Name() string { + return g.name +} + +func (g genericSink) OnSubscribe(evs <-chan audit.Event) { + go func(consumer func(ev audit.Event), evs <-chan audit.Event) { + for ev := range evs { + consumer(ev) + } + }(g.consumer, evs) +} diff --git a/pkg/audit/sink/generic_sink_test.go b/pkg/audit/sink/generic_sink_test.go new file mode 100644 index 0000000..122f105 --- /dev/null +++ b/pkg/audit/sink/generic_sink_test.go @@ -0,0 +1,65 @@ +package sink_test + +import ( + "context" + "sync" + "testing" + "time" + + "gitlab.com/inetmock/inetmock/pkg/audit" + "gitlab.com/inetmock/inetmock/pkg/audit/sink" + "gitlab.com/inetmock/inetmock/pkg/logging" + "gitlab.com/inetmock/inetmock/pkg/wait" +) + +func Test_genericSink_OnSubscribe(t *testing.T) { + type testCase struct { + name string + events []*audit.Event + } + tests := []testCase{ + { + name: "Get a single log line", + events: testEvents[:1], + }, + { + name: "Get multiple events", + events: testEvents, + }, + } + scenario := func(tt testCase) func(t *testing.T) { + return func(t *testing.T) { + wg := new(sync.WaitGroup) + wg.Add(len(tt.events)) + + genericSink := sink.NewGenericSink(t.Name(), func(ev audit.Event) { + wg.Done() + }) + + var evs audit.EventStream + var err error + if evs, err = audit.NewEventStream(logging.CreateTestLogger(t)); err != nil { + t.Errorf("NewEventStream() error = %v", err) + } + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + if err = evs.RegisterSink(ctx, genericSink); err != nil { + t.Errorf("RegisterSink() error = %v", err) + } + + for _, ev := range tt.events { + evs.Emit(*ev) + } + + select { + case <-time.After(100 * time.Millisecond): + t.Errorf("not all events recorded in time") + case <-wait.ForWaitGroupDone(wg): + } + } + } + for _, tt := range tests { + t.Run(tt.name, scenario(tt)) + } +} diff --git a/pkg/audit/sink/log_sink.go b/pkg/audit/sink/log_sink.go new file mode 100644 index 0000000..efebc0b --- /dev/null +++ b/pkg/audit/sink/log_sink.go @@ -0,0 +1,53 @@ +package sink + +import ( + "gitlab.com/inetmock/inetmock/pkg/audit" + "gitlab.com/inetmock/inetmock/pkg/logging" + "go.uber.org/zap" +) + +const ( + logSinkName = "logging" +) + +func NewLogSink(logger logging.Logger) audit.Sink { + return &logSink{ + logger: logger, + } +} + +type logSink struct { + logger logging.Logger +} + +func (logSink) Name() string { + return logSinkName +} + +func (l logSink) OnSubscribe(evs <-chan audit.Event) { + go func(logger logging.Logger, evs <-chan audit.Event) { + for ev := range evs { + eventLogger := logger + + if ev.TLS != nil { + eventLogger = eventLogger.With( + zap.String("tls_server_name", ev.TLS.ServerName), + zap.String("tls_cipher_suite", ev.TLS.CipherSuite), + zap.String("tls_version", ev.TLS.Version), + ) + } + + eventLogger.Info( + "handled request", + zap.Time("timestamp", ev.Timestamp), + zap.String("application", ev.Application.String()), + zap.String("transport", ev.Transport.String()), + zap.String("source_ip", ev.SourceIP.String()), + zap.Uint16("source_port", ev.SourcePort), + zap.String("destination_ip", ev.DestinationIP.String()), + zap.Uint16("destination_port", ev.DestinationPort), + zap.Any("details", ev.ProtocolDetails), + ) + } + }(l.logger, evs) +} diff --git a/pkg/audit/sink/log_sink_test.go b/pkg/audit/sink/log_sink_test.go new file mode 100644 index 0000000..cc8deca --- /dev/null +++ b/pkg/audit/sink/log_sink_test.go @@ -0,0 +1,115 @@ +package sink_test + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/golang/mock/gomock" + logging_mock "gitlab.com/inetmock/inetmock/internal/mock/logging" + "gitlab.com/inetmock/inetmock/pkg/audit" + "gitlab.com/inetmock/inetmock/pkg/audit/sink" + "gitlab.com/inetmock/inetmock/pkg/logging" + "gitlab.com/inetmock/inetmock/pkg/wait" + "go.uber.org/zap" +) + +//nolint:dupl +func Test_logSink_OnSubscribe(t *testing.T) { + type fields struct { + loggerSetup func(t *testing.T, wg *sync.WaitGroup) logging.Logger + } + type testCase struct { + name string + fields fields + events []*audit.Event + } + tests := []testCase{ + { + name: "Get a single log line", + fields: fields{ + loggerSetup: func(t *testing.T, wg *sync.WaitGroup) logging.Logger { + ctrl := gomock.NewController(t) + t.Cleanup(ctrl.Finish) + loggerMock := logging_mock.NewMockLogger(ctrl) + + loggerMock. + EXPECT(). + With(gomock.Any()). + Return(loggerMock) + + loggerMock. + EXPECT(). + Info("handled request", gomock.Any()). + Do(func(_ string, _ ...zap.Field) { + wg.Done() + }). + Times(1) + + return loggerMock + }, + }, + events: testEvents[:1], + }, + { + name: "Get multiple events", + fields: fields{ + loggerSetup: func(t *testing.T, wg *sync.WaitGroup) logging.Logger { + ctrl := gomock.NewController(t) + t.Cleanup(ctrl.Finish) + loggerMock := logging_mock.NewMockLogger(ctrl) + + loggerMock. + EXPECT(). + With(gomock.Any()). + Return(loggerMock) + + loggerMock. + EXPECT(). + Info("handled request", gomock.Any()). + Do(func(_ string, _ ...zap.Field) { + wg.Done() + }). + Times(2) + + return loggerMock + }, + }, + events: testEvents, + }, + } + scenario := func(tt testCase) func(t *testing.T) { + return func(t *testing.T) { + wg := new(sync.WaitGroup) + wg.Add(len(tt.events)) + + logSink := sink.NewLogSink(tt.fields.loggerSetup(t, wg)) + var evs audit.EventStream + var err error + if evs, err = audit.NewEventStream(logging.CreateTestLogger(t)); err != nil { + t.Errorf("NewEventStream() error = %v", err) + } + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + if err = evs.RegisterSink(ctx, logSink); err != nil { + t.Errorf("RegisterSink() error = %v", err) + } + + for _, ev := range tt.events { + evs.Emit(*ev) + } + + select { + case <-time.After(100 * time.Millisecond): + t.Errorf("not all events recorded in time") + case <-wait.ForWaitGroupDone(wg): + } + } + } + + for _, tt := range tests { + t.Run(tt.name, scenario(tt)) + } +} diff --git a/pkg/audit/sink/metric_sink.go b/pkg/audit/sink/metric_sink.go new file mode 100644 index 0000000..2090dcb --- /dev/null +++ b/pkg/audit/sink/metric_sink.go @@ -0,0 +1,34 @@ +package sink + +import ( + "github.com/prometheus/client_golang/prometheus" + "gitlab.com/inetmock/inetmock/pkg/audit" + "gitlab.com/inetmock/inetmock/pkg/metrics" +) + +func NewMetricSink() (sink audit.Sink, err error) { + var totalEventsCounter *prometheus.CounterVec + if totalEventsCounter, err = metrics.Counter("audit", "events_total", "", "application", "transport"); err != nil { + return + } + sink = &metricSink{ + eventCounter: totalEventsCounter, + } + return +} + +type metricSink struct { + eventCounter *prometheus.CounterVec +} + +func (metricSink) Name() string { + return "metrics" +} + +func (m metricSink) OnSubscribe(evs <-chan audit.Event) { + go func(evs <-chan audit.Event) { + for ev := range evs { + m.eventCounter.WithLabelValues(ev.Application.String(), ev.Transport.String()).Inc() + } + }(evs) +} diff --git a/pkg/audit/sink/writer_sink.go b/pkg/audit/sink/writer_sink.go new file mode 100644 index 0000000..e0e2ba5 --- /dev/null +++ b/pkg/audit/sink/writer_sink.go @@ -0,0 +1,45 @@ +package sink + +import "gitlab.com/inetmock/inetmock/pkg/audit" + +type WriterSinkOption func(sink *writerCloserSink) + +var ( + WithCloseOnExit WriterSinkOption = func(sink *writerCloserSink) { + sink.closeOnExit = true + } +) + +func NewWriterSink(name string, target audit.Writer, opts ...WriterSinkOption) audit.Sink { + sink := &writerCloserSink{ + name: name, + target: target, + } + + for _, opt := range opts { + opt(sink) + } + + return sink +} + +type writerCloserSink struct { + name string + target audit.Writer + closeOnExit bool +} + +func (f writerCloserSink) Name() string { + return f.name +} + +func (f writerCloserSink) OnSubscribe(evs <-chan audit.Event) { + go func(target audit.Writer, closeOnExit bool, evs <-chan audit.Event) { + for ev := range evs { + _ = target.Write(&ev) + } + if closeOnExit { + _ = target.Close() + } + }(f.target, f.closeOnExit, evs) +} diff --git a/pkg/audit/sink/writer_sink_test.go b/pkg/audit/sink/writer_sink_test.go new file mode 100644 index 0000000..6d59c32 --- /dev/null +++ b/pkg/audit/sink/writer_sink_test.go @@ -0,0 +1,118 @@ +package sink_test + +import ( + "context" + "crypto/tls" + "net" + "net/http" + "sync" + "testing" + "time" + + "github.com/golang/mock/gomock" + audit_mock "gitlab.com/inetmock/inetmock/internal/mock/audit" + "gitlab.com/inetmock/inetmock/pkg/audit" + "gitlab.com/inetmock/inetmock/pkg/audit/details" + "gitlab.com/inetmock/inetmock/pkg/audit/sink" + "gitlab.com/inetmock/inetmock/pkg/logging" + "gitlab.com/inetmock/inetmock/pkg/wait" +) + +var ( + testEvents = []*audit.Event{ + { + Transport: audit.TransportProtocol_TCP, + Application: audit.AppProtocol_HTTP, + SourceIP: net.ParseIP("127.0.0.1").To4(), + DestinationIP: net.ParseIP("127.0.0.1").To4(), + SourcePort: 32344, + DestinationPort: 80, + TLS: &audit.TLSDetails{ + Version: audit.TLSVersionToEntity(tls.VersionTLS13).String(), + CipherSuite: tls.CipherSuiteName(tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA), + ServerName: "localhost", + }, + ProtocolDetails: details.HTTP{ + Method: "GET", + Host: "localhost", + URI: "http://localhost/asdf", + Proto: "HTTP 1.1", + Headers: http.Header{ + "Accept": []string{"application/json"}, + }, + }, + }, + { + Transport: audit.TransportProtocol_TCP, + Application: audit.AppProtocol_DNS, + SourceIP: net.ParseIP("::1").To16(), + DestinationIP: net.ParseIP("::1").To16(), + SourcePort: 32344, + DestinationPort: 80, + }, + } +) + +func Test_writerCloserSink_OnSubscribe(t *testing.T) { + type testCase struct { + name string + events []*audit.Event + } + tests := []testCase{ + { + name: "Get a single event", + events: testEvents[:1], + }, + { + name: "Get multiple events", + events: testEvents, + }, + } + scenario := func(tt testCase) func(t *testing.T) { + return func(t *testing.T) { + wg := new(sync.WaitGroup) + wg.Add(len(tt.events)) + + ctrl := gomock.NewController(t) + t.Cleanup(ctrl.Finish) + + writerMock := audit_mock.NewMockWriter(ctrl) + writerMock. + EXPECT(). + Write(gomock.Any()). + Do(func(_ *audit.Event) { + wg.Done() + }). + Times(len(tt.events)) + + writerCloserSink := sink.NewWriterSink("WriterMock", writerMock) + var evs audit.EventStream + var err error + + if evs, err = audit.NewEventStream(logging.CreateTestLogger(t)); err != nil { + t.Errorf("NewEventStream() error = %v", err) + } + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + if err = evs.RegisterSink(ctx, writerCloserSink); err != nil { + t.Errorf("RegisterSink() error = %v", err) + } + + for _, ev := range tt.events { + evs.Emit(*ev) + } + + select { + case <-time.After(100 * time.Millisecond): + t.Errorf("not all events recorded in time") + case <-wait.ForWaitGroupDone(wg): + } + } + } + + for _, tt := range tests { + t.Run(tt.name, scenario(tt)) + } +} diff --git a/pkg/audit/tls_details.go b/pkg/audit/tls_details.go new file mode 100644 index 0000000..e4565ff --- /dev/null +++ b/pkg/audit/tls_details.go @@ -0,0 +1,46 @@ +package audit + +import "crypto/tls" + +var ( + tlsToEntity = map[uint16]TLSVersion{ + tls.VersionSSL30: TLSVersion_SSLv30, + tls.VersionTLS10: TLSVersion_TLS10, + tls.VersionTLS11: TLSVersion_TLS11, + tls.VersionTLS12: TLSVersion_TLS12, + tls.VersionTLS13: TLSVersion_TLS13, + } +) + +type TLSDetails struct { + Version string + CipherSuite string + ServerName string +} + +func TLSVersionToEntity(version uint16) TLSVersion { + if v, known := tlsToEntity[version]; known { + return v + } + return TLSVersion_SSLv30 +} + +func NewTLSDetailsFromProto(entity *TLSDetailsEntity) *TLSDetails { + if entity == nil { + return nil + } + + return &TLSDetails{ + Version: entity.GetVersion().String(), + CipherSuite: entity.GetCipherSuite(), + ServerName: entity.GetServerName(), + } +} + +func (d TLSDetails) ProtoMessage() *TLSDetailsEntity { + return &TLSDetailsEntity{ + Version: TLSVersion(TLSVersion_value[d.Version]), + CipherSuite: d.CipherSuite, + ServerName: d.ServerName, + } +} diff --git a/pkg/audit/writer.go b/pkg/audit/writer.go new file mode 100644 index 0000000..15424d7 --- /dev/null +++ b/pkg/audit/writer.go @@ -0,0 +1,89 @@ +//go:generate mockgen -source=$GOFILE -destination=./../../internal/mock/audit/writer.mock.go -package=audit_mock + +package audit + +import ( + "encoding/binary" + "errors" + "io" + "sync" + + "google.golang.org/protobuf/proto" +) + +var ( + WithWriterByteOrder = func(order binary.ByteOrder) func(writer *eventWriter) { + return func(writer *eventWriter) { + writer.byteOrder = order + } + } + defaultOrder = binary.BigEndian + ErrValueMostNotBeNil = errors.New("event value must not be nil") +) + +type Writer interface { + io.Closer + Write(ev *Event) error +} + +type EventWriterOption func(writer *eventWriter) + +func NewEventWriter(target io.Writer, opts ...EventWriterOption) Writer { + writer := &eventWriter{ + target: target, + byteOrder: defaultOrder, + lengthPool: &sync.Pool{ + New: func() interface{} { + return make([]byte, lengthBufferSize) + }, + }, + } + + for _, opt := range opts { + opt(writer) + } + + return writer +} + +type eventWriter struct { + lengthPool *sync.Pool + target io.Writer + byteOrder binary.ByteOrder +} + +type syncer interface { + Sync() error +} + +func (e eventWriter) Write(ev *Event) (err error) { + if ev == nil { + return ErrValueMostNotBeNil + } + var bytes []byte + + if bytes, err = proto.Marshal(ev.ProtoMessage()); err != nil { + return + } + buf := e.lengthPool.Get().([]byte) + e.byteOrder.PutUint32(buf, uint32(len(bytes))) + + if _, err = e.target.Write(buf); err != nil { + return + } + if _, err = e.target.Write(bytes); err != nil { + return + } + if syncerInst, ok := e.target.(syncer); ok { + err = syncerInst.Sync() + } + + return +} + +func (e eventWriter) Close() error { + if closer, isCloser := e.target.(io.Closer); isCloser { + return closer.Close() + } + return nil +} diff --git a/pkg/audit/writer_test.go b/pkg/audit/writer_test.go new file mode 100644 index 0000000..17c2e88 --- /dev/null +++ b/pkg/audit/writer_test.go @@ -0,0 +1,117 @@ +//go:generate mockgen -source=$GOFILE -destination=./../../internal/mock/audit/writercloser.mock.go -package=audit_mock + +package audit_test + +import ( + "encoding/binary" + "encoding/hex" + "io" + "testing" + + "github.com/golang/mock/gomock" + audit_mock "gitlab.com/inetmock/inetmock/internal/mock/audit" + "gitlab.com/inetmock/inetmock/pkg/audit" +) + +type WriterCloserSyncer interface { + io.WriteCloser + Sync() error +} + +func Test_eventWriter_Write(t *testing.T) { + type fields struct { + order binary.ByteOrder + } + type args struct { + evs []*audit.Event + } + type testCase struct { + name string + fields fields + args args + wantErr bool + } + tests := []testCase{ + { + name: "Write a single event - little endian", + fields: fields{ + order: binary.LittleEndian, + }, + args: args{ + evs: testEvents[:1], + }, + wantErr: false, + }, + { + name: "Write a single event - big endian", + fields: fields{ + order: binary.BigEndian, + }, + args: args{ + evs: testEvents[:1], + }, + wantErr: false, + }, + { + name: "Write multiple events - little endian", + fields: fields{ + order: binary.LittleEndian, + }, + args: args{ + evs: testEvents, + }, + wantErr: false, + }, + { + name: "Write multiple events - big endian", + fields: fields{ + order: binary.BigEndian, + }, + args: args{ + evs: testEvents, + }, + wantErr: false, + }, + } + scenario := func(tt testCase) func(t *testing.T) { + return func(t *testing.T) { + ctrl := gomock.NewController(t) + t.Cleanup(ctrl.Finish) + writerMock := audit_mock.NewMockWriterCloserSyncer(ctrl) + calls := make([]*gomock.Call, 0) + for i := 0; i < len(tt.args.evs); i++ { + calls = append(calls, + writerMock. + EXPECT(). + Write(gomock.Any()). + Do(func(data []byte) { + t.Logf("got payload = %s", hex.EncodeToString(data)) + t.Logf("got length %d", tt.fields.order.Uint32(data)) + }), + writerMock. + EXPECT(). + Write(gomock.Any()). + Do(func(data []byte) { + t.Logf("got payload = %s", hex.EncodeToString(data)) + }), + writerMock. + EXPECT(). + Sync(), + ) + } + gomock.InOrder(calls...) + + e := audit.NewEventWriter(writerMock, audit.WithWriterByteOrder(tt.fields.order)) + + for _, ev := range tt.args.evs { + if err := e.Write(ev); (err != nil) != tt.wantErr { + t.Errorf("Write() error = %v, wantErr %v", err, tt.wantErr) + } + } + } + } + + for _, tt := range tests { + t.Run(tt.name, scenario(tt)) + } +} diff --git a/pkg/logging/factory.go b/pkg/logging/factory.go index 727d55b..961ee55 100644 --- a/pkg/logging/factory.go +++ b/pkg/logging/factory.go @@ -2,6 +2,7 @@ package logging import ( "strings" + "testing" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -48,6 +49,13 @@ func CreateLogger() (Logger, error) { } } +func CreateTestLogger(tb testing.TB) Logger { + return &testLogger{ + tb: tb, + encoder: zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), + } +} + func MustCreateLogger() Logger { if logger, err := CreateLogger(); err != nil { panic(err) diff --git a/pkg/logging/test_logger.go b/pkg/logging/test_logger.go new file mode 100644 index 0000000..3ae9a93 --- /dev/null +++ b/pkg/logging/test_logger.go @@ -0,0 +1,131 @@ +package logging + +import ( + "runtime" + "runtime/debug" + "testing" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type testLogger struct { + name string + fields []zap.Field + tb testing.TB + encoder zapcore.Encoder +} + +func (t testLogger) Named(s string) Logger { + return testLogger{ + 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, + } +} + +func (t testLogger) Debug(msg string, fields ...zap.Field) { + t.tb.Helper() + buf, err := t.encoder.EncodeEntry(zapcore.Entry{ + Level: zapcore.DebugLevel, + Time: time.Now(), + LoggerName: t.name, + Message: msg, + Caller: zapcore.NewEntryCaller(runtime.Caller(2)), + }, append(t.fields, fields...)) + + if err == nil { + t.tb.Log(buf.String()) + } +} + +func (t testLogger) Info(msg string, fields ...zap.Field) { + t.tb.Helper() + buf, err := t.encoder.EncodeEntry(zapcore.Entry{ + Level: zapcore.InfoLevel, + Time: time.Now(), + LoggerName: t.name, + Message: msg, + Caller: zapcore.NewEntryCaller(runtime.Caller(2)), + }, append(t.fields, fields...)) + + if err == nil { + t.tb.Log(buf.String()) + } +} + +func (t testLogger) Warn(msg string, fields ...zap.Field) { + t.tb.Helper() + buf, err := t.encoder.EncodeEntry(zapcore.Entry{ + Level: zapcore.WarnLevel, + Time: time.Now(), + LoggerName: t.name, + Message: msg, + Caller: zapcore.NewEntryCaller(runtime.Caller(2)), + }, append(t.fields, fields...)) + + if err == nil { + t.tb.Log(buf.String()) + } +} + +func (t testLogger) Error(msg string, fields ...zap.Field) { + t.tb.Helper() + buf, err := t.encoder.EncodeEntry(zapcore.Entry{ + Level: zapcore.ErrorLevel, + Time: time.Now(), + LoggerName: t.name, + Message: msg, + Caller: zapcore.NewEntryCaller(runtime.Caller(2)), + Stack: string(debug.Stack()), + }, append(t.fields, fields...)) + + if err == nil { + t.tb.Log(buf.String()) + } +} + +func (t testLogger) Panic(msg string, fields ...zap.Field) { + t.tb.Helper() + buf, err := t.encoder.EncodeEntry(zapcore.Entry{ + Level: zapcore.PanicLevel, + Time: time.Now(), + LoggerName: t.name, + Message: msg, + Caller: zapcore.NewEntryCaller(runtime.Caller(2)), + Stack: string(debug.Stack()), + }, append(t.fields, fields...)) + + if err == nil { + t.tb.Error(buf.String()) + } +} + +func (t testLogger) Fatal(msg string, fields ...zap.Field) { + t.tb.Helper() + buf, err := t.encoder.EncodeEntry(zapcore.Entry{ + Level: zapcore.FatalLevel, + Time: time.Now(), + LoggerName: t.name, + Message: msg, + Caller: zapcore.NewEntryCaller(runtime.Caller(2)), + Stack: string(debug.Stack()), + }, append(t.fields, fields...)) + + if err == nil { + t.tb.Error(buf.String()) + } +} + +func (t testLogger) Sync() error { + return nil +} diff --git a/pkg/wait/wg.go b/pkg/wait/wg.go new file mode 100644 index 0000000..11adf3f --- /dev/null +++ b/pkg/wait/wg.go @@ -0,0 +1,14 @@ +package wait + +import "sync" + +func ForWaitGroupDone(wg *sync.WaitGroup) <-chan struct{} { + done := make(chan struct{}) + + go func(wg *sync.WaitGroup) { + wg.Wait() + close(done) + }(wg) + + return done +} diff --git a/plugins/dns_mock/regex_handler.go b/plugins/dns_mock/regex_handler.go deleted file mode 100644 index b6bc4c0..0000000 --- a/plugins/dns_mock/regex_handler.go +++ /dev/null @@ -1,101 +0,0 @@ -package dns_mock - -import ( - "github.com/miekg/dns" - "github.com/prometheus/client_golang/prometheus" - "gitlab.com/inetmock/inetmock/pkg/logging" - "go.uber.org/zap" -) - -type regexHandler struct { - handlerName string - routes []resolverRule - fallback ResolverFallback - logger logging.Logger -} - -func (rh *regexHandler) AddRule(rule resolverRule) { - rh.routes = append(rh.routes, rule) -} - -func (rh regexHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { - timer := prometheus.NewTimer(requestDurationHistogram.WithLabelValues(rh.handlerName)) - defer func() { - timer.ObserveDuration() - }() - - m := new(dns.Msg) - m.Compress = false - m.SetReply(r) - - switch r.Opcode { - case dns.OpcodeQuery: - rh.handleQuery(m) - } - if err := w.WriteMsg(m); err != nil { - rh.logger.Error( - "Failed to write DNS response message", - zap.Error(err), - ) - } -} - -func (rh regexHandler) handleQuery(m *dns.Msg) { - for _, q := range m.Question { - rh.logger.Info( - "handling question", - zap.String("question", q.Name), - ) - switch q.Qtype { - case dns.TypeA: - totalHandledRequestsCounter.WithLabelValues(rh.handlerName).Inc() - for _, rule := range rh.routes { - if rule.pattern.MatchString(q.Name) { - m.Authoritative = true - answer := &dns.A{ - Hdr: dns.RR_Header{ - Name: q.Name, - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 60, - }, - A: rule.response, - } - m.Answer = append(m.Answer, answer) - rh.logger.Info( - "matched DNS rule", - zap.String("pattern", rule.pattern.String()), - zap.String("response", rule.response.String()), - ) - return - } - } - rh.handleFallbackForMessage(m, q) - default: - unhandledRequestsCounter.WithLabelValues(rh.handlerName).Inc() - rh.logger.Warn( - "Unhandled DNS question type - no response will be sent", - zap.Uint16("question_type", q.Qtype), - ) - } - } -} - -func (rh regexHandler) handleFallbackForMessage(m *dns.Msg, q dns.Question) { - fallbackIP := rh.fallback.GetIP() - answer := &dns.A{ - Hdr: dns.RR_Header{ - Name: q.Name, - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 60, - }, - A: fallbackIP, - } - rh.logger.Info( - "Falling back to generated IP", - zap.String("response", fallbackIP.String()), - ) - m.Authoritative = true - m.Answer = append(m.Answer, answer) -}