pax_global_header00006660000000000000000000000064147776622100014526gustar00rootroot0000000000000052 comment=e85266e5989d7467ba9dd40b15f892167c299142 fever-1.3.7/000077500000000000000000000000001477766221000126455ustar00rootroot00000000000000fever-1.3.7/.github/000077500000000000000000000000001477766221000142055ustar00rootroot00000000000000fever-1.3.7/.github/workflows/000077500000000000000000000000001477766221000162425ustar00rootroot00000000000000fever-1.3.7/.github/workflows/go.yml000066400000000000000000000010641477766221000173730ustar00rootroot00000000000000--- name: Go build on: - push - pull_request jobs: build: name: "Go build" runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Go uses: actions/setup-go@v2 with: go-version: 1.21 - name: Install non-Go deps run: | sudo apt update sudo apt -yq install redis-server - name: Get and build deps run: go get -v -t ./... - name: Build executable run: go build -v -o fever cmd/fever/main.go - name: Run tests run: go test -v ./... fever-1.3.7/.gitignore000066400000000000000000000000431477766221000146320ustar00rootroot00000000000000*.eve.json *.eve.json.gz .vs tmp/**fever-1.3.7/CHANGELOG.md000066400000000000000000000121021477766221000144520ustar00rootroot00000000000000# Changelog All notable changes to FEVER will be documented in this file. ## [1.3.7] - 2025-04-16 ### Changed - Move from deprecated AMQP library `github.com/streadway/amqp` to `github.com/rabbitmq/amqp091-go`. - Move from deprecated Redis library `github.com/garyburd/redigo` to `github.com/gomodule/redigo`. - Update dependencies. ## [1.3.6] - 2024-07-03 ### Added - Add support for sending aggregations from all flows, not just TCP bidirectional ones. ## [1.3.5] - 2023-03-27 ### Fixed - Properly handle `null` fields in DNS v2 data (#104) ## [1.3.4] - 2022-04-28 ### Changed - Log heartbeat creation with Info level (#100) - Update Go dependency versions (#99) ### Removed - Support for Stenosis (#98) ## [1.3.3] - 2022-01-25 ### Changed - Fixed handling of JSON `null` values (#97) ## [1.3.2] - 2021-12-09 ### Added - End-to-end test support - Add heartbeat alerts to forwarded events (#94) - Add flow report testdata submission (#93) - Add passive DNS testdata submission (#92) - Add option to remove `null` JSON fields when using `fever alertify` (#91) ## [1.3.1] - 2021-11-03 ### Fixed - Ensure that alertified events also contain added fields (#90) ## [1.3.0] - 2021-08-15 ### Added - gRPC based infrastructure for remote runtime communication with FEVER process. - Runtime control tool for Bloom filter matcher `fever bloom` (#86, #85) ### Changed - CI now uses GitHub Actions (#87, #81) ## [1.2.0] - 2021-06-25 ### Added - Support for multiple output sockets with event type filtering and buffers (#84) ### Changed - Speed up addition of fields to forwarded EVE-JSON (#83) ## [1.1.0] - 2021-06-09 ### Added - Support for input buffering (#82) ## [1.0.19] - 2021-05-04 ### Added - Support Bloom filter matching for TLS fingerprints (#76, #38) ### Changed - Reduce log noise by moving AMQP messages to debug log level (#78) ## [1.0.18] - 2021-03-30 ### Added - Added `version` subcommand (#73) ### Changed - Prevent deadlock on main event stream during reconnect (#75) ## [1.0.17] - 2021-03-04 ### Changed - change timestamp handling when alertifying (#72) ## [1.0.16] - 2021-02-19 ### Changed - Remove potentially blocking calls/locks (#71) - Use Go modules. ## [1.0.15] - 2021-01-22 ### Changed - Make sure timestamps created by alertifier match regular Suricata timestamps. - Ensure FEVER starts up with unreachable AMQP endpoint (#69) ## [1.0.14] - 2020-12-04 ### Added - Add heartbeat injector (#67) ## [1.0.13] - 2020-11-05 ### Added - Add flow profiling metrics gathering (#66) ## [1.0.12] - 2020-10-13 ### Added - Add interface filtering for Stenosis connector (#60) - Add alertify tool (#62) ### Changed - Various bugfixes (#63, #64) ## [1.0.11] - 2020-08-11 ### Added - CHANGELOG.md now available. - Add option to inject arbitrary fields into EVE-JSON (#49) ### Changed - Various code simplifications and robustness improvements. ## [1.0.10] - 2020-06-11 ### Changed - Only extend incoming EVE-JSON instead of marshaling into predefined schema. This enables future-proof consistent output of EVE-JSON as there are no assuptions about what fields are present or allowed in the JSON schema (#54) ### Fixed - Some bugfixes (such as race conditions). ## [1.0.9] - 2020-05-14 ### Added - Support for interacting with an external persistence tool (Stenosis). ### Changed Various cleanups as well as test and code simplifications. ## [1.0.8] - 2019-09-19 ### Added - Optional collection of metadata bundles (context) for each alert, to be submitted over a separate AMQP connection (#46) ### Changed - Flow IDs are now forwarded as strings to work around potential issues with syslog-ng (#48) ## [1.0.7] - 2019-08-06 ### Fixed - Bloom filter alerts might not be properly forwarded (cf. rhaist/surevego@b1cf215) ## [1.0.6] - 2019-08-02 ### Added - Support for active rDNS queries (#36) - Bloom filter IoC blocking (#44) ### Changed - Do not use explicit types in InfluxDB submissions (#34) - Distinguish DNS query and answer in Bloom filter alerting (#40) - Allow AMQP channel multiplexing (#43) ### Fixed - Fix bug causing 100% CPU on AMQP reconnect (#43) ## [1.0.5] - 2019-02-14 ### Added - Support for more flexible URL Bloom filter matching (#33) ### Fixed - Improved stability of tests w.r.t. run time, see (#32 and #31) ## [1.0.4] - 2019-01-25 ### Added - Forwarding can be disabled by setting -o to empty string (#22) - TLS metadata is included in TLS SNI Bloom filter alert (#26) ### Fixed - Tests no longer fail intermittently (#27) ### Changed - All events are sent to the database, not just those unhandled by any additional processors (#29) ## [1.0.3] - 2019-01-11 ### Added - Support for IP alerting via EVE metadata (#18) ### Changed - Improves robustness of Bloom filter matching by more relaxed handling of corrupted filter input files (#19) ## [1.0.2] - 2018-12-11 ### Added - Configurable Bloom filter prefixes (#16) ## [1.0.1] - 2018-11-12 ### Added - `makeman` subcommand ### Changed - Do not fail when no config file can be read. - Do not use DCSO-specific alert prefixes by default for Bloom filter alerts. ## [1.0.0] - 2018-11-09 First proper open-source release. fever-1.3.7/LICENSE000066400000000000000000000030421477766221000136510ustar00rootroot00000000000000Copyright (c) 2017, 2018, 2019, DCSO Deutsche Cyber-Sicherheitsorganisation GmbH All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the DCSO Deutsche Cyber-Sicherheitsorganisation GmbH nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. fever-1.3.7/README.md000066400000000000000000000337101477766221000141300ustar00rootroot00000000000000# 🔥 FEVER ![Build Status](https://github.com/DCSO/fever/actions/workflows/go.yml/badge.svg) [![DebianSBadge](https://badges.debian.net/badges/debian/stable/fever/version.svg)](https://packages.debian.org/stable/fever) [![DebianTBadge](https://badges.debian.net/badges/debian/testing/fever/version.svg)](https://packages.debian.org/testing/fever) ![Ubuntu package](https://img.shields.io/ubuntu/v/fever) The Fast, Extensible, Versatile Event Router (FEVER) is a tool for fast processing of events from Suricata's JSON EVE output. What is meant by 'processing' is defined by a number of modular components, for example facilitating fast ingestion into a database. Other processors implement collection, aggregation and forwarding of various metadata (e.g. aggregated and raw flows, passive DNS data, etc.) as well as performance metrics. It is meant to be used in front of (or as a replacement for) general-purpose log processors like Logstash to increase event throughput as observed on sensors that see a lot of traffic. ## Building Like any good Go program: ``` $ go get -t ./... $ go build ./... $ go install -v ./... ... $ fever run -h ``` ## Usage ``` $ ./fever run -h The 'run' command starts the FEVER service, consuming events from the input and executing all processing components. Usage: fever run [flags] Flags: --active-rdns enable active rDNS enrichment for src/dst IPs --active-rdns-cache-expiry duration cache expiry interval for rDNS lookups (default 2m0s) --active-rdns-private-only only do active rDNS enrichment for RFC1918 IPs --bloom-alert-prefix string String prefix for Bloom filter alerts (default "BLF") --bloom-blacklist-iocs strings Blacklisted strings in Bloom filter (will cause filter to be rejected) (default [/,/index.htm,/index.html]) -b, --bloom-file string Bloom filter for external indicator screening -z, --bloom-zipped use gzipped Bloom filter file -c, --chunksize uint chunk size for batched event handling (e.g. inserts) (default 50000) --context-cache-timeout duration time for flow metadata to be kept for uncompleted flows (default 1h0m0s) --context-enable collect and forward flow context for alerted flows --context-submission-exchange string Exchange to which flow context events will be submitted (default "context") --context-submission-url string URL to which flow context will be submitted (default "amqp://guest:guest@localhost:5672/") -d, --db-database string database DB (default "events") --db-enable write events to database -s, --db-host string database host (default "localhost:5432") --db-maxtablesize uint Maximum allowed cumulative table size in GB (default 500) -m, --db-mongo use MongoDB -p, --db-password string database password (default "sensor") --db-rotate duration time interval for database table rotations (default 1h0m0s) -u, --db-user string database user (default "sensor") --dummy log locally instead of sending home --flowextract-bloom-selector string IP address Bloom filter to select flows to extract --flowextract-enable extract and forward flow metadata --flowextract-submission-exchange string Exchange to which raw flow events will be submitted (default "flows") --flowextract-submission-url string URL to which raw flow events will be submitted (default "amqp://guest:guest@localhost:5672/") -n, --flowreport-interval duration time interval for report submissions --flowreport-nocompress send uncompressed flow reports (default is gzip) --flowreport-submission-exchange string Exchange to which flow reports will be submitted (default "aggregations") --flowreport-submission-url string URL to which flow reports will be submitted (default "amqp://guest:guest@localhost:5672/") --flushcount uint maximum number of events in one batch (e.g. for flow extraction) (default 100000) -f, --flushtime duration time interval for event aggregation (default 1m0s) -T, --fwd-all-types forward all event types -t, --fwd-event-types strings event types to forward to socket (default [alert,stats]) --heartbeat-enable Forward HTTP heartbeat event --heartbeat-times strings Times of day to send heartbeat (list of 24h HH:MM strings) -h, --help help for run --in-buffer-drop drop incoming events on FEVER side instead of blocking the input socket (default true) --in-buffer-length uint input buffer length (counted in EVE objects) (default 500000) -r, --in-redis string Redis input server (assumes "suricata" list key, no pwd) --in-redis-nopipe do not use Redis pipelining -i, --in-socket string filename of input socket (accepts EVE JSON) (default "/tmp/suri.sock") --ip-alert-prefix string String prefix for IP blacklist alerts (default "IP-BLACKLIST") --ip-blacklist string List with IP ranges to alert on --logfile string Path to log file --logjson Output logs in JSON format --metrics-enable submit performance metrics to central sink --metrics-submission-exchange string Exchange to which metrics will be submitted (default "metrics") --metrics-submission-url string URL to which metrics will be submitted (default "amqp://guest:guest@localhost:5672/") -o, --out-socket string path to output socket (to forwarder), empty string disables forwarding (default "/tmp/suri-forward.sock") --pdns-enable collect and forward aggregated passive DNS data --pdns-submission-exchange string Exchange to which passive DNS events will be submitted (default "pdns") --pdns-submission-url string URL to which passive DNS events will be submitted (default "amqp://guest:guest@localhost:5672/") --profile string enable runtime profiling to given file --reconnect-retries uint number of retries connecting to socket or sink, 0 = no retry limit --toolname string set toolname (default "fever") -v, --verbose enable verbose logging (debug log level) Global Flags: --config string config file (default is $HOME/.fever.yaml) --mgmt-host string hostname:port definition for management server --mgmt-network string network (tcp/udp) definition for management server (default "tcp") --mgmt-socket string Socket path for management server (default "/tmp/fever-mgmt.sock") ``` It is also possible to use a config file in YAML format ([Example](fever.yaml)). Configuration is cascading: first settings are loaded from the config file and can then be overridden by command line parameters. ## Running tests The test suite requires a Redis executable in the current path. Most simply, this requirement can be satisfied by just installing Redis. For instance, via `apt`: ``` $ apt install redis-server ``` Then the test suite can be run via Go's generic testing framework: ``` $ go test -v -race -cover ./... ... ``` ## Suricata settings The tool is designed to consume JSON events from a socket, by default `/tmp/suri.sock`. This can be enabled using the following setting in `suricata.yaml`: ```yaml ... # Extensible Event Format (nicknamed EVE) event log in JSON format - eve-log: enabled: yes filetype: unix_stream filename: /tmp/suri.sock ... ``` All JSON is also passed through to another socket, which allows to plug it between Suricata and another log consumer, e.g. Logstash and friends. Another way to consume events is via Redis. Use the `-r` parameters to specify a Redis host, the key `suricata` will be queried as a list to BRPOP events from. ## Important settings - Database connection: use the `-db-*` parameters to specify a database connection. PostgreSQL 9.5 or later is required. Use `-m` to use the parameters as MongoDB connection parameters instead. - Chunk size: determines the number of events that is imported as a whole at the same time. Larger values may be faster and lead to better throughput, but will use more RAM and also lose more events in case a bulk import (=transaction) fails. Smaller values will increase the overhead on the database. - Profiling: optional output of a pprof file to be used with `go tool pprof`. - Table rotation: tables are created as unlogged tables without indexes for maximal write performance. To keep table sizes in check, tables are timestamped and rotated in a time interval chosen by the user, e.g. 1h. Index creation is deferred until a table is rotated away and no longer written to, and also happens in the background. Indexing jobs are queued so if indexing takes longer than one rotation period, data should not be lost. - Event forwarding: Events processed by FEVER can be forwarded to another socket to be processed by a downstream tool, e.g. Logstash. By default, only `alert` and `stats` event types are forwarded, but the set of forwarded types can be extended using `-t ` for additional types to be forwarded. As a catch-all (and probably the best option for sensors still running a full ELK stack) the option `-T` will forward everything. - Bloom filters can be reloaded by sending a `SIGUSR1` to the main process. ## Development test runs with local data Create local socket to consume forwarded events. You can also use [pv](http://www.ivarch.com/programs/pv.shtml) to monitor if data is flowing and how much (you may need to install the necessary tools using `apt install pv netcat-openbsd` before): ```bash $ nc -klU /tmp/suri-forward.sock | pv > /dev/null ``` Instead of simply sending it to `/dev/null`, one can of course filter the output using `jq` etc. to visually confirm that certain output is forwarded. Start the service: ```bash $ ./fever run -v -n 0 -o '' --logfile '' & ``` The `-n 0` option disables submission of flow metadata. The `-o ''` disables forwarding to a local socket sink. Optionally, `--dummy`/`--nodb` can be used to disable database inserts and only test input parsing and metadata aggregation. Finally, push test data into the input socket: ```bash $ head -n 100000 huge.eve.json | socat /tmp/suri.sock STDIO ``` which would feed the first 100k events from `huge.eve.json` into the socket. The `socat` tool can be installed as usual via `apt install socat`. To feed EVE data into FEVER using Redis (started with `-r`), you can simply LPUSH the JSON events into a list referenced by the key `suricata`. Use the Lua script `scripts/makelpush` to convert raw EVE lines into Redis statements: ``` $ head -n 100000 huge.eve.json | scripts/makelpush | redis-cli > /dev/null ``` ## End-to-end testing support FEVER can optionally inject in-band test data into downstream submissions, such as passive DNS observations, so allow automated checks that receiving components are updated correctly. * For injecting test alerts into the forwarded stream, use the `heartbeat.alert-times` list to specify when an alert heartbeat should be injected. The approach is identical to the one for the general heartbeats: at each specified time, an alert like ```json { "timestamp": "2021-12-09T09:49:35.641252+0000", "event_type": "alert", "src_ip": "192.0.2.1", "src_port": 39106, "dest_ip": "192.0.2.2", "dest_port": 80, "proto": "TCP", "alert": { "action": "allowed", "gid": 0, "signature_id": 0, "rev": 0, "signature": "DCSO FEVER TEST alert", "category": "Not Suspicious Traffic", "severity": 0 }, "http": { "hostname": "test-2021-12-09.vast", "url": "/just-visiting", "http_user_agent": "FEVER", "http_content_type": "text/html", "http_method": "GET", "protocol": "HTTP/1.1", "status": 200, "length": 42 } } ``` will be created and forwarded. * For passive DNS observation submissions, use the `pdns.test-domain` config item to insert a dummy entry for that domain, e.g. for `pdns.test-domain` set to `heartbeat.fever-heartbeat`: ```json { "timestamp_start": "2021-12-07T18:18:00.029197078Z", "timestamp_end": "2021-12-07T18:19:00.063460044Z", "dns": { "heartbeat.fever-heartbeat": { "rdata": [ { "answering_host": "0.0.0.0", "rrtype": "A", "rdata": "0.0.0.0", "rcode": "NOERROR", "count": 1 } ] }, ... } } ``` * For flow report submission, use the `flowreport.testdata*` config items to insert a dummy flow for that specific IPs and ports, e.g. for : ```yaml flowreport: # ... testdata-srcip: 0.0.0.1 testdata-destip: 0.0.0.2 testdata-destport: 99999 ``` we would get ```json { "sensor-id": "XXX", "time-start": "2021-12-08T13:53:36.442182896+01:00", "time-end": "2021-12-08T13:53:46.490743527+01:00", "tuples": { "0.0.0.1_0.0.0.2_99999": { "count": 1, "total_bytes_toclient": 23, "total_bytes_toserver": 42 } }, ... } ``` ## Author/Contact Sascha Steinbiss ## License BSD-3-clause fever-1.3.7/cmd/000077500000000000000000000000001477766221000134105ustar00rootroot00000000000000fever-1.3.7/cmd/fever/000077500000000000000000000000001477766221000145175ustar00rootroot00000000000000fever-1.3.7/cmd/fever/cmds/000077500000000000000000000000001477766221000154455ustar00rootroot00000000000000fever-1.3.7/cmd/fever/cmds/alertify.go000066400000000000000000000144561477766221000176250ustar00rootroot00000000000000package cmd // DCSO FEVER // Copyright (c) 2020, DCSO GmbH import ( "encoding/json" "fmt" "io" "os" "reflect" "strings" "github.com/DCSO/fever/input" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" log "github.com/sirupsen/logrus" "github.com/buger/jsonparser" "github.com/spf13/cobra" "github.com/spf13/viper" ) var count uint64 type alertifyAlertJSONProvider struct{} // GetAlertJSON returns the "alert" subobject for an alert EVE event. func (a alertifyAlertJSONProvider) GetAlertJSON(inputEvent types.Entry, prefix string, ioc string) ([]byte, error) { return util.GenericGetAlertObjForIoc(inputEvent, prefix, ioc, "%s Generic IoC match for: %s") } func makeAlertifyAlertifier(prefix, extrakey string) *util.Alertifier { a := util.MakeAlertifier(prefix) a.RegisterMatchType("dns-req", util.AlertJSONProviderDNSReq{}) a.RegisterMatchType("dns-resp", util.AlertJSONProviderDNSResp{}) a.RegisterMatchType("tls-sni", util.AlertJSONProviderTLSSNI{}) a.RegisterMatchType("http-host", util.AlertJSONProviderHTTPHost{}) a.RegisterMatchType("http-url", util.AlertJSONProviderHTTPURL{}) a.RegisterMatchType("generic", alertifyAlertJSONProvider{}) a.SetExtraModifier(func(inputAlert *types.Entry, ioc string) error { iocEscaped, err := util.EscapeJSON(ioc) if err != nil { return err } val, err := jsonparser.Set([]byte(inputAlert.JSONLine), iocEscaped, "_extra", extrakey) if err != nil { return err } inputAlert.JSONLine = string(val) return nil }) return a } func emitAlertsForEvent(a *util.Alertifier, e types.Entry, ioc string, out io.Writer, limit uint64) error { var err error var alert *types.Entry var specificMatch = false if e.TLSSNI == ioc { specificMatch = true alert, err = a.MakeAlert(e, ioc, "tls-sni") if err != nil { return err } fmt.Fprintf(out, "%s\n", string(alert.JSONLine)) if limit > 0 && count >= limit { return fmt.Errorf("limit reached (%d)", limit) } count++ } if e.DNSRRName == ioc { specificMatch = true if e.DNSType == "answer" { alert, err = a.MakeAlert(e, ioc, "dns-resp") } else { alert, err = a.MakeAlert(e, ioc, "dns-req") } if err != nil { return err } fmt.Fprintf(out, "%s\n", string(alert.JSONLine)) if limit > 0 && count >= limit { return fmt.Errorf("limit reached (%d)", limit) } count++ } if e.HTTPHost == ioc { specificMatch = true alert, err = a.MakeAlert(e, ioc, "http-host") if err != nil { return err } fmt.Fprintf(out, "%s\n", string(alert.JSONLine)) if limit > 0 && count >= limit { return fmt.Errorf("limit reached (%d)", limit) } count++ } if strings.Contains(e.HTTPUrl, ioc) { specificMatch = true alert, err = a.MakeAlert(e, ioc, "http-url") if err != nil { return err } fmt.Fprintf(out, "%s\n", string(alert.JSONLine)) if limit > 0 && count >= limit { return fmt.Errorf("limit reached (%d)", limit) } count++ } if !specificMatch { alert, err = a.MakeAlert(e, ioc, "generic") if err != nil { return err } fmt.Fprintf(out, "%s\n", string(alert.JSONLine)) if limit > 0 && count >= limit { return fmt.Errorf("limit reached (%d)", limit) } count++ } return nil } func removeItemsWithNullValues(root map[string]interface{}, rootArr []interface{}) { // Note we never have a call with both of these possibly set. if root != nil { rVal := reflect.ValueOf(root) for _, k := range rVal.MapKeys() { v := rVal.MapIndex(k) if v.IsNil() { delete(root, k.String()) continue } switch t := v.Interface().(type) { // We support recursion into maps and slices. case map[string]interface{}: removeItemsWithNullValues(t, nil) case []interface{}: removeItemsWithNullValues(nil, t) } } } else if rootArr != nil { rVal := reflect.ValueOf(rootArr) for i := 0; i < rVal.Len(); i++ { v := rVal.Index(i) switch t := v.Interface().(type) { // We support recursion into maps and slices. case map[string]interface{}: removeItemsWithNullValues(t, nil) case []interface{}: removeItemsWithNullValues(nil, t) } } } } func alertify(cmd *cobra.Command, args []string) { eventChan := make(chan types.Entry, defaultQueueSize) sinput := input.MakeStdinInput(eventChan) sinput.Run() c := make(chan bool) prefix := viper.GetString("alert-prefix") ioc := viper.GetString("ioc") if len(ioc) == 0 { log.Fatal("IoC cannot be empty") } limit := viper.GetUint("alert-limit") extrakey := viper.GetString("extra-key") removeNulls := viper.GetBool("remove-nulls") addFields := viper.GetStringMapString("add-fields") a := makeAlertifyAlertifier(prefix, extrakey) if err := a.SetAddedFields(addFields); err != nil { log.Fatal(err) } for e := range eventChan { var err error var eJSON map[string]interface{} if removeNulls { err = json.Unmarshal([]byte(e.JSONLine), &eJSON) if err != nil { log.Error(err) continue } removeItemsWithNullValues(eJSON, nil) fJSON, err := json.Marshal(eJSON) if err != nil { log.Error(err) continue } e.JSONLine = string(fJSON) } err = emitAlertsForEvent(a, e, ioc, os.Stdout, uint64(limit)) if err != nil { log.Error(err) } } sinput.Stop(c) <-c } var alertifyCmd = &cobra.Command{ Use: "alertify", Short: "convert metadata events into alerts", Long: `The 'alertify' command converts all metadata events read from stdin to alert events.`, Run: alertify, } func init() { rootCmd.AddCommand(alertifyCmd) alertifyCmd.PersistentFlags().StringP("ioc", "i", "", "indicator to flag in an input event") viper.BindPFlag("ioc", alertifyCmd.PersistentFlags().Lookup("ioc")) alertifyCmd.PersistentFlags().StringP("extra-key", "e", "alertify-ioc", "key for IoC container field in _extra subobject") viper.BindPFlag("extra-key", alertifyCmd.PersistentFlags().Lookup("extra-key")) alertifyCmd.PersistentFlags().StringP("alert-prefix", "p", "ALERTIFY", "prefix for alert.signature field") viper.BindPFlag("alert-prefix", alertifyCmd.PersistentFlags().Lookup("alert-prefix")) alertifyCmd.PersistentFlags().UintP("alert-limit", "l", 0, "limit for alerts to be created (0 = no limit)") viper.BindPFlag("alert-limit", alertifyCmd.PersistentFlags().Lookup("alert-limit")) alertifyCmd.PersistentFlags().BoolP("remove-nulls", "n", true, "remove items with nulls") viper.BindPFlag("remove-nulls", alertifyCmd.PersistentFlags().Lookup("remove-nulls")) } fever-1.3.7/cmd/fever/cmds/alertify_test.go000066400000000000000000000036311477766221000206550ustar00rootroot00000000000000package cmd import ( "bytes" "io/ioutil" "strings" "testing" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" ) func checkAlertified(t *testing.T, es []types.Entry, ioc string, result string) bool { a := makeAlertifyAlertifier("TEST", "test-ioc") var buf bytes.Buffer for _, e := range es { err := emitAlertsForEvent(a, e, ioc, &buf, 0) if err != nil { t.Fatal(err) } } return strings.Contains(buf.String(), result) } func checkLimit(t *testing.T, es []types.Entry, ioc string) { a := makeAlertifyAlertifier("TEST", "test-ioc") var buf bytes.Buffer i := 0 for _, e := range es { err := emitAlertsForEvent(a, e, ioc, &buf, 1) if i == 1 { if err == nil { t.Fatal(err) } if !strings.Contains(err.Error(), `limit reached (1)`) { t.Fatal("wrong limit error message: ", err.Error()) } } i++ } } func TestAlertify(t *testing.T) { ins, err := ioutil.ReadFile("testdata/alertify_input.json") if err != nil { t.Fatal(err) } inputs := make([]types.Entry, 0) for _, line := range strings.Split(string(ins), "\n") { e, err := util.ParseJSON([]byte(line)) if err != nil { t.Fatal(err) } inputs = append(inputs, e) } if !checkAlertified(t, inputs, "evader.example.com", `TEST Possibly bad HTTP host: evader.example.com`) { t.Fatal("evader.example.com not processed") } if !checkAlertified(t, inputs, "static.programme-tv.net", `TEST Possibly bad DNS response for static.programme-tv.net`) { t.Fatal("static.programme-tv.net not processed") } if !checkAlertified(t, inputs, "example.com", `TEST Possibly bad TLS SNI: example.com`) { t.Fatal("example.com not processed") } if !checkAlertified(t, inputs, "/compressed/eicar.txt/ce%3Agzip,gzip;gzip;gzip", `TEST Possibly bad HTTP URL: GET | evader.example.com | /compressed/eicar.txt/ce%3Agzip,gzip;gzip;gzip`) { t.Fatal("example.com URL not processed") } checkLimit(t, inputs, "example.com") } fever-1.3.7/cmd/fever/cmds/bloom.go000066400000000000000000000060341477766221000171070ustar00rootroot00000000000000package cmd // DCSO FEVER // Copyright (c) 2021, DCSO GmbH import ( "bufio" "context" "fmt" "os" "github.com/DCSO/fever/mgmt" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "google.golang.org/grpc" "google.golang.org/protobuf/types/known/emptypb" ) var ( clt mgmt.MgmtServiceClient conn *grpc.ClientConn ) func bloomAdd(cmd *cobra.Command, args []string) { stream, err := clt.BloomAdd(context.TODO()) if err != nil { logrus.Fatal(err) } scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { if err := stream.Send(&mgmt.MgmtBloomAddRequest{ Ioc: scanner.Text(), }); err != nil { logrus.Fatal(err) } } resp, err := stream.CloseAndRecv() if err != nil { logrus.Fatal(err) } logrus.Debugf("added %d items", resp.GetAdded()) } func bloomInfo(cmd *cobra.Command, args []string) { got, err := clt.BloomInfo(context.TODO(), &emptypb.Empty{}) if err != nil { logrus.Fatal(err) } fmt.Printf("Capacity: %d\n", got.GetCapacity()) fmt.Printf("Elements: %d\n", got.GetElements()) fmt.Printf("# Hashfuncs: %d\n", got.GetHashfuncs()) fmt.Printf("FP Probability: %v\n", got.GetFpprob()) fmt.Printf("Bits: %d\n", got.GetBits()) } func bloomSave(cmd *cobra.Command, args []string) { _, err := clt.BloomSave(context.TODO(), &emptypb.Empty{}) if err != nil { logrus.Fatal(err) } } func bloomReload(cmd *cobra.Command, args []string) { _, err := clt.BloomReload(context.TODO(), &emptypb.Empty{}) if err != nil { logrus.Fatal(err) } } var bloomInfoCmd = &cobra.Command{ Use: "show", Short: "print information on Bloom filter", Long: `The 'bloom info' command shows stats on the Bloom filter in FEVER's Bloom filter matcher.`, Run: bloomInfo, } var bloomAddCmd = &cobra.Command{ Use: "add", Short: "add items to Bloom filter", Long: `The 'bloom add' command adds IoCs from stdin into FEVER's Bloom filter matcher.`, Run: bloomAdd, } var bloomSaveCmd = &cobra.Command{ Use: "save", Short: "save Bloom filter to disk", Long: `The 'bloom save' command persists the current state of FEVER's Bloom filter matcher to disk.`, Run: bloomSave, } var bloomReloadCmd = &cobra.Command{ Use: "reload", Short: "reload Bloom filter from disk", Long: `The 'bloom reload' command reloads FEVER's Bloom filter from disk.`, Run: bloomReload, } var bloomCmd = &cobra.Command{ Use: "bloom", Short: "modify Bloom filter used by FEVER for detection", Long: `The 'bloom' command interacts with FEVER's Bloom filter matcher.`, } func init() { rootCmd.AddCommand(bloomCmd) bloomCmd.AddCommand(bloomAddCmd) bloomCmd.AddCommand(bloomInfoCmd) bloomCmd.AddCommand(bloomSaveCmd) bloomCmd.AddCommand(bloomReloadCmd) bloomCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { var err error mgmtCfg := mgmt.EndpointConfigFromViper() conn, err = grpc.Dial(mgmtCfg.DialString(), mgmtCfg.DialOptions...) if err != nil { return err } clt = mgmt.NewMgmtServiceClient(conn) return nil } bloomCmd.PersistentPostRunE = func(cmd *cobra.Command, args []string) error { return conn.Close() } } fever-1.3.7/cmd/fever/cmds/makeman.go000066400000000000000000000013761477766221000174140ustar00rootroot00000000000000package cmd import ( log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/cobra/doc" ) // mmanCmd represents the makeman command var mmanCmd = &cobra.Command{ Use: "makeman [options]", Short: "Create man pages", Run: func(cmd *cobra.Command, args []string) { targetDir, err := cmd.Flags().GetString("dir") if err != nil { log.Fatal(err) } header := &doc.GenManHeader{} err = doc.GenManTree(rootCmd, header, targetDir) if err != nil { log.Fatal(err) } for _, v := range rootCmd.Commands() { err = doc.GenManTree(v, header, targetDir) if err != nil { log.Fatal(err) } } }, } func init() { rootCmd.AddCommand(mmanCmd) mmanCmd.Flags().StringP("dir", "d", ".", "target directory for man pages") } fever-1.3.7/cmd/fever/cmds/root.go000066400000000000000000000045371477766221000167700ustar00rootroot00000000000000package cmd // DCSO FEVER // Copyright (c) 2018, 2021, DCSO GmbH import ( "fmt" "os" homedir "github.com/mitchellh/go-homedir" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" ) var cfgFile string // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "fever", Short: "fast, extensible and flexible event router", Long: `FEVER is a fast execution engine for processing, aggregation and reporting components that act on Suricata's EVE output.`, } // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } } func init() { cobra.OnInitialize(initConfig) // Here you will define your flags and configuration settings. // Cobra supports persistent flags, which, if defined here, // will be global for your application. rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.fever.yaml)") // Management server options rootCmd.PersistentFlags().StringP("mgmt-socket", "", "/tmp/fever-mgmt.sock", "Socket path for management server") viper.BindPFlag("mgmt.socket", rootCmd.PersistentFlags().Lookup("mgmt-socket")) rootCmd.PersistentFlags().StringP("mgmt-host", "", "", "hostname:port definition for management server") viper.BindPFlag("mgmt.host", rootCmd.PersistentFlags().Lookup("mgmt-host")) rootCmd.PersistentFlags().StringP("mgmt-network", "", "tcp", "network (tcp/udp) definition for management server") viper.BindPFlag("mgmt.network", rootCmd.PersistentFlags().Lookup("mgmt-network")) } // initConfig reads in config file and ENV variables if set. func initConfig() { if cfgFile != "" { // Use config file from the flag. viper.SetConfigFile(cfgFile) } else { // Find home directory. home, err := homedir.Dir() if err != nil { fmt.Println(err) os.Exit(1) } // Search config in home directory with name ".fever" (without extension). viper.AddConfigPath(home) viper.SetConfigName(".fever") } viper.AutomaticEnv() // read in environment variables that match // If a config file is found, read it in. if err := viper.ReadInConfig(); err == nil { log.Infof("Using config file: %s", viper.ConfigFileUsed()) } } fever-1.3.7/cmd/fever/cmds/run.go000066400000000000000000000653621477766221000166140ustar00rootroot00000000000000package cmd // DCSO FEVER // Copyright (c) 2017, 2021, DCSO GmbH import ( "context" "io" "os" "os/signal" "runtime/pprof" "syscall" "time" "github.com/DCSO/fever/db" "github.com/DCSO/fever/input" "github.com/DCSO/fever/mgmt" "github.com/DCSO/fever/processing" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" "github.com/NeowayLabs/wabbit" "github.com/NeowayLabs/wabbit/amqp" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" ) var dispatcher *processing.HandlerDispatcher var forward bool const defaultQueueSize = 50000 func mainfunc(cmd *cobra.Command, args []string) { var s db.Slurper var err error var submitter util.StatsSubmitter var statssubmitter util.StatsSubmitter var pse *util.PerformanceStatsEncoder eventChan := make(chan types.Entry, defaultQueueSize) util.ToolName = viper.GetString("toolname") logfilename := viper.GetString("logging.file") if len(logfilename) > 0 { log.Println("Switching to log file", logfilename) file, err := os.OpenFile(logfilename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0640) if err != nil { log.Fatal(err) } defer file.Close() log.SetFormatter(&log.TextFormatter{ DisableColors: true, FullTimestamp: true, }) log.SetOutput(file) } logjson := viper.GetBool("logging.json") if logjson { log.SetFormatter(&log.JSONFormatter{}) } verbose := viper.GetBool("verbose") if verbose { log.Info("verbose log output enabled") log.SetLevel(log.DebugLevel) } dummyMode := viper.GetBool("dummy") // configure metrics enableMetrics := viper.GetBool("metrics.enable") if err != nil { log.Fatal(err) } if enableMetrics { if dummyMode { statssubmitter, err = util.MakeDummySubmitter() if err != nil { log.Fatal(err) } } else { metricsSubmissionURL := viper.GetString("metrics.submission-url") metricsSubmissionExchange := viper.GetString("metrics.submission-exchange") statssubmitter, err = util.MakeAMQPSubmitterWithReconnector(metricsSubmissionURL, metricsSubmissionExchange, verbose, func(amqpURI string) (wabbit.Conn, error) { conn, err := amqp.Dial(amqpURI) if err != nil { return nil, err } return conn, err }) if err != nil { log.Fatal(err) } } // create InfluxDB line protocol encoder/submitter pse = util.MakePerformanceStatsEncoder(statssubmitter, 10*time.Second, dummyMode) } // create dispatcher dispatcher = processing.MakeHandlerDispatcher(eventChan) if pse != nil { dispatcher.SubmitStats(pse) } dispatcher.Run() defer func() { c := make(chan bool) dispatcher.Stop(c) <-c }() // create event type counter if enableMetrics { evp, err := processing.MakeEventProfiler(10*time.Second, statssubmitter) if err != nil { log.Fatal(err) } dispatcher.RegisterHandler(evp) evp.Run() defer func() { c := make(chan bool) evp.Stop(c) <-c }() flp, err := processing.MakeFlowProfiler(30*time.Second, statssubmitter) if err != nil { log.Fatal(err) } dispatcher.RegisterHandler(flp) flp.Run() defer func() { c := make(chan bool) flp.Stop(c) <-c }() } // Get config from viper var multiForwardConf processing.MultiForwardConfiguration err = viper.Unmarshal(&multiForwardConf) if err != nil { log.Fatal(err) } // Keep supporting legacy config ("output" and "forward" sections) if len(multiForwardConf.Outputs) == 0 { log.Info("no multi-forwarder configuration, found, using legacy config") outSocketPath := viper.GetString("output.socket") if len(outSocketPath) > 0 { multiForwardConf.Outputs = make(map[string]processing.MultiForwardOutput) multiForwardConf.Outputs["default"] = processing.MultiForwardOutput{ Socket: viper.GetString("output.socket"), All: viper.GetBool("forward.all"), Types: viper.GetStringSlice("forward.types"), } } } else { log.Info("found multi-forwarder configuration, ignoring legacy config") } if len(multiForwardConf.Outputs) > 0 { forward = true } if pse != nil { multiForwardConf.SubmitStats(pse) } multiFwdChan := make(chan types.Entry) reconnectTimes := viper.GetInt("reconnect-retries") multiForwardConf.Run(multiFwdChan, reconnectTimes) // Optional profiling profileFile := viper.GetString("profile") if profileFile != "" { var f io.Writer f, err = os.Create(profileFile) if err != nil { log.Fatal(err) } pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } // Set up database writing components DBenabled := viper.GetBool("database.enable") chunkSize := viper.GetInt("chunksize") if DBenabled { dbUseMongo := viper.GetBool("database.mongo") dbHost := viper.GetString("database.host") dbDatabase := viper.GetString("database.database") dbUser := viper.GetString("database.user") dbPassword := viper.GetString("database.password") maxTableSize := viper.GetInt64("database.maxtablesize") if dbUseMongo { s = db.MakeMongoSlurper(dbHost, dbDatabase, dbUser, dbPassword, int(chunkSize), int64(maxTableSize)) } else { rotationInterval := viper.GetDuration("database.rotate") s = db.MakePostgresSlurper(dbHost, dbDatabase, dbUser, dbPassword, rotationInterval, int64(maxTableSize), int(chunkSize)) } } else { if verbose { log.Println("database not in use") } s = &db.DummySlurper{} } s.Run(eventChan) var forwardHandler processing.Handler // start forwarding if forward { forwardHandler = processing.MakeForwardHandler(multiFwdChan) fh := forwardHandler.(*processing.ForwardHandler) rdns := viper.GetBool("active.rdns") if rdns { expiryPeriod := viper.GetDuration("active.rdns-cache-expiry") fh.EnableRDNS(expiryPeriod) privateOnly := viper.GetBool("active.rdns-private-only") if privateOnly { fh.RDNSHandler.EnableOnlyPrivateIPRanges() } } addFields := viper.GetStringMapString("add-fields") fh.AddFields(addFields) } else { // in this case we use a void handler that does nothing forwardHandler = processing.MakeVoidHandler() } dispatcher.RegisterHandler(forwardHandler) // Bloom filter setup bloomFilePath := viper.GetString("bloom.file") bloomAlertPrefix := viper.GetString("bloom.alert-prefix") bloomCompressed := viper.GetBool("bloom.zipped") bloomBlacklist := viper.GetStringSlice("bloom.blacklist-iocs") var bloomHandler *processing.BloomHandler if bloomFilePath != "" { bloomHandler, err = processing.MakeBloomHandlerFromFile(bloomFilePath, bloomCompressed, eventChan, forwardHandler, bloomAlertPrefix, bloomBlacklist) if err != nil { log.Fatal(err) } dispatcher.RegisterHandler(bloomHandler) } ipFilePath := viper.GetString("ip.blacklist") ipAlertPrefix := viper.GetString("ip.alert-prefix") var ipHandler *processing.IPHandler if ipFilePath != "" { ipHandler, err = processing.MakeIPHandlerFromFile(ipFilePath, eventChan, forwardHandler, ipAlertPrefix) if err != nil { log.Fatal(err) } dispatcher.RegisterHandler(ipHandler) } // flow aggregation setup flushPeriod := viper.GetDuration("flushtime") log.Debugf("flushtime set to %v", flushPeriod) fa := processing.MakeFlowAggregator(flushPeriod, eventChan) if pse != nil { fa.SubmitStats(pse) } dispatcher.RegisterHandler(fa) fa.Run() defer func() { c := make(chan bool) fa.Stop(c) <-c }() // DNS aggregation setup da := processing.MakeDNSAggregator(flushPeriod, eventChan) if pse != nil { da.SubmitStats(pse) } dispatcher.RegisterHandler(da) da.Run() defer func() { c := make(chan bool) da.Stop(c) <-c }() // context collector setup enableContext := viper.GetBool("context.enable") if enableContext { var csubmitter util.StatsSubmitter if dummyMode { csubmitter, err = util.MakeDummySubmitter() if err != nil { log.Fatal(err) } } else { cSubmissionURL := viper.GetString("context.submission-url") cSubmissionExchange := viper.GetString("context.submission-exchange") csubmitter, err = util.MakeAMQPSubmitter(cSubmissionURL, cSubmissionExchange, verbose) if err != nil { log.Fatal(err) } csubmitter.UseCompression() defer csubmitter.Finish() } cshp := processing.ContextShipperAMQP{} shipChan, err := cshp.Start(csubmitter) if err != nil { log.Fatal(err) } processing.GlobalContextCollector = processing.MakeContextCollector( func(entries processing.Context, logger *log.Entry) error { shipChan <- entries return nil }, viper.GetDuration("context.cache-timeout"), ) dispatcher.RegisterHandler(processing.GlobalContextCollector) if pse != nil { processing.GlobalContextCollector.SubmitStats(pse) } processing.GlobalContextCollector.Run() defer func() { c := make(chan bool) processing.GlobalContextCollector.Stop(c) <-c }() } // passive DNS setup enablePDNS := viper.GetBool("pdns.enable") if enablePDNS { var pdcsubmitter util.StatsSubmitter if dummyMode { pdcsubmitter, err = util.MakeDummySubmitter() if err != nil { log.Fatal(err) } } else { pdnsSubmissionURL := viper.GetString("pdns.submission-url") pdnsSubmissionExchange := viper.GetString("pdns.submission-exchange") pdcsubmitter, err = util.MakeAMQPSubmitter(pdnsSubmissionURL, pdnsSubmissionExchange, verbose) if err != nil { log.Fatal(err) } pdcsubmitter.UseCompression() defer pdcsubmitter.Finish() } pdc, err := processing.MakePDNSCollector(flushPeriod, pdcsubmitter) if err != nil { log.Fatal(err) } testdomain := viper.GetString("pdns.test-domain") if testdomain != "" { pdc.EnableTestdataDomain(testdomain) } dispatcher.RegisterHandler(pdc) pdc.Run() defer func() { c := make(chan bool) pdc.Stop(c) <-c }() } else { log.Info("passive DNS collection disabled") } noCompressMsg := viper.GetBool("flowreport.nocompress") // Aggregate stats reporting setup unicornSleep := viper.GetDuration("flowreport.interval") if unicornSleep > 0 { var submitter util.StatsSubmitter if dummyMode { submitter, err = util.MakeDummySubmitter() if err != nil { log.Fatal(err) } } else { unicornSubmissionURL := viper.GetString("flowreport.submission-url") unicornSubmissionExchange := viper.GetString("flowreport.submission-exchange") submitter, err = util.MakeAMQPSubmitter(unicornSubmissionURL, unicornSubmissionExchange, verbose) if err != nil { log.Fatal(err) } defer submitter.Finish() } if !noCompressMsg { submitter.UseCompression() log.WithFields(log.Fields{ "domain": "aggregate", "state": "enabled", }).Info("compression of flow stats") } else { log.WithFields(log.Fields{ "domain": "aggregate", "state": "disabled", }).Info("compression of flow stats") } allFlows := viper.GetBool("flowreport.all") ua := processing.MakeUnicornAggregator(submitter, unicornSleep, dummyMode, allFlows) testSrcIP := viper.GetString("flowreport.testdata-srcip") testDestIP := viper.GetString("flowreport.testdata-destip") testDestPort := viper.GetInt64("flowreport.testdata-destport") if testSrcIP != "" && testDestIP != "" { ua.EnableTestFlow(testSrcIP, testDestIP, testDestPort) } dispatcher.RegisterHandler(ua) ua.Run() defer func() { c := make(chan bool) ua.Stop(c) <-c }() } else { log.WithFields(log.Fields{ "domain": "aggregate", }).Info("flow stats reporting disabled") } // Flow extraction extractFlows := viper.GetBool("flowextract.enable") if extractFlows { var submitter util.StatsSubmitter if dummyMode { submitter, err = util.MakeDummySubmitter() if err != nil { log.Fatal(err) } } else { flowSubmissionURL := viper.GetString("flowextract.submission-url") flowSubmissionExchange := viper.GetString("flowextract.submission-exchange") submitter, err = util.MakeAMQPSubmitter(flowSubmissionURL, flowSubmissionExchange, verbose) if err != nil { log.Fatal(err) } defer submitter.Finish() } if noCompressMsg { submitter.UseCompression() log.WithFields(log.Fields{ "domain": "flow-extraction", "state": "enabled", }).Info("compression of flows") } else { log.WithFields(log.Fields{ "domain": "flow-extraction", "state": "disabled", }).Info("no compression of flows") } flushCount := viper.GetInt("flushcount") flowBloomFilePath := viper.GetString("flowextract-bloom-selector") ua, err := processing.MakeFlowExtractor(flushPeriod, int(flushCount), flowBloomFilePath, submitter) if err != nil { log.Fatal(err) } dispatcher.RegisterHandler(ua) ua.Run() defer func() { c := make(chan bool) ua.Stop(c) <-c }() } else { log.WithFields(log.Fields{ "domain": "flow-extraction", }).Info("Flow extraction disabled") } // Heartbeat injector enableHeartbeat := viper.GetBool("heartbeat.enable") heartbeatTimes := viper.GetStringSlice("heartbeat.times") heartbeatAlertTimes := viper.GetStringSlice("heartbeat.alert-times") if enableHeartbeat { hi, err := processing.MakeHeartbeatInjector(forwardHandler, heartbeatTimes, heartbeatAlertTimes) if err != nil { log.Fatal(err) } hi.Run() defer hi.Stop() } // create mgmt server state := &mgmt.State{ BloomHandler: bloomHandler, } mgmtCfg := mgmt.EndpointConfigFromViper() msrv, err := mgmt.NewMgmtServer(context.TODO(), mgmtCfg, state) if err != nil { log.Fatal(err) } go func() error { if err := msrv.ListenAndServe(); err != nil { log.WithFields(log.Fields{ "domain": "mgmt", }).WithError(err).Error("starting gRPC server failed") os.Exit(1) } return nil }() c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGINT, syscall.SIGUSR1) go func() { for sig := range c { if sig == syscall.SIGTERM || sig == syscall.SIGINT { pprof.StopCPUProfile() if submitter != nil { submitter.Finish() } if s != nil { s.Finish() } log.WithFields(log.Fields{ "domain": "main", }).Println("received SIGTERM, terminating") inputSocket := viper.GetString("input.socket") _, myerr := os.Stat(inputSocket) if myerr == nil { os.Remove(inputSocket) } msrv.Stop() os.Exit(1) } else if sig == syscall.SIGUSR1 { if bloomHandler != nil { err := bloomHandler.Reload() if err != nil { log.Warnf("reloading of Bloom filter failed: %s", err.Error()) } else { log.Info("reloading of Bloom complete") } } } } }() // create input inputBufferLen := viper.GetUint("input.buffer-length") inputChan := make(chan types.Entry, inputBufferLen) var sinput input.Input inputRedis := viper.GetString("input.redis.server") noUseRedisPipeline := viper.GetBool("input.redis.nopipe") if len(inputRedis) > 0 { in, err := input.MakeRedisInput(inputRedis, inputChan, int(chunkSize)) if err != nil { log.Fatal(err) } in.UsePipelining = !noUseRedisPipeline in.SubmitStats(pse) sinput = in } else { inputSocket := viper.GetString("input.socket") bufDrop := viper.GetBool("input.buffer-drop") in, err := input.MakeSocketInput(inputSocket, inputChan, bufDrop) if err != nil { log.Fatal(err) } in.SubmitStats(pse) sinput = in } log.WithFields(log.Fields{ "input": sinput.GetName(), }).Info("selected input driver") sinput.SetVerbose(verbose) sinput.Run() defer func() { c := make(chan bool) sinput.Stop(c) <-c }() for v := range inputChan { dispatcher.Dispatch(&v) } } var runCmd = &cobra.Command{ Use: "run", Short: "start FEVER service", Long: `The 'run' command starts the FEVER service, consuming events from the input and executing all processing components.`, Run: mainfunc, } func init() { rootCmd.AddCommand(runCmd) // Input options runCmd.PersistentFlags().StringP("in-socket", "i", "/tmp/suri.sock", "filename of input socket (accepts EVE JSON)") viper.BindPFlag("input.socket", runCmd.PersistentFlags().Lookup("in-socket")) runCmd.PersistentFlags().StringP("in-redis", "r", "", "Redis input server (assumes \"suricata\" list key, no pwd)") viper.BindPFlag("input.redis.server", runCmd.PersistentFlags().Lookup("in-redis")) runCmd.PersistentFlags().BoolP("in-redis-nopipe", "", false, "do not use Redis pipelining") viper.BindPFlag("input.redis.nopipe", runCmd.PersistentFlags().Lookup("in-redis-nopipe")) runCmd.PersistentFlags().UintP("in-buffer-length", "", 500000, "input buffer length (counted in EVE objects)") viper.BindPFlag("input.buffer-length", runCmd.PersistentFlags().Lookup("in-buffer-length")) runCmd.PersistentFlags().BoolP("in-buffer-drop", "", true, "drop incoming events on FEVER side instead of blocking the input socket") viper.BindPFlag("input.buffer-drop", runCmd.PersistentFlags().Lookup("in-buffer-drop")) // Output options runCmd.PersistentFlags().StringP("out-socket", "o", "/tmp/suri-forward.sock", "path to output socket (to forwarder), empty string disables forwarding") viper.BindPFlag("output.socket", runCmd.PersistentFlags().Lookup("out-socket")) // Forwarding options runCmd.PersistentFlags().StringSliceP("fwd-event-types", "t", []string{"alert", "stats"}, "event types to forward to socket") viper.BindPFlag("forward.types", runCmd.PersistentFlags().Lookup("fwd-event-types")) runCmd.PersistentFlags().BoolP("fwd-all-types", "T", false, "forward all event types") viper.BindPFlag("forward.all", runCmd.PersistentFlags().Lookup("fwd-all-types")) // Misc options runCmd.PersistentFlags().StringP("profile", "", "", "enable runtime profiling to given file") viper.BindPFlag("profile", runCmd.PersistentFlags().Lookup("profile")) runCmd.PersistentFlags().BoolP("verbose", "v", false, "enable verbose logging (debug log level)") viper.BindPFlag("verbose", runCmd.PersistentFlags().Lookup("verbose")) runCmd.PersistentFlags().UintP("chunksize", "c", 50000, "chunk size for batched event handling (e.g. inserts)") viper.BindPFlag("chunksize", runCmd.PersistentFlags().Lookup("chunksize")) runCmd.PersistentFlags().BoolP("dummy", "", false, "log locally instead of sending home") viper.BindPFlag("dummy", runCmd.PersistentFlags().Lookup("dummy")) runCmd.PersistentFlags().UintP("reconnect-retries", "", 0, "number of retries connecting to socket or sink, 0 = no retry limit") viper.BindPFlag("reconnect-retries", runCmd.PersistentFlags().Lookup("reconnect-retries")) runCmd.PersistentFlags().DurationP("flushtime", "f", 1*time.Minute, "time interval for event aggregation") viper.BindPFlag("flushtime", runCmd.PersistentFlags().Lookup("flushtime")) runCmd.PersistentFlags().UintP("flushcount", "", 100000, "maximum number of events in one batch (e.g. for flow extraction)") viper.BindPFlag("flushcount", runCmd.PersistentFlags().Lookup("flushcount")) runCmd.PersistentFlags().StringP("toolname", "", "fever", "set toolname") viper.BindPFlag("toolname", runCmd.PersistentFlags().Lookup("toolname")) // Database options runCmd.PersistentFlags().BoolP("db-enable", "", false, "write events to database") viper.BindPFlag("database.enable", runCmd.PersistentFlags().Lookup("db-enable")) runCmd.PersistentFlags().StringP("db-host", "s", "localhost:5432", "database host") viper.BindPFlag("database.host", runCmd.PersistentFlags().Lookup("db-host")) runCmd.PersistentFlags().StringP("db-user", "u", "sensor", "database user") viper.BindPFlag("database.user", runCmd.PersistentFlags().Lookup("db-user")) runCmd.PersistentFlags().StringP("db-database", "d", "events", "database DB") viper.BindPFlag("database.database", runCmd.PersistentFlags().Lookup("db-database")) runCmd.PersistentFlags().StringP("db-password", "p", "sensor", "database password") viper.BindPFlag("database.password", runCmd.PersistentFlags().Lookup("db-password")) runCmd.PersistentFlags().BoolP("db-mongo", "m", false, "use MongoDB") viper.BindPFlag("database.mongo", runCmd.PersistentFlags().Lookup("db-mongo")) runCmd.PersistentFlags().DurationP("db-rotate", "", 1*time.Hour, "time interval for database table rotations") viper.BindPFlag("database.rotate", runCmd.PersistentFlags().Lookup("db-rotate")) runCmd.PersistentFlags().Uint64P("db-maxtablesize", "", 500, "Maximum allowed cumulative table size in GB") viper.BindPFlag("database.maxtablesize", runCmd.PersistentFlags().Lookup("db-maxtablesize")) // Flow report options runCmd.PersistentFlags().BoolP("flowreport-nocompress", "", false, "send uncompressed flow reports (default is gzip)") viper.BindPFlag("flowreport.nocompress", runCmd.PersistentFlags().Lookup("flowreport-nocompress")) runCmd.PersistentFlags().StringP("flowreport-submission-url", "", "amqp://guest:guest@localhost:5672/", "URL to which flow reports will be submitted") viper.BindPFlag("flowreport.submission-url", runCmd.PersistentFlags().Lookup("flowreport-submission-url")) runCmd.PersistentFlags().StringP("flowreport-submission-exchange", "", "aggregations", "Exchange to which flow reports will be submitted") viper.BindPFlag("flowreport.submission-exchange", runCmd.PersistentFlags().Lookup("flowreport-submission-exchange")) runCmd.PersistentFlags().DurationP("flowreport-interval", "n", 0, "time interval for report submissions") viper.BindPFlag("flowreport.interval", runCmd.PersistentFlags().Lookup("flowreport-interval")) // Metrics submission options runCmd.PersistentFlags().BoolP("metrics-enable", "", false, "submit performance metrics to central sink") viper.BindPFlag("metrics.enable", runCmd.PersistentFlags().Lookup("metrics-enable")) runCmd.PersistentFlags().StringP("metrics-submission-url", "", "amqp://guest:guest@localhost:5672/", "URL to which metrics will be submitted") viper.BindPFlag("metrics.submission-url", runCmd.PersistentFlags().Lookup("metrics-submission-url")) runCmd.PersistentFlags().StringP("metrics-submission-exchange", "", "metrics", "Exchange to which metrics will be submitted") viper.BindPFlag("metrics.submission-exchange", runCmd.PersistentFlags().Lookup("metrics-submission-exchange")) // Passive DNS options runCmd.PersistentFlags().BoolP("pdns-enable", "", false, "collect and forward aggregated passive DNS data") viper.BindPFlag("pdns.enable", runCmd.PersistentFlags().Lookup("pdns-enable")) runCmd.PersistentFlags().StringP("pdns-submission-url", "", "amqp://guest:guest@localhost:5672/", "URL to which passive DNS events will be submitted") viper.BindPFlag("pdns.submission-url", runCmd.PersistentFlags().Lookup("pdns-submission-url")) runCmd.PersistentFlags().StringP("pdns-submission-exchange", "", "pdns", "Exchange to which passive DNS events will be submitted") viper.BindPFlag("pdns.submission-exchange", runCmd.PersistentFlags().Lookup("pdns-submission-exchange")) // Context collection options runCmd.PersistentFlags().BoolP("context-enable", "", false, "collect and forward flow context for alerted flows") viper.BindPFlag("context.enable", runCmd.PersistentFlags().Lookup("context-enable")) runCmd.PersistentFlags().StringP("context-submission-url", "", "amqp://guest:guest@localhost:5672/", "URL to which flow context will be submitted") viper.BindPFlag("context.submission-url", runCmd.PersistentFlags().Lookup("context-submission-url")) runCmd.PersistentFlags().StringP("context-submission-exchange", "", "context", "Exchange to which flow context events will be submitted") viper.BindPFlag("context.submission-exchange", runCmd.PersistentFlags().Lookup("context-submission-exchange")) runCmd.PersistentFlags().DurationP("context-cache-timeout", "", 60*time.Minute, "time for flow metadata to be kept for uncompleted flows") viper.BindPFlag("context.cache-timeout", runCmd.PersistentFlags().Lookup("context-cache-timeout")) // Heartbeat options runCmd.PersistentFlags().BoolP("heartbeat-enable", "", false, "Forward HTTP heartbeat event") viper.BindPFlag("heartbeat.enable", runCmd.PersistentFlags().Lookup("heartbeat-enable")) runCmd.PersistentFlags().StringSliceP("heartbeat-times", "", []string{}, "Times of day to send heartbeat (list of 24h HH:MM strings)") viper.BindPFlag("heartbeat.times", runCmd.PersistentFlags().Lookup("heartbeat-times")) // Bloom filter alerting options runCmd.PersistentFlags().StringP("bloom-file", "b", "", "Bloom filter for external indicator screening") viper.BindPFlag("bloom.file", runCmd.PersistentFlags().Lookup("bloom-file")) runCmd.PersistentFlags().BoolP("bloom-zipped", "z", false, "use gzipped Bloom filter file") viper.BindPFlag("bloom.zipped", runCmd.PersistentFlags().Lookup("bloom-zipped")) runCmd.PersistentFlags().StringP("bloom-alert-prefix", "", "BLF", "String prefix for Bloom filter alerts") viper.BindPFlag("bloom.alert-prefix", runCmd.PersistentFlags().Lookup("bloom-alert-prefix")) runCmd.PersistentFlags().StringSliceP("bloom-blacklist-iocs", "", []string{"/", "/index.htm", "/index.html"}, "Blacklisted strings in Bloom filter (will cause filter to be rejected)") viper.BindPFlag("bloom.blacklist-iocs", runCmd.PersistentFlags().Lookup("bloom-blacklist-iocs")) // IP blacklist alerting options runCmd.PersistentFlags().StringP("ip-blacklist", "", "", "List with IP ranges to alert on") viper.BindPFlag("ip.blacklist", runCmd.PersistentFlags().Lookup("ip-blacklist")) runCmd.PersistentFlags().StringP("ip-alert-prefix", "", "IP-BLACKLIST", "String prefix for IP blacklist alerts") viper.BindPFlag("ip.alert-prefix", runCmd.PersistentFlags().Lookup("ip-alert-prefix")) // Flow extraction options runCmd.PersistentFlags().BoolP("flowextract-enable", "", false, "extract and forward flow metadata") viper.BindPFlag("flowextract.enable", runCmd.PersistentFlags().Lookup("flowextract-enable")) runCmd.PersistentFlags().StringP("flowextract-bloom-selector", "", "", "IP address Bloom filter to select flows to extract") viper.BindPFlag("flowextract.bloom-selector", runCmd.PersistentFlags().Lookup("flowextract-bloom-selector")) runCmd.PersistentFlags().StringP("flowextract-submission-url", "", "amqp://guest:guest@localhost:5672/", "URL to which raw flow events will be submitted") viper.BindPFlag("flowextract.submission-url", runCmd.PersistentFlags().Lookup("flowextract-submission-url")) runCmd.PersistentFlags().StringP("flowextract-submission-exchange", "", "flows", "Exchange to which raw flow events will be submitted") viper.BindPFlag("flowextract.submission-exchange", runCmd.PersistentFlags().Lookup("flowextract-submission-exchange")) // Active enrichment options runCmd.PersistentFlags().BoolP("active-rdns", "", false, "enable active rDNS enrichment for src/dst IPs") viper.BindPFlag("active.rdns", runCmd.PersistentFlags().Lookup("active-rdns")) runCmd.PersistentFlags().DurationP("active-rdns-cache-expiry", "", 2*time.Minute, "cache expiry interval for rDNS lookups") viper.BindPFlag("active.rdns-cache-expiry", runCmd.PersistentFlags().Lookup("active-rdns-cache-expiry")) runCmd.PersistentFlags().BoolP("active-rdns-private-only", "", false, "only do active rDNS enrichment for RFC1918 IPs") viper.BindPFlag("active.rdns-private-only", runCmd.PersistentFlags().Lookup("active-rdns-private-only")) // Logging options runCmd.PersistentFlags().StringP("logfile", "", "", "Path to log file") viper.BindPFlag("logging.file", runCmd.PersistentFlags().Lookup("logfile")) runCmd.PersistentFlags().BoolP("logjson", "", false, "Output logs in JSON format") viper.BindPFlag("logging.json", runCmd.PersistentFlags().Lookup("logjson")) } fever-1.3.7/cmd/fever/cmds/testdata/000077500000000000000000000000001477766221000172565ustar00rootroot00000000000000fever-1.3.7/cmd/fever/cmds/testdata/alertify_input.json000066400000000000000000000042151477766221000232110ustar00rootroot00000000000000{"timestamp":"2016-01-02T19:08:01.310448+0000","flow_id":386974943918954,"pcap_cnt":7,"event_type":"http","src_ip":"1.1.1.1","src_port":10305,"dest_ip":"8.8.8.8","dest_port":80,"proto":"TCP","tx_id":0,"http":{"hostname":"evader.example.com","url":"/compressed/eicar.txt/ce%3Agzip,gzip;gzip;gzip","http_content_type":"application/octet-stream","http_method":"GET","protocol":"HTTP/1.1","status":200,"length":106}} {"timestamp":"2013-07-04T19:47:51.592983+0000","flow_id":694559854542603,"pcap_cnt":4,"event_type":"dns","src_ip":"192.168.42.150","src_port":55597,"dest_ip":"192.168.42.129","dest_port":53,"proto":"UDP","dns":{"version":2,"type":"answer","id":25783,"flags":"8180","qr":true,"rd":true,"ra":true,"rrname":"static.programme-tv.net","rrtype":"AAAA","rcode":"NOERROR","answers":[{"rrname":"static.programme-tv.net","rrtype":"CNAME","ttl":630,"rdata":"programme-tv.net.edgesuite.net"},{"rrname":"programme-tv.net.edgesuite.net","rrtype":"CNAME","ttl":20432,"rdata":"a1859.g.akamai.net"}],"grouped":{"CNAME":["programme-tv.net.edgesuite.net","a1859.g.akamai.net"]},"authorities":[{"rrname":"g.akamai.net","rrtype":"SOA","ttl":1000}]}} {"timestamp":"2019-05-15T08:11:18.955582+0000","flow_id":1416337568450228,"pcap_cnt":11,"event_type":"tls","src_ip":"2a03:b0c0:0002:00d0:0000:0000:0bd3:4001","src_port":48106,"dest_ip":"2606:2800:0220:0001:0248:1893:25c8:1946","dest_port":443,"proto":"TCP","tls":{"subject":"C=US, ST=California, L=Los Angeles, O=Internet Corporation for Assigned Names and Numbers, OU=Technology, CN=www.example.org","issuerdn":"C=US, O=DigiCert Inc, CN=DigiCert SHA2 Secure Server CA","serial":"0F:D0:78:DD:48:F1:A2:BD:4D:0F:2B:A9:6B:60:38:FE","fingerprint":"7b:b6:98:38:69:70:36:3d:29:19:cc:57:72:84:69:84:ff:d4:a8:89","sni":"example.com","version":"TLS 1.2","notbefore":"2018-11-28T00:00:00","notafter":"2020-12-02T12:00:00","ja3":{"hash":"1fe4c7a3544eb27afec2adfb3a3dbf60","string":"771,49196-49200-159-52393-52392-52394-49195-49199-158-49188-49192-107-49187-49191-103-49162-49172-57-49161-49171-51-157-156-61-60-53-47-255,0-11-10-13172-16-22-23-13,29-23-25-24,0-1-2"},"ja3s":{"hash":"5d79edf64e03689ff559a54e9d9487bc","string":"771,49199,65281-0-11-16-23"}}}fever-1.3.7/cmd/fever/cmds/version.go000066400000000000000000000005261477766221000174640ustar00rootroot00000000000000package cmd import ( "fmt" "github.com/spf13/cobra" ) const ( version = "1.3.6" ) // versionCmd represents the version command var versionCmd = &cobra.Command{ Use: "version", Short: "Show FEVER version", Run: func(cmd *cobra.Command, args []string) { fmt.Println(version) }, } func init() { rootCmd.AddCommand(versionCmd) } fever-1.3.7/cmd/fever/main.go000066400000000000000000000002261477766221000157720ustar00rootroot00000000000000package main // DCSO FEVER // Copyright (c) 2017, 2018, DCSO GmbH import cmd "github.com/DCSO/fever/cmd/fever/cmds" func main() { cmd.Execute() } fever-1.3.7/db/000077500000000000000000000000001477766221000132325ustar00rootroot00000000000000fever-1.3.7/db/slurper.go000066400000000000000000000006571477766221000152650ustar00rootroot00000000000000package db // DCSO FEVER // Copyright (c) 2017, DCSO GmbH import ( "github.com/DCSO/fever/types" ) // Slurper is an interface for a worker that can be started (Run()) with a given // channel delivering Entries, storing them in an associated data store. // Finish() can be used to finalize any state. // TODO implement proper start/stop (atm 'hard' stop by exit()ing) type Slurper interface { Run(chan types.Entry) Finish() } fever-1.3.7/db/slurper_dummy.go000066400000000000000000000006711477766221000164740ustar00rootroot00000000000000package db // DCSO FEVER // Copyright (c) 2017, DCSO GmbH import ( "github.com/DCSO/fever/types" ) // DummySlurper is a slurper that just consumes entries with no action. type DummySlurper struct{} // Run starts a DummySlurper. func (s *DummySlurper) Run(eventchan chan types.Entry) { go func() { for range eventchan { } }() } // Finish is a null operation in the DummySlurper implementation. func (s *DummySlurper) Finish() { } fever-1.3.7/db/slurper_ejdb.go000066400000000000000000000017261477766221000162470ustar00rootroot00000000000000//go:build ignore // +build ignore package db import ( "github.com/mkilling/goejdb" log "github.com/sirupsen/logrus" ) // EJDBSlurper is a Slurper that stores events in an EJDB database. type EJDBSlurper struct { db *goejdb.Ejdb } // Run starts an EJDBSlurper. func (s *EJDBSlurper) Run(eventchan chan Entry) { var err error i := 0 s.db, err = goejdb.Open("eventsdb", goejdb.JBOWRITER|goejdb.JBOCREAT) if err != nil { log.Warn(err) } coll, _ := s.db.CreateColl("events", nil) coll.SetIndex("timestamp", goejdb.JBIDXSTR) coll.SetIndex("event_type", goejdb.JBIDXSTR) coll.SetIndex("dns.rrname", goejdb.JBIDXSTR) coll.SetIndex("alert.payload_printable", goejdb.JBIDXSTR) go func() { coll.BeginTransaction() for d := range eventchan { if i%5000 == 0 { coll.CommitTransaction() coll.BeginTransaction() } coll.SaveJson(d.JSONLine) i++ } }() } // Finish closes the associated EJDB database.. func (s *EJDBSlurper) Finish() { s.db.Close() } fever-1.3.7/db/slurper_mongodb.go000066400000000000000000000153711477766221000167710ustar00rootroot00000000000000package db import ( "encoding/json" "fmt" "github.com/DCSO/fever/types" log "github.com/sirupsen/logrus" "gopkg.in/mgo.v2" ) // TYPES are event types/collections supported by us var TYPES = []string{ "alert", "dns", "fileinfo", "flow", "http", "smtp", "ssh", "stats", "tls", "misc", } // MAXCOLLSIZEFRACTIONS are the proportions of the general space cap to be // assigned to the collections for each event type -- used to determine // limits for capped collections var MAXCOLLSIZEFRACTIONS = map[string]float64{ "dns": 0.25, "http": 0.2, "flow": 0.25, "smtp": 0.05, "ssh": 0.05, "alert": 0.05, "tls": 0.05, "stats": 0.02, "misc": 0.03, "fileinfo": 0.05, } // INDEXES assigns index parameters to each collection, denoted by the // corresponding event type var INDEXES = map[string]([]mgo.Index){ "dns": []mgo.Index{ //mgo.Index{ // Key: []string{"src_ip", // "dest_ip"}, // Background: true, //}, mgo.Index{ Key: []string{"dns.rrname"}, Background: true, }, mgo.Index{ Key: []string{"timestamp"}, Background: true, }, }, "fileinfo": []mgo.Index{ mgo.Index{ Key: []string{"src_ip", "dest_ip"}, Background: true, }, mgo.Index{ Key: []string{"fileinfo.filename", "fileinfo.md5"}, Background: true, }, mgo.Index{ Key: []string{"timestamp"}, Background: true, }, }, "flow": []mgo.Index{ mgo.Index{ Key: []string{"src_ip", "dest_ip"}, Background: true, }, mgo.Index{ Key: []string{"timestamp"}, Background: true, }, }, "http": []mgo.Index{ mgo.Index{ Key: []string{"src_ip", "dest_ip"}, Background: true, }, mgo.Index{ Key: []string{"http.hostname", "http.http_user_agent"}, Background: true, }, mgo.Index{ Key: []string{"$text:http.url"}, Background: true, }, mgo.Index{ Key: []string{"timestamp"}, Background: true, }, }, "alert": []mgo.Index{ mgo.Index{ Key: []string{"src_ip", "dest_ip"}, Background: true, }, mgo.Index{ Key: []string{"$text:alert.payload_printable"}, Background: true, }, mgo.Index{ Key: []string{"timestamp"}, Background: true, }, }, "smtp": []mgo.Index{ mgo.Index{ Key: []string{"src_ip", "dest_ip"}, Background: true, }, mgo.Index{ Key: []string{"smtp.helo", "smtp.mail_from", "smtp.rcpt_to"}, Background: true, }, mgo.Index{ Key: []string{"email.attachment"}, Background: true, }, mgo.Index{ Key: []string{"timestamp"}, Background: true, }, }, "tls": []mgo.Index{ mgo.Index{ Key: []string{"src_ip", "dest_ip"}, Background: true, }, mgo.Index{ Key: []string{"tls.subject", "tls.issuerdn", "tls.fingerprint"}, Background: true, }, mgo.Index{ Key: []string{"timestamp"}, Background: true, }, }, "misc": []mgo.Index{ mgo.Index{ Key: []string{"src_ip", "dest_ip"}, Background: true, }, mgo.Index{ Key: []string{"timestamp"}, Background: true, }, }, } // MongoSlurper is a Slurper that stores events in an MongoDB database. type MongoSlurper struct { User string Password string Host string Database string TypeDispatch map[string](chan types.Entry) ChunkSize int MaxSize int64 Logger *log.Entry } func (s *MongoSlurper) eventTypeWorker(eventchan chan types.Entry, eventType string) error { var err error cnt := 0 url := fmt.Sprintf("mongodb://%s:%s@%s/%s", s.User, s.Password, s.Host, s.Database) s.Logger.WithFields(log.Fields{"type": eventType}).Info("worker connecting") sess, err := mgo.Dial(url) if err != nil { s.Logger.Fatal(err) return err } s.Logger.WithFields(log.Fields{"type": eventType}).Info("connection established") db := sess.DB(s.Database) // create capped collection coll := db.C(eventType) sizeFrac := MAXCOLLSIZEFRACTIONS[eventType] if sizeFrac == 0 { s.Logger.Warn("Invalid type", eventType, "no max size available for collection") sizeFrac = 0.01 } s.Logger.WithFields(log.Fields{"type": eventType, "sizeFrac": sizeFrac}).Info("determining size fraction") sizeBytes := int(float64(s.MaxSize) * sizeFrac) s.Logger.WithFields(log.Fields{"type": eventType, "maxSize": sizeBytes}).Info("determining size cap") err = coll.Create(&mgo.CollectionInfo{ Capped: true, DisableIdIndex: true, MaxBytes: sizeBytes, }) if err != nil { s.Logger.WithFields(log.Fields{"type": eventType}).Info(err) } // check indexes on collection, create if needed idxList := INDEXES[eventType] if idxList != nil { s.Logger.WithFields(log.Fields{"type": eventType}).Info("checking indexes") for _, idx := range idxList { s.Logger.WithFields(log.Fields{"type": eventType, "idx": idx.Key}).Info("index check") coll.EnsureIndex(idx) } s.Logger.WithFields(log.Fields{"type": eventType}).Info("index check done") } b := coll.Bulk() b.Unordered() for event := range eventchan { var ev map[string]interface{} err := json.Unmarshal([]byte(event.JSONLine), &ev) if err != nil { s.Logger.Warn(err) } else { b.Insert(&ev) cnt++ if cnt%s.ChunkSize == 0 { s.Logger.WithFields(log.Fields{"type": eventType}).Debugf("flushing bulk") _, err = b.Run() if err != nil { s.Logger.Warn(err) } else { s.Logger.WithFields(log.Fields{"type": eventType}).Debugf("flushing complete") } b = coll.Bulk() b.Unordered() cnt = 0 } } } return nil } // MakeMongoSlurper creates a new MongoSlurper instance. func MakeMongoSlurper(host string, database string, user string, password string, chunkSize int, maxSize int64) *MongoSlurper { s := &MongoSlurper{ ChunkSize: chunkSize, Host: host, Database: database, User: user, Password: password, TypeDispatch: make(map[string](chan types.Entry)), MaxSize: maxSize * 1024 * 1024 * 1024, Logger: log.WithFields(log.Fields{"domain": "slurper", "slurper": "mongo"}), } for _, t := range TYPES { s.TypeDispatch[t] = make(chan types.Entry, 1000) } url := fmt.Sprintf("mongodb://%s:%s@%s/%s", s.User, s.Password, s.Host, s.Database) s.Logger.WithFields(log.Fields{"url": url}).Info("preparing for MongoDB connection") return s } // Run starts a MongoSlurper. func (s *MongoSlurper) Run(eventchan chan types.Entry) { // set up workers for each event type for k, v := range s.TypeDispatch { go s.eventTypeWorker(v, k) } // dispatch events to their corresponding worker go func() { for entry := range eventchan { targetchan := s.TypeDispatch[entry.EventType] if targetchan != nil { targetchan <- entry } else { s.TypeDispatch["misc"] <- entry } } }() } // Finish is a null operation in the MongoSlurper implementation. func (s *MongoSlurper) Finish() { } fever-1.3.7/db/slurper_postgres.go000066400000000000000000000125551477766221000172130ustar00rootroot00000000000000package db import ( "bytes" "fmt" "strings" "time" "github.com/DCSO/fever/types" log "github.com/sirupsen/logrus" pg "gopkg.in/pg.v5" ) var maxRetries = 20 // PostgresSlurper is a Slurper that stores events in an PostgreSQL database. type PostgresSlurper struct { DB *pg.DB LastRotatedTime time.Time IndexChan chan string CurrentTableName string RotationInterval time.Duration MaxTableSize int64 ChunkSize int Logger *log.Entry } // This is a fixed format for table names. func formatTableName(timestamp time.Time) string { return timestamp.Format("event-2006-01-02-1504") } // MakePostgresSlurper creates a new PostgresSlurper instance. func MakePostgresSlurper(host string, database string, user string, password string, rotationInterval time.Duration, maxTableSize int64, chunkSize int) *PostgresSlurper { var err error var i int var hasExt int db := pg.Connect(&pg.Options{ User: user, Password: password, Addr: host, Database: database, }) l := log.WithFields(log.Fields{ "domain": "slurper", "slurper": "postgres", }) l.WithFields(log.Fields{ "user": user, "host": host, "database": database, }).Info("connected to database") _, err = db.Query(pg.Scan(&hasExt), SQLCheckForTrigramExtension) for i = 0; err != nil && strings.Contains(err.Error(), "system is starting up"); i++ { if i > maxRetries { break } l.Warnf("problem checking for trigram extension: %s -- retrying %d/%d", err.Error(), i, maxRetries) _, err = db.Query(pg.Scan(&hasExt), SQLCheckForTrigramExtension) time.Sleep(10 * time.Second) } if err != nil { l.Fatalf("permanent error checking for trigram extension: %s", err.Error()) } if hasExt < 1 { l.Fatal("trigram extension ('pg_trgm') not loaded, please run "+ "'CREATE EXTENSION pg_trgm;'", err) } _, err = db.Exec(SQLTrigramFunction) if err != nil { l.Fatalf("error creating index preparation function: %s", err) } _, err = db.Exec(SQLQueryAllEvents) if err != nil { l.Fatalf("error creating global query function: %s", err) } s := &PostgresSlurper{ DB: db, RotationInterval: rotationInterval, MaxTableSize: maxTableSize * 1024 * 1024 * 1024, ChunkSize: chunkSize, Logger: l, } return s } type tableSize struct { Table string Size int64 } func (s *PostgresSlurper) expireOldTables() error { var tblSizes []tableSize _, err := s.DB.Query(&tblSizes, SQLGetTableSizes) if err != nil { s.Logger.Warn("error determining table sizes", err) return err } totalSize := int64(0) for _, v := range tblSizes { totalSize += v.Size if totalSize > s.MaxTableSize && s.CurrentTableName != v.Table { s.Logger.WithFields(log.Fields{ "table": v.Table, "size": v.Size, }).Info("table expired") _, err = s.DB.Exec(fmt.Sprintf(`DROP TABLE "%s";`, v.Table)) if err != nil { s.Logger.WithFields(log.Fields{ "table": v.Table, "size": v.Size, "error": err.Error(), }).Warn("error dropping table") return err } } } return nil } func (s *PostgresSlurper) indexFunc() { for tblToIndex := range s.IndexChan { s.Logger.WithFields(log.Fields{ "table": tblToIndex, }).Info("creating indexes") idxSQL := fmt.Sprintf(SQLIndex, tblToIndex, tblToIndex, tblToIndex, tblToIndex, tblToIndex) _, idxErr := s.DB.Exec(idxSQL) if idxErr != nil { s.Logger.WithFields(log.Fields{ "table": tblToIndex, "error": idxErr.Error(), }).Info("error creating index") } s.Logger.Info("expiring old tables") s.expireOldTables() } } func (s *PostgresSlurper) slurpPostgres(eventchan chan types.Entry) { cnt := 0 var copybuf bytes.Buffer for { event := <-eventchan copybuf.WriteString(event.Timestamp) copybuf.WriteString("\t") copybuf.WriteString(event.JSONLine) copybuf.WriteString("\n") if cnt > 0 && cnt%s.ChunkSize == 0 { if s.LastRotatedTime.IsZero() || (time.Since(s.LastRotatedTime) > s.RotationInterval) { newTableName := formatTableName(time.Now()) if s.LastRotatedTime.IsZero() { s.Logger.WithFields(log.Fields{ "table": newTableName, }).Info("initializing table") } else { s.Logger.WithFields(log.Fields{ "from": s.CurrentTableName, "to": newTableName, }).Info("rotating tables") } crSQL := fmt.Sprintf(SQLCreate, newTableName, newTableName, s.DB.Options().User) _, crErr := s.DB.Exec(crSQL) if crErr != nil { s.Logger.WithFields(log.Fields{ "table": newTableName, "error": crErr.Error(), }).Warn("error creating table") } if !s.LastRotatedTime.IsZero() { s.IndexChan <- s.CurrentTableName } s.CurrentTableName = newTableName s.LastRotatedTime = time.Now() } cnt = 0 r := strings.NewReader(copybuf.String()) _, err := s.DB.CopyFrom(r, fmt.Sprintf(SQLCopy, s.CurrentTableName)) if err != nil { s.Logger.Warn(err) } else { s.Logger.WithFields(log.Fields{ "chunksize": s.ChunkSize, "table": s.CurrentTableName, }).Debug("COPY complete") } copybuf.Reset() } cnt++ } } // Run starts a PostgresSlurper. func (s *PostgresSlurper) Run(eventchan chan types.Entry) { // start indexer thread s.IndexChan = make(chan string, 1000) go s.indexFunc() // run slurper thread go s.slurpPostgres(eventchan) } // Finish is a null operation in the PostgresSlurper implementation. func (s *PostgresSlurper) Finish() { } fever-1.3.7/db/sql.go000066400000000000000000000122421477766221000143610ustar00rootroot00000000000000package db // DCSO FEVER // Copyright (c) 2017, DCSO GmbH // SQLTrigramFunction is a plpgsql function to pull out indexable content from event JSON const SQLTrigramFunction = `CREATE OR REPLACE FUNCTION trigram_string(payload jsonb) RETURNS text AS $$ DECLARE buffer varchar := ''; BEGIN -- trying in typical order of frequency IF payload->>'event_type' = 'dns' THEN RETURN payload->'dns'->>'rdata'; END IF; IF payload->>'event_type' = 'http' THEN RETURN (payload->'http'->>'hostname') || '|' || (payload->'http'->>'url') || '|' || (payload->'http'->>'http_user_agent'); END IF; IF payload->>'event_type' = 'tls' THEN RETURN (payload->'tls'->>'subject') ||'|' || (payload->'tls'->>'issuerdn') || '|' || (payload->'tls'->>'fingerprint'); END IF; IF payload->>'event_type' = 'alert' THEN RETURN (payload->'alert'->>'payload_printable') || '|' || (payload->'alert'->>'payload'); END IF; IF payload->>'event_type' = 'smtp' THEN RETURN (payload->'smtp'->>'helo') || '|' || (payload->'smtp'->>'mail_from') || '|' || (payload->'smtp'->>'rcpt_to') || '|' || (payload->'email'->>'from') || '|' || (payload->'email'->>'to') || '|' || (payload->'email'->>'attachment'); END IF; IF payload->>'event_type' = 'fileinfo' THEN RETURN (payload->'fileinfo'->>'filename') || '|' || (payload->'fileinfo'->>'md5'); END IF; RETURN buffer; END; $$ LANGUAGE plpgsql IMMUTABLE;` // SQLCheckForTrigramExtension is an SQL query to check whether the trigram extension is available. const SQLCheckForTrigramExtension = `SELECT COUNT(*) FROM pg_available_extensions WHERE name = 'pg_trgm';` // SQLCreate is an SQL/DDL clause to create a new event table const SQLCreate = `CREATE UNLOGGED TABLE IF NOT EXISTS "%s" (ts timestamp without time zone default now(), payload jsonb); GRANT ALL PRIVILEGES ON TABLE "%s" to %s;` // SQLCopy is an SQL/DDL clause to bulk insert a chunk of JSON into the database const SQLCopy = `COPY "%s" (ts, payload) FROM STDIN WITH CSV DELIMITER E'\t' QUOTE E'\b'` // SQLIndex is an SQL/DDL clause to create indexes on event tables const SQLIndex = `CREATE INDEX ON "%s" (((payload->>'src_ip')::INET), ((payload->>'src_port')::INT)); CREATE INDEX ON "%s" (ts); CREATE INDEX ON "%s" (((payload->>'dest_ip')::INET), ((payload->>'dest_port')::INT)); CREATE INDEX ON "%s" ((payload->>'event_type')); CREATE INDEX ON "%s" using GIN (trigram_string(payload) gin_trgm_ops)` // SQLGetTableSizes is an SQL query to obtain the names of tables in the current schema and their size in bytes. const SQLGetTableSizes = `SELECT relname as table, pg_total_relation_size(relid) as size FROM pg_catalog.pg_statio_user_tables ORDER BY 1 DESC;` // SQLGenericQuery is the main kind of query used to pull out event metadata. const SQLGenericQuery = `SELECT * FROM all_events_query($1::text, $2::timestamp, $3::timestamp, $4::text[], $5::inet, $6::int, $7::inet, $8::int, $9::int);` // SQLQueryAllEvents is a plpgsql function to enable queries over all hourly tables // Example: SELECT COUNT(*) FROM all_events_query('WHERE trigram_string(payload) LIKE ''%%foo%%'''); const SQLQueryAllEvents = `CREATE OR REPLACE FUNCTION all_events_query(keyword text, start_time timestamp with time zone, end_time timestamp with time zone, event_type text[], ipsrc inet, portsrc int, ipdest inet, portdest int, mlimit int) RETURNS TABLE (ts timestamp, payload jsonb) AS $$ DECLARE clause text; t RECORD; tables CURSOR FOR SELECT * FROM information_schema.tables WHERE table_name LIKE 'event%'; BEGIN clause := ''; OPEN tables; LOOP FETCH tables INTO t; EXIT WHEN NOT FOUND; IF clause != '' THEN clause := clause || ' UNION ALL '; END IF; clause := clause || 'SELECT * FROM ' || quote_ident(t.table_name) || ' WHERE ts BETWEEN ' || quote_literal(start_time) || ' AND ' || quote_literal(end_time); IF keyword IS NOT NULL THEN clause := clause || ' AND trigram_string(payload) LIKE ' || quote_literal(keyword); END IF; IF event_type IS NOT NULL THEN clause := clause || ' AND payload->>''event_type'' = ANY(' || quote_literal(event_type) || ')'; END IF; IF ipsrc IS NOT NULL THEN clause := clause || ' AND (payload->>''src_ip'')::inet <<= inet ' || quote_literal(ipsrc); END IF; IF portsrc IS NOT NULL THEN clause := clause || ' AND payload->>''src_port'' = ' || quote_literal(portsrc); END IF; IF ipdest IS NOT NULL THEN clause := clause || ' AND (payload->>''dest_ip'')::inet <<= inet ' || quote_literal(ipdest); END IF; IF portdest IS NOT NULL THEN clause := clause || ' AND payload->>''dest_port'' = ' || quote_literal(portdest); END IF; END LOOP; IF mlimit IS NOT NULL THEN clause := clause || ' LIMIT ' || quote_literal(mlimit); END IF; RAISE NOTICE '%', clause; CLOSE tables; RETURN QUERY EXECUTE clause; END; $$ LANGUAGE plpgsql STABLE; ` fever-1.3.7/doc/000077500000000000000000000000001477766221000134125ustar00rootroot00000000000000fever-1.3.7/doc/database.md000066400000000000000000000040251477766221000155010ustar00rootroot00000000000000## Database schema Events are stored in a JSONB column tagged with a timestamp. Indexes will be created on this timestamp, the source/destination IP/port values (composite), and the event type. Another full-text (trigram) index will be built for event type-specific plain-text fields that are concatenated using a `|`. The keyword-based full-text matches are intended to serve as the main means of access to 'interesting' events, and can be further refined by IP/port/type/... constraints, which are also indexed. All further queries on JSON fields **will be unindexed**, so care should be taken to reduce the search space as much as possible using indexed queries. A separate database must be used and the connecting user must be able to `CREATE` and `DROP` tables in the public schema. ```sql -- Initial table CREATE UNLOGGED TABLE IF NOT EXISTS "events-YY-MM-DD-HHMM" (ts timestamp without time zone default now(), payload jsonb); GRANT ALL PRIVILEGES ON TABLE "events-YY-MM-DD-HHMM" to sensor; -- Deferred CREATE INDEX ON "events-YY-MM-DD-HHMM" (ts); CREATE INDEX ON "events-YY-MM-DD-HHMM" (((payload->>'src_ip')::INET), ((payload->>'src_port')::INT)); CREATE INDEX ON "events-YY-MM-DD-HHMM" (((payload->>'dest_ip')::INET), ((payload->>'dest_port')::INT)); CREATE INDEX ON "events-YY-MM-DD-HHMM" ((payload->>'event_type')); CREATE INDEX ON "events-YY-MM-DD-HHMM" using GIN (trigram_string(payload) gin_trgm_ops) ``` `trigram_string(payload jsonb)` is a PL/PgSQL function that extracts and concatenates relevant data for indexing, see `sql.go`. The following contents are used to build the full-text index: - `dns` events: - `dns->rdata` - `http` events: - `http->hostname` + `http->url` + `http->http_user_agent` - `tls` events: - `tls->subject` + `tls->issuerdn` + `tls->fingerprint` - `alert` events: - `alert->payload_printable` + `alert->payload` - `smtp` events: - `smtp->helo` + `smtp->mail_from` + `smtp->rcpt_to` + `email->from`+ `email->to` + `email->attachment` - `fileinfo` events: - `fileinfo->filename` + `fileinfo->md5`fever-1.3.7/doc/flow-agg.md000066400000000000000000000026761477766221000154520ustar00rootroot00000000000000## Aggregated flow metadata JSON example ```json { "sensor-id": "foobar", "time-start": "2017-03-13T17:36:53.205850748+01:00", "time-end": "2017-03-13T17:36:58.205967348+01:00", "tuples": { "172.22.0.214_172.18.8.116_993": { "count": 1, "total_bytes_toclient": 86895, "total_bytes_toserver": 17880 }, "172.22.0.214_172.18.8.145_2222": { "count": 2, "total_bytes_toclient": 36326, "total_bytes_toserver": 4332 }, "172.22.0.214_198.232.125.113_80": { "count": 3, "total_bytes_toclient": 23242, "total_bytes_toserver": 1223 }, "172.22.0.214_198.232.125.123_80": { "count": 1, "total_bytes_toclient": 1026322, "total_bytes_toserver": 51232 } }, "proxy-map": { "23.37.43.27": { "ss.symcd.com": 1 } } } ``` The `tuples` keys represent routes in which sourceIP/destIP/destPort (concatenated using `_`) map to the number of flow events observed in the reported time period. In the `proxy-map` dict, the keys are destination IP addresses which have had observed HTTP requests on ports 8000-8999, 80 or 3128 (i.e. typical proxy ports). The associated values are the number of times that these requests were made with certain HTTP Host headers. Using the `-n` parameter, the reporting frequency can be tuned. Longer intervals (e.g. hours) will reduce load on the consuming endpoint, but may also lead to larger payloads in the JSON outlined above.fever-1.3.7/fever.service000066400000000000000000000005031477766221000153340ustar00rootroot00000000000000[Unit] Description=fast, extensible, versatile event router Documentation=https://github.com/DCSO/fever After=network.target [Service] SyslogIdentifier=fever EnvironmentFile=-/etc/default/fever ExecStart=/usr/bin/fever run $FEVER_ARGS ExecStop=/usr/bin/pkill fever Restart=on-failure [Install] WantedBy=multi-user.target fever-1.3.7/fever.yaml000066400000000000000000000123511477766221000146420ustar00rootroot00000000000000# Config file for FEVER # --------------------- # Output additional debug information. # verbose: true # Enable output of profiling information to specified file. # profile: profile.out # Use the given size for defining the size of data blocks to be handled at once. # chunksize: 50000 # Do not submit data to the sinks, only print on stdout. # dummy: true # Retry connection to sockets or servers for at most the given amount of times before # giving up. Use the value of 0 to never give up. # reconnect-retries: 5 # Specify time interval or number of items to cache before flushing to # database, whichever happens first. # flushtime: 1m # flushcount: 100000 # Configuration for PostgreSQL 9.5+ database connection. database: enable: false host: localhost user: user password: pass database: test # Set to true to use the MongoDB interface instead of PostgreSQL. mongo: false # Time interval after which a new table is created and background # indexing is started. rotate: 1h # Maximum size in gigabytes. maxtablesize: 50 # Configuration for input (from Suricata side). Only one of 'socket' # or 'redis' is supported at the same time, comment/uncomment to choose. input: # Path to the socket that Suricata writes to. socket: /tmp/suri.sock # Buffer length for EVE items parsed from input socket. Useful to help FEVER # keep up with input from Suricata in case the processing pipeline is # temporarily slow. # Will track current buffer size in the `input_queue_length` metric. buffer: 500000 # Rather drop items from a full buffer than causing writes to the input # socket to block. # This avoids congestion effects in Suricata (up to packet drops) if FEVER # or its forwarding receiver remains slow for a longer period of time. # Will count the number of dropped items in the `input_queue_dropped` metric. buffer-drop: true #redis: # # Redis server hostname. We assume the 'suricata' list as a source. # server: localhost # # Disables Redis pipelining. # nopipe: true # Configure forwarding of events processed by FEVER, i.e. define what event # types to forward. multi-forward: # Set 'all' to true to forward everything received from Suricata, otherwise # use the 'types' list to choose. Example: # socketall: # socket: /tmp/out-all.sock # buffer-length: 100000 # all: true # types: [] socketalerts: socket: /tmp/suri-forward.sock all: false buffer-length: 1000 types: - alert - stats # Configuration for flow report submission. flowreport: # Interval used for aggregation. interval: 60s submission-url: amqp://guest:guest@localhost:5672/ submission-exchange: aggregations # Set to true to disable gzip compression for uploads. nocompress: false # If both srcip and destip are non-empty, inject an extra flow record for # these towards the given destination port. #testdata-srcip: 0.0.0.1 #testdata-destip: 0.0.0.2 #testdata-destport: 99999 # Set to true to count _all_ flows, not just TCP bidirectional ones. all: false # Configuration for metrics (i.e. InfluxDB) submission. metrics: enable: true submission-url: amqp://guest:guest@localhost:5672/ submission-exchange: metrics # Configuration for passive DNS submission. pdns: enable: true submission-url: amqp://guest:guest@localhost:5672/ submission-exchange: pdns # If test-domain is non-empty, add an extra A observation for this rrname to # all submissions #test-domain: heartbeat.fever-heartbeat # Configuration for alert-associated metadata submission. context: enable: false cache-timeout: 1h submission-url: amqp://guest:guest@localhost:5672/ submission-exchange: context # Extra fields to add to each forwarded event. #add-fields: # sensor-id: foobar # Send 'heartbeat' HTTP or alert event heartbeat: enable: false # 24h HH:MM strings with local times to send heartbeat as HTTP event times: - "00:01" # 24h HH:MM strings with local times to send heartbeat as alert #alert-times: # - "00:02" # Configuration for detailed flow metadata submission. flowextract: enable: false submission-url: amqp://guest:guest@localhost:5672/ submission-exchange: aggregations # Uncomment to enable flow collection only for IPs in the given # Bloom filter. # bloom-selector: /tmp/flows.bloom # Configuration for Bloom filter alerting on HTTP, DNS and # TLS metadata events. #bloom: # file: ./in.bloom.gz # zipped: true # alert-prefix: BLF # blacklist-iocs: # - / # - /index.htm # - /index.html # Configuration for active information gathering. active: # Enable reverse DNS lookups for src/dst IPs. rdns: false # Only do reverse lookups for RFC 1918 IPs. rdns-private-only: true # Duration to cache lookup redults for to avoid excessive DNS load. rdns-cache-expiry: 120s # Configuration for FEVER's log file handling. logging: # Insert file name here to redirect logs to separate file. If left blank, logs # will be printed to the stdout/stderr of the FEVER process. file: # Set to true to enable JSON output. json: false # Configuration for FEVER's remote management interface. mgmt: # Use local socket for gRPC communication. socket: /tmp/fever-mgmt.sock # Use network server for gRPC commmunication. #network: tcp #host: localhost:9999 fever-1.3.7/go.mod000066400000000000000000000021151477766221000137520ustar00rootroot00000000000000module github.com/DCSO/fever go 1.14 require ( github.com/DCSO/bloom v0.2.3 github.com/DCSO/fluxline v0.0.0-20200907065040-78686e5e68f6 github.com/NeowayLabs/wabbit v0.0.0-20210927194032-73ad61d1620e github.com/Showmax/go-fqdn v1.0.0 // indirect github.com/buger/jsonparser v1.1.1 github.com/containerd/containerd v1.6.38 // indirect github.com/fsouza/go-dockerclient v1.7.1 // indirect github.com/gomodule/redigo v1.8.3 github.com/jinzhu/inflection v1.0.0 // indirect github.com/mitchellh/go-homedir v1.1.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pborman/uuid v1.2.1 // indirect github.com/rabbitmq/amqp091-go v1.10.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.1.3 github.com/spf13/viper v1.7.1 github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 github.com/tiago4orion/conjure v0.0.0-20150908101743-93cb30b9d218 // indirect github.com/yl2chen/cidranger v1.0.2 golang.org/x/sync v0.3.0 google.golang.org/grpc v1.59.0 google.golang.org/protobuf v1.33.0 gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 gopkg.in/pg.v5 v5.3.3 ) fever-1.3.7/go.sum000066400000000000000000010546201477766221000140100ustar00rootroot00000000000000bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= 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.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 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= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw= cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= cloud.google.com/go v0.110.6/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= cloud.google.com/go v0.110.7/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= cloud.google.com/go/accessapproval v1.7.1/go.mod h1:JYczztsHRMK7NTXb6Xw+dwbs/WnOJxbo/2mTI+Kgg68= cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= cloud.google.com/go/accesscontextmanager v1.8.0/go.mod h1:uI+AI/r1oyWK99NN8cQ3UK76AMelMzgZCvJfsi2c+ps= cloud.google.com/go/accesscontextmanager v1.8.1/go.mod h1:JFJHfvuaTC+++1iL1coPiG1eu5D24db2wXCDWDjIrxo= cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k= cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= cloud.google.com/go/aiplatform v1.45.0/go.mod h1:Iu2Q7sC7QGhXUeOhAj/oCK9a+ULz1O4AotZiqjQ8MYA= cloud.google.com/go/aiplatform v1.48.0/go.mod h1:Iu2Q7sC7QGhXUeOhAj/oCK9a+ULz1O4AotZiqjQ8MYA= cloud.google.com/go/aiplatform v1.50.0/go.mod h1:IRc2b8XAMTa9ZmfJV1BCCQbieWWvDnP1A8znyz5N7y4= cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= cloud.google.com/go/analytics v0.21.2/go.mod h1:U8dcUtmDmjrmUTnnnRnI4m6zKn/yaA5N9RlEkYFHpQo= cloud.google.com/go/analytics v0.21.3/go.mod h1:U8dcUtmDmjrmUTnnnRnI4m6zKn/yaA5N9RlEkYFHpQo= cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= cloud.google.com/go/apigateway v1.6.1/go.mod h1:ufAS3wpbRjqfZrzpvLC2oh0MFlpRJm2E/ts25yyqmXA= cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= cloud.google.com/go/apigeeconnect v1.6.1/go.mod h1:C4awq7x0JpLtrlQCr8AzVIzAaYgngRqWf9S5Uhg+wWs= cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= cloud.google.com/go/apigeeregistry v0.7.1/go.mod h1:1XgyjZye4Mqtw7T9TsY4NW10U7BojBvG4RMD+vRDrIw= cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= cloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A= cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= cloud.google.com/go/appengine v1.8.1/go.mod h1:6NJXGLVhZCN9aQ/AEDvmfzKEfoYBlfB80/BHiKVputY= cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= cloud.google.com/go/area120 v0.8.1/go.mod h1:BVfZpGpB7KFVNxPiQBuHkX6Ed0rS51xIgmGyjrAfzsg= cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI= cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= cloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI= cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= cloud.google.com/go/artifactregistry v1.14.1/go.mod h1:nxVdG19jTaSTu7yA7+VbWL346r3rIdkZ142BSQqhn5E= cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= cloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg= cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= cloud.google.com/go/asset v1.14.1/go.mod h1:4bEJ3dnHCqWCDbWJ/6Vn7GVI9LerSi7Rfdi03hd+WTQ= cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= cloud.google.com/go/assuredworkloads v1.11.1/go.mod h1:+F04I52Pgn5nmPG36CWFtxmav6+7Q+c5QyJoL18Lry0= cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= cloud.google.com/go/automl v1.13.1/go.mod h1:1aowgAHWYZU27MybSCFiukPO7xnyawv7pt3zK4bheQE= cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= cloud.google.com/go/baremetalsolution v1.1.1/go.mod h1:D1AV6xwOksJMV4OSlWHtWuFNZZYujJknMAP4Qa27QIA= cloud.google.com/go/baremetalsolution v1.2.0/go.mod h1:68wi9AwPYkEWIUT4SvSGS9UJwKzNpshjHsH4lzk8iOw= cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= cloud.google.com/go/batch v1.3.1/go.mod h1:VguXeQKXIYaeeIYbuozUmBR13AfL4SJP7IltNPS+A4A= cloud.google.com/go/batch v1.4.1/go.mod h1:KdBmDD61K0ovcxoRHGrN6GmOBWeAOyCgKD0Mugx4Fkk= cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= cloud.google.com/go/beyondcorp v0.6.1/go.mod h1:YhxDWw946SCbmcWo3fAhw3V4XZMSpQ/VYfcKGAEU8/4= cloud.google.com/go/beyondcorp v1.0.0/go.mod h1:YhxDWw946SCbmcWo3fAhw3V4XZMSpQ/VYfcKGAEU8/4= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E= cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= cloud.google.com/go/bigquery v1.52.0/go.mod h1:3b/iXjRQGU4nKa87cXeg6/gogLjO8C6PmuM8i5Bi/u4= cloud.google.com/go/bigquery v1.53.0/go.mod h1:3b/iXjRQGU4nKa87cXeg6/gogLjO8C6PmuM8i5Bi/u4= cloud.google.com/go/bigquery v1.55.0/go.mod h1:9Y5I3PN9kQWuid6183JFhOGOW3GcirA5LpsKCUn+2ec= cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= cloud.google.com/go/billing v1.16.0/go.mod h1:y8vx09JSSJG02k5QxbycNRrN7FGZB6F3CAcgum7jvGA= cloud.google.com/go/billing v1.17.0/go.mod h1:Z9+vZXEq+HwH7bhJkyI4OQcR6TSbeMrjlpEjO2vzY64= cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= cloud.google.com/go/binaryauthorization v1.6.1/go.mod h1:TKt4pa8xhowwffiBmbrbcxijJRZED4zrqnwZ1lKH51U= cloud.google.com/go/binaryauthorization v1.7.0/go.mod h1:Zn+S6QqTMn6odcMU1zDZCJxPjU2tZPV1oDl45lWY154= cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= cloud.google.com/go/certificatemanager v1.7.1/go.mod h1:iW8J3nG6SaRYImIa+wXQ0g8IgoofDFRp5UMzaNk1UqI= cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= cloud.google.com/go/channel v1.16.0/go.mod h1:eN/q1PFSl5gyu0dYdmxNXscY/4Fi7ABmeHCJNf/oHmc= cloud.google.com/go/channel v1.17.0/go.mod h1:RpbhJsGi/lXWAUM1eF4IbQGbsfVlg2o8Iiy2/YLfVT0= cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= cloud.google.com/go/cloudbuild v1.10.1/go.mod h1:lyJg7v97SUIPq4RC2sGsz/9tNczhyv2AjML/ci4ulzU= cloud.google.com/go/cloudbuild v1.13.0/go.mod h1:lyJg7v97SUIPq4RC2sGsz/9tNczhyv2AjML/ci4ulzU= cloud.google.com/go/cloudbuild v1.14.0/go.mod h1:lyJg7v97SUIPq4RC2sGsz/9tNczhyv2AjML/ci4ulzU= cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= cloud.google.com/go/clouddms v1.6.1/go.mod h1:Ygo1vL52Ov4TBZQquhz5fiw2CQ58gvu+PlS6PVXCpZI= cloud.google.com/go/clouddms v1.7.0/go.mod h1:MW1dC6SOtI/tPNCciTsXtsGNEM0i0OccykPvv3hiYeM= cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= cloud.google.com/go/cloudtasks v1.11.1/go.mod h1:a9udmnou9KO2iulGscKR0qBYjreuX8oHwpmFsKspEvM= cloud.google.com/go/cloudtasks v1.12.1/go.mod h1:a9udmnou9KO2iulGscKR0qBYjreuX8oHwpmFsKspEvM= cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI= cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= cloud.google.com/go/contactcenterinsights v1.9.1/go.mod h1:bsg/R7zGLYMVxFFzfh9ooLTruLRCG9fnzhH9KznHhbM= cloud.google.com/go/contactcenterinsights v1.10.0/go.mod h1:bsg/R7zGLYMVxFFzfh9ooLTruLRCG9fnzhH9KznHhbM= cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= cloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM= cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= cloud.google.com/go/container v1.22.1/go.mod h1:lTNExE2R7f+DLbAN+rJiKTisauFCaoDq6NURZ83eVH4= cloud.google.com/go/container v1.24.0/go.mod h1:lTNExE2R7f+DLbAN+rJiKTisauFCaoDq6NURZ83eVH4= cloud.google.com/go/container v1.26.0/go.mod h1:YJCmRet6+6jnYYRS000T6k0D0xUXQgBSaJ7VwI8FBj4= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= cloud.google.com/go/containeranalysis v0.10.1/go.mod h1:Ya2jiILITMY68ZLPaogjmOMNkwsDrWBSTyBubGXO7j0= cloud.google.com/go/containeranalysis v0.11.0/go.mod h1:4n2e99ZwpGxpNcz+YsFT1dfOHPQFGcAC8FN2M2/ne/U= cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= cloud.google.com/go/datacatalog v1.14.0/go.mod h1:h0PrGtlihoutNMp/uvwhawLQ9+c63Kz65UFqh49Yo+E= cloud.google.com/go/datacatalog v1.14.1/go.mod h1:d2CevwTG4yedZilwe+v3E3ZBDRMobQfSG/a6cCCN5R4= cloud.google.com/go/datacatalog v1.16.0/go.mod h1:d2CevwTG4yedZilwe+v3E3ZBDRMobQfSG/a6cCCN5R4= cloud.google.com/go/datacatalog v1.17.1/go.mod h1:nCSYFHgtxh2MiEktWIz71s/X+7ds/UT9kp0PC7waCzE= cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= cloud.google.com/go/dataflow v0.9.1/go.mod h1:Wp7s32QjYuQDWqJPFFlnBKhkAtiFpMTdg00qGbnIHVw= cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= cloud.google.com/go/dataform v0.8.1/go.mod h1:3BhPSiw8xmppbgzeBbmDvmSWlwouuJkXsXsb8UBih9M= cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= cloud.google.com/go/datafusion v1.7.1/go.mod h1:KpoTBbFmoToDExJUso/fcCiguGDk7MEzOWXUsJo0wsI= cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= cloud.google.com/go/datalabeling v0.8.1/go.mod h1:XS62LBSVPbYR54GfYQsPXZjTW8UxCK2fkDciSrpRFdY= cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= cloud.google.com/go/dataplex v1.8.1/go.mod h1:7TyrDT6BCdI8/38Uvp0/ZxBslOslP2X2MPDucliyvSE= cloud.google.com/go/dataplex v1.9.0/go.mod h1:7TyrDT6BCdI8/38Uvp0/ZxBslOslP2X2MPDucliyvSE= cloud.google.com/go/dataplex v1.9.1/go.mod h1:7TyrDT6BCdI8/38Uvp0/ZxBslOslP2X2MPDucliyvSE= cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= cloud.google.com/go/dataproc/v2 v2.0.1/go.mod h1:7Ez3KRHdFGcfY7GcevBbvozX+zyWGcwLJvvAMwCaoZ4= cloud.google.com/go/dataproc/v2 v2.2.0/go.mod h1:lZR7AQtwZPvmINx5J87DSOOpTfof9LVZju6/Qo4lmcY= cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= cloud.google.com/go/dataqna v0.8.1/go.mod h1:zxZM0Bl6liMePWsHA8RMGAfmTG34vJMapbHAxQ5+WA8= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= cloud.google.com/go/datastore v1.12.0/go.mod h1:KjdB88W897MRITkvWWJrg2OUtrR5XVj1EoLgSp6/N70= cloud.google.com/go/datastore v1.12.1/go.mod h1:KjdB88W897MRITkvWWJrg2OUtrR5XVj1EoLgSp6/N70= cloud.google.com/go/datastore v1.13.0/go.mod h1:KjdB88W897MRITkvWWJrg2OUtrR5XVj1EoLgSp6/N70= cloud.google.com/go/datastore v1.14.0/go.mod h1:GAeStMBIt9bPS7jMJA85kgkpsMkvseWWXiaHya9Jes8= cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= cloud.google.com/go/datastream v1.9.1/go.mod h1:hqnmr8kdUBmrnk65k5wNRoHSCYksvpdZIcZIEl8h43Q= cloud.google.com/go/datastream v1.10.0/go.mod h1:hqnmr8kdUBmrnk65k5wNRoHSCYksvpdZIcZIEl8h43Q= cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= cloud.google.com/go/deploy v1.11.0/go.mod h1:tKuSUV5pXbn67KiubiUNUejqLs4f5cxxiCNCeyl0F2g= cloud.google.com/go/deploy v1.13.0/go.mod h1:tKuSUV5pXbn67KiubiUNUejqLs4f5cxxiCNCeyl0F2g= cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= cloud.google.com/go/dialogflow v1.38.0/go.mod h1:L7jnH+JL2mtmdChzAIcXQHXMvQkE3U4hTaNltEuxXn4= cloud.google.com/go/dialogflow v1.40.0/go.mod h1:L7jnH+JL2mtmdChzAIcXQHXMvQkE3U4hTaNltEuxXn4= cloud.google.com/go/dialogflow v1.43.0/go.mod h1:pDUJdi4elL0MFmt1REMvFkdsUTYSHq+rTCS8wg0S3+M= cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= cloud.google.com/go/dlp v1.10.1/go.mod h1:IM8BWz1iJd8njcNcG0+Kyd9OPnqnRNkDV8j42VT5KOI= cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= cloud.google.com/go/documentai v1.20.0/go.mod h1:yJkInoMcK0qNAEdRnqY/D5asy73tnPe88I1YTZT+a8E= cloud.google.com/go/documentai v1.22.0/go.mod h1:yJkInoMcK0qNAEdRnqY/D5asy73tnPe88I1YTZT+a8E= cloud.google.com/go/documentai v1.22.1/go.mod h1:LKs22aDHbJv7ufXuPypzRO7rG3ALLJxzdCXDPutw4Qc= cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= cloud.google.com/go/domains v0.9.1/go.mod h1:aOp1c0MbejQQ2Pjf1iJvnVyT+z6R6s8pX66KaCSDYfE= cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= cloud.google.com/go/edgecontainer v1.1.1/go.mod h1:O5bYcS//7MELQZs3+7mabRqoWQhXCzenBu0R8bz2rwk= cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= cloud.google.com/go/essentialcontacts v1.6.2/go.mod h1:T2tB6tX+TRak7i88Fb2N9Ok3PvY3UNbUsMag9/BARh4= cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= cloud.google.com/go/eventarc v1.12.1/go.mod h1:mAFCW6lukH5+IZjkvrEss+jmt2kOdYlN8aMx3sRJiAI= cloud.google.com/go/eventarc v1.13.0/go.mod h1:mAFCW6lukH5+IZjkvrEss+jmt2kOdYlN8aMx3sRJiAI= cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= cloud.google.com/go/filestore v1.7.1/go.mod h1:y10jsorq40JJnjR/lQ8AfFbbcGlw3g+Dp8oN7i7FjV4= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= cloud.google.com/go/firestore v1.11.0/go.mod h1:b38dKhgzlmNNGTNZZwe7ZRFEuRab1Hay3/DBsIGKKy4= cloud.google.com/go/firestore v1.12.0/go.mod h1:b38dKhgzlmNNGTNZZwe7ZRFEuRab1Hay3/DBsIGKKy4= cloud.google.com/go/firestore v1.13.0/go.mod h1:QojqqOh8IntInDUSTAh0c8ZsPYAr68Ma8c5DWOy8xb8= cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= cloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA= cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= cloud.google.com/go/functions v1.15.1/go.mod h1:P5yNWUTkyU+LvW/S9O6V+V423VZooALQlqoXdoPz5AE= cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= cloud.google.com/go/gaming v1.10.1/go.mod h1:XQQvtfP8Rb9Rxnxm5wFVpAp9zCQkJi2bLIb7iHGwB3s= cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= cloud.google.com/go/gkebackup v1.3.0/go.mod h1:vUDOu++N0U5qs4IhG1pcOnD1Mac79xWy6GoBFlWCWBU= cloud.google.com/go/gkebackup v1.3.1/go.mod h1:vUDOu++N0U5qs4IhG1pcOnD1Mac79xWy6GoBFlWCWBU= cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= cloud.google.com/go/gkeconnect v0.8.1/go.mod h1:KWiK1g9sDLZqhxB2xEuPV8V9NYzrqTUmQR9shJHpOZw= cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= cloud.google.com/go/gkehub v0.14.1/go.mod h1:VEXKIJZ2avzrbd7u+zeMtW00Y8ddk/4V9511C9CQGTY= cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= cloud.google.com/go/gkemulticloud v0.6.1/go.mod h1:kbZ3HKyTsiwqKX7Yw56+wUGwwNZViRnxWK2DVknXWfw= cloud.google.com/go/gkemulticloud v1.0.0/go.mod h1:kbZ3HKyTsiwqKX7Yw56+wUGwwNZViRnxWK2DVknXWfw= cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= cloud.google.com/go/grafeas v0.3.0/go.mod h1:P7hgN24EyONOTMyeJH6DxG4zD7fwiYa5Q6GUgyFSOU8= cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= cloud.google.com/go/gsuiteaddons v1.6.1/go.mod h1:CodrdOqRZcLp5WOwejHWYBjZvfY0kOphkAKpF/3qdZY= cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= cloud.google.com/go/iam v1.0.1/go.mod h1:yR3tmSL8BcZB4bxByRv2jkSIahVmCtfKZwLYGBalRE8= cloud.google.com/go/iam v1.1.0/go.mod h1:nxdHjaKfCr7fNYx/HJMM8LgiMugmveWlkatear5gVyk= cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= cloud.google.com/go/iam v1.1.2/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo= cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= cloud.google.com/go/iap v1.8.1/go.mod h1:sJCbeqg3mvWLqjZNsI6dfAtbbV1DL2Rl7e1mTyXYREQ= cloud.google.com/go/iap v1.9.0/go.mod h1:01OFxd1R+NFrg78S+hoPV5PxEzv22HXaNqUUlmNHFuY= cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= cloud.google.com/go/ids v1.4.1/go.mod h1:np41ed8YMU8zOgv53MMMoCntLTn2lF+SUzlM+O3u/jw= cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= cloud.google.com/go/iot v1.7.1/go.mod h1:46Mgw7ev1k9KqK1ao0ayW9h0lI+3hxeanz+L1zmbbbk= cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= cloud.google.com/go/kms v1.11.0/go.mod h1:hwdiYC0xjnWsKQQCQQmIQnS9asjYVSK6jtXm+zFqXLM= cloud.google.com/go/kms v1.12.1/go.mod h1:c9J991h5DTl+kg7gi3MYomh12YEENGrf48ee/N/2CDM= cloud.google.com/go/kms v1.15.0/go.mod h1:c9J991h5DTl+kg7gi3MYomh12YEENGrf48ee/N/2CDM= cloud.google.com/go/kms v1.15.2/go.mod h1:3hopT4+7ooWRCjc2DxgnpESFxhIraaI2IpAVUEhbT/w= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= cloud.google.com/go/language v1.10.1/go.mod h1:CPp94nsdVNiQEt1CNjF5WkTcisLiHPyIbMhvR8H2AW0= cloud.google.com/go/language v1.11.0/go.mod h1:uDx+pFDdAKTY8ehpWbiXyQdz8tDSYLJbQcXsCkjYyvQ= cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= cloud.google.com/go/lifesciences v0.9.1/go.mod h1:hACAOd1fFbCGLr/+weUKRAJas82Y4vrL3O5326N//Wc= cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= cloud.google.com/go/logging v1.8.1/go.mod h1:TJjR+SimHwuC8MZ9cjByQulAMgni+RkXeI3wwctHJEI= cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= cloud.google.com/go/longrunning v0.4.2/go.mod h1:OHrnaYyLUV6oqwh0xiS7e5sLQhP1m0QU9R+WhGDMgIQ= cloud.google.com/go/longrunning v0.5.0/go.mod h1:0JNuqRShmscVAhIACGtskSAWtqtOoPkwP0YF1oVEchc= cloud.google.com/go/longrunning v0.5.1/go.mod h1:spvimkwdz6SPWKEt/XBij79E9fiTkHSQl/fRUUQJYJc= cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= cloud.google.com/go/managedidentities v1.6.1/go.mod h1:h/irGhTN2SkZ64F43tfGPMbHnypMbu4RB3yl8YcuEak= cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= cloud.google.com/go/maps v1.3.0/go.mod h1:6mWTUv+WhnOwAgjVsSW2QPPECmW+s3PcRyOa9vgG/5s= cloud.google.com/go/maps v1.4.0/go.mod h1:6mWTUv+WhnOwAgjVsSW2QPPECmW+s3PcRyOa9vgG/5s= cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= cloud.google.com/go/mediatranslation v0.8.1/go.mod h1:L/7hBdEYbYHQJhX2sldtTO5SZZ1C1vkapubj0T2aGig= cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= cloud.google.com/go/memcache v1.10.1/go.mod h1:47YRQIarv4I3QS5+hoETgKO40InqzLP6kpNLvyXuyaA= cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= cloud.google.com/go/metastore v1.11.1/go.mod h1:uZuSo80U3Wd4zi6C22ZZliOUJ3XeM/MlYi/z5OAOWRA= cloud.google.com/go/metastore v1.12.0/go.mod h1:uZuSo80U3Wd4zi6C22ZZliOUJ3XeM/MlYi/z5OAOWRA= cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= cloud.google.com/go/monitoring v1.15.1/go.mod h1:lADlSAlFdbqQuwwpaImhsJXu1QSdd3ojypXrFSMr2rM= cloud.google.com/go/monitoring v1.16.0/go.mod h1:Ptp15HgAyM1fNICAojDMoNc/wUmn67mLHQfyqbw+poY= cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= cloud.google.com/go/networkconnectivity v1.12.1/go.mod h1:PelxSWYM7Sh9/guf8CFhi6vIqf19Ir/sbfZRUwXh92E= cloud.google.com/go/networkconnectivity v1.13.0/go.mod h1:SAnGPes88pl7QRLUen2HmcBSE9AowVAcdug8c0RSBFk= cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= cloud.google.com/go/networkmanagement v1.8.0/go.mod h1:Ho/BUGmtyEqrttTgWEe7m+8vDdK74ibQc+Be0q7Fof0= cloud.google.com/go/networkmanagement v1.9.0/go.mod h1:UTUaEU9YwbCAhhz3jEOHr+2/K/MrBk2XxOLS89LQzFw= cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= cloud.google.com/go/networksecurity v0.9.1/go.mod h1:MCMdxOKQ30wsBI1eI659f9kEp4wuuAueoC9AJKSPWZQ= cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= cloud.google.com/go/notebooks v1.9.1/go.mod h1:zqG9/gk05JrzgBt4ghLzEepPHNwE5jgPcHZRKhlC1A8= cloud.google.com/go/notebooks v1.10.0/go.mod h1:SOPYMZnttHxqot0SGSFSkRrwE29eqnKPBJFqgWmiK2k= cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= cloud.google.com/go/optimization v1.4.1/go.mod h1:j64vZQP7h9bO49m2rVaTVoNM0vEBEN5eKPUPbZyXOrk= cloud.google.com/go/optimization v1.5.0/go.mod h1:evo1OvTxeBRBu6ydPlrIRizKY/LJKo/drDMMRKqGEUU= cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= cloud.google.com/go/orchestration v1.8.1/go.mod h1:4sluRF3wgbYVRqz7zJ1/EUNc90TTprliq9477fGobD8= cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= cloud.google.com/go/orgpolicy v1.11.0/go.mod h1:2RK748+FtVvnfuynxBzdnyu7sygtoZa1za/0ZfpOs1M= cloud.google.com/go/orgpolicy v1.11.1/go.mod h1:8+E3jQcpZJQliP+zaFfayC2Pg5bmhuLK755wKhIIUCE= cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= cloud.google.com/go/osconfig v1.12.0/go.mod h1:8f/PaYzoS3JMVfdfTubkowZYGmAhUCjjwnjqWI7NVBc= cloud.google.com/go/osconfig v1.12.1/go.mod h1:4CjBxND0gswz2gfYRCUoUzCm9zCABp91EeTtWXyz0tE= cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= cloud.google.com/go/oslogin v1.10.1/go.mod h1:x692z7yAue5nE7CsSnoG0aaMbNoRJRXO4sn73R+ZqAs= cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= cloud.google.com/go/phishingprotection v0.8.1/go.mod h1:AxonW7GovcA8qdEk13NfHq9hNx5KPtfxXNeUxTDxB6I= cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= cloud.google.com/go/policytroubleshooter v1.7.1/go.mod h1:0NaT5v3Ag1M7U5r0GfDCpUFkWd9YqpubBWsQlhanRv0= cloud.google.com/go/policytroubleshooter v1.8.0/go.mod h1:tmn5Ir5EToWe384EuboTcVQT7nTag2+DuH3uHmKd1HU= cloud.google.com/go/policytroubleshooter v1.9.0/go.mod h1:+E2Lga7TycpeSTj2FsH4oXxTnrbHJGRlKhVZBLGgU64= cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= cloud.google.com/go/privatecatalog v0.9.1/go.mod h1:0XlDXW2unJXdf9zFz968Hp35gl/bhF4twwpXZAW50JA= 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/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= cloud.google.com/go/pubsub v1.32.0/go.mod h1:f+w71I33OMyxf9VpMVcZbnG5KSUkCOUHYpFd5U1GdRc= cloud.google.com/go/pubsub v1.33.0/go.mod h1:f+w71I33OMyxf9VpMVcZbnG5KSUkCOUHYpFd5U1GdRc= cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= cloud.google.com/go/pubsublite v1.8.1/go.mod h1:fOLdU4f5xldK4RGJrBMm+J7zMWNj/k4PxwEZXy39QS0= cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= cloud.google.com/go/recaptchaenterprise/v2 v2.7.2/go.mod h1:kR0KjsJS7Jt1YSyWFkseQ756D45kaYNTlDPPaRAvDBU= cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= cloud.google.com/go/recommendationengine v0.8.1/go.mod h1:MrZihWwtFYWDzE6Hz5nKcNz3gLizXVIDI/o3G1DLcrE= cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= cloud.google.com/go/recommender v1.10.1/go.mod h1:XFvrE4Suqn5Cq0Lf+mCP6oBHD/yRMA8XxP5sb7Q7gpA= cloud.google.com/go/recommender v1.11.0/go.mod h1:kPiRQhPyTJ9kyXPCG6u/dlPLbYfFlkwHNRwdzPVAoII= cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= cloud.google.com/go/redis v1.13.1/go.mod h1:VP7DGLpE91M6bcsDdMuyCm2hIpB6Vp2hI090Mfd1tcg= cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo= cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= cloud.google.com/go/resourcemanager v1.9.1/go.mod h1:dVCuosgrh1tINZ/RwBufr8lULmWGOkPS8gL5gqyjdT8= cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= cloud.google.com/go/resourcesettings v1.6.1/go.mod h1:M7mk9PIZrC5Fgsu1kZJci6mpgN8o0IUzVx3eJU3y4Jw= cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= cloud.google.com/go/retail v1.14.1/go.mod h1:y3Wv3Vr2k54dLNIrCzenyKG8g8dhvhncT2NcNjb/6gE= cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= cloud.google.com/go/run v1.2.0/go.mod h1:36V1IlDzQ0XxbQjUx6IYbw8H3TJnWvhii963WW3B/bo= cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= cloud.google.com/go/scheduler v1.10.1/go.mod h1:R63Ldltd47Bs4gnhQkmNDse5w8gBRrhObZ54PxgR2Oo= cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= cloud.google.com/go/secretmanager v1.11.1/go.mod h1:znq9JlXgTNdBeQk9TBW/FnR/W4uChEKGeqQWAJ8SXFw= cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= cloud.google.com/go/security v1.15.1/go.mod h1:MvTnnbsWnehoizHi09zoiZob0iCHVcL4AUBj76h9fXA= cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= cloud.google.com/go/securitycenter v1.23.0/go.mod h1:8pwQ4n+Y9WCWM278R8W3nF65QtY172h4S8aXyI9/hsQ= cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= cloud.google.com/go/servicedirectory v1.10.1/go.mod h1:Xv0YVH8s4pVOwfM/1eMTl0XJ6bzIOSLDt8f8eLaGOxQ= cloud.google.com/go/servicedirectory v1.11.0/go.mod h1:Xv0YVH8s4pVOwfM/1eMTl0XJ6bzIOSLDt8f8eLaGOxQ= cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= cloud.google.com/go/shell v1.7.1/go.mod h1:u1RaM+huXFaTojTbW4g9P5emOrrmLE69KrxqQahKn4g= cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= cloud.google.com/go/spanner v1.47.0/go.mod h1:IXsJwVW2j4UKs0eYDqodab6HgGuA1bViSqW4uH9lfUI= cloud.google.com/go/spanner v1.49.0/go.mod h1:eGj9mQGK8+hkgSVbHNQ06pQ4oS+cyc4tXXd6Dif1KoM= cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= cloud.google.com/go/speech v1.17.1/go.mod h1:8rVNzU43tQvxDaGvqOhpDqgkJTFowBpDvCJ14kGlJYo= cloud.google.com/go/speech v1.19.0/go.mod h1:8rVNzU43tQvxDaGvqOhpDqgkJTFowBpDvCJ14kGlJYo= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= cloud.google.com/go/storagetransfer v1.10.0/go.mod h1:DM4sTlSmGiNczmV6iZyceIh2dbs+7z2Ayg6YAiQlYfA= cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= cloud.google.com/go/talent v1.6.2/go.mod h1:CbGvmKCG61mkdjcqTcLOkb2ZN1SrQI8MDyma2l7VD24= cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= cloud.google.com/go/texttospeech v1.7.1/go.mod h1:m7QfG5IXxeneGqTapXNxv2ItxP/FS0hCZBwXYqucgSk= cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= cloud.google.com/go/tpu v1.6.1/go.mod h1:sOdcHVIgDEEOKuqUoi6Fq53MKHJAtOwtz0GuKsWSH3E= cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= cloud.google.com/go/trace v1.10.1/go.mod h1:gbtL94KE5AJLH3y+WVpfWILmqgc6dXcqgNXdOPAQTYk= cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= cloud.google.com/go/translate v1.8.1/go.mod h1:d1ZH5aaOA0CNhWeXeC8ujd4tdCFw8XoNWRljklu5RHs= cloud.google.com/go/translate v1.8.2/go.mod h1:d1ZH5aaOA0CNhWeXeC8ujd4tdCFw8XoNWRljklu5RHs= cloud.google.com/go/translate v1.9.0/go.mod h1:d1ZH5aaOA0CNhWeXeC8ujd4tdCFw8XoNWRljklu5RHs= cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= cloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= cloud.google.com/go/video v1.17.1/go.mod h1:9qmqPqw/Ib2tLqaeHgtakU+l5TcJxCJbhFXM7UJjVzU= cloud.google.com/go/video v1.19.0/go.mod h1:9qmqPqw/Ib2tLqaeHgtakU+l5TcJxCJbhFXM7UJjVzU= cloud.google.com/go/video v1.20.0/go.mod h1:U3G3FTnsvAGqglq9LxgqzOiBc/Nt8zis8S+850N2DUM= cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= cloud.google.com/go/videointelligence v1.11.1/go.mod h1:76xn/8InyQHarjTWsBR058SmlPCwQjgcvoW0aZykOvo= cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= cloud.google.com/go/vision/v2 v2.7.2/go.mod h1:jKa8oSYBWhYiXarHPvP4USxYANYUEdEsQrloLjrSwJU= cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= cloud.google.com/go/vmmigration v1.7.1/go.mod h1:WD+5z7a/IpZ5bKK//YmT9E047AD+rjycCAvyMxGJbro= cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= cloud.google.com/go/vmwareengine v0.4.1/go.mod h1:Px64x+BvjPZwWuc4HdmVhoygcXqEkGHXoa7uyfTgSI0= cloud.google.com/go/vmwareengine v1.0.0/go.mod h1:Px64x+BvjPZwWuc4HdmVhoygcXqEkGHXoa7uyfTgSI0= cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= cloud.google.com/go/vpcaccess v1.7.1/go.mod h1:FogoD46/ZU+JUBX9D606X21EnxiszYi2tArQwLY4SXs= cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= cloud.google.com/go/webrisk v1.9.1/go.mod h1:4GCmXKcOa2BZcZPn6DCEvE7HypmEJcJkr4mtM+sqYPc= cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= cloud.google.com/go/websecurityscanner v1.6.1/go.mod h1:Njgaw3rttgRHXzwCB8kgCYqv5/rGpFCsBOvPbYgszpg= cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= cloud.google.com/go/workflows v1.11.1/go.mod h1:Z+t10G1wF7h8LgdY/EmRcQY8ptBD/nvofaL6FqlET6g= cloud.google.com/go/workflows v1.12.0/go.mod h1:PYhSk2b6DhZ508tj8HXKaBh+OFe+xdl0dHF/tJdzPQM= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DCSO/bloom v0.2.3 h1:QVatZ34XB0aFrr7OSGDztgWwlYwOIF+wFRo6L8Er9vE= github.com/DCSO/bloom v0.2.3/go.mod h1:KUqcoyCSFP4wHsEScGtpIUlte5FDr5bBrCyzDK96xQM= github.com/DCSO/fluxline v0.0.0-20200907065040-78686e5e68f6 h1:ILpYBsvnQwnM7PvDKoLjLx47tE0jPsR/oX3CvhuhJG4= github.com/DCSO/fluxline v0.0.0-20200907065040-78686e5e68f6/go.mod h1:PvQC78r0wMuOsJ8Sl9isIEYR+00WKI/gliLjVYfDVjQ= github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.21/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.5.3 h1:YFS1RpkNc47wuNQBGlQNYniUvOIKqtVQ0+MKgZLbxls= github.com/Microsoft/go-winio v0.5.3/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= github.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= github.com/Microsoft/hcsshim v0.9.2/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/Microsoft/hcsshim v0.9.6/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/Microsoft/hcsshim v0.9.10/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/Microsoft/hcsshim v0.9.12 h1:0Wgl1fRF4WmBuqP6EnHk2w3m7CCCumD/KUumZxp7vKg= github.com/Microsoft/hcsshim v0.9.12/go.mod h1:qAiPvMgZoM0wpkVg6qMdSEu+1VtI6/qHOOPkTGt8ftQ= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/NeowayLabs/wabbit v0.0.0-20210927194032-73ad61d1620e h1:7/tYogeGpldbXfNKNMqe87rNX8WGeAzZSnB5i4mrJXI= github.com/NeowayLabs/wabbit v0.0.0-20210927194032-73ad61d1620e/go.mod h1:Jp6npmrFAOZdPpJQgIQyDzaSApVtYTSrK5VAV49OtfQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 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/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/Showmax/go-fqdn v1.0.0 h1:0rG5IbmVliNT5O19Mfuvna9LL7zlHyRfsSvBPZmF9tM= github.com/Showmax/go-fqdn v1.0.0/go.mod h1:SfrFBzmDCtCGrnHhoDjuvFnKsWjEQX/Q9ARZvOrJAko= github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= github.com/apache/arrow/go/v12 v12.0.0/go.mod h1:d+tV/eHZZ7Dz7RPrFKtPK02tpr+c9/PEd/zm8mDS9Vg= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/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/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-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230428030218-4003588d1b74/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= github.com/containerd/containerd v1.6.1/go.mod h1:1nJz5xCZPusx6jJU8Frfct988y0NpumIq9ODB0kLtoE= github.com/containerd/containerd v1.6.18/go.mod h1:1RdCUu95+gc2v9t3IL+zIlpClSmew7/0YS8O5eQZrOw= github.com/containerd/containerd v1.6.23/go.mod h1:UrQOiyzrLi3n4aezYJbQH6Il+YzTvnHFbEuO3yfDrM4= github.com/containerd/containerd v1.6.38 h1:AgSP9hVZT8JHzIAUjA7/wSmkTCuhzJCsaJ8QJ+zP84g= github.com/containerd/containerd v1.6.38/go.mod h1:MtQjP1WJnC0DoVVzDWj5V1i0m0evpOlSmDPOV7w7zJY= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= github.com/containerd/go-cni v1.1.0/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= github.com/containerd/go-cni v1.1.3/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= github.com/containerd/go-cni v1.1.6/go.mod h1:BWtoWl5ghVymxu6MBjg79W9NZrCRyHIdUtk4cauMe34= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= github.com/containerd/imgcrypt v1.1.3/go.mod h1:/TPA1GIDXMzbj01yd8pIbQiLdQxed5ue1wb8bP7PQu4= github.com/containerd/imgcrypt v1.1.4/go.mod h1:LorQnPtzL/T0IyCeftcsMEO7AqxUDbdO8j/tSUpgxvo= github.com/containerd/imgcrypt v1.1.8/go.mod h1:x6QvFIkMyO2qGIY2zXc88ivEzcbgvLdWjoZyGqDap5U= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/nri v0.1.1/go.mod h1:ZoFbxeZjXxrY3TpGxRjz/naxN16QK9n1Mx2YV2vJoLU= github.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ= github.com/containerd/ttrpc v1.1.2/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= github.com/containerd/zfs v1.1.0/go.mod h1:oZF9wBnrnQjpWLaPKEinrx3TQ9a+W/RJO7Zb41d8YLE= github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y= github.com/containernetworking/cni v1.1.1/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNBDZcxSOplJT5ico8/FLE= github.com/containernetworking/plugins v1.1.1/go.mod h1:Sr5TH/eBsGLXK/h71HeLfX19sZPp3ry5uHSkI4LPxV8= github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= github.com/containers/ocicrypt v1.1.2/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= github.com/containers/ocicrypt v1.1.3/go.mod h1:xpdkbVAuaH3WzbEabUd5yDsl9SwJA5pABH85425Es2g= github.com/containers/ocicrypt v1.1.8/go.mod h1:jM362hyBtbwLMWzXQZTlkjKGAQf/BN/LFMtH0FIRt34= github.com/containers/ocicrypt v1.1.10/go.mod h1:YfzSSr06PTHQwSTUKqDSjish9BeW1E4HUmreluQcMd8= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= 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/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.3-0.20210216175712-646072ed6524+incompatible h1:Yu2uGErhwEoOT/OxAFe+/SiJCqRLs+pgcS5XKrDXnG4= github.com/docker/docker v20.10.3-0.20210216175712-646072ed6524+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= 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/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.16.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI= github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsouza/go-dockerclient v1.7.1 h1:OHRaYvQslCqBStrel+I3OcXZBmpoTnRCGsmH2tAk7Hk= github.com/fsouza/go-dockerclient v1.7.1/go.mod h1:PHUSk8IVIp+nkIVGrQa2GK69jPOeW/43OUKQgQcOH+M= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= 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/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= 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/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= 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-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= 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 v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= 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.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= 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/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= 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.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 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/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 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/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= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v1.8.3 h1:HR0kYDX2RJZvAup8CsiJwxB4dTCSC0AaUq6S4SiLwUc= github.com/gomodule/redigo v1.8.3/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/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/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= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= 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/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/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-20210407192527-94a9f03dee38/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/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.0/go.mod h1:OJpEgntRZo8ugHpF9hkoLJbS5dSI20XZeXJ9JVywLlM= github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/enterprise-certificate-proxy v0.2.4/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/googleapis/gax-go/v2 v2.10.0/go.mod h1:4UOEnMCrxsSqQ940WnTiD6qJ63le2ev3xfyagutxiPw= github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= 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/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= 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/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 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/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 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/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 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/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= 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/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= github.com/lyft/protoc-gen-star/v2 v2.0.3/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/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.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mistifyio/go-zfs/v3 v3.0.1/go.mod h1:CzVgeB0RvF2EGzQnytKVvVSDwmKJXxkOTUGbNrTja/k= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 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/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mount v0.2.0 h1:WhCW5B355jtxndN5ovugJlMFJawbUODuW8fSnEH6SSM= github.com/moby/sys/mount v0.2.0/go.mod h1:aAivFE2LB3W4bACsUXChRHQ0qKWsetY4Y9V7sxOougM= github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/sys/signal v0.6.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs= github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 h1:yH0SvLzcbZxcJXho2yh7CqdENGMQe73Cw3woZBpPli0= github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/networkplumbing/go-nft v0.2.0/go.mod h1:HnnM+tYvlGAsMU7yoYwXEVLLiDW9gdMmb5HoGcwpuQs= 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/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= 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-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= 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/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opencontainers/selinux v1.10.1/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 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/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 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.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 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.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= 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-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw= github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= 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.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/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/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/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.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6/go.mod h1:39R/xuhNgVhi+K0/zst4TLrJrVmbm6LVgl4A0+ZFS5M= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/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= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= github.com/tiago4orion/conjure v0.0.0-20150908101743-93cb30b9d218 h1:tOESt7J50fPC9NqR0VdU1Zxk2zo5QYH70ap5TsU1bt4= github.com/tiago4orion/conjure v0.0.0-20150908101743-93cb30b9d218/go.mod h1:GQei++1WClbEC7AN1B9ipY1jCjzllM/7UNg0okAh/Z4= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU= github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= go.etcd.io/gofail v0.1.0/go.mod h1:VZBCXYGZhHAinaBiiqYvuDynvahNsAyLFwB3kEHKz1M= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= 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/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0/go.mod h1:vEhqr0m4eTc+DWxfsXoXue2GBgV2uUwVznkGIHW/e5w= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1:QNX1aly8ehqqX1LEa6YniTU7VY9I6R3X/oPxhGdTceE= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= go.opentelemetry.io/otel/metric v1.20.0/go.mod h1:90DRw3nfK4D7Sm/75yQ00gTJxtkBxX+wu6YaNymbpVM= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= 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-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-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-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 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-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 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/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/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/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-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= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-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-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 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/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= 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-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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/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-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-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-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/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-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/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-20201202213521-69691e467435/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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210216224549-f992740a1bac/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/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-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/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-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/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/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-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220405210540-1e041c57c461/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201113234701-d7a72108b828/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= 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.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/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/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-20210220033141-f8bda1e9f3ba/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-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/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-20180525024113-a5b4c53f6e8b/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-20190206041539-40960b6deb8e/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-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/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-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/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-20200616133436-c1934b75d054/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-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 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/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.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/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= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= google.golang.org/api v0.118.0/go.mod h1:76TtD3vkgmZ66zZzp72bUUklpmQmKlhh6sYtIjYK+5E= google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= google.golang.org/api v0.124.0/go.mod h1:xu2HQurE5gi/3t1aFCvhPD781p0a3p11sdunTJ2BlP4= google.golang.org/api v0.125.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750= 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/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= 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-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/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-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/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/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/genproto v0.0.0-20230525234025-438c736192d0/go.mod h1:9ExIQyXL5hZrHzQceCwuSYwZZ5QZBazOcprJ5rgs3lY= google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk= google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk= google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= google.golang.org/genproto v0.0.0-20230629202037-9506855d4529/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:O9kGHb51iE/nOGvQaDUuadVYqovW56s5emA88lQnj6Y= google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0= google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:0ggbjUrZYpy1q+ANUS30SEoGZ53cdfwtbuG7Ptgy108= google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8= google.golang.org/genproto v0.0.0-20230821184602-ccc8af3d0e93/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13 h1:vlzZttNJGVqTsRFU9AmdnrcO1Znh8Ew9kCD//yjigk0= google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:CCviP9RmpZ1mxVr8MUjCnSiY09IbAXZxhLE6EhHIdPU= google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:mPBs5jNgx2GuQGvFwUvVKqtn6HsUw9nP64BedgvqEsQ= google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5/go.mod h1:5DZzOUPCLYL3mNkQ0ms0F3EuUNZ7py1Bqeq6sxzI7/Q= google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.mod h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:8mL13HKkDa+IuJ8yruA3ci0q+0vsUz4m//+ottjwS5o= google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I= google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/genproto/googleapis/rpc v0.0.0-20230920183334-c177e329c48b/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0= google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 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.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= 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/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.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/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/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/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= 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/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/pg.v5 v5.3.3 h1:WGfb/lVyakKS3dFgAu5KOab/vHrNZYHPbLYkjKQuVJo= gopkg.in/pg.v5 v5.3.3/go.mod h1:A7QNerFceY0s3tPuavny+PCy6m9RQ9LrsF3onyQVvNc= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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/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/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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= 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/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= k8s.io/api v0.22.5/go.mod h1:mEhXyLaSD1qTOf40rRiKXkc+2iCem09rWLlFwhCEiAs= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= k8s.io/apimachinery v0.22.5/go.mod h1:xziclGKwuuJ2RM5/rSFQSYAj0zdbci3DH8kj+WvyN0U= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= k8s.io/apiserver v0.22.5/go.mod h1:s2WbtgZAkTKt679sYtSudEQrTGWUSQAPe6MupLnlmaQ= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= k8s.io/client-go v0.22.5/go.mod h1:cs6yf/61q2T1SdQL5Rdcjg9J1ElXSwbjSrW2vFImM4Y= k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= k8s.io/component-base v0.22.5/go.mod h1:VK3I+TjuF9eaa+Ln67dKxhGar5ynVbwnGrUiNF4MqCI= k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= k8s.io/cri-api v0.23.1/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4= k8s.io/cri-api v0.25.0/go.mod h1:J1rAyQkSJ2Q6I+aBMOVgg2/cbbebso6FNa0UagiR0kc= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= 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.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= modernc.org/ccgo/v3 v3.0.0-20220904174949-82d86e1b6d56/go.mod h1:YSXjPL62P2AMSxBphRHPn7IkzhVHqkvOnRKAKh+W6ZI= modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g= modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= modernc.org/libc v1.17.4/go.mod h1:WNg2ZH56rDEwdropAJeZPQkXmDwh+JCA1s/htl6r2fA= modernc.org/libc v1.18.0/go.mod h1:vj6zehR5bfc98ipowQOM2nIDUZnVew/wNC/2tOGS+q0= modernc.org/libc v1.20.3/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= modernc.org/sqlite v1.18.2/go.mod h1:kvrTLEWgxUcHa2GfHBQtanR1H9ht3hTJNtKpzH9k1u0= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= modernc.org/tcl v1.13.2/go.mod h1:7CLiGIPo1M8Rv1Mitpv5akc2+8fxUd2y2UzC/MfMzy0= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 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/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= fever-1.3.7/input/000077500000000000000000000000001477766221000140045ustar00rootroot00000000000000fever-1.3.7/input/input.go000066400000000000000000000004011477766221000154650ustar00rootroot00000000000000package input // DCSO FEVER // Copyright (c) 2017, DCSO GmbH // Input is an interface describing the behaviour for a component to // handle events parsed from EVE input. type Input interface { GetName() string Run() SetVerbose(bool) Stop(chan bool) } fever-1.3.7/input/input_redis.go000066400000000000000000000167131477766221000166700ustar00rootroot00000000000000package input // DCSO FEVER // Copyright (c) 2017, DCSO GmbH import ( "io" "sync" "time" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" "github.com/gomodule/redigo/redis" log "github.com/sirupsen/logrus" ) var perfStatsSendInterval = 10 * time.Second var backOffTime = 500 * time.Millisecond // RedisInputPerfStats contains performance stats written to InfluxDB // for monitoring. type RedisInputPerfStats struct { RedisQueueLength uint64 `influx:"redis_queue_length"` } // RedisInput is an Input reading JSON EVE input from Redis list. type RedisInput struct { EventChan chan types.Entry Verbose bool Running bool Pool *redis.Pool StopChan chan bool StoppedChan chan bool Addr string Proto string Reconnecting bool ParseWorkers int BatchSize int PerfStats RedisInputPerfStats StatsEncoder *util.PerformanceStatsEncoder UsePipelining bool } // GetName returns a printable name for the input func (ri *RedisInput) GetName() string { return "Redis input" } func doParseJSON(inchan chan []byte, outchan chan types.Entry, wg *sync.WaitGroup) { defer wg.Done() log.Info("started parse worker") for v := range inchan { e, err := util.ParseJSON(v) if err != nil { log.Warn(err, v) continue } outchan <- e } } func (ri *RedisInput) popPipeline(wg *sync.WaitGroup, stopChan chan bool, parseChan chan []byte) { var err error defer wg.Done() var skipLogs = false for { select { case <-stopChan: return default: conn := ri.Pool.Get() err = conn.Send("MULTI") if err != nil { if !skipLogs { log.Warnf("MULTI error %s, backing off (%v) and disabling further warnings", err.Error(), backOffTime) skipLogs = true } conn.Close() time.Sleep(backOffTime) continue } else { if skipLogs { skipLogs = false log.Warnf("MULTI succeeded, showing warnings again") } } for i := 0; i < ri.BatchSize; i++ { err = conn.Send("RPOP", "suricata") if err != nil { if !skipLogs { log.Warnf("RPOP error %s, backing off (%v) and disabling further warnings", err.Error(), backOffTime) skipLogs = true } conn.Close() time.Sleep(backOffTime) break } else { if skipLogs { skipLogs = false log.Warnf("RPOP sending succeeded, showing warnings again") } } } r, err := redis.Values(conn.Do("EXEC")) if err != nil { if !skipLogs { log.Warnf("EXEC error %s, backing off (%v) and disabling further warnings", err.Error(), backOffTime) skipLogs = true } conn.Close() continue } else { if skipLogs { skipLogs = false log.Warnf("EXEC sending succeeded, showing warnings again") } } conn.Close() for i, v := range r { if v == nil { if i == 0 { log.Debugf("empty result received, backing off (%v)", backOffTime) time.Sleep(backOffTime) } conn.Close() break } else { parseChan <- v.([]byte) } } conn.Close() } } } func (ri *RedisInput) noPipePop(wg *sync.WaitGroup, stopChan chan bool, parseChan chan []byte) { conn := ri.Pool.Get() defer wg.Done() defer conn.Close() for { select { case <-stopChan: return default: vals, err := redis.Values(conn.Do("BRPOP", "suricata", "1")) if vals != nil && err == nil && len(vals) > 0 { parseChan <- vals[1].([]byte) } else { time.Sleep(backOffTime) if err.Error() != "redigo: nil returned" && err != io.EOF { log.Warn(err) conn = ri.Pool.Get() } } } } } func (ri *RedisInput) handleServerConnection() { var wg sync.WaitGroup var parsewg sync.WaitGroup parseChan := make(chan []byte) pipelineStopChan := make(chan bool) for i := 0; i < ri.ParseWorkers; i++ { parsewg.Add(1) go doParseJSON(parseChan, ri.EventChan, &parsewg) } if ri.UsePipelining { wg.Add(1) go ri.popPipeline(&wg, pipelineStopChan, parseChan) } else { log.Info("Not using Redis pipelining.") wg.Add(3) go ri.noPipePop(&wg, pipelineStopChan, parseChan) go ri.noPipePop(&wg, pipelineStopChan, parseChan) go ri.noPipePop(&wg, pipelineStopChan, parseChan) } wg.Add(1) go ri.sendPerfStats(&wg) <-ri.StopChan close(pipelineStopChan) wg.Wait() close(parseChan) parsewg.Wait() close(ri.StoppedChan) } func (ri *RedisInput) sendPerfStats(wg *sync.WaitGroup) { defer wg.Done() start := time.Now() for { conn := ri.Pool.Get() select { case <-ri.StopChan: conn.Close() return default: if time.Since(start) > perfStatsSendInterval { if ri.StatsEncoder != nil { r, err := conn.Do("LLEN", "suricata") if err != nil { if err == io.EOF { conn.Close() time.Sleep(perfStatsSendInterval) continue } else { log.Warnf("error retrieving Redis list length: %s", err.Error()) } } else { ri.PerfStats.RedisQueueLength, err = redis.Uint64(r, err) if err == nil { ri.StatsEncoder.Submit(ri.PerfStats) } } } start = time.Now() } time.Sleep(1 * time.Second) } conn.Close() } } // MakeRedisInput returns a new RedisInput, where the string parameter denotes a // hostname:port combination. func MakeRedisInput(addr string, outChan chan types.Entry, batchSize int) (*RedisInput, error) { var err error ri := &RedisInput{ EventChan: outChan, Verbose: false, StopChan: make(chan bool), Addr: addr, Proto: "tcp", ParseWorkers: 3, BatchSize: batchSize, Pool: &redis.Pool{ MaxIdle: 5, IdleTimeout: 240 * time.Second, Dial: func() (redis.Conn, error) { c, err := redis.Dial("tcp", addr) if err != nil { return nil, err } log.Infof("Dialing %s... result: %v", addr, err == nil) return c, err }, TestOnBorrow: func(c redis.Conn, t time.Time) error { _, err := c.Do("PING") return err }, }, } return ri, err } // MakeRedisInputSocket returns a new RedisInput, where string parameter // denotes a socket. func MakeRedisInputSocket(addr string, outChan chan types.Entry, batchSize int) (*RedisInput, error) { var err error ri := &RedisInput{ EventChan: outChan, Verbose: false, StopChan: make(chan bool), Addr: addr, Proto: "unix", ParseWorkers: 3, BatchSize: batchSize, Pool: &redis.Pool{ MaxIdle: 5, IdleTimeout: 240 * time.Second, Dial: func() (redis.Conn, error) { c, err := redis.Dial("unix", addr) if err != nil { return nil, err } log.Infof("Dialing %s... result: %v", addr, err == nil) return c, err }, TestOnBorrow: func(c redis.Conn, t time.Time) error { _, err := c.Do("PING") if err != nil { log.Println(err) } return err }, }, } return ri, err } // SubmitStats registers a PerformanceStatsEncoder for runtime stats submission. func (ri *RedisInput) SubmitStats(sc *util.PerformanceStatsEncoder) { ri.StatsEncoder = sc } // Run starts the RedisInput func (ri *RedisInput) Run() { if !ri.Running { ri.Running = true ri.StopChan = make(chan bool) go ri.handleServerConnection() } } // Stop causes the RedisInput to stop reading from the Redis list and close all // associated channels, including the passed notification channel. func (ri *RedisInput) Stop(stoppedChan chan bool) { if ri.Running { ri.StoppedChan = stoppedChan ri.StopChan <- true close(ri.StopChan) ri.Pool.Close() ri.Running = false } } // SetVerbose sets the input's verbosity level func (ri *RedisInput) SetVerbose(verbose bool) { ri.Verbose = verbose } fever-1.3.7/input/input_redis_test.go000066400000000000000000000124241477766221000177220ustar00rootroot00000000000000package input // DCSO FEVER // Copyright (c) 2017, 2019, DCSO GmbH import ( "encoding/json" "fmt" "io/ioutil" "math/rand" "os" "path/filepath" "sort" "sync" "testing" "time" "github.com/DCSO/fever/types" "github.com/gomodule/redigo/redis" log "github.com/sirupsen/logrus" "github.com/stvp/tempredis" ) const nofRedisTests = 10000 func makeEveEvent(etype string, number int) string { eve := types.EveEvent{ EventType: etype, FlowID: int64(number), SrcIP: fmt.Sprintf("10.0.0.%d", number), SrcPort: []int{11, 12, 13, 14, 15}[rand.Intn(5)], DestIP: fmt.Sprintf("10.0.0.%d", rand.Intn(50)), DestPort: []int{11, 12, 13, 14, 15}[rand.Intn(5)], Proto: []string{"TCP", "UDP"}[rand.Intn(2)], } json, err := json.Marshal(eve) if err != nil { panic(err) } return string(json) } type byID []types.Entry func (a byID) Len() int { return len(a) } func (a byID) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a byID) Less(i, j int) bool { var ie, je types.EveEvent err := json.Unmarshal([]byte(a[i].JSONLine), &ie) if err != nil { log.Fatal(err) } err = json.Unmarshal([]byte(a[j].JSONLine), &je) if err != nil { log.Fatal(err) } return ie.FlowID < je.FlowID } func _TestRedisInput(t *testing.T, usePipelining bool, sock string) { s, err := tempredis.Start(tempredis.Config{ "unixsocket": sock, }) if err != nil { t.Fatal(err) } defer s.Term() client, err := redis.Dial("unix", s.Socket()) if err != nil { t.Fatal(err) } defer client.Close() events := make([]string, nofRedisTests) var wg sync.WaitGroup wg.Add(1) go func(myWg *sync.WaitGroup) { defer myWg.Done() for i := 0; i < nofRedisTests; i++ { events[i] = makeEveEvent([]string{"http", "dns", "foo"}[rand.Intn(3)], i) client.Do("LPUSH", "suricata", events[i]) } }(&wg) wg.Wait() evChan := make(chan types.Entry) coll := make([]types.Entry, 0) wg.Add(1) go func(myWg *sync.WaitGroup) { defer myWg.Done() i := 0 for e := range evChan { coll = append(coll, e) if i == nofRedisTests-1 { return } i++ } }(&wg) ri, err := MakeRedisInputSocket(s.Socket(), evChan, 500) ri.UsePipelining = usePipelining if err != nil { t.Fatal(err) } ri.Run() wg.Wait() stopChan := make(chan bool) ri.Stop(stopChan) <-stopChan close(evChan) sort.Sort(byID(coll)) if len(coll) != nofRedisTests { t.Fatalf("unexpected number of items read from Redis queue: %d != %d", len(coll), nofRedisTests) } for i := 0; i < nofRedisTests; i++ { var checkEvent types.EveEvent err := json.Unmarshal([]byte(events[i]), &checkEvent) if err != nil { t.Fatal(err) } if coll[i].EventType != checkEvent.EventType { t.Fatalf("wrong event type for test event %d: %s != %s", i, coll[i].EventType, checkEvent.EventType) } } } func TestRedisInputWithPipelining(t *testing.T) { dir, err := ioutil.TempDir("", "test") if err != nil { log.Fatal(err) } defer os.RemoveAll(dir) tmpfn := filepath.Join(dir, "withPipe.sock") _TestRedisInput(t, true, tmpfn) } func TestRedisInputNoPipelining(t *testing.T) { dir, err := ioutil.TempDir("", "test") if err != nil { log.Fatal(err) } defer os.RemoveAll(dir) tmpfn := filepath.Join(dir, "withPipe.sock") _TestRedisInput(t, false, tmpfn) } func _TestRedisGone(t *testing.T, usePipelining bool, sock string) { s, err := tempredis.Start(tempredis.Config{ "unixsocket": sock, }) if err != nil { t.Fatal(err) } evChan := make(chan types.Entry) ri, err := MakeRedisInputSocket(s.Socket(), evChan, 500) ri.UsePipelining = usePipelining if err != nil { t.Fatal(err) } ri.Run() time.Sleep(2 * time.Second) s.Term() s, err = tempredis.Start(tempredis.Config{ "unixsocket": sock, }) if err != nil { t.Fatal(err) } client, err := redis.Dial("unix", s.Socket()) if err != nil { t.Fatal(err) } defer client.Close() events := make([]string, nofRedisTests) var wg sync.WaitGroup go func() { for i := 0; i < nofRedisTests; i++ { events[i] = makeEveEvent([]string{"http", "dns", "foo"}[rand.Intn(3)], i) client.Do("LPUSH", "suricata", events[i]) } }() coll := make([]types.Entry, 0) wg.Add(1) go func(myWg *sync.WaitGroup) { defer myWg.Done() i := 0 for e := range evChan { coll = append(coll, e) if i == nofRedisTests-1 { return } i++ } }(&wg) wg.Wait() stopChan := make(chan bool) ri.Stop(stopChan) <-stopChan close(evChan) sort.Sort(byID(coll)) if len(coll) != nofRedisTests { t.Fatalf("unexpected number of items read from Redis queue: %d != %d", len(coll), nofRedisTests) } for i := 0; i < nofRedisTests; i++ { var checkEvent types.EveEvent err := json.Unmarshal([]byte(events[i]), &checkEvent) if err != nil { t.Fatal(err) } if coll[i].EventType != checkEvent.EventType { t.Fatalf("wrong event type for test event %d: %s != %s", i, coll[i].EventType, checkEvent.EventType) } } } func TestRedisGoneWithPipelining(t *testing.T) { dir, err := ioutil.TempDir("", "test") if err != nil { log.Fatal(err) } defer os.RemoveAll(dir) tmpfn := filepath.Join(dir, "withPipe.sock") _TestRedisGone(t, true, tmpfn) } func TestRedisGoneNoPipelining(t *testing.T) { dir, err := ioutil.TempDir("", "test") if err != nil { log.Fatal(err) } defer os.RemoveAll(dir) tmpfn := filepath.Join(dir, "withPipe.sock") _TestRedisGone(t, false, tmpfn) } fever-1.3.7/input/input_socket.go000066400000000000000000000106701477766221000170460ustar00rootroot00000000000000package input // DCSO FEVER // Copyright (c) 2017, DCSO GmbH import ( "bufio" "net" "time" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" log "github.com/sirupsen/logrus" ) // SocketInputPerfStats contains performance stats written to InfluxDB // for monitoring. type SocketInputPerfStats struct { SocketQueueLength uint64 `influx:"input_queue_length"` SocketQueueDropped uint64 `influx:"input_queue_dropped"` } // SocketInput is an Input reading JSON EVE input from a Unix socket. type SocketInput struct { EventChan chan types.Entry Verbose bool Running bool InputListener net.Listener StopChan chan bool StoppedChan chan bool DropIfChannelFull bool PerfStats SocketInputPerfStats StatsEncoder *util.PerformanceStatsEncoder } // GetName returns a printable name for the input func (si *SocketInput) GetName() string { return "Socket input" } func (si *SocketInput) handleServerConnection() { for { select { case <-si.StopChan: close(si.StoppedChan) return default: var start time.Time var totalLen int si.InputListener.(*net.UnixListener).SetDeadline(time.Now().Add(1e9)) c, err := si.InputListener.Accept() if nil != err { if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() { continue } log.Info(err) } if si.Verbose { start = time.Now() } scanner := bufio.NewScanner(c) buf := make([]byte, 0, 32*1024*1024) scanner.Buffer(buf, 32*1024*1024) for { for scanner.Scan() { select { case <-si.StopChan: close(si.StoppedChan) return default: json := scanner.Bytes() totalLen += len(json) e, err := util.ParseJSON(json) if err != nil { log.Warn(err, string(json[:])) continue } if si.DropIfChannelFull { select { case si.EventChan <- e: // pass default: si.PerfStats.SocketQueueDropped++ } } else { si.EventChan <- e } } } errRead := scanner.Err() if errRead == nil { break } else if errRead == bufio.ErrTooLong { log.Warn(errRead) scanner = bufio.NewScanner(c) scanner.Buffer(buf, 2*cap(buf)) } else { log.Warn(errRead) } } if si.Verbose { elapsed := time.Since(start) log.WithFields(log.Fields{ "size": totalLen, "elapsedTime": elapsed, }).Info("connection handled") } } } } func (si *SocketInput) sendPerfStats() { start := time.Now() for { select { case <-si.StopChan: return default: // We briefly wake up once a second to check whether we are asked // to stop or whether it's time to submit stats. This is neglegible // in overhead but massively improves shutdown time, as a simple // time.Sleep() is non-interruptible by the stop channel. if time.Since(start) > perfStatsSendInterval { if si.StatsEncoder != nil { si.PerfStats.SocketQueueLength = uint64(len(si.EventChan)) si.StatsEncoder.Submit(si.PerfStats) } start = time.Now() } time.Sleep(1 * time.Second) } } } // MakeSocketInput returns a new SocketInput reading from the Unix socket // inputSocket and writing parsed events to outChan. If no such socket could be // created for listening, the error returned is set accordingly. func MakeSocketInput(inputSocket string, outChan chan types.Entry, bufDrop bool) (*SocketInput, error) { var err error si := &SocketInput{ EventChan: outChan, Verbose: false, StopChan: make(chan bool), DropIfChannelFull: bufDrop, } si.InputListener, err = net.Listen("unix", inputSocket) if err != nil { return nil, err } return si, err } // SubmitStats registers a PerformanceStatsEncoder for runtime stats submission. func (si *SocketInput) SubmitStats(sc *util.PerformanceStatsEncoder) { si.StatsEncoder = sc } // Run starts the SocketInput func (si *SocketInput) Run() { if !si.Running { si.Running = true si.StopChan = make(chan bool) go si.handleServerConnection() go si.sendPerfStats() } } // Stop causes the SocketInput to stop reading from the socket and close all // associated channels, including the passed notification channel. func (si *SocketInput) Stop(stoppedChan chan bool) { if si.Running { si.StoppedChan = stoppedChan close(si.StopChan) si.Running = false } } // SetVerbose sets the input's verbosity level func (si *SocketInput) SetVerbose(verbose bool) { si.Verbose = verbose } fever-1.3.7/input/input_socket_test.go000066400000000000000000000030341477766221000201010ustar00rootroot00000000000000package input // DCSO FEVER // Copyright (c) 2017, DCSO GmbH import ( "encoding/json" "fmt" "io/ioutil" "math/rand" "net" "os" "path/filepath" "testing" "github.com/DCSO/fever/types" log "github.com/sirupsen/logrus" ) func TestSocketInput(t *testing.T) { dir, err := ioutil.TempDir("", "test") if err != nil { t.Fatal(err) } defer os.RemoveAll(dir) tmpfn := filepath.Join(dir, fmt.Sprintf("t%d", rand.Int63())) evChan := make(chan types.Entry) events := make([]string, 1000) is, err := MakeSocketInput(tmpfn, evChan, false) if err != nil { t.Fatal(err) } is.Run() submitDone := make(chan bool) collectDone := make(chan bool) go func() { c, err := net.Dial("unix", tmpfn) if err != nil { log.Println(err) } for i := 0; i < 1000; i++ { events[i] = makeEveEvent([]string{"http", "dns", "foo"}[rand.Intn(3)], i) c.Write([]byte(events[i])) c.Write([]byte("\n")) } c.Close() close(submitDone) }() coll := make([]types.Entry, 0) go func() { for i := 0; i < 1000; i++ { e := <-evChan coll = append(coll, e) } close(collectDone) }() <-submitDone <-collectDone ch := make(chan bool) is.Stop(ch) <-ch if len(coll) != 1000 { t.Fatalf("unexpected number of items read from socket: %d != 1000", len(coll)) } for i := 0; i < 1000; i++ { var checkEvent types.EveEvent json.Unmarshal([]byte(events[i]), &checkEvent) if coll[i].EventType != checkEvent.EventType { t.Fatalf("wrong event type for test event %d: %s != %s", i, coll[i].EventType, checkEvent.EventType) } } } fever-1.3.7/input/input_stdin.go000066400000000000000000000032251477766221000166750ustar00rootroot00000000000000package input // DCSO FEVER // Copyright (c) 2020, 2023, DCSO GmbH import ( "bufio" "net" "os" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" log "github.com/sirupsen/logrus" ) // StdinInput is an Input reading JSON EVE input from standard input. type StdinInput struct { EventChan chan types.Entry Verbose bool Running bool InputListener net.Listener StopChan chan bool StoppedChan chan bool } // GetName returns a printable name for the input func (si *StdinInput) GetName() string { return "Stdin input" } func (si *StdinInput) handleStdinStream() { scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { json := scanner.Bytes() e, err := util.ParseJSON(json) if err != nil { log.Error(err, string(json[:])) continue } si.EventChan <- e } close(si.EventChan) } // MakeStdinInput returns a new StdinInput reading from stdin and writing // parsed events to outChan. func MakeStdinInput(outChan chan types.Entry) *StdinInput { si := &StdinInput{ EventChan: outChan, Verbose: false, StopChan: make(chan bool), } return si } // Run starts the StdinInput func (si *StdinInput) Run() { if !si.Running { si.Running = true si.StopChan = make(chan bool) go si.handleStdinStream() } } // Stop causes the StdinInput to stop reading from stdin and close all // associated channels, including the passed notification channel. func (si *StdinInput) Stop(stoppedChan chan bool) { if si.Running { si.StoppedChan = stoppedChan si.Running = false close(stoppedChan) } } // SetVerbose sets the input's verbosity level func (si *StdinInput) SetVerbose(verbose bool) { si.Verbose = verbose } fever-1.3.7/mgmt/000077500000000000000000000000001477766221000136115ustar00rootroot00000000000000fever-1.3.7/mgmt/endpointconfig.go000066400000000000000000000033141477766221000171470ustar00rootroot00000000000000package mgmt // DCSO FEVER // Copyright (c) 2021, DCSO GmbH import ( "crypto/tls" fmt "fmt" "github.com/spf13/viper" "google.golang.org/grpc" ) // EndpointConfig ... type EndpointConfig struct { ListenerAddress string ServerAddress string Network string Params map[string]interface{} TLSConfig *tls.Config TLSDisable bool Disable bool } // GRPCEndpointConfig ... type GRPCEndpointConfig struct { EndpointConfig ServerOptions []grpc.ServerOption DialOptions []grpc.DialOption } // EndpointConfigFromViper creates a new GRPCEndpointConfig from the relevant // Viper configs func EndpointConfigFromViper() GRPCEndpointConfig { var mgmtCfg GRPCEndpointConfig host := viper.GetString("mgmt.host") network := viper.GetString("mgmt.network") socket := viper.GetString("mgmt.socket") if host != "" { mgmtCfg = GRPCEndpointConfig{ EndpointConfig: EndpointConfig{ Network: network, ListenerAddress: host, ServerAddress: host, TLSDisable: true, // XXX we may choose to support TLS eventually }, DialOptions: []grpc.DialOption{grpc.WithInsecure()}, } } else { mgmtCfg = GRPCEndpointConfig{ EndpointConfig: EndpointConfig{ Network: "unix", ListenerAddress: socket, }, DialOptions: []grpc.DialOption{grpc.WithInsecure()}, } } return mgmtCfg } // DialString returns a string from the given config that is suitable to be // passed into a grpc.Dial() function. func (e GRPCEndpointConfig) DialString() string { if e.EndpointConfig.Network == "unix" { return fmt.Sprintf("%s:%s", e.EndpointConfig.Network, e.EndpointConfig.ListenerAddress) } return fmt.Sprintf("dns:///%s", e.EndpointConfig.ListenerAddress) } fever-1.3.7/mgmt/mgmt.pb.go000066400000000000000000000610331477766221000155070ustar00rootroot00000000000000// Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.26.0 // protoc v3.12.4 // source: mgmt.proto package mgmt import ( context "context" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" emptypb "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) type MgmtBloomInfoResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields HasBloom bool `protobuf:"varint,1,opt,name=has_bloom,json=hasBloom,proto3" json:"has_bloom,omitempty"` Capacity uint64 `protobuf:"varint,2,opt,name=capacity,proto3" json:"capacity,omitempty"` Elements uint64 `protobuf:"varint,3,opt,name=elements,proto3" json:"elements,omitempty"` Bits uint64 `protobuf:"varint,4,opt,name=bits,proto3" json:"bits,omitempty"` Hashfuncs uint64 `protobuf:"varint,5,opt,name=hashfuncs,proto3" json:"hashfuncs,omitempty"` Fpprob float64 `protobuf:"fixed64,6,opt,name=fpprob,proto3" json:"fpprob,omitempty"` } func (x *MgmtBloomInfoResponse) Reset() { *x = MgmtBloomInfoResponse{} if protoimpl.UnsafeEnabled { mi := &file_mgmt_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *MgmtBloomInfoResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*MgmtBloomInfoResponse) ProtoMessage() {} func (x *MgmtBloomInfoResponse) ProtoReflect() protoreflect.Message { mi := &file_mgmt_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MgmtBloomInfoResponse.ProtoReflect.Descriptor instead. func (*MgmtBloomInfoResponse) Descriptor() ([]byte, []int) { return file_mgmt_proto_rawDescGZIP(), []int{0} } func (x *MgmtBloomInfoResponse) GetHasBloom() bool { if x != nil { return x.HasBloom } return false } func (x *MgmtBloomInfoResponse) GetCapacity() uint64 { if x != nil { return x.Capacity } return 0 } func (x *MgmtBloomInfoResponse) GetElements() uint64 { if x != nil { return x.Elements } return 0 } func (x *MgmtBloomInfoResponse) GetBits() uint64 { if x != nil { return x.Bits } return 0 } func (x *MgmtBloomInfoResponse) GetHashfuncs() uint64 { if x != nil { return x.Hashfuncs } return 0 } func (x *MgmtBloomInfoResponse) GetFpprob() float64 { if x != nil { return x.Fpprob } return 0 } type MgmtAliveRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Alive string `protobuf:"bytes,1,opt,name=alive,proto3" json:"alive,omitempty"` } func (x *MgmtAliveRequest) Reset() { *x = MgmtAliveRequest{} if protoimpl.UnsafeEnabled { mi := &file_mgmt_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *MgmtAliveRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*MgmtAliveRequest) ProtoMessage() {} func (x *MgmtAliveRequest) ProtoReflect() protoreflect.Message { mi := &file_mgmt_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MgmtAliveRequest.ProtoReflect.Descriptor instead. func (*MgmtAliveRequest) Descriptor() ([]byte, []int) { return file_mgmt_proto_rawDescGZIP(), []int{1} } func (x *MgmtAliveRequest) GetAlive() string { if x != nil { return x.Alive } return "" } type MgmtAliveResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Echo string `protobuf:"bytes,1,opt,name=echo,proto3" json:"echo,omitempty"` } func (x *MgmtAliveResponse) Reset() { *x = MgmtAliveResponse{} if protoimpl.UnsafeEnabled { mi := &file_mgmt_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *MgmtAliveResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*MgmtAliveResponse) ProtoMessage() {} func (x *MgmtAliveResponse) ProtoReflect() protoreflect.Message { mi := &file_mgmt_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MgmtAliveResponse.ProtoReflect.Descriptor instead. func (*MgmtAliveResponse) Descriptor() ([]byte, []int) { return file_mgmt_proto_rawDescGZIP(), []int{2} } func (x *MgmtAliveResponse) GetEcho() string { if x != nil { return x.Echo } return "" } type MgmtBloomAddRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Ioc string `protobuf:"bytes,1,opt,name=ioc,proto3" json:"ioc,omitempty"` } func (x *MgmtBloomAddRequest) Reset() { *x = MgmtBloomAddRequest{} if protoimpl.UnsafeEnabled { mi := &file_mgmt_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *MgmtBloomAddRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*MgmtBloomAddRequest) ProtoMessage() {} func (x *MgmtBloomAddRequest) ProtoReflect() protoreflect.Message { mi := &file_mgmt_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MgmtBloomAddRequest.ProtoReflect.Descriptor instead. func (*MgmtBloomAddRequest) Descriptor() ([]byte, []int) { return file_mgmt_proto_rawDescGZIP(), []int{3} } func (x *MgmtBloomAddRequest) GetIoc() string { if x != nil { return x.Ioc } return "" } type MgmtBloomAddResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Added uint64 `protobuf:"varint,1,opt,name=added,proto3" json:"added,omitempty"` } func (x *MgmtBloomAddResponse) Reset() { *x = MgmtBloomAddResponse{} if protoimpl.UnsafeEnabled { mi := &file_mgmt_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *MgmtBloomAddResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*MgmtBloomAddResponse) ProtoMessage() {} func (x *MgmtBloomAddResponse) ProtoReflect() protoreflect.Message { mi := &file_mgmt_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use MgmtBloomAddResponse.ProtoReflect.Descriptor instead. func (*MgmtBloomAddResponse) Descriptor() ([]byte, []int) { return file_mgmt_proto_rawDescGZIP(), []int{4} } func (x *MgmtBloomAddResponse) GetAdded() uint64 { if x != nil { return x.Added } return 0 } var File_mgmt_proto protoreflect.FileDescriptor var file_mgmt_proto_rawDesc = []byte{ 0x0a, 0x0a, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x6d, 0x67, 0x6d, 0x74, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb6, 0x01, 0x0a, 0x15, 0x4d, 0x67, 0x6d, 0x74, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x68, 0x61, 0x73, 0x5f, 0x62, 0x6c, 0x6f, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x68, 0x61, 0x73, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x69, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x62, 0x69, 0x74, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x68, 0x61, 0x73, 0x68, 0x66, 0x75, 0x6e, 0x63, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x68, 0x61, 0x73, 0x68, 0x66, 0x75, 0x6e, 0x63, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x70, 0x70, 0x72, 0x6f, 0x62, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x66, 0x70, 0x70, 0x72, 0x6f, 0x62, 0x22, 0x28, 0x0a, 0x10, 0x4d, 0x67, 0x6d, 0x74, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x22, 0x27, 0x0a, 0x11, 0x4d, 0x67, 0x6d, 0x74, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x65, 0x63, 0x68, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x65, 0x63, 0x68, 0x6f, 0x22, 0x27, 0x0a, 0x13, 0x4d, 0x67, 0x6d, 0x74, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x6f, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x69, 0x6f, 0x63, 0x22, 0x2c, 0x0a, 0x14, 0x4d, 0x67, 0x6d, 0x74, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x61, 0x64, 0x64, 0x65, 0x64, 0x32, 0xca, 0x02, 0x0a, 0x0b, 0x4d, 0x67, 0x6d, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x38, 0x0a, 0x05, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x16, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x4d, 0x67, 0x6d, 0x74, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x4d, 0x67, 0x6d, 0x74, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x09, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1b, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x4d, 0x67, 0x6d, 0x74, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x08, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x64, 0x12, 0x19, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x4d, 0x67, 0x6d, 0x74, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x4d, 0x67, 0x6d, 0x74, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x12, 0x3b, 0x0a, 0x09, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x53, 0x61, 0x76, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3d, 0x0a, 0x0b, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x1c, 0x5a, 0x1a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x43, 0x53, 0x4f, 0x2f, 0x66, 0x65, 0x76, 0x65, 0x72, 0x2f, 0x6d, 0x67, 0x6d, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_mgmt_proto_rawDescOnce sync.Once file_mgmt_proto_rawDescData = file_mgmt_proto_rawDesc ) func file_mgmt_proto_rawDescGZIP() []byte { file_mgmt_proto_rawDescOnce.Do(func() { file_mgmt_proto_rawDescData = protoimpl.X.CompressGZIP(file_mgmt_proto_rawDescData) }) return file_mgmt_proto_rawDescData } var file_mgmt_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_mgmt_proto_goTypes = []interface{}{ (*MgmtBloomInfoResponse)(nil), // 0: mgmt.MgmtBloomInfoResponse (*MgmtAliveRequest)(nil), // 1: mgmt.MgmtAliveRequest (*MgmtAliveResponse)(nil), // 2: mgmt.MgmtAliveResponse (*MgmtBloomAddRequest)(nil), // 3: mgmt.MgmtBloomAddRequest (*MgmtBloomAddResponse)(nil), // 4: mgmt.MgmtBloomAddResponse (*emptypb.Empty)(nil), // 5: google.protobuf.Empty } var file_mgmt_proto_depIdxs = []int32{ 1, // 0: mgmt.MgmtService.Alive:input_type -> mgmt.MgmtAliveRequest 5, // 1: mgmt.MgmtService.BloomInfo:input_type -> google.protobuf.Empty 3, // 2: mgmt.MgmtService.BloomAdd:input_type -> mgmt.MgmtBloomAddRequest 5, // 3: mgmt.MgmtService.BloomSave:input_type -> google.protobuf.Empty 5, // 4: mgmt.MgmtService.BloomReload:input_type -> google.protobuf.Empty 2, // 5: mgmt.MgmtService.Alive:output_type -> mgmt.MgmtAliveResponse 0, // 6: mgmt.MgmtService.BloomInfo:output_type -> mgmt.MgmtBloomInfoResponse 4, // 7: mgmt.MgmtService.BloomAdd:output_type -> mgmt.MgmtBloomAddResponse 5, // 8: mgmt.MgmtService.BloomSave:output_type -> google.protobuf.Empty 5, // 9: mgmt.MgmtService.BloomReload:output_type -> google.protobuf.Empty 5, // [5:10] is the sub-list for method output_type 0, // [0:5] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_mgmt_proto_init() } func file_mgmt_proto_init() { if File_mgmt_proto != nil { return } if !protoimpl.UnsafeEnabled { file_mgmt_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MgmtBloomInfoResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_mgmt_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MgmtAliveRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_mgmt_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MgmtAliveResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_mgmt_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MgmtBloomAddRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_mgmt_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*MgmtBloomAddResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_mgmt_proto_rawDesc, NumEnums: 0, NumMessages: 5, NumExtensions: 0, NumServices: 1, }, GoTypes: file_mgmt_proto_goTypes, DependencyIndexes: file_mgmt_proto_depIdxs, MessageInfos: file_mgmt_proto_msgTypes, }.Build() File_mgmt_proto = out.File file_mgmt_proto_rawDesc = nil file_mgmt_proto_goTypes = nil file_mgmt_proto_depIdxs = nil } // Reference imports to suppress errors if they are not otherwise used. var _ context.Context var _ grpc.ClientConnInterface // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. const _ = grpc.SupportPackageIsVersion6 // MgmtServiceClient is the client API for MgmtService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type MgmtServiceClient interface { Alive(ctx context.Context, in *MgmtAliveRequest, opts ...grpc.CallOption) (*MgmtAliveResponse, error) BloomInfo(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*MgmtBloomInfoResponse, error) BloomAdd(ctx context.Context, opts ...grpc.CallOption) (MgmtService_BloomAddClient, error) BloomSave(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) BloomReload(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) } type mgmtServiceClient struct { cc grpc.ClientConnInterface } func NewMgmtServiceClient(cc grpc.ClientConnInterface) MgmtServiceClient { return &mgmtServiceClient{cc} } func (c *mgmtServiceClient) Alive(ctx context.Context, in *MgmtAliveRequest, opts ...grpc.CallOption) (*MgmtAliveResponse, error) { out := new(MgmtAliveResponse) err := c.cc.Invoke(ctx, "/mgmt.MgmtService/Alive", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *mgmtServiceClient) BloomInfo(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*MgmtBloomInfoResponse, error) { out := new(MgmtBloomInfoResponse) err := c.cc.Invoke(ctx, "/mgmt.MgmtService/BloomInfo", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *mgmtServiceClient) BloomAdd(ctx context.Context, opts ...grpc.CallOption) (MgmtService_BloomAddClient, error) { stream, err := c.cc.NewStream(ctx, &_MgmtService_serviceDesc.Streams[0], "/mgmt.MgmtService/BloomAdd", opts...) if err != nil { return nil, err } x := &mgmtServiceBloomAddClient{stream} return x, nil } type MgmtService_BloomAddClient interface { Send(*MgmtBloomAddRequest) error CloseAndRecv() (*MgmtBloomAddResponse, error) grpc.ClientStream } type mgmtServiceBloomAddClient struct { grpc.ClientStream } func (x *mgmtServiceBloomAddClient) Send(m *MgmtBloomAddRequest) error { return x.ClientStream.SendMsg(m) } func (x *mgmtServiceBloomAddClient) CloseAndRecv() (*MgmtBloomAddResponse, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } m := new(MgmtBloomAddResponse) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *mgmtServiceClient) BloomSave(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { out := new(emptypb.Empty) err := c.cc.Invoke(ctx, "/mgmt.MgmtService/BloomSave", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *mgmtServiceClient) BloomReload(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { out := new(emptypb.Empty) err := c.cc.Invoke(ctx, "/mgmt.MgmtService/BloomReload", in, out, opts...) if err != nil { return nil, err } return out, nil } // MgmtServiceServer is the server API for MgmtService service. type MgmtServiceServer interface { Alive(context.Context, *MgmtAliveRequest) (*MgmtAliveResponse, error) BloomInfo(context.Context, *emptypb.Empty) (*MgmtBloomInfoResponse, error) BloomAdd(MgmtService_BloomAddServer) error BloomSave(context.Context, *emptypb.Empty) (*emptypb.Empty, error) BloomReload(context.Context, *emptypb.Empty) (*emptypb.Empty, error) } // UnimplementedMgmtServiceServer can be embedded to have forward compatible implementations. type UnimplementedMgmtServiceServer struct { } func (*UnimplementedMgmtServiceServer) Alive(context.Context, *MgmtAliveRequest) (*MgmtAliveResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Alive not implemented") } func (*UnimplementedMgmtServiceServer) BloomInfo(context.Context, *emptypb.Empty) (*MgmtBloomInfoResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method BloomInfo not implemented") } func (*UnimplementedMgmtServiceServer) BloomAdd(MgmtService_BloomAddServer) error { return status.Errorf(codes.Unimplemented, "method BloomAdd not implemented") } func (*UnimplementedMgmtServiceServer) BloomSave(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method BloomSave not implemented") } func (*UnimplementedMgmtServiceServer) BloomReload(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method BloomReload not implemented") } func RegisterMgmtServiceServer(s *grpc.Server, srv MgmtServiceServer) { s.RegisterService(&_MgmtService_serviceDesc, srv) } func _MgmtService_Alive_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(MgmtAliveRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(MgmtServiceServer).Alive(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/mgmt.MgmtService/Alive", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(MgmtServiceServer).Alive(ctx, req.(*MgmtAliveRequest)) } return interceptor(ctx, in, info, handler) } func _MgmtService_BloomInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(emptypb.Empty) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(MgmtServiceServer).BloomInfo(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/mgmt.MgmtService/BloomInfo", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(MgmtServiceServer).BloomInfo(ctx, req.(*emptypb.Empty)) } return interceptor(ctx, in, info, handler) } func _MgmtService_BloomAdd_Handler(srv interface{}, stream grpc.ServerStream) error { return srv.(MgmtServiceServer).BloomAdd(&mgmtServiceBloomAddServer{stream}) } type MgmtService_BloomAddServer interface { SendAndClose(*MgmtBloomAddResponse) error Recv() (*MgmtBloomAddRequest, error) grpc.ServerStream } type mgmtServiceBloomAddServer struct { grpc.ServerStream } func (x *mgmtServiceBloomAddServer) SendAndClose(m *MgmtBloomAddResponse) error { return x.ServerStream.SendMsg(m) } func (x *mgmtServiceBloomAddServer) Recv() (*MgmtBloomAddRequest, error) { m := new(MgmtBloomAddRequest) if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func _MgmtService_BloomSave_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(emptypb.Empty) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(MgmtServiceServer).BloomSave(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/mgmt.MgmtService/BloomSave", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(MgmtServiceServer).BloomSave(ctx, req.(*emptypb.Empty)) } return interceptor(ctx, in, info, handler) } func _MgmtService_BloomReload_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(emptypb.Empty) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(MgmtServiceServer).BloomReload(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/mgmt.MgmtService/BloomReload", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(MgmtServiceServer).BloomReload(ctx, req.(*emptypb.Empty)) } return interceptor(ctx, in, info, handler) } var _MgmtService_serviceDesc = grpc.ServiceDesc{ ServiceName: "mgmt.MgmtService", HandlerType: (*MgmtServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Alive", Handler: _MgmtService_Alive_Handler, }, { MethodName: "BloomInfo", Handler: _MgmtService_BloomInfo_Handler, }, { MethodName: "BloomSave", Handler: _MgmtService_BloomSave_Handler, }, { MethodName: "BloomReload", Handler: _MgmtService_BloomReload_Handler, }, }, Streams: []grpc.StreamDesc{ { StreamName: "BloomAdd", Handler: _MgmtService_BloomAdd_Handler, ClientStreams: true, }, }, Metadata: "mgmt.proto", } fever-1.3.7/mgmt/mgmt.proto000066400000000000000000000015501477766221000156430ustar00rootroot00000000000000syntax = "proto3"; package mgmt; import "google/protobuf/empty.proto"; option go_package = "github.com/DCSO/fever/mgmt"; message MgmtBloomInfoResponse { bool has_bloom = 1; uint64 capacity = 2; uint64 elements = 3; uint64 bits = 4; uint64 hashfuncs = 5; double fpprob = 6; } message MgmtAliveRequest { string alive = 1; } message MgmtAliveResponse { string echo = 1; } message MgmtBloomAddRequest { string ioc = 1; } message MgmtBloomAddResponse { uint64 added = 1; } service MgmtService { rpc Alive(MgmtAliveRequest) returns (MgmtAliveResponse); rpc BloomInfo(google.protobuf.Empty) returns (MgmtBloomInfoResponse); rpc BloomAdd(stream MgmtBloomAddRequest) returns (MgmtBloomAddResponse); rpc BloomSave(google.protobuf.Empty) returns (google.protobuf.Empty); rpc BloomReload(google.protobuf.Empty) returns (google.protobuf.Empty); }fever-1.3.7/mgmt/mgmtserver.go000066400000000000000000000120071477766221000163330ustar00rootroot00000000000000package mgmt // DCSO FEVER // Copyright (c) 2021, DCSO GmbH import ( context "context" "errors" "fmt" "io" "net" "os" "path/filepath" "github.com/DCSO/bloom" "github.com/sirupsen/logrus" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" emptypb "google.golang.org/protobuf/types/known/emptypb" ) const ( permSocketPath = 0750 ) type mgmtServer struct { UnimplementedMgmtServiceServer ctx context.Context Logger *logrus.Entry grpcSrv *grpc.Server cfg GRPCEndpointConfig state *State } // NewMgmtServer returns a new mamagement server instance registered with gRPC. func NewMgmtServer(parent context.Context, cfg GRPCEndpointConfig, state *State) (Server, error) { srv := &mgmtServer{ ctx: parent, cfg: cfg, state: state, Logger: logrus.StandardLogger().WithFields(logrus.Fields{ "domain": "mgmt", }), } srv.grpcSrv = grpc.NewServer(cfg.ServerOptions...) RegisterMgmtServiceServer(srv.grpcSrv, srv) return srv, nil } // Stop stops the mgmtServer. func (srv *mgmtServer) Stop() { srv.grpcSrv.GracefulStop() } // ListenAndServe starts the mgmtServer, accepting connections on the given // communication channel. func (srv *mgmtServer) ListenAndServe() (err error) { err = errors.New("ListenAndServe() can only be called once") var ln net.Listener if ln, err = net.Listen(srv.cfg.Network, srv.cfg.ListenerAddress); err != nil { srv.Logger.WithError(err).WithFields(logrus.Fields{ "network": srv.cfg.Network, "address": srv.cfg.ListenerAddress, }).Error("setting up mgmt endpoint") return } defer ln.Close() if dsln, ok := ln.(*net.UnixListener); ok { if err = os.MkdirAll(filepath.Dir(srv.cfg.ListenerAddress), permSocketPath); err != nil { srv.Logger.WithError(err).WithFields(logrus.Fields{ "path": filepath.Dir(srv.cfg.ListenerAddress), "perm_path": permSocketPath, }).Error("unable to create path") return } dsln.SetUnlinkOnClose(true) } srv.Logger.Info("gRPC mgmt service listening ...") err = srv.grpcSrv.Serve(ln) srv.Logger.Info("gRPC mgmt service stopped") return err } // // MgmtServiceServer interface // // BloomInfo implements the function to return internal status information about // the Bloom filter currently loaded in the FEVER instance. func (srv *mgmtServer) BloomInfo(ctx context.Context, _req *emptypb.Empty) (*MgmtBloomInfoResponse, error) { srv.Logger.Debug("responding to BloomInfo") var resp *MgmtBloomInfoResponse hasBloom := (srv.state.BloomHandler != nil) if hasBloom { resp = &MgmtBloomInfoResponse{ HasBloom: true, Capacity: srv.state.BloomHandler.IocBloom.MaxNumElements(), Elements: srv.state.BloomHandler.IocBloom.N, Bits: srv.state.BloomHandler.IocBloom.NumBits(), Fpprob: srv.state.BloomHandler.IocBloom.FalsePositiveProb(), Hashfuncs: srv.state.BloomHandler.IocBloom.NumHashFuncs(), } } else { resp = &MgmtBloomInfoResponse{ HasBloom: false, } } return resp, nil } // BloomAdd implements the function to add items from an incoming stream to the // Bloom filter currently loaded in the FEVER instance. func (srv *mgmtServer) BloomAdd(stream MgmtService_BloomAddServer) error { srv.Logger.Debug("responding to BloomAdd") hasBloom := (srv.state.BloomHandler != nil) if !hasBloom { return stream.SendAndClose(&MgmtBloomAddResponse{Added: 0}) } i := uint64(0) for { req, err := stream.Recv() if err != nil { if err == io.EOF { return stream.SendAndClose(&MgmtBloomAddResponse{Added: i}) } return status.Error(codes.InvalidArgument, err.Error()) } srv.state.BloomHandler.IocBloom.Add([]byte(req.GetIoc())) i++ } } // BloomSave implements the function to serialize the Bloom filter currently // loaded in the FEVER instance to disk. func (srv *mgmtServer) BloomSave(ctx context.Context, _req *emptypb.Empty) (*emptypb.Empty, error) { srv.Logger.Debug("responding to BloomSave") hasBloom := (srv.state.BloomHandler != nil) if !hasBloom { return &emptypb.Empty{}, nil } if srv.state.BloomHandler.BloomFilename == "" { return &emptypb.Empty{}, fmt.Errorf("filter was not created from file, cannot be saved") } err := bloom.WriteFilter(srv.state.BloomHandler.IocBloom, srv.state.BloomHandler.BloomFilename, srv.state.BloomHandler.BloomFileIsCompressed) if err != nil { return &emptypb.Empty{}, err } return &emptypb.Empty{}, nil } // BloomReload implements the function to reload the Bloom filter currently // loaded in the FEVER instance from disk. func (srv *mgmtServer) BloomReload(ctx context.Context, _req *emptypb.Empty) (*emptypb.Empty, error) { srv.Logger.Debug("responding to BloomReload") hasBloom := (srv.state.BloomHandler != nil) if !hasBloom { return &emptypb.Empty{}, nil } err := srv.state.BloomHandler.Reload() if err != nil { return &emptypb.Empty{}, err } return &emptypb.Empty{}, nil } // Alive implements a simple echo command. func (srv *mgmtServer) Alive(ctx context.Context, req *MgmtAliveRequest) (*MgmtAliveResponse, error) { return &MgmtAliveResponse{Echo: req.GetAlive()}, nil } fever-1.3.7/mgmt/server.go000066400000000000000000000002701477766221000154450ustar00rootroot00000000000000package mgmt // Server ... type Server interface { // ListenAndServe is expected to create a listener and to block until a // shutdown is invoked. ListenAndServe() error Stop() } fever-1.3.7/mgmt/server_test.go000066400000000000000000000114041477766221000165050ustar00rootroot00000000000000package mgmt // DCSO FEVER // Copyright (c) 2021, DCSO GmbH import ( context "context" "io/ioutil" "os" "testing" "time" "github.com/DCSO/bloom" "github.com/DCSO/fever/processing" "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" grpc "google.golang.org/grpc" emptypb "google.golang.org/protobuf/types/known/emptypb" ) var ( mgmtCfg = GRPCEndpointConfig{ EndpointConfig: EndpointConfig{ Network: "unix", ListenerAddress: "../tmp/test-fever-mgmt.socket", TLSDisable: true, }, DialOptions: []grpc.DialOption{grpc.WithInsecure()}, } ) func TestMain(m *testing.M) { logrus.SetLevel(logrus.TraceLevel) cctx, cancel := context.WithCancel(context.Background()) defer cancel() eg, ectx := errgroup.WithContext(cctx) if _, err := os.Stat("../tmp"); os.IsNotExist(err) { err := os.Mkdir("../tmp", os.ModePerm) if err != nil { logrus.Fatal(err) } } bf := bloom.Initialize(100000, 0.0000001) bf.Add([]byte("foo")) bfFile, err := ioutil.TempFile("", "example") if err != nil { logrus.Fatal(err) } defer os.Remove(bfFile.Name()) bf.Write(bfFile) bfFile.Close() bh, err := processing.MakeBloomHandlerFromFile(bfFile.Name(), false, nil, nil, "alert", []string{}) if err != nil { logrus.Fatal(err) } msrv, err := NewMgmtServer(ectx, mgmtCfg, &State{ BloomHandler: bh, }) if err != nil { logrus.Fatal(err) } eg.Go(func() error { if err := msrv.ListenAndServe(); err != nil { logrus.WithError(err).Error("gRPC server failed") return err } return nil }) time.Sleep(100 * time.Millisecond) defer func() { cancel() }() if rc := m.Run(); rc != 0 { cancel() msrv.Stop() logrus.Warnf("test failed with %d", rc) os.Exit(rc) } cancel() msrv.Stop() } func TestAlive(t *testing.T) { logrus.StandardLogger().SetLevel(logrus.DebugLevel) conn, err := grpc.Dial(mgmtCfg.Network+":"+mgmtCfg.ListenerAddress, mgmtCfg.DialOptions...) if err != nil { t.Fatal(err) } defer conn.Close() clt := NewMgmtServiceClient(conn) got, err := clt.Alive(context.TODO(), &MgmtAliveRequest{Alive: "TestAlive"}) if err != nil { t.Fatal(err) } if got.GetEcho() != "TestAlive" { t.Errorf("Alive(): %v, want %v", got.GetEcho(), "TestAlive") } } func TestBloomInfo(t *testing.T) { logrus.StandardLogger().SetLevel(logrus.DebugLevel) conn, err := grpc.Dial(mgmtCfg.Network+":"+mgmtCfg.ListenerAddress, mgmtCfg.DialOptions...) if err != nil { t.Fatal(err) } defer conn.Close() clt := NewMgmtServiceClient(conn) got, err := clt.BloomInfo(context.TODO(), &emptypb.Empty{}) if err != nil { t.Fatal(err) } if got.GetCapacity() != 100000 { t.Errorf("BloomInfo(): %v, want %v", got.GetCapacity(), 100000) } if got.GetFpprob() != 0.0000001 { t.Errorf("BloomInfo(): %v, want %v", got.GetFpprob(), 0.0000001) } if got.GetElements() != 1 { t.Errorf("BloomInfo(): %v, want %v", got.GetElements(), 1) } if got.GetHashfuncs() != 24 { t.Errorf("BloomInfo(): %v, want %v", got.GetHashfuncs(), 24) } if got.GetBits() != 3354770 { t.Errorf("BloomInfo(): %v, want %v", got.GetBits(), 3354770) } } func TestBloomSave(t *testing.T) { logrus.StandardLogger().SetLevel(logrus.DebugLevel) conn, err := grpc.Dial(mgmtCfg.Network+":"+mgmtCfg.ListenerAddress, mgmtCfg.DialOptions...) if err != nil { t.Fatal(err) } defer conn.Close() clt := NewMgmtServiceClient(conn) _, err = clt.BloomSave(context.TODO(), &emptypb.Empty{}) if err != nil { t.Fatal(err) } } func TestBloomReload(t *testing.T) { logrus.StandardLogger().SetLevel(logrus.DebugLevel) conn, err := grpc.Dial(mgmtCfg.Network+":"+mgmtCfg.ListenerAddress, mgmtCfg.DialOptions...) if err != nil { t.Fatal(err) } defer conn.Close() clt := NewMgmtServiceClient(conn) _, err = clt.BloomReload(context.TODO(), &emptypb.Empty{}) if err != nil { t.Fatal(err) } } func TestBloomAdd(t *testing.T) { logrus.StandardLogger().SetLevel(logrus.DebugLevel) conn, err := grpc.Dial(mgmtCfg.Network+":"+mgmtCfg.ListenerAddress, mgmtCfg.DialOptions...) if err != nil { t.Fatal(err) } defer conn.Close() clt := NewMgmtServiceClient(conn) got, err := clt.BloomInfo(context.TODO(), &emptypb.Empty{}) if err != nil { t.Fatal(err) } if got.GetElements() != 1 { t.Errorf("BloomAdd(): %v, want %v", got.GetElements(), 1) } stream, err := clt.BloomAdd(context.TODO()) if err != nil { t.Fatal(err) } for _, part := range []string{"a", "b", "c"} { if err := stream.Send(&MgmtBloomAddRequest{Ioc: part}); err != nil { t.Fatal(err) } } resp, err := stream.CloseAndRecv() if err != nil { t.Fatal(err) } if resp.GetAdded() != 3 { t.Fatalf("wanted 3, got %d", resp.GetAdded()) } got, err = clt.BloomInfo(context.TODO(), &emptypb.Empty{}) if err != nil { t.Fatal(err) } if got.GetElements() != 4 { t.Errorf("BloomAdd(): %v, want %v", got.GetElements(), 4) } } fever-1.3.7/mgmt/state.go000066400000000000000000000003561477766221000152640ustar00rootroot00000000000000package mgmt // DCSO FEVER // Copyright (c) 2021, DCSO GmbH import "github.com/DCSO/fever/processing" // State contains references to components to be affected by RPC calls. type State struct { BloomHandler *processing.BloomHandler } fever-1.3.7/processing/000077500000000000000000000000001477766221000150215ustar00rootroot00000000000000fever-1.3.7/processing/bloom_handler.go000066400000000000000000000216631477766221000201650ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2017, 2020, DCSO GmbH import ( "fmt" "io" "net/url" "strings" "sync" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" "github.com/buger/jsonparser" "github.com/DCSO/bloom" log "github.com/sirupsen/logrus" ) // BloomHandler is a Handler which is meant to check for the presence of // event type-specific keywords in a Bloom filter, raising new 'alert' type // events when matches are found. type BloomHandler struct { sync.Mutex Logger *log.Entry Name string EventType string IocBloom *bloom.BloomFilter BloomFilename string BloomFileIsCompressed bool DatabaseEventChan chan types.Entry ForwardHandler Handler AlertPrefix string Alertifier *util.Alertifier BlacklistIOCs map[string]struct{} } // BloomNoFileErr is an error thrown when a file-based operation (e.g. // reloading) is attempted on a bloom filter object with no file information // attached. type BloomNoFileErr struct { s string } // Error returns the error message. func (e *BloomNoFileErr) Error() string { return e.s } func bloomExtraModifier(inputAlert *types.Entry, ioc string) error { iocEscaped, err := util.EscapeJSON(ioc) if err != nil { return err } val, err := jsonparser.Set([]byte(inputAlert.JSONLine), iocEscaped, "_extra", "bloom-ioc") if err != nil { return err } inputAlert.JSONLine = string(val) return nil } // MakeBloomHandler returns a new BloomHandler, checking against the given // Bloom filter and sending alerts to databaseChan as well as forwarding them // to a given forwarding handler. func MakeBloomHandler(iocBloom *bloom.BloomFilter, databaseChan chan types.Entry, forwardHandler Handler, alertPrefix string) *BloomHandler { bh := &BloomHandler{ Logger: log.WithFields(log.Fields{ "domain": "bloom", }), IocBloom: iocBloom, DatabaseEventChan: databaseChan, ForwardHandler: forwardHandler, AlertPrefix: alertPrefix, Alertifier: util.MakeAlertifier(alertPrefix), BlacklistIOCs: make(map[string]struct{}), } bh.Alertifier.SetExtraModifier(bloomExtraModifier) bh.Alertifier.RegisterMatchType("dns-req", util.AlertJSONProviderDNSReq{}) bh.Alertifier.RegisterMatchType("dns-resp", util.AlertJSONProviderDNSResp{}) bh.Alertifier.RegisterMatchType("tls-sni", util.AlertJSONProviderTLSSNI{}) bh.Alertifier.RegisterMatchType("tls-fingerprint", util.AlertJSONProviderTLSFingerprint{}) bh.Alertifier.RegisterMatchType("http-host", util.AlertJSONProviderHTTPHost{}) bh.Alertifier.RegisterMatchType("http-url", util.AlertJSONProviderHTTPURL{}) log.WithFields(log.Fields{ "N": iocBloom.N, "domain": "bloom", }).Info("Bloom filter loaded") return bh } // MakeBloomHandlerFromFile returns a new BloomHandler created from a new // Bloom filter specified by the given file name. func MakeBloomHandlerFromFile(bloomFilename string, compressed bool, databaseChan chan types.Entry, forwardHandler Handler, alertPrefix string, blacklistIOCs []string) (*BloomHandler, error) { log.WithFields(log.Fields{ "domain": "bloom", "filename": bloomFilename, }).Info("loading Bloom filter from file") iocBloom, err := bloom.LoadFilter(bloomFilename, compressed) if err != nil { if err == io.EOF { log.Warnf("file is empty, using empty default one") myBloom := bloom.Initialize(100, 0.00000001) iocBloom = &myBloom } else if strings.Contains(err.Error(), "value of k (number of hash functions) is too high") { log.Warnf("malformed Bloom filter file, using empty default one") myBloom := bloom.Initialize(100, 0.00000001) iocBloom = &myBloom } else { return nil, err } } bh := MakeBloomHandler(iocBloom, databaseChan, forwardHandler, alertPrefix) for _, v := range blacklistIOCs { if bh.IocBloom.Check([]byte(v)) { bh.Logger.Warnf("filter contains blacklisted indicator '%s'", v) } bh.BlacklistIOCs[v] = struct{}{} } bh.BloomFilename = bloomFilename bh.BloomFileIsCompressed = compressed bh.Logger.WithFields(log.Fields{}).Info("filter file loaded successfully") return bh, nil } // Reload triggers a reload of the contents of the file with the name. func (a *BloomHandler) Reload() error { if a.BloomFilename == "" { return &BloomNoFileErr{"BloomHandler was not created from a file, no reloading possible"} } iocBloom, err := bloom.LoadFilter(a.BloomFilename, a.BloomFileIsCompressed) if err != nil { if err == io.EOF { log.Warnf("file is empty, using empty default one") myBloom := bloom.Initialize(100, 0.00000001) iocBloom = &myBloom } else if strings.Contains(err.Error(), "value of k (number of hash functions) is too high") { log.Warnf("malformed Bloom filter file, using empty default one") myBloom := bloom.Initialize(100, 0.00000001) iocBloom = &myBloom } else { return err } } a.Lock() a.IocBloom = iocBloom for k := range a.BlacklistIOCs { if a.IocBloom.Check([]byte(k)) { a.Logger.Warnf("filter contains blacklisted indicator '%s'", k) } } a.Unlock() log.WithFields(log.Fields{ "N": iocBloom.N, }).Info("Bloom filter reloaded") return nil } // Consume processes an Entry, emitting alerts if there is a match func (a *BloomHandler) Consume(e *types.Entry) error { if e.EventType == "http" { var fullURL string a.Lock() // check HTTP host first: foo.bar.de if a.IocBloom.Check([]byte(e.HTTPHost)) { if _, present := a.BlacklistIOCs[e.HTTPHost]; !present { if n, err := a.Alertifier.MakeAlert(*e, e.HTTPHost, "http-host"); err == nil { a.DatabaseEventChan <- *n a.ForwardHandler.Consume(n) } else { log.Warn(err) } } } // we sometimes see full 'URLs' in the corresponding EVE field when // observing requests via proxies. In this case there is no need to // canonicalize the URL, it is already qualified. if strings.Contains(e.HTTPUrl, "://") { fullURL = e.HTTPUrl } else { // in all other cases, we need to create a full URL from the components fullURL = "http://" + e.HTTPHost + e.HTTPUrl } // we now should have a full URL regardless of where it came from: // http://foo.bar.de:123/baz u, err := url.Parse(fullURL) if err != nil { log.Warnf("could not parse URL '%s': %s", fullURL, err.Error()) a.Unlock() return nil } hostPath := fmt.Sprintf("%s%s", u.Host, u.Path) // http://foo.bar.de:123/baz if a.IocBloom.Check([]byte(fullURL)) { if _, present := a.BlacklistIOCs[fullURL]; !present { if n, err := a.Alertifier.MakeAlert(*e, fullURL, "http-url"); err == nil { a.DatabaseEventChan <- *n a.ForwardHandler.Consume(n) } else { log.Warn(err) } } } else // foo.bar.de:123/baz if a.IocBloom.Check([]byte(hostPath)) { if _, present := a.BlacklistIOCs[hostPath]; !present { if n, err := a.Alertifier.MakeAlert(*e, hostPath, "http-url"); err == nil { a.DatabaseEventChan <- *n a.ForwardHandler.Consume(n) } else { log.Warn(err) } } } else // /baz if a.IocBloom.Check([]byte(u.Path)) { if _, present := a.BlacklistIOCs[u.Path]; !present { if n, err := a.Alertifier.MakeAlert(*e, u.Path, "http-url"); err == nil { a.DatabaseEventChan <- *n a.ForwardHandler.Consume(n) } else { log.Warn(err) } } } a.Unlock() } else if e.EventType == "dns" { a.Lock() if a.IocBloom.Check([]byte(e.DNSRRName)) { if _, present := a.BlacklistIOCs[e.DNSRRName]; !present { if e.DNSType == "query" { if n, err := a.Alertifier.MakeAlert(*e, e.DNSRRName, "dns-req"); err == nil { a.DatabaseEventChan <- *n a.ForwardHandler.Consume(n) } else { log.Warn(err) } } else if e.DNSType == "answer" { if n, err := a.Alertifier.MakeAlert(*e, e.DNSRRName, "dns-resp"); err == nil { a.DatabaseEventChan <- *n a.ForwardHandler.Consume(n) } else { log.Warn(err) } } else { log.Warnf("invalid DNS type: '%s'", e.DNSType) a.Unlock() return nil } } } a.Unlock() } else if e.EventType == "tls" { a.Lock() if a.IocBloom.Check([]byte(e.TLSSNI)) { if _, present := a.BlacklistIOCs[e.TLSSNI]; !present { if n, err := a.Alertifier.MakeAlert(*e, e.TLSSNI, "tls-sni"); err == nil { a.DatabaseEventChan <- *n a.ForwardHandler.Consume(n) } else { log.Warn(err) } } } else if a.IocBloom.Check([]byte(e.TLSFingerprint)) { if _, present := a.BlacklistIOCs[e.TLSFingerprint]; !present { if n, err := a.Alertifier.MakeAlert(*e, e.TLSFingerprint, "tls-fingerprint"); err == nil { a.DatabaseEventChan <- *n a.ForwardHandler.Consume(n) } else { log.Warn(err) } } } a.Unlock() } return nil } // GetName returns the name of the handler func (a *BloomHandler) GetName() string { return "Bloom filter handler" } // GetEventTypes returns a slice of event type strings that this handler // should be applied to func (a *BloomHandler) GetEventTypes() []string { return []string{"http", "dns", "tls"} } fever-1.3.7/processing/bloom_handler_test.go000066400000000000000000000601231477766221000212160ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2017, 2020, DCSO GmbH import ( "encoding/json" "fmt" "io/ioutil" "math/rand" "os" "regexp" "sync" "testing" "time" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" "github.com/DCSO/bloom" log "github.com/sirupsen/logrus" "github.com/sirupsen/logrus/hooks/test" ) var ( reHTTPURL = regexp.MustCompile(`Possibly bad HTTP URL: [^ ]+ . ([^ ]+) . ([^" ]+)`) reHTTPHost = regexp.MustCompile(`Possibly bad HTTP host: ([^" ]+)`) reDNSReq = regexp.MustCompile("Possibly bad DNS lookup to ([^\" ]+)") reDNSRep = regexp.MustCompile("Possibly bad DNS response for ([^\" ]+)") reSNI = regexp.MustCompile("Possibly bad TLS SNI: ([^\" ]+)") reFingerprint = regexp.MustCompile("Possibly bad TLS Fingerprint: ([^\" ]+)") ) func makeBloomDNSEvent(rrname string) types.Entry { e := types.Entry{ SrcIP: fmt.Sprintf("10.0.0.%d", rand.Intn(5)+1), SrcPort: 53, DestIP: fmt.Sprintf("10.0.0.%d", rand.Intn(50)), DestPort: []int64{11, 12, 13, 14, 15}[rand.Intn(5)], Timestamp: time.Now().Format(types.SuricataTimestampFormat), EventType: "dns", Proto: "TCP", DNSRCode: []string{"NOERROR", "NXDOMAIN"}[rand.Intn(2)], DNSRData: fmt.Sprintf("10.%d.0.%d", rand.Intn(50), rand.Intn(50)+100), DNSRRName: rrname, DNSRRType: "A", DNSType: []string{"answer", "query"}[rand.Intn(2)], } eve := types.EveEvent{ Timestamp: &types.SuriTime{ Time: time.Now().UTC(), }, EventType: e.EventType, SrcIP: e.SrcIP, SrcPort: int(e.SrcPort), DestIP: e.DestIP, DestPort: int(e.DestPort), Proto: e.Proto, DNS: &types.DNSEvent{ Rcode: e.DNSRCode, Rrname: e.DNSRRName, Rdata: e.DNSRData, Rrtype: e.DNSRRType, Type: e.DNSType, }, } json, err := json.Marshal(eve) if err != nil { log.Warn(err) } else { e.JSONLine = string(json) } return e } func makeBloomHTTPEvent(host string, url string) types.Entry { e := types.Entry{ SrcIP: fmt.Sprintf("10.0.0.%d", rand.Intn(5)+1), SrcPort: int64(rand.Intn(60000) + 1025), DestIP: fmt.Sprintf("10.0.0.%d", rand.Intn(50)), DestPort: 80, Timestamp: time.Now().Format(types.SuricataTimestampFormat), EventType: "http", Proto: "TCP", HTTPHost: host, HTTPUrl: url, HTTPMethod: "GET", } eve := types.EveEvent{ Timestamp: &types.SuriTime{ Time: time.Now().UTC(), }, EventType: e.EventType, SrcIP: e.SrcIP, SrcPort: int(e.SrcPort), DestIP: e.DestIP, DestPort: int(e.DestPort), Proto: e.Proto, HTTP: &types.HTTPEvent{ Hostname: e.HTTPHost, URL: e.HTTPUrl, }, } json, err := json.Marshal(eve) if err != nil { log.Warn(err) } else { e.JSONLine = string(json) } return e } func makeBloomTLSEvent(host string, fingerprint string) types.Entry { e := types.Entry{ SrcIP: fmt.Sprintf("10.0.0.%d", rand.Intn(5)+1), SrcPort: int64(rand.Intn(60000) + 1025), DestIP: fmt.Sprintf("10.0.0.%d", rand.Intn(50)), DestPort: 443, Timestamp: time.Now().Format(types.SuricataTimestampFormat), EventType: "tls", Proto: "TCP", TLSSNI: host, TLSFingerprint: fingerprint, } eve := types.EveEvent{ Timestamp: &types.SuriTime{ Time: time.Now().UTC(), }, EventType: e.EventType, SrcIP: e.SrcIP, SrcPort: int(e.SrcPort), DestIP: e.DestIP, DestPort: int(e.DestPort), Proto: e.Proto, TLS: &types.TLSEvent{ Sni: e.TLSSNI, Fingerprint: e.TLSFingerprint, }, } json, err := json.Marshal(eve) if err != nil { log.Warn(err) } else { e.JSONLine = string(json) } return e } var testURLs []string var testHosts []string var testTLSHosts []string var testTLSFingerprints []string const numOfTestBloomItems = 1000 // fill Bloom filter with disjunct set of values func fillBloom(b *bloom.BloomFilter) { testURLs = make([]string, 0) testHosts = make([]string, 0) testTLSHosts = make([]string, 0) i := 0 for i < numOfTestBloomItems { val := fmt.Sprintf("%s.com", util.RndStringFromAlpha(6)) for b.Check([]byte(val)) { val = fmt.Sprintf("%s.com", util.RndStringFromAlpha(6)) } i++ testHosts = append(testHosts, val) b.Add([]byte(val)) } i = 0 for i < numOfTestBloomItems { val := fmt.Sprintf("%s.com", util.RndStringFromAlpha(6)) for b.Check([]byte(val)) { val = fmt.Sprintf("%s.com", util.RndStringFromAlpha(6)) } i++ testTLSHosts = append(testTLSHosts, val) b.Add([]byte(val)) } i = 0 for i < numOfTestBloomItems { val := fmt.Sprintf("http://foo.com/%s.html", util.RndStringFromAlpha(6)) for b.Check([]byte(val)) { val = fmt.Sprintf("http://foo.com/%s.html", util.RndStringFromAlpha(6)) } i++ testURLs = append(testURLs, val) b.Add([]byte(val)) } i = 0 for i < numOfTestBloomItems { i++ fingp := util.RndTLSFingerprint() testTLSFingerprints = append(testTLSFingerprints, fingp) b.Add([]byte(fingp)) } } // CollectorHandler simply gathers consumed events in a list type CollectorHandler struct { EntriesLock sync.Mutex Entries map[string]bool } func (h *CollectorHandler) GetName() string { return "Collector handler" } func (h *CollectorHandler) GetEventTypes() []string { return []string{"alert"} } func (h *CollectorHandler) Consume(e *types.Entry) error { h.EntriesLock.Lock() defer h.EntriesLock.Unlock() match := reHTTPURL.FindStringSubmatch(e.JSONLine) if match != nil { url := match[2] h.Entries[url] = true return nil } match = reHTTPHost.FindStringSubmatch(e.JSONLine) if match != nil { host := match[1] h.Entries[host] = true return nil } match = reDNSReq.FindStringSubmatch(e.JSONLine) if match != nil { var eve types.EveEvent var err = json.Unmarshal([]byte(e.JSONLine), &eve) if err != nil { log.Fatal(err) } if eve.DNS.Type != "query" { log.Fatalf("request alert for type (%s) != query", eve.DNS.Type) } h.Entries[match[1]] = true return nil } match = reDNSRep.FindStringSubmatch(e.JSONLine) if match != nil { var eve types.EveEvent var err = json.Unmarshal([]byte(e.JSONLine), &eve) if err != nil { log.Fatal(err) } if eve.DNS.Type != "answer" { log.Fatalf("request alert for type (%s) != answer", eve.DNS.Type) } h.Entries[match[1]] = true return nil } match = reSNI.FindStringSubmatch(e.JSONLine) if match != nil { h.Entries[match[1]] = true return nil } match = reFingerprint.FindStringSubmatch(e.JSONLine) if match != nil { h.Entries[match[1]] = true return nil } return nil } func (h *CollectorHandler) Reset() { h.EntriesLock.Lock() defer h.EntriesLock.Unlock() h.Entries = make(map[string]bool) } func (h *CollectorHandler) GetEntries() map[string]bool { h.EntriesLock.Lock() defer h.EntriesLock.Unlock() return h.Entries } func TestBloomHandler(t *testing.T) { // initalize Bloom filter and fill with 'interesting' values bf := bloom.Initialize(100000, 0.0000001) fillBloom(&bf) // channel to receive events to be saved to database dbChan := make(chan types.Entry) // handler to receive forwarded events fwhandler := &CollectorHandler{ Entries: make(map[string]bool), } // concurrently gather entries to be written to DB dbWritten := make([]types.Entry, 0) consumeWaitChan := make(chan bool) go func() { for e := range dbChan { dbWritten = append(dbWritten, e) } close(consumeWaitChan) }() bh := MakeBloomHandler(&bf, dbChan, fwhandler, "FOO BAR") err := bh.Reload() if err == nil { t.Fatal("reloading without file should fail") } bhTypes := bh.GetEventTypes() if len(bhTypes) != 3 { t.Fatal("Bloom handler should claim three types") } if bhTypes[0] != "http" { t.Fatal("Bloom handler should claim 'http' type") } if bhTypes[1] != "dns" { t.Fatal("Bloom handler should claim 'dns' type") } if bhTypes[2] != "tls" { t.Fatal("Bloom handler should claim 'tls' type") } if bh.GetName() != "Bloom filter handler" { t.Fatal("Bloom handler has wrong name") } i := 0 j := 0 k := 0 l := 0 for { var e types.Entry // emit Bloom filter TP event with 20% individual probability, at most // each if 2 < rand.Intn(10) { if i == numOfTestBloomItems && j == numOfTestBloomItems && k == numOfTestBloomItems { break } // uniformly distribute hits over HTTP URL/Host and DNS lookups switch rnd := rand.Intn(4); rnd { case 0: if i < numOfTestBloomItems { e = makeBloomDNSEvent(testHosts[i]) bh.Consume(&e) i++ } case 1: if j < numOfTestBloomItems { e = makeBloomHTTPEvent("foo.com", testURLs[j]) bh.Consume(&e) j++ } case 2: if k < numOfTestBloomItems { e = makeBloomTLSEvent(testTLSHosts[k], ":::") bh.Consume(&e) k++ } case 3: if l < numOfTestBloomItems { e = makeBloomTLSEvent("foo.com", testTLSFingerprints[l]) bh.Consume(&e) l++ } } } else { // uniformly distribute non-matching hits over HTTP URL/Host and DNS lookups switch rnd := rand.Intn(4); rnd { case 0: s := fmt.Sprintf("%s.com", util.RndStringFromAlpha(6)) for bf.Check([]byte(s)) { s = fmt.Sprintf("%s.%s", util.RndStringFromAlpha(6), util.RndStringFromAlpha(2)) } e = makeBloomDNSEvent(s) bh.Consume(&e) case 1: s := fmt.Sprintf("/%s.html", util.RndStringFromAlpha(6)) for bf.Check([]byte(s)) { s = fmt.Sprintf("/%s.%s.html", util.RndStringFromAlpha(6), util.RndStringFromAlpha(6)) } e = makeBloomHTTPEvent("foo.com", s) bh.Consume(&e) case 2: s := fmt.Sprintf("%s.com", util.RndStringFromAlpha(6)) for bf.Check([]byte(s)) { s = fmt.Sprintf("%s.%s", util.RndStringFromAlpha(6), util.RndStringFromAlpha(2)) } e = makeBloomTLSEvent(s, ":::") bh.Consume(&e) case 3: f := util.RndStringFromAlpha(6) for bf.Check([]byte(f)) { f = util.RndStringFromAlpha(6) } e = makeBloomTLSEvent("foo.com", f) bh.Consume(&e) } } } // wait until all values have been collected close(dbChan) <-consumeWaitChan // check that we haven't missed anything if len(fwhandler.Entries) < 4*numOfTestBloomItems { t.Fatalf("expected %d forwarded BLF alerts, seen less (%d)", numOfTestBloomItems, len(fwhandler.Entries)) } // we want _at least_ to have the test values forwarded as alerts // (as FP are possible) for _, v := range testHosts { if _, ok := fwhandler.Entries[v]; !ok { t.Fatalf("testhost %s not forwarded", v) } } for _, v := range testURLs { if _, ok := fwhandler.Entries[v]; !ok { t.Fatalf("testurl %s not forwarded", v) } } } func TestBloomHandlerFromFile(t *testing.T) { b1 := bloom.Initialize(1000, 0.0001) b2 := bloom.Initialize(1000, 0.0001) b1.Add([]byte("foobar")) b2.Add([]byte("baz")) b1File, err := ioutil.TempFile("", "example") if err != nil { t.Fatal(err) } defer os.Remove(b1File.Name()) b1.Write(b1File) b1File.Close() // handler to receive forwarded events fwhandler := &CollectorHandler{ Entries: make(map[string]bool), } dbChan := make(chan types.Entry, 10) defer close(dbChan) bh, err := MakeBloomHandlerFromFile(b1File.Name(), false, dbChan, fwhandler, "FOO BAR", []string{"/"}) if err != nil { t.Fatal(err) } e := makeBloomDNSEvent("foobar") bh.Consume(&e) if len(fwhandler.Entries) != 1 { t.Fatalf("Unexpected number of entries: %d != 1 ", len(fwhandler.Entries)) } if !fwhandler.Entries["foobar"] { t.Fatalf("expected entry is missing") } e = makeBloomDNSEvent("baz") bh.Consume(&e) if len(fwhandler.Entries) != 1 { t.Fatalf("Unexpected number of entries: %d != 1 ", len(fwhandler.Entries)) } if !fwhandler.Entries["foobar"] { t.Fatalf("expected entry is missing") } b2File, err := os.OpenFile(b1File.Name(), os.O_RDWR|os.O_CREATE, 0755) if err != nil { t.Fatal(err) } b2.Write(b2File) b2File.Close() bh.Reload() fwhandler.Entries = make(map[string]bool) e = makeBloomDNSEvent("baz") bh.Consume(&e) if len(fwhandler.Entries) != 1 { t.Fatalf("Unexpected number of entries: %d != 1 ", len(fwhandler.Entries)) } if !fwhandler.Entries["baz"] { t.Fatalf("expected entry is missing") } if fwhandler.Entries["foobar"] { t.Fatalf("unexpected entry") } e = makeBloomDNSEvent("foobar") bh.Consume(&e) if len(fwhandler.Entries) != 1 { t.Fatalf("Unexpected number of entries: %d != 1 ", len(fwhandler.Entries)) } if !fwhandler.Entries["baz"] { t.Fatalf("expected entry is missing") } if fwhandler.Entries["foobar"] { t.Fatalf("unexpected entry") } } func TestBloomHandlerEmptyInput(t *testing.T) { blFile, err := ioutil.TempFile("", "empty") if err != nil { t.Fatal(err) } defer os.Remove(blFile.Name()) blFile.Close() dbChan := make(chan types.Entry, 10) defer close(dbChan) bf, err := MakeBloomHandlerFromFile(blFile.Name(), false, dbChan, nil, "FOO BAR", []string{"/"}) if err != nil { t.Fatal(err) } if bf == nil { t.Fatal("bloom filter should not be nil for empty file") } } func TestBloomHandlerBlacklistedInputFromFile(t *testing.T) { b1 := bloom.Initialize(1000, 0.0001) b1.Add([]byte("/")) b1File, err := ioutil.TempFile("", "blist") if err != nil { t.Fatal(err) } defer os.Remove(b1File.Name()) b1.Write(b1File) b1File.Close() b2 := bloom.Initialize(1000, 0.0001) b2.Add([]byte("/foobarbaz")) dbChan := make(chan types.Entry, 10) defer close(dbChan) hook := test.NewGlobal() _, err = MakeBloomHandlerFromFile(b1File.Name(), false, nil, nil, "FOO BAR", []string{"/"}) if err != nil { t.Fatal(err) } entries := hook.AllEntries() if len(entries) != 4 { t.Fatal("missing log entries") } if entries[2].Message != "filter contains blacklisted indicator '/'" { t.Fatal("wrong log entry for invalid IP range") } b2File, err := os.OpenFile(b1File.Name(), os.O_RDWR|os.O_CREATE, 0755) if err != nil { t.Fatal(err) } b2.Write(b2File) b2File.Close() bf, err := MakeBloomHandlerFromFile(b1File.Name(), false, nil, nil, "FOO BAR", []string{"/"}) if err != nil { t.Fatal(err) } b2File, err = os.OpenFile(b1File.Name(), os.O_RDWR|os.O_CREATE, 0755) if err != nil { t.Fatal(err) } b1.Write(b2File) b2File.Close() hook.Reset() err = bf.Reload() if err != nil { t.Fatal(err) } entries = hook.AllEntries() if len(entries) != 2 { t.Fatal("missing log entries") } if entries[0].Message != "filter contains blacklisted indicator '/'" { t.Fatal("wrong log entry for invalid IP range") } } func TestBloomHandlerURL(t *testing.T) { e1 := types.Entry{ SrcIP: "10.0.0.1", SrcPort: 23545, DestIP: "10.0.0.2", DestPort: 80, Timestamp: time.Now().Format(types.SuricataTimestampFormat), EventType: "http", Proto: "TCP", HTTPHost: "foo.bar.de", HTTPUrl: "http://foo.bar.de/oddlyspecific", HTTPMethod: "GET", } eve1 := types.EveEvent{ EventType: e1.EventType, SrcIP: e1.SrcIP, SrcPort: int(e1.SrcPort), DestIP: e1.DestIP, DestPort: int(e1.DestPort), Proto: e1.Proto, HTTP: &types.HTTPEvent{ Hostname: e1.HTTPHost, URL: e1.HTTPUrl, }, } json1, err := json.Marshal(eve1) if err != nil { log.Warn(err) } else { e1.JSONLine = string(json1) } e2 := types.Entry{ SrcIP: "10.0.0.1", SrcPort: 23545, DestIP: "10.0.0.2", DestPort: 80, Timestamp: time.Now().Format(types.SuricataTimestampFormat), EventType: "http", Proto: "TCP", HTTPHost: "foo.bar.de", HTTPUrl: "/oddlyspecific", HTTPMethod: "GET", } eve2 := types.EveEvent{ EventType: e2.EventType, SrcIP: e2.SrcIP, SrcPort: int(e2.SrcPort), DestIP: e2.DestIP, DestPort: int(e2.DestPort), Proto: e2.Proto, HTTP: &types.HTTPEvent{ Hostname: e2.HTTPHost, URL: e2.HTTPUrl, }, } json2, err := json.Marshal(eve2) if err != nil { log.Warn(err) } else { e2.JSONLine = string(json2) } e3 := types.Entry{ SrcIP: "10.0.0.1", SrcPort: 23545, DestIP: "10.0.0.2", DestPort: 80, Timestamp: time.Now().Format(types.SuricataTimestampFormat), EventType: "http", Proto: "TCP", HTTPHost: "foo.bar.com", HTTPUrl: "/oddlyspecific", HTTPMethod: "GET", } eve3 := types.EveEvent{ EventType: e3.EventType, SrcIP: e3.SrcIP, SrcPort: int(e3.SrcPort), DestIP: e3.DestIP, DestPort: int(e3.DestPort), Proto: e3.Proto, HTTP: &types.HTTPEvent{ Hostname: e3.HTTPHost, URL: e3.HTTPUrl, }, } json3, err := json.Marshal(eve3) if err != nil { log.Warn(err) } else { e3.JSONLine = string(json3) } dbChan := make(chan types.Entry) dbWritten := make([]types.Entry, 0) consumeWaitChan := make(chan bool) go func() { for e := range dbChan { dbWritten = append(dbWritten, e) } close(consumeWaitChan) }() // initalize Bloom filter and fill with 'interesting' values bf := bloom.Initialize(100000, 0.0000001) bf.Add([]byte("/oddlyspecific")) // handler to receive forwarded events fwhandler := &CollectorHandler{ Entries: make(map[string]bool), } bh := MakeBloomHandler(&bf, dbChan, fwhandler, "FOO BAR") bh.Consume(&e1) if len(fwhandler.GetEntries()) != 1 { t.Fatalf("not enough alerts: %d", len(fwhandler.GetEntries())) } bf = bloom.Initialize(100000, 0.0000001) bf.Add([]byte("foo.bar.de/oddlyspecific")) fwhandler.Reset() bh.Consume(&e1) if len(fwhandler.GetEntries()) != 1 { t.Fatalf("not enough alerts: %d", len(fwhandler.GetEntries())) } bf = bloom.Initialize(100000, 0.0000001) bf.Add([]byte("http://foo.bar.de/oddlyspecific")) fwhandler.Reset() bh.Consume(&e1) if len(fwhandler.GetEntries()) != 1 { t.Fatalf("not enough alerts: %d", len(fwhandler.GetEntries())) } bf = bloom.Initialize(100000, 0.0000001) bf.Add([]byte("https://foo.bar.de/oddlyspecific")) fwhandler.Reset() bh.Consume(&e1) if len(fwhandler.GetEntries()) != 0 { t.Fatalf("too many alerts: %d", len(fwhandler.GetEntries())) } bf = bloom.Initialize(100000, 0.0000001) bf.Add([]byte("https://foo.bar.com/oddlyspecific")) fwhandler.Reset() bh.Consume(&e1) if len(fwhandler.GetEntries()) != 0 { t.Fatalf("too many alerts: %d", len(fwhandler.GetEntries())) } bf = bloom.Initialize(100000, 0.0000001) bf.Add([]byte("/")) fwhandler.Reset() bh.Consume(&e1) if len(fwhandler.GetEntries()) != 0 { t.Fatalf("too many alerts: %d", len(fwhandler.GetEntries())) } bf = bloom.Initialize(100000, 0.0000001) bf.Add([]byte("/oddlyspecific")) fwhandler.Reset() bh.Consume(&e2) if len(fwhandler.GetEntries()) != 1 { t.Fatalf("not enough alerts: %d", len(fwhandler.GetEntries())) } bf = bloom.Initialize(100000, 0.0000001) bf.Add([]byte("foo.bar.de/oddlyspecific")) fwhandler.Reset() bh.Consume(&e2) if len(fwhandler.GetEntries()) != 1 { t.Fatalf("not enough alerts: %d", len(fwhandler.GetEntries())) } bf = bloom.Initialize(100000, 0.0000001) bf.Add([]byte("http://foo.bar.de/oddlyspecific")) fwhandler.Reset() bh.Consume(&e2) if len(fwhandler.GetEntries()) != 1 { t.Fatalf("not enough alerts: %d", len(fwhandler.GetEntries())) } bf = bloom.Initialize(100000, 0.0000001) bf.Add([]byte("https://foo.bar.de/oddlyspecific")) fwhandler.Reset() bh.Consume(&e2) if len(fwhandler.GetEntries()) != 0 { t.Fatalf("too many alerts: %d", len(fwhandler.GetEntries())) } bf = bloom.Initialize(100000, 0.0000001) bf.Add([]byte("https://foo.bar.com/oddlyspecific")) fwhandler.Reset() bh.Consume(&e2) if len(fwhandler.GetEntries()) != 0 { t.Fatalf("too many alerts: %d", len(fwhandler.GetEntries())) } bf = bloom.Initialize(100000, 0.0000001) bf.Add([]byte("/")) fwhandler.Reset() bh.Consume(&e2) if len(fwhandler.GetEntries()) != 0 { t.Fatalf("too many alerts: %d", len(fwhandler.GetEntries())) } bf = bloom.Initialize(100000, 0.0000001) bf.Add([]byte("/oddlyspecific")) fwhandler.Reset() bh.Consume(&e3) if len(fwhandler.GetEntries()) != 1 { t.Fatalf("not enough alerts: %d", len(fwhandler.GetEntries())) } bf = bloom.Initialize(100000, 0.0000001) bf.Add([]byte("foo.bar.de/oddlyspecific")) fwhandler.Reset() bh.Consume(&e3) if len(fwhandler.GetEntries()) != 0 { t.Fatalf("too many alerts: %d", len(fwhandler.GetEntries())) } bf = bloom.Initialize(100000, 0.0000001) bf.Add([]byte("http://foo.bar.de/oddlyspecific")) fwhandler.Reset() bh.Consume(&e3) if len(fwhandler.GetEntries()) != 0 { t.Fatalf("too many alerts: %d", len(fwhandler.GetEntries())) } bf = bloom.Initialize(100000, 0.0000001) bf.Add([]byte("https://foo.bar.de/oddlyspecific")) fwhandler.Reset() bh.Consume(&e3) if len(fwhandler.GetEntries()) != 0 { t.Fatalf("too many alerts: %d", len(fwhandler.GetEntries())) } bf = bloom.Initialize(100000, 0.0000001) bf.Add([]byte("https://foo.bar.com/oddlyspecific")) fwhandler.Reset() bh.Consume(&e3) if len(fwhandler.GetEntries()) != 0 { t.Fatalf("too many alerts: %d", len(fwhandler.GetEntries())) } bf = bloom.Initialize(100000, 0.0000001) bf.Add([]byte("/")) fwhandler.Reset() bh.Consume(&e3) if len(fwhandler.GetEntries()) != 0 { t.Fatalf("too many alerts: %d", len(fwhandler.GetEntries())) } } func TestBloomHandlerBlacklistedSkip(t *testing.T) { e1 := types.Entry{ SrcIP: "10.0.0.1", SrcPort: 23545, DestIP: "10.0.0.2", DestPort: 80, Timestamp: time.Now().Format(types.SuricataTimestampFormat), EventType: "http", Proto: "TCP", HTTPHost: "foo.bar.de", HTTPUrl: "http://foo.bar.de/oddlyspecific", HTTPMethod: "GET", } eve1 := types.EveEvent{ EventType: e1.EventType, SrcIP: e1.SrcIP, SrcPort: int(e1.SrcPort), DestIP: e1.DestIP, DestPort: int(e1.DestPort), Proto: e1.Proto, HTTP: &types.HTTPEvent{ Hostname: e1.HTTPHost, URL: e1.HTTPUrl, }, } json1, err := json.Marshal(eve1) if err != nil { log.Warn(err) } else { e1.JSONLine = string(json1) } e2 := types.Entry{ SrcIP: "10.0.0.1", SrcPort: 23545, DestIP: "10.0.0.2", DestPort: 80, Timestamp: time.Now().Format(types.SuricataTimestampFormat), EventType: "http", Proto: "TCP", HTTPHost: "foo.bar.de", HTTPUrl: "/", HTTPMethod: "GET", } eve2 := types.EveEvent{ EventType: e2.EventType, SrcIP: e2.SrcIP, SrcPort: int(e2.SrcPort), DestIP: e2.DestIP, DestPort: int(e2.DestPort), Proto: e2.Proto, HTTP: &types.HTTPEvent{ Hostname: e2.HTTPHost, URL: e2.HTTPUrl, }, } json2, err := json.Marshal(eve2) if err != nil { log.Warn(err) } else { e2.JSONLine = string(json2) } b1 := bloom.Initialize(1000, 0.0001) b1.Add([]byte("/oddlyspecific")) b1.Add([]byte("/")) b1File, err := ioutil.TempFile("", "blist") if err != nil { t.Fatal(err) } defer os.Remove(b1File.Name()) b1.Write(b1File) b1File.Close() dbChan := make(chan types.Entry, 5) dbWritten := make([]types.Entry, 0) consumeWaitChan := make(chan bool) go func() { for e := range dbChan { dbWritten = append(dbWritten, e) } close(consumeWaitChan) }() // handler to receive forwarded events fwhandler := &CollectorHandler{ Entries: make(map[string]bool), } bh, err := MakeBloomHandlerFromFile(b1File.Name(), false, dbChan, fwhandler, "FOO BAR", []string{"/"}) if err != nil { t.Fatal(err) } bh.Consume(&e1) if len(fwhandler.GetEntries()) != 1 { t.Fatalf("not enough alerts: %d", len(fwhandler.GetEntries())) } fwhandler.Reset() bh.Consume(&e2) if len(fwhandler.GetEntries()) != 0 { t.Fatalf("should not create alert but got %d", len(fwhandler.GetEntries())) } bh.Consume(&e1) if len(fwhandler.GetEntries()) != 1 { t.Fatalf("not enough alerts: %d", len(fwhandler.GetEntries())) } } func TestBloomHandlerInvalidDNS(t *testing.T) { // initalize Bloom filter and fill with 'interesting' values bf := bloom.Initialize(100000, 0.0000001) // channel to receive events to be saved to database dbChan := make(chan types.Entry) // handler to receive forwarded events fwhandler := &CollectorHandler{ Entries: make(map[string]bool), } // concurrently gather entries to be written to DB dbWritten := make([]types.Entry, 0) consumeWaitChan := make(chan bool) go func() { for e := range dbChan { dbWritten = append(dbWritten, e) } close(consumeWaitChan) }() bh := MakeBloomHandler(&bf, dbChan, fwhandler, "FOO BAR") e := makeBloomDNSEvent("foobar") e.DNSType = "foobar" bf.Add([]byte(e.DNSRRName)) hook := test.NewGlobal() bh.Consume(&e) entries := hook.AllEntries() if len(entries) < 1 { t.Fatal("missing log entries") } if entries[0].Message != "invalid DNS type: 'foobar'" { t.Fatal("wrong log entry for invalid DNS type") } } fever-1.3.7/processing/context_collector.go000066400000000000000000000132601477766221000211040ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2019, DCSO GmbH import ( "sync" "time" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" "github.com/patrickmn/go-cache" log "github.com/sirupsen/logrus" ) // DebugOutputInterval specifies the amount of cache operations before // printing the current cache size, in verbose mode. const DebugOutputInterval = 100000 // GlobalContextCollector is a shared ContextCollector to be used by FEVER. var GlobalContextCollector *ContextCollector // ContextShipper is a function that processes a slice of Entries that make up a // context of an alert, e.g. all events that share a flow ID relevant for the // alert. type ContextShipper func(Context, *log.Entry) error // ContextCollectorPerfStats contains performance stats written to InfluxDB // for monitoring. type ContextCollectorPerfStats struct { Flows uint64 `influx:"context_flows"` Events uint64 `influx:"context_events"` JSONBytes uint64 `influx:"context_json_bytes"` } // ContextCollector is a component that maintains a cache of metadata per // flow ID, forwarding it to a specified sink if associated with an alert. type ContextCollector struct { PerfStats ContextCollectorPerfStats StatsEncoder *util.PerformanceStatsEncoder StopChan chan bool StoppedChan chan bool StopCounterChan chan bool StoppedCounterChan chan bool Running bool StatsLock sync.Mutex FlowListeners []chan types.Entry Cache *cache.Cache MarkLock sync.Mutex Marked map[string]struct{} Logger *log.Entry i uint64 Ship ContextShipper } // Context is a collection of JSON events that belong to a given flow. type Context []string // MakeContextCollector creates a new ContextCollector. func MakeContextCollector(shipper ContextShipper, defaultTTL time.Duration) *ContextCollector { c := &ContextCollector{ Logger: log.WithFields(log.Fields{ "domain": "context", }), Cache: cache.New(defaultTTL, defaultTTL), Marked: make(map[string]struct{}), i: 0, Ship: shipper, FlowListeners: make([]chan types.Entry, 0), } c.Logger.Debugf("created cache with default TTL %v", defaultTTL) return c } // Mark queues metadata for a given flow for forwarding, identified by its // flow ID. func (c *ContextCollector) Mark(flowID string) { // when seeing an alert, just mark the flow ID as relevant c.MarkLock.Lock() c.Marked[flowID] = struct{}{} c.MarkLock.Unlock() } // Consume processes an Entry, adding the data within to the internal // aggregated state func (c *ContextCollector) Consume(e *types.Entry) error { var myC Context // Some events, e.g. stats, have no flow ID set if e.FlowID == "" { return nil } cval, exist := c.Cache.Get(e.FlowID) if exist { // the 'flow' event always comes last, so we can use it as an // indicator that the flow is complete and can be processed if e.EventType == types.EventTypeFlow { var isMarked bool c.MarkLock.Lock() if _, ok := c.Marked[e.FlowID]; ok { isMarked = true } c.MarkLock.Unlock() if isMarked { c.StatsLock.Lock() c.PerfStats.Flows++ c.PerfStats.Events += uint64(len(cval.(Context))) for _, v := range cval.(Context) { c.PerfStats.JSONBytes += uint64(len(v)) } c.StatsLock.Unlock() if c.Ship != nil { c.Ship(cval.(Context), c.Logger) } for _, fl := range c.FlowListeners { fl <- *e } delete(c.Marked, e.FlowID) } c.Cache.Delete(e.FlowID) } else { myC = cval.(Context) myC = append(myC, e.JSONLine) c.Cache.Set(e.FlowID, myC, cache.DefaultExpiration) } } else { if e.EventType != types.EventTypeFlow { myC = append(myC, e.JSONLine) c.Cache.Set(e.FlowID, myC, cache.DefaultExpiration) } } c.i++ if c.i%DebugOutputInterval == 0 { count := c.Cache.ItemCount() c.Logger.WithFields(log.Fields{ "n": count, }).Debugf("cache size after another %d events", DebugOutputInterval) c.i = 0 } return nil } func (c *ContextCollector) runCounter() { sTime := time.Now() for { time.Sleep(500 * time.Millisecond) select { case <-c.StopCounterChan: close(c.StoppedCounterChan) return default: if c.StatsEncoder == nil || time.Since(sTime) < c.StatsEncoder.SubmitPeriod { continue } c.StatsEncoder.Submit(c.PerfStats) c.StatsLock.Lock() c.PerfStats.JSONBytes = 0 c.PerfStats.Flows = 0 c.PerfStats.Events = 0 sTime = time.Now() c.StatsLock.Unlock() } } } // GetName returns the name of the handler func (c *ContextCollector) GetName() string { return "Context collector" } // GetEventTypes returns a slice of event type strings that this handler // should be applied to func (c *ContextCollector) GetEventTypes() []string { return []string{"*"} } // Run starts the metrics collection and submission in the ContextCollector. func (c *ContextCollector) Run() { if !c.Running { c.StopChan = make(chan bool) c.StopCounterChan = make(chan bool) c.StoppedCounterChan = make(chan bool) go c.runCounter() c.Running = true } } // Stop stops the metrics collection and submission in the ContextCollector. func (c *ContextCollector) Stop(stoppedChan chan bool) { if c.Running { close(c.StopCounterChan) <-c.StoppedCounterChan c.StoppedChan = stoppedChan close(c.StopChan) c.Running = false } } // SubmitStats registers a PerformanceStatsEncoder for runtime stats submission. func (c *ContextCollector) SubmitStats(sc *util.PerformanceStatsEncoder) { c.StatsEncoder = sc } // AddFlowListener registers flowChan as a channel to emit a 'flow' Entry on // whenever a marked flow is forwarded func (c *ContextCollector) AddFlowListener(flowChan chan types.Entry) { c.FlowListeners = append(c.FlowListeners, flowChan) } fever-1.3.7/processing/context_collector_test.go000066400000000000000000000051541477766221000221460ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2019, DCSO GmbH import ( "encoding/json" "fmt" "math/rand" "reflect" "testing" "time" "github.com/DCSO/fever/types" log "github.com/sirupsen/logrus" ) func makeCCTestEvent(eType, flowID string) types.Entry { e := types.Entry{ SrcIP: fmt.Sprintf("10.%d.%d.%d", rand.Intn(250), rand.Intn(250), rand.Intn(250)), SrcPort: []int64{1, 2, 3, 4, 5}[rand.Intn(5)], DestIP: fmt.Sprintf("10.0.0.%d", rand.Intn(250)), DestPort: []int64{11, 12, 13, 14, 15}[rand.Intn(5)], Timestamp: time.Now().Format(types.SuricataTimestampFormat), EventType: eType, Proto: "TCP", FlowID: flowID, } jsonBytes, _ := json.Marshal(e) e.JSONLine = string(jsonBytes) return e } func TestContextCollector(t *testing.T) { markedVals := make(map[string][]string) seenMarked := make(map[string][]string) dsub := func(entries Context, logger *log.Entry) error { for _, v := range entries { var parsed struct { FlowID string } err := json.Unmarshal([]byte(v), &parsed) if err != nil { t.Fatal(err) } seenMarked[parsed.FlowID] = append(seenMarked[parsed.FlowID], v) } return nil } cc := MakeContextCollector(dsub, 5*time.Minute) nofReports := 0 for i := 0; i < 10000; i++ { isMarked := (rand.Intn(20) < 1) flowID := fmt.Sprintf("%d", rand.Intn(10000000)+10000) if isMarked { nofReports++ cc.Mark(flowID) } for j := 0; j < rand.Intn(200)+1; j++ { ev := makeCCTestEvent([]string{"http", "smb", "dns"}[rand.Intn(3)], flowID) if isMarked { markedVals[flowID] = append(markedVals[flowID], ev.JSONLine) } cc.Consume(&ev) } ev := makeCCTestEvent("flow", flowID) cc.Consume(&ev) } if len(markedVals) != len(seenMarked) { t.Fatalf("number of marked flows (%d) != number of results (%d)", len(markedVals), len(seenMarked)) } if !reflect.DeepEqual(markedVals, seenMarked) { t.Fatal("contents of results and recorded metadata maps differ") } } func TestContextCollectorMissingFlowID(t *testing.T) { e := types.Entry{ Timestamp: time.Now().Format(types.SuricataTimestampFormat), EventType: "stats", } jsonBytes, _ := json.Marshal(e) e.JSONLine = string(jsonBytes) count := 0 dsub := func(entries Context, logger *log.Entry) error { count++ return nil } cc := MakeContextCollector(dsub, 5*time.Minute) cc.Consume(&e) if count != 0 { t.Fatalf("event with empty flow ID was considered") } flowID := "12345" cc.Mark(flowID) ev := makeCCTestEvent("dns", flowID) cc.Consume(&ev) ev = makeCCTestEvent("flow", flowID) cc.Consume(&ev) if count != 1 { t.Fatalf("wrong number of entries: %d", count) } } fever-1.3.7/processing/context_shipper_amqp.go000066400000000000000000000034301477766221000216040ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2019, DCSO GmbH import ( "encoding/json" "time" "github.com/DCSO/fever/util" log "github.com/sirupsen/logrus" ) const ( // ContextQueueLength is the length of the queue buffering incoming context // bundles to balance out potential transmission delays. ContextQueueLength = 100 ) // ContextChunk represents a collection of events for transmission via AMQP. type ContextChunk struct { Timestamp time.Time `json:"timestamp"` SensorID string `json:"sensor_id"` Events []interface{} `json:"events"` } // ContextShipperAMQP is a ContextShipper that sends incoming context bundles to // an AMQP exchange. type ContextShipperAMQP struct { Submitter util.StatsSubmitter InChan chan Context SensorID string } // Start initiates the concurrent handling of incoming context bundles in the // Shipper's input channel. It will stop automatically once this channel is // closed. func (cs *ContextShipperAMQP) Start(s util.StatsSubmitter) (chan<- Context, error) { var err error cs.Submitter = s cs.InChan = make(chan Context, ContextQueueLength) cs.SensorID, err = util.GetSensorID() if err != nil { return nil, err } go func() { for ctx := range cs.InChan { out := make([]interface{}, 0) for _, ctxItem := range ctx { var myItem interface{} err := json.Unmarshal([]byte(ctxItem), &myItem) if err != nil { log.Warnf("could not marshal event JSON: %s", string(ctxItem)) continue } out = append(out, myItem) } chunk := ContextChunk{ Timestamp: time.Now(), SensorID: cs.SensorID, Events: out, } json, err := json.Marshal(chunk) if err != nil { log.Warn(err) continue } s.Submit(json, "context", "application/json") } }() return cs.InChan, nil } fever-1.3.7/processing/context_shipper_amqp_test.go000066400000000000000000000057141477766221000226520ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2019, 2020, DCSO GmbH import ( "bytes" "fmt" "strings" "testing" "time" "github.com/DCSO/fever/util" "github.com/NeowayLabs/wabbit" "github.com/NeowayLabs/wabbit/amqptest" "github.com/NeowayLabs/wabbit/amqptest/server" log "github.com/sirupsen/logrus" "github.com/sirupsen/logrus/hooks/test" ) func TestContextShipperAMQP(t *testing.T) { serverURL := "amqp://sensor:sensor@localhost:9988/%2f/" log.SetLevel(log.DebugLevel) // start mock server fakeServer := server.NewServer(serverURL) fakeServer.Start() // set up consumer allDone := make(chan bool) coll := make([]string, 0) c, err := util.NewConsumer(serverURL, "context", "direct", "context", "context", "foo-test1", func(d wabbit.Delivery) { coll = append(coll, string(d.Body())) if len(coll) == 4 { allDone <- true } }) if err != nil { t.Fatal(err) } // set up submitter submitter, err := util.MakeAMQPSubmitterWithReconnector(serverURL, "context", true, func(url string) (wabbit.Conn, error) { // we pass in a custom reconnector which uses the amqptest implementation var conn wabbit.Conn conn, err = amqptest.Dial(url) return conn, err }) if err != nil { t.Fatal(err) } cs := &ContextShipperAMQP{} inChan, err := cs.Start(submitter) if err != nil { t.Fatal(err) } inChan <- Context{`{"value":"c1"}`} inChan <- Context{`{"value":"c2"}`} inChan <- Context{`{"value":"c3"}`} inChan <- Context{`{"value":"c4"}`} // ... and wait until they are received and processed <-allDone // check if output is correct if len(coll) != 4 { t.Fail() } if !strings.Contains(coll[0], `"value":"c1"`) { t.Fatalf("value 1 incorrect: %v", coll[0]) } if !strings.Contains(coll[1], `"value":"c2"`) { t.Fatalf("value 2 incorrect: %v", coll[1]) } if !strings.Contains(coll[2], `"value":"c3"`) { t.Fatalf("value 3 incorrect: %v", coll[2]) } if !strings.Contains(coll[3], `"value":"c4"`) { t.Fatalf("value 4 incorrect: %v", coll[3]) } close(inChan) // tear down test setup submitter.Finish() fakeServer.Stop() c.Shutdown() } func TestContextShipperAMQPBrokenJSON(t *testing.T) { cs := &ContextShipperAMQP{} ds, _ := util.MakeDummySubmitter() inChan, err := cs.Start(ds) if err != nil { t.Fatal(err) } hook := test.NewGlobal() var entries []*log.Entry inChan <- Context{`{""value":1}`} for i := 0; i < 60; i++ { time.Sleep(1 * time.Second) entries = hook.AllEntries() if len(entries) > 0 { break } if i > 58 { t.Fatalf("timed out trying to receive error message for malformed JSON") } } close(inChan) found := false for _, entry := range entries { if entry.Message == `could not marshal event JSON: {""value":1}` { found = true break } } if !found { var entryStrings bytes.Buffer for i, entry := range entries { entryStrings.WriteString(fmt.Sprintf("%03d: %s\n", i, entry.Message)) } t.Fatalf("malformed JSON error message not found: %v", entryStrings.String()) } } fever-1.3.7/processing/dns_aggregator.go000066400000000000000000000137441477766221000203470ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2017, DCSO GmbH import ( "bytes" "encoding/json" "os" "sync" "time" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" log "github.com/sirupsen/logrus" ) // DNSAggregatorPerfStats contains performance stats written to InfluxDB // for monitoring. type DNSAggregatorPerfStats struct { DNSAggregateRawCount uint64 `influx:"dns_aggregate_raw_count"` DNSAggregateCount uint64 `influx:"dns_aggregate_count"` } // AggregateDNSReplyDetails holds data for a query tuple. type AggregateDNSReplyDetails struct { Rrtype string `json:"rrtype,omitempty"` Rdata string `json:"rdata,omitempty"` Rcode string `json:"rcode,omitempty"` Type string `json:"type,omitempty"` } // AggregatedDNSDetails holds summarized traffic stats for a given // AggregateDNSEvent. type AggregatedDNSDetails struct { Rrname string `json:"rrname,omitempty"` Details []AggregateDNSReplyDetails `json:"rdata,omitempty"` } // AggregateDNSEvent holds aggregated flow data. type AggregateDNSEvent struct { Timestamp []string `json:"timestamp"` EventType string `json:"event_type"` SrcIP []string `json:"src_ip,omitempty"` SrcPort []int `json:"src_port,omitempty"` DestIP []string `json:"dest_ip,omitempty"` DestPort int `json:"dest_port,omitempty"` DNS AggregatedDNSDetails `json:"dns,omitempty"` } // DNSAggregator is an aggregator that groups DNS events with the same // domain name. type DNSAggregator struct { SensorID string Count int64 DNSMutex sync.RWMutex DNS map[string]*AggregateDNSEvent PerfStats DNSAggregatorPerfStats StatsEncoder *util.PerformanceStatsEncoder SrcIPSet map[string]bool DestIPSet map[string]bool AnswerSet map[string]bool StringBuf bytes.Buffer FlushPeriod time.Duration DatabaseOutChan chan types.Entry CloseChan chan bool ClosedChan chan bool Logger *log.Entry } // MakeDNSAggregator creates a new empty DNSAggregator. func MakeDNSAggregator(flushPeriod time.Duration, outChan chan types.Entry) *DNSAggregator { a := &DNSAggregator{ FlushPeriod: flushPeriod, Logger: log.WithFields(log.Fields{ "domain": "dns_aggregate", }), DNS: make(map[string]*AggregateDNSEvent), SrcIPSet: make(map[string]bool), DestIPSet: make(map[string]bool), AnswerSet: make(map[string]bool), DatabaseOutChan: outChan, CloseChan: make(chan bool), ClosedChan: make(chan bool), } a.SensorID, _ = os.Hostname() return a } func (a *DNSAggregator) flush() { // reset live counters a.DNSMutex.Lock() myDNS := a.DNS myCount := a.Count a.DNS = make(map[string]*AggregateDNSEvent) a.SrcIPSet = make(map[string]bool) a.DestIPSet = make(map[string]bool) a.AnswerSet = make(map[string]bool) a.Count = 0 a.PerfStats.DNSAggregateCount = uint64(len(myDNS)) a.PerfStats.DNSAggregateRawCount = uint64(myCount) a.DNSMutex.Unlock() if a.StatsEncoder != nil { a.StatsEncoder.Submit(a.PerfStats) } a.Logger.WithFields(log.Fields{ "agg_dns": a.PerfStats.DNSAggregateCount, "in_dns": a.PerfStats.DNSAggregateRawCount, }).Debug("flushing events") for _, v := range myDNS { jsonString, _ := json.Marshal(v) newEntry := types.Entry{ Timestamp: v.Timestamp[0], EventType: v.EventType, JSONLine: string(jsonString[:]), } a.DatabaseOutChan <- newEntry } } func (a *DNSAggregator) countRequest(key string, e *types.Entry) { a.DNSMutex.Lock() a.Count++ if _, ok := a.DNS[key]; !ok { a.DNS[key] = &AggregateDNSEvent{ Timestamp: []string{e.Timestamp}, EventType: "dns", SrcIP: []string{e.SrcIP}, SrcPort: []int{int(e.SrcPort)}, DestIP: []string{e.DestIP}, DestPort: int(e.DestPort), DNS: AggregatedDNSDetails{ Rrname: e.DNSRRName, Details: []AggregateDNSReplyDetails{ AggregateDNSReplyDetails{ Rrtype: e.DNSRRType, Rdata: e.DNSRData, Rcode: e.DNSRCode, Type: e.DNSType, }, }, }, } } else { req := a.DNS[key] req.SrcPort = append(req.SrcPort, int(e.SrcPort)) if _, ok := a.SrcIPSet[e.SrcIP]; !ok { req.SrcIP = append(req.SrcIP, e.SrcIP) a.SrcIPSet[e.SrcIP] = true } if _, ok := a.DestIPSet[e.DestIP]; !ok { req.DestIP = append(req.DestIP, e.DestIP) a.DestIPSet[e.DestIP] = true } a.StringBuf.Write([]byte(e.DNSRRType)) a.StringBuf.Write([]byte(e.DNSRData)) a.StringBuf.Write([]byte(e.DNSRCode)) a.StringBuf.Write([]byte(e.DNSType)) if _, ok = a.AnswerSet[a.StringBuf.String()]; !ok { req.DNS.Details = append(req.DNS.Details, AggregateDNSReplyDetails{ Rrtype: e.DNSRRType, Rdata: e.DNSRData, Rcode: e.DNSRCode, Type: e.DNSType, }) } a.StringBuf.Reset() } a.DNSMutex.Unlock() } // Consume processes an Entry, adding the data within to the internal // aggregated state func (a *DNSAggregator) Consume(e *types.Entry) error { a.countRequest(e.DNSRRName, e) return nil } // Run starts the background aggregation service for this handler func (a *DNSAggregator) Run() { go func() { i := 0 * time.Second for { select { case <-a.CloseChan: close(a.ClosedChan) return default: if i >= a.FlushPeriod { a.flush() i = 0 * time.Second } time.Sleep(1 * time.Second) i += 1 * time.Second } } }() } // Stop causes the aggregator to cease aggregating and submitting data func (a *DNSAggregator) Stop(stopChan chan bool) { close(a.CloseChan) <-a.ClosedChan close(stopChan) } // SubmitStats registers a PerformanceStatsEncoder for runtime stats submission. func (a *DNSAggregator) SubmitStats(sc *util.PerformanceStatsEncoder) { a.StatsEncoder = sc } // GetName returns the name of the handler func (a *DNSAggregator) GetName() string { return "DB DNS aggregator" } // GetEventTypes returns a slice of event type strings that this handler // should be applied to func (a *DNSAggregator) GetEventTypes() []string { return []string{"dns"} } fever-1.3.7/processing/dns_aggregator_test.go000066400000000000000000000072221477766221000214000ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2017, 2019, DCSO GmbH import ( "bytes" "encoding/json" "fmt" "math/rand" "sync" "testing" "time" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" ) const ( numTestEvents = 100000 ) func makeDNSEvent() types.Entry { e := types.Entry{ SrcIP: fmt.Sprintf("10.0.0.%d", rand.Intn(5)+1), SrcPort: 53, DestIP: fmt.Sprintf("10.0.0.%d", rand.Intn(50)), DestPort: []int64{11, 12, 13, 14, 15}[rand.Intn(5)], Timestamp: time.Now().Format(types.SuricataTimestampFormat), EventType: "DNS", Proto: "TCP", DNSRCode: []string{"NOERROR", "NXDOMAIN"}[rand.Intn(2)], DNSRData: fmt.Sprintf("10.%d.0.%d", rand.Intn(50), rand.Intn(50)+100), DNSRRName: fmt.Sprintf("%s.com", util.RndStringFromAlpha(4)), DNSRRType: "answer", } return e } func TestDNSAggregator(t *testing.T) { rand.Seed(time.Now().UTC().UnixNano()) outChan := make(chan types.Entry) consumeWaitChan := make(chan bool) closeChan := make(chan bool) f := MakeDNSAggregator(1*time.Second, outChan) daTypes := f.GetEventTypes() if len(daTypes) != 1 { t.Fatal("DNS aggregation handler should only claim one type") } if daTypes[0] != "dns" { t.Fatal("DNS aggregation handler should only claim 'dns' type") } if f.GetName() != "DB DNS aggregator" { t.Fatal("DNS aggregation handler has wrong name") } var observedLock sync.Mutex observedSituations := make(map[string]int) observedDomains := make(map[string]bool) setupSituations := make(map[string]int) setupDomains := make(map[string]bool) go func() { var buf bytes.Buffer for { select { case e := <-outChan: var out AggregateDNSEvent err := json.Unmarshal([]byte(e.JSONLine), &out) if err != nil { t.Fail() } for _, v := range out.DNS.Details { buf.Write([]byte(out.DNS.Rrname)) buf.Write([]byte(v.Rrtype)) buf.Write([]byte(v.Rdata)) buf.Write([]byte(v.Rcode)) observedLock.Lock() observedSituations[buf.String()]++ observedLock.Unlock() observedDomains[out.DNS.Rrname] = true buf.Reset() } case <-closeChan: close(consumeWaitChan) return } } }() f.Run() for i := 0; i < numTestEvents; i++ { var buf bytes.Buffer ev := makeDNSEvent() buf.Write([]byte(ev.DNSRRName)) buf.Write([]byte(ev.DNSRRType)) buf.Write([]byte(ev.DNSRData)) buf.Write([]byte(ev.DNSRCode)) setupSituations[buf.String()]++ setupDomains[ev.DNSRRName] = true buf.Reset() f.Consume(&ev) } go func() { for { observedLock.Lock() if len(setupSituations) <= len(observedSituations) { observedLock.Unlock() break } observedLock.Unlock() time.Sleep(100 * time.Millisecond) } close(closeChan) }() <-consumeWaitChan close(outChan) waitChan := make(chan bool) f.Stop(waitChan) <-waitChan if len(setupSituations) != len(observedSituations) { t.Fatalf("results have different dimensions: %d/%d", len(setupSituations), len(observedSituations)) } for k, v := range setupSituations { if _, ok := observedSituations[k]; !ok { t.Fatalf("missing key: %s", k) } v2 := observedSituations[k] if v2 != v { t.Fatalf("mismatching counts for key %s: %d/%d", k, v, v2) } } for k, v := range observedSituations { if _, ok := setupSituations[k]; !ok { t.Fatalf("missing key: %s", k) } v2 := setupSituations[k] if v2 != v { t.Fatalf("mismatching counts for key %s: %d/%d", k, v, v2) } } if len(setupDomains) != len(observedDomains) { t.Fatalf("results have different dimensions: %d/%d", len(setupDomains), len(observedDomains)) } for k := range observedDomains { if _, ok := setupDomains[k]; !ok { t.Fatalf("missing key: %s", k) } } } fever-1.3.7/processing/event_profiler.go000066400000000000000000000076761477766221000204130ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2018, DCSO GmbH import ( "bytes" "fmt" "os" "os/exec" "sync" "time" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" log "github.com/sirupsen/logrus" ) // EventProfile contains counts per event_type such as occurrences and // JSON size. type EventProfile struct { CountMap map[string]uint64 SizeMap map[string]uint64 } // EventProfiler counts EVE event type statistics, such as number and size // of JSON data received from the input. type EventProfiler struct { SensorID string Host string Profile EventProfile FlushPeriod time.Duration ProfileMutex sync.Mutex CloseChan chan bool ClosedChan chan bool Logger *log.Entry Submitter util.StatsSubmitter SubmitChannel chan []byte } func getFQDN() (fqdn string) { cmd := exec.Command("/bin/hostname", "-f") var out bytes.Buffer cmd.Stdout = &out err := cmd.Run() if err != nil { log.Warn(err) host, err := os.Hostname() if err != nil { return "unknown" } return host } fqdn = out.String() if len(fqdn) > 1 { fqdn = fqdn[:len(fqdn)-1] } else { fqdn = "unknown" } return fqdn } // MakeEventProfiler creates a new EventProfiler. func MakeEventProfiler(flushPeriod time.Duration, submitter util.StatsSubmitter) (*EventProfiler, error) { sensorID, err := util.GetSensorID() if err != nil { return nil, err } a := &EventProfiler{ FlushPeriod: flushPeriod, Logger: log.WithFields(log.Fields{ "domain": "eventprofiler", }), Profile: EventProfile{ CountMap: make(map[string]uint64), SizeMap: make(map[string]uint64), }, CloseChan: make(chan bool), ClosedChan: make(chan bool), SubmitChannel: make(chan []byte, 60), Submitter: submitter, SensorID: sensorID, } a.SensorID, _ = os.Hostname() a.Host = getFQDN() return a, nil } func (a *EventProfiler) formatLineProtocol() string { out := "" a.ProfileMutex.Lock() myProfile := a.Profile first := true for k, v := range myProfile.SizeMap { if !first { out += "," } else { first = false } out += fmt.Sprintf("size.%s=%d", k, v) } for k, v := range myProfile.CountMap { out += fmt.Sprintf(",count.%s=%d", k, v) } a.ProfileMutex.Unlock() if out == "" { return "" } return fmt.Sprintf("%s,host=%s %s %d", util.ToolName, a.Host, out, uint64(time.Now().UnixNano())) } func (a *EventProfiler) flush() { lineString := a.formatLineProtocol() if lineString == "" { return } select { case a.SubmitChannel <- []byte(lineString): break default: log.Warning("channel is full, cannot submit message...") } } // Consume processes an Entry, adding the data within to the internal // aggregated state func (a *EventProfiler) Consume(e *types.Entry) error { etype := e.EventType a.ProfileMutex.Lock() a.Profile.CountMap[etype]++ a.Profile.SizeMap[etype] += uint64(len(e.JSONLine)) a.ProfileMutex.Unlock() return nil } // Run starts the background aggregation service for this handler func (a *EventProfiler) Run() { go func() { for message := range a.SubmitChannel { a.Submitter.SubmitWithHeaders(message, "", "text/plain", map[string]string{ "database": "telegraf", "retention_policy": "default", }) } }() go func() { i := 0 * time.Second for { select { case <-a.CloseChan: close(a.SubmitChannel) close(a.ClosedChan) return default: if i >= a.FlushPeriod { a.flush() i = 0 * time.Second } time.Sleep(1 * time.Second) i += 1 * time.Second } } }() } // Stop causes the aggregator to cease aggregating and submitting data func (a *EventProfiler) Stop(stopChan chan bool) { close(a.CloseChan) <-a.ClosedChan close(stopChan) } // GetName returns the name of the handler func (a *EventProfiler) GetName() string { return "Event profiler" } // GetEventTypes returns a slice of event type strings that this handler // should be applied to func (a *EventProfiler) GetEventTypes() []string { return []string{"*"} } fever-1.3.7/processing/flow_aggregator.go000066400000000000000000000123441477766221000205250ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2017, DCSO GmbH import ( "bytes" "encoding/json" "fmt" "os" "sync" "time" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" log "github.com/sirupsen/logrus" ) // FlowAggregatorPerfStats contains performance stats written to InfluxDB // for monitoring. type FlowAggregatorPerfStats struct { FlowAggregateRawCount uint64 `influx:"flow_aggregate_raw_count"` FlowAggregateCount uint64 `influx:"flow_aggregate_count"` } // AggregatedFlowDetails holds summarized traffic stats for a given // AggregateFlowEvent. type AggregatedFlowDetails struct { PktsToserver int64 `json:"pkts_toserver"` PktsToclient int64 `json:"pkts_toclient"` BytesToserver int64 `json:"bytes_toserver"` BytesToclient int64 `json:"bytes_toclient"` } // AggregateFlowEvent holds aggregated flow data. type AggregateFlowEvent struct { Timestamp []string `json:"timestamp"` EventType string `json:"event_type"` SrcIP string `json:"src_ip,omitempty"` SrcPort []int `json:"src_port,omitempty"` DestIP string `json:"dest_ip,omitempty"` DestPort int `json:"dest_port,omitempty"` Flow AggregatedFlowDetails `json:"flow,omitempty"` } // FlowAggregator is an aggregator that groups flows with the same combination // of srcIP/destIP/destPort. type FlowAggregator struct { SensorID string Count int64 FlowsMutex sync.RWMutex Flows map[string]*AggregateFlowEvent PerfStats FlowAggregatorPerfStats StatsEncoder *util.PerformanceStatsEncoder FlushPeriod time.Duration StringBuf bytes.Buffer DatabaseOutChan chan types.Entry CloseChan chan bool ClosedChan chan bool Logger *log.Entry } // MakeFlowAggregator creates a new empty FlowAggregator. func MakeFlowAggregator(flushPeriod time.Duration, outChan chan types.Entry) *FlowAggregator { a := &FlowAggregator{ FlushPeriod: flushPeriod, Logger: log.WithFields(log.Fields{ "domain": "flow_aggregate", }), Flows: make(map[string]*AggregateFlowEvent), DatabaseOutChan: outChan, CloseChan: make(chan bool), ClosedChan: make(chan bool), } a.SensorID, _ = os.Hostname() return a } func (a *FlowAggregator) flush() { a.FlowsMutex.Lock() myFlows := a.Flows myCount := a.Count a.Flows = make(map[string]*AggregateFlowEvent) a.Count = 0 a.PerfStats.FlowAggregateRawCount = uint64(myCount) a.PerfStats.FlowAggregateCount = uint64(len(myFlows)) a.FlowsMutex.Unlock() if a.StatsEncoder != nil { a.StatsEncoder.Submit(a.PerfStats) } a.Logger.WithFields(log.Fields{ "agg_flows": a.PerfStats.FlowAggregateCount, "in_flows": a.PerfStats.FlowAggregateRawCount, }).Info("flushing events") for _, v := range myFlows { jsonString, _ := json.Marshal(v) newEntry := types.Entry{ SrcIP: v.SrcIP, SrcPort: int64(v.SrcPort[0]), DestIP: v.DestIP, DestPort: int64(v.DestPort), Timestamp: v.Timestamp[0], EventType: v.EventType, JSONLine: string(jsonString[:]), } a.DatabaseOutChan <- newEntry } } func (a *FlowAggregator) countFlow(key string, e *types.Entry) { a.FlowsMutex.Lock() a.Count++ if _, ok := a.Flows[key]; !ok { a.Flows[key] = &AggregateFlowEvent{ Timestamp: []string{e.Timestamp}, EventType: "flow", SrcIP: e.SrcIP, SrcPort: []int{int(e.SrcPort)}, DestIP: e.DestIP, DestPort: int(e.DestPort), Flow: AggregatedFlowDetails{ PktsToserver: e.PktsToServer, PktsToclient: e.PktsToClient, BytesToserver: e.BytesToServer, BytesToclient: e.BytesToClient, }, } } else { flow := a.Flows[key] flow.SrcPort = append(flow.SrcPort, int(e.SrcPort)) flow.Flow.PktsToserver += e.PktsToServer flow.Flow.PktsToclient += e.PktsToClient flow.Flow.BytesToserver += e.BytesToServer flow.Flow.BytesToclient += e.BytesToClient } a.FlowsMutex.Unlock() } // Consume processes an Entry, adding the data within to the internal // aggregated state func (a *FlowAggregator) Consume(e *types.Entry) error { a.StringBuf.Write([]byte(e.SrcIP)) a.StringBuf.Write([]byte(e.DestIP)) a.StringBuf.Write([]byte(fmt.Sprint(e.DestPort))) a.countFlow(a.StringBuf.String(), e) a.StringBuf.Reset() return nil } // Run starts the background aggregation service for this handler func (a *FlowAggregator) Run() { go func() { i := 0 * time.Second for { select { case <-a.CloseChan: close(a.ClosedChan) return default: if i >= a.FlushPeriod { a.flush() i = 0 * time.Second } time.Sleep(1 * time.Second) i += 1 * time.Second } } }() } // SubmitStats registers a PerformanceStatsEncoder for runtime stats submission. func (a *FlowAggregator) SubmitStats(sc *util.PerformanceStatsEncoder) { a.StatsEncoder = sc } // Stop causes the aggregator to cease aggregating and submitting data func (a *FlowAggregator) Stop(stopChan chan bool) { close(a.CloseChan) <-a.ClosedChan close(stopChan) } // GetName returns the name of the handler func (a *FlowAggregator) GetName() string { return "DB flow aggregator" } // GetEventTypes returns a slice of event type strings that this handler // should be applied to func (a *FlowAggregator) GetEventTypes() []string { return []string{"flow"} } fever-1.3.7/processing/flow_aggregator_test.go000066400000000000000000000144351477766221000215670ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2017, 2019, DCSO GmbH import ( "encoding/json" "fmt" "math/rand" "sync" "testing" "time" "github.com/DCSO/fever/types" ) const ( numOfTestFlowItems = 200000 ) func makeFlowEvent() types.Entry { e := types.Entry{ SrcIP: fmt.Sprintf("10.0.0.%d", rand.Intn(250)), SrcPort: []int64{1, 2, 3, 4, 5}[rand.Intn(5)], DestIP: fmt.Sprintf("10.0.0.%d", rand.Intn(250)), DestPort: []int64{11, 12, 13, 14, 15}[rand.Intn(5)], Timestamp: time.Now().Format(types.SuricataTimestampFormat), EventType: "flow", Proto: "TCP", BytesToClient: int64(rand.Intn(10000)), BytesToServer: int64(rand.Intn(10000)), PktsToClient: int64(rand.Intn(100)), PktsToServer: int64(rand.Intn(100)), } jsonBytes, _ := json.Marshal(e) e.JSONLine = string(jsonBytes) return e } func TestFlowAggregator(t *testing.T) { rand.Seed(time.Now().UTC().UnixNano()) outChan := make(chan types.Entry) feedWaitChan := make(chan bool) closeChan := make(chan bool) f := MakeFlowAggregator(1*time.Second, outChan) var procFlowsLock sync.Mutex var processedFlows int var eTotalPktsToClient int64 var eTotalPktsToServer int64 var eTotalBytesToClient int64 var eTotalBytesToServer int64 var rTotalPktsToClient int64 var rTotalPktsToServer int64 var rTotalBytesToClient int64 var rTotalBytesToServer int64 go func(pc *int64, ps *int64, bc *int64, bs *int64) { for { select { case e := <-outChan: var out struct { SrcPort []int `json:"src_port"` Flow struct { BytesToServer int64 `json:"bytes_toserver"` BytesToClient int64 `json:"bytes_toclient"` PktsToServer int64 `json:"pkts_toserver"` PktsToClient int64 `json:"pkts_toclient"` } `json:"flow"` } err := json.Unmarshal([]byte(e.JSONLine), &out) if err != nil { t.Fail() } // we count the source ports to determine the number of // aggregated flows procFlowsLock.Lock() processedFlows += len(out.SrcPort) procFlowsLock.Unlock() *bc += out.Flow.BytesToClient *bs += out.Flow.BytesToServer *pc += out.Flow.PktsToClient *ps += out.Flow.PktsToServer case <-closeChan: close(feedWaitChan) return } } }(&rTotalPktsToClient, &rTotalPktsToServer, &rTotalBytesToClient, &rTotalBytesToServer) f.Run() for i := 0; i < numOfTestFlowItems; i++ { ev := makeFlowEvent() eTotalBytesToClient += ev.BytesToClient eTotalBytesToServer += ev.BytesToServer eTotalPktsToClient += ev.PktsToClient eTotalPktsToServer += ev.PktsToServer f.Consume(&ev) } go func() { for { procFlowsLock.Lock() if processedFlows == numOfTestFlowItems { procFlowsLock.Unlock() break } procFlowsLock.Unlock() time.Sleep(100 * time.Millisecond) } close(closeChan) }() <-feedWaitChan consumeWaitChan := make(chan bool) f.Stop(consumeWaitChan) <-consumeWaitChan if eTotalBytesToClient != rTotalBytesToClient { t.Fatalf("total bytes to client differ: %d/%d", eTotalBytesToClient, rTotalBytesToClient) } if eTotalBytesToServer != rTotalBytesToServer { t.Fatalf("total bytes to server differ: %d/%d", eTotalBytesToServer, rTotalBytesToServer) } if eTotalPktsToClient != rTotalPktsToClient { t.Fatalf("total pkts to client differ: %d/%d", eTotalPktsToClient, rTotalPktsToClient) } if eTotalPktsToServer != rTotalPktsToServer { t.Fatalf("total pkts to server differ: %d/%d", eTotalPktsToServer, rTotalPktsToServer) } } func TestFlowAggregatorWithDispatch(t *testing.T) { rand.Seed(time.Now().UTC().UnixNano()) outChan := make(chan types.Entry) dbChan := make(chan types.Entry, numOfTestFlowItems) feedWaitChan := make(chan bool) closeChan := make(chan bool) f := MakeFlowAggregator(1*time.Second, outChan) var procFlowsLock sync.Mutex var processedFlows int var eTotalPktsToClient int64 var eTotalPktsToServer int64 var eTotalBytesToClient int64 var eTotalBytesToServer int64 var rTotalPktsToClient int64 var rTotalPktsToServer int64 var rTotalBytesToClient int64 var rTotalBytesToServer int64 go func(pc *int64, ps *int64, bc *int64, bs *int64) { for { select { case e := <-outChan: var out struct { SrcPort []int `json:"src_port"` Flow struct { BytesToServer int64 `json:"bytes_toserver"` BytesToClient int64 `json:"bytes_toclient"` PktsToServer int64 `json:"pkts_toserver"` PktsToClient int64 `json:"pkts_toclient"` } `json:"flow"` } err := json.Unmarshal([]byte(e.JSONLine), &out) if err != nil { t.Fail() } // we count the source ports to determine the number of // aggregated flows procFlowsLock.Lock() processedFlows += len(out.SrcPort) procFlowsLock.Unlock() *bc += out.Flow.BytesToClient *bs += out.Flow.BytesToServer *pc += out.Flow.PktsToClient *ps += out.Flow.PktsToServer case <-closeChan: close(feedWaitChan) return } } }(&rTotalPktsToClient, &rTotalPktsToServer, &rTotalBytesToClient, &rTotalBytesToServer) d := MakeHandlerDispatcher(dbChan) d.RegisterHandler(f) f.Run() for i := 0; i < numOfTestFlowItems; i++ { ev := makeFlowEvent() eTotalBytesToClient += ev.BytesToClient eTotalBytesToServer += ev.BytesToServer eTotalPktsToClient += ev.PktsToClient eTotalPktsToServer += ev.PktsToServer d.Dispatch(&ev) } go func() { for { procFlowsLock.Lock() if processedFlows == numOfTestFlowItems { procFlowsLock.Unlock() break } procFlowsLock.Unlock() time.Sleep(100 * time.Millisecond) } close(closeChan) }() <-feedWaitChan consumeWaitChan := make(chan bool) f.Stop(consumeWaitChan) <-consumeWaitChan if len(dbChan) != numOfTestFlowItems { t.Fatalf("not all input events forwarded: %d", len(dbChan)) } close(dbChan) if eTotalBytesToClient != rTotalBytesToClient { t.Fatalf("total bytes to client differ: %d/%d", eTotalBytesToClient, rTotalBytesToClient) } if eTotalBytesToServer != rTotalBytesToServer { t.Fatalf("total bytes to server differ: %d/%d", eTotalBytesToServer, rTotalBytesToServer) } if eTotalPktsToClient != rTotalPktsToClient { t.Fatalf("total pkts to client differ: %d/%d", eTotalPktsToClient, rTotalPktsToClient) } if eTotalPktsToServer != rTotalPktsToServer { t.Fatalf("total pkts to server differ: %d/%d", eTotalPktsToServer, rTotalPktsToServer) } } fever-1.3.7/processing/flow_extractor.go000066400000000000000000000072641477766221000204230ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2017, DCSO GmbH import ( "bytes" "os" "strings" "sync" "time" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" "github.com/DCSO/bloom" log "github.com/sirupsen/logrus" ) // FlowExtractor is an aggregator that extracts the flows from // "hosts of interest" and sends them to the backend. type FlowExtractor struct { SensorID string BloomPath string BloomFilter *bloom.BloomFilter FlowsMutex sync.RWMutex flowCount int Flows *bytes.Buffer SubmitChannel chan []byte Submitter util.StatsSubmitter FlushPeriod time.Duration FlushCount int CloseChan chan bool ClosedChan chan bool Logger *log.Entry } // MakeFlowExtractor creates a new empty FlowExtractor. func MakeFlowExtractor(flushPeriod time.Duration, flushCount int, bloomPath string, submitter util.StatsSubmitter) (*FlowExtractor, error) { var bloomFilter *bloom.BloomFilter if bloomPath != "" { compressed := false if strings.HasSuffix(bloomPath, ".gz") { compressed = true } var err error bloomFilter, err = bloom.LoadFilter(bloomPath, compressed) if err != nil { return nil, err } } fe := &FlowExtractor{ FlushPeriod: flushPeriod, Submitter: submitter, BloomPath: bloomPath, Logger: log.WithFields(log.Fields{ "domain": "flow_extractor", }), Flows: new(bytes.Buffer), SubmitChannel: make(chan []byte, 60), BloomFilter: bloomFilter, CloseChan: make(chan bool), ClosedChan: make(chan bool), FlushCount: flushCount, flowCount: 0, } fe.SensorID, _ = os.Hostname() return fe, nil } func (fe *FlowExtractor) flush() { fe.FlowsMutex.Lock() myFlows := fe.Flows fe.Flows = new(bytes.Buffer) fe.flowCount = 0 fe.FlowsMutex.Unlock() select { case fe.SubmitChannel <- myFlows.Bytes(): break default: log.Warning("Flow channel is full, cannot submit message...") } } // Consume processes an Entry, adding the data within to the flows func (fe *FlowExtractor) Consume(e *types.Entry) error { fe.FlowsMutex.Lock() defer fe.FlowsMutex.Unlock() if fe.BloomFilter != nil { if !fe.BloomFilter.Check([]byte(e.SrcIP)) && !fe.BloomFilter.Check([]byte(e.DestIP)) { return nil } } var fev types.FlowEvent err := fev.FromEntry(e) if err != nil { return err } err = fev.Marshal(fe.Flows) fe.flowCount++ return err } // Run starts the background aggregation service for this handler func (fe *FlowExtractor) Run() { //this goroutine asynchronously submit flow messages go func() { for message := range fe.SubmitChannel { fe.Submitter.Submit(message, "", "application/binary-flows") } }() //this go routine takes care of flushing the flows go func() { i := 0 * time.Second interval := 100 * time.Millisecond for { select { case <-fe.CloseChan: close(fe.SubmitChannel) close(fe.ClosedChan) return default: //we flush if the flush period has passed, or if the count //of events is larger then the flush count fe.FlowsMutex.Lock() flowCount := fe.flowCount fe.FlowsMutex.Unlock() if i >= fe.FlushPeriod || flowCount > fe.FlushCount { fe.flush() i = 0 * time.Second } time.Sleep(interval) i += interval } } }() } // Stop causes the aggregator to cease aggregating and submitting data func (fe *FlowExtractor) Stop(stopChan chan bool) { close(fe.CloseChan) <-fe.ClosedChan close(stopChan) } // GetName returns the name of the handler func (fe *FlowExtractor) GetName() string { return "Flow extractor" } // GetEventTypes returns a slice of event type strings that this handler // should be applied to func (fe *FlowExtractor) GetEventTypes() []string { return []string{"flow"} } fever-1.3.7/processing/flow_extractor_test.go000066400000000000000000000120711477766221000214520ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2017, 2019, DCSO GmbH import ( "github.com/NeowayLabs/wabbit" "github.com/NeowayLabs/wabbit/amqptest" "github.com/NeowayLabs/wabbit/amqptest/server" "github.com/DCSO/bloom" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" "bytes" "fmt" "math/rand" "reflect" "sync" "testing" "time" ) const ( numFlowExtractorEvents = 100000 ) func makeFlowExtractorEvent(ipv6 bool) types.Entry { protos := []string{"TCP", "UDP"} n := rand.Int() % len(protos) var srcIP, destIP string if !ipv6 { srcIP = fmt.Sprintf("10.0.0.%d", rand.Intn(50)) destIP = fmt.Sprintf("10.0.0.%d", rand.Intn(50)) } else { srcIP = "2001:0db8:85a3:0000:0000:8a2e:0370:7334" destIP = "2001:0db8:85a3:0000:0000:8a2e:0370:7334" } e := types.Entry{ SrcIP: srcIP, SrcPort: []int64{1, 2, 3, 4, 5}[rand.Intn(5)], DestIP: destIP, DestPort: []int64{11, 12, 13, 14, 15}[rand.Intn(5)], Timestamp: time.Now().Format(types.SuricataTimestampFormat), EventType: "flow", Proto: protos[n], BytesToClient: int64(rand.Intn(10000)), BytesToServer: int64(rand.Intn(10000)), PktsToClient: int64(rand.Intn(100)), PktsToServer: int64(rand.Intn(100)), } return e } func makeBloomFilter() *bloom.BloomFilter { bf := bloom.Initialize(10000, 1e-10) for i := 0; i < 10000; i++ { bf.Add([]byte(fmt.Sprintf("10.0.0.%d", rand.Intn(50)))) } bf.Add([]byte("2001:0db8:85a3:0000:0000:8a2e:0370:7334")) return &bf } func flowExtractorWaitForResults(results *[]string, expectedFlows []types.Entry, resultsLock *sync.Mutex) []types.FlowEvent { defer resultsLock.Unlock() var flows []types.FlowEvent for { flows = make([]types.FlowEvent, 0) resultsLock.Lock() for i := range *results { result := (*results)[i] buffer := bytes.NewBufferString(result) for { var fe types.FlowEvent err := fe.Unmarshal(buffer) if err != nil { break } flows = append(flows, fe) } if len(flows) == len(expectedFlows) { return flows } } resultsLock.Unlock() time.Sleep(100 * time.Millisecond) } } func TestFlowExtractor(t *testing.T) { serverURL := "amqp://sensor:sensor@127.0.0.1:11111/%2f/" // start mock AMQP server fakeServer := server.NewServer(serverURL) fakeServer.Start() defer fakeServer.Stop() // set up consumer results := make([]string, 0) var resultsLock sync.Mutex c, err := util.NewConsumer(serverURL, "tdh.flows", "direct", "tdh.flows.testqueue", "", "", func(d wabbit.Delivery) { resultsLock.Lock() results = append(results, string(d.Body())) resultsLock.Unlock() }) if err != nil { t.Fatal(err) } defer c.Shutdown() // set up submitter submitter, err := util.MakeAMQPSubmitterWithReconnector(serverURL, "tdh.flows", true, func(url string) (wabbit.Conn, error) { var conn wabbit.Conn conn, err = amqptest.Dial(url) return conn, err }) if err != nil { t.Fatal(err) } defer submitter.Finish() mla, err := MakeFlowExtractor(1*time.Second, 100, "", submitter) mla.BloomFilter = makeBloomFilter() if err != nil { t.Fatal(err) } mla.Run() expectedFlows := make([]types.Entry, 0) for i := 0; i < numFlowExtractorEvents; i++ { ipv6 := false //we mix in some IPv6 packets... if rand.Intn(2) == 0 { ipv6 = true } ev := makeFlowExtractorEvent(ipv6) err := mla.Consume(&ev) if err != nil { t.Fatal(err) } if mla.BloomFilter.Check([]byte(ev.SrcIP)) || mla.BloomFilter.Check([]byte(ev.DestIP)) { expectedFlows = append(expectedFlows, ev) } } flows := flowExtractorWaitForResults(&results, expectedFlows, &resultsLock) stopChan := make(chan bool) mla.Stop(stopChan) <-stopChan if len(flows) != len(expectedFlows) { t.Fatalf("Error: Expected %d flows, got %d!", len(expectedFlows), len(flows)) } for i := range flows { flow := flows[i] expectedEntry := expectedFlows[i] var expectedFlow types.FlowEvent expectedFlow.FromEntry(&expectedEntry) if !reflect.DeepEqual(flow, expectedFlow) { t.Errorf("Flows do not match!") if flow.Format != expectedFlow.Format { t.Errorf("Formats do not match!") } if flow.Timestamp != expectedFlow.Timestamp { t.Errorf("Timestamps do not match!") } if !bytes.Equal(flow.SrcIP, expectedFlow.SrcIP) { t.Errorf("Source IPs do not match!") } if !bytes.Equal(flow.DestIP, expectedFlow.DestIP) { t.Errorf("Destination IPs do not match!") } if flow.SrcPort != expectedFlow.SrcPort { t.Errorf("Source Ports do not match!") } if flow.DestPort != expectedFlow.DestPort { t.Errorf("Destination Ports do not match!") } if flow.Flags != expectedFlow.Flags { t.Errorf("Flags do not match!") } if flow.BytesToServer != expectedFlow.BytesToServer { t.Errorf("BytesToServer do not match!") } if flow.BytesToClient != expectedFlow.BytesToClient { t.Errorf("BytesToClient do not match!") } if flow.PktsToServer != expectedFlow.PktsToServer { t.Errorf("PktsToServer do not match!") } if flow.PktsToClient != expectedFlow.PktsToClient { t.Errorf("PktsToClient do not match!") } } } } fever-1.3.7/processing/flow_notifier.go000066400000000000000000000016521477766221000202220ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2020, DCSO GmbH import ( "github.com/DCSO/fever/types" ) // FlowNotifier is a handler that just passes flow events on a // given channel once encountered. type FlowNotifier struct { FlowNotifyChan chan types.Entry } // MakeFlowNotifier creates a new FlowNotifier. func MakeFlowNotifier(outChan chan types.Entry) *FlowNotifier { notifier := &FlowNotifier{ FlowNotifyChan: outChan, } return notifier } // Consume processes an Entry, emitting an Entry on the output // channel func (n *FlowNotifier) Consume(e *types.Entry) error { n.FlowNotifyChan <- *e return nil } // GetName returns the name of the handler func (n *FlowNotifier) GetName() string { return "Flow notifier" } // GetEventTypes returns a slice of event type strings that this handler // should be applied to -- flow in this case. func (n *FlowNotifier) GetEventTypes() []string { return []string{"flow"} } fever-1.3.7/processing/flow_profiler.go000066400000000000000000000075721477766221000202340ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2020, DCSO GmbH import ( "fmt" "sync" "time" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" log "github.com/sirupsen/logrus" ) // ProtoProfile contains flow statistics for a give app layer protocol. type ProtoProfile struct { PacketsToSrv uint64 PacketsToClt uint64 BytesToSrv uint64 BytesToClt uint64 } // FlowProfiler counts EVE event type statistics, such as number and size // of JSON data received from the input. type FlowProfiler struct { SensorID string Host string Profile map[string]ProtoProfile FlushPeriod time.Duration ProfileMutex sync.Mutex CloseChan chan bool ClosedChan chan bool Logger *log.Entry Submitter util.StatsSubmitter SubmitChannel chan []byte SubmitChannelFull bool } // MakeFlowProfiler creates a new FlowProfiler. func MakeFlowProfiler(flushPeriod time.Duration, submitter util.StatsSubmitter) (*FlowProfiler, error) { a := &FlowProfiler{ FlushPeriod: flushPeriod, Logger: log.WithFields(log.Fields{ "domain": "flowprofiler", }), Profile: make(map[string]ProtoProfile), CloseChan: make(chan bool), ClosedChan: make(chan bool), SubmitChannel: make(chan []byte, 60), Submitter: submitter, } a.Host = getFQDN() return a, nil } func (a *FlowProfiler) formatLineProtocol() []string { out := make([]string, 0) a.ProfileMutex.Lock() myProfile := a.Profile for proto, protoVals := range myProfile { out = append(out, fmt.Sprintf("%s,host=%s,proto=%s flowbytestoclient=%d,flowbytestoserver=%d,flowpktstoclient=%d,flowpktstoserver=%d %d", util.ToolName, a.Host, proto, protoVals.BytesToClt, protoVals.BytesToSrv, protoVals.PacketsToClt, protoVals.PacketsToSrv, uint64(time.Now().UnixNano()))) a.Profile[proto] = ProtoProfile{} } a.ProfileMutex.Unlock() return out } func (a *FlowProfiler) flush() { lineStrings := a.formatLineProtocol() for _, lineString := range lineStrings { select { case a.SubmitChannel <- []byte(lineString): if a.SubmitChannelFull { log.Warning("channel was free to submit again") a.SubmitChannelFull = false } default: if !a.SubmitChannelFull { log.Warning("channel is full, cannot submit message...") a.SubmitChannelFull = true } } } } // Consume processes an Entry, adding the data within to the internal // aggregated state func (a *FlowProfiler) Consume(e *types.Entry) error { aproto := e.AppProto if aproto == "" { aproto = "unknown" } a.ProfileMutex.Lock() profile := a.Profile[aproto] profile.BytesToClt += uint64(e.BytesToClient) profile.BytesToSrv += uint64(e.BytesToServer) profile.PacketsToClt += uint64(e.PktsToClient) profile.PacketsToSrv += uint64(e.PktsToServer) a.Profile[aproto] = profile a.ProfileMutex.Unlock() return nil } // Run starts the background aggregation service for this handler func (a *FlowProfiler) Run() { go func() { for message := range a.SubmitChannel { a.Submitter.SubmitWithHeaders(message, "", "text/plain", map[string]string{ "database": "telegraf", "retention_policy": "default", }) } }() go func() { i := 0 * time.Second for { select { case <-a.CloseChan: close(a.SubmitChannel) close(a.ClosedChan) return default: if i >= a.FlushPeriod { a.flush() i = 0 * time.Second } time.Sleep(1 * time.Second) i += 1 * time.Second } } }() } // Stop causes the aggregator to cease aggregating and submitting data func (a *FlowProfiler) Stop(stopChan chan bool) { close(a.CloseChan) <-a.ClosedChan close(stopChan) } // GetName returns the name of the handler func (a *FlowProfiler) GetName() string { return "Flow profiler" } // GetEventTypes returns a slice of event type strings that this handler // should be applied to func (a *FlowProfiler) GetEventTypes() []string { return []string{"flow"} } fever-1.3.7/processing/flow_profiler_test.go000066400000000000000000000076541477766221000212740ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2020, DCSO GmbH import ( "encoding/json" "fmt" "math/rand" "reflect" "regexp" "strconv" "strings" "sync" "testing" "time" "github.com/DCSO/fever/types" ) const ( numOfProfiledFlowItems = 10000 ) func makeFlowProfilerEvent() types.Entry { e := types.Entry{ SrcIP: fmt.Sprintf("10.0.0.%d", rand.Intn(250)), SrcPort: []int64{1, 2, 3, 4, 5}[rand.Intn(5)], DestIP: fmt.Sprintf("10.0.0.%d", rand.Intn(250)), DestPort: []int64{11, 12, 13, 14, 15}[rand.Intn(5)], Timestamp: time.Now().Format(types.SuricataTimestampFormat), EventType: "flow", Proto: "TCP", AppProto: []string{"foo", "bar", "baz"}[rand.Intn(3)], BytesToClient: int64(rand.Intn(10000)), BytesToServer: int64(rand.Intn(10000)), PktsToClient: int64(rand.Intn(100)), PktsToServer: int64(rand.Intn(100)), } jsonBytes, _ := json.Marshal(e) e.JSONLine = string(jsonBytes) return e } type flowProfilerTestSubmitter struct { sync.Mutex Values [][]byte } func (fpts *flowProfilerTestSubmitter) SubmitWithHeaders(rawData []byte, key string, contentType string, myHeaders map[string]string) { fpts.Lock() defer fpts.Unlock() fpts.Values = append(fpts.Values, rawData) } func (fpts *flowProfilerTestSubmitter) Submit(rawData []byte, key string, contentType string) { fpts.Lock() defer fpts.Unlock() fpts.Values = append(fpts.Values, rawData) } func (fpts *flowProfilerTestSubmitter) UseCompression() { // pass } func (fpts *flowProfilerTestSubmitter) Finish() { // pass } // TestFlowProfiler checks whether flow profiles are generated correctly. // To do this, it consumes a set of example events with randomized event types // and sizes, generates a reference set of statistics and then compares it to // the values submitted to a test submitter which simply stores these values. func TestFlowProfiler(t *testing.T) { rand.Seed(time.Now().UTC().UnixNano()) myMap := make(map[string]ProtoProfile) seenProfile := make(map[string]ProtoProfile) feedWaitChan := make(chan bool) s := &flowProfilerTestSubmitter{ Values: make([][]byte, 0), } f, err := MakeFlowProfiler(1*time.Second, s) if err != nil { t.Fatal(err) } f.Run() for i := 0; i < numOfProfiledFlowItems; i++ { ev := makeFlowProfilerEvent() myProfile := myMap[ev.AppProto] myProfile.BytesToClt += uint64(ev.BytesToClient) myProfile.BytesToSrv += uint64(ev.BytesToServer) myProfile.PacketsToClt += uint64(ev.PktsToClient) myProfile.PacketsToSrv += uint64(ev.PktsToServer) myMap[ev.AppProto] = myProfile f.Consume(&ev) } go func() { r := regexp.MustCompile(`proto=(?P[^ ]+) flowbytestoclient=(?P[0-9]+),flowbytestoserver=(?P[0-9]+),flowpktstoclient=(?P[0-9]+),flowpktstoserver=(?P[0-9]+)`) for { s.Lock() found := 0 for _, v := range s.Values { for _, proto := range []string{"foo", "bar", "baz"} { if strings.Contains(string(v), fmt.Sprintf("proto=%s flowbytestoclient=0,flowbytestoserver=0,flowpktstoclient=0,flowpktstoserver=0", proto)) { found++ } } } s.Unlock() if found == 3 { break } time.Sleep(100 * time.Millisecond) } s.Lock() for _, v := range s.Values { sm := r.FindStringSubmatch(string(v)) if sm == nil { continue } p := seenProfile[sm[1]] intV, err := strconv.ParseUint(sm[2], 10, 64) if err == nil { p.BytesToClt += intV } intV, err = strconv.ParseUint(sm[3], 10, 64) if err == nil { p.BytesToSrv += intV } intV, err = strconv.ParseUint(sm[4], 10, 64) if err == nil { p.PacketsToClt += intV } intV, err = strconv.ParseUint(sm[5], 10, 64) if err == nil { p.PacketsToSrv += intV } seenProfile[sm[1]] = p } s.Unlock() close(feedWaitChan) }() <-feedWaitChan consumeWaitChan := make(chan bool) f.Stop(consumeWaitChan) <-consumeWaitChan if !reflect.DeepEqual(myMap, seenProfile) { t.Fatal("different result for test") } } fever-1.3.7/processing/forward_handler.go000066400000000000000000000053671477766221000205240ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2017, 2020, DCSO GmbH import ( "sync" "time" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" log "github.com/sirupsen/logrus" ) // ForwardHandler is a handler that processes events by writing their JSON // representation into a UNIX socket. This is limited by a list of allowed // event types to be forwarded. type ForwardHandler struct { Logger *log.Entry DoRDNS bool RDNSHandler *RDNSHandler AddedFields string ContextCollector *ContextCollector FlowNotifyChan chan types.Entry MultiFwdChan chan types.Entry Running bool Lock sync.Mutex } // MakeForwardHandler creates a new forwarding handler func MakeForwardHandler(multiFwdChan chan types.Entry) *ForwardHandler { fh := &ForwardHandler{ Logger: log.WithFields(log.Fields{ "domain": "forward", }), MultiFwdChan: multiFwdChan, } return fh } // Consume processes an Entry and prepares it to be sent off to the // forwarding sink func (fh *ForwardHandler) Consume(inEntry *types.Entry) error { // make copy to pass on from here e := *inEntry // mark flow as relevant when alert is seen if GlobalContextCollector != nil && e.EventType == types.EventTypeAlert { GlobalContextCollector.Mark(string(e.FlowID)) } // we also perform active rDNS enrichment if requested if fh.DoRDNS && fh.RDNSHandler != nil { err := fh.RDNSHandler.Consume(&e) if err != nil { return err } } // Replace the final brace `}` in the JSON with the prepared string to // add the 'added fields' defined in the config. I the length of this // string is 1 then there are no added fields, only a final brace '}'. // In this case we don't even need to modify the JSON string at all. if len(fh.AddedFields) > 1 { j := e.JSONLine l := len(j) j = j[:l-1] j += fh.AddedFields e.JSONLine = j } fh.MultiFwdChan <- e return nil } // GetName returns the name of the handler func (fh *ForwardHandler) GetName() string { return "Forwarding handler" } // GetEventTypes returns a slice of event type strings that this handler // should be applied to func (fh *ForwardHandler) GetEventTypes() []string { return []string{"*"} } // EnableRDNS switches on reverse DNS enrichment for source and destination // IPs in outgoing EVE events. func (fh *ForwardHandler) EnableRDNS(expiryPeriod time.Duration) { fh.DoRDNS = true fh.RDNSHandler = MakeRDNSHandler(util.NewHostNamerRDNS(expiryPeriod, 2*expiryPeriod)) } // AddFields enables the addition of a custom set of top-level fields to the // forwarded JSON. func (fh *ForwardHandler) AddFields(fields map[string]string) error { addedFields, err := util.PreprocessAddedFields(fields) if err != nil { return err } fh.AddedFields = addedFields return nil } fever-1.3.7/processing/forward_handler_test.go000066400000000000000000000170771477766221000215640ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2017, 2019, DCSO GmbH import ( "bufio" "encoding/json" "fmt" "io" "io/ioutil" "math/rand" "net" "os" "path/filepath" "strings" "sync" "testing" "time" "github.com/DCSO/fever/types" log "github.com/sirupsen/logrus" ) func makeMultiForwarder(fn string, all bool, types []string) MultiForwardConfiguration { mf := MultiForwardConfiguration{ Outputs: map[string]MultiForwardOutput{ "default": MultiForwardOutput{ Socket: fn, All: all, BufferLength: 100, Types: types, }, }, } return mf } func makeEvent(eType string, tag string) types.Entry { e := types.Entry{ SrcIP: fmt.Sprintf("10.0.0.%d", rand.Intn(5)+1), SrcPort: 53, DestIP: fmt.Sprintf("10.0.0.%d", rand.Intn(50)), DestPort: []int64{11, 12, 13, 14, 15}[rand.Intn(5)], Timestamp: time.Now().Format(types.SuricataTimestampFormat), EventType: eType, Proto: "TCP", } eve := types.EveEvent{ EventType: e.EventType, SrcIP: e.SrcIP, SrcPort: int(e.SrcPort), DestIP: e.DestIP, DestPort: int(e.DestPort), Proto: e.Proto, DNS: &types.DNSEvent{ Rrname: tag, }, } json, err := json.Marshal(eve) if err != nil { log.Warn(err) } else { e.JSONLine = string(json) } return e } func consumeSocket(inputListener net.Listener, stopChan chan bool, stoppedChan chan bool, t *testing.T, coll *[]string, wg *sync.WaitGroup) { for { select { case <-stopChan: close(stoppedChan) return default: var conn net.Conn inputListener.(*net.UnixListener).SetDeadline(time.Now().Add(1e9)) conn, err := inputListener.Accept() if nil != err { if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() { continue } t.Log(err) } reader := bufio.NewReaderSize(conn, 10485760) for { select { case <-stopChan: inputListener.Close() close(stoppedChan) return default: line, isPrefix, rerr := reader.ReadLine() if rerr == nil || rerr != io.EOF { if isPrefix { t.Log("incomplete line read from input") continue } else { *coll = append(*coll, string(line)) wg.Done() } } } } } } } func TestForwardHandler(t *testing.T) { dir, err := ioutil.TempDir("", "test") if err != nil { t.Fatal(err) } defer os.RemoveAll(dir) tmpfn := filepath.Join(dir, fmt.Sprintf("t%d", rand.Int63())) inputListener, err := net.Listen("unix", tmpfn) if err != nil { t.Fatal("error opening input socket:", err) } defer inputListener.Close() // prepare slice to hold collected strings coll := make([]string, 0) // setup comms channels clCh := make(chan bool) cldCh := make(chan bool) // start socket consumer var wg sync.WaitGroup wg.Add(2) go consumeSocket(inputListener, clCh, cldCh, t, &coll, &wg) // start forwarding handler c := make(chan types.Entry, 100) fh := MakeForwardHandler(c) mf := makeMultiForwarder(tmpfn, false, []string{"alert"}) mf.Run(c, 5) fhTypes := fh.GetEventTypes() if len(fhTypes) != 1 { t.Fatal("Forwarding handler should only claim one type") } if fhTypes[0] != "*" { t.Fatal("Forwarding handler should claim '*' type") } if fh.GetName() != "Forwarding handler" { t.Fatal("Forwarding handler has wrong name") } e := makeEvent("alert", "foo1") fh.Consume(&e) e = makeEvent("http", "foo2") fh.Consume(&e) e = makeEvent("alert", "foo3") fh.Consume(&e) // wait for socket consumer to receive all wg.Wait() if len(coll) != 2 { t.Fatalf("unexpected number of alerts: %d != 2", len(coll)) } var eve types.EveOutEvent err = json.Unmarshal([]byte(coll[0]), &eve) if err != nil { t.Fatal(err) } if eve.DNS.Rrname != "foo1" { t.Fatalf("invalid event data, expected 'foo1', got %s", eve.DNS.Rrname) } err = json.Unmarshal([]byte(coll[1]), &eve) if err != nil { t.Fatal(err) } if eve.DNS.Rrname != "foo3" { t.Fatalf("invalid event data, expected 'foo3', got %s", eve.DNS.Rrname) } } func TestForwardHandlerWithAddedFields(t *testing.T) { dir, err := ioutil.TempDir("", "test") if err != nil { t.Fatal(err) } defer os.RemoveAll(dir) tmpfn := filepath.Join(dir, fmt.Sprintf("t%d", rand.Int63())) inputListener, err := net.Listen("unix", tmpfn) if err != nil { t.Fatal("error opening input socket:", err) } defer inputListener.Close() // prepare slice to hold collected strings coll := make([]string, 0) // setup comms channels clCh := make(chan bool) cldCh := make(chan bool) // start socket consumer var wg sync.WaitGroup wg.Add(2) go consumeSocket(inputListener, clCh, cldCh, t, &coll, &wg) // start forwarding handler c := make(chan types.Entry) mf := makeMultiForwarder(tmpfn, false, []string{"alert"}) fh := MakeForwardHandler(c) mf.Run(c, 5) fh.AddFields(map[string]string{ "foo": "bar", }) fhTypes := fh.GetEventTypes() if len(fhTypes) != 1 { t.Fatal("Forwarding handler should only claim one type") } if fhTypes[0] != "*" { t.Fatal("Forwarding handler should claim '*' type") } if fh.GetName() != "Forwarding handler" { t.Fatal("Forwarding handler has wrong name") } e := makeEvent("alert", "foo1") fh.Consume(&e) e = makeEvent("alert", "foo2") fh.Consume(&e) // wait for socket consumer to receive all wg.Wait() if len(coll) != 2 { t.Fatalf("unexpected number of alerts: %d != 2", len(coll)) } var eve types.EveOutEvent err = json.Unmarshal([]byte(coll[0]), &eve) if err != nil { t.Fatal(err) } if !strings.Contains(coll[0], `"foo":"bar"`) { t.Fatal("added string missing: ", coll[0]) } err = json.Unmarshal([]byte(coll[1]), &eve) if err != nil { t.Fatal(err) } if !strings.Contains(coll[1], `"foo":"bar"`) { t.Fatal("added string missing: ", coll[1]) } } func TestForwardAllHandler(t *testing.T) { dir, err := ioutil.TempDir("", "test") if err != nil { t.Fatal(err) } defer os.RemoveAll(dir) tmpfn := filepath.Join(dir, fmt.Sprintf("t%d", rand.Int63())) inputListener, err := net.Listen("unix", tmpfn) if err != nil { t.Fatal("error opening input socket:", err) } defer inputListener.Close() // prepare slice to hold collected strings coll := make([]string, 0) // setup comms channels clCh := make(chan bool) cldCh := make(chan bool) // start socket consumer var wg sync.WaitGroup wg.Add(3) go consumeSocket(inputListener, clCh, cldCh, t, &coll, &wg) // start forwarding handler c := make(chan types.Entry, 100) mf := makeMultiForwarder(tmpfn, true, []string{}) fh := MakeForwardHandler(c) mf.Run(c, 5) fhTypes := fh.GetEventTypes() if len(fhTypes) != 1 { t.Fatal("Forwarding handler should only claim one type") } if fhTypes[0] != "*" { t.Fatal("Forwarding handler should claim '*' type") } if fh.GetName() != "Forwarding handler" { t.Fatal("Forwarding handler has wrong name") } e := makeEvent("alert", "foo1") fh.Consume(&e) e = makeEvent("http", "foo2") fh.Consume(&e) e = makeEvent("alert", "foo3") fh.Consume(&e) wg.Wait() // stop socket consumer inputListener.Close() close(clCh) if len(coll) != 3 { t.Fatalf("unexpected number of alerts: %d != 3", len(coll)) } var eve types.EveOutEvent err = json.Unmarshal([]byte(coll[0]), &eve) if err != nil { t.Fatal(err) } if eve.DNS.Rrname != "foo1" { t.Fatalf("invalid event data, expected 'foo1', got %s", eve.DNS.Rrname) } err = json.Unmarshal([]byte(coll[1]), &eve) if err != nil { t.Fatal(err) } if eve.DNS.Rrname != "foo2" { t.Fatalf("invalid event data, expected 'foo2', got %s", eve.DNS.Rrname) } err = json.Unmarshal([]byte(coll[2]), &eve) if err != nil { t.Fatal(err) } if eve.DNS.Rrname != "foo3" { t.Fatalf("invalid event data, expected 'foo3', got %s", eve.DNS.Rrname) } } fever-1.3.7/processing/handler.go000066400000000000000000000016201477766221000167640ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2017, DCSO GmbH import ( "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" ) // Handler is an interface describing the behaviour for a component to // handle events parsed from EVE input. type Handler interface { GetEventTypes() []string GetName() string Consume(*types.Entry) error } // ConcurrentHandler is an interface describing the behaviour for a component to // handle events parsed from EVE input, while concurrently performing other // actions, such as collecting, integrating and/or forwarding data. type ConcurrentHandler interface { Handler Run() Stop(chan bool) } // StatsGeneratingHandler is an interface describing a Handler which also // periodically outputs performance statistics using the provided // PerformanceStatsEncoder. type StatsGeneratingHandler interface { Handler SubmitStats(*util.PerformanceStatsEncoder) } fever-1.3.7/processing/handler_dispatcher.go000066400000000000000000000115671477766221000212050ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2017, 2018, DCSO GmbH import ( "sync" "time" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" log "github.com/sirupsen/logrus" ) // HandlerDispatcherPerfStats contains performance stats written to InfluxDB // for monitoring. type HandlerDispatcherPerfStats struct { DispatchedPerSec uint64 `influx:"dispatch_calls_per_sec"` } // HandlerDispatcher is a component to collect and properly apply a set of // Handlers to a stream of Entry objects. Handlers can register the event types // they are meant to act on and are called with relevant Entries to perform // their job. type HandlerDispatcher struct { Lock sync.Mutex DispatchMap map[string]([]Handler) DBHandler Handler PerfStats HandlerDispatcherPerfStats Logger *log.Entry StatsEncoder *util.PerformanceStatsEncoder StopCounterChan chan bool StoppedCounterChan chan bool } // DBHandler writes consumed events to a database. type DBHandler struct { OutChan chan types.Entry } // GetName just returns the name of the default handler func (h *DBHandler) GetName() string { return "Default handler" } // GetEventTypes here is a dummy method -- since this handler is never // registered we don't need to set this to an actual event type func (h *DBHandler) GetEventTypes() []string { return []string{"not applicable"} } // Consume simply emits the consumed entry on the default output channel func (h *DBHandler) Consume(e *types.Entry) error { h.OutChan <- *e return nil } func (ad *HandlerDispatcher) runCounter() { sTime := time.Now() for { time.Sleep(500 * time.Millisecond) select { case <-ad.StopCounterChan: close(ad.StoppedCounterChan) return default: if ad.StatsEncoder == nil || time.Since(sTime) < ad.StatsEncoder.SubmitPeriod { continue } // Lock the current measurements for submission. Since this is a blocking // operation, we don't want this to depend on how long submitter.Submit() // takes but keep it independent of that. Hence we take the time to create // a local copy of the counter to be able to reset and release the live // one as quickly as possible. ad.Lock.Lock() // Make our own copy of the current counter myStats := HandlerDispatcherPerfStats{ DispatchedPerSec: ad.PerfStats.DispatchedPerSec, } myStats.DispatchedPerSec /= uint64(ad.StatsEncoder.SubmitPeriod.Seconds()) // Reset live counter ad.PerfStats.DispatchedPerSec = 0 // Release live counter to not block further events ad.Lock.Unlock() ad.StatsEncoder.Submit(myStats) sTime = time.Now() } } } // MakeHandlerDispatcher returns a new HandlerDispatcher. The channel passed // as an argument is used as an output channel for the default handler, which // simply forwards events to a given channel (for example to be written to a // database) func MakeHandlerDispatcher(databaseOut chan types.Entry) *HandlerDispatcher { ad := &HandlerDispatcher{ DispatchMap: make(map[string]([]Handler)), Logger: log.WithFields(log.Fields{ "domain": "dispatch", }), } if databaseOut != nil { ad.DBHandler = &DBHandler{ OutChan: databaseOut, } } ad.Logger.WithFields(log.Fields{ "type": "*", "name": "default handler", }).Debugf("event handler added") return ad } // RegisterHandler adds the given Handler to the set of callbacks to be // called on the relevant Entries received by the dispatcher. func (ad *HandlerDispatcher) RegisterHandler(agg Handler) { eventTypes := agg.GetEventTypes() for _, eventType := range eventTypes { if _, ok := ad.DispatchMap[eventType]; !ok { ad.DispatchMap[eventType] = make([]Handler, 0) } ad.DispatchMap[eventType] = append(ad.DispatchMap[eventType], agg) ad.Logger.WithFields(log.Fields{ "type": eventType, "name": agg.GetName(), }).Info("event handler added") } } // Dispatch applies the set of handlers currently registered in the dispatcher // to the Entry object passed to it. func (ad *HandlerDispatcher) Dispatch(e *types.Entry) { if _, ok := ad.DispatchMap[e.EventType]; ok { for _, agg := range ad.DispatchMap[e.EventType] { agg.Consume(e) } } if a, ok := ad.DispatchMap["*"]; ok { for _, agg := range a { agg.Consume(e) } } if ad.DBHandler != nil { ad.DBHandler.Consume(e) } ad.Lock.Lock() ad.PerfStats.DispatchedPerSec++ ad.Lock.Unlock() } // SubmitStats registers a PerformanceStatsEncoder for runtime stats submission. func (ad *HandlerDispatcher) SubmitStats(sc *util.PerformanceStatsEncoder) { ad.StatsEncoder = sc } // Run starts the background service for this handler func (ad *HandlerDispatcher) Run() { ad.StopCounterChan = make(chan bool) ad.StoppedCounterChan = make(chan bool) go ad.runCounter() } // Stop causes the handler to cease counting and submitting data func (ad *HandlerDispatcher) Stop(stopChan chan bool) { close(ad.StopCounterChan) <-ad.StoppedCounterChan close(stopChan) } fever-1.3.7/processing/handler_dispatcher_test.go000066400000000000000000000111321477766221000222300ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2017, DCSO GmbH import ( "fmt" "math/rand" "regexp" "testing" "time" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" "github.com/NeowayLabs/wabbit" "github.com/NeowayLabs/wabbit/amqptest" "github.com/NeowayLabs/wabbit/amqptest/server" ) type Test1Handler struct { Vals []string } func (h *Test1Handler) GetName() string { return "Test handler 1" } func (h *Test1Handler) GetEventTypes() []string { return []string{"dns"} } func (h *Test1Handler) Consume(e *types.Entry) error { h.Vals = append(h.Vals, e.JSONLine) return nil } type Test2Handler struct { Vals []string } func (h *Test2Handler) GetName() string { return "Test handler 2" } func (h *Test2Handler) GetEventTypes() []string { return []string{"http"} } func (h *Test2Handler) Consume(e *types.Entry) error { h.Vals = append(h.Vals, e.JSONLine) return nil } func TestHandlerDispatcherExampleHandler(t *testing.T) { outChan := make(chan types.Entry) closeChan := make(chan bool) defaultSelection := make([]string, 0) go func(closeChan chan bool, inChan chan types.Entry) { for v := range inChan { defaultSelection = append(defaultSelection, v.JSONLine) } close(closeChan) }(closeChan, outChan) ad := MakeHandlerDispatcher(outChan) t1 := &Test1Handler{ Vals: make([]string, 0), } ad.RegisterHandler(t1) t2 := &Test2Handler{ Vals: make([]string, 0), } ad.RegisterHandler(t2) rand.Seed(time.Now().UTC().UnixNano()) // make test entries typestrs := []string{"http", "dns", "flow", "foo"} var createdEntries [10000]types.Entry entries := make(map[string]([]string)) for i := 0; i < 10000; i++ { myIdentifier := fmt.Sprintf("val%d", i) myType := typestrs[rand.Intn(len(typestrs))] createdEntries[i] = types.Entry{ EventType: myType, JSONLine: myIdentifier, } if _, ok := entries[myType]; !ok { entries[myType] = make([]string, 0) } entries[myType] = append(entries[myType], myIdentifier) ad.Dispatch(&createdEntries[i]) } close(outChan) <-closeChan if len(t1.Vals) != len(entries["dns"]) { t.Fatalf("wrong number of 'dns' entries delivered to DNS handler (%d/%d)", len(t1.Vals), len(entries["dns"])) } for i := 0; i < len(t1.Vals); i++ { if t1.Vals[i] != entries["dns"][i] { t.Fatalf("'dns' pair of entries differs: %s/%s", t1.Vals[i], entries["dns"][i]) } } if len(t2.Vals) != len(entries["http"]) { t.Fatalf("wrong number of 'http' entries delivered to HTTP handler (%d/%d)", len(t2.Vals), len(entries["http"])) } for i := 0; i < len(t2.Vals); i++ { if t2.Vals[i] != entries["http"][i] { t.Fatalf("'http' pair of entries differs: %s/%s", t2.Vals[i], entries["http"][i]) } } } func TestHandlerDispatcherMonitoring(t *testing.T) { serverURL := "amqp://sensor:sensor@127.0.0.1:9999/%2f/" // start mock AMQP server fakeServer := server.NewServer(serverURL) fakeServer.Start() defer fakeServer.Stop() // set up consumer results := make([]string, 0) c, err := util.NewConsumer(serverURL, "nsm.test.metrics", "direct", "nsm.test.metrics.testqueue", "", "", func(d wabbit.Delivery) { results = append(results, string(d.Body())) }) if err != nil { t.Fatal(err) } // set up submitter statssubmitter, err := util.MakeAMQPSubmitterWithReconnector(serverURL, "nsm.test.metrics", true, func(url string) (wabbit.Conn, error) { // we pass in a custom reconnector which uses the amqptest implementation var conn wabbit.Conn conn, err = amqptest.Dial(url) return conn, err }) if err != nil { t.Fatal(err) } defer statssubmitter.Finish() // create InfluxDB line protocol encoder/submitter pse := util.MakePerformanceStatsEncoder(statssubmitter, 2*time.Second, false) outChan := make(chan types.Entry) closeChan := make(chan bool) ad := MakeHandlerDispatcher(outChan) ad.SubmitStats(pse) ad.Run() go func() { for i := 0; i < 100; i++ { ad.Dispatch(&types.Entry{ JSONLine: "foo", }) ad.Dispatch(&types.Entry{ JSONLine: "bar", }) ad.Dispatch(&types.Entry{ JSONLine: "baz", }) time.Sleep(50 * time.Millisecond) } }() go func(closeChan chan bool, inChan chan types.Entry) { i := 0 for v := range inChan { _ = v i++ if i == 300 { break } } close(closeChan) }(closeChan, outChan) <-closeChan close(outChan) stopChan := make(chan bool) ad.Stop(stopChan) <-stopChan c.Shutdown() if len(results) == 0 { t.Fatalf("unexpected result length: 0") } if match, _ := regexp.Match(fmt.Sprintf("^%s,[^ ]+ dispatch_calls_per_sec=[0-9]+", util.ToolName), []byte(results[0])); !match { t.Fatalf("unexpected match content: %s", results[0]) } } fever-1.3.7/processing/heartbeat_injector.go000066400000000000000000000077201477766221000212120ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2020, 2021, DCSO GmbH import ( "encoding/json" "fmt" "math/rand" "regexp" "time" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" log "github.com/sirupsen/logrus" ) var ( // match 24 hour local time string, separated by colon injectTimeRegex = regexp.MustCompile(`^(([01][0-9])|(2[0-3])):[0-5][0-9]$`) // We pick this value as a tick interval to check the time against the list // of times to send heartbeats at. We check once per minute as that is the // resolution of the specified times as well. injectTimeCheckTick = 1 * time.Minute ) // HeartbeatInjector regularly adds a date-based pseudo-event to the forwarded // event stream. type HeartbeatInjector struct { SensorID string Times []string AlertTimes []string CloseChan chan bool Logger *log.Entry ForwardHandler Handler } // MakeHeartbeatInjector creates a new HeartbeatInjector. func MakeHeartbeatInjector(forwardHandler Handler, injectTimes []string, alertTimes []string) (*HeartbeatInjector, error) { sensorID, err := util.GetSensorID() if err != nil { return nil, err } for _, v := range injectTimes { if !injectTimeRegex.Match([]byte(v)) { return nil, fmt.Errorf("invalid time specification in heartbeat injector config: '%s'", v) } } for _, v := range alertTimes { if !injectTimeRegex.Match([]byte(v)) { return nil, fmt.Errorf("invalid alert time specification in heartbeat injector config: '%s'", v) } } a := &HeartbeatInjector{ ForwardHandler: forwardHandler, Logger: log.WithFields(log.Fields{ "domain": "heartbeat_injector", }), Times: injectTimes, AlertTimes: alertTimes, CloseChan: make(chan bool), SensorID: sensorID, } return a, nil } func makeHeartbeatEvent(eventType string) types.Entry { now := time.Now() entry := types.Entry{ SrcIP: "192.0.2.1", SrcPort: int64(rand.Intn(60000) + 1025), DestIP: "192.0.2.2", DestPort: 80, Timestamp: time.Now().Format(types.SuricataTimestampFormat), EventType: eventType, Proto: "TCP", HTTPHost: fmt.Sprintf("test-%d-%02d-%02d.vast", now.Year(), now.Month(), now.Day()), HTTPUrl: "/just-visiting", HTTPMethod: "GET", } eve := types.EveEvent{ Timestamp: &types.SuriTime{ Time: time.Now().UTC(), }, EventType: entry.EventType, SrcIP: entry.SrcIP, SrcPort: int(entry.SrcPort), DestIP: entry.DestIP, DestPort: int(entry.DestPort), Proto: entry.Proto, HTTP: &types.HTTPEvent{ Hostname: entry.HTTPHost, URL: entry.HTTPUrl, HTTPMethod: entry.HTTPMethod, HTTPUserAgent: "FEVER", Status: 200, Protocol: "HTTP/1.1", Length: 42, HTTPContentType: "text/html", }, } if eventType == "alert" { eve.Alert = &types.AlertEvent{ Action: "allowed", Category: "Not Suspicious Traffic", Signature: "DCSO FEVER TEST alert", } entry.HTTPHost = "testalert.fever" eve.HTTP.Hostname = entry.HTTPHost } json, err := json.Marshal(eve) if err != nil { log.Warn(err) } else { entry.JSONLine = string(json) } return entry } // Run starts the background service. func (a *HeartbeatInjector) Run() { go func() { for { select { case <-a.CloseChan: return default: curTime := time.Now().Format("15:04") for _, timeVal := range a.Times { if curTime == timeVal { ev := makeHeartbeatEvent("http") a.Logger.Infof("creating heartbeat HTTP event for %s: %s", curTime, string(ev.JSONLine)) a.ForwardHandler.Consume(&ev) } } for _, timeVal := range a.AlertTimes { if curTime == timeVal { ev := makeHeartbeatEvent("alert") a.Logger.Infof("creating heartbeat alert event for %s: %s", curTime, string(ev.JSONLine)) a.ForwardHandler.Consume(&ev) } } time.Sleep(injectTimeCheckTick) } } }() } // Stop causes the service to cease the background work. func (a *HeartbeatInjector) Stop() { close(a.CloseChan) } fever-1.3.7/processing/heartbeat_injector_test.go000066400000000000000000000050611477766221000222450ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2020, 2021, DCSO GmbH import ( "fmt" "sync" "testing" "time" "github.com/DCSO/fever/types" "github.com/buger/jsonparser" ) type HeartbeatTestFwdHandler struct { Entries []types.Entry Lock sync.Mutex } func (h *HeartbeatTestFwdHandler) Consume(e *types.Entry) error { h.Lock.Lock() defer h.Lock.Unlock() h.Entries = append(h.Entries, *e) return nil } func (h *HeartbeatTestFwdHandler) GetEventTypes() []string { return []string{"*"} } func (h *HeartbeatTestFwdHandler) GetName() string { return "Heartbeat Injector Forwarding Test Handler" } func TestHeartbeatInjectorInvalidTime(t *testing.T) { hbth := HeartbeatTestFwdHandler{ Entries: make([]types.Entry, 0), } _, err := MakeHeartbeatInjector(&hbth, []string{"foo"}, []string{}) if err == nil { t.Fatal("invalid time not caught") } } func TestHeartbeatInjectorInvalidAlertTime(t *testing.T) { hbth := HeartbeatTestFwdHandler{ Entries: make([]types.Entry, 0), } _, err := MakeHeartbeatInjector(&hbth, []string{}, []string{"foo"}) if err == nil { t.Fatal("invalid time not caught") } } func TestHeartbeatInjector(t *testing.T) { hbth := HeartbeatTestFwdHandler{ Entries: make([]types.Entry, 0), } now := time.Now() ctime := []string{now.Format("15:04")} hbi, err := MakeHeartbeatInjector(&hbth, ctime, []string{}) if err != nil { t.Fatal(err) } hbi.Run() for { hbth.Lock.Lock() if len(hbth.Entries) > 0 { hbth.Lock.Unlock() break } hbth.Lock.Unlock() time.Sleep(100 * time.Millisecond) } hbi.Stop() hbJSON := hbth.Entries[0].JSONLine expectedHost := fmt.Sprintf("test-%d-%02d-%02d.vast", now.Year(), now.Month(), now.Day()) seenHost, err := jsonparser.GetString([]byte(hbJSON), "http", "hostname") if err != nil { t.Fatal(err) } if seenHost != expectedHost { t.Fatalf("wrong hostname for heartbeat: %s", seenHost) } } func TestHeartbeatAlertInjector(t *testing.T) { hbth := HeartbeatTestFwdHandler{ Entries: make([]types.Entry, 0), } now := time.Now() atime := []string{now.Format("15:04")} hbi, err := MakeHeartbeatInjector(&hbth, []string{}, atime) if err != nil { t.Fatal(err) } hbi.Run() for { hbth.Lock.Lock() if len(hbth.Entries) > 0 { hbth.Lock.Unlock() break } hbth.Lock.Unlock() time.Sleep(100 * time.Millisecond) } hbi.Stop() hbJSON := hbth.Entries[0].JSONLine sig, err := jsonparser.GetString([]byte(hbJSON), "alert", "signature") if err != nil { t.Fatal(err) } if sig != "DCSO FEVER TEST alert" { t.Fatalf("wrong signature for test alert: %s", sig) } } fever-1.3.7/processing/ip_handler.go000066400000000000000000000120021477766221000174500ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2018, 2020, DCSO GmbH import ( "bufio" "net" "os" "sync" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" log "github.com/sirupsen/logrus" "github.com/yl2chen/cidranger" ) // IPAlertJSONProviderSrcIP is an AlertJSONProvider for source IP address matches. type IPAlertJSONProviderSrcIP struct{} // GetAlertJSON returns the "alert" subobject for an alert EVE event. func (a IPAlertJSONProviderSrcIP) GetAlertJSON(inputEvent types.Entry, prefix string, ioc string) ([]byte, error) { return util.GenericGetAlertObjForIoc(inputEvent, prefix, ioc, "%s Communication involving IP "+inputEvent.SrcIP+" in listed range %s") } // IPAlertJSONProviderDstIP is an AlertJSONProvider for destination IP address // matches. type IPAlertJSONProviderDstIP struct{} // GetAlertJSON returns the "alert" subobject for an alert EVE event. func (a IPAlertJSONProviderDstIP) GetAlertJSON(inputEvent types.Entry, prefix string, ioc string) ([]byte, error) { return util.GenericGetAlertObjForIoc(inputEvent, prefix, ioc, "%s Communication involving IP "+inputEvent.DestIP+" in listed range %s") } // IPHandler is a Handler which is meant to check for the presence of // event type-specific keywords in a Bloom filter, raising new 'alert' type // events when matches are found. type IPHandler struct { sync.Mutex Logger *log.Entry Name string EventType string Ranger cidranger.Ranger IPListFilename string DatabaseEventChan chan types.Entry ForwardHandler Handler AlertPrefix string Alertifier *util.Alertifier } // MakeIPHandler returns a new IPHandler, checking against the given // IP ranges and sending alerts to databaseChan as well as forwarding them // to a given forwarding handler. func MakeIPHandler(ranger cidranger.Ranger, databaseChan chan types.Entry, forwardHandler Handler, alertPrefix string) *IPHandler { ih := &IPHandler{ Logger: log.WithFields(log.Fields{ "domain": "ip-blacklist", }), Ranger: ranger, DatabaseEventChan: databaseChan, ForwardHandler: forwardHandler, AlertPrefix: alertPrefix, Alertifier: util.MakeAlertifier(alertPrefix), } ih.Alertifier.SetExtraModifier(bloomExtraModifier) ih.Alertifier.RegisterMatchType("ip-src", IPAlertJSONProviderSrcIP{}) ih.Alertifier.RegisterMatchType("ip-dst", IPAlertJSONProviderDstIP{}) ih.Alertifier.SetExtraModifier(nil) log.WithFields(log.Fields{}).Info("IP range list loaded") return ih } func rangerFromFile(IPListFilename string) (cidranger.Ranger, error) { inFile, err := os.Open(IPListFilename) if err != nil { return nil, err } defer inFile.Close() ranger := cidranger.NewPCTrieRanger() scanner := bufio.NewScanner(inFile) scanner.Split(bufio.ScanLines) for scanner.Scan() { lineText := scanner.Text() _, network, err := net.ParseCIDR(lineText) if err != nil { log.Warnf("invalid IP range %s, skipping", lineText) } else { log.Debugf("adding IP range %s", lineText) ranger.Insert(cidranger.NewBasicRangerEntry(*network)) } } return ranger, nil } // MakeIPHandlerFromFile returns a new IPHandler created from a new // IP range list specified by the given file name. func MakeIPHandlerFromFile(IPListFilename string, databaseChan chan types.Entry, forwardHandler Handler, alertPrefix string) (*IPHandler, error) { ranger, err := rangerFromFile(IPListFilename) if err != nil { return nil, err } ih := MakeIPHandler(ranger, databaseChan, forwardHandler, alertPrefix) ih.IPListFilename = IPListFilename return ih, nil } // Reload triggers a reload of the contents of the IP list file. func (a *IPHandler) Reload() error { ranger, err := rangerFromFile(a.IPListFilename) if err != nil { return err } a.Lock() a.Ranger = ranger a.Unlock() return nil } // Consume processes an Entry, emitting alerts if there is a match func (a *IPHandler) Consume(e *types.Entry) error { a.Lock() srcRanges, err := a.Ranger.ContainingNetworks(net.ParseIP(e.SrcIP)) if err != nil { log.Warn(err) } for _, v := range srcRanges { matchedNet := v.Network() matchedNetString := matchedNet.String() if n, err := a.Alertifier.MakeAlert(*e, matchedNetString, "ip-src"); err == nil { a.DatabaseEventChan <- *n a.ForwardHandler.Consume(n) } else { log.Warn(err) } } dstRanges, err := a.Ranger.ContainingNetworks(net.ParseIP(e.DestIP)) if err != nil { log.Warn(err) } for _, v := range dstRanges { matchedNet := v.Network() matchedNetString := matchedNet.String() if n, err := a.Alertifier.MakeAlert(*e, matchedNetString, "ip-dst"); err == nil { a.DatabaseEventChan <- *n a.ForwardHandler.Consume(n) } else { log.Warn(err) } } a.Unlock() return nil } // GetName returns the name of the handler func (a *IPHandler) GetName() string { return "IP blacklist handler" } // GetEventTypes returns a slice of event type strings that this handler // should be applied to func (a *IPHandler) GetEventTypes() []string { return []string{"http", "dns", "tls", "smtp", "flow", "ssh", "tls", "smb"} } fever-1.3.7/processing/ip_handler_test.go000066400000000000000000000135621477766221000205230ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2018, 2020, DCSO GmbH import ( "bufio" "encoding/json" "io/ioutil" "math/rand" "net" "os" "regexp" "testing" "time" "github.com/DCSO/fever/types" log "github.com/sirupsen/logrus" "github.com/sirupsen/logrus/hooks/test" "github.com/yl2chen/cidranger" ) var ( reIPmsg = regexp.MustCompile(`Communication involving IP ([^ ]+) in listed range ([^ ]+)`) ) func makeIPHTTPEvent(srcip string, dstip string) types.Entry { e := types.Entry{ SrcIP: srcip, SrcPort: int64(rand.Intn(60000) + 1025), DestIP: dstip, DestPort: 80, Timestamp: time.Now().Format(types.SuricataTimestampFormat), EventType: "http", Proto: "TCP", HTTPHost: "http://foo.bar", HTTPUrl: "/baz", HTTPMethod: "GET", } eve := types.EveEvent{ Timestamp: &types.SuriTime{ Time: time.Now().UTC(), }, EventType: e.EventType, SrcIP: e.SrcIP, SrcPort: int(e.SrcPort), DestIP: e.DestIP, DestPort: int(e.DestPort), Proto: e.Proto, HTTP: &types.HTTPEvent{ Hostname: e.HTTPHost, URL: e.HTTPUrl, }, } json, err := json.Marshal(eve) if err != nil { log.Warn(err) } else { e.JSONLine = string(json) } return e } // IPCollectorHandler gathers consumed alerts in a list type IPCollectorHandler struct { Entries []string } func (h *IPCollectorHandler) GetName() string { return "Collector handler" } func (h *IPCollectorHandler) GetEventTypes() []string { return []string{"alert"} } func (h *IPCollectorHandler) Consume(e *types.Entry) error { match := reIPmsg.FindStringSubmatch(e.JSONLine) if match != nil { h.Entries = append(h.Entries, e.JSONLine) return nil } return nil } func TestIPHandler(t *testing.T) { // channel to receive events to be saved to database dbChan := make(chan types.Entry) // handler to receive forwarded events fwhandler := &IPCollectorHandler{ Entries: make([]string, 0), } // concurrently gather entries to be written to DB dbWritten := make([]types.Entry, 0) consumeWaitChan := make(chan bool) go func() { for e := range dbChan { dbWritten = append(dbWritten, e) } close(consumeWaitChan) }() // make test ranger _, network, _ := net.ParseCIDR("10.0.0.1/32") rng := cidranger.NewPCTrieRanger() rng.Insert(cidranger.NewBasicRangerEntry(*network)) ih := MakeIPHandler(rng, dbChan, fwhandler, "IPF") bhTypes := ih.GetEventTypes() if len(bhTypes) != 8 { t.Fatal("IP handler should claim eight types") } if ih.GetName() != "IP blacklist handler" { t.Fatal("IP handler has wrong name") } e := makeIPHTTPEvent("10.0.0.1", "10.0.0.2") ih.Consume(&e) e = makeIPHTTPEvent("10.0.0.3", "10.0.0.2") ih.Consume(&e) e = makeIPHTTPEvent("10.0.0.3", "10.0.0.1") ih.Consume(&e) // wait until all values have been collected close(dbChan) <-consumeWaitChan // check that we haven't missed anything if len(fwhandler.Entries) < 2 { t.Fatalf("expected %d forwarded BLF alerts, seen less (%d)", 2, len(fwhandler.Entries)) } // check that the result is indeed valid JSON again var result interface{} err := json.Unmarshal([]byte(fwhandler.Entries[0]), &result) if err != nil { t.Fatalf("could not unmarshal JSON: %s", err.Error()) } err = json.Unmarshal([]byte(fwhandler.Entries[1]), &result) if err != nil { t.Fatalf("could not unmarshal JSON: %s", err.Error()) } } func TestIPHandlerFromFile(t *testing.T) { // channel to receive events to be saved to database dbChan := make(chan types.Entry) // handler to receive forwarded events fwhandler := &IPCollectorHandler{ Entries: make([]string, 0), } // concurrently gather entries to be written to DB dbWritten := make([]types.Entry, 0) consumeWaitChan := make(chan bool) go func() { for e := range dbChan { dbWritten = append(dbWritten, e) } close(consumeWaitChan) }() ipFile, err := ioutil.TempFile("", "ipexample") if err != nil { t.Fatal(err) } defer os.Remove(ipFile.Name()) w := bufio.NewWriter(ipFile) _, err = w.WriteString("10.0.0.1/32\n") if err != nil { t.Fatal(err) } w.Flush() ipFile.Close() ih, err := MakeIPHandlerFromFile(ipFile.Name(), dbChan, fwhandler, "IPF") if err != nil { t.Fatal(err) } bhTypes := ih.GetEventTypes() if len(bhTypes) != 8 { t.Fatal("IP handler should claim eight types") } if ih.GetName() != "IP blacklist handler" { t.Fatal("IP handler has wrong name") } e := makeIPHTTPEvent("10.0.0.1", "10.0.0.2") ih.Consume(&e) e = makeIPHTTPEvent("10.0.0.3", "10.0.0.2") ih.Consume(&e) e = makeIPHTTPEvent("10.0.0.3", "10.0.0.1") ih.Consume(&e) // wait until all values have been collected close(dbChan) <-consumeWaitChan // check that we haven't missed anything if len(fwhandler.Entries) < 2 { t.Fatalf("expected %d forwarded BLF alerts, seen less (%d)", 2, len(fwhandler.Entries)) } var i interface{} err = json.Unmarshal([]byte(fwhandler.Entries[0]), &i) if err != nil { t.Fatalf("could not unmarshal JSON: %s", err.Error()) } err = json.Unmarshal([]byte(fwhandler.Entries[1]), &i) if err != nil { t.Fatalf("could not unmarshal JSON: %s", err.Error()) } } func TestIPHandlerFromFileInvalidFormat(t *testing.T) { // channel to receive events to be saved to database dbChan := make(chan types.Entry) // handler to receive forwarded events fwhandler := &IPCollectorHandler{ Entries: make([]string, 0), } ipFile, err := ioutil.TempFile("", "invalidipexample") if err != nil { t.Fatal(err) } defer os.Remove(ipFile.Name()) w := bufio.NewWriter(ipFile) _, err = w.WriteString("10.0.0.1/3q5435\n") if err != nil { t.Fatal(err) } w.Flush() ipFile.Close() hook := test.NewGlobal() _, err = MakeIPHandlerFromFile(ipFile.Name(), dbChan, fwhandler, "IPF") if err != nil { t.Fatal(err) } entries := hook.AllEntries() if len(entries) < 2 { t.Fatal("missing log entries") } if entries[0].Message != "invalid IP range 10.0.0.1/3q5435, skipping" { t.Fatal("wrong log entry for invalid IP range") } } fever-1.3.7/processing/multi_forward.go000066400000000000000000000163741477766221000202410ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2021, DCSO GmbH import ( "net" "sync" "time" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" log "github.com/sirupsen/logrus" ) // MultiForwardPerfStats contains performance stats written to InfluxDB // for monitoring. type MultiForwardPerfStats struct { Received uint64 `influx:"output_received_per_sec"` Dropped uint64 `influx:"output_dropped"` BufferLength uint64 `influx:"output_buffer_length"` } // MultiForwardOutput defines a single output target including socket path, // whether to filter the output by event type and if so, what event types to let // pass. type MultiForwardOutput struct { Socket string `mapstructure:"socket"` All bool `mapstructure:"all"` BufferLength uint64 `mapstructure:"buffer-length"` Types []string `mapstructure:"types"` } // MultiForwardConfiguration contains a setup for the multi-forwarder as read // and parsed from the configuration file. type MultiForwardConfiguration struct { Outputs map[string]MultiForwardOutput `mapstructure:"multi-forward"` Shippers []*MultiForwardShipper StatsEncoder *util.PerformanceStatsEncoder } // MultiForwardShipper is a concurrent, self-contained component that receives // Entries from an input channel and writes the associated JSON to an output // socket, filtering the output if desired. Also handles reconnection. type MultiForwardShipper struct { OutputName string Logger *log.Entry ForwardInChan chan types.Entry OutputSocket string OutputConn net.Conn Reconnecting bool ReconnLock sync.Mutex ReconnectNotifyChan chan bool StopReconnectChan chan bool ReconnectTimes int PerfStats MultiForwardPerfStats StatsEncoder *util.PerformanceStatsEncoder StopChan chan bool StoppedChan chan bool StopCounterChan chan bool StoppedCounterChan chan bool Running bool Lock sync.Mutex } func (mfs *MultiForwardShipper) reconnectForward() { for range mfs.ReconnectNotifyChan { var i int mfs.Logger.Infof("Reconnecting to forwarding socket (%s)...", mfs.OutputSocket) outputConn, myerror := net.Dial("unix", mfs.OutputSocket) mfs.ReconnLock.Lock() if !mfs.Reconnecting { mfs.Reconnecting = true } else { mfs.ReconnLock.Unlock() continue } mfs.ReconnLock.Unlock() for i = 0; (mfs.ReconnectTimes == 0 || i < mfs.ReconnectTimes) && myerror != nil; i++ { select { case <-mfs.StopReconnectChan: return default: mfs.Logger.WithFields(log.Fields{ "retry": i + 1, "maxretries": mfs.ReconnectTimes, }).Warnf("error connecting to output socket, retrying: %s", myerror) time.Sleep(10 * time.Second) outputConn, myerror = net.Dial("unix", mfs.OutputSocket) } } if myerror != nil { mfs.Logger.WithFields(log.Fields{ "retries": i, }).Fatalf("permanent error connecting to output socket: %s", myerror) mfs.ReconnLock.Unlock() } else { if i > 0 { mfs.Logger.WithFields(log.Fields{ "retry_attempts": i, }).Infof("connection to output socket successful") } mfs.Lock.Lock() mfs.OutputConn = outputConn mfs.Lock.Unlock() mfs.ReconnLock.Lock() mfs.Reconnecting = false mfs.ReconnLock.Unlock() } } } func (mfs *MultiForwardShipper) runForward() { var err error for { select { case <-mfs.StopChan: close(mfs.StoppedChan) return default: for item := range mfs.ForwardInChan { mfs.PerfStats.Received++ select { case <-mfs.StopChan: close(mfs.StoppedChan) return default: mfs.ReconnLock.Lock() if mfs.Reconnecting { mfs.ReconnLock.Unlock() mfs.PerfStats.Dropped++ continue } mfs.ReconnLock.Unlock() mfs.Lock.Lock() if mfs.OutputConn != nil { _, err = mfs.OutputConn.Write([]byte(item.JSONLine)) if err != nil { mfs.OutputConn.Close() mfs.Lock.Unlock() log.Warn(err) mfs.ReconnectNotifyChan <- true continue } _, err = mfs.OutputConn.Write([]byte("\n")) if err != nil { mfs.OutputConn.Close() mfs.Lock.Unlock() mfs.Logger.Warn(err) continue } } mfs.Lock.Unlock() } } } } } func (mfs *MultiForwardShipper) runCounter() { sTime := time.Now() for { time.Sleep(500 * time.Millisecond) select { case <-mfs.StopCounterChan: close(mfs.StoppedCounterChan) return default: if mfs.StatsEncoder == nil || time.Since(sTime) < mfs.StatsEncoder.SubmitPeriod { continue } // Lock the current measurements for submission. Since this is a blocking // operation, we don't want this to depend on how long submitter.Submit() // takes but keep it independent of that. Hence we take the time to create // a local copy of the counter to be able to reset and release the live // one as quickly as possible. mfs.Lock.Lock() // Make our own copy of the current counter myStats := MultiForwardPerfStats{ Dropped: mfs.PerfStats.Dropped, Received: mfs.PerfStats.Received / uint64(mfs.StatsEncoder.SubmitPeriod.Seconds()), BufferLength: uint64(len(mfs.ForwardInChan)), } // Reset live counter mfs.PerfStats.Received = 0 // Release live counter to not block further events mfs.Lock.Unlock() mfs.StatsEncoder.SubmitWithTags(myStats, map[string]string{ "output": mfs.OutputName, }) sTime = time.Now() } } } // Run starts all concurrent aspects of the forwarder, reading from the input // channel and distributing incoming events after setting up the shippers from // the configuration. func (m *MultiForwardConfiguration) Run(inChan <-chan types.Entry, reconnectTimes int) { outputMap := make(map[string][]*MultiForwardShipper) fwdAll := make([]*MultiForwardShipper, 0) for name, output := range m.Outputs { mfs := &MultiForwardShipper{ OutputName: name, OutputSocket: output.Socket, ReconnectTimes: reconnectTimes, Logger: log.WithFields(log.Fields{ "domain": "forward", "output": name, }), ReconnectNotifyChan: make(chan bool), StopReconnectChan: make(chan bool), StatsEncoder: m.StatsEncoder, } mfs.StopChan = make(chan bool) mfs.ForwardInChan = make(chan types.Entry, output.BufferLength) if output.All { fwdAll = append(fwdAll, mfs) } else { for _, outT := range output.Types { outputMap[outT] = append(outputMap[outT], mfs) } } mfs.StopCounterChan = make(chan bool) mfs.StoppedCounterChan = make(chan bool) go mfs.reconnectForward() mfs.ReconnectNotifyChan <- true go mfs.runForward() go mfs.runCounter() } go func() { for inEntry := range inChan { if len(fwdAll) > 0 { for _, shipper := range fwdAll { select { case shipper.ForwardInChan <- inEntry: //pass default: shipper.PerfStats.Dropped++ } } } if shippers, ok := outputMap[inEntry.EventType]; ok { for _, shipper := range shippers { select { case shipper.ForwardInChan <- inEntry: //pass default: shipper.PerfStats.Dropped++ } } } } }() } // SubmitStats registers a PerformanceStatsEncoder for runtime stats submission. func (m *MultiForwardConfiguration) SubmitStats(sc *util.PerformanceStatsEncoder) { m.StatsEncoder = sc } fever-1.3.7/processing/pdns_collector.go000066400000000000000000000166721477766221000203760ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2017, 2019, DCSO GmbH import ( "bytes" "encoding/json" "os" "sync" "time" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" log "github.com/sirupsen/logrus" ) // pDNSReplyDetails holds data for a DNS answer. type pDNSReplyDetails struct { AnsweringHost string `json:"answering_host,omitempty"` Rrtype string `json:"rrtype,omitempty"` Rdata string `json:"rdata,omitempty"` Rcode string `json:"rcode,omitempty"` Type string `json:"type,omitempty"` Count uint64 `json:"count,omitempty"` } // pDNSDetails holds summarized stats for a given domain name. type pDNSDetails struct { AnswerSet map[string]*pDNSReplyDetails `json:"-"` Details []pDNSReplyDetails `json:"rdata,omitempty"` } type pDNSEvent struct { TimestampFrom time.Time `json:"timestamp_start"` TimestampTo time.Time `json:"timestamp_end"` DNSDetails map[string]*pDNSDetails `json:"dns,omitempty"` SensorID string `json:"sensor_id,omitempty"` } // PDNSCollector extracts and aggregates DNS response data from // EVE events and sends them to the backend. type PDNSCollector struct { SensorID string Count int64 DNSMutex sync.RWMutex DNS pDNSEvent StringBuf bytes.Buffer FlushPeriod time.Duration CloseChan chan bool ClosedChan chan bool Logger *log.Entry Submitter util.StatsSubmitter SubmitChannel chan []byte TestdataDummyDomain string } // MakePDNSCollector creates a new pDNSCollector. func MakePDNSCollector(flushPeriod time.Duration, submitter util.StatsSubmitter) (*PDNSCollector, error) { sensorID, err := util.GetSensorID() if err != nil { return nil, err } a := &PDNSCollector{ FlushPeriod: flushPeriod, Logger: log.WithFields(log.Fields{ "domain": "pdns", }), DNS: pDNSEvent{ TimestampFrom: time.Now().UTC(), SensorID: sensorID, DNSDetails: make(map[string]*pDNSDetails), }, CloseChan: make(chan bool), ClosedChan: make(chan bool), SubmitChannel: make(chan []byte, 60), Submitter: submitter, SensorID: sensorID, TestdataDummyDomain: "", } a.SensorID, _ = os.Hostname() return a, nil } func (a *PDNSCollector) flush() { a.DNSMutex.Lock() myDNS := a.DNS myDNS.TimestampTo = time.Now().UTC() a.DNS = pDNSEvent{ TimestampFrom: time.Now().UTC(), SensorID: a.SensorID, DNSDetails: make(map[string]*pDNSDetails), } a.Count = 0 a.DNSMutex.Unlock() if a.TestdataDummyDomain != "" { myDNS.DNSDetails[a.TestdataDummyDomain] = &pDNSDetails{ Details: []pDNSReplyDetails{ { AnsweringHost: "0.0.0.0", Rrtype: "A", Rdata: "0.0.0.0", Rcode: "NOERROR", Count: 1, }, }, } } jsonString, myerror := json.MarshalIndent(myDNS, "", " ") if myerror == nil { select { case a.SubmitChannel <- jsonString: break default: log.Warning("pDNS channel is full, cannot submit message...") } } else { a.Logger.Warn("error marshaling JSON for passive DNS") } } func (a *PDNSCollector) countRequestV1(e *types.Entry) { a.DNSMutex.Lock() a.Count++ if e.DNSRRName == "" { a.DNSMutex.Unlock() return } key := e.DNSRRName a.StringBuf.Write([]byte(e.SrcIP)) a.StringBuf.Write([]byte(e.DNSRRType)) a.StringBuf.Write([]byte(e.DNSRData)) a.StringBuf.Write([]byte(e.DNSRCode)) a.StringBuf.Write([]byte(e.DNSType)) k := a.StringBuf.String() a.StringBuf.Reset() if _, ok := a.DNS.DNSDetails[key]; !ok { a.DNS.DNSDetails[key] = &pDNSDetails{ AnswerSet: make(map[string]*pDNSReplyDetails), Details: []pDNSReplyDetails{ { AnsweringHost: e.SrcIP, Rrtype: e.DNSRRType, Rdata: e.DNSRData, Rcode: e.DNSRCode, Type: e.DNSType, Count: 1, }, }, } a.DNS.DNSDetails[key].AnswerSet[k] = &a.DNS.DNSDetails[key].Details[0] } else { as, ok := a.DNS.DNSDetails[key].AnswerSet[k] if !ok { newDetail := pDNSReplyDetails{ AnsweringHost: e.SrcIP, Rrtype: e.DNSRRType, Rdata: e.DNSRData, Rcode: e.DNSRCode, Type: e.DNSType, Count: 1, } a.DNS.DNSDetails[key].Details = append(a.DNS.DNSDetails[key].Details, newDetail) a.DNS.DNSDetails[key].AnswerSet[k] = &a.DNS.DNSDetails[key].Details[len(a.DNS.DNSDetails[key].AnswerSet)-1] } else { as.Count++ } } a.DNSMutex.Unlock() } func (a *PDNSCollector) countRequestV2(e *types.Entry) { a.DNSMutex.Lock() a.Count++ if e.DNSRRName == "" || len(e.DNSAnswers) == 0 { a.DNSMutex.Unlock() return } for _, v := range e.DNSAnswers { key := e.DNSRRName a.StringBuf.Write([]byte(e.SrcIP)) a.StringBuf.Write([]byte(v.DNSRRType)) a.StringBuf.Write([]byte(v.DNSRData)) a.StringBuf.Write([]byte(v.DNSRCode)) a.StringBuf.Write([]byte(v.DNSType)) k := a.StringBuf.String() a.StringBuf.Reset() if _, ok := a.DNS.DNSDetails[key]; !ok { a.DNS.DNSDetails[key] = &pDNSDetails{ AnswerSet: make(map[string]*pDNSReplyDetails), Details: []pDNSReplyDetails{ pDNSReplyDetails{ AnsweringHost: e.SrcIP, Rrtype: v.DNSRRType, Rdata: v.DNSRData, Rcode: v.DNSRCode, Type: v.DNSType, Count: 1, }, }, } a.DNS.DNSDetails[key].AnswerSet[k] = &a.DNS.DNSDetails[key].Details[0] } else { as, ok := a.DNS.DNSDetails[key].AnswerSet[k] if !ok { newDetail := pDNSReplyDetails{ AnsweringHost: e.SrcIP, Rrtype: v.DNSRRType, Rdata: v.DNSRData, Rcode: v.DNSRCode, Type: v.DNSType, Count: 1, } a.DNS.DNSDetails[key].Details = append(a.DNS.DNSDetails[key].Details, newDetail) a.DNS.DNSDetails[key].AnswerSet[k] = &a.DNS.DNSDetails[key].Details[len(a.DNS.DNSDetails[key].AnswerSet)-1] } else { as.Count++ } } } a.DNSMutex.Unlock() } // Consume processes an Entry, adding the data within to the internal // aggregated state func (a *PDNSCollector) Consume(e *types.Entry) error { if e.DNSType == "answer" { if e.DNSVersion == 2 { a.countRequestV2(e) } else { a.countRequestV1(e) } } return nil } // Run starts the background aggregation service for this handler func (a *PDNSCollector) Run() { go func() { for message := range a.SubmitChannel { a.Submitter.Submit(message, "pdns", "application/json") } }() go func() { i := 0 * time.Second for { select { case <-a.CloseChan: close(a.SubmitChannel) close(a.ClosedChan) return default: if i >= a.FlushPeriod { a.flush() i = 0 * time.Second } time.Sleep(1 * time.Second) i += 1 * time.Second } } }() } // Stop causes the aggregator to cease aggregating and submitting data func (a *PDNSCollector) Stop(stopChan chan bool) { close(a.CloseChan) <-a.ClosedChan close(stopChan) } // GetName returns the name of the handler func (a *PDNSCollector) GetName() string { return "passive DNS collector" } // GetEventTypes returns a slice of event type strings that this handler // should be applied to func (a *PDNSCollector) GetEventTypes() []string { return []string{"dns"} } // EnableTestdataDomain registers a domain name to be included as each // submission as dummy data to be used for end-to-end testing. func (a *PDNSCollector) EnableTestdataDomain(domain string) { a.TestdataDummyDomain = domain } fever-1.3.7/processing/rdns_handler.go000066400000000000000000000065421477766221000200220ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2019, 2020, DCSO GmbH import ( "fmt" "net" "sync" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" "github.com/buger/jsonparser" log "github.com/sirupsen/logrus" "github.com/yl2chen/cidranger" ) // RDNSHandler is a handler that enriches events with reverse DNS // information looked up on the sensor, for both source and destination // IP addresses. type RDNSHandler struct { sync.Mutex Logger *log.Entry HostNamer util.HostNamer PrivateRanges cidranger.Ranger PrivateRangesOnly bool } // MakeRDNSHandler returns a new RDNSHandler, backed by the passed HostNamer. func MakeRDNSHandler(hn util.HostNamer) *RDNSHandler { rh := &RDNSHandler{ Logger: log.WithFields(log.Fields{ "domain": "rdns", }), PrivateRanges: cidranger.NewPCTrieRanger(), HostNamer: hn, } for _, cidr := range []string{ "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "fc00::/7", } { _, block, err := net.ParseCIDR(cidr) if err != nil { log.Fatalf("cannot parse fixed private IP range %v", cidr) } rh.PrivateRanges.Insert(cidranger.NewBasicRangerEntry(*block)) } return rh } // EnableOnlyPrivateIPRanges ensures that only private (RFC1918) IP ranges // are enriched func (a *RDNSHandler) EnableOnlyPrivateIPRanges() { a.PrivateRangesOnly = true } // Consume processes an Entry and enriches it func (a *RDNSHandler) Consume(e *types.Entry) error { var res []string var err error var isPrivate bool if e.SrcIP != "" { ip := net.ParseIP(e.SrcIP) if ip != nil { isPrivate, err = a.PrivateRanges.Contains(ip) if err != nil { return err } if !a.PrivateRangesOnly || isPrivate { res, err = a.HostNamer.GetHostname(e.SrcIP) if err == nil { for i, v := range res { hostname, err := util.EscapeJSON(v) if err != nil { log.Warningf("cannot escape hostname: %s", v) continue } newJSON, err := jsonparser.Set([]byte(e.JSONLine), hostname, "src_host", fmt.Sprintf("[%d]", i), "rdns") if err != nil { log.Warningf("cannot set hostname: %s", hostname) continue } else { e.JSONLine = string(newJSON) } } } } } else { log.Error("IP not valid") } } if e.DestIP != "" { ip := net.ParseIP(e.DestIP) if ip != nil { isPrivate, err = a.PrivateRanges.Contains(ip) if err != nil { return err } if !a.PrivateRangesOnly || isPrivate { res, err = a.HostNamer.GetHostname(e.DestIP) if err == nil { for i, v := range res { hostname, err := util.EscapeJSON(v) if err != nil { log.Warningf("cannot escape hostname: %s", v) continue } newJSON, err := jsonparser.Set([]byte(e.JSONLine), hostname, "dest_host", fmt.Sprintf("[%d]", i), "rdns") if err != nil { log.Warningf("cannot set hostname: %s", hostname) continue } else { e.JSONLine = string(newJSON) } } } } } else { log.Error("IP not valid") } } return nil } // GetName returns the name of the handler func (a *RDNSHandler) GetName() string { return "reverse DNS handler" } // GetEventTypes returns a slice of event type strings that this handler // should be applied to func (a *RDNSHandler) GetEventTypes() []string { return []string{"http", "dns", "tls", "smtp", "flow", "ssh", "tls", "smb", "alert"} } fever-1.3.7/processing/rdns_handler_test.go000066400000000000000000000044271477766221000210610ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2020, DCSO GmbH import ( "encoding/json" "math/rand" "testing" "time" "github.com/DCSO/fever/types" log "github.com/sirupsen/logrus" ) type MockHostNamer struct{} func (m *MockHostNamer) GetHostname(ipAddr string) ([]string, error) { return []string{"foo.bar", "foo.baz"}, nil } func (m *MockHostNamer) Flush() {} func makeRDNSEvent() types.Entry { e := types.Entry{ SrcIP: "8.8.8.8", SrcPort: int64(rand.Intn(60000) + 1025), DestIP: "8.8.8.8", DestPort: 53, Timestamp: time.Now().Format(types.SuricataTimestampFormat), EventType: "http", Proto: "TCP", } eve := types.EveEvent{ Timestamp: &types.SuriTime{ Time: time.Now(), }, EventType: e.EventType, SrcIP: e.SrcIP, SrcPort: int(e.SrcPort), DestIP: e.DestIP, DestPort: int(e.DestPort), Proto: e.Proto, } json, err := json.Marshal(eve) if err != nil { log.Warn(err) } else { e.JSONLine = string(json) } return e } type SrcHostResponse struct { Evidence []struct { Hostname string `json:"rdns"` } `json:"src_host"` } type DstHostResponse struct { Evidence []struct { Hostname string `json:"rdns"` } `json:"dest_host"` } func TestRDNSHandler(t *testing.T) { hn := MockHostNamer{} rdnsh := MakeRDNSHandler(&hn) e := makeRDNSEvent() err := rdnsh.Consume(&e) if err != nil { t.Fatal(err) } var srchosts SrcHostResponse err = json.Unmarshal([]byte(e.JSONLine), &srchosts) if err != nil { t.Fatal(err) } if len(srchosts.Evidence) != 2 { t.Fatalf("src hosts length is not 2: length %d", len(srchosts.Evidence)) } if srchosts.Evidence[0].Hostname != "foo.bar" { t.Fatalf("wrong hostname:%s", srchosts.Evidence[0].Hostname) } if srchosts.Evidence[1].Hostname != "foo.baz" { t.Fatalf("wrong hostname:%s", srchosts.Evidence[1].Hostname) } var desthosts DstHostResponse err = json.Unmarshal([]byte(e.JSONLine), &desthosts) if err != nil { t.Fatal(err) } if len(desthosts.Evidence) != 2 { t.Fatalf("dest hosts length is not 2: length %d", len(desthosts.Evidence)) } if desthosts.Evidence[0].Hostname != "foo.bar" { t.Fatalf("wrong hostname:%s", desthosts.Evidence[0].Hostname) } if desthosts.Evidence[1].Hostname != "foo.baz" { t.Fatalf("wrong hostname:%s", desthosts.Evidence[1].Hostname) } } fever-1.3.7/processing/unicorn_aggregator.go000066400000000000000000000163771477766221000212450ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2017, DCSO GmbH import ( "bytes" "encoding/json" "fmt" "os" "strconv" "sync" "time" "github.com/DCSO/fever/types" "github.com/DCSO/fever/util" log "github.com/sirupsen/logrus" ) // UnicornAggregate represents UNICORN relevant aggregated flow stats. type UnicornAggregate struct { SensorID string `json:"sensor-id"` TimestampStart time.Time `json:"time-start"` TimestampEnd time.Time `json:"time-end"` FlowTuples map[string](map[string]int64) `json:"tuples"` ProxyMap map[string](map[string]int64) `json:"proxy-map"` } // UnicornAggregator collects and updates an internal structure of flow // events grouped by route type UnicornAggregator struct { Logger *log.Entry Name string EventType string Aggregate UnicornAggregate Submitter util.StatsSubmitter DummyMode bool SubmitPeriod time.Duration CloseChan chan bool ClosedChan chan bool StringBuf bytes.Buffer UnicornTuplesMutex sync.RWMutex `json:"-"` UnicornProxyMapMutex sync.RWMutex `json:"-"` TestFlowSrcIP string TestFlowDestIP string TestFlowDestPort int64 AllFlows bool } // MakeUnicornAggregate creates a new empty UnicornAggregate object. func MakeUnicornAggregate() *UnicornAggregate { a := &UnicornAggregate{} a.SensorID, _ = os.Hostname() a.FlowTuples = make(map[string](map[string]int64)) a.ProxyMap = make(map[string](map[string]int64)) return a } // MakeUnicornAggregator creates a new empty UnicornAggregator object. func MakeUnicornAggregator(statsSubmitter util.StatsSubmitter, submitPeriod time.Duration, dummyMode bool, allFlows bool) *UnicornAggregator { a := &UnicornAggregator{ Logger: log.WithFields(log.Fields{ "domain": "aggregate", }), Submitter: statsSubmitter, DummyMode: dummyMode, SubmitPeriod: submitPeriod, CloseChan: make(chan bool), ClosedChan: make(chan bool), Aggregate: *MakeUnicornAggregate(), TestFlowDestPort: 99999, AllFlows: allFlows, } return a } func (a *UnicornAggregator) start() { timestamp := time.Now() a.Logger.WithFields(log.Fields{ "timestamp": timestamp, }).Debug("aggregation started") a.Aggregate.TimestampStart = timestamp } func (a *UnicornAggregator) stop() { timestamp := time.Now() a.Logger.WithFields(log.Fields{ "timestamp": timestamp, }).Debug("aggregation stopped") a.Aggregate.TimestampEnd = timestamp } func (a *UnicornAggregator) submit(submitter util.StatsSubmitter, dummyMode bool) { if a.TestFlowSrcIP != "" && a.TestFlowDestIP != "" { // Inject test flow into aggregation a.CountFlowTuple( fmt.Sprintf("%s_%s_%d", a.TestFlowSrcIP, a.TestFlowDestIP, a.TestFlowDestPort), 23, 42, 20, // count 20 to ensure some limits are met downstream ) } // Lock the current measurements for submission. Since this is a blocking // operation, we don't want this to depend on how long submitter.Submit() // takes but keep it independent of that. Hence we take the time to create // a local copy of the aggregate to be able to reset and release the live // one as quickly as possible. a.UnicornTuplesMutex.Lock() a.UnicornProxyMapMutex.Lock() // Make our own copy of the current aggregate, claiming ownership of the // maps with the measurements myAgg := UnicornAggregate{ SensorID: a.Aggregate.SensorID, TimestampStart: a.Aggregate.TimestampStart, TimestampEnd: a.Aggregate.TimestampEnd, ProxyMap: a.Aggregate.ProxyMap, FlowTuples: a.Aggregate.FlowTuples, } // Replace live maps with empty ones a.Aggregate.FlowTuples = make(map[string](map[string]int64)) a.Aggregate.ProxyMap = make(map[string](map[string]int64)) // Release aggregate to not block further blocking ops on map contents a.UnicornTuplesMutex.Unlock() a.UnicornProxyMapMutex.Unlock() jsonString, myerror := json.Marshal(myAgg) if myerror == nil { a.Logger.WithFields(log.Fields{ "flowtuples": len(myAgg.FlowTuples), "http-destips": len(myAgg.ProxyMap)}, ).Info("preparing to submit") submitter.Submit(jsonString, "unicorn", "application/json") } else { a.Logger.Warn("error marshaling JSON for metadata aggregation") } } // CountFlowTuple increments the flow tuple counter for the given key. If addCnt // is >1, then the caller is responsible for providing the correct (sub-total) // counts for bytestoclient and bytestoserver. func (a *UnicornAggregator) CountFlowTuple(key string, bytestoclient int64, bytestoserver int64, addCnt int64) { a.UnicornTuplesMutex.Lock() if _, ok := a.Aggregate.FlowTuples[key]; !ok { a.Aggregate.FlowTuples[key] = make(map[string]int64) } a.Aggregate.FlowTuples[key]["count"] += addCnt a.Aggregate.FlowTuples[key]["total_bytes_toclient"] += bytestoclient a.Aggregate.FlowTuples[key]["total_bytes_toserver"] += bytestoserver a.UnicornTuplesMutex.Unlock() } // CountHTTPHost increments the count for the given IP-hostname pair. func (a *UnicornAggregator) CountHTTPHost(destip string, hostname string) { a.UnicornProxyMapMutex.Lock() if _, ok := a.Aggregate.ProxyMap[destip]; !ok { a.Aggregate.ProxyMap[destip] = make(map[string]int64) } a.Aggregate.ProxyMap[destip][hostname]++ a.UnicornProxyMapMutex.Unlock() } // Run starts the background aggregation service for this handler func (a *UnicornAggregator) Run() { go func() { i := 0 * time.Second a.start() for { select { case <-a.CloseChan: close(a.ClosedChan) return default: if i >= a.SubmitPeriod { a.stop() a.submit(a.Submitter, a.DummyMode) a.start() i = 0 * time.Second } time.Sleep(100 * time.Millisecond) i += 100 * time.Millisecond } } }() } // Stop causes the aggregator to cease aggregating and submitting data func (a *UnicornAggregator) Stop(stopChan chan bool) { close(a.CloseChan) <-a.ClosedChan close(stopChan) } // Consume processes an Entry, adding the data within to the internal // aggregated state func (a *UnicornAggregator) Consume(e *types.Entry) error { // Unicorn flow aggregation update if e.EventType == "flow" && (a.AllFlows || (e.Proto == "TCP" && e.BytesToClient > 0)) { a.StringBuf.Write([]byte(e.SrcIP)) a.StringBuf.Write([]byte("_")) a.StringBuf.Write([]byte(e.DestIP)) a.StringBuf.Write([]byte("_")) a.StringBuf.Write([]byte(strconv.Itoa(int(e.DestPort)))) a.CountFlowTuple(a.StringBuf.String(), e.BytesToClient, e.BytesToServer, 1) a.StringBuf.Reset() } // Proxy detection update if e.EventType == "http" { if (e.DestPort >= 8000 && e.DestPort <= 8999) || e.DestPort == 3128 || e.DestPort == 80 { a.CountHTTPHost(e.DestIP, e.HTTPHost) } } return nil } // GetName returns the name of the handler func (a *UnicornAggregator) GetName() string { return "Unicorn aggregator/submitter" } // GetEventTypes returns a slice of event type strings that this handler // should be applied to func (a *UnicornAggregator) GetEventTypes() []string { return []string{"http", "flow"} } // EnableTestFlow adds a dummy flow with the given specs to each aggregation func (a *UnicornAggregator) EnableTestFlow(srcip, dstip string, dstport int64) { a.TestFlowSrcIP = srcip a.TestFlowDestIP = dstip a.TestFlowDestPort = dstport } fever-1.3.7/processing/unicorn_aggregator_test.go000066400000000000000000000207701477766221000222740ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2017, 2019, DCSO GmbH import ( "encoding/json" "fmt" "math/rand" "sync" "testing" "time" "github.com/DCSO/fever/types" log "github.com/sirupsen/logrus" ) func makeUnicornFlowEvent(proto string) types.Entry { e := types.Entry{ SrcIP: fmt.Sprintf("10.%d.%d.%d", rand.Intn(250), rand.Intn(250), rand.Intn(250)), SrcPort: []int64{1, 2, 3, 4, 5}[rand.Intn(5)], DestIP: fmt.Sprintf("10.0.0.%d", rand.Intn(250)), DestPort: []int64{11, 12, 13, 14, 15}[rand.Intn(5)], Timestamp: time.Now().Format(types.SuricataTimestampFormat), EventType: "flow", Proto: proto, BytesToClient: int64(rand.Intn(10000)), BytesToServer: int64(rand.Intn(10000)), PktsToClient: int64(rand.Intn(100)), PktsToServer: int64(rand.Intn(100)), } jsonBytes, _ := json.Marshal(e) e.JSONLine = string(jsonBytes) return e } type testSubmitter struct { DataLock sync.Mutex Data []string } func (s *testSubmitter) Submit(in []byte, key string, contentType string) { s.DataLock.Lock() defer s.DataLock.Unlock() s.Data = append(s.Data, string(in)) } func (s *testSubmitter) SubmitWithHeaders(in []byte, key string, contentType string, hdr map[string]string) { s.Submit(in, key, contentType) } func (s *testSubmitter) GetNumberSubmissions() int { s.DataLock.Lock() defer s.DataLock.Unlock() return len(s.Data) } func (s *testSubmitter) GetTotalAggs() int { s.DataLock.Lock() defer s.DataLock.Unlock() totalTuples := make(map[string](int)) for _, data := range s.Data { var agg UnicornAggregate err := json.Unmarshal([]byte(data), &agg) if err != nil { log.Fatalf("error parsing JSON: %s", err.Error()) } for k := range agg.FlowTuples { totalTuples[k]++ } } return len(totalTuples) } func (s *testSubmitter) GetFlowTuples() map[string](map[string]int64) { s.DataLock.Lock() defer s.DataLock.Unlock() allTuples := make(map[string](map[string]int64)) for _, data := range s.Data { var agg UnicornAggregate err := json.Unmarshal([]byte(data), &agg) if err != nil { log.Fatalf("error parsing JSON: %s", err.Error()) } for k := range agg.FlowTuples { if _, ok := allTuples[k]; !ok { allTuples[k] = make(map[string]int64) } allTuples[k]["count"] += agg.FlowTuples[k]["count"] } } return allTuples } func (s *testSubmitter) UseCompression() {} func (s *testSubmitter) Finish() {} func TestUnicornAggregatorNoSubmission(t *testing.T) { rand.Seed(time.Now().UTC().UnixNano()) dsub := &testSubmitter{ Data: make([]string, 0), } f := MakeUnicornAggregator(dsub, 100*time.Millisecond, false, false) f.Run() time.Sleep(1 * time.Second) consumeWaitChan := make(chan bool) f.Stop(consumeWaitChan) <-consumeWaitChan if dsub.GetNumberSubmissions() == 0 { t.Fatalf("collected aggregations are empty") } var totallen int for _, v := range dsub.Data { totallen += len(v) } if totallen == 0 { t.Fatalf("length of collected aggregations is zero") } } func TestUnicornAggregator(t *testing.T) { rand.Seed(time.Now().UTC().UnixNano()) dsub := &testSubmitter{ Data: make([]string, 0), } f := MakeUnicornAggregator(dsub, 500*time.Millisecond, false, false) f.Run() createdFlows := make(map[string]int) for i := 0; i < 200000; i++ { ev := makeUnicornFlowEvent("TCP") if ev.BytesToClient > 0 { key := fmt.Sprintf("%s_%s_%d", ev.SrcIP, ev.DestIP, ev.DestPort) createdFlows[key]++ } f.Consume(&ev) } for { if dsub.GetTotalAggs() < len(createdFlows) { log.Debug(dsub.GetTotalAggs()) time.Sleep(100 * time.Millisecond) } else { break } } consumeWaitChan := make(chan bool) f.Stop(consumeWaitChan) <-consumeWaitChan if len(dsub.Data) == 0 { t.Fatalf("collected aggregations are empty") } log.Info(dsub.GetTotalAggs(), len(createdFlows), len(dsub.Data)) var totallen int for _, v := range dsub.Data { totallen += len(v) } if totallen == 0 { t.Fatalf("length of collected aggregations is zero") } if dsub.GetTotalAggs() != len(createdFlows) { t.Fatalf("unexpected number of flow aggregates: %d/%d", dsub.GetTotalAggs(), len(createdFlows)) } for k, v := range dsub.GetFlowTuples() { if _, ok := createdFlows[k]; !ok { t.Fatalf("missing flow aggregate: %s", k) } if v["count"] != int64(createdFlows[k]) { t.Fatalf("unexpected number of flows for %s: %d/%d", k, v["count"], createdFlows[k]) } } } func TestUnicornAggregatorWithTestdata(t *testing.T) { rand.Seed(time.Now().UTC().UnixNano()) dsub := &testSubmitter{ Data: make([]string, 0), } f := MakeUnicornAggregator(dsub, 500*time.Millisecond, false, false) f.EnableTestFlow("1.2.3.4", "5.6.7.8", 33333) f.Run() for { if dsub.GetTotalAggs() < 1 { log.Debug(dsub.GetTotalAggs()) time.Sleep(100 * time.Millisecond) } else { break } } consumeWaitChan := make(chan bool) f.Stop(consumeWaitChan) <-consumeWaitChan var d UnicornAggregate err := json.Unmarshal([]byte(dsub.Data[0]), &d) if err != nil { t.Fatal(err) } if v, ok := d.FlowTuples["1.2.3.4_5.6.7.8_33333"]; ok { if val, ok := v["count"]; ok { if val != 20 { t.Fatalf("wrong value: %v", val) } } if val, ok := v["total_bytes_toclient"]; ok { if val != 23 { t.Fatalf("wrong value: %v", val) } } if val, ok := v["total_bytes_toserver"]; ok { if val != 42 { t.Fatalf("wrong value: %v", val) } } } else { t.Fatalf("missing key in map: %v", d.FlowTuples) } } func TestUnicornAggregatorWithDispatch(t *testing.T) { rand.Seed(time.Now().UTC().UnixNano()) dsub := &testSubmitter{ Data: make([]string, 0), } f := MakeUnicornAggregator(dsub, 500*time.Millisecond, false, false) feedWaitChan := make(chan bool) outChan := make(chan types.Entry) go func() { for range outChan { // pass } close(feedWaitChan) }() d := MakeHandlerDispatcher(outChan) d.RegisterHandler(f) f.Run() createdFlows := make(map[string]int) for i := 0; i < 400000; i++ { proto := "TCP" if i%2 == 0 { proto = "UDP" } ev := makeUnicornFlowEvent(proto) if proto == "TCP" && ev.BytesToClient > 0 { key := fmt.Sprintf("%s_%s_%d", ev.SrcIP, ev.DestIP, ev.DestPort) createdFlows[key]++ } d.Dispatch(&ev) } for { if dsub.GetTotalAggs() < (len(createdFlows) / 2) { log.Debug(dsub.GetTotalAggs()) time.Sleep(100 * time.Millisecond) } else { break } } consumeWaitChan := make(chan bool) f.Stop(consumeWaitChan) close(outChan) <-feedWaitChan <-consumeWaitChan if len(dsub.Data) == 0 { t.Fatalf("collected aggregations are empty") } log.Info(dsub.GetTotalAggs(), len(createdFlows), len(dsub.Data)) var totallen int for _, v := range dsub.Data { totallen += len(v) } if totallen == 0 { t.Fatalf("length of collected aggregations is zero") } if dsub.GetTotalAggs() != len(createdFlows) { t.Fatalf("unexpected number of flow aggregates: %d/%d", dsub.GetTotalAggs(), len(createdFlows)) } for k, v := range dsub.GetFlowTuples() { if _, ok := createdFlows[k]; !ok { t.Fatalf("missing flow aggregate: %s", k) } if v["count"] != int64(createdFlows[k]) { t.Fatalf("unexpected number of flows for %s: %d/%d", k, v["count"], createdFlows[k]) } } } func TestUnicornMixedUDPTCP(t *testing.T) { rand.Seed(time.Now().UTC().UnixNano()) dsub := &testSubmitter{ Data: make([]string, 0), } f := MakeUnicornAggregator(dsub, 500*time.Millisecond, false, true) f.Run() createdFlows := make(map[string]int) for i := 0; i < 200000; i++ { proto := "TCP" if i%2 == 0 { proto = "UDP" } ev := makeUnicornFlowEvent(proto) key := fmt.Sprintf("%s_%s_%d", ev.SrcIP, ev.DestIP, ev.DestPort) createdFlows[key]++ f.Consume(&ev) } for { if dsub.GetTotalAggs() < len(createdFlows) { log.Debug(dsub.GetTotalAggs()) time.Sleep(100 * time.Millisecond) } else { break } } consumeWaitChan := make(chan bool) f.Stop(consumeWaitChan) <-consumeWaitChan if len(dsub.Data) == 0 { t.Fatalf("collected aggregations are empty") } log.Info(dsub.GetTotalAggs(), len(createdFlows), len(dsub.Data)) var totallen int for _, v := range dsub.Data { totallen += len(v) } if totallen == 0 { t.Fatalf("length of collected aggregations is zero") } if dsub.GetTotalAggs() != len(createdFlows) { t.Fatalf("unexpected number of flow aggregates: %d/%d", dsub.GetTotalAggs(), len(createdFlows)) } for k, v := range dsub.GetFlowTuples() { if _, ok := createdFlows[k]; !ok { t.Fatalf("missing flow aggregate: %s", k) } if v["count"] != int64(createdFlows[k]) { t.Fatalf("unexpected number of flows for %s: %d/%d", k, v["count"], createdFlows[k]) } } } fever-1.3.7/processing/void_handler.go000066400000000000000000000015241477766221000200100ustar00rootroot00000000000000package processing // DCSO FEVER // Copyright (c) 2019, DCSO GmbH import ( "github.com/DCSO/fever/types" log "github.com/sirupsen/logrus" ) // VoidHandler is a handler that does nothing. type VoidHandler struct { Logger *log.Entry } // MakeVoidHandler creates a new forwarding handler func MakeVoidHandler() *VoidHandler { fh := &VoidHandler{ Logger: log.WithFields(log.Fields{ "domain": "forward", }), } return fh } // Consume processes an Entry and discards it func (fh *VoidHandler) Consume(e *types.Entry) error { _ = e return nil } // GetName returns the name of the handler func (fh *VoidHandler) GetName() string { return "Void forwarding handler" } // GetEventTypes returns a slice of event type strings that this handler // should be applied to func (fh *VoidHandler) GetEventTypes() []string { return []string{"*"} } fever-1.3.7/protoc.sh000077500000000000000000000015011477766221000145070ustar00rootroot00000000000000#! /usr/bin/env bash # this path has to contain protobuf's well-known types WELL_KNOWN_TYPES="thirdparty" # this is the stenosis project's root path STENOSIS_PATH=stenosis find ${STENOSIS_PATH} -name "*.pb.go" -delete protoc \ --proto_path="${WELL_KNOWN_TYPES}" \ --proto_path="${STENOSIS_PATH}" \ --proto_path="${GOPATH}/src" \ --go_out=:${GOPATH}/src \ ${STENOSIS_PATH}/api/hateoas.proto protoc \ --proto_path="${WELL_KNOWN_TYPES}" \ --proto_path="${STENOSIS_PATH}" \ --proto_path="${GOPATH}/src" \ --go_out=:${GOPATH}/src \ ${STENOSIS_PATH}/task/*.proto protoc \ --proto_path="${WELL_KNOWN_TYPES}" \ --proto_path="${STENOSIS_PATH}" \ --proto_path="${GOPATH}/src" \ --go_out=plugins=grpc:${GOPATH}/src \ ${STENOSIS_PATH}/api/stenosisservicequery.proto fever-1.3.7/protomgmtc.sh000077500000000000000000000005521477766221000154010ustar00rootroot00000000000000#! /usr/bin/env bash # this path has to contain protobuf's well-known types WELL_KNOWN_TYPES="thirdparty" # this is the mgmt project's root path MGMT_PATH=mgmt find ${MGMT_PATH} -name "*.pb.go" -delete protoc \ --proto_path="${WELL_KNOWN_TYPES}" \ --proto_path="${MGMT_PATH}" \ --go_out=plugins=grpc:${GOPATH}/src \ ${MGMT_PATH}/mgmt.proto fever-1.3.7/scripts/000077500000000000000000000000001477766221000143345ustar00rootroot00000000000000fever-1.3.7/scripts/makelpush000077500000000000000000000003021477766221000162460ustar00rootroot00000000000000#!/usr/bin/env lua for line in io.lines() do io.stdout:write("LPUSH suricata \"") escapedstr = string.gsub(line, "\"", "\\\"") io.stdout:write(escapedstr) io.stdout:write("\"\r\n") end fever-1.3.7/thirdparty/000077500000000000000000000000001477766221000150375ustar00rootroot00000000000000fever-1.3.7/thirdparty/google/000077500000000000000000000000001477766221000163135ustar00rootroot00000000000000fever-1.3.7/thirdparty/google/protobuf/000077500000000000000000000000001477766221000201535ustar00rootroot00000000000000fever-1.3.7/thirdparty/google/protobuf/empty.proto000066400000000000000000000045751477766221000224110ustar00rootroot00000000000000// Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. syntax = "proto3"; package google.protobuf; option csharp_namespace = "Google.Protobuf.WellKnownTypes"; option go_package = "google.golang.org/protobuf/types/known/emptypb"; option java_package = "com.google.protobuf"; option java_outer_classname = "EmptyProto"; option java_multiple_files = true; option objc_class_prefix = "GPB"; option cc_enable_arenas = true; // A generic empty message that you can re-use to avoid defining duplicated // empty messages in your APIs. A typical example is to use it as the request // or the response type of an API method. For instance: // // service Foo { // rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); // } // // The JSON representation for `Empty` is empty JSON object `{}`. message Empty {} fever-1.3.7/thirdparty/google/protobuf/timestamp.proto000066400000000000000000000140701477766221000232450ustar00rootroot00000000000000// Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. syntax = "proto3"; package google.protobuf; option csharp_namespace = "Google.Protobuf.WellKnownTypes"; option cc_enable_arenas = true; option go_package = "github.com/golang/protobuf/ptypes/timestamp"; option java_package = "com.google.protobuf"; option java_outer_classname = "TimestampProto"; option java_multiple_files = true; option objc_class_prefix = "GPB"; // A Timestamp represents a point in time independent of any time zone or local // calendar, encoded as a count of seconds and fractions of seconds at // nanosecond resolution. The count is relative to an epoch at UTC midnight on // January 1, 1970, in the proleptic Gregorian calendar which extends the // Gregorian calendar backwards to year one. // // All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap // second table is needed for interpretation, using a [24-hour linear // smear](https://developers.google.com/time/smear). // // The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By // restricting to that range, we ensure that we can convert to and from [RFC // 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. // // # Examples // // Example 1: Compute Timestamp from POSIX `time()`. // // Timestamp timestamp; // timestamp.set_seconds(time(NULL)); // timestamp.set_nanos(0); // // Example 2: Compute Timestamp from POSIX `gettimeofday()`. // // struct timeval tv; // gettimeofday(&tv, NULL); // // Timestamp timestamp; // timestamp.set_seconds(tv.tv_sec); // timestamp.set_nanos(tv.tv_usec * 1000); // // Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. // // FILETIME ft; // GetSystemTimeAsFileTime(&ft); // UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; // // // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z // // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. // Timestamp timestamp; // timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); // timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); // // Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. // // long millis = System.currentTimeMillis(); // // Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) // .setNanos((int) ((millis % 1000) * 1000000)).build(); // // // Example 5: Compute Timestamp from current time in Python. // // timestamp = Timestamp() // timestamp.GetCurrentTime() // // # JSON Mapping // // In JSON format, the Timestamp type is encoded as a string in the // [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the // format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" // where {year} is always expressed using four digits while {month}, {day}, // {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional // seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), // are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone // is required. A proto3 JSON serializer should always use UTC (as indicated by // "Z") when printing the Timestamp type and a proto3 JSON parser should be // able to accept both UTC and other timezones (as indicated by an offset). // // For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past // 01:30 UTC on January 15, 2017. // // In JavaScript, one can convert a Date object to this format using the // standard // [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) // method. In Python, a standard `datetime.datetime` object can be converted // to this format using // [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with // the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use // the Joda Time's [`ISODateTimeFormat.dateTime()`]( // http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D // ) to obtain a formatter capable of generating timestamps in this format. // // message Timestamp { // Represents seconds of UTC time since Unix epoch // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to // 9999-12-31T23:59:59Z inclusive. int64 seconds = 1; // Non-negative fractions of a second at nanosecond resolution. Negative // second values with fractions must still have non-negative nanos values // that count forward in time. Must be from 0 to 999,999,999 // inclusive. int32 nanos = 2; } fever-1.3.7/types/000077500000000000000000000000001477766221000140115ustar00rootroot00000000000000fever-1.3.7/types/entry.go000066400000000000000000000017661477766221000155130ustar00rootroot00000000000000package types // DCSO FEVER // Copyright (c) 2017, 2018, DCSO GmbH // DNSAnswer is a single DNS answer as observed by Suricata type DNSAnswer struct { DNSRRName string DNSRRType string DNSRCode string DNSRData string DNSType string } // Entry is a collection of data that needs to be parsed FAST from the entry type Entry struct { SrcIP string SrcHosts []string SrcPort int64 DestIP string DestHosts []string DestPort int64 Timestamp string EventType string Proto string HTTPHost string HTTPUrl string HTTPMethod string JSONLine string DNSVersion int64 DNSRRName string DNSRRType string DNSRCode string DNSRData string DNSType string DNSAnswers []DNSAnswer TLSSNI string BytesToClient int64 BytesToServer int64 PktsToClient int64 PktsToServer int64 FlowID string Iface string AppProto string TLSFingerprint string } fever-1.3.7/types/eve.go000066400000000000000000000247431477766221000151310ustar00rootroot00000000000000package types // DCSO FEVER // Copyright (c) 2017, 2019, 2020, DCSO GmbH import ( "encoding/json" "strconv" "time" ) const ( // SuricataTimestampFormat is a Go time formatting string describing the // timestamp format used by Suricata's EVE JSON output. SuricataTimestampFormat = "2006-01-02T15:04:05.999999-0700" // EventTypeFlow is the EventType string for the flow type. EventTypeFlow = "flow" // EventTypeAlert is the EventType string for the alert type. EventTypeAlert = "alert" ) // SuriTime is a Suricata-formatted timestamp string. type SuriTime struct{ time.Time } // UnmarshalJSON converts a JSON byte slice into a SuriTime struct. func (t *SuriTime) UnmarshalJSON(b []byte) error { data, err := strconv.Unquote(string(b)) if err != nil { return err } t.Time, err = time.Parse(SuricataTimestampFormat, data) return err } // MarshalJSON converts a SuriTime struct into a JSON byte slice. func (t *SuriTime) MarshalJSON() ([]byte, error) { return []byte("\"" + t.Time.Format(SuricataTimestampFormat) + "\""), nil } // AlertEvent is am alert sub-object of an EVE entry. type AlertEvent struct { Action string `json:"action"` Gid int `json:"gid"` SignatureID int `json:"signature_id"` Rev int `json:"rev"` Signature string `json:"signature"` Category string `json:"category"` Severity int `json:"severity"` } // DNSEvent is a DNS sub-object of an EVE entry. type DNSEvent struct { Type string `json:"type"` ID int `json:"id"` Rcode string `json:"rcode"` Rrname string `json:"rrname"` Rrtype string `json:"rrtype"` TTL int `json:"ttl"` Rdata string `json:"rdata"` TxID int `json:"tx_id"` } // HTTPEvent is an HTTP sub-object of an EVE entry. type HTTPEvent struct { Hostname string `json:"hostname"` URL string `json:"url"` HTTPUserAgent string `json:"http_user_agent"` HTTPContentType string `json:"http_content_type"` HTTPMethod string `json:"http_method"` Protocol string `json:"protocol"` Status int `json:"status"` Length int `json:"length"` } type fileinfoEvent struct { Filename string `json:"filename"` Magic string `json:"magic"` State string `json:"state"` Md5 string `json:"md5"` Stored bool `json:"stored"` Size int `json:"size"` TxID int `json:"tx_id"` } // EveFlowEvent is an Flow sub-object of an EVE entry. type EveFlowEvent struct { PktsToserver int `json:"pkts_toserver"` PktsToclient int `json:"pkts_toclient"` BytesToserver int `json:"bytes_toserver"` BytesToclient int `json:"bytes_toclient"` Start *SuriTime `json:"start"` End *SuriTime `json:"end"` Age int `json:"age"` State string `json:"state"` Reason string `json:"reason"` } // TLSEvent is a TLS sub-object of an EVE entry. type TLSEvent struct { Subject string `json:"subject"` Issuerdn string `json:"issuerdn"` Fingerprint string `json:"fingerprint"` Sni string `json:"sni"` Version string `json:"version"` } type statsEvent struct { Uptime int `json:"uptime"` Capture struct { KernelPackets int `json:"kernel_packets"` KernelDrops int `json:"kernel_drops"` } `json:"capture"` Decoder struct { Pkts int `json:"pkts"` Bytes int64 `json:"bytes"` Invalid int `json:"invalid"` Ipv4 int `json:"ipv4"` Ipv6 int `json:"ipv6"` Ethernet int `json:"ethernet"` Raw int `json:"raw"` Null int `json:"null"` Sll int `json:"sll"` TCP int `json:"tcp"` UDP int `json:"udp"` Sctp int `json:"sctp"` Icmpv4 int `json:"icmpv4"` Icmpv6 int `json:"icmpv6"` Ppp int `json:"ppp"` Pppoe int `json:"pppoe"` Gre int `json:"gre"` Vlan int `json:"vlan"` VlanQinq int `json:"vlan_qinq"` Teredo int `json:"teredo"` Ipv4InIpv6 int `json:"ipv4_in_ipv6"` Ipv6InIpv6 int `json:"ipv6_in_ipv6"` Mpls int `json:"mpls"` AvgPktSize int `json:"avg_pkt_size"` MaxPktSize int `json:"max_pkt_size"` Erspan int `json:"erspan"` Ipraw struct { InvalidIPVersion int `json:"invalid_ip_version"` } `json:"ipraw"` Ltnull struct { PktTooSmall int `json:"pkt_too_small"` UnsupportedType int `json:"unsupported_type"` } `json:"ltnull"` Dce struct { PktTooSmall int `json:"pkt_too_small"` } `json:"dce"` } `json:"decoder"` Flow struct { Memcap int `json:"memcap"` Spare int `json:"spare"` EmergModeEntered int `json:"emerg_mode_entered"` EmergModeOver int `json:"emerg_mode_over"` TCPReuse int `json:"tcp_reuse"` Memuse int `json:"memuse"` } `json:"flow"` Defrag struct { Ipv4 struct { Fragments int `json:"fragments"` Reassembled int `json:"reassembled"` Timeouts int `json:"timeouts"` } `json:"ipv4"` Ipv6 struct { Fragments int `json:"fragments"` Reassembled int `json:"reassembled"` Timeouts int `json:"timeouts"` } `json:"ipv6"` MaxFragHits int `json:"max_frag_hits"` } `json:"defrag"` Stream struct { ThreeWhsAckInWrongDir int `json:"3whs_ack_in_wrong_dir"` ThreeWhsAsyncWrongSeq int `json:"3whs_async_wrong_seq"` ThreeWhsRightSeqWrongAckEvasion int `json:"3whs_right_seq_wrong_ack_evasion"` } `json:"stream"` TCP struct { Sessions int `json:"sessions"` SsnMemcapDrop int `json:"ssn_memcap_drop"` Pseudo int `json:"pseudo"` PseudoFailed int `json:"pseudo_failed"` InvalidChecksum int `json:"invalid_checksum"` NoFlow int `json:"no_flow"` Syn int `json:"syn"` Synack int `json:"synack"` Rst int `json:"rst"` SegmentMemcapDrop int `json:"segment_memcap_drop"` StreamDepthReached int `json:"stream_depth_reached"` ReassemblyGap int `json:"reassembly_gap"` Memuse int `json:"memuse"` ReassemblyMemuse int `json:"reassembly_memuse"` } `json:"tcp"` Detect struct { Alert int `json:"alert"` } `json:"detect"` FlowMgr struct { ClosedPruned int `json:"closed_pruned"` NewPruned int `json:"new_pruned"` EstPruned int `json:"est_pruned"` } `json:"flow_mgr"` DNS struct { Memuse int `json:"memuse"` MemcapState int `json:"memcap_state"` MemcapGlobal int `json:"memcap_global"` } `json:"dns"` HTTP struct { Memuse int `json:"memuse"` Memcap int `json:"memcap"` } `json:"http"` } type sshEvent struct { Client struct { ProtoVersion string `json:"proto_version"` SoftwareVersion string `json:"software_version"` } `json:"client"` Server struct { ProtoVersion string `json:"proto_version"` SoftwareVersion string `json:"software_version"` } `json:"server"` } type smtpEvent struct { Helo string `json:"helo"` MailFrom string `json:"mail_from"` RcptTo []string `json:"rcpt_to"` } type tcpEvent struct { State string `json:"state"` Syn bool `json:"syn"` TCPflags string `json:"tcp_flags"` TCPflagsTc string `json:"tcp_flags_tc"` TCPflagsTs string `json:"tcp_flags_ts"` } type emailEvent struct { Status string `json:"status"` } type packetInfo struct { Linktype int `json:"linktype"` } // ExtraInfo contains non-EVE-standard extra information type ExtraInfo struct { BloomIOC string `json:"bloom-ioc,omitempty"` VastIOC string `json:"vast-ioc,omitempty"` StenosisInfo interface{} `json:"stenosis-info,omitempty"` } // EveEvent is the huge struct which can contain a parsed suricata eve.json // log event. type EveEvent struct { Timestamp *SuriTime `json:"timestamp"` EventType string `json:"event_type"` FlowID int64 `json:"flow_id,omitempty"` InIface string `json:"in_iface,omitempty"` SrcIP string `json:"src_ip,omitempty"` SrcPort int `json:"src_port,omitempty"` SrcHost []string `json:"src_host,omitempty"` DestIP string `json:"dest_ip,omitempty"` DestPort int `json:"dest_port,omitempty"` DestHost []string `json:"dest_host,omitempty"` Proto string `json:"proto,omitempty"` AppProto string `json:"app_proto,omitempty"` TxID int `json:"tx_id,omitempty"` TCP *tcpEvent `json:"tcp,omitempty"` PacketInfo *packetInfo `json:"packet_info,omitempty"` Alert *AlertEvent `json:"alert,omitempty"` Payload string `json:"payload,omitempty"` PayloadPrintable string `json:"payload_printable,omitempty"` Stream int `json:"stream,omitempty"` Packet string `json:"packet,omitempty"` SMTP *smtpEvent `json:"smtp,omitempty"` Email *emailEvent `json:"email,omitempty"` DNS *DNSEvent `json:"dns,omitempty"` HTTP *HTTPEvent `json:"http,omitempty"` Fileinfo *fileinfoEvent `json:"fileinfo,omitempty"` Flow *EveFlowEvent `json:"flow,omitempty"` SSH *sshEvent `json:"ssh,omitempty"` TLS *TLSEvent `json:"tls,omitempty"` Stats *statsEvent `json:"stats,omitempty"` ExtraInfo *ExtraInfo `json:"_extra,omitempty"` } // EveOutEvent is the version of EveEvent that we use to marshal the output for // downstream consumption. type EveOutEvent EveEvent // MarshalJSON for EveOutEvents ensures that FlowIDs are represented in JSON // as a string. This is necessary to work around some arbitrary limitations such // as syslog-ng's funny JSON parser implementation, which truncates large // integers found in JSON values. func (e EveOutEvent) MarshalJSON() ([]byte, error) { type Alias EveOutEvent v, err := json.Marshal(&struct { FlowID string `json:"flow_id"` Alias }{ FlowID: strconv.FormatInt(e.FlowID, 10), Alias: (Alias)(e), }) return v, err } // UnmarshalJSON implements filling an EveOutEvent from a byte slice, converting // the string in the FlowID field back into a number. This is necessary to // ensure that a round-trip (write+read) works. func (e *EveOutEvent) UnmarshalJSON(d []byte) error { type EveOutEvent2 EveOutEvent x := struct { EveOutEvent2 FlowID json.Number `json:"flow_id"` }{EveOutEvent2: EveOutEvent2(*e)} if err := json.Unmarshal(d, &x); err != nil { return err } *e = EveOutEvent(x.EveOutEvent2) var err error e.FlowID, _ = x.FlowID.Int64() // ignore error; defaulting to zero return err } fever-1.3.7/types/eve_test.go000066400000000000000000000030651477766221000161620ustar00rootroot00000000000000package types // DCSO FEVER // Copyright (c) 2019, DCSO GmbH import ( "encoding/json" "strings" "testing" "time" ) func TestEVERoundtripTimestamp(t *testing.T) { timeCmp, _ := time.Parse(time.RFC3339, "2019-08-06 13:30:01.690233 +0200 CEST") ee := EveEvent{ Timestamp: &SuriTime{ Time: timeCmp, }, EventType: "http", SrcIP: "1.2.3.4", SrcPort: 2222, DestIP: "3.4.5.6", DestPort: 80, Proto: "tcp", FlowID: 642, HTTP: &HTTPEvent{ Hostname: "test", URL: "/", }, } out, err := json.Marshal(ee) if err != nil { t.Error(err) } var inEVE EveEvent err = json.Unmarshal(out, &inEVE) if err != nil { t.Error(err) } if !inEVE.Timestamp.Time.Equal(ee.Timestamp.Time) { t.Fatalf("timestamp round-trip failed: %v <-> %v", inEVE.Timestamp, ee.Timestamp) } } func TestEVEStringFlowIDRoundtrip(t *testing.T) { timeCmp, _ := time.Parse(time.RFC3339, "2019-08-06 13:30:01.690233 +0200 CEST") ee := EveOutEvent{ Timestamp: &SuriTime{ Time: timeCmp, }, EventType: "http", SrcIP: "1.2.3.4", SrcPort: 2222, DestIP: "3.4.5.6", DestPort: 80, Proto: "tcp", FlowID: 649, HTTP: &HTTPEvent{ Hostname: "test", URL: "/", }, } out, err := json.Marshal(ee) if err != nil { t.Error(err) } var inEVE EveOutEvent err = json.Unmarshal(out, &inEVE) if err != nil { t.Error(err) } if !strings.Contains(string(out), `"flow_id":"649"`) { t.Fatalf("flow ID missing") } if inEVE.FlowID != ee.FlowID { t.Fatalf("round-trip failed: %v <-> %v", inEVE.FlowID, ee.FlowID) } } fever-1.3.7/types/flow_event.go000066400000000000000000000126521477766221000165160ustar00rootroot00000000000000package types // DCSO FEVER // Copyright (c) 2017, 2018, DCSO GmbH import ( "encoding/binary" "errors" "fmt" "io" "net" "time" ) // FlowEvent stores the meta-data of a flow event in a compact, binary form. type FlowEvent struct { Timestamp uint64 Format byte SrcIP []byte DestIP []byte SrcPort uint16 DestPort uint16 BytesToServer uint32 BytesToClient uint32 PktsToServer uint32 PktsToClient uint32 Flags uint16 } // FlowEventFlags defines various flags for use in FlowEvent.Flags (e.g. the protocol). var FlowEventFlags = map[string]uint16{ "TCP": 1 << 0, "UDP": 1 << 1, } var maxBytes = int64(^uint32(0)) func parseIP(stringIP string) ([]byte, error) { ip := net.ParseIP(stringIP) if ip == nil { return nil, errors.New("invalid IP") } ipv4 := ip.To4() if ipv4 == nil { //this is an IPv6 address reverseIP(ip) return ip, nil } //this is an IPv4 address reverseIP(ipv4) return ipv4, nil } func reverseIP(b []byte) { for i := 0; i < len(b); i++ { b[i], b[len(b)-i-1] = b[len(b)-i-1], b[i] } } // FromEntry populates a FlowEvent using an Entry func (fe *FlowEvent) FromEntry(e *Entry) error { ts, err := time.Parse(SuricataTimestampFormat, e.Timestamp) if err != nil { return err } srcIP, err := parseIP(e.SrcIP) if err != nil { return err } destIP, err := parseIP(e.DestIP) if err != nil { return err } flags := uint16(0) if e.Proto == "TCP" { flags |= FlowEventFlags["TCP"] } if e.Proto == "UDP" { flags |= FlowEventFlags["UDP"] } fe.Timestamp = uint64(ts.UnixNano()) fe.SrcIP = srcIP fe.SrcPort = uint16(e.SrcPort) fe.DestIP = destIP fe.DestPort = uint16(e.DestPort) fe.Format = 1 if len(srcIP) == 16 { fe.Format |= 1 << 1 } fe.Format |= 1 << 2 //bits 3,4,5 and 6 mark the version (currently 1) if len(srcIP) != len(destIP) { return fmt.Errorf("source and destination IPS have different lengths O.o") } if e.BytesToServer > maxBytes { return errors.New("BytesToServer is too large") } if e.BytesToClient > maxBytes { return errors.New("BytesToClient is too large") } if e.PktsToServer > maxBytes { return errors.New("PktsToServer is too large") } if e.PktsToClient > maxBytes { return errors.New("PktsToClient is too large") } fe.BytesToServer = uint32(e.BytesToServer) fe.BytesToClient = uint32(e.BytesToClient) fe.PktsToServer = uint32(e.PktsToServer) fe.PktsToClient = uint32(e.PktsToClient) fe.Flags = flags return nil } // Unmarshal reads a FlowEvent from an io.Reader. func (fe *FlowEvent) Unmarshal(reader io.Reader) error { bs1 := make([]byte, 1) bs2 := make([]byte, 2) bs4 := make([]byte, 4) bs8 := make([]byte, 8) //format if _, err := io.ReadFull(reader, bs1); err != nil { return err } fe.Format = bs1[0] if fe.Format&0x01 != 0x01 { return fmt.Errorf("invalid format byte (should start with a 1)") } isIPv6 := (fe.Format & 0x02) == 0x02 //timestamp if _, err := io.ReadFull(reader, bs8); err != nil { return err } fe.Timestamp = binary.LittleEndian.Uint64(bs8) //src ip if isIPv6 { fe.SrcIP = make([]byte, 4*4) if _, err := io.ReadFull(reader, fe.SrcIP); err != nil { return err } } else { fe.SrcIP = make([]byte, 4) if _, err := io.ReadFull(reader, fe.SrcIP); err != nil { return err } } //src port if _, err := io.ReadFull(reader, bs2); err != nil { return err } fe.SrcPort = binary.LittleEndian.Uint16(bs2) //dest ip if isIPv6 { fe.DestIP = make([]byte, 4*4) if _, err := io.ReadFull(reader, fe.DestIP); err != nil { return err } } else { fe.DestIP = make([]byte, 4) if _, err := io.ReadFull(reader, fe.DestIP); err != nil { return err } } //dest port if _, err := io.ReadFull(reader, bs2); err != nil { return err } fe.DestPort = binary.LittleEndian.Uint16(bs2) //PktsToServer if _, err := io.ReadFull(reader, bs4); err != nil { return err } fe.PktsToServer = binary.LittleEndian.Uint32(bs4) //PktsToClient if _, err := io.ReadFull(reader, bs4); err != nil { return err } fe.PktsToClient = binary.LittleEndian.Uint32(bs4) //BytesToServer if _, err := io.ReadFull(reader, bs4); err != nil { return err } fe.BytesToServer = binary.LittleEndian.Uint32(bs4) //BytesToClient if _, err := io.ReadFull(reader, bs4); err != nil { return err } fe.BytesToClient = binary.LittleEndian.Uint32(bs4) //Flags if _, err := io.ReadFull(reader, bs2); err != nil { return err } fe.Flags = binary.LittleEndian.Uint16(bs2) return nil } // Marshal writes a FlowEvent to an io.Writer. func (fe *FlowEvent) Marshal(writer io.Writer) error { bs1 := make([]byte, 1) bs2 := make([]byte, 2) bs4 := make([]byte, 4) bs8 := make([]byte, 8) //format bs1[0] = fe.Format writer.Write(bs1) //timestamp binary.LittleEndian.PutUint64(bs8, fe.Timestamp) writer.Write(bs8) //src ip writer.Write(fe.SrcIP) //src port binary.LittleEndian.PutUint16(bs2, fe.SrcPort) writer.Write(bs2) //dest ip writer.Write(fe.DestIP) //dest port binary.LittleEndian.PutUint16(bs2, fe.DestPort) writer.Write(bs2) //PktsToServer binary.LittleEndian.PutUint32(bs4, fe.PktsToServer) writer.Write(bs4) //PktsToClient binary.LittleEndian.PutUint32(bs4, fe.PktsToClient) writer.Write(bs4) //BytesToServer binary.LittleEndian.PutUint32(bs4, fe.BytesToServer) writer.Write(bs4) //BytesToClient binary.LittleEndian.PutUint32(bs4, fe.BytesToClient) writer.Write(bs4) //Flags binary.LittleEndian.PutUint16(bs2, fe.Flags) writer.Write(bs2) return nil } fever-1.3.7/types/flow_event_test.go000066400000000000000000000007471477766221000175570ustar00rootroot00000000000000package types // DCSO FEVER // Copyright (c) 2017, 2018, DCSO GmbH import ( "bytes" "net" "testing" ) func TestIPParsing(t *testing.T) { ipv4 := "8.8.8.8" ipv6 := "2001:0db8:85a3:0000:0000:8a2e:0370:7334" parsedIPv4, err := parseIP(ipv4) if err != nil || !bytes.Equal(parsedIPv4, net.ParseIP(ipv4).To4()) { t.Fatal("Conversion failed!") } parsedIPv6, err := parseIP(ipv6) if err != nil || !bytes.Equal(parsedIPv6, net.ParseIP(ipv6)) { t.Fatal("Conversion failed!") } } fever-1.3.7/util/000077500000000000000000000000001477766221000136225ustar00rootroot00000000000000fever-1.3.7/util/add_fields_preprocess.go000066400000000000000000000021231477766221000204720ustar00rootroot00000000000000package util import ( "fmt" log "github.com/sirupsen/logrus" ) // PreprocessAddedFields preprocesses the added fields to be able to only use // fast string operations to add them to JSON text later. This code // progressively builds a JSON snippet by adding JSON key-value pairs for each // added field, e.g. `, "foo":"bar"`. func PreprocessAddedFields(fields map[string]string) (string, error) { j := "" for k, v := range fields { // Escape the fields to make sure we do not mess up the JSON when // encountering weird symbols in field names or values. kval, err := EscapeJSON(k) if err != nil { log.Warningf("cannot escape value: %s", v) return "", err } vval, err := EscapeJSON(v) if err != nil { log.Warningf("cannot escape value: %s", v) return "", err } j += fmt.Sprintf(",%s:%s", kval, vval) } // We finish the list of key-value pairs with a final brace: // `, "foo":"bar"}`. This string can now just replace the final brace in a // given JSON string. If there were no added fields, we just leave the // output at the final brace. j += "}" return j, nil } fever-1.3.7/util/add_fields_preprocess_test.go000066400000000000000000000020071477766221000215320ustar00rootroot00000000000000package util import "testing" func TestPreprocessAddedFields(t *testing.T) { type args struct { fields map[string]string } tests := []struct { name string args args want []string wantErr bool }{ { name: "empty fieldset", args: args{ fields: map[string]string{}, }, want: []string{ "}", }, }, { name: "fieldset present", args: args{ fields: map[string]string{ "foo": "bar", "baz": "quux", }, }, want: []string{ `,"foo":"bar","baz":"quux"}`, `,"baz":"quux","foo":"bar"}`, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := PreprocessAddedFields(tt.args.fields) if (err != nil) != tt.wantErr { t.Errorf("PreprocessAddedFields() error = %v, wantErr %v", err, tt.wantErr) return } found := false for _, w := range tt.want { if got == w { found = true break } } if !found { t.Errorf("PreprocessAddedFields() = %v, want %v", got, tt.want) } }) } } fever-1.3.7/util/alertifier.go000066400000000000000000000143501477766221000163020ustar00rootroot00000000000000package util // DCSO FEVER // Copyright (c) 2020, DCSO GmbH import ( "fmt" "time" "github.com/DCSO/fever/types" "github.com/buger/jsonparser" log "github.com/sirupsen/logrus" ) // ExtraModifier is a function type that describes a function that adds the // appropriate `_extra` sub-object entries to a EVE-JSON event. type ExtraModifier func(inputAlert *types.Entry, ioc string) error // AlertJSONProvider is an interface describing a component that returns an // `alert` JSON sub-object to use in an EVE-JSON event. type AlertJSONProvider interface { // GetAlertJSON is a function that returns a byte slice containing the // JSON data for an `alert` EVE-JSON sub-object. GetAlertJSON(inputEvent types.Entry, prefix string, ioc string) ([]byte, error) } // Alertifier is a component that creates EVE-JSON alerts from arbitrary // EVE-JSON events. It does this by cloning the original event and adding // alert-specific fields, depending on the given ExtraModifier and a set of // AlertJSONProviders, selectable using a string tag. type Alertifier struct { alertPrefix string extraModifier ExtraModifier addedFields string matchTypes map[string]AlertJSONProvider } // MakeAlertifier returns a new Alertifier, with no AlertJSONProviders set for // any match types, but with the given alert prefix preconfigured. // The alert prefix is a string that is prepended to all alert.signature values, // as in "DCSO TIE-BLF" or "ETPRO CURRENT_EVENTS", etc. func MakeAlertifier(prefix string) *Alertifier { a := &Alertifier{ alertPrefix: prefix, matchTypes: make(map[string]AlertJSONProvider), } return a } // RegisterMatchType associates a given AlertJSONProvider with a match type tag. // It makes it callable in the MakeAlert() function in this Alertifier. func (a *Alertifier) RegisterMatchType(matchTypeName string, mt AlertJSONProvider) { a.matchTypes[matchTypeName] = mt } // SetPrefix sets the signature prefix of the current Alertifier to the given // string value. func (a *Alertifier) SetPrefix(prefix string) { a.alertPrefix = prefix } // SetExtraModifier sets the _extra modifier of the current Alertifier to the // passed function. Set it to nil to disable modification of the _extra // sub-object. func (a *Alertifier) SetExtraModifier(em ExtraModifier) { a.extraModifier = em } // SetAddedFields adds string key-value pairs to be added as extra JSON // values. func (a *Alertifier) SetAddedFields(fields map[string]string) error { af, err := PreprocessAddedFields(fields) if err != nil { return err } a.addedFields = af return nil } // MakeAlert generates a new Entry representing an `alert` event based on the // given input metadata event. It uses the information from the Alertifier as // well as the given IoC to craft an `alert` sub-object in the resulting // alert, which is built by the AlertJSONProvider registered under the specified // matchType. func (a *Alertifier) MakeAlert(inputEvent types.Entry, ioc string, matchType string) (*types.Entry, error) { v, ok := a.matchTypes[matchType] if !ok { return nil, fmt.Errorf("cannot create alert for metadata, unknown "+ "matchtype '%s'", matchType) } // clone the original event newEntry := inputEvent // set a new event type in Entry newEntry.EventType = "alert" // update JSON text l, err := jsonparser.Set([]byte(newEntry.JSONLine), []byte(`"alert"`), "event_type") if err != nil { return nil, err } newEntry.JSONLine = string(l) // generate alert sub-object JSON val, err := v.GetAlertJSON(inputEvent, a.alertPrefix, ioc) if err != nil { return nil, err } // update JSON text l, err = jsonparser.Set([]byte(newEntry.JSONLine), val, "alert") if err != nil { return nil, err } newEntry.JSONLine = string(l) // add custom extra modifier if a.extraModifier != nil { err = a.extraModifier(&newEntry, ioc) if err != nil { return nil, err } } // ensure consistent timestamp formatting: try to parse as Suricata timestamp eventTimestampFormatted := newEntry.Timestamp inTimestampParsed, err := time.Parse(types.SuricataTimestampFormat, newEntry.Timestamp) if err != nil { // otherwise try to parse without zone information inTimestampParsed, err = time.Parse("2006-01-02T15:04:05.999999", newEntry.Timestamp) if err == nil { eventTimestampFormatted = inTimestampParsed.Format(types.SuricataTimestampFormat) } else { log.Warningf("keeping non-offset timestamp '%s', could not be transformed: %s", newEntry.Timestamp, err.Error()) } } // Set received original timestamp as "timestamp_event" field escapedTimestamp, err := EscapeJSON(eventTimestampFormatted) if err != nil { return nil, err } l, err = jsonparser.Set([]byte(newEntry.JSONLine), escapedTimestamp, "timestamp_event") if err != nil { return nil, err } // Add current (alerting) timestamp as "timestamp" field nowTimestampEscaped, err := EscapeJSON(time.Now().UTC().Format(types.SuricataTimestampFormat)) if err != nil { return nil, err } l, err = jsonparser.Set(l, []byte(nowTimestampEscaped), "timestamp") if err != nil { return nil, err } // Append added fields string, if present if len(a.addedFields) > 1 { j := l jlen := len(j) j = j[:jlen-1] j = append(j, a.addedFields...) l = j } // update returned entry newEntry.Timestamp = eventTimestampFormatted newEntry.JSONLine = string(l) return &newEntry, nil } // GenericGetAlertObjForIoc is a simple helper function that takes a format // string with string ('%s') placeholders for the prefix and the IoC. It also // sets basic other alert fields such as `category` and `action`. func GenericGetAlertObjForIoc(inputEvent types.Entry, prefix string, ioc string, msg string) ([]byte, error) { sig := fmt.Sprintf(msg, prefix, ioc) val, err := EscapeJSON(sig) if err != nil { return nil, err } newAlertSubObj := "{}" if l, err := jsonparser.Set([]byte(newAlertSubObj), val, "signature"); err != nil { log.Warning(err) } else { newAlertSubObj = string(l) } if l, err := jsonparser.Set([]byte(newAlertSubObj), []byte(`"Potentially Bad Traffic"`), "category"); err != nil { log.Warning(err) } else { newAlertSubObj = string(l) } if l, err := jsonparser.Set([]byte(newAlertSubObj), []byte(`"allowed"`), "action"); err != nil { log.Warning(err) } else { newAlertSubObj = string(l) } return []byte(newAlertSubObj), err } fever-1.3.7/util/alertifier_providers.go000066400000000000000000000051261477766221000204000ustar00rootroot00000000000000package util // DCSO FEVER // Copyright (c) 2020, DCSO GmbH import ( "fmt" "github.com/DCSO/fever/types" ) // AlertJSONProviderHTTPURL is an AlertJSONProvider for HTTP URL matches. type AlertJSONProviderHTTPURL struct{} // GetAlertJSON returns the "alert" subobject for an alert EVE event. func (a AlertJSONProviderHTTPURL) GetAlertJSON(inputEvent types.Entry, prefix string, ioc string) ([]byte, error) { v := fmt.Sprintf("%s | %s | %s", inputEvent.HTTPMethod, inputEvent.HTTPHost, inputEvent.HTTPUrl) return GenericGetAlertObjForIoc(inputEvent, prefix, v, "%s Possibly bad HTTP URL: %s") } // AlertJSONProviderHTTPHost is an AlertJSONProvider for HTTP Host header // matches. type AlertJSONProviderHTTPHost struct{} // GetAlertJSON returns the "alert" subobject for an alert EVE event. func (a AlertJSONProviderHTTPHost) GetAlertJSON(inputEvent types.Entry, prefix string, ioc string) ([]byte, error) { return GenericGetAlertObjForIoc(inputEvent, prefix, ioc, "%s Possibly bad HTTP host: %s") } // AlertJSONProviderDNSReq is an AlertJSONProvider for DNS request matches. type AlertJSONProviderDNSReq struct{} // GetAlertJSON returns the "alert" subobject for an alert EVE event. func (a AlertJSONProviderDNSReq) GetAlertJSON(inputEvent types.Entry, prefix string, ioc string) ([]byte, error) { return GenericGetAlertObjForIoc(inputEvent, prefix, ioc, "%s Possibly bad DNS lookup to %s") } // AlertJSONProviderDNSResp is an AlertJSONProvider for DNS response matches. type AlertJSONProviderDNSResp struct{} // GetAlertJSON returns the "alert" subobject for an alert EVE event. func (a AlertJSONProviderDNSResp) GetAlertJSON(inputEvent types.Entry, prefix string, ioc string) ([]byte, error) { return GenericGetAlertObjForIoc(inputEvent, prefix, ioc, "%s Possibly bad DNS response for %s") } // AlertJSONProviderTLSSNI is an AlertJSONProvider for TLS SNI matches. type AlertJSONProviderTLSSNI struct{} // GetAlertJSON returns the "alert" subobject for an alert EVE event. func (a AlertJSONProviderTLSSNI) GetAlertJSON(inputEvent types.Entry, prefix string, ioc string) ([]byte, error) { return GenericGetAlertObjForIoc(inputEvent, prefix, ioc, "%s Possibly bad TLS SNI: %s") } // AlertJSONProviderTLSFingerprint is an AlertJSONProvider for TLS Fingerprint matches. type AlertJSONProviderTLSFingerprint struct{} // GetAlertJSON returns the "alert" subobject for an alert EVE event. func (a AlertJSONProviderTLSFingerprint) GetAlertJSON(inputEvent types.Entry, prefix string, ioc string) ([]byte, error) { return GenericGetAlertObjForIoc(inputEvent, prefix, ioc, "%s Possibly bad TLS Fingerprint: %s") } fever-1.3.7/util/alertifier_test.go000066400000000000000000000135571477766221000173510ustar00rootroot00000000000000package util // DCSO FEVER // Copyright (c) 2020, 2021, DCSO GmbH import ( "encoding/json" "fmt" "math/rand" "testing" "time" "github.com/DCSO/fever/types" "github.com/buger/jsonparser" log "github.com/sirupsen/logrus" ) func makeTestHTTPEvent(host string, url string) types.Entry { testTime, _ := time.Parse("2006-Jan-02", "2013-Feb-03") e := types.Entry{ SrcIP: fmt.Sprintf("10.0.0.%d", rand.Intn(5)+1), SrcPort: int64(rand.Intn(60000) + 1025), DestIP: fmt.Sprintf("10.0.0.%d", rand.Intn(50)), DestPort: 80, Timestamp: testTime.Format(types.SuricataTimestampFormat), EventType: "http", Proto: "TCP", HTTPHost: host, HTTPUrl: url, HTTPMethod: "GET", } eve := types.EveEvent{ Timestamp: &types.SuriTime{ Time: time.Now().UTC(), }, EventType: e.EventType, SrcIP: e.SrcIP, SrcPort: int(e.SrcPort), DestIP: e.DestIP, DestPort: int(e.DestPort), Proto: e.Proto, HTTP: &types.HTTPEvent{ Hostname: e.HTTPHost, URL: e.HTTPUrl, HTTPMethod: "GET", Status: 200, Length: 19000, Protocol: "HTTP/1.1", HTTPContentType: "application/html", HTTPUserAgent: "Go", }, } json, err := json.Marshal(eve) if err != nil { log.Warn(err) } else { e.JSONLine = string(json) } return e } type TestAlertJSONProviderHost struct{} func (a TestAlertJSONProviderHost) GetAlertJSON(inputEvent types.Entry, prefix string, ioc string) ([]byte, error) { sig := fmt.Sprintf("%s Possibly bad HTTP Host match for '%s'", prefix, ioc) val, err := EscapeJSON(sig) if err != nil { return nil, err } v, err := jsonparser.Set([]byte("{}"), val, "signature") return v, err } type TestAlertJSONProviderURLPath struct{} func (a TestAlertJSONProviderURLPath) GetAlertJSON(inputEvent types.Entry, prefix string, ioc string) ([]byte, error) { sig := fmt.Sprintf("%s Possibly bad HTTP Path match for '%s' in '%s'", prefix, ioc, inputEvent.HTTPUrl) val, err := EscapeJSON(sig) if err != nil { return nil, err } v, err := jsonparser.Set([]byte("{}"), val, "signature") return v, err } func checkAlertifierAlerts(t *testing.T, a *types.Entry, msg string, ioc string) { var resAlert types.EveEvent if err := json.Unmarshal([]byte(a.JSONLine), &resAlert); err != nil { t.Fatal(err) } if resAlert.Alert.Signature != msg { t.Fatalf("wrong signature ('%s' <-> '%s')", resAlert.Alert.Signature, msg) } if resAlert.ExtraInfo == nil { t.Fatalf("missing _extra in '%s'", string(a.JSONLine)) } if resAlert.ExtraInfo.VastIOC != ioc { t.Fatalf("wrong ioc ('%s' <-> '%s')", resAlert.ExtraInfo.VastIOC, ioc) } eventTimeVal, _, _, err := jsonparser.Get([]byte(a.JSONLine), "timestamp_event") if err != nil { t.Fatal(err) } if string(eventTimeVal) != "2013-02-03T00:00:00+0000" { t.Fatalf("wrong event timestamp ('%s' <-> '%s')", string(eventTimeVal), "2013-02-03T00:00:00+0000") } alertTimeVal, _, _, err := jsonparser.Get([]byte(a.JSONLine), "timestamp") if err != nil { t.Fatal(err) } alertTime, err := time.Parse(types.SuricataTimestampFormat, string(alertTimeVal)) if err != nil { t.Fatal(err) } if !alertTime.Add(48 * time.Hour).After(time.Now()) { t.Fatalf("wrong alert unexpected ('%s' < '%s')", alertTime.Add(48*time.Hour), time.Now()) } } func testExtraModifier(inputAlert *types.Entry, ioc string) error { iocEscaped, err := EscapeJSON(ioc) if err != nil { return err } val, err := jsonparser.Set([]byte(inputAlert.JSONLine), iocEscaped, "_extra", "vast-ioc") if err != nil { return err } inputAlert.JSONLine = string(val) return nil } func TestAlertifierSimple(t *testing.T) { a := MakeAlertifier("TEST") a.SetExtraModifier(testExtraModifier) a.RegisterMatchType("http_host", TestAlertJSONProviderHost{}) a.RegisterMatchType("http_path", TestAlertJSONProviderURLPath{}) e := makeTestHTTPEvent("foo.bar", "http://foo.bar/baz") alert, err := a.MakeAlert(e, "foo.bar", "http_host") if err != nil { t.Fatal(err) } checkAlertifierAlerts(t, alert, "TEST Possibly bad HTTP Host match "+ "for 'foo.bar'", "foo.bar") alert, err = a.MakeAlert(e, "foo.bar", "http_path") if err != nil { t.Fatal(err) } checkAlertifierAlerts(t, alert, "TEST Possibly bad HTTP Path match for "+ "'foo.bar' in 'http://foo.bar/baz'", "foo.bar") } func TestAlertifierTimestampMissingOffset(t *testing.T) { a := MakeAlertifier("TEST") a.SetExtraModifier(testExtraModifier) a.RegisterMatchType("http_host", TestAlertJSONProviderHost{}) a.RegisterMatchType("http_path", TestAlertJSONProviderURLPath{}) e := makeTestHTTPEvent("foo.bar", "http://foo.bar/baz") newTimestamp := time.Now().Format("2006-01-02T15:04:05.999999") e.Timestamp = newTimestamp nts, err := EscapeJSON(newTimestamp) if err != nil { t.Fatal(err) } l, err := jsonparser.Set([]byte(e.JSONLine), nts, "timestamp") if err != nil { t.Fatal(err) } e.JSONLine = string(l) // check if value has offset-naive format _, err = time.Parse(types.SuricataTimestampFormat, e.Timestamp) if err == nil { t.Fatal("timestamp should not be in Suricata format") } // alertify, this should convert the timestamp to common offset-aware format alert, err := a.MakeAlert(e, "foo.bar", "http_host") if err != nil { t.Fatal(err) } // check if Entry field is correct now _, err = time.Parse(types.SuricataTimestampFormat, alert.Timestamp) if err != nil { t.Fatal(err) } // check if JSON field is correct now v, _, _, err := jsonparser.Get([]byte(alert.JSONLine), "timestamp") if err != nil { t.Fatal(err) } _, err = time.Parse(types.SuricataTimestampFormat, string(v)) if err != nil { t.Fatal(err) } } func TestAlertifierUnknownMatchtype(t *testing.T) { a := MakeAlertifier("TEST") a.SetExtraModifier(testExtraModifier) e := makeTestHTTPEvent("foo.bar", "http://foo.bar/baz") _, err := a.MakeAlert(e, "foo.bar", "nonexistant") if err == nil { t.Fatal("nonexistant matchType did not trigger an error") } } fever-1.3.7/util/consumer.go000066400000000000000000000113471477766221000160120ustar00rootroot00000000000000package util // Parts of this code have been taken from // https://github.com/streadway/amqp/blob/master/_examples/simple-consumer/consumer.go // released under the license of the main streadway/amqp project: // // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this // list of conditions and the following disclaimer in the documentation and/or // other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import ( "fmt" "github.com/NeowayLabs/wabbit" "github.com/NeowayLabs/wabbit/amqptest" log "github.com/sirupsen/logrus" ) // Consumer reads and processes messages from a fake RabbitMQ server. type Consumer struct { conn wabbit.Conn channel wabbit.Channel tag string done chan error Callback func(wabbit.Delivery) } // NewConsumer creates a new consumer with the given properties. The callback // function is called for each delivery accepted from a consumer channel. func NewConsumer(amqpURI, exchange, exchangeType, queueName, key, ctag string, callback func(wabbit.Delivery)) (*Consumer, error) { var err error c := &Consumer{ conn: nil, channel: nil, tag: ctag, done: make(chan error), Callback: callback, } log.Debugf("dialing %q", amqpURI) c.conn, err = amqptest.Dial(amqpURI) if err != nil { return nil, fmt.Errorf("dial: %s", err) } log.Debugf("got Connection, getting Channel") c.channel, err = c.conn.Channel() if err != nil { return nil, fmt.Errorf("channel: %s", err) } log.Debugf("got Channel, declaring Exchange (%q)", exchange) if err = c.channel.ExchangeDeclare( exchange, // name of the exchange exchangeType, // type wabbit.Option{ "durable": true, "delete": false, "internal": false, "noWait": false, }, ); err != nil { return nil, fmt.Errorf("exchange declare: %s", err) } queue, err := c.channel.QueueDeclare( queueName, // name of the queue wabbit.Option{ "durable": true, "delete": false, "exclusive": false, "noWait": false, }, ) if err != nil { return nil, fmt.Errorf("queue declare: %s", err) } log.Debugf("declared Queue (%q %d messages, %d consumers), binding to Exchange (key %q)", queue.Name(), queue.Messages(), queue.Consumers(), key) if err = c.channel.QueueBind( queue.Name(), // name of the queue key, // bindingKey exchange, // sourceExchange wabbit.Option{ "noWait": false, }, ); err != nil { return nil, fmt.Errorf("queue bind: %s", err) } log.Debugf("Queue bound to Exchange, starting Consume (consumer tag %q)", c.tag) deliveries, err := c.channel.Consume( queue.Name(), // name c.tag, // consumerTag, wabbit.Option{ "exclusive": false, "noLocal": false, "noWait": false, }, ) if err != nil { return nil, fmt.Errorf("queue consume: %s", err) } go handle(deliveries, c.done, c.Callback) return c, nil } // Shutdown shuts down a consumer, closing down its channels and connections. func (c *Consumer) Shutdown() error { // will close() the deliveries channel if err := c.channel.Close(); err != nil { return fmt.Errorf("channel close failed: %s", err) } if err := c.conn.Close(); err != nil { return fmt.Errorf("AMQP connection close error: %s", err) } defer log.Debugf("AMQP shutdown OK") // wait for handle() to exit return <-c.done } const maxLogLen = 100 func handle(deliveries <-chan wabbit.Delivery, done chan error, callback func(wabbit.Delivery)) { for d := range deliveries { v := d.Body() if len(v) > maxLogLen { v = v[:maxLogLen] } log.Debugf( "got %dB delivery: [%v] %q", len(d.Body()), d.DeliveryTag(), v, ) callback(d) d.Ack(false) } done <- nil } fever-1.3.7/util/hostnamer.go000066400000000000000000000003341477766221000161510ustar00rootroot00000000000000package util // HostNamer is an interface specifying a component that provides // cached hostnames for IP addresses passed as strings. type HostNamer interface { GetHostname(ipAddr string) ([]string, error) Flush() } fever-1.3.7/util/hostnamer_rdns.go000066400000000000000000000022711477766221000172010ustar00rootroot00000000000000package util import ( "net" "strings" "sync" "time" "github.com/patrickmn/go-cache" ) // HostNamerRDNS is a component that provides cached hostnames for IP // addresses passed as strings, determined via reverse DNS lookups. type HostNamerRDNS struct { cache *cache.Cache lock sync.Mutex } // NewHostNamerRDNS returns a new HostNamer with the given default expiration time. // Data entries will be purged after each cleanupInterval. func NewHostNamerRDNS(defaultExpiration, cleanupInterval time.Duration) *HostNamerRDNS { return &HostNamerRDNS{ cache: cache.New(defaultExpiration, cleanupInterval), } } // GetHostname returns a list of host names for a given IP address. func (n *HostNamerRDNS) GetHostname(ipAddr string) ([]string, error) { n.lock.Lock() defer n.lock.Unlock() val, found := n.cache.Get(ipAddr) if found { return val.([]string), nil } hns, err := net.LookupAddr(ipAddr) if err != nil { return nil, err } for i, hn := range hns { hns[i] = strings.TrimRight(hn, ".") } n.cache.Set(ipAddr, hns, cache.DefaultExpiration) val = hns return val.([]string), nil } // Flush clears the cache of a HostNamerRDNS. func (n *HostNamerRDNS) Flush() { n.cache.Flush() } fever-1.3.7/util/hostnamer_rdns_test.go000066400000000000000000000020221477766221000202320ustar00rootroot00000000000000package util import ( "testing" "time" log "github.com/sirupsen/logrus" ) func _TestHostNamerQuad8(t *testing.T, ip string) { hn := NewHostNamerRDNS(5*time.Second, 5*time.Second) v, err := hn.GetHostname(ip) if err != nil { log.Info(err) t.Skip() } if len(v) == 0 { t.Fatal("no response") } else { log.Infof("got response %v", v) } v, err = hn.GetHostname(ip) if err != nil { t.Fatal(err) } if len(v) == 0 { t.Fatal("no response") } else { log.Infof("got response %v", v) } time.Sleep(6 * time.Second) v, err = hn.GetHostname(ip) if err != nil { t.Fatal(err) } if len(v) == 0 { t.Fatal("no response") } else { log.Infof("got response %v", v) } } func TestHostNamerQuad8v4(t *testing.T) { _TestHostNamerQuad8(t, "8.8.8.8") } func TestHostNamerQuad8v6(t *testing.T) { _TestHostNamerQuad8(t, "2001:4860:4860::8888") } func TestHostNamerInvalid(t *testing.T) { hn := NewHostNamerRDNS(5*time.Second, 5*time.Second) _, err := hn.GetHostname("8.") if err == nil { t.Fatal("missed error") } } fever-1.3.7/util/performance_stats_encoder.go000066400000000000000000000044251477766221000213740ustar00rootroot00000000000000package util // DCSO FEVER // Copyright (c) 2017, DCSO GmbH import ( "bytes" "strings" "sync" "time" "github.com/DCSO/fluxline" log "github.com/sirupsen/logrus" ) // PerformanceStatsEncoder is a component to collect, encode and submit data // to an InfluxDb via RabbitMQ. type PerformanceStatsEncoder struct { sync.RWMutex Encoder *fluxline.Encoder Buffer bytes.Buffer Logger *log.Entry Tags map[string]string Submitter StatsSubmitter SubmitPeriod time.Duration LastSubmitted time.Time DummyMode bool } // MakePerformanceStatsEncoder creates a new stats encoder, submitting via // the given StatsSubmitter, with at least submitPeriod time between submissions. // if dummyMode is set, then the result will be printed to stdout instead of // submitting. func MakePerformanceStatsEncoder(statsSubmitter StatsSubmitter, submitPeriod time.Duration, dummyMode bool) *PerformanceStatsEncoder { a := &PerformanceStatsEncoder{ Logger: log.WithFields(log.Fields{ "domain": "statscollect", }), Submitter: statsSubmitter, DummyMode: dummyMode, Tags: make(map[string]string), LastSubmitted: time.Now(), SubmitPeriod: submitPeriod, } a.Encoder = fluxline.NewEncoder(&a.Buffer) return a } // SubmitWithTags encodes the data annotated with 'influx' tags in the passed // struct and sends it to the configured submitter. This version also allows to // add a set of user-defined tags as a key-value map. func (a *PerformanceStatsEncoder) SubmitWithTags(val interface{}, tags map[string]string) { a.Lock() a.Buffer.Reset() err := a.Encoder.EncodeWithoutTypes(ToolName, val, tags) if err != nil { if a.Logger != nil { a.Logger.WithFields(log.Fields{}).Warn(err) } } line := strings.TrimSpace(a.Buffer.String()) if line == "" { a.Logger.WithFields(log.Fields{}).Warn("skipping empty influx line") a.Unlock() return } jsonString := []byte(line) a.Submitter.SubmitWithHeaders(jsonString, "", "text/plain", map[string]string{ "database": "telegraf", "retention_policy": "default", }) a.Unlock() } // Submit encodes the data annotated with 'influx' tags in the passed struct and // sends it to the configured submitter. func (a *PerformanceStatsEncoder) Submit(val interface{}) { a.SubmitWithTags(val, a.Tags) } fever-1.3.7/util/performance_stats_encoder_test.go000066400000000000000000000074031477766221000224320ustar00rootroot00000000000000package util // DCSO FEVER // Copyright (c) 2017, 2019, DCSO GmbH import ( "fmt" "regexp" "sync" "testing" "time" "github.com/NeowayLabs/wabbit" "github.com/NeowayLabs/wabbit/amqptest" "github.com/NeowayLabs/wabbit/amqptest/server" log "github.com/sirupsen/logrus" ) var testStruct = struct { TestVal uint64 `influx:"testval"` TestVal2 uint64 `influx:"testvalue"` TestVal3 uint64 }{ 1, 2, 3, } var testStructUntagged = struct { TestVal uint64 TestVal2 uint64 TestVal3 uint64 }{ 1, 2, 3, } func TestPerformanceStatsEncoderEmpty(t *testing.T) { serverURL := "amqp://sensor:sensor@127.0.0.1:9999/%2f/" // start mock AMQP server fakeServer := server.NewServer(serverURL) fakeServer.Start() defer fakeServer.Stop() // set up consumer results := make([]string, 0) c, err := NewConsumer(serverURL, "tdh.metrics", "direct", "tdh.metrics.testqueue", "", "", func(d wabbit.Delivery) { results = append(results, string(d.Body())) }) if err != nil { t.Fatal(err) } defer c.Shutdown() // set up submitter statssubmitter, err := MakeAMQPSubmitterWithReconnector(serverURL, "tdh.metrics", true, func(url string) (wabbit.Conn, error) { // we pass in a custom reconnector which uses the amqptest implementation var conn wabbit.Conn conn, err = amqptest.Dial(url) return conn, err }) if err != nil { t.Fatal(err) } defer statssubmitter.Finish() // create InfluxDB line protocol encoder/submitter pse := MakePerformanceStatsEncoder(statssubmitter, 1*time.Second, false) pse.Submit(testStructUntagged) time.Sleep(1 * time.Second) if len(results) != 0 { t.Fatalf("unexpected result length: %d !=0", len(results)) } } func TestPerformanceStatsEncoder(t *testing.T) { serverURL := "amqp://sensor:sensor@127.0.0.1:9999/%2f/" // start mock AMQP server fakeServer := server.NewServer(serverURL) fakeServer.Start() defer fakeServer.Stop() // set up consumer results := make([]string, 0) gateChan := make(chan bool) var resultsLock sync.Mutex c, err := NewConsumer(serverURL, "tdh.metrics", "direct", "tdh.metrics.testqueue", "", "", func(d wabbit.Delivery) { resultsLock.Lock() results = append(results, string(d.Body())) resultsLock.Unlock() log.Info(string(d.Body())) gateChan <- true }) if err != nil { t.Fatal(err) } defer c.Shutdown() // set up submitter statssubmitter, err := MakeAMQPSubmitterWithReconnector(serverURL, "tdh.metrics", true, func(url string) (wabbit.Conn, error) { // we pass in a custom reconnector which uses the amqptest implementation var conn wabbit.Conn conn, err = amqptest.Dial(url) return conn, err }) if err != nil { t.Fatal(err) } defer statssubmitter.Finish() // create InfluxDB line protocol encoder/submitter pse := MakePerformanceStatsEncoder(statssubmitter, 1*time.Second, false) pse.Submit(testStruct) <-gateChan pse.Submit(testStruct) <-gateChan testStruct.TestVal = 3 pse.Submit(testStruct) <-gateChan pse.Submit(testStruct) <-gateChan resultsLock.Lock() if len(results) != 4 { t.Fatalf("unexpected result length: %d != 4", len(results)) } if match, _ := regexp.Match(fmt.Sprintf("^%s,[^ ]+ testval=1,testvalue=2", ToolName), []byte(results[0])); !match { t.Fatalf("unexpected match content: %s", results[0]) } if match, _ := regexp.Match(fmt.Sprintf("^%s,[^ ]+ testval=1,testvalue=2", ToolName), []byte(results[1])); !match { t.Fatalf("unexpected match content: %s", results[1]) } if match, _ := regexp.Match(fmt.Sprintf("^%s,[^ ]+ testval=3,testvalue=2", ToolName), []byte(results[2])); !match { t.Fatalf("unexpected match content: %s", results[2]) } if match, _ := regexp.Match(fmt.Sprintf("^%s,[^ ]+ testval=3,testvalue=2", ToolName), []byte(results[3])); !match { t.Fatalf("unexpected match content: %s", results[3]) } resultsLock.Unlock() } fever-1.3.7/util/submitter.go000066400000000000000000000005501477766221000161670ustar00rootroot00000000000000package util // DCSO FEVER // Copyright (c) 2017, DCSO GmbH // StatsSubmitter is an interface for an entity that sends JSON data to an endpoint type StatsSubmitter interface { Submit(rawData []byte, key string, contentType string) SubmitWithHeaders(rawData []byte, key string, contentType string, myHeaders map[string]string) UseCompression() Finish() } fever-1.3.7/util/submitter_amqp.go000066400000000000000000000153541477766221000172150ustar00rootroot00000000000000package util // DCSO FEVER // Copyright (c) 2017, 2021, DCSO GmbH import ( "bytes" "compress/gzip" "sync" "time" "github.com/NeowayLabs/wabbit" "github.com/NeowayLabs/wabbit/amqp" "github.com/NeowayLabs/wabbit/utils" origamqp "github.com/rabbitmq/amqp091-go" log "github.com/sirupsen/logrus" ) // AMQPBaseSubmitter is the base engine that sends reports to a RabbitMQ host and // handles reconnection. type AMQPBaseSubmitter struct { URL string Verbose bool SensorID string Conn wabbit.Conn Channel wabbit.Channel StopReconnection chan bool ErrorChan chan wabbit.Error Logger *log.Entry ChanMutex sync.Mutex ConnMutex sync.Mutex Reconnector func(string) (wabbit.Conn, error) NofSubmitters uint } // AMQPSubmitter is a StatsSubmitter that sends reports to a RabbitMQ exchange. type AMQPSubmitter struct { Submitter *AMQPBaseSubmitter Target string Compress bool } const ( amqpReconnDelay = 5 * time.Second ) var ( gSubmitters = make(map[string]*AMQPBaseSubmitter) ) func defaultReconnector(amqpURI string) (wabbit.Conn, error) { conn, err := amqp.Dial(amqpURI) if err != nil { return nil, err } return conn, err } func reconnectOnFailure(s *AMQPBaseSubmitter) { errChan := s.ErrorChan for { select { case <-s.StopReconnection: return case rabbitErr := <-errChan: if rabbitErr != nil { log.Warnf("RabbitMQ connection failed: %s", rabbitErr.Reason()) s.ChanMutex.Lock() for { connErr := s.connect() if connErr != nil { log.Warnf("RabbitMQ error: %s", connErr) } else { log.Infof("Reestablished connection to %s", s.URL) errChan = make(chan wabbit.Error) s.Conn.NotifyClose(errChan) s.ErrorChan = errChan break } time.Sleep(amqpReconnDelay) } s.ChanMutex.Unlock() } } } } func (s *AMQPBaseSubmitter) connect() error { var err error s.ConnMutex.Lock() s.Logger.Debugf("calling reconnector") s.Conn, err = s.Reconnector(s.URL) if err != nil { s.Conn = nil s.ConnMutex.Unlock() return err } s.Channel, err = s.Conn.Channel() if err != nil { s.Conn.Close() s.ConnMutex.Unlock() return err } log.Debugf("Submitter established connection to %s", s.URL) s.ConnMutex.Unlock() return nil } // MakeAMQPSubmitterWithReconnector creates a new submitter connected to a // RabbitMQ server at the given URL, using the reconnector function as a means // to Dial() in order to obtain a Connection object. func MakeAMQPSubmitterWithReconnector(url string, target string, verbose bool, reconnector func(string) (wabbit.Conn, error)) (*AMQPSubmitter, error) { var err error var mySubmitter *AMQPBaseSubmitter if _, ok := gSubmitters[url]; !ok { mySubmitter = &AMQPBaseSubmitter{ URL: url, Verbose: verbose, ErrorChan: make(chan wabbit.Error), Reconnector: reconnector, StopReconnection: make(chan bool), } mySubmitter.Logger = log.WithFields(log.Fields{ "domain": "submitter", "submitter": "AMQP", "url": url, }) mySubmitter.Logger.Debugf("new base submitter created") mySubmitter.SensorID, err = GetSensorID() if err != nil { return nil, err } // Start reconnect loop in separate goroutine. go reconnectOnFailure(mySubmitter) // The reconnect loop started in the line above will only trigger when // something is received on the error channel. We'll emit a fake error // to trigger the initial connect to avoid code duplication. mySubmitter.ErrorChan <- utils.NewError(0, "Initial connect", true, true) gSubmitters[url] = mySubmitter mySubmitter.NofSubmitters++ mySubmitter.Logger.Debugf("number of submitters now %d", mySubmitter.NofSubmitters) } else { mySubmitter = gSubmitters[url] } retSubmitter := &AMQPSubmitter{ Submitter: mySubmitter, Target: target, } return retSubmitter, nil } // MakeAMQPSubmitter creates a new submitter connected to a RabbitMQ server // at the given URL. func MakeAMQPSubmitter(url string, target string, verbose bool) (*AMQPSubmitter, error) { return MakeAMQPSubmitterWithReconnector(url, target, verbose, defaultReconnector) } // UseCompression enables gzip compression of submitted payloads. func (s *AMQPSubmitter) UseCompression() { s.Compress = true } // Submit sends the rawData payload via the registered RabbitMQ connection. func (s *AMQPSubmitter) Submit(rawData []byte, key string, contentType string) { s.SubmitWithHeaders(rawData, key, contentType, nil) } // SubmitWithHeaders sends the rawData payload via the registered RabbitMQ connection, // adding some extra key-value pairs to the header. func (s *AMQPSubmitter) SubmitWithHeaders(rawData []byte, key string, contentType string, myHeaders map[string]string) { var payload []byte var encoding string var isCompressed string defer s.Submitter.ConnMutex.Unlock() if s.Compress { var b bytes.Buffer w := gzip.NewWriter(&b) w.Write(rawData) w.Close() payload = b.Bytes() isCompressed = "true" encoding = "gzip" } else { payload = rawData isCompressed = "false" } option := wabbit.Option{ "contentType": contentType, "contentEncoding": encoding, "headers": origamqp.Table{ "sensor_id": s.Submitter.SensorID, "compressed": isCompressed, }, } for k, v := range myHeaders { option["headers"].(origamqp.Table)[k] = v } s.Submitter.ConnMutex.Lock() // Double-check whether a value is nil and make sure we won't deref it. switch { case s.Submitter == nil: s.Submitter.Logger.Errorf("submitter was nil, skipping submission for %s", s.Target) return case s.Submitter.Channel == nil: s.Submitter.Logger.Errorf("channel was nil, skipping submission for %s/%s", s.Submitter.URL, s.Target) return } err := s.Submitter.Channel.Publish( s.Target, // exchange key, // routing key payload, option) if err != nil { s.Submitter.Logger.Warn(err) } else { s.Submitter.Logger.WithFields(log.Fields{ "rawsize": len(rawData), "payloadsize": len(payload), }).Debugf("submission to %s:%s (%s) successful", s.Submitter.URL, s.Target, key) } } // Finish cleans up the AMQP connection (reference counted). func (s *AMQPSubmitter) Finish() { s.Submitter.Logger.Debugf("finishing submitter %s (%s)", s.Submitter.URL, s.Target) if s.Submitter.NofSubmitters == 1 { close(s.Submitter.StopReconnection) if s.Submitter.Verbose { s.Submitter.Logger.Info("closing connection") } if s.Submitter.Channel != nil { s.Submitter.Channel.Close() } s.Submitter.ConnMutex.Lock() if s.Submitter.Conn != nil { s.Submitter.Conn.Close() } s.Submitter.ConnMutex.Unlock() delete(gSubmitters, s.Submitter.URL) } else { s.Submitter.NofSubmitters-- s.Submitter.Logger.Debugf("number of submitters now %d", s.Submitter.NofSubmitters) } } fever-1.3.7/util/submitter_dummy.go000066400000000000000000000032741477766221000174100ustar00rootroot00000000000000package util // DCSO FEVER // Copyright (c) 2018, DCSO GmbH import ( "unicode" log "github.com/sirupsen/logrus" ) // DummySubmitter is a StatsSubmitter that just logs submissions without // sending them over the network. type DummySubmitter struct { Logger *log.Entry SensorID string } func isASCIIPrintable(s string) bool { for _, r := range s { if r > unicode.MaxASCII || !unicode.IsPrint(r) { return false } } return true } // MakeDummySubmitter creates a new submitter just logging to the default log // target. func MakeDummySubmitter() (*DummySubmitter, error) { mySubmitter := &DummySubmitter{ Logger: log.WithFields(log.Fields{ "domain": "submitter", "submitter": "dummy", }), } sensorID, err := GetSensorID() if err != nil { return nil, err } mySubmitter.SensorID = sensorID return mySubmitter, nil } // UseCompression enables gzip compression of submitted payloads (not // applicable in this implementation). func (s *DummySubmitter) UseCompression() { // pass } // Submit logs the rawData payload. func (s *DummySubmitter) Submit(rawData []byte, key string, contentType string) { s.SubmitWithHeaders(rawData, key, contentType, nil) } // SubmitWithHeaders logs rawData payload, adding some extra key-value pairs to // the header. func (s *DummySubmitter) SubmitWithHeaders(rawData []byte, key string, contentType string, myHeaders map[string]string) { bytestring := string(rawData) if isASCIIPrintable(bytestring) { s.Logger.Info(bytestring) } else { s.Logger.Infof("%s (%s) - submitting non-printable byte array of length %d", key, contentType, len(rawData)) } } // Finish is a no-op in this implementation. func (s *DummySubmitter) Finish() { // pass } fever-1.3.7/util/submitter_test.go000066400000000000000000000074371477766221000172410ustar00rootroot00000000000000package util // DCSO FEVER // Copyright (c) 2017, DCSO GmbH import ( "bytes" "testing" "time" log "github.com/sirupsen/logrus" "github.com/NeowayLabs/wabbit" "github.com/NeowayLabs/wabbit/amqptest" "github.com/NeowayLabs/wabbit/amqptest/server" ) func TestSubmitter(t *testing.T) { serverURL := "amqp://sensor:sensor@localhost:9999/%2f/" log.SetLevel(log.DebugLevel) // start mock server fakeServer := server.NewServer(serverURL) fakeServer.Start() // set up consumer var buf bytes.Buffer allDone := make(chan bool) c, err := NewConsumer(serverURL, "foo.bar.test", "direct", "foo", "foo", "foo-test1", func(d wabbit.Delivery) { buf.Write(d.Body()) if buf.Len() == 4 { allDone <- true } }) if err != nil { t.Fatal(err) } // set up submitter submitter, err := MakeAMQPSubmitterWithReconnector(serverURL, "foo.bar.test", true, func(url string) (wabbit.Conn, error) { // we pass in a custom reconnector which uses the amqptest implementation var conn wabbit.Conn conn, err = amqptest.Dial(url) return conn, err }) if err != nil { t.Fatal(err) } // send some messages... submitter.Submit([]byte("1"), "foo", "text/plain") submitter.Submit([]byte("2"), "foo", "text/plain") submitter.Submit([]byte("3"), "foo", "text/plain") submitter.Submit([]byte("4"), "foo", "text/plain") // ... and wait until they are received and processed <-allDone // check if order and length is correct if buf.String() != "1234" { t.Fail() } // tear down test setup submitter.Finish() fakeServer.Stop() c.Shutdown() } func TestSubmitterReconnect(t *testing.T) { serverURL := "amqp://sensor:sensor@localhost:9992/%2f/" log.SetLevel(log.DebugLevel) // start mock server fakeServer := server.NewServer(serverURL) fakeServer.Start() // set up consumer var buf bytes.Buffer done := make(chan bool) c, err := NewConsumer(serverURL, "foo.bar.test", "direct", "foo", "foo", "foo-test1", func(d wabbit.Delivery) { buf.Write(d.Body()) log.Printf("received '%s', buf length %d", d.Body(), buf.Len()) if buf.Len() == 2 { done <- true } }) if err != nil { t.Fatal(err) } // set up submitter submitter, err := MakeAMQPSubmitterWithReconnector(serverURL, "foo.bar.test", true, func(url string) (wabbit.Conn, error) { // we pass in a custom reconnector which uses the amqptest implementation var conn wabbit.Conn conn, err = amqptest.Dial(url) return conn, err }) if err != nil { t.Fatal(err) } defer submitter.Finish() // send some messages... submitter.Submit([]byte("A"), "foo", "text/plain") submitter.Submit([]byte("B"), "foo", "text/plain") stopped := make(chan bool) restarted := make(chan bool) <-done go func() { fakeServer.Stop() close(stopped) time.Sleep(5 * time.Second) fakeServer := server.NewServer(serverURL) fakeServer.Start() close(restarted) }() <-stopped log.Info("server stopped") // these are buffered on client side because submitter will not publish // with immediate flag set submitter.Submit([]byte("C"), "foo", "text/plain") submitter.Submit([]byte("D"), "foo", "text/plain") <-restarted log.Info("server restarted") // reconnect consumer c.Shutdown() c2, err := NewConsumer(serverURL, "foo.bar.test", "direct", "foo", "foo", "foo-test1", func(d wabbit.Delivery) { buf.Write(d.Body()) log.Printf("received '%s', buf length %d", d.Body(), buf.Len()) if buf.Len() == 6 { done <- true } }) if err != nil { t.Fatal(err) } submitter.Submit([]byte("E"), "foo", "text/plain") submitter.Submit([]byte("F"), "foo", "text/plain") // ... and wait until they are received and processed <-done log.Debug("All done") // check if order and length is correct log.Info(buf.String()) if buf.String() != "ABCDEF" { t.Fail() } // tear down test setup c2.Shutdown() fakeServer.Stop() } fever-1.3.7/util/testdata/000077500000000000000000000000001477766221000154335ustar00rootroot00000000000000fever-1.3.7/util/testdata/jsonparse_eve.json000066400000000000000000000025051477766221000211730ustar00rootroot00000000000000{"timestamp":"2017-03-06T06:54:06.047429+0000","flow_id":4711,"in_iface":"enp2s0f1","event_type":"dns","vlan":61,"src_ip":"10.0.0.10","src_port":53,"dest_ip":"10.0.0.11","dest_port":51323,"proto":"UDP","dns":{"type":"answer","id":1,"rcode":"NOERROR","rrname":"test.test.local","rrtype":"A","ttl":2365,"rdata":"10.0.0.12"}} {"timestamp":"2017-03-06T06:54:10.839668+0000","flow_id":2323,"in_iface":"enp2s0f1","event_type":"fileinfo","vlan":91,"src_ip":"10.0.0.10","src_port":80,"dest_ip":"10.0.0.11","dest_port":52914,"proto":"TCP","http":{"hostname":"api.icndb.com","url":"\/jokes\/random?firstName=Chuck&lastName=Norris&limitTo=[nerdy]","http_user_agent":"Ruby","http_content_type":"application\/json","http_method":"GET","protocol":"HTTP\/1.1","status":200,"length":178},"app_proto":"http","fileinfo":{"filename":"\/jokes\/random","magic":"ASCII text, with no line terminators","state":"CLOSED","md5":"8d81d793b28b098e8623d47bae23cf44","stored":false,"size":176,"tx_id":0}} {"timestamp":"2017-03-06T06:54:14.002504+0000","flow_id":2134,"in_iface":"enp2s0f1","event_type":"http","vlan":72,"src_ip":"10.0.0.10","src_port":24092,"dest_ip":"10.0.0.11","dest_port":80,"proto":"TCP","tx_id":0,"http":{"hostname":"foobar","url":"\/scripts\/wpnbr.dll","http_content_type":"text\/xml","http_method":"POST","protocol":"HTTP\/1.1","status":200,"length":347}} fever-1.3.7/util/testdata/jsonparse_eve_broken1.json000066400000000000000000000013331477766221000226120ustar00rootroot00000000000000{"timestamp":"2017-03-06T06:54:06.047429+0000","flow_id":4711,"in_iface":"enp2s0f1","event_type":"dns","vlan":61,"src_ip":"10.0.0.10","src_port":53,"dest_ip":"10.0.0.11","dest_port":51323,"proto":"UDP","dns":{"type":"answer","id":1,"rcode":"NOERROR","rrname":"test.test.local","rrtype":"A","ttl":2365,"rdata":"10.0.0.12"}} {"timestamp":"2017-03-06T06:54:10 {"timestamp":"2017-03-06T06:54:14.002504+0000","flow_id":2134,"in_iface":"enp2s0f1","event_type":"http","vlan":72,"src_ip":"10.0.0.10","src_port":24092,"dest_ip":"10.0.0.11","dest_port":80,"proto":"TCP","tx_id":0,"http":{"hostname":"foobar","url":"\/scripts\/wpnbr.dll","http_content_type":"text\/xml","http_method":"POST","protocol":"HTTP\/1.1","status":200,"length":347}} fever-1.3.7/util/testdata/jsonparse_eve_empty.json000066400000000000000000000000001477766221000223750ustar00rootroot00000000000000fever-1.3.7/util/testdata/jsonparse_eve_nulls.json000066400000000000000000000004021477766221000224020ustar00rootroot00000000000000{"timestamp":"2017-03-06T06:54:10.839668+0000","flow_id":null,"in_iface":"enp2s0f1","event_type":"fileinfo","vlan":null,"src_ip":null,"src_port":null,"dest_ip":null,"dest_port":null,"http":{"hostname":"api.icndb.com","url":null,"state":"CLOSED","md5":null}} fever-1.3.7/util/util.go000066400000000000000000000235371477766221000151400ustar00rootroot00000000000000package util // DCSO FEVER // Copyright (c) 2017, 2023, DCSO GmbH import ( "bytes" "crypto/tls" "crypto/x509" "encoding/json" "fmt" "io/ioutil" "math/rand" "os" "strings" "github.com/DCSO/fever/types" "github.com/buger/jsonparser" ) // ToolName is a string containing the name of this software, lowercase. var ToolName = "fever" // ToolNameUpper is a string containing the name of this software, uppercase. var ToolNameUpper = "FEVER" var evekeys = [][]string{ []string{"event_type"}, // 0 []string{"src_ip"}, // 1 []string{"src_port"}, // 2 []string{"dest_ip"}, // 3 []string{"dest_port"}, // 4 []string{"timestamp"}, // 5 []string{"proto"}, // 6 []string{"flow", "bytes_toclient"}, // 7 []string{"flow", "bytes_toserver"}, // 8 []string{"http", "hostname"}, // 9 []string{"http", "url"}, // 10 []string{"http", "http_method"}, // 11 []string{"dns", "rrname"}, // 12 []string{"flow", "pkts_toclient"}, // 13 []string{"flow", "pkts_toserver"}, // 14 []string{"dns", "rcode"}, // 15 []string{"dns", "rdata"}, // 16 []string{"dns", "rrtype"}, // 17 []string{"dns", "type"}, // 18 []string{"tls", "sni"}, // 19 []string{"dns", "version"}, // 20 []string{"dns", "answers"}, // 21 []string{"flow_id"}, // 22 []string{"in_iface"}, // 23 []string{"app_proto"}, // 24 []string{"tls", "fingerprint"}, // 25 } // EscapeJSON escapes a string as a quoted byte slice for direct use in jsonparser.Set(). func EscapeJSON(i string) ([]byte, error) { b, err := json.Marshal(i) if err != nil { return []byte(""), err } return b, nil } // ParseJSON extracts relevant fields from an EVE JSON entry into an Entry struct. func ParseJSON(json []byte) (e types.Entry, parseerr error) { e = types.Entry{} jsonparser.EachKey(json, func(idx int, value []byte, vt jsonparser.ValueType, err error) { if parseerr != nil { return } if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } // skip null fields; these will not be handled by the low-level // jsonparser.Parse* () functions if bytes.Equal(value, []byte("null")) { return } switch idx { case 0: e.EventType, err = jsonparser.ParseString(value) if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } case 1: e.SrcIP = string(value[:]) case 2: e.SrcPort, err = jsonparser.ParseInt(value) if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } case 3: e.DestIP = string(value[:]) case 4: e.DestPort, err = jsonparser.ParseInt(value) if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } case 5: e.Timestamp = string(value[:]) case 6: e.Proto = string(value[:]) case 7: e.BytesToClient, err = jsonparser.ParseInt(value) if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } case 8: e.BytesToServer, err = jsonparser.ParseInt(value) if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } case 9: e.HTTPHost, err = jsonparser.ParseString(value) if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } case 10: e.HTTPUrl, err = jsonparser.ParseString(value) if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } case 11: e.HTTPMethod, err = jsonparser.ParseString(value) if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } case 12: e.DNSRRName, err = jsonparser.ParseString(value) if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } case 13: e.PktsToClient, err = jsonparser.ParseInt(value) if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } case 14: e.PktsToServer, err = jsonparser.ParseInt(value) if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } case 15: e.DNSRCode, err = jsonparser.ParseString(value) if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } case 16: e.DNSRData, err = jsonparser.ParseString(value) if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } case 17: e.DNSRRType, err = jsonparser.ParseString(value) if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } case 18: e.DNSType, err = jsonparser.ParseString(value) if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } case 19: e.TLSSNI, err = jsonparser.ParseString(value) if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } case 20: e.DNSVersion, err = jsonparser.ParseInt(value) if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } case 21: if e.DNSVersion == 2 { e.DNSAnswers = make([]types.DNSAnswer, 0) jsonparser.ArrayEach(value, func(mvalue []byte, dataType jsonparser.ValueType, offset int, err error) { var rrname, rdata, rrtype string var merr error if parseerr != nil { return } if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } if bytes.Equal(mvalue, []byte("null")) { return } rdata, merr = jsonparser.GetString(mvalue, "rdata") if merr != nil { if merr != jsonparser.KeyPathNotFoundError { // We do not want to report errors caused by the // parser not being able to parse "null" values. // In this case it would report the message // "Value is not a string: null". if !strings.Contains(merr.Error(), "null") { parseerr = merr return } } } rrname, merr = jsonparser.GetString(mvalue, "rrname") if merr != nil { if merr != jsonparser.KeyPathNotFoundError { // See above. if !strings.Contains(merr.Error(), "null") { parseerr = merr return } } } rrtype, merr = jsonparser.GetString(mvalue, "rrtype") if merr != nil { if merr != jsonparser.KeyPathNotFoundError { // See above. if !strings.Contains(merr.Error(), "null") { parseerr = merr return } } } dnsa := types.DNSAnswer{ DNSRCode: e.DNSRCode, DNSRData: rdata, DNSRRName: rrname, DNSRRType: rrtype, } e.DNSAnswers = append(e.DNSAnswers, dnsa) }) } if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } case 22: e.FlowID, err = jsonparser.ParseString(value) if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } case 23: e.Iface, err = jsonparser.ParseString(value) if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } case 24: e.AppProto, err = jsonparser.ParseString(value) if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } case 25: e.TLSFingerprint, err = jsonparser.ParseString(value) if err != nil { parseerr = fmt.Errorf("%d: %w", idx, err) return } } }, evekeys...) e.JSONLine = string(json) return e, parseerr } // GetSensorID returns the machine ID of the system it is being run on, or // the string """ if the ID cannot be determined. func GetSensorID() (string, error) { if _, err := os.Stat("/etc/machine-id"); os.IsNotExist(err) { return "", nil } b, err := ioutil.ReadFile("/etc/machine-id") if err != nil { return "", nil } return strings.TrimSpace(string(b)), nil } // RndStringFromRunes returns a string of length n // with randomly picked runes from fromRunes slice func RndStringFromRunes(fromRunes []rune, n int) string { result := make([]rune, n) numRunes := len(fromRunes) for i := range result { result[i] = fromRunes[rand.Intn(numRunes)] } return string(result) } // RndStringFromBytes returns a string of length n // with randomly picked bytes from fromBytes slice func RndStringFromBytes(fromBytes []byte, n int) string { result := make([]byte, n) numBytes := len(fromBytes) for i := range result { result[i] = fromBytes[rand.Intn(numBytes)] } return string(result) } // RndStringFromAlpha returns a string of length n // with randomly picked alphabetic characters func RndStringFromAlpha(n int) string { return RndStringFromBytes([]byte("abcdefghijklmnopqrstuvwxyz"), n) } // RndHexString returns a Hex string of length n func RndHexString(n int) string { return RndStringFromBytes([]byte("0123456789abcdef"), n) } // RndTLSFingerprint returns a random string in // the form of a TLS fingerprint func RndTLSFingerprint() string { nums := make([]string, 20) for i := 0; i < 20; i++ { nums[i] = RndHexString(2) } return strings.Join(nums, ":") } // MakeTLSConfig returns a TLS configuration suitable for an endpoint with private // key stored in keyFile and corresponding certificate stored in certFile. rcas // defines a list of root CA filenames. // If certFile and keyFile are empty, e.g., when configuring a tls-client // endpoint w/o mutual authentication, only the RootCA pool is populated. // Note: It appears as if ICAs have to be loaded via a chained server // certificate file as the RootCAs pool in tls.Config appears to be referred to // for RCAs only. func MakeTLSConfig(certFile, keyFile string, rcas []string, skipVerify bool) (*tls.Config, error) { certs := make([]tls.Certificate, 0, 1) if certFile != "" && keyFile != "" { c, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { return nil, err } certs = append(certs, c) } rcaPool := x509.NewCertPool() for _, filename := range rcas { rca, err := ioutil.ReadFile(filename) if err != nil { return nil, err } rcaPool.AppendCertsFromPEM(rca) } return &tls.Config{ Certificates: certs, RootCAs: rcaPool, InsecureSkipVerify: skipVerify, }, nil } fever-1.3.7/util/util_test.go000066400000000000000000000117651477766221000161770ustar00rootroot00000000000000package util // DCSO FEVER // Copyright (c) 2017, DCSO GmbH import ( "bufio" "os" "reflect" "testing" "github.com/DCSO/fever/types" ) var nullEntry = types.Entry{ Timestamp: "2017-03-06T06:54:10.839668+0000", EventType: "fileinfo", JSONLine: `{"timestamp":"2017-03-06T06:54:10.839668+0000","flow_id":null,"in_iface":"enp2s0f1","event_type":"fileinfo","vlan":null,"src_ip":null,"src_port":null,"dest_ip":null,"dest_port":null,"http":{"hostname":"api.icndb.com","url":null,"state":"CLOSED","md5":null}}`, Iface: "enp2s0f1", HTTPHost: "api.icndb.com", } var entries = []types.Entry{ types.Entry{ SrcIP: "10.0.0.10", SrcPort: 53, DestIP: "10.0.0.11", DestPort: 51323, Timestamp: "2017-03-06T06:54:06.047429+0000", EventType: "dns", Proto: "UDP", JSONLine: `{"timestamp":"2017-03-06T06:54:06.047429+0000","flow_id":4711,"in_iface":"enp2s0f1","event_type":"dns","vlan":61,"src_ip":"10.0.0.10","src_port":53,"dest_ip":"10.0.0.11","dest_port":51323,"proto":"UDP","dns":{"type":"answer","id":1,"rcode":"NOERROR","rrname":"test.test.local","rrtype":"A","ttl":2365,"rdata":"10.0.0.12"}}`, DNSRRName: "test.test.local", DNSRRType: "A", DNSRCode: "NOERROR", DNSRData: "10.0.0.12", DNSType: "answer", Iface: "enp2s0f1", FlowID: "4711", }, types.Entry{ SrcIP: "10.0.0.10", SrcPort: 80, DestIP: "10.0.0.11", DestPort: 52914, Timestamp: "2017-03-06T06:54:10.839668+0000", EventType: "fileinfo", Proto: "TCP", JSONLine: `{"timestamp":"2017-03-06T06:54:10.839668+0000","flow_id":2323,"in_iface":"enp2s0f1","event_type":"fileinfo","vlan":91,"src_ip":"10.0.0.10","src_port":80,"dest_ip":"10.0.0.11","dest_port":52914,"proto":"TCP","http":{"hostname":"api.icndb.com","url":"\/jokes\/random?firstName=Chuck&lastName=Norris&limitTo=[nerdy]","http_user_agent":"Ruby","http_content_type":"application\/json","http_method":"GET","protocol":"HTTP\/1.1","status":200,"length":178},"app_proto":"http","fileinfo":{"filename":"\/jokes\/random","magic":"ASCII text, with no line terminators","state":"CLOSED","md5":"8d81d793b28b098e8623d47bae23cf44","stored":false,"size":176,"tx_id":0}}`, HTTPHost: "api.icndb.com", HTTPUrl: `/jokes/random?firstName=Chuck&lastName=Norris&limitTo=[nerdy]`, HTTPMethod: `GET`, Iface: "enp2s0f1", AppProto: "http", FlowID: "2323", }, types.Entry{ SrcIP: "10.0.0.10", SrcPort: 24092, DestIP: "10.0.0.11", DestPort: 80, Timestamp: "2017-03-06T06:54:14.002504+0000", EventType: "http", Proto: "TCP", JSONLine: `{"timestamp":"2017-03-06T06:54:14.002504+0000","flow_id":2134,"in_iface":"enp2s0f1","event_type":"http","vlan":72,"src_ip":"10.0.0.10","src_port":24092,"dest_ip":"10.0.0.11","dest_port":80,"proto":"TCP","tx_id":0,"http":{"hostname":"foobar","url":"\/scripts\/wpnbr.dll","http_content_type":"text\/xml","http_method":"POST","protocol":"HTTP\/1.1","status":200,"length":347}}`, HTTPHost: "foobar", HTTPUrl: `/scripts/wpnbr.dll`, HTTPMethod: `POST`, Iface: "enp2s0f1", FlowID: "2134", }, } func TestJSONParseEVE(t *testing.T) { f, err := os.Open("testdata/jsonparse_eve.json") if err != nil { t.Fatalf(err.Error()) } scanner := bufio.NewScanner(f) i := 0 for scanner.Scan() { json := scanner.Bytes() e, err := ParseJSON(json) if err != nil { t.Fatalf(err.Error()) } if !reflect.DeepEqual(entries[i], e) { t.Fatalf("entry %d parsed from JSON does not match expected value", i) } i++ } } func TestJSONParseEVEBroken(t *testing.T) { f, err := os.Open("testdata/jsonparse_eve_broken1.json") if err != nil { t.Fatalf(err.Error()) } scanner := bufio.NewScanner(f) i := 0 for scanner.Scan() { json := scanner.Bytes() e, err := ParseJSON(json) if i != 1 { if err != nil { t.Fatalf(err.Error()) } } if i == 1 { if err == nil { t.Fatalf("broken JSON line should raise an error") } } if i != 1 { if !reflect.DeepEqual(entries[i], e) { t.Fatalf("entry %d parsed from JSON does not match expected value", i) } } i++ } } func TestJSONParseEVEempty(t *testing.T) { f, err := os.Open("testdata/jsonparse_eve_empty.json") if err != nil { t.Fatalf(err.Error()) } scanner := bufio.NewScanner(f) i := 0 for scanner.Scan() { i++ } if i > 0 { t.Fatal("empty file should not generate any entries") } } func TestJSONParseEVEwithnull(t *testing.T) { f, err := os.Open("testdata/jsonparse_eve_nulls.json") if err != nil { t.Fatalf(err.Error()) } scanner := bufio.NewScanner(f) i := 0 var entry types.Entry for scanner.Scan() { json := scanner.Bytes() e, err := ParseJSON(json) if err != nil { t.Fatalf(err.Error()) } entry = e i++ } if i != 1 { t.Fatalf("should parse only one entry, got %d", i) } if !reflect.DeepEqual(nullEntry, entry) { t.Fatalf("entry %d parsed from JSON does not match expected value", i) } } func TestGetSensorID(t *testing.T) { sid, err := GetSensorID() if err != nil { t.Fatalf(err.Error()) } if len(sid) == 0 { t.Fatal("missing sensor ID") } }