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\":\"