Merge branch '10-handler-event-stream' into 'master'

Resolve "Handler Event Stream"

Closes #13 and #10

See merge request inetmock/inetmock!12
This commit is contained in:
Peter 2021-01-27 12:53:22 +00:00
commit dd4b191abb
90 changed files with 3323 additions and 481 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<string, HTTPHeaderValue> headers = 5;
}

View file

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

View file

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

View file

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

View file

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

118
cmd/imctl/audit_sinks.go Normal file
View file

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

54
cmd/imctl/audit_watch.go Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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(&notBefore, 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(&notAfter, 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
}

View file

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

View file

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

View file

@ -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
port: 53
metrics:
handler: metrics_exporter
listenAddress: 0.0.0.0
ports:
- 9110
options:
route: /metrics

View file

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

17
go.mod
View file

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

245
go.sum
View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
package endpoints
package endpoint
import "time"

View file

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

View file

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

View file

@ -1,4 +1,4 @@
package endpoints
package endpoint
import (
"reflect"

View file

@ -1,4 +1,4 @@
package dns_mock
package mock
import (
"encoding/binary"

View file

@ -1,4 +1,4 @@
package dns_mock
package mock
import (
"net"

View file

@ -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 := &regexHandler{
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 {

View file

@ -1,4 +1,4 @@
package dns_mock
package mock
import (
"net"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
package http_mock
package mock
import (
"github.com/prometheus/client_golang/prometheus"

View file

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

View file

@ -1,4 +1,4 @@
package http_proxy
package proxy
import (
"fmt"

View file

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

View file

@ -1,4 +1,4 @@
package http_proxy
package proxy
import (
"github.com/prometheus/client_golang/prometheus"

View file

@ -1,4 +1,4 @@
package metrics_exporter
package metrics
import (
"context"

View file

@ -1,4 +1,4 @@
package metrics_exporter
package metrics
type metricsExporterOptions struct {
Route string

View file

@ -1,4 +1,4 @@
package metrics_exporter
package metrics
import (
"gitlab.com/inetmock/inetmock/pkg/api"

View file

@ -1,4 +1,4 @@
package tls_interceptor
package interceptor
import (
"context"

View file

@ -1,4 +1,4 @@
package tls_interceptor
package interceptor
import (
"fmt"

View file

@ -1,4 +1,4 @@
package tls_interceptor
package interceptor
import (
"net"

View file

@ -1,4 +1,4 @@
package tls_interceptor
package interceptor
import (
"fmt"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

30
pkg/audit/api.go Normal file
View file

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

View file

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

View file

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

134
pkg/audit/event.go Normal file
View file

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

145
pkg/audit/event_stream.go Normal file
View file

@ -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 := &registeredSink{
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()
}
}
}

View file

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

61
pkg/audit/event_test.go Normal file
View file

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

74
pkg/audit/options.go Normal file
View file

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

87
pkg/audit/reader.go Normal file
View file

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

99
pkg/audit/reader_test.go Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

46
pkg/audit/tls_details.go Normal file
View file

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

89
pkg/audit/writer.go Normal file
View file

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

117
pkg/audit/writer_test.go Normal file
View file

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

View file

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

131
pkg/logging/test_logger.go Normal file
View file

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

14
pkg/wait/wg.go Normal file
View file

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

View file

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