diff --git a/.circleci/config.yml b/.circleci/config.yml index 0496ada1c6..bae0a7f30f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,11 +15,20 @@ jobs: description: "go build tags used to compile" default: "" type: string + goflags: + description: "extra goflags to pass to go test" + default: "" + type: string docker: - image: circleci/golang:1.12 environment: GOPATH: "/home/circleci/go" working_directory: /home/circleci/dd-trace-go.v1 + environment: + # Go 1.12 doesn't have the proxy turned on by default but we need it to + # fetch some dependencies that aren't handled by fetching directly + # (such as github.com/go-chi/chi/v4@v4.0.0-rc1) + GOPROXY: "https://proxy.golang.org" steps: - checkout @@ -106,9 +115,14 @@ jobs: description: "go build tags to use to compile the tests" default: "" type: string + goflags: + description: "extra goflags to pass to go test" + default: "" + type: string resource_class: xlarge environment: # environment variables for the build itself TEST_RESULTS: /tmp/test-results # path to where test results will be saved + DD_APPSEC_WAF_TIMEOUT: 1s <<: *plain-go114 steps: @@ -118,16 +132,11 @@ jobs: - restore_cache: # restores saved cache if no changes are detected since last run keys: - 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: | PACKAGE_NAMES=$(go list ./... | grep -v /contrib/ | 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 >>" + env DD_APPSEC_ENABLED=$(test "<< parameters.build_tags >>" = appsec && echo -n true) gotestsum --junitfile ${TEST_RESULTS}/gotestsum-report.xml -- $PACKAGE_NAMES -v << parameters.goflags >> -coverprofile=coverage.txt -covermode=atomic -tags "<< parameters.build_tags >>" - save_cache: key: go-mod-v5-core-{{ checksum "go.sum.orig" }} @@ -152,9 +161,14 @@ jobs: description: "go build tags to use to compile the tests" default: "" type: string + goflags: + description: "extra goflags to pass to go test" + default: "" + type: string resource_class: xlarge environment: # environment variables for the build itself TEST_RESULTS: /tmp/test-results # path to where test results will be saved + DD_APPSEC_WAF_TIMEOUT: 1s working_directory: /home/circleci/dd-trace-go.v1 docker: - image: circleci/golang:1.14 @@ -174,6 +188,10 @@ jobs: POSTGRES_PASSWORD: postgres POSTGRES_USER: postgres POSTGRES_DB: postgres + - image: mcr.microsoft.com/mssql/server:2019-latest + environment: + SA_PASSWORD: myPassw0rd + ACCEPT_EULA: Y - image: consul:1.6.0 - image: redis:3.2 - image: elasticsearch:2 @@ -199,16 +217,19 @@ jobs: DD_API_KEY: invalid_key_but_this_is_fine - image: circleci/mongo:latest-ram - image: memcached:1.5.9 - - image: confluentinc/cp-zookeeper:5.0.0 + - image: bitnami/zookeeper:latest + environment: + ALLOW_ANONYMOUS_LOGIN: yes + - image: bitnami/kafka:2 environment: - ZOOKEEPER_CLIENT_PORT: "2181" - - image: confluentinc/cp-kafka:5.0.0 + KAFKA_CFG_ZOOKEEPER_CONNECT: localhost:2181 + KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 + KAFKA_CFG_LISTENERS: PLAINTEXT://0.0.0.0:9092 + ALLOW_PLAINTEXT_LISTENER: yes + - image: bitnami/kafka:2 environment: - KAFKA_ZOOKEEPER_CONNECT: localhost:2181 - KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 - KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092 - KAFKA_CREATE_TOPICS: gotest:1:1 - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: "1" + KAFKA_CFG_ZOOKEEPER_CONNECT: localhost:2181 + command: [kafka-topics.sh, --create, --topic, gosegtest, --bootstrap-server, localhost:9092] steps: - checkout @@ -237,19 +258,6 @@ jobs: paths: - /tmp/librdkafka-v1.3.0 - - run: - name: Enforce some dependencies - command: | - go get k8s.io/client-go@v0.17.0 - go get k8s.io/apimachinery@v0.17.0 - 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 - # 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 - - run: name: Wait for MySQL command: dockerize -wait tcp://localhost:3306 -timeout 1m @@ -258,6 +266,10 @@ jobs: name: Wait for Postgres command: dockerize -wait tcp://localhost:5432 -timeout 1m + - run: + name: Wait for MS SQL Server + command: dockerize -wait tcp://localhost:1433 -timeout 1m + - run: name: Wait for Redis command: dockerize -wait tcp://localhost:6379 -timeout 1m @@ -294,13 +306,22 @@ jobs: name: Wait for Consul command: dockerize -wait http://localhost:8500 -timeout 1m + - run: + name: Go module graph (before) + command: go mod graph + - run: 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) 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 >>" + gotestsum --junitfile ${TEST_RESULTS}/gotestsum-report.xml -- $PACKAGE_NAMES -v << parameters.goflags >> -coverprofile=coverage.txt -covermode=atomic -tags "<< parameters.build_tags >>" + + - run: + name: Go module graph (after) + command: go mod graph + when: always - store_artifacts: # upload test summary for display in Artifacts path: /tmp/test-results @@ -361,3 +382,27 @@ workflows: matrix: parameters: build_tags: [ "", "appsec" ] + nightly: + triggers: + - schedule: + cron: "0 0 * * *" + filters: + branches: + only: + - v1 + jobs: + - go1_12-build: + matrix: + parameters: + build_tags: [ "", "appsec" ] + goflags: [ "-race" ] + - test-core: + matrix: + parameters: + build_tags: [ "", "appsec" ] + goflags: [ "-race" ] + - test-contrib: + matrix: + parameters: + build_tags: [ "", "appsec" ] + goflags: [ "-race" ] diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index 4bd5a18a4b..0f1efd30b5 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -1,6 +1,11 @@ name: System Tests on: + push: + branches: + - v1 + tags: + - "**" pull_request: branches: - "**" @@ -10,7 +15,6 @@ on: jobs: system-tests: - if: ${{ github.event.pull_request.head.repo.full_name == 'DataDog/dd-trace-go' }} runs-on: ubuntu-latest strategy: matrix: @@ -19,6 +23,10 @@ jobs: weblog-variant: net-http - library: golang weblog-variant: gorilla + - library: golang + weblog-variant: echo + - library: golang + weblog-variant: chi fail-fast: false env: TEST_LIBRARY: golang diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000000..7702abe6ef --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,18 @@ +# 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 +/appsec @DataDog/appsec-go +/internal/appsec @DataDog/appsec-go +/contrib/**/appsec.go @DataDog/appsec-go diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9c44533b48..bab11fb058 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,15 +40,9 @@ Please view our contrib [README.md](contrib/README.md) for information on new in ### Go Modules -This repository currently takes an [idiosyncratic approach](https://github.com/DataDog/dd-trace-go/issues/810) to using Go modules which means that you should not commit modified versions of the `go.mod` or `go.sum` files. +When adding a new dependency, especially for `contrib/` packages, prefer the minimum secure versions of any modules rather than the latest versions. This is to avoid forcing upgrades on downstream users for modules such as `google.golang.org/grpc` which often introduce breaking changes within major versions. -The following git command can be used to permanently ignore modifications to these files: - -``` -git update-index --assume-unchanged go.* -``` - -If you need to undo this for any reason, you can run: +This repository used to omit many dependencies from the `go.mod` file due to concerns around version compatibility [(ref)](https://github.com/DataDog/dd-trace-go/issues/810). As such, you may have configured git to ignore changes to `go.mod` and `go.sum`. To undo this, run ``` git update-index --no-assume-unchanged go.* diff --git a/appsec/appsec.go b/appsec/appsec.go new file mode 100644 index 0000000000..36833749a9 --- /dev/null +++ b/appsec/appsec.go @@ -0,0 +1,32 @@ +// 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 2022 Datadog, Inc. + +// Package appsec provides application security features in the form of SDK +// functions that can be manually called to monitor specific code paths and data. +// Application Security is currently transparently integrated into the APM tracer +// and cannot be used nor started alone at the moment. +// You can read more on how to enable and start Application Security for Go at +// https://docs.datadoghq.com/security_platform/application_security/getting_started/go +package appsec + +import ( + "golang.org/x/net/context" + + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo/instrumentation/httpsec" +) + +// MonitorParsedHTTPBody runs the security monitoring rules on the given *parsed* +// HTTP request body. The given context must be the HTTP request context as returned +// by the Context() method of an HTTP request. Calls to this function are ignored if +// AppSec is disabled or the given context is incorrect. +// Note that passing the raw bytes of the HTTP request body is not expected and would +// result in inaccurate attack detection. +func MonitorParsedHTTPBody(ctx context.Context, body interface{}) { + if appsec.Enabled() { + httpsec.MonitorParsedBody(ctx, body) + } + // bonus: use sync.Once to log a debug message once if AppSec is disabled +} diff --git a/appsec/example_test.go b/appsec/example_test.go new file mode 100644 index 0000000000..e290e657e0 --- /dev/null +++ b/appsec/example_test.go @@ -0,0 +1,62 @@ +// 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 2022 Datadog, Inc. + +package appsec_test + +import ( + "encoding/json" + "io" + "net/http" + + "gopkg.in/DataDog/dd-trace-go.v1/appsec" + echotrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/labstack/echo.v4" + httptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http" + + "github.com/labstack/echo/v4" +) + +type parsedBodyType struct { + Value string `json:"value"` +} + +func customBodyParser(body io.ReadCloser) (*parsedBodyType, error) { + var parsedBody parsedBodyType + err := json.NewDecoder(body).Decode(&parsedBody) + return &parsedBody, err +} + +// Monitor HTTP request parsed body +func ExampleMonitorParsedHTTPBody() { + mux := httptrace.NewServeMux() + mux.HandleFunc("/body", func(w http.ResponseWriter, r *http.Request) { + // Use the SDK to monitor the request's parsed body + body, err := customBodyParser(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + appsec.MonitorParsedHTTPBody(r.Context(), body) + w.Write([]byte("Body monitored using AppSec SDK\n")) + }) + http.ListenAndServe(":8080", mux) +} + +// Monitor HTTP request parsed body with a framework customized context type +func ExampleMonitorParsedHTTPBody_CustomContext() { + r := echo.New() + r.Use(echotrace.Middleware()) + r.POST("/body", func(c echo.Context) (e error) { + req := c.Request() + body, err := customBodyParser(req.Body) + if err != nil { + return c.String(http.StatusInternalServerError, err.Error()) + } + // Use the SDK to monitor the request's parsed body + appsec.MonitorParsedHTTPBody(c.Request().Context(), body) + return c.String(http.StatusOK, "Body monitored using AppSec SDK") + }) + + r.Start(":8080") +} diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index fa214bbe15..d382301a5d 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -22,7 +22,8 @@ var _ driver.Conn = (*tracedConn)(nil) type queryType string const ( - queryTypeQuery queryType = "Query" + queryTypeConnect queryType = "Connect" + queryTypeQuery = "Query" queryTypePing = "Ping" queryTypePrepare = "Prepare" queryTypeExec = "Exec" @@ -73,13 +74,6 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr return &tracedStmt{stmt, tc.traceParams, ctx, query}, nil } -func (tc *tracedConn) Exec(query string, args []driver.Value) (driver.Result, error) { - if execer, ok := tc.Conn.(driver.Execer); ok { - return execer.Exec(query, args) - } - return nil, driver.ErrSkip -} - func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (r driver.Result, err error) { start := time.Now() if execContext, ok := tc.Conn.(driver.ExecerContext); ok { @@ -87,18 +81,21 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv tc.tryTrace(ctx, queryTypeExec, query, start, err) return r, err } - dargs, err := namedValueToValue(args) - if err != nil { - return nil, err - } - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: + if execer, ok := tc.Conn.(driver.Execer); ok { + dargs, err := namedValueToValue(args) + if err != nil { + return nil, err + } + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + r, err = execer.Exec(query, dargs) + tc.tryTrace(ctx, queryTypeExec, query, start, err) + return r, err } - r, err = tc.Exec(query, dargs) - tc.tryTrace(ctx, queryTypeExec, query, start, err) - return r, err + return nil, driver.ErrSkip } // tracedConn has a Ping method in order to implement the pinger interface @@ -111,13 +108,6 @@ func (tc *tracedConn) Ping(ctx context.Context) (err error) { return err } -func (tc *tracedConn) Query(query string, args []driver.Value) (driver.Rows, error) { - if queryer, ok := tc.Conn.(driver.Queryer); ok { - return queryer.Query(query, args) - } - return nil, driver.ErrSkip -} - func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (rows driver.Rows, err error) { start := time.Now() if queryerContext, ok := tc.Conn.(driver.QueryerContext); ok { @@ -125,18 +115,21 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri tc.tryTrace(ctx, queryTypeQuery, query, start, err) return rows, err } - dargs, err := namedValueToValue(args) - if err != nil { - return nil, err - } - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: + if queryer, ok := tc.Conn.(driver.Queryer); ok { + dargs, err := namedValueToValue(args) + if err != nil { + return nil, err + } + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + rows, err = queryer.Query(query, dargs) + tc.tryTrace(ctx, queryTypeQuery, query, start, err) + return rows, err } - rows, err = tc.Query(query, dargs) - tc.tryTrace(ctx, queryTypeQuery, query, start, err) - return rows, err + return nil, driver.ErrSkip } func (tc *tracedConn) CheckNamedValue(value *driver.NamedValue) error { @@ -153,7 +146,8 @@ func (tc *tracedConn) ResetSession(ctx context.Context) error { if resetter, ok := tc.Conn.(driver.SessionResetter); ok { return resetter.ResetSession(ctx) } - return driver.ErrSkip + // If driver doesn't implement driver.SessionResetter there's nothing to do + return nil } // traceParams stores all information related to tracing the driver.Conn diff --git a/contrib/database/sql/conn_test.go b/contrib/database/sql/conn_test.go index d08aa45d4e..3203bf0e27 100644 --- a/contrib/database/sql/conn_test.go +++ b/contrib/database/sql/conn_test.go @@ -92,9 +92,16 @@ func TestWithSpanTags(t *testing.T) { rows.Close() spans := mt.FinishedSpans() - assert.Len(t, spans, 1) + assert.Len(t, spans, 2) - span := spans[0] + connectSpan := spans[0] + assert.Equal(t, tt.want.opName, connectSpan.OperationName()) + assert.Equal(t, "Connect", connectSpan.Tag("sql.query_type")) + for k, v := range tt.want.ctxTags { + assert.Equal(t, v, connectSpan.Tag(k), "Value mismatch on tag %s", k) + } + + span := spans[1] assert.Equal(t, tt.want.opName, span.OperationName()) for k, v := range tt.want.ctxTags { assert.Equal(t, v, span.Tag(k), "Value mismatch on tag %s", k) diff --git a/contrib/database/sql/internal/dsn.go b/contrib/database/sql/internal/dsn.go index f087b4784a..da6f0da8a5 100644 --- a/contrib/database/sql/internal/dsn.go +++ b/contrib/database/sql/internal/dsn.go @@ -27,6 +27,11 @@ func ParseDSN(driverName, dsn string) (meta map[string]string, err error) { if err != nil { return } + case "sqlserver": + meta, err = parseSQLServerDSN(dsn) + if err != nil { + return + } default: // not supported } @@ -86,3 +91,23 @@ func parsePostgresDSN(dsn string) (map[string]string, error) { delete(meta, "password") return meta, nil } + +// parseSQLServerDSN parses a sqlserver-type dsn into a map +func parseSQLServerDSN(dsn string) (map[string]string, error) { + var err error + var meta map[string]string + if strings.HasPrefix(dsn, "sqlserver://") { + // url form + meta, err = parseSQLServerURL(dsn) + if err != nil { + return nil, err + } + } else { + meta, err = parseSQLServerADO(dsn) + if err != nil { + return nil, err + } + } + delete(meta, "password") + return meta, nil +} diff --git a/contrib/database/sql/internal/dsn_test.go b/contrib/database/sql/internal/dsn_test.go index ceba66317c..bd902b5fb9 100644 --- a/contrib/database/sql/internal/dsn_test.go +++ b/contrib/database/sql/internal/dsn_test.go @@ -51,6 +51,16 @@ func TestParseDSN(t *testing.T) { ext.DBUser: "dog", }, }, + { + driverName: "sqlserver", + dsn: "sqlserver://bob:secret@1.2.3.4:1433?database=mydb", + expected: map[string]string{ + ext.DBUser: "bob", + ext.TargetHost: "1.2.3.4", + ext.TargetPort: "1433", + ext.DBName: "mydb", + }, + }, } { m, err := ParseDSN(tt.driverName, tt.dsn) assert.Equal(nil, err) @@ -104,3 +114,52 @@ func TestParsePostgresDSN(t *testing.T) { assert.Equal(tt.expected, m) } } + +func TestParseSqlServerDSN(t *testing.T) { + assert := assert.New(t) + + for _, tt := range []struct { + dsn string + expected map[string]string + }{ + { + dsn: "sqlserver://bob:secret@1.2.3.4:1433?database=mydb", + expected: map[string]string{ + "user": "bob", + "host": "1.2.3.4", + "port": "1433", + "dbname": "mydb", + }, + }, + { + dsn: "sqlserver://alice:secret@localhost/SQLExpress?database=mydb", + expected: map[string]string{ + "user": "alice", + "host": "localhost", + "dbname": "mydb", + "instanceName": "SQLExpress", + }, + }, + { + dsn: "server=1.2.3.4,1433;User Id=dog;Password=secret;Database=mydb;", + expected: map[string]string{ + "user": "dog", + "port": "1433", + "host": "1.2.3.4", + "dbname": "mydb", + }, + }, + { + dsn: "ADDRESS=1.2.3.4;UID=cat;PASSWORD=secret;INITIAL CATALOG=mydb;", + expected: map[string]string{ + "user": "cat", + "host": "1.2.3.4", + "dbname": "mydb", + }, + }, + } { + m, err := parseSQLServerDSN(tt.dsn) + assert.Equal(nil, err) + assert.Equal(tt.expected, m) + } +} diff --git a/contrib/database/sql/internal/sqlserver.go b/contrib/database/sql/internal/sqlserver.go new file mode 100644 index 0000000000..d248f089d3 --- /dev/null +++ b/contrib/database/sql/internal/sqlserver.go @@ -0,0 +1,101 @@ +// 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 internal + +import ( + "fmt" + "net" + nurl "net/url" + "strings" +) + +func parseSQLServerURL(url string) (map[string]string, error) { + u, err := nurl.Parse(url) + if err != nil { + return nil, err + } + + if u.Scheme != "sqlserver" { + return nil, fmt.Errorf("invalid connection protocol: %s", u.Scheme) + } + + kvs := map[string]string{} + escaper := strings.NewReplacer(` `, `\ `, `'`, `\'`, `\`, `\\`) + accrue := func(k, v string) { + if v != "" { + kvs[k] = escaper.Replace(v) + } + } + + if u.User != nil { + v := u.User.Username() + accrue("user", v) + } + + if host, port, err := net.SplitHostPort(u.Host); err != nil { + accrue("host", u.Host) + } else { + accrue("host", host) + accrue("port", port) + } + + if u.Path != "" { + accrue("instanceName", u.Path[1:]) + } + + q := u.Query() + for k := range q { + if k == "database" { + accrue("dbname", q.Get(k)) + } + } + + return kvs, nil +} + +var keySynonyms = map[string]string{ + "server": "host", + "data source": "host", + "address": "host", + "network address": "host", + "addr": "host", + "uid": "user", + "user id": "user", + "initial catalog": "dbname", + "database": "dbname", +} + +func parseSQLServerADO(dsn string) (map[string]string, error) { + kvs := map[string]string{} + fields := strings.Split(dsn, ";") + for _, f := range fields { + if len(f) == 0 { + continue + } + pts := strings.SplitN(f, "=", 2) + key := strings.TrimSpace(strings.ToLower(pts[0])) + if len(key) == 0 { + continue + } + val := "" + if len(pts) > 1 { + val = strings.TrimSpace(pts[1]) + } + if synonym, found := keySynonyms[key]; found { + key = synonym + } + if key == "host" { + val = strings.TrimPrefix(val, "tcp:") + hostParts := strings.Split(val, ",") + if len(hostParts) == 2 && len(hostParts[1]) > 0 { + val = hostParts[0] + kvs["port"] = hostParts[1] + } + } + kvs[key] = val + } + return kvs, nil +} diff --git a/contrib/database/sql/sql.go b/contrib/database/sql/sql.go index 755bcd9eff..2383f4a499 100644 --- a/contrib/database/sql/sql.go +++ b/contrib/database/sql/sql.go @@ -23,6 +23,7 @@ import ( "errors" "math" "reflect" + "time" "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql/internal" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" @@ -128,11 +129,7 @@ type tracedConnector struct { cfg *config } -func (t *tracedConnector) Connect(c context.Context) (driver.Conn, error) { - conn, err := t.connector.Connect(c) - if err != nil { - return nil, err - } +func (t *tracedConnector) Connect(ctx context.Context) (driver.Conn, error) { tp := &traceParams{ driverName: t.driverName, cfg: t.cfg, @@ -142,6 +139,12 @@ func (t *tracedConnector) Connect(c context.Context) (driver.Conn, error) { } else if t.cfg.dsn != "" { tp.meta, _ = internal.ParseDSN(t.driverName, t.cfg.dsn) } + start := time.Now() + conn, err := t.connector.Connect(ctx) + tp.tryTrace(ctx, queryTypeConnect, "", start, err) + if err != nil { + return nil, err + } return &tracedConn{conn, tp}, err } @@ -163,7 +166,7 @@ func (t dsnConnector) Driver() driver.Driver { return t.driver } -// OpenDB returns connection to a DB using a the traced version of the given driver. In order for OpenDB +// OpenDB returns connection to a DB using the traced version of the given driver. In order for OpenDB // to work, the driver must first be registered using Register. If this did not occur, OpenDB will panic. func OpenDB(c driver.Connector, opts ...Option) *sql.DB { name, ok := registeredDrivers.name(c.Driver()) @@ -192,7 +195,7 @@ func OpenDB(c driver.Connector, opts ...Option) *sql.DB { return sql.OpenDB(tc) } -// Open returns connection to a DB using a the traced version of the given driver. In order for Open +// Open returns connection to a DB using the traced version of the given driver. In order for Open // to work, the driver must first be registered using Register. If this did not occur, Open will // return an error. func Open(driverName, dataSourceName string, opts ...Option) (*sql.DB, error) { diff --git a/contrib/database/sql/sql_test.go b/contrib/database/sql/sql_test.go index d19f92f7bc..9022305927 100644 --- a/contrib/database/sql/sql_test.go +++ b/contrib/database/sql/sql_test.go @@ -6,15 +6,21 @@ package sql import ( + "context" + "database/sql/driver" + "errors" "fmt" "log" "math" "os" "testing" + "time" "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/sqltest" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" + mssql "github.com/denisenkom/go-mssqldb" "github.com/go-sql-driver/mysql" "github.com/lib/pq" "github.com/stretchr/testify/assert" @@ -33,6 +39,32 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } +func TestSqlServer(t *testing.T) { + Register("sqlserver", &mssql.Driver{}) + db, err := Open("sqlserver", "sqlserver://sa:myPassw0rd@127.0.0.1:1433?database=master") + if err != nil { + log.Fatal(err) + } + defer db.Close() + + testConfig := &sqltest.Config{ + DB: db, + DriverName: "sqlserver", + TableName: tableName, + ExpectName: "sqlserver.query", + ExpectTags: map[string]interface{}{ + ext.ServiceName: "sqlserver.db", + ext.SpanType: ext.SpanTypeSQL, + ext.TargetHost: "127.0.0.1", + ext.TargetPort: "1433", + ext.DBUser: "sa", + ext.DBName: "master", + ext.EventSampleRate: nil, + }, + } + sqltest.RunAll(t, testConfig) +} + func TestMySQL(t *testing.T) { Register("mysql", &mysql.MySQLDriver{}) db, err := Open("mysql", "test:test@tcp(127.0.0.1:3306)/test") @@ -190,3 +222,42 @@ func TestMySQLUint64(t *testing.T) { assert.NoError(rows.Err()) assert.NoError(rows.Close()) } + +// hangingConnector hangs on Connect until ctx is cancelled. +type hangingConnector struct{} + +func (h *hangingConnector) Connect(ctx context.Context) (driver.Conn, error) { + select { + case <-ctx.Done(): + return nil, errors.New("context cancelled") + } +} + +func (h *hangingConnector) Driver() driver.Driver { + panic("hangingConnector: Driver() not implemented") +} + +func TestConnectCancelledCtx(t *testing.T) { + mockTracer := mocktracer.Start() + defer mockTracer.Stop() + assert := assert.New(t) + tc := tracedConnector{ + connector: &hangingConnector{}, + driverName: "hangingConnector", + cfg: new(config), + } + ctx, cancelFunc := context.WithCancel(context.Background()) + + go func() { + tc.Connect(ctx) + }() + time.Sleep(time.Millisecond * 100) + cancelFunc() + time.Sleep(time.Millisecond * 100) + + spans := mockTracer.FinishedSpans() + assert.Len(spans, 1) + s := spans[0] + assert.Equal("hangingConnector.query", s.OperationName()) + assert.Equal("Connect", s.Tag("sql.query_type")) +} diff --git a/contrib/gin-gonic/gin/appsec.go b/contrib/gin-gonic/gin/appsec.go new file mode 100644 index 0000000000..ddbef4f7b5 --- /dev/null +++ b/contrib/gin-gonic/gin/appsec.go @@ -0,0 +1,42 @@ +// 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 2022 Datadog, Inc. + +package gin + +import ( + "net" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo/instrumentation/httpsec" + + "github.com/gin-gonic/gin" +) + +// useAppSec executes the AppSec logic related to the operation start and +// returns the function to be executed upon finishing the operation +func useAppSec(c *gin.Context, span tracer.Span) func() { + req := c.Request + httpsec.SetAppSecTags(span) + var params map[string]string + if l := len(c.Params); l > 0 { + params = make(map[string]string, l) + for _, p := range c.Params { + params[p.Key] = p.Value + } + } + args := httpsec.MakeHandlerOperationArgs(req, params) + ctx, op := httpsec.StartOperation(req.Context(), args) + c.Request = req.WithContext(ctx) + return func() { + events := op.Finish(httpsec.HandlerOperationRes{Status: c.Writer.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.Writer.Header()) + } + } +} diff --git a/contrib/gin-gonic/gin/gintrace.go b/contrib/gin-gonic/gin/gintrace.go index e1342965b1..a5b53d32e9 100644 --- a/contrib/gin-gonic/gin/gintrace.go +++ b/contrib/gin-gonic/gin/gintrace.go @@ -15,6 +15,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/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "github.com/gin-gonic/gin" @@ -23,6 +24,7 @@ import ( // Middleware returns middleware that will trace incoming requests. If service is empty then the // default service name will be used. func Middleware(service string, opts ...Option) gin.HandlerFunc { + appsecEnabled := appsec.Enabled() cfg := newConfig(service) for _, opt := range opts { opt(cfg) @@ -53,6 +55,12 @@ func Middleware(service string, opts ...Option) gin.HandlerFunc { // pass the span through the request context c.Request = c.Request.WithContext(ctx) + // Use AppSec if enabled by user + if appsecEnabled { + afterMiddleware := useAppSec(c, span) + defer afterMiddleware() + } + // serve the request to the next middleware c.Next() diff --git a/contrib/gin-gonic/gin/gintrace_test.go b/contrib/gin-gonic/gin/gintrace_test.go index 1009caa061..d25e4646ff 100644 --- a/contrib/gin-gonic/gin/gintrace_test.go +++ b/contrib/gin-gonic/gin/gintrace_test.go @@ -9,17 +9,22 @@ import ( "errors" "fmt" "html/template" + "io/ioutil" + "net/http" "net/http/httptest" "strings" "testing" + pappsec "gopkg.in/DataDog/dd-trace-go.v1/appsec" "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" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func init() { @@ -80,7 +85,81 @@ func TestTrace200(t *testing.T) { assert.Contains(span.Tag(ext.ResourceName), "GET /user/:id") assert.Equal("200", span.Tag(ext.HTTPCode)) assert.Equal("GET", span.Tag(ext.HTTPMethod)) - // TODO(x) would be much nicer to have "/user/:id" here + assert.Equal("/user/123", span.Tag(ext.HTTPURL)) +} + +func TestTraceDefaultResponse(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + + router := gin.New() + router.Use(Middleware("foobar")) + router.GET("/user/:id", func(c *gin.Context) { + _, ok := tracer.SpanFromContext(c.Request.Context()) + assert.True(ok) + }) + + r := httptest.NewRequest("GET", "/user/123", nil) + w := httptest.NewRecorder() + + // do and verify the request + router.ServeHTTP(w, r) + response := w.Result() + assert.Equal(response.StatusCode, 200) + + // verify traces look good + spans := mt.FinishedSpans() + assert.Len(spans, 1) + if len(spans) < 1 { + t.Fatalf("no spans") + } + span := spans[0] + assert.Equal("http.request", span.OperationName()) + assert.Equal(ext.SpanTypeWeb, span.Tag(ext.SpanType)) + assert.Equal("foobar", span.Tag(ext.ServiceName)) + assert.Contains(span.Tag(ext.ResourceName), "GET /user/:id") + assert.Equal("200", span.Tag(ext.HTTPCode)) + assert.Equal("GET", span.Tag(ext.HTTPMethod)) + assert.Equal("/user/123", span.Tag(ext.HTTPURL)) +} + +func TestTraceMultipleResponses(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + + router := gin.New() + router.Use(Middleware("foobar")) + router.GET("/user/:id", func(c *gin.Context) { + _, ok := tracer.SpanFromContext(c.Request.Context()) + assert.True(ok) + c.Status(142) + c.Writer.WriteString("test") + c.Status(133) + }) + + r := httptest.NewRequest("GET", "/user/123", nil) + w := httptest.NewRecorder() + + // do and verify the request + router.ServeHTTP(w, r) + response := w.Result() + assert.Equal(response.StatusCode, 142) + + // verify traces look good + spans := mt.FinishedSpans() + assert.Len(spans, 1) + if len(spans) < 1 { + t.Fatalf("no spans") + } + span := spans[0] + assert.Equal("http.request", span.OperationName()) + assert.Equal(ext.SpanTypeWeb, span.Tag(ext.SpanType)) + assert.Equal("foobar", span.Tag(ext.ServiceName)) + assert.Contains(span.Tag(ext.ResourceName), "GET /user/:id") + assert.Equal("133", span.Tag(ext.HTTPCode)) // Will be fixed by https://github.com/gin-gonic/gin/pull/2627 once merged and released + assert.Equal("GET", span.Tag(ext.HTTPMethod)) assert.Equal("/user/123", span.Tag(ext.HTTPURL)) } @@ -460,3 +539,125 @@ func TestServiceName(t *testing.T) { assert.Equal("my-service", span.Tag(ext.ServiceName)) }) } + +func TestAppSec(t *testing.T) { + appsec.Start() + defer appsec.Stop() + if !appsec.Enabled() { + t.Skip("appsec disabled") + } + + r := gin.New() + r.Use(Middleware("appsec")) + r.Any("/lfi/*allPaths", func(c *gin.Context) { + c.String(200, "Hello World!\n") + }) + r.Any("/path0.0/:myPathParam0/path0.1/:myPathParam1/path0.2/:myPathParam2/path0.3/*param3", func(c *gin.Context) { + c.String(200, "Hello Params!\n") + }) + r.Any("/body", func(c *gin.Context) { + pappsec.MonitorParsedHTTPBody(c.Request.Context(), "$globals") + c.String(200, "Hello Body!\n") + }) + + srv := httptest.NewServer(r) + defer srv.Close() + + t.Run("request-uri", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + // Send an LFI attack (according to appsec rule id crs-930-110) + req, err := http.NewRequest("POST", srv.URL+"/lfi/../../../secret.txt", nil) + if err != nil { + panic(err) + } + res, err := srv.Client().Do(req) + require.NoError(t, err) + // Check that the server behaved as intended + require.Equal(t, http.StatusOK, res.StatusCode) + b, err := ioutil.ReadAll(res.Body) + require.NoError(t, err) + require.Equal(t, "Hello World!\n", string(b)) + // The span should contain the security event + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + + // The first 301 redirection should contain the attack via the request uri + event := finished[0].Tag("_dd.appsec.json").(string) + require.NotNil(t, event) + require.True(t, strings.Contains(event, "server.request.uri.raw")) + require.True(t, strings.Contains(event, "crs-930-110")) + }) + + // Test a security scanner attack via path parameters + t.Run("path-params", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + // Send a security scanner attack (according to appsec rule id crs-913-120) + req, err := http.NewRequest("POST", srv.URL+"/path0.0/param0/path0.1/param1/path0.2/appscan_fingerprint/path0.3/param3", 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 Params!\n", string(b)) + require.Equal(t, http.StatusOK, res.StatusCode) + // The span should contain the security event + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + event := finished[0].Tag("_dd.appsec.json").(string) + require.NotNil(t, event) + require.True(t, strings.Contains(event, "crs-913-120")) + require.True(t, strings.Contains(event, "myPathParam2")) + require.True(t, strings.Contains(event, "server.request.path_params")) + }) + + t.Run("nfd-000-001", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + req, err := http.NewRequest("POST", srv.URL+"/etc/", nil) + if err != nil { + panic(err) + } + res, err := srv.Client().Do(req) + require.NoError(t, err) + require.Equal(t, 404, res.StatusCode) + + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + event := finished[0].Tag("_dd.appsec.json").(string) + require.NotNil(t, event) + require.True(t, strings.Contains(event, "server.response.status")) + require.True(t, strings.Contains(event, "nfd-000-001")) + + }) + + // Test a PHP injection attack via request parsed body + t.Run("SDK-body", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + req, err := http.NewRequest("POST", srv.URL+"/body", 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 Body!\n", string(b)) + + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + + event := finished[0].Tag("_dd.appsec.json") + require.NotNil(t, event) + require.True(t, strings.Contains(event.(string), "crs-933-130")) + }) +} diff --git a/contrib/go-chi/chi.v4/appsec.go b/contrib/go-chi/chi.v4/appsec.go new file mode 100644 index 0000000000..5ada4d6299 --- /dev/null +++ b/contrib/go-chi/chi.v4/appsec.go @@ -0,0 +1,32 @@ +// 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 chi + +import ( + "net/http" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo/instrumentation/httpsec" + + "github.com/go-chi/chi/v4" +) + +func withAppsec(next http.Handler, r *http.Request, span tracer.Span) http.Handler { + rctx := chi.RouteContext(r.Context()) + if rctx == nil { + return httpsec.WrapHandler(next, span, nil) + } + var pathParams map[string]string + keys := rctx.URLParams.Keys + values := rctx.URLParams.Values + if len(keys) > 0 && len(keys) == len(values) { + pathParams = make(map[string]string, len(keys)) + for i, key := range keys { + pathParams[key] = values[i] + } + } + return httpsec.WrapHandler(next, span, pathParams) +} diff --git a/contrib/go-chi/chi.v4/chi.go b/contrib/go-chi/chi.v4/chi.go new file mode 100644 index 0000000000..a99e55ee2a --- /dev/null +++ b/contrib/go-chi/chi.v4/chi.go @@ -0,0 +1,90 @@ +// 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 chi provides tracing functions for tracing the go-chi/chi/v4 package (https://github.com/go-chi/chi). +package chi // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/go-chi/chi.v4" + +import ( + "fmt" + "math" + "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/log" + + "github.com/go-chi/chi/v4" + "github.com/go-chi/chi/v4/middleware" +) + +// Middleware returns middleware that will trace incoming requests. +func Middleware(opts ...Option) func(next http.Handler) http.Handler { + cfg := new(config) + defaults(cfg) + for _, fn := range opts { + fn(cfg) + } + log.Debug("contrib/go-chi/chi.v4: 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), + tracer.Tag(ext.HTTPMethod, r.Method), + tracer.Tag(ext.HTTPURL, r.URL.Path), + tracer.Measured(), + } + if !math.IsNaN(cfg.analyticsRate) { + opts = append(opts, tracer.Tag(ext.EventSampleRate, cfg.analyticsRate)) + } + if spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(r.Header)); err == nil { + opts = append(opts, tracer.ChildOf(spanctx)) + } + opts = append(opts, cfg.spanOpts...) + span, ctx := tracer.StartSpanFromContext(r.Context(), "http.request", opts...) + defer span.Finish() + + next := next // avoid modifying the value of next in the outer closure scope + if appsec.Enabled() { + next = withAppsec(next, r, span) + // Note that the following response writer passed to the handler + // implements the `interface { Status() int }` expected by httpsec. + } + + ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor) + + // pass the span through the request context and serve the request to the next middleware + next.ServeHTTP(ww, r.WithContext(ctx)) + + // set the resource name as we get it only once the handler is executed + resourceName := chi.RouteContext(r.Context()).RoutePattern() + if resourceName == "" { + resourceName = "unknown" + } + resourceName = r.Method + " " + resourceName + span.SetTag(ext.ResourceName, resourceName) + + // set the status code + status := ww.Status() + // 0 status means one has not yet been sent in which case net/http library will write StatusOK + if ww.Status() == 0 { + status = http.StatusOK + } + span.SetTag(ext.HTTPCode, strconv.Itoa(status)) + + if cfg.isStatusError(status) { + // mark 5xx server error + span.SetTag(ext.Error, fmt.Errorf("%d: %s", status, http.StatusText(status))) + } + }) + } +} diff --git a/contrib/go-chi/chi.v4/chi_test.go b/contrib/go-chi/chi.v4/chi_test.go new file mode 100644 index 0000000000..f957e790a8 --- /dev/null +++ b/contrib/go-chi/chi.v4/chi_test.go @@ -0,0 +1,413 @@ +// 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 chi + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "strconv" + "strings" + "testing" + + pappsec "gopkg.in/DataDog/dd-trace-go.v1/appsec" + "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" + "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" + + "github.com/go-chi/chi/v4" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestChildSpan(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + + router := chi.NewRouter() + router.Use(Middleware(WithServiceName("foobar"))) + router.Get("/user/{id}", func(w http.ResponseWriter, r *http.Request) { + _, ok := tracer.SpanFromContext(r.Context()) + assert.True(ok) + }) + + r := httptest.NewRequest("GET", "/user/123", nil) + w := httptest.NewRecorder() + + router.ServeHTTP(w, r) +} + +func TestTrace200(t *testing.T) { + assertDoRequest := func(assert *assert.Assertions, mt mocktracer.Tracer, router *chi.Mux) { + r := httptest.NewRequest("GET", "/user/123", nil) + w := httptest.NewRecorder() + + // do and verify the request + router.ServeHTTP(w, r) + response := w.Result() + assert.Equal(response.StatusCode, 200) + + // verify traces look good + spans := mt.FinishedSpans() + assert.Len(spans, 1) + if len(spans) < 1 { + t.Fatalf("no spans") + } + span := spans[0] + assert.Equal("http.request", span.OperationName()) + assert.Equal(ext.SpanTypeWeb, span.Tag(ext.SpanType)) + assert.Equal("foobar", span.Tag(ext.ServiceName)) + assert.Equal("GET /user/{id}", span.Tag(ext.ResourceName)) + assert.Equal("200", span.Tag(ext.HTTPCode)) + assert.Equal("GET", span.Tag(ext.HTTPMethod)) + assert.Equal("/user/123", span.Tag(ext.HTTPURL)) + } + + t.Run("response written", func(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + + router := chi.NewRouter() + router.Use(Middleware(WithServiceName("foobar"))) + router.Get("/user/{id}", func(w http.ResponseWriter, r *http.Request) { + span, ok := tracer.SpanFromContext(r.Context()) + assert.True(ok) + assert.Equal(span.(mocktracer.Span).Tag(ext.ServiceName), "foobar") + id := chi.URLParam(r, "id") + _, err := w.Write([]byte(id)) + assert.NoError(err) + }) + assertDoRequest(assert, mt, router) + }) + + t.Run("no response written", func(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + + router := chi.NewRouter() + router.Use(Middleware(WithServiceName("foobar"))) + router.Get("/user/{id}", func(w http.ResponseWriter, r *http.Request) { + span, ok := tracer.SpanFromContext(r.Context()) + assert.True(ok) + assert.Equal(span.(mocktracer.Span).Tag(ext.ServiceName), "foobar") + }) + assertDoRequest(assert, mt, router) + }) +} + +func TestError(t *testing.T) { + assertSpan := func(assert *assert.Assertions, spans []mocktracer.Span, code int) { + assert.Len(spans, 1) + if len(spans) < 1 { + t.Fatalf("no spans") + } + span := spans[0] + assert.Equal("http.request", span.OperationName()) + assert.Equal("foobar", span.Tag(ext.ServiceName)) + + assert.Equal(strconv.Itoa(code), span.Tag(ext.HTTPCode)) + + wantErr := fmt.Sprintf("%d: %s", code, http.StatusText(code)) + assert.Equal(wantErr, span.Tag(ext.Error).(error).Error()) + } + + t.Run("default", func(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + + // setup + router := chi.NewRouter() + router.Use(Middleware(WithServiceName("foobar"))) + code := 500 + + // a handler with an error and make the requests + router.Get("/err", func(w http.ResponseWriter, r *http.Request) { + http.Error(w, fmt.Sprintf("%d!", code), code) + }) + r := httptest.NewRequest("GET", "/err", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, r) + response := w.Result() + assert.Equal(response.StatusCode, code) + + // verify the errors and status are correct + spans := mt.FinishedSpans() + assertSpan(assert, spans, code) + }) + + t.Run("custom", func(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + + // setup + router := chi.NewRouter() + router.Use(Middleware( + WithServiceName("foobar"), + WithStatusCheck(func(statusCode int) bool { + return statusCode >= 400 + }), + )) + code := 404 + // a handler with an error and make the requests + router.Get("/err", func(w http.ResponseWriter, r *http.Request) { + http.Error(w, fmt.Sprintf("%d!", code), code) + }) + r := httptest.NewRequest("GET", "/err", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, r) + response := w.Result() + assert.Equal(response.StatusCode, code) + + // verify the errors and status are correct + spans := mt.FinishedSpans() + assertSpan(assert, spans, code) + }) +} + +func TestGetSpanNotInstrumented(t *testing.T) { + assert := assert.New(t) + router := chi.NewRouter() + router.Get("/ping", func(w http.ResponseWriter, r *http.Request) { + // Assert we don't have a span on the context. + _, ok := tracer.SpanFromContext(r.Context()) + assert.False(ok) + w.Write([]byte("ok")) + }) + r := httptest.NewRequest("GET", "/ping", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, r) + response := w.Result() + assert.Equal(response.StatusCode, 200) +} + +func TestPropagation(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + + r := httptest.NewRequest("GET", "/user/123", nil) + w := httptest.NewRecorder() + + pspan := tracer.StartSpan("test") + tracer.Inject(pspan.Context(), tracer.HTTPHeadersCarrier(r.Header)) + + router := chi.NewRouter() + router.Use(Middleware(WithServiceName("foobar"))) + router.Get("/user/{id}", func(w http.ResponseWriter, r *http.Request) { + span, ok := tracer.SpanFromContext(r.Context()) + assert.True(ok) + assert.Equal(span.(mocktracer.Span).ParentID(), pspan.(mocktracer.Span).SpanID()) + }) + + router.ServeHTTP(w, r) +} + +func TestAnalyticsSettings(t *testing.T) { + assertRate := func(t *testing.T, mt mocktracer.Tracer, rate interface{}, opts ...Option) { + router := chi.NewRouter() + router.Use(Middleware(opts...)) + router.Get("/user/{id}", func(w http.ResponseWriter, r *http.Request) { + _, ok := tracer.SpanFromContext(r.Context()) + assert.True(t, ok) + }) + + r := httptest.NewRequest("GET", "/user/123", nil) + w := httptest.NewRecorder() + + router.ServeHTTP(w, r) + 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) { + 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)) + }) +} + +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) + } +} + +func TestAppSec(t *testing.T) { + appsec.Start() + defer appsec.Stop() + + if !appsec.Enabled() { + t.Skip("appsec disabled") + } + + // Start and trace an HTTP server with some testing routes + router := chi.NewRouter().With(Middleware()) + router.HandleFunc("/path0.0/{myPathParam0}/path0.1/{myPathParam1}/path0.2/{myPathParam2}/path0.3/*", func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte("Hello World!\n")) + require.NoError(t, err) + }) + router.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte("Hello World!\n")) + require.NoError(t, err) + }) + router.HandleFunc("/body", func(w http.ResponseWriter, r *http.Request) { + pappsec.MonitorParsedHTTPBody(r.Context(), "$globals") + _, err := w.Write([]byte("Hello Body!\n")) + require.NoError(t, err) + }) + + srv := httptest.NewServer(router) + defer srv.Close() + + // Test an LFI attack via path parameters + t.Run("request-uri", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + // Send an LFI attack (according to appsec rule id crs-930-110) + 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 server behaved as intended + require.Equal(t, http.StatusOK, res.StatusCode) + b, err := ioutil.ReadAll(res.Body) + require.NoError(t, err) + require.Equal(t, "Hello World!\n", string(b)) + // The span should contain the security event + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + + // The first 301 redirection should contain the attack via the request uri + event := finished[0].Tag("_dd.appsec.json").(string) + require.NotNil(t, event) + require.True(t, strings.Contains(event, "server.request.uri.raw")) + require.True(t, strings.Contains(event, "crs-930-110")) + }) + + // Test a security scanner attack via path parameters + t.Run("path-params", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + // Send a security scanner attack (according to appsec rule id crs-913-120) + req, err := http.NewRequest("POST", srv.URL+"/path0.0/param0/path0.1/param1/path0.2/appscan_fingerprint/path0.3/param3", 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)) + require.Equal(t, http.StatusOK, res.StatusCode) + // The span should contain the security event + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + event := finished[0].Tag("_dd.appsec.json").(string) + require.NotNil(t, event) + require.True(t, strings.Contains(event, "crs-913-120")) + require.True(t, strings.Contains(event, "myPathParam2")) + require.True(t, strings.Contains(event, "server.request.path_params")) + }) + + // Test a PHP injection attack via request parsed body + t.Run("SDK-body", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + req, err := http.NewRequest("POST", srv.URL+"/body", 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 Body!\n", string(b)) + + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + + event := finished[0].Tag("_dd.appsec.json") + require.NotNil(t, event) + require.True(t, strings.Contains(event.(string), "crs-933-130")) + }) +} diff --git a/contrib/go-chi/chi.v4/example_test.go b/contrib/go-chi/chi.v4/example_test.go new file mode 100644 index 0000000000..2a5002a412 --- /dev/null +++ b/contrib/go-chi/chi.v4/example_test.go @@ -0,0 +1,55 @@ +// 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 chi_test + +import ( + "net/http" + + "github.com/go-chi/chi/v4" + + chitrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/go-chi/chi.v4" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" +) + +func handler(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello World!\n")) +} + +func Example() { + // Start the tracer + tracer.Start() + defer tracer.Stop() + + // Create a chi Router + router := chi.NewRouter() + + // Use the tracer middleware with the default service name "chi.router". + router.Use(chitrace.Middleware()) + + // Set up some endpoints. + router.Get("/", handler) + + // And start gathering request traces + http.ListenAndServe(":8080", router) +} + +func Example_withServiceName() { + // Start the tracer + tracer.Start() + defer tracer.Stop() + + // Create a chi Router + router := chi.NewRouter() + + // Use the tracer middleware with your desired service name. + router.Use(chitrace.Middleware(chitrace.WithServiceName("chi-server"))) + + // Set up some endpoints. + router.Get("/", handler) + + // And start gathering request traces + http.ListenAndServe(":8080", router) +} diff --git a/contrib/go-chi/chi.v4/option.go b/contrib/go-chi/chi.v4/option.go new file mode 100644 index 0000000000..6c19dd3cc7 --- /dev/null +++ b/contrib/go-chi/chi.v4/option.go @@ -0,0 +1,98 @@ +// 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 chi + +import ( + "math" + "net/http" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" + "gopkg.in/DataDog/dd-trace-go.v1/internal" + "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" +) + +type config struct { + serviceName string + 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. +type Option func(*config) + +func defaults(cfg *config) { + cfg.serviceName = "chi.router" + if svc := globalconfig.ServiceName(); svc != "" { + cfg.serviceName = svc + } + if internal.BoolEnv("DD_TRACE_CHI_ANALYTICS_ENABLED", false) { + cfg.analyticsRate = 1.0 + } else { + cfg.analyticsRate = globalconfig.AnalyticsRate() + } + cfg.isStatusError = isServerError + cfg.ignoreRequest = func(_ *http.Request) bool { return false } +} + +// WithServiceName sets the given service name for the router. +func WithServiceName(name string) Option { + return func(cfg *config) { + cfg.serviceName = name + } +} + +// WithSpanOptions applies the given set of options to the spans started +// by the router. +func WithSpanOptions(opts ...ddtrace.StartSpanOption) Option { + return func(cfg *config) { + cfg.spanOpts = opts + } +} + +// WithAnalytics enables Trace Analytics for all started spans. +func WithAnalytics(on bool) Option { + return func(cfg *config) { + 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) Option { + return func(cfg *config) { + if rate >= 0.0 && rate <= 1.0 { + cfg.analyticsRate = rate + } else { + cfg.analyticsRate = math.NaN() + } + } +} + +// WithStatusCheck specifies a function fn which reports whether the passed +// statusCode should be considered an error. +func WithStatusCheck(fn func(statusCode int) bool) Option { + return func(cfg *config) { + cfg.isStatusError = fn + } +} + +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.v5/appsec.go b/contrib/go-chi/chi.v5/appsec.go new file mode 100644 index 0000000000..4a4166d096 --- /dev/null +++ b/contrib/go-chi/chi.v5/appsec.go @@ -0,0 +1,32 @@ +// 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 chi + +import ( + "net/http" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo/instrumentation/httpsec" + + "github.com/go-chi/chi/v5" +) + +func withAppsec(next http.Handler, r *http.Request, span tracer.Span) http.Handler { + rctx := chi.RouteContext(r.Context()) + if rctx == nil { + return httpsec.WrapHandler(next, span, nil) + } + var pathParams map[string]string + keys := rctx.URLParams.Keys + values := rctx.URLParams.Values + if len(keys) > 0 && len(keys) == len(values) { + pathParams = make(map[string]string, len(keys)) + for i, key := range keys { + pathParams[key] = values[i] + } + } + return httpsec.WrapHandler(next, span, pathParams) +} diff --git a/contrib/go-chi/chi.v5/chi.go b/contrib/go-chi/chi.v5/chi.go index 9a5cae94d2..9ca29e24f9 100644 --- a/contrib/go-chi/chi.v5/chi.go +++ b/contrib/go-chi/chi.v5/chi.go @@ -15,6 +15,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/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "github.com/go-chi/chi/v5" @@ -31,6 +32,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), @@ -48,6 +53,13 @@ func Middleware(opts ...Option) func(next http.Handler) http.Handler { span, ctx := tracer.StartSpanFromContext(r.Context(), "http.request", opts...) defer span.Finish() + next := next // avoid modifying the value of next in the outer closure scope + if appsec.Enabled() { + next = withAppsec(next, r, span) + // Note that the following response writer passed to the handler + // implements the `interface { Status() int }` expected by httpsec. + } + ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor) // pass the span through the request context and serve the request to the next middleware diff --git a/contrib/go-chi/chi.v5/chi_test.go b/contrib/go-chi/chi.v5/chi_test.go index 1c90f06066..0cda43fc37 100644 --- a/contrib/go-chi/chi.v5/chi_test.go +++ b/contrib/go-chi/chi.v5/chi_test.go @@ -7,18 +7,23 @@ package chi import ( "fmt" + "io/ioutil" "net/http" "net/http/httptest" "strconv" + "strings" "testing" + pappsec "gopkg.in/DataDog/dd-trace-go.v1/appsec" "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" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "github.com/go-chi/chi/v5" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestChildSpan(t *testing.T) { @@ -270,3 +275,139 @@ 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) + } +} + +func TestAppSec(t *testing.T) { + appsec.Start() + defer appsec.Stop() + + if !appsec.Enabled() { + t.Skip("appsec disabled") + } + + // Start and trace an HTTP server with some testing routes + router := chi.NewRouter().With(Middleware()) + router.HandleFunc("/path0.0/{myPathParam0}/path0.1/{myPathParam1}/path0.2/{myPathParam2}/path0.3/*", func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte("Hello World!\n")) + require.NoError(t, err) + }) + router.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte("Hello World!\n")) + require.NoError(t, err) + }) + router.HandleFunc("/body", func(w http.ResponseWriter, r *http.Request) { + pappsec.MonitorParsedHTTPBody(r.Context(), "$globals") + _, err := w.Write([]byte("Hello Body!\n")) + require.NoError(t, err) + }) + + srv := httptest.NewServer(router) + defer srv.Close() + + // Test an LFI attack via path parameters + t.Run("request-uri", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + // Send an LFI attack (according to appsec rule id crs-930-110) + 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 server behaved as intended + require.Equal(t, http.StatusOK, res.StatusCode) + b, err := ioutil.ReadAll(res.Body) + require.NoError(t, err) + require.Equal(t, "Hello World!\n", string(b)) + // The span should contain the security event + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + + // The first 301 redirection should contain the attack via the request uri + event := finished[0].Tag("_dd.appsec.json").(string) + require.NotNil(t, event) + require.True(t, strings.Contains(event, "server.request.uri.raw")) + require.True(t, strings.Contains(event, "crs-930-110")) + }) + + // Test a security scanner attack via path parameters + t.Run("path-params", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + // Send a security scanner attack (according to appsec rule id crs-913-120) + req, err := http.NewRequest("POST", srv.URL+"/path0.0/param0/path0.1/param1/path0.2/appscan_fingerprint/path0.3/param3", 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)) + require.Equal(t, http.StatusOK, res.StatusCode) + // The span should contain the security event + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + event := finished[0].Tag("_dd.appsec.json").(string) + require.NotNil(t, event) + require.True(t, strings.Contains(event, "crs-913-120")) + require.True(t, strings.Contains(event, "myPathParam2")) + require.True(t, strings.Contains(event, "server.request.path_params")) + }) + + // Test a PHP injection attack via request parsed body + t.Run("SDK-body", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + req, err := http.NewRequest("POST", srv.URL+"/body", 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 Body!\n", string(b)) + + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + + event := finished[0].Tag("_dd.appsec.json") + require.NotNil(t, event) + require.True(t, strings.Contains(event.(string), "crs-933-130")) + }) +} 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/appsec.go b/contrib/go-chi/chi/appsec.go new file mode 100644 index 0000000000..0ff5149ba9 --- /dev/null +++ b/contrib/go-chi/chi/appsec.go @@ -0,0 +1,32 @@ +// 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 chi + +import ( + "net/http" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo/instrumentation/httpsec" + + "github.com/go-chi/chi" +) + +func withAppsec(next http.Handler, r *http.Request, span tracer.Span) http.Handler { + rctx := chi.RouteContext(r.Context()) + if rctx == nil { + return httpsec.WrapHandler(next, span, nil) + } + var pathParams map[string]string + keys := rctx.URLParams.Keys + values := rctx.URLParams.Values + if len(keys) > 0 && len(keys) == len(values) { + pathParams = make(map[string]string, len(keys)) + for i, key := range keys { + pathParams[key] = values[i] + } + } + return httpsec.WrapHandler(next, span, pathParams) +} diff --git a/contrib/go-chi/chi/chi.go b/contrib/go-chi/chi/chi.go index e4a90a961e..08c37dbabc 100644 --- a/contrib/go-chi/chi/chi.go +++ b/contrib/go-chi/chi/chi.go @@ -15,6 +15,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/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "github.com/go-chi/chi" @@ -31,6 +32,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), @@ -48,6 +53,13 @@ func Middleware(opts ...Option) func(next http.Handler) http.Handler { span, ctx := tracer.StartSpanFromContext(r.Context(), "http.request", opts...) defer span.Finish() + next := next // avoid modifying the value of next in the outer closure scope + if appsec.Enabled() { + next = withAppsec(next, r, span) + // Note that the following response writer passed to the handler + // implements the `interface { Status() int }` expected by httpsec. + } + ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor) // pass the span through the request context and serve the request to the next middleware diff --git a/contrib/go-chi/chi/chi_test.go b/contrib/go-chi/chi/chi_test.go index c3972745e3..46a74f1633 100644 --- a/contrib/go-chi/chi/chi_test.go +++ b/contrib/go-chi/chi/chi_test.go @@ -7,18 +7,23 @@ package chi import ( "fmt" + "io/ioutil" "net/http" "net/http/httptest" "strconv" + "strings" "testing" + pappsec "gopkg.in/DataDog/dd-trace-go.v1/appsec" "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" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "github.com/go-chi/chi" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestChildSpan(t *testing.T) { @@ -270,3 +275,139 @@ 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) + } +} + +func TestAppSec(t *testing.T) { + appsec.Start() + defer appsec.Stop() + + if !appsec.Enabled() { + t.Skip("appsec disabled") + } + + // Start and trace an HTTP server with some testing routes + router := chi.NewRouter().With(Middleware()) + router.HandleFunc("/path0.0/{myPathParam0}/path0.1/{myPathParam1}/path0.2/{myPathParam2}/path0.3/*", func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte("Hello World!\n")) + require.NoError(t, err) + }) + router.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte("Hello World!\n")) + require.NoError(t, err) + }) + router.HandleFunc("/body", func(w http.ResponseWriter, r *http.Request) { + pappsec.MonitorParsedHTTPBody(r.Context(), "$globals") + _, err := w.Write([]byte("Hello Body!\n")) + require.NoError(t, err) + }) + + srv := httptest.NewServer(router) + defer srv.Close() + + // Test an LFI attack via path parameters + t.Run("request-uri", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + // Send an LFI attack (according to appsec rule id crs-930-110) + 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 server behaved as intended + require.Equal(t, http.StatusOK, res.StatusCode) + b, err := ioutil.ReadAll(res.Body) + require.NoError(t, err) + require.Equal(t, "Hello World!\n", string(b)) + // The span should contain the security event + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + + // The first 301 redirection should contain the attack via the request uri + event := finished[0].Tag("_dd.appsec.json").(string) + require.NotNil(t, event) + require.True(t, strings.Contains(event, "server.request.uri.raw")) + require.True(t, strings.Contains(event, "crs-930-110")) + }) + + // Test a security scanner attack via path parameters + t.Run("path-params", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + // Send a security scanner attack (according to appsec rule id crs-913-120) + req, err := http.NewRequest("POST", srv.URL+"/path0.0/param0/path0.1/param1/path0.2/appscan_fingerprint/path0.3/param3", 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)) + require.Equal(t, http.StatusOK, res.StatusCode) + // The span should contain the security event + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + event := finished[0].Tag("_dd.appsec.json").(string) + require.NotNil(t, event) + require.True(t, strings.Contains(event, "crs-913-120")) + require.True(t, strings.Contains(event, "myPathParam2")) + require.True(t, strings.Contains(event, "server.request.path_params")) + }) + + // Test a PHP injection attack via request parsed body + t.Run("SDK-body", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + req, err := http.NewRequest("POST", srv.URL+"/body", 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 Body!\n", string(b)) + + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + + event := finished[0].Tag("_dd.appsec.json") + require.NotNil(t, event) + require.True(t, strings.Contains(event.(string), "crs-933-130")) + }) +} 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/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..e5ec845867 --- /dev/null +++ b/contrib/google.golang.org/grpc/appsec.go @@ -0,0 +1,80 @@ +// 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) { + md, _ := metadata.FromIncomingContext(ctx) + op := grpcsec.StartHandlerOperation(grpcsec.HandlerOperationArgs{Metadata: md}, 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 { + md, _ := metadata.FromIncomingContext(stream.Context()) + op := grpcsec.StartHandlerOperation(grpcsec.HandlerOperationArgs{Metadata: md}, 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..06481c9e29 --- /dev/null +++ b/contrib/google.golang.org/grpc/appsec_test.go @@ -0,0 +1,96 @@ +// 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" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" + + "github.com/stretchr/testify/require" + "google.golang.org/grpc/metadata" +) + +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 in the payload along with the canary value in the RPC metadata + ctx := metadata.NewOutgoingContext(context.Background(), metadata.Pairs("dd-canary", "dd-test-scanner-log")) + res, err := client.Ping(ctx, &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 attack attempts + event, _ := finished[0].Tag("_dd.appsec.json").(string) + require.NotNil(t, event) + require.True(t, strings.Contains(event, "crs-941-100")) // XSS attack attempt + require.True(t, strings.Contains(event, "ua0-600-55x")) // canary rule attack attempt + }) + + t.Run("stream", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + // Send a XSS attack in the payload along with the canary value in the RPC metadata + ctx := metadata.NewOutgoingContext(context.Background(), metadata.Pairs("dd-canary", "dd-test-scanner-log")) + stream, err := client.StreamPing(ctx) + 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 have the attack attempts + event, _ := finished[5].Tag("_dd.appsec.json").(string) + require.NotNil(t, event) + require.True(t, strings.Contains(event, "crs-941-100")) // XSS attack attempt + require.True(t, strings.Contains(event, "crs-942-100")) // SQL-injection attack attempt + require.True(t, strings.Contains(event, "ua0-600-55x")) // canary rule attack attempt + }) +} diff --git a/contrib/google.golang.org/grpc/grpc.go b/contrib/google.golang.org/grpc/grpc.go index a668951527..c462326291 100644 --- a/contrib/google.golang.org/grpc/grpc.go +++ b/contrib/google.golang.org/grpc/grpc.go @@ -48,11 +48,15 @@ func finishWithError(span ddtrace.Span, err error, cfg *config) { err = nil } span.SetTag(tagCode, errcode.String()) - finishOptions := []tracer.FinishOption{ - tracer.WithError(err), - } - if cfg.noDebugStack { - finishOptions = append(finishOptions, tracer.NoDebugStack()) + + // only allocate finishOptions if needed, and allocate the exact right size + var finishOptions []tracer.FinishOption + if err != nil { + if cfg.noDebugStack { + finishOptions = []tracer.FinishOption{tracer.WithError(err), tracer.NoDebugStack()} + } else { + finishOptions = []tracer.FinishOption{tracer.WithError(err)} + } } span.Finish(finishOptions...) } diff --git a/contrib/google.golang.org/grpc/grpc_test.go b/contrib/google.golang.org/grpc/grpc_test.go index 98bb15a59c..6d89f3eb31 100644 --- a/contrib/google.golang.org/grpc/grpc_test.go +++ b/contrib/google.golang.org/grpc/grpc_test.go @@ -19,6 +19,7 @@ import ( "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/globalconfig" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "github.com/stretchr/testify/assert" context "golang.org/x/net/context" @@ -269,7 +270,7 @@ func TestSpanTree(t *testing.T) { assert.Equal(t, operationName, span.OperationName()) assert.Equal(t, "grpc", span.Tag(ext.ServiceName)) assert.Equal(t, span.Tag(ext.ResourceName), resourceName) - assert.True(t, span.FinishTime().Sub(span.StartTime()) > 0) + assert.True(t, span.FinishTime().Sub(span.StartTime()) >= 0) if parent == nil { return @@ -405,7 +406,7 @@ func TestPass(t *testing.T) { assert.Equal(s.Tag(ext.SpanType), ext.AppTypeRPC) assert.NotContains(s.Tags(), tagRequest) assert.NotContains(s.Tags(), tagMetadataPrefix+"test-key") - assert.True(s.FinishTime().Sub(s.StartTime()) > 0) + assert.True(s.FinishTime().Sub(s.StartTime()) >= 0) } func TestPreservesMetadata(t *testing.T) { @@ -795,3 +796,88 @@ func TestIgnoredMetadata(t *testing.T) { mt.Reset() } } + +func BenchmarkUnaryServerInterceptor(b *testing.B) { + // need to use the real tracer to get representative measurments + tracer.Start(tracer.WithLogger(log.DiscardLogger{}), + tracer.WithEnv("test"), + tracer.WithServiceVersion("0.1.2")) + defer tracer.Stop() + + doNothingOKGRPCHandler := func(ctx context.Context, req interface{}) (interface{}, error) { + return nil, nil + } + + unknownErr := status.Error(codes.Unknown, "some unknown error") + doNothingErrorGRPCHandler := func(ctx context.Context, req interface{}) (interface{}, error) { + return nil, unknownErr + } + + // Add gRPC metadata to ctx to get resonably accurate performance numbers. From a production + // application, there can be quite a few key/value pairs. A number of these are added by + // gRPC itself, and others by Datadog tracing + md := metadata.Pairs( + ":authority", "example-service-name.example.com:12345", + "content-type", "application/grpc", + "user-agent", "grpc-go/1.32.0", + "x-datadog-sampling-priority", "1", + ) + mdWithParent := metadata.Join(md, metadata.Pairs( + "x-datadog-trace-id", "9219028207762307503", + "x-datadog-parent-id", "7525005002014855056", + )) + ctx := context.Background() + ctxWithMetadataNoParent := metadata.NewIncomingContext(ctx, md) + ctxWithMetadataWithParent := metadata.NewIncomingContext(ctx, mdWithParent) + + methodInfo := &grpc.UnaryServerInfo{FullMethod: "/package.MyService/ExampleMethod"} + interceptor := UnaryServerInterceptor() + b.Run("ok_no_metadata", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + interceptor(ctx, "ignoredRequestValue", methodInfo, doNothingOKGRPCHandler) + } + }) + + b.Run("ok_with_metadata_no_parent", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + interceptor(ctxWithMetadataNoParent, "ignoredRequestValue", methodInfo, doNothingOKGRPCHandler) + } + }) + + b.Run("ok_with_metadata_with_parent", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + interceptor(ctxWithMetadataWithParent, "ignoredRequestValue", methodInfo, doNothingOKGRPCHandler) + } + }) + + interceptorWithRate := UnaryServerInterceptor(WithAnalyticsRate(0.5)) + b.Run("ok_no_metadata_with_analytics_rate", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + interceptorWithRate(ctx, "ignoredRequestValue", methodInfo, doNothingOKGRPCHandler) + } + }) + + b.Run("error_no_metadata", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + interceptor(ctx, "ignoredRequestValue", methodInfo, doNothingErrorGRPCHandler) + } + }) + interceptorNoStack := UnaryServerInterceptor(NoDebugStack()) + b.Run("error_no_metadata_no_stack", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + interceptorNoStack(ctx, "ignoredRequestValue", methodInfo, doNothingErrorGRPCHandler) + } + }) +} diff --git a/contrib/google.golang.org/grpc/server.go b/contrib/google.golang.org/grpc/server.go index 11320294bc..404368337e 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" ) @@ -75,9 +76,6 @@ func StreamServerInterceptor(opts ...Option) grpc.StreamServerInterceptor { for _, fn := range opts { fn(cfg) } - if cfg.serviceName == "" { - cfg.serviceName = "grpc.server" - } log.Debug("contrib/google.golang.org/grpc: Configuring StreamServerInterceptor: %#v", cfg) return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) (err error) { ctx := ss.Context() @@ -101,18 +99,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 +153,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_client_test.go b/contrib/google.golang.org/grpc/stats_client_test.go index 0e3d86e051..34d92da3d8 100644 --- a/contrib/google.golang.org/grpc/stats_client_test.go +++ b/contrib/google.golang.org/grpc/stats_client_test.go @@ -46,7 +46,7 @@ func TestClientStatsHandler(t *testing.T) { span := spans[0] assert.Equal(rootSpan.Context().SpanID(), span.ParentID()) assert.NotZero(span.StartTime()) - assert.True(span.FinishTime().After(span.StartTime())) + assert.True(span.FinishTime().Sub(span.StartTime()) >= 0) assert.Equal("grpc.client", span.OperationName()) tags := span.Tags() assert.Equal(ext.AppTypeRPC, tags["span.type"]) diff --git a/contrib/google.golang.org/grpc/stats_server_test.go b/contrib/google.golang.org/grpc/stats_server_test.go index 389fd1b312..16f7fae993 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" @@ -42,7 +42,7 @@ func TestServerStatsHandler(t *testing.T) { span := spans[0] assert.Zero(span.ParentID()) assert.NotZero(span.StartTime()) - assert.True(span.FinishTime().After(span.StartTime())) + assert.True(span.FinishTime().Sub(span.StartTime()) >= 0) assert.Equal("grpc.server", span.OperationName()) tags := span.Tags() assert.Equal(ext.AppTypeRPC, tags["span.type"]) diff --git a/contrib/gopkg.in/jinzhu/gorm.v1/gorm_test.go b/contrib/gopkg.in/jinzhu/gorm.v1/gorm_test.go index c6f1b67214..168013b74f 100644 --- a/contrib/gopkg.in/jinzhu/gorm.v1/gorm_test.go +++ b/contrib/gopkg.in/jinzhu/gorm.v1/gorm_test.go @@ -19,6 +19,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" + mssql "github.com/denisenkom/go-mssqldb" "github.com/go-sql-driver/mysql" "github.com/lib/pq" "github.com/stretchr/testify/assert" @@ -88,6 +89,32 @@ func TestPostgres(t *testing.T) { sqltest.RunAll(t, testConfig) } +func TestSqlServer(t *testing.T) { + sqltrace.Register("sqlserver", &mssql.Driver{}) + db, err := Open("sqlserver", "sqlserver://sa:myPassw0rd@127.0.0.1:1433?database=master") + if err != nil { + log.Fatal(err) + } + defer db.Close() + + testConfig := &sqltest.Config{ + DB: db.DB(), + DriverName: "sqlserver", + TableName: tableName, + ExpectName: "sqlserver.query", + ExpectTags: map[string]interface{}{ + ext.ServiceName: "sqlserver.db", + ext.SpanType: ext.SpanTypeSQL, + ext.TargetHost: "127.0.0.1", + ext.TargetPort: "1433", + ext.DBUser: "sa", + ext.DBName: "master", + ext.EventSampleRate: nil, + }, + } + sqltest.RunAll(t, testConfig) +} + type Product struct { gorm.Model Code string diff --git a/contrib/gorilla/mux/mux.go b/contrib/gorilla/mux/mux.go index 86181225ba..2207230f10 100644 --- a/contrib/gorilla/mux/mux.go +++ b/contrib/gorilla/mux/mux.go @@ -7,13 +7,11 @@ package mux // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/gorilla/mux" import ( - "math" "net/http" "strings" - "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httputil" + httptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http" "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/log" @@ -76,20 +74,7 @@ func (r *Router) UseEncodedPath() *Router { // NewRouter returns a new router instance traced with the global tracer. func NewRouter(opts ...RouterOption) *Router { - cfg := new(routerConfig) - defaults(cfg) - for _, fn := range opts { - fn(cfg) - } - if !math.IsNaN(cfg.analyticsRate) { - cfg.spanOpts = append(cfg.spanOpts, tracer.Tag(ext.EventSampleRate, cfg.analyticsRate)) - } - cfg.spanOpts = append(cfg.spanOpts, tracer.Measured()) - log.Debug("contrib/gorilla/mux: Configuring Router: %#v", cfg) - return &Router{ - Router: mux.NewRouter(), - config: cfg, - } + return WrapRouter(mux.NewRouter(), opts...) } // ServeHTTP dispatches the request to the handler @@ -116,17 +101,27 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { spanopts = append(spanopts, headerTagsFromRequest(req)) } resource := r.config.resourceNamer(r, req) - httputil.TraceAndServe(r.Router, &httputil.TraceConfig{ - ResponseWriter: w, - Request: req, - Service: r.config.serviceName, - Resource: resource, - FinishOpts: r.config.finishOpts, - SpanOpts: spanopts, - QueryParams: r.config.queryParams, + httptrace.TraceAndServe(r.Router, w, req, &httptrace.ServeConfig{ + Service: r.config.serviceName, + Resource: resource, + FinishOpts: r.config.finishOpts, + SpanOpts: spanopts, + QueryParams: r.config.queryParams, + RouteParams: match.Vars, }) } +// WrapRouter returns the given router wrapped with the tracing of the HTTP +// requests and responses served by the router. +func WrapRouter(router *mux.Router, opts ...RouterOption) *Router { + cfg := newConfig(opts) + log.Debug("contrib/gorilla/mux: Configuring Router: %#v", cfg) + return &Router{ + Router: router, + config: cfg, + } +} + // defaultResourceNamer attempts to quantize the resource for an HTTP request by // retrieving the path template associated with the route from the request. func defaultResourceNamer(router *Router, req *http.Request) string { diff --git a/contrib/gorilla/mux/mux_test.go b/contrib/gorilla/mux/mux_test.go index bfc7a03ba8..04ae4f1e66 100644 --- a/contrib/gorilla/mux/mux_test.go +++ b/contrib/gorilla/mux/mux_test.go @@ -7,17 +7,22 @@ package mux import ( "fmt" + "io/ioutil" "net/http" "net/http/httptest" "strconv" + "strings" "testing" + pappsec "gopkg.in/DataDog/dd-trace-go.v1/appsec" "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" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestHttpTracer(t *testing.T) { @@ -303,3 +308,129 @@ func okHandler() http.Handler { w.Write([]byte("200!\n")) }) } + +func TestAppSec(t *testing.T) { + appsec.Start() + defer appsec.Stop() + + if !appsec.Enabled() { + t.Skip("appsec disabled") + } + + // Start and trace an HTTP server with some testing routes + router := NewRouter() + router.HandleFunc("/path0.0/{myPathParam0}/path0.1/{myPathParam1}/path0.2/{myPathParam2}/path0.3/{myPathParam3}", func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte("Hello World!\n")) + require.NoError(t, err) + }) + router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte("Hello World!\n")) + require.NoError(t, err) + }) + router.HandleFunc("/body", func(w http.ResponseWriter, r *http.Request) { + pappsec.MonitorParsedHTTPBody(r.Context(), "$globals") + _, err := w.Write([]byte("Hello Body!\n")) + require.NoError(t, err) + }) + + srv := httptest.NewServer(router) + defer srv.Close() + + // Test an LFI attack via path parameters + t.Run("request-uri", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + // Send an LFI attack (according to appsec rule id crs-930-110) + 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 server behaved as intended (404 after the 301) + require.Equal(t, http.StatusNotFound, res.StatusCode) + // The span should contain the security event + finished := mt.FinishedSpans() + require.Len(t, finished, 2) // 301 + 404 + + // The first 301 redirection should contain the attack via the request uri + event := finished[0].Tag("_dd.appsec.json").(string) + require.NotNil(t, event) + require.True(t, strings.Contains(event, "server.request.uri.raw")) + require.True(t, strings.Contains(event, "crs-930-110")) + // The second request should contain the event via the referrer header + event = finished[1].Tag("_dd.appsec.json").(string) + require.NotNil(t, event) + require.True(t, strings.Contains(event, "server.request.headers.no_cookies")) + require.True(t, strings.Contains(event, "crs-930-110")) + }) + + // Test a security scanner attack via path parameters + t.Run("path-params", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + // Send a security scanner attack (according to appsec rule id crs-913-120) + req, err := http.NewRequest("POST", srv.URL+"/path0.0/param0/path0.1/param1/path0.2/appscan_fingerprint/path0.3/param3", 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)) + require.Equal(t, http.StatusOK, res.StatusCode) + // The span should contain the security event + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + event := finished[0].Tag("_dd.appsec.json").(string) + require.NotNil(t, event) + require.True(t, strings.Contains(event, "crs-913-120")) + require.True(t, strings.Contains(event, "myPathParam2")) + require.True(t, strings.Contains(event, "server.request.path_params")) + }) + + t.Run("response-status", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + req, err := http.NewRequest("POST", srv.URL+"/etc/", nil) + if err != nil { + panic(err) + } + res, err := srv.Client().Do(req) + require.NoError(t, err) + require.Equal(t, 404, res.StatusCode) + + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + event := finished[0].Tag("_dd.appsec.json").(string) + require.NotNil(t, event) + require.True(t, strings.Contains(event, "server.response.status")) + require.True(t, strings.Contains(event, "nfd-000-001")) + }) + + // Test a PHP injection attack via request parsed body + t.Run("SDK-body", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + req, err := http.NewRequest("POST", srv.URL+"/body", 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 Body!\n", string(b)) + + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + event := finished[0].Tag("_dd.appsec.json") + require.NotNil(t, event) + require.True(t, strings.Contains(event.(string), "crs-933-130")) + }) +} diff --git a/contrib/gorilla/mux/option.go b/contrib/gorilla/mux/option.go index 6dbd6482bc..2d960244c5 100644 --- a/contrib/gorilla/mux/option.go +++ b/contrib/gorilla/mux/option.go @@ -10,6 +10,7 @@ import ( "net/http" "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" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" @@ -29,6 +30,19 @@ type routerConfig struct { // RouterOption represents an option that can be passed to NewRouter. type RouterOption func(*routerConfig) +func newConfig(opts []RouterOption) *routerConfig { + cfg := new(routerConfig) + defaults(cfg) + for _, fn := range opts { + fn(cfg) + } + if !math.IsNaN(cfg.analyticsRate) { + cfg.spanOpts = append(cfg.spanOpts, tracer.Tag(ext.EventSampleRate, cfg.analyticsRate)) + } + cfg.spanOpts = append(cfg.spanOpts, tracer.Measured()) + return cfg +} + func defaults(cfg *routerConfig) { if internal.BoolEnv("DD_TRACE_MUX_ANALYTICS_ENABLED", false) { cfg.analyticsRate = 1.0 diff --git a/contrib/gorm.io/gorm.v1/gorm_test.go b/contrib/gorm.io/gorm.v1/gorm_test.go index 9d4d1fec19..1452d184d3 100644 --- a/contrib/gorm.io/gorm.v1/gorm_test.go +++ b/contrib/gorm.io/gorm.v1/gorm_test.go @@ -19,20 +19,23 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" + mssql "github.com/denisenkom/go-mssqldb" "github.com/go-sql-driver/mysql" "github.com/jackc/pgx/v4/stdlib" _ "github.com/lib/pq" "github.com/stretchr/testify/assert" mysqlgorm "gorm.io/driver/mysql" "gorm.io/driver/postgres" + "gorm.io/driver/sqlserver" "gorm.io/gorm" ) // tableName holds the SQL table that these tests will be run against. It must be unique cross-repo. const ( - tableName = "testgorm" - pgConnString = "postgres://postgres:postgres@127.0.0.1:5432/postgres?sslmode=disable" - mysqlConnString = "test:test@tcp(127.0.0.1:3306)/test" + tableName = "testgorm" + pgConnString = "postgres://postgres:postgres@127.0.0.1:5432/postgres?sslmode=disable" + sqlServerConnString = "sqlserver://sa:myPassw0rd@127.0.0.1:1433?database=master" + mysqlConnString = "test:test@tcp(127.0.0.1:3306)/test" ) func TestMain(m *testing.M) { @@ -113,6 +116,40 @@ func TestPostgres(t *testing.T) { sqltest.RunAll(t, testConfig) } +func TestSQLServer(t *testing.T) { + sqltrace.Register("sqlserver", &mssql.Driver{}) + sqlDb, err := sqltrace.Open("sqlserver", sqlServerConnString) + if err != nil { + log.Fatal(err) + } + + db, err := Open(sqlserver.New(sqlserver.Config{Conn: sqlDb}), &gorm.Config{}) + if err != nil { + log.Fatal(err) + } + + internalDB, err := db.DB() + if err != nil { + log.Fatal(err) + } + + testConfig := &sqltest.Config{ + DB: internalDB, + DriverName: "sqlserver", + TableName: tableName, + ExpectName: "sqlserver.query", + ExpectTags: map[string]interface{}{ + ext.ServiceName: "sqlserver.db", + ext.SpanType: ext.SpanTypeSQL, + ext.TargetHost: "127.0.0.1", + ext.TargetPort: "1433", + ext.DBUser: "sa", + ext.DBName: "master", + }, + } + sqltest.RunAll(t, testConfig) +} + type Product struct { gorm.Model Code string diff --git a/contrib/internal/sqltest/sqltest.go b/contrib/internal/sqltest/sqltest.go index 7c9d64a5c4..35dbd1ddd0 100644 --- a/contrib/internal/sqltest/sqltest.go +++ b/contrib/internal/sqltest/sqltest.go @@ -38,9 +38,17 @@ func Prepare(tableName string) func() { } postgres.Exec(queryDrop) postgres.Exec(queryCreate) + mssql, err := sql.Open("sqlserver", "sqlserver://sa:myPassw0rd@localhost:1433?database=master") + defer mssql.Close() + if err != nil { + log.Fatal(err) + } + mssql.Exec(queryDrop) + mssql.Exec(queryCreate) return func() { mysql.Exec(queryDrop) postgres.Exec(queryDrop) + mssql.Exec(queryDrop) } } @@ -48,8 +56,10 @@ func Prepare(tableName string) func() { func RunAll(t *testing.T, cfg *Config) { cfg.mockTracer = mocktracer.Start() defer cfg.mockTracer.Stop() + cfg.DB.SetMaxIdleConns(0) for name, test := range map[string]func(*Config) func(*testing.T){ + "Connect": testConnect, "Ping": testPing, "Query": testQuery, "Statement": testStatement, @@ -60,17 +70,37 @@ func RunAll(t *testing.T, cfg *Config) { } } -func testPing(cfg *Config) func(*testing.T) { +func testConnect(cfg *Config) func(*testing.T) { return func(t *testing.T) { cfg.mockTracer.Reset() assert := assert.New(t) err := cfg.DB.Ping() assert.Nil(err) spans := cfg.mockTracer.FinishedSpans() - assert.Len(spans, 1) + assert.Len(spans, 2) span := spans[0] assert.Equal(cfg.ExpectName, span.OperationName()) + cfg.ExpectTags["sql.query_type"] = "Connect" + for k, v := range cfg.ExpectTags { + assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) + } + } +} + +func testPing(cfg *Config) func(*testing.T) { + return func(t *testing.T) { + cfg.mockTracer.Reset() + assert := assert.New(t) + err := cfg.DB.Ping() + assert.Nil(err) + spans := cfg.mockTracer.FinishedSpans() + assert.Len(spans, 2) + + verifyConnectSpan(spans[0], assert, cfg) + + span := spans[1] + assert.Equal(cfg.ExpectName, span.OperationName()) cfg.ExpectTags["sql.query_type"] = "Ping" for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) @@ -79,7 +109,13 @@ func testPing(cfg *Config) func(*testing.T) { } func testQuery(cfg *Config) func(*testing.T) { - query := fmt.Sprintf("SELECT id, name FROM %s LIMIT 5", cfg.TableName) + var query string + switch cfg.DriverName { + case "postgres", "pgx", "mysql": + query = fmt.Sprintf("SELECT id, name FROM %s LIMIT 5", cfg.TableName) + case "sqlserver": + query = fmt.Sprintf("SELECT TOP 5 id, name FROM %s", cfg.TableName) + } return func(t *testing.T) { cfg.mockTracer.Reset() assert := assert.New(t) @@ -88,13 +124,29 @@ func testQuery(cfg *Config) func(*testing.T) { assert.Nil(err) spans := cfg.mockTracer.FinishedSpans() - assert.Len(spans, 1) + var querySpan mocktracer.Span + if cfg.DriverName == "sqlserver" { + //The mssql driver doesn't support non-prepared queries so there are 3 spans + //connect, prepare, and query + assert.Len(spans, 3) + span := spans[1] + cfg.ExpectTags["sql.query_type"] = "Prepare" + assert.Equal(cfg.ExpectName, span.OperationName()) + for k, v := range cfg.ExpectTags { + assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) + } + querySpan = spans[2] - span := spans[0] + } else { + assert.Len(spans, 2) + querySpan = spans[1] + } + + verifyConnectSpan(spans[0], assert, cfg) cfg.ExpectTags["sql.query_type"] = "Query" - assert.Equal(cfg.ExpectName, span.OperationName()) + assert.Equal(cfg.ExpectName, querySpan.OperationName()) for k, v := range cfg.ExpectTags { - assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) + assert.Equal(v, querySpan.Tag(k), "Value mismatch on tag %s", k) } } } @@ -106,6 +158,8 @@ func testStatement(cfg *Config) func(*testing.T) { query = fmt.Sprintf(query, cfg.TableName, "$1") case "mysql": query = fmt.Sprintf(query, cfg.TableName, "?") + case "sqlserver": + query = fmt.Sprintf(query, cfg.TableName, "@p1") } return func(t *testing.T) { cfg.mockTracer.Reset() @@ -114,9 +168,11 @@ func testStatement(cfg *Config) func(*testing.T) { assert.Equal(nil, err) spans := cfg.mockTracer.FinishedSpans() - assert.Len(spans, 1) + assert.Len(spans, 3) - span := spans[0] + verifyConnectSpan(spans[0], assert, cfg) + + span := spans[1] assert.Equal(cfg.ExpectName, span.OperationName()) cfg.ExpectTags["sql.query_type"] = "Prepare" for k, v := range cfg.ExpectTags { @@ -128,8 +184,8 @@ func testStatement(cfg *Config) func(*testing.T) { assert.Equal(nil, err2) spans = cfg.mockTracer.FinishedSpans() - assert.Len(spans, 1) - span = spans[0] + assert.Len(spans, 4) + span = spans[2] assert.Equal(cfg.ExpectName, span.OperationName()) cfg.ExpectTags["sql.query_type"] = "Exec" for k, v := range cfg.ExpectTags { @@ -147,9 +203,11 @@ func testBeginRollback(cfg *Config) func(*testing.T) { assert.Equal(nil, err) spans := cfg.mockTracer.FinishedSpans() - assert.Len(spans, 1) + assert.Len(spans, 2) - span := spans[0] + verifyConnectSpan(spans[0], assert, cfg) + + span := spans[1] assert.Equal(cfg.ExpectName, span.OperationName()) cfg.ExpectTags["sql.query_type"] = "Begin" for k, v := range cfg.ExpectTags { @@ -192,7 +250,25 @@ func testExec(cfg *Config) func(*testing.T) { parent.Finish() // flush children spans := cfg.mockTracer.FinishedSpans() - assert.Len(spans, 4) + if cfg.DriverName == "sqlserver" { + //The mssql driver doesn't support non-prepared exec so there are 2 extra spans for the exec: + //prepare, exec, and then a close + assert.Len(spans, 7) + span := spans[2] + cfg.ExpectTags["sql.query_type"] = "Prepare" + assert.Equal(cfg.ExpectName, span.OperationName()) + for k, v := range cfg.ExpectTags { + assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) + } + span = spans[4] + cfg.ExpectTags["sql.query_type"] = "Close" + assert.Equal(cfg.ExpectName, span.OperationName()) + for k, v := range cfg.ExpectTags { + assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) + } + } else { + assert.Len(spans, 5) + } var span mocktracer.Span for _, s := range spans { @@ -218,6 +294,14 @@ func testExec(cfg *Config) func(*testing.T) { } } +func verifyConnectSpan(span mocktracer.Span, assert *assert.Assertions, cfg *Config) { + assert.Equal(cfg.ExpectName, span.OperationName()) + cfg.ExpectTags["sql.query_type"] = "Connect" + for k, v := range cfg.ExpectTags { + assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) + } +} + // Config holds the test configuration. type Config struct { *sql.DB diff --git a/contrib/jinzhu/gorm/gorm_test.go b/contrib/jinzhu/gorm/gorm_test.go index 70b48ae017..436b779451 100644 --- a/contrib/jinzhu/gorm/gorm_test.go +++ b/contrib/jinzhu/gorm/gorm_test.go @@ -19,6 +19,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" + mssql "github.com/denisenkom/go-mssqldb" "github.com/go-sql-driver/mysql" "github.com/jinzhu/gorm" "github.com/lib/pq" @@ -38,6 +39,32 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } +func TestSqlServer(t *testing.T) { + sqltrace.Register("sqlserver", &mssql.Driver{}) + db, err := Open("sqlserver", "sqlserver://sa:myPassw0rd@127.0.0.1:1433?database=master") + if err != nil { + log.Fatal(err) + } + defer db.Close() + + testConfig := &sqltest.Config{ + DB: db.DB(), + DriverName: "sqlserver", + TableName: tableName, + ExpectName: "sqlserver.query", + ExpectTags: map[string]interface{}{ + ext.ServiceName: "sqlserver.db", + ext.SpanType: ext.SpanTypeSQL, + ext.TargetHost: "127.0.0.1", + ext.TargetPort: "1433", + ext.DBUser: "sa", + ext.DBName: "master", + ext.EventSampleRate: nil, + }, + } + sqltest.RunAll(t, testConfig) +} + func TestMySQL(t *testing.T) { sqltrace.Register("mysql", &mysql.MySQLDriver{}, sqltrace.WithServiceName("mysql-test")) db, err := Open("mysql", "test:test@tcp(127.0.0.1:3306)/test") diff --git a/contrib/jmoiron/sqlx/sql_test.go b/contrib/jmoiron/sqlx/sql_test.go index a2ca3be917..54322f839a 100644 --- a/contrib/jmoiron/sqlx/sql_test.go +++ b/contrib/jmoiron/sqlx/sql_test.go @@ -15,6 +15,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/sqltest" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + mssql "github.com/denisenkom/go-mssqldb" "github.com/go-sql-driver/mysql" "github.com/lib/pq" ) @@ -82,6 +83,31 @@ func TestPostgres(t *testing.T) { sqltest.RunAll(t, testConfig) } +func TestSQLServer(t *testing.T) { + sqltrace.Register("sqlserver", &mssql.Driver{}) + dbx, err := Open("sqlserver", "sqlserver://sa:myPassw0rd@127.0.0.1:1433?database=master") + if err != nil { + log.Fatal(err) + } + defer dbx.Close() + + testConfig := &sqltest.Config{ + DB: dbx.DB, + DriverName: "sqlserver", + TableName: tableName, + ExpectName: "sqlserver.query", + ExpectTags: map[string]interface{}{ + ext.ServiceName: "sqlserver.db", + ext.SpanType: ext.SpanTypeSQL, + ext.TargetHost: "127.0.0.1", + ext.TargetPort: "1433", + ext.DBUser: "sa", + ext.DBName: "master", + }, + } + sqltest.RunAll(t, testConfig) +} + func TestOpenWithOptions(t *testing.T) { sqltrace.Register("mysql", &mysql.MySQLDriver{}, sqltrace.WithServiceName("mysql-test")) dbx, err := Open("mysql", "test:test@tcp(127.0.0.1:3306)/test", sqltrace.WithServiceName("other-service")) diff --git a/contrib/julienschmidt/httprouter/httprouter.go b/contrib/julienschmidt/httprouter/httprouter.go index 4938497357..4889d53791 100644 --- a/contrib/julienschmidt/httprouter/httprouter.go +++ b/contrib/julienschmidt/httprouter/httprouter.go @@ -11,7 +11,7 @@ import ( "net/http" "strings" - "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httputil" + httptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http" "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/log" @@ -49,11 +49,9 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { route = strings.Replace(route, param.Value, ":"+param.Key, 1) } resource := req.Method + " " + route - httputil.TraceAndServe(r.Router, &httputil.TraceConfig{ - ResponseWriter: w, - Request: req, - Service: r.config.serviceName, - Resource: resource, - SpanOpts: r.config.spanOpts, + httptrace.TraceAndServe(r.Router, w, req, &httptrace.ServeConfig{ + Service: r.config.serviceName, + Resource: resource, + SpanOpts: r.config.spanOpts, }) } diff --git a/contrib/labstack/echo.v4/appsec.go b/contrib/labstack/echo.v4/appsec.go new file mode 100644 index 0000000000..675af4e48b --- /dev/null +++ b/contrib/labstack/echo.v4/appsec.go @@ -0,0 +1,37 @@ +// 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 echo + +import ( + "net" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo/instrumentation/httpsec" + + "github.com/labstack/echo/v4" +) + +func useAppSec(c echo.Context, span tracer.Span) func() { + req := c.Request() + httpsec.SetAppSecTags(span) + params := make(map[string]string) + for _, n := range c.ParamNames() { + params[n] = c.Param(n) + } + args := httpsec.MakeHandlerOperationArgs(req, params) + ctx, op := httpsec.StartOperation(req.Context(), args) + c.SetRequest(req.WithContext(ctx)) + return 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()) + } + } +} diff --git a/contrib/labstack/echo.v4/echotrace.go b/contrib/labstack/echo.v4/echotrace.go index 2f078c5e00..fc16489626 100644 --- a/contrib/labstack/echo.v4/echotrace.go +++ b/contrib/labstack/echo.v4/echotrace.go @@ -14,13 +14,13 @@ import ( "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 { + appsecEnabled := appsec.Enabled() cfg := new(config) defaults(cfg) for _, fn := range opts { @@ -45,23 +45,23 @@ func Middleware(opts ...Option) echo.MiddlewareFunc { if spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(request.Header)); err == nil { opts = append(opts, tracer.ChildOf(spanctx)) } + var finishOpts []tracer.FinishOption + if cfg.noDebugStack { + finishOpts = append(finishOpts, tracer.NoDebugStack()) + } span, ctx := tracer.StartSpanFromContext(request.Context(), "http.request", opts...) - defer span.Finish() + defer func() { span.Finish(finishOpts...) }() // pass the span through the request context - req := request.WithContext(ctx) - c.SetRequest(req) - - if appsec.Enabled() { - op := httpsec.StartOperation(httpsec.MakeHandlerOperationArgs(req, span), nil) - defer func() { - op.Finish(httpsec.HandlerOperationRes{Status: c.Response().Status}) - }() - } + c.SetRequest(request.WithContext(ctx)) // serve the request to the next middleware + if appsecEnabled { + afterMiddleware := useAppSec(c, span) + defer afterMiddleware() + } err := next(c) if err != nil { - span.SetTag(ext.Error, err) + finishOpts = append(finishOpts, tracer.WithError(err)) // invokes the registered HTTP error handler c.Error(err) } diff --git a/contrib/labstack/echo.v4/echotrace_test.go b/contrib/labstack/echo.v4/echotrace_test.go index 610836b829..c43695466f 100644 --- a/contrib/labstack/echo.v4/echotrace_test.go +++ b/contrib/labstack/echo.v4/echotrace_test.go @@ -7,16 +7,21 @@ package echo import ( "errors" + "io/ioutil" "net/http" "net/http/httptest" + "strings" "testing" + pappsec "gopkg.in/DataDog/dd-trace-go.v1/appsec" "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) { @@ -230,3 +235,185 @@ func TestGetSpanNotInstrumented(t *testing.T) { assert.True(called) assert.False(traced) } + +func TestNoDebugStack(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + var called, traced bool + + // setup + router := echo.New() + router.Use(Middleware(NoDebugStack())) + wantErr := errors.New("oh no") + + // a handler with an error and make the requests + router.GET("/err", func(c echo.Context) error { + _, traced = tracer.SpanFromContext(c.Request().Context()) + called = true + + err := wantErr + c.Error(err) + return err + }) + r := httptest.NewRequest("GET", "/err", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, r) + + // verify the error is correct and the stacktrace is disabled + assert.True(called) + assert.True(traced) + + spans := mt.FinishedSpans() + assert.Len(spans, 1) + + span := spans[0] + assert.Equal(wantErr.Error(), span.Tag(ext.Error).(error).Error()) + assert.Equal("", span.Tag(ext.ErrorStack)) +} + +func TestAppSec(t *testing.T) { + appsec.Start() + defer appsec.Stop() + + if !appsec.Enabled() { + t.Skip("appsec disabled") + } + + // Start and trace an HTTP server + e := echo.New() + e.Use(Middleware()) + + // Add some testing routes + e.POST("/path0.0/:myPathParam0/path0.1/:myPathParam1/path0.2/:myPathParam2/path0.3/*myPathParam3", func(c echo.Context) error { + return c.String(200, "Hello World!\n") + }) + e.POST("/", func(c echo.Context) error { + return c.String(200, "Hello World!\n") + }) + e.POST("/body", func(c echo.Context) error { + pappsec.MonitorParsedHTTPBody(c.Request().Context(), "$globals") + return c.String(200, "Hello Body!\n") + }) + srv := httptest.NewServer(e) + defer srv.Close() + + // Test an LFI attack via path parameters + t.Run("request-uri", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + // Send an LFI attack (according to appsec rule id crs-930-110) + 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 server behaved as intended (no 301 but 404 directly) + require.Equal(t, http.StatusNotFound, res.StatusCode) + // The span should contain the security event + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + event := finished[0].Tag("_dd.appsec.json").(string) + require.NotNil(t, event) + require.True(t, strings.Contains(event, "crs-930-110")) + require.True(t, strings.Contains(event, "server.request.uri.raw")) + }) + + // Test a security scanner attack via path parameters + t.Run("path-params", func(t *testing.T) { + t.Run("regular", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + // Send a security scanner attack (according to appsec rule id crs-913-120) + req, err := http.NewRequest("POST", srv.URL+"/path0.0/param0/path0.1/param1/path0.2/appscan_fingerprint/path0.3/param3", 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)) + require.Equal(t, http.StatusOK, res.StatusCode) + // The span should contain the security event + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + event := finished[0].Tag("_dd.appsec.json").(string) + require.NotNil(t, event) + require.True(t, strings.Contains(event, "crs-913-120")) + require.True(t, strings.Contains(event, "myPathParam2")) + require.True(t, strings.Contains(event, "server.request.path_params")) + }) + + t.Run("wildcard", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + // Send a security scanner attack (according to appsec rule id crs-913-120) + req, err := http.NewRequest("POST", srv.URL+"/path0.0/param0/path0.1/param1/path0.2/param2/path0.3/appscan_fingerprint", 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)) + require.Equal(t, http.StatusOK, res.StatusCode) + // The span should contain the security event + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + event := finished[0].Tag("_dd.appsec.json").(string) + require.NotNil(t, event) + require.True(t, strings.Contains(event, "crs-913-120")) + // Wildcards are not named in echo + require.False(t, strings.Contains(event, "myPathParam3")) + require.True(t, strings.Contains(event, "server.request.path_params")) + }) + }) + + t.Run("response-status", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + req, err := http.NewRequest("POST", srv.URL+"/etc/", nil) + if err != nil { + panic(err) + } + res, err := srv.Client().Do(req) + require.NoError(t, err) + require.Equal(t, 404, res.StatusCode) + + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + event := finished[0].Tag("_dd.appsec.json").(string) + require.NotNil(t, event) + require.True(t, strings.Contains(event, "server.response.status")) + require.True(t, strings.Contains(event, "nfd-000-001")) + }) + + // Test a PHP injection attack via request parsed body + t.Run("SDK-body", func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + + req, err := http.NewRequest("POST", srv.URL+"/body", 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 Body!\n", string(b)) + + finished := mt.FinishedSpans() + require.Len(t, finished, 1) + event := finished[0].Tag("_dd.appsec.json") + require.NotNil(t, event) + require.True(t, strings.Contains(event.(string), "crs-933-130")) + }) +} diff --git a/contrib/labstack/echo.v4/option.go b/contrib/labstack/echo.v4/option.go index 056de543a0..1ad9dc7246 100644 --- a/contrib/labstack/echo.v4/option.go +++ b/contrib/labstack/echo.v4/option.go @@ -14,6 +14,7 @@ import ( type config struct { serviceName string analyticsRate float64 + noDebugStack bool } // Option represents an option that can be passed to Middleware. @@ -56,3 +57,12 @@ func WithAnalyticsRate(rate float64) Option { } } } + +// NoDebugStack prevents stack traces from being attached to spans finishing +// with an error. This is useful in situations where errors are frequent and +// performance is critical. +func NoDebugStack() Option { + return func(cfg *config) { + cfg.noDebugStack = true + } +} diff --git a/contrib/labstack/echo/echotrace.go b/contrib/labstack/echo/echotrace.go index ee13275dee..bd8aca362e 100644 --- a/contrib/labstack/echo/echotrace.go +++ b/contrib/labstack/echo/echotrace.go @@ -45,8 +45,12 @@ func Middleware(opts ...Option) echo.MiddlewareFunc { if spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(request.Header)); err == nil { opts = append(opts, tracer.ChildOf(spanctx)) } + var finishOpts []tracer.FinishOption + if cfg.noDebugStack { + finishOpts = append(finishOpts, tracer.NoDebugStack()) + } span, ctx := tracer.StartSpanFromContext(request.Context(), "http.request", opts...) - defer span.Finish() + defer func() { span.Finish(finishOpts...) }() // pass the span through the request context c.SetRequest(request.WithContext(ctx)) @@ -54,7 +58,7 @@ func Middleware(opts ...Option) echo.MiddlewareFunc { // serve the request to the next middleware err := next(c) if err != nil { - span.SetTag(ext.Error, err) + finishOpts = append(finishOpts, tracer.WithError(err)) // invokes the registered HTTP error handler c.Error(err) } diff --git a/contrib/labstack/echo/echotrace_test.go b/contrib/labstack/echo/echotrace_test.go index eb8a289e94..249bf233f8 100644 --- a/contrib/labstack/echo/echotrace_test.go +++ b/contrib/labstack/echo/echotrace_test.go @@ -230,3 +230,39 @@ func TestGetSpanNotInstrumented(t *testing.T) { assert.True(called) assert.False(traced) } + +func TestNoDebugStack(t *testing.T) { + assert := assert.New(t) + mt := mocktracer.Start() + defer mt.Stop() + var called, traced bool + + // setup + router := echo.New() + router.Use(Middleware(NoDebugStack())) + wantErr := errors.New("oh no") + + // a handler with an error and make the requests + router.GET("/err", func(c echo.Context) error { + _, traced = tracer.SpanFromContext(c.Request().Context()) + called = true + + err := wantErr + c.Error(err) + return err + }) + r := httptest.NewRequest("GET", "/err", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, r) + + // verify the error is correct and the stacktrace is disabled + assert.True(called) + assert.True(traced) + + spans := mt.FinishedSpans() + assert.Len(spans, 1) + + span := spans[0] + assert.Equal(wantErr.Error(), span.Tag(ext.Error).(error).Error()) + assert.Equal("", span.Tag(ext.ErrorStack)) +} diff --git a/contrib/labstack/echo/option.go b/contrib/labstack/echo/option.go index d3e3930c3f..4af218c5bb 100644 --- a/contrib/labstack/echo/option.go +++ b/contrib/labstack/echo/option.go @@ -15,6 +15,7 @@ import ( type config struct { serviceName string analyticsRate float64 + noDebugStack bool } // Option represents an option that can be passed to Middleware. @@ -61,3 +62,12 @@ func WithAnalyticsRate(rate float64) Option { } } } + +// NoDebugStack prevents stack traces from being attached to spans finishing +// with an error. This is useful in situations where errors are frequent and +// performance is critical. +func NoDebugStack() Option { + return func(cfg *config) { + cfg.noDebugStack = true + } +} diff --git a/contrib/net/http/example_test.go b/contrib/net/http/example_test.go index 93e55655b4..5e7cf8409c 100644 --- a/contrib/net/http/example_test.go +++ b/contrib/net/http/example_test.go @@ -26,3 +26,25 @@ func Example_withServiceName() { }) http.ListenAndServe(":8080", mux) } + +func ExampleTraceAndServe() { + mux := http.NewServeMux() + mux.Handle("/", traceMiddleware(mux, http.HandlerFunc(Index))) + http.ListenAndServe(":8080", mux) +} + +func Index(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello World!\n")) +} + +func traceMiddleware(mux *http.ServeMux, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, route := mux.Handler(r) + resource := r.Method + " " + route + httptrace.TraceAndServe(next, w, r, &httptrace.ServeConfig{ + Service: "http.router", + Resource: resource, + QueryParams: true, + }) + }) +} diff --git a/contrib/net/http/http.go b/contrib/net/http/http.go index a98821b94a..55560164ba 100644 --- a/contrib/net/http/http.go +++ b/contrib/net/http/http.go @@ -9,7 +9,6 @@ package http // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http" import ( "net/http" - "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httputil" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" ) @@ -46,12 +45,10 @@ func (mux *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { // get the resource associated to this request _, route := mux.Handler(r) resource := r.Method + " " + route - httputil.TraceAndServe(mux.ServeMux, &httputil.TraceConfig{ - ResponseWriter: w, - Request: r, - Service: mux.cfg.serviceName, - Resource: resource, - SpanOpts: mux.cfg.spanOpts, + TraceAndServe(mux.ServeMux, w, r, &ServeConfig{ + Service: mux.cfg.serviceName, + Resource: resource, + SpanOpts: mux.cfg.spanOpts, }) } @@ -64,13 +61,15 @@ func WrapHandler(h http.Handler, service, resource string, opts ...Option) http. } log.Debug("contrib/net/http: Wrapping Handler: Service: %s, Resource: %s, %#v", service, resource, cfg) return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - httputil.TraceAndServe(h, &httputil.TraceConfig{ - ResponseWriter: w, - Request: req, - Service: service, - Resource: resource, - FinishOpts: cfg.finishOpts, - SpanOpts: cfg.spanOpts, + if cfg.ignoreRequest(req) { + h.ServeHTTP(w, req) + return + } + TraceAndServe(h, w, req, &ServeConfig{ + Service: service, + Resource: resource, + FinishOpts: cfg.finishOpts, + SpanOpts: cfg.spanOpts, }) }) } diff --git a/contrib/net/http/http_test.go b/contrib/net/http/http_test.go index 9cdf5b511b..4041da2276 100644 --- a/contrib/net/http/http_test.go +++ b/contrib/net/http/http_test.go @@ -216,14 +216,15 @@ func TestIgnoreRequestOption(t *testing.T) { spanCount: 1, }, } - mux := NewServeMux(WithIgnoreRequest(func(req *http.Request) bool { + ignore := func(req *http.Request) bool { return req.URL.Path == "/skip" - })) + } + mux := NewServeMux(WithIgnoreRequest(ignore)) mux.HandleFunc("/skip", handler200) mux.HandleFunc("/200", handler200) for _, test := range tests { - t.Run(test.url, func(t *testing.T) { + t.Run("servemux"+test.url, func(t *testing.T) { mt := mocktracer.Start() defer mt.Stop() r := httptest.NewRequest("GET", "http://localhost"+test.url, nil) @@ -233,6 +234,19 @@ func TestIgnoreRequestOption(t *testing.T) { spans := mt.FinishedSpans() assert.Equal(t, test.spanCount, len(spans)) }) + + t.Run("wraphandler"+test.url, func(t *testing.T) { + mt := mocktracer.Start() + defer mt.Stop() + r := httptest.NewRequest("GET", "http://localhost"+test.url, nil) + w := httptest.NewRecorder() + f := http.HandlerFunc(handler200) + handler := WrapHandler(f, "my-service", "my-resource", WithIgnoreRequest(ignore)) + handler.ServeHTTP(w, r) + + spans := mt.FinishedSpans() + assert.Equal(t, test.spanCount, len(spans)) + }) } } diff --git a/contrib/internal/httputil/make_responsewriter.go b/contrib/net/http/make_responsewriter.go similarity index 99% rename from contrib/internal/httputil/make_responsewriter.go rename to contrib/net/http/make_responsewriter.go index cc4e85c04d..f175cb945f 100644 --- a/contrib/internal/httputil/make_responsewriter.go +++ b/contrib/net/http/make_responsewriter.go @@ -39,7 +39,7 @@ var tpl = `// Unless explicitly stated otherwise all files in this repository ar // Code generated by make_responsewriter.go DO NOT EDIT -package httputil +package http import ( "net/http" diff --git a/contrib/internal/httputil/trace.go b/contrib/net/http/trace.go similarity index 56% rename from contrib/internal/httputil/trace.go rename to contrib/net/http/trace.go index 52c2c39565..45b88711f0 100644 --- a/contrib/internal/httputil/trace.go +++ b/contrib/net/http/trace.go @@ -3,7 +3,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2016 Datadog, Inc. -package httputil // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httputil" +package http // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http" //go:generate sh -c "go run make_responsewriter.go | gofmt > trace_gen.go" @@ -19,45 +19,57 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo/instrumentation/httpsec" ) -// TraceConfig defines the configuration for request tracing. -type TraceConfig struct { - ResponseWriter http.ResponseWriter // response writer - Request *http.Request // request that is traced - Service string // service name - Resource string // resource name - QueryParams bool // specifies that request query parameters should be appended to http.url tag - FinishOpts []ddtrace.FinishOption // span finish options to be applied - SpanOpts []ddtrace.StartSpanOption // additional span options to be applied +// ServeConfig specifies the tracing configuration when using TraceAndServe. +type ServeConfig struct { + // Service specifies the service name to use. If left blank, the global service name + // will be inherited. + Service string + // Resource optionally specifies the resource name for this request. + Resource string + // QueryParams specifies any query parameters that be appended to the resulting "http.url" tag. + QueryParams bool + // RouteParams specifies framework-specific route parameters (e.g. for route /user/:id coming + // in as /user/123 we'll have {"id": "123"}). This field is optional and is used for monitoring + // by AppSec. It is only taken into account when AppSec is enabled. + RouteParams map[string]string + // FinishOpts specifies any options to be used when finishing the request span. + FinishOpts []ddtrace.FinishOption + // SpanOpts specifies any options to be applied to the request starting span. + SpanOpts []ddtrace.StartSpanOption } -// TraceAndServe will apply tracing to the given http.Handler using the passed tracer under the given service and resource. -func TraceAndServe(h http.Handler, cfg *TraceConfig) { - path := cfg.Request.URL.Path +// TraceAndServe serves the handler h using the given ResponseWriter and Request, applying tracing +// according to the specified config. +func TraceAndServe(h http.Handler, w http.ResponseWriter, r *http.Request, cfg *ServeConfig) { + if cfg == nil { + cfg = new(ServeConfig) + } + path := r.URL.Path if cfg.QueryParams { - path += "?" + cfg.Request.URL.RawQuery + path += "?" + r.URL.RawQuery } opts := append([]ddtrace.StartSpanOption{ tracer.SpanType(ext.SpanTypeWeb), tracer.ServiceName(cfg.Service), tracer.ResourceName(cfg.Resource), - tracer.Tag(ext.HTTPMethod, cfg.Request.Method), + tracer.Tag(ext.HTTPMethod, r.Method), tracer.Tag(ext.HTTPURL, path), }, cfg.SpanOpts...) - if cfg.Request.URL.Host != "" { + if r.URL.Host != "" { opts = append([]ddtrace.StartSpanOption{ - tracer.Tag("http.host", cfg.Request.URL.Host), + tracer.Tag("http.host", r.URL.Host), }, opts...) } - if spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(cfg.Request.Header)); err == nil { + if spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(r.Header)); err == nil { opts = append(opts, tracer.ChildOf(spanctx)) } - span, ctx := tracer.StartSpanFromContext(cfg.Request.Context(), "http.request", opts...) + span, ctx := tracer.StartSpanFromContext(r.Context(), "http.request", opts...) defer span.Finish(cfg.FinishOpts...) if appsec.Enabled() { - h = httpsec.WrapHandler(h, span) + h = httpsec.WrapHandler(h, span, cfg.RouteParams) } - h.ServeHTTP(wrapResponseWriter(cfg.ResponseWriter, span), cfg.Request.WithContext(ctx)) + h.ServeHTTP(wrapResponseWriter(w, span), r.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/net/http/trace_gen.go similarity index 99% rename from contrib/internal/httputil/trace_gen.go rename to contrib/net/http/trace_gen.go index bbe8bd55c6..fd93bd461d 100644 --- a/contrib/internal/httputil/trace_gen.go +++ b/contrib/net/http/trace_gen.go @@ -5,7 +5,7 @@ // Code generated by make_responsewriter.go DO NOT EDIT -package httputil +package http import ( "net/http" diff --git a/contrib/internal/httputil/trace_test.go b/contrib/net/http/trace_test.go similarity index 81% rename from contrib/internal/httputil/trace_test.go rename to contrib/net/http/trace_test.go index b3c9bb0a07..77504b4b35 100644 --- a/contrib/internal/httputil/trace_test.go +++ b/contrib/net/http/trace_test.go @@ -3,7 +3,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2016 Datadog, Inc. -package httputil +package http import ( "fmt" @@ -36,11 +36,9 @@ func TestTraceAndServe(t *testing.T) { http.Error(w, "some error", http.StatusServiceUnavailable) called = true } - TraceAndServe(http.HandlerFunc(handler), &TraceConfig{ - ResponseWriter: w, - Request: r, - Service: "service", - Resource: "resource", + TraceAndServe(http.HandlerFunc(handler), w, r, &ServeConfig{ + Service: "service", + Resource: "resource", }) spans := mt.FinishedSpans() span := spans[0] @@ -74,11 +72,9 @@ func TestTraceAndServe(t *testing.T) { http.Error(w, "some error", http.StatusServiceUnavailable) called = true } - TraceAndServe(http.HandlerFunc(handler), &TraceConfig{ - ResponseWriter: w, - Request: r, - Service: "service", - Resource: "resource", + TraceAndServe(http.HandlerFunc(handler), w, r, &ServeConfig{ + Service: "service", + Resource: "resource", }) spans := mt.FinishedSpans() span := spans[0] @@ -106,12 +102,10 @@ func TestTraceAndServe(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { called = true } - TraceAndServe(http.HandlerFunc(handler), &TraceConfig{ - ResponseWriter: w, - Request: r, - Service: "service", - Resource: "resource", - QueryParams: true, + TraceAndServe(http.HandlerFunc(handler), w, r, &ServeConfig{ + Service: "service", + Resource: "resource", + QueryParams: true, }) spans := mt.FinishedSpans() @@ -134,11 +128,9 @@ func TestTraceAndServe(t *testing.T) { called = true } srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - TraceAndServe(http.HandlerFunc(handler), &TraceConfig{ - ResponseWriter: w, - Request: r, - Service: "service", - Resource: "resource", + TraceAndServe(http.HandlerFunc(handler), w, r, &ServeConfig{ + Service: "service", + Resource: "resource", }) })) defer srv.Close() @@ -192,11 +184,9 @@ func TestTraceAndServe(t *testing.T) { assert.NoError(err) w := httptest.NewRecorder() - TraceAndServe(http.HandlerFunc(handler), &TraceConfig{ - ResponseWriter: w, - Request: r, - Service: "service", - Resource: "resource", + TraceAndServe(http.HandlerFunc(handler), w, r, &ServeConfig{ + Service: "service", + Resource: "resource", }) var p, c mocktracer.Span @@ -229,11 +219,9 @@ func TestTraceAndServe(t *testing.T) { r = r.WithContext(tracer.ContextWithSpan(r.Context(), parent)) w := httptest.NewRecorder() - TraceAndServe(http.HandlerFunc(handler), &TraceConfig{ - ResponseWriter: w, - Request: r, - Service: "service", - Resource: "resource", + TraceAndServe(http.HandlerFunc(handler), w, r, &ServeConfig{ + Service: "service", + Resource: "resource", }) var p, c mocktracer.Span @@ -260,11 +248,9 @@ func TestTraceAndServe(t *testing.T) { r, err := http.NewRequest("GET", "/", nil) assert.NoError(err) w := httptest.NewRecorder() - TraceAndServe(http.HandlerFunc(handler), &TraceConfig{ - ResponseWriter: w, - Request: r, - Service: "service", - Resource: "resource", + TraceAndServe(http.HandlerFunc(handler), w, r, &ServeConfig{ + Service: "service", + Resource: "resource", }) spans := mt.FinishedSpans() @@ -285,11 +271,9 @@ func TestTraceAndServeHost(t *testing.T) { r, err := http.NewRequest("GET", "http://localhost/", nil) assert.NoError(err) - TraceAndServe(handler, &TraceConfig{ - ResponseWriter: httptest.NewRecorder(), - Request: r, - Service: "service", - Resource: "resource", + TraceAndServe(handler, httptest.NewRecorder(), r, &ServeConfig{ + Service: "service", + Resource: "resource", }) span := mt.FinishedSpans()[0] @@ -303,11 +287,9 @@ func TestTraceAndServeHost(t *testing.T) { r, err := http.NewRequest("GET", "/", nil) assert.NoError(err) - TraceAndServe(handler, &TraceConfig{ - ResponseWriter: httptest.NewRecorder(), - Request: r, - Service: "service", - Resource: "resource", + TraceAndServe(handler, httptest.NewRecorder(), r, &ServeConfig{ + Service: "service", + Resource: "resource", }) span := mt.FinishedSpans()[0] @@ -332,15 +314,13 @@ func BenchmarkTraceAndServe(b *testing.B) { b.Fatal(err) } for i := 0; i < b.N; i++ { - cfg := TraceConfig{ - ResponseWriter: noopWriter{}, - Request: req, - Service: "service-name", - Resource: "resource-name", - FinishOpts: []ddtrace.FinishOption{}, - SpanOpts: []ddtrace.StartSpanOption{}, - QueryParams: false, + cfg := ServeConfig{ + Service: "service-name", + Resource: "resource-name", + FinishOpts: []ddtrace.FinishOption{}, + SpanOpts: []ddtrace.StartSpanOption{}, + QueryParams: false, } - TraceAndServe(handler, &cfg) + TraceAndServe(handler, noopWriter{}, req, &cfg) } } diff --git a/contrib/segmentio/kafka.go.v0/example_test.go b/contrib/segmentio/kafka.go.v0/example_test.go new file mode 100644 index 0000000000..11dbd328a3 --- /dev/null +++ b/contrib/segmentio/kafka.go.v0/example_test.go @@ -0,0 +1,57 @@ +// 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 kafka_test + +import ( + "context" + "log" + "time" + + kafkatrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/segmentio/kafka.go.v0" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + + kafka "github.com/segmentio/kafka-go" +) + +func ExampleWriter() { + w := kafkatrace.NewWriter(kafka.WriterConfig{ + Brokers: []string{"localhost:9092"}, + Topic: "some-topic", + }) + + // use slice as it passes the value by reference if you want message headers updated in kafkatrace + msgs := []kafka.Message{ + { + Key: []byte("key1"), + Value: []byte("value1"), + }, + } + if err := w.WriteMessages(context.Background(), msgs...); err != nil { + log.Fatal("Failed to write message", err) + } +} + +func ExampleReader() { + r := kafkatrace.NewReader(kafka.ReaderConfig{ + Brokers: []string{"localhost:9092"}, + Topic: "some-topic", + GroupID: "group-id", + SessionTimeout: 30 * time.Second, + }) + msg, err := r.ReadMessage(context.Background()) + if err != nil { + log.Fatal("Failed to read message", err) + } + + // create a child span using span id and trace id in message header + spanContext, err := kafkatrace.ExtractSpanContext(msg) + if err != nil { + log.Fatal("Failed to extract span context from carrier", err) + } + operationName := "child-span" + s := tracer.StartSpan(operationName, tracer.ChildOf(spanContext)) + defer s.Finish() +} diff --git a/contrib/segmentio/kafka.go.v0/headers.go b/contrib/segmentio/kafka.go.v0/headers.go new file mode 100644 index 0000000000..ce19449d5a --- /dev/null +++ b/contrib/segmentio/kafka.go.v0/headers.go @@ -0,0 +1,54 @@ +// 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 kafka + +import ( + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + + "github.com/segmentio/kafka-go" +) + +// A messageCarrier implements TextMapReader/TextMapWriter for extracting/injecting traces on a kafka.Message +type messageCarrier struct { + msg *kafka.Message +} + +var _ interface { + tracer.TextMapReader + tracer.TextMapWriter +} = (*messageCarrier)(nil) + +// ForeachKey conforms to the TextMapReader interface. +func (c messageCarrier) ForeachKey(handler func(key, val string) error) error { + for _, h := range c.msg.Headers { + err := handler(h.Key, string(h.Value)) + if err != nil { + return err + } + } + return nil +} + +// Set implements TextMapWriter +func (c messageCarrier) Set(key, val string) { + // ensure uniqueness of keys + for i := 0; i < len(c.msg.Headers); i++ { + if string(c.msg.Headers[i].Key) == key { + c.msg.Headers = append(c.msg.Headers[:i], c.msg.Headers[i+1:]...) + i-- + } + } + c.msg.Headers = append(c.msg.Headers, kafka.Header{ + Key: key, + Value: []byte(val), + }) +} + +// ExtractSpanContext retrieves the SpanContext from a kafka.Message +func ExtractSpanContext(msg kafka.Message) (ddtrace.SpanContext, error) { + return tracer.Extract(messageCarrier{&msg}) +} diff --git a/contrib/segmentio/kafka.go.v0/kafka.go b/contrib/segmentio/kafka.go.v0/kafka.go new file mode 100644 index 0000000000..2c580a064e --- /dev/null +++ b/contrib/segmentio/kafka.go.v0/kafka.go @@ -0,0 +1,148 @@ +// 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 kafka + +import ( + "context" + "math" + + "github.com/segmentio/kafka-go" + + "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/log" +) + +// NewReader calls kafka.NewReader and wraps the resulting Consumer. +func NewReader(conf kafka.ReaderConfig, opts ...Option) *Reader { + return WrapReader(kafka.NewReader(conf), opts...) +} + +// NewWriter calls kafka.NewWriter and wraps the resulting Producer. +func NewWriter(conf kafka.WriterConfig, opts ...Option) *Writer { + return WrapWriter(kafka.NewWriter(conf), opts...) +} + +// WrapReader wraps a kafka.Reader so that any consumed events are traced. +func WrapReader(c *kafka.Reader, opts ...Option) *Reader { + wrapped := &Reader{ + Reader: c, + cfg: newConfig(opts...), + } + log.Debug("contrib/confluentinc/confluent-kafka.go.v0/kafka: Wrapping Reader: %#v", wrapped.cfg) + return wrapped +} + +// A Reader wraps a kafka.Reader. +type Reader struct { + *kafka.Reader + cfg *config + prev ddtrace.Span +} + +func (r *Reader) startSpan(ctx context.Context, msg *kafka.Message) ddtrace.Span { + opts := []tracer.StartSpanOption{ + tracer.ServiceName(r.cfg.consumerServiceName), + tracer.ResourceName("Consume Topic " + msg.Topic), + tracer.SpanType(ext.SpanTypeMessageConsumer), + tracer.Tag("partition", msg.Partition), + tracer.Tag("offset", msg.Offset), + tracer.Measured(), + } + if !math.IsNaN(r.cfg.analyticsRate) { + opts = append(opts, tracer.Tag(ext.EventSampleRate, r.cfg.analyticsRate)) + } + // kafka supports headers, so try to extract a span context + carrier := messageCarrier{msg} + if spanctx, err := tracer.Extract(carrier); err == nil { + opts = append(opts, tracer.ChildOf(spanctx)) + } + span, _ := tracer.StartSpanFromContext(ctx, "kafka.consume", opts...) + // reinject the span context so consumers can pick it up + if err := tracer.Inject(span.Context(), carrier); err != nil { + log.Debug("contrib/segmentio/kafka.go.v0: Failed to inject span context into carrier, %v", err) + } + return span +} + +// Close calls the underlying Reader.Close and if polling is enabled, finishes +// any remaining span. +func (r *Reader) Close() error { + err := r.Reader.Close() + if r.prev != nil { + r.prev.Finish() + r.prev = nil + } + return err +} + +// ReadMessage polls the consumer for a message. Message will be traced. +func (r *Reader) ReadMessage(ctx context.Context) (kafka.Message, error) { + if r.prev != nil { + r.prev.Finish() + r.prev = nil + } + msg, err := r.Reader.ReadMessage(ctx) + if err != nil { + return kafka.Message{}, err + } + r.prev = r.startSpan(ctx, &msg) + return msg, nil +} + +// WrapWriter wraps a kafka.Writer so requests are traced. +func WrapWriter(w *kafka.Writer, opts ...Option) *Writer { + writer := &Writer{ + Writer: w, + cfg: newConfig(opts...), + } + log.Debug("contrib/segmentio/kafka.go.v0: Wrapping Writer: %#v", writer.cfg) + return writer +} + +// Writer wraps a kafka.Writer with tracing config data +type Writer struct { + *kafka.Writer + cfg *config +} + +func (w *Writer) startSpan(ctx context.Context, msg *kafka.Message) ddtrace.Span { + opts := []tracer.StartSpanOption{ + tracer.ServiceName(w.cfg.producerServiceName), + tracer.ResourceName("Produce Topic " + w.Writer.Topic), + tracer.SpanType(ext.SpanTypeMessageProducer), + } + if !math.IsNaN(w.cfg.analyticsRate) { + opts = append(opts, tracer.Tag(ext.EventSampleRate, w.cfg.analyticsRate)) + } + carrier := messageCarrier{msg} + span, _ := tracer.StartSpanFromContext(ctx, "kafka.produce", opts...) + err := tracer.Inject(span.Context(), carrier) + log.Debug("contrib/segmentio/kafka.go.v0: Failed to inject span context into carrier, %v", err) + return span +} + +func finishSpan(span ddtrace.Span, partition int, offset int64, err error) { + span.SetTag("partition", partition) + span.SetTag("offset", offset) + span.Finish(tracer.WithError(err)) +} + +// WriteMessages calls kafka.go.v0.Writer.WriteMessages and traces the requests. +func (w *Writer) WriteMessages(ctx context.Context, msgs ...kafka.Message) error { + // although there's only one call made to the SyncProducer, the messages are + // treated individually, so we create a span for each one + spans := make([]ddtrace.Span, len(msgs)) + for i := range msgs { + spans[i] = w.startSpan(ctx, &msgs[i]) + } + err := w.Writer.WriteMessages(ctx, msgs...) + for i, span := range spans { + finishSpan(span, msgs[i].Partition, msgs[i].Offset, err) + } + return err +} diff --git a/contrib/segmentio/kafka.go.v0/kafka_test.go b/contrib/segmentio/kafka.go.v0/kafka_test.go new file mode 100644 index 0000000000..31733b4f98 --- /dev/null +++ b/contrib/segmentio/kafka.go.v0/kafka_test.go @@ -0,0 +1,92 @@ +// 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 kafka + +import ( + "context" + "os" + "testing" + "time" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" + + kafka "github.com/segmentio/kafka-go" + "github.com/stretchr/testify/assert" +) + +const ( + testGroupID = "gosegtest" + testTopic = "gosegtest" +) + +func skipIntegrationTest(t *testing.T) { + if _, ok := os.LookupEnv("INTEGRATION"); !ok { + t.Skip("🚧 Skipping integration test (INTEGRATION environment variable is not set)") + } +} + +/* +to setup the integration test locally run: + docker-compose -f local_testing.yaml up +*/ + +func TestConsumerFunctional(t *testing.T) { + skipIntegrationTest(t) + mt := mocktracer.Start() + defer mt.Stop() + + kw := &kafka.Writer{ + Addr: kafka.TCP("localhost:9092"), + Topic: testTopic, + RequiredAcks: kafka.RequireOne, + } + + w := WrapWriter(kw, WithAnalyticsRate(0.1)) + msg1 := []kafka.Message{ + { + Key: []byte("key1"), + Value: []byte("value1"), + }, + } + err := w.WriteMessages(context.Background(), msg1...) + assert.NoError(t, err, "Expected to write message to topic") + err = w.Close() + assert.NoError(t, err) + + tctx, _ := context.WithTimeout(context.Background(), 30*time.Second) + r := NewReader(kafka.ReaderConfig{ + Brokers: []string{"localhost:9092"}, + GroupID: testGroupID, + Topic: testTopic, + }) + msg2, err := r.ReadMessage(tctx) + assert.NoError(t, err, "Expected to consume message") + assert.Equal(t, msg1[0].Value, msg2.Value, "Values should be equal") + r.Close() + + // now verify the spans + spans := mt.FinishedSpans() + assert.Len(t, spans, 2) + // they should be linked via headers + assert.Equal(t, spans[0].TraceID(), spans[1].TraceID(), "Trace IDs should match") + + s0 := spans[0] // produce + assert.Equal(t, "kafka.produce", s0.OperationName()) + assert.Equal(t, "kafka", s0.Tag(ext.ServiceName)) + assert.Equal(t, "Produce Topic "+testTopic, s0.Tag(ext.ResourceName)) + assert.Equal(t, 0.1, s0.Tag(ext.EventSampleRate)) + assert.Equal(t, "queue", s0.Tag(ext.SpanType)) + assert.Equal(t, 0, s0.Tag("partition")) + + s1 := spans[1] // consume + assert.Equal(t, "kafka.consume", s1.OperationName()) + assert.Equal(t, "kafka", s1.Tag(ext.ServiceName)) + assert.Equal(t, "Consume Topic "+testTopic, s1.Tag(ext.ResourceName)) + assert.Equal(t, nil, s1.Tag(ext.EventSampleRate)) + assert.Equal(t, "queue", s1.Tag(ext.SpanType)) + assert.Equal(t, 0, s1.Tag("partition")) +} diff --git a/contrib/segmentio/kafka.go.v0/local_testing.yaml b/contrib/segmentio/kafka.go.v0/local_testing.yaml new file mode 100644 index 0000000000..a770f93804 --- /dev/null +++ b/contrib/segmentio/kafka.go.v0/local_testing.yaml @@ -0,0 +1,32 @@ +version: "3" +services: + zookeeper: + image: 'bitnami/zookeeper:latest' + ports: + - '2181:2181' + environment: + - ALLOW_ANONYMOUS_LOGIN=yes + kafka: + image: 'bitnami/kafka:2' + ports: + - '9092:9092' + environment: + - KAFKA_BROKER_ID=1 + - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092 + - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://127.0.0.1:9092 + - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 + - ALLOW_PLAINTEXT_LISTENER=yes + depends_on: + - zookeeper + create-topics: + image: confluentinc/cp-kafka:5.5.0 + hostname: create-topics + container_name: create-topics + depends_on: + - kafka + command: " + bash -c 'sleep 5 && \ + kafka-topics --create --zookeeper zookeeper:2181 --partitions 1 --replication-factor 1 --topic gosegtest'" + environment: + KAFKA_BROKER_ID: ignored + KAFKA_ZOOKEEPER_CONNECT: ignored \ No newline at end of file diff --git a/contrib/segmentio/kafka.go.v0/option.go b/contrib/segmentio/kafka.go.v0/option.go new file mode 100644 index 0000000000..e5ac9afb66 --- /dev/null +++ b/contrib/segmentio/kafka.go.v0/option.go @@ -0,0 +1,72 @@ +// 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 kafka + +import ( + "math" + + "gopkg.in/DataDog/dd-trace-go.v1/internal" + "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" +) + +type config struct { + consumerServiceName string + producerServiceName string + analyticsRate float64 +} + +// An Option customizes the config. +type Option func(cfg *config) + +func newConfig(opts ...Option) *config { + cfg := &config{ + consumerServiceName: "kafka", + producerServiceName: "kafka", + // analyticsRate: globalconfig.AnalyticsRate(), + analyticsRate: math.NaN(), + } + if internal.BoolEnv("DD_TRACE_KAFKA_ANALYTICS_ENABLED", false) { + cfg.analyticsRate = 1.0 + } + if svc := globalconfig.ServiceName(); svc != "" { + cfg.consumerServiceName = svc + } + for _, opt := range opts { + opt(cfg) + } + return cfg +} + +// WithServiceName sets the config service name to serviceName. +func WithServiceName(serviceName string) Option { + return func(cfg *config) { + cfg.consumerServiceName = serviceName + cfg.producerServiceName = serviceName + } +} + +// WithAnalytics enables Trace Analytics for all started spans. +func WithAnalytics(on bool) Option { + return func(cfg *config) { + 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) Option { + return func(cfg *config) { + if rate >= 0.0 && rate <= 1.0 { + cfg.analyticsRate = rate + } else { + cfg.analyticsRate = math.NaN() + } + } +} diff --git a/contrib/segmentio/kafka.go.v0/option_test.go b/contrib/segmentio/kafka.go.v0/option_test.go new file mode 100644 index 0000000000..c97e149801 --- /dev/null +++ b/contrib/segmentio/kafka.go.v0/option_test.go @@ -0,0 +1,46 @@ +// 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 kafka + +import ( + "math" + "testing" + + "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" + + "github.com/stretchr/testify/assert" +) + +func TestAnalyticsSettings(t *testing.T) { + t.Run("defaults", func(t *testing.T) { + cfg := newConfig() + assert.True(t, math.IsNaN(cfg.analyticsRate)) + }) + + t.Run("global", func(t *testing.T) { + t.Skip("global flag disabled") + rate := globalconfig.AnalyticsRate() + defer globalconfig.SetAnalyticsRate(rate) + globalconfig.SetAnalyticsRate(0.4) + + cfg := newConfig() + assert.Equal(t, 0.4, cfg.analyticsRate) + }) + + t.Run("enabled", func(t *testing.T) { + cfg := newConfig(WithAnalytics(true)) + assert.Equal(t, 1.0, cfg.analyticsRate) + }) + + t.Run("override", func(t *testing.T) { + rate := globalconfig.AnalyticsRate() + defer globalconfig.SetAnalyticsRate(rate) + globalconfig.SetAnalyticsRate(0.4) + + cfg := newConfig(WithAnalyticsRate(0.2)) + assert.Equal(t, 0.2, cfg.analyticsRate) + }) +} diff --git a/contrib/zenazn/goji.v1/web/goji.go b/contrib/zenazn/goji.v1/web/goji.go index 8f119a64df..232f4c9e84 100644 --- a/contrib/zenazn/goji.v1/web/goji.go +++ b/contrib/zenazn/goji.v1/web/goji.go @@ -12,7 +12,7 @@ import ( "net/http" "sync" - "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httputil" + httptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http" "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/log" @@ -48,13 +48,11 @@ func Middleware(opts ...Option) func(*web.C, http.Handler) http.Handler { log.Warn("contrib/zenazn/goji.v1/web: routes are unavailable. To enable them add the goji Router middleware before the tracer middleware.") }) } - httputil.TraceAndServe(h, &httputil.TraceConfig{ - ResponseWriter: w, - Request: r, - Service: cfg.serviceName, - Resource: resource, - FinishOpts: cfg.finishOpts, - SpanOpts: cfg.spanOpts, + httptrace.TraceAndServe(h, w, r, &httptrace.ServeConfig{ + Service: cfg.serviceName, + Resource: resource, + FinishOpts: cfg.finishOpts, + SpanOpts: cfg.spanOpts, }) }) } diff --git a/ddtrace/ddtrace.go b/ddtrace/ddtrace.go index 9aeafebe8d..06a1732fc1 100644 --- a/ddtrace/ddtrace.go +++ b/ddtrace/ddtrace.go @@ -25,12 +25,6 @@ type Tracer interface { // StartSpan starts a span with the given operation name and options. StartSpan(operationName string, opts ...StartSpanOption) Span - // StartSpanFromContext starts a span with the given operation name and - // options. If a span is found in the context, it will be used as the parent - // of the resulting span. If the ChildOf option is passed, the span from - // context will take precedence over it as the parent span. - StartSpanFromContext(ctx context.Context, operationName string, opts ...StartSpanOption) (Span, context.Context) - // Extract extracts a span context from a given carrier. Note that baggage item // keys will always be lower-cased to maintain consistency. It is impossible to // maintain the original casing due to MIME header canonicalization standards. @@ -131,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/ext/tags.go b/ddtrace/ext/tags.go index a18a60833b..51d1cd3645 100644 --- a/ddtrace/ext/tags.go +++ b/ddtrace/ext/tags.go @@ -15,6 +15,7 @@ const ( TargetPort = "out.port" // SamplingPriority is the tag that marks the sampling priority of a span. + // Deprecated in favor of ManualKeep and ManualDrop. SamplingPriority = "sampling.priority" // SQLType sets the sql type tag. diff --git a/ddtrace/internal/globaltracer.go b/ddtrace/internal/globaltracer.go index c086d0ee01..239e976267 100644 --- a/ddtrace/internal/globaltracer.go +++ b/ddtrace/internal/globaltracer.go @@ -6,7 +6,6 @@ package internal // import "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/internal" import ( - "context" "sync" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" @@ -52,11 +51,6 @@ func (NoopTracer) StartSpan(operationName string, opts ...ddtrace.StartSpanOptio return NoopSpan{} } -// StartSpanFromContext implements ddtrace.Tracer. -func (NoopTracer) StartSpanFromContext(ctx context.Context, operationName string, options ...ddtrace.StartSpanOption) (ddtrace.Span, context.Context) { - return NoopSpan{}, context.Background() -} - // SetServiceInfo implements ddtrace.Tracer. func (NoopTracer) SetServiceInfo(name, app, appType string) {} diff --git a/ddtrace/mocktracer/mocktracer.go b/ddtrace/mocktracer/mocktracer.go index 8edda50181..bcdb6953e9 100644 --- a/ddtrace/mocktracer/mocktracer.go +++ b/ddtrace/mocktracer/mocktracer.go @@ -13,7 +13,6 @@ package mocktracer import ( - "context" "strconv" "strings" "sync" @@ -74,28 +73,17 @@ func (*mocktracer) Stop() { } func (t *mocktracer) StartSpan(operationName string, opts ...ddtrace.StartSpanOption) ddtrace.Span { - span, _ := t.StartSpanFromContext(context.Background(), operationName, opts...) - return span -} - -func (t *mocktracer) StartSpanFromContext(ctx context.Context, operationName string, opts ...ddtrace.StartSpanOption) (ddtrace.Span, context.Context) { var cfg ddtrace.StartSpanConfig for _, fn := range opts { fn(&cfg) } - if ctx == nil { - ctx = context.Background() - } else if s, ok := tracer.SpanFromContext(ctx); ok { - // span in ctx overwrite ChildOf() parent if any - cfg.Parent = s.Context() - } span := newSpan(t, operationName, &cfg) t.Lock() t.openSpans[span.SpanID()] = span t.Unlock() - return span, tracer.ContextWithSpan(ctx, span) + return span } func (t *mocktracer) OpenSpans() []Span { diff --git a/ddtrace/tracer/context.go b/ddtrace/tracer/context.go index a8c0edfba5..0194b2cefc 100644 --- a/ddtrace/tracer/context.go +++ b/ddtrace/tracer/context.go @@ -39,5 +39,24 @@ func SpanFromContext(ctx context.Context) (Span, bool) { // is found in the context, it will be used as the parent of the resulting span. If the ChildOf // option is passed, the span from context will take precedence over it as the parent span. func StartSpanFromContext(ctx context.Context, operationName string, opts ...StartSpanOption) (Span, context.Context) { - return internal.GetGlobalTracer().StartSpanFromContext(ctx, operationName, opts...) + // copy opts in case the caller reuses the slice in parallel + // we will add at least 1, at most 2 items + optsLocal := make([]StartSpanOption, len(opts), len(opts)+2) + copy(optsLocal, opts) + + if ctx == nil { + // default to context.Background() to avoid panics on Go >= 1.15 + ctx = context.Background() + } else if s, ok := SpanFromContext(ctx); ok { + optsLocal = append(optsLocal, ChildOf(s.Context())) + } + optsLocal = append(optsLocal, withContext(ctx)) + s := StartSpan(operationName, optsLocal...) + 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/context_test.go b/ddtrace/tracer/context_test.go index ab6ad3b53f..53f4374151 100644 --- a/ddtrace/tracer/context_test.go +++ b/ddtrace/tracer/context_test.go @@ -76,6 +76,37 @@ func TestStartSpanFromContext(t *testing.T) { assert.Equal("/", got.Resource) } +func TestStartSpanFromContextRace(t *testing.T) { + _, _, _, stop := startTestTracer(t) + defer stop() + + // Start 100 goroutines that create child spans with StartSpanFromContext in parallel, + // with a shared options slice. The child spans should get parented to the correct spans + const contextKey = "key" + const numContexts = 100 + options := make([]StartSpanOption, 0, 3) + outputValues := make(chan uint64, numContexts) + var expectedTraceIDs []uint64 + for i := 0; i < numContexts; i++ { + parent, childCtx := StartSpanFromContext(context.Background(), "parent") + expectedTraceIDs = append(expectedTraceIDs, parent.Context().TraceID()) + go func() { + span, _ := StartSpanFromContext(childCtx, "testoperation", options...) + defer span.Finish() + outputValues <- span.Context().TraceID() + }() + parent.Finish() + } + + // collect the outputs + var outputs []uint64 + for i := 0; i < numContexts; i++ { + outputs = append(outputs, <-outputValues) + } + assert.Len(t, outputs, numContexts) + assert.ElementsMatch(t, outputs, expectedTraceIDs) +} + func TestStartSpanFromNilContext(t *testing.T) { _, _, _, stop := startTestTracer(t) defer stop() diff --git a/ddtrace/tracer/log.go b/ddtrace/tracer/log.go index 96a688341c..f609c4b774 100644 --- a/ddtrace/tracer/log.go +++ b/ddtrace/tracer/log.go @@ -17,40 +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 - 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 - 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. + 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. @@ -79,29 +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, - ServiceMappings: t.config.serviceMappings, - 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.config.agent, - AppSec: appsec.Enabled(), + 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 cf90a99eec..8ee9bc7c1a 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":"","service_mappings":null,"tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"health_metrics_enabled":false,"dd_version":"","architecture":"[^"]*","global_service":"","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":false,"Stats":false,"StatsdPort":0}}`, 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)|(true)),"profiler_endpoints_enabled":((false)|(true)),"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) { @@ -55,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":"","service_mappings":{"initial_service":"new_service"},"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":((true)|(false)),"agent_features":{"DropP0s":false,"Stats":false,"StatsdPort":0}}`, 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)|(true)),"profiler_endpoints_enabled":((false)|(true)),"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) { @@ -69,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","service_mappings":null,"tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"health_metrics_enabled":false,"dd_version":"","architecture":"[^"]*","global_service":"","lambda_mode":"false","appsec":((true)|(false)),"agent_features":{"DropP0s":false,"Stats":false,"StatsdPort":0}}`, 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)|(true)),"profiler_endpoints_enabled":((false)|(true)),"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) { @@ -81,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":"","service_mappings":null,"tags":{"runtime-id":"[^"]*"},"runtime_metrics_enabled":false,"health_metrics_enabled":false,"dd_version":"","architecture":"[^"]*","global_service":"","lambda_mode":"true","appsec":((true)|(false)),"agent_features":{"DropP0s":false,"Stats":false,"StatsdPort":0}}`, 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)|(true)),"profiler_endpoints_enabled":((false)|(true)),"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 f055679c9b..f2799288c6 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -38,6 +38,9 @@ var ( // defaultSocketDSD specifies the socket path to use for connecting to the statsd server. // Replaced in tests defaultSocketDSD = "/var/run/datadog/dsd.socket" + + // defaultMaxTagsHeaderLen specifies the default maximum length of the X-Datadog-Tags header value. + defaultMaxTagsHeaderLen = 512 ) // config holds the tracer configuration. @@ -174,7 +177,7 @@ func forEachStringTag(str string, fn func(key string, val string)) { func newConfig(opts ...StartOption) *config { c := new(config) c.sampler = NewAllSampler() - c.agentAddr = defaultAddress + c.agentAddr = resolveAgentAddr(defaultAddress) c.httpClient = defaultHTTPClient() if internal.BoolEnv("DD_TRACE_ANALYTICS_ENABLED", false) { @@ -220,9 +223,8 @@ func newConfig(opts ...StartOption) *config { 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) + c.profilerEndpoints = internal.BoolEnv(traceprof.EndpointEnvVar, true) + c.profilerHotspots = internal.BoolEnv(traceprof.CodeHotspotsEnvVar, true) for _, fn := range opts { fn(c) @@ -256,7 +258,9 @@ func newConfig(opts ...StartOption) *config { c.transport = newHTTPTransport(c.agentAddr, c.httpClient) } if c.propagator == nil { - c.propagator = NewPropagator(nil) + c.propagator = NewPropagator(&PropagatorConfig{ + MaxTagsHeaderLen: internal.IntEnv("DD_TRACE_TAGS_PROPAGATION_MAX_LENGTH", defaultMaxTagsHeaderLen), + }) } if c.logger != nil { log.UseLogger(c.logger) @@ -403,10 +407,7 @@ func (c *config) loadAgentFeatures() { 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.Stats = true } } c.agent.featureFlags = make(map[string]struct{}, len(info.FeatureFlags)) @@ -415,6 +416,14 @@ func (c *config) loadAgentFeatures() { } } +func (c *config) canComputeStats() bool { + return c.agent.Stats && c.HasFeature("discovery") +} + +func (c *config) canDropP0s() bool { + return c.canComputeStats() && c.agent.DropP0s +} + func statsTags(c *config) []string { tags := []string{ "lang:go", @@ -680,7 +689,7 @@ func WithLogStartup(enabled bool) StartOption { // 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. +// DD_PROFILING_CODE_HOTSPOTS_COLLECTION_ENABLED env variable or true. func WithProfilerCodeHotspots(enabled bool) StartOption { return func(c *config) { c.profilerHotspots = enabled @@ -690,9 +699,10 @@ func WithProfilerCodeHotspots(enabled bool) StartOption { // 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 or rpc. 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. +// 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 +// true. func WithProfilerEndpoints(enabled bool) StartOption { return func(c *config) { c.profilerEndpoints = enabled @@ -731,9 +741,12 @@ func SpanType(name string) StartSpanOption { return Tag(ext.SpanType, name) } +var measuredTag = Tag(keyMeasured, 1) + // Measured marks this span to be measured for metrics and stats calculations. func Measured() StartSpanOption { - return Tag(keyMeasured, 1) + // cache a global instance of this tag: saves one alloc/call + return measuredTag } // WithSpanID sets the SpanID on the started span, instead of using a random number. @@ -753,6 +766,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 { @@ -812,3 +832,41 @@ func StackFrames(n, skip uint) FinishOption { cfg.SkipStackFrames = skip } } + +// UserMonitoringOption represents a function that can be provided as a parameter to SetUser. +type UserMonitoringOption func(Span) + +// WithUserEmail returns the option setting the email of the authenticated user. +func WithUserEmail(email string) UserMonitoringOption { + return func(s Span) { + s.SetTag("usr.email", email) + } +} + +// WithUserName returns the option setting the name of the authenticated user. +func WithUserName(name string) UserMonitoringOption { + return func(s Span) { + s.SetTag("usr.name", name) + } +} + +// WithUserSessionID returns the option setting the session ID of the authenticated user. +func WithUserSessionID(sessionID string) UserMonitoringOption { + return func(s Span) { + s.SetTag("usr.session_id", sessionID) + } +} + +// WithUserRole returns the option setting the role of the authenticated user. +func WithUserRole(role string) UserMonitoringOption { + return func(s Span) { + s.SetTag("usr.role", role) + } +} + +// WithUserScope returns the option setting the scope (authorizations) of the authenticated user +func WithUserScope(scope string) UserMonitoringOption { + return func(s Span) { + s.SetTag("usr.scope", scope) + } +} diff --git a/ddtrace/tracer/option_test.go b/ddtrace/tracer/option_test.go index 410b4e43ee..a055c4fb7a 100644 --- a/ddtrace/tracer/option_test.go +++ b/ddtrace/tracer/option_test.go @@ -186,7 +186,7 @@ func TestLoadAgentFeatures(t *testing.T) { "a": struct{}{}, "b": struct{}{}, }) - assert.False(t, cfg.agent.Stats) + assert.True(t, cfg.agent.Stats) assert.True(t, cfg.agent.HasFlag("a")) assert.True(t, cfg.agent.HasFlag("b")) }) @@ -303,6 +303,14 @@ func TestTracerOptionsDefaults(t *testing.T) { }) }) + t.Run("env-agentAddr", func(t *testing.T) { + os.Setenv("DD_AGENT_HOST", "trace-agent") + defer os.Unsetenv("DD_AGENT_HOST") + tracer := newTracer() + c := tracer.config + assert.Equal(t, "trace-agent:8126", c.agentAddr) + }) + t.Run("override", func(t *testing.T) { os.Setenv("DD_ENV", "dev") defer os.Unsetenv("DD_ENV") @@ -367,28 +375,28 @@ func TestTracerOptionsDefaults(t *testing.T) { t.Run("profiler-endpoints", func(t *testing.T) { t.Run("default", func(t *testing.T) { c := newConfig() - assert.False(t, c.profilerEndpoints) + assert.True(t, c.profilerEndpoints) }) t.Run("override", func(t *testing.T) { - os.Setenv(traceprof.EndpointEnvVar, "true") + os.Setenv(traceprof.EndpointEnvVar, "false") defer os.Unsetenv(traceprof.EndpointEnvVar) c := newConfig() - assert.True(t, c.profilerEndpoints) + assert.False(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) + assert.True(t, c.profilerHotspots) }) t.Run("override", func(t *testing.T) { - os.Setenv(traceprof.CodeHotspotsEnvVar, "true") + os.Setenv(traceprof.CodeHotspotsEnvVar, "false") defer os.Unsetenv(traceprof.CodeHotspotsEnvVar) c := newConfig() - assert.True(t, c.profilerHotspots) + assert.False(t, c.profilerHotspots) }) }) diff --git a/ddtrace/tracer/sampler.go b/ddtrace/tracer/sampler.go index e9931686c5..e85218a47f 100644 --- a/ddtrace/tracer/sampler.go +++ b/ddtrace/tracer/sampler.go @@ -20,6 +20,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/internal/log" + "gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames" "golang.org/x/time/rate" ) @@ -150,9 +151,9 @@ func (ps *prioritySampler) getRate(spn *span) float64 { func (ps *prioritySampler) apply(spn *span) { rate := ps.getRate(spn) if sampledByRate(spn.TraceID, rate) { - spn.SetTag(ext.SamplingPriority, ext.PriorityAutoKeep) + spn.setSamplingPriority(ext.PriorityAutoKeep, samplernames.AgentRate, rate) } else { - spn.SetTag(ext.SamplingPriority, ext.PriorityAutoReject) + spn.setSamplingPriority(ext.PriorityAutoReject, samplernames.AgentRate, rate) } spn.SetTag(keySamplingPriorityRate, rate) } @@ -311,15 +312,15 @@ func (rs *rulesSampler) apply(span *span) bool { func (rs *rulesSampler) applyRate(span *span, rate float64, now time.Time) { span.SetTag(keyRulesSamplerAppliedRate, rate) if !sampledByRate(span.TraceID, rate) { - span.SetTag(ext.SamplingPriority, ext.PriorityUserReject) + span.setSamplingPriority(ext.PriorityUserReject, samplernames.RuleRate, rate) return } sampled, rate := rs.limiter.allowOne(now) if sampled { - span.SetTag(ext.SamplingPriority, ext.PriorityUserKeep) + span.setSamplingPriority(ext.PriorityUserKeep, samplernames.RuleRate, rate) } else { - span.SetTag(ext.SamplingPriority, ext.PriorityUserReject) + span.setSamplingPriority(ext.PriorityUserReject, samplernames.RuleRate, rate) } span.SetTag(keyRulesSamplerLimiterRate, rate) } diff --git a/ddtrace/tracer/span.go b/ddtrace/tracer/span.go index 630271989f..8b63dabb46 100644 --- a/ddtrace/tracer/span.go +++ b/ddtrace/tracer/span.go @@ -10,6 +10,7 @@ package tracer import ( "context" "fmt" + "math" "os" "reflect" "runtime" @@ -25,6 +26,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/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/samplernames" "github.com/DataDog/datadog-agent/pkg/obfuscate" "github.com/tinylib/msgp/msgp" @@ -148,6 +150,27 @@ func (s *span) SetTag(key string, value interface{}) { s.setMeta(key, fmt.Sprint(value)) } +// setSamplingPriority locks then span, then updates the sampling priority. +// It also updates the trace's sampling priority. +func (s *span) setSamplingPriority(priority int, sampler samplernames.SamplerName, rate float64) { + s.Lock() + defer s.Unlock() + s.setSamplingPriorityLocked(priority, sampler, rate) +} + +// setSamplingPriorityLocked updates the sampling priority. +// It also updates the trace's sampling priority. +func (s *span) setSamplingPriorityLocked(priority int, sampler samplernames.SamplerName, rate float64) { + // We don't lock spans when flushing, so we could have a data race when + // modifying a span as it's being flushed. This protects us against that + // race, since spans are marked `finished` before we flush them. + if s.finished { + return + } + s.setMetric(keySamplingPriority, float64(priority)) + s.context.setSamplingPriority(s.Service, priority, sampler, rate) +} + // setTagError sets the error tag. It accounts for various valid scenarios. // This method is not safe for concurrent use. func (s *span) setTagError(value interface{}, cfg errorConfig) { @@ -266,11 +289,11 @@ func (s *span) setTagBool(key string, v bool) { } case ext.ManualDrop: if v { - s.setMetric(ext.SamplingPriority, ext.PriorityUserReject) + s.setSamplingPriorityLocked(ext.PriorityUserReject, samplernames.Manual, math.NaN()) } case ext.ManualKeep: if v { - s.setMetric(ext.SamplingPriority, ext.PriorityUserKeep) + s.setSamplingPriorityLocked(ext.PriorityUserKeep, samplernames.Manual, math.NaN()) } default: if v { @@ -289,10 +312,14 @@ func (s *span) setMetric(key string, v float64) { } delete(s.Meta, key) switch key { + case ext.ManualKeep: + if v == float64(samplernames.AppSec) { + s.setSamplingPriorityLocked(ext.PriorityUserKeep, samplernames.AppSec, math.NaN()) + } case ext.SamplingPriority: - // setting sampling priority per spec - s.Metrics[keySamplingPriority] = v - s.context.setSamplingPriority(int(v)) + // ext.SamplingPriority is deprecated in favor of ext.ManualKeep and ext.ManualDrop. + // We have it here for backward compatibility. + s.setSamplingPriorityLocked(int(v), samplernames.Manual, math.NaN()) default: s.Metrics[key] = v } @@ -363,8 +390,7 @@ func (s *span) finish(finishTime int64) { keep := true if t, ok := internal.GetGlobalTracer().(*tracer); ok { // we have an active tracer - feats := t.config.agent - if feats.Stats && shouldComputeStats(s) { + if t.config.canComputeStats() && shouldComputeStats(s) { // the agent supports computed stats select { case t.stats.In <- newAggregableSpan(s, t.obfuscator): @@ -373,7 +399,7 @@ func (s *span) finish(finishTime int64) { log.Error("Stats channel full, disregarding span.") } } - if feats.DropP0s { + if t.config.canDropP0s() { // the agent supports dropping p0's in the client keep = shouldKeep(s) } @@ -525,6 +551,7 @@ func (s *span) Format(f fmt.State, c rune) { const ( keySamplingPriority = "_sampling_priority_v1" keySamplingPriorityRate = "_dd.agent_psr" + keyUpstreamServices = "_dd.p.upstream_services" keyOrigin = "_dd.origin" keyHostname = "_dd.hostname" keyRulesSamplerAppliedRate = "_dd.rule_psr" diff --git a/ddtrace/tracer/span_test.go b/ddtrace/tracer/span_test.go index 7730b4758e..0431bae885 100644 --- a/ddtrace/tracer/span_test.go +++ b/ddtrace/tracer/span_test.go @@ -456,7 +456,9 @@ func TestSpanError(t *testing.T) { span.Finish() span.SetTag(ext.Error, err) assert.Equal(int32(0), span.Error) - assert.Equal(nMeta, len(span.Meta)) + // '+1' is `_dd.p.upstream_services`, + // because we add it into Meta of the first span, when root is finished. + assert.Equal(nMeta+1, len(span.Meta)) assert.Equal("", span.Meta["error.msg"]) assert.Equal("", span.Meta["error.type"]) assert.Equal("", span.Meta["error.stack"]) diff --git a/ddtrace/tracer/spancontext.go b/ddtrace/tracer/spancontext.go index 950b7c7590..7297fa8349 100644 --- a/ddtrace/tracer/spancontext.go +++ b/ddtrace/tracer/spancontext.go @@ -6,6 +6,8 @@ package tracer import ( + "math" + "strconv" "sync" "sync/atomic" @@ -13,6 +15,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/internal" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" + "gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames" ) var _ ddtrace.SpanContext = (*spanContext)(nil) @@ -91,11 +94,11 @@ func (c *spanContext) ForeachBaggageItem(handler func(k, v string) bool) { } } -func (c *spanContext) setSamplingPriority(p int) { +func (c *spanContext) setSamplingPriority(service string, p int, sampler samplernames.SamplerName, rate float64) { if c.trace == nil { c.trace = newTrace() } - c.trace.setSamplingPriority(float64(p)) + c.trace.setSamplingPriority(service, p, sampler, rate) } func (c *spanContext) samplingPriority() (p int, ok bool) { @@ -144,13 +147,15 @@ const ( // priority, the root reference and a buffer of the spans which are part of the // trace, if these exist. type trace struct { - mu sync.RWMutex // guards below fields - spans []*span // all the spans that are part of this trace - finished int // the number of finished spans - full bool // signifies that the span buffer is full - priority *float64 // sampling priority - locked bool // specifies if the sampling priority can be altered - samplingDecision samplingDecision // samplingDecision indicates whether to send the trace to the agent. + mu sync.RWMutex // guards below fields + spans []*span // all the spans that are part of this trace + tags map[string]string // trace level tags + upstreamServices string // _dd.p.upstream_services value from the upstream service + finished int // the number of finished spans + full bool // signifies that the span buffer is full + priority *float64 // sampling priority + locked bool // specifies if the sampling priority can be altered + samplingDecision samplingDecision // samplingDecision indicates whether to send the trace to the agent. // root specifies the root of the trace, if known; it is nil when a span // context is extracted from a carrier, at which point there are no spans in @@ -191,10 +196,10 @@ func (t *trace) samplingPriority() (p int, ok bool) { return t.samplingPriorityLocked() } -func (t *trace) setSamplingPriority(p float64) { +func (t *trace) setSamplingPriority(service string, p int, sampler samplernames.SamplerName, rate float64) { t.mu.Lock() defer t.mu.Unlock() - t.setSamplingPriorityLocked(p) + t.setSamplingPriorityLocked(service, p, sampler, rate) } func (t *trace) keep() { @@ -205,7 +210,14 @@ func (t *trace) drop() { atomic.CompareAndSwapInt64((*int64)(&t.samplingDecision), int64(decisionNone), int64(decisionDrop)) } -func (t *trace) setSamplingPriorityLocked(p float64) { +func (t *trace) setTag(key, value string) { + if t.tags == nil { + t.tags = make(map[string]string, 1) + } + t.tags[key] = value +} + +func (t *trace) setSamplingPriorityLocked(service string, p int, sampler samplernames.SamplerName, rate float64) { if t.locked { return } @@ -217,7 +229,14 @@ func (t *trace) setSamplingPriorityLocked(p float64) { if t.priority == nil { t.priority = new(float64) } - *t.priority = p + *t.priority = float64(p) + if sampler != samplernames.Upstream { + if t.upstreamServices != "" { + t.setTag(keyUpstreamServices, t.upstreamServices+";"+compactUpstreamServices(service, p, sampler, rate)) + } else { + t.setTag(keyUpstreamServices, compactUpstreamServices(service, p, sampler, rate)) + } + } } // push pushes a new span into the trace. If the buffer is full, it returns @@ -240,7 +259,7 @@ func (t *trace) push(sp *span) { return } if v, ok := sp.Metrics[keySamplingPriority]; ok { - t.setSamplingPriorityLocked(v) + t.setSamplingPriorityLocked(sp.Service, int(v), samplernames.Upstream, math.NaN()) } t.spans = append(t.spans, sp) if haveTracer { @@ -269,6 +288,16 @@ func (t *trace) finishedOne(s *span) { t.root.setMetric(keySamplingPriority, *t.priority) t.locked = true } + if len(t.spans) > 0 && s == t.spans[0] { + // first span in chunk finished, lock down the tags + // + // TODO(barbayar): make sure this doesn't happen in vain when switching to + // the new wire format. We won't need to set the tags on the first span + // in the chunk there. + for k, v := range t.tags { + s.setMeta(k, v) + } + } if len(t.spans) != t.finished { return } @@ -292,3 +321,14 @@ func (t *trace) finishedOne(s *span) { } tr.pushTrace(t.spans) } + +func compactUpstreamServices(service string, priority int, sampler samplernames.SamplerName, rate float64) string { + sb64 := b64Encode(service) + p := strconv.Itoa(priority) + s := strconv.Itoa(int(sampler)) + r := "" + if !math.IsNaN(rate) { + r = strconv.FormatFloat(rate, 'f', 4, 64) + } + return sb64 + "|" + p + "|" + s + "|" + r +} diff --git a/ddtrace/tracer/spancontext_test.go b/ddtrace/tracer/spancontext_test.go index 7a00f2a071..38f91c2772 100644 --- a/ddtrace/tracer/spancontext_test.go +++ b/ddtrace/tracer/spancontext_test.go @@ -7,6 +7,7 @@ package tracer import ( "context" + "math" "strings" "sync" "testing" @@ -16,6 +17,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" + "gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames" ) func setupteardown(start, max int) func() { @@ -75,6 +77,21 @@ func TestAsyncSpanRace(t *testing.T) { } }() wg.Add(1) + go func() { + defer wg.Done() + select { + case <-done: + root.Finish() + for i := 0; i < 500; i++ { + for range root.(*span).Meta { + // this range simulates iterating over the meta map + // as we do when encoding msgpack upon flushing. + } + } + return + } + }() + wg.Add(1) go func() { defer wg.Done() select { @@ -405,6 +422,24 @@ func TestSpanContextIteratorBreak(t *testing.T) { assert.Len(t, got, 0) } +func TestBuildNewUpstreamServices(t *testing.T) { + var testCases = []struct { + service string + priority int + sampler samplernames.SamplerName + rate float64 + expected string + }{ + {"service-account", 1, samplernames.AgentRate, 0.99, "c2VydmljZS1hY2NvdW50|1|1|0.9900"}, + {"service-storage", 2, samplernames.Manual, math.NaN(), "c2VydmljZS1zdG9yYWdl|2|4|"}, + {"service-video", 1, samplernames.RuleRate, 1, "c2VydmljZS12aWRlbw|1|3|1.0000"}, + } + + for _, tt := range testCases { + assert.Equal(t, tt.expected, compactUpstreamServices(tt.service, tt.priority, tt.sampler, tt.rate)) + } +} + // testLogger implements a mock Printer. type testLogger struct { mu sync.RWMutex diff --git a/ddtrace/tracer/textmap.go b/ddtrace/tracer/textmap.go index 505ced0823..66a3047b4d 100644 --- a/ddtrace/tracer/textmap.go +++ b/ddtrace/tracer/textmap.go @@ -7,6 +7,7 @@ package tracer import ( "fmt" + "math" "net/http" "os" "strconv" @@ -15,6 +16,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/internal/log" + "gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames" ) // HTTPHeadersCarrier wraps an http.Header as a TextMapWriter and TextMapReader, allowing @@ -90,6 +92,9 @@ const ( // It is used with the Synthetics product and usually has the value "synthetics". const originHeader = "x-datadog-origin" +// traceTagsHeader holds the propagated trace tags +const traceTagsHeader = "x-datadog-tags" + // PropagatorConfig defines the configuration for initializing a propagator. type PropagatorConfig struct { // BaggagePrefix specifies the prefix that will be used to store baggage @@ -105,8 +110,15 @@ type PropagatorConfig struct { ParentHeader string // PriorityHeader specifies the map key that will be used to store the sampling priority. - // It deafults to DefaultPriorityHeader. + // It defaults to DefaultPriorityHeader. PriorityHeader string + + // MaxTagsHeaderLen specifies the maximum length of trace tags header value. + MaxTagsHeaderLen int + + // B3 specifies if B3 headers should be added for trace propagation. + // See https://github.com/openzipkin/b3-propagation + B3 bool } // NewPropagator returns a new propagator which uses TextMap to inject @@ -143,28 +155,39 @@ type chainedPropagator struct { } // getPropagators returns a list of propagators based on the list found in the -// given environment variable. If the list doesn't contain a value or has invalid -// values, the default propagator will be returned. +// given environment variable. If the list doesn't contain any valid values the +// default propagator will be returned. Any invalid values in the list will log +// a warning and be ignored. func getPropagators(cfg *PropagatorConfig, env string) []Propagator { dd := &propagator{cfg} ps := os.Getenv(env) + defaultPs := []Propagator{dd} + if cfg.B3 { + defaultPs = append(defaultPs, &propagatorB3{}) + } if ps == "" { - return []Propagator{dd} + return defaultPs } var list []Propagator + if cfg.B3 { + list = append(list, &propagatorB3{}) + } for _, v := range strings.Split(ps, ",") { switch strings.ToLower(v) { case "datadog": list = append(list, dd) case "b3": - list = append(list, &propagatorB3{}) + if !cfg.B3 { + // propagatorB3 hasn't already been added, add a new one. + list = append(list, &propagatorB3{}) + } default: log.Warn("unrecognized propagator: %s\n", v) } } if len(list) == 0 { // return the default - return []Propagator{dd} + return defaultPs } return list } @@ -265,9 +288,18 @@ func (p *propagator) extractTextMap(reader TextMapReader) (ddtrace.SpanContext, if err != nil { return ErrSpanContextCorrupted } - ctx.setSamplingPriority(priority) + ctx.setSamplingPriority("", priority, samplernames.Upstream, math.NaN()) case originHeader: ctx.origin = v + case traceTagsHeader: + if ctx.trace == nil { + ctx.trace = newTrace() + } + ctx.trace.tags, err = parsePropagatableTraceTags(v) + ctx.trace.upstreamServices = ctx.trace.tags[keyUpstreamServices] + if err != nil { + log.Warn("did not extract trace tags (err: %s)", err.Error()) + } default: if strings.HasPrefix(key, p.cfg.BaggagePrefix) { ctx.setBaggageItem(strings.TrimPrefix(key, p.cfg.BaggagePrefix), v) @@ -353,7 +385,7 @@ func (*propagatorB3) extractTextMap(reader TextMapReader) (ddtrace.SpanContext, if err != nil { return ErrSpanContextCorrupted } - ctx.setSamplingPriority(priority) + ctx.setSamplingPriority("", priority, samplernames.Upstream, math.NaN()) default: } return nil diff --git a/ddtrace/tracer/textmap_test.go b/ddtrace/tracer/textmap_test.go index b011598a7f..dd878f14fb 100644 --- a/ddtrace/tracer/textmap_test.go +++ b/ddtrace/tracer/textmap_test.go @@ -7,9 +7,11 @@ package tracer import ( "errors" + "fmt" "net/http" "os" "strconv" + "strings" "testing" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" @@ -173,6 +175,50 @@ func TestTextMapPropagatorOrigin(t *testing.T) { } } +func TestTextMapPropagatorInvalidTraceTagsHeader(t *testing.T) { + src := TextMapCarrier(map[string]string{ + DefaultTraceIDHeader: "1", + DefaultParentIDHeader: "1", + traceTagsHeader: "hello=world,=", // invalid value + }) + tracer := newTracer() + ctx, err := tracer.Extract(src) + assert.Nil(t, err) + sctx, ok := ctx.(*spanContext) + assert.True(t, ok) + assert.Equal(t, map[string]string(nil), sctx.trace.tags) +} + +func TestTextMapPropagatorTraceTagsTooLong(t *testing.T) { + tags := make([]string, 0) + for i := 0; i < 100; i++ { + tags = append(tags, fmt.Sprintf("_dd.p.tag%d=value%d", i, i)) + } + traceTags := strings.Join(tags, ",") + src := TextMapCarrier(map[string]string{ + DefaultPriorityHeader: "1", + DefaultTraceIDHeader: "1", + DefaultParentIDHeader: "1", + traceTagsHeader: traceTags, + }) + tracer := newTracer() + ctx, err := tracer.Extract(src) + assert.Nil(t, err) + sctx, ok := ctx.(*spanContext) + assert.True(t, ok) + child := tracer.StartSpan("test", ChildOf(sctx)) + childSpanID := child.Context().(*spanContext).spanID + assert.Equal(t, 100, len(sctx.trace.tags)) + dst := map[string]string{} + err = tracer.Inject(child.Context(), TextMapCarrier(dst)) + assert.Nil(t, err) + assert.Equal(t, map[string]string{ + "x-datadog-parent-id": strconv.Itoa(int(childSpanID)), + "x-datadog-trace-id": "1", + "x-datadog-sampling-priority": "1", + }, dst) +} + func TestTextMapPropagatorInjectExtract(t *testing.T) { propagator := NewPropagator(&PropagatorConfig{ BaggagePrefix: "bg-", @@ -341,4 +387,60 @@ func TestB3(t *testing.T) { assert.True(ok) assert.Equal(2, p) }) + + t.Run("config", func(t *testing.T) { + os.Setenv("DD_PROPAGATION_STYLE_INJECT", "datadog") + defer os.Unsetenv("DD_PROPAGATION_STYLE_INJECT") + + var tests = []struct { + in []uint64 + out map[string]string + }{ + { + []uint64{1412508178991881, 1842642739201064}, + map[string]string{ + b3TraceIDHeader: "000504ab30404b09", + b3SpanIDHeader: "00068bdfb1eb0428", + }, + }, + { + []uint64{9530669991610245, 9455715668862222}, + map[string]string{ + b3TraceIDHeader: "0021dc1807524785", + b3SpanIDHeader: "002197ec5d8a250e", + }, + }, + { + []uint64{1, 1}, + map[string]string{ + b3TraceIDHeader: "0000000000000001", + b3SpanIDHeader: "0000000000000001", + }, + }, + } + + for _, test := range tests { + t.Run("", func(t *testing.T) { + tracer := newTracer(WithPropagator(NewPropagator(&PropagatorConfig{B3: true}))) + root := tracer.StartSpan("web.request").(*span) + root.SetTag(ext.SamplingPriority, -1) + root.SetBaggageItem("item", "x") + ctx, ok := root.Context().(*spanContext) + ctx.traceID = test.in[0] + ctx.spanID = test.in[1] + headers := TextMapCarrier(map[string]string{}) + err := tracer.Inject(ctx, headers) + + assert := assert.New(t) + assert.True(ok) + assert.Nil(err) + assert.Equal(test.out[b3TraceIDHeader], headers[b3TraceIDHeader]) + assert.Equal(test.out[b3SpanIDHeader], headers[b3SpanIDHeader]) + }) + } + }) +} + +func assertTraceTags(t *testing.T, expected, actual string) { + assert.ElementsMatch(t, strings.Split(expected, ","), strings.Split(actual, ",")) } diff --git a/ddtrace/tracer/time.go b/ddtrace/tracer/time.go index b77f0745ef..86ac9d1253 100644 --- a/ddtrace/tracer/time.go +++ b/ddtrace/tracer/time.go @@ -3,13 +3,14 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2016 Datadog, Inc. +//go:build !windows // +build !windows package tracer import "time" -// now returns current UTC time in nanos. +// now returns the current UNIX time in nanoseconds, as computed by Time.UnixNano(). func now() int64 { - return time.Now().UTC().UnixNano() + return time.Now().UnixNano() } diff --git a/ddtrace/tracer/time_windows.go b/ddtrace/tracer/time_windows.go index 7775627bca..0eaa37e85d 100644 --- a/ddtrace/tracer/time_windows.go +++ b/ddtrace/tracer/time_windows.go @@ -23,7 +23,7 @@ func highPrecisionNow() int64 { } func lowPrecisionNow() int64 { - return time.Now().UTC().UnixNano() + return time.Now().UnixNano() } var now func() int64 diff --git a/ddtrace/tracer/tracer.go b/ddtrace/tracer/tracer.go index 1f7e4cc737..e3c40e58f8 100644 --- a/ddtrace/tracer/tracer.go +++ b/ddtrace/tracer/tracer.go @@ -6,9 +6,7 @@ package tracer import ( - "context" gocontext "context" - "fmt" "os" "runtime/pprof" "strconv" @@ -21,7 +19,6 @@ import ( "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/traceprof" - "gopkg.in/DataDog/dd-trace-go.v1/internal/version" "github.com/DataDog/datadog-agent/pkg/obfuscate" ) @@ -156,6 +153,23 @@ func Inject(ctx ddtrace.SpanContext, carrier interface{}) error { return internal.GetGlobalTracer().Inject(ctx, carrier) } +// SetUser associates user information to the current trace which the +// provided span belongs to. The options can be used to tune which user +// bit of information gets monitored. +func SetUser(s Span, id string, opts ...UserMonitoringOption) { + if s == nil { + return + } + if span, ok := s.(*span); ok && span.context != nil { + span = span.context.trace.root + s = span + } + s.SetTag("usr.id", id) + for _, fn := range opts { + fn(s) + } +} + // payloadQueueSize is the buffer size of the trace channel. const payloadQueueSize = 1000 @@ -227,18 +241,7 @@ func newTracer(opts ...StartOption) *tracer { t.reportHealthMetrics(statsInterval) }() t.stats.Start() - appsec.Start(&appsec.Config{ - Client: c.httpClient, - 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 } @@ -315,24 +318,12 @@ func (t *tracer) pushTrace(trace []*span) { } } -// StartSpan implements ddtrace.Tracer. +// StartSpan creates, starts, and returns a new Span with the given `operationName`. func (t *tracer) StartSpan(operationName string, options ...ddtrace.StartSpanOption) ddtrace.Span { - span, _ := t.StartSpanFromContext(gocontext.Background(), operationName, options...) - return span -} - -// StartSpanFromContext implements ddtrace.Tracer. -func (t *tracer) StartSpanFromContext(ctx gocontext.Context, operationName string, options ...ddtrace.StartSpanOption) (ddtrace.Span, gocontext.Context) { var opts ddtrace.StartSpanConfig for _, fn := range options { fn(&opts) } - if ctx == nil { - ctx = gocontext.Background() - } else if s, ok := SpanFromContext(ctx); ok { - // span in ctx overwrite ChildOf() parent if any - opts.Parent = s.Context() - } var startTime int64 if opts.StartTime.IsZero() { startTime = now() @@ -340,19 +331,30 @@ func (t *tracer) StartSpanFromContext(ctx gocontext.Context, operationName strin startTime = opts.StartTime.UnixNano() } var context *spanContext - pprofCtx := ctx + // 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 parentContext, ok := opts.Parent.(*spanContext); ok { - context = parentContext - if pprofCtx == gocontext.Background() && parentContext.span != nil && parentContext.span.pprofCtxActive != nil { - // Inherit the pprof labels from parent span if it was propagated using - // ChildOf() rather than StartSpanFromContext(). Having a separate ctx - // and pprofCtx is done to avoid subtle problems with callers relying - // on the details of the ContextWithSpan() wrapping below. - pprofCtx = parentContext.span.pprofCtxActive + 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() @@ -409,37 +411,46 @@ func (t *tracer) StartSpanFromContext(ctx gocontext.Context, operationName strin for k, v := range t.config.globalTags { span.SetTag(k, v) } + if t.config.serviceMappings != nil { + if newSvc, ok := t.config.serviceMappings[span.Service]; ok { + span.Service = newSvc + } + } if context == nil || context.span == nil || context.span.Service != span.Service { span.setMetric(keyTopLevel, 1) // all top level spans are measured. So the measured tag is redundant. delete(span.Metrics, keyMeasured) } if t.config.version != "" && span.Service == t.config.serviceName { - span.SetTag(ext.Version, t.config.version) + span.setMeta(ext.Version, t.config.version) } if t.config.env != "" { - span.SetTag(ext.Environment, t.config.env) + span.setMeta(ext.Environment, t.config.env) } if _, ok := span.context.samplingPriority(); !ok { // if not already sampled or a brand new trace, sample it t.sample(span) } if t.config.profilerHotspots || t.config.profilerEndpoints { - ctx = t.applyPPROFLabels(pprofCtx, span) + 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, ContextWithSpan(ctx, span) + if log.DebugEnabled() { + // avoid allocating the ...interface{} argument if debug logging is disabled + 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) context.Context { +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)) @@ -458,16 +469,14 @@ func (t *tracer) applyPPROFLabels(ctx gocontext.Context, span *span) context.Con span.pprofCtxRestore = ctx span.pprofCtxActive = pprof.WithLabels(ctx, pprof.Labels(labels...)) pprof.SetGoroutineLabels(span.pprofCtxActive) - return span.pprofCtxActive } - return ctx } // 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 or rpc endpoint names generally do not. +// 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 + return s.Type == ext.SpanTypeWeb || s.Type == ext.AppTypeRPC || s.Type == "" } // Stop stops the tracer. diff --git a/ddtrace/tracer/tracer_test.go b/ddtrace/tracer/tracer_test.go index eb63a2b310..164f35e7b5 100644 --- a/ddtrace/tracer/tracer_test.go +++ b/ddtrace/tracer/tracer_test.go @@ -240,6 +240,7 @@ func TestTracerStartSpan(t *testing.T) { ext.PriorityAutoReject, ext.PriorityAutoKeep, }, span.Metrics[keySamplingPriority]) + assert.Equal("dHJhY2VyLnRlc3Q|1|1|1.0000", span.context.trace.tags[keyUpstreamServices]) // A span is not measured unless made so specifically _, ok := span.Meta[keyMeasured] assert.False(ok) @@ -251,6 +252,7 @@ func TestTracerStartSpan(t *testing.T) { tracer := newTracer() span := tracer.StartSpan("web.request", Tag(ext.SamplingPriority, ext.PriorityUserKeep)).(*span) assert.Equal(t, float64(ext.PriorityUserKeep), span.Metrics[keySamplingPriority]) + assert.Equal(t, "dHJhY2VyLnRlc3Q|2|4|", span.context.trace.tags[keyUpstreamServices]) }) t.Run("name", func(t *testing.T) { @@ -287,10 +289,13 @@ func TestSamplingDecision(t *testing.T) { child.Finish() span.Finish() assert.Equal(t, float64(ext.PriorityAutoReject), span.Metrics[keySamplingPriority]) + assert.Equal(t, "dGVzdF9zZXJ2aWNl|0|1|0.0000", span.context.trace.tags[keyUpstreamServices]) assert.Equal(t, decisionKeep, span.context.trace.samplingDecision) }) - t.Run("dropped", func(t *testing.T) { + t.Run("dropped_sent", func(t *testing.T) { + // Even if DropP0s is enabled, spans should always be kept unless + // client-side stats are also enabled. tracer, _, _, stop := startTestTracer(t) defer stop() tracer.config.agent.DropP0s = true @@ -301,6 +306,25 @@ func TestSamplingDecision(t *testing.T) { child.Finish() span.Finish() assert.Equal(t, float64(ext.PriorityAutoReject), span.Metrics[keySamplingPriority]) + assert.Equal(t, "dGVzdF9zZXJ2aWNl|0|1|0.0000", span.context.trace.tags[keyUpstreamServices]) + assert.Equal(t, decisionKeep, span.context.trace.samplingDecision) + }) + + t.Run("dropped_stats", func(t *testing.T) { + tracer, _, _, stop := startTestTracer(t) + defer stop() + tracer.config.featureFlags = make(map[string]struct{}) + tracer.config.featureFlags["discovery"] = struct{}{} + tracer.config.agent.DropP0s = true + tracer.config.agent.Stats = true + tracer.prioritySampling.defaultRate = 0 + tracer.config.serviceName = "test_service" + span := tracer.StartSpan("name_1").(*span) + child := tracer.StartSpan("name_2", ChildOf(span.context)) + child.Finish() + span.Finish() + assert.Equal(t, float64(ext.PriorityAutoReject), span.Metrics[keySamplingPriority]) + assert.Equal(t, "dGVzdF9zZXJ2aWNl|0|1|0.0000", span.context.trace.tags[keyUpstreamServices]) assert.Equal(t, decisionNone, span.context.trace.samplingDecision) }) @@ -316,6 +340,7 @@ func TestSamplingDecision(t *testing.T) { child.Finish() span.Finish() assert.Equal(t, float64(ext.PriorityAutoReject), span.Metrics[keySamplingPriority]) + assert.Equal(t, "dGVzdF9zZXJ2aWNl|0|1|0.0000", span.context.trace.tags[keyUpstreamServices]) assert.Equal(t, decisionKeep, span.context.trace.samplingDecision) }) @@ -332,6 +357,9 @@ func TestSamplingDecision(t *testing.T) { child.Finish() span.Finish() assert.Equal(t, float64(ext.PriorityAutoReject), span.Metrics[keySamplingPriority]) + // this trace won't be sent to the agent, + // therefore not necessary to populate keyUpstreamServices + assert.Equal(t, "", span.context.trace.tags[keyUpstreamServices]) assert.Equal(t, decisionDrop, span.context.trace.samplingDecision) }) } @@ -524,6 +552,7 @@ func TestTracerSamplingPriorityPropagation(t *testing.T) { root := tracer.StartSpan("web.request", Tag(ext.SamplingPriority, 2)).(*span) child := tracer.StartSpan("db.query", ChildOf(root.Context())).(*span) assert.EqualValues(2, root.Metrics[keySamplingPriority]) + assert.Equal("dHJhY2VyLnRlc3Q|2|4|", root.context.trace.tags[keyUpstreamServices]) assert.EqualValues(2, child.Metrics[keySamplingPriority]) assert.EqualValues(2., *root.context.trace.priority) assert.EqualValues(2., *child.context.trace.priority) @@ -536,9 +565,36 @@ func TestTracerSamplingPriorityEmptySpanCtx(t *testing.T) { spanCtx := &spanContext{ traceID: root.context.TraceID(), spanID: root.context.SpanID(), + trace: &trace{ + tags: map[string]string{ + keyUpstreamServices: "previous", + }, + upstreamServices: "previous", + }, } child := tracer.StartSpan("db.query", ChildOf(spanCtx)).(*span) assert.EqualValues(1, child.Metrics[keySamplingPriority]) + assert.Equal("previous;dHJhY2VyLnRlc3Q|1|1|1.0000", child.context.trace.tags[keyUpstreamServices]) +} + +func TestTracerDDUpstreamServicesManualKeep(t *testing.T) { + assert := assert.New(t) + tracer := newTracer() + root := newBasicSpan("web.request") + spanCtx := &spanContext{ + traceID: root.context.TraceID(), + spanID: root.context.SpanID(), + trace: &trace{ + tags: map[string]string{ + keyUpstreamServices: "previous", + }, + upstreamServices: "previous", + }, + } + child := tracer.StartSpan("db.query", ChildOf(spanCtx)).(*span) + grandChild := tracer.StartSpan("db.query", ChildOf(child.Context())).(*span) + grandChild.SetTag(ext.ManualKeep, true) + assert.Equal("previous;dHJhY2VyLnRlc3Q|2|4|", grandChild.context.trace.tags[keyUpstreamServices]) } func TestTracerBaggageImmutability(t *testing.T) { @@ -723,6 +779,7 @@ func TestTracerPrioritySampler(t *testing.T) { s := tr.newEnvSpan("pylons", "") assert.Equal(1., s.Metrics[keySamplingPriorityRate]) assert.Equal(1., s.Metrics[keySamplingPriority]) + assert.Equal("cHlsb25z|1|1|1.0000", s.context.trace.tags[keyUpstreamServices]) p, ok := s.context.samplingPriority() assert.True(ok) assert.EqualValues(p, s.Metrics[keySamplingPriority]) @@ -758,6 +815,7 @@ func TestTracerPrioritySampler(t *testing.T) { s := tr.newEnvSpan(tt.service, tt.env) assert.Equal(tt.rate, s.Metrics[keySamplingPriorityRate], strconv.Itoa(i)) prio, ok := s.Metrics[keySamplingPriority] + assert.Equal(b64Encode(tt.service)+"|"+strconv.Itoa(int(prio))+"|1|"+strconv.FormatFloat(tt.rate, 'f', 4, 64), s.context.trace.tags[keyUpstreamServices]) assert.True(ok) assert.Contains([]float64{0, 1}, prio) p, ok := s.context.samplingPriority() @@ -1315,6 +1373,28 @@ func TestVersion(t *testing.T) { }) } +func TestEnvironment(t *testing.T) { + t.Run("normal", func(t *testing.T) { + tracer, _, _, stop := startTestTracer(t, WithEnv("test")) + defer stop() + + assert := assert.New(t) + sp := tracer.StartSpan("http.request").(*span) + v := sp.Meta[ext.Environment] + assert.Equal("test", v) + }) + + t.Run("unset", func(t *testing.T) { + tracer, _, _, stop := startTestTracer(t) + defer stop() + + assert := assert.New(t) + sp := tracer.StartSpan("http.request").(*span) + _, ok := sp.Meta[ext.Environment] + assert.False(ok) + }) +} + // BenchmarkConcurrentTracing tests the performance of spawning a lot of // goroutines where each one creates a trace with a parent and a child. func BenchmarkConcurrentTracing(b *testing.B) { @@ -1614,6 +1694,47 @@ func TestTakeStackTrace(t *testing.T) { }) } +func TestUserMonitoring(t *testing.T) { + const id = "john.doe#12345" + const name = "John Doe" + const email = "john.doe@hostname.com" + const scope = "read:message, write:files" + const role = "admin" + const sessionID = "session#12345" + expected := []struct{ key, value string }{ + {key: "usr.id", value: id}, + {key: "usr.name", value: name}, + {key: "usr.email", value: email}, + {key: "usr.scope", value: scope}, + {key: "usr.role", value: role}, + {key: "usr.session_id", value: sessionID}, + } + tr := newTracer() + defer tr.Stop() + + t.Run("root", func(t *testing.T) { + s := tr.newRootSpan("root", "test", "test") + SetUser(s, id, WithUserEmail(email), WithUserName(name), WithUserScope(scope), + WithUserRole(role), WithUserSessionID(sessionID)) + s.Finish() + for _, pair := range expected { + assert.Equal(t, pair.value, s.Meta[pair.key]) + } + }) + + t.Run("nested", func(t *testing.T) { + root := tr.newRootSpan("root", "test", "test") + child := tr.newChildSpan("child", root) + SetUser(child, id, WithUserEmail(email), WithUserName(name), WithUserScope(scope), + WithUserRole(role), WithUserSessionID(sessionID)) + child.Finish() + root.Finish() + for _, pair := range expected { + assert.Equal(t, pair.value, root.Meta[pair.key]) + } + }) +} + // BenchmarkTracerStackFrames tests the performance of taking stack trace. func BenchmarkTracerStackFrames(b *testing.B) { tracer, _, _, stop := startTestTracer(b, WithSampler(NewRateSampler(0))) diff --git a/ddtrace/tracer/transport.go b/ddtrace/tracer/transport.go index b1ceba655e..aad1ce69c8 100644 --- a/ddtrace/tracer/transport.go +++ b/ddtrace/tracer/transport.go @@ -101,8 +101,8 @@ func newHTTPTransport(addr string, client *http.Client) *httpTransport { defaultHeaders["Datadog-Container-ID"] = cid } return &httpTransport{ - traceURL: fmt.Sprintf("http://%s/v0.4/traces", resolveAddr(addr)), - statsURL: fmt.Sprintf("http://%s/v0.6/stats", resolveAddr(addr)), + traceURL: fmt.Sprintf("http://%s/v0.4/traces", resolveAgentAddr(addr)), + statsURL: fmt.Sprintf("http://%s/v0.6/stats", resolveAgentAddr(addr)), client: client, headers: defaultHeaders, } @@ -148,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.config.agent.Stats { + if t.config.canComputeStats() { req.Header.Set("Datadog-Client-Computed-Stats", "yes") } droppedTraces := int(atomic.SwapUint64(&t.droppedP0Traces, 0)) @@ -183,10 +183,10 @@ func (t *httpTransport) endpoint() string { return t.traceURL } -// resolveAddr resolves the given agent address and fills in any missing host +// resolveAgentAddr resolves the given agent address and fills in any missing host // and port using the defaults. Some environment variable settings will // take precedence over configuration. -func resolveAddr(addr string) string { +func resolveAgentAddr(addr string) string { host, port, err := net.SplitHostPort(addr) if err != nil { // no port in addr diff --git a/ddtrace/tracer/transport_test.go b/ddtrace/tracer/transport_test.go index 0fada24a98..1e8ce3d019 100644 --- a/ddtrace/tracer/transport_test.go +++ b/ddtrace/tracer/transport_test.go @@ -77,7 +77,7 @@ func TestTracesAgentIntegration(t *testing.T) { } } -func TestResolveAddr(t *testing.T) { +func TestResolveAgentAddr(t *testing.T) { for _, tt := range []struct { in, envHost, envPort, out string }{ @@ -107,7 +107,7 @@ func TestResolveAddr(t *testing.T) { os.Setenv("DD_TRACE_AGENT_PORT", tt.envPort) defer os.Unsetenv("DD_TRACE_AGENT_PORT") } - assert.Equal(t, resolveAddr(tt.in), tt.out) + assert.Equal(t, resolveAgentAddr(tt.in), tt.out) }) } } diff --git a/ddtrace/tracer/util.go b/ddtrace/tracer/util.go index 84da0a9efd..116d0aef4d 100644 --- a/ddtrace/tracer/util.go +++ b/ddtrace/tracer/util.go @@ -6,8 +6,12 @@ package tracer import ( + "encoding/base64" + "fmt" "strconv" "strings" + + "gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames" ) // toFloat64 attempts to convert value into a float64. If the value is an integer @@ -26,6 +30,8 @@ func toFloat64(value interface{}) (f float64, ok bool) { return i, true case int: return float64(i), true + case int8: + return float64(i), true case int16: return float64(i), true case int32: @@ -46,6 +52,8 @@ func toFloat64(value interface{}) (f float64, ok bool) { return 0, false } return float64(i), true + case samplernames.SamplerName: + return float64(i), true default: return 0, false } @@ -63,3 +71,61 @@ func parseUint64(str string) (uint64, error) { } return strconv.ParseUint(str, 10, 64) } + +func isValidPropagatableTraceTag(k, v string) error { + if len(k) == 0 { + return fmt.Errorf("key length must be greater than zero") + } + for _, ch := range k { + if ch < 32 || ch > 126 || ch == ' ' || ch == '=' || ch == ',' { + return fmt.Errorf("key contains an invalid character %d", ch) + } + } + if len(v) == 0 { + return fmt.Errorf("value length must be greater than zero") + } + for _, ch := range v { + if ch < 32 || ch > 126 || ch == '=' || ch == ',' { + return fmt.Errorf("value contains an invalid character %d", ch) + } + } + return nil +} + +func parsePropagatableTraceTags(s string) (map[string]string, error) { + if len(s) == 0 { + return nil, nil + } + tags := make(map[string]string) + searchingKey, start := true, 0 + var key string + for i, ch := range s { + switch ch { + case '=': + if searchingKey { + if i-start == 0 { + return nil, fmt.Errorf("invalid format") + } + key = s[start:i] + searchingKey, start = false, i+1 + } + case ',': + if searchingKey || i-start == 0 { + return nil, fmt.Errorf("invalid format") + } + tags[key] = s[start:i] + searchingKey, start = true, i+1 + } + } + if searchingKey || len(s)-start == 0 { + return nil, fmt.Errorf("invalid format") + } + tags[key] = s[start:] + return tags, nil +} + +var b64 = base64.StdEncoding.WithPadding(base64.NoPadding) + +func b64Encode(s string) string { + return b64.EncodeToString([]byte(s)) +} diff --git a/ddtrace/tracer/util_test.go b/ddtrace/tracer/util_test.go index 917af9054f..42cc0a5f39 100644 --- a/ddtrace/tracer/util_test.go +++ b/ddtrace/tracer/util_test.go @@ -70,3 +70,48 @@ func TestParseUint64(t *testing.T) { assert.Error(t, err) }) } + +func TestIsValidPropagatableTraceTag(t *testing.T) { + for i, tt := range [...]struct { + key string + value string + err error + }{ + {"hello", "world", nil}, + {"hello=", "world", fmt.Errorf("key contains an invalid character 61")}, + {"hello", "world=", fmt.Errorf("value contains an invalid character 61")}, + {"", "world", fmt.Errorf("key length must be greater than zero")}, + {"hello", "", fmt.Errorf("value length must be greater than zero")}, + {"こんにちは", "world", fmt.Errorf("key contains an invalid character 12371")}, + {"hello", "世界", fmt.Errorf("value contains an invalid character 19990")}, + } { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + assert.Equal(t, tt.err, isValidPropagatableTraceTag(tt.key, tt.value)) + }) + } +} + +func TestParsePropagatableTraceTags(t *testing.T) { + for i, tt := range [...]struct { + input string + output map[string]string + err error + }{ + {"hello=world", map[string]string{"hello": "world"}, nil}, + {" hello = world ", map[string]string{" hello ": " world "}, nil}, + {"hello=world,service=account", map[string]string{"hello": "world", "service": "account"}, nil}, + {"hello=wor=ld====,service=account,tag1=val=ue1", map[string]string{"hello": "wor=ld====", "service": "account", "tag1": "val=ue1"}, nil}, + {"hello", nil, fmt.Errorf("invalid format")}, + {"hello=world,service=", nil, fmt.Errorf("invalid format")}, + {"hello=world,", nil, fmt.Errorf("invalid format")}, + {"=world", nil, fmt.Errorf("invalid format")}, + {"hello=,tag1=value1", nil, fmt.Errorf("invalid format")}, + {",hello=world", nil, fmt.Errorf("invalid format")}, + } { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + output, err := parsePropagatableTraceTags(tt.input) + assert.Equal(t, tt.output, output) + assert.Equal(t, tt.err, err) + }) + } +} diff --git a/go.mod b/go.mod index d29b199771..8d7756c006 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,188 @@ module gopkg.in/DataDog/dd-trace-go.v1 go 1.12 require ( + cloud.google.com/go/pubsub v1.4.0 + github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect 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.2.1 + github.com/DataDog/zstd v1.3.5 // indirect + github.com/Microsoft/hcsshim v0.8.9 // indirect + github.com/PuerkitoBio/goquery v1.5.1 // indirect + github.com/Shopify/sarama v1.22.0 + github.com/Shopify/toxiproxy v2.1.4+incompatible // indirect + github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect + github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 // indirect + github.com/armon/go-radix v1.0.0 // indirect + github.com/aws/aws-sdk-go v1.34.28 + github.com/aws/aws-sdk-go-v2 v1.0.0 + github.com/aws/aws-sdk-go-v2/config v1.0.0 + github.com/aws/aws-sdk-go-v2/service/sqs v1.0.0 + github.com/aws/aws-sdk-go-v2/service/sso v1.0.0 // indirect + github.com/aws/smithy-go v1.11.0 + github.com/beorn7/perks v1.0.1 // indirect + github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d + github.com/cenkalti/backoff/v3 v3.0.0 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect + github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1 // indirect + github.com/confluentinc/confluent-kafka-go v1.4.0 + github.com/containerd/containerd v1.3.4 // indirect + github.com/containerd/continuity v0.0.0-20200709052629-daa8e1ccc0bc // indirect + github.com/denisenkom/go-mssqldb v0.11.0 + github.com/docker/distribution v2.7.1+incompatible // indirect + github.com/docker/docker v1.4.2-0.20200319182547-c7ad2b866182 // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/eapache/go-resiliency v1.1.0 // indirect + github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect + github.com/eapache/queue v1.1.0 // indirect + github.com/elastic/go-elasticsearch/v6 v6.8.5 + github.com/elastic/go-elasticsearch/v7 v7.17.1 + github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 + github.com/envoyproxy/go-control-plane v0.9.8 // indirect + github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect + github.com/evanphx/json-patch/v5 v5.5.0 // indirect + github.com/fatih/structs v1.1.0 // indirect + github.com/frankban/quicktest v1.13.0 // indirect + github.com/garyburd/redigo v1.6.3 + github.com/gin-gonic/gin v1.7.0 + github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 + github.com/go-chi/chi v1.5.0 + github.com/go-chi/chi/v4 v4.0.0-rc1 + github.com/go-chi/chi/v5 v5.0.0 + github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 // indirect + github.com/go-ini/ini v1.25.4 // indirect + github.com/go-kit/kit v0.9.0 // indirect + github.com/go-ldap/ldap v3.0.2+incompatible // indirect + github.com/go-ldap/ldap/v3 v3.1.10 // indirect + github.com/go-pg/pg/v10 v10.0.0 + github.com/go-playground/validator/v10 v10.4.1 // indirect + github.com/go-redis/redis v6.15.9+incompatible + github.com/go-redis/redis/v7 v7.1.0 + github.com/go-redis/redis/v8 v8.0.0 + github.com/go-sql-driver/mysql v1.6.0 + github.com/go-test/deep v1.0.2 // indirect + github.com/gocql/gocql v0.0.0-20220224095938-0eacd3183625 + github.com/gofiber/fiber/v2 v2.11.0 + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect + github.com/golang/mock v1.4.3 // indirect + github.com/golang/protobuf v1.5.2 + github.com/golang/snappy v0.0.4 // indirect + github.com/gomodule/redigo v1.7.0 + github.com/google/martian/v3 v3.0.0 // indirect github.com/google/pprof v0.0.0-20210423192551-a2663126120b github.com/google/uuid v1.3.0 + github.com/gorilla/context v1.1.1 // indirect + github.com/gorilla/mux v1.5.0 + github.com/graph-gophers/graphql-go v1.3.0 + github.com/grpc-ecosystem/grpc-gateway v1.15.2 // indirect + github.com/hashicorp/consul/api v1.0.0 + github.com/hashicorp/consul/sdk v0.7.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-hclog v0.16.2 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-kms-wrapping/entropy v0.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-plugin v1.4.3 // indirect + github.com/hashicorp/go-retryablehttp v0.6.6 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-secure-stdlib/base62 v0.1.1 // indirect + github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect + github.com/hashicorp/go-secure-stdlib/password v0.1.1 // indirect + github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1 // indirect + github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/hashicorp/go-uuid v1.0.2 // indirect + github.com/hashicorp/go-version v1.2.0 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hashicorp/mdns v1.0.1 // indirect + github.com/hashicorp/memberlist v0.1.6 // indirect + github.com/hashicorp/serf v0.8.6 // indirect + github.com/hashicorp/vault/api v1.1.0 + github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267 + github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect + github.com/jackc/pgx/v4 v4.14.0 + github.com/jinzhu/gorm v1.9.1 + github.com/jinzhu/now v1.1.3 // indirect + github.com/jmoiron/sqlx v1.2.0 + github.com/jstemmer/go-junit-report v0.9.1 // indirect + github.com/julienschmidt/httprouter v1.1.0 + github.com/klauspost/crc32 v1.2.0 // indirect + github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/labstack/echo v3.3.10+incompatible + github.com/labstack/echo/v4 v4.2.0 + github.com/labstack/gommon v0.3.1 // indirect + github.com/lib/pq v1.10.2 + github.com/mattn/go-sqlite3 v1.14.12 + github.com/miekg/dns v1.1.25 + github.com/mitchellh/cli v1.1.0 // indirect + github.com/mitchellh/copystructure v1.0.0 // indirect + github.com/mitchellh/mapstructure v1.4.2 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 // indirect + github.com/onsi/gomega v1.16.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.1 // indirect + github.com/opencontainers/runc v0.1.1 // indirect + github.com/opentracing/opentracing-go v1.2.0 github.com/philhofer/fwd v1.1.1 // indirect + github.com/pierrec/lz4 v2.5.2+incompatible // indirect + github.com/pkg/profile v1.2.1 // indirect + github.com/posener/complete v1.2.3 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/procfs v0.0.8 // indirect + github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/segmentio/kafka-go v0.4.29 + github.com/smartystreets/gunit v1.1.3 // indirect github.com/stretchr/testify v1.7.0 + github.com/syndtr/goleveldb v1.0.0 + github.com/tidwall/assert v0.1.0 // indirect + github.com/tidwall/btree v1.1.0 // indirect + github.com/tidwall/buntdb v1.2.0 + github.com/tidwall/grect v0.1.4 // indirect + github.com/tidwall/rtred v0.1.2 // indirect + github.com/tidwall/rtree v1.3.1 // indirect github.com/tinylib/msgp v1.1.2 + github.com/twitchtv/twirp v8.1.1+incompatible + github.com/urfave/negroni v1.0.0 + github.com/valyala/fasthttp v1.32.0 // indirect + github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect + github.com/vmihailenco/tagparser v0.1.2 // indirect + github.com/zenazn/goji v1.0.1 + go.mongodb.org/mongo-driver v1.5.1 + go.opencensus.io v0.22.4 // indirect + go.uber.org/atomic v1.9.0 // indirect + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect + golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect + golang.org/x/net v0.0.0-20211020060615-d418f374d309 + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d + golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + golang.org/x/text v0.3.7 // indirect 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 + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 + google.golang.org/api v0.29.0 + google.golang.org/appengine v1.6.6 // indirect + google.golang.org/genproto v0.0.0-20200726014623-da3ae01ef02d // indirect + google.golang.org/grpc v1.32.0 + google.golang.org/protobuf v1.27.1 + gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect + gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect + gopkg.in/jinzhu/gorm.v1 v1.9.1 + gopkg.in/olivere/elastic.v3 v3.0.75 + gopkg.in/olivere/elastic.v5 v5.0.84 + gopkg.in/square/go-jose.v2 v2.5.1 // indirect + gorm.io/driver/mysql v1.0.1 + gorm.io/driver/postgres v1.0.0 + gorm.io/driver/sqlserver v1.0.4 + gorm.io/gorm v1.20.6 + gotest.tools/v3 v3.0.2 // indirect + honnef.co/go/tools v0.0.1-2020.1.4 // indirect + k8s.io/apimachinery v0.17.0 + k8s.io/client-go v0.17.0 ) diff --git a/go.sum b/go.sum index 5f97c2b0cc..fdb4f65a70 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,32 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 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= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1 h1:lRi0CHyU+ytlvylOlFKKq0af6JncuyoRh1J+QJBqQx0= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.0/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.46.1/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.46.2/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.47.0/go.mod h1:5p3Ky/7f3N10VBkhuR5LFtddroTiMyjZV/Kj5qOQFxU= +cloud.google.com/go v0.48.0/go.mod h1:gGOnoa/XMQYHAscREBlbdHduGchEaP9N0//OXdrPI/M= +cloud.google.com/go v0.49.0/go.mod h1:hGvAdzcWNbyuxS3nWhD7H2cIJxjRRTRLQVB0bdputVY= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= 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.55.0/go.mod h1:ZHmoY+/lIMNkN2+fBmuTiqZ4inFhvQad8ft7MT8IV5Y= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0 h1:EpMNVUorLiZIELdMZbCYX/ByTFCdoYopYAGxaGVz9ms= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.58.0/go.mod h1:W+9FnSUw6nhVwXlFcp1eL+krq5+HQUJeUogSeJZZiWg= +cloud.google.com/go v0.59.0/go.mod h1:qJxNOVCRTxHfwLhvDxxSI9vQc1zI59b9pEglp1Iv60E= +cloud.google.com/go v0.60.0/go.mod h1:yw2G51M9IfRboUH61Us8GqCeF1PzPblB823Mn2q2eAU= +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/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= @@ -24,37 +40,66 @@ cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWc 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 v0.99.0 h1:y/cM2iqGgGi5D5DQZl6D9STN/3dR/Vx5Mp8s752oJTY= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.1.0/go.mod h1:g4RsfUkOvV3Vi5yRujQETpqwCN0F+faPZ2/ykNYfBJc= +cloud.google.com/go/bigquery v1.2.0/go.mod h1:Cqg1qaK3wRdys8sKlow0jIBVFwSTiHoFx5um4ujCpyE= 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= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.6.0/go.mod h1:hyFDG0qSGdHNz8Q6nDN8rYIkld0q/+5uBZaelxiDLfE= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA= 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 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ= 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.0 h1:GiyqIdED84r6Z6ZvD/F118d1swo7iQQbWszkKaNfRms= +cloud.google.com/go/pubsub v1.0.0/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 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.4.0 h1:76oR7VBOkL7ivoIrFKyW0k7YDCRelrlxktIzQiIUGgg= +cloud.google.com/go/pubsub v1.4.0/go.mod h1:LFrqilwgdw4X2cJS9ALgzYmMu+ULyrUN6IHV3CPK4TM= +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.1.0/go.mod h1:a81gKs1KmeOyF/qrbeu4APVXICPLcsl0Ilx2XvD7ZYU= +cloud.google.com/go/storage v1.1.1/go.mod h1:nbQkUX8zrWh07WKekXr/Phd0q/ERj4IOJnkE+v56Qys= +cloud.google.com/go/storage v1.1.2/go.mod h1:/03MkR5FWjF0OpcKpdJ4RgWybEaYAr2boHXq5RDlxbw= +cloud.google.com/go/storage v1.2.0/go.mod h1:9gYIx9Yslppm7JnJFIRyUhybmKXFCReO7Eq8bdUJZ4k= +cloud.google.com/go/storage v1.2.1/go.mod h1:kpwTAahUQmhyVVGgLWQh2GdyPDZSA3UJDjMm/fDV2oQ= +cloud.google.com/go/storage v1.3.0/go.mod h1:9IAwXhoyBJ7z9LcAwkj0/7NnPzYaPeZxxVp3zm+5IqA= +cloud.google.com/go/storage v1.4.0/go.mod h1:ZusYJWlOshgSBGbt6K3GnB3MT3H1xs2id9+TCl4fDBA= 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.7.0/go.mod h1:jGMIBwF+L/tL6WN/W5InNgYYu4HP0DvGB6rQ1mufWfs= +cloud.google.com/go/storage v1.8.0 h1:86K1Gel7BQ9/WmNWn7dTKMvTLFzwtBe5FNqYbi9X35g= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.9.0/go.mod h1:m+/etGaqZbylxaNT876QGXqEHp4PR2Rq5GMqICWb9bU= 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/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-agent/pkg/obfuscate v0.0.0-20211129110424-6491aa3bf583 h1:3nVO1nQyh64IUY6BPZUpMYMZ738Pu+LsMt3E0eqqIYw= @@ -66,65 +111,355 @@ github.com/DataDog/datadog-go/v5 v5.0.2 h1:UFtEe7662/Qojxkw1d6SboAeA0CPI3naKhVAS 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/DataDog/sketches-go v1.2.1 h1:qTBzWLnZ3kM2kw39ymh6rMcnN+5VULwFs++lEYUUsro= github.com/DataDog/sketches-go v1.2.1/go.mod h1:1xYmPLY1So10AwxV6MJV0J53XVH+WL9Ad1KetxVivVI= +github.com/DataDog/zstd v1.3.5 h1:DtpNbljikUepEPD16hD4LvIcmhnhdLTiW/5pHgbmp14= +github.com/DataDog/zstd v1.3.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= 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.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= 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/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= 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.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/Shopify/sarama v1.30.1 h1:z47lP/5PBw2UVKf1lvfS5uWXaJws6ggk9PLnKEHtZiQ= -github.com/Shopify/sarama v1.30.1/go.mod h1:hGgx05L/DiW8XYBXeJdKIN6V2QUy2H6JqME5VT1NLRw= +github.com/Shopify/sarama v1.0.0 h1:K5TCfhy9Qk2fF5sTDorLEGzKYFlD5/ql54NXdn7zK1E= +github.com/Shopify/sarama v1.0.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/sarama v1.10.0 h1:Mg4AfuW66Lx9jqawTgdK23dNXrRu4CS0kv5ksUl8SoE= +github.com/Shopify/sarama v1.10.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/sarama v1.14.0 h1:ybE26/v5eppjkQZmMAttQK8lFiNYnk/aWYVU/IgmWpg= +github.com/Shopify/sarama v1.14.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/sarama v1.17.0 h1:Y2/FBwElFVwt7aLKL3fDG6hh+rrlywR6uLgTgKObwTc= +github.com/Shopify/sarama v1.17.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/sarama v1.18.0 h1:Ha2FAOngREft7C44ouUXDxSZ/Y/77IDCMV1YS4AnUkI= +github.com/Shopify/sarama v1.18.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/sarama v1.19.0 h1:9oksLxC6uxVPHPVYUmq6xhr1BOF/hHobWH2UzO67z1s= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/sarama v1.20.0 h1:wAMHhl1lGRlobeoV/xOKpbqD2OQsOvY4A/vIOGroIe8= +github.com/Shopify/sarama v1.20.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/sarama v1.21.0 h1:0GKs+e8mn1RRUzfg9oUXv3v7ZieQLmOZF/bfnmmGhM8= +github.com/Shopify/sarama v1.21.0/go.mod h1:yuqtN/pe8cXRWG5zPaO7hCfNJp5MwmkoJEoLjkm5tCQ= +github.com/Shopify/sarama v1.22.0 h1:rtiODsvY4jW6nUV6n3K+0gx/8WlAwVt+Ixt6RIvpYyo= +github.com/Shopify/sarama v1.22.0/go.mod h1:lm3THZ8reqBDBQKQyb5HB3sY1lKp3grEbQ81aWSgPp4= +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.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= 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.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= 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.0 h1:B7AQgHi8QSEi4uHu7Sbsga+IJDU+CENgjxoo81vDUqU= +github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs= +github.com/armon/go-metrics v0.3.1/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.3.2/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.3.3 h1:a9F4rlj7EWWrbj7BYw8J8+x+ZZkJeqzNyRk8hdPF+ro= +github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.3.4/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.3.5/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.3.6/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.3.7/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.3.8/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= 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/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.15.29 h1:30bxxxQjf2nzXUrV9ckeY0R27JMP+MnfzgyUnM4V84w= +github.com/aws/aws-sdk-go v1.15.29/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.29.11/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg= -github.com/aws/aws-sdk-go v1.42.23 h1:V0V5hqMEyVelgpu1e4gMPVCJ+KhmscdNxP/NWP1iCOA= -github.com/aws/aws-sdk-go v1.42.23/go.mod h1:gyRszuZ/icHmHAVE4gc/r+cfCmhA1AD+vqfWbgI+eHs= -github.com/aws/aws-sdk-go-v2 v1.11.2 h1:SDiCYqxdIYi6HgQfAWRhgdZrdnOuGyLDJVRSWLeHWvs= -github.com/aws/aws-sdk-go-v2 v1.11.2/go.mod h1:SQfA+m2ltnu1cA0soUkj4dRSsmITiVQUJvBIZjzfPyQ= -github.com/aws/aws-sdk-go-v2/config v1.11.0 h1:Czlld5zBB61A3/aoegA9/buZulwL9mHHfizh/Oq+Kqs= +github.com/aws/aws-sdk-go v1.30.27 h1:9gPjZWVDSoQrBO2AvqrWObS6KAZByfEJxQoCYo4ZfK0= +github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk= +github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= +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.0.0 h1:ncEVPoHArsG+HjoDe/3ex/TG1CbLwMQ4eaWj0UGdyTo= +github.com/aws/aws-sdk-go-v2 v1.0.0/go.mod h1:smfAbmpW+tcRVuNUjo3MOArSZmW72t62rkCzc2i0TWM= +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 v1.14.0 h1:IzSYBJHu0ZdUi27kIW6xVrs0eSxI4AzwbenzfXhhVs4= +github.com/aws/aws-sdk-go-v2 v1.14.0/go.mod h1:ZA3Y8V0LrlWj63MQAnRHgKf/5QB//LSZCPNWlWrNGLU= +github.com/aws/aws-sdk-go-v2/config v1.0.0 h1:x6vSFAwqAvhYPeSu60f0ZUlGHo3PKKmwDOTL8aMXtv4= +github.com/aws/aws-sdk-go-v2/config v1.0.0/go.mod h1:WysE/OpUgE37tjtmtJd8GXgT8s1euilE5XtUkRNUQ1w= +github.com/aws/aws-sdk-go-v2/config v1.1.0/go.mod h1:zfTyI6wH8yiZEvb6hGVza+S5oIB2lts2M7TDB4zMoeo= +github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= +github.com/aws/aws-sdk-go-v2/config v1.1.2/go.mod h1:77yIk+qmCS/94JlxbwV1d+YEyu6Z8FBlCGcSz3TdM6A= +github.com/aws/aws-sdk-go-v2/config v1.1.3/go.mod h1:yf3tNRNqZKlylefSdp5R3v+sm1el90fhUTcSa/t69Ro= +github.com/aws/aws-sdk-go-v2/config v1.1.4/go.mod h1:op05ummoVoAqctpA80jVt/+hvEtLfuKmDyx0bIuvfbE= +github.com/aws/aws-sdk-go-v2/config v1.1.5/go.mod h1:P3F1hku7qzC81txjwXnwOM6Ex6ezkU6+/557Teyb64E= +github.com/aws/aws-sdk-go-v2/config v1.1.6/go.mod h1:Kx90DDOgkMpRfSkzGbF13AVXHHfBNct1liO+95KxXsU= +github.com/aws/aws-sdk-go-v2/config v1.1.7/go.mod h1:6GFyKv06rDCBCIOmWe9vLi7ofCkE7y8aqI6a3tFWNQ0= +github.com/aws/aws-sdk-go-v2/config v1.2.0/go.mod h1:JgPbg7YzzczkGu1Zi0hHVKYXVzx4OTKnNSD+h+qlpLw= +github.com/aws/aws-sdk-go-v2/config v1.3.0/go.mod h1:lOxzHWDt/k7MMidA/K8DgXL4+ynnZYsDq65Qhs/l3dg= +github.com/aws/aws-sdk-go-v2/config v1.4.0/go.mod h1:lSD+PE8OsriBSidyfYyAadDrbJrUJTlBd3IF0qXkszQ= +github.com/aws/aws-sdk-go-v2/config v1.4.1/go.mod h1:HCDWZ/oeY59TPtXslxlbkCqLQBsVu6b09kiG43tdP+I= +github.com/aws/aws-sdk-go-v2/config v1.5.0/go.mod h1:RWlPOAW3E3tbtNAqTwvSW54Of/yP3oiZXMI0xfUdjyA= +github.com/aws/aws-sdk-go-v2/config v1.6.0/go.mod h1:TNtBVmka80lRPk5+S9ZqVfFszOQAGJJ9KbT3EM3CHNU= +github.com/aws/aws-sdk-go-v2/config v1.6.1/go.mod h1:t/y3UPu0XEDy0cEw6mvygaBQaPzWiYAxfP2SzgtvclA= +github.com/aws/aws-sdk-go-v2/config v1.7.0/go.mod h1:w9+nMZ7soXCe5nT46Ri354SNhXDQ6v+V5wqDjnZE+GY= +github.com/aws/aws-sdk-go-v2/config v1.8.0/go.mod h1:w9+nMZ7soXCe5nT46Ri354SNhXDQ6v+V5wqDjnZE+GY= +github.com/aws/aws-sdk-go-v2/config v1.8.1/go.mod h1:AQtpYfVYjuuft4Dgh0jGSkPQJ9MvmK9vXfSub7oSXlI= +github.com/aws/aws-sdk-go-v2/config v1.8.2/go.mod h1:r0bkX9NyuCuf28qVcsEMtpAQibT7gA1Q0gzkjvgJdLU= +github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw= +github.com/aws/aws-sdk-go-v2/config v1.9.0/go.mod h1:qhK5NNSgo9/nOSMu3HyE60WHXZTWTHTgd5qtIF44vOQ= +github.com/aws/aws-sdk-go-v2/config v1.10.0/go.mod h1:xuqoV5etD3N3B8Ts9je4ijgAv6mb+6NiOPFMUhwRcjA= +github.com/aws/aws-sdk-go-v2/config v1.10.1/go.mod h1:auIv5pIIn3jIBHNRcVQcsczn6Pfa6Dyv80Fai0ueoJU= +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/config v1.10.3/go.mod h1:yPMKrwzpPrBny2yk70tIXHCfKnIuPLc+Y9tgY9Ms2NU= github.com/aws/aws-sdk-go-v2/config v1.11.0/go.mod h1:VrQDJGFBM5yZe+IOeenNZ/DWoErdny+k2MHEIpwDsEY= -github.com/aws/aws-sdk-go-v2/credentials v1.6.4 h1:2hvbUoHufns0lDIsaK8FVCMukT1WngtZPavN+W2FkSw= +github.com/aws/aws-sdk-go-v2/config v1.11.1/go.mod h1:VvfkzUhVtntSg1JfGFMSKS0CyiTZd3NqBxK5af4zsME= +github.com/aws/aws-sdk-go-v2/config v1.12.0/go.mod h1:GQONFVSDdG6RRho1C730SGNyDhS1kSTnxpOE76ptBqo= +github.com/aws/aws-sdk-go-v2/config v1.13.0/go.mod h1:Pjv2OafecIn+4miw9VFDCr06YhKyf/oKOkIcpQOgWKk= +github.com/aws/aws-sdk-go-v2/config v1.13.1/go.mod h1:Ba5Z4yL/UGbjQUzsiaN378YobhFo0MLfueXGiOsYtEs= +github.com/aws/aws-sdk-go-v2/config v1.14.0 h1:Yr8/7R6H8nqqfqgLATrcB83ax6FE2HcDXEB54XPhE98= +github.com/aws/aws-sdk-go-v2/config v1.14.0/go.mod h1:GKDRrvsq/PTaOYc9252u8Uah1hsIdtor4oIrFvUNPNM= +github.com/aws/aws-sdk-go-v2/credentials v1.0.0 h1:0M7netgZ8gCV4v7z1km+Fbl7j6KQYyZL7SS0/l5Jn/4= +github.com/aws/aws-sdk-go-v2/credentials v1.0.0/go.mod h1:/SvsiqBf509hG4Bddigr3NB12MIpfHhZapyBurJe8aY= +github.com/aws/aws-sdk-go-v2/credentials v1.1.0/go.mod h1:cV0qgln5tz/76IxAV0EsJVmmR5ZzKSQwWixsIvzk6lY= +github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo= +github.com/aws/aws-sdk-go-v2/credentials v1.1.2/go.mod h1:hofjw//lM0XLplgvzPPMA7oD0doQU1QpaIK1nweEEWg= +github.com/aws/aws-sdk-go-v2/credentials v1.1.3/go.mod h1:afuzRuLhPEe08fePFh4gI9jnHuXd8AJDCYZNo3rKRKE= +github.com/aws/aws-sdk-go-v2/credentials v1.1.4/go.mod h1:UQwsT2w2XelrWoVV2v/zL2uce1RxmVCiHaZsoKLamZg= +github.com/aws/aws-sdk-go-v2/credentials v1.1.5/go.mod h1:Ir1R6tPiR1/2y1hes8yOijFMz54hzSmgcmCDo6F45Qc= +github.com/aws/aws-sdk-go-v2/credentials v1.1.6/go.mod h1:q1wQ5jHdFNhc4wnNcOEpnovs4keJA5Ds+qESCnfEsgU= +github.com/aws/aws-sdk-go-v2/credentials v1.1.7/go.mod h1:xYCvIyeVCRC9DmG3Zv/pxlZEEIBYf4fY/jSUVSrr58M= +github.com/aws/aws-sdk-go-v2/credentials v1.2.0/go.mod h1:3Xxgc7WsldLnLnPSRcNOT5eVRgb55Kkgp8mE5kAmLrU= +github.com/aws/aws-sdk-go-v2/credentials v1.2.1/go.mod h1:Rfvim1eZTC9W5s8YJyYYtl1KMk6e8fHv+wMRQGO4Ru0= +github.com/aws/aws-sdk-go-v2/credentials v1.3.0/go.mod h1:tOcv+qDZ0O+6Jk2beMl5JnZX6N0H7O8fw9UsD3bP7GI= +github.com/aws/aws-sdk-go-v2/credentials v1.3.1/go.mod h1:r0n73xwsIVagq8RsxmZbGSRQFj9As3je72C2WzUIToc= +github.com/aws/aws-sdk-go-v2/credentials v1.3.2/go.mod h1:PACKuTJdt6AlXvEq8rFI4eDmoqDFC5DpVKQbWysaDgM= +github.com/aws/aws-sdk-go-v2/credentials v1.3.3/go.mod h1:oVieKMT3m9BSfqhOfuQ+E0j/yN84ZAJ7Qv8Sfume/ak= +github.com/aws/aws-sdk-go-v2/credentials v1.4.0/go.mod h1:dgGR+Qq7Wjcd4AOAW5Rf5Tnv3+x7ed6kETXyS9WCuAY= +github.com/aws/aws-sdk-go-v2/credentials v1.4.1/go.mod h1:dgGR+Qq7Wjcd4AOAW5Rf5Tnv3+x7ed6kETXyS9WCuAY= +github.com/aws/aws-sdk-go-v2/credentials v1.4.2/go.mod h1:9Sp6u121/f0NnvHyhG7dgoYeUTEFC2vsvJqJ6wXpkaI= +github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ= +github.com/aws/aws-sdk-go-v2/credentials v1.5.0/go.mod h1:kvqTkpzQmzri9PbsiTY+LvwFzM0gY19emlAWwBOJMb0= +github.com/aws/aws-sdk-go-v2/credentials v1.6.0/go.mod h1:rQkYdQPDXRrvPLeEuCNwSgtwMzBo9eDGWlTNC69Sh/0= +github.com/aws/aws-sdk-go-v2/credentials v1.6.1/go.mod h1:QyvQk1IYTqBWSi1T6UgT/W8DMxBVa5pVuLFSRLLhGf8= +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/credentials v1.6.3/go.mod h1:9YEFqXj6X6lpCCXMmSWWo1jCISkx2lnbLFhAjx+mUWw= github.com/aws/aws-sdk-go-v2/credentials v1.6.4/go.mod h1:tTrhvBPHyPde4pdIPSba4Nv7RYr4wP9jxXEDa1bKn/8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.2 h1:KiN5TPOLrEjbGCvdTQR4t0U4T87vVwALZ5Bg3jpMqPY= +github.com/aws/aws-sdk-go-v2/credentials v1.6.5/go.mod h1:HWSOnsnqVMbLcWUmom6AN1cqhcLzLJ62AObW28CbYbU= +github.com/aws/aws-sdk-go-v2/credentials v1.7.0/go.mod h1:Kmq64kahHJtXfmnEwnvRKeNjLBqkdP++Itln9BmQerE= +github.com/aws/aws-sdk-go-v2/credentials v1.8.0/go.mod h1:gnMo58Vwx3Mu7hj1wpcG8DI0s57c9o42UQ6wgTQT5to= +github.com/aws/aws-sdk-go-v2/credentials v1.9.0 h1:R3Q5s1uGLUg0aUzi+oRaUqRXhd17G/9+PiVnAwXp4sY= +github.com/aws/aws-sdk-go-v2/credentials v1.9.0/go.mod h1:PyHKqk/+tJuDY7T8R580S1j/AcSD+ODeUZ99CAUKLqQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.0 h1:lO7fH5n7Q1dKcDBpuTmwJylD1bOQiRig8LI6TD9yVQk= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.0/go.mod h1:wpMHDCXvOXZxGCRSidyepa8uJHY4vaBGfY2/+oKU/Bc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.1/go.mod h1:b+8dhYiS3m1xpzTZWk5EuQml/vSmPhKlzM/bAm/fttY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.3/go.mod h1:Zr1Mj+KUMGVQ+WJvTT68EZJxqhjiie2PWSPGEUPaNY0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.4/go.mod h1:BDw1ukadBHn//M/n7LqpEgimGS0QtiJePnygMsbuYMs= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.5/go.mod h1:z/NKNlYxMzphl7TzjV+ctUebHF4CFNGGlSvmV/NKcJU= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.6/go.mod h1:0+fWMitrmIpENiY8/1DyhdYPUCAPvd9UNz9mtCsEoLQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.7/go.mod h1:51hY5nMAiL2EF8ny/pFovWYoKZTcEfOw0WWKcq2E9AQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.1.0/go.mod h1:GOKx1449nzMoUdTKrP41RsPn1hogOaxb+5MaoOiZgqc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.1.1/go.mod h1:GTXAhrxHQOj9N+J5tYVjwt+rpRyy/42qLjlgw9pz1a0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.2.0/go.mod h1:XvzoGzuS0kKPzCQtJCC22Xh/mMgVAzfGo/0V+mk/Cu0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.3.0/go.mod h1:2LAuqPx1I6jNfaGDucWfA2zqQCYCOMCDHiCOciALyNw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.4.0/go.mod h1:Mj/U8OpDbcVcoctrYwA2bak8k/HFPdcLzI/vaiXMwuM= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.4.1/go.mod h1:+GTydg3uHmVlQdkRoetz6VHKbOMEYof70m19IpMLifc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.5.0/go.mod h1:CpNzHK9VEFUCknu50kkB8z58AH2B5DvPP7ea1LHve/Y= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.5.1/go.mod h1:W1ldHfsgeGlKpJ4xZMKZUI6Wmp6EAstU7PxnhbXWWrI= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.7.0/go.mod h1:KqEkRkxm/+1Pd/rENRNbQpfblDBYeg5HDSqjB6ks8hA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.0/go.mod h1:5E1J3/TTYy6z909QNR0QnXGBpfESYGDqd3O0zqONghU= +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/feature/ec2/imds v1.8.2/go.mod h1:dF2F6tXEOgmW5X1ZFO/EPtWrcm7XkW07KNcJUGNtt4s= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.2 h1:XJLnluKuUxQG255zPNe+04izXl7GSyUVafIsgfv9aw4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.9.0/go.mod h1:19SxQ+9zANyJCnNaoF3ovl8bFil4TaqCYEDdqNGKM+A= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.10.0/go.mod h1:I6/fHT/fH460v09eg2gVrd8B/IqskhNdpcLH0WNO3QI= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.11.0 h1:CkM4d3lNeMXMZ0BDX3BtCktnKA1Ftud84Hb6d+Ix4Rk= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.11.0/go.mod h1:rwdUKJV5rm+vHu1ncD1iGDqahBEL8O0tBjVqo9eO2N0= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.0.0/go.mod h1:gVCp/Wo3eyri2BAFHafQwkpDSldgAyE+4TCGS5zrM44= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.0.1/go.mod h1:H2dIRXkSkAPkxIA74UD1wYu0eS+cQxJcPKSmsfeZLUc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.0.2/go.mod h1:1QsSZvLUuaQ6VJsCXolYCEzV0mVBkNBp64pIJy9yRks= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.0.3/go.mod h1:e7I5I0tt1DAZT2LfvbcVg6IEsBWlinSXXx5pyHfkJH0= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.0.4/go.mod h1:W5gGbtNXFpF9/ssYZTaItzG/B+j0bjTnwStiCP2AtWU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.0.5/go.mod h1:qAHnlYl9sMN7zmlWCFhmrUtMXe+rluOJUgaIFgObXvo= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.0.6/go.mod h1:eEfiJP/OO/wZXqQ3GXxTjjrvOXuUWnKj2CaZ7Y5+3nM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.0.7/go.mod h1:QXoZAXmBEHeMIFiBr3XumpTyoNTXTQbqPV+qaGX7gfY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.0/go.mod h1:NO3Q5ZTTQtO2xIg2+xTXYDiT7knSejfeDm7WGDaOo0U= +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/configsources v1.1.2/go.mod h1:SgKKNBIoDC/E1ZCDhhMW3yalWjwuLjMcpLzsM/QQnWo= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.2 h1:EauRoYZVNPlidZSZJDscjJBQ22JhVF2+tdteatax2Ak= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.3/go.mod h1:L72JSFj9OwHwyukeuKFFyTj6uFWE4AjB0IQp97bd9Lc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.4/go.mod h1:XHgQ7Hz2WY2GAn//UXHofLfPXWh+s62MbMOijrg12Lw= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.5 h1:+phazLmKkjBYhFTsGYH9J7jgnA8+Aer2yE4QeS4zn6A= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.5/go.mod h1:2hXc8ooJqF2nAznsbJQIn+7h851/bu8GVC80OVTTqf8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.0/go.mod h1:anlUzBoEWglcUxUQwZA7HQOEVEnQALVZsizAapB2hq8= +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/endpoints/v2 v2.0.2/go.mod h1:xT4XX6w5Sa3dhg50JrYyy3e4WPYo/+WjY/BXtqXVunU= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.2 h1:IQup8Q6lorXeiA/rK72PeToWoWK8h7VAPgHNWdSrtgE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.1.0/go.mod h1:KdVvdk4gb7iatuHZgIkIqvJlWHBtjCJLUtD/uO/FkWw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.2.0/go.mod h1:BsCSJHx5DnDXIrOcqB8KN1/B+hXLG/bi4Y6Vjcx/x9E= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.3.0 h1:PO+HNeJBeRK0yVD9CQZ+VUrYfd5sXqS7YdPYHHcDkR4= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.3.0/go.mod h1:miRSv9l093jX/t/j+mBCaLqFHo9xKYzJ7DGm1BsGoJM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.0.0/go.mod h1:g3XMXuxvqSMUjnsXXp/960152w0wFS4CXVYgQaSVOHE= +github.com/aws/aws-sdk-go-v2/internal/ini v1.0.1/go.mod h1:qGQ/9IfkZonRNSNLE99/yBJ7EPA/h8jlWEqtJCcaj+Q= +github.com/aws/aws-sdk-go-v2/internal/ini v1.1.0/go.mod h1:qGQ/9IfkZonRNSNLE99/yBJ7EPA/h8jlWEqtJCcaj+Q= +github.com/aws/aws-sdk-go-v2/internal/ini v1.1.1/go.mod h1:Zy8smImhTdOETZqfyn01iNOe0CNggVbPjCajyaz6Gvg= +github.com/aws/aws-sdk-go-v2/internal/ini v1.2.0/go.mod h1:Q5jATQc+f1MfZp3PDMhn6ry18hGvE0i8yvbXoKbnZaE= +github.com/aws/aws-sdk-go-v2/internal/ini v1.2.1/go.mod h1:Pv3WenDjI0v2Jl7UaMFIIbPOBbhn33RmmAmGgkXDoqY= +github.com/aws/aws-sdk-go-v2/internal/ini v1.2.2/go.mod h1:BQV0agm+JEhqR+2RT5e1XTFIDcAAV0eW6z2trp+iduw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.2.3/go.mod h1:EES9ToeC3h063zCFDdqWGnARExNdULPaBvARm1FLwxA= +github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.2.5/go.mod h1:6ZBTuDmvpCOD4Sf1i2/I3PgftlEcDGgvi8ocq64oQEg= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.0/go.mod h1:6oXGy4GLpypD3uCh8wcqztigGgmhLToMfjavgh+VySg= +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/internal/ini v1.3.2/go.mod h1:VITe/MdW6EMXPb0o0txu/fsonXbMHUU2OC2Qp7ivU4o= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.2 h1:CKdUNKmuilw/KNmO2Q53Av8u+ZyXMC2M9aX8Z+c/gzg= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.3/go.mod h1:N4dv+zawriMFZBO/6UKg3zt+XO6xWOQo1neAA0lFbo4= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.4/go.mod h1:R3sWUqPcfXSiF/LSFJhjyJmpg9uV6yP2yv3YZZjldVI= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.5/go.mod h1:R3sWUqPcfXSiF/LSFJhjyJmpg9uV6yP2yv3YZZjldVI= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.6 h1:c8s9EhIPVFMFS+R1+rtEghGrf7v83gSUWbcCYX/OPes= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.6/go.mod h1:o1ippSg3yJx5EuT4AOGXJCUcmt5vrcxla1cg6K1Q8Iw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.0 h1:IAutMPSrynpvKOpHG6HyWHmh1xmxWAmYOK84NrQVqVQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.0/go.mod h1:3jExOmpbjgPnz2FJaMOfbSk1heTkZ66aD3yNtVhnjvI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.1/go.mod h1:PISaKWylTYAyruocNk4Lr9miOOJjOcVBd7twCPbydDk= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.3/go.mod h1:C50Z41fJaJ7WgaeeCulOGAU3q4+4se4B3uOPFdhBi2I= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.4/go.mod h1:DGOKKGeqXdIWX3xD5DKr4otrgNw5cstwUCJYwSKxbp0= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.5/go.mod h1:MW0O/RpmVpS6MWKn6W03XEJmqXlG7+d3iaYLzkd2fAc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.6/go.mod h1:L0KWr0ASo83PRZu9NaZaDsw3koS6PspKv137DMDZjHo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.7/go.mod h1:Fl3kxN4ucBIPuAHKHRn+wTGylFzMCEjRfmfHqZuEh8o= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.1.0/go.mod h1:mruB7K2oMCoU0WhUeTV1CxpqoP7Q0N1uo5TXH4r2dZA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.1.1/go.mod h1:2+ehJPkdIdl46VCj67Emz/EH2hpebHZtaLdzqg+sWOI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.2.0/go.mod h1:a7XLWNKuVgOxjssEF019IiHPv35k8KHBaWv/wJAfi2A= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.2.1/go.mod h1:zceowr5Z1Nh2WVP8bf/3ikB41IZW59E4yIYbg+pC6mw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.2.2/go.mod h1:NXmNI41bdEsJMrD0v9rUvbGCB5GwdBEpKvUvIY3vTFg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.2.3/go.mod h1:7gcsONBmFoCcKrAqrm95trrMd2+C/ReYKP7Vfu8yHHA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.0/go.mod h1:R1KK+vY8AfalhG1AOu5e35pOD2SdoPKQCFLTvnxiohk= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.1/go.mod h1:Ve+eJOx9UWaT/lMVebnFhDhO49fSLVedHoA82+Rqme0= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.4.0/go.mod h1:X5/JuOxPLU/ogICgDTtnpfaQzdQJO0yKDcpoxWLLJ8Y= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.0/go.mod h1:Mq6AEc+oEjCUlBuLiK5YwW4shSOAKCQ3tXN0sQeYoBA= +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/internal/presigned-url v1.5.2/go.mod h1:FgR1tCsn8C6+Hf+N5qkfrE4IXvUL1RgW87sunJ+5J4I= -github.com/aws/aws-sdk-go-v2/service/sqs v1.13.1 h1:F2+s4Niqvlvmdzi+wNHvqa9tvgy2VfawUuLhsnLaQbQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.6.0/go.mod h1:wTgFkG6t7jS/6Y0SILXwfspV3IXowb6ngsAlSajW0Kc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.7.0/go.mod h1:K/qPe6AP2TGYv4l6n7c88zh9jWBDf6nHhvg1fx/EWfU= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.8.0 h1:JNMALY8/ZnFsfAzBHtC4gq8JeZPANmIoI2VaBgYzbf8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.8.0/go.mod h1:rBDLgXDAwHOfxZKLRDl8OGTPzFDC+a2pLqNNj8+QwfI= +github.com/aws/aws-sdk-go-v2/service/sqs v1.0.0 h1:k+iXUEMp688JqUcxb4/bzt7xgJX4TLqahrwgWA/qO6E= +github.com/aws/aws-sdk-go-v2/service/sqs v1.0.0/go.mod h1:w5BclCU8ptTbagzXS/fHBr+vAyXUjggg/72qDIURKMk= +github.com/aws/aws-sdk-go-v2/service/sqs v1.1.0/go.mod h1:eegUYm2QRvTXZHxoJVJb++B9KuuwY9FlEN8M1m4UGdQ= +github.com/aws/aws-sdk-go-v2/service/sqs v1.1.1/go.mod h1:vT8RRjBL5Z9KBZGGhjLcG6pngVLeq7MqySFsNdGFjSc= +github.com/aws/aws-sdk-go-v2/service/sqs v1.1.2/go.mod h1:WE80cEEKvwrSokRKjw0rVRLpOMEmz/ohunPPz6RPFmE= +github.com/aws/aws-sdk-go-v2/service/sqs v1.2.0/go.mod h1:awrG8Wc10KOM699PzQLaXrqJ0hTKQWeF5wyqQg+FC4Q= +github.com/aws/aws-sdk-go-v2/service/sqs v1.3.0/go.mod h1:9bo6m/Pes+TR6ORT97E1BkczbSgbpKB1qTyFt08s9yw= +github.com/aws/aws-sdk-go-v2/service/sqs v1.3.1/go.mod h1:E6ASpQhmNCYI1xKp//5LwIsdvtAqcFMFA7chocaSWY8= +github.com/aws/aws-sdk-go-v2/service/sqs v1.3.2/go.mod h1:azU2fdsGG7VXFyFLUT5LhN19DjGIJIbUFacx29jR6H4= +github.com/aws/aws-sdk-go-v2/service/sqs v1.4.0/go.mod h1:p/0QcWboH0jsZIAJjDR6MwoCfhXQpAZ3Q5ixVRdjfGk= +github.com/aws/aws-sdk-go-v2/service/sqs v1.4.1/go.mod h1:iNDdLqeQrLCNX00hEi9lh5nNtQmjpigLgYwaTBLa0Ss= +github.com/aws/aws-sdk-go-v2/service/sqs v1.4.2/go.mod h1:iNDdLqeQrLCNX00hEi9lh5nNtQmjpigLgYwaTBLa0Ss= +github.com/aws/aws-sdk-go-v2/service/sqs v1.5.0/go.mod h1:8iLn005F6ASRIXmp6U4hfRAk8EHAtRPrx1oHyxxz2xg= +github.com/aws/aws-sdk-go-v2/service/sqs v1.6.0/go.mod h1:8iLn005F6ASRIXmp6U4hfRAk8EHAtRPrx1oHyxxz2xg= +github.com/aws/aws-sdk-go-v2/service/sqs v1.7.0/go.mod h1:HVJRLGOun8iIoQkfgsNrhnPhuuC+qGV9Nqn5kUJbCFE= +github.com/aws/aws-sdk-go-v2/service/sqs v1.7.1/go.mod h1:7cXocuYx/NzeI/VS3pWStOyQQo/JafG3uglH1M5nME8= +github.com/aws/aws-sdk-go-v2/service/sqs v1.7.2/go.mod h1:TGLWOGp2jII8DZhzRUQXcrsYMvk7fqz8zYdNPq4YQ8Y= +github.com/aws/aws-sdk-go-v2/service/sqs v1.8.0/go.mod h1:BXA1CVaEd9TBOQ8G2ke7lMWdVggAeh35+h2HDO50z7s= +github.com/aws/aws-sdk-go-v2/service/sqs v1.9.0/go.mod h1:BXA1CVaEd9TBOQ8G2ke7lMWdVggAeh35+h2HDO50z7s= +github.com/aws/aws-sdk-go-v2/service/sqs v1.9.1/go.mod h1:nbjBtoH25NLQ7Pv/QqmB94JLDdy3kSGvys2iH2OBspk= +github.com/aws/aws-sdk-go-v2/service/sqs v1.9.2/go.mod h1:2OlivRJM+dMMrVwgPN+NILHNC0hAutQ0IbfPD7638uY= +github.com/aws/aws-sdk-go-v2/service/sqs v1.10.0/go.mod h1:l6Q5eEmSTmzpFLp8XZk4dUs7SHw1lZ3WaAHaoYSWx6g= +github.com/aws/aws-sdk-go-v2/service/sqs v1.11.0/go.mod h1:TDqDmQnsbgL2ZMIGUf3z9xTzCMqFX7FP1geAgIlYqvA= +github.com/aws/aws-sdk-go-v2/service/sqs v1.12.0/go.mod h1:TDqDmQnsbgL2ZMIGUf3z9xTzCMqFX7FP1geAgIlYqvA= +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/sqs v1.13.0/go.mod h1:yKe1+YpZtnwz7h6juYHGg3ZfNMv8qbjexS6sUcPn3yY= github.com/aws/aws-sdk-go-v2/service/sqs v1.13.1/go.mod h1:gOsepb5p+dWNJqP37uG78TR3cO0zYlGFLJT9zCCaaX8= -github.com/aws/aws-sdk-go-v2/service/sso v1.6.2 h1:2IDmvSb86KT44lSg1uU4ONpzgWLOuApRl6Tg54mZ6Dk= +github.com/aws/aws-sdk-go-v2/service/sqs v1.14.0/go.mod h1:gOsepb5p+dWNJqP37uG78TR3cO0zYlGFLJT9zCCaaX8= +github.com/aws/aws-sdk-go-v2/service/sqs v1.15.0/go.mod h1:z9jr/hWntzJNl1ISnw27SCKa/bnI9Pm0u0OgEKxrE2Y= +github.com/aws/aws-sdk-go-v2/service/sqs v1.16.0/go.mod h1:IBTQMG8mtyj37OWg7vIXcg714Ntcb/LlYou/rZpvV1k= +github.com/aws/aws-sdk-go-v2/service/sqs v1.17.0 h1:lIB4FVkPa0+grey3qDUAyCGj8jJso9KSz11Gr3ws5HQ= +github.com/aws/aws-sdk-go-v2/service/sqs v1.17.0/go.mod h1:z6LyQ9Qh7lhLI+e/NOg5/jA7/Fc0dtsia91JMqBinII= +github.com/aws/aws-sdk-go-v2/service/sso v1.0.0/go.mod h1:qNdDupP6xoM//zL1JmPl2XGbyPL5kKrlsoYVh8XZxzQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.1.0/go.mod h1:VnS0vieB4YxutHFP9ROJ3ciT3T/XJZjxxv9L39eo8OQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= +github.com/aws/aws-sdk-go-v2/service/sso v1.1.2/go.mod h1:5yU1oE3+CVYYLUsaHt2AVU3CJJZ6ER4pwsrRD1L2KSc= +github.com/aws/aws-sdk-go-v2/service/sso v1.1.3/go.mod h1:F1l5lKzDzoY3/0cFbB3AA/ey9MsNiH5rhf6HOssy1/Q= +github.com/aws/aws-sdk-go-v2/service/sso v1.1.4/go.mod h1:yQayEbOWH75NaKFylsFocBc3yanYEGndlOaH4i/Lvno= +github.com/aws/aws-sdk-go-v2/service/sso v1.1.5/go.mod h1:bpGz0tidC4y39sZkQSkpO/J0tzWCMXHbw6FZ0j1GkWM= +github.com/aws/aws-sdk-go-v2/service/sso v1.1.6/go.mod h1:EO4s+JzAllrWKgNjS/Q4mj43vGimSvhWCB6BLognegc= +github.com/aws/aws-sdk-go-v2/service/sso v1.2.0/go.mod h1:5qnaL4AtNElFr+a5mdkvD+89jGwpTVyWWX5W/eLzmes= +github.com/aws/aws-sdk-go-v2/service/sso v1.2.1/go.mod h1:VimPFPltQ/920i1X0Sb0VJBROLIHkDg2MNP10D46OGs= +github.com/aws/aws-sdk-go-v2/service/sso v1.3.0/go.mod h1:qWR+TUuvfji9udM79e4CPe87C5+SjMEb2TFXkZaI0Vc= +github.com/aws/aws-sdk-go-v2/service/sso v1.3.1/go.mod h1:J3A3RGUvuCZjvSuZEcOpHDnzZP/sKbhDWV2T1EOzFIM= +github.com/aws/aws-sdk-go-v2/service/sso v1.3.2/go.mod h1:J21I6kF+d/6XHVk7kp/cx9YVD2TMD2TbLwtRGVcinXo= +github.com/aws/aws-sdk-go-v2/service/sso v1.3.3/go.mod h1:Jgw5O+SK7MZ2Yi9Yvzb4PggAPYaFSliiQuWR0hNjexk= +github.com/aws/aws-sdk-go-v2/service/sso v1.4.0/go.mod h1:+1fpWnL96DL23aXPpMGbsmKe8jLTEfbjuQoA4WS1VaA= +github.com/aws/aws-sdk-go-v2/service/sso v1.4.1/go.mod h1:ycPdbJZlM0BLhuBnd80WX9PucWPG88qps/2jl9HugXs= +github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk= +github.com/aws/aws-sdk-go-v2/service/sso v1.5.0/go.mod h1:GsqaJOJeOfeYD88/2vHWKXegvDRofDqWwC5i48A2kgs= +github.com/aws/aws-sdk-go-v2/service/sso v1.6.0/go.mod h1:Q/l0ON1annSU+mc0JybDy1Gy6dnJxIcWjphO6qJPzvM= +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/sso v1.6.2/go.mod h1:KnIpszaIdwI33tmc/W/GGXyn22c1USYxA/2KyvoeDY0= -github.com/aws/aws-sdk-go-v2/service/sts v1.11.1 h1:QKR7wy5e650q70PFKMfGF9sTo0rZgUevSSJ4wxmyWXk= +github.com/aws/aws-sdk-go-v2/service/sso v1.7.0/go.mod h1:KnIpszaIdwI33tmc/W/GGXyn22c1USYxA/2KyvoeDY0= +github.com/aws/aws-sdk-go-v2/service/sso v1.8.0/go.mod h1:AB6v3BedyhVRIbPQbJnUsBmtup2pFiikpp5n3YyB6Ac= +github.com/aws/aws-sdk-go-v2/service/sso v1.9.0/go.mod h1:vCV4glupK3tR7pw7ks7Y4jYRL86VvxS+g5qk04YeWrU= +github.com/aws/aws-sdk-go-v2/service/sso v1.10.0 h1:qCuSRiQhsPU46NH79HUyPQEn5AcpMj+2gsqMYwtzdw8= +github.com/aws/aws-sdk-go-v2/service/sso v1.10.0/go.mod h1:m1CRRFX7eH3EE6w0ntdu+lo+Ph9VS7y8qRV/vdym0ZY= +github.com/aws/aws-sdk-go-v2/service/sts v1.0.0 h1:6XCgxNfE4L/Fnq+InhVNd16DKc6Ue1f3dJl3IwwJRUQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.0.0/go.mod h1:5f+cELGATgill5Pu3/vK3Ebuigstc+qYEHW5MvGWZO4= +github.com/aws/aws-sdk-go-v2/service/sts v1.1.0/go.mod h1:A15vQm/MsXL3a410CxwKQ5IBoSvIg+cr10fEFzPgEYs= +github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= +github.com/aws/aws-sdk-go-v2/service/sts v1.1.2/go.mod h1:zu7rotIY9P4Aoc6ytqLP9jeYrECDHUODB5Gbp+BSHl8= +github.com/aws/aws-sdk-go-v2/service/sts v1.2.0/go.mod h1:iGyHChDhzbddWEbC/+g/mT3z+A2JTJthcw+8QubXSgk= +github.com/aws/aws-sdk-go-v2/service/sts v1.2.1/go.mod h1:L1LH5nHMXxdkKj057ZUx7Wi50CCrkZ+9jkTnBnY2j/w= +github.com/aws/aws-sdk-go-v2/service/sts v1.2.2/go.mod h1:ssRzzJ2RZOVuKj2Vx1YE7ypfil/BIlgmQnCSW4DistU= +github.com/aws/aws-sdk-go-v2/service/sts v1.3.0/go.mod h1:ssRzzJ2RZOVuKj2Vx1YE7ypfil/BIlgmQnCSW4DistU= +github.com/aws/aws-sdk-go-v2/service/sts v1.3.1/go.mod h1:QPpnumNNgybBiz3HXGgDRf/QypAMOOsh4+JdOhG5mLU= +github.com/aws/aws-sdk-go-v2/service/sts v1.4.0/go.mod h1:VJE1MpZYuhpWfOVmz7xFou78H5uJc7RX+8CKpbRaG9k= +github.com/aws/aws-sdk-go-v2/service/sts v1.4.1/go.mod h1:G9osDWA52WQ38BDcj65VY1cNmcAQXAXTsE8IWH8j81w= +github.com/aws/aws-sdk-go-v2/service/sts v1.5.0/go.mod h1:HjDKUmissf6Mlut+WzG2r35r6LeTKmLEDJ6p9NryzLg= +github.com/aws/aws-sdk-go-v2/service/sts v1.6.0/go.mod h1:q7o0j7d7HrJk/vr9uUt3BVRASvcU7gYZB9PUgPiByXg= +github.com/aws/aws-sdk-go-v2/service/sts v1.6.1/go.mod h1:hLZ/AnkIKHLuPGjEiyghNEdvJ2PP0MgOxcmv9EBJ4xs= +github.com/aws/aws-sdk-go-v2/service/sts v1.6.2/go.mod h1:RBhoMJB8yFToaCnbe0jNq5Dcdy0jp6LhHqg55rjClkM= +github.com/aws/aws-sdk-go-v2/service/sts v1.7.0/go.mod h1:0qcSMCyASQPN2sk/1KQLQ2Fh6yq8wm0HSDAimPhzCoM= +github.com/aws/aws-sdk-go-v2/service/sts v1.7.1/go.mod h1:r1i8QwKPzwByXqZb3POQfBs7jozrdnHz8PVbsvyx73w= +github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g= +github.com/aws/aws-sdk-go-v2/service/sts v1.8.0/go.mod h1:dOlm91B439le5y1vtPCk5yJtbx3RdT3hRGYRY8TYKvQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.9.0/go.mod h1:jLKCFqS+1T4i7HDqCP9GM4Uk75YW1cS0o82LdxpMyOE= +github.com/aws/aws-sdk-go-v2/service/sts v1.10.0/go.mod h1:jLKCFqS+1T4i7HDqCP9GM4Uk75YW1cS0o82LdxpMyOE= +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/aws-sdk-go-v2/service/sts v1.11.0/go.mod h1:+BmlPeQ1Y+PuIho93MMKDby12PoUnt1SZXQdEHCzSlw= github.com/aws/aws-sdk-go-v2/service/sts v1.11.1/go.mod h1:UV2N5HaPfdbDpkgkz4sRzWCvQswZjdO1FfqCWl0t7RA= +github.com/aws/aws-sdk-go-v2/service/sts v1.12.0/go.mod h1:UV2N5HaPfdbDpkgkz4sRzWCvQswZjdO1FfqCWl0t7RA= +github.com/aws/aws-sdk-go-v2/service/sts v1.13.0/go.mod h1:jQto17aC9pJ6xRa1g29uXZhbcS6qNT3PSnKfPShq4sY= +github.com/aws/aws-sdk-go-v2/service/sts v1.14.0/go.mod h1:u0xMJKDvvfocRjiozsoZglVNXRG19043xzp3r2ivLIk= +github.com/aws/aws-sdk-go-v2/service/sts v1.15.0 h1:zC/vHxWTlqZ0tIPJItg0zWHsa25cH7tXsUknSGcH39o= +github.com/aws/aws-sdk-go-v2/service/sts v1.15.0/go.mod h1:E264g2Gl5U9KTGzmd8ypGEAoh75VmqyuA/Ox5O1eRE4= +github.com/aws/smithy-go v1.0.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= 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/aws/smithy-go v1.11.0 h1:nOfSDwiiH232f90OuevPnAEQO5ZqH+xnn8uGVsvBCw4= +github.com/aws/smithy-go v1.11.0/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= 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= @@ -135,12 +470,16 @@ 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/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw= +github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/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.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= +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= @@ -154,43 +493,98 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX 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/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= 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/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/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.0.0 h1:y+G9NTXsvoelf1cRzjtLKOZsPqh71noS4+t+e+eINIk= +github.com/confluentinc/confluent-kafka-go v1.0.0/go.mod h1:u2zNLny2xq+5rWeTQjFHbDzzNuba4P1vo31r9r4uAdg= +github.com/confluentinc/confluent-kafka-go v1.1.0 h1:HIW7Nkm8IeKRotC34mGY06DwQMf9Mp9PZMyqDxid2wI= +github.com/confluentinc/confluent-kafka-go v1.1.0/go.mod h1:u2zNLny2xq+5rWeTQjFHbDzzNuba4P1vo31r9r4uAdg= +github.com/confluentinc/confluent-kafka-go v1.3.0 h1:1gJm/SgUqG01jRYJ1pSZ9GldAhXZ0xrFPKxINlVZ3z0= +github.com/confluentinc/confluent-kafka-go v1.3.0/go.mod h1:MPUvNqmycSJrQZKGPS6LyLZLJH1hmLKkBmHQgQR7Ma0= +github.com/confluentinc/confluent-kafka-go v1.4.0 h1:GCEMecax8zLZsCVn1cea7Y1uR/lRCdCDednpkc0NLsY= +github.com/confluentinc/confluent-kafka-go v1.4.0/go.mod h1:u2zNLny2xq+5rWeTQjFHbDzzNuba4P1vo31r9r4uAdg= +github.com/confluentinc/confluent-kafka-go v1.5.2 h1:l+qt+a0Okmq0Bdr1P55IX4fiwFJyg0lZQmfHkAFkv7E= +github.com/confluentinc/confluent-kafka-go v1.5.2/go.mod h1:u2zNLny2xq+5rWeTQjFHbDzzNuba4P1vo31r9r4uAdg= +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/confluentinc/confluent-kafka-go v1.8.2 h1:PBdbvYpyOdFLehj8j+9ba7FL4c4Moxn79gy9cYKxG5E= github.com/confluentinc/confluent-kafka-go v1.8.2/go.mod h1:u2zNLny2xq+5rWeTQjFHbDzzNuba4P1vo31r9r4uAdg= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200709052629-daa8e1ccc0bc/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= 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 v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denisenkom/go-mssqldb v0.11.0 h1:9rHa233rhdOyrz2GcP9NM+gi2psgJZ4GWDpL/7ND8HI= +github.com/denisenkom/go-mssqldb v0.11.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= 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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 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/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.4.2-0.20200319182547-c7ad2b866182/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 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.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= 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 v0.0.0 h1:Pd5fqOuBxKxv83b0+xOAJDAkziWYwFinWnBO0y+TZaA= +github.com/elastic/go-elasticsearch/v6 v6.7.0 h1:Bluo96v8weQRM6IYBXrI3O1+iDbxU6iEzcXbDBfDlwA= +github.com/elastic/go-elasticsearch/v6 v6.7.0/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI= +github.com/elastic/go-elasticsearch/v6 v6.8.0 h1:dW7bU4Jrh1qTT+632bvhwT7ty9njItogsbjA9BMqec0= +github.com/elastic/go-elasticsearch/v6 v6.8.0/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI= +github.com/elastic/go-elasticsearch/v6 v6.8.1 h1:y2bZNOSIimpKuxR/MVeDPERCkDYuBj9kkCTYUqDlhY0= +github.com/elastic/go-elasticsearch/v6 v6.8.1/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI= +github.com/elastic/go-elasticsearch/v6 v6.8.2 h1:rp5DGrd63V5c6nHLjF6QEXUpZSvs0+QM3ld7m9VhV2g= +github.com/elastic/go-elasticsearch/v6 v6.8.2/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI= +github.com/elastic/go-elasticsearch/v6 v6.8.5 h1:U2HtkBseC1FNBmDr0TR2tKltL6FxoY+niDAlj5M8TK8= +github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI= 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.16.0 h1:GHsxDFXIAlhSleXun4kwA89P7kQFADRChqvgOPeYP5A= -github.com/elastic/go-elasticsearch/v7 v7.16.0/go.mod h1:OJ4wdbtDNk5g503kvlHLyErCgQwwzmDtaFC4XyOxXA4= +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/elastic/go-elasticsearch/v7 v7.17.1 h1:49mHcHx7lpCL8cW1aioEwSEVKQF3s+Igi4Ye/QTWwmk= +github.com/elastic/go-elasticsearch/v7 v7.17.1/go.mod h1:OJ4wdbtDNk5g503kvlHLyErCgQwwzmDtaFC4XyOxXA4= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 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 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/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= @@ -198,16 +592,19 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF 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.8/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 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.9/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.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +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= @@ -218,6 +615,7 @@ github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoD 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.10.0 h1:Gfh+GAJZOAoKZsIZeZbdn2JF10kN1XHNvjsvQK8gVkE= 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= @@ -227,37 +625,58 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo 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/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 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.0 h1:jGB9xAJQ12AIGNB4HguylppmDK1Am9ppF7XnGXXJuoU= +github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= 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-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-chi/chi v1.0.0 h1:s/kv1cTXfivYjdKJdyUzNGyAWZ/2t7duW1gKn5ivu+c= +github.com/go-chi/chi v1.0.0/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/chi v1.5.0 h1:2ZcJZozJ+rj6BA0c19ffBUGXEKAT/aOLOtQjD46vBRA= +github.com/go-chi/chi v1.5.0/go.mod h1:REp24E+25iKvxgeTfHmdUoL5x15kBiDBlnIl5bCwe2k= 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/v4 v4.0.0-rc1 h1:Yv+WYpL04c1RUcMGrPLpITSDxXt48VyxCPgyGyzHYXE= +github.com/go-chi/chi/v4 v4.0.0-rc1/go.mod h1:Yfiy+5nynjDc7IMJiguACIro1KxlGW2dLUqcroaEUEY= +github.com/go-chi/chi/v5 v5.0.0 h1:DBPx88FjZJH3FsICfDAfIfnb7XxKIYVGG6lOPlhENAg= +github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= 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-ini/ini v1.25.4 h1:Mujh4R/dH6YL8bxuISne3xX2+qcQ9p0IxKAP6ExWoUo= +github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 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.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= +github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8= 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-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +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.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +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-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-pg/pg v8.0.7+incompatible h1:ty/sXL1OZLo+47KK9N8llRcmbA9tZasqbQ/OO4ld53g= +github.com/go-pg/pg/v10 v10.0.0 h1:2XP/r9XdRfiC+LKWrIwqi2qqc+bhvW7/UpUVnwkT7wk= +github.com/go-pg/pg/v10 v10.0.0/go.mod h1:XHU1AkQW534GFuUdSiQ46+Xw6Ah+9+b8DlT4YwhiXL8= 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= @@ -268,29 +687,77 @@ github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8c 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/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= 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.0.0 h1:YnNbhACmIG6EXnOga4EkokjndozVr+/EGXgE9MHa1n4= +github.com/go-redis/redis/v7 v7.0.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= +github.com/go-redis/redis/v7 v7.1.0 h1:I4C4a8UGbFejiVjtYVTRVOiMIJ5pm5Yru6ibvDX/OS0= +github.com/go-redis/redis/v7 v7.1.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= 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.0.0 h1:PC0VsF9sFFd2sko5bu30aEFc8F1TKl6n65o0b8FnCIE= +github.com/go-redis/redis/v8 v8.0.0/go.mod h1:isLoQT/NFSP7V67lyvM9GmdvLdyZ7pEhsXvvyQtnQTo= 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.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 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-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 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/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-20211015133455-b225f9b53fa1 h1:px9qUCy/RNJNsfCam4m2IxWGxNuimkrioEF0vrrbPsg= github.com/gocql/gocql v0.0.0-20211015133455-b225f9b53fa1/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8= -github.com/gofiber/fiber/v2 v2.23.0 h1:kcJGMC6SULJ2G7p7mbs+A28cVLOeJSR694jfGyGZqRI= -github.com/gofiber/fiber/v2 v2.23.0/go.mod h1:MR1usVH3JHYRyQwMe2eZXRSZHRX38fkV+A7CPB+DlDQ= +github.com/gocql/gocql v0.0.0-20220224095938-0eacd3183625 h1:6ImvI6U901e1ezn/8u2z3bh1DZIvMOia0yTSBxhy4Ao= +github.com/gocql/gocql v0.0.0-20220224095938-0eacd3183625/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/gofiber/fiber/v2 v2.0.0 h1:YF2BcEO7aJtIR84QjfMzxwEVsbOmh2xFzPlNvX53hTs= +github.com/gofiber/fiber/v2 v2.0.0/go.mod h1:GeIpT8VILgZt3Tn6gATjwb39Ff8OdM0qnZ2grAA0Vts= +github.com/gofiber/fiber/v2 v2.11.0 h1:97PoVZI3JLlJyfMHFhKZoEHQEfTwOXvhQs2+YoLr9jk= +github.com/gofiber/fiber/v2 v2.11.0/go.mod h1:oZTLWqYnqpMMuF922SjGbsYZsdpE1MCfh416HNdweIM= +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/gofiber/fiber/v2 v2.26.0 h1:Awnfqp3fqbZzV3wZWMRJ6Xo2U8X0Ls68M7tXjx52NcM= +github.com/gofiber/fiber/v2 v2.26.0/go.mod h1:7efVWcBOZi1PyMWznnbitjnARPA7nYZxmQXJVod0bo0= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 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.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 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= @@ -298,8 +765,10 @@ github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZ 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= @@ -312,6 +781,7 @@ github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 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 v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 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= @@ -324,6 +794,7 @@ 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/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -331,13 +802,21 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx 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/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= 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.6 h1:h7kHSqUl2kxeaQtVslsfUCPJ1oz2pxcyzLy4zezIzPw= -github.com/gomodule/redigo v1.8.6/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= +github.com/gomodule/redigo v1.7.0 h1:ZKld1VOtsGhAe37E7wMxEDgAlGM5dvFY+DiOhSkhP9Y= +github.com/gomodule/redigo v1.7.0/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +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/gomodule/redigo v1.8.8 h1:f6cXq6RRfiyrOJEV7p3JhLDlmawGBVBBP1MggY8Mo4E= +github.com/gomodule/redigo v1.8.8/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= +github.com/gomodule/redigo/redis v0.0.0-do-not-use h1:J7XIp6Kau0WoyT4JtXHT3Ei0gA1KkSc6bc87j9v9WIo= 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= @@ -347,6 +826,7 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw 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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -354,10 +834,14 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ 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/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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 h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= 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= @@ -373,22 +857,37 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe 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 h1:l2YRhr+YLzmSp7KJMswRVk/lO5SwoFIcCLzJsVj+YPc= +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/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/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.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 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/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.2.0 h1:3XN1wbFJAJzEqzeUqlVF0qgpqZFHfV1YNHYPU+odUnw= +github.com/gorilla/mux v1.2.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.5.0 h1:mq8bRov+5x+pZNR/uAHyUEgovR9gLgYFwDQIeuYi9TM= +github.com/gorilla/mux v1.5.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 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= @@ -398,40 +897,69 @@ github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/z 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/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0= +github.com/graph-gophers/graphql-go v1.3.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.15.2/go.mod h1:vO11I9oWA+KsxmfFQPhLnnIb1VDE24M+pdxZFiuZcA8= 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/api v1.12.0 h1:k3y1FYv6nuKyNTqj6w9gXOx5r5CfLj/k/euUeBXj1OY= -github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= +github.com/hashicorp/consul v1.8.1 h1:wYXDYBHhCO7FNGq1dWuCFeZHjzSe/v24YNVPJhtwMbk= +github.com/hashicorp/consul/api v1.0.0 h1:9GEZpB5zZ2Vm+rQ0t0JL/Ey2iaXbmIN1evA/LUU4do4= +github.com/hashicorp/consul/api v1.0.0/go.mod h1:mbFwfRxOTDHZpT3iUsMAFcLNoVm6Xbe1xZ6KiSm8FY0= +github.com/hashicorp/consul/api v1.8.1 h1:BOEQaMWoGMhmQ29fC26bi0qb7/rId9JzZP2V0Xmx7m8= +github.com/hashicorp/consul/api v1.8.1/go.mod h1:sDjTOq0yUyv5G4h+BqSea7Fn6BU+XbolEz1952UB+mk= +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/internal v0.1.0 h1:jNHZgWe9hbMO05/LRoJYHp/wWNiBwz+Ce6YlppiAEo8= +github.com/hashicorp/consul/internal v0.1.0/go.mod h1:zi9bMZYbiPHyAjgBWo7kCUcy5l2NrTdrkVupCc7Oo6c= +github.com/hashicorp/consul/sdk v0.7.0 h1:H6R9d008jDcHPQPAqPNuydAshJ4v5/8URdFnUvK/+sc= +github.com/hashicorp/consul/sdk v0.7.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= 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 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= 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-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +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.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.12.0 h1:d4QkX8FRTYaKaCZBoXYY8zJX2BXjWxurN/GA2tkrmZM= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU= 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 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.1.0 h1:vN9wG1D6KG6YHRTWr8512cxGOVgTMEfgEdSj/hr8MPc= +github.com/hashicorp/go-immutable-radix v1.1.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/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= 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.0.0/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= 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.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.6.2/go.mod h1:gEx6HMUGxYYhJScX7W1Il64m6cc2C1mDaW3NQ9sY1FY= 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.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= 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= @@ -451,31 +979,83 @@ 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-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 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.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 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.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= -github.com/hashicorp/memberlist v0.3.0 h1:8+567mCcFDnS5ADl7lrpxPMWiFCElyUEeW0gtj34fMA= -github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/serf v0.9.6 h1:uuEX1kLR6aoda1TBttmJQKDLZE1Ob7KN0NPdE7EtCDc= -github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/memberlist v0.1.6 h1:ouPxvwKYaNZe+eTcHxYP0EblPduVLvIPycul+vv8his= +github.com/hashicorp/memberlist v0.1.6/go.mod h1:5VDNHjqFMgEcclnwmkCnC99IPwxBmIsxwY8qn+Nl0H4= +github.com/hashicorp/memberlist v0.1.7/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.2.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.2.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +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.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/serf v0.8.6 h1:w2ZEHuK1297elT/WbZjUojVzpZA3BuPUusa9vdXXTjc= +github.com/hashicorp/serf v0.8.6/go.mod h1:P/AVgr4UHsUYqVHG1y9eFhz8S35pqhGhLZaDpfGKIMo= +github.com/hashicorp/serf v0.9.0/go.mod h1:YL0HO+FifKOW2u1ke99DGVu1zhcpZzNwrLIqBC7vbYU= +github.com/hashicorp/serf v0.9.1/go.mod h1:9O54gmUahS6cDUNpIQFouJdluzAIfBOp4mSLSvf5ZAE= +github.com/hashicorp/serf v0.9.2/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/serf v0.9.3/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/serf v0.9.4/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +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 v0.2.0 h1:g6xnu+h48EPjqb+kMGjq0vpvpZlZl9UatYVTeu+/nbk= +github.com/hashicorp/vault v1.9.3 h1:dc3moxoDHuT1gMwKsyqo0STIV+6X+kJVcfQGRCv7UTg= +github.com/hashicorp/vault/api v0.4.0/go.mod h1:iFkkmIUHES5OQ3OPXHusNSPOzVwRSerk0AtUYhYhmNI= +github.com/hashicorp/vault/api v1.0.1 h1:YQI4SgOlkmbEKZI8ZClo6fm9oXlBHJUlrbEtFiRPrng= +github.com/hashicorp/vault/api v1.0.1/go.mod h1:AV/+M5VPDpB90arloVX0rVDUIHkONiwz5Uza9HRtpUE= +github.com/hashicorp/vault/api v1.0.2/go.mod h1:AV/+M5VPDpB90arloVX0rVDUIHkONiwz5Uza9HRtpUE= +github.com/hashicorp/vault/api v1.0.3/go.mod h1:aXl6A3Tfh50BoCNJd3oM3IK+bj2m/vVQGu8blfCvEsY= +github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= +github.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f h1:PYtnlUZzFSZxPcq7mYp5oC9N+BcJ8IKYf6/EG0GHM2Y= +github.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f/go.mod h1:euTFbi2YJgwcju3imEt919lhJKF68nN1cQPq3aA+kBE= +github.com/hashicorp/vault/api v1.1.0 h1:QcxC7FuqEl0sZaIjcXB/kNEeBa0DH5z57qbWBvZwLC4= +github.com/hashicorp/vault/api v1.1.0/go.mod h1:R3Umvhlxi2TN7Ex2hzOowyeNb+SfbVWI973N+ctaFMk= 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/api v1.4.1 h1:mWLfPT0RhxBitjKr6swieCEP2v5pp/M//t70S3kMLRo= +github.com/hashicorp/vault/api v1.4.1/go.mod h1:LkMdrZnWNrFaQyYYazWVn7KshilfDidgVBq6YiTq/bM= +github.com/hashicorp/vault/sdk v0.1.8/go.mod h1:tHZfc6St71twLizWNHvnnbiGFo1aq0eD2jGPLtP8kAU= +github.com/hashicorp/vault/sdk v0.1.9/go.mod h1:tHZfc6St71twLizWNHvnnbiGFo1aq0eD2jGPLtP8kAU= +github.com/hashicorp/vault/sdk v0.1.10/go.mod h1:XF2Bod+ahPWGARnyFq5LfkOZwWwvveR5ptYwJLqK0ZI= +github.com/hashicorp/vault/sdk v0.1.11/go.mod h1:XF2Bod+ahPWGARnyFq5LfkOZwWwvveR5ptYwJLqK0ZI= +github.com/hashicorp/vault/sdk v0.1.12/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= +github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= +github.com/hashicorp/vault/sdk v0.1.14-0.20200519221530-14615acda45f/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= +github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267 h1:e1ok06zGrWJW91rzRroyl5nRNqraaBe4d5hiKcVZuHM= +github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= +github.com/hashicorp/vault/sdk v0.2.0 h1:hvVswvMA9LvXwLBFDJLIoDBXi8hj90Q+gSS7vRYmLvQ= +github.com/hashicorp/vault/sdk v0.2.0/go.mod h1:cAGI4nVnEfAyMeqt9oB+Mase8DNn3qA/LDNHURiwssY= +github.com/hashicorp/vault/sdk v0.2.1/go.mod h1:WfUiO1vYzfBkz1TmoE4ZGU7HD0T0Cl/rZwaxjBkgN4U= 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/vault/sdk v0.4.0/go.mod h1:aZ3fNuL5VNydQk8GcLJ2TV8YCRVvyaakYkhZRoVuhj0= +github.com/hashicorp/vault/sdk v0.4.1 h1:3SaHOJY687jY1fnB61PtL0cOkKItphrbLmux7T92HBo= +github.com/hashicorp/vault/sdk v0.4.1/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 h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= 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.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 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= @@ -484,9 +1064,15 @@ github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgO 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.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk= +github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= +github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= +github.com/jackc/pgconn v1.6.4/go.mod h1:w2pne1C2tZgP+TvjqLpOigGzNqjBgQW9dUw/4Chex78= +github.com/jackc/pgconn v1.7.0/go.mod h1:sF/lPpNEMEOp+IYhyQGdAvrG20gWf6A1tKlr0v7JMeA= 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.0/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= @@ -503,28 +1089,52 @@ github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod 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.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.0.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.0.5/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 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-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= 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.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0= +github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po= +github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ= +github.com/jackc/pgtype v1.4.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig= +github.com/jackc/pgtype v1.5.0/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig= +github.com/jackc/pgtype v1.6.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.8.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.9.0 h1:/SH1RxEtltvJgsDqp3TbiTFApD3mey3iygpuEGeuBXk= 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.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA= +github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o= +github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= +github.com/jackc/pgx/v4 v4.8.1/go.mod h1:4HOLxrl8wToZJReD04/yB20GDwf4KBYETvlHciCnwW0= +github.com/jackc/pgx/v4 v4.9.0/go.mod h1:MNGWmViCgqbZck9ujOOBN63gK9XVGILXWCvKLGKmnms= +github.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA= +github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0= +github.com/jackc/pgx/v4 v4.14.0 h1:TgdrmgnM7VY72EuSQzBbBd4JA1RLqJolrw9nQVZABVc= 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.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.2/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= @@ -542,41 +1152,75 @@ github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJk 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.1 h1:lDSDtsCt5AGGSKTs8AHlSDbbgif4G4+CKJ8ETBDVHTA= +github.com/jinzhu/gorm v1.9.1/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= 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/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.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/jinzhu/now v1.1.3 h1:PlHq1bSCSZL9K0wUhbm2pGLoTWs2GwVhsP6emvGV/ZI= github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 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/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.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.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 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 v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9 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.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +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.0.0 h1:wqU8SDF8HdYSM+My1MRVV+Er1dDqGTH93dqI6J2wL0E= +github.com/julienschmidt/httprouter v1.0.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.1.0 h1:7wLdtIiIpzOkC9u6sXOozpBauPdskj3ru4EI5MABq68= +github.com/julienschmidt/httprouter v1.1.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= 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.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 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/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.4 h1:0zhec2I8zGnjWcKyLl6i3gPqKANCCn5e9xmviEEeX6s= 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/klauspost/compress v1.14.2 h1:S0OHlFk/Gbon/yauFJ4FfJJF5V0fc5HbBTJazi28pRw= +github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/crc32 v1.2.0 h1:0VuyqOCruD33/lJ/ojXNvzVyl8Zr5zdTmj9l9qLZ86I= +github.com/klauspost/crc32 v1.2.0/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= 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/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= @@ -585,33 +1229,50 @@ 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 v1.4.4 h1:1bEiBNeGSUKxcPDGfZ/7IgdhJJZx8wV/pICJh4W2NJI= 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.2.0 h1:jkCSsjXmBmapVXF6U4BrSz/cgofWM0CU3Q74wQvXkIc= +github.com/labstack/echo/v4 v4.2.0/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= 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/echo/v4 v4.6.3 h1:VhPuIZYxsbPmo4m9KAkMU/el2442eB7EBFFhNTTT9ac= +github.com/labstack/echo/v4 v4.6.3/go.mod h1:Hk5OiHj0kDqmFq7aHe7eDqI7CUhuCrfpupQtLGGLm7A= github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= +github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= 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.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= 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-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20180730094502-03f2033d19d5/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +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/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/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.7/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-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 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= @@ -619,30 +1280,43 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd 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-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.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 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/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0= +github.com/mattn/go-sqlite3 v1.14.12/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.25 h1:dFwPR6SfLtrSwgDcIq2bcU/gVutB4sNApq2HBdqcakg= +github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= 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.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 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.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 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= @@ -652,20 +1326,34 @@ github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0Gq 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= +github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 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.0/go.mod h1:h2vSaBKzz7eL+VsYPtIOXOURZlXmp+yY5MgyIW3Y/M0= +github.com/olivere/elastic/v7 v7.0.1/go.mod h1:h2vSaBKzz7eL+VsYPtIOXOURZlXmp+yY5MgyIW3Y/M0= +github.com/olivere/elastic/v7 v7.0.2/go.mod h1:h2vSaBKzz7eL+VsYPtIOXOURZlXmp+yY5MgyIW3Y/M0= +github.com/olivere/elastic/v7 v7.0.3/go.mod h1:h2vSaBKzz7eL+VsYPtIOXOURZlXmp+yY5MgyIW3Y/M0= +github.com/olivere/elastic/v7 v7.0.4/go.mod h1:l4YWa59iTCcOJQXI5ZtxVjcd3p5U8GCxVgvzHZqGn3o= +github.com/olivere/elastic/v7 v7.0.5/go.mod h1:nut831m8vw5KQbQxX1oXjj3/buiDpDZc5pqNVdH9xYk= +github.com/olivere/elastic/v7 v7.0.6/go.mod h1:nut831m8vw5KQbQxX1oXjj3/buiDpDZc5pqNVdH9xYk= +github.com/olivere/elastic/v7 v7.0.7/go.mod h1:UcXCjbh5xfX9uMB1VCcIYgGJBItbd4uRBdYRsBnnXHo= +github.com/olivere/elastic/v7 v7.0.8/go.mod h1:UcXCjbh5xfX9uMB1VCcIYgGJBItbd4uRBdYRsBnnXHo= +github.com/olivere/elastic/v7 v7.0.9/go.mod h1:2TeRd0vhLRTK9zqm5xP0uLiVeZ5yUoL7kZ+8SZA9r9Y= +github.com/olivere/elastic/v7 v7.0.10/go.mod h1:8eSLXtTkehgLQuc7IioUWYert99D2nQItslIEIpD+xw= +github.com/olivere/elastic/v7 v7.0.11/go.mod h1:p/2CeyP8h6DINTMLi9nUvElbZqkKCpgldwuaLJ6VJwc= 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= @@ -673,6 +1361,7 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W 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.1/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= @@ -681,44 +1370,86 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa 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.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.2/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/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= 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.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= 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 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= 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/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= +github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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 v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v0.9.4/go.mod h1:oCXIBxdI62A4cR6aTRJCgetEjecSIYzOEaeAn4iYEpM= +github.com/prometheus/client_golang v0.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.2.0/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= +github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= 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/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.1.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.3.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.5.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.8.0/go.mod h1:PC/OgXc+UN7B4ALwvn1yzVZmVwvhXp5JsbBv6wSv6i0= +github.com/prometheus/common v0.9.0/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/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-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 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.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 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= @@ -731,11 +1462,16 @@ github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIH 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/segmentio/kafka-go v0.4.29 h1:4ujULpikzHG0HqKhjumDghFjy/0RRCSl/7lbriwQAH0= +github.com/segmentio/kafka-go v0.4.29/go.mod h1:m1lXeqJtIFYZayv0shM/tjrAFljvWLTprxBHd+3PnaU= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 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.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.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/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -745,6 +1481,11 @@ github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h github.com/smartystreets/gunit v1.1.3/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ= 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.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +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.1-0.20171106142849-4c012f6dcd95/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/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= @@ -752,6 +1493,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ 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 v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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= @@ -763,23 +1505,62 @@ github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFd github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= 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.7.1 h1:LPXN3VRIxsdMwyfbtPgOA60jLuj/eEmMpDjOh2szRPw= -github.com/tidwall/btree v0.7.1/go.mod h1:TzIRzen6yHbibdSfK6t8QimqbUnoxUSrZfeW7Uob0q4= -github.com/tidwall/buntdb v1.2.8 h1:Fpwx28OjYBAsZEcffLow6lDAXg4b3k0cWtPTdzArP9c= -github.com/tidwall/buntdb v1.2.8/go.mod h1:Ksv/lHcBXW3JH8A+chohAzMwrQ51ivstZZnEb9RVXww= +github.com/tidwall/btree v0.2.2/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= +github.com/tidwall/btree v0.3.0/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= +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/btree v1.1.0 h1:5P+9WU8ui5uhmcg3SoPyTwoI0mVyZ1nps7YQzTZFkYM= +github.com/tidwall/btree v1.1.0/go.mod h1:TzIRzen6yHbibdSfK6t8QimqbUnoxUSrZfeW7Uob0q4= +github.com/tidwall/buntdb v1.0.0 h1:urIJqQ8OR9fibXXtFSu8sR5arMqZK8ZnNq22yWl+A+8= +github.com/tidwall/buntdb v1.0.0/go.mod h1:Y39xhcDW10WlyYXeLgGftXVbjtM0QP+/kpz8xl9cbzE= +github.com/tidwall/buntdb v1.1.0 h1:H6LzK59KiNjf1nHVPFrYj4Qnl8d8YLBsYamdL8N+Bao= +github.com/tidwall/buntdb v1.1.0/go.mod h1:Y39xhcDW10WlyYXeLgGftXVbjtM0QP+/kpz8xl9cbzE= +github.com/tidwall/buntdb v1.1.5 h1:rxlXykuv5DjQBFJEmWK6kw5332iAzLmSoWsh1gy1KUo= +github.com/tidwall/buntdb v1.1.5/go.mod h1:06+/n7EFf6uUaIG5r9xZcExYN3H0Lnc+g/Kqx0fZFkI= +github.com/tidwall/buntdb v1.1.8 h1:3rHHTlR4uFACKLbC+ws9jH0Ytk1Pd+n/cMEERtbMVKk= +github.com/tidwall/buntdb v1.1.8/go.mod h1:a/Xp8Hsllzxex0WTNdANz2+hOwGaYDHW4xBK9dMp30w= +github.com/tidwall/buntdb v1.1.9 h1:fpqQ6LOxU4H/z1I+xBG1Ozz0WaEyK43kk5XOTKLuHX0= +github.com/tidwall/buntdb v1.1.9/go.mod h1:DAB8UwGyBcBUA5HfxMa0jX7aZ/4rxtzonQHHJRGviA8= +github.com/tidwall/buntdb v1.2.0 h1:8KOzf5Gg97DoCMSOgcwZjnM0FfROtq0fcZkPW54oGKU= +github.com/tidwall/buntdb v1.2.0/go.mod h1:XLza/dhlwzO6dc5o/KWor4kfZSt3BP8QV+77ZMKfI58= +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/buntdb v1.2.9 h1:XVz684P7X6HCTrdr385yDZWB1zt/n20ZNG3M1iGyFm4= +github.com/tidwall/buntdb v1.2.9/go.mod h1:IwyGSvvDg6hnKSIhtdZ0AqhCZGH8ukdtCAzaP8fI1X4= +github.com/tidwall/cities v0.1.0/go.mod h1:lV/HDp2gCcRcHJWqgt6Di54GiDrTZwh1aG2ZUPNbqa4= +github.com/tidwall/geoindex v1.4.4 h1:hdwzy5qNtK75i7nus59Ibr+SwcH4F2v65bw4txrLJ9M= +github.com/tidwall/geoindex v1.4.4/go.mod h1:rvVVNEFfkJVWGUdEfU8QaoOg/9zFX0h9ofWzA60mz1I= +github.com/tidwall/gjson v1.6.1/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0= +github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= +github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= +github.com/tidwall/gjson v1.10.2 h1:APbLGOM0rrEkd8WBw9C24nllro4ajFuJu0Sc9hRz8Bo= +github.com/tidwall/gjson v1.10.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.12.1 h1:ikuZsLdhr8Ws0IdROXUS1Gi4v9Z4pGqpX/CvJkxvfpo= github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M= +github.com/tidwall/grect v0.1.0/go.mod h1:sa5O42oP6jWfTShL9ka6Sgmg3TgIK649veZe05B7+J8= +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/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg= github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q= 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.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 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/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 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/rtree v0.0.0-20201027154624-32188eeb08a8/go.mod h1:/h+UnNGt0IhNNJLkGikcdcJqm66zGD/uJGMRxK/9+Ao= +github.com/tidwall/rtree v0.1.0/go.mod h1:xsujlpupsrYVnWaok1pqGtxhj9TKHSbMbY8eKPatQ4U= +github.com/tidwall/rtree v0.1.1/go.mod h1:xsujlpupsrYVnWaok1pqGtxhj9TKHSbMbY8eKPatQ4U= +github.com/tidwall/rtree v1.3.1 h1:xu3vJPKJrmGce7YJcFUCoqLrp9DTUEJBnVgdPSXHgHs= +github.com/tidwall/rtree v1.3.1/go.mod h1:S+JSsqPTI8LfWA4xHBo5eXzie8WJLVFeppAutSegl6M= +github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ= 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= @@ -787,28 +1568,39 @@ github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDW 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/twitchtv/twirp v8.1.1+incompatible h1:s5WnVKMhC4Xz1jOfNAqTg85iguOWAvsrCJoPiezlLFA= github.com/twitchtv/twirp v8.1.1+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 v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 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/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA= +github.com/valyala/fasthttp v1.26.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA= github.com/valyala/fasthttp v1.31.0 h1:lrauRLII19afgCs2fnWRJ4M5IkV0lo2FqA61uGkNBfE= github.com/valyala/fasthttp v1.31.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= +github.com/valyala/fasthttp v1.32.0 h1:keswgWzyKyNIIjz2a7JmCYHOOIkRp6HMx9oTV6QrZWY= +github.com/valyala/fasthttp v1.32.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= 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 v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= 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/v4 v4.3.11/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/msgpack/v5 v5.0.0-beta.1/go.mod h1:xlngVLeyQ/Qi05oQxhQ+oTuqa03RjMwMfk/7/TCs+QI= 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.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= 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= @@ -819,6 +1611,10 @@ 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/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 v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= 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= @@ -829,16 +1625,23 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 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.8.1 h1:OZE4Wni/SJlrcmSIBRYNzunX5TKxjrTS4jKSnA99oKU= -go.mongodb.org/mongo-driver v1.8.1/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= +go.mongodb.org/mongo-driver v1.5.1 h1:9nOVLGDfOaZ9R0tBumx/BcuqkbFpyTCU2r/Po7A2azI= +go.mongodb.org/mongo-driver v1.5.1/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw= +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.mongodb.org/mongo-driver v1.8.3 h1:TDKlTkGDKm9kkJVUOAXDK5/fkqKHJVwYQSpoRfB43R4= +go.mongodb.org/mongo-driver v1.8.3/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/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/otel v0.11.0 h1:IN2tzQa9Gc4ZVKnTaMbPVcHjvzOdg5n9QfnmlqiET7E= +go.opentelemetry.io/otel v0.11.0/go.mod h1:G8UCk+KooF2HLkgo8RHX9epABH/aRGYET7gQOqBVdB0= 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= @@ -853,22 +1656,36 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E 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-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 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-20190211182817-74369b46fc67/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-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/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-20190911031432-227b76d455e7/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-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/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-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 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= @@ -886,6 +1703,10 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20200901203048-c4f52b2c50aa h1:i1+omYRtqpxiCaQJB4MQhUToKvMPFqUUJKvRiRp0gtE= +golang.org/x/exp v0.0.0-20200901203048-c4f52b2c50aa/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20200908183739-ae8ad444f925 h1:5XVKs2rlCg8EFyRcvO8/XFwYxh1oKJO1Q3X5vttIf9c= +golang.org/x/exp v0.0.0-20200908183739-ae8ad444f925/go.mod h1:1phAWC201xIgDyaFpmDeZkgf70Q4Pd/CNqfRtVPtxNw= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -897,6 +1718,7 @@ 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= @@ -907,16 +1729,23 @@ 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/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/mod v0.3.1-0.20200828183125-ce943fd02449 h1:xUIPaMhvROX9dhPvRCenIJtU78+lbEenGbgqB5hfHCQ= +golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108155000-395948e2f546/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= @@ -931,6 +1760,8 @@ golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLL 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-20191002035440-2ec189313ef0/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= @@ -943,9 +1774,11 @@ golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/ 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/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/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 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= @@ -955,18 +1788,23 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v 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-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= 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/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY= -golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211020060615-d418f374d309 h1:A0lJIi+hcTR6aajJH4YqKWwohY4aW9RO7oRMcdv+HKI= +golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 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= @@ -978,38 +1816,50 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ 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-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/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/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/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= 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-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20181116152217-5ac8a444bdc5/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-20190209173611-3b5209105503/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-20190514135907-3a4b5fb9f71f/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-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/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= @@ -1035,14 +1885,19 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/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= @@ -1050,34 +1905,47 @@ golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7w 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 h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= 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 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E= 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 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= 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-20210831042530-f4d43177bf5e/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-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486 h1:5hpz5aRr+W1erYCL5JRhSUBJRph7l9XkNveoExlrKYk= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/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/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/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 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE= -golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +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/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.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/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 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= @@ -1085,16 +1953,23 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb 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/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-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= @@ -1102,6 +1977,7 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/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= @@ -1130,11 +2006,14 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 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-20200505023115-26f46d2f7ef8/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-20200527183253-8e7acdbce89d/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-20200619180055-7c47624df98f/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-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= @@ -1142,6 +2021,7 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u 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 h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE= 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= @@ -1155,6 +2035,7 @@ golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8T 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= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1169,9 +2050,16 @@ google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/ google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0 h1:J1Pl9P2lnmYFSJvgs70DKELqHNh8CNWXPbud4njEE2s= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.23.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.25.0 h1:LodzhlzZEUfhXzNUMIfVlf9Gr6Ua5MMtoFWh7+f47qA= +google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.26.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.27.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.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= @@ -1188,20 +2076,21 @@ google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqiv 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.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0 h1:n2bqqK895ygnBpdPDYetfy23K7fJ22wsrZKCyfuRkkA= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +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/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/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/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= @@ -1226,7 +2115,11 @@ google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfG 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-20200528110217-3d3490e7e671/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 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= @@ -1259,28 +2152,36 @@ google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEc 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-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/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/genproto v0.0.0-20220228155957-1da8797a5878 h1:gERY0VtsF9UyyyCsPSjRk9/RWlcKSa/Gw/aenR/5z48= +google.golang.org/genproto v0.0.0-20220228155957-1da8797a5878/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.19.1/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.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 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 h1:o1bcQ6imQMIOpdrO3SWf2z5RV72WbDwdXuK0MDlc8As= 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= @@ -1289,9 +2190,10 @@ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ 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.40.1/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 v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= 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= @@ -1302,29 +2204,42 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 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= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= 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/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= 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/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/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= 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/jinzhu/gorm.v1 v1.9.1 h1:63D1Sk0C0mhCbK930D0PkD3nKT8wLxz6lLPh5V6D2hM= +gopkg.in/jinzhu/gorm.v1 v1.9.1/go.mod h1:56JJPUzbikvTVnoyP1nppSkbJ2L8sunqTBDY2fDrmFg= 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/go.mod h1:M3WNlsF+WhYn7api4D87NIflwTV/c0iVs8cqfWhK+68= 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.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 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= @@ -1334,53 +2249,135 @@ 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.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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-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.1 h1:h+3f1l9Ng2C072Y2tIiLgPpWN78r1KXL7bHJ0nTjlhU= +gorm.io/driver/mysql v1.0.1 h1:omJoilUzyrAp0xNoio88lGJCroGdIOen9hq2A/+3ifw= +gorm.io/driver/mysql v1.0.1/go.mod h1:KtqSthtg55lFp3S5kUXqlGaelnWpKitn4k1xZTnoiPw= +gorm.io/driver/mysql v1.0.2/go.mod h1:T+Fv7Rq/8+lpS3X1KKVUbj8Y/SzbPa5esK9KpPAKXR8= +gorm.io/driver/mysql v1.0.3/go.mod h1:twGxftLBlFgNVNakL7F+P/x9oYqoymG3YYT8cAfI9oI= +gorm.io/driver/mysql v1.0.4/go.mod h1:MEgp8tk2n60cSBCq5iTcPDw3ns8Gs+zOva9EUhkknTs= +gorm.io/driver/mysql v1.0.5/go.mod h1:N1OIhHAIhx5SunkMGqWbGFVeh4yTNWKmMo1GOAsohLI= +gorm.io/driver/mysql v1.0.6/go.mod h1:KdrTanmfLPPyAOeYGyG+UpDys7/7eeWT1zCq+oekYnU= +gorm.io/driver/mysql v1.1.0/go.mod h1:KdrTanmfLPPyAOeYGyG+UpDys7/7eeWT1zCq+oekYnU= +gorm.io/driver/mysql v1.1.1/go.mod h1:KdrTanmfLPPyAOeYGyG+UpDys7/7eeWT1zCq+oekYnU= +gorm.io/driver/mysql v1.1.2/go.mod h1:4P/X9vSc3WTrhTLZ259cpFd6xKNYiSSdSZngkSBGIMM= +gorm.io/driver/mysql v1.1.3/go.mod h1:4P/X9vSc3WTrhTLZ259cpFd6xKNYiSSdSZngkSBGIMM= +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/mysql v1.2.1/go.mod h1:qsiz+XcAyMrS6QY+X3M9R6b/lKM1imKmcuK9kac5LTo= +gorm.io/driver/mysql v1.2.2/go.mod h1:qsiz+XcAyMrS6QY+X3M9R6b/lKM1imKmcuK9kac5LTo= +gorm.io/driver/mysql v1.2.3 h1:cZqzlOfg5Kf1VIdLC1D9hT6Cy9BgxhExLj/2tIgUe7Y= +gorm.io/driver/mysql v1.2.3/go.mod h1:qsiz+XcAyMrS6QY+X3M9R6b/lKM1imKmcuK9kac5LTo= +gorm.io/driver/postgres v1.0.0 h1:Yh4jyFQ0a7F+JPU0Gtiam/eKmpT/XFc1FKxotGqc6FM= +gorm.io/driver/postgres v1.0.0/go.mod h1:wtMFcOzmuA5QigNsgEIb7O5lhvH1tHAF1RbWmLWV4to= +gorm.io/driver/postgres v1.0.1/go.mod h1:pv4dVhHvEVrP7k/UYqdBIllbdbpB5VTz89X1O0uOrCA= +gorm.io/driver/postgres v1.0.2/go.mod h1:FvRSYfBI9jEp6ZSjlpS9qNcSjxwYxFc03UOTrHdvvYA= +gorm.io/driver/postgres v1.0.3/go.mod h1:FvRSYfBI9jEp6ZSjlpS9qNcSjxwYxFc03UOTrHdvvYA= +gorm.io/driver/postgres v1.0.4/go.mod h1:FvRSYfBI9jEp6ZSjlpS9qNcSjxwYxFc03UOTrHdvvYA= +gorm.io/driver/postgres v1.0.5/go.mod h1:qrD92UurYzNctBMVCJ8C3VQEjffEuphycXtxOudXNCA= +gorm.io/driver/postgres v1.0.6/go.mod h1:r0nvX27yHDNbVeXMM9Y+9i5xSePcT18RfH8clP6wpwI= +gorm.io/driver/postgres v1.0.7/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg= +gorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg= +gorm.io/driver/postgres v1.1.0/go.mod h1:hXQIwafeRjJvUm+OMxcFWyswJ/vevcpPLlGocwAwuqw= +gorm.io/driver/postgres v1.1.1/go.mod h1:tpe2xN7aCst1NUdYyWQyxPtnHC+Zfp6NEux9PXD1OU0= +gorm.io/driver/postgres v1.1.2/go.mod h1:/AGV0zvqF3mt9ZtzLzQmXWQ/5vr+1V1TyHZGZVjzmwI= +gorm.io/driver/postgres v1.2.0/go.mod h1:c/8rVZUl30/ZyaQtAobsLRbBTubskhCrkWZDwZe1KfI= +gorm.io/driver/postgres v1.2.1/go.mod h1:SHRZhu+D0tLOHV5qbxZRUM6kBcf3jp/kxPz2mYMTsNY= +gorm.io/driver/postgres v1.2.2/go.mod h1:Ik3tK+a3FMp8ORZl29v4b3M0RsgXsaeMXh9s9eVMXco= gorm.io/driver/postgres v1.2.3 h1:f4t0TmNMy9gh3TU2PX+EppoA6YsgFnyq8Ojtddb42To= gorm.io/driver/postgres v1.2.3/go.mod h1:pJV6RgYQPG47aM1f0QeOzFH9HxQc8JcmAgjRCgS0wjs= +gorm.io/driver/sqlserver v1.0.4 h1:V15fszi0XAo7fbx3/cF50ngshDSN4QT0MXpWTylyPTY= +gorm.io/driver/sqlserver v1.0.4/go.mod h1:ciEo5btfITTBCj9BkoUVDvgQbUdLWQNqdFY5OGuGnRg= +gorm.io/driver/sqlserver v1.0.5/go.mod h1:WI/bfZ+s9TigYXe3hb3XjNaUP0TqmTdXl11pECyLATs= +gorm.io/driver/sqlserver v1.0.6/go.mod h1:+DhmnmNftPZOMOTkyLcs+WU5l6Q82TlTDy8skoKb5V8= +gorm.io/driver/sqlserver v1.0.7/go.mod h1:ng66aHI47ZIKz/vvnxzDoonzmTS8HXP+JYlgg67wOog= +gorm.io/driver/sqlserver v1.0.8/go.mod h1:WHXpEvhU8VqBD4Rb2TflB1el5hTUiw0F7ynv4923djo= +gorm.io/driver/sqlserver v1.0.9/go.mod h1:iBdxY2CepkTt9Q1r84RbZA1qCai300Qlp8kQf9qE9II= +gorm.io/driver/sqlserver v1.1.0/go.mod h1:28YUIocRQqNZkPrvrU2DOBG94THZ4N0IKoNGpMcgSfk= +gorm.io/driver/sqlserver v1.1.1/go.mod h1:28YUIocRQqNZkPrvrU2DOBG94THZ4N0IKoNGpMcgSfk= +gorm.io/driver/sqlserver v1.1.2/go.mod h1:mEmVwRbwsgY+EmU7MWypjefdbX5uFgbE07kklGvLmcg= +gorm.io/driver/sqlserver v1.2.0/go.mod h1:nixq0OB3iLXZDiPv6JSOjWuPgpyaRpOIIevYtA4Ulb4= +gorm.io/driver/sqlserver v1.2.1 h1:KhGOjvPX7JZ5hPyQICTJfMuTz88zgJ2lk9bWiHVNHd8= +gorm.io/driver/sqlserver v1.2.1/go.mod h1:nixq0OB3iLXZDiPv6JSOjWuPgpyaRpOIIevYtA4Ulb4= +gorm.io/gorm v1.9.19/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.20.0 h1:qfIlyaZvrF7kMWY3jBdEBXkXJ2M5MFYMTppjILxS3fQ= +gorm.io/gorm v1.20.0/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.20.1 h1:+hOwlHDqvqmBIMflemMVPLJH7tZYK4RxFDBHEfJTup0= +gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.20.2 h1:bZzSEnq7NDGsrd+n3evOOedDrY5oLM5QPlCjZJUK2ro= +gorm.io/gorm v1.20.2/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.20.3 h1:Fn3rtLA6wdE52uZ6xARpsIRBcoIHJNVFE3K4KczRMso= +gorm.io/gorm v1.20.3/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.20.4 h1:fMFR+3bdgx2/vf6VXFgNcsjUL3kSD7ioOFvby3PYTgE= +gorm.io/gorm v1.20.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.20.5 h1:g3tpSF9kggASzReK+Z3dYei1IJODLqNUbOjSuCczY8g= +gorm.io/gorm v1.20.5/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.20.6 h1:qa7tC1WcU+DBI/ZKMxvXy1FcrlGsvxlaKufHrT2qQ08= +gorm.io/gorm v1.20.6/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.20.12 h1:ebZ5KrSHzet+sqOCVdH9mTjW91L298nX3v5lVxAzSUY= +gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.21.0 h1:p5r2eGuuWxNrLFgimttWP51cut3iNuTj0dK0bMUvTgw= +gorm.io/gorm v1.21.0/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.22.2/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= +gorm.io/gorm v1.22.3 h1:/JS6z+GStEQvJNW3t1FTwJwG/gZ+A7crFdRqtvG5ehA= gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= gorm.io/gorm v1.22.4 h1:8aPcyEJhY0MAt8aY6Dc524Pn+pO29K+ydu+e/cXSpQM= gorm.io/gorm v1.22.4/go.mod h1:1aeVC+pe9ZmvKZban/gW4QPra7PRoTEssyc922qCAkk= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= 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/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.23.1 h1:ncu/qfBfUoClqwkTGbeRqqOqBCRoUAflMuOaOD7J0c8= -k8s.io/api v0.23.1/go.mod h1:WfXnOnwSqNtG62Y1CdjoMxh7r7u9QXGCkA1u0na2jgo= -k8s.io/apimachinery v0.23.1 h1:sfBjlDFwj2onG0Ijx5C+SrAoeUscPrmghm7wHP+uXlo= -k8s.io/apimachinery v0.23.1/go.mod h1:SADt2Kl8/sttJ62RRsi9MIV4o8f5S3coArm0Iu3fBno= -k8s.io/client-go v0.23.1 h1:Ma4Fhf/p07Nmj9yAB1H7UwbFHEBrSPg8lviR24U2GiQ= -k8s.io/client-go v0.23.1/go.mod h1:6QSI8fEuqD4zgFK0xbdwfB/PthBsIxCJMa3s17WlcO0= -k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/api v0.17.0 h1:H9d/lw+VkZKEVIUc8F3wgiQ+FUXTTr21M87jXLU7yqM= +k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI= +k8s.io/api v0.22.4 h1:UvyHW0ezB2oIgHAxlYoo6UJQObYXU7awuNarwoHEOjw= +k8s.io/api v0.22.4/go.mod h1:Rgs+9gIGYC5laXQSZZ9JqT5NevNgoGiOdVWi1BAB3qk= +k8s.io/apimachinery v0.17.0 h1:xRBnuie9rXcPxUkDizUsGvPf1cnlZCFu210op7J7LJo= +k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= +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.17.0 h1:8QOGvUGdqDMFrm9sD6IUFl256BcffynGoe80sxgTEDg= +k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= +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-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/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/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= -k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4= -k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= -k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b h1:wxEMGetGMur3J1xuGLQY7GEQYg9bZxKn3tKo5k/eYcs= -k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/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-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo= +k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +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/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= -sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= 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.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 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/_tools/libddwaf-updater/update.sh b/internal/appsec/_tools/libddwaf-updater/update.sh new file mode 100755 index 0000000000..ffeb0193b0 --- /dev/null +++ b/internal/appsec/_tools/libddwaf-updater/update.sh @@ -0,0 +1,84 @@ +#!/bin/sh + +# +# 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. +# + +# Update the libddwaf to the latest GitHub release version. +# Usage: ./update-libddwaf.sh +# + +set -ex + +bindings_dir=$(readlink -f "$(dirname $0)/../../waf") + +version="" +if [ $# -eq 1 ]; then + version=$1 +else + echo Looking up for the latest GitHub release + latest_release=$(curl -s https://api.github.com/repos/DataDog/libddwaf/releases/latest) + version=$(jq -r '.tag_name') << EOF +$latest_release +EOF +fi + +echo Updating to libddwaf v$version + +tmpdir=$(mktemp -d /tmp/libddwaf-XXXXXXXX) +echo Using $tmpdir + +run_binutils() { + docker run -it --rm -v $bindings_dir:$bindings_dir -v $tmpdir:$tmpdir -w $PWD ghcr.io/datadog/binutils-gdb:2.37 $@ +} + +run_strip() { + run_binutils $1-strip --strip-dwo --strip-unneeded --strip-debug $2 +} + +# +# darwin/amd64 +# + +echo Updating libddwaf for darwin/amd64 +curl -L https://github.com/DataDog/libddwaf/releases/download/$version/libddwaf-$version-darwin-x86_64.tar.gz | tar -xz -C$tmpdir +echo Copying the darwin/amd64 library +cp -v $tmpdir/libddwaf-$version-darwin-x86_64/lib/libddwaf.a $bindings_dir/lib/darwin-amd64 +run_strip x86_64-apple-darwin $bindings_dir/lib/darwin-amd64/libddwaf.a + +# +# linux/amd64 +# + +echo Updating libddwaf for linux/amd64 +# 1. Download the libddwaf build +curl -L https://github.com/DataDog/libddwaf/releases/download/$version/libddwaf-$version-linux-x86_64.tar.gz | tar -xz -C$tmpdir +# 2. Download the libc++ build +libcxx_dir=$tmpdir/libc++-x86_64-linux +mkdir $libcxx_dir +curl -L https://github.com/DataDog/libddwaf/releases/download/$version/libc++-static-x86_64-linux.tar.gz | tar -xz -C$libcxx_dir +# 3. Combine libddwaf.a + libc++.a + libc++abi.a + libunwind.a in a single +# object file by using ld -r +run_binutils x86_64-linux-gnu-ld \ + -r -o $bindings_dir/lib/linux-amd64/libddwaf.a \ + --require-defined=ddwaf_init \ + --require-defined=ddwaf_get_version \ + --require-defined=ddwaf_destroy \ + --require-defined=ddwaf_context_init \ + --require-defined=ddwaf_result_free \ + --require-defined=ddwaf_context_destroy \ + --require-defined=ddwaf_required_addresses \ + $tmpdir/libddwaf-$version-linux-x86_64/lib/libddwaf.a $libcxx_dir/libc++.a $libcxx_dir/libc++abi.a $libcxx_dir/libunwind.a +# 4. Strip +run_strip x86_64-linux-gnu $bindings_dir/lib/linux-amd64/libddwaf.a + +# +# ddwaf.h +# Note that we arbitrarily take it from the linux/amd64 archive as it does not +# depend on the target. +# +echo Updating ddwaf.h +cp -v $tmpdir/libddwaf-$version-linux-x86_64/include/ddwaf.h $bindings_dir/include diff --git a/internal/appsec/_tools/rules-updater/Dockerfile b/internal/appsec/_tools/rules-updater/Dockerfile new file mode 100644 index 0000000000..34209b0f29 --- /dev/null +++ b/internal/appsec/_tools/rules-updater/Dockerfile @@ -0,0 +1,16 @@ +FROM tdewolff/minify as minify +ARG version +ADD https://raw.githubusercontent.com/DataDog/appsec-event-rules/$version/build/recommended.json /home +RUN minify --type=json -o /home/out.json /home/recommended.json + +FROM golang as go-format +ARG version +ENV VERSION=$version +COPY escaper/ /home/ +COPY --from=minify /home/out.json /home/rules.json +WORKDIR /home/ +RUN go run escaper.go ${VERSION} > rule.go + +FROM scratch +ARG version=1.2.5 +COPY --from=go-format /home/rule.go rule.go diff --git a/internal/appsec/_tools/rules-updater/escaper/escaper.go b/internal/appsec/_tools/rules-updater/escaper/escaper.go new file mode 100644 index 0000000000..393cee9306 --- /dev/null +++ b/internal/appsec/_tools/rules-updater/escaper/escaper.go @@ -0,0 +1,45 @@ +// 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 2022 Datadog, Inc. + +package main + +import ( + _ "embed" + "fmt" + "os" + "strconv" + "text/template" +) + +//go:embed rules.json +var jsonStr string + +//go:embed template.txt +var ruleGoTemplate string + +func main() { + if len(os.Args) != 2 { + fmt.Fprintln(os.Stderr, "Usage: %s ", os.Args[0]) + os.Exit(1) + } + + tmpl, err := template.New("rule.go").Parse(ruleGoTemplate) + if err != nil { + fmt.Fprintln(os.Stderr, "Text template creation failed") + os.Exit(1) + } + err = tmpl.Execute(os.Stdout, struct { + Version string + Rules string + }{ + Version: os.Args[1], + Rules: strconv.Quote(jsonStr), + }) + + if err != nil { + fmt.Fprintln(os.Stderr, "Text template execution failed") + os.Exit(1) + } +} diff --git a/internal/appsec/_tools/rules-updater/escaper/go.mod b/internal/appsec/_tools/rules-updater/escaper/go.mod new file mode 100644 index 0000000000..8f5d7f136e --- /dev/null +++ b/internal/appsec/_tools/rules-updater/escaper/go.mod @@ -0,0 +1,3 @@ +module escaper + +go 1.16 diff --git a/internal/appsec/_tools/rules-updater/escaper/template.txt b/internal/appsec/_tools/rules-updater/escaper/template.txt new file mode 100644 index 0000000000..e68569355c --- /dev/null +++ b/internal/appsec/_tools/rules-updater/escaper/template.txt @@ -0,0 +1,13 @@ +// 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 + +// Static recommended AppSec rule {{.Version}} +// Source: https://github.com/DataDog/appsec-event-rules/blob/{{.Version}}/build/recommended.json +const staticRecommendedRule = {{.Rules}} diff --git a/internal/appsec/_tools/rules-updater/update.sh b/internal/appsec/_tools/rules-updater/update.sh new file mode 100755 index 0000000000..1b27921038 --- /dev/null +++ b/internal/appsec/_tools/rules-updater/update.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# 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 2022 Datadog, Inc. +# + +# Generates the rule.go file using the recommended rules for the specified tag version +# Usage: ./update.sh +# Example: ./update.sh 1.2.5 +# + +set -e + +[ $# -ne 1 ] && echo "Usage: $0 \"version\"" >&2 && exit 1 + +echo "================ Minifying ================" + +tmpDir=$(mktemp -d /tmp/rule-update-XXXXXXXXX) +scriptDir=$PWD/$(dirname $0) + +trap "rm -rf $tmpDir" EXIT + +DOCKER_BUILDKIT=1 docker build -o type=local,dest=$tmpDir --build-arg version=$1 --no-cache $scriptDir +echo "================ Done ================" +cp -v $tmpDir/rule.go ../../ +echo "Output written to ../../rule.go" 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 9d0aaccc50..a6f8bfee18 100644 --- a/internal/appsec/appsec.go +++ b/internal/appsec/appsec.go @@ -9,27 +9,12 @@ 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 - // 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 { @@ -40,7 +25,7 @@ func Enabled() bool { // 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) @@ -51,38 +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") - } - - 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) - } - } - - 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 @@ -115,160 +74,32 @@ func setActiveAppSec(a *appsec) { } type appsec struct { - client *client - eventChan chan securityEvent - wg sync.WaitGroup - cfg *Config + cfg *config unregisterWAF dyngo.UnregisterFunc + limiter *TokenTicker } -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.cfg.wafTimeout, a) + a.limiter = NewTokenTicker(int64(a.cfg.traceRateLimit), int64(a.cfg.traceRateLimit)) + a.limiter.Start() + unregisterWAF, err := registerWAF(a.cfg.rules, a.cfg.wafTimeout, a.limiter) 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 - } + a.limiter.Stop() } diff --git a/internal/appsec/appsec_disabled.go b/internal/appsec/appsec_disabled.go index b8763da06c..12039e0359 100644 --- a/internal/appsec/appsec_disabled.go +++ b/internal/appsec/appsec_disabled.go @@ -18,7 +18,7 @@ func Enabled() bool { // 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) @@ -33,3 +33,6 @@ func Start(*Config) { // Stop AppSec. func Stop() {} + +// Static rule stubs when disabled. +const staticRecommendedRule = "" diff --git a/internal/appsec/appsec_disabled_test.go b/internal/appsec/appsec_disabled_test.go index 7fe29ed674..95024b53f5 100644 --- a/internal/appsec/appsec_disabled_test.go +++ b/internal/appsec/appsec_disabled_test.go @@ -9,13 +9,33 @@ package appsec_test import ( + "os" "testing" + "github.com/stretchr/testify/assert" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" "github.com/stretchr/testify/require" ) func TestEnabled(t *testing.T) { + enabledStr := os.Getenv("DD_APPSEC_ENABLED") + if enabledStr != "" { + defer os.Setenv("DD_APPSEC_ENABLED", enabledStr) + } + // AppSec should be always disabled + require.False(t, appsec.Enabled()) + tracer.Start() + assert.False(t, appsec.Enabled()) + tracer.Stop() + assert.False(t, appsec.Enabled()) + os.Setenv("DD_APPSEC_ENABLED", "true") require.False(t, appsec.Enabled()) + tracer.Start() + assert.False(t, appsec.Enabled()) + tracer.Stop() + assert.False(t, appsec.Enabled()) + } diff --git a/internal/appsec/appsec_test.go b/internal/appsec/appsec_test.go index dd5905a30c..2ea8a887be 100644 --- a/internal/appsec/appsec_test.go +++ b/internal/appsec/appsec_test.go @@ -15,17 +15,20 @@ import ( "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/waf" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestEnabled(t *testing.T) { - enabled, _ := strconv.ParseBool(os.Getenv("DD_APPSEC_ENABLED")) + enabledConfig, _ := strconv.ParseBool(os.Getenv("DD_APPSEC_ENABLED")) + _, err := waf.Health() + canBeEnabled := enabledConfig && err == nil require.False(t, appsec.Enabled()) tracer.Start() - assert.Equal(t, enabled, appsec.Enabled()) + assert.Equal(t, canBeEnabled, 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 d73a52e7af..b4eaf02060 100644 --- a/internal/appsec/config.go +++ b/internal/appsec/config.go @@ -7,62 +7,130 @@ 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 + "unicode" + "unicode/utf8" - // 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 + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" +) - // 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 - } +const ( + enabledEnvVar = "DD_APPSEC_ENABLED" + rulesEnvVar = "DD_APPSEC_RULES" + wafTimeoutEnvVar = "DD_APPSEC_WAF_TIMEOUT" + traceRateLimitEnvVar = "DD_APPSEC_TRACE_RATE_LIMIT" +) - // 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 - } +const ( + defaultWAFTimeout = 4 * time.Millisecond + defaultTraceRate uint = 100 // up to 100 appsec traces/s ) +// 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 + // AppSec trace rate limit (traces per second). + traceRateLimit uint +} + // isEnabled returns true when appsec is enabled when the environment variable // DD_APPSEC_ENABLED is set to true. func isEnabled() (bool, error) { - enabledStr := os.Getenv("DD_APPSEC_ENABLED") + enabledStr := os.Getenv(enabledEnvVar) if enabledStr == "" { return false, nil } enabled, err := strconv.ParseBool(enabledStr) if err != nil { - return false, fmt.Errorf("could not parse DD_APPSEC_ENABLED value `%s` as a boolean value", enabledStr) + return false, fmt.Errorf("could not parse %s value `%s` as a boolean value", enabledEnvVar, enabledStr) } return enabled, nil } + +func newConfig() (*config, error) { + rules, err := readRulesConfig() + if err != nil { + return nil, err + } + return &config{ + rules: rules, + wafTimeout: readWAFTimeoutConfig(), + traceRateLimit: readRateLimitConfig(), + }, nil +} + +func readWAFTimeoutConfig() (timeout time.Duration) { + timeout = defaultWAFTimeout + value := os.Getenv(wafTimeoutEnvVar) + if value == "" { + return + } + + // Check if the value ends with a letter, which means the user has + // specified their own time duration unit(s) such as 1s200ms. + // Otherwise, default to microseconds. + if lastRune, _ := utf8.DecodeLastRuneInString(value); !unicode.IsLetter(lastRune) { + value += "us" // Add the default microsecond time-duration suffix + } + + parsed, err := time.ParseDuration(value) + if err != nil { + logEnvVarParsingError(wafTimeoutEnvVar, value, err, timeout) + return + } + if parsed <= 0 { + logUnexpectedEnvVarValue(wafTimeoutEnvVar, parsed, "expecting a strictly positive duration", timeout) + return + } + return parsed +} + +func readRateLimitConfig() (rate uint) { + rate = defaultTraceRate + value := os.Getenv(traceRateLimitEnvVar) + if value == "" { + return rate + } + parsed, err := strconv.ParseUint(value, 10, 0) + if err != nil { + logEnvVarParsingError(traceRateLimitEnvVar, value, err, rate) + return + } + if rate == 0 { + logUnexpectedEnvVarValue(traceRateLimitEnvVar, parsed, "expecting a value strictly greater than 0", rate) + return + } + return uint(parsed) +} + +func readRulesConfig() (rules []byte, err error) { + rules = []byte(staticRecommendedRule) + filepath := os.Getenv(rulesEnvVar) + if filepath == "" { + log.Info("appsec: starting with the default recommended security rules") + return rules, nil + } + buf, 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 + } + log.Info("appsec: starting with the security rules from file %s", filepath) + return buf, nil +} + +func logEnvVarParsingError(name, value string, err error, defaultValue interface{}) { + log.Error("appsec: could not parse the env var %s=%s as a duration: %v. Using default value %v.", name, value, err, defaultValue) +} + +func logUnexpectedEnvVarValue(name string, value interface{}, reason string, defaultValue interface{}) { + log.Error("appsec: unexpected configuration value of %s=%v: %s. Using default value %v.", name, value, reason, defaultValue) +} diff --git a/internal/appsec/config_test.go b/internal/appsec/config_test.go new file mode 100644 index 0000000000..6e64bac370 --- /dev/null +++ b/internal/appsec/config_test.go @@ -0,0 +1,235 @@ +// 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 ( + "io/ioutil" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestConfig(t *testing.T) { + expectedDefaultConfig := &config{ + rules: []byte(staticRecommendedRule), + wafTimeout: defaultWAFTimeout, + traceRateLimit: defaultTraceRate, + } + + t.Run("default", func(t *testing.T) { + restoreEnv := cleanEnv() + defer restoreEnv() + cfg, err := newConfig() + require.NoError(t, err) + require.Equal(t, expectedDefaultConfig, cfg) + }) + + t.Run("waf-timeout", func(t *testing.T) { + t.Run("parsable", func(t *testing.T) { + restoreEnv := cleanEnv() + defer restoreEnv() + require.NoError(t, os.Setenv(wafTimeoutEnvVar, "5s")) + cfg, err := newConfig() + require.NoError(t, err) + require.Equal( + t, + &config{ + rules: []byte(staticRecommendedRule), + wafTimeout: 5 * time.Second, + traceRateLimit: defaultTraceRate, + }, + cfg, + ) + }) + + t.Run("parsable-default-microsecond", func(t *testing.T) { + restoreEnv := cleanEnv() + defer restoreEnv() + require.NoError(t, os.Setenv(wafTimeoutEnvVar, "1")) + cfg, err := newConfig() + require.NoError(t, err) + require.Equal( + t, + &config{ + rules: []byte(staticRecommendedRule), + wafTimeout: 1 * time.Microsecond, + traceRateLimit: defaultTraceRate, + }, + cfg, + ) + }) + + t.Run("not-parsable", func(t *testing.T) { + restoreEnv := cleanEnv() + defer restoreEnv() + require.NoError(t, os.Setenv(wafTimeoutEnvVar, "not a duration string")) + cfg, err := newConfig() + require.NoError(t, err) + require.Equal(t, expectedDefaultConfig, cfg) + }) + + t.Run("negative", func(t *testing.T) { + restoreEnv := cleanEnv() + defer restoreEnv() + require.NoError(t, os.Setenv(wafTimeoutEnvVar, "-1s")) + cfg, err := newConfig() + require.NoError(t, err) + require.Equal(t, expectedDefaultConfig, cfg) + }) + + t.Run("zero", func(t *testing.T) { + restoreEnv := cleanEnv() + defer restoreEnv() + require.NoError(t, os.Setenv(wafTimeoutEnvVar, "0")) + cfg, err := newConfig() + require.NoError(t, err) + require.Equal(t, expectedDefaultConfig, cfg) + }) + + t.Run("empty-string", func(t *testing.T) { + restoreEnv := cleanEnv() + defer restoreEnv() + require.NoError(t, os.Setenv(wafTimeoutEnvVar, "")) + cfg, err := newConfig() + require.NoError(t, err) + require.Equal(t, expectedDefaultConfig, cfg) + }) + }) + + t.Run("rules", func(t *testing.T) { + t.Run("empty-string", func(t *testing.T) { + restoreEnv := cleanEnv() + defer restoreEnv() + os.Setenv(rulesEnvVar, "") + cfg, err := newConfig() + require.NoError(t, err) + require.Equal(t, expectedDefaultConfig, cfg) + }) + + t.Run("file-not-found", func(t *testing.T) { + restoreEnv := cleanEnv() + defer restoreEnv() + os.Setenv(rulesEnvVar, "i do not exist") + cfg, err := newConfig() + require.Error(t, err) + require.Nil(t, cfg) + }) + + t.Run("local-file", func(t *testing.T) { + restoreEnv := cleanEnv() + defer restoreEnv() + file, err := ioutil.TempFile("", "example-*") + require.NoError(t, err) + defer func() { + file.Close() + os.Remove(file.Name()) + }() + expectedRules := `custom rule file content` + _, err = file.WriteString(expectedRules) + require.NoError(t, err) + os.Setenv(rulesEnvVar, file.Name()) + cfg, err := newConfig() + require.NoError(t, err) + require.Equal(t, &config{ + rules: []byte(expectedRules), + wafTimeout: defaultWAFTimeout, + traceRateLimit: defaultTraceRate, + }, cfg) + }) + }) + + t.Run("trace-rate-limit", func(t *testing.T) { + t.Run("parsable", func(t *testing.T) { + restoreEnv := cleanEnv() + defer restoreEnv() + require.NoError(t, os.Setenv(traceRateLimitEnvVar, "1234567890")) + cfg, err := newConfig() + require.NoError(t, err) + require.Equal( + t, + &config{ + rules: []byte(staticRecommendedRule), + wafTimeout: defaultWAFTimeout, + traceRateLimit: 1234567890, + }, + cfg, + ) + }) + + t.Run("not-parsable", func(t *testing.T) { + restoreEnv := cleanEnv() + defer restoreEnv() + require.NoError(t, os.Setenv(wafTimeoutEnvVar, "not a uint")) + cfg, err := newConfig() + require.NoError(t, err) + require.Equal(t, expectedDefaultConfig, cfg) + }) + + t.Run("negative", func(t *testing.T) { + restoreEnv := cleanEnv() + defer restoreEnv() + require.NoError(t, os.Setenv(wafTimeoutEnvVar, "-1")) + cfg, err := newConfig() + require.NoError(t, err) + require.Equal(t, expectedDefaultConfig, cfg) + }) + + t.Run("zero", func(t *testing.T) { + restoreEnv := cleanEnv() + defer restoreEnv() + require.NoError(t, os.Setenv(wafTimeoutEnvVar, "0")) + cfg, err := newConfig() + require.NoError(t, err) + require.Equal(t, expectedDefaultConfig, cfg) + }) + + t.Run("empty-string", func(t *testing.T) { + restoreEnv := cleanEnv() + defer restoreEnv() + require.NoError(t, os.Setenv(wafTimeoutEnvVar, "")) + cfg, err := newConfig() + require.NoError(t, err) + require.Equal(t, expectedDefaultConfig, cfg) + }) + }) +} + +func cleanEnv() func() { + wafTimeout := os.Getenv(wafTimeoutEnvVar) + if err := os.Unsetenv(wafTimeoutEnvVar); err != nil { + panic(err) + } + rules := os.Getenv(rulesEnvVar) + if err := os.Unsetenv(rulesEnvVar); err != nil { + panic(err) + } + traceRateLimit := os.Getenv(traceRateLimitEnvVar) + if err := os.Unsetenv(traceRateLimitEnvVar); err != nil { + panic(err) + } + return func() { + restoreEnv(wafTimeoutEnvVar, wafTimeout) + restoreEnv(rulesEnvVar, rules) + restoreEnv(traceRateLimitEnvVar, traceRateLimit) + } +} + +func restoreEnv(key, value string) { + if value != "" { + if err := os.Setenv(key, value); err != nil { + panic(err) + } + } else { + if err := os.Unsetenv(key); err != nil { + panic(err) + } + } +} diff --git a/internal/appsec/dyngo/instrumentation/grpcsec/grpc.go b/internal/appsec/dyngo/instrumentation/grpcsec/grpc.go new file mode 100644 index 0000000000..304ffb46b5 --- /dev/null +++ b/internal/appsec/dyngo/instrumentation/grpcsec/grpc.go @@ -0,0 +1,181 @@ +// 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. + HandlerOperationArgs struct { + // Message received by the gRPC handler. + // Corresponds to the address `grpc.server.request.metadata`. + Metadata map[string][]string + } + // 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(events []json.RawMessage) { + op.mu.Lock() + defer op.mu.Unlock() + op.events = append(op.events, events...) +} + +// 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..bab9867b79 --- /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{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..b23436b146 --- /dev/null +++ b/internal/appsec/dyngo/instrumentation/grpcsec/tags.go @@ -0,0 +1,108 @@ +// 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" + "gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames" +) + +// 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 + // + // This is a workaround to tell the tracer that the trace was kept by AppSec. + // Passing any other value than `appsec.SamplerAppSec` has no effect. + // Customers should use `span.SetTag(ext.ManualKeep, true)` pattern + // to keep the trace, manually. + span.SetTag(ext.ManualKeep, samplernames.AppSec) + 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..5e0e21db69 --- /dev/null +++ b/internal/appsec/dyngo/instrumentation/grpcsec/tags_test.go @@ -0,0 +1,190 @@ +// 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" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames" +) + +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{}) + } + if key == ext.ManualKeep { + if value == samplernames.AppSec { + m.tags[ext.ManualKeep] = true + } + } else { + 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/httpsec/http.go b/internal/appsec/dyngo/instrumentation/httpsec/http.go index 5e1cfdfb72..b7b647bbf1 100644 --- a/internal/appsec/dyngo/instrumentation/httpsec/http.go +++ b/internal/appsec/dyngo/instrumentation/httpsec/http.go @@ -3,67 +3,93 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2016 Datadog, Inc. -// Package httpsec 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 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 ( + "context" + "encoding/json" + "net" "net/http" - "net/url" "reflect" "strings" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" ) // Abstract HTTP handler operation definition. 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 + // PathParams corresponds to the address `server.request.path_params` + PathParams 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 } + + // SDKBodyOperationArgs is the SDK body operation arguments. + SDKBodyOperationArgs struct { + // Body corresponds to the address `server.request.body`. + Body interface{} + } + + // SDKBodyOperationRes is the SDK body operation results. + SDKBodyOperationRes struct{} ) +// MonitorParsedBody starts and finishes the SDK body operation. +// This function should not be called when AppSec is disabled in order to +// get preciser error logs. +func MonitorParsedBody(ctx context.Context, body interface{}) { + if parent := fromContext(ctx); parent != nil { + op := StartSDKBodyOperation(parent, SDKBodyOperationArgs{Body: body}) + op.Finish() + } else { + log.Error("appsec: parsed http body monitoring ignored: could not find the http handler instrumentation metadata in the request context: the request handler is not being monitored by a middleware function or the provided context is not the expected request context") + } +} + // 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 { - // TODO(Julio-Guerra): move these to service entry tags - span.SetTag("_dd.appsec.enabled", 1) - span.SetTag("_dd.runtime_family", "go") - +func WrapHandler(handler http.Handler, span ddtrace.Span, pathParams map[string]string) http.Handler { + SetAppSecTags(span) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - op := StartOperation( - makeHandlerOperationArgs(r, span), - nil, - ) + args := MakeHandlerOperationArgs(r, pathParams) + ctx, op := StartOperation(r.Context(), args) + r = r.WithContext(ctx) defer func() { var status int 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) }) @@ -72,45 +98,27 @@ func WrapHandler(handler http.Handler, span ddtrace.Span) http.Handler { // 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, span ddtrace.Span) HandlerOperationArgs { - return makeHandlerOperationArgs(r, span) -} - -// makeHandlerOperationArgs implements MakeHandlerOperationArgs regardless of appsec being disabled. -func makeHandlerOperationArgs(r *http.Request, span ddtrace.Span) HandlerOperationArgs { +func MakeHandlerOperationArgs(r *http.Request, pathParams map[string]string) 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 } - 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) - } - } headers["host"] = []string{r.Host} return 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-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(), + Query: r.URL.Query(), + PathParams: pathParams, } } @@ -118,55 +126,125 @@ func makeHandlerOperationArgs(r *http.Request, span ddtrace.Span) HandlerOperati // Operation type representing an HTTP operation. It must be created with // StartOperation() and finished with its Finish(). -type Operation struct { - *dyngo.OperationImpl -} +type ( + Operation struct { + dyngo.Operation + events json.RawMessage + } + + // SDKBodyOperation type representing an SDK body. It must be created with + // StartSDKBodyOperation() and finished with its Finish() method. + SDKBodyOperation struct { + dyngo.Operation + } + + contextKey struct{} +) // 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)} +// context and arguments and emits a start event up in the operation stack. +// The operation is linked to the global root operation since an HTTP operation +// is always expected to be first in the operation stack. +func StartOperation(ctx context.Context, args HandlerOperationArgs) (context.Context, *Operation) { + op := &Operation{Operation: dyngo.NewOperation(nil)} + newCtx := context.WithValue(ctx, contextKey{}, op) + dyngo.StartOperation(op, args) + return newCtx, op +} + +func fromContext(ctx context.Context) *Operation { + // Avoid a runtime panic in case of type-assertion error by collecting the 2 return values + op, _ := ctx.Value(contextKey{}).(*Operation) + return op } -// Finish the HTTP handler operation, along with the given results, and emits a +// 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 +} + +// StartSDKBodyOperation starts the SDKBody operation and emits a start event +func StartSDKBodyOperation(parent *Operation, args SDKBodyOperationArgs) *SDKBodyOperation { + op := &SDKBodyOperation{Operation: dyngo.NewOperation(parent)} + dyngo.StartOperation(op, args) + return op +} + +// Finish finishes the SDKBody operation and emits a finish event +func (op *SDKBodyOperation) Finish() { + dyngo.FinishOperation(op, SDKBodyOperationRes{}) +} + +// 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) + // OnSDKBodyOperationStart function type, called when an SDK body + // operation starts. + OnSDKBodyOperationStart func(*SDKBodyOperation, SDKBodyOperationArgs) + // OnSDKBodyOperationFinish function type, called when an SDK body + // operation finishes. + OnSDKBodyOperationFinish func(*SDKBodyOperation, SDKBodyOperationRes) ) var ( handlerOperationArgsType = reflect.TypeOf((*HandlerOperationArgs)(nil)).Elem() handlerOperationResType = reflect.TypeOf((*HandlerOperationRes)(nil)).Elem() + sdkBodyOperationArgsType = reflect.TypeOf((*SDKBodyOperationArgs)(nil)).Elem() + sdkBodyOperationResType = reflect.TypeOf((*SDKBodyOperationRes)(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(). +// Call calls 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 // 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(). +// Call calls 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)) +} + +// ListenedType returns the type a OnSDKBodyOperationStart event listener +// listens to, which is the SDKBodyOperationStartArgs type. +func (OnSDKBodyOperationStart) ListenedType() reflect.Type { return sdkBodyOperationArgsType } + +// Call calls the underlying event listener function by performing the +// type-assertion on v whose type is the one returned by ListenedType(). +func (f OnSDKBodyOperationStart) Call(op dyngo.Operation, v interface{}) { + f(op.(*SDKBodyOperation), v.(SDKBodyOperationArgs)) +} + +// ListenedType returns the type a OnSDKBodyOperationFinish event listener +// listens to, which is the SDKBodyOperationRes type. +func (OnSDKBodyOperationFinish) ListenedType() reflect.Type { return sdkBodyOperationResType } + +// Call calls the underlying event listener function by performing the +// type-assertion on v whose type is the one returned by ListenedType(). +func (f OnSDKBodyOperationFinish) Call(op dyngo.Operation, v interface{}) { + f(op.(*SDKBodyOperation), v.(SDKBodyOperationRes)) } diff --git a/internal/appsec/dyngo/instrumentation/httpsec/tags.go b/internal/appsec/dyngo/instrumentation/httpsec/tags.go new file mode 100644 index 0000000000..3261db644c --- /dev/null +++ b/internal/appsec/dyngo/instrumentation/httpsec/tags.go @@ -0,0 +1,109 @@ +// 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" + "gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames" +) + +// 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 + // + // This is a workaround to tell the tracer that the trace was kept by AppSec. + // Passing any other value than `appsec.SamplerAppSec` has no effect. + // Customers should use `span.SetTag(ext.ManualKeep, true)` pattern + // to keep the trace, manually. + span.SetTag(ext.ManualKeep, samplernames.AppSec) + 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 3b9bca53be..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/httpsec" -) - -// 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 httpsec.HandlerOperationArgs, res httpsec.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/limiter.go b/internal/appsec/limiter.go new file mode 100644 index 0000000000..c022ecfc63 --- /dev/null +++ b/internal/appsec/limiter.go @@ -0,0 +1,143 @@ +// 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 2022 Datadog, Inc. + +//go:build appsec +// +build appsec + +package appsec + +import ( + "sync/atomic" + "time" +) + +// Limiter is used to abstract the rate limiter implementation to only expose the needed function for rate limiting. +// This is for example useful for testing, allowing us to use a modified rate limiter tuned for testing through the same +// interface. +type Limiter interface { + Allow() bool +} + +// TokenTicker is a thread-safe and lock-free rate limiter based on a token bucket. +// The idea is to have a goroutine that will update the bucket with fresh tokens at regular intervals using a time.Ticker. +// The advantage of using a goroutine here is that the implementation becomes easily thread-safe using a few +// atomic operations with little overhead overall. TokenTicker.Start() *should* be called before the first call to +// TokenTicker.Allow() and TokenTicker.Stop() *must* be called once done using. Note that calling TokenTicker.Allow() +// before TokenTicker.Start() is valid, but it means the bucket won't be refilling until the call to TokenTicker.Start() is made +type TokenTicker struct { + tokens int64 + maxTokens int64 + ticker *time.Ticker + stopChan chan struct{} +} + +// NewTokenTicker is a utility function that allocates a token ticker, initializes necessary fields and returns it +func NewTokenTicker(tokens, maxTokens int64) *TokenTicker { + return &TokenTicker{ + tokens: tokens, + maxTokens: maxTokens, + } +} + +// updateBucket performs a select loop to update the token amount in the bucket. +// Used in a goroutine by the rate limiter. +func (t *TokenTicker) updateBucket(ticksChan <-chan time.Time, startTime time.Time, syncChan chan struct{}) { + nsPerToken := time.Second.Nanoseconds() / t.maxTokens + elapsedNs := int64(0) + prevStamp := startTime + + for { + select { + case <-t.stopChan: + if syncChan != nil { + close(syncChan) + } + return + case stamp := <-ticksChan: + // Compute the time in nanoseconds that passed between the previous timestamp and this one + // This will be used to know how many tokens can be added into the bucket depending on the limiter rate + elapsedNs += stamp.Sub(prevStamp).Nanoseconds() + if elapsedNs > t.maxTokens*nsPerToken { + elapsedNs = t.maxTokens * nsPerToken + } + prevStamp = stamp + // Update the number of tokens in the bucket if enough nanoseconds have passed + if elapsedNs >= nsPerToken { + // Atomic spin lock to make sure we don't race for `t.tokens` + for { + tokens := atomic.LoadInt64(&t.tokens) + if tokens == t.maxTokens { + break // Bucket is already full, nothing to do + } + inc := elapsedNs / nsPerToken + // Make sure not to add more tokens than we are allowed to into the bucket + if tokens+inc > t.maxTokens { + inc -= (tokens + inc) % t.maxTokens + } + if atomic.CompareAndSwapInt64(&t.tokens, tokens, tokens+inc) { + // Keep track of remaining elapsed ns that were not taken into account for this computation, + // so that increment computation remains precise over time + elapsedNs = elapsedNs % nsPerToken + break + } + } + } + // Sync channel used to signify that the goroutine is done updating the bucket. Used for tests to guarantee + // that the goroutine ticked at least once. + if syncChan != nil { + syncChan <- struct{}{} + } + } + } +} + +// Start starts the ticker and launches the goroutine responsible for updating the token bucket. +// The ticker is set to tick at a fixed rate of 500us. +func (t *TokenTicker) Start() { + timeNow := time.Now() + t.ticker = time.NewTicker(500 * time.Microsecond) + t.start(t.ticker.C, timeNow, false) +} + +// start is used for internal testing. Controlling the ticker means being able to test per-tick +// rather than per-duration, which is more reliable if the app is under a lot of stress. +// sync is used to decide whether the limiter should create a channel for synchronization with the testing app after a +// bucket update. The limiter is in charge of closing the channel in this case. +func (t *TokenTicker) start(ticksChan <-chan time.Time, startTime time.Time, sync bool) <-chan struct{} { + t.stopChan = make(chan struct{}) + var syncChan chan struct{} + + if sync { + syncChan = make(chan struct{}) + } + go t.updateBucket(ticksChan, startTime, syncChan) + return syncChan +} + +// Stop shuts down the rate limiter, taking care stopping the ticker and closing all channels +func (t *TokenTicker) Stop() { + // Stop the ticker only if it has been instantiated (not the case when testing by calling start() directly) + if t.ticker != nil { + t.ticker.Stop() + } + // Close the stop channel only if it has been created. This covers the case where Stop() is called without any prior + // call to Start() + if t.stopChan != nil { + close(t.stopChan) + } +} + +// Allow checks and returns whether a token can be retrieved from the bucket and consumed. +// Thread-safe. +func (t *TokenTicker) Allow() bool { + for { + tokens := atomic.LoadInt64(&t.tokens) + if tokens == 0 { + return false + } else if atomic.CompareAndSwapInt64(&t.tokens, tokens, tokens-1) { + return true + } + } +} diff --git a/internal/appsec/limiter_test.go b/internal/appsec/limiter_test.go new file mode 100644 index 0000000000..bd1b439398 --- /dev/null +++ b/internal/appsec/limiter_test.go @@ -0,0 +1,307 @@ +// 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 2022 Datadog, Inc. + +//go:build appsec +// +build appsec + +package appsec + +import ( + "fmt" + "math" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestLimiterUnit(t *testing.T) { + startTime := time.Now() + + t.Run("no-ticks-1", func(t *testing.T) { + l := NewTestTicker(1, 100) + l.start(startTime) + defer l.stop() + // No ticks between the requests + require.True(t, l.Allow(), "First call to limiter.Allow() should return True") + require.False(t, l.Allow(), "Second call to limiter.Allow() should return False") + }) + + t.Run("no-ticks-2", func(t *testing.T) { + l := NewTestTicker(100, 100) + l.start(startTime) + defer l.stop() + // No ticks between the requests + for i := 0; i < 100; i++ { + require.True(t, l.Allow()) + } + require.False(t, l.Allow()) + }) + + t.Run("10ms-ticks", func(t *testing.T) { + l := NewTestTicker(1, 100) + l.start(startTime) + defer l.stop() + require.True(t, l.Allow(), "First call to limiter.Allow() should return True") + require.False(t, l.Allow(), "Second call to limiter.Allow() should return false") + l.tick(startTime.Add(10 * time.Millisecond)) + require.True(t, l.Allow(), "Third call to limiter.Allow() after 10ms should return True") + }) + + t.Run("9ms-ticks", func(t *testing.T) { + l := NewTestTicker(1, 100) + l.start(startTime) + defer l.stop() + require.True(t, l.Allow(), "First call to limiter.Allow() should return True") + l.tick(startTime.Add(9 * time.Millisecond)) + require.False(t, l.Allow(), "Second call to limiter.Allow() after 9ms should return False") + l.tick(startTime.Add(10 * time.Millisecond)) + require.True(t, l.Allow(), "Third call to limiter.Allow() after 10ms should return True") + }) + + t.Run("1s-rate", func(t *testing.T) { + l := NewTestTicker(1, 1) + l.start(startTime) + defer l.stop() + require.True(t, l.Allow(), "First call to limiter.Allow() should return True with 1s per token") + l.tick(startTime.Add(500 * time.Millisecond)) + require.False(t, l.Allow(), "Second call to limiter.Allow() should return False with 1s per Token") + l.tick(startTime.Add(1000 * time.Millisecond)) + require.True(t, l.Allow(), "Third call to limiter.Allow() should return True with 1s per Token") + }) + + t.Run("100-requests-burst", func(t *testing.T) { + l := NewTestTicker(100, 100) + l.start(startTime) + defer l.stop() + for i := 0; i < 100; i++ { + require.Truef(t, l.Allow(), + "Burst call %d to limiter.Allow() should return True with 100 initial tokens", i) + startTime = startTime.Add(50 * time.Microsecond) + l.tick(startTime) + } + }) + + t.Run("101-requests-burst", func(t *testing.T) { + l := NewTestTicker(100, 100) + l.start(startTime) + defer l.stop() + for i := 0; i < 100; i++ { + require.Truef(t, l.Allow(), + "Burst call %d to limiter.Allow() should return True with 100 initial tokens", i) + startTime = startTime.Add(50 * time.Microsecond) + l.tick(startTime) + } + require.False(t, l.Allow(), + "Burst call 101 to limiter.Allow() should return False with 100 initial tokens") + }) + + t.Run("bucket-refill-short", func(t *testing.T) { + l := NewTestTicker(100, 100) + l.start(startTime) + defer l.stop() + + for i := 0; i < 1000; i++ { + startTime = startTime.Add(time.Millisecond) + l.tick(startTime) + require.Equalf(t, int64(100), atomic.LoadInt64(&l.t.tokens), "Bucket should have exactly 100 tokens") + } + }) + + t.Run("bucket-refill-long", func(t *testing.T) { + l := NewTestTicker(100, 100) + l.start(startTime) + defer l.stop() + + for i := 0; i < 1000; i++ { + startTime = startTime.Add(3 * time.Second) + l.tick(startTime) + } + require.Equalf(t, int64(100), atomic.LoadInt64(&l.t.tokens), "Bucket should have exactly 100 tokens") + }) + + t.Run("allow-after-stop", func(t *testing.T) { + l := NewTestTicker(3, 3) + l.start(startTime) + require.True(t, l.Allow()) + l.stop() + // The limiter keeps allowing until there's no more tokens + require.True(t, l.Allow()) + require.True(t, l.Allow()) + require.False(t, l.Allow()) + }) + + t.Run("allow-before-start", func(t *testing.T) { + l := NewTestTicker(2, 100) + // The limiter keeps allowing until there's no more tokens + require.True(t, l.Allow()) + require.True(t, l.Allow()) + require.False(t, l.Allow()) + l.start(startTime) + // The limiter has used all its tokens and the bucket is not getting refilled yet + require.False(t, l.Allow()) + l.tick(startTime.Add(10 * time.Millisecond)) + // The limiter has started refilling its tokens + require.True(t, l.Allow()) + l.stop() + }) +} + +func TestLimiter(t *testing.T) { + t.Run("concurrency", func(t *testing.T) { + // Tests the limiter's ability to sample the traces when subjected to a continuous flow of requests + // Each goroutine will continuously call the rate limiter for 1 second + for nbUsers := 1; nbUsers <= 10; nbUsers *= 10 { + t.Run(fmt.Sprintf("continuous-requests-%d-users", nbUsers), func(t *testing.T) { + var startBarrier, stopBarrier sync.WaitGroup + // Create a start barrier to synchronize every goroutine's launch and + // increase the chances of parallel accesses + startBarrier.Add(1) + // Create a stopBarrier to signal when all user goroutines are done. + stopBarrier.Add(nbUsers) + skipped := int64(0) + kept := int64(0) + l := NewTokenTicker(0, 100) + + for n := 0; n < nbUsers; n++ { + go func(l Limiter, kept *int64, skipped *int64) { + startBarrier.Wait() // Sync the starts of the goroutines + defer stopBarrier.Done() // Signal we are done when returning + + for tStart := time.Now(); time.Since(tStart) < 1*time.Second; { + if !l.Allow() { + atomic.AddInt64(skipped, 1) + } else { + atomic.AddInt64(kept, 1) + } + } + }(l, &kept, &skipped) + } + + l.Start() + defer l.Stop() + start := time.Now() + startBarrier.Done() // Unblock the user goroutines + stopBarrier.Wait() // Wait for the user goroutines to be done + duration := time.Since(start).Seconds() + maxExpectedKept := int64(math.Ceil(duration) * 100) + + require.LessOrEqualf(t, kept, maxExpectedKept, + "Expected at most %d kept tokens for a %fs duration", maxExpectedKept, duration) + }) + } + + burstFreq := 1000 * time.Millisecond + burstSize := 101 + startTime := time.Now() + // Simulate sporadic bursts during up to 1 minute + for burstAmount := 1; burstAmount <= 10; burstAmount++ { + t.Run(fmt.Sprintf("requests-bursts-%d-iterations", burstAmount), func(t *testing.T) { + skipped := 0 + kept := 0 + l := NewTestTicker(100, 100) + l.start(startTime) + defer l.stop() + + for c := 0; c < burstAmount; c++ { + for i := 0; i < burstSize; i++ { + if !l.Allow() { + skipped++ + } else { + kept++ + } + } + // Schedule next burst 1sec later + startTime = startTime.Add(burstFreq) + l.tick(startTime) + } + + expectedSkipped := (burstSize - 100) * burstAmount + expectedKept := 100 * burstAmount + if burstSize < 100 { + expectedSkipped = 0 + expectedKept = burstSize * burstAmount + } + require.Equalf(t, kept, expectedKept, "Expected %d burst requests to be kept", expectedKept) + require.Equalf(t, expectedSkipped, skipped, "Expected %d burst requests to be skipped", expectedSkipped) + }) + } + }) +} + +func BenchmarkLimiter(b *testing.B) { + for nbUsers := 1; nbUsers <= 1000; nbUsers *= 10 { + b.Run(fmt.Sprintf("%d-users", nbUsers), func(b *testing.B) { + var skipped int64 + var kept int64 + limiter := NewTokenTicker(0, 100) + limiter.Start() + defer limiter.Stop() + b.ResetTimer() + + for n := 0; n < b.N; n++ { + var startBarrier, stopBarrier sync.WaitGroup + // Create a start barrier to synchronize every goroutine's launch and + // increase the chances of parallel accesses + startBarrier.Add(1) + // Create a stopBarrier to signal when all user goroutines are done. + stopBarrier.Add(nbUsers) + + for n := 0; n < nbUsers; n++ { + go func(l Limiter, kept *int64, skipped *int64) { + startBarrier.Wait() // Sync the starts of the goroutines + defer stopBarrier.Done() // Signal we are done when returning + + for i := 0; i < 100; i++ { + if !l.Allow() { + atomic.AddInt64(skipped, 1) + } else { + atomic.AddInt64(kept, 1) + } + } + }(limiter, &kept, &skipped) + } + startBarrier.Done() // Unblock the user goroutines + stopBarrier.Wait() // Wait for the user goroutines to be done + } + }) + } +} + +// TestTicker is a utility struct used to send hand-crafted ticks to the rate limiter for controlled testing +// It also makes sure to give time to the bucket update goroutine by using the optional sync channel +type TestTicker struct { + C chan time.Time + syncChan <-chan struct{} + t *TokenTicker +} + +func NewTestTicker(tokens, maxTokens int64) *TestTicker { + return &TestTicker{ + C: make(chan time.Time), + t: NewTokenTicker(tokens, maxTokens), + } +} + +func (t *TestTicker) start(timeStamp time.Time) { + t.syncChan = t.t.start(t.C, timeStamp, true) +} + +func (t *TestTicker) stop() { + t.t.Stop() + close(t.C) + // syncChan is closed by the token ticker when sure that nothing else will be sent on it +} + +func (t *TestTicker) tick(timeStamp time.Time) { + t.C <- timeStamp + <-t.syncChan +} + +func (t *TestTicker) Allow() bool { + return t.t.Allow() +} diff --git a/internal/appsec/rule.go b/internal/appsec/rule.go index 5345071e43..ebc4703e25 100644 --- a/internal/appsec/rule.go +++ b/internal/appsec/rule.go @@ -8,6 +8,6 @@ package appsec -// Static recommended AppSec rule v1.2.3 -// Source: https://github.com/DataDog/appsec-event-rules/blob/1.2.3/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\"},{\"address\":\"grpc.server.request.message\"}],\"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\",\".git\",\".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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"]},{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"x_filename\"]},{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"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\"]},{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"x_filename\"]},{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"x.filename\"]},{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"]},{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"referer\"]},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"},{\"address\":\"grpc.server.request.message\"}]},\"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\"]},{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"referer\"]},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"},{\"address\":\"grpc.server.request.message\"}],\"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\"]},{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"referer\"]},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"},{\"address\":\"grpc.server.request.message\"}],\"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\"]},{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"referer\"]},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"},{\"address\":\"grpc.server.request.message\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\":\"sql_injection\",\"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\"},{\"address\":\"grpc.server.request.message\"}]},\"operator\":\"is_sqli\"}],\"transformers\":[\"removeNulls\"]},{\"id\":\"crs-942-140\",\"name\":\"SQL Injection Attack: Common DB Names Detected\",\"tags\":{\"type\":\"sql_injection\",\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\":\"sql_injection\",\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\":\"sql_injection\",\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\":\"sql_injection\",\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\":\"sql_injection\",\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\":\"sql_injection\",\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\":\"sql_injection\",\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\":\"sql_injection\",\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\":\"nosql_injection\",\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\":\"sql_injection\",\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\":\"sql_injection\",\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\":\"nfd-000-001\",\"name\":\"Detect common directory discovery scans\",\"tags\":{\"type\":\"security_scanner\"},\"conditions\":[{\"operator\":\"match_regex\",\"parameters\":{\"inputs\":[{\"address\":\"server.response.status\"}],\"regex\":\"^404$\",\"options\":{\"case_sensitive\":true}}},{\"operator\":\"phrase_match\",\"parameters\":{\"inputs\":[{\"address\":\"server.request.uri.raw\"}],\"list\":[\"/wordpress/\",\"/etc/\",\"/login.php\",\"/install.php\",\"/administrator\",\"/admin.php\",\"/wp-config\",\"/phpmyadmin\",\"/fckeditor\",\"/mysql\",\"/manager/html\",\".htaccess\",\"/config.php\",\"/configuration\",\"/cgi-bin/php\",\"/search.php\",\"/tinymce\",\"/tiny_mce\",\"/settings.php\",\"../../..\",\"/install/\",\"/download.php\",\"/webdav\",\"/forum.php\",\"/user.php\",\"/style.php\",\"/jmx-console\",\"/modules.php\",\"/include.php\",\"/default.asp\",\"/help.php\",\"/database.yml\",\"/database.yml.pgsql\",\"/database.yml.sqlite3\",\"/database.yml.sqlite\",\"/database.yml.mysql\",\".%2e/\",\"/view.php\",\"/header.php\",\"/search.asp\",\"%5c%5c\",\"/server/php/\",\"/invoker/jmxinvokerservlet\",\"/phpmyadmin/index.php\",\"/data/admin/allowurl.txt\",\"/verify.php\",\"/misc/ajax.js\",\"/.idea\",\"/module.php\",\"/backup.rar\",\"/backup.tar\",\"/backup.zip\",\"/backup.7z\",\"/backup.gz\",\"/backup.tgz\",\"/backup.tar.gz\",\"waitfor%20delay\",\"/calendar.php\",\"/news.php\",\"/dompdf.php\",\"))))))))))))))))\",\"/web.config\",\"tree.php\",\"/cgi-bin-sdb/printenv\",\"/comments.php\",\"/detail.asp\",\"/license.txt\",\"/admin.asp\",\"/auth.php\",\"/list.php\",\"/content.php\",\"/mod.php\",\"/mini.php\",\"/install.pgsql\",\"/install.mysql\",\"/install.sqlite\",\"/install.sqlite3\",\"/install.txt\",\"/install.md\",\"/doku.php\",\"/main.asp\",\"/myadmin\",\"/force-download.php\",\"/iisprotect/admin\",\"/.gitignore\",\"/print.php\",\"/common.php\",\"/mainfile.php\",\"/functions.php\",\"/scripts/setup.php\",\"/faq.php\",\"/op/op.login.php\",\"/home.php\",\"/includes/hnmain.inc.php3\",\"/preview.php\",\"/dump.rar\",\"/dump.tar\",\"/dump.zip\",\"/dump.7z\",\"/dump.gz\",\"/dump.tgz\",\"/dump.tar.gz\",\"/thumbnail.php\",\"/sendcard.php\",\"/global.asax\",\"/directory.php\",\"/footer.php\",\"/error.asp\",\"/forum.asp\",\"/save.php\",\"/htmlsax3.php\",\"/adm/krgourl.php\",\"/includes/converter.inc.php\",\"/nucleus/libs/pluginadmin.php\",\"/base_qry_common.php\",\"/fileadmin\",\"/bitrix/admin/\",\"/adm.php\",\"/util/barcode.php\",\"/action.php\",\"/rss.asp\",\"/downloads.php\",\"/page.php\",\"/snarf_ajax.php\",\"/fck/editor\",\"/sendmail.php\",\"/detail.php\",\"/iframe.php\",\"/swfupload.swf\",\"/jenkins/login\",\"/phpmyadmin/main.php\",\"/phpmyadmin/scripts/setup.php\",\"/user/index.php\",\"/checkout.php\",\"/process.php\",\"/ks_inc/ajax.js\",\"/export.php\",\"/register.php\",\"/cart.php\",\"/console.php\",\"/friend.php\",\"/readmsg.php\",\"/install.asp\",\"/dagent/downloadreport.asp\",\"/system/index.php\",\"/core/changelog.txt\",\"/js/util.js\",\"/interna.php\",\"/gallery.php\",\"/links.php\",\"/data/admin/ver.txt\",\"/language/zh-cn.xml\",\"/productdetails.asp\",\"/admin/template/article_more/config.htm\",\"/components/com_moofaq/includes/file_includer.php\",\"/licence.txt\",\"/rss.xsl\",\"/vtigerservice.php\",\"/mysql/main.php\",\"/passwiki.php\",\"/scr/soustab.php\",\"/global.php\",\"/email.php\",\"/user.asp\",\"/msd\",\"/products.php\",\"/cultbooking.php\",\"/cron.php\",\"/static/js/admincp.js\",\"/comment.php\",\"/maintainers\",\"/modules/plain/adminpart/addplain.php\",\"/wp-content/plugins/ungallery/source_vuln.php\",\"/upgrade.txt\",\"/category.php\",\"/index_logged.php\",\"/members.asp\",\"/script/html.js\",\"/images/ad.js\",\"/awstats/awstats.pl\",\"/includes/esqueletos/skel_null.php\",\"/modules/profile/user.php\",\"/window_top.php\",\"/openbrowser.php\",\"/thread.php\",\"tinfoil_xss\",\"/includes/include.php\",\"/urheber.php\",\"/header.inc.php\",\"/mysqldumper\",\"/display.php\",\"/website.php\",\"/stats.php\",\"/assets/plugins/mp3_id/mp3_id.php\",\"/siteminderagent/forms/smpwservices.fcc\"]}}],\"transformers\":[\"lowercase\"]},{\"id\":\"nfd-000-002\",\"name\":\"Detect failed attempt to fetch readme files\",\"tags\":{\"type\":\"security_scanner\"},\"conditions\":[{\"operator\":\"match_regex\",\"parameters\":{\"inputs\":[{\"address\":\"server.response.status\"}],\"regex\":\"^404$\",\"options\":{\"case_sensitive\":true}}},{\"operator\":\"match_regex\",\"parameters\":{\"inputs\":[{\"address\":\"server.request.uri.raw\"}],\"regex\":\"readme\\\\.[\\\\.a-z0-9]+$\",\"options\":{\"case_sensitive\":false}}}],\"transformers\":[]},{\"id\":\"nfd-000-003\",\"name\":\"Detect failed attempt to fetch Java EE resource files\",\"tags\":{\"type\":\"security_scanner\"},\"conditions\":[{\"operator\":\"match_regex\",\"parameters\":{\"inputs\":[{\"address\":\"server.response.status\"}],\"regex\":\"^404$\",\"options\":{\"case_sensitive\":true}}},{\"operator\":\"match_regex\",\"parameters\":{\"inputs\":[{\"address\":\"server.request.uri.raw\"}],\"regex\":\"^(?:.*web\\\\-inf)(?:.*web\\\\.xml).*$\",\"options\":{\"case_sensitive\":false}}}],\"transformers\":[]},{\"id\":\"nfd-000-004\",\"name\":\"Detect failed attempt to fetch code files\",\"tags\":{\"type\":\"security_scanner\"},\"conditions\":[{\"operator\":\"match_regex\",\"parameters\":{\"inputs\":[{\"address\":\"server.response.status\"}],\"regex\":\"^404$\",\"options\":{\"case_sensitive\":true}}},{\"operator\":\"match_regex\",\"parameters\":{\"inputs\":[{\"address\":\"server.request.uri.raw\"}],\"regex\":\"\\\\.(java|pyc?|rb|class)\\\\b\",\"options\":{\"case_sensitive\":false}}}],\"transformers\":[]},{\"id\":\"nfd-000-005\",\"name\":\"Detect failed attempt to fetch source code archives\",\"tags\":{\"type\":\"security_scanner\"},\"conditions\":[{\"operator\":\"match_regex\",\"parameters\":{\"inputs\":[{\"address\":\"server.response.status\"}],\"regex\":\"^404$\",\"options\":{\"case_sensitive\":true}}},{\"operator\":\"match_regex\",\"parameters\":{\"inputs\":[{\"address\":\"server.request.uri.raw\"}],\"regex\":\"\\\\.(sql|log|ndb|gz|zip|tar\\\\.gz|tar|regVV|reg|conf|bz2|ini|db|war|bat|inc|btr|server|ds|conf|config|admin|master|sln|bak)\\\\b(?:[^.]|$)\",\"options\":{\"case_sensitive\":false}}}],\"transformers\":[]},{\"id\":\"nfd-000-006\",\"name\":\"Detect failed attempt to fetch sensitive files\",\"tags\":{\"type\":\"security_scanner\"},\"conditions\":[{\"operator\":\"match_regex\",\"parameters\":{\"inputs\":[{\"address\":\"server.response.status\"}],\"regex\":\"^404$\",\"options\":{\"case_sensitive\":true}}},{\"operator\":\"match_regex\",\"parameters\":{\"inputs\":[{\"address\":\"server.request.uri.raw\"}],\"regex\":\"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\"options\":{\"case_sensitive\":false}}}],\"transformers\":[]},{\"id\":\"nfd-000-007\",\"name\":\"Detect failed attempt to fetch archives\",\"tags\":{\"type\":\"security_scanner\"},\"conditions\":[{\"operator\":\"match_regex\",\"parameters\":{\"inputs\":[{\"address\":\"server.response.status\"}],\"regex\":\"^404$\",\"options\":{\"case_sensitive\":true}}},{\"operator\":\"match_regex\",\"parameters\":{\"inputs\":[{\"address\":\"server.request.uri.raw\"}],\"regex\":\"/[\\\\d\\\\-_]*\\\\.(rar|tar|zip|7z|gz|tgz|tar.gz)\",\"options\":{\"case_sensitive\":false}}}],\"transformers\":[]},{\"id\":\"nfd-000-008\",\"name\":\"Detect failed attempt to trigger incorrect application behavior\",\"tags\":{\"type\":\"security_scanner\"},\"conditions\":[{\"operator\":\"match_regex\",\"parameters\":{\"inputs\":[{\"address\":\"server.response.status\"}],\"regex\":\"^404$\",\"options\":{\"case_sensitive\":true}}},{\"operator\":\"match_regex\",\"parameters\":{\"inputs\":[{\"address\":\"server.request.uri.raw\"}],\"regex\":\"(/(administrator/components/com.*\\\\.php|response\\\\.write\\\\(.+\\\\))|select\\\\(.+\\\\)from|\\\\(.*sleep\\\\(.+\\\\)|(%[a-zA-Z0-9]{2}[a-zA-Z]{0,1})+\\\\))\",\"options\":{\"case_sensitive\":false}}}],\"transformers\":[]},{\"id\":\"nfd-000-009\",\"name\":\"Detect failed attempt to leak the structure of the application\",\"tags\":{\"type\":\"security_scanner\"},\"conditions\":[{\"operator\":\"match_regex\",\"parameters\":{\"inputs\":[{\"address\":\"server.response.status\"}],\"regex\":\"^404$\",\"options\":{\"case_sensitive\":true}}},{\"operator\":\"match_regex\",\"parameters\":{\"inputs\":[{\"address\":\"server.request.uri.raw\"}],\"regex\":\"/(login\\\\.rol|LICENSE|[\\\\w-]+\\\\.(plx|pwd))$\",\"options\":{\"case_sensitive\":false}}}],\"transformers\":[]},{\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\":\"nosql_injection\",\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"},{\"address\":\"grpc.server.request.message\"}],\"regex\":\"http(s?):\\\\/\\\\/([A-Za-z0-9\\\\.\\\\-\\\\_]+|\\\\[[A-Fa-f0-9\\\\:]+\\\\]|):5986\\\\/wsman\",\"options\":{\"min_length\":4}},\"operator\":\"match_regex\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"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\"},{\"address\":\"grpc.server.request.message\"}],\"regex\":\"^(jar:)?((file|netdoc):\\\\/\\\\/[\\\\\\\\\\\\/]+|(dict|gopher|ldap|sftp|tftp):\\\\/\\\\/.*:[0-9]{1,5})\"},\"operator\":\"match_regex\"}],\"transformers\":[\"lowercase\"]},{\"id\":\"sqr-000-017\",\"name\":\"JNDI: Attempt to exploit log4j CVE-2021-44228\",\"tags\":{\"type\":\"java_code_injection\",\"category\":\"attack_attempt\"},\"conditions\":[{\"parameters\":{\"inputs\":[{\"address\":\"server.request.uri.raw\"},{\"address\":\"server.request.query\"},{\"address\":\"server.request.body\"},{\"address\":\"server.request.path_params\"},{\"address\":\"server.request.headers.no_cookies\"},{\"address\":\"grpc.server.request.message\"}],\"regex\":\"\\\\${[^j]*j[^n]*n[^d]*d[^i]*i[^:]*:[^}]*}\"},\"operator\":\"match_regex\"}],\"transformers\":[]},{\"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)(