diff --git a/.circleci/config.yml b/.circleci/config.yml index f6dbf8c490..8c03506a08 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -44,8 +44,21 @@ jobs: - run: name: go.sum up-to-date command: | - go run -mod=readonly gosum.go - + if ! go run -mod=readonly gosum.go; then + # Older go versions, e.g. go1.14 will fail the check above with + # the message below. + # + # go: updates to go.sum needed, disabled by -mod=readonly + # + # Newer versions show which go.sum entries are missing. To get + # useful CI errors for older versions, always print an explicit + # diff when go.sum is not up-to-date. + cp go.sum go.sum.before + go mod tidy + echo "--> go.sum diff:" + diff go.sum.before go.sum + exit 1 + fi - run: name: milestone command: | @@ -101,9 +114,15 @@ jobs: steps: - checkout - run: mkdir -p $TEST_RESULTS + - run: cp go.sum go.sum.orig - restore_cache: # restores saved cache if no changes are detected since last run keys: - - go-mod-v4-{{ checksum "go.sum" }} + - go-mod-v5-core-{{ checksum "go.sum.orig" }} + - run: + name: Enforce some dependencies + command: | + # last version compatible with go1.14, needed for testtraceprof + echo 'replace golang.org/x/net => golang.org/x/net d418f374d30933c6c7db22cf349625c295a5afaa' >> go.mod - run: name: Testing command: | @@ -111,9 +130,9 @@ jobs: env DD_APPSEC_ENABLED=$(test "<< parameters.build_tags >>" = appsec && echo -n true) gotestsum --junitfile ${TEST_RESULTS}/gotestsum-report.xml -- $PACKAGE_NAMES -v -race -coverprofile=coverage.txt -covermode=atomic -tags "<< parameters.build_tags >>" - save_cache: - key: go-mod-v4-{{ checksum "go.sum" }} + key: go-mod-v5-core-{{ checksum "go.sum.orig" }} paths: - - "/go/pkg/mod" + - "/home/circleci/go" - store_artifacts: # upload test summary for display in Artifacts path: /tmp/test-results @@ -142,6 +161,8 @@ jobs: environment: GOPATH: "/home/circleci/go" - image: cassandra:3.7 + environment: + JVM_OPTS: "-Xms750m -Xmx750m" - image: circleci/mysql:5.7 environment: MYSQL_ROOT_PASSWORD: admin @@ -161,6 +182,16 @@ jobs: - image: elasticsearch:5 environment: ES_JAVA_OPTS: "-Xms750m -Xmx750m" # https://github.com/10up/wp-local-docker/issues/6 + - image: elasticsearch:6.8.13 + environment: + http.port: 9202-9300 + discovery.type: single-node + ES_JAVA_OPTS: "-Xms750m -Xmx750m" # https://github.com/10up/wp-local-docker/issues/6 + - image: elasticsearch:7.14.1 + environment: + http.port: 9203-9300 + discovery.type: single-node + ES_JAVA_OPTS: "-Xms750m -Xmx750m" # https://github.com/10up/wp-local-docker/issues/6 - image: datadog/docker-dd-agent environment: DD_APM_ENABLED: "true" @@ -182,9 +213,10 @@ jobs: steps: - checkout - run: mkdir -p $TEST_RESULTS + - run: cp go.sum go.sum.orig - restore_cache: # restores saved cache if no changes are detected since last run keys: - - go-mod-v4-{{ checksum "go.sum" }} + - go-mod-v5-contrib-{{ checksum "go.sum.orig" }} - restore_cache: keys: @@ -213,8 +245,12 @@ jobs: go get cloud.google.com/go/pubsub@v1.6.1 # Temporarily enforce this version. 1.9.0 is incompatible with go < 1.16 go get github.com/hashicorp/consul/api@v1.8.1 - # Shopify/sarama < v1.22 doesn't compile with go1.14 + # github.com/hashicorp/vault/sdk > v0.2.0 doesn't compile with go1.14 + go get github.com/hashicorp/vault/sdk@v0.2.0 + # Shopify/sarama > v1.22 doesn't compile with go1.14 go get github.com/Shopify/sarama@v1.22.0 + # Temporary enforcing gorm to v1.22.4 to avoid the problems of v1.22.5 + go get -v gorm.io/gorm@v1.22.4 - run: name: Wait for MySQL @@ -229,13 +265,21 @@ jobs: command: dockerize -wait tcp://localhost:6379 -timeout 1m - run: - name: Wait for ElasticSearch (1) + name: Wait for ElasticSearch (2) command: dockerize -wait http://localhost:9200 -timeout 1m - run: - name: Wait for ElasticSearch (2) + name: Wait for ElasticSearch (5) command: dockerize -wait http://localhost:9201 -timeout 1m + - run: + name: Wait for ElasticSearch (6) + command: dockerize -wait http://localhost:9202 -timeout 1m + + - run: + name: Wait for ElasticSearch (7) + command: dockerize -wait http://localhost:9203 -timeout 1m + - run: name: Wait for Datadog Agent command: dockerize -wait tcp://127.0.0.1:8126 -timeout 1m @@ -256,12 +300,9 @@ jobs: name: Testing integrations command: | PACKAGE_NAMES=$(go list ./contrib/... | grep -v -e grpc.v12 -e google.golang.org/api | circleci tests split --split-by=timings --timings-type=classname) - env DD_APPSEC_ENABLED=$(test "<< parameters.build_tags >>" = appsec && echo -n true) gotestsum --junitfile ${TEST_RESULTS}/gotestsum-report.xml -- $PACKAGE_NAMES -v -race -coverprofile=coverage.txt -covermode=atomic -tags "<< parameters.build_tags >>" - - - save_cache: - key: go-mod-v4-{{ checksum "go.sum" }} - paths: - - "/go/pkg/mod" + export DD_APPSEC_ENABLED=$(test "<< parameters.build_tags >>" = appsec && echo -n true) + export INTEGRATION=true + gotestsum --junitfile ${TEST_RESULTS}/gotestsum-report.xml -- $PACKAGE_NAMES -v -race -coverprofile=coverage.txt -covermode=atomic -tags "<< parameters.build_tags >>" - store_artifacts: # upload test summary for display in Artifacts path: /tmp/test-results @@ -293,7 +334,12 @@ jobs: git clone git@github.com:DataDog/sketches-go && cd sketches-go git fetch origin && git checkout v1.0.0 && cd ../.. - go test -mod=vendor -v ./contrib/google.golang.org/grpc.v12/... + INTEGRATION=true go test -mod=vendor -v ./contrib/google.golang.org/grpc.v12/... + + - save_cache: + key: go-mod-v5-contrib-{{ checksum "go.sum.orig" }} + paths: + - "/home/circleci/go" - run: name: Upload coverage report to Codecov diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml new file mode 100644 index 0000000000..4bd5a18a4b --- /dev/null +++ b/.github/workflows/system-tests.yml @@ -0,0 +1,53 @@ +name: System Tests + +on: + pull_request: + branches: + - "**" + workflow_dispatch: {} + schedule: + - cron: '00 04 * * 2-6' + +jobs: + system-tests: + if: ${{ github.event.pull_request.head.repo.full_name == 'DataDog/dd-trace-go' }} + runs-on: ubuntu-latest + strategy: + matrix: + include: + - library: golang + weblog-variant: net-http + - library: golang + weblog-variant: gorilla + fail-fast: false + env: + TEST_LIBRARY: golang + WEBLOG_VARIANT: ${{ matrix.weblog-variant }} + DD_API_KEY: ${{ secrets.DD_API_KEY }} + steps: + - name: Checkout system tests + uses: actions/checkout@v2 + with: + repository: 'DataDog/system-tests' + + - name: Checkout dd-trace-go + uses: actions/checkout@v2 + with: + path: 'binaries/dd-trace-go' + + - name: Build + run: ./build.sh + + - name: Run + run: ./run.sh + + - name: Compress artifact + if: ${{ always() }} + run: tar -czvf artifact.tar.gz $(ls | grep logs) + + - name: Upload artifact + uses: actions/upload-artifact@v2 + if: ${{ always() }} + with: + name: logs_${{ matrix.weblog-variant }} + path: artifact.tar.gz diff --git a/.gitignore b/.gitignore index 44460622d7..4da4073034 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ bin/ *.swp .idea dd-trace-go.iml +vendor /contrib/google.golang.org/grpc.v12/vendor/ diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000000..b0c28372e5 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,17 @@ +# Note: Later matches take precedence + +# default owner +* @DataDog/apm-go + +# tracing +/contrib @DataDog/tracing-go +/ddtrace @DataDog/tracing-go +/internal @DataDog/tracing-go + +# profiling +/profiler @DataDog/profiling-go +/internal/traceprof @DataDog/profiling-go + +# appsec +/internal/appsec @DataDog/appsec-go +/contrib/**/appsec.go @DataDog/appsec-go diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 0000000000..ed68ebd2d6 --- /dev/null +++ b/FAQ.md @@ -0,0 +1,41 @@ +# Frequently Asked Questions (dd-trace-go) +This document contains answers to questions frequently asked by users. Just because something is listed here doesn't mean it's beyond question, so feel free to open an issue if you want to change or improve any of these things, but this should provide useful information about *how* things are and *why* they are that way. + +#### Why does dd-trace-go use go modules in a non-standard way? +This repository currently takes an [idiosyncratic approach](https://github.com/DataDog/dd-trace-go/issues/810) to using Go modules. You may notice that the `go.mod` file does not list all dependencies of the project, but instead just lists the ones that the core logic depends on (`ddtrace/...`, `profiler/...` and a couple other packages). + +As a user of the `dd-trace-go` module, this should be transparent and, if anything, make life easier for you. For those wanting to contribute, this can be confusing and annoying. + +The primary reason that we do this is for compatibility. `dd-trace-go` contains many contrib packages for many many libraries, all of which are optional features. If we included all of the dd-trace-go dependencies in the `go.mod` file, users' projects would transitively include dependencies for multiple web frameworks, grpc, sql libraries, redis, and lots of other stuff. That's bothersome enough, but worse is that users may end up having versions of libraries forcibly updated, even when they're not using integrations for those libraries. If a user wanted to include `dd-trace-go` to trace their web router but was using an old version of `go-redis` or somesuch, they would be forced to update, even if they don't want to trace `go-redis` and `dd-trace-go` doesn't actually need it. The more integrations we include in `dd-trace-go`, the more likely this situation becomes when we add all dependencies into the `go.mod` file. + +Another reason is that we can't. Due to some of the projects we integrate with breaking semantic versioning, it's not possible for us to include all the required dependencies and versions, since we need to depend on multiple, incompatible minor versions of some packages, namely google.golang.org/grpc (See: https://github.com/grpc/grpc-go/issues/3726) + +The way forward is through submodules, allowing us to have multiple dependency sets inside `dd-trace-go` and only including what's actually necessary in a user's application. Unfortunately, a prerequisite for this is to get away from `gopkg.in`, which does not support submodules. + +Please follow: [848](https://github.com/DataDog/dd-trace-go/issues/848) and [922](https://github.com/DataDog/dd-trace-go/pull/922) + +Note also that the [Contribution Guidelines](https://github.com/DataDog/dd-trace-go/blob/v1/CONTRIBUTING.md#go-modules) contain information on working with this setup. + + +#### Why do client integration spans not use the global service name? +Integrations that are considered *clients* (http clients, grpc clients, sql clients) do **not** use the globally-configured service name. This is by design and is a product-level decision that spans across all the languages' tracers. This is likely to segregate the time spent actually doing the work of the service from the time waiting for another service (i.e. waiting on a web server to return a response). + +While there are good arguments to be made that client integrations should take the same service name as everything else in the service, that's not how the library is intended to function today. As a work-around, most integrations have a `WithServiceName` `Option` that will allow you to override the default. If the integration you are using cannot be configured the way you want, please open an issue to discuss adding as option. + +#### Why are client integration spans not measured? +This is primarily for 2 reasons: +1. Cost - often a traced client will speak to a traced server. If both are measured, there is duplication of measurement here, and duplication of cost for no benefit. By measuring **only** the server, we get analytics without duplication. +2. Name conflicts - Today, metrics are calculated based on a key of the span's service name and operation name. This can cause clashes when a client and server both use the same operation name. + +For example, `net/http` [server tracing](https://github.com/DataDog/dd-trace-go/blob/f86a82b0ae679be3bbd2fe3652ae17f06aabd960/contrib/internal/httputil/trace.go#L52): +``` +span, ctx := tracer.StartSpanFromContext(cfg.Request.Context(), "http.request", opts...) +``` + +and `net/http` [client tracing](https://github.com/DataDog/dd-trace-go/blob/f86a82b0ae679be3bbd2fe3652ae17f06aabd960/contrib/net/http/roundtripper.go#L39): +``` +span, ctx := tracer.StartSpanFromContext(req.Context(), "http.request", opts...) +``` + +This is something that users ask for from time to time, and there is work internally to resolve this. Please follow [#1006](https://github.com/DataDog/dd-trace-go/issues/1006) to track the progress. + diff --git a/checkcopyright.go b/checkcopyright.go index 678b5d246b..f0f9849eb0 100644 --- a/checkcopyright.go +++ b/checkcopyright.go @@ -3,6 +3,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2016 Datadog, Inc. +//go:build ignore // +build ignore // This tool validates that all *.go files in the repository have the copyright text attached. @@ -22,6 +23,8 @@ func main() { // copyrightRegexp matches years or year ranges like "2016", "2016-2019", // "2016,2018-2020" in the copyright header. copyrightRegexp := regexp.MustCompile(`// Copyright 20[0-9]{2}[0-9,\-]* Datadog, Inc.`) + generatedRegexp := regexp.MustCompile(`Code generated by.+DO NOT EDIT`) + if err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { if err != nil { return err @@ -40,7 +43,7 @@ func main() { if err != nil && err != io.EOF { return err } - if !copyrightRegexp.Match(snip) { + if !copyrightRegexp.Match(snip) && !generatedRegexp.Match(snip) { // report missing header missing = true log.Printf("Copyright header missing in %q.\n", path) diff --git a/codecov.yml b/codecov.yml index ff3a82e774..f77fe6b1da 100644 --- a/codecov.yml +++ b/codecov.yml @@ -15,6 +15,11 @@ coverage: patch: default: target: 90% + # Only run this check for pull requests, but skip it for merged + # commits. This highlights patches with potential coverage problems + # during development, but doesn't make our v1 (release) branch look + # like it fails CI checks as we consider this check to be optional. + only_pulls: true flags: core: diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index cd80d654d3..fa214bbe15 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -182,6 +182,9 @@ func (tp *traceParams) tryTrace(ctx context.Context, qtype queryType, query stri // See: https://github.com/DataDog/dd-trace-go/issues/270 return } + if _, exists := tracer.SpanFromContext(ctx); tp.cfg.childSpansOnly && !exists { + return + } name := fmt.Sprintf("%s.query", tp.driverName) opts := []ddtrace.StartSpanOption{ tracer.ServiceName(tp.cfg.serviceName), diff --git a/contrib/database/sql/conn_test.go b/contrib/database/sql/conn_test.go index 55b361162a..d08aa45d4e 100644 --- a/contrib/database/sql/conn_test.go +++ b/contrib/database/sql/conn_test.go @@ -16,6 +16,7 @@ import ( "github.com/go-sql-driver/mysql" "github.com/lib/pq" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestWithSpanTags(t *testing.T) { @@ -76,6 +77,7 @@ func TestWithSpanTags(t *testing.T) { for _, tt := range testcases { t.Run(tt.name, func(t *testing.T) { Register(tt.sqlRegister.name, tt.sqlRegister.driver, tt.sqlRegister.opts...) + defer unregister(tt.sqlRegister.name) db, err := Open(tt.sqlRegister.name, tt.sqlRegister.dsn) if err != nil { log.Fatal(err) @@ -100,3 +102,62 @@ func TestWithSpanTags(t *testing.T) { }) } } + +func TestWithChildSpansOnly(t *testing.T) { + type sqlRegister struct { + name string + dsn string + driver driver.Driver + opts []RegisterOption + } + testcases := []struct { + name string + sqlRegister sqlRegister + }{ + { + name: "mysql", + sqlRegister: sqlRegister{ + name: "mysql", + dsn: "test:test@tcp(127.0.0.1:3306)/test", + driver: &mysql.MySQLDriver{}, + opts: []RegisterOption{ + WithChildSpansOnly(), + }, + }, + }, + { + name: "postgres", + sqlRegister: sqlRegister{ + name: "postgres", + dsn: "postgres://postgres:postgres@127.0.0.1:5432/postgres?sslmode=disable", + driver: &pq.Driver{}, + opts: []RegisterOption{ + WithChildSpansOnly(), + WithServiceName("postgres-test"), + WithAnalyticsRate(0.2), + }, + }, + }, + } + mt := mocktracer.Start() + defer mt.Stop() + for _, tt := range testcases { + t.Run(tt.name, func(t *testing.T) { + Register(tt.sqlRegister.name, tt.sqlRegister.driver, tt.sqlRegister.opts...) + defer unregister(tt.sqlRegister.name) + db, err := Open(tt.sqlRegister.name, tt.sqlRegister.dsn) + require.NoError(t, err) + defer db.Close() + mt.Reset() + + ctx := context.Background() + + rows, err := db.QueryContext(ctx, "SELECT 1") + require.NoError(t, err) + rows.Close() + + spans := mt.FinishedSpans() + assert.Len(t, spans, 0) + }) + } +} diff --git a/contrib/database/sql/option.go b/contrib/database/sql/option.go index 9c89204a5d..47bd4208bd 100644 --- a/contrib/database/sql/option.go +++ b/contrib/database/sql/option.go @@ -12,9 +12,10 @@ import ( ) type config struct { - serviceName string - analyticsRate float64 - dsn string + serviceName string + analyticsRate float64 + dsn string + childSpansOnly bool } // Option represents an option that can be passed to Register, Open or OpenDB. @@ -74,3 +75,11 @@ func WithDSN(name string) Option { cfg.dsn = name } } + +// WithChildSpansOnly causes spans to be created only when +// there is an existing parent span in the Context. +func WithChildSpansOnly() Option { + return func(cfg *config) { + cfg.childSpansOnly = true + } +} diff --git a/contrib/database/sql/sql.go b/contrib/database/sql/sql.go index d80fa41869..755bcd9eff 100644 --- a/contrib/database/sql/sql.go +++ b/contrib/database/sql/sql.go @@ -79,6 +79,14 @@ func (d *driverRegistry) config(name string) (*config, bool) { return config, ok } +// unregister is used to make tests idempotent. +func (d *driverRegistry) unregister(name string) { + driver := d.drivers[name] + delete(d.keys, reflect.TypeOf(driver)) + delete(d.configs, name) + delete(d.drivers, name) +} + // Register tells the sql integration package about the driver that we will be tracing. It must // be called before Open, if that connection is to be traced. It uses the driverName suffixed // with ".db" as the default service name. @@ -103,6 +111,13 @@ func Register(driverName string, driver driver.Driver, opts ...RegisterOption) { registeredDrivers.add(driverName, driver, cfg) } +// unregister is used to make tests idempotent. +func unregister(name string) { + if registeredDrivers.isRegistered(name) { + registeredDrivers.unregister(name) + } +} + // errNotRegistered is returned when there is an attempt to open a database connection towards a driver // that has not previously been registered using this package. var errNotRegistered = errors.New("sqltrace: Register must be called before Open") @@ -168,6 +183,7 @@ func OpenDB(c driver.Connector, opts ...Option) *sql.DB { if math.IsNaN(cfg.analyticsRate) { cfg.analyticsRate = rc.analyticsRate } + cfg.childSpansOnly = rc.childSpansOnly tc := &tracedConnector{ connector: c, driverName: name, diff --git a/contrib/elastic/go-elasticsearch.v6/elastictrace.go b/contrib/elastic/go-elasticsearch.v6/elastictrace.go new file mode 100644 index 0000000000..8d864ed896 --- /dev/null +++ b/contrib/elastic/go-elasticsearch.v6/elastictrace.go @@ -0,0 +1,149 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +// Package elastic provides functions to trace the github.com/elastic/go-elasticsearch packages. +package elastic // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/elastic/go-elasticsearch + +import ( + "bufio" + "bytes" + "compress/gzip" + "errors" + "fmt" + "io" + "io/ioutil" + "math" + "net/http" + "regexp" + "strconv" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" +) + +// NewRoundTripper returns a new http.Client which traces requests under the given service name. +func NewRoundTripper(opts ...ClientOption) http.RoundTripper { + cfg := new(clientConfig) + defaults(cfg) + for _, fn := range opts { + fn(cfg) + } + return &roundTripper{config: *cfg} +} + +// bodyCutoff specifies the maximum number of bytes that will be stored as a tag +// value obtained from an HTTP request or response body. +var bodyCutoff = 5 * 1024 + +// roundTripper is an implementation of http.RoundTripper that captures Elasticsearch spans. +type roundTripper struct { + config clientConfig +} + +var _ http.RoundTripper = &roundTripper{} + +// RoundTrip satisfies the RoundTripper interface, wraps the sub Transport and +// captures a span of the Elasticsearch request. +func (t *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + url := req.URL.Path + method := req.Method + resource := t.config.resourceNamer(url, method) + opts := []ddtrace.StartSpanOption{ + tracer.ServiceName(t.config.serviceName), + tracer.SpanType(ext.SpanTypeElasticSearch), + tracer.ResourceName(resource), + tracer.Tag("elasticsearch.method", method), + tracer.Tag("elasticsearch.url", url), + tracer.Tag("elasticsearch.params", req.URL.Query().Encode()), + } + if !math.IsNaN(t.config.analyticsRate) { + opts = append(opts, tracer.Tag(ext.EventSampleRate, t.config.analyticsRate)) + } + span, _ := tracer.StartSpanFromContext(req.Context(), "elasticsearch.query", opts...) + defer span.Finish() + + contentEncoding := req.Header.Get("Content-Encoding") + snip, rc, err := peek(req.Body, contentEncoding, int(req.ContentLength), bodyCutoff) + if err == nil { + span.SetTag("elasticsearch.body", snip) + } + req.Body = rc + // process using the standard transport + res, err := t.config.transport.RoundTrip(req) + if err != nil { + // roundtrip error + span.SetTag(ext.Error, err) + } else if res.StatusCode < 200 || res.StatusCode > 299 { + // HTTP error + snip, rc, err := peek(res.Body, contentEncoding, int(res.ContentLength), bodyCutoff) + if err != nil { + snip = http.StatusText(res.StatusCode) + } + span.SetTag(ext.Error, errors.New(snip)) + res.Body = rc + } + if res != nil { + span.SetTag(ext.HTTPCode, strconv.Itoa(res.StatusCode)) + } + return res, err + +} + +var ( + idRegexp = regexp.MustCompile("/([0-9]+)([/\\?]|$)") + idPlaceholder = []byte("/?$2") + indexRegexp = regexp.MustCompile("[0-9]{2,}") + indexPlaceholder = []byte("?") +) + +// quantize quantizes an Elasticsearch to extract a meaningful resource from the request. +// We quantize based on the method+url with some cleanup applied to the URL. +// URLs with an ID will be generalized as will (potential) timestamped indices. +func quantize(url, method string) string { + quantizedURL := idRegexp.ReplaceAll([]byte(url), idPlaceholder) + quantizedURL = indexRegexp.ReplaceAll(quantizedURL, indexPlaceholder) + return fmt.Sprintf("%s %s", method, quantizedURL) +} + +// peek attempts to return the first n bytes, as a string, from the provided io.ReadCloser. +// It returns a new io.ReadCloser which points to the same underlying stream and can be read +// from to access the entire data including the snippet. max is used to specify the length +// of the stream contained in the reader. If unknown, it should be -1. If 0 < max < n it +// will override n. +func peek(rc io.ReadCloser, encoding string, max, n int) (string, io.ReadCloser, error) { + if rc == nil { + return "", rc, errors.New("empty stream") + } + if max > 0 && max < n { + n = max + } + r := bufio.NewReaderSize(rc, n) + rc2 := struct { + io.Reader + io.Closer + }{ + Reader: r, + Closer: rc, + } + snip, err := r.Peek(n) + if err == io.EOF { + err = nil + } + if err != nil { + return string(snip), rc2, err + } + if encoding == "gzip" { + // unpack the snippet + gzr, err2 := gzip.NewReader(bytes.NewReader(snip)) + if err2 != nil { + // snip wasn't gzip; return it as is + return string(snip), rc2, nil + } + defer gzr.Close() + snip, err = ioutil.ReadAll(gzr) + } + return string(snip), rc2, err +} diff --git a/contrib/elastic/go-elasticsearch.v6/elastictrace_test.go b/contrib/elastic/go-elasticsearch.v6/elastictrace_test.go new file mode 100644 index 0000000000..1feb0b478d --- /dev/null +++ b/contrib/elastic/go-elasticsearch.v6/elastictrace_test.go @@ -0,0 +1,167 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package elastic + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/assert" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" +) + +const debug = false + +const ( + elasticV6URL = "http://127.0.0.1:9202" + elasticV7URL = "http://127.0.0.1:9203" +) + +func TestMain(m *testing.M) { + _, ok := os.LookupEnv("INTEGRATION") + if !ok { + fmt.Println("--- SKIP: to enable integration test, set the INTEGRATION environment variable") + os.Exit(0) + } + os.Exit(m.Run()) +} + +func checkPUTTrace(assert *assert.Assertions, mt mocktracer.Tracer) { + span := mt.FinishedSpans()[1] + assert.Equal("my-es-service", span.Tag(ext.ServiceName)) + assert.Equal("PUT /twitter/tweet/?", span.Tag(ext.ResourceName)) + assert.Equal("/twitter/tweet/1", span.Tag("elasticsearch.url")) + assert.Equal("PUT", span.Tag("elasticsearch.method")) + assert.Equal(`{"user": "test", "message": "hello"}`, span.Tag("elasticsearch.body")) +} + +func checkGETTrace(assert *assert.Assertions, mt mocktracer.Tracer) { + span := mt.FinishedSpans()[0] + assert.Equal("my-es-service", span.Tag(ext.ServiceName)) + assert.Equal("GET /twitter/tweet/?", span.Tag(ext.ResourceName)) + assert.Equal("/twitter/tweet/1", span.Tag("elasticsearch.url")) + assert.Equal("GET", span.Tag("elasticsearch.method")) +} + +func checkErrTrace(assert *assert.Assertions, mt mocktracer.Tracer) { + span := mt.FinishedSpans()[0] + assert.Equal("my-es-service", span.Tag(ext.ServiceName)) + assert.Equal("GET /not-real-index/_doc/?", span.Tag(ext.ResourceName)) + assert.Equal("/not-real-index/_doc/1", span.Tag("elasticsearch.url")) + assert.NotEmpty(span.Tag(ext.Error)) + assert.Equal("*errors.errorString", fmt.Sprintf("%T", span.Tag(ext.Error).(error))) +} + +func TestQuantize(t *testing.T) { + for _, tc := range []struct { + url, method string + expected string + }{ + { + url: "/twitter/tweets", + method: "POST", + expected: "POST /twitter/tweets", + }, + { + url: "/logs_2016_05/event/_search", + method: "GET", + expected: "GET /logs_?_?/event/_search", + }, + { + url: "/twitter/tweets/123", + method: "GET", + expected: "GET /twitter/tweets/?", + }, + { + url: "/logs_2016_05/event/123", + method: "PUT", + expected: "PUT /logs_?_?/event/?", + }, + } { + assert.Equal(t, tc.expected, quantize(tc.url, tc.method)) + } +} + +func TestPeek(t *testing.T) { + assert := assert.New(t) + + for _, tt := range [...]struct { + max int // content length + txt string // stream + n int // bytes to peek at + snip string // expected snippet + err error // expected error + }{ + 0: { + // extract 3 bytes from a content of length 7 + txt: "ABCDEFG", + max: 7, + n: 3, + snip: "ABC", + }, + 1: { + // extract 7 bytes from a content of length 7 + txt: "ABCDEFG", + max: 7, + n: 7, + snip: "ABCDEFG", + }, + 2: { + // extract 100 bytes from a content of length 9 (impossible scenario) + txt: "ABCDEFG", + max: 9, + n: 100, + snip: "ABCDEFG", + }, + 3: { + // extract 5 bytes from a content of length 2 (impossible scenario) + txt: "ABCDEFG", + max: 2, + n: 5, + snip: "AB", + }, + 4: { + txt: "ABCDEFG", + max: 0, + n: 1, + snip: "A", + }, + 5: { + n: 4, + max: 4, + err: errors.New("empty stream"), + }, + 6: { + txt: "ABCDEFG", + n: 4, + max: -1, + snip: "ABCD", + }, + } { + var readcloser io.ReadCloser + if tt.txt != "" { + readcloser = ioutil.NopCloser(bytes.NewBufferString(tt.txt)) + } + snip, rc, err := peek(readcloser, "", tt.max, tt.n) + assert.Equal(tt.err, err) + assert.Equal(tt.snip, snip) + + if readcloser != nil { + // if a non-nil io.ReadCloser was sent, the returned io.ReadCloser + // must always return the entire original content. + all, err := ioutil.ReadAll(rc) + assert.Nil(err) + assert.Equal(tt.txt, string(all)) + } + } +} diff --git a/contrib/elastic/go-elasticsearch.v6/elastictrace_v6_test.go b/contrib/elastic/go-elasticsearch.v6/elastictrace_v6_test.go new file mode 100644 index 0000000000..13837aaf5d --- /dev/null +++ b/contrib/elastic/go-elasticsearch.v6/elastictrace_v6_test.go @@ -0,0 +1,244 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package elastic + +import ( + "context" + "fmt" + "strings" + "testing" + + elasticsearch6 "github.com/elastic/go-elasticsearch/v6" + esapi6 "github.com/elastic/go-elasticsearch/v6/esapi" + "github.com/stretchr/testify/assert" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" +) + +func TestClientV6(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + + cfg := elasticsearch6.Config{ + Transport: NewRoundTripper(WithServiceName("my-es-service")), + Addresses: []string{ + elasticV6URL, + }, + } + client, err := elasticsearch6.NewClient(cfg) + assert.NoError(err) + + _, err = esapi6.IndexRequest{ + Index: "twitter", + DocumentID: "1", + DocumentType: "tweet", + Body: strings.NewReader(`{"user": "test", "message": "hello"}`), + }.Do(context.Background(), client) + assert.NoError(err) + + mt.Reset() + _, err = esapi6.GetRequest{ + Index: "twitter", + DocumentID: "1", + DocumentType: "tweet", + }.Do(context.Background(), client) + assert.NoError(err) + checkGETTrace(assert, mt) + + mt.Reset() + _, err = esapi6.GetRequest{ + Index: "not-real-index", + DocumentID: "1", + }.Do(context.Background(), client) + assert.NoError(err) + checkErrTrace(assert, mt) + +} + +func TestClientErrorCutoffV6(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + oldCutoff := bodyCutoff + defer func() { + bodyCutoff = oldCutoff + }() + bodyCutoff = 10 + + cfg := elasticsearch6.Config{ + Transport: NewRoundTripper(WithServiceName("my-es-service")), + Addresses: []string{ + elasticV6URL, + }, + } + client, err := elasticsearch6.NewClient(cfg) + assert.NoError(err) + + _, err = esapi6.GetRequest{ + Index: "not-real-index", + DocumentID: "1", + }.Do(context.Background(), client) + assert.NoError(err) + + span := mt.FinishedSpans()[0] + assert.Equal(`{"error":{`, span.Tag(ext.Error).(error).Error()) +} + +func TestClientV6Failure(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + + cfg := elasticsearch6.Config{ + Transport: NewRoundTripper(WithServiceName("my-es-service")), + Addresses: []string{ + "http://127.0.0.1:9207", // inexistent service, it must fail + }, + } + client, err := elasticsearch6.NewClient(cfg) + assert.NoError(err) + + _, err = esapi6.IndexRequest{ + Index: "twitter", + DocumentID: "1", + DocumentType: "tweet", + Body: strings.NewReader(`{"user": "test", "message": "hello"}`), + }.Do(context.Background(), client) + assert.Error(err) + + spans := mt.FinishedSpans() + checkPUTTrace(assert, mt) + + assert.NotEmpty(spans[0].Tag(ext.Error)) + assert.Equal("*net.OpError", fmt.Sprintf("%T", spans[0].Tag(ext.Error).(error))) +} + +func TestResourceNamerSettingsV6(t *testing.T) { + staticName := "static resource name" + staticNamer := func(url, method string) string { + return staticName + } + + t.Run("default", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + cfg := elasticsearch6.Config{ + Transport: NewRoundTripper(), + Addresses: []string{ + elasticV6URL, + }, + } + client, err := elasticsearch6.NewClient(cfg) + assert.NoError(t, err) + + _, err = esapi6.GetRequest{ + Index: "logs_2017_05/event/_search", + DocumentID: "1", + DocumentType: "tweet", + }.Do(context.Background(), client) + + span := mt.FinishedSpans()[0] + assert.Equal(t, "GET /logs_?_?/event/_search/tweet/?", span.Tag(ext.ResourceName)) + }) + + t.Run("custom", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + cfg := elasticsearch6.Config{ + Transport: NewRoundTripper(WithResourceNamer(staticNamer)), + Addresses: []string{ + elasticV6URL, + }, + } + client, err := elasticsearch6.NewClient(cfg) + assert.NoError(t, err) + + _, err = esapi6.GetRequest{ + Index: "logs_2017_05/event/_search", + DocumentID: "1", + DocumentType: "tweet", + }.Do(context.Background(), client) + + span := mt.FinishedSpans()[0] + assert.Equal(t, staticName, span.Tag(ext.ResourceName)) + }) +} + +func TestAnalyticsSettingsV6(t *testing.T) { + assertRate := func(t *testing.T, mt mocktracer.Tracer, rate interface{}, opts ...ClientOption) { + + cfg := elasticsearch6.Config{ + Transport: NewRoundTripper(opts...), + Addresses: []string{ + elasticV6URL, + }, + } + client, err := elasticsearch6.NewClient(cfg) + assert.NoError(t, err) + + _, err = esapi6.IndexRequest{ + Index: "twitter", + DocumentID: "1", + DocumentType: "tweet", + Body: strings.NewReader(`{"user": "test", "message": "hello"}`), + }.Do(context.Background(), client) + assert.NoError(t, err) + + spans := mt.FinishedSpans() + assert.Len(t, spans, 1) + s := spans[0] + assert.Equal(t, rate, s.Tag(ext.EventSampleRate)) + } + + t.Run("defaults", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + assertRate(t, mt, nil) + }) + + t.Run("global", func(t *testing.T) { + t.Skip("global flag disabled") + mt := mocktracer.Start() + defer mt.Stop() + + rate := globalconfig.AnalyticsRate() + defer globalconfig.SetAnalyticsRate(rate) + globalconfig.SetAnalyticsRate(0.4) + + assertRate(t, mt, 0.4) + }) + + t.Run("enabled", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + assertRate(t, mt, 1.0, WithAnalytics(true)) + }) + + t.Run("disabled", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + assertRate(t, mt, nil, WithAnalytics(false)) + }) + + t.Run("override", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + rate := globalconfig.AnalyticsRate() + defer globalconfig.SetAnalyticsRate(rate) + globalconfig.SetAnalyticsRate(0.4) + + assertRate(t, mt, 0.23, WithAnalyticsRate(0.23)) + }) +} diff --git a/contrib/elastic/go-elasticsearch.v6/elastictrace_v7_test.go b/contrib/elastic/go-elasticsearch.v6/elastictrace_v7_test.go new file mode 100644 index 0000000000..02c2af40b1 --- /dev/null +++ b/contrib/elastic/go-elasticsearch.v6/elastictrace_v7_test.go @@ -0,0 +1,242 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package elastic + +import ( + "context" + "fmt" + "strings" + "testing" + + elasticsearch7 "github.com/elastic/go-elasticsearch/v7" + esapi7 "github.com/elastic/go-elasticsearch/v7/esapi" + "github.com/stretchr/testify/assert" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" +) + +func TestClientV7(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + + cfg := elasticsearch7.Config{ + Transport: NewRoundTripper(WithServiceName("my-es-service")), + Addresses: []string{ + elasticV7URL, + }, + } + client, err := elasticsearch7.NewClient(cfg) + assert.NoError(err) + + _, err = esapi7.IndexRequest{ + Index: "twitter", + DocumentID: "1", + DocumentType: "tweet", + Body: strings.NewReader(`{"user": "test", "message": "hello"}`), + }.Do(context.Background(), client) + assert.NoError(err) + + mt.Reset() + _, err = esapi7.GetRequest{ + Index: "twitter", + DocumentID: "1", + DocumentType: "tweet", + }.Do(context.Background(), client) + assert.NoError(err) + checkGETTrace(assert, mt) + + mt.Reset() + _, err = esapi7.GetRequest{ + Index: "not-real-index", + DocumentID: "1", + }.Do(context.Background(), client) + assert.NoError(err) + checkErrTrace(assert, mt) + +} + +func TestClientErrorCutoffV7(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + oldCutoff := bodyCutoff + defer func() { + bodyCutoff = oldCutoff + }() + bodyCutoff = 10 + + cfg := elasticsearch7.Config{ + Transport: NewRoundTripper(WithServiceName("my-es-service")), + Addresses: []string{ + elasticV7URL, + }, + } + client, err := elasticsearch7.NewClient(cfg) + assert.NoError(err) + + _, err = esapi7.GetRequest{ + Index: "not-real-index", + DocumentID: "1", + }.Do(context.Background(), client) + assert.NoError(err) + + span := mt.FinishedSpans()[1] + assert.Equal(`{"error":{`, span.Tag(ext.Error).(error).Error()) +} + +func TestClientV7Failure(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + + cfg := elasticsearch7.Config{ + Transport: NewRoundTripper(WithServiceName("my-es-service")), + Addresses: []string{ + "http://127.0.0.1:9207", // inexistent service, it must fail + }, + } + client, err := elasticsearch7.NewClient(cfg) + assert.NoError(err) + + _, err = esapi7.IndexRequest{ + Index: "twitter", + DocumentID: "1", + DocumentType: "tweet", + Body: strings.NewReader(`{"user": "test", "message": "hello"}`), + }.Do(context.Background(), client) + assert.Error(err) + + spans := mt.FinishedSpans() + assert.NotEmpty(spans[0].Tag(ext.Error)) + assert.Equal("*net.OpError", fmt.Sprintf("%T", spans[0].Tag(ext.Error).(error))) +} + +func TestResourceNamerSettingsV7(t *testing.T) { + staticName := "static resource name" + staticNamer := func(url, method string) string { + return staticName + } + + t.Run("default", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + cfg := elasticsearch7.Config{ + Transport: NewRoundTripper(), + Addresses: []string{ + elasticV7URL, + }, + } + client, err := elasticsearch7.NewClient(cfg) + assert.NoError(t, err) + + _, err = esapi7.GetRequest{ + Index: "logs_2017_05/event/_search", + DocumentID: "1", + DocumentType: "tweet", + }.Do(context.Background(), client) + + span := mt.FinishedSpans()[1] + assert.Equal(t, "GET /logs_?_?/event/_search/tweet/?", span.Tag(ext.ResourceName)) + }) + + t.Run("custom", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + cfg := elasticsearch7.Config{ + Transport: NewRoundTripper(WithResourceNamer(staticNamer)), + Addresses: []string{ + elasticV7URL, + }, + } + client, err := elasticsearch7.NewClient(cfg) + assert.NoError(t, err) + + _, err = esapi7.GetRequest{ + Index: "logs_2017_05/event/_search", + DocumentID: "1", + DocumentType: "tweet", + }.Do(context.Background(), client) + + span := mt.FinishedSpans()[0] + assert.Equal(t, staticName, span.Tag(ext.ResourceName)) + }) +} + +func TestAnalyticsSettingsV7(t *testing.T) { + assertRate := func(t *testing.T, mt mocktracer.Tracer, rate interface{}, opts ...ClientOption) { + + cfg := elasticsearch7.Config{ + Transport: NewRoundTripper(opts...), + Addresses: []string{ + elasticV7URL, + }, + } + client, err := elasticsearch7.NewClient(cfg) + assert.NoError(t, err) + + _, err = esapi7.IndexRequest{ + Index: "twitter", + DocumentID: "1", + DocumentType: "tweet", + Body: strings.NewReader(`{"user": "test", "message": "hello"}`), + }.Do(context.Background(), client) + assert.NoError(t, err) + + spans := mt.FinishedSpans() + assert.Len(t, spans, 2) + s := spans[1] + assert.Equal(t, rate, s.Tag(ext.EventSampleRate)) + } + + t.Run("defaults", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + assertRate(t, mt, nil) + }) + + t.Run("global", func(t *testing.T) { + t.Skip("global flag disabled") + mt := mocktracer.Start() + defer mt.Stop() + + rate := globalconfig.AnalyticsRate() + defer globalconfig.SetAnalyticsRate(rate) + globalconfig.SetAnalyticsRate(0.4) + + assertRate(t, mt, 0.4) + }) + + t.Run("enabled", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + assertRate(t, mt, 1.0, WithAnalytics(true)) + }) + + t.Run("disabled", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + assertRate(t, mt, nil, WithAnalytics(false)) + }) + + t.Run("override", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + rate := globalconfig.AnalyticsRate() + defer globalconfig.SetAnalyticsRate(rate) + globalconfig.SetAnalyticsRate(0.4) + + assertRate(t, mt, 0.23, WithAnalyticsRate(0.23)) + }) +} diff --git a/contrib/elastic/go-elasticsearch.v6/example_test.go b/contrib/elastic/go-elasticsearch.v6/example_test.go new file mode 100644 index 0000000000..1922c8b1cc --- /dev/null +++ b/contrib/elastic/go-elasticsearch.v6/example_test.go @@ -0,0 +1,60 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. +package elastic_test + +import ( + "context" + "log" + "strings" + + elasticsearch "github.com/elastic/go-elasticsearch/v7" + "github.com/elastic/go-elasticsearch/v7/esapi" + + elastictrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/elastic/go-elasticsearch.v6" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" +) + +func Example_v7() { + cfg := elasticsearch.Config{ + Transport: elastictrace.NewRoundTripper(elastictrace.WithServiceName("my-es-service")), + Addresses: []string{ + "http://127.0.0.1:9200", + }, + } + es, err := elasticsearch.NewClient(cfg) + if err != nil { + log.Fatalf("Error creating the client: %s", err) + } + + _, err = esapi.IndexRequest{ + Index: "twitter", + DocumentID: "1", + DocumentType: "tweet", + Body: strings.NewReader(`{"user": "test", "message": "hello"}`), + }.Do(context.Background(), es) + + if err != nil { + log.Fatalf("Error creating index: %s", err) + } + + // Use a context to pass information down the call chain + root, ctx := tracer.StartSpanFromContext(context.Background(), "parent.request", + tracer.ServiceName("web"), + tracer.ResourceName("/tweet/1"), + ) + + _, err = esapi.GetRequest{ + Index: "twitter", + DocumentID: "1", + DocumentType: "tweet", + }.Do(ctx, es) + + if err != nil { + log.Fatalf("Error getting index: %s", err) + } + + root.Finish() + +} diff --git a/contrib/elastic/go-elasticsearch.v6/option.go b/contrib/elastic/go-elasticsearch.v6/option.go new file mode 100644 index 0000000000..ab9fbfe964 --- /dev/null +++ b/contrib/elastic/go-elasticsearch.v6/option.go @@ -0,0 +1,81 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package elastic + +import ( + "math" + "net/http" + + "gopkg.in/DataDog/dd-trace-go.v1/internal" +) + +type clientConfig struct { + serviceName string + transport http.RoundTripper + analyticsRate float64 + resourceNamer func(url, method string) string +} + +// ClientOption represents an option that can be used when creating a client. +type ClientOption func(*clientConfig) + +func defaults(cfg *clientConfig) { + cfg.serviceName = "elastic.client" + cfg.transport = http.DefaultTransport + cfg.resourceNamer = quantize + if internal.BoolEnv("DD_TRACE_ELASTIC_ANALYTICS_ENABLED", false) { + cfg.analyticsRate = 1.0 + } else { + cfg.analyticsRate = math.NaN() + } +} + +// WithTransport sets the given transport as an http.Transport for the client. +func WithTransport(t http.RoundTripper) ClientOption { + return func(cfg *clientConfig) { + cfg.transport = t + } +} + +// WithServiceName sets the given service name for the client. +func WithServiceName(name string) ClientOption { + return func(cfg *clientConfig) { + cfg.serviceName = name + } +} + +// WithAnalytics enables Trace Analytics for all started spans. +func WithAnalytics(on bool) ClientOption { + return func(cfg *clientConfig) { + if on { + cfg.analyticsRate = 1.0 + } else { + cfg.analyticsRate = math.NaN() + } + } +} + +// WithAnalyticsRate sets the sampling rate for Trace Analytics events +// correlated to started spans. +func WithAnalyticsRate(rate float64) ClientOption { + return func(cfg *clientConfig) { + if rate >= 0.0 && rate <= 1.0 { + cfg.analyticsRate = rate + } else { + cfg.analyticsRate = math.NaN() + } + } +} + +// WithResourceNamer specifies a quantizing function which will be used to obtain a resource name for a given +// ElasticSearch request, using the request's URL and method. Note that the default quantizer obfuscates +// IDs and indexes and by replacing it, sensitive data could possibly be exposed, unless the new quantizer +// specifically takes care of that. +func WithResourceNamer(namer func(url, method string) string) ClientOption { + return func(cfg *clientConfig) { + cfg.resourceNamer = namer + } +} diff --git a/contrib/gin-gonic/gin/gintrace.go b/contrib/gin-gonic/gin/gintrace.go index 8f36026ab8..e1342965b1 100644 --- a/contrib/gin-gonic/gin/gintrace.go +++ b/contrib/gin-gonic/gin/gintrace.go @@ -29,6 +29,9 @@ func Middleware(service string, opts ...Option) gin.HandlerFunc { } log.Debug("contrib/gin-gonic/gin: Configuring Middleware: Service: %s, %#v", service, cfg) return func(c *gin.Context) { + if cfg.ignoreRequest(c) { + return + } resource := cfg.resourceNamer(c) opts := []ddtrace.StartSpanOption{ tracer.ServiceName(cfg.serviceName), diff --git a/contrib/gin-gonic/gin/gintrace_test.go b/contrib/gin-gonic/gin/gintrace_test.go index ce8baacfcf..1009caa061 100644 --- a/contrib/gin-gonic/gin/gintrace_test.go +++ b/contrib/gin-gonic/gin/gintrace_test.go @@ -10,6 +10,7 @@ import ( "fmt" "html/template" "net/http/httptest" + "strings" "testing" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" @@ -340,6 +341,34 @@ func TestResourceNamerSettings(t *testing.T) { }) } +func TestIgnoreRequestSettings(t *testing.T) { + router := gin.New() + router.Use(Middleware("foobar", WithIgnoreRequest(func(c *gin.Context) bool { + return strings.HasPrefix(c.Request.URL.Path, "/skip") + }))) + + router.GET("/OK", func(c *gin.Context) { + c.Writer.Write([]byte("OK")) + }) + + router.GET("/skip", func(c *gin.Context) { + c.Writer.Write([]byte("Skip")) + }) + + for path, shouldSkip := range map[string]bool{ + "/OK": false, + "/skip": true, + "/skipfoo": true, + } { + mt := mocktracer.Start() + defer mt.Reset() + + r := httptest.NewRequest("GET", "http://localhost"+path, nil) + router.ServeHTTP(httptest.NewRecorder(), r) + assert.Equal(t, shouldSkip, len(mt.FinishedSpans()) == 0) + } +} + func TestServiceName(t *testing.T) { t.Run("default", func(t *testing.T) { assert := assert.New(t) diff --git a/contrib/gin-gonic/gin/option.go b/contrib/gin-gonic/gin/option.go index 7271f8e2dd..351bdfd61b 100644 --- a/contrib/gin-gonic/gin/option.go +++ b/contrib/gin-gonic/gin/option.go @@ -19,6 +19,7 @@ type config struct { analyticsRate float64 resourceNamer func(c *gin.Context) string serviceName string + ignoreRequest func(c *gin.Context) bool } func newConfig(service string) *config { @@ -36,6 +37,7 @@ func newConfig(service string) *config { analyticsRate: rate, resourceNamer: defaultResourceNamer, serviceName: service, + ignoreRequest: func(_ *gin.Context) bool { return false }, } } @@ -73,6 +75,14 @@ func WithResourceNamer(namer func(c *gin.Context) string) Option { } } +// WithIgnoreRequest specifies a function to use for determining if the +// incoming HTTP request tracing should be skipped. +func WithIgnoreRequest(f func(c *gin.Context) bool) Option { + return func(cfg *config) { + cfg.ignoreRequest = f + } +} + func defaultResourceNamer(c *gin.Context) string { // getName is a hacky way to check whether *gin.Context implements the FullPath() // method introduced in v1.4.0, falling back to the previous implementation otherwise. diff --git a/contrib/go-chi/chi.v5/chi.go b/contrib/go-chi/chi.v5/chi.go index 9a5cae94d2..477f8e9636 100644 --- a/contrib/go-chi/chi.v5/chi.go +++ b/contrib/go-chi/chi.v5/chi.go @@ -31,6 +31,10 @@ func Middleware(opts ...Option) func(next http.Handler) http.Handler { log.Debug("contrib/go-chi/chi.v5: Configuring Middleware: %#v", cfg) return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if cfg.ignoreRequest(r) { + next.ServeHTTP(w, r) + return + } opts := []ddtrace.StartSpanOption{ tracer.SpanType(ext.SpanTypeWeb), tracer.ServiceName(cfg.serviceName), diff --git a/contrib/go-chi/chi.v5/chi_test.go b/contrib/go-chi/chi.v5/chi_test.go index 1c90f06066..3cd1c3294e 100644 --- a/contrib/go-chi/chi.v5/chi_test.go +++ b/contrib/go-chi/chi.v5/chi_test.go @@ -10,6 +10,7 @@ import ( "net/http" "net/http/httptest" "strconv" + "strings" "testing" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" @@ -270,3 +271,33 @@ func TestAnalyticsSettings(t *testing.T) { assertRate(t, mt, 0.23, WithAnalyticsRate(0.23)) }) } + +func TestIgnoreRequest(t *testing.T) { + router := chi.NewRouter() + router.Use(Middleware( + WithIgnoreRequest(func(r *http.Request) bool { + return strings.HasPrefix(r.URL.Path, "/skip") + }), + )) + + router.Get("/ok", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("ok")) + }) + + router.Get("/skip", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("skip")) + }) + + for path, shouldSkip := range map[string]bool{ + "/ok": false, + "/skip": true, + "/skipfoo": true, + } { + mt := mocktracer.Start() + defer mt.Reset() + + r := httptest.NewRequest("GET", "http://localhost"+path, nil) + router.ServeHTTP(httptest.NewRecorder(), r) + assert.Equal(t, shouldSkip, len(mt.FinishedSpans()) == 0) + } +} diff --git a/contrib/go-chi/chi.v5/option.go b/contrib/go-chi/chi.v5/option.go index f9a6b2aaa4..6c19dd3cc7 100644 --- a/contrib/go-chi/chi.v5/option.go +++ b/contrib/go-chi/chi.v5/option.go @@ -7,6 +7,7 @@ package chi import ( "math" + "net/http" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/internal" @@ -18,6 +19,7 @@ type config struct { spanOpts []ddtrace.StartSpanOption // additional span options to be applied analyticsRate float64 isStatusError func(statusCode int) bool + ignoreRequest func(r *http.Request) bool } // Option represents an option that can be passed to NewRouter. @@ -34,6 +36,7 @@ func defaults(cfg *config) { cfg.analyticsRate = globalconfig.AnalyticsRate() } cfg.isStatusError = isServerError + cfg.ignoreRequest = func(_ *http.Request) bool { return false } } // WithServiceName sets the given service name for the router. @@ -85,3 +88,11 @@ func WithStatusCheck(fn func(statusCode int) bool) Option { func isServerError(statusCode int) bool { return statusCode >= 500 && statusCode < 600 } + +// WithIgnoreRequest specifies a function to use for determining if the +// incoming HTTP request tracing should be skipped. +func WithIgnoreRequest(fn func(r *http.Request) bool) Option { + return func(cfg *config) { + cfg.ignoreRequest = fn + } +} diff --git a/contrib/go-chi/chi/chi.go b/contrib/go-chi/chi/chi.go index e4a90a961e..c5866dd146 100644 --- a/contrib/go-chi/chi/chi.go +++ b/contrib/go-chi/chi/chi.go @@ -31,6 +31,10 @@ func Middleware(opts ...Option) func(next http.Handler) http.Handler { log.Debug("contrib/go-chi/chi: Configuring Middleware: %#v", cfg) return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if cfg.ignoreRequest(r) { + next.ServeHTTP(w, r) + return + } opts := []ddtrace.StartSpanOption{ tracer.SpanType(ext.SpanTypeWeb), tracer.ServiceName(cfg.serviceName), diff --git a/contrib/go-chi/chi/chi_test.go b/contrib/go-chi/chi/chi_test.go index c3972745e3..2fcf68977e 100644 --- a/contrib/go-chi/chi/chi_test.go +++ b/contrib/go-chi/chi/chi_test.go @@ -10,6 +10,7 @@ import ( "net/http" "net/http/httptest" "strconv" + "strings" "testing" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" @@ -270,3 +271,33 @@ func TestAnalyticsSettings(t *testing.T) { assertRate(t, mt, 0.23, WithAnalyticsRate(0.23)) }) } + +func TestIgnoreRequest(t *testing.T) { + router := chi.NewRouter() + router.Use(Middleware( + WithIgnoreRequest(func(r *http.Request) bool { + return strings.HasPrefix(r.URL.Path, "/skip") + }), + )) + + router.Get("/ok", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("ok")) + }) + + router.Get("/skip", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("skip")) + }) + + for path, shouldSkip := range map[string]bool{ + "/ok": false, + "/skip": true, + "/skipfoo": true, + } { + mt := mocktracer.Start() + defer mt.Reset() + + r := httptest.NewRequest("GET", "http://localhost"+path, nil) + router.ServeHTTP(httptest.NewRecorder(), r) + assert.Equal(t, shouldSkip, len(mt.FinishedSpans()) == 0) + } +} diff --git a/contrib/go-chi/chi/option.go b/contrib/go-chi/chi/option.go index f9a6b2aaa4..6c19dd3cc7 100644 --- a/contrib/go-chi/chi/option.go +++ b/contrib/go-chi/chi/option.go @@ -7,6 +7,7 @@ package chi import ( "math" + "net/http" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/internal" @@ -18,6 +19,7 @@ type config struct { spanOpts []ddtrace.StartSpanOption // additional span options to be applied analyticsRate float64 isStatusError func(statusCode int) bool + ignoreRequest func(r *http.Request) bool } // Option represents an option that can be passed to NewRouter. @@ -34,6 +36,7 @@ func defaults(cfg *config) { cfg.analyticsRate = globalconfig.AnalyticsRate() } cfg.isStatusError = isServerError + cfg.ignoreRequest = func(_ *http.Request) bool { return false } } // WithServiceName sets the given service name for the router. @@ -85,3 +88,11 @@ func WithStatusCheck(fn func(statusCode int) bool) Option { func isServerError(statusCode int) bool { return statusCode >= 500 && statusCode < 600 } + +// WithIgnoreRequest specifies a function to use for determining if the +// incoming HTTP request tracing should be skipped. +func WithIgnoreRequest(fn func(r *http.Request) bool) Option { + return func(cfg *config) { + cfg.ignoreRequest = fn + } +} diff --git a/contrib/go-redis/redis.v8/option.go b/contrib/go-redis/redis.v8/option.go index 86f90d8b59..400eb9b241 100644 --- a/contrib/go-redis/redis.v8/option.go +++ b/contrib/go-redis/redis.v8/option.go @@ -14,6 +14,7 @@ import ( type clientConfig struct { serviceName string analyticsRate float64 + skipRaw bool } // ClientOption represents an option that can be used to create or wrap a client. @@ -29,6 +30,15 @@ func defaults(cfg *clientConfig) { } } +// WithSkipRawCommand reports whether to skip setting the "redis.raw_command" tag +// on instrumenation spans. This may be useful if the Datadog Agent is not +// set up to obfuscate this value and it could contain sensitive information. +func WithSkipRawCommand(skip bool) ClientOption { + return func(cfg *clientConfig) { + cfg.skipRaw = skip + } +} + // WithServiceName sets the given service name for the client. func WithServiceName(name string) ClientOption { return func(cfg *clientConfig) { diff --git a/contrib/go-redis/redis.v8/redis.go b/contrib/go-redis/redis.v8/redis.go index afa9728e20..e03f5ad799 100644 --- a/contrib/go-redis/redis.v8/redis.go +++ b/contrib/go-redis/redis.v8/redis.go @@ -102,14 +102,16 @@ func (ddh *datadogHook) BeforeProcess(ctx context.Context, cmd redis.Cmder) (con raw := cmd.String() length := strings.Count(raw, " ") p := ddh.params - opts := make([]ddtrace.StartSpanOption, 0, 5+len(ddh.additionalTags)+1) // 5 options below + for additionalTags + for analyticsRate + opts := make([]ddtrace.StartSpanOption, 0, 4+1+len(ddh.additionalTags)+1) // 4 options below + redis.raw_command + ddh.additionalTags + analyticsRate opts = append(opts, tracer.SpanType(ext.SpanTypeRedis), tracer.ServiceName(p.config.serviceName), tracer.ResourceName(raw[:strings.IndexByte(raw, ' ')]), - tracer.Tag("redis.raw_command", raw), tracer.Tag("redis.args_length", strconv.Itoa(length)), ) + if !p.config.skipRaw { + opts = append(opts, tracer.Tag("redis.raw_command", raw)) + } opts = append(opts, ddh.additionalTags...) if !math.IsNaN(p.config.analyticsRate) { opts = append(opts, tracer.Tag(ext.EventSampleRate, p.config.analyticsRate)) @@ -134,16 +136,17 @@ func (ddh *datadogHook) BeforeProcessPipeline(ctx context.Context, cmds []redis. raw := commandsToString(cmds) length := strings.Count(raw, " ") p := ddh.params - opts := make([]ddtrace.StartSpanOption, 0, 7+len(ddh.additionalTags)+1) // 7 options below + for additionalTags + for analyticsRate + opts := make([]ddtrace.StartSpanOption, 0, 5+1+len(ddh.additionalTags)+1) // 5 options below + redis.raw_command + ddh.additionalTags + analyticsRate opts = append(opts, tracer.SpanType(ext.SpanTypeRedis), tracer.ServiceName(p.config.serviceName), tracer.ResourceName(raw[:strings.IndexByte(raw, ' ')]), - tracer.Tag("redis.raw_command", raw), tracer.Tag("redis.args_length", strconv.Itoa(length)), - tracer.Tag(ext.ResourceName, raw), tracer.Tag("redis.pipeline_length", strconv.Itoa(len(cmds))), ) + if !p.config.skipRaw { + opts = append(opts, tracer.Tag("redis.raw_command", raw)) + } opts = append(opts, ddh.additionalTags...) if !math.IsNaN(p.config.analyticsRate) { opts = append(opts, tracer.Tag(ext.EventSampleRate, p.config.analyticsRate)) diff --git a/contrib/go-redis/redis.v8/redis_test.go b/contrib/go-redis/redis.v8/redis_test.go index bd68d4bdf0..270a30334f 100644 --- a/contrib/go-redis/redis.v8/redis_test.go +++ b/contrib/go-redis/redis.v8/redis_test.go @@ -36,6 +36,51 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } +func TestSkipRaw(t *testing.T) { + runCmds := func(t *testing.T, opts ...ClientOption) []mocktracer.Span { + mt := mocktracer.Start() + defer mt.Stop() + ctx := context.Background() + client := NewClient(&redis.Options{Addr: "127.0.0.1:6379"}, opts...) + client.Set(ctx, "test_key", "test_value", 0) + pipeline := client.Pipeline() + pipeline.Expire(ctx, "pipeline_counter", time.Hour) + pipeline.Exec(ctx) + spans := mt.FinishedSpans() + assert.Len(t, spans, 2) + return spans + } + + t.Run("true", func(t *testing.T) { + spans := runCmds(t, WithSkipRawCommand(true)) + for _, span := range spans { + raw, ok := span.Tags()["redis.raw_command"] + assert.False(t, ok) + assert.Empty(t, raw) + } + }) + + t.Run("default", func(t *testing.T) { + spans := runCmds(t) + raw, ok := spans[0].Tags()["redis.raw_command"] + assert.True(t, ok) + assert.Equal(t, "set test_key test_value: ", raw) + raw, ok = spans[1].Tags()["redis.raw_command"] + assert.True(t, ok) + assert.Equal(t, "expire pipeline_counter 3600: false\n", raw) + }) + + t.Run("false", func(t *testing.T) { + spans := runCmds(t, WithSkipRawCommand(false)) + raw, ok := spans[0].Tags()["redis.raw_command"] + assert.True(t, ok) + assert.Equal(t, "set test_key test_value: ", raw) + raw, ok = spans[1].Tags()["redis.raw_command"] + assert.True(t, ok) + assert.Equal(t, "expire pipeline_counter 3600: false\n", raw) + }) +} + func TestClientEvalSha(t *testing.T) { ctx := context.Background() opts := &redis.Options{Addr: "127.0.0.1:6379"} @@ -225,7 +270,7 @@ func TestPipeline(t *testing.T) { assert.Equal("redis.command", span.OperationName()) assert.Equal(ext.SpanTypeRedis, span.Tag(ext.SpanType)) assert.Equal("my-redis", span.Tag(ext.ServiceName)) - assert.Equal("expire pipeline_counter 3600: false\n", span.Tag(ext.ResourceName)) + assert.Equal("expire", span.Tag(ext.ResourceName)) assert.Equal("127.0.0.1", span.Tag(ext.TargetHost)) assert.Equal("6379", span.Tag(ext.TargetPort)) assert.Equal("1", span.Tag("redis.pipeline_length")) @@ -244,7 +289,7 @@ func TestPipeline(t *testing.T) { assert.Equal("redis.command", span.OperationName()) assert.Equal(ext.SpanTypeRedis, span.Tag(ext.SpanType)) assert.Equal("my-redis", span.Tag(ext.ServiceName)) - assert.Equal("expire pipeline_counter 3600: false\nexpire pipeline_counter_1 60: false\n", span.Tag(ext.ResourceName)) + assert.Equal("expire", span.Tag(ext.ResourceName)) assert.Equal("2", span.Tag("redis.pipeline_length")) } diff --git a/contrib/go.mongodb.org/mongo-driver/mongo/mongo_test.go b/contrib/go.mongodb.org/mongo-driver/mongo/mongo_test.go index 97c77a5e32..779e64199a 100644 --- a/contrib/go.mongodb.org/mongo-driver/mongo/mongo_test.go +++ b/contrib/go.mongodb.org/mongo-driver/mongo/mongo_test.go @@ -53,10 +53,13 @@ func Test(t *testing.T) { t.Fatal(err) } - client. + _, err = client. Database("test-database"). Collection("test-collection"). InsertOne(ctx, bson.D{{Key: "test-item", Value: "test-value"}}) + if err != nil { + t.Fatal(err) + } span.Finish() @@ -70,7 +73,7 @@ func Test(t *testing.T) { assert.Equal(t, "mongo.insert", s.Tag(ext.ResourceName)) assert.Equal(t, hostname, s.Tag(ext.PeerHostname)) assert.Equal(t, port, s.Tag(ext.PeerPort)) - assert.Contains(t, s.Tag(ext.DBStatement), `"test-item":"test-value"`) + assert.Contains(t, s.Tag("mongodb.query"), `"test-item":"test-value"`) assert.Equal(t, "test-database", s.Tag(ext.DBInstance)) assert.Equal(t, "mongo", s.Tag(ext.DBType)) } diff --git a/contrib/gocql/gocql/gocql.go b/contrib/gocql/gocql/gocql.go index 5eb4b2ae20..4115620f83 100644 --- a/contrib/gocql/gocql/gocql.go +++ b/contrib/gocql/gocql/gocql.go @@ -34,6 +34,19 @@ type Iter struct { span ddtrace.Span } +// Scanner inherits from a gocql.Scanner derived from an Iter +type Scanner struct { + gocql.Scanner + span ddtrace.Span +} + +// Batch inherits from gocql.Batch, it keeps the tracer and the context. +type Batch struct { + *gocql.Batch + *params + ctx context.Context +} + // params containes fields and metadata useful for command tracing type params struct { config *queryConfig @@ -62,7 +75,7 @@ func WrapQuery(q *gocql.Query, opts ...WrapOption) *Query { } } log.Debug("contrib/gocql/gocql: Wrapping Query: %#v", cfg) - tq := &Query{q, ¶ms{config: cfg}, context.Background()} + tq := &Query{q, ¶ms{config: cfg}, q.Context()} return tq } @@ -163,3 +176,92 @@ func (tIter *Iter) Close() error { tIter.span.Finish() return err } + +// Scanner returns a row Scanner which provides an interface to scan rows in a +// manner which is similar to database/sql. The Iter should NOT be used again after +// calling this method. +func (tIter *Iter) Scanner() gocql.Scanner { + return &Scanner{ + Scanner: tIter.Iter.Scanner(), + span: tIter.span, + } +} + +// Err calls the wrapped Scanner.Err, releasing the Scanner resources and closing the span. +func (s *Scanner) Err() error { + err := s.Scanner.Err() + if err != nil { + s.span.SetTag(ext.Error, err) + } + s.span.Finish() + return err +} + +// WrapBatch wraps a gocql.Batch into a traced Batch under the given service name. +// Note that the returned Batch structure embeds the original gocql.Batch structure. +// This means that any method returning the batch for chaining that is not part +// of this package's Batch structure should be called before WrapBatch, otherwise +// the tracing context could be lost. +// +// To be more specific: it is ok (and recommended) to use and chain the return value +// of `WithContext` and `WithTimestamp` but not that of `SerialConsistency`, `Trace`, +// `Observer`, etc. +func WrapBatch(b *gocql.Batch, opts ...WrapOption) *Batch { + cfg := new(queryConfig) + defaults(cfg) + for _, fn := range opts { + fn(cfg) + } + log.Debug("contrib/gocql/gocql: Wrapping Batch: %#v", cfg) + tb := &Batch{b, ¶ms{config: cfg}, b.Context()} + return tb +} + +// WithContext adds the specified context to the traced Batch structure. +func (tb *Batch) WithContext(ctx context.Context) *Batch { + tb.ctx = ctx + tb.Batch = tb.Batch.WithContext(ctx) + return tb +} + +// WithTimestamp will enable the with default timestamp flag on the query like +// DefaultTimestamp does. But also allows to define value for timestamp. It works the +// same way as USING TIMESTAMP in the query itself, but should not break prepared +// query optimization. +func (tb *Batch) WithTimestamp(timestamp int64) *Batch { + tb.Batch = tb.Batch.WithTimestamp(timestamp) + return tb +} + +// ExecuteBatch calls session.ExecuteBatch on the Batch, tracing the execution. +func (tb *Batch) ExecuteBatch(session *gocql.Session) error { + span := tb.newChildSpan(tb.ctx) + err := session.ExecuteBatch(tb.Batch) + tb.finishSpan(span, err) + return err +} + +// newChildSpan creates a new span from the params and the context. +func (tb *Batch) newChildSpan(ctx context.Context) ddtrace.Span { + p := tb.params + opts := []ddtrace.StartSpanOption{ + tracer.SpanType(ext.SpanTypeCassandra), + tracer.ServiceName(p.config.serviceName), + tracer.ResourceName(p.config.resourceName), + tracer.Tag(ext.CassandraConsistencyLevel, tb.Cons.String()), + tracer.Tag(ext.CassandraKeyspace, tb.Keyspace()), + } + if !math.IsNaN(p.config.analyticsRate) { + opts = append(opts, tracer.Tag(ext.EventSampleRate, p.config.analyticsRate)) + } + span, _ := tracer.StartSpanFromContext(ctx, ext.CassandraBatch, opts...) + return span +} + +func (tb *Batch) finishSpan(span ddtrace.Span, err error) { + if tb.params.config.noDebugStack { + span.Finish(tracer.WithError(err), tracer.NoDebugStack()) + } else { + span.Finish(tracer.WithError(err)) + } +} diff --git a/contrib/gocql/gocql/gocql_test.go b/contrib/gocql/gocql/gocql_test.go index 8c7ecdec2d..3d90975cbf 100644 --- a/contrib/gocql/gocql/gocql_test.go +++ b/contrib/gocql/gocql/gocql_test.go @@ -60,7 +60,7 @@ func TestMain(m *testing.M) { session.Query("CREATE TABLE if not exists trace.person (name text PRIMARY KEY, age int, description text)").Exec() session.Query("INSERT INTO trace.person (name, age, description) VALUES ('Cassandra', 100, 'A cruel mistress')").Exec() - m.Run() + os.Exit(m.Run()) } func TestErrorWrapper(t *testing.T) { @@ -103,9 +103,12 @@ func TestChildWrapperSpan(t *testing.T) { cluster := newCassandraCluster() session, err := cluster.CreateSession() assert.Nil(err) - q := session.Query("SELECT * from trace.person") + + // Call WithContext before WrapQuery to prove WrapQuery needs to use the query.Context() + // instead of context.Background() + q := session.Query("SELECT * from trace.person").WithContext(ctx) tq := WrapQuery(q, WithServiceName("TestServiceName")) - iter := tq.WithContext(ctx).Iter() + iter := tq.Iter() iter.Close() parentSpan.Finish() @@ -137,15 +140,28 @@ func TestAnalyticsSettings(t *testing.T) { cluster := newCassandraCluster() session, err := cluster.CreateSession() assert.Nil(t, err) + + // Create a query for testing Iter spans q := session.Query("CREATE KEYSPACE trace WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy', 'datacenter1' : 1 };") iter := WrapQuery(q, opts...).Iter() - err = iter.Close() + iter.Close() // this will error, we're inspecting the trace not the error + + // Create a query for testing Scanner spans + q2 := session.Query("CREATE KEYSPACE trace WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy', 'datacenter1' : 1 };") + scanner := WrapQuery(q2, opts...).Iter().Scanner() + scanner.Err() // this will error, we're inspecting the trace not the error + + // Create a batch query for testing Batch spans + b := WrapBatch(session.NewBatch(gocql.UnloggedBatch), opts...) + b.Query("CREATE KEYSPACE trace WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy', 'datacenter1' : 1 };") + b.ExecuteBatch(session) // this will error, we're inspecting the trace not the error spans := mt.FinishedSpans() - assert.Len(t, spans, 1) - s := spans[0] - if !math.IsNaN(rate) { - assert.Equal(t, rate, s.Tag(ext.EventSampleRate)) + assert.Len(t, spans, 3) + for _, s := range spans { + if !math.IsNaN(rate) { + assert.Equal(t, rate, s.Tag(ext.EventSampleRate)) + } } } @@ -193,3 +209,87 @@ func TestAnalyticsSettings(t *testing.T) { assertRate(t, mt, 0.23, WithAnalyticsRate(0.23)) }) } + +func TestIterScanner(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + + // Parent span + parentSpan, ctx := tracer.StartSpanFromContext(context.Background(), "parentSpan") + cluster := newCassandraCluster() + session, err := cluster.CreateSession() + assert.NoError(err) + + q := session.Query("SELECT * from trace.person") + tq := WrapQuery(q, WithServiceName("TestServiceName")) + iter := tq.WithContext(ctx).Iter() + sc := iter.Scanner() + for sc.Next() { + var t1, t2, t3 interface{} + sc.Scan(&t1, t2, t3) + } + sc.Err() + + parentSpan.Finish() + + spans := mt.FinishedSpans() + assert.Len(spans, 2) + + var childSpan, pSpan mocktracer.Span + if spans[0].ParentID() == spans[1].SpanID() { + childSpan = spans[0] + pSpan = spans[1] + } else { + childSpan = spans[1] + pSpan = spans[0] + } + + assert.Equal(pSpan.OperationName(), "parentSpan") + assert.Equal(childSpan.ParentID(), pSpan.SpanID()) + assert.Equal(childSpan.OperationName(), ext.CassandraQuery) + assert.Equal(childSpan.Tag(ext.ResourceName), "SELECT * from trace.person") + assert.Equal(childSpan.Tag(ext.CassandraKeyspace), "trace") +} + +func TestBatch(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + + // Parent span + parentSpan, ctx := tracer.StartSpanFromContext(context.Background(), "parentSpan") + cluster := newCassandraCluster() + cluster.Keyspace = "trace" + session, err := cluster.CreateSession() + assert.NoError(err) + + b := session.NewBatch(gocql.UnloggedBatch) + tb := WrapBatch(b, WithServiceName("TestServiceName"), WithResourceName("BatchInsert")) + + stmt := "INSERT INTO trace.person (name, age, description) VALUES (?, ?, ?)" + tb.Query(stmt, "Kate", 80, "Cassandra's sister running in kubernetes") + tb.Query(stmt, "Lucas", 60, "Another person") + err = tb.WithContext(ctx).WithTimestamp(time.Now().Unix() * 1e3).ExecuteBatch(session) + assert.NoError(err) + + parentSpan.Finish() + + spans := mt.FinishedSpans() + assert.Len(spans, 2) + + var childSpan, pSpan mocktracer.Span + if spans[0].ParentID() == spans[1].SpanID() { + childSpan = spans[0] + pSpan = spans[1] + } else { + childSpan = spans[1] + pSpan = spans[0] + } + + assert.Equal(pSpan.OperationName(), "parentSpan") + assert.Equal(childSpan.ParentID(), pSpan.SpanID()) + assert.Equal(childSpan.OperationName(), ext.CassandraBatch) + assert.Equal(childSpan.Tag(ext.ResourceName), "BatchInsert") + assert.Equal(childSpan.Tag(ext.CassandraKeyspace), "trace") +} diff --git a/contrib/google.golang.org/grpc/appsec.go b/contrib/google.golang.org/grpc/appsec.go new file mode 100644 index 0000000000..cec9c2d424 --- /dev/null +++ b/contrib/google.golang.org/grpc/appsec.go @@ -0,0 +1,78 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package grpc + +import ( + "encoding/json" + "net" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo/instrumentation/grpcsec" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo/instrumentation/httpsec" + + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/peer" +) + +// UnaryHandler wrapper to use when AppSec is enabled to monitor its execution. +func appsecUnaryHandlerMiddleware(span ddtrace.Span, handler grpc.UnaryHandler) grpc.UnaryHandler { + httpsec.SetAppSecTags(span) + return func(ctx context.Context, req interface{}) (interface{}, error) { + op := grpcsec.StartHandlerOperation(grpcsec.HandlerOperationArgs{}, nil) + defer func() { + events := op.Finish(grpcsec.HandlerOperationRes{}) + if len(events) == 0 { + return + } + setAppSecTags(ctx, span, events) + }() + defer grpcsec.StartReceiveOperation(grpcsec.ReceiveOperationArgs{}, op).Finish(grpcsec.ReceiveOperationRes{Message: req}) + return handler(ctx, req) + } +} + +// StreamHandler wrapper to use when AppSec is enabled to monitor its execution. +func appsecStreamHandlerMiddleware(span ddtrace.Span, handler grpc.StreamHandler) grpc.StreamHandler { + httpsec.SetAppSecTags(span) + return func(srv interface{}, stream grpc.ServerStream) error { + op := grpcsec.StartHandlerOperation(grpcsec.HandlerOperationArgs{}, nil) + defer func() { + events := op.Finish(grpcsec.HandlerOperationRes{}) + if len(events) == 0 { + return + } + setAppSecTags(stream.Context(), span, events) + }() + return handler(srv, appsecServerStream{ServerStream: stream, handlerOperation: op}) + } +} + +type appsecServerStream struct { + grpc.ServerStream + handlerOperation *grpcsec.HandlerOperation +} + +// RecvMsg implements grpc.ServerStream interface method to monitor its +// execution with AppSec. +func (ss appsecServerStream) RecvMsg(m interface{}) error { + op := grpcsec.StartReceiveOperation(grpcsec.ReceiveOperationArgs{}, ss.handlerOperation) + defer func() { + op.Finish(grpcsec.ReceiveOperationRes{Message: m}) + }() + return ss.ServerStream.RecvMsg(m) +} + +// Set the AppSec tags when security events were found. +func setAppSecTags(ctx context.Context, span ddtrace.Span, events []json.RawMessage) { + md, _ := metadata.FromIncomingContext(ctx) + var addr net.Addr + if p, ok := peer.FromContext(ctx); ok { + addr = p.Addr + } + grpcsec.SetSecurityEventTags(span, events, addr, md) +} diff --git a/contrib/google.golang.org/grpc/appsec_test.go b/contrib/google.golang.org/grpc/appsec_test.go new file mode 100644 index 0000000000..f424380cb4 --- /dev/null +++ b/contrib/google.golang.org/grpc/appsec_test.go @@ -0,0 +1,91 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package grpc + +import ( + "context" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" +) + +func TestAppSec(t *testing.T) { + appsec.Start() + defer appsec.Stop() + if !appsec.Enabled() { + t.Skip("appsec disabled") + } + + rig, err := newRig(false) + require.NoError(t, err) + defer rig.Close() + + client := rig.client + + t.Run("unary", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + // Send a XSS attack + res, err := client.Ping(context.Background(), &FixtureRequest{Name: ""}) + // Check that the handler was properly called + require.NoError(t, err) + require.Equal(t, "passed", res.Message) + + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + + // The request should have the XSS attack attempt event (appsec rule id crs-941-100). + event := finished[0].Tag("_dd.appsec.json") + require.NotNil(t, event) + require.True(t, strings.Contains(event.(string), "crs-941-100")) + }) + + t.Run("stream", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + stream, err := client.StreamPing(context.Background()) + require.NoError(t, err) + + // Send a XSS attack + err = stream.Send(&FixtureRequest{Name: ""}) + require.NoError(t, err) + + // Check that the handler was properly called + res, err := stream.Recv() + require.Equal(t, "passed", res.Message) + require.NoError(t, err) + + // Send a SQLi attack + err = stream.Send(&FixtureRequest{Name: "something UNION SELECT * from users"}) + require.NoError(t, err) + + // Check that the handler was properly called + res, err = stream.Recv() + require.Equal(t, "passed", res.Message) + require.NoError(t, err) + + err = stream.CloseSend() + require.NoError(t, err) + // to flush the spans + stream.Recv() + + finished := mt.FinishedSpans() + require.Len(t, finished, 6) + + // The request should both attacks: the XSS and SQLi attack attempt + // events (appsec rule id crs-941-100, crs-942-100). + event := finished[5].Tag("_dd.appsec.json") + require.NotNil(t, event) + require.True(t, strings.Contains(event.(string), "crs-941-100")) + require.True(t, strings.Contains(event.(string), "crs-942-100")) + }) +} diff --git a/contrib/google.golang.org/grpc/server.go b/contrib/google.golang.org/grpc/server.go index 11320294bc..89806f2ff5 100644 --- a/contrib/google.golang.org/grpc/server.go +++ b/contrib/google.golang.org/grpc/server.go @@ -8,11 +8,12 @@ package grpc import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "github.com/golang/protobuf/jsonpb" "github.com/golang/protobuf/proto" - context "golang.org/x/net/context" + "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) @@ -101,18 +102,19 @@ func StreamServerInterceptor(opts ...Option) grpc.StreamServerInterceptor { span.SetTag(tagMethodKind, methodKindClientStream) } defer func() { finishWithError(span, err, cfg) }() + if appsec.Enabled() { + handler = appsecStreamHandlerMiddleware(span, handler) + } } // call the original handler with a new stream, which traces each send // and recv if message tracing is enabled - err = handler(srv, &serverStream{ + return handler(srv, &serverStream{ ServerStream: ss, cfg: cfg, method: info.FullMethod, ctx: ctx, }) - - return err } } @@ -154,6 +156,9 @@ func UnaryServerInterceptor(opts ...Option) grpc.UnaryServerInterceptor { } } } + if appsec.Enabled() { + handler = appsecUnaryHandlerMiddleware(span, handler) + } resp, err := handler(ctx, req) finishWithError(span, err, cfg) return resp, err diff --git a/contrib/google.golang.org/grpc/stats_server_test.go b/contrib/google.golang.org/grpc/stats_server_test.go index 389fd1b312..a35efb1c0e 100644 --- a/contrib/google.golang.org/grpc/stats_server_test.go +++ b/contrib/google.golang.org/grpc/stats_server_test.go @@ -11,7 +11,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - context "golang.org/x/net/context" + "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/stats" diff --git a/contrib/internal/httputil/make_responsewriter.go b/contrib/internal/httputil/make_responsewriter.go index 6c1fb1f4aa..cc4e85c04d 100644 --- a/contrib/internal/httputil/make_responsewriter.go +++ b/contrib/internal/httputil/make_responsewriter.go @@ -77,6 +77,8 @@ func wrapResponseWriter(w http.ResponseWriter, span ddtrace.Span) http.ResponseW }{mw{{ range . }}, h{{.}}{{ end }}} {{- end }} {{- end }} + default: + w = mw } return w diff --git a/contrib/internal/httputil/trace.go b/contrib/internal/httputil/trace.go index 92728b7966..52c2c39565 100644 --- a/contrib/internal/httputil/trace.go +++ b/contrib/internal/httputil/trace.go @@ -15,7 +15,8 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" - "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo/instrumentation/httpinstr" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo/instrumentation/httpsec" ) // TraceConfig defines the configuration for request tracing. @@ -53,8 +54,10 @@ func TraceAndServe(h http.Handler, cfg *TraceConfig) { span, ctx := tracer.StartSpanFromContext(cfg.Request.Context(), "http.request", opts...) defer span.Finish(cfg.FinishOpts...) - cfg.ResponseWriter = wrapResponseWriter(cfg.ResponseWriter, span) - httpinstr.WrapHandler(h, span).ServeHTTP(cfg.ResponseWriter, cfg.Request.WithContext(ctx)) + if appsec.Enabled() { + h = httpsec.WrapHandler(h, span) + } + h.ServeHTTP(wrapResponseWriter(cfg.ResponseWriter, span), cfg.Request.WithContext(ctx)) } // responseWriter is a small wrapper around an http response writer that will diff --git a/contrib/internal/httputil/trace_gen.go b/contrib/internal/httputil/trace_gen.go index 4e5c523a69..bbe8bd55c6 100644 --- a/contrib/internal/httputil/trace_gen.go +++ b/contrib/internal/httputil/trace_gen.go @@ -125,6 +125,8 @@ func wrapResponseWriter(w http.ResponseWriter, span ddtrace.Span) http.ResponseW monitoredResponseWriter http.Hijacker }{mw, hHijacker} + default: + w = mw } return w diff --git a/contrib/internal/httputil/trace_test.go b/contrib/internal/httputil/trace_test.go index 26e65a4439..b3c9bb0a07 100644 --- a/contrib/internal/httputil/trace_test.go +++ b/contrib/internal/httputil/trace_test.go @@ -56,6 +56,44 @@ func TestTraceAndServe(t *testing.T) { assert.Equal("503: Service Unavailable", span.Tag(ext.Error).(error).Error()) }) + t.Run("custom", func(t *testing.T) { + mt := mocktracer.Start() + assert := assert.New(t) + defer mt.Stop() + + called := false + // w is a custom struct that *exclusively* implements ResponseWriter + w := struct { + http.ResponseWriter + }{httptest.NewRecorder()} + r, err := http.NewRequest("GET", "/path?token=value", nil) + assert.NoError(err) + handler := func(w http.ResponseWriter, r *http.Request) { + _, ok := w.(http.Hijacker) + assert.False(ok) + http.Error(w, "some error", http.StatusServiceUnavailable) + called = true + } + TraceAndServe(http.HandlerFunc(handler), &TraceConfig{ + ResponseWriter: w, + Request: r, + Service: "service", + Resource: "resource", + }) + spans := mt.FinishedSpans() + span := spans[0] + + assert.True(called) + assert.Len(spans, 1) + assert.Equal(ext.SpanTypeWeb, span.Tag(ext.SpanType)) + assert.Equal("service", span.Tag(ext.ServiceName)) + assert.Equal("resource", span.Tag(ext.ResourceName)) + assert.Equal("GET", span.Tag(ext.HTTPMethod)) + assert.Equal("/path", span.Tag(ext.HTTPURL)) + assert.Equal("503", span.Tag(ext.HTTPCode)) + assert.Equal("503: Service Unavailable", span.Tag(ext.Error).(error).Error()) + }) + t.Run("query-params", func(t *testing.T) { mt := mocktracer.Start() assert := assert.New(t) diff --git a/contrib/labstack/echo.v4/echotrace.go b/contrib/labstack/echo.v4/echotrace.go index 92ce48719b..9c05c96273 100644 --- a/contrib/labstack/echo.v4/echotrace.go +++ b/contrib/labstack/echo.v4/echotrace.go @@ -9,23 +9,29 @@ package echo import ( "fmt" "math" - "net/http" + "net" + "net/http" "strconv" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo/instrumentation/httpsec" "github.com/labstack/echo/v4" ) // Middleware returns echo middleware which will trace incoming requests. func Middleware(opts ...Option) echo.MiddlewareFunc { + cfg := new(config) + defaults(cfg) + for _, fn := range opts { + fn(cfg) + } return func(next echo.HandlerFunc) echo.HandlerFunc { - cfg := new(config) - defaults(cfg) - for _, fn := range opts { - fn(cfg) + if appsec.Enabled() { + next = withAppSec(next) } return func(c echo.Context) error { request := c.Request() @@ -50,7 +56,6 @@ func Middleware(opts ...Option) echo.MiddlewareFunc { // pass the span through the request context c.SetRequest(request.WithContext(ctx)) - // serve the request to the next middleware err := next(c) if err != nil { @@ -87,3 +92,27 @@ func Middleware(opts ...Option) echo.MiddlewareFunc { } } } + +func withAppSec(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + req := c.Request() + span, ok := tracer.SpanFromContext(req.Context()) + if !ok { + return next(c) + } + httpsec.SetAppSecTags(span) + args := httpsec.MakeHandlerOperationArgs(req) + op := httpsec.StartOperation(args, nil) + defer func() { + events := op.Finish(httpsec.HandlerOperationRes{Status: c.Response().Status}) + if len(events) > 0 { + remoteIP, _, err := net.SplitHostPort(req.RemoteAddr) + if err != nil { + remoteIP = req.RemoteAddr + } + httpsec.SetSecurityEventTags(span, events, remoteIP, args.Headers, c.Response().Writer.Header()) + } + }() + return next(c) + } +} diff --git a/contrib/labstack/echo.v4/echotrace_test.go b/contrib/labstack/echo.v4/echotrace_test.go index a93e5407e0..7fb2757581 100644 --- a/contrib/labstack/echo.v4/echotrace_test.go +++ b/contrib/labstack/echo.v4/echotrace_test.go @@ -10,14 +10,17 @@ import ( "fmt" "net/http" "net/http/httptest" + "strings" "testing" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestChildSpan(t *testing.T) { @@ -265,3 +268,47 @@ func TestGetSpanNotInstrumented(t *testing.T) { assert.True(called) assert.False(traced) } + +func TestAppSec(t *testing.T) { + // Start the tracer along with the fake agent HTTP server + mt := mocktracer.Start() + defer mt.Stop() + + appsec.Start() + defer appsec.Stop() + + if !appsec.Enabled() { + t.Skip("appsec disabled") + } + + // Start and trace an HTTP server + e := echo.New() + e.Use(Middleware()) + + e.POST("/*tmp", func(c echo.Context) error { + return c.String(200, "Hello World!\n") + }) + srv := httptest.NewServer(e) + defer srv.Close() + + // Send an LFI attack + req, err := http.NewRequest("POST", srv.URL+"/../../../secret.txt", nil) + if err != nil { + panic(err) + } + res, err := srv.Client().Do(req) + require.NoError(t, err) + + // Check that the handler was properly called + b, err := ioutil.ReadAll(res.Body) + require.NoError(t, err) + require.Equal(t, "Hello World!\n", string(b)) + + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + + // The request should have the LFI attack attempt event (appsec rule id crs-930-100). + event := finished[0].Tag("_dd.appsec.json") + require.NotNil(t, event) + require.True(t, strings.Contains(event.(string), "crs-930-100")) +} diff --git a/contrib/net/http/option.go b/contrib/net/http/option.go index 259ef2e14b..fc68ecbce8 100644 --- a/contrib/net/http/option.go +++ b/contrib/net/http/option.go @@ -118,6 +118,7 @@ type roundTripperConfig struct { analyticsRate float64 serviceName string resourceNamer func(req *http.Request) string + spanOpts []ddtrace.StartSpanOption } func newRoundTripperConfig() *roundTripperConfig { @@ -155,6 +156,14 @@ func RTWithResourceNamer(namer func(req *http.Request) string) RoundTripperOptio } } +// RTWithSpanOptions defines a set of additional ddtrace.StartSpanOption to be added +// to spans started by the integration. +func RTWithSpanOptions(opts ...ddtrace.StartSpanOption) RoundTripperOption { + return func(cfg *roundTripperConfig) { + cfg.spanOpts = append(cfg.spanOpts, opts...) + } +} + func defaultResourceNamer(_ *http.Request) string { return "http.request" } diff --git a/contrib/net/http/roundtripper.go b/contrib/net/http/roundtripper.go index 37313d05fe..00b85f2c7d 100644 --- a/contrib/net/http/roundtripper.go +++ b/contrib/net/http/roundtripper.go @@ -36,6 +36,9 @@ func (rt *roundTripper) RoundTrip(req *http.Request) (res *http.Response, err er if rt.cfg.serviceName != "" { opts = append(opts, tracer.ServiceName(rt.cfg.serviceName)) } + if len(rt.cfg.spanOpts) > 0 { + opts = append(opts, rt.cfg.spanOpts...) + } span, ctx := tracer.StartSpanFromContext(req.Context(), "http.request", opts...) defer func() { if rt.cfg.after != nil { diff --git a/contrib/net/http/roundtripper_test.go b/contrib/net/http/roundtripper_test.go index f31da1466d..a60cc1977a 100644 --- a/contrib/net/http/roundtripper_test.go +++ b/contrib/net/http/roundtripper_test.go @@ -308,3 +308,21 @@ func TestResourceNamer(t *testing.T) { assert.Equal(t, "GET /hello/world", spans[0].Tag(ext.ResourceName)) }) } + +func TestSpanOptions(t *testing.T) { + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("")) })) + defer s.Close() + + tagKey := "foo" + tagValue := "bar" + mt := mocktracer.Start() + defer mt.Stop() + rt := WrapRoundTripper(http.DefaultTransport, RTWithSpanOptions(tracer.Tag(tagKey, tagValue))) + client := &http.Client{Transport: rt} + + client.Get(s.URL) + + spans := mt.FinishedSpans() + assert.Len(t, spans, 1) + assert.Equal(t, tagValue, spans[0].Tag(tagKey)) +} diff --git a/ddtrace/ddtrace.go b/ddtrace/ddtrace.go index 9fe41b1eb1..06a1732fc1 100644 --- a/ddtrace/ddtrace.go +++ b/ddtrace/ddtrace.go @@ -13,7 +13,10 @@ // with by accessing the subdirectories of this package: https://godoc.org/gopkg.in/DataDog/dd-trace-go.v1/ddtrace#pkg-subdirectories. package ddtrace // import "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" -import "time" +import ( + "context" + "time" +) // Tracer specifies an implementation of the Datadog tracer which allows starting // and propagating spans. The official implementation if exposed as functions @@ -122,6 +125,9 @@ type StartSpanConfig struct { // Force-set the SpanID, rather than use a random number. If no Parent SpanContext is present, // then this will also set the TraceID to the same value. SpanID uint64 + + // Context is the parent context where the span should be stored. + Context context.Context } // Logger implementations are able to log given messages that the tracer might output. diff --git a/ddtrace/ext/cassandra.go b/ddtrace/ext/cassandra.go index 38cebbdc3d..5660a17fc0 100644 --- a/ddtrace/ext/cassandra.go +++ b/ddtrace/ext/cassandra.go @@ -9,6 +9,9 @@ const ( // CassandraQuery is the tag name used for cassandra queries. CassandraQuery = "cassandra.query" + // CassandraBatch is the tag name used for cassandra batches. + CassandraBatch = "cassandra.batch" + // CassandraConsistencyLevel is the tag name to set for consitency level. CassandraConsistencyLevel = "cassandra.consistency_level" diff --git a/ddtrace/tracer/context.go b/ddtrace/tracer/context.go index 0add06e99a..7fdbe8c8bf 100644 --- a/ddtrace/tracer/context.go +++ b/ddtrace/tracer/context.go @@ -42,10 +42,16 @@ func StartSpanFromContext(ctx context.Context, operationName string, opts ...Sta if ctx == nil { // default to context.Background() to avoid panics on Go >= 1.15 ctx = context.Background() - } - if s, ok := SpanFromContext(ctx); ok { + } else if s, ok := SpanFromContext(ctx); ok { opts = append(opts, ChildOf(s.Context())) } + opts = append(opts, withContext(ctx)) s := StartSpan(operationName, opts...) + if span, ok := s.(*span); ok && span.pprofCtxActive != nil { + // If pprof labels were applied for this span, use the derived ctx that + // includes them. Otherwise a child of this span wouldn't be able to + // correctly restore the labels of its parent when it finishes. + ctx = span.pprofCtxActive + } return s, ContextWithSpan(ctx, s) } diff --git a/ddtrace/tracer/log.go b/ddtrace/tracer/log.go index 8298815e93..f609c4b774 100644 --- a/ddtrace/tracer/log.go +++ b/ddtrace/tracer/log.go @@ -17,39 +17,39 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" + "gopkg.in/DataDog/dd-trace-go.v1/internal/osinfo" "gopkg.in/DataDog/dd-trace-go.v1/internal/version" ) -const ( - unknown = "unknown" -) - // startupInfo contains various information about the status of the tracer on startup. type startupInfo struct { - Date string `json:"date"` // ISO 8601 date and time of start - OSName string `json:"os_name"` // Windows, Darwin, Debian, etc. - OSVersion string `json:"os_version"` // Version of the OS - Version string `json:"version"` // Tracer version - Lang string `json:"lang"` // "Go" - LangVersion string `json:"lang_version"` // Go version, e.g. go1.13 - Env string `json:"env"` // Tracer env - Service string `json:"service"` // Tracer Service - AgentURL string `json:"agent_url"` // The address of the agent - AgentError string `json:"agent_error"` // Any error that occurred trying to connect to agent - Debug bool `json:"debug"` // Whether debug mode is enabled - AnalyticsEnabled bool `json:"analytics_enabled"` // True if there is a global analytics rate set - SampleRate string `json:"sample_rate"` // The default sampling rate for the rules sampler - SamplingRules []SamplingRule `json:"sampling_rules"` // Rules used by the rules sampler - SamplingRulesError string `json:"sampling_rules_error"` // Any errors that occurred while parsing sampling rules - Tags map[string]string `json:"tags"` // Global tags - RuntimeMetricsEnabled bool `json:"runtime_metrics_enabled"` // Whether or not runtime metrics are enabled - HealthMetricsEnabled bool `json:"health_metrics_enabled"` // Whether or not health metrics are enabled - ApplicationVersion string `json:"dd_version"` // Version of the user's application - Architecture string `json:"architecture"` // Architecture of host machine - GlobalService string `json:"global_service"` // Global service string. If not-nil should be same as Service. (#614) - LambdaMode string `json:"lambda_mode"` // Whether or not the client has enabled lambda mode - AppSec string `json:"appsec"` // AppSec status string - AgentFeatures agentFeatures `json:"agent_features"` // Lists the capabilities of the agent. + Date string `json:"date"` // ISO 8601 date and time of start + OSName string `json:"os_name"` // Windows, Darwin, Debian, etc. + OSVersion string `json:"os_version"` // Version of the OS + Version string `json:"version"` // Tracer version + Lang string `json:"lang"` // "Go" + LangVersion string `json:"lang_version"` // Go version, e.g. go1.13 + Env string `json:"env"` // Tracer env + Service string `json:"service"` // Tracer Service + AgentURL string `json:"agent_url"` // The address of the agent + AgentError string `json:"agent_error"` // Any error that occurred trying to connect to agent + Debug bool `json:"debug"` // Whether debug mode is enabled + AnalyticsEnabled bool `json:"analytics_enabled"` // True if there is a global analytics rate set + SampleRate string `json:"sample_rate"` // The default sampling rate for the rules sampler + SamplingRules []SamplingRule `json:"sampling_rules"` // Rules used by the rules sampler + SamplingRulesError string `json:"sampling_rules_error"` // Any errors that occurred while parsing sampling rules + ServiceMappings map[string]string `json:"service_mappings"` // Service Mappings + Tags map[string]string `json:"tags"` // Global tags + RuntimeMetricsEnabled bool `json:"runtime_metrics_enabled"` // Whether or not runtime metrics are enabled + HealthMetricsEnabled bool `json:"health_metrics_enabled"` // Whether or not health metrics are enabled + ProfilerCodeHotspotsEnabled bool `json:"profiler_code_hotspots_enabled"` // Whether or not profiler code hotspots are enabled + ProfilerEndpointsEnabled bool `json:"profiler_endpoints_enabled"` // Whether or not profiler endpoints are enabled + ApplicationVersion string `json:"dd_version"` // Version of the user's application + Architecture string `json:"architecture"` // Architecture of host machine + GlobalService string `json:"global_service"` // Global service string. If not-nil should be same as Service. (#614) + LambdaMode string `json:"lambda_mode"` // Whether or not the client has enabled lambda mode + AppSec bool `json:"appsec"` // AppSec status: true when started, false otherwise. + AgentFeatures agentFeatures `json:"agent_features"` // Lists the capabilities of the agent. } // checkEndpoint tries to connect to the URL specified by endpoint. @@ -78,28 +78,31 @@ func logStartup(t *tracer) { } info := startupInfo{ - Date: time.Now().Format(time.RFC3339), - OSName: osName(), - OSVersion: osVersion(), - Version: version.Tag, - Lang: "Go", - LangVersion: runtime.Version(), - Env: t.config.env, - Service: t.config.serviceName, - AgentURL: t.config.transport.endpoint(), - Debug: t.config.debug, - AnalyticsEnabled: !math.IsNaN(globalconfig.AnalyticsRate()), - SampleRate: fmt.Sprintf("%f", t.rulesSampling.globalRate), - SamplingRules: t.rulesSampling.rules, - Tags: tags, - RuntimeMetricsEnabled: t.config.runtimeMetrics, - HealthMetricsEnabled: t.config.runtimeMetrics, - ApplicationVersion: t.config.version, - Architecture: runtime.GOARCH, - GlobalService: globalconfig.ServiceName(), - LambdaMode: fmt.Sprintf("%t", t.config.logToStdout), - AgentFeatures: t.features.Load(), - AppSec: appsec.Status(), + Date: time.Now().Format(time.RFC3339), + OSName: osinfo.OSName(), + OSVersion: osinfo.OSVersion(), + Version: version.Tag, + Lang: "Go", + LangVersion: runtime.Version(), + Env: t.config.env, + Service: t.config.serviceName, + AgentURL: t.config.transport.endpoint(), + Debug: t.config.debug, + AnalyticsEnabled: !math.IsNaN(globalconfig.AnalyticsRate()), + SampleRate: fmt.Sprintf("%f", t.rulesSampling.globalRate), + SamplingRules: t.rulesSampling.rules, + ServiceMappings: t.config.serviceMappings, + Tags: tags, + RuntimeMetricsEnabled: t.config.runtimeMetrics, + HealthMetricsEnabled: t.config.runtimeMetrics, + ApplicationVersion: t.config.version, + ProfilerCodeHotspotsEnabled: t.config.profilerHotspots, + ProfilerEndpointsEnabled: t.config.profilerEndpoints, + Architecture: runtime.GOARCH, + GlobalService: globalconfig.ServiceName(), + LambdaMode: fmt.Sprintf("%t", t.config.logToStdout), + AgentFeatures: t.config.agent, + AppSec: appsec.Enabled(), } if _, err := samplingRulesFromEnv(); err != nil { info.SamplingRulesError = fmt.Sprintf("%s", err) diff --git a/ddtrace/tracer/log_test.go b/ddtrace/tracer/log_test.go index 5ee4870695..8233717ce3 100644 --- a/ddtrace/tracer/log_test.go +++ b/ddtrace/tracer/log_test.go @@ -26,7 +26,7 @@ func TestStartupLog(t *testing.T) { logStartup(tracer) lines := removeAppSec(tp.Lines()) assert.Len(lines, 2) - assert.Regexp(`Datadog Tracer v[0-9]+\.[0-9]+\.[0-9]+ INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"","service":"tracer\.test","agent_url":"http://localhost:9/v0.4/traces","agent_error":"Post .*","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sampling_rules":null,"sampling_rules_error":"","tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"health_metrics_enabled":false,"dd_version":"","architecture":"[^"]*","global_service":"","lambda_mode":"false","appsec":"((enabled)|(disabled))","agent_features":{"DropP0s":false,"V05":false,"Stats":false}}`, lines[1]) + assert.Regexp(`Datadog Tracer v[0-9]+\.[0-9]+\.[0-9]+ INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"","service":"tracer\.test","agent_url":"http://localhost:9/v0.4/traces","agent_error":"Post .*","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sampling_rules":null,"sampling_rules_error":"","service_mappings":null,"tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"health_metrics_enabled":false,"profiler_code_hotspots_enabled":false,"profiler_endpoints_enabled":false,"dd_version":"","architecture":"[^"]*","global_service":"","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":false,"Stats":false,"StatsdPort":0}}`, lines[1]) }) t.Run("configured", func(t *testing.T) { @@ -39,6 +39,7 @@ func TestStartupLog(t *testing.T) { WithService("configured.service"), WithAgentAddr("test.host:1234"), WithEnv("configuredEnv"), + WithServiceMapping("initial_service", "new_service"), WithGlobalTag("tag", "value"), WithGlobalTag("tag2", math.NaN()), WithRuntimeMetrics(), @@ -54,7 +55,7 @@ func TestStartupLog(t *testing.T) { tp.Reset() logStartup(tracer) assert.Len(tp.Lines(), 2) - assert.Regexp(`Datadog Tracer v[0-9]+\.[0-9]+\.[0-9]+ INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"configuredEnv","service":"configured.service","agent_url":"http://localhost:9/v0.4/traces","agent_error":"Post .*","debug":true,"analytics_enabled":true,"sample_rate":"0\.123000","sampling_rules":\[{"service":"mysql","name":"","sample_rate":0\.75}\],"sampling_rules_error":"","tags":{"runtime-id":"[^"]*","tag":"value","tag2":"NaN"},"runtime_metrics_enabled":true,"health_metrics_enabled":true,"dd_version":"2.3.4","architecture":"[^"]*","global_service":"configured.service","lambda_mode":"false","appsec":"((enabled)|(disabled))","agent_features":{"DropP0s":false,"V05":false,"Stats":false}}`, tp.Lines()[1]) + assert.Regexp(`Datadog Tracer v[0-9]+\.[0-9]+\.[0-9]+ INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"configuredEnv","service":"configured.service","agent_url":"http://localhost:9/v0.4/traces","agent_error":"Post .*","debug":true,"analytics_enabled":true,"sample_rate":"0\.123000","sampling_rules":\[{"service":"mysql","name":"","sample_rate":0\.75}\],"sampling_rules_error":"","service_mappings":{"initial_service":"new_service"},"tags":{"runtime-id":"[^"]*","tag":"value","tag2":"NaN"},"runtime_metrics_enabled":true,"health_metrics_enabled":true,"profiler_code_hotspots_enabled":false,"profiler_endpoints_enabled":false,"dd_version":"2.3.4","architecture":"[^"]*","global_service":"configured.service","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":false,"Stats":false,"StatsdPort":0}}`, tp.Lines()[1]) }) t.Run("errors", func(t *testing.T) { @@ -68,7 +69,7 @@ func TestStartupLog(t *testing.T) { tp.Reset() logStartup(tracer) assert.Len(tp.Lines(), 2) - assert.Regexp(`Datadog Tracer v[0-9]+\.[0-9]+\.[0-9]+ INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"","service":"tracer\.test","agent_url":"http://localhost:9/v0.4/traces","agent_error":"Post .*","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sampling_rules":\[{"service":"some.service","name":"","sample_rate":0\.234}\],"sampling_rules_error":"found errors:\\n\\tat index 1: rate not provided","tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"health_metrics_enabled":false,"dd_version":"","architecture":"[^"]*","global_service":"","lambda_mode":"false","appsec":"((enabled)|(disabled))","agent_features":{"DropP0s":false,"V05":false,"Stats":false}}`, tp.Lines()[1]) + assert.Regexp(`Datadog Tracer v[0-9]+\.[0-9]+\.[0-9]+ INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"","service":"tracer\.test","agent_url":"http://localhost:9/v0.4/traces","agent_error":"Post .*","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sampling_rules":\[{"service":"some.service","name":"","sample_rate":0\.234}\],"sampling_rules_error":"found errors:\\n\\tat index 1: rate not provided","service_mappings":null,"tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"health_metrics_enabled":false,"profiler_code_hotspots_enabled":false,"profiler_endpoints_enabled":false,"dd_version":"","architecture":"[^"]*","global_service":"","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":false,"Stats":false,"StatsdPort":0}}`, tp.Lines()[1]) }) t.Run("lambda", func(t *testing.T) { @@ -80,7 +81,7 @@ func TestStartupLog(t *testing.T) { tp.Reset() logStartup(tracer) assert.Len(tp.Lines(), 1) - assert.Regexp(`Datadog Tracer v[0-9]+\.[0-9]+\.[0-9]+ INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"","service":"tracer\.test","agent_url":"http://localhost:9/v0.4/traces","agent_error":"","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sampling_rules":null,"sampling_rules_error":"","tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"health_metrics_enabled":false,"dd_version":"","architecture":"[^"]*","global_service":"","lambda_mode":"true","appsec":"((enabled)|(disabled))","agent_features":{"DropP0s":false,"V05":false,"Stats":false}}`, tp.Lines()[0]) + assert.Regexp(`Datadog Tracer v[0-9]+\.[0-9]+\.[0-9]+ INFO: DATADOG TRACER CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"","service":"tracer\.test","agent_url":"http://localhost:9/v0.4/traces","agent_error":"","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sampling_rules":null,"sampling_rules_error":"","service_mappings":null,"tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"health_metrics_enabled":false,"profiler_code_hotspots_enabled":false,"profiler_endpoints_enabled":false,"dd_version":"","architecture":"[^"]*","global_service":"","lambda_mode":"true","appsec":((true)|(false)),"agent_features":{"DropP0s":false,"Stats":false,"StatsdPort":0}}`, tp.Lines()[0]) }) } diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index ab502d8168..e21404d988 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -7,12 +7,15 @@ package tracer import ( "context" + "encoding/json" + "fmt" "math" "net" "net/http" "os" "path/filepath" "runtime" + "strconv" "strings" "time" @@ -21,9 +24,20 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/internal" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" + "gopkg.in/DataDog/dd-trace-go.v1/internal/traceprof" "gopkg.in/DataDog/dd-trace-go.v1/internal/version" - "github.com/DataDog/datadog-go/statsd" + "github.com/DataDog/datadog-go/v5/statsd" +) + +var ( + // defaultSocketAPM specifies the socket path to use for connecting to the trace-agent. + // Replaced in tests + defaultSocketAPM = "/var/run/datadog/apm.socket" + + // defaultSocketDSD specifies the socket path to use for connecting to the statsd server. + // Replaced in tests + defaultSocketDSD = "/var/run/datadog/dsd.socket" ) // config holds the tracer configuration. @@ -31,6 +45,10 @@ type config struct { // debug, when true, writes details to logs. debug bool + // agent holds the capabilities of the agent and determines some + // of the behaviour of the tracer. + agent agentFeatures + // featureFlags specifies any enabled feature flags. featureFlags map[string]struct{} @@ -58,6 +76,9 @@ type config struct { // are sent to. agentAddr string + // serviceMappings holds a set of service mappings to dynamically rename services + serviceMappings map[string]string + // globalTags holds a set of tags that will be automatically applied to // all spans. globalTags map[string]interface{} @@ -101,6 +122,15 @@ type config struct { // noDebugStack disables the collection of debug stack traces globally. No traces reporting // errors will record a stack trace when this option is set. noDebugStack bool + + // profilerHotspots specifies whether profiler Code Hotspots is enabled. + profilerHotspots bool + + // profilerEndpoints specifies whether profiler endpoint filtering is enabled. + profilerEndpoints bool + + // enabled reports whether tracing is enabled. + enabled bool } // HasFeature reports whether feature f is enabled. @@ -109,31 +139,43 @@ func (c *config) HasFeature(f string) bool { return ok } -// client returns the HTTP client to use. -func (c *config) client() *http.Client { - if c.httpClient == nil { - return defaultClient - } - return c.httpClient -} - // StartOption represents a function that can be provided as a parameter to Start. type StartOption func(*config) +// forEachStringTag runs fn on every key:val pair encountered in str. +// str may contain multiple key:val pairs separated by either space +// or comma (but not a mixture of both). +func forEachStringTag(str string, fn func(key string, val string)) { + sep := " " + if strings.Index(str, ",") > -1 { + // falling back to comma as separator + sep = "," + } + for _, tag := range strings.Split(str, sep) { + tag = strings.TrimSpace(tag) + if tag == "" { + continue + } + kv := strings.SplitN(tag, ":", 2) + key := strings.TrimSpace(kv[0]) + if key == "" { + continue + } + var val string + if len(kv) == 2 { + val = strings.TrimSpace(kv[1]) + } + fn(key, val) + } +} + // newConfig renders the tracer configuration based on defaults, environment variables // and passed user opts. func newConfig(opts ...StartOption) *config { c := new(config) c.sampler = NewAllSampler() c.agentAddr = defaultAddress - statsdHost, statsdPort := "localhost", "8125" - if v := os.Getenv("DD_AGENT_HOST"); v != "" { - statsdHost = v - } - if v := os.Getenv("DD_DOGSTATSD_PORT"); v != "" { - statsdPort = v - } - c.dogstatsdAddr = net.JoinHostPort(statsdHost, statsdPort) + c.httpClient = defaultHTTPClient() if internal.BoolEnv("DD_TRACE_ANALYTICS_ENABLED", false) { globalconfig.SetAnalyticsRate(1.0) @@ -163,28 +205,11 @@ func newConfig(opts ...StartOption) *config { if ver := os.Getenv("DD_VERSION"); ver != "" { c.version = ver } + if v := os.Getenv("DD_SERVICE_MAPPING"); v != "" { + forEachStringTag(v, func(key, val string) { WithServiceMapping(key, val)(c) }) + } if v := os.Getenv("DD_TAGS"); v != "" { - sep := " " - if strings.Index(v, ",") > -1 { - // falling back to comma as separator - sep = "," - } - for _, tag := range strings.Split(v, sep) { - tag = strings.TrimSpace(tag) - if tag == "" { - continue - } - kv := strings.SplitN(tag, ":", 2) - key := strings.TrimSpace(kv[0]) - if key == "" { - continue - } - var val string - if len(kv) == 2 { - val = strings.TrimSpace(kv[1]) - } - WithGlobalTag(key, val)(c) - } + forEachStringTag(v, func(key, val string) { WithGlobalTag(key, val)(c) }) } if _, ok := os.LookupEnv("AWS_LAMBDA_FUNCTION_NAME"); ok { // AWS_LAMBDA_FUNCTION_NAME being set indicates that we're running in an AWS Lambda environment. @@ -194,6 +219,11 @@ func newConfig(opts ...StartOption) *config { c.logStartup = internal.BoolEnv("DD_TRACE_STARTUP_LOGS", true) c.runtimeMetrics = internal.BoolEnv("DD_RUNTIME_METRICS_ENABLED", false) c.debug = internal.BoolEnv("DD_TRACE_DEBUG", false) + c.enabled = internal.BoolEnv("DD_TRACE_ENABLED", true) + // TODO(fg): set these to true before going GA with this. + c.profilerEndpoints = internal.BoolEnv(traceprof.EndpointEnvVar, false) + c.profilerHotspots = internal.BoolEnv(traceprof.CodeHotspotsEnvVar, false) + for _, fn := range opts { fn(c) } @@ -223,7 +253,7 @@ func newConfig(opts ...StartOption) *config { } } if c.transport == nil { - c.transport = newHTTPTransport(c.agentAddr, c.client()) + c.transport = newHTTPTransport(c.agentAddr, c.httpClient) } if c.propagator == nil { c.propagator = NewPropagator(nil) @@ -234,8 +264,29 @@ func newConfig(opts ...StartOption) *config { if c.debug { log.SetLevel(log.LevelDebug) } + c.loadAgentFeatures() if c.statsd == nil { - client, err := statsd.New(c.dogstatsdAddr, statsd.WithMaxMessagesPerPayload(40), statsd.WithTags(statsTags(c))) + // configure statsd client + addr := c.dogstatsdAddr + if addr == "" { + // no config defined address; use defaults + addr = defaultDogstatsdAddr() + } + if agentport := c.agent.StatsdPort; agentport > 0 { + // the agent reported a non-standard port + host, _, err := net.SplitHostPort(addr) + if err == nil { + // we have a valid host:port address; replace the port because + // the agent knows better + if host == "" { + host = defaultHostname + } + addr = net.JoinHostPort(host, strconv.Itoa(agentport)) + } + // not a valid TCP address, leave it as it is (could be a socket connection) + } + c.dogstatsdAddr = addr + client, err := statsd.New(addr, statsd.WithMaxMessagesPerPayload(40), statsd.WithTags(statsTags(c))) if err != nil { log.Warn("Runtime and health metrics disabled: %v", err) c.statsd = &statsd.NoOpClient{} @@ -246,6 +297,124 @@ func newConfig(opts ...StartOption) *config { return c } +// defaultHTTPClient returns the default http.Client to start the tracer with. +func defaultHTTPClient() *http.Client { + if _, err := os.Stat(defaultSocketAPM); err == nil { + // we have the UDS socket file, use it + return udsClient(defaultSocketAPM) + } + return defaultClient +} + +// udsClient returns a new http.Client which connects using the given UDS socket path. +func udsClient(socketPath string) *http.Client { + return &http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { + return defaultDialer.DialContext(ctx, "unix", (&net.UnixAddr{ + Name: socketPath, + Net: "unix", + }).String()) + }, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + }, + Timeout: defaultHTTPTimeout, + } +} + +// defaultDogstatsdAddr returns the default connection address for Dogstatsd. +func defaultDogstatsdAddr() string { + envHost, envPort := os.Getenv("DD_AGENT_HOST"), os.Getenv("DD_DOGSTATSD_PORT") + if _, err := os.Stat(defaultSocketDSD); err == nil && envHost == "" && envPort == "" { + // socket exists and user didn't specify otherwise via env vars + return "unix://" + defaultSocketDSD + } + host, port := defaultHostname, "8125" + if envHost != "" { + host = envHost + } + if envPort != "" { + port = envPort + } + return net.JoinHostPort(host, port) +} + +// agentFeatures holds information about the trace-agent's capabilities. +// When running WithLambdaMode, a zero-value of this struct will be used +// as features. +type agentFeatures struct { + // DropP0s reports whether it's ok for the tracer to not send any + // P0 traces to the agent. + DropP0s bool + + // Stats reports whether the agent can receive client-computed stats on + // the /v0.6/stats endpoint. + Stats bool + + // StatsdPort specifies the Dogstatsd port as provided by the agent. + // If it's the default, it will be 0, which means 8125. + StatsdPort int + + // featureFlags specifies all the feature flags reported by the trace-agent. + featureFlags map[string]struct{} +} + +// HasFlag reports whether the agent has set the feat feature flag. +func (a *agentFeatures) HasFlag(feat string) bool { + _, ok := a.featureFlags[feat] + return ok +} + +// loadAgentFeatures queries the trace-agent for its capabilities and updates +// the tracer's behaviour. +func (c *config) loadAgentFeatures() { + c.agent = agentFeatures{} + if c.logToStdout { + // there is no agent; all features off + return + } + resp, err := c.httpClient.Get(fmt.Sprintf("http://%s/info", c.agentAddr)) + if err != nil { + log.Error("Loading features: %v", err) + return + } + if resp.StatusCode == http.StatusNotFound { + // agent is older than 7.28.0, features not discoverable + return + } + defer resp.Body.Close() + type infoResponse struct { + Endpoints []string `json:"endpoints"` + ClientDropP0s bool `json:"client_drop_p0s"` + StatsdPort int `json:"statsd_port"` + FeatureFlags []string `json:"feature_flags"` + } + var info infoResponse + if err := json.NewDecoder(resp.Body).Decode(&info); err != nil { + log.Error("Decoding features: %v", err) + return + } + c.agent.DropP0s = info.ClientDropP0s + c.agent.StatsdPort = info.StatsdPort + for _, endpoint := range info.Endpoints { + switch endpoint { + case "/v0.6/stats": + if c.HasFeature("discovery") { + // client-stats computation is off by default + c.agent.Stats = true + } + } + } + c.agent.featureFlags = make(map[string]struct{}, len(info.FeatureFlags)) + for _, flag := range info.FeatureFlags { + c.agent.featureFlags[flag] = struct{}{} + } +} + func statsTags(c *config) []string { tags := []string{ "lang:go", @@ -269,6 +438,13 @@ func statsTags(c *config) []string { return tags } +// withNoopStats is used for testing to disable statsd client +func withNoopStats() StartOption { + return func(c *config) { + c.statsd = &statsd.NoOpClient{} + } +} + // WithFeatureFlags specifies a set of feature flags to enable. Please take into account // that most, if not all features flags are considered to be experimental and result in // unexpected bugs. @@ -371,6 +547,17 @@ func WithEnv(env string) StartOption { } } +// WithServiceMapping determines service "from" to be renamed to service "to". +// This option is is case sensitive and can be used multiple times. +func WithServiceMapping(from, to string) StartOption { + return func(c *config) { + if c.serviceMappings == nil { + c.serviceMappings = make(map[string]string) + } + c.serviceMappings[from] = to + } +} + // WithGlobalTag sets a key/value pair which will be set as a tag on all spans // created by tracer. This option may be used multiple times. func WithGlobalTag(k string, v interface{}) StartOption { @@ -408,14 +595,7 @@ func WithHTTPClient(client *http.Client) StartOption { // WithUDS configures the HTTP client to dial the Datadog Agent via the specified Unix Domain Socket path. func WithUDS(socketPath string) StartOption { - return WithHTTPClient(&http.Client{ - Transport: &http.Transport{ - DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { - return net.Dial("unix", socketPath) - }, - }, - Timeout: defaultHTTPTimeout, - }) + return WithHTTPClient(udsClient(socketPath)) } // WithAnalytics allows specifying whether Trace Search & Analytics should be enabled @@ -481,6 +661,13 @@ func WithHostname(name string) StartOption { } } +// WithTraceEnabled allows specifying whether tracing will be enabled +func WithTraceEnabled(enabled bool) StartOption { + return func(c *config) { + c.enabled = enabled + } +} + // WithLogStartup allows enabling or disabling the startup log. func WithLogStartup(enabled bool) StartOption { return func(c *config) { @@ -488,6 +675,31 @@ func WithLogStartup(enabled bool) StartOption { } } +// WithProfilerCodeHotspots enables the code hotspots integration between the +// tracer and profiler. This is done by automatically attaching pprof labels +// called "span id" and "local root span id" when new spans are created. You +// should not use these label names in your own code when this is enabled. The +// enabled value defaults to the value of the +// DD_PROFILING_CODE_HOTSPOTS_COLLECTION_ENABLED env variable or false. +func WithProfilerCodeHotspots(enabled bool) StartOption { + return func(c *config) { + c.profilerHotspots = enabled + } +} + +// WithProfilerEndpoints enables the endpoints integration between the tracer +// and profiler. This is done by automatically attaching a pprof label called +// "trace endpoint" holding the resource name of the top-level service span if +// its type is "http", "rpc" or "" (default). You should not use this label +// name in your own code when this is enabled. The enabled value defaults to +// the value of the DD_PROFILING_ENDPOINT_COLLECTION_ENABLED env variable or +// false. +func WithProfilerEndpoints(enabled bool) StartOption { + return func(c *config) { + c.profilerEndpoints = enabled + } +} + // StartSpanOption is a configuration option for StartSpan. It is aliased in order // to help godoc group all the functions returning it together. It is considered // more correct to refer to it as the type as the origin, ddtrace.StartSpanOption. @@ -542,6 +754,13 @@ func ChildOf(ctx ddtrace.SpanContext) StartSpanOption { } } +// withContext associates the ctx with the span. +func withContext(ctx context.Context) StartSpanOption { + return func(cfg *ddtrace.StartSpanConfig) { + cfg.Context = ctx + } +} + // StartTime sets a custom time as the start time for the created span. By // default a span is started using the creation time. func StartTime(t time.Time) StartSpanOption { diff --git a/ddtrace/tracer/option_test.go b/ddtrace/tracer/option_test.go index 4ec1bb71f4..410b4e43ee 100644 --- a/ddtrace/tracer/option_test.go +++ b/ddtrace/tracer/option_test.go @@ -6,16 +6,24 @@ package tracer import ( + "io" + "io/ioutil" "math" + "net" "net/http" + "net/http/httptest" "os" "path/filepath" + "runtime" + "strings" "testing" "time" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" + "gopkg.in/DataDog/dd-trace-go.v1/internal/traceprof" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func withTransport(t transport) StartOption { @@ -30,6 +38,173 @@ func withTickChan(ch <-chan time.Time) StartOption { } } +// testStatsd asserts that the given statsd.Client can successfully send metrics +// to a UDP listener located at addr. +func testStatsd(t *testing.T, cfg *config, addr string) { + client := cfg.statsd + require.Equal(t, addr, cfg.dogstatsdAddr) + udpaddr, err := net.ResolveUDPAddr("udp", addr) + require.NoError(t, err) + conn, err := net.ListenUDP("udp", udpaddr) + require.NoError(t, err) + defer conn.Close() + + client.Count("name", 1, []string{"tag"}, 1) + require.NoError(t, client.Close()) + + done := make(chan struct{}) + buf := make([]byte, 4096) + n := 0 + go func() { + n, _ = io.ReadAtLeast(conn, buf, 1) + close(done) + }() + + select { + case <-done: + // OK + case <-time.After(1 * time.Second): + require.Fail(t, "No data was flushed.") + } + assert.Contains(t, string(buf[:n]), "name:1|c|#lang:go") +} + +func TestAutoDetectStatsd(t *testing.T) { + t.Run("default", func(t *testing.T) { + testStatsd(t, newConfig(), net.JoinHostPort(defaultHostname, "8125")) + }) + + t.Run("socket", func(t *testing.T) { + if strings.HasPrefix(runtime.GOOS, "windows") { + t.Skip("Unix only") + } + if testing.Short() { + return + } + dir, err := ioutil.TempDir("", "socket") + if err != nil { + t.Fatal(err) + } + addr := filepath.Join(dir, "dsd.socket") + + defer func(old string) { defaultSocketDSD = old }(defaultSocketDSD) + defaultSocketDSD = addr + + uaddr, err := net.ResolveUnixAddr("unixgram", addr) + if err != nil { + t.Fatal(err) + } + conn, err := net.ListenUnixgram("unixgram", uaddr) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + conn.SetDeadline(time.Now().Add(5 * time.Second)) + + cfg := newConfig() + require.Equal(t, cfg.dogstatsdAddr, "unix://"+addr) + cfg.statsd.Count("name", 1, []string{"tag"}, 1) + + buf := make([]byte, 17) + n, err := conn.Read(buf) + if err != nil { + t.Fatal(err) + } + require.Contains(t, string(buf[:n]), "name:1|c|#lang:go") + }) + + t.Run("env", func(t *testing.T) { + defer func(old string) { os.Setenv("DD_DOGSTATSD_PORT", old) }(os.Getenv("DD_DOGSTATSD_PORT")) + os.Setenv("DD_DOGSTATSD_PORT", "8111") + testStatsd(t, newConfig(), net.JoinHostPort(defaultHostname, "8111")) + }) + + t.Run("agent", func(t *testing.T) { + t.Run("default", func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Write([]byte(`{"statsd_port":0}`)) + })) + defer srv.Close() + cfg := newConfig(WithAgentAddr(strings.TrimPrefix(srv.URL, "http://"))) + testStatsd(t, cfg, net.JoinHostPort(defaultHostname, "8125")) + }) + + t.Run("port", func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Write([]byte(`{"statsd_port":8999}`)) + })) + defer srv.Close() + cfg := newConfig(WithAgentAddr(strings.TrimPrefix(srv.URL, "http://"))) + testStatsd(t, cfg, net.JoinHostPort(defaultHostname, "8999")) + }) + }) +} + +func TestLoadAgentFeatures(t *testing.T) { + t.Run("zero", func(t *testing.T) { + t.Run("disabled", func(t *testing.T) { + assert.Zero(t, newConfig(WithLambdaMode(true)).agent) + }) + + t.Run("unreachable", func(t *testing.T) { + if testing.Short() { + return + } + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer srv.Close() + assert.Zero(t, newConfig(WithAgentAddr("127.9.9.9:8181")).agent) + }) + + t.Run("StatusNotFound", func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + })) + defer srv.Close() + assert.Zero(t, newConfig(WithAgentAddr(strings.TrimPrefix(srv.URL, "http://"))).agent) + }) + + t.Run("error", func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Write([]byte("Not JSON")) + })) + defer srv.Close() + assert.Zero(t, newConfig(WithAgentAddr(strings.TrimPrefix(srv.URL, "http://"))).agent) + }) + }) + + t.Run("OK", func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Write([]byte(`{"endpoints":["/v0.6/stats"],"feature_flags":["a","b"],"client_drop_p0s":true,"statsd_port":8999}`)) + })) + defer srv.Close() + cfg := newConfig(WithAgentAddr(strings.TrimPrefix(srv.URL, "http://"))) + assert.True(t, cfg.agent.DropP0s) + assert.Equal(t, cfg.agent.StatsdPort, 8999) + assert.EqualValues(t, cfg.agent.featureFlags, map[string]struct{}{ + "a": struct{}{}, + "b": struct{}{}, + }) + assert.False(t, cfg.agent.Stats) + assert.True(t, cfg.agent.HasFlag("a")) + assert.True(t, cfg.agent.HasFlag("b")) + }) + + t.Run("discovery", func(t *testing.T) { + defer func(old string) { os.Setenv("DD_TRACE_FEATURES", old) }(os.Getenv("DD_TRACE_FEATURES")) + os.Setenv("DD_TRACE_FEATURES", "discovery") + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Write([]byte(`{"endpoints":["/v0.6/stats"],"client_drop_p0s":true,"statsd_port":8999}`)) + })) + defer srv.Close() + cfg := newConfig(WithAgentAddr(strings.TrimPrefix(srv.URL, "http://"))) + assert.True(t, cfg.agent.DropP0s) + assert.True(t, cfg.agent.Stats) + assert.Equal(t, cfg.agent.StatsdPort, 8999) + }) +} + func TestTracerOptionsDefaults(t *testing.T) { t.Run("defaults", func(t *testing.T) { assert := assert.New(t) @@ -39,15 +214,15 @@ func TestTracerOptionsDefaults(t *testing.T) { assert.Equal("localhost:8126", c.agentAddr) assert.Equal("localhost:8125", c.dogstatsdAddr) assert.Nil(nil, c.httpClient) - assert.Equal(defaultClient, c.client()) + assert.Equal(defaultClient, c.httpClient) }) t.Run("http-client", func(t *testing.T) { c := newConfig() - assert.Equal(t, defaultClient, c.client()) + assert.Equal(t, defaultClient, c.httpClient) client := &http.Client{} WithHTTPClient(client)(c) - assert.Equal(t, client, c.client()) + assert.Equal(t, client, c.httpClient) }) t.Run("analytics", func(t *testing.T) { @@ -138,6 +313,22 @@ func TestTracerOptionsDefaults(t *testing.T) { assert.Equal(env, c.env) }) + t.Run("trace_enabled", func(t *testing.T) { + t.Run("default", func(t *testing.T) { + tracer := newTracer() + c := tracer.config + assert.True(t, c.enabled) + }) + + t.Run("override", func(t *testing.T) { + os.Setenv("DD_TRACE_ENABLED", "false") + defer os.Unsetenv("DD_TRACE_ENABLED") + tracer := newTracer() + c := tracer.config + assert.False(t, c.enabled) + }) + }) + t.Run("other", func(t *testing.T) { assert := assert.New(t) tracer := newTracer( @@ -172,6 +363,114 @@ func TestTracerOptionsDefaults(t *testing.T) { assert.False(ok) assert.Equal(nil, dVal) }) + + t.Run("profiler-endpoints", func(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := newConfig() + assert.False(t, c.profilerEndpoints) + }) + + t.Run("override", func(t *testing.T) { + os.Setenv(traceprof.EndpointEnvVar, "true") + defer os.Unsetenv(traceprof.EndpointEnvVar) + c := newConfig() + assert.True(t, c.profilerEndpoints) + }) + }) + + t.Run("profiler-hotspots", func(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := newConfig() + assert.False(t, c.profilerHotspots) + }) + + t.Run("override", func(t *testing.T) { + os.Setenv(traceprof.CodeHotspotsEnvVar, "true") + defer os.Unsetenv(traceprof.CodeHotspotsEnvVar) + c := newConfig() + assert.True(t, c.profilerHotspots) + }) + }) + + t.Run("env-mapping", func(t *testing.T) { + os.Setenv("DD_SERVICE_MAPPING", "tracer.test:test2, svc:Newsvc,http.router:myRouter, noval:") + defer os.Unsetenv("DD_SERVICE_MAPPING") + + assert := assert.New(t) + c := newConfig() + + assert.Equal("test2", c.serviceMappings["tracer.test"]) + assert.Equal("Newsvc", c.serviceMappings["svc"]) + assert.Equal("myRouter", c.serviceMappings["http.router"]) + assert.Equal("", c.serviceMappings["noval"]) + }) +} + +func TestDefaultHTTPClient(t *testing.T) { + t.Run("no-socket", func(t *testing.T) { + assert.Equal(t, defaultHTTPClient(), defaultClient) + }) + + t.Run("socket", func(t *testing.T) { + f, err := ioutil.TempFile("", "apm.socket") + if err != nil { + t.Fatal(err) + } + if err := f.Close(); err != nil { + t.Fatal(err) + } + defer os.RemoveAll(f.Name()) + defer func(old string) { defaultSocketAPM = old }(defaultSocketAPM) + defaultSocketAPM = f.Name() + assert.NotEqual(t, defaultHTTPClient(), defaultClient) + }) +} + +func TestDefaultDogstatsdAddr(t *testing.T) { + t.Run("no-socket", func(t *testing.T) { + assert.Equal(t, defaultDogstatsdAddr(), "localhost:8125") + }) + + t.Run("env", func(t *testing.T) { + defer func(old string) { os.Setenv("DD_DOGSTATSD_PORT", old) }(os.Getenv("DD_DOGSTATSD_PORT")) + os.Setenv("DD_DOGSTATSD_PORT", "8111") + assert.Equal(t, defaultDogstatsdAddr(), "localhost:8111") + }) + + t.Run("env+socket", func(t *testing.T) { + defer func(old string) { os.Setenv("DD_DOGSTATSD_PORT", old) }(os.Getenv("DD_DOGSTATSD_PORT")) + os.Setenv("DD_DOGSTATSD_PORT", "8111") + assert.Equal(t, defaultDogstatsdAddr(), "localhost:8111") + f, err := ioutil.TempFile("", "dsd.socket") + if err != nil { + t.Fatal(err) + } + if err := f.Close(); err != nil { + t.Fatal(err) + } + defer os.RemoveAll(f.Name()) + defer func(old string) { defaultSocketDSD = old }(defaultSocketDSD) + defaultSocketDSD = f.Name() + assert.Equal(t, defaultDogstatsdAddr(), "localhost:8111") + }) + + t.Run("socket", func(t *testing.T) { + defer func(old string) { os.Setenv("DD_AGENT_HOST", old) }(os.Getenv("DD_AGENT_HOST")) + defer func(old string) { os.Setenv("DD_DOGSTATSD_PORT", old) }(os.Getenv("DD_DOGSTATSD_PORT")) + os.Unsetenv("DD_AGENT_HOST") + os.Unsetenv("DD_DOGSTATSD_PORT") + f, err := ioutil.TempFile("", "dsd.socket") + if err != nil { + t.Fatal(err) + } + if err := f.Close(); err != nil { + t.Fatal(err) + } + defer os.RemoveAll(f.Name()) + defer func(old string) { defaultSocketDSD = old }(defaultSocketDSD) + defaultSocketDSD = f.Name() + assert.Equal(t, defaultDogstatsdAddr(), "unix://"+f.Name()) + }) } func TestServiceName(t *testing.T) { @@ -509,6 +808,30 @@ func TestWithHostname(t *testing.T) { }) } +func TestWithTraceEnabled(t *testing.T) { + t.Run("WithTraceEnabled", func(t *testing.T) { + assert := assert.New(t) + c := newConfig(WithTraceEnabled(false)) + assert.False(c.enabled) + }) + + t.Run("env", func(t *testing.T) { + assert := assert.New(t) + os.Setenv("DD_TRACE_ENABLED", "false") + defer os.Unsetenv("DD_TRACE_ENABLED") + c := newConfig() + assert.False(c.enabled) + }) + + t.Run("env-override", func(t *testing.T) { + assert := assert.New(t) + os.Setenv("DD_TRACE_ENABLED", "false") + defer os.Unsetenv("DD_TRACE_ENABLED") + c := newConfig(WithTraceEnabled(true)) + assert.True(c.enabled) + }) +} + func TestWithLogStartup(t *testing.T) { c := newConfig() assert.True(t, c.logStartup) diff --git a/ddtrace/tracer/span.go b/ddtrace/tracer/span.go index 3d6c7de98e..630271989f 100644 --- a/ddtrace/tracer/span.go +++ b/ddtrace/tracer/span.go @@ -8,10 +8,12 @@ package tracer import ( + "context" "fmt" "os" "reflect" "runtime" + "runtime/pprof" "strconv" "strings" "sync" @@ -24,6 +26,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" + "github.com/DataDog/datadog-agent/pkg/obfuscate" "github.com/tinylib/msgp/msgp" "golang.org/x/xerrors" ) @@ -71,7 +74,11 @@ type span struct { noDebugStack bool `msg:"-"` // disables debug stack traces finished bool `msg:"-"` // true if the span has been submitted to a tracer. context *spanContext `msg:"-"` // span propagation context - taskEnd func() // ends execution tracer (runtime/trace) task, if started + + pprofCtxActive context.Context `msg:"-"` // contains pprof.WithLabel labels to tell the profiler more about this span + pprofCtxRestore context.Context `msg:"-"` // contains pprof.WithLabel labels of the parent span (if any) that need to be restored when this span finishes + + taskEnd func() // ends execution tracer (runtime/trace) task, if started } // Context yields the SpanContext for this Span. Note that the return @@ -319,6 +326,12 @@ func (s *span) Finish(opts ...ddtrace.FinishOption) { s.taskEnd() } s.finish(t) + + if s.pprofCtxRestore != nil { + // Restore the labels of the parent span so any CPU samples after this + // point are attributed correctly. + pprof.SetGoroutineLabels(s.pprofCtxRestore) + } } // SetOperationName sets or changes the operation name. @@ -350,11 +363,11 @@ func (s *span) finish(finishTime int64) { keep := true if t, ok := internal.GetGlobalTracer().(*tracer); ok { // we have an active tracer - feats := t.features.Load() + feats := t.config.agent if feats.Stats && shouldComputeStats(s) { // the agent supports computed stats select { - case t.stats.In <- newAggregableSpan(s, t.config): + case t.stats.In <- newAggregableSpan(s, t.obfuscator): // ok default: log.Error("Stats channel full, disregarding span.") @@ -374,7 +387,7 @@ func (s *span) finish(finishTime int64) { // newAggregableSpan creates a new summary for the span s, within an application // version version. -func newAggregableSpan(s *span, cfg *config) *aggregableSpan { +func newAggregableSpan(s *span, obfuscator *obfuscate.Obfuscator) *aggregableSpan { var statusCode uint32 if sc, ok := s.Meta["http.status_code"]; ok && sc != "" { if c, err := strconv.Atoi(sc); err == nil { @@ -383,7 +396,7 @@ func newAggregableSpan(s *span, cfg *config) *aggregableSpan { } key := aggregation{ Name: s.Name, - Resource: s.Resource, + Resource: obfuscatedResource(obfuscator, s.Type, s.Resource), Service: s.Service, Type: s.Type, Synthetics: strings.HasPrefix(s.Meta[keyOrigin], "synthetics"), @@ -398,6 +411,31 @@ func newAggregableSpan(s *span, cfg *config) *aggregableSpan { } } +// textNonParsable specifies the text that will be assigned to resources for which the resource +// can not be parsed due to an obfuscation error. +const textNonParsable = "Non-parsable SQL query" + +// obfuscatedResource returns the obfuscated version of the given resource. It is +// obfuscated using the given obfuscator for the given span type typ. +func obfuscatedResource(o *obfuscate.Obfuscator, typ, resource string) string { + if o == nil { + return resource + } + switch typ { + case "sql", "cassandra": + oq, err := o.ObfuscateSQLString(resource) + if err != nil { + log.Error("Error obfuscating stats group resource %q: %v", resource, err) + return textNonParsable + } + return oq.Query + case "redis": + return o.QuantizeRedisString(resource) + default: + return resource + } +} + // shouldKeep reports whether the trace should be kept. // a single span being kept implies the whole trace being kept. func shouldKeep(s *span) bool { diff --git a/ddtrace/tracer/span_test.go b/ddtrace/tracer/span_test.go index accbdbe9e9..7730b4758e 100644 --- a/ddtrace/tracer/span_test.go +++ b/ddtrace/tracer/span_test.go @@ -16,6 +16,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "github.com/DataDog/datadog-agent/pkg/obfuscate" "github.com/stretchr/testify/assert" ) @@ -152,6 +153,39 @@ func TestShouldComputeStats(t *testing.T) { } } +func TestNewAggregableSpan(t *testing.T) { + t.Run("obfuscating", func(t *testing.T) { + o := obfuscate.NewObfuscator(obfuscate.Config{}) + aggspan := newAggregableSpan(&span{ + Name: "name", + Resource: "SELECT * FROM table WHERE password='secret'", + Service: "service", + Type: "sql", + }, o) + assert.Equal(t, aggregation{ + Name: "name", + Type: "sql", + Resource: "SELECT * FROM table WHERE password = ?", + Service: "service", + }, aggspan.key) + }) + + t.Run("nil-obfuscator", func(t *testing.T) { + aggspan := newAggregableSpan(&span{ + Name: "name", + Resource: "SELECT * FROM table WHERE password='secret'", + Service: "service", + Type: "sql", + }, nil) + assert.Equal(t, aggregation{ + Name: "name", + Type: "sql", + Resource: "SELECT * FROM table WHERE password='secret'", + Service: "service", + }, aggspan.key) + }) +} + func TestSpanFinishWithTime(t *testing.T) { assert := assert.New(t) diff --git a/ddtrace/tracer/spancontext_test.go b/ddtrace/tracer/spancontext_test.go index c2982baf17..7a00f2a071 100644 --- a/ddtrace/tracer/spancontext_test.go +++ b/ddtrace/tracer/spancontext_test.go @@ -33,7 +33,7 @@ func TestNewSpanContextPushError(t *testing.T) { defer setupteardown(2, 2)() tp := new(testLogger) - _, _, _, stop := startTestTracer(t, WithLogger(tp)) + _, _, _, stop := startTestTracer(t, WithLogger(tp), WithLambdaMode(true)) defer stop() parent := newBasicSpan("test1") // 1st span in trace parent.context.trace.push(newBasicSpan("test2")) // 2nd span in trace @@ -129,7 +129,7 @@ func TestSpanTracePushNoFinish(t *testing.T) { assert := assert.New(t) tp := new(testLogger) - _, _, _, stop := startTestTracer(t, WithLogger(tp)) + _, _, _, stop := startTestTracer(t, WithLogger(tp), WithLambdaMode(true)) defer stop() buffer := newTrace() @@ -353,7 +353,7 @@ func TestSpanContextPushFull(t *testing.T) { defer func(old int) { traceMaxSize = old }(traceMaxSize) traceMaxSize = 2 tp := new(testLogger) - _, _, _, stop := startTestTracer(t, WithLogger(tp)) + _, _, _, stop := startTestTracer(t, WithLogger(tp), WithLambdaMode(true)) defer stop() span1 := newBasicSpan("span1") diff --git a/ddtrace/tracer/stats.go b/ddtrace/tracer/stats.go index f6944ecd1d..b22068afc9 100644 --- a/ddtrace/tracer/stats.go +++ b/ddtrace/tracer/stats.go @@ -14,7 +14,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/internal/log" - "github.com/DataDog/datadog-go/statsd" + "github.com/DataDog/datadog-go/v5/statsd" "github.com/DataDog/sketches-go/ddsketch" "google.golang.org/protobuf/proto" ) diff --git a/ddtrace/tracer/stats_test.go b/ddtrace/tracer/stats_test.go index 42a237a295..b2754ea9d1 100644 --- a/ddtrace/tracer/stats_test.go +++ b/ddtrace/tracer/stats_test.go @@ -16,7 +16,7 @@ import ( // period. func waitForBuckets(c *concentrator, n int) bool { for i := 0; i < 5; i++ { - time.Sleep(time.Millisecond) + time.Sleep(time.Millisecond * timeMultiplicator) c.mu.Lock() if len(c.buckets) == n { return true @@ -134,7 +134,7 @@ func TestConcentrator(t *testing.T) { Start: time.Now().UnixNano() - 4*500000, Duration: 1, } - time.Sleep(2 * time.Millisecond) + time.Sleep(2 * time.Millisecond * timeMultiplicator) c.Stop() assert.NotZero(t, transport.Stats()) }) diff --git a/ddtrace/tracer/tracer.go b/ddtrace/tracer/tracer.go index 2192a385f9..c3855daf8e 100644 --- a/ddtrace/tracer/tracer.go +++ b/ddtrace/tracer/tracer.go @@ -6,10 +6,9 @@ package tracer import ( - "encoding/json" - "fmt" - "net/http" + gocontext "context" "os" + "runtime/pprof" "strconv" "sync" "time" @@ -19,7 +18,9 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/internal" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" - "gopkg.in/DataDog/dd-trace-go.v1/internal/version" + "gopkg.in/DataDog/dd-trace-go.v1/internal/traceprof" + + "github.com/DataDog/datadog-agent/pkg/obfuscate" ) var _ ddtrace.Tracer = (*tracer)(nil) @@ -35,10 +36,6 @@ var _ ddtrace.Tracer = (*tracer)(nil) type tracer struct { config *config - // features holds the capabilities of the agent and determines some - // of the behaviour of the tracer. - features *agentFeatures - // stats specifies the concentrator used to compute statistics, when client-side // stats are enabled. stats *concentrator @@ -80,6 +77,10 @@ type tracer struct { // rules for applying a sampling rate to spans that match the designated service // or operation name. rulesSampling *rulesSampler + + // obfuscator holds the obfuscator used to obfuscate resources in aggregated stats. + // obfuscator may be nil if disabled. + obfuscator *obfuscate.Obfuscator } const ( @@ -112,8 +113,8 @@ func Start(opts ...StartOption) { return // mock tracer active } t := newTracer(opts...) - if t.config.HasFeature("discovery") { - t.loadAgentFeatures() + if !t.config.enabled { + return } internal.SetGlobalTracer(t) if t.config.logStartup { @@ -180,8 +181,16 @@ func newUnstartedTracer(opts ...StartOption) *tracer { rulesSampling: newRulesSampler(c.samplingRules), prioritySampling: sampler, pid: strconv.Itoa(os.Getpid()), - features: &agentFeatures{}, stats: newConcentrator(c, defaultStatsBucketSize), + obfuscator: obfuscate.NewObfuscator(obfuscate.Config{ + SQL: obfuscate.SQLConfig{ + TableNames: c.agent.HasFlag("table_names"), + ReplaceDigits: c.agent.HasFlag("quantize_sql_tables") || c.agent.HasFlag("replace_sql_digits"), + KeepSQLAlias: c.agent.HasFlag("keep_sql_alias"), + DollarQuotedFunc: c.agent.HasFlag("dollar_quoted_func"), + Cache: c.agent.HasFlag("sql_cache"), + }, + }), } return t } @@ -215,18 +224,7 @@ func newTracer(opts ...StartOption) *tracer { t.reportHealthMetrics(statsInterval) }() t.stats.Start() - appsec.Start(&appsec.Config{ - Client: c.client(), - Version: version.Tag, - AgentURL: fmt.Sprintf("http://%s/", resolveAddr(c.agentAddr)), - Hostname: c.hostname, - Service: appsec.ServiceConfig{ - Name: c.serviceName, - Version: c.version, - Environment: c.env, - }, - Tags: c.globalTags, - }) + appsec.Start() return t } @@ -253,78 +251,6 @@ func (t *tracer) flushSync() { <-done } -// agentFeatures holds information about the trace-agent's capabilities. -type agentFeatures struct { - mu sync.RWMutex - - // DropP0s reports whether it's ok for the tracer to not send any - // P0 traces to the agent. - DropP0s bool - - // V05 reports whether it's ok to use the /v0.5/traces endpoint format. - V05 bool // TODO(x): Not yet implemented - - // Stats reports whether the agent can receive client-computed stats on - // the /v0.6/stats endpoint. - Stats bool -} - -// Load returns the current features. -func (f *agentFeatures) Load() agentFeatures { - f.mu.RLock() - out := *f - f.mu.RUnlock() - return out -} - -// Store stores the new features. -func (f *agentFeatures) Store(newf agentFeatures) { - f.mu.Lock() - f.DropP0s = newf.DropP0s - f.V05 = newf.V05 - f.Stats = newf.Stats - f.mu.Unlock() -} - -// loadAgentFeatures queries the trace-agent for its capabilities and updates -// the tracer's behaviour. -func (t *tracer) loadAgentFeatures() { - if t.config.logToStdout { - // there is no agent - return - } - resp, err := http.Get(fmt.Sprintf("http://%s/info", t.config.agentAddr)) - if err != nil { - log.Error("Loading features: %v", err) - return - } - if resp.StatusCode == http.StatusNotFound { - // agent is older than 7.28.0, features not discoverable - t.features.Store(agentFeatures{}) - return - } - defer resp.Body.Close() - type infoResponse struct { - Endpoints []string `json:"endpoints"` - ClientDropP0s bool `json:"client_drop_p0s"` - } - var info infoResponse - if err := json.NewDecoder(resp.Body).Decode(&info); err != nil { - log.Error("Decoding features: %v", err) - return - } - f := agentFeatures{DropP0s: info.ClientDropP0s} - for _, endpoint := range info.Endpoints { - switch endpoint { - case "/v0.6/stats": - f.Stats = true - case "/v0.5/traces": - f.V05 = true - } - } - t.features.Store(f) -} - // worker receives finished traces to be added into the payload, as well // as periodically flushes traces to the transport. func (t *tracer) worker(tick <-chan time.Time) { @@ -388,11 +314,30 @@ func (t *tracer) StartSpan(operationName string, options ...ddtrace.StartSpanOpt startTime = opts.StartTime.UnixNano() } var context *spanContext + // The default pprof context is taken from the start options and is + // not nil when using StartSpanFromContext() + pprofContext := opts.Context if opts.Parent != nil { if ctx, ok := opts.Parent.(*spanContext); ok { context = ctx + if pprofContext == nil && ctx.span != nil { + // Inherit the context.Context from parent span if it was propagated + // using ChildOf() rather than StartSpanFromContext(), see + // applyPPROFLabels() below. + pprofContext = ctx.span.pprofCtxActive + } } } + if pprofContext == nil { + // For root span's without context, there is no pprofContext, but we need + // one to avoid a panic() in pprof.WithLabels(). Using context.Background() + // is not ideal here, as it will cause us to remove all labels from the + // goroutine when the span finishes. However, the alternatives of not + // applying labels for such spans or to leave the endpoint/hotspot labels + // on the goroutine after it finishes are even less appealing. We'll have + // to properly document this for users. + pprofContext = gocontext.Background() + } id := opts.SpanID if id == 0 { id = random.Uint64() @@ -464,10 +409,50 @@ func (t *tracer) StartSpan(operationName string, options ...ddtrace.StartSpanOpt // if not already sampled or a brand new trace, sample it t.sample(span) } + if t.config.profilerHotspots || t.config.profilerEndpoints { + t.applyPPROFLabels(pprofContext, span) + } + if t.config.serviceMappings != nil { + if newSvc, ok := t.config.serviceMappings[span.Service]; ok { + span.Service = newSvc + } + } log.Debug("Started Span: %v, Operation: %s, Resource: %s, Tags: %v, %v", span, span.Name, span.Resource, span.Meta, span.Metrics) return span } +// applyPPROFLabels applies pprof labels for the profiler's code hotspots and +// endpoint filtering feature to span. When span finishes, any pprof labels +// found in ctx are restored. +func (t *tracer) applyPPROFLabels(ctx gocontext.Context, span *span) { + var labels []string + if t.config.profilerHotspots { + labels = append(labels, traceprof.SpanID, strconv.FormatUint(span.SpanID, 10)) + } + // nil checks might not be needed, but better be safe than sorry + if span.context.trace != nil && span.context.trace.root != nil { + localRootSpan := span.context.trace.root + if t.config.profilerHotspots { + labels = append(labels, traceprof.LocalRootSpanID, strconv.FormatUint(localRootSpan.SpanID, 10)) + } + if t.config.profilerEndpoints && spanResourcePIISafe(localRootSpan) { + labels = append(labels, traceprof.TraceEndpoint, localRootSpan.Resource) + } + } + if len(labels) > 0 { + span.pprofCtxRestore = ctx + span.pprofCtxActive = pprof.WithLabels(ctx, pprof.Labels(labels...)) + pprof.SetGoroutineLabels(span.pprofCtxActive) + } +} + +// spanResourcePIISafe returns true if s.Resource can be considered to not +// include PII with reasonable confidence. E.g. SQL queries may contain PII, +// but http, rpc or custom (s.Type == "") span resource names generally do not. +func spanResourcePIISafe(s *span) bool { + return s.Type == ext.SpanTypeWeb || s.Type == ext.AppTypeRPC || s.Type == "" +} + // Stop stops the tracer. func (t *tracer) Stop() { t.stopOnce.Do(func() { diff --git a/ddtrace/tracer/tracer_test.go b/ddtrace/tracer/tracer_test.go index 4104737f92..eb63a2b310 100644 --- a/ddtrace/tracer/tracer_test.go +++ b/ddtrace/tracer/tracer_test.go @@ -22,6 +22,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/internal" + maininternal "gopkg.in/DataDog/dd-trace-go.v1/internal" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" @@ -44,8 +45,27 @@ func (t *tracer) newChildSpan(name string, parent *span) *span { return t.StartSpan(name, ChildOf(parent.Context())).(*span) } +var ( + // timeMultiplicator specifies by how long to extend waiting times. + // It may be altered in some envinronment (like AppSec) where things + // move slower and could otherwise create flaky tests. + timeMultiplicator = time.Duration(1) + + // integration indicates if the test suite should run integration tests. + integration bool +) + +func TestMain(m *testing.M) { + if maininternal.BoolEnv("DD_APPSEC_ENABLED", false) { + // things are slower with AppSec; double wait times + timeMultiplicator = time.Duration(2) + } + _, integration = os.LookupEnv("INTEGRATION") + os.Exit(m.Run()) +} + func (t *tracer) awaitPayload(tst *testing.T, n int) { - timeout := time.After(200 * time.Millisecond) + timeout := time.After(200 * time.Millisecond * timeMultiplicator) loop: for { select { @@ -74,9 +94,8 @@ func TestTracerCleanStop(t *testing.T) { if testing.Short() { return } - // Avoid CI timeouts due to AppSec slowing down this test due to its init - // time. if old := os.Getenv("DD_APPSEC_ENABLED"); old != "" { + // avoid CI timeouts due to AppSec slowing down this test os.Unsetenv("DD_APPSEC_ENABLED") defer os.Setenv("DD_APPSEC_ENABLED", old) } @@ -109,10 +128,10 @@ func TestTracerCleanStop(t *testing.T) { defer wg.Done() for i := 0; i < n; i++ { // Lambda mode is used to avoid the startup cost associated with agent discovery. - Start(withTransport(transport), WithLambdaMode(true)) + Start(withTransport(transport), WithLambdaMode(true), withNoopStats()) time.Sleep(time.Millisecond) - Start(withTransport(transport), WithLambdaMode(true), WithSampler(NewRateSampler(0.99))) - Start(withTransport(transport), WithLambdaMode(true), WithSampler(NewRateSampler(0.99))) + Start(withTransport(transport), WithLambdaMode(true), WithSampler(NewRateSampler(0.99)), withNoopStats()) + Start(withTransport(transport), WithLambdaMode(true), WithSampler(NewRateSampler(0.99)), withNoopStats()) } }() @@ -155,6 +174,19 @@ func TestTracerStart(t *testing.T) { internal.Testing = false }) + t.Run("tracing_not_enabled", func(t *testing.T) { + os.Setenv("DD_TRACE_ENABLED", "false") + defer os.Unsetenv("DD_TRACE_ENABLED") + Start() + defer Stop() + if _, ok := internal.GetGlobalTracer().(*tracer); ok { + t.Fail() + } + if _, ok := internal.GetGlobalTracer().(*internal.NoopTracer); !ok { + t.Fail() + } + }) + t.Run("deadlock/api", func(t *testing.T) { Stop() Stop() @@ -261,7 +293,7 @@ func TestSamplingDecision(t *testing.T) { t.Run("dropped", func(t *testing.T) { tracer, _, _, stop := startTestTracer(t) defer stop() - tracer.features.DropP0s = true + tracer.config.agent.DropP0s = true tracer.prioritySampling.defaultRate = 0 tracer.config.serviceName = "test_service" span := tracer.StartSpan("name_1").(*span) @@ -275,7 +307,7 @@ func TestSamplingDecision(t *testing.T) { t.Run("events_sampled", func(t *testing.T) { tracer, _, _, stop := startTestTracer(t) defer stop() - tracer.features.DropP0s = true + tracer.config.agent.DropP0s = true tracer.prioritySampling.defaultRate = 0 tracer.config.serviceName = "test_service" span := tracer.StartSpan("name_1").(*span) @@ -290,7 +322,7 @@ func TestSamplingDecision(t *testing.T) { t.Run("client_dropped", func(t *testing.T) { tracer, _, _, stop := startTestTracer(t) defer stop() - tracer.features.DropP0s = true + tracer.config.agent.DropP0s = true tracer.config.sampler = NewRateSampler(0) tracer.prioritySampling.defaultRate = 0 tracer.config.serviceName = "test_service" @@ -539,6 +571,44 @@ func TestTracerSpanGlobalTags(t *testing.T) { assert.Equal("value", child.Meta["key"]) } +func TestTracerSpanServiceMappings(t *testing.T) { + assert := assert.New(t) + + t.Run("WithServiceMapping", func(t *testing.T) { + tracer := newTracer(WithServiceName("initial_service"), WithServiceMapping("initial_service", "new_service")) + s := tracer.StartSpan("web.request").(*span) + assert.Equal("new_service", s.Service) + + }) + + t.Run("child", func(t *testing.T) { + tracer := newTracer(WithServiceMapping("initial_service", "new_service")) + s := tracer.StartSpan("web.request", ServiceName("initial_service")).(*span) + child := tracer.StartSpan("db.query", ChildOf(s.Context())).(*span) + assert.Equal("new_service", child.Service) + + }) + + t.Run("StartSpanOption", func(t *testing.T) { + tracer := newTracer(WithServiceMapping("initial_service", "new_service")) + s := tracer.StartSpan("web.request", ServiceName("initial_service")).(*span) + assert.Equal("new_service", s.Service) + + }) + + t.Run("tag", func(t *testing.T) { + tracer := newTracer(WithServiceMapping("initial_service", "new_service")) + s := tracer.StartSpan("web.request", Tag("service.name", "initial_service")).(*span) + assert.Equal("new_service", s.Service) + }) + + t.Run("globalTags", func(t *testing.T) { + tracer := newTracer(WithGlobalTag("service.name", "initial_service"), WithServiceMapping("initial_service", "new_service")) + s := tracer.StartSpan("web.request").(*span) + assert.Equal("new_service", s.Service) + }) +} + func TestTracerNoDebugStack(t *testing.T) { assert := assert.New(t) @@ -1014,7 +1084,7 @@ func TestWorker(t *testing.T) { } flush(-1) - timeout := time.After(2 * time.Second) + timeout := time.After(2 * time.Second * timeMultiplicator) loop: for { select { @@ -1310,7 +1380,7 @@ func startTestTracer(t interface { tick <- time.Now() return } - d := 200 * time.Millisecond + d := 200 * time.Millisecond * timeMultiplicator expire := time.After(d) loop: for { diff --git a/ddtrace/tracer/transport.go b/ddtrace/tracer/transport.go index eb93a04451..b1ceba655e 100644 --- a/ddtrace/tracer/transport.go +++ b/ddtrace/tracer/transport.go @@ -31,17 +31,19 @@ const ( headerComputedTopLevel = "Datadog-Client-Computed-Top-Level" ) +var defaultDialer = &net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, +} + var defaultClient = &http.Client{ // We copy the transport to avoid using the default one, as it might be // augmented with tracing and we don't want these calls to be recorded. // See https://golang.org/pkg/net/http/#DefaultTransport . Transport: &http.Transport{ - Proxy: http.ProxyFromEnvironment, - DialContext: (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - DualStack: true, - }).DialContext, + Proxy: http.ProxyFromEnvironment, + DialContext: defaultDialer.DialContext, MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, @@ -146,7 +148,7 @@ func (t *httpTransport) send(p *payload) (body io.ReadCloser, err error) { req.Header.Set("Content-Length", strconv.Itoa(p.size())) req.Header.Set(headerComputedTopLevel, "yes") if t, ok := traceinternal.GetGlobalTracer().(*tracer); ok { - if t.features.Load().Stats { + if t.config.agent.Stats { req.Header.Set("Datadog-Client-Computed-Stats", "yes") } droppedTraces := int(atomic.SwapUint64(&t.droppedP0Traces, 0)) diff --git a/ddtrace/tracer/transport_test.go b/ddtrace/tracer/transport_test.go index a0cbaa7112..0fada24a98 100644 --- a/ddtrace/tracer/transport_test.go +++ b/ddtrace/tracer/transport_test.go @@ -13,6 +13,7 @@ import ( "net/http/httptest" "net/url" "os" + "path/filepath" "strconv" "strings" "testing" @@ -20,14 +21,6 @@ import ( "github.com/stretchr/testify/assert" ) -// integration indicates if the test suite should run integration tests. -var integration bool - -func TestMain(m *testing.M) { - _, integration = os.LookupEnv("INTEGRATION") - os.Exit(m.Run()) -} - // getTestSpan returns a Span with different fields set func getTestSpan() *span { return &span{ @@ -60,26 +53,6 @@ func getTestTrace(traceN, size int) [][]*span { return traces } -type mockDatadogAPIHandler struct { - t *testing.T -} - -func (m mockDatadogAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - assert := assert.New(m.t) - - header := r.Header.Get("X-Datadog-Trace-Count") - assert.NotEqual("", header, "X-Datadog-Trace-Count header should be here") - count, err := strconv.Atoi(header) - assert.Nil(err, "header should be an int") - assert.NotEqual(0, count, "there should be a non-zero amount of traces") -} - -func mockDatadogAPINewServer(t *testing.T) *httptest.Server { - handler := mockDatadogAPIHandler{t: t} - server := httptest.NewServer(handler) - return server -} - func TestTracesAgentIntegration(t *testing.T) { if !integration { t.Skip("to enable integration test, set the INTEGRATION environment variable") @@ -191,22 +164,27 @@ func TestTraceCountHeader(t *testing.T) { {getTestTrace(100, 10)}, } - receiver := mockDatadogAPINewServer(t) - parsedURL, err := url.Parse(receiver.URL) - assert.NoError(err) - host := parsedURL.Host - _, port, err := net.SplitHostPort(host) - assert.Nil(err) - assert.NotEmpty(port, "port should be given, as it's chosen randomly") + var hits int + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + hits++ + if r.URL.Path == "/info" { + return + } + header := r.Header.Get("X-Datadog-Trace-Count") + assert.NotEqual("", header, "X-Datadog-Trace-Count header should be here") + count, err := strconv.Atoi(header) + assert.Nil(err, "header should be an int") + assert.NotEqual(0, count, "there should be a non-zero amount of traces") + })) + defer srv.Close() for _, tc := range testCases { - transport := newHTTPTransport(host, defaultClient) + transport := newHTTPTransport(strings.TrimPrefix(srv.URL, "http://"), defaultClient) p, err := encode(tc.payload) assert.NoError(err) _, err = transport.send(p) assert.NoError(err) } - - receiver.Close() + assert.Equal(hits, len(testCases)) } type recordingRoundTripper struct { @@ -226,32 +204,34 @@ func (r *recordingRoundTripper) RoundTrip(req *http.Request) (*http.Response, er func TestCustomTransport(t *testing.T) { assert := assert.New(t) - receiver := mockDatadogAPINewServer(t) - defer receiver.Close() - - parsedURL, err := url.Parse(receiver.URL) - assert.NoError(err) - host := parsedURL.Host - _, port, err := net.SplitHostPort(host) - assert.Nil(err) - assert.NotEmpty(port, "port should be given, as it's chosen randomly") + var hits int + srv := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { + hits++ + })) + defer srv.Close() - customRoundTripper := newRecordingRoundTripper(defaultClient) - transport := newHTTPTransport(host, &http.Client{Transport: customRoundTripper}) + crt := newRecordingRoundTripper(defaultClient) + transport := newHTTPTransport(strings.TrimPrefix(srv.URL, "http://"), &http.Client{ + Transport: crt, + }) p, err := encode(getTestTrace(1, 1)) assert.NoError(err) _, err = transport.send(p) assert.NoError(err) // make sure our custom round tripper was used - assert.Len(customRoundTripper.reqs, 1) + assert.Len(crt.reqs, 1) + assert.Equal(hits, 1) } func TestWithHTTPClient(t *testing.T) { os.Setenv("DD_TRACE_STARTUP_LOGS", "0") defer os.Unsetenv("DD_TRACE_STARTUP_LOGS") assert := assert.New(t) - srv := mockDatadogAPINewServer(t) + var hits int + srv := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { + hits++ + })) defer srv.Close() u, err := url.Parse(srv.URL) @@ -264,19 +244,30 @@ func TestWithHTTPClient(t *testing.T) { assert.NoError(err) _, err = trc.config.transport.send(p) assert.NoError(err) - assert.Len(rt.reqs, 1) + assert.Len(rt.reqs, 2) + assert.Contains(rt.reqs[0].URL.Path, "/info") + assert.Contains(rt.reqs[1].URL.Path, "/traces") + assert.Equal(hits, 2) } func TestWithUDS(t *testing.T) { os.Setenv("DD_TRACE_STARTUP_LOGS", "0") defer os.Unsetenv("DD_TRACE_STARTUP_LOGS") assert := assert.New(t) - udsPath := "/tmp/com.datadoghq.dd-trace-go.tracer.test.sock" + dir, err := ioutil.TempDir("", "socket") + if err != nil { + t.Fatal(err) + } + udsPath := filepath.Join(dir, "apm.socket") + defer os.RemoveAll(udsPath) unixListener, err := net.Listen("unix", udsPath) if err != nil { t.Fatal(err) } - srv := &http.Server{Handler: mockDatadogAPIHandler{t: t}} + var hits int + srv := http.Server{Handler: http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { + hits++ + })} go srv.Serve(unixListener) defer srv.Close() @@ -290,5 +281,6 @@ func TestWithUDS(t *testing.T) { assert.NoError(err) _, err = trc.config.transport.send(p) assert.NoError(err) - assert.Len(rt.reqs, 1) + assert.Len(rt.reqs, 2) + assert.Equal(hits, 2) } diff --git a/go.mod b/go.mod index c14cb50b33..da807f3d36 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,16 @@ module gopkg.in/DataDog/dd-trace-go.v1 go 1.12 require ( - github.com/DataDog/datadog-go v4.4.0+incompatible + github.com/DataDog/datadog-agent/pkg/obfuscate v0.0.0-20211129110424-6491aa3bf583 + github.com/DataDog/datadog-go/v5 v5.0.2 github.com/DataDog/gostackparse v0.5.0 github.com/DataDog/sketches-go v1.0.0 github.com/google/pprof v0.0.0-20210423192551-a2663126120b github.com/google/uuid v1.3.0 + github.com/philhofer/fwd v1.1.1 // indirect github.com/stretchr/testify v1.7.0 github.com/tinylib/msgp v1.1.2 + golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 + google.golang.org/protobuf v1.25.0 ) diff --git a/go.sum b/go.sum index cdd1f53fe0..a198bbe303 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,4 @@ -cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= @@ -11,14 +9,23 @@ cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6T cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0 h1:WRz29PgAsVEyPSDHyk+0fpEkwEFyfhHn+JbksT6gIL4= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.61.0 h1:NLQf5e1OMspfNT1RAHOB3ublr1TW3YTXO8OiWwVjK2U= -cloud.google.com/go v0.61.0/go.mod h1:XukKJg4Y7QsUu0Hxg3qQKUWR4VuWivmyMK2+rUyxAqw= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0 h1:3DXvAyifywvq64LfkKaMOmkWPS1CikIQdMe2lY9vxU8= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -27,55 +34,97 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/kms v1.1.0 h1:1yc4rLqCkVDS9Zvc7m+3mJ47kw0Uo5Q5+sMjcmUVUeM= +cloud.google.com/go/kms v1.1.0/go.mod h1:WdbppnCDMDpOvoYBMn1+gNmOeEoZYqAv+HeuKARGCXI= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/pubsub v1.6.1 h1:lhCQrTgu7f5SjWm5yJO0geSsPORQ2OAD+Eq1AMyBW8Y= -cloud.google.com/go/pubsub v1.6.1/go.mod h1:kvW9rcn9OLEx6eTIzMBbWbpB8YsK3vu9jxgPolVz+p4= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= 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/DataDog/datadog-go v3.7.1+incompatible h1:HmA9qHVrHIAqpSvoCYJ+c6qst0lgqEhNW6/KwfkHbS8= -github.com/DataDog/datadog-go v3.7.1+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/DataDog/datadog-go v4.0.0+incompatible h1:Dq8Dr+4sV1gBO1sHDWdW+4G+PdsA+YSJOK925MxrrCY= -github.com/DataDog/datadog-go v4.0.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/DataDog/datadog-go v4.4.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/datadog-agent/pkg/obfuscate v0.0.0-20211129110424-6491aa3bf583 h1:3nVO1nQyh64IUY6BPZUpMYMZ738Pu+LsMt3E0eqqIYw= +github.com/DataDog/datadog-agent/pkg/obfuscate v0.0.0-20211129110424-6491aa3bf583/go.mod h1:EP9f4GqaDJyP1F5jTNMtzdIpw3JpNs3rMSJOnYywCiw= +github.com/DataDog/datadog-go v4.8.2+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/datadog-go/v5 v5.0.2 h1:UFtEe7662/Qojxkw1d6SboAeA0CPI3naKhVASwFn+04= +github.com/DataDog/datadog-go/v5 v5.0.2/go.mod h1:ZI9JFB4ewXbw1sBnF4sxsR2k1H3xjV+PUAOUsHvKpcU= github.com/DataDog/gostackparse v0.5.0 h1:jb72P6GFHPHz2W0onsN51cS3FkaMDcjb0QzgxxA4gDk= github.com/DataDog/gostackparse v0.5.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM= +github.com/DataDog/sketches-go v1.0.0 h1:chm5KSXO7kO+ywGWJ0Zs6tdmWU8PBXSbywFVciL6BG4= github.com/DataDog/sketches-go v1.0.0/go.mod h1:O+XkJHWk9w4hDwY2ZUDU31ZC9sNYlYo8DiFsxjYeo1k= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= +github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/Shopify/sarama v1.26.4 h1:+17TxUq/PJEAfZAll0T7XJjSgQWCpaQSoki/x5yN8o8= -github.com/Shopify/sarama v1.26.4/go.mod h1:NbSGBSSndYaIhRcBtY9V0U7AyH+x71bG668AuWys/yU= -github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5 h1:P5U+E4x5OkVEKQDklVPmzs71WM56RTTRqV4OrDC//Y4= -github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro= -github.com/apex/log v1.9.0 h1:FHtw/xuaM8AgmvDDTI9fiwoAL25Sq2cxojnZICUU8l0= -github.com/apex/log v1.9.0/go.mod h1:m82fZlWIuiWzWP04XCTXmnX0xRkYYbCdYn8jbJeLBEA= -github.com/apex/logs v1.0.0/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo= -github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE= -github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/sarama v1.30.0 h1:TOZL6r37xJBDEMLx4yjB77jxbZYXPaDow08TSK6vIL0= +github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fTKCulPVs= +github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae h1:ePgznFqEG1v3AjMklnK8H7BSc++FDSo7xfK9K7Af+0Y= +github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0= +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/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E= +github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.9 h1:O2sNqxBdvq8Eq5xmzljcYzAORli6RWCvEym4cJf9m18= +github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.29.11/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg= -github.com/aws/aws-sdk-go v1.31.9 h1:n+b34ydVfgC30j0Qm69yaapmjejQPW2BoDBX7Uy/tLI= -github.com/aws/aws-sdk-go v1.31.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/aws/aws-sdk-go v1.31.10 h1:33jOMifUSdOP9pvNEOj+PGwljzunc8bJvKKNF/JuGzo= -github.com/aws/aws-sdk-go v1.31.10/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= +github.com/aws/aws-sdk-go v1.42.14 h1:Eqwjl/cwRY9z0TTU4RGpiElWo9oPzG+Y8r5Thu6Ug5A= +github.com/aws/aws-sdk-go v1.42.14/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= +github.com/aws/aws-sdk-go-v2 v1.11.1 h1:GzvOVAdTbWxhEMRK4FfiblkGverOkAT0UodDxC1jHQM= +github.com/aws/aws-sdk-go-v2 v1.11.1/go.mod h1:SQfA+m2ltnu1cA0soUkj4dRSsmITiVQUJvBIZjzfPyQ= +github.com/aws/aws-sdk-go-v2/config v1.10.2 h1:lrNnqRpPDgrozyKMnt5/Bhcv01kel7JO6KFx4VdroCY= +github.com/aws/aws-sdk-go-v2/config v1.10.2/go.mod h1:OY1jfuHozx6GDg+NITKNukVQi4fLlnenu1PAbDJg5fk= +github.com/aws/aws-sdk-go-v2/credentials v1.6.2 h1:2faRNX8JgZVy7dDxERkaGBqb/xo5Rgmc8JMPL5j1o58= +github.com/aws/aws-sdk-go-v2/credentials v1.6.2/go.mod h1:8kRH9fthlxHEeNJ3g1N3NTSUMBba+KtTM8hp6SvUWn8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.1 h1:pXwGBINU30CsjYztV/IyCgA7QKp99Q8wM4Gb0Ls3rB0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.1/go.mod h1:MYiG3oeEcmrdBOV7JOIWhionzyRZJWCnByS5FmvhAoU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.1 h1:LZwqhOyqQ2w64PZk04V0Om9AEExtW8WMkCRoE1h9/94= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.1/go.mod h1:22SEiBSQm5AyKEjoPcG1hzpeTI+m9CXfE6yt1h49wBE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.1 h1:ObMfGNk0xjOWduPxsrRWVwZZia3e9fOcO6zlKCkt38s= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.1/go.mod h1:1xvCD+I5BcDuQUc+psZr7LI1a9pclAWZs3S3Gce5+lg= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.1 h1:fdQSN/ieDwbxdj7ptvFKjS2cS2a91l/WdjacCt5GgTE= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.1/go.mod h1:5eEM4wZ6I2GaeOaVXsiJexIH4P1sFnK5Yp2Tlw9Ah3c= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.1 h1:ZFSfgetO5kf4WXy+a2B8zug6DXGUYjsWacyvwx5cgXU= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.1/go.mod h1:fEaHB2bi+wVZw4uKMHEXTL9LwtT4EL//DOhTeflqIVo= +github.com/aws/aws-sdk-go-v2/service/sqs v1.12.1 h1:t76IPhbZRQdnPBMPIg1IWI/rZyNuWsXMrRdvHkOCx0s= +github.com/aws/aws-sdk-go-v2/service/sqs v1.12.1/go.mod h1:yKe1+YpZtnwz7h6juYHGg3ZfNMv8qbjexS6sUcPn3yY= +github.com/aws/aws-sdk-go-v2/service/sso v1.6.1 h1:NF/qN6e8hdHO/Pt5jN+S65dxFom3b8+ciVdyv8Jr00U= +github.com/aws/aws-sdk-go-v2/service/sso v1.6.1/go.mod h1:/73aFBwUl60wKBKhdth2pEOkut5ZNjVHGF9hjXz0bM0= +github.com/aws/aws-sdk-go-v2/service/sts v1.10.1 h1:2DKYFOmC7d3WOzdBTFJxfkcMXVVIgcitrpEoJDUKlN4= +github.com/aws/aws-sdk-go-v2/service/sts v1.10.1/go.mod h1:+BmlPeQ1Y+PuIho93MMKDby12PoUnt1SZXQdEHCzSlw= +github.com/aws/smithy-go v1.9.0 h1:c7FUdEqrQA1/UVKKCNDFQPNKGp4FQg3YW4Ck5SLTG58= +github.com/aws/smithy-go v1.9.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= @@ -83,123 +132,172 @@ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4Yn github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= +github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= 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/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= 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/confluentinc/confluent-kafka-go v1.4.2 h1:13EK9RTujF7lVkvHQ5Hbu6bM+Yfrq8L0MkJNnjHSd4Q= -github.com/confluentinc/confluent-kafka-go v1.4.2/go.mod h1:u2zNLny2xq+5rWeTQjFHbDzzNuba4P1vo31r9r4uAdg= +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 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158 h1:CevA8fI91PAnP8vpnXuB8ZYAZ5wqY86nAbxfgK8tWO4= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/confluentinc/confluent-kafka-go v1.7.0 h1:tXh3LWb2Ne0WiU3ng4h5qiGA9XV61rz46w60O+cq8bM= +github.com/confluentinc/confluent-kafka-go v1.7.0/go.mod h1:u2zNLny2xq+5rWeTQjFHbDzzNuba4P1vo31r9r4uAdg= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 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/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= +github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q= github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/elastic/go-elasticsearch/v6 v6.8.10 h1:2lN0gJ93gMBXvkhwih5xquldszpm8FlUwqG5sPzr6a8= +github.com/elastic/go-elasticsearch/v6 v6.8.10/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI= +github.com/elastic/go-elasticsearch/v7 v7.15.1 h1:Wd8RLHb5D8xPBU8vGlnLXyflkso9G+rCmsXjqH8LLQQ= +github.com/elastic/go-elasticsearch/v7 v7.15.1/go.mod h1:OJ4wdbtDNk5g503kvlHLyErCgQwwzmDtaFC4XyOxXA4= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.12.0+incompatible h1:SIvoTSbsMEwuM3dzFirLwKc4BH6VXP5CNf+G1FfJVr4= -github.com/emicklei/go-restful v2.12.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.15.0+incompatible h1:8KpYO/Xl/ZudZs5RNOEhWMBY4hmzlZhhRd9cu+jrZP4= +github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 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/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021 h1:fP+fF0up6oPY49OrjPrhIJ8yQfdIM85NXMLkMg1EXVs= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= -github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.5.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/frankban/quicktest v1.7.2 h1:2QxQoC1TS09S7fhCPsrvqYdvP1H5M1P1ih5ABm3BTYk= -github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= +github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc= -github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +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/garyburd/redigo v1.6.3 h1:HCeeRluvAgMusMomi1+6Y5dmFOdYV/JzoRrrbFlkGIc= +github.com/garyburd/redigo v1.6.3/go.mod h1:rTb6epsqigu3kYKBnaF028A7Tf/Aw5s0cqA47doKKqw= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= -github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= +github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= -github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= +github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg= +github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= +github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= +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/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q= +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-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-pg/pg/v10 v10.10.6 h1:1vNtPZ4Z9dWUw/TjJwOfFUbF5nEq1IkR6yG8Mq/Iwso= +github.com/go-pg/pg/v10 v10.10.6/go.mod h1:GLmFXufrElQHf5uzM3BQlcfwV3nsgnHue5uzjQ6Nqxg= +github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU= +github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/go-redis/redis v6.15.8+incompatible h1:BKZuG6mCnRj5AOaWJXoCgf6rqTYnYJLe4en2hxT7r9o= -github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= +github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOrTI= +github.com/go-redis/redis/v7 v7.4.1/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= +github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= +github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= -github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= -github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= -github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= -github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= -github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= -github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= -github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= -github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= -github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= -github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= -github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= -github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= -github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= -github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= -github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= -github.com/gocql/gocql v0.0.0-20200526081602-cd04bd7f22a7 h1:TvUE5vjfoa7fFHMlmGOk0CsauNj1w4yJjR9+/GnWVCw= -github.com/gocql/gocql v0.0.0-20200526081602-cd04bd7f22a7/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= -github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= +github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/gocql/gocql v0.0.0-20211015133455-b225f9b53fa1 h1:px9qUCy/RNJNsfCam4m2IxWGxNuimkrioEF0vrrbPsg= +github.com/gocql/gocql v0.0.0-20211015133455-b225f9b53fa1/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8= +github.com/gofiber/fiber/v2 v2.22.0 h1:+iyKK4ooDH6z0lAHdaWO1AFIB/DZ9AVo6vz8VZIA0EU= +github.com/gofiber/fiber/v2 v2.22.0/go.mod h1:MR1usVH3JHYRyQwMe2eZXRSZHRX38fkV+A7CPB+DlDQ= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 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-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/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= @@ -207,7 +305,8 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 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= @@ -220,37 +319,44 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gomodule/redigo v1.8.1 h1:Abmo0bI7Xf0IhdIPc7HZQzZcShdnmxeoVuDDtIQp8N8= -github.com/gomodule/redigo v1.8.1/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= -github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= -github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= -github.com/gomodule/redigo/redis v0.0.0-do-not-use h1:J7XIp6Kau0WoyT4JtXHT3Ei0gA1KkSc6bc87j9v9WIo= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v1.8.5 h1:nRAxCa+SVsyjSBrtZmG/cqb6VbTmuRzpg/PoTFlpumc= +github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= 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/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -258,57 +364,82 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20210125172800-10e9aeb4a998/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210423192551-a2663126120b h1:l2YRhr+YLzmSp7KJMswRVk/lO5SwoFIcCLzJsVj+YPc= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210423192551-a2663126120b/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/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.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.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 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.1.0 h1:rVsPeBmXbYv4If/cumu1AzZPwV58q433hvONV1UEZoI= -github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= -github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= -github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/graph-gophers/graphql-go v0.0.0-20200309224638-dae41bde9ef9 h1:kLnsdud6Fl1/7ZX/5oD23cqYAzBfuZBhNkGr2NvuEsU= -github.com/graph-gophers/graphql-go v0.0.0-20200309224638-dae41bde9ef9/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/graph-gophers/graphql-go v1.2.0 h1:j3tCG0UcE+3f84OAw/4/6YQKyTr+r0yuUKtnxiu5OH4= +github.com/graph-gophers/graphql-go v1.2.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= -github.com/hashicorp/consul v1.7.3 h1:b33lARUsnuz0I5VapThpf7TMIirD4Co2X0UtZIax/+k= -github.com/hashicorp/consul/api v1.4.0 h1:jfESivXnO5uLdH650JU/6AnjRoHrLhULq0FnC3Kp9EY= -github.com/hashicorp/consul/api v1.4.0/go.mod h1:xc8u05kyMa3Wjr9eEAsIAo3dg8+LywT5E/Cl7cNS5nU= -github.com/hashicorp/consul/sdk v0.4.0 h1:zBtCfKJZcJDBvSCkQJch4ulp59m1rATFLKwNo/LYY30= -github.com/hashicorp/consul/sdk v0.4.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/consul/api v1.11.0 h1:Hw/G8TtRvOElqxVIhBzXciiSTbapq8hZ2XKZsXk5ZCE= +github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/sdk v0.8.0 h1:OJtKBtEjboEZvG6AOUdh4Z1Zbyu0WcxQ0qatRrZHTVU= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= -github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v0.12.0 h1:d4QkX8FRTYaKaCZBoXYY8zJX2BXjWxurN/GA2tkrmZM= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.16.2 h1:K4ev2ib4LdQETX5cSZBG0DVLk1jwGqSPXBjdah3veNs= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g= github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= -github.com/hashicorp/go-retryablehttp v0.5.4 h1:1BZvpawXoJCWX6pNtow9+rpEj+3itIlutiqnntI6jOE= -github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= +github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= +github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= +github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 h1:cCRo8gK7oq6A2L6LICkUZ+/a5rLiRXFMf1Qd4xSwxTc= +github.com/hashicorp/go-secure-stdlib/mlock v0.1.1/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 h1:78ki3QBevHwYrVxnyVeaEz+7WtifHhauYF23es/0KlI= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/password v0.1.1/go.mod h1:9hH302QllNwu1o2TGYtSk8I8kTAN0ca1EHpwhm5Mmzo= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 h1:nd0HIW15E6FG1MsnArYaHfuw9C2zgzM8LxkG5Ty/788= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= +github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1/go.mod h1:l8slYwnJA26yBz+ErHpp2IRCLr0vuOMGBORIz4rRiAs= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= @@ -317,34 +448,82 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/vault v1.4.2 h1:KnAPBTb4G7JidQiUXVDk3+LPp+iWPMbMsGmw4POJI4k= -github.com/hashicorp/vault/api v1.0.4 h1:j08Or/wryXT4AcHj1oCbMd7IijXcKzYUGw59LGu9onU= -github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= -github.com/hashicorp/vault/sdk v0.1.13 h1:mOEPeOhT7jl0J4AMl1E705+BcmeRs1VmKNb9F0sMLy8= -github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC7AO2g= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.9.5 h1:EBWvyu9tcRszt3Bxp3KNssBMP1KuHWyO51lz9+786iM= +github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/vault/api v1.3.0 h1:uDy39PLSvy6gtKyjOCRPizy2QdFiIYSWBR2pxCEzYL8= +github.com/hashicorp/vault/api v1.3.0/go.mod h1:EabNQLI0VWbWoGlA+oBLC8PXmR9D60aUVgQGvangFWQ= +github.com/hashicorp/vault/sdk v0.3.0 h1:kR3dpxNkhh/wr6ycaJYqp6AFT/i2xaftbfnwZduTKEY= +github.com/hashicorp/vault/sdk v0.3.0/go.mod h1:aZ3fNuL5VNydQk8GcLJ2TV8YCRVvyaakYkhZRoVuhj0= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= -github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.10.1 h1:DzdIHIjG1AxGwoEEqS+mGsURyjt4enSmqzACXvVzOT8= +github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns= +github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.9.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.9.1 h1:MJc2s0MFS8C3ok1wQTdQxWuXQcB6+HwAm5x1CzW7mf0= +github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.14.0/go.mod h1:jT3ibf/A0ZVCp89rtCIN0zCJxcE74ypROmHEZYsG/j8= +github.com/jackc/pgx/v4 v4.14.1 h1:71oo1KAGI6mXhLiTMn6iDFcp3e7+zon/capWjl2OEFU= +github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= @@ -353,93 +532,104 @@ github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= -github.com/jcmturner/gokrb5/v8 v8.2.0 h1:lzPl/30ZLkTveYsYZPKMcgXc8MbnE6RsTd4F9KgiLtk= -github.com/jcmturner/gokrb5/v8 v8.2.0/go.mod h1:T1hnNppQsBtxW0tCHMHTkAt8n/sABdzZgZdoFrZaZNM= -github.com/jcmturner/rpc/v2 v2.0.2 h1:gMB4IwRXYsWw4Bc6o/az2HJgFUA1ffSh90i26ZJ6Xl0= -github.com/jcmturner/rpc/v2 v2.0.2/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= -github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q= -github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs= +github.com/jcmturner/gokrb5/v8 v8.4.2 h1:6ZIM6b/JJN0X8UM43ZOM6Z4SJzla+a/u7scXFJzodkA= +github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= +github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= +github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI= +github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= -github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= -github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5 h1:lrdPtrORjGv1HbbEvKWDUAy97mPpFm4B8hp77tcCUJY= -github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= +github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= -github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= -github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.8 h1:VMAMUUOh+gaxKTMk+zqbjsSjsIcUcL/LF4o63i82QyA= -github.com/klauspost/compress v1.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 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.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= +github.com/labstack/echo/v4 v4.6.1 h1:OMVsrnNFzYlGSdaiYGHbgWQnr+JM7NG+B9suCPie14M= +github.com/labstack/echo/v4 v4.6.1/go.mod h1:RnjgMWNDB9g/HucVWhQYNQP9PvbYf6adqftqryo7s9k= github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.6.0 h1:I5DPxhYJChW9KYc66se+oKFFQX6VuQrKiprsX6ivRZc= -github.com/lib/pq v1.6.0/go.mod h1:4vXEAYvW1fRQ2/FhZ78H73A60MHw1geSm145z2mdY1g= -github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20180730094502-03f2033d19d5/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= +github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= -github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= -github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA= +github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg= -github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -447,12 +637,14 @@ github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go. github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 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.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= +github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -461,168 +653,227 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olivere/elastic/v7 v7.0.12/go.mod h1:14rWX28Pnh3qCKYRVnSGXWLf9MbLonYS/4FDCY3LAPo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= -github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= -github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= +github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= -github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= -github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= -github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pierrec/lz4 v2.4.1+incompatible h1:mFe7ttWaflA46Mhqh+jUfjp2qTbPYxLB2/OyBppH9dg= -github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ= +github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= +github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/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/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 h1:dY6ETXrvDG7Sa4vE8ZQG4yqWg6UnOcbqTAahkV813vQ= -github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +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/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +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/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +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.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= -github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs= github.com/smartystreets/gunit v1.1.3/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ= -github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/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/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 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/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho= -github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= -github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E= -github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= -github.com/tidwall/buntdb v1.1.2 h1:noCrqQXL9EKMtcdwJcmuVKSEjqu1ua99RHHgbLTEHRo= -github.com/tidwall/buntdb v1.1.2/go.mod h1:xAzi36Hir4FarpSHyfuZ6JzPJdjRZ8QlLZSntE2mqlI= -github.com/tidwall/gjson v1.3.4 h1:On5waDnyKKk3SWE4EthbjjirAWXp43xx5cKCUZY1eZw= -github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= -github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb h1:5NSYaAdrnblKByzd7XByQEJVT8+9v0W/tIY0Oo4OwrE= -github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M= -github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= -github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= -github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI= +github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8= +github.com/tidwall/btree v0.6.1 h1:75VVgBeviiDO+3g4U+7+BaNBNhNINxB0ULPT3fs9pMY= +github.com/tidwall/btree v0.6.1/go.mod h1:TzIRzen6yHbibdSfK6t8QimqbUnoxUSrZfeW7Uob0q4= +github.com/tidwall/buntdb v1.2.7 h1:SIyObKAymzLyGhDeIhVk2Yc1/EwfCC75Uyu77CHlVoA= +github.com/tidwall/buntdb v1.2.7/go.mod h1:b6KvZM27x/8JLI5hgRhRu60pa3q0Tz9c50TyD46OHUM= +github.com/tidwall/gjson v1.10.2 h1:APbLGOM0rrEkd8WBw9C24nllro4ajFuJu0Sc9hRz8Bo= +github.com/tidwall/gjson v1.10.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/grect v0.1.3 h1:z9YwQAMUxVSBde3b7Sl8Da37rffgNfZ6Fq6h9t6KdXE= +github.com/tidwall/grect v0.1.3/go.mod h1:8GMjwh3gPZVpLBI/jDz9uslCe0dpxRpWDdtN0lWAS/E= +github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8= +github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e h1:+NL1GDIUOKxVfbp2KoJQD9cTQ6dyP2co9q4yzmT9FZo= -github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e/go.mod h1:/h+UnNGt0IhNNJLkGikcdcJqm66zGD/uJGMRxK/9+Ao= -github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 h1:Otn9S136ELckZ3KKDyCkxapfufrqDqwmGjcHfAyXRrE= -github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8= +github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ= +github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE= +github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw= github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ= github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= -github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0= -github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= -github.com/tj/go-buffer v1.1.0/go.mod h1:iyiJpfFcR2B9sXu7KvjbT9fpM4mOelRSDTbntVj52Uc= -github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0= -github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao= -github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= -github.com/twitchtv/twirp v5.11.0+incompatible h1:hO4u9Yep59kTN1JhYjM+cpELT2mqjTf+xMi21UOPXQc= -github.com/twitchtv/twirp v5.11.0+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/twitchtv/twirp v8.1.0+incompatible h1:KGXanpa9LXdVE/V5P/tA27rkKFmXRGCtSNT7zdeeVOY= +github.com/twitchtv/twirp v8.1.0+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= +github.com/valyala/fasthttp v1.31.0 h1:lrauRLII19afgCs2fnWRJ4M5IkV0lo2FqA61uGkNBfE= +github.com/valyala/fasthttp v1.31.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= -github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= -github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo= -github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= -github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= -github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94= +github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ= +github.com/vmihailenco/msgpack/v5 v5.3.4 h1:qMKAwOV+meBw2Y8k9cVwAy7qErtYCwBzZ2ellBfvnqc= +github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc= +github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w= +github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= +github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc= +github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/zenazn/goji v0.9.1-0.20160507202103-64eb34159fe5 h1:mXV20Aj/BdWrlVzIn1kXFa+Tq62INlUi0cFFlztTaK0= -github.com/zenazn/goji v0.9.1-0.20160507202103-64eb34159fe5/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.mongodb.org/mongo-driver v1.3.3 h1:9kX7WY6sU/5qBuhm5mdnNWdqaDAQKB2qSZOd5wMEPGQ= -go.mongodb.org/mongo-driver v1.3.3/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= -go.mongodb.org/mongo-driver v1.3.4 h1:zs/dKNwX0gYUtzwrN9lLiR15hCO0nDwQj5xXx+vjCdE= -go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.mongodb.org/mongo-driver v1.8.0 h1:R/P/JJzu8LJvJ1lDfph9GLNIKQxEtIHFfnUUUve35zY= +go.mongodb.org/mongo-driver v1.8.0/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= 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.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +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/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.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/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72 h1:+ELyKg6m8UBf0nPFSqD0mi7zUfwPyXo23HNjMnXPz7w= -golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA= -golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 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= @@ -644,24 +895,27 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 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/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-20181201002055-351d144fa1fc/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-20190108225652-1e06a53dbb7e/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= @@ -669,11 +923,13 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/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-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -684,57 +940,82 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210917221730-978cfadd31cf h1:R150MpwJIv1MpS0N/pc+NhTM8ajzvlmxlY5OYsrevXQ= +golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 h1:B333XXssMuKQeBwiNODx4TupZy7bf4sxFZnN2ZOcvUE= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/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-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -747,48 +1028,74 @@ golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y= -golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/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/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210923061019-b8560ed6a9b7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 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/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M= +golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -796,16 +1103,20 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -816,19 +1127,30 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4 h1:kDtqNkeBrZb8B+atrj50B5XLHpzXXqcCdZPP/ApQ5NY= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200725200936-102e7d357031 h1:VtIxiVHWPhnny2ZTi4f9/2diZKqyLaq3FUTuud5+khA= -golang.org/x/tools v0.0.0-20200725200936-102e7d357031/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200828161849-5deb26317202/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= @@ -848,32 +1170,40 @@ google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/ google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.26.0 h1:VJZ8h6E8ip82FRpQl848c5vAadxlTXrUh8RzQzSRm08= -google.golang.org/api v0.26.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0 h1:BaiDisFir8O4IJxvAabCGGkQ6yCJegNQqSVoYUNAnbk= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.31.0 h1:1w5Sz/puhxFo9lTtip2n47k7toB/U2nCqOKNHd3Yrbo= -google.golang.org/api v0.31.0/go.mod h1:CL+9IBCa2WWU6gRuBWaKqGWLFFwbEUXkfeMkHLQWYWo= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E= +google.golang.org/api v0.60.0 h1:eq/zs5WPH4J9undYM9IP1O7dSr7Yh8Y0GtSCpzGzIUk= +google.golang.org/api v0.60.0/go.mod h1:d7rl65NZAkEQ90JFzqBjcRq1TVeG5ZoGV3sSpEnnVb4= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= 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= -google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 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-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= 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= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= @@ -888,125 +1218,160 @@ google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940 h1:MRHtG0U6SnaUb+s+LhNE1qt1FQ1wlhqr5E4usBKC0uA= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200726014623-da3ae01ef02d h1:HJaAqDnKreMkv+AQyf1Mcw0jEmL9kKBNL07RDJu1N/k= -google.golang.org/genproto v0.0.0-20200726014623-da3ae01ef02d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200831141814-d751682dd103 h1:z46CEPU+LlO0kGGwrH8h5epkkJhRZbAHYWOWD9JhLPI= -google.golang.org/genproto v0.0.0-20200831141814-d751682dd103/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211018162055-cf77aa76bad2/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211021150943-2b146023228c h1:FqrtZMB5Wr+/RecOM3uPJNPfWR8Upb5hAPnt7PU6i4k= +google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0= 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.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -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.30.0 h1:M5a8xTlYTxwMn5ZFkwhRabsygDY5G8TYLyQDBxJNAxE= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= -google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E= +google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= 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= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/confluentinc/confluent-kafka-go.v1 v1.4.2 h1:JabkIV98VYFqYKHHzXtgGMFuRgFBNTNzBytbGByzrJI= -gopkg.in/confluentinc/confluent-kafka-go.v1 v1.4.2/go.mod h1:ZdI3yfYmdNSLQPNCpO1y00EHyWaHG5EnQEyL/ntAegY= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw= -gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= -gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM= -gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= -gopkg.in/jcmturner/goidentity.v3 v3.0.0 h1:1duIyWiTaYvVx3YX2CYtpJbUFd7/UuPYCfgXtQ3VTbI= -gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= -gopkg.in/jcmturner/gokrb5.v7 v7.5.0 h1:a9tsXlIDD9SKxotJMK3niV7rPZAJeX2aD/0yg3qlIrg= -gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= -gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU= -gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= +gopkg.in/jinzhu/gorm.v1 v1.9.2 h1:sTqyEcgrxG68jdeUXA9syQHNdeRhhfaYZ+vcL3x730I= +gopkg.in/jinzhu/gorm.v1 v1.9.2/go.mod h1:56JJPUzbikvTVnoyP1nppSkbJ2L8sunqTBDY2fDrmFg= gopkg.in/olivere/elastic.v3 v3.0.75 h1:u3B8p1VlHF3yNLVOlhIWFT3F1ICcHfM5V6FFJe6pPSo= gopkg.in/olivere/elastic.v3 v3.0.75/go.mod h1:yDEuSnrM51Pc8dM5ov7U8aI/ToR3PG0llA8aRv2qmw0= -gopkg.in/olivere/elastic.v5 v5.0.84 h1:acF/tRSg5geZpE3rqLglkS79CQMIMzOpWZE7hRXIkjs= -gopkg.in/olivere/elastic.v5 v5.0.84/go.mod h1:LXF6q9XNBxpMqrcgax95C6xyARXWbbCXUrtTxrNrxJI= -gopkg.in/olivere/elastic.v5 v5.0.85 h1:GwBqEsvRIHVfCQVXDHYi9LHec2yEkc3GNKh9WB8G/es= -gopkg.in/olivere/elastic.v5 v5.0.85/go.mod h1:M3WNlsF+WhYn7api4D87NIflwTV/c0iVs8cqfWhK+68= -gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/olivere/elastic.v5 v5.0.86 h1:xFy6qRCGAmo5Wjx96srho9BitLhZl2fcnpuidPwduXM= +gopkg.in/olivere/elastic.v5 v5.0.86/go.mod h1:M3WNlsF+WhYn7api4D87NIflwTV/c0iVs8cqfWhK+68= +gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.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/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-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo= -gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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= +gorm.io/driver/mysql v1.2.0 h1:l8+9VwjjyzEkw0PNPBOr2JHhLOGVk7XEnl5hk42bcvs= +gorm.io/driver/mysql v1.2.0/go.mod h1:4RQmTg4okPghdt+kbe6e1bTXIQp7Ny1NnBn/3Z6ghjk= +gorm.io/driver/postgres v1.2.3 h1:f4t0TmNMy9gh3TU2PX+EppoA6YsgFnyq8Ojtddb42To= +gorm.io/driver/postgres v1.2.3/go.mod h1:pJV6RgYQPG47aM1f0QeOzFH9HxQc8JcmAgjRCgS0wjs= +gorm.io/gorm v1.22.3 h1:/JS6z+GStEQvJNW3t1FTwJwG/gZ+A7crFdRqtvG5ehA= +gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= 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= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.18.3 h1:2AJaUQdgUZLoDZHrun21PW2Nx9+ll6cUzvn3IKhSIn0= -k8s.io/api v0.18.3/go.mod h1:UOaMwERbqJMfeeeHc8XJKawj4P9TgDRnViIqqBeH2QA= -k8s.io/apimachinery v0.18.3 h1:pOGcbVAhxADgUYnjS08EFXs9QMl8qaH5U4fr5LGUrSk= -k8s.io/apimachinery v0.18.3/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= -k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o= -k8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= -k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= -k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/api v0.22.4 h1:UvyHW0ezB2oIgHAxlYoo6UJQObYXU7awuNarwoHEOjw= +k8s.io/api v0.22.4/go.mod h1:Rgs+9gIGYC5laXQSZZ9JqT5NevNgoGiOdVWi1BAB3qk= +k8s.io/apimachinery v0.22.4 h1:9uwcvPpukBw/Ri0EUmWz+49cnFtaoiyEhQTK+xOe7Ck= +k8s.io/apimachinery v0.22.4/go.mod h1:yU6oA6Gnax9RrxGzVvPFFJ+mpnW6PBSqp0sx0I0HHW0= +k8s.io/client-go v0.22.4 h1:aAQ1Wk+I3bjCNk35YWUqbaueqrIonkfDPJSPDDe8Kfg= +k8s.io/client-go v0.22.4/go.mod h1:Yzw4e5e7h1LNHA4uqnMVrpEpUs1hJOiuBsJKIlRCHDA= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= -k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 h1:v8ud2Up6QK1lNOKFgiIVrZdMg7MpmSnvtrOieolJKoE= -k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= +k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a h1:8dYfu/Fc9Gz2rNJKB9IQRGgQOh2clmRzNIPPY1xLY5g= +k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +mellium.im/sasl v0.2.1 h1:nspKSRg7/SyO0cRGY71OkfHab8tf9kCts6a6oTDut0w= +mellium.im/sasl v0.2.1/go.mod h1:ROaEDLQNuf9vjKqE1SrAfnsobm2YKXT1gnN1uDp1PjQ= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/internal/appsec/api.go b/internal/appsec/api.go deleted file mode 100644 index 818d51d0bf..0000000000 --- a/internal/appsec/api.go +++ /dev/null @@ -1,303 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed -// under the Apache License Version 2.0. -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2016 Datadog, Inc. - -package appsec - -import ( - "fmt" - "net" - "sort" - "strings" - "time" - - "github.com/google/uuid" -) - -// Intake API payloads. -type ( - // eventBatch intake API payload. - eventBatch struct { - IdempotencyKey string `json:"idempotency_key"` - Events []*attackEvent `json:"events"` - } - - // attackEvent intake API payload. - attackEvent struct { - EventVersion string `json:"event_version"` - EventID string `json:"event_id"` - EventType string `json:"event_type"` - DetectedAt time.Time `json:"detected_at"` - Type string `json:"type"` - Blocked bool `json:"blocked"` - Rule attackRule `json:"rule"` - RuleMatch *attackRuleMatch `json:"rule_match"` - Context attackContext `json:"context"` - } - - // attackRule intake API payload. - attackRule struct { - ID string `json:"id"` - Name string `json:"name"` - } - - // attackRuleMatch intake API payload. - attackRuleMatch struct { - Operator string `json:"operator"` - OperatorValue string `json:"operator_value"` - Parameters []attackRuleMatchParameter `json:"parameters"` - Highlight []string `json:"highlight"` - } - - // attackRuleMatchParameter intake API payload. - attackRuleMatchParameter struct { - Name string `json:"name"` - Value string `json:"value"` - } - - // attackContext intake API payload. - attackContext struct { - Host attackContextHost `json:"host,omitempty"` - HTTP attackContextHTTP `json:"http"` - Service attackContextService `json:"service"` - Tags *attackContextTags `json:"tags,omitempty"` - Span attackContextSpan `json:"span"` - Trace attackContextTrace `json:"trace"` - Tracer attackContextTracer `json:"tracer"` - } - - // attackContextHost intake API payload. - attackContextHost struct { - ContextVersion string `json:"context_version"` - OsType string `json:"os_type"` - Hostname string `json:"hostname,omitempty"` - } - - // attackContextHTTP intake API payload. - attackContextHTTP struct { - ContextVersion string `json:"context_version"` - Request attackContextHTTPRequest `json:"request"` - Response attackContextHTTPResponse `json:"response"` - } - - // attackContextHTTPRequest intake API payload. - attackContextHTTPRequest struct { - Scheme string `json:"scheme"` - Method string `json:"method"` - URL string `json:"url"` - Host string `json:"host"` - Port int `json:"port"` - Path string `json:"path"` - Resource string `json:"resource,omitempty"` - RemoteIP string `json:"remote_ip"` - RemotePort int `json:"remote_port"` - Headers map[string]string `json:"headers"` - Parameters attackContextHTTPRequestParameters `json:"parameters,omitempty"` - } - - attackContextHTTPRequestParameters struct { - Query map[string][]string `json:"query,omitempty"` - } - - // attackContextHTTPResponse intake API payload. - attackContextHTTPResponse struct { - Status int `json:"status"` - } - - // attackContextService intake API payload. - attackContextService struct { - ContextVersion string `json:"context_version"` - Name string `json:"name,omitempty"` - Environment string `json:"environment,omitempty"` - Version string `json:"version,omitempty"` - } - - // attackContextTags intake API payload. - attackContextTags struct { - ContextVersion string `json:"context_version"` - Values []string `json:"values"` - } - - // attackContextTrace intake API payload. - attackContextTrace struct { - ContextVersion string `json:"context_version"` - ID string `json:"id"` - } - - // attackContextSpan intake API payload. - attackContextSpan struct { - ContextVersion string `json:"context_version"` - ID string `json:"id"` - } - - // attackContextTracer intake API payload. - attackContextTracer struct { - ContextVersion string `json:"context_version"` - RuntimeType string `json:"runtime_type"` - RuntimeVersion string `json:"runtime_version"` - LibVersion string `json:"lib_version"` - } -) - -// makeEventBatch returns the event batch of the given security events. -func makeEventBatch(events []*attackEvent) eventBatch { - id, _ := uuid.NewUUID() - return eventBatch{ - IdempotencyKey: id.String(), - Events: events, - } -} - -// newAttackEvent returns a new attack event payload. -func newAttackEvent(ruleID, ruleName, attackType string, at time.Time, match *attackRuleMatch) *attackEvent { - id, _ := uuid.NewUUID() - return &attackEvent{ - EventVersion: "0.1.0", - EventID: id.String(), - EventType: "appsec.threat.attack", - DetectedAt: at, - Type: attackType, - Rule: attackRule{ - ID: ruleID, - Name: ruleName, - }, - RuleMatch: match, - } -} - -// makeAttackContextTrace create an attackContextTrace payload. -func makeAttackContextTrace(traceID string) attackContextTrace { - return attackContextTrace{ - ContextVersion: "0.1.0", - ID: traceID, - } -} - -// makeAttackContextSpan create an attackContextSpan payload. -func makeAttackContextSpan(spanID string) attackContextSpan { - return attackContextSpan{ - ContextVersion: "0.1.0", - ID: spanID, - } -} - -// makeAttackContextHost create an attackContextHost payload. -func makeAttackContextHost(hostname string, os string) attackContextHost { - return attackContextHost{ - ContextVersion: "0.1.0", - OsType: os, - Hostname: hostname, - } -} - -// makeAttackContextTracer create an attackContextTracer payload. -func makeAttackContextTracer(version string, rt string, rtVersion string) attackContextTracer { - return attackContextTracer{ - ContextVersion: "0.1.0", - RuntimeType: rt, - RuntimeVersion: rtVersion, - LibVersion: version, - } -} - -// newAttackContextTags create an attackContextTags payload. -func newAttackContextTags(tags []string) *attackContextTags { - return &attackContextTags{ - ContextVersion: "0.1.0", - Values: tags, - } -} - -// makeServiceContext create an attackContextService payload. -func makeServiceContext(name, version, environment string) attackContextService { - return attackContextService{ - ContextVersion: "0.1.0", - Name: name, - Environment: environment, - Version: version, - } -} - -// makeAttackContextHTTP create an attackContextHTTP payload. -func makeAttackContextHTTP(req attackContextHTTPRequest, res attackContextHTTPResponse) attackContextHTTP { - return attackContextHTTP{ - ContextVersion: "0.1.0", - Request: req, - Response: res, - } -} - -// makeAttackContextHTTPResponse creates an attackContextHTTPResponse payload. -func makeAttackContextHTTPResponse(status int) attackContextHTTPResponse { - return attackContextHTTPResponse{ - Status: status, - } -} - -// splitHostPort splits a network address of the form `host:port` or -// `[host]:port` into `host` and `port`. As opposed to `net.SplitHostPort()`, -// it doesn't fail when there is no port number and returns the given address -// as the host value. -func splitHostPort(addr string) (host, port string) { - addr = strings.TrimSpace(addr) - host, port, err := net.SplitHostPort(addr) - if err == nil { - return - } - if l := len(addr); l >= 2 && addr[0] == '[' && addr[l-1] == ']' { - // ipv6 without port number - return addr[1 : l-1], "" - } - return addr, "" -} - -// List of HTTP headers we collect and send. -var collectedHTTPHeaders = [...]string{ - "host", - "x-forwarded-for", - "x-client-ip", - "x-real-ip", - "x-forwarded", - "x-cluster-client-ip", - "forwarded-for", - "forwarded", - "via", - "true-client-ip", - "content-length", - "content-type", - "content-encoding", - "content-language", - "forwarded", - "user-agent", - "accept", - "accept-encoding", - "accept-language", -} - -func init() { - // Required by sort.SearchStrings - sort.Strings(collectedHTTPHeaders[:]) -} - -// makeHTTPHeaders returns the HTTP headers following the intake payload format. -func makeHTTPHeaders(reqHeaders map[string][]string) (headers map[string]string) { - if len(reqHeaders) == 0 { - return nil - } - headers = make(map[string]string) - for k, v := range reqHeaders { - if i := sort.SearchStrings(collectedHTTPHeaders[:], k); i < len(collectedHTTPHeaders) && collectedHTTPHeaders[i] == k { - headers[k] = strings.Join(v, ";") - } - } - if len(headers) == 0 { - return nil - } - return headers -} - -// makeHTTPURL returns the HTTP URL from the given scheme, host and path. -func makeHTTPURL(scheme, host, path string) string { - return fmt.Sprintf("%s://%s%s", scheme, host, path) -} diff --git a/internal/appsec/api_test.go b/internal/appsec/api_test.go deleted file mode 100644 index aab4696304..0000000000 --- a/internal/appsec/api_test.go +++ /dev/null @@ -1,98 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed -// under the Apache License Version 2.0. -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2016 Datadog, Inc. - -package appsec - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestSplitHostPort(t *testing.T) { - for _, tc := range []struct { - Addr string - ExpectedHost, ExpectedPort string - }{ - { - Addr: "", - ExpectedHost: "", - ExpectedPort: "", - }, - { - Addr: ":33", - ExpectedPort: "33", - }, - { - Addr: "33:", - ExpectedHost: "33", - }, - { - Addr: "[fe80::1.2.3.4]:33", - ExpectedHost: "fe80::1.2.3.4", - ExpectedPort: "33", - }, - { - Addr: "[fe80::1.2.3.4]", - ExpectedHost: "fe80::1.2.3.4", - }, - { - Addr: " [fe80::1.2.3.4] ", - ExpectedHost: "fe80::1.2.3.4", - }, - { - Addr: "localhost:80 ", - ExpectedHost: "localhost", - ExpectedPort: "80", - }, - } { - t.Run(tc.Addr, func(t *testing.T) { - host, port := splitHostPort(tc.Addr) - require.Equal(t, tc.ExpectedHost, host) - require.Equal(t, tc.ExpectedPort, port) - }) - } -} - -func TestMakeHTTPHeaders(t *testing.T) { - for _, tc := range []struct { - headers map[string][]string - expected map[string]string - }{ - { - headers: nil, - expected: nil, - }, - { - headers: map[string][]string{ - "cookie": {"not-collected"}, - }, - expected: nil, - }, - { - headers: map[string][]string{ - "cookie": {"not-collected"}, - "x-forwarded-for": {"1.2.3.4,5.6.7.8"}, - }, - expected: map[string]string{ - "x-forwarded-for": "1.2.3.4,5.6.7.8", - }, - }, - { - headers: map[string][]string{ - "cookie": {"not-collected"}, - "x-forwarded-for": {"1.2.3.4,5.6.7.8", "9.10.11.12,13.14.15.16"}, - }, - expected: map[string]string{ - "x-forwarded-for": "1.2.3.4,5.6.7.8;9.10.11.12,13.14.15.16", - }, - }, - } { - t.Run("makeHTTPHeaders", func(t *testing.T) { - headers := makeHTTPHeaders(tc.headers) - require.Equal(t, tc.expected, headers) - }) - } -} diff --git a/internal/appsec/appsec.go b/internal/appsec/appsec.go index 6e89b8f00a..da0935fb33 100644 --- a/internal/appsec/appsec.go +++ b/internal/appsec/appsec.go @@ -9,40 +9,23 @@ package appsec import ( - "context" - "fmt" - "io/ioutil" - "os" - "runtime" "sync" - "time" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" ) -// Default batching configuration values. -const ( - defaultMaxBatchLen = 1024 - defaultMaxBatchStaleTime = time.Second -) - -// Default timeout of intake requests. -const defaultIntakeTimeout = 10 * time.Second - -// Status returns the AppSec status string: "enabled" when both the appsec -// build tag is enabled and the env var DD_APPSEC_ENABLED is set to true, or -// "disabled" otherwise. -func Status() string { - if enabled, _ := isEnabled(); enabled { - return "enabled" - } - return "disabled" +// Enabled returns true when AppSec is up and running. Meaning that the appsec build tag is enabled, the env var +// DD_APPSEC_ENABLED is set to true, and the tracer is started. +func Enabled() bool { + mu.RLock() + defer mu.RUnlock() + return activeAppSec != nil } // Start AppSec when enabled is enabled by both using the appsec build tag and // setting the environment variable DD_APPSEC_ENABLED to true. -func Start(cfg *Config) { +func Start() { enabled, err := isEnabled() if err != nil { logUnexpectedStartError(err) @@ -53,28 +36,12 @@ func Start(cfg *Config) { return } - filepath := os.Getenv("DD_APPSEC_RULES") - if filepath != "" { - rules, err := ioutil.ReadFile(filepath) - if err != nil { - if os.IsNotExist(err) { - log.Error("appsec: could not find the rules file in path %s: %v.\nAppSec will not run any protections in this application. No security activities will be collected.", filepath, err) - } else { - logUnexpectedStartError(err) - } - return - } - cfg.rules = rules - log.Info("appsec: starting with the security rules from file %s", filepath) - } else { - log.Info("appsec: starting with default recommended security rules") - } - - appsec, err := newAppSec(cfg) + cfg, err := newConfig() if err != nil { logUnexpectedStartError(err) return } + appsec := newAppSec(cfg) if err := appsec.start(); err != nil { logUnexpectedStartError(err) return @@ -94,7 +61,7 @@ func Stop() { var ( activeAppSec *appsec - mu sync.Mutex + mu sync.RWMutex ) func setActiveAppSec(a *appsec) { @@ -107,160 +74,28 @@ func setActiveAppSec(a *appsec) { } type appsec struct { - client *client - eventChan chan securityEvent - wg sync.WaitGroup - cfg *Config + cfg *config unregisterWAF dyngo.UnregisterFunc } -func newAppSec(cfg *Config) (*appsec, error) { - intakeClient, err := newClient(cfg.Client, cfg.AgentURL) - if err != nil { - return nil, err - } - - if cfg.MaxBatchLen <= 0 { - cfg.MaxBatchLen = defaultMaxBatchLen - } - - if cfg.MaxBatchStaleTime <= 0 { - cfg.MaxBatchStaleTime = defaultMaxBatchStaleTime - } - +func newAppSec(cfg *config) *appsec { return &appsec{ - eventChan: make(chan securityEvent, 1000), - client: intakeClient, - cfg: cfg, - }, nil + cfg: cfg, + } } -// Start starts the AppSec background goroutine. +// Start AppSec by registering its security protections according to the configured the security rules. func (a *appsec) start() error { // Register the WAF operation event listener - unregisterWAF, err := registerWAF(a.cfg.rules, a) + unregisterWAF, err := registerWAF(a.cfg.rules, a.cfg.wafTimeout) if err != nil { return err } a.unregisterWAF = unregisterWAF - - // Start the background goroutine reading the channel of security events and sending them to the backend - a.wg.Add(1) - go func() { - defer a.wg.Done() - - strTags := stringTags(a.cfg.Tags) - osName := fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH) - applyContext := func(event securityEvent) securityEvent { - if len(strTags) > 0 { - event = withTagsContext(event, strTags) - } - event = withServiceContext(event, a.cfg.Service.Name, a.cfg.Service.Version, a.cfg.Service.Environment) - event = withTracerContext(event, "go", runtime.Version(), a.cfg.Version) - event = withHostContext(event, a.cfg.Hostname, osName) - return event - } - eventBatchingLoop(a.client, a.eventChan, applyContext, a.cfg) - }() - return nil } -func stringTags(tagsMap map[string]interface{}) (tags []string) { - tags = make([]string, 0, len(tagsMap)) - for tag, value := range tagsMap { - if str, ok := value.(string); ok { - tags = append(tags, fmt.Sprintf("%s:%v", tag, str)) - } - } - return tags -} - -// Stop stops the AppSec agent goroutine. +// Stop AppSec by unregistering the security protections. func (a *appsec) stop() { a.unregisterWAF() - // Stop the batching goroutine - close(a.eventChan) - // Gracefully stop by waiting for the event loop goroutine to stop - a.wg.Wait() -} - -type intakeClient interface { - sendBatch(context.Context, eventBatch) error -} - -func eventBatchingLoop(client intakeClient, eventChan <-chan securityEvent, withGlobalContext func(event securityEvent) securityEvent, cfg *Config) { - // The batch of events - batch := make([]securityEvent, 0, cfg.MaxBatchLen) - - // Timer initialized to a first dummy time value to initialize it and so that we can immediately - // use its channel field in the following select statement. - timer := time.NewTimer(time.Hour) - timer.Stop() - - // Helper function stopping the timer, sending the batch and resetting it. - sendBatch := func() { - if !timer.Stop() { - // Remove the time value from the channel in case the timer fired and so that we avoid - // sending the batch again in the next loop iteration due to a value in the timer - // channel. - select { - case <-timer.C: - default: - } - } - ctx, cancel := context.WithTimeout(context.Background(), defaultIntakeTimeout) - defer cancel() - intakeBatch := make([]*attackEvent, 0, len(batch)) - for _, e := range batch { - intakeEvents, err := withGlobalContext(e).toIntakeEvents() - if err != nil { - log.Error("appsec: could not create intake security events: %v", err) - continue - } - intakeBatch = append(intakeBatch, intakeEvents...) - } - log.Debug("appsec: sending %d security events", len(intakeBatch)) - if err := client.sendBatch(ctx, makeEventBatch(intakeBatch)); err != nil { - log.Error("appsec: could not send the event batch: %v", err) - } - batch = batch[0:0] - } - - // Loop-select between the event channel or the stale timer (when enabled). - for { - select { - case event, ok := <-eventChan: - // Add the event to the batch. - // The event might be nil when closing the channel while it was empty. - if event != nil { - batch = append(batch, event) - } - if !ok { - // The event channel has been closed. Send the batch if it's not empty. - if len(batch) > 0 { - sendBatch() - } - return - } - // Send the batch when it's full or start the timer when this is the first value in - // the batch. - if l := len(batch); l == cfg.MaxBatchLen { - sendBatch() - } else if l == 1 { - timer.Reset(cfg.MaxBatchStaleTime) - } - - case <-timer.C: - sendBatch() - } - } -} - -func (a *appsec) sendEvent(event securityEvent) { - select { - case a.eventChan <- event: - default: - // TODO(julio): add metrics on the nb of dropped events - } } diff --git a/internal/appsec/appsec_disabled.go b/internal/appsec/appsec_disabled.go index 1e229b154d..1a408b6121 100644 --- a/internal/appsec/appsec_disabled.go +++ b/internal/appsec/appsec_disabled.go @@ -10,16 +10,15 @@ package appsec import "gopkg.in/DataDog/dd-trace-go.v1/internal/log" -// Status returns the AppSec status string: "enabled" when both the appsec -// build tag is enabled and the env var DD_APPSEC_ENABLED is set to true, or -// "disabled" otherwise. -func Status() string { - return "disabled" +// Enabled returns true when AppSec is up and running. Meaning that the appsec build tag is enabled, the env var +// DD_APPSEC_ENABLED is set to true, and the tracer is started. +func Enabled() bool { + return false } // Start AppSec when enabled is enabled by both using the appsec build tag and // setting the environment variable DD_APPSEC_ENABLED to true. -func Start(*Config) { +func Start() { if enabled, err := isEnabled(); err != nil { // Something went wrong while checking the DD_APPSEC_ENABLED configuration log.Error("appsec: error while checking if appsec is enabled: %v", err) diff --git a/internal/appsec/appsec_disabled_test.go b/internal/appsec/appsec_disabled_test.go index d5aae800d8..7fe29ed674 100644 --- a/internal/appsec/appsec_disabled_test.go +++ b/internal/appsec/appsec_disabled_test.go @@ -16,6 +16,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestStatus(t *testing.T) { - require.Equal(t, "disabled", appsec.Status()) +func TestEnabled(t *testing.T) { + require.False(t, appsec.Enabled()) } diff --git a/internal/appsec/appsec_test.go b/internal/appsec/appsec_test.go index 2c06614f63..dd5905a30c 100644 --- a/internal/appsec/appsec_test.go +++ b/internal/appsec/appsec_test.go @@ -6,21 +6,26 @@ //go:build appsec // +build appsec -package appsec +package appsec_test import ( + "os" + "strconv" "testing" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" + + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestStatus(t *testing.T) { - enabled, err := isEnabled() - require.NoError(t, err) - status := Status() - if enabled { - require.Equal(t, "enabled", status) - } else { - require.Equal(t, "disabled", status) - } +func TestEnabled(t *testing.T) { + enabled, _ := strconv.ParseBool(os.Getenv("DD_APPSEC_ENABLED")) + + require.False(t, appsec.Enabled()) + tracer.Start() + assert.Equal(t, enabled, appsec.Enabled()) + tracer.Stop() + assert.False(t, appsec.Enabled()) } diff --git a/internal/appsec/batching_test.go b/internal/appsec/batching_test.go deleted file mode 100644 index 382cab5127..0000000000 --- a/internal/appsec/batching_test.go +++ /dev/null @@ -1,201 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed -// under the Apache License Version 2.0. -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2016 Datadog, Inc. - -//go:build appsec -// +build appsec - -package appsec - -import ( - "context" - "fmt" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/mock" -) - -type IntakeClientMock struct { - mock.Mock - SendBatchCalled chan struct{} -} - -func (i *IntakeClientMock) sendBatch(ctx context.Context, batch eventBatch) error { - err := i.Called(ctx, batch).Error(0) - if i.SendBatchCalled != nil { - i.SendBatchCalled <- struct{}{} - } - return err -} - -func TestEventBatchingLoop(t *testing.T) { - t.Run("batching", func(t *testing.T) { - for _, eventChanLen := range []int{1, 2, 512, 1024} { - eventChanLen := eventChanLen - t.Run(fmt.Sprintf("EventChanLen=%d", eventChanLen), func(t *testing.T) { - for _, maxBatchLen := range []int{1, 2, 512, 1024} { - maxBatchLen := maxBatchLen - t.Run(fmt.Sprintf("MaxBatchLen=%d", maxBatchLen), func(t *testing.T) { - // Send 10 batches of events and check they were properly sent - expectedNbBatches := 10 - client := &IntakeClientMock{ - // Have enough room for the amount of expected batches - SendBatchCalled: make(chan struct{}, expectedNbBatches), - } - eventChan := make(chan securityEvent, eventChanLen) - cfg := &Config{ - MaxBatchLen: maxBatchLen, - MaxBatchStaleTime: time.Hour, // Long enough so that it never triggers and we only test the batching logic - } - - // Start the batching goroutine - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - eventBatchingLoop(client, eventChan, applyGlobalContextNoop, cfg) - }() - - client.On("sendBatch", mock.Anything, mock.AnythingOfType("eventBatch")).Times(expectedNbBatches).Return(nil) - // Send enough events to generate expectedNbBatches - for i := 0; i < maxBatchLen*expectedNbBatches; i++ { - eventChan <- myEvent{} - } - // Sync with the client and check the client calls are being done as expected - for i := 0; i < expectedNbBatches; i++ { - <-client.SendBatchCalled - } - client.AssertExpectations(t) - - // Close the event channel to stop the loop - close(eventChan) - wg.Wait() - }) - } - }) - } - }) - - t.Run("stale-time", func(t *testing.T) { - client := &IntakeClientMock{ - SendBatchCalled: make(chan struct{}, 2), - } - eventChan := make(chan securityEvent, 1024) - maxStaleTime := time.Millisecond - cfg := &Config{ - MaxBatchLen: 1024, - MaxBatchStaleTime: maxStaleTime, - } - - // Start the batching goroutine - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - eventBatchingLoop(client, eventChan, applyGlobalContextNoop, cfg) - }() - - // - client.On("sendBatch", mock.Anything, mock.AnythingOfType("eventBatch")).Times(2).Return(nil) - - // Send a few events and wait for the configured max stale time so that the batch gets sent - eventChan <- myEvent{} - eventChan <- myEvent{} - eventChan <- myEvent{} - eventChan <- myEvent{} - time.Sleep(maxStaleTime) - // Sync with the client - <-client.SendBatchCalled - - // Send a few events and wait for the configured max stale time so that the batch gets sent - eventChan <- myEvent{} - // Sync with the client - <-client.SendBatchCalled - time.Sleep(maxStaleTime) - - // No new events - time.Sleep(maxStaleTime) - - // 2 batches should have been sent - client.AssertExpectations(t) - - // Close the event channel to stop the loop - close(eventChan) - wg.Wait() - }) - - t.Run("canceling-the-loop", func(t *testing.T) { - t.Run("empty-batch", func(t *testing.T) { - client := &IntakeClientMock{} - eventChan := make(chan securityEvent, 1024) - cfg := &Config{ - MaxBatchLen: 1024, - MaxBatchStaleTime: time.Hour, - } - - // Start the batching goroutine - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - eventBatchingLoop(client, eventChan, applyGlobalContextNoop, cfg) - }() - - // No client calls should be made - client.AssertExpectations(t) - - // Close the context to stop the loop - close(eventChan) - // Wait() should therefore return - wg.Wait() - }) - - t.Run("non-empty-batch", func(t *testing.T) { - client := &IntakeClientMock{ - SendBatchCalled: make(chan struct{}, 1), - } - eventChan := make(chan securityEvent, 1024) - cfg := &Config{ - MaxBatchLen: 1024, - MaxBatchStaleTime: time.Hour, - } - - // Start the batching goroutine - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - eventBatchingLoop(client, eventChan, applyGlobalContextNoop, cfg) - }() - - // Perform an event - client.On("sendBatch", mock.Anything, mock.AnythingOfType("eventBatch")).Times(1).Return(nil) - eventChan <- myEvent{} - - // Close the context to stop the loop - close(eventChan) - - // Wait() should therefore return - wg.Wait() - - // The event should be properly sent before returning - <-client.SendBatchCalled - client.AssertExpectations(t) - }) - }) -} - -type myEvent struct{} - -func (m myEvent) toIntakeEvents() ([]*attackEvent, error) { - return []*attackEvent{ - newAttackEvent("my.rule.id", "my.rule.name", "my.attack.type", time.Now(), nil), - }, nil -} - -func applyGlobalContextNoop(e securityEvent) securityEvent { - return e -} diff --git a/internal/appsec/client.go b/internal/appsec/client.go deleted file mode 100644 index fe5a237f76..0000000000 --- a/internal/appsec/client.go +++ /dev/null @@ -1,188 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed -// under the Apache License Version 2.0. -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2016 Datadog, Inc. - -package appsec - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/http/httputil" - "net/url" -) - -// Client is the HTTP client to use to communicate with the intake API via the agent API. -type client struct { - // Logger should be set to obtain debugging logs in debug level to see the HTTP requests and their responses. - Logger debugLogger - client *http.Client - baseURL *url.URL -} - -// debugLogger interface of the debug-level logger. -type debugLogger interface { - Debug(format string, v ...interface{}) -} - -// newClient returns a new intake client using the given HTTP client and base-URL. -func newClient(httpClient *http.Client, baseURL string) (*client, error) { - if httpClient == nil { - httpClient = &http.Client{} - } - u, err := url.Parse(baseURL) - if err != nil { - return nil, err - } - return &client{ - client: httpClient, - baseURL: u, - }, nil -} - -// sendBatch sends the batch. -func (c *client) sendBatch(ctx context.Context, b eventBatch) error { - r, err := c.newRequest("POST", "appsec/proxy/api/v2/appsecevts", b) - if err != nil { - return err - } - return c.do(ctx, r, nil) -} - -func (c *client) newRequest(method, urlStr string, reqBody interface{}) (*http.Request, error) { - u, err := c.baseURL.Parse(urlStr) - if err != nil { - return nil, err - } - - var buf io.ReadWriter - if reqBody != nil { - buf = &bytes.Buffer{} - enc := json.NewEncoder(buf) - enc.SetEscapeHTML(false) - if err := enc.Encode(reqBody); err != nil { - return nil, err - } - } - - req, err := http.NewRequest(method, u.String(), buf) - if err != nil { - return nil, err - } - - if reqBody != nil { - req.Header.Set("Content-Type", "application/json") - } - req.Header.Set("Accept", "application/json") - return req, nil -} - -func (c *client) do(ctx context.Context, req *http.Request, respBody interface{}) error { - if ctx == nil { - return errors.New("context must be non-nil") - } - - req = req.WithContext(ctx) - - c.debug("sending request\n%s\n", (*httpRequestStringer)(req)) - - resp, err := c.client.Do(req) - if err != nil { - return err - } - defer func() { - // Drain the body and close it in order to make the underlying connection - // available again in the pool - _, _ = io.Copy(ioutil.Discard, resp.Body) - _ = resp.Body.Close() - }() - - c.debug("received response\n%s\n", (*httpResponseStringer)(resp)) - - err = checkResponse(resp) - if err != nil { - return err - } - - if respBody != nil { - decErr := json.NewDecoder(resp.Body).Decode(respBody) - if decErr != nil && decErr != io.EOF { - return decErr - } - } - - return nil -} - -func (c *client) debug(fmt string, args ...interface{}) { - if c.Logger == nil { - return - } - c.Logger.Debug(fmt, args...) -} - -type ( - httpRequestStringer http.Request - httpResponseStringer http.Response -) - -func (r *httpRequestStringer) String() string { - dump, _ := httputil.DumpRequestOut((*http.Request)(r), true) - return string(dump) -} - -func (r *httpResponseStringer) String() string { - dump, _ := httputil.DumpResponse((*http.Response)(r), true) - return string(dump) -} - -// Client error types. -type ( - // APIError is the generic request error returned when the request status - // code is unknown. - APIError struct { - Response *http.Response - } - // AuthTokenError is a request error returned when the request could not be - // authenticated. - AuthTokenError APIError - // InvalidSignalError is a request error returned when one or more signal(s) - // sent are invalid. - InvalidSignalError APIError -) - -// Error return the error string representation. -func (e APIError) Error() string { - return fmt.Sprintf("api error: response with status code %s", e.Response.Status) -} - -// Error return the error string representation. -func (e AuthTokenError) Error() string { - return "api error: access token is missing or invalid" -} - -// Error return the error string representation. -func (e InvalidSignalError) Error() string { - return "api error: one of the provided events is invalid" -} - -func checkResponse(r *http.Response) error { - if c := r.StatusCode; 200 <= c && c <= 299 { - return nil - } - errorResponse := APIError{Response: r} - switch r.StatusCode { - case http.StatusUnauthorized: - return AuthTokenError(errorResponse) - case http.StatusUnprocessableEntity: - return InvalidSignalError(errorResponse) - default: - return errorResponse - } -} diff --git a/internal/appsec/client_test.go b/internal/appsec/client_test.go deleted file mode 100644 index 59850eaf24..0000000000 --- a/internal/appsec/client_test.go +++ /dev/null @@ -1,244 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed -// under the Apache License Version 2.0. -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2016 Datadog, Inc. - -package appsec - -import ( - "context" - "errors" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func TestClient(t *testing.T) { - t.Run("newClient", func(t *testing.T) { - t.Run("default-client", func(t *testing.T) { - client, err := newClient(nil, "target") - require.NoError(t, err) - require.NotNil(t, client) - }) - - t.Run("given-client", func(t *testing.T) { - httpClient := &http.Client{ - Timeout: 10 * time.Second, - } - client, err := newClient(httpClient, "target") - require.NoError(t, err) - require.NotNil(t, client) - }) - }) - - t.Run("newRequest", func(t *testing.T) { - baseURL := "http://target/" - c, err := newClient(nil, baseURL) - require.NoError(t, err) - - for _, tc := range []struct { - name string - endpoint string - method string - body interface{} - wantError bool - expectedBody string - }{ - { - name: "get-no-body", - endpoint: "endpoint", - method: http.MethodGet, - body: nil, - wantError: false, - }, - { - name: "post-no-body", - endpoint: "endpoint", - method: http.MethodPost, - body: nil, - wantError: false, - }, - { - name: "bad-method", - endpoint: "endpoint", - method: ";", - body: nil, - wantError: true, - }, - { - name: "bad-endpoint", - endpoint: ":endpoint", - method: "GET", - body: nil, - wantError: true, - }, - { - name: "bad-endpoint", - endpoint: ":endpoint", - method: "GET", - body: nil, - wantError: true, - }, - { - name: "post-body", - endpoint: "version/endpoint", - method: http.MethodPost, - body: []string{"a", "b", "c"}, - expectedBody: "[\"a\",\"b\",\"c\"]\n", - }, - { - name: "post-body", - endpoint: "version/endpoint", - method: http.MethodPost, - body: "no html & éscaping <", - expectedBody: "\"no html & éscaping <\"\n", - }, - { - name: "post-error", - endpoint: "version/endpoint", - method: http.MethodPost, - body: jsonMarshalError{}, - wantError: true, - }, - } { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - req, err := c.newRequest(tc.method, tc.endpoint, tc.body) - - if tc.wantError { - require.Error(t, err) - return - } - - require.NoError(t, err) - require.Equal(t, tc.method, req.Method) - require.Equal(t, baseURL+tc.endpoint, req.URL.String()) - if tc.expectedBody != "" { - body, err := ioutil.ReadAll(req.Body) - require.NoError(t, err) - require.Equal(t, tc.expectedBody, string(body)) - } - }) - } - }) - - t.Run("do", func(t *testing.T) { - for _, tc := range []struct { - name string - reqBody interface{} - expectedReqBody string - respBody string - expectedRespBody interface{} - wantError bool - status int - }{ - { - name: "no request nor response bodies", - }, - { - name: "request body without response body", - reqBody: "string", - expectedReqBody: "\"string\"\n", - }, - { - name: "request and response body", - reqBody: "request", - expectedReqBody: "\"request\"\n", - respBody: "\"response\"\n", - expectedRespBody: "response", - }, - { - name: "no request body and response body", - respBody: "\"response\"\n", - expectedRespBody: "response", - }, - { - name: "bad response json", - respBody: "\"oops", - wantError: true, - }, - { - name: "error status code", - status: http.StatusUnprocessableEntity, - wantError: true, - }, - { - name: "ok status code", - status: 200, - }, - } { - tc := tc - t.Run(tc.name, func(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if tc.status != 0 { - w.WriteHeader(tc.status) - } - if tc.expectedReqBody != "" { - require.Equal(t, "application/json", r.Header.Get("Content-Type")) - body, err := ioutil.ReadAll(r.Body) - require.NoError(t, err) - require.Equal(t, tc.expectedReqBody, string(body)) - } - _, _ = w.Write([]byte(tc.respBody)) - })) - defer srv.Close() - - c, err := newClient(srv.Client(), srv.URL) - - req, err := c.newRequest("GET", "endpoint", tc.reqBody) - require.NoError(t, err) - - var respBody interface{} - err = c.do(context.Background(), req, &respBody) - if tc.wantError { - require.Error(t, err) - return - } - require.NoError(t, err) - require.Equal(t, tc.expectedRespBody, respBody) - }) - } - }) - - t.Run("do-with-context", func(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { - time.Sleep(time.Minute) - })) - defer srv.Close() - - c, err := newClient(srv.Client(), srv.URL) - require.NoError(t, err) - - req, err := c.newRequest("PUT", "endpoint", nil) - require.NoError(t, err) - - ctx, cancel := context.WithTimeout(context.Background(), time.Microsecond) - defer cancel() - err = c.do(ctx, req, nil) - require.Error(t, err) - }) - - t.Run("do-with-context", func(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})) - defer srv.Close() - - c, err := newClient(srv.Client(), srv.URL) - require.NoError(t, err) - - req, err := c.newRequest("PUT", "endpoint", nil) - require.NoError(t, err) - - err = c.do(nil, req, nil) - require.Error(t, err) - }) -} - -type jsonMarshalError struct{} - -func (jsonMarshalError) UnmarshalJSON([]byte) error { return errors.New("oops") } -func (jsonMarshalError) MarshalJSON() ([]byte, error) { return nil, errors.New("oops") } diff --git a/internal/appsec/config.go b/internal/appsec/config.go index 36524822cb..1bd3c74000 100644 --- a/internal/appsec/config.go +++ b/internal/appsec/config.go @@ -7,50 +7,22 @@ package appsec import ( "fmt" - "net/http" + "io/ioutil" "os" "strconv" "time" -) - -type ( - // Config is the AppSec configuration. - Config struct { - // Client is the HTTP client to use to perform HTTP requests to the agent. This value is mandatory. - Client *http.Client - // AgentURL is the datadog agent URL the API client should use. - AgentURL string - // ServiceConfig is the information about the running service we currently protect. - Service ServiceConfig - // Tags is the list of tags that should be added to security events (eg. pid, os name, etc.). - Tags map[string]interface{} - // Hostname of the machine we run in. - Hostname string - // Version of the Go client library - Version string - - // MaxBatchLen is the maximum batch length the event batching loop should use. The event batch is sent when - // this length is reached. Defaults to 1024. - MaxBatchLen int - // MaxBatchStaleTime is the maximum amount of time events are kept in the batch. This allows to send the batch - // after this amount of time even if the maximum batch length is not reached yet. Defaults to 1 second. - MaxBatchStaleTime time.Duration - - // rules loaded via the env var DD_APPSEC_RULES. When not set, the builtin rules will be used. - rules []byte - } - // ServiceConfig is the optional context about the running service. - ServiceConfig struct { - // Name of the service. - Name string - // Version of the service. - Version string - // Environment of the service (eg. dev, staging, prod, etc.) - Environment string - } + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" ) +// config is the AppSec configuration. +type config struct { + // rules loaded via the env var DD_APPSEC_RULES. When not set, the builtin rules will be used. + rules []byte + // Maximum WAF execution time + wafTimeout time.Duration +} + // isEnabled returns true when appsec is enabled when the environment variable // DD_APPSEC_ENABLED is set to true. func isEnabled() (bool, error) { @@ -64,3 +36,34 @@ func isEnabled() (bool, error) { } return enabled, nil } + +func newConfig() (*config, error) { + cfg := &config{} + + filepath := os.Getenv("DD_APPSEC_RULES") + if filepath != "" { + rules, err := ioutil.ReadFile(filepath) + if err != nil { + if os.IsNotExist(err) { + log.Error("appsec: could not find the rules file in path %s: %v.", filepath, err) + } + return nil, err + } + cfg.rules = rules + log.Info("appsec: starting with the security rules from file %s", filepath) + } else { + log.Info("appsec: starting with the default recommended security rules") + } + + cfg.wafTimeout = 4 * time.Millisecond + if wafTimeout := os.Getenv("DD_APPSEC_WAF_TIMEOUT"); wafTimeout != "" { + timeout, err := time.ParseDuration(wafTimeout) + if err != nil { + cfg.wafTimeout = timeout + } else { + log.Error("appsec: could not parse the value of DD_APPSEC_WAF_TIMEOUT %s as a duration: %v. Using default value %s.", wafTimeout, err, cfg.wafTimeout) + } + } + + return cfg, nil +} diff --git a/internal/appsec/dyngo/instrumentation/grpcsec/grpc.go b/internal/appsec/dyngo/instrumentation/grpcsec/grpc.go new file mode 100644 index 0000000000..41fe5d3838 --- /dev/null +++ b/internal/appsec/dyngo/instrumentation/grpcsec/grpc.go @@ -0,0 +1,177 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +// Package grpcsec is the gRPC instrumentation API and contract for AppSec +// defining an abstract run-time representation of gRPC handlers. +// gRPC integrations must use this package to enable AppSec features for gRPC, +// which listens to this package's operation events. +package grpcsec + +import ( + "encoding/json" + "reflect" + "sync" + + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo" +) + +// Abstract gRPC server handler operation definitions. It is based on two +// operations allowing to describe every type of RPC: the HandlerOperation type +// which represents the RPC handler, and the ReceiveOperation type which +// represents the messages the RPC handler receives during its lifetime. +// This means that the ReceiveOperation(s) will happen within the +// HandlerOperation. +// Every type of RPC, unary, client streaming, server streaming, and +// bidirectional streaming RPCs, can be all represented with a HandlerOperation +// having one or several ReceiveOperation. +// The send operation is not required for now and therefore not defined, which +// means that server and bidirectional streaming RPCs currently have the same +// run-time representation as unary and client streaming RPCs. +type ( + // HandlerOperation represents a gRPC server handler operation. + // It must be created with StartHandlerOperation() and finished with its + // Finish() method. + // Security events observed during the operation lifetime should be added + // to the operation using its AddSecurityEvent() method. + HandlerOperation struct { + dyngo.Operation + + events []json.RawMessage + mu sync.Mutex + } + // HandlerOperationArgs is the grpc handler arguments. Empty as of today. + HandlerOperationArgs struct{} + // HandlerOperationRes is the grpc handler results. Empty as of today. + HandlerOperationRes struct{} + + // ReceiveOperation type representing an gRPC server handler operation. It must + // be created with StartReceiveOperation() and finished with its Finish(). + ReceiveOperation struct { + dyngo.Operation + } + // ReceiveOperationArgs is the gRPC handler receive operation arguments + // Empty as of today. + ReceiveOperationArgs struct{} + // ReceiveOperationRes is the gRPC handler receive operation results which + // contains the message the gRPC handler received. + ReceiveOperationRes struct { + // Message received by the gRPC handler. + // Corresponds to the address `grpc.server.request.message`. + Message interface{} + } +) + +// TODO(Julio-Guerra): create a go-generate tool to generate the types, vars and methods below + +// StartHandlerOperation starts an gRPC server handler operation, along with the +// given arguments and parent operation, and emits a start event up in the +// operation stack. When parent is nil, the operation is linked to the global +// root operation. +func StartHandlerOperation(args HandlerOperationArgs, parent dyngo.Operation) *HandlerOperation { + op := &HandlerOperation{Operation: dyngo.NewOperation(parent)} + dyngo.StartOperation(op, args) + return op +} + +// Finish the gRPC handler operation, along with the given results, and emit a +// finish event up in the operation stack. +func (op *HandlerOperation) Finish(res HandlerOperationRes) []json.RawMessage { + dyngo.FinishOperation(op, res) + return op.events +} + +// AddSecurityEvent adds the security event to the list of events observed +// during the operation lifetime. +func (op *HandlerOperation) AddSecurityEvent(event json.RawMessage) { + op.mu.Lock() + defer op.mu.Unlock() + op.events = append(op.events, event) +} + +// gRPC handler operation's start and finish event callback function types. +type ( + // OnHandlerOperationStart function type, called when an gRPC handler + // operation starts. + OnHandlerOperationStart func(*HandlerOperation, HandlerOperationArgs) + // OnHandlerOperationFinish function type, called when an gRPC handler + // operation finishes. + OnHandlerOperationFinish func(*HandlerOperation, HandlerOperationRes) +) + +var ( + handlerOperationArgsType = reflect.TypeOf((*HandlerOperationArgs)(nil)).Elem() + handlerOperationResType = reflect.TypeOf((*HandlerOperationRes)(nil)).Elem() +) + +// ListenedType returns the type a OnHandlerOperationStart event listener +// listens to, which is the HandlerOperationArgs type. +func (OnHandlerOperationStart) ListenedType() reflect.Type { return handlerOperationArgsType } + +// Call the underlying event listener function by performing the type-assertion +// on v whose type is the one returned by ListenedType(). +func (f OnHandlerOperationStart) Call(op dyngo.Operation, v interface{}) { + f(op.(*HandlerOperation), v.(HandlerOperationArgs)) +} + +// ListenedType returns the type a OnHandlerOperationFinish event listener +// listens to, which is the HandlerOperationRes type. +func (OnHandlerOperationFinish) ListenedType() reflect.Type { return handlerOperationResType } + +// Call the underlying event listener function by performing the type-assertion +// on v whose type is the one returned by ListenedType(). +func (f OnHandlerOperationFinish) Call(op dyngo.Operation, v interface{}) { + f(op.(*HandlerOperation), v.(HandlerOperationRes)) +} + +// StartReceiveOperation starts a receive operation of a gRPC handler, along +// with the given arguments and parent operation, and emits a start event up in +// the operation stack. When parent is nil, the operation is linked to the +// global root operation. +func StartReceiveOperation(args ReceiveOperationArgs, parent dyngo.Operation) ReceiveOperation { + op := ReceiveOperation{Operation: dyngo.NewOperation(parent)} + dyngo.StartOperation(op, args) + return op +} + +// Finish the gRPC handler operation, along with the given results, and emits a +// finish event up in the operation stack. +func (op ReceiveOperation) Finish(res ReceiveOperationRes) { + dyngo.FinishOperation(op, res) +} + +// gRPC receive operation's start and finish event callback function types. +type ( + // OnReceiveOperationStart function type, called when a gRPC receive + // operation starts. + OnReceiveOperationStart func(ReceiveOperation, ReceiveOperationArgs) + // OnReceiveOperationFinish function type, called when a grpc receive + // operation finishes. + OnReceiveOperationFinish func(ReceiveOperation, ReceiveOperationRes) +) + +var ( + receiveOperationArgsType = reflect.TypeOf((*ReceiveOperationArgs)(nil)).Elem() + receiveOperationResType = reflect.TypeOf((*ReceiveOperationRes)(nil)).Elem() +) + +// ListenedType returns the type a OnHandlerOperationStart event listener +// listens to, which is the HandlerOperationArgs type. +func (OnReceiveOperationStart) ListenedType() reflect.Type { return receiveOperationArgsType } + +// Call the underlying event listener function by performing the type-assertion +// on v whose type is the one returned by ListenedType(). +func (f OnReceiveOperationStart) Call(op dyngo.Operation, v interface{}) { + f(op.(ReceiveOperation), v.(ReceiveOperationArgs)) +} + +// ListenedType returns the type a OnHandlerOperationFinish event listener +// listens to, which is the HandlerOperationRes type. +func (OnReceiveOperationFinish) ListenedType() reflect.Type { return receiveOperationResType } + +// Call the underlying event listener function by performing the type-assertion +// on v whose type is the one returned by ListenedType(). +func (f OnReceiveOperationFinish) Call(op dyngo.Operation, v interface{}) { + f(op.(ReceiveOperation), v.(ReceiveOperationRes)) +} diff --git a/internal/appsec/dyngo/instrumentation/grpcsec/grpc_test.go b/internal/appsec/dyngo/instrumentation/grpcsec/grpc_test.go new file mode 100644 index 0000000000..cc7e637c1b --- /dev/null +++ b/internal/appsec/dyngo/instrumentation/grpcsec/grpc_test.go @@ -0,0 +1,85 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package grpcsec_test + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo/instrumentation/grpcsec" +) + +func TestUsage(t *testing.T) { + testRPCRepresentation := func(expectedRecvOperation int) func(*testing.T) { + return func(t *testing.T) { + type ( + rootArgs struct{} + rootRes struct{} + ) + localRootOp := dyngo.NewOperation(nil) + dyngo.StartOperation(localRootOp, rootArgs{}) + defer dyngo.FinishOperation(localRootOp, rootRes{}) + + var handlerStarted, handlerFinished, recvStarted, recvFinished int + defer func() { + require.Equal(t, 1, handlerStarted) + require.Equal(t, 1, handlerFinished) + require.Equal(t, expectedRecvOperation, recvStarted) + require.Equal(t, expectedRecvOperation, recvFinished) + }() + + const expectedMessageFormat = "message number %d" + + localRootOp.On(grpcsec.OnHandlerOperationStart(func(handlerOp *grpcsec.HandlerOperation, args grpcsec.HandlerOperationArgs) { + handlerStarted++ + + handlerOp.On(grpcsec.OnReceiveOperationStart(func(op grpcsec.ReceiveOperation, _ grpcsec.ReceiveOperationArgs) { + recvStarted++ + + op.On(grpcsec.OnReceiveOperationFinish(func(_ grpcsec.ReceiveOperation, res grpcsec.ReceiveOperationRes) { + expectedMessage := fmt.Sprintf(expectedMessageFormat, recvStarted) + require.Equal(t, expectedMessage, res.Message) + recvFinished++ + + handlerOp.AddSecurityEvent(json.RawMessage(expectedMessage)) + })) + })) + + handlerOp.On(grpcsec.OnHandlerOperationFinish(func(*grpcsec.HandlerOperation, grpcsec.HandlerOperationRes) { + handlerFinished++ + })) + })) + + rpcOp := grpcsec.StartHandlerOperation(grpcsec.HandlerOperationArgs{}, localRootOp) + + for i := 1; i <= expectedRecvOperation; i++ { + recvOp := grpcsec.StartReceiveOperation(grpcsec.ReceiveOperationArgs{}, rpcOp) + recvOp.Finish(grpcsec.ReceiveOperationRes{Message: fmt.Sprintf(expectedMessageFormat, i)}) + } + + secEvents := rpcOp.Finish(grpcsec.HandlerOperationRes{}) + + require.Len(t, secEvents, expectedRecvOperation) + for i, e := range secEvents { + require.Equal(t, fmt.Sprintf(expectedMessageFormat, i+1), string(e)) + } + } + } + + // Unary RPCs are represented by a single receive operation + t.Run("unary-representation", testRPCRepresentation(1)) + // Client streaming RPCs are represented by many receive operations. + t.Run("client-streaming-representation", testRPCRepresentation(10)) + // Server and bidirectional streaming RPCs cannot be tested for now because + // the send operations are not used nor defined yet, server streaming RPCs + // are currently represented like unary RPCs (1 client message, N server + // messages), and bidirectional RPCs like client streaming RPCs (N client + // messages, M server messages). +} diff --git a/internal/appsec/dyngo/instrumentation/grpcsec/tags.go b/internal/appsec/dyngo/instrumentation/grpcsec/tags.go new file mode 100644 index 0000000000..9f36ce2fd1 --- /dev/null +++ b/internal/appsec/dyngo/instrumentation/grpcsec/tags.go @@ -0,0 +1,102 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package grpcsec + +import ( + "encoding/json" + "fmt" + "net" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo/instrumentation/httpsec" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" +) + +// SetSecurityEventTags sets the AppSec-specific span tags when a security event +// occurred into the service entry span. +func SetSecurityEventTags(span ddtrace.Span, events []json.RawMessage, addr net.Addr, md map[string][]string) { + if err := setSecurityEventTags(span, events, addr, md); err != nil { + log.Error("appsec: %v", err) + } +} + +func setSecurityEventTags(span ddtrace.Span, events []json.RawMessage, addr net.Addr, md map[string][]string) error { + if err := setEventSpanTags(span, events); err != nil { + return err + } + var ip string + switch actual := addr.(type) { + case *net.UDPAddr: + ip = actual.IP.String() + case *net.TCPAddr: + ip = actual.IP.String() + } + if ip != "" { + span.SetTag("network.client.ip", ip) + } + for h, v := range httpsec.NormalizeHTTPHeaders(md) { + span.SetTag("grpc.metadata."+h, v) + } + return nil +} + +// setEventSpanTags sets the security event span tags into the service entry span. +func setEventSpanTags(span ddtrace.Span, events []json.RawMessage) error { + // Set the appsec event span tag + val, err := makeEventTagValue(events) + if err != nil { + return err + } + span.SetTag("_dd.appsec.json", string(val)) + // Keep this span due to the security event + span.SetTag(ext.ManualKeep, true) + span.SetTag("_dd.origin", "appsec") + // Set the appsec.event tag needed by the appsec backend + span.SetTag("appsec.event", true) + return nil +} + +// Create the value of the security event tag. +// TODO(Julio-Guerra): a future libddwaf version should return something +// avoiding us the following events concatenation logic which currently +// involves unserializing the top-level JSON arrays to concatenate them +// together. +// TODO(Julio-Guerra): avoid serializing the json in the request hot path +func makeEventTagValue(events []json.RawMessage) (json.RawMessage, error) { + var v interface{} + if l := len(events); l == 1 { + // eventTag is the structure to use in the `_dd.appsec.json` span tag. + // In this case of 1 event, it already is an array as expected. + type eventTag struct { + Triggers json.RawMessage `json:"triggers"` + } + v = eventTag{Triggers: events[0]} + } else { + // eventTag is the structure to use in the `_dd.appsec.json` span tag. + // With more than one event, we need to concatenate the arrays together + // (ie. convert [][]json.RawMessage into []json.RawMessage). + type eventTag struct { + Triggers []json.RawMessage `json:"triggers"` + } + concatenated := make([]json.RawMessage, 0, l) // at least len(events) + for _, event := range events { + // Unmarshal the top level array + var tmp []json.RawMessage + if err := json.Unmarshal(event, &tmp); err != nil { + return nil, fmt.Errorf("unexpected error while unserializing the appsec event `%s`: %v", string(event), err) + } + concatenated = append(concatenated, tmp...) + } + v = eventTag{Triggers: concatenated} + } + + tag, err := json.Marshal(v) + if err != nil { + return nil, fmt.Errorf("unexpected error while serializing the appsec event span tag: %v", err) + } + return tag, nil +} diff --git a/internal/appsec/dyngo/instrumentation/grpcsec/tags_test.go b/internal/appsec/dyngo/instrumentation/grpcsec/tags_test.go new file mode 100644 index 0000000000..ce6e79144e --- /dev/null +++ b/internal/appsec/dyngo/instrumentation/grpcsec/tags_test.go @@ -0,0 +1,182 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package grpcsec + +import ( + "encoding/json" + "fmt" + "net" + "testing" + + "github.com/stretchr/testify/require" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" +) + +func TestSetSecurityEventTags(t *testing.T) { + for _, eventCase := range []struct { + name string + events []json.RawMessage + expectedTag string + expectedError bool + }{ + { + name: "one-event", + events: []json.RawMessage{json.RawMessage(`["one","two"]`)}, + expectedTag: `{"triggers":["one","two"]}`, + }, + { + name: "one-event-with-json-error", + events: []json.RawMessage{json.RawMessage(`["one",two"]`)}, + expectedError: true, + }, + { + name: "two-events", + events: []json.RawMessage{json.RawMessage(`["one","two"]`), json.RawMessage(`["three","four"]`)}, + expectedTag: `{"triggers":["one","two","three","four"]}`, + }, + { + name: "two-events-with-json-error", + events: []json.RawMessage{json.RawMessage(`["one","two"]`), json.RawMessage(`["three,"four"]`)}, + expectedError: true, + }, + { + name: "three-events-with-json-error", + events: []json.RawMessage{json.RawMessage(`["one","two"]`), json.RawMessage(`["three","four"]`), json.RawMessage(`"five"`)}, + expectedError: true, + }, + } { + eventCase := eventCase + for _, addrCase := range []struct { + name string + addr net.Addr + expectedTag string + }{ + { + name: "tcp-ipv4-address", + addr: &net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 6789}, + expectedTag: "1.2.3.4", + }, + { + name: "tcp-ipv6-address", + addr: &net.TCPAddr{IP: net.ParseIP("::1"), Port: 6789}, + expectedTag: "::1", + }, + { + name: "udp-ipv4-address", + addr: &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 6789}, + expectedTag: "1.2.3.4", + }, + { + name: "udp-ipv6-address", + addr: &net.UDPAddr{IP: net.ParseIP("::1"), Port: 6789}, + expectedTag: "::1", + }, + { + name: "unix-socket-address", + addr: &net.UnixAddr{Name: "/var/my.sock"}, + }, + } { + addrCase := addrCase + for _, metadataCase := range []struct { + name string + md map[string][]string + expectedTags map[string]string + }{ + { + name: "zero-metadata", + }, + { + name: "xff-metadata", + md: map[string][]string{ + "x-forwarded-for": {"1.2.3.4", "4.5.6.7"}, + ":authority": {"something"}, + }, + expectedTags: map[string]string{ + "grpc.metadata.x-forwarded-for": "1.2.3.4,4.5.6.7", + }, + }, + { + name: "xff-metadata", + md: map[string][]string{ + "x-forwarded-for": {"1.2.3.4"}, + ":authority": {"something"}, + }, + expectedTags: map[string]string{ + "grpc.metadata.x-forwarded-for": "1.2.3.4", + }, + }, + { + name: "no-monitored-metadata", + md: map[string][]string{ + ":authority": {"something"}, + }, + }, + } { + metadataCase := metadataCase + t.Run(fmt.Sprintf("%s-%s-%s", eventCase.name, addrCase.name, metadataCase.name), func(t *testing.T) { + var span MockSpan + err := setSecurityEventTags(&span, eventCase.events, addrCase.addr, metadataCase.md) + if eventCase.expectedError { + require.Error(t, err) + return + } + require.NoError(t, err) + + expectedTags := map[string]interface{}{ + "_dd.appsec.json": eventCase.expectedTag, + "manual.keep": true, + "appsec.event": true, + "_dd.origin": "appsec", + } + + if addr := addrCase.expectedTag; addr != "" { + expectedTags["network.client.ip"] = addr + } + + for k, v := range metadataCase.expectedTags { + expectedTags[k] = v + } + + require.Equal(t, expectedTags, span.tags) + require.False(t, span.finished) + }) + } + } + } +} + +type MockSpan struct { + tags map[string]interface{} + finished bool +} + +func (m *MockSpan) SetTag(key string, value interface{}) { + if m.tags == nil { + m.tags = make(map[string]interface{}) + } + m.tags[key] = value +} + +func (m *MockSpan) SetOperationName(operationName string) { + panic("unused") +} + +func (m *MockSpan) BaggageItem(key string) string { + panic("unused") +} + +func (m *MockSpan) SetBaggageItem(key, val string) { + panic("unused") +} + +func (m *MockSpan) Finish(opts ...ddtrace.FinishOption) { + m.finished = true +} + +func (m *MockSpan) Context() ddtrace.SpanContext { + panic("unused") +} diff --git a/internal/appsec/dyngo/instrumentation/httpinstr/http.go b/internal/appsec/dyngo/instrumentation/httpsec/http.go similarity index 55% rename from internal/appsec/dyngo/instrumentation/httpinstr/http.go rename to internal/appsec/dyngo/instrumentation/httpsec/http.go index cdbb45fbef..e3351bb9f6 100644 --- a/internal/appsec/dyngo/instrumentation/httpinstr/http.go +++ b/internal/appsec/dyngo/instrumentation/httpsec/http.go @@ -3,17 +3,18 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2016 Datadog, Inc. -// Package httpinstr defines the HTTP operation that can be listened to using -// dyngo's operation instrumentation. It serves as an abstract representation -// of HTTP handler calls. -package httpinstr +// Package httpsec defines is the HTTP instrumentation API and contract for +// AppSec. It defines an abstract representation of HTTP handlers, along with +// helper functions to wrap (aka. instrument) standard net/http handlers. +// HTTP integrations must use this package to enable AppSec features for HTTP, +// which listens to this package's operation events. +package httpsec import ( + "encoding/json" + "net" "net/http" - "net/url" - "os" "reflect" - "strconv" "strings" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" @@ -24,86 +25,31 @@ import ( type ( // HandlerOperationArgs is the HTTP handler operation arguments. HandlerOperationArgs struct { - Method string - Host string - RemoteAddr string - Path string - IsTLS bool - Span ddtrace.Span - // RequestURI corresponds to the address `server.request.uri.raw` RequestURI string // Headers corresponds to the address `server.request.headers.no_cookies` Headers map[string][]string // Cookies corresponds to the address `server.request.cookies` - Cookies map[string][]string + Cookies []string // Query corresponds to the address `server.request.query` - Query url.Values + Query map[string][]string } // HandlerOperationRes is the HTTP handler operation results. HandlerOperationRes struct { - // Status corresponds to the address `server.response.status` + // Status corresponds to the address `server.response.status`. Status int } ) -// enabled is true when appsec is enabled so that WrapHandler only wraps the -// handler when appsec is enabled. -// TODO(julio): remove this as soon as appsec becomes enabled by default -var enabled bool - -func init() { - enabled, _ = strconv.ParseBool(os.Getenv("DD_APPSEC_ENABLED")) -} - // WrapHandler wraps the given HTTP handler with the abstract HTTP operation defined by HandlerOperationArgs and // HandlerOperationRes. func WrapHandler(handler http.Handler, span ddtrace.Span) http.Handler { - if !enabled { - span.SetTag("_dd.appsec.enabled", 0) - return handler - } + SetAppSecTags(span) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - span.SetTag("_dd.appsec.enabled", 1) - span.SetTag("_dd.runtime_family", "go") - - headers := make(http.Header, len(r.Header)) - for k, v := range r.Header { - k := strings.ToLower(k) - if k == "cookie" { - // Do not include cookies in the request headers - continue - } - headers[k] = v - } - var cookies map[string][]string - if reqCookies := r.Cookies(); len(reqCookies) > 0 { - cookies = make(map[string][]string, len(reqCookies)) - for _, cookie := range reqCookies { - if cookie == nil { - continue - } - cookies[cookie.Name] = append(cookies[cookie.Name], cookie.Value) - } - } - host := r.Host - headers["host"] = []string{host} + args := MakeHandlerOperationArgs(r) op := StartOperation( - HandlerOperationArgs{ - Span: span, - IsTLS: r.TLS != nil, - Method: r.Method, - Host: r.Host, - Path: r.URL.Path, - RequestURI: r.RequestURI, - RemoteAddr: r.RemoteAddr, - Headers: headers, - Cookies: cookies, - // TODO(julio): avoid actively parsing the query string and move to a lazy monitoring of this value with - // the dynamic instrumentation of the Query() method. - Query: r.URL.Query(), - }, + args, nil, ) defer func() { @@ -111,42 +57,90 @@ func WrapHandler(handler http.Handler, span ddtrace.Span) http.Handler { if mw, ok := w.(interface{ Status() int }); ok { status = mw.Status() } - op.Finish(HandlerOperationRes{Status: status}) + events := op.Finish(HandlerOperationRes{Status: status}) + if len(events) == 0 { + return + } + + remoteIP, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + remoteIP = r.RemoteAddr + } + SetSecurityEventTags(span, events, remoteIP, args.Headers, w.Header()) }() handler.ServeHTTP(w, r) }) } -// TODO(julio): create a go-generate tool to generate the types, vars and methods below +// MakeHandlerOperationArgs creates the HandlerOperationArgs out of a standard +// http.Request along with the given current span. It returns an empty structure +// when appsec is disabled. +func MakeHandlerOperationArgs(r *http.Request) HandlerOperationArgs { + headers := make(http.Header, len(r.Header)) + var cookies []string + for k, v := range r.Header { + k := strings.ToLower(k) + if k == "cookie" { + // Do not include cookies in the request headers + cookies = v + continue + } + headers[k] = v + } + headers["host"] = []string{r.Host} + return HandlerOperationArgs{ + RequestURI: r.RequestURI, + Headers: headers, + Cookies: cookies, + // TODO(Julio-Guerra): avoid actively parsing the query string and move to a lazy monitoring of this value with + // the dynamic instrumentation of the Query() method. + Query: r.URL.Query(), + } +} + +// TODO(Julio-Guerra): create a go-generate tool to generate the types, vars and methods below // Operation type representing an HTTP operation. It must be created with // StartOperation() and finished with its Finish(). type Operation struct { - *dyngo.OperationImpl + dyngo.Operation + events json.RawMessage } // StartOperation starts an HTTP handler operation, along with the given // arguments and parent operation, and emits a start event up in the // operation stack. When parent is nil, the operation is linked to the global // root operation. -func StartOperation(args HandlerOperationArgs, parent dyngo.Operation) Operation { - return Operation{OperationImpl: dyngo.StartOperation(args, parent)} +func StartOperation(args HandlerOperationArgs, parent dyngo.Operation) *Operation { + op := &Operation{Operation: dyngo.NewOperation(parent)} + dyngo.StartOperation(op, args) + return op } // Finish the HTTP handler operation, along with the given results, and emits a // finish event up in the operation stack. -func (op Operation) Finish(res HandlerOperationRes) { - op.OperationImpl.Finish(res) +func (op *Operation) Finish(res HandlerOperationRes) json.RawMessage { + dyngo.FinishOperation(op, res) + return op.events +} + +// AddSecurityEvent adds the security event to the list of events observed +// during the operation lifetime. +func (op *Operation) AddSecurityEvent(event json.RawMessage) { + // TODO(Julio-Guerra): the current situation involves only one event per + // operation. In the future, multiple events per operation will become + // possible and the append operation should be made thread-safe. + op.events = event } // HTTP handler operation's start and finish event callback function types. type ( // OnHandlerOperationStart function type, called when an HTTP handler // operation starts. - OnHandlerOperationStart func(dyngo.Operation, HandlerOperationArgs) + OnHandlerOperationStart func(*Operation, HandlerOperationArgs) // OnHandlerOperationFinish function type, called when an HTTP handler // operation finishes. - OnHandlerOperationFinish func(dyngo.Operation, HandlerOperationRes) + OnHandlerOperationFinish func(*Operation, HandlerOperationRes) ) var ( @@ -161,7 +155,7 @@ func (OnHandlerOperationStart) ListenedType() reflect.Type { return handlerOpera // Call the underlying event listener function by performing the type-assertion // on v whose type is the one returned by ListenedType(). func (f OnHandlerOperationStart) Call(op dyngo.Operation, v interface{}) { - f(op, v.(HandlerOperationArgs)) + f(op.(*Operation), v.(HandlerOperationArgs)) } // ListenedType returns the type a OnHandlerOperationFinish event listener @@ -171,5 +165,5 @@ func (OnHandlerOperationFinish) ListenedType() reflect.Type { return handlerOper // Call the underlying event listener function by performing the type-assertion // on v whose type is the one returned by ListenedType(). func (f OnHandlerOperationFinish) Call(op dyngo.Operation, v interface{}) { - f(op, v.(HandlerOperationRes)) + f(op.(*Operation), v.(HandlerOperationRes)) } diff --git a/internal/appsec/dyngo/instrumentation/httpsec/tags.go b/internal/appsec/dyngo/instrumentation/httpsec/tags.go new file mode 100644 index 0000000000..77ae050586 --- /dev/null +++ b/internal/appsec/dyngo/instrumentation/httpsec/tags.go @@ -0,0 +1,103 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package httpsec + +import ( + "encoding/json" + "sort" + "strings" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" +) + +// SetAppSecTags sets the AppSec-specific span tags that are expected to be in +// the web service entry span (span of type `web`) when AppSec is enabled. +func SetAppSecTags(span ddtrace.Span) { + span.SetTag("_dd.appsec.enabled", 1) + span.SetTag("_dd.runtime_family", "go") +} + +// setEventSpanTags sets the security event span tags into the service entry span. +func setEventSpanTags(span ddtrace.Span, events json.RawMessage) { + // Set the appsec event span tag + // eventTag is the structure to use in the `_dd.appsec.json` span tag. + type eventTag struct { + Triggers json.RawMessage `json:"triggers"` + } + // TODO(Julio-Guerra): avoid serializing the json in the request hot path + event, err := json.Marshal(eventTag{Triggers: events}) + if err != nil { + log.Error("appsec: unexpected error while serializing the appsec event span tag: %v", err) + return + } + span.SetTag("_dd.appsec.json", string(event)) + // Keep this span due to the security event + span.SetTag(ext.ManualKeep, true) + span.SetTag("_dd.origin", "appsec") + // Set the appsec.event tag needed by the appsec backend + span.SetTag("appsec.event", true) +} + +// SetSecurityEventTags sets the AppSec-specific span tags when a security event occurred into the service entry span. +func SetSecurityEventTags(span ddtrace.Span, events json.RawMessage, remoteIP string, headers, respHeaders map[string][]string) { + setEventSpanTags(span, events) + span.SetTag("network.client.ip", remoteIP) + for h, v := range NormalizeHTTPHeaders(headers) { + span.SetTag("http.request.headers."+h, v) + } + for h, v := range NormalizeHTTPHeaders(respHeaders) { + span.SetTag("http.response.headers."+h, v) + } +} + +// List of HTTP headers we collect and send. +var collectedHTTPHeaders = [...]string{ + "host", + "x-forwarded-for", + "x-client-ip", + "x-real-ip", + "x-forwarded", + "x-cluster-client-ip", + "forwarded-for", + "forwarded", + "via", + "true-client-ip", + "content-length", + "content-type", + "content-encoding", + "content-language", + "forwarded", + "user-agent", + "accept", + "accept-encoding", + "accept-language", +} + +func init() { + // Required by sort.SearchStrings + sort.Strings(collectedHTTPHeaders[:]) +} + +// NormalizeHTTPHeaders returns the HTTP headers following Datadog's +// normalization format. +func NormalizeHTTPHeaders(headers map[string][]string) (normalized map[string]string) { + if len(headers) == 0 { + return nil + } + normalized = make(map[string]string) + for k, v := range headers { + k = strings.ToLower(k) + if i := sort.SearchStrings(collectedHTTPHeaders[:], k); i < len(collectedHTTPHeaders) && collectedHTTPHeaders[i] == k { + normalized[k] = strings.Join(v, ",") + } + } + if len(normalized) == 0 { + return nil + } + return normalized +} diff --git a/internal/appsec/dyngo/instrumentation/httpsec/tags_test.go b/internal/appsec/dyngo/instrumentation/httpsec/tags_test.go new file mode 100644 index 0000000000..fc07db4c12 --- /dev/null +++ b/internal/appsec/dyngo/instrumentation/httpsec/tags_test.go @@ -0,0 +1,51 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +package httpsec + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNormalizeHTTPHeaders(t *testing.T) { + for _, tc := range []struct { + headers map[string][]string + expected map[string]string + }{ + { + headers: nil, + expected: nil, + }, + { + headers: map[string][]string{ + "cookie": {"not-collected"}, + }, + expected: nil, + }, + { + headers: map[string][]string{ + "cookie": {"not-collected"}, + "x-forwarded-for": {"1.2.3.4,5.6.7.8"}, + }, + expected: map[string]string{ + "x-forwarded-for": "1.2.3.4,5.6.7.8", + }, + }, + { + headers: map[string][]string{ + "cookie": {"not-collected"}, + "x-forwarded-for": {"1.2.3.4,5.6.7.8", "9.10.11.12,13.14.15.16"}, + }, + expected: map[string]string{ + "x-forwarded-for": "1.2.3.4,5.6.7.8,9.10.11.12,13.14.15.16", + }, + }, + } { + headers := NormalizeHTTPHeaders(tc.headers) + require.Equal(t, tc.expected, headers) + } +} diff --git a/internal/appsec/dyngo/operation.go b/internal/appsec/dyngo/operation.go index af9a39c64c..de8b57b4a4 100644 --- a/internal/appsec/dyngo/operation.go +++ b/internal/appsec/dyngo/operation.go @@ -3,6 +3,21 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2016 Datadog, Inc. +// Package dyngo is the Go implementation of Datadog's Instrumentation Gateway +// which provides an event-based instrumentation API based on a stack +// representation of instrumented functions along with nested event listeners. +// It allows to both correlate passed and future function calls in order to +// react and monitor specific function call scenarios, while keeping the +// monitoring state local to the monitoring logic thanks to nested Go function +// closures. +// dyngo is not intended to be directly used and should be instead wrapped +// behind statically and strongly typed wrapper types. Indeed, dyngo is a +// generic implementation relying on empty interface values (values of type +// `interface{}`) and using it directly can be error-prone due to the lack of +// compile-time type-checking. For example, AppSec provides the package +// `httpsec`, built on top of dyngo, as its HTTP instrumentation API and which +// defines the abstract HTTP operation representation expected by the AppSec +// monitoring. package dyngo import ( @@ -27,14 +42,23 @@ type Operation interface { // operation. Parent() Operation - // emitEvent is a private method implemented by OperationImpl. - // We don't want to expose it to avoid allowing users to emit events - // themselves. + // emitEvent emits the event to listeners of the given argsType and calls + // them with the given op and v values. + // emitEvent is a private method implemented by the operation struct type so + // that no other package can define it. emitEvent(argsType reflect.Type, op Operation, v interface{}) // register the given event listeners and return the unregistration // function allowing to remove the event listener from the operation. + // register is a private method implemented by the operation struct type so + // that no other package can define it. register(...EventListener) UnregisterFunc + + // finish the operation. This method allows to pass the operation value to + // use to emit the finish event. + // finish is a private method implemented by the operation struct type so + // that no other package can define it. + finish(op Operation, results interface{}) } // EventListener interface allowing to identify the Go type listened to and @@ -48,7 +72,8 @@ type EventListener interface { Call(op Operation, v interface{}) } -// UnregisterFunc is a function allowing to unregister from an operation the previously registered event listeners. +// UnregisterFunc is a function allowing to unregister from an operation the +// previously registered event listeners. type UnregisterFunc func() var rootOperation = newOperation(nil) @@ -58,9 +83,11 @@ func Register(listeners ...EventListener) UnregisterFunc { return rootOperation.register(listeners...) } -// OperationImpl structure allowing to subscribe to operation events and to navigate in the operation stack. Events -// bubble-up the operation stack, which allows listening to future events that might happen in the operation lifetime. -type OperationImpl struct { +// operation structure allowing to subscribe to operation events and to +// navigate in the operation stack. Events +// bubble-up the operation stack, which allows listening to future events that +// might happen in the operation lifetime. +type operation struct { parent Operation eventRegister @@ -68,52 +95,92 @@ type OperationImpl struct { mu sync.RWMutex } -// StartOperation starts a new operation along with its arguments and emits a start event with the operation arguments. -func StartOperation(args interface{}, parent Operation) *OperationImpl { +// NewOperation creates and returns a new operationIt must be started by calling +// StartOperation, and finished by calling FinishOperation. The returned +// operation should be used in wrapper types to provide statically typed start +// and finish functions. The following example shows how to wrap an operation +// so that its functions are statically typed (instead of dyngo's interface{} +// values): +// package mypackage +// import "dyngo" +// type ( +// MyOperation struct { +// dyngo.Operation +// } +// MyOperationArgs { /* ... */ } +// MyOperationRes { /* ... */ } +// ) +// func StartOperation(args MyOperationArgs, parent dyngo.Operation) MyOperation { +// op := MyOperation{Operation: dyngo.NewOperation(parent)} +// dyngo.StartOperation(op, args) +// return op +// } +// func (op MyOperation) Finish(res MyOperationRes) { +// dyngo.FinishOperation(op, res) +// } +func NewOperation(parent Operation) Operation { if parent == nil { parent = rootOperation } - newOp := newOperation(parent) + return newOperation(parent) +} + +// StartOperation starts a new operation along with its arguments and emits a +// start event with the operation arguments. +func StartOperation(op Operation, args interface{}) { argsType := reflect.TypeOf(args) - // Bubble-up the start event starting from the parent operation as you can't listen for your own start event - for op := parent; op != nil; op = op.Parent() { - op.emitEvent(argsType, newOp, args) + // Bubble-up the start event starting from the parent operation as you can't + // listen for your own start event + for current := op.Parent(); current != nil; current = current.Parent() { + current.emitEvent(argsType, op, args) } - return newOp } -func newOperation(parent Operation) *OperationImpl { - return &OperationImpl{parent: parent} +func newOperation(parent Operation) *operation { + return &operation{parent: parent} } // Parent return the parent operation. It returns nil for the root operation. -func (o *OperationImpl) Parent() Operation { +func (o *operation) Parent() Operation { return o.parent } -// Finish finishes the operation along with its results and emits a finish event with the operation results. +// FinishOperation finishes the operation along with its results and emits a +// finish event with the operation results. // The operation is then disabled and its event listeners removed. -func (o *OperationImpl) Finish(results interface{}) { +func FinishOperation(op Operation, results interface{}) { + op.finish(op, results) +} + +func (o *operation) finish(op Operation, results interface{}) { + // Defer the call to o.disable() first so that the RWMutex gets unlocked first + defer o.disable() o.mu.RLock() - defer o.mu.RUnlock() + defer o.mu.RUnlock() // Deferred and stacked on top of the previously deferred call to o.disable() if o.disabled { return } - defer o.disable() resType := reflect.TypeOf(results) - for op := Operation(o); op != nil; op = op.Parent() { - op.emitEvent(resType, o, results) + for current := op; current != nil; current = current.Parent() { + current.emitEvent(resType, op, results) } } -func (o *OperationImpl) disable() { +// Disable the operation and remove all its event listeners. +func (o *operation) disable() { + o.mu.Lock() + defer o.mu.Unlock() + if o.disabled { + return + } o.disabled = true o.eventRegister.clear() } -// Register allows to register the given event listeners to the operation. An unregistration function is returned -// allowing to unregister the event listeners from the operation. -func (o *OperationImpl) register(l ...EventListener) UnregisterFunc { +// Register allows to register the given event listeners to the operation. An +// unregistration function is returned allowing to unregister the event +// listeners from the operation. +func (o *operation) register(l ...EventListener) UnregisterFunc { // eventRegisterIndex allows to lookup for the event listener in the event register. type eventRegisterIndex struct { key reflect.Type @@ -143,13 +210,13 @@ func (o *OperationImpl) register(l ...EventListener) UnregisterFunc { } } -// On registers the event listener. The difference with the Register() is that it doesn't return a function closure, -// which avoids unnecessary allocations +// On registers the event listener. The difference with the Register() is that +// it doesn't return a function closure, which avoids unnecessary allocations // For example: -// op.On(HTTPHandlerOperationStart(func (op *OperationImpl, args HTTPHandlerOperationArgs) { +// op.On(MyOperationStart(func (op MyOperation, args MyOperationArgs) { // // ... // })) -func (o *OperationImpl) On(l EventListener) { +func (o *operation) On(l EventListener) { o.mu.RLock() defer o.mu.RUnlock() if o.disabled { @@ -165,23 +232,27 @@ type ( listeners eventListenerMap } - // eventListenerMap is the map of event listeners. The list of listeners are indexed by the operation argument or - // result type the event listener expects. + // eventListenerMap is the map of event listeners. The list of listeners are + // indexed by the operation argument or result type the event listener + // expects. eventListenerMap map[reflect.Type][]eventListenerMapEntry eventListenerMapEntry struct { id eventListenerID listener EventListener } - // eventListenerID is the unique ID of an event when registering it. It allows to find it back and remove it from - // the list of event listeners when unregistering it. + // eventListenerID is the unique ID of an event when registering it. It + // allows to find it back and remove it from the list of event listeners + // when unregistering it. eventListenerID uint32 ) -// lastID is the last event listener ID that was given to the latest event listener. +// lastID is the last event listener ID that was given to the latest event +// listener. var lastID eventListenerID -// nextID atomically increments lastID and returns the new event listener ID to use. +// nextID atomically increments lastID and returns the new event listener ID to +// use. func nextID() eventListenerID { return eventListenerID(atomic.AddUint32((*uint32)(&lastID), 1)) } @@ -192,8 +263,10 @@ func (r *eventRegister) add(key reflect.Type, l EventListener) eventListenerID { if r.listeners == nil { r.listeners = make(eventListenerMap) } - // id is computed when the lock is exclusively taken so that we know listeners are added in incremental id order. - // This allows to use the optimized sort.Search() function to remove the entry. + // id is computed when the lock is exclusively taken so that we know + // listeners are added in incremental id order. + // This allows to use the optimized sort.Search() function to remove the + // entry. id := nextID() r.listeners[key] = append(r.listeners[key], eventListenerMapEntry{ id: id, diff --git a/internal/appsec/dyngo/operation_test.go b/internal/appsec/dyngo/operation_test.go index 93365ede99..9e15e953c9 100644 --- a/internal/appsec/dyngo/operation_test.go +++ b/internal/appsec/dyngo/operation_test.go @@ -17,6 +17,7 @@ import ( "sync/atomic" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo" @@ -261,7 +262,7 @@ func TestUsage(t *testing.T) { t.Run("operation-stacking", func(t *testing.T) { // Run an operation stack that is monitored and not blocked by waf - root := dyngo.StartOperation(RootArgs{}, nil) + root := startOperation(RootArgs{}, nil) var ( WAFBlocked bool @@ -286,20 +287,20 @@ func TestUsage(t *testing.T) { root.On(jsonBodyValueListener) // Run the monitored stack of operations - operation( + runOperation( root, HTTPHandlerArgs{ URL: &url.URL{RawQuery: "?v=ok"}, Headers: http.Header{"header": []string{"value"}}}, HTTPHandlerRes{}, func(op dyngo.Operation) { - operation(op, JSONParserArgs{}, JSONParserRes{Value: []interface{}{"a", "json", "array"}}, func(op dyngo.Operation) { - operation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("my ")}, nil) - operation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("raw ")}, nil) - operation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("bo")}, nil) - operation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("dy"), Err: io.EOF}, nil) + runOperation(op, JSONParserArgs{}, JSONParserRes{Value: []interface{}{"a", "json", "array"}}, func(op dyngo.Operation) { + runOperation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("my ")}, nil) + runOperation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("raw ")}, nil) + runOperation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("bo")}, nil) + runOperation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("dy"), Err: io.EOF}, nil) }) - operation(op, SQLQueryArgs{}, SQLQueryRes{}, nil) + runOperation(op, SQLQueryArgs{}, SQLQueryRes{}, nil) }, ) @@ -318,7 +319,7 @@ func TestUsage(t *testing.T) { t.Run("operation-stacking", func(t *testing.T) { // Operation stack monitored and blocked by waf via the http operation monitoring - root := dyngo.StartOperation(RootArgs{}, nil) + root := startOperation(RootArgs{}, nil) var ( WAFBlocked bool @@ -344,22 +345,22 @@ func TestUsage(t *testing.T) { // Run the monitored stack of operations RawBodyBuf = nil - operation( + runOperation( root, HTTPHandlerArgs{ URL: &url.URL{RawQuery: "?v=attack"}, Headers: http.Header{"header": []string{"value"}}}, HTTPHandlerRes{}, func(op dyngo.Operation) { - operation(op, JSONParserArgs{}, JSONParserRes{Value: "a string", Err: errors.New("an error")}, func(op dyngo.Operation) { - operation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("another ")}, nil) - operation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("raw ")}, nil) - operation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("bo")}, nil) - operation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("dy"), Err: nil}, nil) - operation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte(" value"), Err: io.EOF}, nil) + runOperation(op, JSONParserArgs{}, JSONParserRes{Value: "a string", Err: errors.New("an error")}, func(op dyngo.Operation) { + runOperation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("another ")}, nil) + runOperation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("raw ")}, nil) + runOperation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("bo")}, nil) + runOperation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("dy"), Err: nil}, nil) + runOperation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte(" value"), Err: io.EOF}, nil) }) - operation(op, SQLQueryArgs{}, SQLQueryRes{}, nil) + runOperation(op, SQLQueryArgs{}, SQLQueryRes{}, nil) }, ) @@ -378,7 +379,7 @@ func TestUsage(t *testing.T) { t.Run("operation-stack", func(t *testing.T) { // Operation stack not monitored - root := dyngo.StartOperation(RootArgs{}, nil) + root := startOperation(RootArgs{}, nil) var ( WAFBlocked bool @@ -403,17 +404,17 @@ func TestUsage(t *testing.T) { root.On(jsonBodyValueListener) // Run the monitored stack of operations - operation( + runOperation( root, GRPCHandlerArgs{}, GRPCHandlerRes{}, func(op dyngo.Operation) { - operation(op, JSONParserArgs{}, JSONParserRes{Value: []interface{}{"a", "json", "array"}}, func(op dyngo.Operation) { - operation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("my ")}, nil) - operation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("raw ")}, nil) - operation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("bo")}, nil) - operation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("dy"), Err: io.EOF}, nil) + runOperation(op, JSONParserArgs{}, JSONParserRes{Value: []interface{}{"a", "json", "array"}}, func(op dyngo.Operation) { + runOperation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("my ")}, nil) + runOperation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("raw ")}, nil) + runOperation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("bo")}, nil) + runOperation(op, BodyReadArgs{}, BodyReadRes{Buf: []byte("dy"), Err: io.EOF}, nil) }) - operation(op, SQLQueryArgs{}, SQLQueryRes{}, nil) + runOperation(op, SQLQueryArgs{}, SQLQueryRes{}, nil) }, ) @@ -432,7 +433,7 @@ func TestUsage(t *testing.T) { }) t.Run("recursive-operation", func(t *testing.T) { - root := dyngo.StartOperation(RootArgs{}, nil) + root := startOperation(RootArgs{}, nil) defer root.Finish(RootRes{}) called := 0 @@ -440,11 +441,11 @@ func TestUsage(t *testing.T) { called++ })) - operation(root, HTTPHandlerArgs{}, HTTPHandlerRes{}, func(o dyngo.Operation) { - operation(o, HTTPHandlerArgs{}, HTTPHandlerRes{}, func(o dyngo.Operation) { - operation(o, HTTPHandlerArgs{}, HTTPHandlerRes{}, func(o dyngo.Operation) { - operation(o, HTTPHandlerArgs{}, HTTPHandlerRes{}, func(o dyngo.Operation) { - operation(o, HTTPHandlerArgs{}, HTTPHandlerRes{}, func(dyngo.Operation) { + runOperation(root, HTTPHandlerArgs{}, HTTPHandlerRes{}, func(o dyngo.Operation) { + runOperation(o, HTTPHandlerArgs{}, HTTPHandlerRes{}, func(o dyngo.Operation) { + runOperation(o, HTTPHandlerArgs{}, HTTPHandlerRes{}, func(o dyngo.Operation) { + runOperation(o, HTTPHandlerArgs{}, HTTPHandlerRes{}, func(o dyngo.Operation) { + runOperation(o, HTTPHandlerArgs{}, HTTPHandlerRes{}, func(dyngo.Operation) { }) }) }) @@ -454,9 +455,46 @@ func TestUsage(t *testing.T) { require.Equal(t, 5, called) }) + t.Run("wrapped-operation-type-assertion", func(t *testing.T) { + // dyngo's API should allow to retrieve the actual wrapper types: an + // event listener should be called with the wrapped value. + + // Define `myop` so that it wraps a dyngo.Operation value so that it + // implements dyngo.Operation interface and we can check the event + // listeners get called with a value of type `myop`. + type myop struct { + dyngo.Operation + // count the number of calls to check the test is working as expected + called int + } + + // Create a root operation to listen for a child `myop` operation. + someRoot := dyngo.NewOperation(nil) + dyngo.StartOperation(someRoot, RootArgs{}) + defer dyngo.FinishOperation(someRoot, RootRes{}) + // Register start and finish event listeners, and type-assert that the + // passed operation has type `myop`. + someRoot.On(OnMyOperationStart(func(op dyngo.Operation, _ MyOperationArgs) { + v, ok := op.(*myop) + assert.True(t, ok) + v.called++ + })) + someRoot.On(OnMyOperationFinish(func(op dyngo.Operation, _ MyOperationRes) { + v, ok := op.(*myop) + assert.True(t, ok) + v.called++ + })) + + // Create a `myop` pointer value and start an operation with it. + op := &myop{Operation: dyngo.NewOperation(someRoot)} + dyngo.StartOperation(op, MyOperationArgs{}) + dyngo.FinishOperation(op, MyOperationRes{}) + require.Equal(t, 2, op.called) + }) + t.Run("concurrency", func(t *testing.T) { // root is the shared operation having concurrent accesses in this test - root := dyngo.StartOperation(RootArgs{}, nil) + root := startOperation(RootArgs{}, nil) defer root.Finish(RootRes{}) // Create nbGoroutines registering event listeners concurrently @@ -499,8 +537,8 @@ func TestUsage(t *testing.T) { started.Done() startBarrier.Wait() defer done.Done() - op := dyngo.StartOperation(MyOperationArgs{}, root) - defer op.Finish(MyOperationRes{}) + op := startOperation(MyOperationArgs{}, root) + defer dyngo.FinishOperation(op, MyOperationRes{}) }() } @@ -549,7 +587,7 @@ func TestUsage(t *testing.T) { defer unregister() // Emit events by passing the goroutine number g - op := dyngo.StartOperation(MyOperationArgs{g}, nil) + op := startOperation(MyOperationArgs{g}, nil) defer op.Finish(MyOperationRes{g}) }(g) } @@ -572,21 +610,21 @@ func TestRegisterUnregister(t *testing.T) { called++ })) - op := dyngo.StartOperation(MyOperationArgs{}, nil) + op := startOperation(MyOperationArgs{}, nil) require.Equal(t, 1, called) - op.Finish(MyOperationRes{}) + dyngo.FinishOperation(op, MyOperationRes{}) unregister() - op = dyngo.StartOperation(MyOperationArgs{}, nil) + op = startOperation(MyOperationArgs{}, nil) require.Equal(t, 1, called) - op.Finish(MyOperationRes{}) + dyngo.FinishOperation(op, MyOperationRes{}) require.NotPanics(t, func() { unregister() }) - op = dyngo.StartOperation(MyOperationArgs{}, nil) + op = startOperation(MyOperationArgs{}, nil) require.Equal(t, 1, called) - op.Finish(MyOperationRes{}) + dyngo.FinishOperation(op, MyOperationRes{}) }) t.Run("multiple-listeners", func(t *testing.T) { @@ -601,12 +639,12 @@ func TestRegisterUnregister(t *testing.T) { }), ) - operation(nil, MyOperationArgs{}, MyOperationRes{}, func(op dyngo.Operation) {}) + runOperation(nil, MyOperationArgs{}, MyOperationRes{}, func(op dyngo.Operation) {}) require.Equal(t, 1, onStartCalled) require.Equal(t, 1, onFinishCalled) unregister() - operation(nil, MyOperationArgs{}, MyOperationRes{}, func(op dyngo.Operation) {}) + runOperation(nil, MyOperationArgs{}, MyOperationRes{}, func(op dyngo.Operation) {}) require.Equal(t, 1, onStartCalled) require.Equal(t, 1, onFinishCalled) @@ -616,9 +654,24 @@ func TestRegisterUnregister(t *testing.T) { }) } -func operation(parent dyngo.Operation, args, res interface{}, child func(dyngo.Operation)) { - op := dyngo.StartOperation(args, parent) - defer op.Finish(res) +// Helper type wrapping a dyngo.Operation to provide some helper function and +// method helping to simplify the source code of the tests +type operation struct{ dyngo.Operation } + +// Helper function to create an operation, wrap it and start it +func startOperation(args interface{}, parent dyngo.Operation) operation { + op := operation{dyngo.NewOperation(parent)} + dyngo.StartOperation(op, args) + return op +} + +// Helper method to finish the operation +func (op operation) Finish(res interface{}) { dyngo.FinishOperation(op, res) } + +// Helper function to run operations recursively. +func runOperation(parent dyngo.Operation, args, res interface{}, child func(dyngo.Operation)) { + op := startOperation(args, parent) + defer dyngo.FinishOperation(op, res) if child != nil { child(op) } @@ -626,29 +679,29 @@ func operation(parent dyngo.Operation, args, res interface{}, child func(dyngo.O func TestOperationEvents(t *testing.T) { t.Run("start-event", func(t *testing.T) { - op1 := dyngo.StartOperation(MyOperationArgs{}, nil) + op1 := startOperation(MyOperationArgs{}, nil) var called int op1.On(OnMyOperation2Start(func(dyngo.Operation, MyOperation2Args) { called++ })) - op2 := dyngo.StartOperation(MyOperation2Args{}, op1) + op2 := startOperation(MyOperation2Args{}, op1) op2.Finish(MyOperation2Res{}) // Called once require.Equal(t, 1, called) - op2 = dyngo.StartOperation(MyOperation2Args{}, op1) + op2 = startOperation(MyOperation2Args{}, op1) op2.Finish(MyOperation2Res{}) // Called again require.Equal(t, 2, called) // Finish the operation so that it gets disabled and its listeners removed - op1.Finish(MyOperationRes{}) + dyngo.FinishOperation(op1, MyOperationRes{}) - op2 = dyngo.StartOperation(MyOperation2Args{}, op1) + op2 = startOperation(MyOperation2Args{}, op1) op2.Finish(MyOperation2Res{}) // No longer called @@ -656,29 +709,29 @@ func TestOperationEvents(t *testing.T) { }) t.Run("finish-event", func(t *testing.T) { - op1 := dyngo.StartOperation(MyOperationArgs{}, nil) + op1 := startOperation(MyOperationArgs{}, nil) var called int op1.On(OnMyOperation2Finish(func(dyngo.Operation, MyOperation2Res) { called++ })) - op2 := dyngo.StartOperation(MyOperation2Args{}, op1) + op2 := startOperation(MyOperation2Args{}, op1) op2.Finish(MyOperation2Res{}) // Called once require.Equal(t, 1, called) - op2 = dyngo.StartOperation(MyOperation2Args{}, op1) + op2 = startOperation(MyOperation2Args{}, op1) op2.Finish(MyOperation2Res{}) // Called again require.Equal(t, 2, called) - op3 := dyngo.StartOperation(MyOperation3Args{}, op2) + op3 := startOperation(MyOperation3Args{}, op2) op3.Finish(MyOperation3Res{}) // Not called require.Equal(t, 2, called) - op2 = dyngo.StartOperation(MyOperation2Args{}, op3) + op2 = startOperation(MyOperation2Args{}, op3) op2.Finish(MyOperation2Res{}) // Called again require.Equal(t, 3, called) @@ -686,12 +739,12 @@ func TestOperationEvents(t *testing.T) { // Finish the operation so that it gets disabled and its listeners removed op1.Finish(MyOperationRes{}) - op2 = dyngo.StartOperation(MyOperation2Args{}, op3) + op2 = startOperation(MyOperation2Args{}, op3) op2.Finish(MyOperation2Res{}) // No longer called require.Equal(t, 3, called) - op2 = dyngo.StartOperation(MyOperation2Args{}, op2) + op2 = startOperation(MyOperation2Args{}, op2) op2.Finish(MyOperation2Res{}) // No longer called require.Equal(t, 3, called) @@ -710,11 +763,11 @@ func TestOperationEvents(t *testing.T) { // Start an operation and register event listeners to it. // This step allows to test the listeners are called when the operation is alive - op := dyngo.StartOperation(MyOperationArgs{}, nil) + op := startOperation(MyOperationArgs{}, nil) registerTo(op) // Trigger the registered events - op2 := dyngo.StartOperation(MyOperation2Args{}, op) + op2 := startOperation(MyOperation2Args{}, op) op2.Finish(MyOperation2Res{}) // We should have 4 calls require.Equal(t, 2, calls) @@ -723,7 +776,7 @@ func TestOperationEvents(t *testing.T) { op.Finish(MyOperationRes{}) // Trigger the same events - op2 = dyngo.StartOperation(MyOperation2Args{}, op) + op2 = startOperation(MyOperation2Args{}, op) op2.Finish(MyOperation2Res{}) // The number of calls should be unchanged require.Equal(t, 2, calls) @@ -731,7 +784,7 @@ func TestOperationEvents(t *testing.T) { // Register again, but it shouldn't work because the operation is finished. registerTo(op) // Trigger the same events - op2 = dyngo.StartOperation(MyOperation2Args{}, op) + op2 = startOperation(MyOperation2Args{}, op) op2.Finish(MyOperation2Res{}) // The number of calls should be unchanged require.Equal(t, 2, calls) @@ -739,7 +792,7 @@ func TestOperationEvents(t *testing.T) { t.Run("event-listener-panic", func(t *testing.T) { t.Run("start", func(t *testing.T) { - op := dyngo.StartOperation(MyOperationArgs{}, nil) + op := startOperation(MyOperationArgs{}, nil) defer op.Finish(MyOperationRes{}) // Panic on start @@ -751,7 +804,7 @@ func TestOperationEvents(t *testing.T) { })) // Start the operation triggering the event: it should not panic require.NotPanics(t, func() { - op := dyngo.StartOperation(MyOperationArgs{}, op) + op := startOperation(MyOperationArgs{}, op) require.NotNil(t, op) defer op.Finish(MyOperationRes{}) require.Equal(t, calls, 1) @@ -759,7 +812,7 @@ func TestOperationEvents(t *testing.T) { }) t.Run("finish", func(t *testing.T) { - op := dyngo.StartOperation(MyOperationArgs{}, nil) + op := startOperation(MyOperationArgs{}, nil) defer op.Finish(MyOperationRes{}) // Panic on finish calls := 0 @@ -770,7 +823,7 @@ func TestOperationEvents(t *testing.T) { })) // Run the operation triggering the finish event: it should not panic require.NotPanics(t, func() { - op := dyngo.StartOperation(MyOperationArgs{}, op) + op := startOperation(MyOperationArgs{}, op) require.NotNil(t, op) op.Finish(MyOperationRes{}) require.Equal(t, calls, 1) @@ -784,12 +837,12 @@ func BenchmarkEvents(b *testing.B) { // Benchmark the emission of events according to the operation stack length for length := 1; length <= 64; length *= 2 { b.Run(fmt.Sprintf("stack=%d", length), func(b *testing.B) { - root := dyngo.StartOperation(MyOperationArgs{}, nil) + root := startOperation(MyOperationArgs{}, nil) defer root.Finish(MyOperationRes{}) op := root for i := 0; i < length-1; i++ { - op = dyngo.StartOperation(MyOperationArgs{}, op) + op = startOperation(MyOperationArgs{}, op) defer op.Finish(MyOperationRes{}) } @@ -799,7 +852,7 @@ func BenchmarkEvents(b *testing.B) { b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { - dyngo.StartOperation(MyOperationArgs{}, op) + startOperation(MyOperationArgs{}, op) } }) @@ -809,7 +862,7 @@ func BenchmarkEvents(b *testing.B) { b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { - leafOp := dyngo.StartOperation(MyOperationArgs{}, op) + leafOp := startOperation(MyOperationArgs{}, op) leafOp.Finish(MyOperationRes{}) } }) @@ -818,7 +871,7 @@ func BenchmarkEvents(b *testing.B) { }) b.Run("registering", func(b *testing.B) { - op := dyngo.StartOperation(MyOperationArgs{}, nil) + op := startOperation(MyOperationArgs{}, nil) defer op.Finish(MyOperationRes{}) b.Run("start event", func(b *testing.B) { diff --git a/internal/appsec/events.go b/internal/appsec/events.go deleted file mode 100644 index b3ca6bd349..0000000000 --- a/internal/appsec/events.go +++ /dev/null @@ -1,264 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed -// under the Apache License Version 2.0. -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2016 Datadog, Inc. - -package appsec - -import ( - "strconv" - - "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo/instrumentation/httpinstr" -) - -// securityEvent interface allowing to lazily serialize an event into an intake -// api struct actually sending it. Additional context can be optionally added to -// the security event using the following event wrappers. -type securityEvent interface { - toIntakeEvents() ([]*attackEvent, error) -} - -type ( - // httpContext is the security event context describing an HTTP handler. - // It includes information about its request and response. - httpContext struct { - Request httpRequestContext - Response httpResponseContext - } - - // httpRequestContext is the HTTP request context of an HTTP operation - // context. - httpRequestContext struct { - Method string - Host string - IsTLS bool - RequestURI string - RemoteAddr string - Path string - Headers map[string][]string - Query map[string][]string - } - - // httpResponseContext is the HTTP response context of an HTTP operation - // context. - httpResponseContext struct { - Status int - } -) - -type withHTTPContext struct { - securityEvent - ctx httpContext -} - -// withHTTPOperationContext adds the HTTP context to the event. -func withHTTPOperationContext(event securityEvent, args httpinstr.HandlerOperationArgs, res httpinstr.HandlerOperationRes) securityEvent { - return withHTTPContext{ - securityEvent: event, - ctx: httpContext{ - Request: httpRequestContext{ - Method: args.Method, - Host: args.Host, - IsTLS: args.IsTLS, - RequestURI: args.RequestURI, - Path: args.Path, - RemoteAddr: args.RemoteAddr, - Headers: args.Headers, - Query: args.Query, - }, - Response: httpResponseContext{ - Status: res.Status, - }, - }, - } -} - -// toIntakeEvent converts the current event with the HTTP context into an -// intake security event. -func (e withHTTPContext) toIntakeEvents() ([]*attackEvent, error) { - events, err := e.securityEvent.toIntakeEvents() - if err != nil { - return nil, err - } - reqContext := makeAttackContextHTTPRequest(e.ctx.Request) - resContext := makeAttackContextHTTPResponse(e.ctx.Response.Status) - httpContext := makeAttackContextHTTP(reqContext, resContext) - for _, event := range events { - event.Context.HTTP = httpContext - } - return events, nil -} - -// makeAttackContextHTTPRequest create the api.attackContextHTTPRequest payload -// from the given httpRequestContext. -func makeAttackContextHTTPRequest(req httpRequestContext) attackContextHTTPRequest { - host, portStr := splitHostPort(req.Host) - port, _ := strconv.Atoi(portStr) - remoteIP, remotePortStr := splitHostPort(req.RemoteAddr) - remotePort, _ := strconv.Atoi(remotePortStr) - var scheme string - if req.IsTLS { - scheme = "https" - } else { - scheme = "http" - } - url := makeHTTPURL(scheme, req.Host, req.Path) - headers := makeHTTPHeaders(req.Headers) - return attackContextHTTPRequest{ - Scheme: scheme, - Method: req.Method, - URL: url, - Host: host, - Port: port, - Path: req.Path, - RemoteIP: remoteIP, - RemotePort: remotePort, - Headers: headers, - Parameters: attackContextHTTPRequestParameters{Query: req.Query}, - } -} - -type spanContext struct { - securityEvent - traceID, spanID uint64 -} - -// withSpanContext adds the span context to the event. -func withSpanContext(event securityEvent, traceID, spanID uint64) securityEvent { - return spanContext{ - securityEvent: event, - traceID: traceID, - spanID: spanID, - } -} - -// ToIntakeEvent converts the current event with the span context into an -// intake security event. -func (ctx spanContext) toIntakeEvents() ([]*attackEvent, error) { - events, err := ctx.securityEvent.toIntakeEvents() - if err != nil { - return nil, err - } - traceID := strconv.FormatUint(ctx.traceID, 10) - spanID := strconv.FormatUint(ctx.spanID, 10) - traceContext := makeAttackContextTrace(traceID) - spanContext := makeAttackContextSpan(spanID) - for _, event := range events { - event.Context.Trace = traceContext - event.Context.Span = spanContext - } - return events, nil -} - -type serviceContext struct { - securityEvent - name, version, environment string -} - -// withServiceContext adds the service context to the event. -func withServiceContext(event securityEvent, name, version, environment string) securityEvent { - return serviceContext{ - securityEvent: event, - name: name, - version: version, - environment: environment, - } -} - -// ToIntakeEvent converts the current event with the service context into an -// intake security event. -func (ctx serviceContext) toIntakeEvents() ([]*attackEvent, error) { - events, err := ctx.securityEvent.toIntakeEvents() - if err != nil { - return nil, err - } - serviceContext := makeServiceContext(ctx.name, ctx.version, ctx.environment) - for _, event := range events { - event.Context.Service = serviceContext - } - return events, nil -} - -type tagsContext struct { - securityEvent - tags []string -} - -// withTagsContext adds the tags context to the event. -func withTagsContext(event securityEvent, tags []string) securityEvent { - return tagsContext{ - securityEvent: event, - tags: tags, - } -} - -// ToIntakeEvent converts the current event with the tags context into an -// intake security event. -func (ctx tagsContext) toIntakeEvents() ([]*attackEvent, error) { - events, err := ctx.securityEvent.toIntakeEvents() - if err != nil { - return nil, err - } - tagsContext := newAttackContextTags(ctx.tags) - for _, event := range events { - event.Context.Tags = tagsContext - } - return events, nil -} - -type tracerContext struct { - securityEvent - runtime, runtimeVersion, version string -} - -// withTracerContext adds the tracer context to the event. -func withTracerContext(event securityEvent, runtime, runtimeVersion, version string) securityEvent { - return tracerContext{ - securityEvent: event, - runtime: runtime, - runtimeVersion: runtimeVersion, - version: version, - } -} - -// ToIntakeEvent converts the current event with the tracer context into an -// intake security event. -func (ctx tracerContext) toIntakeEvents() ([]*attackEvent, error) { - events, err := ctx.securityEvent.toIntakeEvents() - if err != nil { - return nil, err - } - tracerContext := makeAttackContextTracer(ctx.version, ctx.runtime, ctx.runtimeVersion) - for _, event := range events { - event.Context.Tracer = tracerContext - } - return events, nil -} - -// withHostContext adds the running host context to the event. -func withHostContext(event securityEvent, hostname, osname string) securityEvent { - return hostContext{ - securityEvent: event, - hostname: hostname, - osname: osname, - } -} - -type hostContext struct { - securityEvent - hostname, osname string -} - -// ToIntakeEvent converts the current event with the host context into an intake -// security event. -func (ctx hostContext) toIntakeEvents() ([]*attackEvent, error) { - events, err := ctx.securityEvent.toIntakeEvents() - if err != nil { - return nil, err - } - hostContext := makeAttackContextHost(ctx.hostname, ctx.osname) - for _, event := range events { - event.Context.Host = hostContext - } - return events, nil -} diff --git a/internal/appsec/rule.go b/internal/appsec/rule.go index 798d1d8390..07f0ff40d5 100644 --- a/internal/appsec/rule.go +++ b/internal/appsec/rule.go @@ -8,6 +8,6 @@ package appsec -// Static recommended AppSec rule -// Source: https://github.com/DataDog/appsec-event-rules/blob/main/v2/build/recommended.json -const staticRecommendedRule = "{\"version\":\"2.1\",\"rules\":[{\"id\":\"crs-913-110\",\"name\":\"Found request header associated with Acunetix security scanner\",\"tags\":{\"type\":\"security_scanner\",\"crs_id\":\"913110\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.headers.no_cookies\"}],\"list\":[\"acunetix-product\",\"(acunetix web vulnerability scanner\",\"acunetix-scanning-agreement\",\"acunetix-user-agreement\"]},\"operator\":\"phrase_match\"}],\"transformers\":[\"lowercase\"]},{\"id\":\"crs-913-120\",\"name\":\"Found request filename/argument associated with security scanner\",\"tags\":{\"type\":\"security_scanner\",\"crs_id\":\"913120\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"list\":[\"/.adsensepostnottherenonobook\",\"/hello.html\",\"/actsensepostnottherenonotive\",\"/acunetix-wvs-test-for-some-inexistent-file\",\"/antidisestablishmentarianism\",\"/appscan_fingerprint/mac_address\",\"/arachni-\",\"/cybercop\",\"/nessus_is_probing_you_\",\"/nessustest\",\"/netsparker-\",\"/rfiinc.txt\",\"/thereisnowaythat-you-canbethere\",\"/w3af/remotefileinclude.html\",\"appscan_fingerprint\",\"w00tw00t.at.isc.sans.dfind\",\"w00tw00t.at.blackhats.romanian.anti-sec\"]},\"operator\":\"phrase_match\"}],\"transformers\":[\"lowercase\"]},{\"id\":\"crs-920-260\",\"name\":\"Unicode Full/Half Width Abuse Attack Attempt\",\"tags\":{\"type\":\"http_protocol_violation\",\"crs_id\":\"920260\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.uri.raw\"}],\"regex\":\"\\\\%u[fF]{2}[0-9a-fA-F]{2}\",\"options\":{\"case_sensitive\":true,\"min_length\":6}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-921-110\",\"name\":\"HTTP Request Smuggling Attack\",\"tags\":{\"type\":\"http_protocol_violation\",\"crs_id\":\"921110\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"(?:get|post|head|options|connect|put|delete|trace|track|patch|propfind|propatch|mkcol|copy|move|lock|unlock)\\\\s+[^\\\\s]+\\\\s+http/\\\\d\",\"options\":{\"case_sensitive\":true,\"min_length\":12}},\"operator\":\"match_regex\"}],\"transformers\":[\"lowercase\"]},{\"id\":\"crs-921-140\",\"name\":\"HTTP Header Injection Attack via headers\",\"tags\":{\"type\":\"http_protocol_violation\",\"crs_id\":\"921140\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.headers.no_cookies\"}],\"regex\":\"[\\\\n\\\\r]\",\"options\":{\"case_sensitive\":true,\"min_length\":1}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-921-160\",\"name\":\"HTTP Header Injection Attack via payload (CR/LF and header-name detected)\",\"tags\":{\"type\":\"http_protocol_violation\",\"crs_id\":\"921160\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.query\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"[\\\\n\\\\r]+(?:\\\\s|location|refresh|(?:set-)?cookie|(?:x-)?(?:forwarded-(?:for|host|server)|host|via|remote-ip|remote-addr|originating-IP))\\\\s*:\",\"options\":{\"case_sensitive\":true,\"min_length\":3}},\"operator\":\"match_regex\"}],\"transformers\":[\"lowercase\"]},{\"id\":\"crs-930-100\",\"name\":\"Obfuscated Path Traversal Attack (/../)\",\"tags\":{\"type\":\"lfi\",\"crs_id\":\"930100\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.uri.raw\"},{\"address\":\"server.request.headers.no_cookies\"}],\"regex\":\"(?:\\\\x5c|(?:%(?:c(?:0%(?:[2aq]f|5c|9v)|1%(?:[19p]c|8s|af))|2(?:5(?:c(?:0%25af|1%259c)|2f|5c)|%46|f)|(?:(?:f(?:8%8)?0%8|e)0%80%a|bg%q)f|%3(?:2(?:%(?:%6|4)6|F)|5%%63)|u(?:221[56]|002f|EFC8|F025)|1u|5c)|0x(?:2f|5c)|/))(?:%(?:(?:f(?:(?:c%80|8)%8)?0%8|e)0%80%ae|2(?:(?:5(?:c0%25a|2))?e|%45)|u(?:(?:002|ff0)e|2024)|%32(?:%(?:%6|4)5|E)|c0(?:%[256aef]e|\\\\.))|\\\\.(?:%0[01]|\\\\?)?|\\\\?\\\\.?|0x2e){2}(?:\\\\x5c|(?:%(?:c(?:0%(?:[2aq]f|5c|9v)|1%(?:[19p]c|8s|af))|2(?:5(?:c(?:0%25af|1%259c)|2f|5c)|%46|f)|(?:(?:f(?:8%8)?0%8|e)0%80%a|bg%q)f|%3(?:2(?:%(?:%6|4)6|F)|5%%63)|u(?:221[56]|002f|EFC8|F025)|1u|5c)|0x(?:2f|5c)|/))\",\"options\":{\"min_length\":4}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-930-110\",\"name\":\"Simple Path Traversal Attack (/../)\",\"tags\":{\"type\":\"lfi\",\"crs_id\":\"930110\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.uri.raw\"},{\"address\":\"server.request.headers.no_cookies\"}],\"regex\":\"(?:(?:^|[\\\\\\\\/])\\\\.\\\\.[\\\\\\\\/]|[\\\\\\\\/]\\\\.\\\\.(?:[\\\\\\\\/]|$))\",\"options\":{\"case_sensitive\":true,\"min_length\":3}},\"operator\":\"match_regex\"}],\"transformers\":[\"removeNulls\"]},{\"id\":\"crs-930-120\",\"name\":\"OS File Access Attempt\",\"tags\":{\"type\":\"lfi\",\"crs_id\":\"930120\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"list\":[\".htaccess\",\".htdigest\",\".htpasswd\",\".addressbook\",\".aptitude/config\",\".bash_config\",\".bash_history\",\".bash_logout\",\".bash_profile\",\".bashrc\",\".cache/notify-osd.log\",\".config/odesk/odesk team.conf\",\".cshrc\",\".dockerignore\",\".drush/\",\".eslintignore\",\".fbcindex\",\".forward\",\".gitattributes\",\".gitconfig\",\".gnupg/\",\".hplip/hplip.conf\",\".ksh_history\",\".lesshst\",\".lftp/\",\".lhistory\",\".lldb-history\",\".local/share/mc/\",\".lynx_cookies\",\".my.cnf\",\".mysql_history\",\".nano_history\",\".node_repl_history\",\".pearrc\",\".php_history\",\".pinerc\",\".pki/\",\".proclog\",\".procmailrc\",\".psql_history\",\".python_history\",\".rediscli_history\",\".rhistory\",\".rhosts\",\".sh_history\",\".sqlite_history\",\".ssh/authorized_keys\",\".ssh/config\",\".ssh/id_dsa\",\".ssh/id_dsa.pub\",\".ssh/id_rsa\",\".ssh/id_rsa.pub\",\".ssh/identity\",\".ssh/identity.pub\",\".ssh/known_hosts\",\".subversion/auth\",\".subversion/config\",\".subversion/servers\",\".tconn/tconn.conf\",\".tcshrc\",\".vidalia/vidalia.conf\",\".viminfo\",\".vimrc\",\".www_acl\",\".wwwacl\",\".xauthority\",\".zhistory\",\".zshrc\",\".zsh_history\",\".nsconfig\",\"etc/redis.conf\",\"etc/redis-sentinel.conf\",\"etc/php.ini\",\"bin/php.ini\",\"etc/httpd/php.ini\",\"usr/lib/php.ini\",\"usr/lib/php/php.ini\",\"usr/local/etc/php.ini\",\"usr/local/lib/php.ini\",\"usr/local/php/lib/php.ini\",\"usr/local/php4/lib/php.ini\",\"usr/local/php5/lib/php.ini\",\"usr/local/apache/conf/php.ini\",\"etc/php4.4/fcgi/php.ini\",\"etc/php4/apache/php.ini\",\"etc/php4/apache2/php.ini\",\"etc/php5/apache/php.ini\",\"etc/php5/apache2/php.ini\",\"etc/php/php.ini\",\"etc/php/php4/php.ini\",\"etc/php/apache/php.ini\",\"etc/php/apache2/php.ini\",\"web/conf/php.ini\",\"usr/local/zend/etc/php.ini\",\"opt/xampp/etc/php.ini\",\"var/local/www/conf/php.ini\",\"etc/php/cgi/php.ini\",\"etc/php4/cgi/php.ini\",\"etc/php5/cgi/php.ini\",\"home2/bin/stable/apache/php.ini\",\"home/bin/stable/apache/php.ini\",\"etc/httpd/conf.d/php.conf\",\"php5/php.ini\",\"php4/php.ini\",\"php/php.ini\",\"windows/php.ini\",\"winnt/php.ini\",\"apache/php/php.ini\",\"xampp/apache/bin/php.ini\",\"netserver/bin/stable/apache/php.ini\",\"volumes/macintosh_hd1/usr/local/php/lib/php.ini\",\"etc/mono/1.0/machine.config\",\"etc/mono/2.0/machine.config\",\"etc/mono/2.0/web.config\",\"etc/mono/config\",\"usr/local/cpanel/logs/stats_log\",\"usr/local/cpanel/logs/access_log\",\"usr/local/cpanel/logs/error_log\",\"usr/local/cpanel/logs/license_log\",\"usr/local/cpanel/logs/login_log\",\"var/cpanel/cpanel.config\",\"var/log/sw-cp-server/error_log\",\"usr/local/psa/admin/logs/httpsd_access_log\",\"usr/local/psa/admin/logs/panel.log\",\"var/log/sso/sso.log\",\"usr/local/psa/admin/conf/php.ini\",\"etc/sw-cp-server/applications.d/plesk.conf\",\"usr/local/psa/admin/conf/site_isolation_settings.ini\",\"usr/local/sb/config\",\"etc/sw-cp-server/applications.d/00-sso-cpserver.conf\",\"etc/sso/sso_config.ini\",\"etc/mysql/conf.d/old_passwords.cnf\",\"var/log/mysql/mysql-bin.log\",\"var/log/mysql/mysql-bin.index\",\"var/log/mysql/data/mysql-bin.index\",\"var/log/mysql.log\",\"var/log/mysql.err\",\"var/log/mysqlderror.log\",\"var/log/mysql/mysql.log\",\"var/log/mysql/mysql-slow.log\",\"var/log/mysql-bin.index\",\"var/log/data/mysql-bin.index\",\"var/mysql.log\",\"var/mysql-bin.index\",\"var/data/mysql-bin.index\",\"program files/mysql/mysql server 5.0/data/{host}.err\",\"program files/mysql/mysql server 5.0/data/mysql.log\",\"program files/mysql/mysql server 5.0/data/mysql.err\",\"program files/mysql/mysql server 5.0/data/mysql-bin.log\",\"program files/mysql/mysql server 5.0/data/mysql-bin.index\",\"program files/mysql/data/{host}.err\",\"program files/mysql/data/mysql.log\",\"program files/mysql/data/mysql.err\",\"program files/mysql/data/mysql-bin.log\",\"program files/mysql/data/mysql-bin.index\",\"mysql/data/{host}.err\",\"mysql/data/mysql.log\",\"mysql/data/mysql.err\",\"mysql/data/mysql-bin.log\",\"mysql/data/mysql-bin.index\",\"usr/local/mysql/data/mysql.log\",\"usr/local/mysql/data/mysql.err\",\"usr/local/mysql/data/mysql-bin.log\",\"usr/local/mysql/data/mysql-slow.log\",\"usr/local/mysql/data/mysqlderror.log\",\"usr/local/mysql/data/{host}.err\",\"usr/local/mysql/data/mysql-bin.index\",\"var/lib/mysql/my.cnf\",\"etc/mysql/my.cnf\",\"etc/my.cnf\",\"program files/mysql/mysql server 5.0/my.ini\",\"program files/mysql/mysql server 5.0/my.cnf\",\"program files/mysql/my.ini\",\"program files/mysql/my.cnf\",\"mysql/my.ini\",\"mysql/my.cnf\",\"mysql/bin/my.ini\",\"var/postgresql/log/postgresql.log\",\"var/log/postgresql/postgresql.log\",\"var/log/postgres/pg_backup.log\",\"var/log/postgres/postgres.log\",\"var/log/postgresql.log\",\"var/log/pgsql/pgsql.log\",\"var/log/postgresql/postgresql-8.1-main.log\",\"var/log/postgresql/postgresql-8.3-main.log\",\"var/log/postgresql/postgresql-8.4-main.log\",\"var/log/postgresql/postgresql-9.0-main.log\",\"var/log/postgresql/postgresql-9.1-main.log\",\"var/log/pgsql8.log\",\"var/log/postgresql/postgres.log\",\"var/log/pgsql_log\",\"var/log/postgresql/main.log\",\"var/log/cron/var/log/postgres.log\",\"usr/internet/pgsql/data/postmaster.log\",\"usr/local/pgsql/data/postgresql.log\",\"usr/local/pgsql/data/pg_log\",\"postgresql/log/pgadmin.log\",\"var/lib/pgsql/data/postgresql.conf\",\"var/postgresql/db/postgresql.conf\",\"var/nm2/postgresql.conf\",\"usr/local/pgsql/data/postgresql.conf\",\"usr/local/pgsql/data/pg_hba.conf\",\"usr/internet/pgsql/data/pg_hba.conf\",\"usr/local/pgsql/data/passwd\",\"usr/local/pgsql/bin/pg_passwd\",\"etc/postgresql/postgresql.conf\",\"etc/postgresql/pg_hba.conf\",\"home/postgres/data/postgresql.conf\",\"home/postgres/data/pg_version\",\"home/postgres/data/pg_ident.conf\",\"home/postgres/data/pg_hba.conf\",\"program files/postgresql/8.3/data/pg_hba.conf\",\"program files/postgresql/8.3/data/pg_ident.conf\",\"program files/postgresql/8.3/data/postgresql.conf\",\"program files/postgresql/8.4/data/pg_hba.conf\",\"program files/postgresql/8.4/data/pg_ident.conf\",\"program files/postgresql/8.4/data/postgresql.conf\",\"program files/postgresql/9.0/data/pg_hba.conf\",\"program files/postgresql/9.0/data/pg_ident.conf\",\"program files/postgresql/9.0/data/postgresql.conf\",\"program files/postgresql/9.1/data/pg_hba.conf\",\"program files/postgresql/9.1/data/pg_ident.conf\",\"program files/postgresql/9.1/data/postgresql.conf\",\"wamp/logs/access.log\",\"wamp/logs/apache_error.log\",\"wamp/logs/genquery.log\",\"wamp/logs/mysql.log\",\"wamp/logs/slowquery.log\",\"wamp/bin/apache/apache2.2.22/logs/access.log\",\"wamp/bin/apache/apache2.2.22/logs/error.log\",\"wamp/bin/apache/apache2.2.21/logs/access.log\",\"wamp/bin/apache/apache2.2.21/logs/error.log\",\"wamp/bin/mysql/mysql5.5.24/data/mysql-bin.index\",\"wamp/bin/mysql/mysql5.5.16/data/mysql-bin.index\",\"wamp/bin/apache/apache2.2.21/conf/httpd.conf\",\"wamp/bin/apache/apache2.2.22/conf/httpd.conf\",\"wamp/bin/apache/apache2.2.21/wampserver.conf\",\"wamp/bin/apache/apache2.2.22/wampserver.conf\",\"wamp/bin/apache/apache2.2.22/conf/wampserver.conf\",\"wamp/bin/mysql/mysql5.5.24/my.ini\",\"wamp/bin/mysql/mysql5.5.24/wampserver.conf\",\"wamp/bin/mysql/mysql5.5.16/my.ini\",\"wamp/bin/mysql/mysql5.5.16/wampserver.conf\",\"wamp/bin/php/php5.3.8/php.ini\",\"wamp/bin/php/php5.4.3/php.ini\",\"xampp/apache/logs/access.log\",\"xampp/apache/logs/error.log\",\"xampp/mysql/data/mysql-bin.index\",\"xampp/mysql/data/mysql.err\",\"xampp/mysql/data/{host}.err\",\"xampp/sendmail/sendmail.log\",\"xampp/apache/conf/httpd.conf\",\"xampp/filezillaftp/filezilla server.xml\",\"xampp/mercurymail/mercury.ini\",\"xampp/php/php.ini\",\"xampp/phpmyadmin/config.inc.php\",\"xampp/sendmail/sendmail.ini\",\"xampp/webalizer/webalizer.conf\",\"opt/lampp/etc/httpd.conf\",\"xampp/htdocs/aca.txt\",\"xampp/htdocs/admin.php\",\"xampp/htdocs/leer.txt\",\"usr/local/apache/logs/audit_log\",\"usr/local/apache2/logs/audit_log\",\"logs/security_debug_log\",\"logs/security_log\",\"usr/local/apache/conf/modsec.conf\",\"usr/local/apache2/conf/modsec.conf\",\"winnt/system32/logfiles/msftpsvc\",\"winnt/system32/logfiles/msftpsvc1\",\"winnt/system32/logfiles/msftpsvc2\",\"windows/system32/logfiles/msftpsvc\",\"windows/system32/logfiles/msftpsvc1\",\"windows/system32/logfiles/msftpsvc2\",\"etc/logrotate.d/proftpd\",\"www/logs/proftpd.system.log\",\"var/log/proftpd\",\"var/log/proftpd/xferlog.legacy\",\"var/log/proftpd.access_log\",\"var/log/proftpd.xferlog\",\"etc/pam.d/proftpd\",\"etc/proftp.conf\",\"etc/protpd/proftpd.conf\",\"etc/vhcs2/proftpd/proftpd.conf\",\"etc/proftpd/modules.conf\",\"var/log/vsftpd.log\",\"etc/vsftpd.chroot_list\",\"etc/logrotate.d/vsftpd.log\",\"etc/vsftpd/vsftpd.conf\",\"etc/vsftpd.conf\",\"etc/chrootusers\",\"var/log/xferlog\",\"var/adm/log/xferlog\",\"etc/wu-ftpd/ftpaccess\",\"etc/wu-ftpd/ftphosts\",\"etc/wu-ftpd/ftpusers\",\"var/log/pure-ftpd/pure-ftpd.log\",\"logs/pure-ftpd.log\",\"var/log/pureftpd.log\",\"usr/sbin/pure-config.pl\",\"usr/etc/pure-ftpd.conf\",\"etc/pure-ftpd/pure-ftpd.conf\",\"usr/local/etc/pure-ftpd.conf\",\"usr/local/etc/pureftpd.pdb\",\"usr/local/pureftpd/etc/pureftpd.pdb\",\"usr/local/pureftpd/sbin/pure-config.pl\",\"usr/local/pureftpd/etc/pure-ftpd.conf\",\"etc/pure-ftpd.conf\",\"etc/pure-ftpd/pure-ftpd.pdb\",\"etc/pureftpd.pdb\",\"etc/pureftpd.passwd\",\"etc/pure-ftpd/pureftpd.pdb\",\"usr/ports/ftp/pure-ftpd/pure-ftpd.conf\",\"usr/ports/ftp/pure-ftpd/pureftpd.pdb\",\"usr/ports/ftp/pure-ftpd/pureftpd.passwd\",\"usr/ports/net/pure-ftpd/pure-ftpd.conf\",\"usr/ports/net/pure-ftpd/pureftpd.pdb\",\"usr/ports/net/pure-ftpd/pureftpd.passwd\",\"usr/pkgsrc/net/pureftpd/pure-ftpd.conf\",\"usr/pkgsrc/net/pureftpd/pureftpd.pdb\",\"usr/pkgsrc/net/pureftpd/pureftpd.passwd\",\"usr/ports/contrib/pure-ftpd/pure-ftpd.conf\",\"usr/ports/contrib/pure-ftpd/pureftpd.pdb\",\"usr/ports/contrib/pure-ftpd/pureftpd.passwd\",\"var/log/muddleftpd\",\"usr/sbin/mudlogd\",\"etc/muddleftpd/mudlog\",\"etc/muddleftpd.com\",\"etc/muddleftpd/mudlogd.conf\",\"etc/muddleftpd/muddleftpd.conf\",\"var/log/muddleftpd.conf\",\"usr/sbin/mudpasswd\",\"etc/muddleftpd/muddleftpd.passwd\",\"etc/muddleftpd/passwd\",\"var/log/ftp-proxy/ftp-proxy.log\",\"var/log/ftp-proxy\",\"var/log/ftplog\",\"etc/logrotate.d/ftp\",\"etc/ftpchroot\",\"etc/ftphosts\",\"etc/ftpusers\",\"var/log/exim_mainlog\",\"var/log/exim/mainlog\",\"var/log/maillog\",\"var/log/exim_paniclog\",\"var/log/exim/paniclog\",\"var/log/exim/rejectlog\",\"var/log/exim_rejectlog\",\"winnt/system32/logfiles/smtpsvc\",\"winnt/system32/logfiles/smtpsvc1\",\"winnt/system32/logfiles/smtpsvc2\",\"winnt/system32/logfiles/smtpsvc3\",\"winnt/system32/logfiles/smtpsvc4\",\"winnt/system32/logfiles/smtpsvc5\",\"windows/system32/logfiles/smtpsvc\",\"windows/system32/logfiles/smtpsvc1\",\"windows/system32/logfiles/smtpsvc2\",\"windows/system32/logfiles/smtpsvc3\",\"windows/system32/logfiles/smtpsvc4\",\"windows/system32/logfiles/smtpsvc5\",\"etc/osxhttpd/osxhttpd.conf\",\"system/library/webobjects/adaptors/apache2.2/apache.conf\",\"etc/apache2/sites-available/default\",\"etc/apache2/sites-available/default-ssl\",\"etc/apache2/sites-enabled/000-default\",\"etc/apache2/sites-enabled/default\",\"etc/apache2/apache2.conf\",\"etc/apache2/ports.conf\",\"usr/local/etc/apache/httpd.conf\",\"usr/pkg/etc/httpd/httpd.conf\",\"usr/pkg/etc/httpd/httpd-default.conf\",\"usr/pkg/etc/httpd/httpd-vhosts.conf\",\"etc/httpd/mod_php.conf\",\"etc/httpd/extra/httpd-ssl.conf\",\"etc/rc.d/rc.httpd\",\"usr/local/apache/conf/httpd.conf.default\",\"usr/local/apache/conf/access.conf\",\"usr/local/apache22/conf/httpd.conf\",\"usr/local/apache22/httpd.conf\",\"usr/local/etc/apache22/conf/httpd.conf\",\"usr/local/apps/apache22/conf/httpd.conf\",\"etc/apache22/conf/httpd.conf\",\"etc/apache22/httpd.conf\",\"opt/apache22/conf/httpd.conf\",\"usr/local/etc/apache2/vhosts.conf\",\"usr/local/apache/conf/vhosts.conf\",\"usr/local/apache2/conf/vhosts.conf\",\"usr/local/apache/conf/vhosts-custom.conf\",\"usr/local/apache2/conf/vhosts-custom.conf\",\"etc/apache/default-server.conf\",\"etc/apache2/default-server.conf\",\"usr/local/apache2/conf/extra/httpd-ssl.conf\",\"usr/local/apache2/conf/ssl.conf\",\"etc/httpd/conf.d\",\"usr/local/etc/apache22/httpd.conf\",\"usr/local/etc/apache2/httpd.conf\",\"etc/apache2/httpd2.conf\",\"etc/apache2/ssl-global.conf\",\"etc/apache2/vhosts.d/00_default_vhost.conf\",\"apache/conf/httpd.conf\",\"etc/apache/httpd.conf\",\"etc/httpd/conf\",\"http/httpd.conf\",\"usr/local/apache1.3/conf/httpd.conf\",\"usr/local/etc/httpd/conf\",\"var/apache/conf/httpd.conf\",\"var/www/conf\",\"www/apache/conf/httpd.conf\",\"www/conf/httpd.conf\",\"etc/init.d\",\"etc/apache/access.conf\",\"etc/rc.conf\",\"www/logs/freebsddiary-error.log\",\"www/logs/freebsddiary-access_log\",\"library/webserver/documents/index.html\",\"library/webserver/documents/index.htm\",\"library/webserver/documents/default.html\",\"library/webserver/documents/default.htm\",\"library/webserver/documents/index.php\",\"library/webserver/documents/default.php\",\"var/log/webmin/miniserv.log\",\"usr/local/etc/webmin/miniserv.conf\",\"etc/webmin/miniserv.conf\",\"usr/local/etc/webmin/miniserv.users\",\"etc/webmin/miniserv.users\",\"winnt/system32/logfiles/w3svc/inetsvn1.log\",\"winnt/system32/logfiles/w3svc1/inetsvn1.log\",\"winnt/system32/logfiles/w3svc2/inetsvn1.log\",\"winnt/system32/logfiles/w3svc3/inetsvn1.log\",\"windows/system32/logfiles/w3svc/inetsvn1.log\",\"windows/system32/logfiles/w3svc1/inetsvn1.log\",\"windows/system32/logfiles/w3svc2/inetsvn1.log\",\"windows/system32/logfiles/w3svc3/inetsvn1.log\",\"var/log/httpd/access_log\",\"var/log/httpd/error_log\",\"apache/logs/error.log\",\"apache/logs/access.log\",\"apache2/logs/error.log\",\"apache2/logs/access.log\",\"logs/error.log\",\"logs/access.log\",\"etc/httpd/logs/access_log\",\"etc/httpd/logs/access.log\",\"etc/httpd/logs/error_log\",\"etc/httpd/logs/error.log\",\"usr/local/apache/logs/access_log\",\"usr/local/apache/logs/access.log\",\"usr/local/apache/logs/error_log\",\"usr/local/apache/logs/error.log\",\"usr/local/apache2/logs/access_log\",\"usr/local/apache2/logs/access.log\",\"usr/local/apache2/logs/error_log\",\"usr/local/apache2/logs/error.log\",\"var/www/logs/access_log\",\"var/www/logs/access.log\",\"var/www/logs/error_log\",\"var/www/logs/error.log\",\"var/log/httpd/access.log\",\"var/log/httpd/error.log\",\"var/log/apache/access_log\",\"var/log/apache/access.log\",\"var/log/apache/error_log\",\"var/log/apache/error.log\",\"var/log/apache2/access_log\",\"var/log/apache2/access.log\",\"var/log/apache2/error_log\",\"var/log/apache2/error.log\",\"var/log/access_log\",\"var/log/access.log\",\"var/log/error_log\",\"var/log/error.log\",\"opt/lampp/logs/access_log\",\"opt/lampp/logs/error_log\",\"opt/xampp/logs/access_log\",\"opt/xampp/logs/error_log\",\"opt/lampp/logs/access.log\",\"opt/lampp/logs/error.log\",\"opt/xampp/logs/access.log\",\"opt/xampp/logs/error.log\",\"program files/apache group/apache/logs/access.log\",\"program files/apache group/apache/logs/error.log\",\"program files/apache software foundation/apache2.2/logs/error.log\",\"program files/apache software foundation/apache2.2/logs/access.log\",\"opt/apache/apache.conf\",\"opt/apache/conf/apache.conf\",\"opt/apache2/apache.conf\",\"opt/apache2/conf/apache.conf\",\"opt/httpd/apache.conf\",\"opt/httpd/conf/apache.conf\",\"etc/httpd/apache.conf\",\"etc/apache2/apache.conf\",\"etc/httpd/conf/apache.conf\",\"usr/local/apache/apache.conf\",\"usr/local/apache/conf/apache.conf\",\"usr/local/apache2/apache.conf\",\"usr/local/apache2/conf/apache.conf\",\"usr/local/php/apache.conf.php\",\"usr/local/php4/apache.conf.php\",\"usr/local/php5/apache.conf.php\",\"usr/local/php/apache.conf\",\"usr/local/php4/apache.conf\",\"usr/local/php5/apache.conf\",\"private/etc/httpd/apache.conf\",\"opt/apache/apache2.conf\",\"opt/apache/conf/apache2.conf\",\"opt/apache2/apache2.conf\",\"opt/apache2/conf/apache2.conf\",\"opt/httpd/apache2.conf\",\"opt/httpd/conf/apache2.conf\",\"etc/httpd/apache2.conf\",\"etc/httpd/conf/apache2.conf\",\"usr/local/apache/apache2.conf\",\"usr/local/apache/conf/apache2.conf\",\"usr/local/apache2/apache2.conf\",\"usr/local/apache2/conf/apache2.conf\",\"usr/local/php/apache2.conf.php\",\"usr/local/php4/apache2.conf.php\",\"usr/local/php5/apache2.conf.php\",\"usr/local/php/apache2.conf\",\"usr/local/php4/apache2.conf\",\"usr/local/php5/apache2.conf\",\"private/etc/httpd/apache2.conf\",\"usr/local/apache/conf/httpd.conf\",\"usr/local/apache2/conf/httpd.conf\",\"etc/httpd/conf/httpd.conf\",\"etc/apache/apache.conf\",\"etc/apache/conf/httpd.conf\",\"etc/apache2/httpd.conf\",\"usr/apache2/conf/httpd.conf\",\"usr/apache/conf/httpd.conf\",\"usr/local/etc/apache/conf/httpd.conf\",\"usr/local/apache/httpd.conf\",\"usr/local/apache2/httpd.conf\",\"usr/local/httpd/conf/httpd.conf\",\"usr/local/etc/apache2/conf/httpd.conf\",\"usr/local/etc/httpd/conf/httpd.conf\",\"usr/local/apps/apache2/conf/httpd.conf\",\"usr/local/apps/apache/conf/httpd.conf\",\"usr/local/php/httpd.conf.php\",\"usr/local/php4/httpd.conf.php\",\"usr/local/php5/httpd.conf.php\",\"usr/local/php/httpd.conf\",\"usr/local/php4/httpd.conf\",\"usr/local/php5/httpd.conf\",\"etc/apache2/conf/httpd.conf\",\"etc/http/conf/httpd.conf\",\"etc/httpd/httpd.conf\",\"etc/http/httpd.conf\",\"etc/httpd.conf\",\"opt/apache/conf/httpd.conf\",\"opt/apache2/conf/httpd.conf\",\"var/www/conf/httpd.conf\",\"private/etc/httpd/httpd.conf\",\"private/etc/httpd/httpd.conf.default\",\"etc/apache2/vhosts.d/default_vhost.include\",\"etc/apache2/conf.d/charset\",\"etc/apache2/conf.d/security\",\"etc/apache2/envvars\",\"etc/apache2/mods-available/autoindex.conf\",\"etc/apache2/mods-available/deflate.conf\",\"etc/apache2/mods-available/dir.conf\",\"etc/apache2/mods-available/mem_cache.conf\",\"etc/apache2/mods-available/mime.conf\",\"etc/apache2/mods-available/proxy.conf\",\"etc/apache2/mods-available/setenvif.conf\",\"etc/apache2/mods-available/ssl.conf\",\"etc/apache2/mods-enabled/alias.conf\",\"etc/apache2/mods-enabled/deflate.conf\",\"etc/apache2/mods-enabled/dir.conf\",\"etc/apache2/mods-enabled/mime.conf\",\"etc/apache2/mods-enabled/negotiation.conf\",\"etc/apache2/mods-enabled/php5.conf\",\"etc/apache2/mods-enabled/status.conf\",\"program files/apache group/apache/conf/httpd.conf\",\"program files/apache group/apache2/conf/httpd.conf\",\"program files/xampp/apache/conf/apache.conf\",\"program files/xampp/apache/conf/apache2.conf\",\"program files/xampp/apache/conf/httpd.conf\",\"program files/apache group/apache/apache.conf\",\"program files/apache group/apache/conf/apache.conf\",\"program files/apache group/apache2/conf/apache.conf\",\"program files/apache group/apache/apache2.conf\",\"program files/apache group/apache/conf/apache2.conf\",\"program files/apache group/apache2/conf/apache2.conf\",\"program files/apache software foundation/apache2.2/conf/httpd.conf\",\"volumes/macintosh_hd1/opt/httpd/conf/httpd.conf\",\"volumes/macintosh_hd1/opt/apache/conf/httpd.conf\",\"volumes/macintosh_hd1/opt/apache2/conf/httpd.conf\",\"volumes/macintosh_hd1/usr/local/php/httpd.conf.php\",\"volumes/macintosh_hd1/usr/local/php4/httpd.conf.php\",\"volumes/macintosh_hd1/usr/local/php5/httpd.conf.php\",\"volumes/webbackup/opt/apache2/conf/httpd.conf\",\"volumes/webbackup/private/etc/httpd/httpd.conf\",\"volumes/webbackup/private/etc/httpd/httpd.conf.default\",\"usr/local/etc/apache/vhosts.conf\",\"usr/local/jakarta/tomcat/conf/jakarta.conf\",\"usr/local/jakarta/tomcat/conf/server.xml\",\"usr/local/jakarta/tomcat/conf/context.xml\",\"usr/local/jakarta/tomcat/conf/workers.properties\",\"usr/local/jakarta/tomcat/conf/logging.properties\",\"usr/local/jakarta/dist/tomcat/conf/jakarta.conf\",\"usr/local/jakarta/dist/tomcat/conf/server.xml\",\"usr/local/jakarta/dist/tomcat/conf/context.xml\",\"usr/local/jakarta/dist/tomcat/conf/workers.properties\",\"usr/local/jakarta/dist/tomcat/conf/logging.properties\",\"usr/share/tomcat6/conf/server.xml\",\"usr/share/tomcat6/conf/context.xml\",\"usr/share/tomcat6/conf/workers.properties\",\"usr/share/tomcat6/conf/logging.properties\",\"var/log/tomcat6/catalina.out\",\"var/cpanel/tomcat.options\",\"usr/local/jakarta/tomcat/logs/catalina.out\",\"usr/local/jakarta/tomcat/logs/catalina.err\",\"opt/tomcat/logs/catalina.out\",\"opt/tomcat/logs/catalina.err\",\"usr/share/logs/catalina.out\",\"usr/share/logs/catalina.err\",\"usr/share/tomcat/logs/catalina.out\",\"usr/share/tomcat/logs/catalina.err\",\"usr/share/tomcat6/logs/catalina.out\",\"usr/share/tomcat6/logs/catalina.err\",\"usr/local/apache/logs/mod_jk.log\",\"usr/local/jakarta/tomcat/logs/mod_jk.log\",\"usr/local/jakarta/dist/tomcat/logs/mod_jk.log\",\"opt/[jboss]/server/default/conf/jboss-minimal.xml\",\"opt/[jboss]/server/default/conf/jboss-service.xml\",\"opt/[jboss]/server/default/conf/jndi.properties\",\"opt/[jboss]/server/default/conf/log4j.xml\",\"opt/[jboss]/server/default/conf/login-config.xml\",\"opt/[jboss]/server/default/conf/standardjaws.xml\",\"opt/[jboss]/server/default/conf/standardjboss.xml\",\"opt/[jboss]/server/default/conf/server.log.properties\",\"opt/[jboss]/server/default/deploy/jboss-logging.xml\",\"usr/local/[jboss]/server/default/conf/jboss-minimal.xml\",\"usr/local/[jboss]/server/default/conf/jboss-service.xml\",\"usr/local/[jboss]/server/default/conf/jndi.properties\",\"usr/local/[jboss]/server/default/conf/log4j.xml\",\"usr/local/[jboss]/server/default/conf/login-config.xml\",\"usr/local/[jboss]/server/default/conf/standardjaws.xml\",\"usr/local/[jboss]/server/default/conf/standardjboss.xml\",\"usr/local/[jboss]/server/default/conf/server.log.properties\",\"usr/local/[jboss]/server/default/deploy/jboss-logging.xml\",\"private/tmp/[jboss]/server/default/conf/jboss-minimal.xml\",\"private/tmp/[jboss]/server/default/conf/jboss-service.xml\",\"private/tmp/[jboss]/server/default/conf/jndi.properties\",\"private/tmp/[jboss]/server/default/conf/log4j.xml\",\"private/tmp/[jboss]/server/default/conf/login-config.xml\",\"private/tmp/[jboss]/server/default/conf/standardjaws.xml\",\"private/tmp/[jboss]/server/default/conf/standardjboss.xml\",\"private/tmp/[jboss]/server/default/conf/server.log.properties\",\"private/tmp/[jboss]/server/default/deploy/jboss-logging.xml\",\"tmp/[jboss]/server/default/conf/jboss-minimal.xml\",\"tmp/[jboss]/server/default/conf/jboss-service.xml\",\"tmp/[jboss]/server/default/conf/jndi.properties\",\"tmp/[jboss]/server/default/conf/log4j.xml\",\"tmp/[jboss]/server/default/conf/login-config.xml\",\"tmp/[jboss]/server/default/conf/standardjaws.xml\",\"tmp/[jboss]/server/default/conf/standardjboss.xml\",\"tmp/[jboss]/server/default/conf/server.log.properties\",\"tmp/[jboss]/server/default/deploy/jboss-logging.xml\",\"program files/[jboss]/server/default/conf/jboss-minimal.xml\",\"program files/[jboss]/server/default/conf/jboss-service.xml\",\"program files/[jboss]/server/default/conf/jndi.properties\",\"program files/[jboss]/server/default/conf/log4j.xml\",\"program files/[jboss]/server/default/conf/login-config.xml\",\"program files/[jboss]/server/default/conf/standardjaws.xml\",\"program files/[jboss]/server/default/conf/standardjboss.xml\",\"program files/[jboss]/server/default/conf/server.log.properties\",\"program files/[jboss]/server/default/deploy/jboss-logging.xml\",\"[jboss]/server/default/conf/jboss-minimal.xml\",\"[jboss]/server/default/conf/jboss-service.xml\",\"[jboss]/server/default/conf/jndi.properties\",\"[jboss]/server/default/conf/log4j.xml\",\"[jboss]/server/default/conf/login-config.xml\",\"[jboss]/server/default/conf/standardjaws.xml\",\"[jboss]/server/default/conf/standardjboss.xml\",\"[jboss]/server/default/conf/server.log.properties\",\"[jboss]/server/default/deploy/jboss-logging.xml\",\"opt/[jboss]/server/default/log/server.log\",\"opt/[jboss]/server/default/log/boot.log\",\"usr/local/[jboss]/server/default/log/server.log\",\"usr/local/[jboss]/server/default/log/boot.log\",\"private/tmp/[jboss]/server/default/log/server.log\",\"private/tmp/[jboss]/server/default/log/boot.log\",\"tmp/[jboss]/server/default/log/server.log\",\"tmp/[jboss]/server/default/log/boot.log\",\"program files/[jboss]/server/default/log/server.log\",\"program files/[jboss]/server/default/log/boot.log\",\"[jboss]/server/default/log/server.log\",\"[jboss]/server/default/log/boot.log\",\"var/log/lighttpd.error.log\",\"var/log/lighttpd.access.log\",\"var/lighttpd.log\",\"var/logs/access.log\",\"var/log/lighttpd/\",\"var/log/lighttpd/error.log\",\"var/log/lighttpd/access.www.log\",\"var/log/lighttpd/error.www.log\",\"var/log/lighttpd/access.log\",\"usr/local/apache2/logs/lighttpd.error.log\",\"usr/local/apache2/logs/lighttpd.log\",\"usr/local/apache/logs/lighttpd.error.log\",\"usr/local/apache/logs/lighttpd.log\",\"usr/local/lighttpd/log/lighttpd.error.log\",\"usr/local/lighttpd/log/access.log\",\"var/log/lighttpd/{domain}/access.log\",\"var/log/lighttpd/{domain}/error.log\",\"usr/home/user/var/log/lighttpd.error.log\",\"usr/home/user/var/log/apache.log\",\"home/user/lighttpd/lighttpd.conf\",\"usr/home/user/lighttpd/lighttpd.conf\",\"etc/lighttpd/lighthttpd.conf\",\"usr/local/etc/lighttpd.conf\",\"usr/local/lighttpd/conf/lighttpd.conf\",\"usr/local/etc/lighttpd.conf.new\",\"var/www/.lighttpdpassword\",\"var/log/nginx/access_log\",\"var/log/nginx/error_log\",\"var/log/nginx/access.log\",\"var/log/nginx/error.log\",\"var/log/nginx.access_log\",\"var/log/nginx.error_log\",\"logs/access_log\",\"logs/error_log\",\"etc/nginx/nginx.conf\",\"usr/local/etc/nginx/nginx.conf\",\"usr/local/nginx/conf/nginx.conf\",\"usr/local/zeus/web/global.cfg\",\"usr/local/zeus/web/log/errors\",\"opt/lsws/conf/httpd_conf.xml\",\"usr/local/lsws/conf/httpd_conf.xml\",\"opt/lsws/logs/error.log\",\"opt/lsws/logs/access.log\",\"usr/local/lsws/logs/error.log\",\"usr/local/logs/access.log\",\"usr/local/samba/lib/log.user\",\"usr/local/logs/samba.log\",\"var/log/samba/log.smbd\",\"var/log/samba/log.nmbd\",\"var/log/samba.log\",\"var/log/samba.log1\",\"var/log/samba.log2\",\"var/log/log.smb\",\"etc/samba/netlogon\",\"etc/smbpasswd\",\"etc/smb.conf\",\"etc/samba/dhcp.conf\",\"etc/samba/smb.conf\",\"etc/samba/samba.conf\",\"etc/samba/smb.conf.user\",\"etc/samba/smbpasswd\",\"etc/samba/smbusers\",\"etc/samba/private/smbpasswd\",\"usr/local/etc/smb.conf\",\"usr/local/samba/lib/smb.conf.user\",\"etc/dhcp3/dhclient.conf\",\"etc/dhcp3/dhcpd.conf\",\"etc/dhcp/dhclient.conf\",\"program files/vidalia bundle/polipo/polipo.conf\",\"etc/tor/tor-tsocks.conf\",\"etc/stunnel/stunnel.conf\",\"etc/tsocks.conf\",\"etc/tinyproxy/tinyproxy.conf\",\"etc/miredo-server.conf\",\"etc/miredo.conf\",\"etc/miredo/miredo-server.conf\",\"etc/miredo/miredo.conf\",\"etc/wicd/dhclient.conf.template.default\",\"etc/wicd/manager-settings.conf\",\"etc/wicd/wired-settings.conf\",\"etc/wicd/wireless-settings.conf\",\"var/log/ipfw.log\",\"var/log/ipfw\",\"var/log/ipfw/ipfw.log\",\"var/log/ipfw.today\",\"etc/ipfw.rules\",\"etc/ipfw.conf\",\"etc/firewall.rules\",\"winnt/system32/logfiles/firewall/pfirewall.log\",\"winnt/system32/logfiles/firewall/pfirewall.log.old\",\"windows/system32/logfiles/firewall/pfirewall.log\",\"windows/system32/logfiles/firewall/pfirewall.log.old\",\"etc/clamav/clamd.conf\",\"etc/clamav/freshclam.conf\",\"etc/x11/xorg.conf\",\"etc/x11/xorg.conf-vesa\",\"etc/x11/xorg.conf-vmware\",\"etc/x11/xorg.conf.beforevmwaretoolsinstall\",\"etc/x11/xorg.conf.orig\",\"etc/bluetooth/input.conf\",\"etc/bluetooth/main.conf\",\"etc/bluetooth/network.conf\",\"etc/bluetooth/rfcomm.conf\",\"proc/self/environ\",\"proc/self/mounts\",\"proc/self/stat\",\"proc/self/status\",\"proc/self/cmdline\",\"proc/self/fd/0\",\"proc/self/fd/1\",\"proc/self/fd/2\",\"proc/self/fd/3\",\"proc/self/fd/4\",\"proc/self/fd/5\",\"proc/self/fd/6\",\"proc/self/fd/7\",\"proc/self/fd/8\",\"proc/self/fd/9\",\"proc/self/fd/10\",\"proc/self/fd/11\",\"proc/self/fd/12\",\"proc/self/fd/13\",\"proc/self/fd/14\",\"proc/self/fd/15\",\"proc/version\",\"proc/devices\",\"proc/cpuinfo\",\"proc/meminfo\",\"proc/net/tcp\",\"proc/net/udp\",\"etc/bash_completion.d/debconf\",\"root/.bash_logout\",\"root/.bash_history\",\"root/.bash_config\",\"root/.bashrc\",\"etc/bash.bashrc\",\"var/adm/syslog\",\"var/adm/sulog\",\"var/adm/utmp\",\"var/adm/utmpx\",\"var/adm/wtmp\",\"var/adm/wtmpx\",\"var/adm/lastlog/username\",\"usr/spool/lp/log\",\"var/adm/lp/lpd-errs\",\"usr/lib/cron/log\",\"var/adm/loginlog\",\"var/adm/pacct\",\"var/adm/dtmp\",\"var/adm/acct/sum/loginlog\",\"var/adm/x0msgs\",\"var/adm/crash/vmcore\",\"var/adm/crash/unix\",\"etc/newsyslog.conf\",\"var/adm/qacct\",\"var/adm/ras/errlog\",\"var/adm/ras/bootlog\",\"var/adm/cron/log\",\"etc/utmp\",\"etc/security/lastlog\",\"etc/security/failedlogin\",\"usr/spool/mqueue/syslog\",\"var/adm/messages\",\"var/adm/aculogs\",\"var/adm/aculog\",\"var/adm/vold.log\",\"var/adm/log/asppp.log\",\"var/log/poplog\",\"var/log/authlog\",\"var/lp/logs/lpsched\",\"var/lp/logs/lpnet\",\"var/lp/logs/requests\",\"var/cron/log\",\"var/saf/_log\",\"var/saf/port/log\",\"var/log/news.all\",\"var/log/news/news.all\",\"var/log/news/news.crit\",\"var/log/news/news.err\",\"var/log/news/news.notice\",\"var/log/news/suck.err\",\"var/log/news/suck.notice\",\"var/log/messages\",\"var/log/messages.1\",\"var/log/user.log\",\"var/log/user.log.1\",\"var/log/auth.log\",\"var/log/pm-powersave.log\",\"var/log/xorg.0.log\",\"var/log/daemon.log\",\"var/log/daemon.log.1\",\"var/log/kern.log\",\"var/log/kern.log.1\",\"var/log/mail.err\",\"var/log/mail.info\",\"var/log/mail.warn\",\"var/log/ufw.log\",\"var/log/boot.log\",\"var/log/syslog\",\"var/log/syslog.1\",\"tmp/access.log\",\"etc/sensors.conf\",\"etc/sensors3.conf\",\"etc/host.conf\",\"etc/pam.conf\",\"etc/resolv.conf\",\"etc/apt/apt.conf\",\"etc/inetd.conf\",\"etc/syslog.conf\",\"etc/sysctl.conf\",\"etc/sysctl.d/10-console-messages.conf\",\"etc/sysctl.d/10-network-security.conf\",\"etc/sysctl.d/10-process-security.conf\",\"etc/sysctl.d/wine.sysctl.conf\",\"etc/security/access.conf\",\"etc/security/group.conf\",\"etc/security/limits.conf\",\"etc/security/namespace.conf\",\"etc/security/pam_env.conf\",\"etc/security/sepermit.conf\",\"etc/security/time.conf\",\"etc/ssh/sshd_config\",\"etc/adduser.conf\",\"etc/deluser.conf\",\"etc/avahi/avahi-daemon.conf\",\"etc/ca-certificates.conf\",\"etc/ca-certificates.conf.dpkg-old\",\"etc/casper.conf\",\"etc/chkrootkit.conf\",\"etc/debconf.conf\",\"etc/dns2tcpd.conf\",\"etc/e2fsck.conf\",\"etc/esound/esd.conf\",\"etc/etter.conf\",\"etc/fuse.conf\",\"etc/foremost.conf\",\"etc/hdparm.conf\",\"etc/kernel-img.conf\",\"etc/kernel-pkg.conf\",\"etc/ld.so.conf\",\"etc/ltrace.conf\",\"etc/mail/sendmail.conf\",\"etc/manpath.config\",\"etc/kbd/config\",\"etc/ldap/ldap.conf\",\"etc/logrotate.conf\",\"etc/mtools.conf\",\"etc/smi.conf\",\"etc/updatedb.conf\",\"etc/pulse/client.conf\",\"usr/share/adduser/adduser.conf\",\"etc/hostname\",\"etc/networks\",\"etc/timezone\",\"etc/modules\",\"etc/passwd\",\"etc/passwd~\",\"etc/passwd-\",\"etc/shadow\",\"etc/shadow~\",\"etc/shadow-\",\"etc/fstab\",\"etc/motd\",\"etc/hosts\",\"etc/group\",\"etc/group-\",\"etc/alias\",\"etc/crontab\",\"etc/crypttab\",\"etc/exports\",\"etc/mtab\",\"etc/hosts.allow\",\"etc/hosts.deny\",\"etc/os-release\",\"etc/password.master\",\"etc/profile\",\"etc/default/grub\",\"etc/resolvconf/update-libc.d/sendmail\",\"etc/inittab\",\"etc/issue\",\"etc/issue.net\",\"etc/login.defs\",\"etc/sudoers\",\"etc/sysconfig/network-scripts/ifcfg-eth0\",\"etc/redhat-release\",\"etc/debian_version\",\"etc/fedora-release\",\"etc/mandrake-release\",\"etc/slackware-release\",\"etc/suse-release\",\"etc/security/group\",\"etc/security/passwd\",\"etc/security/user\",\"etc/security/environ\",\"etc/security/limits\",\"etc/security/opasswd\",\"boot/grub/grub.cfg\",\"boot/grub/menu.lst\",\"root/.ksh_history\",\"root/.xauthority\",\"usr/lib/security/mkuser.default\",\"var/log/squirrelmail.log\",\"var/log/apache2/squirrelmail.log\",\"var/log/apache2/squirrelmail.err.log\",\"var/lib/squirrelmail/prefs/squirrelmail.log\",\"var/log/mail.log\",\"etc/squirrelmail/apache.conf\",\"etc/squirrelmail/config_local.php\",\"etc/squirrelmail/default_pref\",\"etc/squirrelmail/index.php\",\"etc/squirrelmail/config_default.php\",\"etc/squirrelmail/config.php\",\"etc/squirrelmail/filters_setup.php\",\"etc/squirrelmail/sqspell_config.php\",\"etc/squirrelmail/config/config.php\",\"etc/httpd/conf.d/squirrelmail.conf\",\"usr/share/squirrelmail/config/config.php\",\"private/etc/squirrelmail/config/config.php\",\"srv/www/htdos/squirrelmail/config/config.php\",\"var/www/squirrelmail/config/config.php\",\"var/www/html/squirrelmail/config/config.php\",\"var/www/html/squirrelmail-1.2.9/config/config.php\",\"usr/share/squirrelmail/plugins/squirrel_logger/setup.php\",\"usr/local/squirrelmail/www/readme\",\"windows/system32/drivers/etc/hosts\",\"windows/system32/drivers/etc/lmhosts.sam\",\"windows/system32/drivers/etc/networks\",\"windows/system32/drivers/etc/protocol\",\"windows/system32/drivers/etc/services\",\"/boot.ini\",\"windows/debug/netsetup.log\",\"windows/comsetup.log\",\"windows/repair/setup.log\",\"windows/setupact.log\",\"windows/setupapi.log\",\"windows/setuperr.log\",\"windows/updspapi.log\",\"windows/wmsetup.log\",\"windows/windowsupdate.log\",\"windows/odbc.ini\",\"usr/local/psa/admin/htdocs/domains/databases/phpmyadmin/libraries/config.default.php\",\"etc/apache2/conf.d/phpmyadmin.conf\",\"etc/phpmyadmin/config.inc.php\",\"etc/openldap/ldap.conf\",\"etc/cups/acroread.conf\",\"etc/cups/cupsd.conf\",\"etc/cups/cupsd.conf.default\",\"etc/cups/pdftops.conf\",\"etc/cups/printers.conf\",\"windows/system32/macromed/flash/flashinstall.log\",\"windows/system32/macromed/flash/install.log\",\"etc/cvs-cron.conf\",\"etc/cvs-pserver.conf\",\"etc/subversion/config\",\"etc/modprobe.d/vmware-tools.conf\",\"etc/updatedb.conf.beforevmwaretoolsinstall\",\"etc/vmware-tools/config\",\"etc/vmware-tools/tpvmlp.conf\",\"etc/vmware-tools/vmware-tools-libraries.conf\",\"var/log/vmware/hostd.log\",\"var/log/vmware/hostd-1.log\",\"wp-config.php\",\"wp-config.bak\",\"wp-config.old\",\"wp-config.temp\",\"wp-config.tmp\",\"wp-config.txt\",\"config.yml\",\"config_dev.yml\",\"config_prod.yml\",\"config_test.yml\",\"parameters.yml\",\"routing.yml\",\"security.yml\",\"services.yml\",\"sites/default/default.settings.php\",\"sites/default/settings.php\",\"sites/default/settings.local.php\",\"app/etc/local.xml\",\"sftp-config.json\",\"web.config\",\"includes/config.php\",\"includes/configure.php\",\"config.inc.php\",\"localsettings.php\",\"inc/config.php\",\"typo3conf/localconf.php\",\"config/app.php\",\"config/custom.php\",\"config/database.php\",\"/configuration.php\",\"/config.php\",\"var/mail/www-data\",\"etc/network/\",\"etc/init/\",\"inetpub/wwwroot/global.asa\",\"system32/inetsrv/config/applicationhost.config\",\"system32/inetsrv/config/administration.config\",\"system32/inetsrv/config/redirection.config\",\"system32/config/default\",\"system32/config/sam\",\"system32/config/system\",\"system32/config/software\",\"winnt/repair/sam._\",\"package.json\",\"package-lock.json\",\"gruntfile.js\",\"npm-debug.log\",\"ormconfig.json\",\"tsconfig.json\",\"webpack.config.js\",\"yarn.lock\"]},\"operator\":\"phrase_match\"}],\"transformers\":[\"lowercase\"]},{\"id\":\"crs-931-110\",\"name\":\"Possible Remote File Inclusion (RFI) Attack: Common RFI Vulnerable Parameter Name used w/URL Payload\",\"tags\":{\"type\":\"rfi\",\"crs_id\":\"931110\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.query\"}],\"regex\":\"(?:\\\\binclude\\\\s*\\\\([^)]*|mosConfig_absolute_path|_CONF\\\\[path\\\\]|_SERVER\\\\[DOCUMENT_ROOT\\\\]|GALLERY_BASEDIR|path\\\\[docroot\\\\]|appserv_root|config\\\\[root_dir\\\\])=(?:file|ftps?|https?)://\",\"options\":{\"min_length\":15}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-931-120\",\"name\":\"Possible Remote File Inclusion (RFI) Attack: URL Payload Used w/Trailing Question Mark Character (?)\",\"tags\":{\"type\":\"rfi\",\"crs_id\":\"931120\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"^(?i:file|ftps?|https?).*?\\\\?+$\",\"options\":{\"case_sensitive\":true,\"min_length\":4}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-932-160\",\"name\":\"Remote Command Execution: Unix Shell Code Found\",\"tags\":{\"type\":\"command_injection\",\"crs_id\":\"932160\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"list\":[\"${cdpath}\",\"${dirstack}\",\"${home}\",\"${hostname}\",\"${ifs}\",\"${oldpwd}\",\"${ostype}\",\"${path}\",\"${pwd}\",\"$cdpath\",\"$dirstack\",\"$home\",\"$hostname\",\"$ifs\",\"$oldpwd\",\"$ostype\",\"$path\",\"$pwd\",\"bin/bash\",\"bin/cat\",\"bin/csh\",\"bin/dash\",\"bin/du\",\"bin/echo\",\"bin/grep\",\"bin/less\",\"bin/ls\",\"bin/mknod\",\"bin/more\",\"bin/nc\",\"bin/ps\",\"bin/rbash\",\"bin/sh\",\"bin/sleep\",\"bin/su\",\"bin/tcsh\",\"bin/uname\",\"dev/fd/\",\"dev/null\",\"dev/stderr\",\"dev/stdin\",\"dev/stdout\",\"dev/tcp/\",\"dev/udp/\",\"dev/zero\",\"etc/group\",\"etc/master.passwd\",\"etc/passwd\",\"etc/pwd.db\",\"etc/shadow\",\"etc/shells\",\"etc/spwd.db\",\"proc/self/\",\"usr/bin/awk\",\"usr/bin/base64\",\"usr/bin/cat\",\"usr/bin/cc\",\"usr/bin/clang\",\"usr/bin/clang++\",\"usr/bin/curl\",\"usr/bin/diff\",\"usr/bin/env\",\"usr/bin/fetch\",\"usr/bin/file\",\"usr/bin/find\",\"usr/bin/ftp\",\"usr/bin/gawk\",\"usr/bin/gcc\",\"usr/bin/head\",\"usr/bin/hexdump\",\"usr/bin/id\",\"usr/bin/less\",\"usr/bin/ln\",\"usr/bin/mkfifo\",\"usr/bin/more\",\"usr/bin/nc\",\"usr/bin/ncat\",\"usr/bin/nice\",\"usr/bin/nmap\",\"usr/bin/perl\",\"usr/bin/php\",\"usr/bin/php5\",\"usr/bin/php7\",\"usr/bin/php-cgi\",\"usr/bin/printf\",\"usr/bin/psed\",\"usr/bin/python\",\"usr/bin/python2\",\"usr/bin/python3\",\"usr/bin/ruby\",\"usr/bin/sed\",\"usr/bin/socat\",\"usr/bin/tail\",\"usr/bin/tee\",\"usr/bin/telnet\",\"usr/bin/top\",\"usr/bin/uname\",\"usr/bin/wget\",\"usr/bin/who\",\"usr/bin/whoami\",\"usr/bin/xargs\",\"usr/bin/xxd\",\"usr/bin/yes\",\"usr/local/bin/bash\",\"usr/local/bin/curl\",\"usr/local/bin/ncat\",\"usr/local/bin/nmap\",\"usr/local/bin/perl\",\"usr/local/bin/php\",\"usr/local/bin/python\",\"usr/local/bin/python2\",\"usr/local/bin/python3\",\"usr/local/bin/rbash\",\"usr/local/bin/ruby\",\"usr/local/bin/wget\"]},\"operator\":\"phrase_match\"}],\"transformers\":[\"lowercase\"]},{\"id\":\"crs-932-171\",\"name\":\"Remote Command Execution: Shellshock (CVE-2014-6271)\",\"tags\":{\"type\":\"command_injection\",\"crs_id\":\"932171\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"},{\"address\":\"server.request.headers.no_cookies\"}],\"regex\":\"^\\\\(\\\\s*\\\\)\\\\s+{\",\"options\":{\"case_sensitive\":true,\"min_length\":4}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-932-180\",\"name\":\"Restricted File Upload Attempt\",\"tags\":{\"type\":\"command_injection\",\"crs_id\":\"932180\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"x-filename\",\"x_filename\",\"x-file-name\"]}],\"list\":[\".htaccess\",\".htdigest\",\".htpasswd\",\"wp-config.php\",\"config.yml\",\"config_dev.yml\",\"config_prod.yml\",\"config_test.yml\",\"parameters.yml\",\"routing.yml\",\"security.yml\",\"services.yml\",\"default.settings.php\",\"settings.php\",\"settings.local.php\",\"local.xml\",\".env\"]},\"operator\":\"phrase_match\"}],\"transformers\":[\"lowercase\"]},{\"id\":\"crs-933-111\",\"name\":\"PHP Injection Attack: PHP Script File Upload Found\",\"tags\":{\"type\":\"unrestricted_file_upload\",\"crs_id\":\"933111\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"x-filename\",\"x_filename\",\"x.filename\",\"x-file-name\"]}],\"regex\":\".*\\\\.(?:php\\\\d*|phtml)\\\\..*$\",\"options\":{\"case_sensitive\":true,\"min_length\":5}},\"operator\":\"match_regex\"}],\"transformers\":[\"lowercase\"]},{\"id\":\"crs-933-130\",\"name\":\"PHP Injection Attack: Global Variables Found\",\"tags\":{\"type\":\"php_code_injection\",\"crs_id\":\"933130\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"list\":[\"$globals\",\"$http_cookie_vars\",\"$http_env_vars\",\"$http_get_vars\",\"$http_post_files\",\"$http_post_vars\",\"$http_raw_post_data\",\"$http_request_vars\",\"$http_server_vars\",\"$_cookie\",\"$_env\",\"$_files\",\"$_get\",\"$_post\",\"$_request\",\"$_server\",\"$_session\",\"$argc\",\"$argv\"]},\"operator\":\"phrase_match\"}],\"transformers\":[\"lowercase\"]},{\"id\":\"crs-933-131\",\"name\":\"PHP Injection Attack: HTTP Headers Values Found\",\"tags\":{\"type\":\"php_code_injection\",\"crs_id\":\"933131\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"(?:HTTP_(?:ACCEPT(?:_(?:ENCODING|LANGUAGE|CHARSET))?|(?:X_FORWARDED_FO|REFERE)R|(?:USER_AGEN|HOS)T|CONNECTION|KEEP_ALIVE)|PATH_(?:TRANSLATED|INFO)|ORIG_PATH_INFO|QUERY_STRING|REQUEST_URI|AUTH_TYPE)\",\"options\":{\"case_sensitive\":true,\"min_length\":9}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-933-140\",\"name\":\"PHP Injection Attack: I/O Stream Found\",\"tags\":{\"type\":\"php_code_injection\",\"crs_id\":\"933140\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"php://(?:std(?:in|out|err)|(?:in|out)put|fd|memory|temp|filter)\",\"options\":{\"min_length\":8}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-933-150\",\"name\":\"PHP Injection Attack: High-Risk PHP Function Name Found\",\"tags\":{\"type\":\"php_code_injection\",\"crs_id\":\"933150\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"list\":[\"__halt_compiler\",\"apache_child_terminate\",\"base64_decode\",\"bzdecompress\",\"call_user_func\",\"call_user_func_array\",\"call_user_method\",\"call_user_method_array\",\"convert_uudecode\",\"file_get_contents\",\"file_put_contents\",\"fsockopen\",\"get_class_methods\",\"get_class_vars\",\"get_defined_constants\",\"get_defined_functions\",\"get_defined_vars\",\"gzdecode\",\"gzinflate\",\"gzuncompress\",\"include_once\",\"invokeargs\",\"pcntl_exec\",\"pcntl_fork\",\"pfsockopen\",\"posix_getcwd\",\"posix_getpwuid\",\"posix_getuid\",\"posix_uname\",\"reflectionfunction\",\"require_once\",\"shell_exec\",\"str_rot13\",\"sys_get_temp_dir\",\"wp_remote_fopen\",\"wp_remote_get\",\"wp_remote_head\",\"wp_remote_post\",\"wp_remote_request\",\"wp_safe_remote_get\",\"wp_safe_remote_head\",\"wp_safe_remote_post\",\"wp_safe_remote_request\",\"zlib_decode\"]},\"operator\":\"phrase_match\"}],\"transformers\":[\"lowercase\"]},{\"id\":\"crs-933-160\",\"name\":\"PHP Injection Attack: High-Risk PHP Function Call Found\",\"tags\":{\"type\":\"php_code_injection\",\"crs_id\":\"933160\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"\\\\b(?:s(?:e(?:t(?:_(?:e(?:xception|rror)_handler|magic_quotes_runtime|include_path)|defaultstub)|ssion_s(?:et_save_handler|tart))|qlite_(?:(?:(?:unbuffered|single|array)_)?query|create_(?:aggregate|function)|p?open|exec)|tr(?:eam_(?:context_create|socket_client)|ipc?slashes|rev)|implexml_load_(?:string|file)|ocket_c(?:onnect|reate)|h(?:ow_sourc|a1_fil)e|pl_autoload_register|ystem)|p(?:r(?:eg_(?:replace(?:_callback(?:_array)?)?|match(?:_all)?|split)|oc_(?:(?:terminat|clos|nic)e|get_status|open)|int_r)|o(?:six_(?:get(?:(?:e[gu]|g)id|login|pwnam)|mk(?:fifo|nod)|ttyname|kill)|pen)|hp(?:_(?:strip_whitespac|unam)e|version|info)|g_(?:(?:execut|prepar)e|connect|query)|a(?:rse_(?:ini_file|str)|ssthru)|utenv)|r(?:unkit_(?:function_(?:re(?:defin|nam)e|copy|add)|method_(?:re(?:defin|nam)e|copy|add)|constant_(?:redefine|add))|e(?:(?:gister_(?:shutdown|tick)|name)_function|ad(?:(?:gz)?file|_exif_data|dir))|awurl(?:de|en)code)|i(?:mage(?:createfrom(?:(?:jpe|pn)g|x[bp]m|wbmp|gif)|(?:jpe|pn)g|g(?:d2?|if)|2?wbmp|xbm)|s_(?:(?:(?:execut|write?|read)ab|fi)le|dir)|ni_(?:get(?:_all)?|set)|terator_apply|ptcembed)|g(?:et(?:_(?:c(?:urrent_use|fg_va)r|meta_tags)|my(?:[gpu]id|inode)|(?:lastmo|cw)d|imagesize|env)|z(?:(?:(?:defla|wri)t|encod|fil)e|compress|open|read)|lob)|a(?:rray_(?:u(?:intersect(?:_u?assoc)?|diff(?:_u?assoc)?)|intersect_u(?:assoc|key)|diff_u(?:assoc|key)|filter|reduce|map)|ssert(?:_options)?)|h(?:tml(?:specialchars(?:_decode)?|_entity_decode|entities)|(?:ash(?:_(?:update|hmac))?|ighlight)_file|e(?:ader_register_callback|x2bin))|f(?:i(?:le(?:(?:[acm]tim|inod)e|(?:_exist|perm)s|group)?|nfo_open)|tp_(?:nb_(?:ge|pu)|connec|ge|pu)t|(?:unction_exis|pu)ts|write|open)|o(?:b_(?:get_(?:c(?:ontents|lean)|flush)|end_(?:clean|flush)|clean|flush|start)|dbc_(?:result(?:_all)?|exec(?:ute)?|connect)|pendir)|m(?:b_(?:ereg(?:_(?:replace(?:_callback)?|match)|i(?:_replace)?)?|parse_str)|(?:ove_uploaded|d5)_file|ethod_exists|ysql_query|kdir)|e(?:x(?:if_(?:t(?:humbnail|agname)|imagetype|read_data)|ec)|scapeshell(?:arg|cmd)|rror_reporting|val)|c(?:url_(?:file_create|exec|init)|onvert_uuencode|reate_function|hr)|u(?:n(?:serialize|pack)|rl(?:de|en)code|[ak]?sort)|(?:json_(?:de|en)cod|debug_backtrac|tmpfil)e|b(?:(?:son_(?:de|en)|ase64_en)code|zopen)|var_dump)(?:\\\\s|/\\\\*.*\\\\*/|//.*|#.*)*\\\\(.*\\\\)\",\"options\":{\"min_length\":5}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-933-170\",\"name\":\"PHP Injection Attack: Serialized Object Injection\",\"tags\":{\"type\":\"php_code_injection\",\"crs_id\":\"933170\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.headers.no_cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"[oOcC]:\\\\d+:\\\\\\\".+?\\\\\\\":\\\\d+:{[\\\\W\\\\w]*}\",\"options\":{\"case_sensitive\":true,\"min_length\":12}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-933-200\",\"name\":\"PHP Injection Attack: Wrapper scheme detected\",\"tags\":{\"type\":\"php_code_injection\",\"crs_id\":\"933200\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"(?i:zlib|glob|phar|ssh2|rar|ogg|expect|zip)://\",\"options\":{\"case_sensitive\":true,\"min_length\":6}},\"operator\":\"match_regex\"}],\"transformers\":[\"removeNulls\"]},{\"id\":\"crs-934-100\",\"name\":\"Node.js Injection Attack\",\"tags\":{\"type\":\"js_code_injection\",\"crs_id\":\"934100\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"(?:(?:_(?:\\\\$\\\\$ND_FUNC\\\\$\\\\$_|_js_function)|(?:new\\\\s+Function|\\\\beval)\\\\s*\\\\(|String\\\\s*\\\\.\\\\s*fromCharCode|function\\\\s*\\\\(\\\\s*\\\\)\\\\s*{|this\\\\.constructor)|module\\\\.exports\\\\s*=)\",\"options\":{\"case_sensitive\":true,\"min_length\":5}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-941-100\",\"name\":\"XSS Attack Detected via libinjection\",\"tags\":{\"type\":\"xss\",\"crs_id\":\"941100\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"user-agent\",\"referer\"]},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}]},\"operator\":\"is_xss\"}],\"transformers\":[\"removeNulls\"]},{\"id\":\"crs-941-110\",\"name\":\"XSS Filter - Category 1: Script Tag Vector\",\"tags\":{\"type\":\"xss\",\"crs_id\":\"941110\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"user-agent\",\"referer\"]},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"]*>[\\\\s\\\\S]*?\",\"options\":{\"min_length\":8}},\"operator\":\"match_regex\"}],\"transformers\":[\"removeNulls\"]},{\"id\":\"crs-941-120\",\"name\":\"XSS Filter - Category 2: Event Handler Vector\",\"tags\":{\"type\":\"xss\",\"crs_id\":\"941120\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"user-agent\",\"referer\"]},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"[\\\\s\\\\\\\"'`;\\\\/0-9=\\\\x0B\\\\x09\\\\x0C\\\\x3B\\\\x2C\\\\x28\\\\x3B]on[a-zA-Z]{3,25}[\\\\s\\\\x0B\\\\x09\\\\x0C\\\\x3B\\\\x2C\\\\x28\\\\x3B]*?=[^=]\",\"options\":{\"min_length\":8}},\"operator\":\"match_regex\"}],\"transformers\":[\"removeNulls\"]},{\"id\":\"crs-941-140\",\"name\":\"XSS Filter - Category 4: Javascript URI Vector\",\"tags\":{\"type\":\"xss\",\"crs_id\":\"941140\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"user-agent\",\"referer\"]},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"[a-z]+=(?:[^:=]+:.+;)*?[^:=]+:url\\\\(javascript\",\"options\":{\"min_length\":18}},\"operator\":\"match_regex\"}],\"transformers\":[\"removeNulls\"]},{\"id\":\"crs-941-180\",\"name\":\"Node-Validator Deny List Keywords\",\"tags\":{\"type\":\"xss\",\"crs_id\":\"941180\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"list\":[\"document.cookie\",\"document.write\",\".parentnode\",\".innerhtml\",\"window.location\",\"-moz-binding\",\"]\",\"options\":{\"min_length\":8}},\"operator\":\"match_regex\"}],\"transformers\":[\"removeNulls\"]},{\"id\":\"crs-941-300\",\"name\":\"IE XSS Filters - Attack Detected via object tag\",\"tags\":{\"type\":\"xss\",\"crs_id\":\"941300\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\")|<.*\\\\+AD4-\",\"options\":{\"case_sensitive\":true,\"min_length\":6}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-941-360\",\"name\":\"JSFuck / Hieroglyphy obfuscation detected\",\"tags\":{\"type\":\"xss\",\"crs_id\":\"941360\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"![!+ ]\\\\[\\\\]\",\"options\":{\"case_sensitive\":true,\"min_length\":4}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-942-100\",\"name\":\"SQL Injection Attack Detected via libinjection\",\"tags\":{\"type\":\"sqli\",\"crs_id\":\"942100\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}]},\"operator\":\"is_sqli\"}],\"transformers\":[\"removeNulls\"]},{\"id\":\"crs-942-140\",\"name\":\"SQL Injection Attack: Common DB Names Detected\",\"tags\":{\"type\":\"sqli\",\"crs_id\":\"942140\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"\\\\b(?:(?:m(?:s(?:ys(?:ac(?:cess(?:objects|storage|xml)|es)|(?:relationship|object|querie)s|modules2?)|db)|aster\\\\.\\\\.sysdatabases|ysql\\\\.db)|pg_(?:catalog|toast)|information_schema|northwind|tempdb)\\\\b|s(?:(?:ys(?:\\\\.database_name|aux)|qlite(?:_temp)?_master)\\\\b|chema(?:_name\\\\b|\\\\W*\\\\())|d(?:atabas|b_nam)e\\\\W*\\\\()\",\"options\":{\"min_length\":4}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-942-160\",\"name\":\"Detects blind sqli tests using sleep() or benchmark()\",\"tags\":{\"type\":\"sqli\",\"crs_id\":\"942160\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"(?i:sleep\\\\(\\\\s*?\\\\d*?\\\\s*?\\\\)|benchmark\\\\(.*?\\\\,.*?\\\\))\",\"options\":{\"case_sensitive\":true,\"min_length\":7}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-942-190\",\"name\":\"Detects MSSQL code execution and information gathering attempts\",\"tags\":{\"type\":\"sqli\",\"crs_id\":\"942190\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"(?:\\\\b(?:(?:c(?:onnection_id|urrent_user)|database)\\\\s*?\\\\([^\\\\)]*?|u(?:nion(?:[\\\\w(?:\\\\s]*?select| select @)|ser\\\\s*?\\\\([^\\\\)]*?)|s(?:chema\\\\s*?\\\\([^\\\\)]*?|elect.*?\\\\w?user\\\\()|into[\\\\s+]+(?:dump|out)file\\\\s*?[\\\\\\\"'`]|from\\\\W+information_schema\\\\W|exec(?:ute)?\\\\s+master\\\\.)|[\\\\\\\"'`](?:;?\\\\s*?(?:union\\\\b\\\\s*?(?:(?:distin|sele)ct|all)|having|select)\\\\b\\\\s*?[^\\\\s]|\\\\s*?!\\\\s*?[\\\\\\\"'`\\\\w])|\\\\s*?exec(?:ute)?.*?\\\\Wxp_cmdshell|\\\\Wiif\\\\s*?\\\\()\",\"options\":{\"min_length\":3}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-942-220\",\"name\":\"Looking for integer overflow attacks, these are taken from skipfish, except 2.2.2250738585072011e-308 is the \\\\\\\"magic number\\\\\\\" crash\",\"tags\":{\"type\":\"sqli\",\"crs_id\":\"942220\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"^(?i:-0000023456|4294967295|4294967296|2147483648|2147483647|0000012345|-2147483648|-2147483649|0000023456|2.2250738585072007e-308|2.2250738585072011e-308|1e309)$\",\"options\":{\"case_sensitive\":true,\"min_length\":5}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-942-240\",\"name\":\"Detects MySQL charset switch and MSSQL DoS attempts\",\"tags\":{\"type\":\"sqli\",\"crs_id\":\"942240\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"(?:[\\\\\\\"'`](?:;*?\\\\s*?waitfor\\\\s+(?:delay|time)\\\\s+[\\\\\\\"'`]|;.*?:\\\\s*?goto)|alter\\\\s*?\\\\w+.*?cha(?:racte)?r\\\\s+set\\\\s+\\\\w+)\",\"options\":{\"min_length\":7}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-942-250\",\"name\":\"Detects MATCH AGAINST, MERGE and EXECUTE IMMEDIATE injections\",\"tags\":{\"type\":\"sqli\",\"crs_id\":\"942250\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"(?i:merge.*?using\\\\s*?\\\\(|execute\\\\s*?immediate\\\\s*?[\\\\\\\"'`]|match\\\\s*?[\\\\w(?:),+-]+\\\\s*?against\\\\s*?\\\\()\",\"options\":{\"case_sensitive\":true,\"min_length\":11}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-942-270\",\"name\":\"Looking for basic sql injection. Common attack string for mysql, oracle and others\",\"tags\":{\"type\":\"sqli\",\"crs_id\":\"942270\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"union.*?select.*?from\",\"options\":{\"min_length\":15}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-942-280\",\"name\":\"Detects Postgres pg_sleep injection, waitfor delay attacks and database shutdown attempts\",\"tags\":{\"type\":\"sqli\",\"crs_id\":\"942280\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"(?:;\\\\s*?shutdown\\\\s*?(?:[#;{]|\\\\/\\\\*|--)|waitfor\\\\s*?delay\\\\s?[\\\\\\\"'`]+\\\\s?\\\\d|select\\\\s*?pg_sleep)\",\"options\":{\"min_length\":10}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-942-290\",\"name\":\"Finds basic MongoDB SQL injection attempts\",\"tags\":{\"type\":\"nosqli\",\"crs_id\":\"942290\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"(?i:(?:\\\\[\\\\$(?:ne|eq|lte?|gte?|n?in|mod|all|size|exists|type|slice|x?or|div|like|between|and)\\\\]))\",\"options\":{\"case_sensitive\":true,\"min_length\":5}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-942-360\",\"name\":\"Detects concatenated basic SQL injection and SQLLFI attempts\",\"tags\":{\"type\":\"sqli\",\"crs_id\":\"942360\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"(?:^[\\\\W\\\\d]+\\\\s*?(?:alter\\\\s*(?:a(?:(?:pplication\\\\s*rol|ggregat)e|s(?:ymmetric\\\\s*ke|sembl)y|u(?:thorization|dit)|vailability\\\\s*group)|c(?:r(?:yptographic\\\\s*provider|edential)|o(?:l(?:latio|um)|nversio)n|ertificate|luster)|s(?:e(?:rv(?:ice|er)|curity|quence|ssion|arch)|y(?:mmetric\\\\s*key|nonym)|togroup|chema)|m(?:a(?:s(?:ter\\\\s*key|k)|terialized)|e(?:ssage\\\\s*type|thod)|odule)|l(?:o(?:g(?:file\\\\s*group|in)|ckdown)|a(?:ngua|r)ge|ibrary)|t(?:(?:abl(?:espac)?|yp)e|r(?:igger|usted)|hreshold|ext)|p(?:a(?:rtition|ckage)|ro(?:cedur|fil)e|ermission)|d(?:i(?:mension|skgroup)|atabase|efault|omain)|r(?:o(?:l(?:lback|e)|ute)|e(?:sourc|mot)e)|f(?:u(?:lltext|nction)|lashback|oreign)|e(?:xte(?:nsion|rnal)|(?:ndpoi|ve)nt)|in(?:dex(?:type)?|memory|stance)|b(?:roker\\\\s*priority|ufferpool)|x(?:ml\\\\s*schema|srobject)|w(?:ork(?:load)?|rapper)|hi(?:erarchy|stogram)|o(?:perator|utline)|(?:nicknam|queu)e|us(?:age|er)|group|java|view)\\\\b|(?:(?:(?:trunc|cre)at|renam)e|d(?:e(?:lete|sc)|rop)|(?:inser|selec)t|load)\\\\s+\\\\w+|u(?:nion\\\\s*(?:(?:distin|sele)ct|all)\\\\b|pdate\\\\s+\\\\w+))|\\\\b(?:(?:(?:(?:trunc|cre|upd)at|renam)e|(?:inser|selec)t|de(?:lete|sc)|alter|load)\\\\s+(?:group_concat|load_file|char)\\\\b\\\\s*\\\\(?|end\\\\s*?\\\\);)|[\\\\\\\"'`\\\\w]\\\\s+as\\\\b\\\\s*[\\\\\\\"'`\\\\w]+\\\\s*\\\\bfrom|[\\\\s(?:]load_file\\\\s*?\\\\(|[\\\\\\\"'`]\\\\s+regexp\\\\W)\",\"options\":{\"min_length\":5}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-942-500\",\"name\":\"MySQL in-line comment detected\",\"tags\":{\"type\":\"sqli\",\"crs_id\":\"942500\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"(?i:/\\\\*[!+](?:[\\\\w\\\\s=_\\\\-(?:)]+)?\\\\*/)\",\"options\":{\"case_sensitive\":true,\"min_length\":5}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-943-100\",\"name\":\"Possible Session Fixation Attack: Setting Cookie Values in HTML\",\"tags\":{\"type\":\"http_protocol_violation\",\"crs_id\":\"943100\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"(?i:\\\\.cookie\\\\b.*?;\\\\W*?(?:expires|domain)\\\\W*?=|\\\\bhttp-equiv\\\\W+set-cookie\\\\b)\",\"options\":{\"case_sensitive\":true,\"min_length\":15}},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"crs-944-100\",\"name\":\"Remote Command Execution: Suspicious Java class detected\",\"tags\":{\"type\":\"java_code_injection\",\"crs_id\":\"944100\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"},{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.headers.no_cookies\"}],\"regex\":\"java\\\\.lang\\\\.(?:runtime|processbuilder)\",\"options\":{\"case_sensitive\":true,\"min_length\":17}},\"operator\":\"match_regex\"}],\"transformers\":[\"lowercase\"]},{\"id\":\"crs-944-110\",\"name\":\"Remote Command Execution: Java process spawn (CVE-2017-9805)\",\"tags\":{\"type\":\"java_code_injection\",\"crs_id\":\"944110\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"},{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.headers.no_cookies\"}],\"regex\":\"(?:runtime|processbuilder)\",\"options\":{\"case_sensitive\":true,\"min_length\":7}},\"operator\":\"match_regex\"},{\"parameters\":{\"inputs\":[{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"},{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.headers.no_cookies\"}],\"regex\":\"(?:unmarshaller|base64data|java\\\\.)\",\"options\":{\"case_sensitive\":true,\"min_length\":5}},\"operator\":\"match_regex\"}],\"transformers\":[\"lowercase\"]},{\"id\":\"crs-944-130\",\"name\":\"Suspicious Java class detected\",\"tags\":{\"type\":\"java_code_injection\",\"crs_id\":\"944130\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"},{\"address\":\"server.request.cookies\"},{\"address\":\"server.request.headers.no_cookies\"}],\"list\":[\"com.opensymphony.xwork2\",\"com.sun.org.apache\",\"java.io.bufferedinputstream\",\"java.io.bufferedreader\",\"java.io.bytearrayinputstream\",\"java.io.bytearrayoutputstream\",\"java.io.chararrayreader\",\"java.io.datainputstream\",\"java.io.file\",\"java.io.fileoutputstream\",\"java.io.filepermission\",\"java.io.filewriter\",\"java.io.filterinputstream\",\"java.io.filteroutputstream\",\"java.io.filterreader\",\"java.io.inputstream\",\"java.io.inputstreamreader\",\"java.io.linenumberreader\",\"java.io.objectoutputstream\",\"java.io.outputstream\",\"java.io.pipedoutputstream\",\"java.io.pipedreader\",\"java.io.printstream\",\"java.io.pushbackinputstream\",\"java.io.reader\",\"java.io.stringreader\",\"java.lang.class\",\"java.lang.integer\",\"java.lang.number\",\"java.lang.object\",\"java.lang.process\",\"java.lang.processbuilder\",\"java.lang.reflect\",\"java.lang.runtime\",\"java.lang.string\",\"java.lang.stringbuilder\",\"java.lang.system\",\"javax.script.scriptenginemanager\",\"org.apache.commons\",\"org.apache.struts\",\"org.apache.struts2\",\"org.omg.corba\",\"java.beans.xmldecode\"]},\"operator\":\"phrase_match\"}],\"transformers\":[\"lowercase\"]},{\"id\":\"sqr-000-001\",\"name\":\"SSRF: Try to access the credential manager of the main cloud services\",\"tags\":{\"type\":\"ssrf\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"(?i)^\\\\W*((http|ftp)s?://)?\\\\W*((::f{4}:)?(169|(0x)?0*a9|0+251)\\\\.?(254|(0x)?0*fe|0+376)[0-9a-fx\\\\.:]+|metadata\\\\.google\\\\.internal|metadata\\\\.goog)\\\\W*/\",\"options\":{\"min_length\":4}},\"operator\":\"match_regex\"}],\"transformers\":[\"removeNulls\"]},{\"id\":\"sqr-000-002\",\"name\":\"Server-side Javascript injection: Try to detect obvious JS injection\",\"tags\":{\"type\":\"js_code_injection\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"require\\\\(['\\\"][\\\\w\\\\.]+['\\\"]\\\\)|process\\\\.\\\\w+\\\\([\\\\w\\\\.]*\\\\)|\\\\.toString\\\\(\\\\)\",\"options\":{\"min_length\":4}},\"operator\":\"match_regex\"}],\"transformers\":[\"removeNulls\"]},{\"id\":\"sqr-000-007\",\"name\":\"NoSQL: Detect common exploitation strategy\",\"tags\":{\"type\":\"nosqli\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.headers.no_cookies\"}],\"regex\":\"\\\\$(eq|ne|lte?|gte?|n?in)\\\\b\"},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"sqr-000-008\",\"name\":\"Windows: Detect attempts to exfiltrate .ini files\",\"tags\":{\"type\":\"command_injection\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"},{\"address\":\"server.request.headers.no_cookies\"}],\"regex\":\"(?i)[&|]\\\\s*type\\\\s+%\\\\w+%\\\\\\\\+\\\\w+\\\\.ini\\\\s*[&|]\"},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"sqr-000-009\",\"name\":\"Linux: Detect attempts to exfiltrate passwd files\",\"tags\":{\"type\":\"command_injection\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"},{\"address\":\"server.request.headers.no_cookies\"}],\"regex\":\"(?i)[&|]\\\\s*cat\\\\s+\\\\/etc\\\\/[\\\\w\\\\.\\\\/]*passwd\\\\s*[&|]\"},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"sqr-000-010\",\"name\":\"Windows: Detect attempts to timeout a shell\",\"tags\":{\"type\":\"command_injection\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"},{\"address\":\"server.request.headers.no_cookies\"}],\"regex\":\"(?i)[&|]\\\\s*timeout\\\\s+/t\\\\s+\\\\d+\\\\s*[&|]\"},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"sqr-000-011\",\"name\":\"SSRF: Try to access internal OMI service (CVE-2021-38647)\",\"tags\":{\"type\":\"ssrf\",\"category\":\"attack_attempt\"},\"conditions\":[{\"operator\":\"match_regex\",\"parameters\":{\"inputs\":[{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"http(s?):\\\\/\\\\/([A-Za-z0-9\\\\.\\\\-\\\\_]+|\\\\[[A-Fa-f0-9\\\\:]+\\\\]|):5986\\\\/wsman\",\"options\":{\"min_length\":4}}}],\"transformers\":[]},{\"id\":\"sqr-000-012\",\"name\":\"SSRF: Detect SSRF attempt on internal service\",\"tags\":{\"type\":\"ssrf\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"^(jar:)?(http|https):\\\\/\\\\/([0-9oq]{1,5}\\\\.[0-9]{1,3}\\\\.[0-9]{1,3}\\\\.[0-9]{1,3}|[0-9]{1,10}|localhost)(:[0-9]{1,5})?(\\\\/.*|)$\"},\"operator\":\"match_regex\"}],\"transformers\":[\"lowercase\"]},{\"id\":\"sqr-000-013\",\"name\":\"SSRF: Detect SSRF attempts using IPv6 or octal/hexdecimal obfuscation\",\"tags\":{\"type\":\"ssrf\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"}],\"regex\":\"^(jar:)?(http|https):\\\\/\\\\/((\\\\[)?[:0-9a-f\\\\.x]{2,}(\\\\])?)(:[0-9]{1,5})?(\\\\/.*)?$\"},\"operator\":\"match_regex\"}],\"transformers\":[\"lowercase\"]},{\"id\":\"sqr-000-014\",\"name\":\"SSRF: Detect SSRF domain redirection bypass\",\"tags\":{\"type\":\"ssrf\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"},{\"address\":\"server.request.headers.no_cookies\"}],\"regex\":\"^(http|https):\\\\/\\\\/(.*burpcollaborator\\\\.net|localtest\\\\.me|mail\\\\.ebc\\\\.apple\\\\.com|bugbounty\\\\.dod\\\\.network|.*\\\\.[nx]ip\\\\.io)\"},\"operator\":\"match_regex\"}],\"transformers\":[\"lowercase\"]},{\"id\":\"sqr-000-015\",\"name\":\"SSRF: Detect SSRF attempt using non HTTP protocol\",\"tags\":{\"type\":\"ssrf\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"},{\"address\":\"server.request.headers.no_cookies\"}],\"regex\":\"^(jar:)?((file|netdoc):\\\\/\\\\/[\\\\\\\\\\\\/]+|(dict|gopher|ldap|sftp|tftp):\\\\/\\\\/.*:[0-9]{1,5})\"},\"operator\":\"match_regex\"}],\"transformers\":[\"lowercase\"]},{\"id\":\"ua0-600-0xx\",\"name\":\"Joomla exploitation tool\",\"tags\":{\"type\":\"security_scanner\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"user-agent\"]}],\"regex\":\"JDatabaseDriverMysqli\"},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"ua0-600-10x\",\"name\":\"Nessus\",\"tags\":{\"type\":\"security_scanner\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"user-agent\"]}],\"regex\":\"(?i)^Nessus(/|([ :]+SOAP))\"},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"ua0-600-12x\",\"name\":\"Arachni\",\"tags\":{\"type\":\"security_scanner\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"user-agent\"]}],\"regex\":\"^Arachni\\\\/v\"},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"ua0-600-13x\",\"name\":\"Jorgee\",\"tags\":{\"type\":\"security_scanner\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"user-agent\"]}],\"regex\":\"(?i)\\\\bJorgee\\\\b\"},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"ua0-600-14x\",\"name\":\"Probely\",\"tags\":{\"type\":\"security_scanner\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"user-agent\"]}],\"regex\":\"(?i)\\\\bProbely\\\\b\"},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"ua0-600-15x\",\"name\":\"Metis\",\"tags\":{\"type\":\"security_scanner\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"user-agent\"]}],\"regex\":\"(?i)\\\\bmetis\\\\b\"},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"ua0-600-16x\",\"name\":\"SQL power injector\",\"tags\":{\"type\":\"security_scanner\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"user-agent\"]}],\"regex\":\"sql power injector\"},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"ua0-600-18x\",\"name\":\"N-Stealth\",\"tags\":{\"type\":\"security_scanner\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"user-agent\"]}],\"regex\":\"(?i)\\\\bn-stealth\\\\b\"},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"ua0-600-19x\",\"name\":\"Brutus\",\"tags\":{\"type\":\"security_scanner\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"user-agent\"]}],\"regex\":\"(?i)\\\\bbrutus\\\\b\"},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"ua0-600-1xx\",\"name\":\"Shellshock exploitation tool\",\"tags\":{\"type\":\"security_scanner\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"user-agent\"]}],\"regex\":\"\\\\(\\\\) \\\\{ :; *\\\\}\"},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"id\":\"ua0-600-20x\",\"name\":\"Netsparker\",\"tags\":{\"type\":\"security_scanner\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"user-agent\"]}],\"regex\":\"(?i)(