pax_global_header00006660000000000000000000000064147730567700014532gustar00rootroot0000000000000052 comment=585438873defcf06733ffceac94c4d696685056e sigsum-log-go-0.15.2/000077500000000000000000000000001477305677000143105ustar00rootroot00000000000000sigsum-log-go-0.15.2/.gitignore000066400000000000000000000004321477305677000162770ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Vim *.swp # Dependency directories (remove the comment below to include it) # vendor/ sigsum-log-go-0.15.2/.gitlab-ci.yml000066400000000000000000000026151477305677000167500ustar00rootroot00000000000000image: golang:1.22 stages: - build - test - integration # go-build builds all packages. go-build: stage: build script: go build ./... # Succeeds if no changes are suggested by gofmt -d . gofmt: stage: test script: if gofmt -d . | grep . ; then false ; else true ; fi # go-test runs tests with the data-race detector enabled. go-test: stage: test script: go test -v -race ./... # There are three flavors of the integration tests, "default" with # primary and secondary, "extended" where secondary is promoted to new # primary, and "ephemeral" which runs a simpler backend without mysql # and trillian. integration-ephemeral: stage: integration needs: ["go-test"] script: ./integration/test.sh --ephemeral artifacts: paths: - ./integration/tmp/ integration-default: stage: integration needs: ["go-test"] services: - alias: mysql name: git.glasklar.is:5050/sigsum/admin/ci-container-images/trilliandb:latest variables: MYSQL_URI: test:test@tcp(mysql:3306)/test script: ./integration/test.sh artifacts: paths: - ./integration/tmp/ integration-extended: stage: integration needs: ["go-test"] services: - alias: mysql name: git.glasklar.is:5050/sigsum/admin/ci-container-images/trilliandb:latest variables: MYSQL_URI: test:test@tcp(mysql:3306)/test script: ./integration/test.sh --extended artifacts: paths: - ./integration/tmp/ sigsum-log-go-0.15.2/AUTHORS000066400000000000000000000021211477305677000153540ustar00rootroot00000000000000Authors of the Sigsum project The copyright on the Sigsum log server implementation is held by the respective authors (or their organizations/employers). Unless file-specific copyright headers say otherwise, Sigsum is permissively licensed according to the BSD 2-Clause License (see the LICENSE file). This file contains only a summary; for more fine-grained information on who authored a particular file or feature, please refer to the version control history at: https://git.glasklar.is/sigsum/core/log-go For contributions where copyrights are held by an organization, e.g., the author's employer, the copyright holder should be identified by the commit, preferably by using an author email address belonging to the organization, or otherwise explained in the commit message. File-specific copyright headers should be used when necessary to document the origin of a file's contents, e.g., for code copied from other sources, or governed by different license requirements. Authors, in chronological order of initial contribution: Rasmus Dahlberg Linus Nordberg Grégoire Détrez Niels Möller sigsum-log-go-0.15.2/LICENSE000066400000000000000000000024701477305677000153200ustar00rootroot00000000000000BSD 2-Clause License Copyright (c) 2021, The Sigsum Project Authors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. 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. sigsum-log-go-0.15.2/MAINTAINERS000066400000000000000000000012311477305677000160020ustar00rootroot00000000000000The current maintainers of log-go are: +-----------------+--------------------+--------+-------------------------------------------------+ | Name | Affiliation | GitLab | IRC/Matrix | Email | +-----------------+--------------------+--------+------------+------------------------------------+ | Niels Möller | Glasklar Teknik AB | nisse | nielsm | nisse (at) glasklarteknik (dot) se | | Rasmus Dahlberg | Glasklar Teknik AB | rgdd | rgdd | rgdd (at) glasklarteknik (dot) se | +-----------------+--------------------+--------+------------+------------------------------------+ sigsum-log-go-0.15.2/NEWS000066400000000000000000000170451477305677000150160ustar00rootroot00000000000000NEWS for log-go v0.15.2 This is minor release fixing a few issues found since the previous release. Upgrading from the previous release is expected to work with no issues. This release has been tested to work together with: * Sigsum tools (most importantly sigsum-submit and sigsum-verify, as well as sigsum-witness) https://git.glasklar.is/sigsum/core/sigsum-go, tag v0.11.2. Improvements: * Fixed a bug in the rate-limit logic. The intended behavior is to only count the first add-leaf requests for a particular leaf, and not count retries for the same leaf. In v0.15.0, retries could exhaust the rate limit quota. * More relevant logging of witness errors. When a witness starts failing, and when it recovers, the error is logged at INFO level. Repeated errors are logged at DEBUG level. In v0.15.0, all errors were logged at DEBUG level, i.e., no errors logged by default. Incompatible changes: * The --max-range command-line option is now only available for the primary node. Before this change, there was a --max-range command-line option also for the secondary node but that option had no effect. Miscellaneous: * Increased max-range default value to 512. * Updated config.toml.example file and added a corresponding test to help ensure the example config stays up-to-date. NEWS for log-go v0.15.0 This release of the Sigsum log server uses a new interoperable protocol [1] for interacting with the log's witnesses. This change affects operators of logs and witnesses. The log protocol itself [2], i.e., the protocol used for querying a Sigsum log and submitting new entries, is stable and unchanged. This release has been tested to work together with: * Sigsum tools (most importantly sigsum-submit and sigsum-verify, as well as sigsum-witness) https://git.glasklar.is/sigsum/core/sigsum-go, tag v0.9.1. New features: * Implemented the new witness protocol [1], agreed together with other parties interested in witness cosigning of transparency logs. By using this common protocol, the log-go server interoperates with witnesses intended to support a diverse set of transparency log designs. * Added a --version option, where the value is populated automatically at build time based on go module version or git revision. The old way of setting the gitCommit variable using linker flags has no effect. * The log server now responds to GET requests at / or //, providing a basic HTML page with information about the log server, in particular, the software version and the hash of the log's public key. Incompatible changes: * Support for the previous, interrim, witness protocol has been removed. Witnesses that interoperated with log-go v0.14.1 will need updating to support the new protocol. * The minimum go version required for building is now go-1.22. Notes on upgrading: * An upgraded log will not be able to obtain and publish any witness cosignatures until its witnesses have been updated to support the new protocol. There are no other expected complications on upgrade. * When upgrading, you may want to also upgrade the Trillian daemons used for the log's backend storage. Upgrading Trillian from v1.5.1 to v1.6.0 (the latest release that still supports go-1.22) has been tested, with no issues. [1] https://github.com/C2SP/C2SP/blob/tlog-checkpoint/v1.0.0-rc.1/tlog-witness.md. [2] https://git.glasklar.is/sigsum/project/documentation/-/blob/log.md-release-v1.0.0/log.md NEWS for log-go v0.14.1 This release updates the log server to implement the v1 sigsum protocols [1]. The log protocol, i.e., the protocol for querying a log and submitting new entries, is now considered stable: there are no plans for incompatible changes, and future updates will consider transitions carefully. However, the witness protocol, used by logs to interact with witnesses, is under development, and will likely be replaced in later versions. Upgrading existing log services from the previous release, v0.9.0, is automatic, see below for the implications of upgrading. However, downgrading is *NOT* tested nor supported. Downgrading would require manual replacement of the signed-tree-head file by restoring it from a backup or recreating it using sigsum-mktree. This release has been tested to work together with: * Command line tools (most importantly sigsum-submit and sigsum-verify) https://git.glasklar.is/sigsum/core/sigsum-go, tag v0.6.1. * The prototype witness, https://git.glasklar.is/sigsum/core/sigsum-py, tag v0.1.1. * The litetlog witness, https://github.com/FiloSottile/litetlog, tag v0.1.1. New features: * Implemented a new interim witness protocol [2], where a log queries witnesses for cosignatures. Witnesses to use are configured using a sigsum policy file. Further changes to the log <-> witness protocol are planned. Incompatible changes: * Tree head serialization used for signatures and cosignatures changed to use a checkpoint-compatible [3] text serialization. * Leaf signatures as well as submit token signatures changed to no longer use SSH signature format. Notes on upgrading: * Existing logs can be upgraded. When loading a log server's signed-tree-head file, the server accepts either a sigsum v0 tree head signature, as created by log-go v0.9.0, or a v1 signature, as created by the current version. * An upgraded log will publish tree heads signed according to the v1 protocol, and accept new leaves signed according to the v1 protocol. * Old leaves submitted according to the v0 protocol obviously stay in the tree, but they will appear invalid to anyone who has the submitter's public key and attempts to verify the leaf signature according to the v1 specification. * Old sigsum proofs can still be verified using old tools (since verification is purely offline, the log server is not involved at all). However, attempting to create a new proof for an old leaf will fail. Miscellaneous: * An intermediate version, tagged v0.13.0, included an explicit "v1" signature version on the cosignature lines. This protocol change was reverted for v0.14.x. Version v0.13.0 was never announced or properly released, but in case anyone is nevertheless running v0.13.0, upgrading to v0.14.1 is expected to work. The only user-visible change is the removal of cosignature version, which means that any client software and witnesses written to interop with log servers running v0.13.0 will need upgrading as well. [1] https://git.glasklar.is/sigsum/project/documentation/-/blob/main/log.md [2] https://git.glasklar.is/sigsum/project/documentation/-/blob/4ee138f1294f9c17d233f9d61f0fecd465a8d24b/witness.md [3] https://github.com/transparency-dev/formats/blob/main/log/README.md#checkpoint-format NEWS for log-go v0.9.0: First advertised release, implementing the v0 sigsum protocol. Recent user-visible changes: * Support for the initial sigsum cosignature mechanism, with witnesses polling the log, has been removed. A new mechanism, based on the log querying witnesses, will be added in a later release. * The Trillian tree id to use is now read from a file, with contents of the form "tree-id=...". The name of the file is specified using the configuration option trillian-tree-id-file. The old option tree-id, for setting the numerical id directly, is deleted. * Command line options now follow GNU style, with double dash for long options, and naming of command line flags and corresponding configuration file options have been overhauled. sigsum-log-go-0.15.2/README.md000066400000000000000000000122001477305677000155620ustar00rootroot00000000000000# Sigsum log-go This repository provides a log server that implements the [sigsum protocol][] and the [tlog-witness protocol][]. Database replication between a primary node and a secondary node is included. [Trillian][] and [MariaDB][] are used for backing the storage on each node. [sigsum protocol]: https://git.glasklar.is/sigsum/project/documentation/-/blob/log.md-release-v1.0.0/log.md [tlog-witness protocol]: https://github.com/C2SP/C2SP/blob/tlog-checkpoint/v1.0.0-rc.1/tlog-witness.md. [Trillian]: https://transparency.dev/#trillian [MariaDB]: https://mariadb.org/ ## Documentation See the [docs](./doc/readme.md) directory for information on how to setup and configure a Sigsum log instance. An [ansible collection][] is also available for reference. The [RELEASES](./RELEASES.md) file describes how the log-go software is released. Releases are announced on the [sigsum-announce][] list. For documentation on Sigsum in general, refer to the [project website](https://www.sigsum.org/docs/). [ansible collection]: https://git.glasklar.is/sigsum/admin/ansible [sigsum-announce]: https://lists.sigsum.org/mailman3/postorius/lists/sigsum-announce.lists.sigsum.org/ ## Development ### Contributing You are encouraged to file issues and open merge requests. Sign up on our GitLab instance or login using a supported identity provider like GitHub. Note that it is possible to contribute without using GitLab. For example, submit patches and interact with us on the [sigsum-general][] list. You can also file issues directly to our [issue tracker][] by sending an email to: sigsum-core-log-go-issues@incoming.glasklar.is If you are a first-time contributor providing a merge request, please review the log-go [LICENSE](./LICENSE) and copyright in the [AUTHORS](./AUTHORS) file. Append your name to the list of authors at the bottom in a separate commit. [sigsum-general]: https://lists.sigsum.org/mailman3/postorius/lists/sigsum-general.lists.sigsum.org/ [issue tracker]: https://git.glasklar.is/sigsum/core/log-go/-/issues ### Testing Check that the log-go software builds and all unit tests pass: $ go build ./... $ go test -v -race ./... As a rule of thumb, new merge requests should be accompanied by unit tests. Check that the log-go integration tests pass, see [separate quick start instructions](./integration/README.md). Use the `--extended` option for a slower test that includes an automated failover from a primary to a secondary. Note that unit tests as well as integration tests run automatically in our CI pipelines. Please ensure that all pipelines pass before requesting review. ### Commit messages At this time it is a non-goal to have a clean git-commit history. Use your best judgment to write good commit messages. We are not picky about the formatting. ### Public test instance There is a public test instance available that deploys the main branch every 10 minutes. Feel free to use it as you see fit when developing Sigsum use-cases. - Log URL: https://poc.sigsum.org/jellyfish - Public key: `154f49976b59ff09a123675f58cb3e346e0455753c3c3b15d465dcb4f6512b0b` Please note that there are no promises of uptime or stability for this public test instance. For information on test witnesses and more stable public instances, refer to the [project website](https://www.sigsum.org/services/). ### Available tooling Tooling and libraries to use Sigsum is available in the [sigsum-go][] repository. If you are just looking to poke at a log server manually, you may do so by exchanging ASCII key-value pairs as described in the [sigsum protocols][]. For example, try fetching a signed (and possibly cosigned) tree head and a few entries from the public test instance: ``` $ curl https://poc.sigsum.org/jellyfish/get-tree-head size=1291 root_hash=2b06e738e93ad2e8b9e1c8ae86b762cb20f16b0bda4ce3e40680d412b9cae5ea signature=5e91a847fb088341b26dc217c46878cd0bd1b9b576ce7d9d5f0fa781b1b139488bebc1883748c2c731aab546ee37ffcfa5823a37e55a8b5e501390235fcab00f cosignature=70b861a010f25030de6ff6a5267e0b951e70c04b20ba4a3ce41e7fba7b9b7dfc 1697784602 cb584f52b8c65e3eab6a7b0f17eca82ab8cfb766a46f7f26b76c2dc7a0a750ee8bd971dad7101cdebfdd786affd82582b4c42d41ff185d01f2cf756fbce0ef07 cosignature=1c997261f16e6e81d13f420900a2542a4b6a049c2d996324ee5d82a90ca3360c 1697784602 23b808cec68b1d6a9860bf1a4dc8de41d32d8105886e8f45170a49acaa046effd630cccf61235aa12b889bf3ed04002069d10bdf1041f99a6a7e09b42785b50c $ $ curl https://poc.sigsum.org/jellyfish/get-leaves/10/12 leaf=3c14dfb28e7ed39442fe6376feb9f98b5f97a41ccf024dd2c6f38640c699a66c a8140f22aabfd2684635a8c266b557f3a06f958b8cd051605ed17648fb4949ff0922c0a73045c90e4baddf7033ba2a34b5841221ac7067918aada94553f0f104 4f313845ab7b7bc4592e437869e838fcccef45b402bd970f8aa2628ec17ef5cf leaf=1507de45cbe91d7192063c7c143b9d07aa52ac4a47c278d728dd9c9e86c834f3 315ee5e2eb5a7d3a573760e612f76337a1a445fa1c117cf9bf94d0dc327d15f0feee0f67ba679d74cb08cb4748793aa09576f5496abf831a4c1105925c635404 4f313845ab7b7bc4592e437869e838fcccef45b402bd970f8aa2628ec17ef5cf ``` [sigsum-go]: https://git.glasklar.is/sigsum/core/sigsum-go/ ## Contact - IRC room `#sigsum` @ OFTC.net - Matrix room `#sigsum` which is bridged with IRC - The [sigsum-general][] mailing list sigsum-log-go-0.15.2/RELEASES.md000066400000000000000000000073121477305677000160400ustar00rootroot00000000000000# Log server releases ## What's being released? The following programs are released and supported: - `cmd/sigsum-log-primary` - `cmd/sigsum-log-secondary` - `cmd/sigsum-mktree` Releases are announced on the [sigsum-announce][] mailing list. The [NEWS file](./NEWS) documents, for each release, the user visible changes, the recommended upgrade procedure, and other Sigsum components that have been interop-tested with the log server release. Note that a release is simply a git-tag specified on our mailing list. You are expected to build the released tools yourself, e.g., with `go install`. There may be intermediate git-tags between two advertised releases; those are *not* released and supported. The log-go go module is *not* considered released (as signalled by the v0 version tag), even though we release the above programs with the same tag. By the terms of the LICENSE file you are free to use this code "as is" in almost any way you like, but for now, we support its use *only* via the above programs, and we don't aim to provide any backwards compatibility for internal interfaces. We encourage deployment of our released Sigsum log servers. For applications to be able to *depend* on Sigsum logging, they also need trusted log witnesses and log monitoring. Availability and quality of these components is out of scope for the log server release process. [sigsum-announce]: https://lists.glasklarteknik.se/mailman3/postorius/lists/sigsum-announce.lists.sigsum.org/ ## Upgrading You are expected to upgrade linearly from one advertised release to the next advertised release, e.g., from v0.9.0 to v0.14.1, unless specified otherwise. We strive to make upgrading easy. Any complications, e.g., any manual steps required for migration of stored state or configuration, is documented in the [NEWS file](./NEWS). Downgrading is in general not supported. Primary and secondary nodes of a log instance should be upgraded in tandem: running nodes on different software releases is not tested. ## Expected changes in upcoming releases 1. There are no planned changes to the wire protocol between log clients and log servers. The [sigsum v1 protocol][] is used. This also fully specifies the cryptographic details, such as precisely which bytes are being signed, and intended meaning, for each type of signature. Any breaking changes would have to be considered *very carefully* and be *coordinated well in advance*. 2. There are no planned changes to the wire protocol between log servers and witnesses. The [tlog-witness protocol][] is used. 3. Changes are likely to other operational aspects of the log server, e.g., configuration interfaces, available metrics, and storage of the log server's state. Such changes, as well as the migration procedure, will be documented in the [NEWS file](./NEWS). [sigsum v1 protocol]: https://git.glasklar.is/sigsum/project/documentation/-/blob/log.md-release-v1.0.0/log.md [tlog-witness prococol]: https://github.com/C2SP/C2SP/blob/tlog-checkpoint/v1.0.0-rc.1/tlog-witness.md. ## Release cycle We make feature releases when something new is ready. We expect one or several months between feature releases. In case critical bugs are discovered, we intend to provide bugfix-only updates for the latest release in a timely manner. Backporting bugfixes to older releases than the latest one will be considered on a case-by-case basis, with priority to the case that the latest feature release is particularly recent or upgrading to it is particularly disruptive. ## Future improvements Some desired incremental improvements of the release processes: - Document and automate more of the release testing. - Define a process for signing releases, e.g., a signed git tag. sigsum-log-go-0.15.2/cmd/000077500000000000000000000000001477305677000150535ustar00rootroot00000000000000sigsum-log-go-0.15.2/cmd/sigsum-log-primary/000077500000000000000000000000001477305677000206225ustar00rootroot00000000000000sigsum-log-go-0.15.2/cmd/sigsum-log-primary/main.go000066400000000000000000000211271477305677000221000ustar00rootroot00000000000000// Package main provides a sigsum-log-primary binary package main import ( "context" "encoding/hex" "fmt" "html" "net/http" "os" "os/signal" "sync" "syscall" "time" "github.com/pborman/getopt/v2" "github.com/prometheus/client_golang/prometheus/promhttp" "sigsum.org/log-go/internal/config" "sigsum.org/log-go/internal/db" "sigsum.org/log-go/internal/metrics" "sigsum.org/log-go/internal/node/primary" rateLimit "sigsum.org/log-go/internal/rate-limit" "sigsum.org/log-go/internal/state" "sigsum.org/log-go/internal/version" "sigsum.org/sigsum-go/pkg/api" "sigsum.org/sigsum-go/pkg/client" "sigsum.org/sigsum-go/pkg/crypto" "sigsum.org/sigsum-go/pkg/key" "sigsum.org/sigsum-go/pkg/log" "sigsum.org/sigsum-go/pkg/policy" "sigsum.org/sigsum-go/pkg/server" token "sigsum.org/sigsum-go/pkg/submit-token" ) func ParseFlags(c *config.Config) { help := false versionFlag := false getopt.SetParameters("") getopt.FlagLong(&c.Primary.PolicyFile, "policy-file", 0, "Policy, if provided, defines the witnesses to query.") getopt.FlagLong(&c.Primary.RateLimitFile, "rate-limit-file", 0, "Enable rate limiting, based on given config file.", "file") getopt.FlagLong(&c.Primary.AllowTestDomain, "allow-test-domain", 0, "Allow submit tokens from test.sigsum.org.") getopt.FlagLong(&c.Primary.SecondaryURL, "secondary-url", 0, "Secondary node endpoint for fetching latest replicated tree head.", "url") getopt.FlagLong(&c.Primary.SecondaryPubkeyFile, "secondary-pubkey-file", 0, "Public key for secondary node.", "file") getopt.FlagLong(&c.Primary.SthFile, "sth-file", 0, "File where latest published STH is being stored.", "file") getopt.FlagLong(&c.Primary.MaxRange, "max-range", 0, "Maximum number of leaves that can be retrived in a single request.") getopt.FlagLong(&help, "help", '?', "Display help.") getopt.FlagLong(&versionFlag, "version", 0, "Display server version.") getopt.Parse() if help { getopt.PrintUsage(os.Stdout) os.Exit(0) } if versionFlag { fmt.Printf("log-go version: %s\n", version.ModuleVersion()) os.Exit(0) } } func main() { var conf *config.Config // Read default values from the Config struct confFile, err := config.OpenConfigFile() if err != nil { log.Info("didn't find configuration file, using defaults: %v", err) conf = config.NewConfig() } else { conf, err = config.LoadConfig(confFile) if err != nil { log.Fatal("failed to parse config file: %v", err) } } // Allow flags to override them conf.ServerFlags(getopt.CommandLine) ParseFlags(conf) if len(conf.LogFile) > 0 { if err := log.SetLogFile(conf.LogFile); err != nil { log.Fatal("open log file failed: %v", err) } } if err := log.SetLevelFromString(conf.LogLevel); err != nil { log.Fatal("setup logging: %v", err) } moduleVersion := version.ModuleVersion() log.Info("log-go version: %s", moduleVersion) witnesses, err := configuredWitnesses(conf.PolicyFile) if err != nil { log.Fatal("Failed witness configuration: %v", err) } log.Debug("configuring log-go-primary") node, publicKey, err := setupPrimaryFromFlags(conf) if err != nil { log.Fatal("setup primary: %v", err) } // wait for clean-up before exit var wg sync.WaitGroup defer wg.Wait() // Makes signal cancel state manager, and trigger server shutdown below. ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer cancel() log.Debug("starting primary state manager routine") wg.Add(1) go func() { defer wg.Done() node.Stateman.Run(ctx, witnesses, conf.Interval) log.Debug("state manager shutdown") cancel() // must have state manager running }() externalMux := http.NewServeMux() // Register HTTP endpoints. log.Debug("adding external handler under prefix: %s", conf.Prefix) var pattern string if conf.Prefix == "" { pattern = "/" } else { pattern = "/" + conf.Prefix + "/" } externalMux.Handle(pattern, server.NewLog(&server.Config{ Prefix: conf.Prefix, Timeout: conf.Timeout, Metrics: metrics.NewServerMetrics(hex.EncodeToString(publicKey[:])), }, node)) infoPage := []byte(fmt.Sprintf(` Sigsum log server

This is a Sigsum log server

`[1:], crypto.HashBytes(publicKey[:]), html.EscapeString(conf.Prefix), html.EscapeString(moduleVersion))) externalMux.HandleFunc("GET "+pattern+"{$}", func(w http.ResponseWriter, _ *http.Request) { w.Header().Add("content-type", "text/html") w.Write(infoPage) }) if conf.Prefix != "" { externalMux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, conf.Prefix+"/", http.StatusMovedPermanently) }) } extserver := &http.Server{Addr: conf.ExternalEndpoint, Handler: externalMux} internalMux := http.NewServeMux() log.Debug("adding internal handler under prefix: %s", conf.Prefix) internalMux.Handle("/", server.NewGetLeavesServer(&server.Config{ Prefix: conf.Prefix, Timeout: conf.Timeout, // No metrics. If we used the same logging id, we'd // get a mix of get-leaves metrics for internal and // external endpoint. }, node.GetLeavesInternal)) log.Debug("adding prometheus handler to internal mux, on path: /metrics") internalMux.Handle("/metrics", promhttp.Handler()) intserver := &http.Server{Addr: conf.InternalEndpoint, Handler: internalMux} wg.Add(1) go func() { defer wg.Done() log.Info("serving log nodes on %v/%v", conf.InternalEndpoint, conf.Prefix) if err = intserver.ListenAndServe(); err != http.ErrServerClosed { log.Error("serve(intserver): %v", err) } log.Debug("internal endpoints server shut down") cancel() }() wg.Add(1) go func() { defer wg.Done() log.Info("serving clients on %v/%v", conf.ExternalEndpoint, conf.Prefix) if err = extserver.ListenAndServe(); err != http.ErrServerClosed { log.Error("serve(server): %v", err) } log.Debug("public endpoints server shut down") cancel() }() <-ctx.Done() log.Debug("received shutdown signal") shutdownCtx, _ := context.WithTimeout(context.Background(), time.Second*60) log.Info("stopping http server, please wait...") extserver.Shutdown(shutdownCtx) log.Info("... done") log.Info("stopping internal api server, please wait...") intserver.Shutdown(shutdownCtx) log.Info("... done") } // setupPrimaryFromFlags() sets up a new sigsum primary node from flags. func setupPrimaryFromFlags(conf *config.Config) (*primary.Primary, crypto.PublicKey, error) { var p primary.Primary // Setup logging configuration. signer, err := key.ReadPrivateKeyFile(conf.KeyFile) if err != nil { return nil, crypto.PublicKey{}, fmt.Errorf("failed reading private key: %v", err) } publicKey := signer.Public() p.MaxRange = conf.MaxRange switch conf.Backend { default: return nil, crypto.PublicKey{}, fmt.Errorf("unknown backend %q, must be \"trillian\" (default) or \"ephemeral\"", conf.Backend) case "ephemeral": p.DbClient = db.NewMemoryDb() case "trillian": trillianClient, err := db.DialTrillian(conf.TrillianRpcServer, conf.Timeout, db.PrimaryTree, conf.TrillianTreeIDFile) if err != nil { return nil, crypto.PublicKey{}, err } p.DbClient = trillianClient } // Setup secondary node configuration. var secondary api.Secondary var secondaryPub crypto.PublicKey if conf.Primary.SecondaryURL != "" && conf.Primary.SecondaryPubkeyFile != "" { var err error secondaryPub, err = key.ReadPublicKeyFile(conf.Primary.SecondaryPubkeyFile) if err != nil { return nil, crypto.PublicKey{}, fmt.Errorf("failed to read secondary node pubkey: %v", err) } secondary = client.New(client.Config{URL: conf.Primary.SecondaryURL}) } // Setup state manager. p.Stateman, err = state.NewStateManagerSingle(p.DbClient, signer, conf.Timeout, secondary, &secondaryPub, conf.Primary.SthFile) if err != nil { return nil, crypto.PublicKey{}, fmt.Errorf("NewStateManagerSingle: %v", err) } p.TokenVerifier = token.NewDnsVerifier(&publicKey) if len(conf.Primary.RateLimitFile) > 0 { f, err := os.Open(conf.Primary.RateLimitFile) if err != nil { return nil, crypto.PublicKey{}, fmt.Errorf("opening rate limit config file failed: %v", err) } p.RateLimiter, err = rateLimit.NewLimiter(f, conf.Primary.AllowTestDomain) if err != nil { return nil, crypto.PublicKey{}, fmt.Errorf("initializing rate limiter failed: %v", err) } } else { p.RateLimiter = rateLimit.NoLimit{} } return &p, publicKey, nil } func configuredWitnesses(file string) ([]policy.Entity, error) { if len(file) == 0 { return nil, nil } policy, err := policy.ReadPolicyFile(file) if err != nil { return nil, err } return policy.GetWitnessesWithUrl(), nil } sigsum-log-go-0.15.2/cmd/sigsum-log-secondary/000077500000000000000000000000001477305677000211265ustar00rootroot00000000000000sigsum-log-go-0.15.2/cmd/sigsum-log-secondary/main.go000066400000000000000000000116141477305677000224040ustar00rootroot00000000000000// Package main provides a sigsum-log-secondary binary package main import ( "context" "encoding/hex" "fmt" "net/http" "os" "os/signal" "sync" "syscall" "time" "github.com/pborman/getopt/v2" "github.com/prometheus/client_golang/prometheus/promhttp" "sigsum.org/log-go/internal/config" "sigsum.org/log-go/internal/db" "sigsum.org/log-go/internal/metrics" "sigsum.org/log-go/internal/node/secondary" "sigsum.org/log-go/internal/version" "sigsum.org/sigsum-go/pkg/client" "sigsum.org/sigsum-go/pkg/crypto" "sigsum.org/sigsum-go/pkg/key" "sigsum.org/sigsum-go/pkg/log" "sigsum.org/sigsum-go/pkg/server" ) func ParseFlags(c *config.Config) { help := false versionFlag := false getopt.SetParameters("") getopt.FlagLong(&c.Secondary.PrimaryURL, "primary-url", 0, "Primary node endpoint for fetching leaves.", "url") getopt.FlagLong(&help, "help", '?', "Display help.") getopt.FlagLong(&versionFlag, "version", 0, "Display server version.") getopt.Parse() if help { getopt.PrintUsage(os.Stdout) os.Exit(0) } if versionFlag { fmt.Printf("log-go version: %s\n", version.ModuleVersion()) os.Exit(0) } } func main() { var conf *config.Config // Read default values from the Config struct confFile, err := config.OpenConfigFile() if err != nil { log.Info("didn't find configuration file, using defaults: %v", err) conf = config.NewConfig() } else { conf, err = config.LoadConfig(confFile) if err != nil { log.Fatal("failed to parse config file: %v", err) } } // Allow flags to override them conf.ServerFlags(getopt.CommandLine) ParseFlags(conf) if len(conf.LogFile) > 0 { if err := log.SetLogFile(conf.LogFile); err != nil { log.Fatal("open log file failed: %v", err) } } if err := log.SetLevelFromString(conf.LogLevel); err != nil { log.Fatal("setup logging: %v", err) } log.Info("log-go version: %s", version.ModuleVersion()) log.Debug("configuring log-go-secondary") node, publicKey, err := setupSecondaryFromFlags(conf) if err != nil { log.Fatal("setup secondary: %v", err) } // wait for clean-up before exit var wg sync.WaitGroup defer wg.Wait() ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer cancel() log.Debug("starting periodic routine") wg.Add(1) go func() { defer wg.Done() node.Run(ctx) log.Debug("periodic routine shutdown") cancel() // must have periodic running }() // No external endpoints but we want to return 404. extserver := &http.Server{Addr: conf.ExternalEndpoint, Handler: http.NewServeMux()} // Register HTTP endpoints. internalMux := http.NewServeMux() internalMux.Handle("/", server.NewSecondary(&server.Config{ Prefix: conf.Prefix, Timeout: conf.Timeout, Metrics: metrics.NewServerMetrics(hex.EncodeToString(publicKey[:])), }, node)) log.Debug("adding prometheus handler to internal mux, on path: /metrics") internalMux.Handle("/metrics", promhttp.Handler()) intserver := &http.Server{Addr: conf.InternalEndpoint, Handler: internalMux} wg.Add(1) go func() { defer wg.Done() log.Info("serving log nodes on %v/%v", conf.InternalEndpoint, conf.Prefix) if err = intserver.ListenAndServe(); err != http.ErrServerClosed { log.Error("serve(intserver): %v", err) } log.Debug("internal endpoints server shut down") cancel() }() wg.Add(1) go func() { defer wg.Done() log.Info("serving clients on %v/%v", conf.ExternalEndpoint, conf.Prefix) if err = extserver.ListenAndServe(); err != http.ErrServerClosed { log.Error("serve(server): %v", err) } log.Debug("public endpoints server shut down") cancel() }() <-ctx.Done() log.Debug("received shutdown signal") shutdownCtx, _ := context.WithTimeout(context.Background(), time.Second*60) log.Info("stopping http server, please wait...") extserver.Shutdown(shutdownCtx) log.Info("... done") log.Info("stopping internal api server, please wait...") intserver.Shutdown(shutdownCtx) log.Info("... done") } // setupSecondaryFromFlags() sets up a new sigsum secondary node from flags. func setupSecondaryFromFlags(conf *config.Config) (*secondary.Secondary, crypto.PublicKey, error) { var s secondary.Secondary var err error // Setup logging configuration. s.Signer, err = key.ReadPrivateKeyFile(conf.KeyFile) if err != nil { return nil, crypto.PublicKey{}, fmt.Errorf("newLogIdentity: %v", err) } s.Interval = conf.Interval switch conf.Backend { default: return nil, crypto.PublicKey{}, fmt.Errorf("unknown backend %q, must be \"trillian\" (default) or \"ephemeral\"", conf.Backend) case "ephemeral": s.DbClient = db.NewMemoryDb() case "trillian": trillianClient, err := db.DialTrillian(conf.TrillianRpcServer, conf.Timeout, db.SecondaryTree, conf.TrillianTreeIDFile) if err != nil { return nil, crypto.PublicKey{}, err } s.DbClient = trillianClient } // Setup primary node configuration. s.Primary = client.New(client.Config{URL: conf.Secondary.PrimaryURL}) return &s, s.Signer.Public(), nil } sigsum-log-go-0.15.2/cmd/sigsum-mktree/000077500000000000000000000000001477305677000176475ustar00rootroot00000000000000sigsum-log-go-0.15.2/cmd/sigsum-mktree/main.go000066400000000000000000000053731477305677000211320ustar00rootroot00000000000000package main import ( "errors" "fmt" "io/fs" "log" "os" "github.com/pborman/getopt/v2" "sigsum.org/log-go/internal/config" "sigsum.org/log-go/internal/state" "sigsum.org/log-go/internal/version" ) func ParseFlags(c *config.Config) state.StartupMode { mode := "empty" help := false versionFlag := false getopt.SetParameters("") getopt.FlagLong(&c.Primary.SthFile, "sth-file", 0, "File where latest published STH is being stored.", "file") getopt.FlagLong(&mode, "mode", 0, "Mode of operation, 'empty', 'local-tree', or 'saved' (no change, only check that a saved file exists)", "mode") getopt.FlagLong(&help, "help", '?', "Display help.") getopt.FlagLong(&versionFlag, "version", 0, "Display version.") getopt.Parse() if help { getopt.PrintUsage(os.Stdout) os.Exit(0) } if versionFlag { fmt.Printf("log-go version: %s\n", version.ModuleVersion()) os.Exit(0) } switch mode { case "empty": return state.StartupEmpty case "local-tree": return state.StartupLocalTree case "saved": return state.StartupSaved default: log.Fatalf("unknown mode %q, must be one of \"empty\", \"local-tree\", or \"saved\"", mode) return state.StartupEmpty } } func main() { log.SetFlags(0) var conf *config.Config // Read default values from the Config struct confFile, err := config.OpenConfigFile() if err != nil { log.Printf("didn't find configuration file, using defaults: %v", err) conf = config.NewConfig() } else { conf, err = config.LoadConfig(confFile) if err != nil { log.Fatalf("failed to parse config file: %v", err) } } startupMode := ParseFlags(conf) startupFile := conf.SthFile + state.StartupFileSuffix switch startupMode { case state.StartupSaved: if _, err := os.Stat(conf.SthFile); err != nil { log.Fatalf("Signed tree head file %q doesn't exist: %v", conf.SthFile, err) } checkNotExists(startupFile) case state.StartupEmpty: checkNotExists(conf.SthFile) writeStartupFile(startupFile, "empty") case state.StartupLocalTree: checkNotExists(conf.SthFile) writeStartupFile(startupFile, "local-tree") } } func checkNotExists(file string) { if _, err := os.Stat(file); err == nil || !errors.Is(err, fs.ErrNotExist) { log.Fatalf("Unexpected file %q, inconsistent with specified startup state.", file) } } // Writing is not atomic, user is expected to not run this tool under // the feet of log server startup. func writeStartupFile(name string, mode string) { f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644) if err != nil { log.Fatalf("creating startup file failed: %v", err) } defer f.Close() _, err = fmt.Fprintf(f, "startup=%s", mode) // Explicit close, to catch errors. if err == nil { err = f.Close() } if err != nil { log.Fatalf("writing startup file failed: %v", err) } } sigsum-log-go-0.15.2/doc/000077500000000000000000000000001477305677000150555ustar00rootroot00000000000000sigsum-log-go-0.15.2/doc/architecture.md000066400000000000000000000061531477305677000200660ustar00rootroot00000000000000# Log server architecture This document describes the system architecture of the log-go log server implementation. A log instance is identified by the public signing key used for verifying tree head signatures, and by the base url that clients use to interact with the log instance. A log instance consists of several nodes, each node running internal and/or external server components. There are two types of nodes, primary and secondary. The log instance has exactly one primary node. If, at some point in time, the primary node is down or not reachable, the log instance is not usable. A log instance can also have a single secondary node (support for multiple secondaries is planned). Secondary nodes replicate the primary node's log database, to enable failover without losing data or violating the append-only property of the log. For a production log server, it's strongly recommended to configure the log instance to include a secondary. If the primary node fails, it's possible to promote the secondary to become primary (and in this case, it's also strongly recommended to configure a new secondary node). See [fail-over](./failover.md) for details on necessary setup and the promotion procedure. Besides the log server itself, each node also runs an internal Trillian service and a MariaDB database for storing the log state; these servers are not exposed outside of the node, in particular, data replication is not done at this level. ## The primary node A primary node is configured with the private signing key of the log instance, url and public key of the secondary node, if any, and public key and url of each witness that is expected to cosign the log. If a secondary is configured, the primary server queries the secondary's tree, and it will only sign and publish a tree head when corresponding entries are properly stored to disk both locally and by the secondary. This means that in case the secondary is out of service for any reason, the primary will not sign and publish new log entries. The primary will continue to respond to queries from clients, but requests to add new log entries will only get a partial success response (202 Accepted); since the data is not replicated, the log can not commit to publish it. Clients are expected to retry such requests, and will get a success response once the secondary is back in service and has replicated the data. A primary node implements two HTTP APIs, with separate base urls: The public one, used by log clients, and an internal api, used by the secondary node. ## The secondary node A secondary node interacts only with the primary node. It is configured with its own signing key (corresponding signatures are seen and verified only by the primary), and the base url of the primary node's internal HTTP API. The secondary periodically polls the primary for new leaves, and copies them to the secondary's Trillian instance. The trillian instance is configured with a `PREORDERED_LOG` tree and without a sequencer. Polling should use a frequency that is higher than the primary's publishing frequency, typically on the order of once every few seconds and once every few minutes, respectively. sigsum-log-go-0.15.2/doc/config.toml.example000066400000000000000000000007471477305677000206610ustar00rootroot00000000000000external-endpoint = "localhost:6965" internal-endpoint = "localhost:6967" trillian-rpc-server = "localhost:6962" url-prefix = "" backend = "trillian" trillian-tree-id-file = "/var/lib/sigsum-log/tree-id" timeout = "10s" key-file = "" interval = "30s" log-file = "" log-level = "info" [primary] policy-file = "" max-range = 10 rate-limit-file = "" allow-test-domain = false secondary-url = "" secondary-pubkey-file = "" sth-file = "/var/lib/sigsum-log/sth" [secondary] primary-url = "" sigsum-log-go-0.15.2/doc/failover.md000066400000000000000000000037161477305677000172150ustar00rootroot00000000000000# Failover This document describes the required setup and promotion procedure for promoting a secondary node to primary, in case the primary node fails. The primary ensures that it only signs and publishes tree heads that are fully replicated by the secondary node. System backups are out of scope of the Sigsum software, but note that restoring the state of a failed primary node from backup is *not* recommended, since that may lose recent log entries, breaking the log's append-only property beyond repair. ## Log's private key Since the log is identified by its signing key, for the secondary node to take on the role of primary, it must have access to the corresponding private key. Hence, for failover to be possible in case of catastrophic failure, a secure backup of the private key is required. E.g., using n-of-k secret sharing, or a securely stored clone of a hardware key. ## Promoting a secondary to become the primary In order to promote a secondary node to become the primary node of a log instance, the following steps are needed: 1. Shut down the secondary. This effectively stops the primary from advancing its tree head, regardless of its current status. 2. Convert the Trillian tree from type `PREORDERED_LOG` to type `LOG`, using `updatetree`. Note that the tree needs to be `FROZEN` before changing the tree type and unfrozen (`ACTIVE`) afterwards. 3. Configure the secondary to use the signing key of the log instance. 4. Create special startup file `sth.startup` next to the configured location of the sth file (signed tree head), with the contents `startup=local-tree`. This tells the new primary to initially create a signed tree head corresponding to its local tree, i.e., the replica of the old primary. 5. Configure a new node to act as a secondary. 6. Start the primary log server on the node being promoted. 7. In order for clients to reach the new primary rather than the old one, DNS record changes are usually needed as well. sigsum-log-go-0.15.2/doc/rate-limit.md000066400000000000000000000142131477305677000174470ustar00rootroot00000000000000# Sigsum log rate limiting Documentation on how Sigsum rate limiting works, and how it is configured. ## Objective The overall objective of the rate limit mechanism is to limit the rate at which new leaves are added to the log. Note that this does *not* provide any protection from more general denial of service attacks. The rate limit applies only to `add-leaf` requests, and the mechanism is intended to make it feasible to operate a public log, which anyone can submit new leaves to. ## Enabling rate limits Rate limits are enabled by using the `--rate-limit-config=` command line option to the `sigsum-log-primary` server, or the corresponding setting in the main configuration file. The given file specifies allow-lists of various kinds, and corresponding limits. Without this option, there are no rate limits. With respect to public access, there are three modes of operation: 1. Unlimited access. To get this behavior, don't enable rate limiting at all. This mode of operation is also appropriate if access to the log server is restricted by other means. 2. Limited access, subject to configured rate limit. To get this behavior, enable rate limiting, and include a `public ...` line in the configuration file, as described below. 3. No access. To get this behavior, enable rate limiting, but don't include any `public ...` line in the configuration file. Then only explicitly allow-listed keys and domains are allowed to submit new leaves to the log. ## Config file syntax The config file is line based, where each line consist of items separated by white space. Comments are written with "#" and extend to the end of the line. International domain names are written in utf-8 (no punycode). ## Allow-lists The rate limit is based on counts of added leaves per 24 hours. Adding a leaf usually takes several requests; the first one makes the leaf known to the log, yielding a 202 (Accepted) response. A typical client will then repeat the request until it gets a 200 response. For rate limiting purposes, only the first request for each leaf is counted. Which counter is used, and what limit it is compared to, depends on the configured allow-lists. Each entry specifies a limit, an unsigned decimal integer specifying the maximum number of leaves that may be submitted per 24 hours. A limit of zero means that no leaves can be submitted. ### Allowed keys Allowed keys are configured with config lines of the form ``` key ``` The key hash is the hex-encoded hash of the public key used to verify the leaf signature in the request. ### Allowed domains Allowed submitter domains are configured with a config line of the form ``` domain ``` The domain is a DNS domain in standard dotted notation, e.g., `foo.example.org`. The domain associated with the request is based on a `sigsum-token:` header in the http request, which must be provided by the submitter. The header includes a domain name and signature, and it is used only if the log can verify the signature using a public key retrieved from DNS. (In particular, the submitter's IP address and any associated PTR records are not consulted). Note that all subdomains of the configured domain are allowed, i.e., the line applies to all requests with a verified submit token specifying the given domain or a subdomain thereof. All requests from those domains are counted together towards the given limit. ### Enabling public access It's encouraged to enable public access, and allow anyone to submit leaves to the log, restricted only by rate limits. It is enabled using a config line of the form ``` public ``` There can be only one of these lines. The rate limiting for public access depends on a list of [public suffixes](https://publicsuffix.org/), and the configured suffix file should be the name of a copy of . (Automatic updates of this list is under consideration, but not yet implemented). Like for allowed domains, above, a domain is associated with a request via the `sigsum-token:` header. The suffix list is used to extract the "registered domain", roughly, the longest know public suffix matching the domain, and one additional label. The given limit is applied per "registered domain", which means that total requests allowed by this configuration can be very much higher. It is recommended to specify a rather low limit, e.g., 10-100. It is deemed impractical for a prospective attacker to get tens of thousands of registered domain. TODO: Also add a limit on the total number of public requests, so one could have, e.g., 10 per registered domain but 10000 total for all? ### Rule precedence The order of the config lines doesn't matter. When determining which limit should be applied to an incoming `add-leaf` request, it is matched as follows: 1. If the leaf key matches a "key" line, that limit applies. 2. Otherwise, if one or more "domain" lines match, the one with the longest domain applies. 3. Otherwise, if public access is enabled, and the domain matches a known public suffix, then the request count associated with the registered domain determines if the request is allowed. 4. If none of the lines match, the request is refused. This means that if a domain matches a public suffix, one can set a more specific limit (higher or lower) for that domain or a specific subdomain using a "domain" line, which overrides the limit for the registered domain. And similarly, a "key" line can be used to override domain-based limits for a particular key. ## Test domain There's a test domain `test.sigsum.org`, with a public key `4cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba29` registered in DNS. The corresponding private key is `0000000000000000000000000000000000000000000000000000000000000001`, and it can be used by anyone to create valid submit tokens for test purposes. By default, this test domain is banned, as if a line "domain test.sigsum.org 0" were present in the config file, overriding all other domain-based configuration affecting this domain. To enable use of this domain, e.g., for integration tests of the rate limiting feature, there's a command line option `--enable-test-domain=true`. sigsum-log-go-0.15.2/doc/readme.md000066400000000000000000000014251477305677000166360ustar00rootroot00000000000000# Configuring and using the log server implementation The documents in this folder describe the `log-go` implementation of a [Sigsum](https://git.glasklar.is/sigsum/project/documentation) log server. Sections: 1. [System architecture](./architecture.md), in particular, how a log instance can have one primary and one secondary node. 2. [Rate-limit configuration](./rate-limit.md), strongly recommended for public log servers to combat log spam. 3. [Fail-over](./failover.md), instruction for setup and procedures to be able to fail over to the secondary node on catastrophic failure of the primary. 4. [Server-setup](./setup.md). How to manually setup a new log instance (for deployment using ansible, see [ansible](https://git.glasklar.is/sigsum/admin/ansible)), sigsum-log-go-0.15.2/doc/release-checklists.md000066400000000000000000000032001477305677000211440ustar00rootroot00000000000000# Release checklists This document is intended for maintainers making releases of the log-go software. ## Release checklist - [ ] Test the procedure for upgrading from the previous release. - [ ] Test and document interop with sigsum-go command line tools, and with known witness implementations. - [ ] Check that the README, RELEASES.md and MAINTAINERS files are up-to-date. - [ ] After finalizing the release documentation, in particular, the NEWS file, create a new tag, usually incrementing the third number from what was used for release candidates being tested. ## NEWS file checklist - [ ] The previous NEWS entry is for the previous release - [ ] Broad summary of changes - [ ] Detailed instructions on how to upgrade from the previous release. - [ ] Other repositories/tools/tags that are known to be interoperable. ## RELEASES file checklist - [ ] What in the repository is being released and supported. - [ ] Where are releases announced (sigsum-announce mailing list). - [ ] The overall release process is described. - [ ] The expectation we as maintainers have on users. - [ ] The expectations users can have on us as maintainers, e.g., how we're testing a release and what we intend to (not) break in the future. ## Announcement email checklist - [ ] What is being released, e.g., log server software / log-go. - [ ] Specify new release tag. - [ ] Specify previous release tag. - [ ] Specify how to report bugs. - [ ] Refer to the RELEASES file for information on the release process and expectations. - [ ] Copy-paste the NEWS file entries for this release. sigsum-log-go-0.15.2/doc/setup.md000066400000000000000000000163051477305677000165440ustar00rootroot00000000000000# Setting up a Sigsum log server This document describes how to setup and configure the individual components needed for operating a log instance. See [ansible](https://git.glasklar.is/sigsum/admin/ansible) for recipes for more automated deployment. ## Installing server and dependencies To install Sigsum tools and the log server, run ``` go install sigsum.org/sigsum-go/cmd/...@latest go install sigsum.org/log-go/cmd/...@latest ``` If you're unfamiliar with `go install`, it will by default install executables in `$GOBIN`, `$GOPATH/bin`, or `$HOME/go/bin`, depending on which enviroment variables are set. You may want to add this directory to $PATH. The Sigsum server depends on a Trillian service and MariaDB. To install Trillian, run ``` go install github.com/google/trillian/cmd/...@latest ``` For further information on Trillian, see [Introduction to Trillian](https://www.rgdd.se/post/observations-from-a-trillian-play-date/) and (https://github.com/google/trillian/blob/master/README.md). In Trillian terminology, the Sigsum log server is a _Trillian personality_. To install MariaDB, on debian-based systems you may use a command like ``` apt-get install mariadb-server ``` ## One-time database setup To setup permissions on the database, run the `mysql_secure_installation` script, with default answers to all questions. Next, to create the tables needed by Trillian, run the `resetdb.sh` script, located in the `log-go/integration/` directory. It will also use the `storage.sql` file (with table definitions) found in the same directory. By default, the script creates a user and a table both named "sigsum_test", password "zaphod". It can be configured via environment variables, see comments in the script. ## Configuration file The log server looks for a configuration file `/etc/sigsum/config.toml`, to change the location, set the `$SIGSUM_LOGSERVER_CONFIG` environment variable. See [example](./config.toml.example). ## Starting Trillian Trillian is usually two separate processes, which we refer to as "Trillian server" and "Trillian sequencer". The sequencer is used only on the primary log node. To start the Trillian server, ``` trillian_log_server \ -mysql_uri=sigsum_test:zaphod@tcp(127.0.0.1:3306)/sigsum_test \ -rpc_endpoint=localhost:6962 \ -http_endpoint="" ``` See Trillian documentation for further configuration, in particular, the `-log_dir` option can be used to specify where it stores logs. To limit Trillian's stderr logging to messages with WARNING level or above, use `-logtostderr=false -stderrthreshold=WARNING`. To start the Trillian sequencer, on primary log node only, run ``` trillian_log_signer \ -force_master \ -mysql_uri=sigsum_test:zaphod@tcp(127.0.0.1:3306)/sigsum_test \ -rpc_endpoint=localhost:6963 \ -http_endpoint="" ``` ## Creating the Trillian merkle trees Primary and secondary nodes need different types of trees to be configured in the respective database, using Trillian's `createtree` command. On success, numerical id of the new tree is written on standard output. The log server needs that number stored in a file containing a line `tree_id=...`. That file should be passed on a `trillian-tree-id-file=...` line in the log's config file. On the primary node, with the above configuration, the tree and the tree-id file can be created using ``` ( id=$(createtree -admin_server=localhost:6962) && echo tree_id=${id} ) | tee primary-tree-id ``` On the secondary node, instead run ``` ( id=$(createtree -admin_server=localhost:6962 -tree_type PREORDERED_LOG) && echo tree_id=${id} ) | tee secondary-tree-id ``` The `PREORDERED_LOG` type means that entries already have indices (and hence order) assigned when passed to Trillian, which is needed because the secondary node replicates the tree at the primary node, and it's the primary node that determines the order of entries. That is also why the secondary node doesn't need a Trillian sequencer. ## Primary node ### Key management The primary node needs its own signing key pair. This can be an unencrypted private key file, generated using `sigsum-key gen -o KEY` (which generates a new keypair, stores the unencrypted private key in the file `KEY` and corresponding public key in `KEY.pub`). This uses openssh keyfile formats, and is equivalent to `ssh-keygen -q -N '' -t ed25519 -f KEY`. Alternatively, the server can access the private key via the ssh-agent protocol. Using an agent is particularly useful for hardware keys, but it can also help reducing the attack surface when using a key file on disk, e.g., to give the server uid access to a signing oracle, without giving it direct access to the private key file. To enable failover to a secondary node in case of catastrophic failure of the primary node, the private key must be securely backed up elsewhere. The [key-mgmt documentation](https://git.glasklar.is/sigsum/core/key-mgmt/-/blob/main/docs/key-management.md) describes one way to provision and back up private log keys using YubiHSM hardware. The [same repository](https://git.glasklar.is/sigsum/core/key-mgmt) also hosts `sigsum-agent`, a signing oracle that speaks the ssh-agent protocol, and which supports both YubiHSM keys and keys on disk. ### Configuration The most important settings in the config file for the primary server are: 1. `external-endpoint`: ip-address:port for log clients to connect to. 2. `internal-endpoint`: ip-address:port for secondary node to connect to. 3. `trillian-rpc-server=localhost:6962`: ip-address:port where the Trillian server responds to gRPC requests. 4. `trillian-tree-id-file`: file recording the number produced by `createtree`. 5. `key-file`: identifies the log's signing key. Either the name of the private key file, or the name of a public key file, in case the corresponding private key is accessible via ssh-agent. 6. `secondary-url`: base url to the secondary node's internal endpoint. 7. `secondary-pubkey-file`: public key for verifying the secondary's signatures. 8. `sth-file`: name of the file where the latest signed tree head is stored, by default, `/var/lib/sigsum-log/sth`. Before starting the primary the first time, we need to tell it to start out by signing and publishing a tree head corresponding to the empty tree. To do this, run the command `sigsum-mktree`; this reads the `sth-file=` entry in the config file and creates a special startup file next to that file. E.g., with the default location `/var/lib/sigsum-log/sth`, the startup file is `/var/lib/sigsum-log/sth.startup`. The startup file is automatically deleted after use, and it is an error if both files exist. The primary server executable is `sigsum-log-primary`. ## Secondary node The secondary node needs its own signing key pair, it is used only to sign responses to the primary server, so usually no need to back it up; it can be rotated at will by reconfiguring and restarting the primary node with the secondary's new key. Configuration of `external-endpoint` (which returns HTTP 404 for everything), `internal-endpoint`, `trillian-rpc-server`, `trillian-tree-id-file`, and `key-file` is analogous to the primary configuration. In addition, the secondary should be configured with: 1. `primary-url`: base url for the primary node's internal endpoint. The secondary server executable is `sigsum-log-secondary`. sigsum-log-go-0.15.2/go.mod000066400000000000000000000121431477305677000154170ustar00rootroot00000000000000module sigsum.org/log-go // We don't want to depend on golang version later than is available // in debian's stable or backports repos. go 1.22.0 require ( // A fork of github.com/dchest/safefile git.glasklar.is/sigsum/dependencies/safefile v1.1.0 github.com/BurntSushi/toml v1.5.0 github.com/golang/mock v1.6.0 github.com/google/trillian v1.7.1 github.com/pborman/getopt/v2 v2.1.0 github.com/prometheus/client_golang v1.21.1 google.golang.org/grpc v1.71.0 sigsum.org/sigsum-go v0.11.2 ) require ( bitbucket.org/creachadair/shell v0.0.8 // indirect cel.dev/expr v0.19.1 // indirect cloud.google.com/go v0.116.0 // indirect cloud.google.com/go/auth v0.13.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect cloud.google.com/go/compute/metadata v0.6.0 // indirect cloud.google.com/go/monitoring v1.21.2 // indirect cloud.google.com/go/spanner v1.73.0 // indirect cloud.google.com/go/trace v1.11.2 // indirect contrib.go.opencensus.io/exporter/stackdriver v0.13.14 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect github.com/aws/aws-sdk-go v1.51.8 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 // indirect github.com/cockroachdb/cockroach-go/v2 v2.3.8 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185 // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/s2a-go v0.1.8 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/gax-go/v2 v2.14.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgconn v1.14.3 // indirect github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgproto3/v2 v2.3.3 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgtype v1.14.3 // indirect github.com/jackc/pgx/v4 v4.18.3 // indirect github.com/jackc/pgx/v5 v5.7.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/lib/pq v1.10.9 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/prometheus v0.51.0 // indirect github.com/transparency-dev/merkle v0.0.2 // indirect go.etcd.io/etcd/api/v3 v3.5.17 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.17 // indirect go.etcd.io/etcd/client/v3 v3.5.17 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect go.opentelemetry.io/otel v1.34.0 // indirect go.opentelemetry.io/otel/metric v1.34.0 // indirect go.opentelemetry.io/otel/sdk v1.34.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect go.opentelemetry.io/otel/trace v1.34.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.32.0 // indirect golang.org/x/mod v0.22.0 // indirect golang.org/x/net v0.34.0 // indirect golang.org/x/oauth2 v0.25.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.8.0 // indirect golang.org/x/tools v0.29.0 // indirect google.golang.org/api v0.214.0 // indirect google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect google.golang.org/protobuf v1.36.4 // indirect k8s.io/klog/v2 v2.130.1 // indirect ) sigsum-log-go-0.15.2/go.sum000066400000000000000000005510651477305677000154570ustar00rootroot00000000000000bitbucket.org/creachadair/shell v0.0.8 h1:3yM6JcAfaGWzjzcCamTblzSIWXm/YSs0PFGIzBm2HTo= bitbucket.org/creachadair/shell v0.0.8/go.mod h1:vINzudofoUXZSJ5tREgpy+Etyjsag3ait5WOWImEVZ0= cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= 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.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= 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/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/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/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/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/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/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/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/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/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/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/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/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs= cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= 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/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/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/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/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/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/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/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/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/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/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/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/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/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/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= 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/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/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/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/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/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/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= 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/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.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA= cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= 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/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/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/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/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/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/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= 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.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc= cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= 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/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/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/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/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/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.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU= cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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.73.0 h1:0bab8QDn6MNj9lNK6XyGAVFhMlhMU2waePPa6GZNoi8= cloud.google.com/go/spanner v1.73.0/go.mod h1:mw98ua5ggQXVWwp83yjwggqEmW9t8rjs9Po1ohcUGW4= 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/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/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/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/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/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/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.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI= cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io= 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/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/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/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/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/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/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/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/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/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= contrib.go.opencensus.io/exporter/stackdriver v0.13.14 h1:zBakwHardp9Jcb8sQHcHpXy/0+JIb1M8KjigCJzx7+4= contrib.go.opencensus.io/exporter/stackdriver v0.13.14/go.mod h1:5pSSGY0Bhuk7waTHuDf4aQ8D2DrhgETRo9fy6k3Xlzc= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= git.glasklar.is/sigsum/dependencies/safefile v1.1.0 h1:GV2fvvczD44Hyl4z5zFk2/N0UQhKiMndMXSv/MLmS0U= git.glasklar.is/sigsum/dependencies/safefile v1.1.0/go.mod h1:Xd0Q9abOw70hjIaEimdDr8NNeiRr9imCYH6ozAFzs3k= git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0 h1:oVLqHXhnYtUwM89y9T1fXGaK9wTkXHgNp8/ZNMQzUxE= github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 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/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/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/aws/aws-sdk-go v1.51.8 h1:tD7gQq5XKuKdhA6UMEH26ZNQH0s+HbL95rzv/ACz5TQ= github.com/aws/aws-sdk-go v1.51.8/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 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 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= 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.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/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-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 h1:boJj011Hh+874zpIySeApCX4GeOjPl9qhRF3QuIZq+Q= github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/cockroach-go/v2 v2.3.8 h1:53yoUo4+EtrC1NrAEgnnad4AS3ntNvGup1PAXZ7UmpE= github.com/cockroachdb/cockroach-go/v2 v2.3.8/go.mod h1:9uH5jK4yQ3ZQUT9IXe4I2fHzMIF5+JC/oOdzTRgJYJk= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185 h1:3T8ZyTDp5QxTx3NU48JVb2u+75xc040fofcBaN+6jPA= github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185/go.mod h1:cFRxtTwTOJkz2x3rQUNCYKWC93yP1VKjR8NUhqFxZNU= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 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.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= 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.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/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/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-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.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 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-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 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/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 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= 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/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.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 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/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-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.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/google/trillian v1.7.1 h1:+zX8jLM3524bAMPS+VxaDIDgsMv3/ty6DuLWerHXcek= github.com/google/trillian v1.7.1/go.mod h1:E1UMAHqpZCA8AQdrKdWmHmtUfSeiD0sDWD1cv00Xa+c= github.com/google/uuid v1.1.2/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.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/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.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= 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.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o= github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk= 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/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= 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/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= 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/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/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgtype v1.14.3 h1:h6W9cPuHsRWQFTWUZMAKMgG5jSwQI0Zurzdvlx3Plus= github.com/jackc/pgtype v1.14.3/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI= github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 h1:liMMTbpW34dhU4az1GN0pTPADwNmvoRSeoZ6PItiqnY= github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/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/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.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.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 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/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 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.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 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/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 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/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pborman/getopt/v2 v2.1.0 h1:eNfR+r+dWLdWmV8g5OlpyrTYHkhVNxHBdN2cCrJmOEA= github.com/pborman/getopt/v2 v2.1.0/go.mod h1:4NtW75ny4eBw9fO1bhtNdYTlZKYX5/tBLtsOpwKIKd0= 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.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/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= 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.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/prometheus v0.51.0 h1:aRdjTnmHLved29ILtdzZN2GNvOjWATtA/z+3fYuexOc= github.com/prometheus/prometheus v0.51.0/go.mod h1:yv4MwOn3yHMQ6MZGHPg/U7Fcyqf+rxqiZfSur6myVtc= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 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/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/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/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/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.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= 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.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/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/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 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.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4= github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A= github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk= github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 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/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.etcd.io/etcd/api/v3 v3.5.17 h1:cQB8eb8bxwuxOilBpMJAEo8fAONyrdXTHUNcMd8yT1w= go.etcd.io/etcd/api/v3 v3.5.17/go.mod h1:d1hvkRuXkts6PmaYk2Vrgqbv7H4ADfAKhyJqHNLJCB4= go.etcd.io/etcd/client/pkg/v3 v3.5.17 h1:XxnDXAWq2pnxqx76ljWwiQ9jylbpC4rvkAeRVOUKKVw= go.etcd.io/etcd/client/pkg/v3 v3.5.17/go.mod h1:4DqK1TKacp/86nJk4FLQqo6Mn2vvQFBmruW3pP14H/w= go.etcd.io/etcd/client/v2 v2.305.17 h1:ajFukQfI//xY5VuSeuUw4TJ4WnNR2kAFfV/P0pDdPMs= go.etcd.io/etcd/client/v2 v2.305.17/go.mod h1:EttKgEgvwikmXN+b7pkEWxDZr6sEaYsqCiS3k4fa/Vg= go.etcd.io/etcd/client/v3 v3.5.17 h1:o48sINNeWz5+pjy/Z0+HKpj/xSnBkuVhVvXkjEXbqZY= go.etcd.io/etcd/client/v3 v3.5.17/go.mod h1:j2d4eXTHWkT2ClBgnnEPm/Wuu7jsqku41v9DZ3OtjQo= go.etcd.io/etcd/pkg/v3 v3.5.17 h1:1k2wZ+oDp41jrk3F9o15o8o7K3/qliBo0mXqxo1PKaE= go.etcd.io/etcd/pkg/v3 v3.5.17/go.mod h1:FrztuSuaJG0c7RXCOzT08w+PCugh2kCQXmruNYCpCGA= go.etcd.io/etcd/raft/v3 v3.5.17 h1:wHPW/b1oFBw/+HjDAQ9vfr17OIInejTIsmwMZpK1dNo= go.etcd.io/etcd/raft/v3 v3.5.17/go.mod h1:uapEfOMPaJ45CqBYIraLO5+fqyIY2d57nFfxzFwy4D4= go.etcd.io/etcd/server/v3 v3.5.17 h1:xykBwLZk9IdDsB8z8rMdCCPRvhrG+fwvARaGA0TRiyc= go.etcd.io/etcd/server/v3 v3.5.17/go.mod h1:40sqgtGt6ZJNKm8nk8x6LexZakPu+NDl/DCgZTZ69Cc= 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/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao= go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 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.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= 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.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-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-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-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.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= 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.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.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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-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-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 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-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-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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-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-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/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.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.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= 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.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-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-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/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-20200122134326-e047566fdf82/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-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-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/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-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-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-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-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-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-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.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.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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-20210927222741-03fcf44c2211/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.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 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.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 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-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/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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-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-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-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-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/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-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 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-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-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-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-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.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.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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.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.214.0 h1:h2Gkq07OYi6kusGOaT/9rnNljuXmqPnaig7WGPmKbwA= google.golang.org/api v0.214.0/go.mod h1:bYPpLG8AyeMWwDU6NXoB00xC0DFkikVvd5MfwoxjLqE= 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/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-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-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-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-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-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-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/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.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.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.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= 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.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 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.8/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-20210107192922-496545a6307b/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= 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/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 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/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.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/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/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/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/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/token v1.0.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/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sigsum.org/sigsum-go v0.11.2 h1:7HhDPC8gVJzl3wB3gAg3j6gTpO2t0UPHC0ogwhKuNRc= sigsum.org/sigsum-go v0.11.2/go.mod h1:pGa/r4QsNYom+RqRMkdhcG5E00ty1nlTmALEizdRWPk= sigsum-log-go-0.15.2/integration/000077500000000000000000000000001477305677000166335ustar00rootroot00000000000000sigsum-log-go-0.15.2/integration/.gitignore000066400000000000000000000000121477305677000206140ustar00rootroot00000000000000/bin /tmp sigsum-log-go-0.15.2/integration/README.md000066400000000000000000000020451477305677000201130ustar00rootroot00000000000000# Sigsum log integration tests These tests start a sigsum-log-primary and sigsum-log-secondary, and corresponding trillian servers, to verify basic functionality of the complete system. ## Database First install the database server. E.g., on a Debian GNU/Linux system, run `apt-get install mariadb-server && mysql_secure_installation` as root, with default ansers to all questions. Next, run the script `resetdb.sh`, to prepare needed database tables and users. This script needs to run with sufficient privileges to modify the database's user table; with a default install as above, the simplest way of getting sufficient privileges is to run the script as root. If successful, the script creates a user `sigsum_test` and a database `sigsum_test`, and empty tables according to the schema `storage.sql`. ## Running tests There are three modes of running the tests, basic mode `./test.sh`, extensive mode testing failover, `./test.sh --extended`, and ephemeral mode which doesn't store any data to disk and doesn't use trillian, `./test.sh --ephemeral`. sigsum-log-go-0.15.2/integration/conf/000077500000000000000000000000001477305677000175605ustar00rootroot00000000000000sigsum-log-go-0.15.2/integration/conf/logc.config000066400000000000000000000003061477305677000216720ustar00rootroot00000000000000node_name=logc tsrv_rpc=localhost:7162 tseq_rpc=localhost:7163 ssrv_role=secondary ssrv_interval_sec=2 ssrv_endpoint=localhost:7166 ssrv_internal=localhost:7167 ssrv_prefix=testonly ssrv_agent=no sigsum-log-go-0.15.2/integration/conf/primary.config000066400000000000000000000003051477305677000224300ustar00rootroot00000000000000node_name=loga tsrv_rpc=localhost:6962 tseq_rpc=localhost:6963 ssrv_role=primary ssrv_agent=yes ssrv_interval_sec=5 ssrv_endpoint=localhost:6966 ssrv_internal=localhost:6967 ssrv_prefix=testonly sigsum-log-go-0.15.2/integration/conf/secondary.config000066400000000000000000000003061477305677000227350ustar00rootroot00000000000000node_name=logb tsrv_rpc=localhost:7062 tseq_rpc=localhost:7063 ssrv_role=secondary ssrv_interval_sec=2 ssrv_endpoint=localhost:7066 ssrv_internal=localhost:7067 ssrv_prefix=testonly ssrv_agent=no sigsum-log-go-0.15.2/integration/public_suffix_list.dat000066400000000000000000000000441477305677000232200ustar00rootroot00000000000000// For use by integration tests org sigsum-log-go-0.15.2/integration/rate-limit.cfg000066400000000000000000000000421477305677000213570ustar00rootroot00000000000000public public_suffix_list.dat 100 sigsum-log-go-0.15.2/integration/resetdb.sh000077500000000000000000000076721477305677000206360ustar00rootroot00000000000000#!/bin/bash # Copyright 2019 Google LLC. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Based on # https://github.com/google/trillian/blob/master/scripts/resetdb.sh, # as of commit e441824 on May 14, 2019. Note original file lacked # copyright headers, added above based on top-level trillian LICENCE # and git history. set -e usage() { cat < /dev/stderr exit 1 } collect_vars() { # set unset environment variables to defaults [ -z ${MYSQL_ROOT_USER+x} ] && MYSQL_ROOT_USER="root" [ -z ${MYSQL_HOST+x} ] && MYSQL_HOST="localhost" [ -z ${MYSQL_PORT+x} ] && MYSQL_PORT="3306" [ -z ${MYSQL_DATABASE+x} ] && MYSQL_DATABASE="sigsum_test" [ -z ${MYSQL_USER+x} ] && MYSQL_USER="sigsum_test" [ -z ${MYSQL_PASSWORD+x} ] && MYSQL_PASSWORD="zaphod" [ -z ${MYSQL_USER_HOST+x} ] && MYSQL_USER_HOST="localhost" FLAGS=() # handle flags FORCE=false VERBOSE=false while [[ $# -gt 0 ]]; do case "$1" in --force) FORCE=true ;; --verbose) VERBOSE=true ;; --help) usage; exit ;; *) FLAGS+=("$1") esac shift 1 done FLAGS+=(-u "${MYSQL_ROOT_USER}") FLAGS+=(--host "${MYSQL_HOST}") FLAGS+=(--port "${MYSQL_PORT}") # Optionally print flags (before appending password) [[ ${VERBOSE} = 'true' ]] && echo "- Using MySQL Flags: ${FLAGS[@]}" # append password if supplied [ -z ${MYSQL_ROOT_PASSWORD+x} ] || FLAGS+=(-p"${MYSQL_ROOT_PASSWORD}") } main() { collect_vars "$@" echo "Warning: about to destroy and reset database '${MYSQL_DATABASE}'" [[ ${FORCE} = true ]] || read -p "Are you sure? [Y/N]: " -n 1 -r echo # Print newline following the above prompt if [ -z ${REPLY+x} ] || [[ $REPLY =~ ^[Yy]$ ]] then echo "Resetting DB..." mysql "${FLAGS[@]}" -e "DROP DATABASE IF EXISTS ${MYSQL_DATABASE};" || \ die "Error: Failed to drop database '${MYSQL_DATABASE}'." mysql "${FLAGS[@]}" -e "CREATE DATABASE ${MYSQL_DATABASE};" || \ die "Error: Failed to create database '${MYSQL_DATABASE}'." mysql "${FLAGS[@]}" -e "CREATE USER IF NOT EXISTS ${MYSQL_USER}@'${MYSQL_USER_HOST}' IDENTIFIED BY '${MYSQL_PASSWORD}';" || \ die "Error: Failed to create user '${MYSQL_USER}@${MYSQL_USER_HOST}'." mysql "${FLAGS[@]}" -e "GRANT ALL ON ${MYSQL_DATABASE}.* TO ${MYSQL_USER}@'${MYSQL_USER_HOST}'" || \ die "Error: Failed to grant '${MYSQL_USER}' user all privileges on '${MYSQL_DATABASE}'." mysql "${FLAGS[@]}" -D ${MYSQL_DATABASE} < "$(dirname "$0")/storage.sql" || \ die "Error: Failed to create tables in '${MYSQL_DATABASE}' database." echo "Reset Complete" fi } main "$@" sigsum-log-go-0.15.2/integration/storage.sql000066400000000000000000000162071477305677000210260ustar00rootroot00000000000000# MySQL / MariaDB version of the tree schema -- Copyright 2020 Google LLC. All Rights Reserved. -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. -- Database schema from -- https://github.com/google/trillian/blob/master/storage/mysql/schema/storage.sql, -- as of commit e1ac8ba on Feb 18, 2021. Note original file lacked -- copyright headers, added above based on top-level trillian LICENCE -- and git history. -- --------------------------------------------- -- Tree stuff here -- --------------------------------------------- -- Tree parameters should not be changed after creation. Doing so can -- render the data in the tree unusable or inconsistent. CREATE TABLE IF NOT EXISTS Trees( TreeId BIGINT NOT NULL, TreeState ENUM('ACTIVE', 'FROZEN', 'DRAINING') NOT NULL, TreeType ENUM('LOG', 'MAP', 'PREORDERED_LOG') NOT NULL, HashStrategy ENUM('RFC6962_SHA256', 'TEST_MAP_HASHER', 'OBJECT_RFC6962_SHA256', 'CONIKS_SHA512_256', 'CONIKS_SHA256') NOT NULL, HashAlgorithm ENUM('SHA256') NOT NULL, SignatureAlgorithm ENUM('ECDSA', 'RSA', 'ED25519') NOT NULL, DisplayName VARCHAR(20), Description VARCHAR(200), CreateTimeMillis BIGINT NOT NULL, UpdateTimeMillis BIGINT NOT NULL, MaxRootDurationMillis BIGINT NOT NULL, PrivateKey MEDIUMBLOB NOT NULL, PublicKey MEDIUMBLOB NOT NULL, Deleted BOOLEAN, DeleteTimeMillis BIGINT, PRIMARY KEY(TreeId) ); -- This table contains tree parameters that can be changed at runtime such as for -- administrative purposes. CREATE TABLE IF NOT EXISTS TreeControl( TreeId BIGINT NOT NULL, SigningEnabled BOOLEAN NOT NULL, SequencingEnabled BOOLEAN NOT NULL, SequenceIntervalSeconds INTEGER NOT NULL, PRIMARY KEY(TreeId), FOREIGN KEY(TreeId) REFERENCES Trees(TreeId) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS Subtree( TreeId BIGINT NOT NULL, SubtreeId VARBINARY(255) NOT NULL, Nodes MEDIUMBLOB NOT NULL, SubtreeRevision INTEGER NOT NULL, -- Key columns must be in ASC order in order to benefit from group-by/min-max -- optimization in MySQL. PRIMARY KEY(TreeId, SubtreeId, SubtreeRevision), FOREIGN KEY(TreeId) REFERENCES Trees(TreeId) ON DELETE CASCADE ); -- The TreeRevisionIdx is used to enforce that there is only one STH at any -- tree revision CREATE TABLE IF NOT EXISTS TreeHead( TreeId BIGINT NOT NULL, TreeHeadTimestamp BIGINT, TreeSize BIGINT, RootHash VARBINARY(255) NOT NULL, RootSignature VARBINARY(1024) NOT NULL, TreeRevision BIGINT, PRIMARY KEY(TreeId, TreeHeadTimestamp), FOREIGN KEY(TreeId) REFERENCES Trees(TreeId) ON DELETE CASCADE ); CREATE UNIQUE INDEX TreeHeadRevisionIdx ON TreeHead(TreeId, TreeRevision); -- --------------------------------------------- -- Log specific stuff here -- --------------------------------------------- -- Creating index at same time as table allows some storage engines to better -- optimize physical storage layout. Most engines allow multiple nulls in a -- unique index but some may not. -- A leaf that has not been sequenced has a row in this table. If duplicate leaves -- are allowed they will all reference this row. CREATE TABLE IF NOT EXISTS LeafData( TreeId BIGINT NOT NULL, -- This is a personality specific has of some subset of the leaf data. -- It's only purpose is to allow Trillian to identify duplicate entries in -- the context of the personality. LeafIdentityHash VARBINARY(255) NOT NULL, -- This is the data stored in the leaf for example in CT it contains a DER encoded -- X.509 certificate but is application dependent LeafValue LONGBLOB NOT NULL, -- This is extra data that the application can associate with the leaf should it wish to. -- This data is not included in signing and hashing. ExtraData LONGBLOB, -- The timestamp from when this leaf data was first queued for inclusion. QueueTimestampNanos BIGINT NOT NULL, PRIMARY KEY(TreeId, LeafIdentityHash), FOREIGN KEY(TreeId) REFERENCES Trees(TreeId) ON DELETE CASCADE ); -- When a leaf is sequenced a row is added to this table. If logs allow duplicates then -- multiple rows will exist with different sequence numbers. The signed timestamp -- will be communicated via the unsequenced table as this might need to be unique, depending -- on the log parameters and we can't insert into this table until we have the sequence number -- which is not available at the time we queue the entry. We need both hashes because the -- LeafData table is keyed by the raw data hash. CREATE TABLE IF NOT EXISTS SequencedLeafData( TreeId BIGINT NOT NULL, SequenceNumber BIGINT UNSIGNED NOT NULL, -- This is a personality specific has of some subset of the leaf data. -- It's only purpose is to allow Trillian to identify duplicate entries in -- the context of the personality. LeafIdentityHash VARBINARY(255) NOT NULL, -- This is a MerkleLeafHash as defined by the treehasher that the log uses. For example for -- CT this hash will include the leaf prefix byte as well as the leaf data. MerkleLeafHash VARBINARY(255) NOT NULL, IntegrateTimestampNanos BIGINT NOT NULL, PRIMARY KEY(TreeId, SequenceNumber), FOREIGN KEY(TreeId) REFERENCES Trees(TreeId) ON DELETE CASCADE, FOREIGN KEY(TreeId, LeafIdentityHash) REFERENCES LeafData(TreeId, LeafIdentityHash) ON DELETE CASCADE ); CREATE INDEX SequencedLeafMerkleIdx ON SequencedLeafData(TreeId, MerkleLeafHash); CREATE TABLE IF NOT EXISTS Unsequenced( TreeId BIGINT NOT NULL, -- The bucket field is to allow the use of time based ring bucketed schemes if desired. If -- unused this should be set to zero for all entries. Bucket INTEGER NOT NULL, -- This is a personality specific hash of some subset of the leaf data. -- It's only purpose is to allow Trillian to identify duplicate entries in -- the context of the personality. LeafIdentityHash VARBINARY(255) NOT NULL, -- This is a MerkleLeafHash as defined by the treehasher that the log uses. For example for -- CT this hash will include the leaf prefix byte as well as the leaf data. MerkleLeafHash VARBINARY(255) NOT NULL, QueueTimestampNanos BIGINT NOT NULL, -- This is a SHA256 hash of the TreeID, LeafIdentityHash and QueueTimestampNanos. It is used -- for batched deletes from the table when trillian_log_server and trillian_log_signer are -- built with the batched_queue tag. QueueID VARBINARY(32) DEFAULT NULL UNIQUE, PRIMARY KEY (TreeId, Bucket, QueueTimestampNanos, LeafIdentityHash) ); sigsum-log-go-0.15.2/integration/test.sh000077500000000000000000000542141477305677000201570ustar00rootroot00000000000000#!/bin/bash # Example usage: # # $ ./test.sh # set -eu shopt -s nullglob trap cleanup EXIT declare -A nvars declare nodes="loga logb" declare -r loga=conf/primary.config declare -r logb=conf/secondary.config declare -r logc=conf/logc.config declare -r client=conf/client.config declare -r mysql_uri="${MYSQL_URI:-sigsum_test:zaphod@tcp(127.0.0.1:3306)/sigsum_test}" # Set based on --extended / --ephemeral options testflavor=basic function main() { while [[ $# > 0 ]] ; do case $1 in --extended) testflavor=extended ;; --ephemeral) testflavor=ephemeral ;; *) die "Unknown option: $1" ;; esac shift done # Change directory to where script is located. cd $(dirname $0) # Delete any state left from previous run. rm -rf ./tmp install_go_deps node_setup $loga $logb witness_start 7200 "${nvars[$loga:log_dir]}/ssrv.key.pub" cat > ./tmp/log.policy </dev/null) == FROZEN ]] || \ die "unable to freeze tree $tree_id" [[ $(./bin/updatetree --admin_server $srv -tree_id $tree_id -tree_type LOG -logtostderr 2>/dev/null) == FROZEN ]] || \ die "unable to change tree type to LOG for tree $tree_id" [[ $(./bin/updatetree --admin_server $srv -tree_id $tree_id -tree_state ACTIVE -logtostderr 2>/dev/null) == ACTIVE ]] || \ die "unable to unfreeze tree $tree_id" info "tree $tree_id type changed from PREORDERED_LOG to LOG" nvars[$new_primary:ssrv_role]=primary nvars[$new_primary:ssrv_interval]=5 # FIXME: parameterize info "copying key files" mv ${nvars[$prev_primary:log_dir]}/ssrv.key ${nvars[$new_primary:log_dir]}/ssrv.key mv ${nvars[$prev_primary:log_dir]}/ssrv.key.pub ${nvars[$new_primary:log_dir]}/ssrv.key.pub nvars[$new_primary:ssrv_key_hash]=${nvars[$prev_primary:ssrv_key_hash]} nvars[$new_primary:token]=${nvars[$prev_primary:token]} nvars[$new_primary:ssrv_agent]=${nvars[$prev_primary:ssrv_agent]} info "creating sth startup=local-tree" ./bin/sigsum-mktree --mode=local-tree --sth-file=${nvars[$new_primary:log_dir]}/sth-store } function trillian_setup() { for i in $@; do info "setting up Trillian ($i)" source $i nvars[$i:tsrv_rpc]=$tsrv_rpc nvars[$i:tseq_rpc]=$tseq_rpc done } # trillian_start starts trillian components and creates new trees function trillian_start() { trillian_start_server $@ trillian_start_sequencer $@ trillian_createtree $@ } function trillian_start_server() { for i in $@; do info "starting up Trillian server ($i)" ./bin/trillian_log_server\ -mysql_uri=${mysql_uri}\ -rpc_endpoint=${nvars[$i:tsrv_rpc]}\ -http_endpoint=""\ -log_dir=${nvars[$i:log_dir]} 2>/dev/null & nvars[$i:tsrv_pid]=$! info "started Trillian log server (pid ${nvars[$i:tsrv_pid]})" done } function trillian_start_sequencer() { for i in $@; do # no sequencer needed for secondaries [[ ${nvars[$i:ssrv_role]} == secondary ]] && continue info "starting up Trillian sequencer ($i)" ./bin/trillian_log_signer\ -force_master\ -mysql_uri=${mysql_uri}\ -rpc_endpoint=${nvars[$i:tseq_rpc]}\ -http_endpoint=""\ -log_dir=${nvars[$i:log_dir]} 2>/dev/null & nvars[$i:tseq_pid]=$! info "started Trillian log sequencer (pid ${nvars[$i:tseq_pid]})" done } function trillian_createtree() { for i in $@; do local createtree_extra_args="" [[ ${nvars[$i:ssrv_role]} == secondary ]] && createtree_extra_args=" -tree_type PREORDERED_LOG" local tree_id=$(./bin/createtree --admin_server ${nvars[$i:tsrv_rpc]} $createtree_extra_args -logtostderr 2>/dev/null) [[ $? -eq 0 ]] || die "must provision a new Merkle tree" info "provisioned Merkle tree with id ${tree_id}" echo "tree-id=${tree_id}" > ${nvars[$i:log_dir]}/tree-id done } function witness_start() { local port=$1; shift local log_key=$1; shift ./bin/sigsum-key generate -o ./tmp/wit1.key wit1_key_hash=$(./bin/sigsum-key to-hash -k ./tmp/wit1.key.pub) ./bin/sigsum-witness -k ./tmp/wit1.key --log-key "${log_key}" --state-file ./tmp/wit1.state localhost:${port} 2>./tmp/wit1.log & wit1_pid="$!" info "started wit1 (pid ${wit1_pid})" } function witness_stop() { [[ ${wit1_pid} ]] && kill ${wit1_pid} } function sigsum_setup() { for i in $@; do info "setting up Sigsum server ($i)" source $i nvars[$i:ssrv_role]=$ssrv_role nvars[$i:ssrv_endpoint]=$ssrv_endpoint nvars[$i:ssrv_internal]=$ssrv_internal nvars[$i:ssrv_prefix]=$ssrv_prefix nvars[$i:ssrv_interval]=$ssrv_interval_sec nvars[$i:ssrv_agent]=$ssrv_agent nvars[$i:log_url]=${nvars[$i:ssrv_endpoint]}/${nvars[$i:ssrv_prefix]} nvars[$i:int_url]=${nvars[$i:ssrv_internal]}/${nvars[$i:ssrv_prefix]} nvars[$i:metrics_url]=${nvars[$i:ssrv_internal]}/metrics ./bin/sigsum-key generate -o ${nvars[$i:log_dir]}/ssrv.key nvars[$i:ssrv_key_hash]=$(./bin/sigsum-key to-hash -k ${nvars[$i:log_dir]}/ssrv.key.pub) # Use special test.sigsum.org test key to generate token. nvars[$i:token]=$(./bin/sigsum-token create -k <(printf '%064x' 1) --log-key ${nvars[$i:log_dir]}/ssrv.key.pub) done } function sigsum_create_tree() { for i in $@; do if [[ ${nvars[$i:ssrv_role]} = primary ]] ; then info "creating sth startup=empty" ./bin/sigsum-mktree --sth-file=${nvars[$i:log_dir]}/sth-store fi done } function sigsum_start() { for i in $@; do local role=${nvars[$i:ssrv_role]} local binary=sigsum-log-primary; local extra_args="${nvars[$i:ssrv_extra_args]}" if [[ $role = primary ]]; then extra_args+=" --sth-file=${nvars[$i:log_dir]}/sth-store" extra_args+=" --policy-file=./tmp/log.policy" else binary=sigsum-log-secondary fi if [[ "$testflavor" = ephemeral ]] ; then extra_args+=" --backend ephemeral" else extra_args+=" --trillian-rpc-server=${nvars[$i:tsrv_rpc]}" extra_args+=" --trillian-tree-id-file=${nvars[$i:log_dir]}/tree-id" fi info "starting Sigsum log $role node ($i)" args="$extra_args \ --url-prefix=${nvars[$i:ssrv_prefix]} \ --interval=${nvars[$i:ssrv_interval]}s \ --external-endpoint=${nvars[$i:ssrv_endpoint]} \ --internal-endpoint=${nvars[$i:ssrv_internal]} \ --log-level=debug \ --log-file=${nvars[$i:log_dir]}/sigsum-log.log" if [[ ${nvars[$i:ssrv_agent]} = yes ]] ; then info "enabling ssh-agent for $role node ($i)" nvars[$i:ssrv_pid]=$( ./bin/sigsum-agent -k "${nvars[$i:log_dir]}/ssrv.key" --pid-file - \ ./bin/$binary $args --key-file=${nvars[$i:log_dir]}/ssrv.key.pub \ 2>${nvars[$i:log_dir]}/sigsum-log.$(date +%s).stderr & ) else ./bin/$binary $args --key-file=${nvars[$i:log_dir]}/ssrv.key \ 2>${nvars[$i:log_dir]}/sigsum-log.$(date +%s).stderr & nvars[$i:ssrv_pid]=$! fi info "started Sigsum log server on ${nvars[$i:ssrv_endpoint]} / ${nvars[$i:ssrv_internal]} (pid ${nvars[$i:ssrv_pid]})" done } function node_stop() { node_stop_fe $@ node_stop_be $@ } # Delete log tree for, requires trillian server ("backend") to be running function node_destroy() { if [[ "$testflavor" != ephemeral ]] ; then for i in $@; do local tree_id=$(cut -d= -f2 ${nvars[$i:log_dir]}/tree-id) if ! ./bin/deletetree -admin_server=$tsrv_rpc -log_id=${tree_id} -logtostderr 2>/dev/null; then warn "failed deleting provisioned Merkle tree ${tree_id}" else info "deleted provisioned Merkle tree ${tree_id}" fi done fi } function node_stop_fe() { for i in $@; do [[ -v nvars[$i:ssrv_pid] ]] && pp ${nvars[$i:ssrv_pid]} && kill ${nvars[$i:ssrv_pid]} # FIXME: why is SIGINT (often) not enough? if [[ "$testflavor" != ephemeral ]] ; then [[ -v nvars[$i:tseq_pid] ]] && pp ${nvars[$i:tseq_pid]} && kill -2 ${nvars[$i:tseq_pid]} while :; do sleep 1 [[ -v nvars[$i:tseq_pid] ]] && pp ${nvars[$i:tseq_pid]} && continue [[ -v nvars[$i:ssrv_pid] ]] && pp ${nvars[$i:ssrv_pid]} && continue break done info "stopped Trillian log sequencer ($i)" fi info "stopped Sigsum log server ($i)" done } function node_stop_be() { if [[ "$testflavor" != ephemeral ]] ; then for i in $@; do pp ${nvars[$i:tsrv_pid]} && kill ${nvars[$i:tsrv_pid]} while :; do sleep 1 pp ${nvars[$i:tsrv_pid]} && continue break done info "stopped Trillian log server ($i)" done fi } function cleanup() { set +e info "cleaning up, please wait..." witness_stop for var in $nodes; do declare -n cleanup_i=$var # Using unique iterator name, bc leaking node_stop_fe $cleanup_i done for var in $nodes; do declare -n cleanup_i=$var # Using unique iterator name, bc leaking node_destroy $cleanup_i done for var in $nodes; do declare -n cleanup_i=$var # Using unique iterator name, bc leaking node_stop_be $cleanup_i done } function check_setup() { sleep 3 for i in $@; do info "checking setup for $i" if [[ "$testflavor" != ephemeral ]] ; then if [[ ${nvars[$i:ssrv_role]} == primary ]]; then [[ -v nvars[$i:tseq_pid] ]] && pp ${nvars[$i:tseq_pid]} || die "must have Trillian log sequencer ($i)" fi [[ -v nvars[$i:tsrv_pid] ]] && pp ${nvars[$i:tsrv_pid]} || die "must have Trillian log server ($i)" fi [[ -v nvars[$i:ssrv_pid] ]] && pp ${nvars[$i:ssrv_pid]} || die "must have Sigsum log server ($i)" done } function run_tests() { local pri=$1; shift local sec=$1; shift local cli=$1; shift local start_leaf=$1; shift # 0-based local num_leaf=$1; shift info "running ordinary tests, pri=$pri, start_leaf=$start_leaf, num_leaf=$num_leaf" test_signed_tree_head $pri $start_leaf info "adding $num_leaf leaves" test_add_leaves $pri $cli $(( $start_leaf + 1 )) $num_leaf num_leaf=$(( $num_leaf + $start_leaf )) info "waiting for new signed tree head to be available..." sleep ${nvars[$pri:ssrv_interval]} test_signed_tree_head $pri $num_leaf for i in $(seq $(( $start_leaf + 1 )) $(( $num_leaf - 1 ))); do test_consistency_proof $pri $i $num_leaf done for i in $(seq $(( $start_leaf + 1 )) $num_leaf); do test_inclusion_proof $pri $cli $num_leaf $i $(( $i - 1 )) done for i in $(seq $(( $start_leaf + 1 )) $num_leaf); do test_get_leaf $pri $cli $i $(( $i - 1 )) done warn "no signatures and merkle proofs were verified" } run_tests_extended() { local pri=$1; shift local sec=$1; shift local cli=$1; shift local current_size=$1; shift local old_pri_sth_rsp=$1; shift info "running extended tests" info "wait for new primary and secondary to catch up and merge" sleep $(( ${nvars[$pri:ssrv_interval]} + ${nvars[$sec:ssrv_interval]} + 1 )) test_signed_tree_head $pri $current_size test_tree_heads_equal ${nvars[$pri:log_dir]}/rsp $old_pri_sth_rsp run_tests $pri $sec $cli $current_size 5 } function test_signed_tree_head() { local pri=$1; shift local size=$1; shift local log_dir=${nvars[$pri:log_dir]} local desc="GET get-tree-head (size $size)" curl -s -w "%{http_code}" ${nvars[$pri:log_url]}/get-tree-head \ >$log_dir/rsp if [[ $(status_code $pri) != 200 ]]; then fail "$desc: http status code $(status_code $pri)" return fi if ! keys $pri "size" "root_hash" "signature" "cosignature"; then fail "$desc: ascii keys in response $(debug_response $pri)" return fi now=$(date +%s) if [[ $(value_of $pri "size") != $size ]]; then fail "$desc: size $(value_of $pri "size")" return fi while read cs ; do # Check key hash found="" got=$(echo $cs | cut -d' ' -f1) for want in $wit1_key_hash; do if [[ $got == $want ]]; then found=true fi done if [[ -z $found ]]; then fail "$desc: missing witness $got" return fi # Check timestamp ts=$(echo $cs | cut -d' ' -f2) if [[ $ts -gt $now ]]; then fail "$desc: timestamp $(value_of $pri "timestamp") is too large" return fi if [[ $ts -lt $(( $now - ${nvars[$pri:ssrv_interval]} * 2 )) ]]; then fail "$desc: timestamp $(value_of $pri "timestamp") is too small" return fi done < <(value_of $pri cosignature) # TODO: verify tree head signature # TODO: verify tree head cosignatures pass $desc } function test_tree_heads_equal() { local rsp1=$1; shift local rsp2=$1; shift local desc="comparing tree heads ($rsp1, $rsp2)" n1_size=$(value_of_file $rsp1 "size") n2_size=$(value_of_file $rsp2 "size") if [[ $n1_size -ne $n2_size ]]; then fail "$desc: size: $n1_size != $n2_size" return fi n1_root_hash=$(value_of_file $rsp1 "root_hash") n2_root_hash=$(value_of_file $rsp2 "root_hash") if [[ $n1_root_hash != $n2_root_hash ]]; then fail "$desc: root_hash: $n1_root_hash != $n2_root_hash" return fi pass $desc } function test_inclusion_proof() { local pri=$1; shift local cli=$1; shift local size=$1; shift local data=$1; shift local index=$1; shift local log_dir=${nvars[$pri:log_dir]} local desc="GET get-inclusion-proof (size $size, data \"$data\", index $index)" local leaf_hash=$(echo ${data} | ./bin/sigsum-submit --leaf-hash -k ${nvars[$cli:log_dir]}/cli.key) curl -s -w "%{http_code}" ${nvars[$pri:log_url]}/get-inclusion-proof/${size}/${leaf_hash} >${log_dir}/rsp if [[ $(status_code $pri) != 200 ]]; then fail "$desc: http status code $(status_code $pri)" return fi if ! keys $pri "leaf_index" "node_hash"; then fail "$desc: ascii keys in response $(debug_response $pri)" return fi if [[ $(value_of $pri leaf_index) != ${index} ]]; then fail "$desc: wrong leaf index $(value_of $pri leaf_index)" return fi # TODO: verify inclusion proof pass $desc } function test_consistency_proof() { local pri=$1; shift local log_dir=${nvars[$pri:log_dir]} local desc="GET get-consistency-proof (old_size $1, new_size $2)" curl -s -w "%{http_code}" ${nvars[$pri:log_url]}/get-consistency-proof/$1/$2 >$log_dir/rsp if [[ $(status_code $pri) != 200 ]]; then fail "$desc: http status code $(status_code $pri)" return fi if ! keys $pri "node_hash"; then fail "$desc: ascii keys in response $(debug_response $pri)" return fi # TODO: verify consistency proof pass $desc } function test_get_leaf() { local pri=$1; shift local cli=$1; shift local data="$1"; shift local index="$1"; shift local log_dir=${nvars[$pri:log_dir]} local desc="GET get-leaves (data \"$data\", index $index)" curl -s -w "%{http_code}" ${nvars[$pri:log_url]}/get-leaves/$index/$((index + 1)) >$log_dir/rsp if [[ $(status_code $pri) != 200 ]]; then fail "$desc: http status code $(status_code $pri)" return fi if ! keys $pri "leaf"; then fail "$desc: ascii keys in response $(debug_response $pri)" return fi local message=$(openssl dgst -binary <(echo $data) | b16encode) local checksum=$(openssl dgst -binary <(echo $message | b16decode) | b16encode) if [[ $(value_of $pri leaf | cut -d' ' -f1) != $checksum ]]; then fail "$desc: wrong checksum $(value_of $pri checksum)" return fi if [[ $(value_of $pri leaf | cut -d' ' -f3) != ${nvars[$cli:cli_key_hash]} ]]; then fail "$desc: wrong key hash $(value_of $pri key_hash)" fi # TODO: check leaf signature pass $desc } function test_add_leaves() { local s=$1; shift local cli=$1; shift local start=$1; shift # integer, used as data and filename under subs/ local end=$(( $start + $1 - 1 )); shift # number of leaves to add local desc="add leaves" local log_dir=${nvars[$s:log_dir]} [[ -d $log_dir/subs/$s ]] || mkdir -p $log_dir/subs/$s local -a rc for i in $(seq $start $end); do rc[$i]=$(add_leaf $s $cli $i) done # TODO: bail out and fail after $timeout seconds while true; do local keep_going=0 for i in $(seq $start $end); do if [[ ${rc[$i]} -eq 202 ]]; then keep_going=1 break fi done [[ $keep_going -eq 0 ]] && break sleep 1 for i in $(seq $start $end); do if [[ ${rc[$i]} -eq 202 ]]; then rc[$i]=$(add_leaf $s $cli $i) if [[ ${rc[$i]} -eq 200 ]]; then if ! keys $s; then fail "$desc (data \"$i\"): ascii keys in response $(debug_response $s)" fi fi fi done done local all_good=1 for i in $(seq $start $end); do if [[ ${rc[$i]} -ne 200 ]]; then fail "$desc (data \"$i\") HTTP status code: ${rc[$i]}" all_good=0 fi echo ${rc[$i]} > "$log_dir/subs/$s/$i" done [[ $all_good -eq 1 ]] && pass $desc } function add_leaf() { local s=$1; shift local cli=$1; shift local data="$1"; shift local log_dir=${nvars[$s:log_dir]} echo $data | ./bin/sigsum-submit -k ${nvars[$cli:log_dir]}/cli.key | tee $log_dir/req | curl -s -w "%{http_code}" -H "sigsum-token: test.sigsum.org ${nvars[$s:token]}" \ --data-binary @- ${nvars[$s:log_url]}/add-leaf \ >$log_dir/rsp echo $(status_code $s) } function get_metrics() { local i=$1; shift info "Querying metrics for $i" curl -s ${nvars[$i:metrics_url]} > ${nvars[$i:log_dir]}/metrics # Check that metrics include measurement of at least one # get-*tree-head request, with latency up to 1s. if grep '^http_latency_bucket{endpoint="get-[^"]*tree-head".*,status="200",le="1"} [1-9][0-9]*$' >/dev/null ${nvars[$i:log_dir]}/metrics; then pass "got $i metrics" return 0 else fail "no $i metrics" return 1 fi } function get_infopage() { local i=$1; shift info "Querying info page for $i" curl -L -s ${nvars[$i:ssrv_endpoint]}/ > ${nvars[$i:log_dir]}/info.html if grep "Software version:" >/dev/null ${nvars[$i:log_dir]}/info.html; then pass "got $i info page" return 0 else fail "no $i info page" return 1 fi } function debug_response() { local i=$1; shift echo "" cat ${nvars[$i:log_dir]}/rsp } function status_code() { local i=$1; shift tail -n1 ${nvars[$i:log_dir]}/rsp } function value_of() { local s=$1; shift value_of_file ${nvars[$s:log_dir]}/rsp $@ } function value_of_file() { local rsp=$1; shift while read line; do key=$(echo $line | cut -d"=" -f1) if [[ $key != $1 ]]; then continue fi value=$(echo $line | cut -d"=" -f2) echo $value done < <(head --lines=-1 $rsp) } function keys() { local s=$1; shift declare -A map map[thedummystring]=to_avoid_error_on_size_zero while read line; do key=$(echo $line | cut -d"=" -f1) map[$key]=ok done < <(head --lines=-1 ${nvars[$s:log_dir]}/rsp) if [[ $# != $(( ${#map[@]} - 1 )) ]]; then return 1 fi for key in $@; do if [[ -z ${map[$key]} ]]; then return 1 fi done return 0 } # Is proces with PID $1 running or not? function pp() { [[ $1 == -p ]] && shift [[ -d /proc/$1 ]] } function b16encode { python3 -c 'import sys; sys.stdout.write(sys.stdin.buffer.read().hex())' } function b16decode { python3 -c 'import sys; sys.stdout.buffer.write(bytes.fromhex(sys.stdin.read()))' } function die() { echo -e "\e[37m$(date +"%y-%m-%d %H:%M:%S %Z")\e[0m [\e[31mFATA\e[0m] $@" >&2 exit 1 } function info() { echo -e "\e[37m$(date +"%y-%m-%d %H:%M:%S %Z")\e[0m [\e[94mINFO\e[0m] $@" >&2 } function warn() { echo -e "\e[37m$(date +"%y-%m-%d %H:%M:%S %Z")\e[0m [\e[93mWARN\e[0m] $@" >&2 } function pass() { echo -e "\e[37m$(date +"%y-%m-%d %H:%M:%S %Z")\e[0m [\e[32mPASS\e[0m] $@" >&2 } function fail() { echo -e "\e[37m$(date +"%y-%m-%d %H:%M:%S %Z")\e[0m [\e[91mFAIL\e[0m] $@" >&2 } main $@ # Local Variables: # sh-basic-offset: 8 # End: sigsum-log-go-0.15.2/internal/000077500000000000000000000000001477305677000161245ustar00rootroot00000000000000sigsum-log-go-0.15.2/internal/config/000077500000000000000000000000001477305677000173715ustar00rootroot00000000000000sigsum-log-go-0.15.2/internal/config/config.go000066400000000000000000000100651477305677000211670ustar00rootroot00000000000000package config import ( "fmt" "io" "os" "time" "github.com/BurntSushi/toml" "github.com/pborman/getopt/v2" ) // Primary Config type Primary struct { PolicyFile string `toml:"policy-file"` RateLimitFile string `toml:"rate-limit-file"` AllowTestDomain bool `toml:"allow-test-domain"` SecondaryURL string `toml:"secondary-url"` SecondaryPubkeyFile string `toml:"secondary-pubkey-file"` SthFile string `toml:"sth-file"` MaxRange int `toml:"max-range"` } // Secondary Config type Secondary struct { PrimaryURL string `toml:"primary-url"` } type Config struct { Prefix string `toml:"url-prefix"` Timeout time.Duration `toml:"timeout"` Interval time.Duration `toml:"interval"` LogFile string `toml:"log-file"` LogLevel string `toml:"log-level"` ExternalEndpoint string `toml:"external-endpoint"` InternalEndpoint string `toml:"internal-endpoint"` TrillianRpcServer string `toml:"trillian-rpc-server"` Backend string `toml:"backend"` TrillianTreeIDFile string `toml:"trillian-tree-id-file"` KeyFile string `toml:"key-file"` Primary `toml:"primary"` Secondary `toml:"secondary"` } func NewConfig() *Config { // Initialize default configuration return &Config{ ExternalEndpoint: "localhost:6965", InternalEndpoint: "localhost:6967", TrillianRpcServer: "localhost:6962", Backend: "trillian", Prefix: "", TrillianTreeIDFile: "/var/lib/sigsum-log/tree-id", Timeout: time.Second * 10, KeyFile: "", Interval: time.Second * 30, LogFile: "", LogLevel: "info", Primary: Primary{ PolicyFile: "", RateLimitFile: "", AllowTestDomain: false, SecondaryURL: "", SecondaryPubkeyFile: "", SthFile: "/var/lib/sigsum-log/sth", MaxRange: 512, }, Secondary: Secondary{ PrimaryURL: "", }, } } func LoadConfig(f io.Reader) (*Config, error) { conf := NewConfig() metadata, err := toml.NewDecoder(f).Decode(&conf) if err != nil { return nil, err } if undecoded := metadata.Undecoded(); len(undecoded) > 0 { return nil, fmt.Errorf("unknown keywords: %v", undecoded) } return conf, nil } func OpenConfigFile() (io.Reader, error) { var f io.Reader var err error if conf, b := os.LookupEnv("SIGSUM_LOGSERVER_CONFIG"); b { if f, err = os.Open(conf); err == nil { return f, nil } else { return f, err } } default_config := "/etc/sigsum/config.toml" if f, err = os.Open(default_config); err == nil { return f, nil } else { return f, err } } func (c *Config) ServerFlags(set *getopt.Set) { set.FlagLong(&c.ExternalEndpoint, "external-endpoint", 0, "TCP listen port for serving clients.", "host:port") set.FlagLong(&c.InternalEndpoint, "internal-endpoint", 0, "Internal TCP listen port, for metrics and replication with other nodes.", "host:port") set.FlagLong(&c.TrillianRpcServer, "trillian-rpc-server", 0, "TCP port for Trillian backend server.", "host:port") set.FlagLong(&c.Backend, "backend", 0, "Either \"trillian\" (connect to an external Trillian server) or \"ephemeral\" (use in-memory backend, with NO persistent storage.") set.FlagLong(&c.Prefix, "url-prefix", 0, "Optional URL prefix, preceding endpoint names such as /get-tree-head.", "string") set.FlagLong(&c.TrillianTreeIDFile, "trillian-tree-id-file", 0, "Trillian backend tree identifier.", "file") set.FlagLong(&c.Timeout, "timeout", 0, "Timeout for outgoing requests.") set.FlagLong(&c.KeyFile, "key-file", 0, "Key file (openssh format), either an unencrypted private key, or a public key (accessed via ssh-agent).", "file") set.FlagLong(&c.Interval, "interval", 0, "Interval used to rotate the log's cosigned tree head.") set.FlagLong(&c.LogFile, "log-file", 0, "File to write logs to, or stderr if unset.", "file") set.FlagLong(&c.LogLevel, "log-level", 0, "Log level (Available options: debug, info, warning, error).", "level") } sigsum-log-go-0.15.2/internal/config/config_test.go000066400000000000000000000024011477305677000222210ustar00rootroot00000000000000package config import ( "os" "strings" "testing" ) var testConfig = ` external-endpoint = "localhost:6965" internal-endpoint = "localhost:6967" trillian-rpc-server = "localhost:6962" backend = "trillian" url-prefix = "" trillian-tree-id-file = "/var/lib/sigsum-log/tree-id" timeout = "10s" interval = "30s" key-file = "test" log-file = "" log-level = "info" [primary] max-range = 10 rate-limit-file = "" allow-test-domain = false secondary-url = "" secondary-pubkey-file = "" sth-file = "/var/lib/sigsum-log/sth" [secondary] primary-url = "http://localhost:9091" ` func TestReadConfig(t *testing.T) { r := strings.NewReader(testConfig) conf, err := LoadConfig(r) if err != nil { t.Fatalf("Failed read configuration: %v", err) } if conf.Primary.SthFile != "/var/lib/sigsum-log/sth" { t.Fatalf("Failed to parse primary configuration") } if conf.Secondary.PrimaryURL != "http://localhost:9091" { t.Fatalf("Failed to parse primary configuration") } } func TestReadExampleConfigFile(t *testing.T) { example_config := "../../doc/config.toml.example" confFile, err := os.Open(example_config) if err != nil { t.Fatalf("Failed to open example_config file") } _, err = LoadConfig(confFile) if err != nil { t.Fatalf("Failed read configuration: %v", err) } } sigsum-log-go-0.15.2/internal/db/000077500000000000000000000000001477305677000165115ustar00rootroot00000000000000sigsum-log-go-0.15.2/internal/db/client.go000066400000000000000000000014351477305677000203210ustar00rootroot00000000000000package db import ( "context" "errors" "sigsum.org/sigsum-go/pkg/requests" "sigsum.org/sigsum-go/pkg/types" ) type AddLeafStatus struct { AlreadyExists bool IsSequenced bool } var ErrNotIncluded = errors.New("not included") // Client is an interface that interacts with a log's database backend type Client interface { AddLeaf(context.Context, *types.Leaf, uint64) (AddLeafStatus, error) AddSequencedLeaves(ctx context.Context, leaves []types.Leaf, index int64) error GetTreeHead(context.Context) (types.TreeHead, error) GetConsistencyProof(context.Context, *requests.ConsistencyProof) (types.ConsistencyProof, error) GetInclusionProof(context.Context, *requests.InclusionProof) (types.InclusionProof, error) GetLeaves(context.Context, *requests.Leaves) ([]types.Leaf, error) } sigsum-log-go-0.15.2/internal/db/memory.go000066400000000000000000000060611477305677000203530ustar00rootroot00000000000000package db import ( "context" "fmt" "sync" "sigsum.org/sigsum-go/pkg/crypto" "sigsum.org/sigsum-go/pkg/merkle" "sigsum.org/sigsum-go/pkg/requests" "sigsum.org/sigsum-go/pkg/types" ) type leafBlob [2*crypto.HashSize + crypto.SignatureSize]byte type MemoryDb struct { mu sync.RWMutex leafs []leafBlob tree merkle.Tree } func NewMemoryDb() Client { return &MemoryDb{tree: merkle.NewTree()} } func (db *MemoryDb) AddLeaf(_ context.Context, leaf *types.Leaf, treeSize uint64) (AddLeafStatus, error) { var blob leafBlob copy(blob[:], leaf.ToBinary()) h := merkle.HashLeafNode(blob[:]) db.mu.Lock() defer db.mu.Unlock() if !db.tree.AddLeafHash(&h) { i, err := db.tree.GetLeafIndex(&h) return AddLeafStatus{ AlreadyExists: true, IsSequenced: err == nil && i < treeSize, }, nil } db.leafs = append(db.leafs, blob) return AddLeafStatus{}, nil } func (db *MemoryDb) AddSequencedLeaves(_ context.Context, leaves []types.Leaf, index int64) error { db.mu.Lock() defer db.mu.Unlock() if db.tree.Size() != uint64(index) { return fmt.Errorf("incorrect index %d, tree size %d", index, db.tree.Size()) } for i, leaf := range leaves { var blob leafBlob copy(blob[:], leaf.ToBinary()) h := merkle.HashLeafNode(blob[:]) if !db.tree.AddLeafHash(&h) { // TODO: What state can callers expect on error? return fmt.Errorf("unexpected duplicate at index %d", index+int64(i)) } db.leafs = append(db.leafs, blob) } return nil } func (db *MemoryDb) GetTreeHead(_ context.Context) (types.TreeHead, error) { db.mu.RLock() defer db.mu.RUnlock() return types.TreeHead{ Size: uint64(db.tree.Size()), RootHash: db.tree.GetRootHash(), }, nil } func (db *MemoryDb) GetConsistencyProof(_ context.Context, req *requests.ConsistencyProof) (types.ConsistencyProof, error) { db.mu.RLock() defer db.mu.RUnlock() path, err := db.tree.ProveConsistency(req.OldSize, req.NewSize) if err != nil { return types.ConsistencyProof{}, err } return types.ConsistencyProof{Path: path}, nil } func (db *MemoryDb) GetInclusionProof(_ context.Context, req *requests.InclusionProof) (types.InclusionProof, error) { db.mu.RLock() defer db.mu.RUnlock() index, err := db.tree.GetLeafIndex(&req.LeafHash) if err != nil || index >= req.Size { return types.InclusionProof{}, ErrNotIncluded } path, err := db.tree.ProveInclusion(index, req.Size) if err != nil { return types.InclusionProof{}, err } return types.InclusionProof{ LeafIndex: index, Path: path, }, nil } func (db *MemoryDb) GetLeaves(_ context.Context, req *requests.Leaves) ([]types.Leaf, error) { db.mu.RLock() defer db.mu.RUnlock() size := db.tree.Size() if req.StartIndex >= size || req.EndIndex > size || req.StartIndex >= req.EndIndex { return nil, fmt.Errorf("out of range request: start %d, end %d, size %d\n", req.StartIndex, req.EndIndex, size) } list := make([]types.Leaf, req.EndIndex-req.StartIndex) for i, _ := range list { if err := list[i].FromBinary(db.leafs[i+int(req.StartIndex)][:]); err != nil { panic(fmt.Errorf("internal error: %v", err)) } } return list, nil } sigsum-log-go-0.15.2/internal/db/memory_test.go000066400000000000000000000112211477305677000214040ustar00rootroot00000000000000package db import ( "encoding/binary" "testing" "sigsum.org/sigsum-go/pkg/crypto" "sigsum.org/sigsum-go/pkg/merkle" "sigsum.org/sigsum-go/pkg/requests" "sigsum.org/sigsum-go/pkg/types" ) func TestMemoryAddLeaf(t *testing.T) { leaves := newLeaves(2) db := NewMemoryDb() for _, table := range []struct { desc string leaf *types.Leaf size uint64 want AddLeafStatus }{ {"new leaf", &leaves[0], 0, AddLeafStatus{}}, {"existing leaf", &leaves[0], 0, AddLeafStatus{AlreadyExists: true}}, {"sequenced leaf", &leaves[0], 1, AddLeafStatus{AlreadyExists: true, IsSequenced: true}}, // Corner case; this backend sequences leaves immediately. {"second leaf", &leaves[1], 1, AddLeafStatus{}}, } { status, err := db.AddLeaf(nil, table.leaf, table.size) if err != nil { t.Fatalf("AddLeaf failed in test: %q: %v", table.desc, err) } if status != table.want { t.Errorf("got status %#v, wanted %#v in test: %q", status, table.want, table.desc) } } } func TestMemoryAddSequencedLeaves(t *testing.T) { leaves := newLeaves(5) db := NewMemoryDb() if _, err := db.AddLeaf(nil, &leaves[0], 0); err != nil { t.Fatalf("AddLeaf of initial leaf failed: %v", err) } if err := db.AddSequencedLeaves(nil, leaves[1:], 0); err == nil { t.Fatalf("AddSequencedLeaves with bad index unexpectedly succeeded") } if err := db.AddSequencedLeaves(nil, leaves[1:], 1); err != nil { t.Fatalf("AddSequencedLeaves (1:5) failed: %v", err) } } func TestMemoryGetLeaves(t *testing.T) { leaves := newLeaves(5) db := NewMemoryDb() if err := db.AddSequencedLeaves(nil, leaves[:], 0); err != nil { t.Fatalf("AddSequencedLeaves failed: %v", err) } for start := 0; start <= 5; start++ { for end := 0; start <= 5; start++ { res, err := db.GetLeaves(nil, &requests.Leaves{uint64(start), uint64(end)}) if start >= end { if err == nil { t.Errorf("no error for invalid range start %d, end %d", start, end) } } else if err != nil { t.Errorf("GetLeaves failed for range start %d, end %d: %v", start, end, err) } else if len(res) != end-start+1 { t.Errorf("unexpected result len %d for range start %d, end %d", len(res), start, end) } else { for i := start; i <= end; i++ { if res[i] != leaves[start+i] { t.Errorf("wrong leaf data for leaf %d (start %d): got %#v, wanted: %#v", start+i, start, res[i], leaves[start+i]) } } } } } } func TestMemoryInclusionProof(t *testing.T) { leaves := newLeaves(5) db := NewMemoryDb() for i, leaf := range leaves { if _, err := db.AddLeaf(nil, &leaf, 0); err != nil { t.Fatalf("AddLeaf failed of leaf %d failed: %v", i, err) } } th, err := db.GetTreeHead(nil) if err != nil { t.Fatalf("GetTreeHead failed: %v", err) } if th.Size != 5 { t.Fatalf("unexpected tree size, got %d, expected 5", th.Size) } for i, leaf := range leaves { leafHash := merkle.HashLeafNode(leaf.ToBinary()) proof, err := db.GetInclusionProof(nil, &requests.InclusionProof{ LeafHash: leafHash, Size: 5, }) if err != nil { t.Errorf("GetInclusionProof for leaf %d failed: %v", i, err) } else if proof.LeafIndex != uint64(i) { t.Errorf("GetInclusionProof index: got %d, wanted %d", proof.LeafIndex, i) } else if err := proof.Verify(&leafHash, &th); err != nil { t.Errorf("inclusion path for leaf %d is invalid: %v", i, err) } } } func TestMemoryconsistencyProof(t *testing.T) { leaves := newLeaves(5) treeHeads := []types.TreeHead{{Size: 0, RootHash: merkle.HashEmptyTree()}} db := NewMemoryDb() for i, leaf := range leaves { if _, err := db.AddLeaf(nil, &leaf, 0); err != nil { t.Fatalf("AddLeaf failed of leaf %d failed: %v", i, err) } th, err := db.GetTreeHead(nil) if err != nil { t.Fatalf("GetTreeHead failed after leaf %d: %v", i, err) } if th.Size != uint64(i)+1 { t.Fatalf("GetTreeHead return unexpected tree size %d after leaf %d", th.Size, i) } treeHeads = append(treeHeads, th) } for oldSize := 0; oldSize <= 5; oldSize++ { for newSize := oldSize; newSize <= 5; newSize++ { proof, err := db.GetConsistencyProof(nil, &requests.ConsistencyProof{ OldSize: uint64(oldSize), NewSize: uint64(newSize), }) if err != nil { t.Errorf("GetConsistencyProof failed for oldSize %d, newSize %d: %v", oldSize, newSize, err) } else if err := proof.Verify(&treeHeads[oldSize], &treeHeads[newSize]); err != nil { t.Errorf("consistent path for oldSize %d, newSize %d is invalid: %v", oldSize, newSize, err) } } } } func newLeaves(n int) []types.Leaf { leaves := make([]types.Leaf, n) for i := 0; i < n; i++ { var blob [8]byte binary.BigEndian.PutUint64(blob[:], uint64(i)) leaves[i].Checksum = crypto.HashBytes(blob[:]) } return leaves } sigsum-log-go-0.15.2/internal/db/trillian.go000066400000000000000000000235731477305677000206700ustar00rootroot00000000000000package db import ( "context" "errors" "fmt" "os" "time" "github.com/google/trillian" trillianTypes "github.com/google/trillian/types" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "sigsum.org/sigsum-go/pkg/ascii" "sigsum.org/sigsum-go/pkg/crypto" "sigsum.org/sigsum-go/pkg/log" "sigsum.org/sigsum-go/pkg/merkle" "sigsum.org/sigsum-go/pkg/requests" "sigsum.org/sigsum-go/pkg/types" ) // TrillianClient implements the Client interface for Trillian's gRPC backend type TrillianClient struct { // treeID is a Merkle tree identifier that Trillian uses treeID int64 // logClient is a Trillian gRPC client logClient trillian.TrillianLogClient } type TreeType int const ( PrimaryTree TreeType = iota SecondaryTree ) // This is an error if it happens for a get-inclusion-proof request // from a log client, but we must accept it when we internally ask for // an inclusion proof for the very first leaf. var errEmptyInclusionProof = errors.New("not an inclusion proof: empty") func (treeType TreeType) checkTrillianTreeType(trillianType trillian.TreeType) error { switch treeType { case PrimaryTree: if trillianType != trillian.TreeType_LOG { return fmt.Errorf("trillian tree of type %s, but must be of type LOG for a Sigsum primary", trillianType.String()) } case SecondaryTree: if trillianType != trillian.TreeType_PREORDERED_LOG { return fmt.Errorf("trillian tree of type %s, but must be of type PREORDERED_LOG for a Sigsum secondary", trillianType.String()) } default: panic(fmt.Sprintf("internal error, invalid tree type %d", treeType)) } return nil } func DialTrillian(target string, timeout time.Duration, treeType TreeType, treeIdFile string) (*TrillianClient, error) { treeId, err := readTreeId(treeIdFile) if err != nil { return nil, fmt.Errorf("failed to read tree id: %v", err) } conn, err := grpc.Dial(target, grpc.WithInsecure(), grpc.WithBlock(), grpc.WithTimeout(timeout)) if err != nil { return nil, fmt.Errorf("connection to trillian failed: %v", err) } tree, err := trillian.NewTrillianAdminClient(conn).GetTree( context.Background(), &trillian.GetTreeRequest{TreeId: int64(treeId)}) if err != nil { return nil, err } if err := treeType.checkTrillianTreeType(tree.TreeType); err != nil { return nil, err } return &TrillianClient{ treeID: int64(treeId), logClient: trillian.NewTrillianLogClient(conn), }, nil } // AddLeaf adds a leaf to the tree and returns true if the leaf has // been sequenced into the tree of size treeSize. func (c *TrillianClient) AddLeaf(ctx context.Context, leaf *types.Leaf, treeSize uint64) (AddLeafStatus, error) { serialized := leaf.ToBinary() log.Debug("queueing leaf request: %x", merkle.HashLeafNode(serialized)) queueLeafResponse, err := c.logClient.QueueLeaf(ctx, &trillian.QueueLeafRequest{ LogId: c.treeID, Leaf: &trillian.LogLeaf{ LeafValue: serialized, }, }) alreadyExists := false if err != nil { if status.Code(err) != codes.AlreadyExists { return AddLeafStatus{}, fmt.Errorf("back-end rpc failure: %v", err) } alreadyExists = true } else { // It can happen that queueLeafResponse is nil even when err is nil so we must check that here if queueLeafResponse != nil { if s := queueLeafResponse.QueuedLeaf.Status; s != nil && codes.Code(s.Code) == codes.AlreadyExists { alreadyExists = true } } } if treeSize == 0 { // Certainly not sequenced, and passing treeSize = 0 to Trillian results in an InvalidArgument response. return AddLeafStatus{AlreadyExists: alreadyExists, IsSequenced: false}, nil } _, err = c.GetInclusionProof(ctx, &requests.InclusionProof{treeSize, merkle.HashLeafNode(serialized)}) switch err { case nil: return AddLeafStatus{AlreadyExists: alreadyExists, IsSequenced: true}, nil case ErrNotIncluded: return AddLeafStatus{AlreadyExists: alreadyExists, IsSequenced: false}, nil case errEmptyInclusionProof: if treeSize == 1 { // An empty proof is expected, and means that the leaf is present. return AddLeafStatus{AlreadyExists: alreadyExists, IsSequenced: true}, nil } fallthrough default: return AddLeafStatus{}, fmt.Errorf("back-end rpc failure: %v", err) } } // AddSequencedLeaves adds a set of already sequenced leaves to the tree. func (c *TrillianClient) AddSequencedLeaves(ctx context.Context, leaves []types.Leaf, index int64) error { trilLeaves := make([]*trillian.LogLeaf, len(leaves)) for i, leaf := range leaves { trilLeaves[i] = &trillian.LogLeaf{ LeafValue: leaf.ToBinary(), LeafIndex: index + int64(i), } } req := trillian.AddSequencedLeavesRequest{ LogId: c.treeID, Leaves: trilLeaves, } log.Debug("adding sequenced leaves: count %d", len(trilLeaves)) var err error for wait := 1; wait < 30; wait *= 2 { var rsp *trillian.AddSequencedLeavesResponse rsp, err = c.logClient.AddSequencedLeaves(ctx, &req) switch status.Code(err) { case codes.ResourceExhausted: log.Info("waiting %d seconds before retrying to add %d leaves, reason: %v", wait, len(trilLeaves), err) time.Sleep(time.Second * time.Duration(wait)) continue case codes.OK: if rsp == nil { return fmt.Errorf("logClient.AddSequencedLeaves no response") } // FIXME: check rsp.Results.QueuedLogLeaf return nil default: return fmt.Errorf("logClient.AddSequencedLeaves error: %v", err) } } return fmt.Errorf("giving up on adding %d leaves", len(trilLeaves)) } func (c *TrillianClient) GetTreeHead(ctx context.Context) (types.TreeHead, error) { rsp, err := c.logClient.GetLatestSignedLogRoot(ctx, &trillian.GetLatestSignedLogRootRequest{ LogId: c.treeID, }) if err != nil { return types.TreeHead{}, fmt.Errorf("backend failure: %v", err) } if rsp == nil { return types.TreeHead{}, fmt.Errorf("no response") } if rsp.SignedLogRoot == nil { return types.TreeHead{}, fmt.Errorf("no signed log root") } if rsp.SignedLogRoot.LogRoot == nil { return types.TreeHead{}, fmt.Errorf("no log root") } var r trillianTypes.LogRootV1 if err := r.UnmarshalBinary(rsp.SignedLogRoot.LogRoot); err != nil { return types.TreeHead{}, fmt.Errorf("no log root: unmarshal failed: %v", err) } if len(r.RootHash) != crypto.HashSize { return types.TreeHead{}, fmt.Errorf("unexpected hash length: %d", len(r.RootHash)) } return treeHeadFromLogRoot(&r), nil } func (c *TrillianClient) GetConsistencyProof(ctx context.Context, req *requests.ConsistencyProof) (types.ConsistencyProof, error) { // Trivial cases with empty proof. if req.OldSize == 0 || req.OldSize == req.NewSize { return types.ConsistencyProof{}, nil } rsp, err := c.logClient.GetConsistencyProof(ctx, &trillian.GetConsistencyProofRequest{ LogId: c.treeID, FirstTreeSize: int64(req.OldSize), SecondTreeSize: int64(req.NewSize), }) if err != nil { return types.ConsistencyProof{}, fmt.Errorf("backend failure: %v", err) } if rsp == nil { return types.ConsistencyProof{}, fmt.Errorf("no response") } if rsp.Proof == nil { return types.ConsistencyProof{}, fmt.Errorf("no consistency proof") } path, err := nodePathFromHashes(rsp.Proof.Hashes) if err != nil { return types.ConsistencyProof{}, fmt.Errorf("not a consistency proof: %v", err) } return types.ConsistencyProof{Path: path}, nil } func (c *TrillianClient) GetInclusionProof(ctx context.Context, req *requests.InclusionProof) (types.InclusionProof, error) { rsp, err := c.logClient.GetInclusionProofByHash(ctx, &trillian.GetInclusionProofByHashRequest{ LogId: c.treeID, LeafHash: req.LeafHash[:], TreeSize: int64(req.Size), OrderBySequence: true, }) if err != nil { if status.Code(err) == codes.NotFound { return types.InclusionProof{}, ErrNotIncluded } return types.InclusionProof{}, fmt.Errorf("backend failure: %v", err) } if rsp == nil { return types.InclusionProof{}, ErrNotIncluded } if len(rsp.Proof) != 1 { return types.InclusionProof{}, fmt.Errorf("bad proof count: %d", len(rsp.Proof)) } proof := rsp.Proof[0] if len(proof.Hashes) == 0 { return types.InclusionProof{}, errEmptyInclusionProof } path, err := nodePathFromHashes(proof.Hashes) if err != nil { return types.InclusionProof{}, fmt.Errorf("not an inclusion proof: %v", err) } return types.InclusionProof{ LeafIndex: uint64(proof.LeafIndex), Path: path, }, nil } func (c *TrillianClient) GetLeaves(ctx context.Context, req *requests.Leaves) ([]types.Leaf, error) { rsp, err := c.logClient.GetLeavesByRange(ctx, &trillian.GetLeavesByRangeRequest{ LogId: c.treeID, StartIndex: int64(req.StartIndex), Count: int64(req.EndIndex - req.StartIndex), }) if err != nil { return nil, fmt.Errorf("backend failure: %v", err) } if rsp == nil { return nil, fmt.Errorf("no response") } if got, want := len(rsp.Leaves), int(req.EndIndex-req.StartIndex); got != want { return nil, fmt.Errorf("unexpected number of leaves: %d", got) } list := make([]types.Leaf, 0, len(rsp.Leaves)) for i, leaf := range rsp.Leaves { leafIndex := int64(req.StartIndex + uint64(i)) if leafIndex != leaf.LeafIndex { return nil, fmt.Errorf("unexpected leaf(%d): got index %d", leafIndex, leaf.LeafIndex) } var l types.Leaf if err := l.FromBinary(leaf.LeafValue); err != nil { return nil, fmt.Errorf("unexpected leaf(%d): %v", leafIndex, err) } list = append(list[:], l) } return list, nil } func treeHeadFromLogRoot(lr *trillianTypes.LogRootV1) types.TreeHead { th := types.TreeHead{ Size: uint64(lr.TreeSize), } copy(th.RootHash[:], lr.RootHash) return th } func nodePathFromHashes(hashes [][]byte) ([]crypto.Hash, error) { path := make([]crypto.Hash, len(hashes)) for i := 0; i < len(hashes); i++ { if len(hashes[i]) != crypto.HashSize { return nil, fmt.Errorf("unexpected hash length: %v", len(hashes[i])) } copy(path[i][:], hashes[i]) } return path, nil } func readTreeId(file string) (uint64, error) { f, err := os.Open(file) if err != nil { return 0, err } defer f.Close() p := ascii.NewParser(f) return p.GetInt("tree-id") } sigsum-log-go-0.15.2/internal/db/trillian_test.go000066400000000000000000000315751477305677000217300ustar00rootroot00000000000000package db import ( "bytes" "context" "fmt" "reflect" "testing" "github.com/golang/mock/gomock" "github.com/google/trillian" ttypes "github.com/google/trillian/types" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" mocksTrillian "sigsum.org/log-go/internal/mocks/trillian" "sigsum.org/sigsum-go/pkg/crypto" "sigsum.org/sigsum-go/pkg/requests" "sigsum.org/sigsum-go/pkg/types" ) // TODO: Add TestAddSequencedLeaves func TestAddLeaf(t *testing.T) { leaf := &types.Leaf{ Checksum: crypto.Hash{}, Signature: crypto.Signature{}, KeyHash: crypto.Hash{}, } for _, table := range []struct { description string leaf *types.Leaf rsp *trillian.QueueLeafResponse queueLeafErr error inclusionProofErr error wantErr bool wantSequenced bool }{ { description: "invalid: backend failure", leaf: leaf, queueLeafErr: fmt.Errorf("something went wrong"), wantErr: true, }, { description: "unsequenced", leaf: leaf, queueLeafErr: nil, inclusionProofErr: status.Error(codes.NotFound, "not found"), wantErr: false, wantSequenced: false, }, { description: "sequenced", leaf: leaf, queueLeafErr: nil, inclusionProofErr: nil, wantErr: false, wantSequenced: true, }, } { // Run deferred functions at the end of each iteration t.Run(table.description, func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() grpc := mocksTrillian.NewMockTrillianLogClient(ctrl) grpc.EXPECT().QueueLeaf(gomock.Any(), gomock.Any()).Return(table.rsp, table.queueLeafErr) if table.queueLeafErr == nil { grpc.EXPECT().GetInclusionProofByHash(gomock.Any(), gomock.Any()).Return( // returns a fake inclusion proof just to pass validation in GetInclusionProof &trillian.GetInclusionProofByHashResponse{ Proof: []*trillian.Proof{{LeafIndex: 1, Hashes: [][]byte{make([]byte, crypto.HashSize)}}}, }, table.inclusionProofErr, ) } client := TrillianClient{logClient: grpc} status, err := client.AddLeaf(context.Background(), table.leaf, 1) if got, want := err != nil, table.wantErr; got != want { t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) } if err != nil { return } if status.IsSequenced != table.wantSequenced { t.Errorf("got sequenced == %v, expected %v", status.IsSequenced, table.wantSequenced) } }) } } func TestGetTreeHead(t *testing.T) { // valid root root := &ttypes.LogRootV1{ TreeSize: 0, RootHash: make([]byte, crypto.HashSize), TimestampNanos: 1622585623133599429, } buf, err := root.MarshalBinary() if err != nil { t.Fatalf("must marshal log root: %v", err) } // invalid root root.RootHash = make([]byte, crypto.HashSize+1) bufBadHash, err := root.MarshalBinary() if err != nil { t.Fatalf("must marshal log root: %v", err) } for _, table := range []struct { description string rsp *trillian.GetLatestSignedLogRootResponse err error wantErr bool wantTh *types.TreeHead }{ { description: "invalid: backend failure", err: fmt.Errorf("something went wrong"), wantErr: true, }, { description: "invalid: no response", wantErr: true, }, { description: "invalid: no signed log root", rsp: &trillian.GetLatestSignedLogRootResponse{}, wantErr: true, }, { description: "invalid: no log root", rsp: &trillian.GetLatestSignedLogRootResponse{ SignedLogRoot: &trillian.SignedLogRoot{}, }, wantErr: true, }, { description: "invalid: no log root: unmarshal failed", rsp: &trillian.GetLatestSignedLogRootResponse{ SignedLogRoot: &trillian.SignedLogRoot{ LogRoot: buf[1:], }, }, wantErr: true, }, { description: "invalid: unexpected hash length", rsp: &trillian.GetLatestSignedLogRootResponse{ SignedLogRoot: &trillian.SignedLogRoot{ LogRoot: bufBadHash, }, }, wantErr: true, }, { description: "valid", rsp: &trillian.GetLatestSignedLogRootResponse{ SignedLogRoot: &trillian.SignedLogRoot{ LogRoot: buf, }, }, wantTh: &types.TreeHead{ Size: 0, RootHash: crypto.Hash{}, }, }, } { // Run deferred functions at the end of each iteration func() { ctrl := gomock.NewController(t) defer ctrl.Finish() grpc := mocksTrillian.NewMockTrillianLogClient(ctrl) grpc.EXPECT().GetLatestSignedLogRoot(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) client := TrillianClient{logClient: grpc} th, err := client.GetTreeHead(context.Background()) if got, want := err != nil, table.wantErr; got != want { t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) } if err != nil { return } if got, want := th.Size, table.wantTh.Size; got != want { t.Errorf("got tree head with tree size %d but wanted %d in test %q", got, want, table.description) } if got, want := th.RootHash[:], table.wantTh.RootHash[:]; !bytes.Equal(got, want) { t.Errorf("got root hash %x but wanted %x in test %q", got, want, table.description) } }() } } func TestGetConsistencyProof(t *testing.T) { req := &requests.ConsistencyProof{ OldSize: 1, NewSize: 3, } for _, table := range []struct { description string req *requests.ConsistencyProof rsp *trillian.GetConsistencyProofResponse err error wantErr bool wantProof types.ConsistencyProof }{ { description: "invalid: backend failure", req: req, err: fmt.Errorf("something went wrong"), wantErr: true, }, { description: "invalid: no response", req: req, wantErr: true, }, { description: "invalid: no consistency proof", req: req, rsp: &trillian.GetConsistencyProofResponse{}, wantErr: true, }, { description: "invalid: not a consistency proof (2/2)", req: req, rsp: &trillian.GetConsistencyProofResponse{ Proof: &trillian.Proof{ Hashes: [][]byte{ make([]byte, crypto.HashSize), make([]byte, crypto.HashSize+1), }, }, }, wantErr: true, }, { description: "valid", req: req, rsp: &trillian.GetConsistencyProofResponse{ Proof: &trillian.Proof{ Hashes: [][]byte{ make([]byte, crypto.HashSize), make([]byte, crypto.HashSize), }, }, }, wantProof: types.ConsistencyProof{ Path: []crypto.Hash{ crypto.Hash{}, crypto.Hash{}, }, }, }, } { // Run deferred functions at the end of each iteration func() { ctrl := gomock.NewController(t) defer ctrl.Finish() grpc := mocksTrillian.NewMockTrillianLogClient(ctrl) grpc.EXPECT().GetConsistencyProof(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) client := TrillianClient{logClient: grpc} proof, err := client.GetConsistencyProof(context.Background(), table.req) if got, want := err != nil, table.wantErr; got != want { t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) } if err != nil { return } if got, want := proof, table.wantProof; !reflect.DeepEqual(got, want) { t.Errorf("got proof\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) } }() } } func TestGetInclusionProof(t *testing.T) { req := &requests.InclusionProof{ Size: 4, LeafHash: crypto.Hash{}, } for _, table := range []struct { description string req *requests.InclusionProof rsp *trillian.GetInclusionProofByHashResponse err error wantErr bool wantProof types.InclusionProof }{ { description: "invalid: backend failure", req: req, err: fmt.Errorf("something went wrong"), wantErr: true, }, { description: "invalid: no response", req: req, wantErr: true, }, { description: "invalid: bad proof count", req: req, rsp: &trillian.GetInclusionProofByHashResponse{ Proof: []*trillian.Proof{ &trillian.Proof{}, &trillian.Proof{}, }, }, wantErr: true, }, { description: "invalid: not an inclusion proof (1/2)", req: req, rsp: &trillian.GetInclusionProofByHashResponse{ Proof: []*trillian.Proof{ &trillian.Proof{ LeafIndex: 1, Hashes: [][]byte{}, }, }, }, wantErr: true, }, { description: "invalid: not an inclusion proof (2/2)", req: req, rsp: &trillian.GetInclusionProofByHashResponse{ Proof: []*trillian.Proof{ &trillian.Proof{ LeafIndex: 1, Hashes: [][]byte{ make([]byte, crypto.HashSize), make([]byte, crypto.HashSize+1), }, }, }, }, wantErr: true, }, { description: "valid", req: req, rsp: &trillian.GetInclusionProofByHashResponse{ Proof: []*trillian.Proof{ &trillian.Proof{ LeafIndex: 1, Hashes: [][]byte{ make([]byte, crypto.HashSize), make([]byte, crypto.HashSize), }, }, }, }, wantProof: types.InclusionProof{ LeafIndex: 1, Path: []crypto.Hash{ crypto.Hash{}, crypto.Hash{}, }, }, }, } { // Run deferred functions at the end of each iteration func() { ctrl := gomock.NewController(t) defer ctrl.Finish() grpc := mocksTrillian.NewMockTrillianLogClient(ctrl) grpc.EXPECT().GetInclusionProofByHash(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) client := TrillianClient{logClient: grpc} proof, err := client.GetInclusionProof(context.Background(), table.req) if got, want := err != nil, table.wantErr; got != want { t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) } if err != nil { return } if got, want := proof, table.wantProof; !reflect.DeepEqual(got, want) { t.Errorf("got proof\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) } }() } } func TestGetLeaves(t *testing.T) { req := &requests.Leaves{ StartIndex: 1, EndIndex: 3, } firstLeaf := &types.Leaf{ Checksum: crypto.Hash{}, Signature: crypto.Signature{}, KeyHash: crypto.Hash{}, } secondLeaf := &types.Leaf{ Checksum: crypto.Hash{}, Signature: crypto.Signature{}, KeyHash: crypto.Hash{}, } for _, table := range []struct { description string req *requests.Leaves rsp *trillian.GetLeavesByRangeResponse err error wantErr bool wantLeaves []types.Leaf }{ { description: "invalid: backend failure", req: req, err: fmt.Errorf("something went wrong"), wantErr: true, }, { description: "invalid: no response", req: req, wantErr: true, }, { description: "invalid: unexpected number of leaves", req: req, rsp: &trillian.GetLeavesByRangeResponse{ Leaves: []*trillian.LogLeaf{ &trillian.LogLeaf{ LeafValue: firstLeaf.ToBinary(), LeafIndex: 1, }, }, }, wantErr: true, }, { description: "invalid: unexpected leaf (1/2)", req: req, rsp: &trillian.GetLeavesByRangeResponse{ Leaves: []*trillian.LogLeaf{ &trillian.LogLeaf{ LeafValue: firstLeaf.ToBinary(), LeafIndex: 1, }, &trillian.LogLeaf{ LeafValue: secondLeaf.ToBinary(), LeafIndex: 3, }, }, }, wantErr: true, }, { description: "invalid: unexpected leaf (2/2)", req: req, rsp: &trillian.GetLeavesByRangeResponse{ Leaves: []*trillian.LogLeaf{ &trillian.LogLeaf{ LeafValue: firstLeaf.ToBinary(), LeafIndex: 1, }, &trillian.LogLeaf{ LeafValue: secondLeaf.ToBinary()[1:], LeafIndex: 2, }, }, }, wantErr: true, }, { description: "valid", req: req, rsp: &trillian.GetLeavesByRangeResponse{ Leaves: []*trillian.LogLeaf{ &trillian.LogLeaf{ LeafValue: firstLeaf.ToBinary(), LeafIndex: 1, }, &trillian.LogLeaf{ LeafValue: secondLeaf.ToBinary(), LeafIndex: 2, }, }, }, wantLeaves: []types.Leaf{ *firstLeaf, *secondLeaf, }, }, } { // Run deferred functions at the end of each iteration func() { ctrl := gomock.NewController(t) defer ctrl.Finish() grpc := mocksTrillian.NewMockTrillianLogClient(ctrl) grpc.EXPECT().GetLeavesByRange(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) client := TrillianClient{logClient: grpc} leaves, err := client.GetLeaves(context.Background(), table.req) if got, want := err != nil, table.wantErr; got != want { t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) } if err != nil { return } if got, want := leaves, table.wantLeaves; !reflect.DeepEqual(got, want) { t.Errorf("got leaves\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) } }() } } sigsum-log-go-0.15.2/internal/metrics/000077500000000000000000000000001477305677000175725ustar00rootroot00000000000000sigsum-log-go-0.15.2/internal/metrics/metrics.go000066400000000000000000000025211477305677000215670ustar00rootroot00000000000000package metrics import ( "fmt" "time" "github.com/google/trillian/monitoring" "github.com/google/trillian/monitoring/prometheus" "sigsum.org/sigsum-go/pkg/server" ) type serverMetrics struct { LogID string reqcnt monitoring.Counter // number of incoming http requests rspcnt monitoring.Counter // number of valid http responses latency monitoring.Histogram // request-response latency } func (m *serverMetrics) OnRequest(endpoint string) { m.reqcnt.Inc(m.LogID, endpoint) } func (m *serverMetrics) OnResponse(endpoint string, statusCode int, t time.Duration) { sc := fmt.Sprintf("%d", statusCode) m.rspcnt.Inc(m.LogID, endpoint, sc) m.latency.Observe(t.Seconds(), m.LogID, endpoint, sc) } func NewServerMetrics(logID string) server.Metrics { mf := prometheus.MetricFactory{} // Interval 1ms to 10s, with thresholds roughly a factor // 10^{1/4} \appr 1.8 apart. buckets := []float64{1e-3, 2e-3, 3e-3, 6e-3, 10e-3, 20e-3, 30e-3, 60e-3, 0.1, 0.2, 0.3, 0.6, 1, 2, 3, 6, 10} return &serverMetrics{ LogID: logID, reqcnt: mf.NewCounter("http_req", "number of http requests", "logid", "endpoint"), rspcnt: mf.NewCounter("http_rsp", "number of http requests", "logid", "endpoint", "status"), latency: mf.NewHistogramWithBuckets("http_latency", "http request-response latency", buckets, "logid", "endpoint", "status"), } } sigsum-log-go-0.15.2/internal/mocks/000077500000000000000000000000001477305677000172405ustar00rootroot00000000000000sigsum-log-go-0.15.2/internal/mocks/Makefile000066400000000000000000000010461477305677000207010ustar00rootroot00000000000000# Makefile to regenerate mock files. all: db/db.go state/state.go trillian/trillian.go .PHONY: all db/db.go: ../db/client.go go run github.com/golang/mock/mockgen --destination $@ --package db sigsum.org/log-go/internal/db Client state/state.go: ../state/state_manager.go go run github.com/golang/mock/mockgen --destination $@ --package state sigsum.org/log-go/internal/state StateManager trillian/trillian.go: ../../go.mod go run github.com/golang/mock/mockgen --destination $@ --package trillian github.com/google/trillian TrillianLogClient sigsum-log-go-0.15.2/internal/mocks/db/000077500000000000000000000000001477305677000176255ustar00rootroot00000000000000sigsum-log-go-0.15.2/internal/mocks/db/db.go000066400000000000000000000110661477305677000205450ustar00rootroot00000000000000// Code generated by MockGen. DO NOT EDIT. // Source: sigsum.org/log-go/internal/db (interfaces: Client) // Package db is a generated GoMock package. package db import ( context "context" reflect "reflect" gomock "github.com/golang/mock/gomock" db "sigsum.org/log-go/internal/db" requests "sigsum.org/sigsum-go/pkg/requests" types "sigsum.org/sigsum-go/pkg/types" ) // MockClient is a mock of Client interface. type MockClient struct { ctrl *gomock.Controller recorder *MockClientMockRecorder } // MockClientMockRecorder is the mock recorder for MockClient. type MockClientMockRecorder struct { mock *MockClient } // NewMockClient creates a new mock instance. func NewMockClient(ctrl *gomock.Controller) *MockClient { mock := &MockClient{ctrl: ctrl} mock.recorder = &MockClientMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. func (m *MockClient) EXPECT() *MockClientMockRecorder { return m.recorder } // AddLeaf mocks base method. func (m *MockClient) AddLeaf(arg0 context.Context, arg1 *types.Leaf, arg2 uint64) (db.AddLeafStatus, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddLeaf", arg0, arg1, arg2) ret0, _ := ret[0].(db.AddLeafStatus) ret1, _ := ret[1].(error) return ret0, ret1 } // AddLeaf indicates an expected call of AddLeaf. func (mr *MockClientMockRecorder) AddLeaf(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddLeaf", reflect.TypeOf((*MockClient)(nil).AddLeaf), arg0, arg1, arg2) } // AddSequencedLeaves mocks base method. func (m *MockClient) AddSequencedLeaves(arg0 context.Context, arg1 []types.Leaf, arg2 int64) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddSequencedLeaves", arg0, arg1, arg2) ret0, _ := ret[0].(error) return ret0 } // AddSequencedLeaves indicates an expected call of AddSequencedLeaves. func (mr *MockClientMockRecorder) AddSequencedLeaves(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSequencedLeaves", reflect.TypeOf((*MockClient)(nil).AddSequencedLeaves), arg0, arg1, arg2) } // GetConsistencyProof mocks base method. func (m *MockClient) GetConsistencyProof(arg0 context.Context, arg1 *requests.ConsistencyProof) (types.ConsistencyProof, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetConsistencyProof", arg0, arg1) ret0, _ := ret[0].(types.ConsistencyProof) ret1, _ := ret[1].(error) return ret0, ret1 } // GetConsistencyProof indicates an expected call of GetConsistencyProof. func (mr *MockClientMockRecorder) GetConsistencyProof(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConsistencyProof", reflect.TypeOf((*MockClient)(nil).GetConsistencyProof), arg0, arg1) } // GetInclusionProof mocks base method. func (m *MockClient) GetInclusionProof(arg0 context.Context, arg1 *requests.InclusionProof) (types.InclusionProof, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetInclusionProof", arg0, arg1) ret0, _ := ret[0].(types.InclusionProof) ret1, _ := ret[1].(error) return ret0, ret1 } // GetInclusionProof indicates an expected call of GetInclusionProof. func (mr *MockClientMockRecorder) GetInclusionProof(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInclusionProof", reflect.TypeOf((*MockClient)(nil).GetInclusionProof), arg0, arg1) } // GetLeaves mocks base method. func (m *MockClient) GetLeaves(arg0 context.Context, arg1 *requests.Leaves) ([]types.Leaf, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetLeaves", arg0, arg1) ret0, _ := ret[0].([]types.Leaf) ret1, _ := ret[1].(error) return ret0, ret1 } // GetLeaves indicates an expected call of GetLeaves. func (mr *MockClientMockRecorder) GetLeaves(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeaves", reflect.TypeOf((*MockClient)(nil).GetLeaves), arg0, arg1) } // GetTreeHead mocks base method. func (m *MockClient) GetTreeHead(arg0 context.Context) (types.TreeHead, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetTreeHead", arg0) ret0, _ := ret[0].(types.TreeHead) ret1, _ := ret[1].(error) return ret0, ret1 } // GetTreeHead indicates an expected call of GetTreeHead. func (mr *MockClientMockRecorder) GetTreeHead(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTreeHead", reflect.TypeOf((*MockClient)(nil).GetTreeHead), arg0) } sigsum-log-go-0.15.2/internal/mocks/state/000077500000000000000000000000001477305677000203605ustar00rootroot00000000000000sigsum-log-go-0.15.2/internal/mocks/state/state.go000066400000000000000000000050341477305677000220310ustar00rootroot00000000000000// Code generated by MockGen. DO NOT EDIT. // Source: sigsum.org/log-go/internal/state (interfaces: StateManager) // Package state is a generated GoMock package. package state import ( context "context" reflect "reflect" time "time" gomock "github.com/golang/mock/gomock" policy "sigsum.org/sigsum-go/pkg/policy" types "sigsum.org/sigsum-go/pkg/types" ) // MockStateManager is a mock of StateManager interface. type MockStateManager struct { ctrl *gomock.Controller recorder *MockStateManagerMockRecorder } // MockStateManagerMockRecorder is the mock recorder for MockStateManager. type MockStateManagerMockRecorder struct { mock *MockStateManager } // NewMockStateManager creates a new mock instance. func NewMockStateManager(ctrl *gomock.Controller) *MockStateManager { mock := &MockStateManager{ctrl: ctrl} mock.recorder = &MockStateManagerMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. func (m *MockStateManager) EXPECT() *MockStateManagerMockRecorder { return m.recorder } // CosignedTreeHead mocks base method. func (m *MockStateManager) CosignedTreeHead() types.CosignedTreeHead { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CosignedTreeHead") ret0, _ := ret[0].(types.CosignedTreeHead) return ret0 } // CosignedTreeHead indicates an expected call of CosignedTreeHead. func (mr *MockStateManagerMockRecorder) CosignedTreeHead() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CosignedTreeHead", reflect.TypeOf((*MockStateManager)(nil).CosignedTreeHead)) } // Run mocks base method. func (m *MockStateManager) Run(arg0 context.Context, arg1 []policy.Entity, arg2 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "Run", arg0, arg1, arg2) } // Run indicates an expected call of Run. func (mr *MockStateManagerMockRecorder) Run(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockStateManager)(nil).Run), arg0, arg1, arg2) } // SignedTreeHead mocks base method. func (m *MockStateManager) SignedTreeHead() types.SignedTreeHead { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SignedTreeHead") ret0, _ := ret[0].(types.SignedTreeHead) return ret0 } // SignedTreeHead indicates an expected call of SignedTreeHead. func (mr *MockStateManagerMockRecorder) SignedTreeHead() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignedTreeHead", reflect.TypeOf((*MockStateManager)(nil).SignedTreeHead)) } sigsum-log-go-0.15.2/internal/mocks/trillian/000077500000000000000000000000001477305677000210565ustar00rootroot00000000000000sigsum-log-go-0.15.2/internal/mocks/trillian/trillian.go000066400000000000000000000223531477305677000232300ustar00rootroot00000000000000// Code generated by MockGen. DO NOT EDIT. // Source: github.com/google/trillian (interfaces: TrillianLogClient) // Package trillian is a generated GoMock package. package trillian import ( context "context" reflect "reflect" gomock "github.com/golang/mock/gomock" trillian "github.com/google/trillian" grpc "google.golang.org/grpc" ) // MockTrillianLogClient is a mock of TrillianLogClient interface. type MockTrillianLogClient struct { ctrl *gomock.Controller recorder *MockTrillianLogClientMockRecorder } // MockTrillianLogClientMockRecorder is the mock recorder for MockTrillianLogClient. type MockTrillianLogClientMockRecorder struct { mock *MockTrillianLogClient } // NewMockTrillianLogClient creates a new mock instance. func NewMockTrillianLogClient(ctrl *gomock.Controller) *MockTrillianLogClient { mock := &MockTrillianLogClient{ctrl: ctrl} mock.recorder = &MockTrillianLogClientMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. func (m *MockTrillianLogClient) EXPECT() *MockTrillianLogClientMockRecorder { return m.recorder } // AddSequencedLeaves mocks base method. func (m *MockTrillianLogClient) AddSequencedLeaves(arg0 context.Context, arg1 *trillian.AddSequencedLeavesRequest, arg2 ...grpc.CallOption) (*trillian.AddSequencedLeavesResponse, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "AddSequencedLeaves", varargs...) ret0, _ := ret[0].(*trillian.AddSequencedLeavesResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // AddSequencedLeaves indicates an expected call of AddSequencedLeaves. func (mr *MockTrillianLogClientMockRecorder) AddSequencedLeaves(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSequencedLeaves", reflect.TypeOf((*MockTrillianLogClient)(nil).AddSequencedLeaves), varargs...) } // GetConsistencyProof mocks base method. func (m *MockTrillianLogClient) GetConsistencyProof(arg0 context.Context, arg1 *trillian.GetConsistencyProofRequest, arg2 ...grpc.CallOption) (*trillian.GetConsistencyProofResponse, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "GetConsistencyProof", varargs...) ret0, _ := ret[0].(*trillian.GetConsistencyProofResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // GetConsistencyProof indicates an expected call of GetConsistencyProof. func (mr *MockTrillianLogClientMockRecorder) GetConsistencyProof(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConsistencyProof", reflect.TypeOf((*MockTrillianLogClient)(nil).GetConsistencyProof), varargs...) } // GetEntryAndProof mocks base method. func (m *MockTrillianLogClient) GetEntryAndProof(arg0 context.Context, arg1 *trillian.GetEntryAndProofRequest, arg2 ...grpc.CallOption) (*trillian.GetEntryAndProofResponse, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "GetEntryAndProof", varargs...) ret0, _ := ret[0].(*trillian.GetEntryAndProofResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // GetEntryAndProof indicates an expected call of GetEntryAndProof. func (mr *MockTrillianLogClientMockRecorder) GetEntryAndProof(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEntryAndProof", reflect.TypeOf((*MockTrillianLogClient)(nil).GetEntryAndProof), varargs...) } // GetInclusionProof mocks base method. func (m *MockTrillianLogClient) GetInclusionProof(arg0 context.Context, arg1 *trillian.GetInclusionProofRequest, arg2 ...grpc.CallOption) (*trillian.GetInclusionProofResponse, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "GetInclusionProof", varargs...) ret0, _ := ret[0].(*trillian.GetInclusionProofResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // GetInclusionProof indicates an expected call of GetInclusionProof. func (mr *MockTrillianLogClientMockRecorder) GetInclusionProof(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInclusionProof", reflect.TypeOf((*MockTrillianLogClient)(nil).GetInclusionProof), varargs...) } // GetInclusionProofByHash mocks base method. func (m *MockTrillianLogClient) GetInclusionProofByHash(arg0 context.Context, arg1 *trillian.GetInclusionProofByHashRequest, arg2 ...grpc.CallOption) (*trillian.GetInclusionProofByHashResponse, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "GetInclusionProofByHash", varargs...) ret0, _ := ret[0].(*trillian.GetInclusionProofByHashResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // GetInclusionProofByHash indicates an expected call of GetInclusionProofByHash. func (mr *MockTrillianLogClientMockRecorder) GetInclusionProofByHash(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInclusionProofByHash", reflect.TypeOf((*MockTrillianLogClient)(nil).GetInclusionProofByHash), varargs...) } // GetLatestSignedLogRoot mocks base method. func (m *MockTrillianLogClient) GetLatestSignedLogRoot(arg0 context.Context, arg1 *trillian.GetLatestSignedLogRootRequest, arg2 ...grpc.CallOption) (*trillian.GetLatestSignedLogRootResponse, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "GetLatestSignedLogRoot", varargs...) ret0, _ := ret[0].(*trillian.GetLatestSignedLogRootResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // GetLatestSignedLogRoot indicates an expected call of GetLatestSignedLogRoot. func (mr *MockTrillianLogClientMockRecorder) GetLatestSignedLogRoot(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestSignedLogRoot", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLatestSignedLogRoot), varargs...) } // GetLeavesByRange mocks base method. func (m *MockTrillianLogClient) GetLeavesByRange(arg0 context.Context, arg1 *trillian.GetLeavesByRangeRequest, arg2 ...grpc.CallOption) (*trillian.GetLeavesByRangeResponse, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "GetLeavesByRange", varargs...) ret0, _ := ret[0].(*trillian.GetLeavesByRangeResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // GetLeavesByRange indicates an expected call of GetLeavesByRange. func (mr *MockTrillianLogClientMockRecorder) GetLeavesByRange(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeavesByRange", reflect.TypeOf((*MockTrillianLogClient)(nil).GetLeavesByRange), varargs...) } // InitLog mocks base method. func (m *MockTrillianLogClient) InitLog(arg0 context.Context, arg1 *trillian.InitLogRequest, arg2 ...grpc.CallOption) (*trillian.InitLogResponse, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "InitLog", varargs...) ret0, _ := ret[0].(*trillian.InitLogResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // InitLog indicates an expected call of InitLog. func (mr *MockTrillianLogClientMockRecorder) InitLog(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitLog", reflect.TypeOf((*MockTrillianLogClient)(nil).InitLog), varargs...) } // QueueLeaf mocks base method. func (m *MockTrillianLogClient) QueueLeaf(arg0 context.Context, arg1 *trillian.QueueLeafRequest, arg2 ...grpc.CallOption) (*trillian.QueueLeafResponse, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "QueueLeaf", varargs...) ret0, _ := ret[0].(*trillian.QueueLeafResponse) ret1, _ := ret[1].(error) return ret0, ret1 } // QueueLeaf indicates an expected call of QueueLeaf. func (mr *MockTrillianLogClientMockRecorder) QueueLeaf(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueLeaf", reflect.TypeOf((*MockTrillianLogClient)(nil).QueueLeaf), varargs...) } sigsum-log-go-0.15.2/internal/node/000077500000000000000000000000001477305677000170515ustar00rootroot00000000000000sigsum-log-go-0.15.2/internal/node/primary/000077500000000000000000000000001477305677000205345ustar00rootroot00000000000000sigsum-log-go-0.15.2/internal/node/primary/endpoint_external.go000066400000000000000000000102371477305677000246100ustar00rootroot00000000000000package primary // This file implements external HTTP handler callbacks for primary nodes. import ( "context" "fmt" "sigsum.org/log-go/internal/db" "sigsum.org/sigsum-go/pkg/api" "sigsum.org/sigsum-go/pkg/crypto" "sigsum.org/sigsum-go/pkg/log" "sigsum.org/sigsum-go/pkg/requests" "sigsum.org/sigsum-go/pkg/submit-token" "sigsum.org/sigsum-go/pkg/types" ) func (p Primary) AddLeaf(ctx context.Context, req requests.Leaf, t *token.SubmitHeader) (bool, error) { log.Debug("handling add-leaf request") var domain *string if t != nil && p.TokenVerifier != nil { // TODO: Return more appropriate errors from TokenVerifier? if err := p.TokenVerifier.Verify(ctx, t); err != nil { return false, api.ErrBadRequest.WithError(err) } domain = &t.Domain } keyHash := crypto.HashBytes(req.PublicKey[:]) relax := p.RateLimiter.AccessAllowed(domain, &keyHash) if relax == nil { if domain == nil { return false, api.ErrTooManyRequests.WithError(fmt.Errorf("rate-limit for unknown domain exceeded")) } return false, api.ErrTooManyRequests.WithError(fmt.Errorf("rate-limit for domain %q exceeded", *domain)) } leaf, err := req.Verify() if err != nil { return false, api.ErrForbidden.WithError(err) } sth := p.Stateman.SignedTreeHead() status, err := p.DbClient.AddLeaf(ctx, &leaf, sth.Size) log.Debug("status: %#v, err: %v", status, err) if err != nil { return false, err } if status.AlreadyExists { relax() } return status.IsSequenced, nil } func (p Primary) GetTreeHead(_ context.Context) (types.CosignedTreeHead, error) { log.Debug("handling get-tree-head request") return p.Stateman.CosignedTreeHead(), nil } func (p Primary) GetConsistencyProof(ctx context.Context, req requests.ConsistencyProof) (types.ConsistencyProof, error) { log.Debug("handling get-consistency-proof request") curTree := p.Stateman.CosignedTreeHead() if req.NewSize > curTree.TreeHead.Size { return types.ConsistencyProof{}, api.ErrBadRequest.WithError(fmt.Errorf("new_size %d outside of current tree, size %d", req.NewSize, curTree.TreeHead.Size)) } return p.DbClient.GetConsistencyProof(ctx, &req) } func (p Primary) GetInclusionProof(ctx context.Context, req requests.InclusionProof) (types.InclusionProof, error) { log.Debug("handling get-inclusion-proof request") curTree := p.Stateman.CosignedTreeHead() if req.Size > curTree.TreeHead.Size { return types.InclusionProof{}, api.ErrBadRequest.WithError(fmt.Errorf("tree_size outside of current tree")) } proof, err := p.DbClient.GetInclusionProof(ctx, &req) // TODO: Make DbClient return the appropriate api error? if err == db.ErrNotIncluded { err = api.ErrNotFound } return proof, err } func (p Primary) getLeavesGeneral(ctx context.Context, req requests.Leaves, maxIndex uint64, strictEnd bool) ([]types.Leaf, error) { log.Debug("handling get-leaves request") // When invoked via sigsum-go/pkg/server, this error is // already checked for earlier and will not happen here. if req.StartIndex >= req.EndIndex { return nil, api.ErrBadRequest.WithError( fmt.Errorf("start_index(%d) must be less than end_index(%d)", req.StartIndex, req.EndIndex)) } if req.StartIndex > maxIndex || (strictEnd && req.StartIndex >= maxIndex) { return nil, api.ErrBadRequest.WithError( fmt.Errorf("start_index(%d) outside of current tree", req.StartIndex)) } if req.EndIndex > maxIndex { if strictEnd { return nil, api.ErrBadRequest.WithError( fmt.Errorf("end_index(%d) outside of current tree", req.EndIndex)) } req.EndIndex = maxIndex } if req.EndIndex-req.StartIndex > uint64(p.MaxRange) { req.EndIndex = req.StartIndex + uint64(p.MaxRange) } // May happen only when strictEnd is false. if req.StartIndex == req.EndIndex { if strictEnd { return nil, fmt.Errorf("internal error, empty range") } return nil, api.ErrNotFound.WithError(fmt.Errorf("at end of tree")) } leaves, err := p.DbClient.GetLeaves(ctx, &req) if err == nil && len(leaves) == 0 { err = fmt.Errorf("backend get leaves returned an empty list") } return leaves, err } func (p Primary) GetLeaves(ctx context.Context, req requests.Leaves) ([]types.Leaf, error) { return p.getLeavesGeneral(ctx, req, p.Stateman.CosignedTreeHead().Size, true) } sigsum-log-go-0.15.2/internal/node/primary/endpoint_external_test.go000066400000000000000000000262541477305677000256550ustar00rootroot00000000000000package primary import ( "context" "fmt" "net/http" "testing" "github.com/golang/mock/gomock" "sigsum.org/log-go/internal/db" mocksDB "sigsum.org/log-go/internal/mocks/db" mocksState "sigsum.org/log-go/internal/mocks/state" "sigsum.org/log-go/internal/rate-limit" "sigsum.org/sigsum-go/pkg/api" "sigsum.org/sigsum-go/pkg/crypto" "sigsum.org/sigsum-go/pkg/requests" "sigsum.org/sigsum-go/pkg/types" ) func TestAddLeaf(t *testing.T) { // TODO: Add tests for rate limiting. for _, table := range []struct { description string req requests.Leaf errTrillian error // error from Trillian client wantCode int // HTTP status committed bool leafStatus db.AddLeafStatus // return value from db.AddLeaf() }{ { description: "invalid: bad request (signature error)", req: mustLeaf(t, crypto.Hash{}, false), wantCode: http.StatusForbidden, }, { description: "invalid: backend failure", req: mustLeaf(t, crypto.Hash{}, true), errTrillian: fmt.Errorf("something went wrong"), wantCode: http.StatusInternalServerError, }, { description: "valid: 202", req: mustLeaf(t, crypto.Hash{}, true), }, { description: "valid: 200", req: mustLeaf(t, crypto.Hash{}, true), leafStatus: db.AddLeafStatus{IsSequenced: true}, committed: true, }, } { // Run deferred functions at the end of each iteration func() { ctrl := gomock.NewController(t) defer ctrl.Finish() client := mocksDB.NewMockClient(ctrl) client.EXPECT().AddLeaf(gomock.Any(), gomock.Any(), gomock.Any()).Return(table.leafStatus, table.errTrillian).AnyTimes() stateman := mocksState.NewMockStateManager(ctrl) stateman.EXPECT().SignedTreeHead().Return(types.SignedTreeHead{}).AnyTimes() node := Primary{ DbClient: client, Stateman: stateman, RateLimiter: rateLimit.NoLimit{}, } committed, err := node.AddLeaf(context.Background(), table.req, nil) if err := checkError(err, table.wantCode); err != nil { t.Errorf("in test %q: %v", table.description, err) } else if got, want := committed, table.committed; got != want { t.Errorf("unexpected commit status, got %v, wanted %v in test %q", got, want, table.description) } }() } } func TestGetTreeHead(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() stateman := mocksState.NewMockStateManager(ctrl) cth := types.CosignedTreeHead{} cth.Size = 10 stateman.EXPECT().CosignedTreeHead().Return(cth) node := Primary{ Stateman: stateman, } got, err := node.GetTreeHead(context.Background()) if err != nil { t.Fatalf("GetTreeHead failed: %v", err) } // For simplicity, doesn't compare the (nil) cosignature lists. if got.SignedTreeHead != cth.SignedTreeHead { t.Errorf("Bad result from GetTreeHead: got %v, expected %v", got, cth) } } func TestGetConsistencyProof(t *testing.T) { for _, table := range []struct { description string req requests.ConsistencyProof sthSize uint64 rsp types.ConsistencyProof // consistency proof from Trillian client err error // error from Trillian client wantCode int // HTTP status ok }{ { description: "invalid: bad request (OldSize is zero)", req: requests.ConsistencyProof{OldSize: 0, NewSize: 1}, wantCode: http.StatusBadRequest, }, { description: "invalid: bad request (OldSize > NewSize)", req: requests.ConsistencyProof{OldSize: 2, NewSize: 1}, wantCode: http.StatusBadRequest, }, { description: "invalid: bad request (NewSize > tree size)", req: requests.ConsistencyProof{OldSize: 1, NewSize: 2}, sthSize: 1, wantCode: http.StatusBadRequest, }, { description: "invalid: backend failure", req: requests.ConsistencyProof{OldSize: 1, NewSize: 2}, sthSize: 2, err: fmt.Errorf("something went wrong"), wantCode: http.StatusInternalServerError, }, { description: "valid", req: requests.ConsistencyProof{OldSize: 1, NewSize: 2}, sthSize: 2, rsp: types.ConsistencyProof{ Path: []crypto.Hash{ crypto.HashBytes([]byte{}), }, }, }, } { // Run deferred functions at the end of each iteration func() { ctrl := gomock.NewController(t) defer ctrl.Finish() client := mocksDB.NewMockClient(ctrl) if table.err != nil || table.rsp.Path != nil { client.EXPECT().GetConsistencyProof(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) } stateman := mocksState.NewMockStateManager(ctrl) stateman.EXPECT().CosignedTreeHead().Return( types.CosignedTreeHead{SignedTreeHead: types.SignedTreeHead{TreeHead: types.TreeHead{Size: table.sthSize}}}).AnyTimes() node := Primary{ DbClient: client, Stateman: stateman, } proof, err := node.GetConsistencyProof(context.Background(), table.req) if err := checkError(err, table.wantCode); err != nil { t.Errorf("in test %q: %v", table.description, err) } else if !pathIsEqual(proof.Path, table.rsp.Path) { t.Errorf("unexpected proof, got %x, wanted %x", proof, table.rsp) } }() } } func TestGetInclusionProof(t *testing.T) { for _, table := range []struct { description string req requests.InclusionProof sthSize uint64 rsp types.InclusionProof // inclusion proof from Trillian client err error // error from Trillian client wantCode int // HTTP status ok }{ { description: "invalid: bad request (no proof available for tree size 1)", req: requests.InclusionProof{Size: 1}, wantCode: http.StatusBadRequest, }, { description: "invalid: bad request (request outside current tree size)", req: requests.InclusionProof{Size: 2}, sthSize: 1, wantCode: http.StatusBadRequest, }, { description: "invalid: backend failure", req: requests.InclusionProof{Size: 2}, sthSize: 2, err: fmt.Errorf("something went wrong"), wantCode: http.StatusInternalServerError, }, { description: "invalid: not included", req: requests.InclusionProof{Size: 2}, sthSize: 2, err: db.ErrNotIncluded, wantCode: http.StatusNotFound, }, { description: "valid", req: requests.InclusionProof{Size: 2}, sthSize: 2, rsp: types.InclusionProof{ LeafIndex: 0, Path: []crypto.Hash{ crypto.HashBytes([]byte{}), }, }, }, } { // Run deferred functions at the end of each iteration func() { ctrl := gomock.NewController(t) defer ctrl.Finish() client := mocksDB.NewMockClient(ctrl) if table.err != nil || table.rsp.Path != nil { client.EXPECT().GetInclusionProof(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) } stateman := mocksState.NewMockStateManager(ctrl) stateman.EXPECT().CosignedTreeHead().Return( types.CosignedTreeHead{SignedTreeHead: types.SignedTreeHead{TreeHead: types.TreeHead{Size: table.sthSize}}}).AnyTimes() node := Primary{ DbClient: client, Stateman: stateman, } proof, err := node.GetInclusionProof(context.Background(), table.req) if err := checkError(err, table.wantCode); err != nil { t.Errorf("in test %q: %v", table.description, err) } else if proof.LeafIndex != table.rsp.LeafIndex || !pathIsEqual(proof.Path, table.rsp.Path) { t.Errorf("unexpected proof, got %x, wanted %x", proof, table.rsp) } }() } } func TestGetLeaves(t *testing.T) { const testMaxRange = 3 for _, table := range []struct { description string req requests.Leaves sthSize uint64 leafCount int // expected number of leaves err error // error from Trillian client wantCode int // HTTP status ok }{ { description: "invalid: bad request (StartIndex >= EndIndex)", req: requests.Leaves{StartIndex: 1, EndIndex: 1}, sthSize: 2, wantCode: http.StatusBadRequest, }, { description: "invalid: bad request (EndIndex > current tree size)", req: requests.Leaves{StartIndex: 0, EndIndex: 3}, sthSize: 2, wantCode: http.StatusBadRequest, }, { description: "invalid: backend failure", req: requests.Leaves{StartIndex: 0, EndIndex: 1}, sthSize: 2, err: fmt.Errorf("something went wrong"), wantCode: http.StatusInternalServerError, }, { description: "invalid: empty tree", req: requests.Leaves{StartIndex: 0, EndIndex: 1}, sthSize: 0, wantCode: http.StatusBadRequest, }, { description: "valid: three middle elements", req: requests.Leaves{StartIndex: 1, EndIndex: 4}, sthSize: 5, leafCount: 3, }, { description: "valid: one more entry than the configured MaxRange", req: requests.Leaves{StartIndex: 0, EndIndex: 4}, sthSize: 5, leafCount: testMaxRange, }, } { // Run deferred functions at the end of each iteration func() { ctrl := gomock.NewController(t) defer ctrl.Finish() client := mocksDB.NewMockClient(ctrl) if table.err != nil || table.leafCount > 0 { client.EXPECT().GetLeaves(gomock.Any(), gomock.Any()).DoAndReturn( func(_ context.Context, req *requests.Leaves) ([]types.Leaf, error) { if table.err != nil { return nil, table.err } if req.EndIndex <= req.StartIndex { t.Fatalf("invalid call to GetLeaves") } count := int(req.EndIndex - req.StartIndex) var list []types.Leaf for i := 0; i < count; i++ { list = append(list, types.Leaf{ Checksum: crypto.Hash{}, Signature: crypto.Signature{}, KeyHash: crypto.Hash{}, }) } return list, nil }) } stateman := mocksState.NewMockStateManager(ctrl) stateman.EXPECT().CosignedTreeHead().Return( types.CosignedTreeHead{SignedTreeHead: types.SignedTreeHead{TreeHead: types.TreeHead{Size: table.sthSize}}}).AnyTimes() node := Primary{ DbClient: client, Stateman: stateman, MaxRange: testMaxRange, } leaves, err := node.GetLeaves(context.Background(), table.req) if err := checkError(err, table.wantCode); err != nil { t.Errorf("in test %q: %v", table.description, err) } else if got, want := len(leaves), table.leafCount; got != want { t.Errorf("got %d leaves, but wanted %d in test %q", got, want, table.description) } }() } } func mustLeaf(t *testing.T, msg crypto.Hash, wantSig bool) requests.Leaf { t.Helper() vk, sk, err := crypto.NewKeyPair() if err != nil { t.Fatalf("must generate ed25519 keys: %v", err) } sig, err := types.SignLeafMessage(sk, msg[:]) if err != nil { t.Fatalf("must have an ed25519 signature: %v", err) } if !wantSig { sig[0] += 1 } return requests.Leaf{ Message: msg, Signature: sig, PublicKey: vk, } } func pathIsEqual(a []crypto.Hash, b []crypto.Hash) bool { if len(a) != len(b) { return false } for i, h := range a { if h != b[i] { return false } } return true } func checkError(err error, code int) error { if err != nil { if got := api.ErrorStatusCode(err); got != code || code == 0 { return fmt.Errorf("got HTTP status code %v but wanted %v: %v", got, code, err) } return nil } if code != 0 { return fmt.Errorf("no error, expected error with status %d", code) } return nil } sigsum-log-go-0.15.2/internal/node/primary/endpoint_internal.go000066400000000000000000000007311477305677000246000ustar00rootroot00000000000000package primary // This file implements internal HTTP handler callbacks for primary nodes. import ( "context" "fmt" "sigsum.org/sigsum-go/pkg/requests" "sigsum.org/sigsum-go/pkg/types" ) func (p Primary) GetLeavesInternal(ctx context.Context, req requests.Leaves) ([]types.Leaf, error) { th, err := p.DbClient.GetTreeHead(ctx) if err != nil { return nil, fmt.Errorf("failed getting tree head: %v", err) } return p.getLeavesGeneral(ctx, req, th.Size, false) } sigsum-log-go-0.15.2/internal/node/primary/endpoint_internal_test.go000066400000000000000000000064351477305677000256460ustar00rootroot00000000000000package primary import ( "context" "fmt" "net/http" "testing" "github.com/golang/mock/gomock" "sigsum.org/log-go/internal/mocks/db" "sigsum.org/sigsum-go/pkg/crypto" "sigsum.org/sigsum-go/pkg/requests" "sigsum.org/sigsum-go/pkg/types" ) func TestInternalGetLeaves(t *testing.T) { const testMaxRange = 3 for _, table := range []struct { description string req requests.Leaves th types.TreeHead leafCount int // expected number of leaves err error // error from Trillian client wantCode int // HTTP status ok }{ { description: "invalid: bad request (StartIndex >= EndIndex)", req: requests.Leaves{StartIndex: 1, EndIndex: 1}, th: types.TreeHead{Size: 2}, wantCode: http.StatusBadRequest, }, { description: "valid: (EndIndex > current tree size)", req: requests.Leaves{StartIndex: 0, EndIndex: 3}, th: types.TreeHead{Size: 2}, leafCount: 2, }, { description: "valid: StartIndex == current tree size", req: requests.Leaves{StartIndex: 2, EndIndex: 3}, th: types.TreeHead{Size: 2}, wantCode: http.StatusNotFound, }, { description: "invalid: backend failure", req: requests.Leaves{StartIndex: 0, EndIndex: 1}, th: types.TreeHead{Size: 2}, err: fmt.Errorf("something went wrong"), wantCode: http.StatusInternalServerError, }, { description: "valid: empty tree", req: requests.Leaves{StartIndex: 0, EndIndex: 1}, th: types.TreeHead{Size: 0}, wantCode: http.StatusNotFound, }, { description: "valid: three middle elements", req: requests.Leaves{StartIndex: 1, EndIndex: 4}, th: types.TreeHead{Size: 5}, leafCount: 3, }, { description: "valid: one more entry than the configured MaxRange", req: requests.Leaves{StartIndex: 0, EndIndex: 4}, th: types.TreeHead{Size: 5}, leafCount: testMaxRange, }, } { // Run deferred functions at the end of each iteration func() { ctrl := gomock.NewController(t) defer ctrl.Finish() client := db.NewMockClient(ctrl) client.EXPECT().GetTreeHead(gomock.Any()).Return(table.th, nil).AnyTimes() if table.err != nil || table.leafCount > 0 { client.EXPECT().GetLeaves(gomock.Any(), gomock.Any()).DoAndReturn( func(_ context.Context, req *requests.Leaves) ([]types.Leaf, error) { if table.err != nil { return nil, table.err } if req.EndIndex <= req.StartIndex { t.Fatalf("invalid call to GetLeaves") } count := int(req.EndIndex - req.StartIndex) var list []types.Leaf for i := 0; i < count; i++ { list = append(list, types.Leaf{ Checksum: crypto.Hash{}, Signature: crypto.Signature{}, KeyHash: crypto.Hash{}, }) } return list, nil }) } node := Primary{ DbClient: client, MaxRange: testMaxRange, } leaves, err := node.GetLeavesInternal(context.Background(), table.req) if err := checkError(err, table.wantCode); err != nil { t.Errorf("in test %q: %v", table.description, err) } else if got, want := len(leaves), table.leafCount; got != want { t.Errorf("got %d leaves, but wanted %d in test %q", got, want, table.description) } }() } } sigsum-log-go-0.15.2/internal/node/primary/primary.go000066400000000000000000000011531477305677000225460ustar00rootroot00000000000000package primary import ( "sigsum.org/log-go/internal/db" "sigsum.org/log-go/internal/rate-limit" "sigsum.org/log-go/internal/state" "sigsum.org/sigsum-go/pkg/submit-token" ) // Primary is an instance of the log's primary node type Primary struct { MaxRange int // Maximum number of leaves per get-leaves request DbClient db.Client // provides access to the backend, usually Trillian Stateman state.StateManager // coordinates access to (co)signed tree heads TokenVerifier *token.DnsVerifier // checks if domain name knows a public key RateLimiter rateLimit.Limiter } sigsum-log-go-0.15.2/internal/node/secondary/000077500000000000000000000000001477305677000210405ustar00rootroot00000000000000sigsum-log-go-0.15.2/internal/node/secondary/endpoint_internal.go000066400000000000000000000007711477305677000251100ustar00rootroot00000000000000package secondary // This file implements internal HTTP handler callbacks for secondary nodes. import ( "context" "fmt" "sigsum.org/sigsum-go/pkg/log" "sigsum.org/sigsum-go/pkg/types" ) func (s Secondary) GetSecondaryTreeHead(ctx context.Context) (types.SignedTreeHead, error) { log.Debug("handling get-secondary-tree-head request") th, err := s.DbClient.GetTreeHead(ctx) if err != nil { return types.SignedTreeHead{}, fmt.Errorf("getting tree head: %w", err) } return th.Sign(s.Signer) } sigsum-log-go-0.15.2/internal/node/secondary/endpoint_internal_test.go000066400000000000000000000035761477305677000261550ustar00rootroot00000000000000package secondary import ( "context" "fmt" "testing" "github.com/golang/mock/gomock" "sigsum.org/log-go/internal/mocks/db" "sigsum.org/sigsum-go/pkg/crypto" "sigsum.org/sigsum-go/pkg/types" ) // TestSigner implements the signer interface. It can be used to mock // an Ed25519 signer that always return the same public key, // signature, and error. // NOTE: Code duplication with internal/state/single_test.go type TestSigner struct { Error error } func (ts *TestSigner) Public() crypto.PublicKey { return crypto.PublicKey{} } func (ts *TestSigner) Sign(_ []byte) (crypto.Signature, error) { return crypto.Signature{}, ts.Error } func TestGetSecondaryTreeHead(t *testing.T) { publicKey, signer, err := crypto.NewKeyPair() if err != nil { t.Fatal(err) } for _, tbl := range []struct { desc string trillianTHErr error signErr error }{ { desc: "trillian GetTreeHead error", trillianTHErr: fmt.Errorf("mocked error"), }, { desc: "signer error", signErr: fmt.Errorf("mocked error"), }, { desc: "success", }, } { func() { ctrl := gomock.NewController(t) defer ctrl.Finish() trillianClient := db.NewMockClient(ctrl) trillianClient.EXPECT().GetTreeHead(gomock.Any()).Return(types.TreeHead{}, tbl.trillianTHErr) signer := crypto.Signer(signer) if tbl.signErr != nil { signer = &TestSigner{Error: tbl.signErr} } node := Secondary{ DbClient: trillianClient, Signer: signer, } sth, err := node.GetSecondaryTreeHead(context.Background()) if tbl.trillianTHErr != nil || tbl.signErr != nil { if err == nil { t.Errorf("%s: expected error, got none", tbl.desc) } } else { if err != nil { t.Errorf("%s: GetSecondaryTreeHead failed: %v\n", tbl.desc, err) } else if !sth.Verify(&publicKey) { t.Errorf("%s: Invalid tree head signature", tbl.desc) } } }() } } sigsum-log-go-0.15.2/internal/node/secondary/secondary.go000066400000000000000000000033061477305677000233600ustar00rootroot00000000000000package secondary import ( "context" "errors" "time" "sigsum.org/log-go/internal/db" "sigsum.org/sigsum-go/pkg/api" "sigsum.org/sigsum-go/pkg/crypto" "sigsum.org/sigsum-go/pkg/log" "sigsum.org/sigsum-go/pkg/requests" ) const ( leavesBatchSize = 100 ) // Secondary is an instance of a secondary node type Secondary struct { Interval time.Duration // Signing frequency DbClient db.Client // provides access to the backend, usually Trillian Signer crypto.Signer // provides access to Ed25519 private key Primary api.Log } func (s Secondary) Run(ctx context.Context) { ticker := time.NewTicker(s.Interval) defer ticker.Stop() for { select { case <-ticker.C: s.fetchLeavesFromPrimary(ctx) case <-ctx.Done(): return } } } func (s Secondary) fetchLeavesFromPrimary(ctx context.Context) { for { curTH, err := s.DbClient.GetTreeHead(ctx) if err != nil { log.Warning("unable to get tree head from trillian: %v", err) return } req := requests.Leaves{ StartIndex: curTH.Size, EndIndex: curTH.Size + leavesBatchSize, } leaves, err := s.Primary.GetLeaves(ctx, req) if err != nil { if errors.Is(api.ErrNotFound, err) { // Normal way to exit, so don't log at warning level. log.Debug("error fetching leaves [%d:%d] from primary: %v", req.StartIndex, req.EndIndex, err) } else { log.Warning("error fetching leaves [%d:%d] from primary: %v", req.StartIndex, req.EndIndex, err) } return } log.Debug("got %d leaves from primary when asking for [%d:%d]", len(leaves), req.StartIndex, req.EndIndex) if err := s.DbClient.AddSequencedLeaves(ctx, leaves, int64(req.StartIndex)); err != nil { log.Error("AddSequencedLeaves: %v", err) return } } } sigsum-log-go-0.15.2/internal/node/secondary/secondary_test.go000066400000000000000000000052301477305677000244150ustar00rootroot00000000000000package secondary import ( "context" "fmt" "testing" "github.com/golang/mock/gomock" mocksDB "sigsum.org/log-go/internal/mocks/db" "sigsum.org/sigsum-go/pkg/mocks" "sigsum.org/sigsum-go/pkg/types" ) func TestFetchLeavesFromPrimary(t *testing.T) { for _, tbl := range []struct { desc string // db.GetTreeHead() trillianTHRet types.TreeHead trillianTHErr error // client.GetLeaves() primaryGetLeavesRet []types.Leaf primaryGetLeavesErr error // db.AddSequencedLeaves() trillianAddLeavesExp bool trillianAddLeavesErr error }{ { desc: "no tree head from trillian", trillianTHErr: fmt.Errorf("mocked error"), }, { desc: "error fetching leaves", trillianTHRet: types.TreeHead{Size: 5}, // 6-5 => 1 expected GetLeaves primaryGetLeavesErr: fmt.Errorf("mocked error"), }, { desc: "error adding leaves", trillianTHRet: types.TreeHead{Size: 5}, // 6-5 => 1 expected GetLeaves primaryGetLeavesRet: []types.Leaf{ types.Leaf{}, }, trillianAddLeavesErr: fmt.Errorf("mocked error"), }, { desc: "success", trillianTHRet: types.TreeHead{Size: 5}, primaryGetLeavesRet: []types.Leaf{ types.Leaf{}, }, trillianAddLeavesExp: true, }, } { func() { ctrl := gomock.NewController(t) defer ctrl.Finish() fmt.Printf("desc: %s\n", tbl.desc) primaryClient := mocks.NewMockLog(ctrl) trillianClient := mocksDB.NewMockClient(ctrl) trillianClient.EXPECT().GetTreeHead(gomock.Any()).Return(tbl.trillianTHRet, tbl.trillianTHErr) if tbl.trillianTHErr == nil && tbl.primaryGetLeavesErr == nil && tbl.trillianAddLeavesErr == nil { updatedSize := tbl.trillianTHRet.Size + uint64(len(tbl.primaryGetLeavesRet)) trillianClient.EXPECT().GetTreeHead(gomock.Any()).Return( types.TreeHead{Size: updatedSize}, tbl.trillianTHErr) } if tbl.primaryGetLeavesErr != nil || tbl.primaryGetLeavesRet != nil { primaryClient.EXPECT().GetLeaves(gomock.Any(), gomock.Any()).Return(tbl.primaryGetLeavesRet, tbl.primaryGetLeavesErr) if tbl.trillianAddLeavesExp { // XXX End-of-data condition primaryClient.EXPECT().GetLeaves(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("mocked error")) } } if tbl.trillianAddLeavesErr != nil || tbl.trillianAddLeavesExp { trillianClient.EXPECT().AddSequencedLeaves(gomock.Any(), gomock.Any(), gomock.Any()).Return(tbl.trillianAddLeavesErr) } node := Secondary{ Primary: primaryClient, DbClient: trillianClient, } node.fetchLeavesFromPrimary(context.Background()) // NOTE: We are not verifying that // AddSequencedLeaves() is being called with // the right data. }() } } sigsum-log-go-0.15.2/internal/rate-limit/000077500000000000000000000000001477305677000201735ustar00rootroot00000000000000sigsum-log-go-0.15.2/internal/rate-limit/access-count.go000066400000000000000000000014701477305677000231130ustar00rootroot00000000000000package rateLimit import ( "sync" ) // A synchronized map of access counts. type accessCounts struct { // Protects the counts mapping. sync.Mutex counts map[string]int } func (c *accessCounts) GetAccessCount(key string) int { c.Lock() defer c.Unlock() return c.counts[key] } func (c *accessCounts) AccessAllowed(key string, limit int) func() { c.Lock() defer c.Unlock() if c.counts[key] >= limit { return nil } c.counts[key]++ return func() { c.accessRelax(key) } } func (c *accessCounts) accessRelax(key string) { c.Lock() defer c.Unlock() // Non-zero count is the expeced case, except if there were a // Reset call between AccessAllowed and AccessRelax. if c.counts[key] > 0 { c.counts[key]-- } } func (c *accessCounts) Reset() { c.Lock() defer c.Unlock() c.counts = make(map[string]int) } sigsum-log-go-0.15.2/internal/rate-limit/access-count_test.go000066400000000000000000000015301477305677000241470ustar00rootroot00000000000000package rateLimit import ( "testing" ) func TestAccessAllowed(t *testing.T) { m := accessCounts{} m.Reset() checkCount := func(domain string, expected int) { if c := m.GetAccessCount(domain); c != expected { t.Errorf("expected access count (%q) = %d, got %d", domain, expected, c) } } checkAccess := func(desc, domain string, limit int, expected bool) { if res := m.AccessAllowed(domain, limit); (res != nil) != expected { t.Errorf("%v: unexpected access (%q, %d), got %v, expected %v, count = %d", desc, domain, limit, res != nil, expected, m.GetAccessCount(domain)) } } checkCount("foo", 0) checkAccess("first", "foo", 2, true) checkCount("foo", 1) checkAccess("second", "foo", 2, true) checkAccess("third", "foo", 2, false) checkCount("foo", 2) checkCount("bar", 0) checkAccess("other domain", "bar", 2, true) } sigsum-log-go-0.15.2/internal/rate-limit/config.go000066400000000000000000000070401477305677000217700ustar00rootroot00000000000000package rateLimit import ( "bufio" "bytes" "encoding/hex" "fmt" "io" "strconv" submitToken "sigsum.org/sigsum-go/pkg/submit-token" ) type Config struct { // Allowlists, and their daily request limit. AllowedKeys map[string]int // map key is the binary key hash. AllowedDomains map[string]int // map key lowercase domain. AllowPublic int PublicSuffixFile string } // Config file syntax is // key // domain // public // with # used for comments. // The type of config lines. None represent an empty or comment-only line. type configToken int const ( configNone configToken = iota configKey configDomain configPublic ) func parseToken(s []byte) (configToken, error) { switch { case bytes.Equal(s, []byte("key")): return configKey, nil case bytes.Equal(s, []byte("domain")): return configDomain, nil case bytes.Equal(s, []byte("public")): return configPublic, nil default: return configNone, fmt.Errorf("unknown config keyword %q", s) } } func parseLimit(s []byte) (int, error) { // Use ParseUint, to not accept leading +/-. i, err := strconv.ParseUint(string(s), 10, 32) if err != nil { return 0, err } // Limit to 32-bit, so we can always use int. if i >= (1 << 31) { return 0, fmt.Errorf("limit %q is too large", s) } return int(i), nil } func parseLine(line []byte) (configToken, string, int, error) { if comment := bytes.Index(line, []byte{'#'}); comment >= 0 { line = line[:comment] } // TODO: Support quoted file name for public. fields := bytes.Fields(line) if len(fields) == 0 { return configNone, "", 0, nil } if len(fields) != 3 { return 0, "", 0, fmt.Errorf("invalid config line %q", line) } token, err := parseToken(fields[0]) if err != nil { return 0, "", 0, err } limit, err := parseLimit(fields[2]) if err != nil { return 0, "", 0, err } item := string(fields[1]) // Validate item format. switch token { case configKey: b, err := hex.DecodeString(item) if err != nil { return 0, "", 0, err } if len(b) != 32 { return 0, "", 0, fmt.Errorf("invalid length of key hash %q", item) } item = string(b) case configDomain: // Normalize, to be consistent with IDNA2008 (two // different-looking domains that will ultimately be // looked up to the same DNS records should be // normalized to the same string). var err error item, err = submitToken.NormalizeDomainName(item) if err != nil { return 0, "", 0, err } } return token, item, limit, nil } func ParseConfig(file io.Reader) (Config, error) { config := Config{ AllowedKeys: make(map[string]int), AllowedDomains: make(map[string]int), } publicSeen := false for scanner := bufio.NewScanner(file); scanner.Scan(); { configType, item, limit, err := parseLine(scanner.Bytes()) if err != nil { return Config{}, err } switch configType { case configNone: // Do nothing case configKey: if _, ok := config.AllowedKeys[item]; ok { return Config{}, fmt.Errorf("invalid multiple key %x", item) } config.AllowedKeys[item] = limit case configDomain: if _, ok := config.AllowedDomains[item]; ok { return Config{}, fmt.Errorf("invalid multiple domain %s", item) } config.AllowedDomains[item] = limit case configPublic: if publicSeen { return Config{}, fmt.Errorf("invalid multiple \"public\" lines in rate-limit configuration") } config.AllowPublic = limit config.PublicSuffixFile = item publicSeen = true default: panic("internal error in parsing rate limit config") } } return config, nil } sigsum-log-go-0.15.2/internal/rate-limit/config_test.go000066400000000000000000000064421477305677000230340ustar00rootroot00000000000000package rateLimit import ( "bytes" "fmt" "strings" "testing" "sigsum.org/sigsum-go/pkg/crypto" ) func parseConfigString(s string) (Config, error) { return ParseConfig(bytes.NewBuffer([]byte(s))) } var key1 = crypto.HashBytes([]byte{1}) var key2 = crypto.HashBytes([]byte{2}) func keyLine(h *crypto.Hash, limit int) string { return fmt.Sprintf("key %x %d", *h, limit) } func domainLine(d string, limit int) string { return fmt.Sprintf("domain %s %d", d, limit) } func publicLine(f string, limit int) string { return fmt.Sprintf("public %s %d", f, limit) } func configFileForTest() string { return strings.Join([]string{ keyLine(&key1, 10), " " + keyLine(&key2, 20), "\t" + domainLine("example.Net", 30) + "\t", " # comment ", domainLine("WWW.example.org", 40) + " #comment", }, "\n") + "\n" } func TestParseConfigWithPublic(t *testing.T) { configFile := configFileForTest() + publicLine("suffixes.dat", 50) + "\n" config, err := parseConfigString(configFile) if err != nil { t.Fatalf("parse failed: %v", err) } if len(config.AllowedKeys) != 2 { t.Errorf("got %d keys, expected 2", len(config.AllowedKeys)) } if got := config.AllowedKeys[string(key1[:])]; got != 10 { t.Errorf("got limit %d for key1", got) } if got := config.AllowedKeys[string(key2[:])]; got != 20 { t.Errorf("got limit %d for key1", got) } if len(config.AllowedDomains) != 2 { t.Errorf("got %d domains, expected 2", len(config.AllowedKeys)) } if d, got := "example.net", config.AllowedDomains["example.net"]; got != 30 { t.Errorf("got limit %d for domain %s", got, d) } if d, got := "www.example.org", config.AllowedDomains["www.example.org"]; got != 40 { t.Errorf("got limit %d for domain %s", got, d) } if got := config.AllowPublic; got != 50 { t.Errorf("got public limit %d, expected 50", got) } if got := config.PublicSuffixFile; got != "suffixes.dat" { t.Errorf("got unexpected suffix file name %q", got) } } func TestParseConfigWithoutPublic(t *testing.T) { configFile := configFileForTest() config, err := parseConfigString(configFile) if err != nil { t.Fatalf("parse failed: %v", err) } if len(config.AllowedKeys) != 2 { t.Errorf("got %d keys, expected 2", len(config.AllowedKeys)) } if got := config.AllowedKeys[string(key1[:])]; got != 10 { t.Errorf("got limit %d for key1", got) } if got := config.AllowedKeys[string(key2[:])]; got != 20 { t.Errorf("got limit %d for key1", got) } if len(config.AllowedDomains) != 2 { t.Errorf("got %d domains, expected 2", len(config.AllowedKeys)) } if d, got := "example.net", config.AllowedDomains["example.net"]; got != 30 { t.Errorf("got limit %d for domain %s", got, d) } if d, got := "www.example.org", config.AllowedDomains["www.example.org"]; got != 40 { t.Errorf("got limit %d for domain %s", got, d) } if got := config.AllowPublic; got != 0 { t.Errorf("got public limit %d, expected 00", got) } } func TestParseBadConfig(t *testing.T) { configFile := configFileForTest() + "public suffixes.dat 50\n" for _, s := range []string{ keyLine(&key1, 0), domainLine("eXample.net", 7), publicLine("foo.dat", 10), domainLine("other.example.com", -10), } { badConfig := configFile + s + "\n" _, err := parseConfigString(badConfig) if err == nil { t.Errorf("parsing accepted bad input:\n---%s---", badConfig) } } } sigsum-log-go-0.15.2/internal/rate-limit/domain.go000066400000000000000000000073551477305677000220030ustar00rootroot00000000000000package rateLimit import ( "bufio" "bytes" "fmt" "io" "strings" "sigsum.org/sigsum-go/pkg/submit-token" ) type DomainDb struct { // The suffix sets are not modified after construction, hence need no locking. // Represents a plain rule, "example.com". suffixes map[string]bool // Represents a wildcard rule, "*.example.org", and // exceptions, "!foo.example.org". wildcards map[string]map[string]bool } func (db *DomainDb) getSuffix(domain string) (string, error) { s := domain for { if db.suffixes[s] { return s, nil } dot := strings.IndexByte(s, '.') if dot < 0 { return "", fmt.Errorf("no known suffix on domain: %q", domain) } label := s[:dot] next := s[dot+1:] if m := db.wildcards[next]; m != nil && !m[label] { return s, nil } s = next } } // The registered domain is the recognized suffix + one additional label. func (db *DomainDb) GetRegisteredDomain(domain string) (string, error) { suffix, err := db.getSuffix(domain) if err != nil { return "", err } if !strings.HasSuffix(domain, suffix) { panic(fmt.Sprintf("internal error, domain %q and supposed suffix %q", domain, suffix)) } if domain == suffix { // There is no additional label, return domain as is. return domain, nil } dot := strings.LastIndexByte(domain[:len(domain)-len(suffix)-1], '.') if dot < 0 { // Only one additional label. return domain, nil } // Omit additional labels. return domain[dot+1:], nil } type exception struct { label string wildcard string } func parseException(e string) (exception, error) { dot := strings.Index(e, ".") if dot < 0 { return exception{}, fmt.Errorf("must have at least one dot, got %q", e) } return exception{label: e[:dot], wildcard: e[dot+1:]}, nil } func parseSuffixFile(suffixFile io.Reader) (map[string]bool, map[string]map[string]bool, error) { lineno := 0 suffixes := make(map[string]bool) wildcards := make(map[string]map[string]bool) exceptions := []exception{} // Parse file, populate suffixes and wildcard mappings, and record exceptions for later. for scanner := bufio.NewScanner(suffixFile); scanner.Scan(); { lineno++ b := bytes.TrimSpace(scanner.Bytes()) if len(b) == 0 { continue } switch b[0] { case '/': if !bytes.HasPrefix(b, []byte("//")) { return nil, nil, fmt.Errorf("malformed comment on line %d", lineno) } continue case '!': e, err := token.NormalizeDomainName(string(b[1:])) if err != nil { return nil, nil, fmt.Errorf("invalid domain %q on line %d", b[1:], lineno) } exception, err := parseException(e) if err != nil { return nil, nil, fmt.Errorf("invalid exception rule on line %d: %v", lineno, err) } exceptions = append(exceptions, exception) case '*': if !bytes.HasPrefix(b, []byte("*.")) { return nil, nil, fmt.Errorf("invalid wildcard rule %q on line %d", b, lineno) } d, err := token.NormalizeDomainName(string(b[2:])) if err != nil { return nil, nil, fmt.Errorf("invalid domain %q on line %d", b[2:], lineno) } wildcards[d] = make(map[string]bool) default: d, err := token.NormalizeDomainName(string(b)) if err != nil { return nil, nil, fmt.Errorf("invalid domain %q on line %d", b, lineno) } suffixes[d] = true } } for _, e := range exceptions { if wildcards[e.wildcard] == nil { return nil, nil, fmt.Errorf("exception for non-existent wildcard *.%q", e.wildcard) } wildcards[e.wildcard][e.label] = true } return suffixes, wildcards, nil } // The suffix file must be in the format of // https://publicsuffix.org/list/. func NewDomainDb(suffixFile io.Reader) (DomainDb, error) { suffixes, wildcards, err := parseSuffixFile(suffixFile) if err != nil { return DomainDb{}, err } return DomainDb{ suffixes: suffixes, wildcards: wildcards, }, nil } sigsum-log-go-0.15.2/internal/rate-limit/domain_test.go000066400000000000000000000042051477305677000230310ustar00rootroot00000000000000package rateLimit import ( "strings" "testing" ) const exampleSuffixFile = ` org net example.com *.example.net !foo.example.net ` func createDb(t *testing.T, suffixFile string) DomainDb { db, err := NewDomainDb(strings.NewReader(suffixFile)) if err != nil { t.Errorf("db init failed: %v", err) } return db } func TestGetSuffix(t *testing.T) { db := createDb(t, exampleSuffixFile) testOne := func(domain string, suffix string, expectSuccess bool) { res, err := db.getSuffix(domain) if err != nil { if expectSuccess { t.Errorf("getSuffix(%q) failed, got error %v, expected %q", domain, err, suffix) } return } if !expectSuccess { t.Errorf("getSuffix(%q) failed, got %q, expected error", domain, res) } if res != suffix { t.Errorf("getSuffix(%q) failed, got %q, expected %q", domain, res, suffix) } } testOne("example.org", "org", true) testOne("example.com", "example.com", true) testOne("foo.example.com", "example.com", true) testOne("foo.bar.example.com", "example.com", true) testOne("foo.bar.example.net", "bar.example.net", true) testOne("bar.foo.example.net", "net", true) testOne("bar.foo.example.mil", "", false) } func TestGetRegisteredDomain(t *testing.T) { db := createDb(t, exampleSuffixFile) testOne := func(domain string, registeredDomain string, expectSuccess bool) { res, err := db.GetRegisteredDomain(domain) if err != nil { if expectSuccess { t.Errorf("getRegisteredDomain(%q) failed, got error %v, expected %q", domain, err, registeredDomain) } return } if !expectSuccess { t.Errorf("getRegisteredDomain(%q) failed, got %q, expected error", domain, res) } if res != registeredDomain { t.Errorf("getRegisteredDomain(%q) failed, got %q, expected %q", domain, res, registeredDomain) } } testOne("example.org", "example.org", true) testOne("example.com", "example.com", true) testOne("foo.example.com", "foo.example.com", true) testOne("foo.bar.example.com", "bar.example.com", true) testOne("foo.bar.example.net", "foo.bar.example.net", true) testOne("bar.foo.example.net", "example.net", true) testOne("bar.foo.example.mil", "", false) } sigsum-log-go-0.15.2/internal/rate-limit/limiter.go000066400000000000000000000071421477305677000221730ustar00rootroot00000000000000package rateLimit import ( "io" "os" "strings" "sync" "time" "sigsum.org/sigsum-go/pkg/crypto" "sigsum.org/sigsum-go/pkg/submit-token" ) // This domain has the following registered rate limit key pair // // private: 0000000000000000000000000000000000000000000000000000000000000001 // public: 4cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba29 // // for test purposes. const testDomain = "test.sigsum.org" type Limiter interface { // Checks if access count is < limit. If so increment count // and returns a function that can be called to undo the increment, in case no // resources were consumed. Otherwise, returns nil. AccessAllowed(domain *string, keyHash *crypto.Hash) func() } type NoLimit struct{} func (l NoLimit) AccessAllowed(_ *string, _ *crypto.Hash) func() { return func() {} } var schedulePeriod = 24 * time.Hour type clock interface { Now() time.Time } type wallTime struct{} func (_ wallTime) Now() time.Time { return time.Now() } type schedule struct { clock clock sync.Mutex next time.Time } func (s *schedule) IsTime() bool { now := s.clock.Now() s.Lock() defer s.Unlock() if now.Before(s.next) { return false } s.next = s.next.Add(schedulePeriod) return true } type limiter struct { allowedKeys map[string]int allowedDomains map[string]int allowPublic int domainDb DomainDb keyCounts accessCounts domainCounts accessCounts publicCounts accessCounts resetSchedule schedule } // Checks if domain or a suffix of domain is allowed. Second return // value is true if domain was matched by the allow list. func (l *limiter) domainAllowed(domain string) (func(), bool) { s := domain for { if limit, ok := l.allowedDomains[s]; ok { return l.domainCounts.AccessAllowed(s, limit), true } dot := strings.Index(s, ".") if dot < 0 { return nil, false } s = s[dot+1:] } } func (l *limiter) AccessAllowed(submitDomain *string, keyHash *crypto.Hash) func() { if l.resetSchedule.IsTime() { l.keyCounts.Reset() l.domainCounts.Reset() l.publicCounts.Reset() } // TODO: Avoid conversion to string. keyHashString := string(keyHash[:]) if limit, ok := l.allowedKeys[keyHashString]; ok { return l.keyCounts.AccessAllowed(keyHashString, limit) } if submitDomain == nil { // Skip all domain-based checks. return nil } domain, err := token.NormalizeDomainName(*submitDomain) if err != nil { return nil } if relax, ok := l.domainAllowed(domain); ok { return relax } if l.allowPublic <= 0 { return nil } domain, err = l.domainDb.GetRegisteredDomain(domain) if err != nil { // Reject unknown domains. return nil } return l.publicCounts.AccessAllowed(domain, l.allowPublic) } func newLimiter(configFile io.Reader, allowTestDomain bool, clock clock) (Limiter, error) { config, err := ParseConfig(configFile) if err != nil { return nil, err } var db DomainDb if config.AllowPublic > 0 { f, err := os.Open(config.PublicSuffixFile) if err != nil { return nil, err } db, err = NewDomainDb(f) if err != nil { return nil, err } } if !allowTestDomain { config.AllowedDomains[strings.ToLower(testDomain)] = 0 } l := limiter{ allowedKeys: config.AllowedKeys, allowedDomains: config.AllowedDomains, allowPublic: config.AllowPublic, domainDb: db, resetSchedule: schedule{ clock: clock, next: clock.Now().Add(schedulePeriod), }, } // Initialize the mappings. l.keyCounts.Reset() l.domainCounts.Reset() l.publicCounts.Reset() return &l, nil } func NewLimiter(configFile io.Reader, allowTestDomain bool) (Limiter, error) { return newLimiter(configFile, allowTestDomain, wallTime{}) } sigsum-log-go-0.15.2/internal/rate-limit/limiter_test.go000066400000000000000000000134731477305677000232360ustar00rootroot00000000000000package rateLimit import ( "bytes" "fmt" "sync" "testing" "time" "sigsum.org/sigsum-go/pkg/crypto" ) type fakeClock struct { sync.Mutex now time.Time } func (c *fakeClock) Now() time.Time { c.Lock() defer c.Unlock() return c.now } func (c *fakeClock) Advance(delta time.Duration) { c.Lock() defer c.Unlock() c.now = c.now.Add(delta) } func newTestLimiter(config string, clock clock) (Limiter, error) { return newLimiter(bytes.NewBuffer([]byte(config)), false, clock) } type request struct { domain *string keyHash *crypto.Hash delay time.Duration } // Returns the number of successful requests. func repeatedAccess(t *testing.T, config string, count int, requests []request) int { t.Helper() clock := &fakeClock{} limiter, err := newTestLimiter(config, clock) if err != nil { t.Fatal(err) } for i := 0; i < count; i++ { r := &requests[i%len(requests)] if limiter.AccessAllowed(r.domain, r.keyHash) == nil { return i } clock.Advance(r.delay) } return count } func TestNoConfig(t *testing.T) { keyHash := crypto.Hash{1} if repeatedAccess(t, "", 1, []request{request{domain: nil, keyHash: &keyHash, delay: time.Hour}}) != 0 { t.Errorf("access improperly allowed (no domain)") } domain := "foo.example.com" if repeatedAccess(t, "", 1, []request{request{domain: &domain, keyHash: &keyHash, delay: time.Hour}}) != 0 { t.Errorf("access improperly allowed (no domain)") } } func TestKeyLimit(t *testing.T) { key1 := crypto.Hash{1} key2 := crypto.Hash{2} config := fmt.Sprintf("key %x 25 \nkey %x 23\n", key1, key2) if got := repeatedAccess(t, config, 100, []request{request{domain: nil, keyHash: &key1, delay: time.Hour}}); got != 100 { t.Errorf("should sustain one request per hour, but failed after %d requests", got) } if got := repeatedAccess(t, config, 100, []request{request{domain: nil, keyHash: &key2, delay: time.Hour}}); got != 23 { t.Errorf("limit of 23 request per 24 hours not enforced, %d requests were allowed", got) } if got := repeatedAccess(t, config, 100, []request{ request{domain: nil, keyHash: &key1, delay: time.Hour}, request{domain: nil, keyHash: &key2, delay: time.Hour}, }); got != 100 { t.Errorf("should sustain one request per hour, when alternating which key is used, but failed after %d requests", got) } } func TestDomainLimit(t *testing.T) { A := func(s string) *string { return &s } key := crypto.Hash{} config := "domain foo.example.com 25\n" + "domain foo.example.org 23\n" + "domain www.foo.example.org 13\n" if got := repeatedAccess(t, config, 100, []request{request{domain: A("foo.Example.com"), keyHash: &key, delay: time.Hour}}); got != 100 { t.Errorf("should sustain one request per hour, but failed after %d requests", got) } if got := repeatedAccess(t, config, 100, []request{request{domain: A("foo.Example.ORG"), keyHash: &key, delay: time.Hour}}); got != 23 { t.Errorf("limit of 23 request per 24 hours not enforced, %d requests were allowed", got) } if got := repeatedAccess(t, config, 100, []request{ request{domain: A("foo.Example.com"), keyHash: &key, delay: time.Hour}, request{domain: A("foo.Example.ORG"), keyHash: &key, delay: time.Hour}, }); got != 100 { t.Errorf("should sustain one request per hour, when alternating which domain is used, but failed after %d requests", got) } if got := repeatedAccess(t, config, 100, []request{ request{domain: A("foo.Example.org"), keyHash: &key, delay: time.Hour}, request{domain: A("under.foo.Example.org"), keyHash: &key, delay: time.Hour}, }); got != 23 { t.Errorf("limit of 23 request applies also to subdomains, but failed after %d requests", got) } if got := repeatedAccess(t, config, 100, []request{ request{domain: A("foo.Example.org"), keyHash: &key, delay: time.Hour}, request{domain: A("www.foo.Example.org"), keyHash: &key, delay: time.Hour}, }); got != 100 { t.Errorf("should sustain one request per hour, when subdomain has its own configured limit, but failed after %d requests", got) } } func TestPublicLimit(t *testing.T) { A := func(s string) *string { return &s } key := crypto.Hash{} // Test config with only net and org config := "public test_suffix_list.dat 23\n" if got := repeatedAccess(t, config, 100, []request{request{domain: A("foo.Example.com"), keyHash: &key, delay: time.Hour}}); got != 0 { t.Errorf("unknown suffixes should be denied, but %d requests were allowed", got) } if got := repeatedAccess(t, config, 100, []request{request{domain: A("foo.Example.org"), keyHash: &key, delay: time.Hour}}); got != 23 { t.Errorf("limit of 23 request per 24 hours not enforced, %d requests were allowed", got) } if got := repeatedAccess(t, config, 100, []request{ request{domain: A("foo.Example.org"), keyHash: &key, delay: time.Hour}, request{domain: A("bar.Example.ORG"), keyHash: &key, delay: time.Hour}, }); got != 23 { t.Errorf("limit of 23 request (on example.org domains) per 24 hours not enforced, %d requests were allowed", got) } if got := repeatedAccess(t, config, 100, []request{ request{domain: A("foo.Example.org"), keyHash: &key, delay: time.Hour}, request{domain: A("bar.other.org"), keyHash: &key, delay: time.Hour}, }); got != 100 { t.Errorf("should sustain one request per hour, when alternating registered domain, but failed after %d requests", got) } if got := repeatedAccess(t, config, 100, []request{ request{domain: A("foo.Example.org"), keyHash: &key, delay: time.Hour}, request{domain: A("bar.other.org"), keyHash: &key, delay: time.Hour}, }); got != 100 { t.Errorf("should sustain one request per hour, when alternating public suffix, but failed after %d requests", got) } if got := repeatedAccess(t, config, 100, []request{request{domain: A("test.sigsum.org"), keyHash: &key, delay: time.Hour}}); got != 0 { t.Errorf("test domain should be rejected, but failed after %d requests", got) } } sigsum-log-go-0.15.2/internal/rate-limit/test_suffix_list.dat000066400000000000000000000000101477305677000242520ustar00rootroot00000000000000net org sigsum-log-go-0.15.2/internal/state/000077500000000000000000000000001477305677000172445ustar00rootroot00000000000000sigsum-log-go-0.15.2/internal/state/replication_state.go000066400000000000000000000065741477305677000233200ustar00rootroot00000000000000package state import ( "context" "fmt" "time" "sigsum.org/sigsum-go/pkg/api" "sigsum.org/sigsum-go/pkg/crypto" "sigsum.org/sigsum-go/pkg/log" "sigsum.org/sigsum-go/pkg/requests" "sigsum.org/sigsum-go/pkg/types" ) // Subset of the db/client interface. type PrimaryTree interface { GetTreeHead(context.Context) (types.TreeHead, error) GetConsistencyProof(context.Context, *requests.ConsistencyProof) (types.ConsistencyProof, error) } type ReplicationState struct { // Timeout for interaction with primary and secondary. timeout time.Duration primary PrimaryTree secondaryPub crypto.PublicKey secondary api.Secondary } // Return the latest primary tree head with size at least minSize. func (r ReplicationState) getPrimaryTreeHead(ctx context.Context, minSize uint64) (types.TreeHead, error) { primaryTreeHead, err := r.primary.GetTreeHead(ctx) if err != nil { return types.TreeHead{}, fmt.Errorf("get primary tree head: %w", err) } if primaryTreeHead.Size < minSize { return types.TreeHead{}, fmt.Errorf("primary is behind(!), %d < %d", primaryTreeHead.Size, minSize) } return primaryTreeHead, nil } // Return the latest secondary tree head with size at least minSize. func (r ReplicationState) getSecondaryTreeHead(ctx context.Context, minSize uint64, maxSize uint64) (types.TreeHead, error) { sth, err := r.secondary.GetSecondaryTreeHead(ctx) if err != nil { return types.TreeHead{}, fmt.Errorf("failed fetching tree head from secondary: %w", err) } if !sth.Verify(&r.secondaryPub) { return types.TreeHead{}, fmt.Errorf("invalid signature on secondary's tree head") } if sth.Size > maxSize { return types.TreeHead{}, fmt.Errorf("secondary is ahead: %d > %d", sth.Size, maxSize) } if sth.Size < minSize { return types.TreeHead{}, fmt.Errorf("secondary is behind: %d < %d", sth.Size, minSize) } // Responsiblity of GetToCosignTreeHead to check signature, now we no longer need it. return sth.TreeHead, nil } // Check consistency func (r ReplicationState) checkConsistency(ctx context.Context, old *types.TreeHead, new *types.TreeHead) error { if old.Size > new.Size { panic(fmt.Errorf("internal error old.Size (%d) > new.Size (%d)", old.Size, new.Size)) } proof, err := r.primary.GetConsistencyProof(ctx, &requests.ConsistencyProof{ OldSize: old.Size, NewSize: new.Size, }) if err != nil { return fmt.Errorf("unable to get consistency proof from %d to %d: %w", old.Size, new.Size, err) } return proof.Verify(old, new) } // Identifies the latest tree head replicated by the secondary, and // with size >= minSize, or fails if priamry or secondary is in a bad // or too old state. func (r ReplicationState) ReplicatedTreeHead(ctx context.Context, minSize uint64) (types.TreeHead, error) { ctx, cancel := context.WithTimeout(ctx, r.timeout) defer cancel() primaryTreeHead, err := r.getPrimaryTreeHead(ctx, minSize) if err != nil { return types.TreeHead{}, err } if primaryTreeHead.Size == minSize || r.secondary == nil { return primaryTreeHead, nil } secTreeHead, err := r.getSecondaryTreeHead(ctx, minSize, primaryTreeHead.Size) if err != nil { return types.TreeHead{}, fmt.Errorf("failed fetching tree head from secondary: %w", err) } if err := r.checkConsistency(ctx, &secTreeHead, &primaryTreeHead); err != nil { return types.TreeHead{}, err } log.Debug("using latest tree head from secondary: size %d", secTreeHead.Size) return secTreeHead, nil } sigsum-log-go-0.15.2/internal/state/replication_state_test.go000066400000000000000000000076071477305677000243550ustar00rootroot00000000000000package state import ( "context" "testing" "github.com/golang/mock/gomock" "sigsum.org/log-go/internal/mocks/db" "sigsum.org/sigsum-go/pkg/crypto" "sigsum.org/sigsum-go/pkg/merkle" "sigsum.org/sigsum-go/pkg/mocks" "sigsum.org/sigsum-go/pkg/requests" "sigsum.org/sigsum-go/pkg/types" ) func TestGetPrimaryTreeHead(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() th := types.TreeHead{Size: 5} primary := db.NewMockClient(ctrl) primary.EXPECT().GetTreeHead(gomock.Any()).MinTimes(1).Return( types.TreeHead{Size: 5}, nil) state := ReplicationState{primary: primary} ctx := context.Background() for minSize := uint64(0); minSize < 7; minSize++ { got, err := state.getPrimaryTreeHead(ctx, minSize) if minSize <= 5 { if err != nil { t.Errorf("getPrimaryTreeHead size %d failed: %v", minSize, err) } else if got != th { t.Errorf("unexpected tree head %v, expected %v", got, th) } } else { if err == nil { t.Errorf("getPrimaryTreeHead size %d returned unexpected tree head %v", minSize, got) } } } } func TestGetSecondaryTreeHead(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() pub, signer, err := crypto.NewKeyPair() if err != nil { t.Fatal(err) } th := types.TreeHead{Size: 5} sth, err := th.Sign(signer) if err != nil { t.Fatal(err) } secondary := mocks.NewMockSecondary(ctrl) secondary.EXPECT().GetSecondaryTreeHead(gomock.Any()).MinTimes(1).Return(sth, nil) state := ReplicationState{secondary: secondary, secondaryPub: pub} ctx := context.Background() for minSize := uint64(3); minSize < 7; minSize++ { for maxSize := uint64(4); maxSize < 8; maxSize++ { got, err := state.getSecondaryTreeHead(ctx, minSize, maxSize) if minSize <= 5 && 5 <= maxSize { if err != nil { t.Errorf("getSecondaryTreeHead size %d..%d failed: %v", minSize, maxSize, err) } else if got != th { t.Errorf("unexpected tree head %v, expected %v", got, th) } } else { if err == nil { t.Errorf("getSecondaryTreeHead size %d..%d returned unexpected tree head %v", minSize, maxSize, got) } } } } } func TestCheckConsistency(t *testing.T) { withConsistencyProof := func(old *types.TreeHead, new *types.TreeHead, consistencyProof []crypto.Hash) error { t.Helper() state := ReplicationState{} if consistencyProof != nil { ctrl := gomock.NewController(t) defer ctrl.Finish() primary := db.NewMockClient(ctrl) primary.EXPECT().GetConsistencyProof(gomock.Any(), &requests.ConsistencyProof{ OldSize: old.Size, NewSize: new.Size, }).Return( types.ConsistencyProof{ Path: consistencyProof, }, nil) state.primary = primary } return state.checkConsistency(context.Background(), old, new) } // Build a tree, record tree heads as we go. tree := merkle.NewTree() // Tree heads indexed by tree size. treeHeads := []types.TreeHead{types.TreeHead{RootHash: tree.GetRootHash()}} for i := uint64(1); i < 10; i++ { leafHash := crypto.Hash{uint8(i)} tree.AddLeafHash(&leafHash) treeHeads = append(treeHeads, types.TreeHead{ Size: i, RootHash: tree.GetRootHash(), }) } for oldSize := uint64(0); oldSize < 10; oldSize++ { for newSize := oldSize; newSize < 10; newSize++ { consistencyProof, err := tree.ProveConsistency(oldSize, newSize) if err != nil { t.Fatalf("no consistency %d %d: %v", oldSize, newSize, err) } if err := withConsistencyProof( &treeHeads[oldSize], &treeHeads[newSize], consistencyProof); err != nil { t.Errorf("consistency check %d..%d failed: %v", oldSize, newSize, err) } // Invalidate consistency proof. if len(consistencyProof) > 0 { consistencyProof[0][0] ^= 1 if withConsistencyProof( &treeHeads[oldSize], &treeHeads[newSize], consistencyProof) == nil { t.Errorf("consistency check %d..%d succeeded, with bad proof: ", oldSize, newSize) } } } } } sigsum-log-go-0.15.2/internal/state/single.go000066400000000000000000000110721477305677000210550ustar00rootroot00000000000000package state import ( "context" "fmt" "sync" "time" "sigsum.org/log-go/internal/witness" "sigsum.org/sigsum-go/pkg/api" "sigsum.org/sigsum-go/pkg/crypto" "sigsum.org/sigsum-go/pkg/log" "sigsum.org/sigsum-go/pkg/policy" "sigsum.org/sigsum-go/pkg/types" ) // StateManagerSingle implements a single-instance StateManagerPrimary for primary nodes type StateManagerSingle struct { signer crypto.Signer storeSth func(sth *types.SignedTreeHead) error replicationState ReplicationState // Lock-protected access to tree heads. All endpoints are readers. sync.RWMutex signedTreeHead types.SignedTreeHead cosignedTreeHead types.CosignedTreeHead } // NewStateManagerSingle() sets up a new state manager, in particular its // signedTreeHead. An optional secondary node can be used to ensure that // a newer primary tree is not signed unless it has been replicated. func NewStateManagerSingle(primary PrimaryTree, signer crypto.Signer, timeout time.Duration, secondary api.Secondary, secondaryPub *crypto.PublicKey, sthFileName string) (*StateManagerSingle, error) { pub := signer.Public() sthFile := sthFile{name: sthFileName} startupMode, err := sthFile.Startup() if err != nil { return nil, err } var sth types.SignedTreeHead switch startupMode { case StartupSaved: sth, err = sthFile.Load(&pub) if err != nil { return nil, err } case StartupEmpty: th := types.TreeHead{RootHash: crypto.HashBytes([]byte(""))} sth, err = th.Sign(signer) if err != nil { return nil, err } if err := sthFile.Create(&sth); err != nil { return nil, err } case StartupLocalTree: th, err := primary.GetTreeHead(context.Background()) if err != nil { return nil, err } sth, err = th.Sign(signer) if err != nil { return nil, err } if err := sthFile.Create(&sth); err != nil { return nil, err } default: panic(fmt.Sprintf("internal error, unknown startup mode %d", startupMode)) } return &StateManagerSingle{ signer: signer, storeSth: sthFile.Store, replicationState: ReplicationState{ primary: primary, secondary: secondary, secondaryPub: *secondaryPub, timeout: timeout, }, // No cosignatures available at startup. signedTreeHead: sth, cosignedTreeHead: types.CosignedTreeHead{SignedTreeHead: sth}, }, nil } func (sm *StateManagerSingle) SignedTreeHead() types.SignedTreeHead { sm.RLock() defer sm.RUnlock() return sm.signedTreeHead } func (sm *StateManagerSingle) CosignedTreeHead() types.CosignedTreeHead { sm.RLock() defer sm.RUnlock() return sm.cosignedTreeHead } func (sm *StateManagerSingle) Run(ctx context.Context, witnesses []policy.Entity, interval time.Duration) { pub := sm.signer.Public() collector := witness.NewCosignatureCollector(&pub, witnesses, sm.replicationState.primary.GetConsistencyProof) for ctx.Err() == nil { rotateCtx, _ := context.WithTimeout(ctx, interval) currentTH := sm.SignedTreeHead().TreeHead nextTH, err := sm.replicationState.ReplicatedTreeHead( rotateCtx, currentTH.Size) if err != nil { log.Error("no new replicated tree head: %v", err) nextTH = currentTH } if err := sm.rotate(rotateCtx, &nextTH, collector.GetCosignatures); err != nil { log.Warning("failed rotating tree head: %v", err) } // Waits until end of interval <-rotateCtx.Done() } } func (sm *StateManagerSingle) rotate(ctx context.Context, nextTH *types.TreeHead, getCosignatures func(context.Context, *types.SignedTreeHead) map[crypto.Hash]types.Cosignature) error { nextSTH, err := sm.signTreeHead(nextTH) if err != nil { return err } // Blocks (with no locks held), potentially until context times out. cosignatures := getCosignatures(ctx, &nextSTH) sm.Lock() defer sm.Unlock() log.Debug("rotating cosigned tree head: previous size %d, new size %d", sm.cosignedTreeHead.Size, nextSTH.Size) sm.cosignedTreeHead = types.CosignedTreeHead{ SignedTreeHead: nextSTH, Cosignatures: cosignatures, } return nil } func (sm *StateManagerSingle) signTreeHead(nextTH *types.TreeHead) (types.SignedTreeHead, error) { nextSTH, err := nextTH.Sign(sm.signer) if err != nil { return types.SignedTreeHead{}, fmt.Errorf("sign tree head: %v", err) } if err := sm.storeSth(&nextSTH); err != nil { return types.SignedTreeHead{}, err } sm.Lock() defer sm.Unlock() log.Debug("rotating signed tree head: previous size %d, new size %d", sm.signedTreeHead.Size, nextSTH.Size) if nextSTH.Size < sm.signedTreeHead.Size { return types.SignedTreeHead{}, fmt.Errorf("internal error, attempting to truncate signed tree head") } sm.signedTreeHead = nextSTH return nextSTH, nil } sigsum-log-go-0.15.2/internal/state/single_test.go000066400000000000000000000151301477305677000221130ustar00rootroot00000000000000package state import ( "bytes" "context" "fmt" "os" "reflect" "testing" "time" "github.com/golang/mock/gomock" "sigsum.org/log-go/internal/mocks/db" "sigsum.org/sigsum-go/pkg/crypto" "sigsum.org/sigsum-go/pkg/types" ) // TestSigner implements the signer interface. It can be used to mock // an Ed25519 signer that always return the same public key, // signature, and error. // NOTE: Code duplication with internal/node/secondary/endpoint_internal_test.go type TestSigner struct { PublicKey crypto.PublicKey Signature crypto.Signature Error error } func (ts *TestSigner) Public() crypto.PublicKey { return ts.PublicKey } func (ts *TestSigner) Sign(_ []byte) (crypto.Signature, error) { return ts.Signature, ts.Error } const testWitnessTimestamp = 1234 func TestNewStateManagerSingle(t *testing.T) { _, signer, err := crypto.NewKeyPair() if err != nil { t.Fatal(err) } for _, table := range []struct { description string thErr error }{ {"valid", nil}, } { func() { ctrl := gomock.NewController(t) defer ctrl.Finish() trillianClient := db.NewMockClient(ctrl) tmpFile, err := os.CreateTemp("", "sigsum-log-test-sth") if err != nil { t.Fatal(err) } defer tmpFile.Close() defer os.Remove(tmpFile.Name()) emptyTh := types.TreeHead{RootHash: crypto.HashBytes([]byte(""))} emptySth, err := emptyTh.Sign(signer) if err != nil { t.Fatal(err) } if err := emptySth.ToASCII(tmpFile); err != nil { t.Fatal(err) } if err := tmpFile.Close(); err != nil { t.Fatal(err) } // This test uses no secondary. sm, err := NewStateManagerSingle(trillianClient, signer, time.Duration(0), nil, &crypto.PublicKey{}, tmpFile.Name()) if got, want := err != nil, table.description != "valid"; got != want { t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) } if err != nil { return } if got, want := sm.cosignedTreeHead.Size, emptyTh.Size; got != want { t.Errorf("%q: got tree size %d but wanted %d", table.description, got, want) } if got, want := sm.cosignedTreeHead.RootHash[:], emptyTh.RootHash[:]; !bytes.Equal(got, want) { t.Errorf("%q: got tree hash %x but wanted %x", table.description, got, want) } }() } } func TestSignedTreeHead(t *testing.T) { want := types.SignedTreeHead{TreeHead: types.TreeHead{Size: 5}} sm := StateManagerSingle{ signedTreeHead: want, } if got := sm.SignedTreeHead(); got != want { t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v", got, want) } } func TestCosignedTreeHead(t *testing.T) { want := types.CosignedTreeHead{SignedTreeHead: types.SignedTreeHead{TreeHead: types.TreeHead{Size: 5}}} sm := StateManagerSingle{ cosignedTreeHead: want, } if got := sm.CosignedTreeHead(); !reflect.DeepEqual(got, want) { t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v", got, want) } } func TestRotate(t *testing.T) { // Log and witness keys. lPub, lSigner := mustKeyPair(t) wPub, wSigner := mustKeyPair(t) signerErr := TestSigner{lPub, crypto.Signature{}, fmt.Errorf("err")} wKeyHash := crypto.HashBytes(wPub[:]) origin := types.SigsumCheckpointOrigin(&lPub) for _, table := range []struct { desc string signErr bool signedSize uint64 nextSize uint64 withCosignature bool }{ { desc: "empty", nextSize: 1, }, { desc: "1->2", signedSize: 1, nextSize: 2, }, { desc: "cosignatures", signedSize: 2, nextSize: 4, withCosignature: true, }, { desc: "sign failure", signErr: true, signedSize: 1, nextSize: 2, }, } { var signer crypto.Signer if table.signErr { signer = &signerErr } else { signer = lSigner } sth := mustSignTreehead(t, lSigner, table.signedSize) nth := types.TreeHead{Size: table.nextSize} var storedSth types.SignedTreeHead sm := StateManagerSingle{ signer: signer, cosignedTreeHead: types.CosignedTreeHead{SignedTreeHead: sth}, storeSth: func(sth *types.SignedTreeHead) error { storedSth = *sth return nil }, } err := sm.rotate(context.Background(), &nth, func(_ context.Context, sth *types.SignedTreeHead) map[crypto.Hash]types.Cosignature { if !table.withCosignature { return nil } return map[crypto.Hash]types.Cosignature{wKeyHash: mustCosign(t, wSigner, &sth.TreeHead, origin)} }) // Expect error only for signature failures if table.signErr { if err == nil { t.Errorf("%s: rotate succeeded, despite failing signer", table.desc) } } else if err != nil { t.Errorf("%s: rotate failed: %v", table.desc, err) } else { newSth := sm.SignedTreeHead() if !newSth.Verify(&lPub) { t.Errorf("%s: sth signature not valid", table.desc) } if newSth.TreeHead != nth { t.Errorf("%s: unexpected signed tree head after rotation, got size %d, expected %d", table.desc, newSth.Size, table.nextSize) } if storedSth != newSth { t.Errorf("%s: unexpected stored tree head after rotation, got size %d, expected %d", table.desc, storedSth.Size, table.nextSize) } newCth := sm.CosignedTreeHead() if newCth.SignedTreeHead != newSth { t.Errorf("%s: unexpected cosigned tree head after rotation, got size %d, expected %d", table.desc, newCth.Size, table.nextSize) } if table.withCosignature { if len(newCth.Cosignatures) != 1 { t.Fatalf("%s: unexpected cth cosignature count, got %d, expected 1", table.desc, len(newCth.Cosignatures)) } cs, ok := newCth.Cosignatures[wKeyHash] if !ok { t.Fatalf("%s: cosignature missing", table.desc) } if !cs.Verify(&wPub, origin, &newCth.TreeHead) { t.Errorf("%s: cth cosignature not valid", table.desc) } if cs.Timestamp != testWitnessTimestamp { t.Errorf("%s: cth cosignature timestamp not as expected, got %d", table.desc, cs.Timestamp) } } else { if len(newCth.Cosignatures) > 0 { t.Fatalf("%s: non-empty cth cosignature list, got size %d", table.desc, len(newCth.Cosignatures)) } } } } } func mustKeyPair(t *testing.T) (crypto.PublicKey, crypto.Signer) { t.Helper() pub, signer, err := crypto.NewKeyPair() if err != nil { t.Fatal(err) } return pub, signer } func mustCosign(t *testing.T, s crypto.Signer, th *types.TreeHead, origin string) types.Cosignature { t.Helper() signature, err := th.Cosign(s, origin, testWitnessTimestamp) if err != nil { t.Fatal(err) } return signature } func mustSignTreehead(t *testing.T, signer crypto.Signer, size uint64) types.SignedTreeHead { t.Helper() th := types.TreeHead{Size: size} sth, err := th.Sign(signer) if err != nil { t.Fatal(err) } return sth } sigsum-log-go-0.15.2/internal/state/state_manager.go000066400000000000000000000011021477305677000223770ustar00rootroot00000000000000package state import ( "context" "time" "sigsum.org/sigsum-go/pkg/policy" "sigsum.org/sigsum-go/pkg/types" ) // StateManager coordinates access to a nodes tree heads and (co)signatures. type StateManager interface { // Treehead that we have committed to publishing, i.e., // properly replicated, and distributed to witnesses. SignedTreeHead() types.SignedTreeHead // Currently published tree. CosignedTreeHead() types.CosignedTreeHead // Run periodically rotates the node's tree heads and queries witnesses. Run(context.Context, []policy.Entity, time.Duration) } sigsum-log-go-0.15.2/internal/state/sth-file.go000066400000000000000000000060321477305677000213070ustar00rootroot00000000000000package state import ( "bufio" "errors" "fmt" "io" "io/fs" "os" "strings" // Needs extended version with CommitIfNotExists "git.glasklar.is/sigsum/dependencies/safefile" "sigsum.org/sigsum-go/pkg/crypto" "sigsum.org/sigsum-go/pkg/log" "sigsum.org/sigsum-go/pkg/types" ) type sthFile struct { name string } type StartupMode int const ( // Use previously saved sth file. StartupSaved StartupMode = iota // Create sth file representing an empty tree StartupEmpty // Create sth file representing latest local tree head. StartupLocalTree StartupFileSuffix = ".startup" ) func (s sthFile) startupFileName() string { return s.name + StartupFileSuffix } func parseStartupFile(f io.Reader) (StartupMode, error) { // TODO: Add a GetString method to sigsum-go's ascii.Parser? scanner := bufio.NewScanner(f) // Only read first line. if !scanner.Scan() { err := scanner.Err() if err == nil { err = fmt.Errorf("startup file empty") } return StartupSaved, err } line := strings.SplitN( strings.TrimSpace(scanner.Text()), "=", 2) if len(line) != 2 || line[0] != "startup" { return StartupSaved, fmt.Errorf("missing startup= keyword in startup file") } mode := line[1] switch mode { case "empty": return StartupEmpty, nil case "local-tree": return StartupLocalTree, nil default: return StartupSaved, fmt.Errorf("invalid startup mode %q", mode) } } func (s sthFile) Startup() (StartupMode, error) { name := s.startupFileName() f, err := os.Open(name) if errors.Is(err, fs.ErrNotExist) { return StartupSaved, nil } if err != nil { return StartupSaved, err } defer f.Close() return parseStartupFile(f) } func (s sthFile) Load(pub *crypto.PublicKey) (types.SignedTreeHead, error) { f, err := os.Open(s.name) if err != nil { return types.SignedTreeHead{}, err } defer f.Close() var sth types.SignedTreeHead if err := sth.FromASCII(f); err != nil { return types.SignedTreeHead{}, err } if !sth.Verify(pub) { // Accept version 0 signature, to support upgrades. if !sth.VerifyVersion0(pub) { return types.SignedTreeHead{}, fmt.Errorf("invalid signature in file %q", s.name) } log.Info("Loading sth file %q with version 0 tree head signature") } return sth, nil } // Creates a new sth file. Fails if sth file already exists. On // success, any startup file is deleted. func (s sthFile) Create(sth *types.SignedTreeHead) error { f, err := safefile.Create(s.name, 0644) if err != nil { return err } defer f.Close() if err := sth.ToASCII(f); err != nil { return err } // Ensure startup file is deleted before we create the sth // file. if err := os.Remove(s.startupFileName()); err != nil && !errors.Is(err, fs.ErrNotExist) { return err } // Atomically create file, or fail if file already exists. return f.CommitIfNotExists() } func (s sthFile) Store(sth *types.SignedTreeHead) error { f, err := safefile.Create(s.name, 0644) if err != nil { return err } defer f.Close() if err := sth.ToASCII(f); err != nil { return err } // Atomically replace old file with new. return f.Commit() } sigsum-log-go-0.15.2/internal/state/sth-file_test.go000066400000000000000000000111451477305677000223470ustar00rootroot00000000000000package state import ( "bytes" "errors" "fmt" "io/fs" "os" "testing" "sigsum.org/sigsum-go/pkg/crypto" "sigsum.org/sigsum-go/pkg/types" ) func TestParseStartup(t *testing.T) { // Valid inputs, with and without trailing data. for _, table := range []struct { input string output StartupMode }{ {"startup=empty", StartupEmpty}, {"startup=empty\nother line", StartupEmpty}, {"startup=local-tree\n", StartupLocalTree}, } { mode, err := parseStartupFile(bytes.NewBufferString(table.input)) if err != nil { t.Errorf("parsing input %q failed: %v", table.input, err) } else if mode != table.output { t.Errorf("unexpected result for input %q, got %d, wanted %d", table.input, mode, table.output) } } // Invalid inputs. for _, table := range []string{ "", "no-equal", "key=", "startup=", "startup=other", "key=foo=bar", } { mode, err := parseStartupFile(bytes.NewBufferString(table)) if err == nil { t.Errorf("parsing didn't reject invalid input %q, returned mode %d", table, mode) } } } func TestStartupNoFile(t *testing.T) { withTmpDir(t, func(dir string) { sthFile := sthFile{dir + "foo"} mode, err := sthFile.Startup() if err != nil { t.Errorf("error with missing startup file: %v", err) } else if mode != StartupSaved { t.Errorf("got unexpected mode %d with missing startup file", mode) } if os.Geteuid() == 0 { t.Skip("skipping test with supposedly unreadable file, because we appear to run with root privileges") } // Create a file that can't be read. os.WriteFile(dir+"foo.startup", []byte{}, 0) mode, err = sthFile.Startup() if !errors.Is(err, fs.ErrPermission) { t.Errorf("unexpected result for unreadable file, expected permission error, got mode: %d, err: %v", mode, err) } }) } func TestStore(t *testing.T) { withTmpDir(t, func(dir string) { sthFile := sthFile{dir + "foo"} signer := crypto.NewEd25519Signer(&crypto.PrivateKey{7}) pub := signer.Public() sth0 := mustSignTh(t, &types.TreeHead{}, signer) sth1 := mustSignTh(t, &types.TreeHead{Size: 1}, signer) invalidSth := sth1 invalidSth.Size++ // Invalidates signature for _, table := range []struct { sth *types.SignedTreeHead expErr bool }{ {&sth0, false}, {&sth1, false}, {&invalidSth, true}, } { if err := sthFile.Store(table.sth); err != nil { t.Fatalf("storing sth, size %d, failed", table.sth.Size) } sth, err := sthFile.Load(&pub) if table.expErr { if err == nil { t.Errorf("unexpected success loading invalid sth") } } else if err != nil { t.Errorf("loading sth, size %d, failed: %v", table.sth.Size, err) } else if sth != *table.sth { t.Errorf("loading sth incorrectly, got: %v, wanted: %v", sth, *table.sth) } } }) } func TestCreate(t *testing.T) { withTmpDir(t, func(dir string) { sthFile := sthFile{dir + "foo"} startupFileName := dir + "foo.startup" // Create file to be deleted. if err := os.WriteFile(startupFileName, []byte("foo"), 0666); err != nil { t.Fatalf("creating startup file %q failed: %v", startupFileName, err) } if _, err := os.ReadFile(startupFileName); err != nil { t.Fatalf("reading startup file %q failed: %v", startupFileName, err) } signer := crypto.NewEd25519Signer(&crypto.PrivateKey{7}) pub := signer.Public() sth0 := mustSignTh(t, &types.TreeHead{}, signer) sth1 := mustSignTh(t, &types.TreeHead{Size: 1}, signer) if err := sthFile.Create(&sth0); err != nil { t.Fatalf("creating sth file failed: %v", err) } if _, err := os.ReadFile(startupFileName); !errors.Is(err, fs.ErrNotExist) { t.Errorf("startup file is still around after sth was created, err: %v", err) } if sth, err := sthFile.Load(&pub); err != nil || sth != sth0 { if err != nil { t.Errorf("loading sth, failed: %v", err) } else if sth != sth0 { t.Errorf("loading sth incorrectly, got: %v, wanted: %v", sth, sth0) } } if err := sthFile.Create(&sth1); err == nil || !errors.Is(err.(*os.LinkError).Unwrap(), fs.ErrExist) { t.Fatalf("creating sth should have failed with EEXIST, got err: %v", err) } }) } // Creates temporary directory, runs function, end then removes files // and directory. func withTmpDir(t *testing.T, f func(dir string)) { t.Helper() dir, err := os.MkdirTemp("/tmp", "log-go-sthfile-test") if err != nil { t.Fatalf("failed to create temporary directory for test") } defer os.RemoveAll(dir) f(fmt.Sprintf("%s%c", dir, os.PathSeparator)) } func mustSignTh(t *testing.T, th *types.TreeHead, signer crypto.Signer) types.SignedTreeHead { sth, err := th.Sign(signer) if err != nil { t.Fatalf("signing failed: %v", err) } return sth } sigsum-log-go-0.15.2/internal/version/000077500000000000000000000000001477305677000176115ustar00rootroot00000000000000sigsum-log-go-0.15.2/internal/version/version.go000066400000000000000000000024051477305677000216260ustar00rootroot00000000000000package version import ( "fmt" "runtime/debug" ) func ModuleVersion() string { info, ok := debug.ReadBuildInfo() if !ok { return "unknown" } // When built, e.g., using go install .../sigsum-go@vX.Y.Z. version := info.Main.Version if version != "(devel)" { return version } // Use git commit, if available. The vcs.* fields are // populated when running "go build" in a git checkout, // *without* listing specific source files on the commandline. m := make(map[string]string) for _, setting := range info.Settings { m[setting.Key] = setting.Value } revision, ok := m["vcs.revision"] if !ok { return version } version = fmt.Sprintf("git %s", revision) if t, ok := m["vcs.time"]; ok { version += " " + t } // Note that any untracked file (if not listed in .gitignore) // counts as a local modification. Which makes sense, since // the go toolchain determines what to do automatically, based // on which files exist. For this flag to be reliable, avoid // adding patterns in .gitignore that could match files that // have meaning to the go toolchain. if m["vcs.modified"] != "false" { version += " (with local changes)" } return version } func DisplayVersion(tool string) { fmt.Printf("%s (sigsum-go module) %s\n", tool, ModuleVersion()) } sigsum-log-go-0.15.2/internal/witness/000077500000000000000000000000001477305677000176205ustar00rootroot00000000000000sigsum-log-go-0.15.2/internal/witness/witness.go000066400000000000000000000104431477305677000216450ustar00rootroot00000000000000package witness import ( "context" "sync" "sigsum.org/sigsum-go/pkg/api" "sigsum.org/sigsum-go/pkg/checkpoint" "sigsum.org/sigsum-go/pkg/client" "sigsum.org/sigsum-go/pkg/crypto" "sigsum.org/sigsum-go/pkg/log" "sigsum.org/sigsum-go/pkg/policy" "sigsum.org/sigsum-go/pkg/requests" "sigsum.org/sigsum-go/pkg/types" ) type GetConsistencyProofFunc func(ctx context.Context, req *requests.ConsistencyProof) (types.ConsistencyProof, error) // Not concurrency safe, due to updates of prevSize. type witness struct { client api.Witness entity policy.Entity keyHash crypto.Hash prevSize uint64 // Error from previous attempt. prevError error } func newWitness(w *policy.Entity) *witness { return &witness{ client: client.New(client.Config{URL: w.URL, UserAgent: "Sigsum log-go server"}), entity: *w, keyHash: crypto.HashBytes(w.PublicKey[:]), prevSize: 0, } } // Pack key hash and cosignature together, so they can be sent over a channel. type cosignatureItem struct { keyHash crypto.Hash cs types.Cosignature } func (w *witness) getCosignature(ctx context.Context, cp *checkpoint.Checkpoint, getConsistencyProof GetConsistencyProofFunc) (cosignatureItem, error) { freshOldSize := false for { proof, err := getConsistencyProof(ctx, &requests.ConsistencyProof{ OldSize: w.prevSize, NewSize: cp.TreeHead.Size, }) if err != nil { return cosignatureItem{}, err } signatures, err := w.client.AddCheckpoint(ctx, requests.AddCheckpoint{ OldSize: w.prevSize, Proof: proof, Checkpoint: *cp, }) if err == nil { cs, err := cp.VerifyCosignatureByKey(signatures, &w.entity.PublicKey) if err != nil { return cosignatureItem{}, err } w.prevSize = cp.Size return cosignatureItem{keyHash: w.keyHash, cs: cs}, nil } // Retry only once. if freshOldSize { return cosignatureItem{}, err } if oldSize, ok := api.ErrorConflictOldSize(err); ok { w.prevSize = oldSize freshOldSize = true } else { return cosignatureItem{}, err } } } type CosignatureCollector struct { origin string keyId checkpoint.KeyId getConsistencyProof GetConsistencyProofFunc witnesses []*witness } func NewCosignatureCollector(logPublicKey *crypto.PublicKey, witnesses []policy.Entity, getConsistencyProof GetConsistencyProofFunc) *CosignatureCollector { origin := types.SigsumCheckpointOrigin(logPublicKey) collector := CosignatureCollector{ origin: origin, keyId: checkpoint.NewLogKeyId(origin, logPublicKey), getConsistencyProof: getConsistencyProof, } for _, w := range witnesses { collector.witnesses = append(collector.witnesses, newWitness(&w)) } return &collector } // Queries all witnesses in parallel, blocks until we have result or error from each of them. // Must not be concurrently called. func (c *CosignatureCollector) GetCosignatures(ctx context.Context, sth *types.SignedTreeHead) map[crypto.Hash]types.Cosignature { cp := checkpoint.Checkpoint{ SignedTreeHead: *sth, Origin: c.origin, KeyId: c.keyId, } wg := sync.WaitGroup{} ch := make(chan cosignatureItem) // Query witnesses in parallel for i, w := range c.witnesses { wg.Add(1) go func(i int, w *witness) { cs, err := w.getCosignature(ctx, &cp, c.getConsistencyProof) // On logging of errors: api.ErrorStatusCode // returns the explicitly associated status // code, if any, otherwise 500. To reduce // amount of logging at INFO level, log only // errors when there's a change of status // code. Repeated errors are deemed less // interesting, and logged at DEBUG level. if err != nil { if w.prevError == nil || (api.ErrorStatusCode(err) != api.ErrorStatusCode(w.prevError)) { log.Info("querying witness %q failed: %v", w.entity.URL, err) } else { log.Debug("querying witness %q failed: %v", w.entity.URL, err) } } else { if w.prevError != nil { log.Info("querying witness %q succeeded, previous attempt failed: %v", w.entity.URL, w.prevError) } ch <- cs } w.prevError = err wg.Done() }(i, w) } go func() { wg.Wait(); close(ch) }() cosignatures := make(map[crypto.Hash]types.Cosignature) for i := range ch { // TODO: Check that cosignature timestamp is reasonable? cosignatures[i.keyHash] = i.cs } return cosignatures } sigsum-log-go-0.15.2/internal/witness/witness_test.go000066400000000000000000000170051477305677000227050ustar00rootroot00000000000000package witness import ( "context" "fmt" "reflect" "testing" "time" "github.com/golang/mock/gomock" "sigsum.org/log-go/internal/mocks/db" "sigsum.org/sigsum-go/pkg/api" "sigsum.org/sigsum-go/pkg/checkpoint" "sigsum.org/sigsum-go/pkg/crypto" "sigsum.org/sigsum-go/pkg/mocks" "sigsum.org/sigsum-go/pkg/policy" "sigsum.org/sigsum-go/pkg/requests" "sigsum.org/sigsum-go/pkg/types" ) func testWitness(t *testing.T, ctrl *gomock.Controller) (crypto.Signer, *mocks.MockWitness, *witness) { pub, signer := mustKeyPair(t) client := mocks.NewMockWitness(ctrl) return signer, client, &witness{ client: client, entity: policy.Entity{PublicKey: pub, URL: "test://test"}, keyHash: crypto.HashBytes(pub[:]), } } type ptrMatcher struct { m gomock.Matcher } func (p ptrMatcher) Matches(x any) bool { return x != nil && p.m.Matches(reflect.ValueOf(x).Elem().Interface()) } func (p ptrMatcher) String() string { return fmt.Sprintf("non-nil pointer to %s", p.m) } func Ptr(m gomock.Matcher) gomock.Matcher { return ptrMatcher{m: m} } func TestWitnessEmpty(t *testing.T) { testTimestamp := uint64(101010) _, logSigner := mustKeyPair(t) ctrl := gomock.NewController(t) witnessSigner, cli, w := testWitness(t, ctrl) log := db.NewMockClient(ctrl) cp := mustSignTreehead(t, logSigner, 5) log.EXPECT().GetConsistencyProof(gomock.Any(), Ptr(gomock.Eq(requests.ConsistencyProof{OldSize: 0, NewSize: 5}))).Return(types.ConsistencyProof{}, nil) cli.EXPECT().AddCheckpoint(gomock.Any(), gomock.Any()).DoAndReturn( func(_ context.Context, req requests.AddCheckpoint) ([]checkpoint.CosignatureLine, error) { if req.OldSize != 0 || req.Checkpoint != cp { t.Fatalf("unexpected add tree head req, got: %v", req) } return mustCosign(t, witnessSigner, &req.Checkpoint, testTimestamp), nil }) i, err := w.getCosignature(context.Background(), &cp, log.GetConsistencyProof) if err != nil { t.Fatalf("getCosignature failed: %v", err) } if i.cs.Timestamp != testTimestamp { t.Errorf("unexpected timestamp, got %d, want: %d", i.cs.Timestamp, testTimestamp) } } func TestWitnessBadSize(t *testing.T) { testTimestamp := uint64(101010) _, logSigner := mustKeyPair(t) ctrl := gomock.NewController(t) witnessSigner, cli, w := testWitness(t, ctrl) log := db.NewMockClient(ctrl) cp := mustSignTreehead(t, logSigner, 5) log.EXPECT().GetConsistencyProof(gomock.Any(), Ptr(gomock.Eq(requests.ConsistencyProof{OldSize: 0, NewSize: 5}))).Return(types.ConsistencyProof{}, nil) log.EXPECT().GetConsistencyProof(gomock.Any(), Ptr(gomock.Eq(requests.ConsistencyProof{OldSize: 2, NewSize: 5}))).Return( // Dummy path, but length 1 to distinguish the two calls. types.ConsistencyProof{Path: []crypto.Hash{crypto.Hash{}}}, nil) cli.EXPECT().AddCheckpoint(gomock.Any(), gomock.Any()).DoAndReturn( func(_ context.Context, req requests.AddCheckpoint) ([]checkpoint.CosignatureLine, error) { if req.OldSize != 0 || req.Checkpoint != cp || len(req.Proof.Path) != 0 { t.Fatalf("unexpected add tree head req, got: %v", req) } return nil, api.ErrConflict.WithOldSize(2) }) cli.EXPECT().AddCheckpoint(gomock.Any(), gomock.Any()).DoAndReturn( func(_ context.Context, req requests.AddCheckpoint) ([]checkpoint.CosignatureLine, error) { if req.OldSize != 2 || req.Checkpoint != cp || len(req.Proof.Path) != 1 { t.Fatalf("unexpected add tree head req, got: %v", req) } return mustCosign(t, witnessSigner, &req.Checkpoint, testTimestamp), nil }) i, err := w.getCosignature(context.Background(), &cp, log.GetConsistencyProof) if err != nil { t.Fatalf("getCosignature failed: %v", err) } if i.cs.Timestamp != testTimestamp { t.Errorf("unexpected timestamp, got %d, want: %d", i.cs.Timestamp, testTimestamp) } } func TestGetCosignatures(t *testing.T) { testTimestamp := uint64(101010) _, logSigner := mustKeyPair(t) ctrl := gomock.NewController(t) signer1, cli1, w1 := testWitness(t, ctrl) _, cli2, w2 := testWitness(t, ctrl) signer3, cli3, w3 := testWitness(t, ctrl) log := db.NewMockClient(ctrl) cp := mustSignTreehead(t, logSigner, 5) collector := CosignatureCollector{ origin: cp.Origin, keyId: cp.KeyId, getConsistencyProof: log.GetConsistencyProof, witnesses: []*witness{w1, w2, w3}, } log.EXPECT().GetConsistencyProof(gomock.Any(), Ptr(gomock.Eq(requests.ConsistencyProof{OldSize: 0, NewSize: 5}))).Return(types.ConsistencyProof{}, nil).AnyTimes() log.EXPECT().GetConsistencyProof(gomock.Any(), Ptr(gomock.Eq(requests.ConsistencyProof{OldSize: 2, NewSize: 5}))).Return( // Dummy path, but length 1 to distinguish the two calls. types.ConsistencyProof{Path: []crypto.Hash{crypto.Hash{}}}, nil) // First witness needs size query. cli1.EXPECT().AddCheckpoint(gomock.Any(), gomock.Any()).DoAndReturn( func(_ context.Context, req requests.AddCheckpoint) ([]checkpoint.CosignatureLine, error) { if req.OldSize != 0 || req.Checkpoint != cp || len(req.Proof.Path) != 0 { t.Fatalf("unexpected add tree head req, got: %v", req) } return nil, api.ErrConflict.WithOldSize(2) }) cli1.EXPECT().AddCheckpoint(gomock.Any(), gomock.Any()).DoAndReturn( func(_ context.Context, req requests.AddCheckpoint) ([]checkpoint.CosignatureLine, error) { if req.OldSize != 2 || req.Checkpoint != cp || len(req.Proof.Path) != 1 { t.Fatalf("unexpected add tree head req, got: %v", req) } return mustCosign(t, signer1, &req.Checkpoint, testTimestamp), nil }) // Second witness fails. cli2.EXPECT().AddCheckpoint(gomock.Any(), gomock.Any()).DoAndReturn( func(_ context.Context, req requests.AddCheckpoint) ([]checkpoint.CosignatureLine, error) { if req.OldSize != 0 || req.Checkpoint != cp || len(req.Proof.Path) != 0 { t.Fatalf("unexpected add tree head req, got: %v", req) } return nil, fmt.Errorf("mock failure") }) // Third witness succeeds with slight delay. cli3.EXPECT().AddCheckpoint(gomock.Any(), gomock.Any()).DoAndReturn( func(_ context.Context, req requests.AddCheckpoint) ([]checkpoint.CosignatureLine, error) { if req.OldSize != 0 || req.Checkpoint != cp || len(req.Proof.Path) != 0 { t.Fatalf("unexpected add tree head req, got: %v", req) } time.Sleep(50 * time.Millisecond) return mustCosign(t, signer3, &req.Checkpoint, testTimestamp), nil }) cosignatures := collector.GetCosignatures(context.Background(), &cp.SignedTreeHead) if got, want := len(cosignatures), 2; got != want { t.Errorf("unexpected number of cosignatures, got: %d, want: %d", got, want) } } func mustKeyPair(t *testing.T) (crypto.PublicKey, crypto.Signer) { t.Helper() pub, signer, err := crypto.NewKeyPair() if err != nil { t.Fatal(err) } return pub, signer } func mustCosign(t *testing.T, s crypto.Signer, cp *checkpoint.Checkpoint, timestamp uint64) []checkpoint.CosignatureLine { t.Helper() cs, err := cp.Cosign(s, timestamp) if err != nil { t.Fatal(err) } publicKey := s.Public() keyName := "example.org/witness" return []checkpoint.CosignatureLine{ checkpoint.CosignatureLine{ KeyName: keyName, KeyId: checkpoint.NewWitnessKeyId(keyName, &publicKey), Cosignature: types.Cosignature{ Timestamp: cs.Timestamp, Signature: cs.Signature, }, }, } } func mustSignTreehead(t *testing.T, signer crypto.Signer, size uint64) checkpoint.Checkpoint { t.Helper() th := types.TreeHead{Size: size} sth, err := th.Sign(signer) if err != nil { t.Fatal(err) } pub := signer.Public() origin := types.SigsumCheckpointOrigin(&pub) return checkpoint.Checkpoint{ SignedTreeHead: sth, Origin: origin, KeyId: checkpoint.NewLogKeyId(origin, &pub), } } sigsum-log-go-0.15.2/tools.go000066400000000000000000000005711477305677000160020ustar00rootroot00000000000000//go:build tools package tools import ( _ "github.com/golang/mock/mockgen" _ "github.com/google/trillian/cmd/createtree" _ "github.com/google/trillian/cmd/deletetree" _ "github.com/google/trillian/cmd/trillian_log_server" _ "github.com/google/trillian/cmd/trillian_log_signer" _ "github.com/google/trillian/cmd/updatetree" _ "sigsum.org/sigsum-go/cmd/sigsum-submit" )