├── .go-version ├── .github ├── workflows │ ├── acctests.yaml │ ├── backport.yaml │ ├── bulk-dep-upgrades.yaml │ ├── jira.yaml │ └── tests.yaml └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CODEOWNERS ├── tools ├── go.mod ├── tools.go └── go.sum ├── scripts ├── gofmtcheck.sh └── configure.sh ├── bootstrap └── terraform │ ├── docker-compose.yml │ └── redis.tf ├── cmd └── vault-plugin-database-redis │ └── main.go ├── Makefile ├── CHANGELOG.md ├── connection_producer.go ├── go.mod ├── redis.go ├── README.md ├── LICENSE ├── redis_test.go └── go.sum /.go-version: -------------------------------------------------------------------------------- 1 | 1.25.1 2 | -------------------------------------------------------------------------------- /.github/workflows/acctests.yaml: -------------------------------------------------------------------------------- 1 | name: Acceptance Tests 2 | 3 | on: push 4 | 5 | jobs: 6 | run-acceptance-tests: 7 | uses: hashicorp/vault-workflows-common/.github/workflows/acc-tests.yaml@main 8 | secrets: inherit 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .vscode 4 | pkg/* 5 | bin/* 6 | 7 | bootstrap/terraform/.terraform 8 | bootstrap/terraform/terraform.tfstate 9 | bootstrap/terraform/terraform.tfstate.backup 10 | bootstrap/terraform/local_environment_setup.sh 11 | scripts/tests/tls -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Each line is a file pattern followed by one or more owners. Being an owner 2 | # means those groups or individuals will be added as reviewers to PRs affecting 3 | # those areas of the code. 4 | # 5 | # More on CODEOWNERS files: https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners 6 | 7 | * @hashicorp/vault-ecosystem 8 | -------------------------------------------------------------------------------- /.github/workflows/backport.yaml: -------------------------------------------------------------------------------- 1 | name: Backport Assistant 2 | on: 3 | pull_request_target: 4 | types: 5 | - closed 6 | - labeled 7 | permissions: write-all 8 | jobs: 9 | backport: 10 | # using `main` as the ref will keep your workflow up-to-date 11 | uses: hashicorp/vault-workflows-common/.github/workflows/backport.yaml@main 12 | secrets: 13 | VAULT_ECO_GITHUB_TOKEN: ${{ secrets.VAULT_ECO_GITHUB_TOKEN }} -------------------------------------------------------------------------------- /tools/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hashicorp/vault-plugin-scaffolding/tools 2 | 3 | go 1.19 4 | 5 | require mvdan.cc/gofumpt v0.3.1 6 | 7 | require ( 8 | github.com/google/go-cmp v0.5.7 // indirect 9 | golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect 10 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect 11 | golang.org/x/sys v0.1.0 // indirect 12 | golang.org/x/tools v0.1.10 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /scripts/gofmtcheck.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright (c) HashiCorp, Inc. 3 | # SPDX-License-Identifier: MPL-2.0 4 | 5 | 6 | echo "==> Checking that code complies with gofumpt requirements..." 7 | 8 | gofmt_files=$(gofumpt -l `find . -name '*.go'`) 9 | if [[ -n ${gofmt_files} ]]; then 10 | echo 'gofumpt needs running on the following files:' 11 | echo "${gofmt_files}" 12 | echo "You can use the command: \`make fmt\` to reformat code." 13 | exit 1 14 | fi -------------------------------------------------------------------------------- /bootstrap/terraform/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | version: '3.9' 5 | volumes: 6 | redis_data: {} 7 | networks: 8 | redis: 9 | driver: bridge 10 | services: 11 | redis: 12 | image: redis/redis-stack-server:latest 13 | container_name: redis 14 | networks: 15 | - redis 16 | restart: always 17 | command: "redis-server --requirepass default-pa55w0rd" 18 | ports: 19 | - "6379:6379" 20 | volumes: 21 | - ./data/redis:/data -------------------------------------------------------------------------------- /tools/tools.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | //go:build tools 5 | 6 | // This file ensures tool dependencies are kept in sync. This is the 7 | // recommended way of doing this according to 8 | // https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module 9 | // To install the following tools at the version used by this repo run: 10 | // $ make bootstrap 11 | // or 12 | // $ go generate -tags tools tools/tools.go 13 | 14 | package tools 15 | 16 | //go:generate go install mvdan.cc/gofumpt 17 | import ( 18 | _ "mvdan.cc/gofumpt" 19 | ) 20 | -------------------------------------------------------------------------------- /.github/workflows/bulk-dep-upgrades.yaml: -------------------------------------------------------------------------------- 1 | name: Upgrade dependencies 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | # Runs 12:00AM on the first of every month 6 | - cron: '0 0 1 * *' 7 | jobs: 8 | upgrade: 9 | # using `main` as the ref will keep your workflow up-to-date 10 | uses: hashicorp/vault-workflows-common/.github/workflows/bulk-dependency-updates.yaml@main 11 | secrets: 12 | VAULT_ECO_GITHUB_TOKEN: ${{ secrets.VAULT_ECO_GITHUB_TOKEN }} 13 | with: 14 | # either hashicorp/vault-ecosystem-applications or hashicorp/vault-ecosystem-foundations 15 | reviewer-team: hashicorp/vault-ecosystem-applications 16 | repository: ${{ github.repository }} 17 | run-id: ${{ github.run_id }} 18 | -------------------------------------------------------------------------------- /.github/workflows/jira.yaml: -------------------------------------------------------------------------------- 1 | name: Jira Sync 2 | on: 3 | issues: 4 | types: [opened, closed, deleted, reopened] 5 | pull_request_target: 6 | types: [opened, closed, reopened] 7 | issue_comment: # Also triggers when commenting on a PR from the conversation view 8 | types: [created] 9 | jobs: 10 | sync: 11 | uses: hashicorp/vault-workflows-common/.github/workflows/jira.yaml@main 12 | # assuming you use Vault to get secrets 13 | # if you use GitHub secrets, use secrets.XYZ instead of steps.secrets.outputs.XYZ 14 | secrets: 15 | JIRA_SYNC_BASE_URL: ${{ secrets.JIRA_SYNC_BASE_URL }} 16 | JIRA_SYNC_USER_EMAIL: ${{ secrets.JIRA_SYNC_USER_EMAIL }} 17 | JIRA_SYNC_API_TOKEN: ${{ secrets.JIRA_SYNC_API_TOKEN }} 18 | with: 19 | teams-array: '["ecosystem", "applications"]' 20 | -------------------------------------------------------------------------------- /cmd/vault-plugin-database-redis/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "os" 8 | 9 | hclog "github.com/hashicorp/go-hclog" 10 | redis "github.com/hashicorp/vault-plugin-database-redis" 11 | dbplugin "github.com/hashicorp/vault/sdk/database/dbplugin/v5" 12 | ) 13 | 14 | func main() { 15 | err := Run() 16 | if err != nil { 17 | logger := hclog.New(&hclog.LoggerOptions{}) 18 | 19 | logger.Error("plugin shutting down", "error", err) 20 | os.Exit(1) 21 | } 22 | } 23 | 24 | // Run instantiates a RedisDB object, and runs the RPC server for the plugin 25 | func Run() error { 26 | db, err := redis.New() 27 | if err != nil { 28 | return err 29 | } 30 | 31 | dbplugin.Serve(db.(dbplugin.Database)) 32 | 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /bootstrap/terraform/redis.tf: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | resource "null_resource" "docker_compose_up" { 5 | triggers = { 6 | always_run = "${timestamp()}" 7 | } 8 | 9 | // Running down at the beginning so terraform apply can be executed multiple times to pick up on latest docker-compose.yaml changes 10 | provisioner "local-exec" { 11 | command = "docker compose -f ./docker-compose.yml down && docker compose -f ./docker-compose.yml up -d" 12 | when = create 13 | } 14 | } 15 | 16 | resource "null_resource" "docker_compose_down" { 17 | triggers = { 18 | always_run = "${timestamp()}" 19 | } 20 | 21 | provisioner "local-exec" { 22 | command = "docker compose -f ./docker-compose.yml down" 23 | when = destroy 24 | } 25 | } 26 | 27 | resource "local_file" "setup_environment_file" { 28 | filename = "local_environment_setup.sh" 29 | content = < 20 | 21 | - [ ] I have documented a clear reason for, and description of, the change I am making. 22 | 23 | - [ ] If applicable, I've documented a plan to revert these changes if they require more than reverting the pull request. 24 | 25 | - [ ] If applicable, I've documented the impact of any changes to security controls. 26 | 27 | Examples of changes to security controls include using new access control methods, adding or removing logging pipelines, etc. 28 | -------------------------------------------------------------------------------- /scripts/configure.sh: -------------------------------------------------------------------------------- 1 | # Copyright (c) HashiCorp, Inc. 2 | # SPDX-License-Identifier: MPL-2.0 3 | 4 | PLUGIN_DIR=$1 5 | PLUGIN_NAME=$2 6 | TEST_REDIS_HOST=$3 7 | TEST_REDIS_PORT=$4 8 | TEST_REDIS_USERNAME=$5 9 | TEST_REDIS_PASSWORD=$6 10 | 11 | vault plugin deregister "$PLUGIN_NAME" 12 | vault secrets disable database 13 | killall "$PLUGIN_NAME" 14 | 15 | # Give a bit of time for the binary file to be released so we can copy over it 16 | sleep 3 17 | 18 | # Copy the binary so text file is not busy when rebuilding & the plugin is registered 19 | cp ./bin/"$PLUGIN_NAME" "$PLUGIN_DIR"/"$PLUGIN_NAME" 20 | 21 | # Sets up the binary with local changes 22 | vault secrets enable database 23 | vault plugin register \ 24 | -sha256="$(shasum -a 256 "$PLUGIN_DIR"/"$PLUGIN_NAME" | awk '{print $1}')" \ 25 | database "$PLUGIN_NAME" 26 | 27 | # Configure & test the new registered plugin 28 | vault write database/config/local-redis \ 29 | plugin_name="$PLUGIN_NAME" \ 30 | allowed_roles="*" \ 31 | host="$TEST_REDIS_HOST" \ 32 | port="$TEST_REDIS_PORT" \ 33 | username="$TEST_REDIS_USERNAME" \ 34 | password="$TEST_REDIS_PASSWORD" \ 35 | insecure_tls=true 36 | 37 | vault write database/roles/my-dynamic-role \ 38 | db_name="local-redis" \ 39 | creation_statements='["+@read"]' \ 40 | default_ttl="5m" \ 41 | max_ttl="1h" 42 | 43 | vault read database/creds/my-dynamic-role -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REPO_DIR := $(shell basename $(CURDIR)) 2 | PLUGIN_DIR := $(GOPATH)/vault-plugins 3 | PLUGIN_NAME := $(shell command ls cmd/) 4 | 5 | .PHONY: default 6 | default: dev 7 | 8 | .PHONY: dev 9 | dev: 10 | CGO_ENABLED=0 go build -o bin/$(PLUGIN_NAME) cmd/$(PLUGIN_NAME)/main.go 11 | 12 | .PHONY: bootstrap 13 | bootstrap: 14 | @echo "Downloading tools ..." 15 | @go generate -tags tools tools/tools.go 16 | # This should only ever be performed once, so we lean on the cmd/ directory 17 | # to indicate whether this has already been done. 18 | @if [ "$(PLUGIN_NAME)" != "$(REPO_DIR)" ]; then \ 19 | echo "Renaming cmd/$(PLUGIN_NAME) to cmd/$(REPO_DIR) ..."; \ 20 | mv cmd/$(PLUGIN_NAME) to cmd/$(REPO_DIR); \ 21 | echo "Renaming Go module to github.com/hashicorp/$(REPO_DIR) ..."; \ 22 | go mod edit -module github.com/hashicorp/$(REPO_DIR); \ 23 | fi 24 | 25 | .PHONY: test 26 | test: fmtcheck 27 | CGO_ENABLED=0 go test -v ./... $(TESTARGS) -timeout=20m 28 | 29 | .PHONY: testacc 30 | testacc: fmtcheck 31 | CGO_ENABLED=0 VAULT_ACC=1 go test -v ./... $(TESTARGS) -timeout=20m 32 | 33 | .PHONY: fmtcheck 34 | fmtcheck: 35 | @sh -c "'$(CURDIR)/scripts/gofmtcheck.sh'" 36 | 37 | .PHONY: fmt 38 | fmt: 39 | gofumpt -l -w . && cd bootstrap/terraform && terraform fmt 40 | 41 | .PHONY: setup-env 42 | setup-env: 43 | cd bootstrap/terraform && terraform init && terraform apply -auto-approve 44 | 45 | .PHONY: teardown-env 46 | teardown-env: 47 | cd bootstrap/terraform && terraform init && terraform destroy -auto-approve 48 | 49 | .PHONY: configure 50 | configure: dev 51 | @./scripts/configure.sh \ 52 | $(PLUGIN_DIR) \ 53 | $(PLUGIN_NAME) \ 54 | $(TEST_REDIS_HOST) \ 55 | $(TEST_REDIS_PORT) \ 56 | $(TEST_REDIS_USERNAME) \ 57 | $(TEST_REDIS_PASSWORD) 58 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push, workflow_dispatch] 4 | 5 | jobs: 6 | get-go-version: 7 | name: "Determine Go toolchain version" 8 | runs-on: ubuntu-latest 9 | outputs: 10 | go-version: ${{ steps.get-go-version.outputs.go-version }} 11 | steps: 12 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 13 | - name: Determine Go version 14 | id: get-go-version 15 | run: | 16 | echo "Building with Go $(cat .go-version)" 17 | echo "go-version=$(cat .go-version)" >> $GITHUB_OUTPUT 18 | fmtcheck: 19 | runs-on: ubuntu-latest 20 | needs: [get-go-version] 21 | env: 22 | GOFUMPT_VERSION: 0.3.1 23 | steps: 24 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 25 | - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 26 | with: 27 | go-version: ${{ needs.get-go-version.outputs.go-version }} 28 | - run: | 29 | go install "mvdan.cc/gofumpt@v${GOFUMPT_VERSION}" 30 | make fmtcheck 31 | test: 32 | runs-on: ubuntu-latest 33 | needs: [get-go-version] 34 | steps: 35 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 36 | - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 37 | with: 38 | go-version: ${{ needs.get-go-version.outputs.go-version }} 39 | - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 40 | with: 41 | path: | 42 | ~/.cache/go-build 43 | ~/go/pkg/mod 44 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 45 | restore-keys: | 46 | ${{ runner.os }}-go- 47 | - run: make testacc 48 | -------------------------------------------------------------------------------- /tools/go.sum: -------------------------------------------------------------------------------- 1 | github.com/frankban/quicktest v1.14.2 h1:SPb1KFFmM+ybpEjPUhCCkZOM5xlovT5UbrMvWnXyBns= 2 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= 3 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 4 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 5 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 6 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= 7 | github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= 8 | golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= 9 | golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= 10 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 11 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 12 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= 13 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 14 | golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= 15 | golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= 16 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 17 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 18 | gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= 19 | mvdan.cc/gofumpt v0.3.1 h1:avhhrOmv0IuvQVK7fvwV91oFSGAk5/6Po8GXTzICeu8= 20 | mvdan.cc/gofumpt v0.3.1/go.mod h1:w3ymliuxvzVx8DAutBnVyDqYb1Niy/yCJt/lk821YCE= 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v0.7.0 2 | ### October 6, 2025 3 | 4 | * [COMPLIANCE] Add Copyright and License Headers (#97) 5 | * Update changelog for v0.6.1 release (#99) 6 | 7 | ## v0.6.1 8 | ### September 30, 2025 9 | 10 | * Bump go version to resolve sec vuln (#98) 11 | * Automated dependency upgrades (#90) 12 | * Bump github.com/go-viper/mapstructure/v2 from 2.1.0 to 2.4.0 (#95) 13 | * init changie (#96) 14 | * Add backport assistant workflow (#93) 15 | * Add backport assistant workflow (#92) 16 | * [Compliance] - PR Template Changes Required (#91) 17 | 18 | ## v0.6.0 19 | * Bump go version to 1.23.6 20 | IMPROVEMENTS: 21 | * Updated dependencies: 22 | * `github.com/opencontainers/runc` v1.1.13 -> v1.2.6 23 | * `golang.org/x/crypto` v0.32.0 -> v0.36.0 24 | * `golang.org/x/net` v0.34.0 -> v0.38.0 25 | * `github.com/hashicorp/vault/sdk` v0.15.2 -> v0.17.0 26 | * `github.com/ory/dockertest/v3` v3.11.0 -> v3.12.0 27 | 28 | ## v0.5.0 29 | * Bump go version to 1.23.3 30 | IMPROVEMENTS: 31 | * Updated dependencies: 32 | * `github.com/hashicorp/vault/sdk` v0.14.0 -> v0.15.0 33 | * `github.com/ory/dockertest/v3` v3.10.0 -> v3.11.0 34 | 35 | ## v0.4.0 36 | * Bump go version to 1.22.6 37 | * Updated dependencies: 38 | * https://github.com/hashicorp/vault-plugin-database-redis/pull/72 39 | 40 | ## v0.3.0 41 | IMPROVEMENTS: 42 | * Updated dependencies: 43 | * https://github.com/hashicorp/vault-plugin-database-redis/pull/64 44 | 45 | ## v0.2.3 46 | IMPROVEMENTS: 47 | * Updated dependencies: 48 | * `github.com/hashicorp/go-hclog` v1.5.0 -> v1.6.2 49 | * `github.com/hashicorp/vault/sdk` v0.10.0 -> v0.10.2 50 | * `github.com/docker/docker` 23.0.4+incompatible -> 24.0.7+incompatible 51 | * `github.com/opencontainers/runc` 1.1.6 -> 1.1.12 52 | * `golang.org/x/crypto` 0.6.0 -> 0.17.0 53 | * `golang.org/x/net` 0.8.0 -> 0.17.0 54 | * `google.golang.org/grpc` 1.57.0 -> 1.57.1 55 | 56 | 57 | ## v0.2.2 58 | IMPROVEMENTS: 59 | * Updated dependencies: 60 | * `github.com/hashicorp/vault/sdk` v0.9.0 -> v0.9.2 61 | * `github.com/mediocregopher/radix/v4` v4.1.2 -> v4.1.4 62 | * `github.com/ory/dockertest/v3` v3.9.1 -> v3.10.0 63 | 64 | ## v0.2.1 65 | * Dependency upgrades 66 | 67 | ## v0.2.0 68 | * No feature changes 69 | 70 | ## v0.1.0 71 | 72 | * Initial release of the Redis database secrets engine for Vault 73 | -------------------------------------------------------------------------------- /connection_producer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package redis 5 | 6 | import ( 7 | "context" 8 | "crypto/tls" 9 | "crypto/x509" 10 | "fmt" 11 | "net" 12 | "strconv" 13 | "sync" 14 | 15 | "github.com/hashicorp/vault/sdk/database/helper/connutil" 16 | "github.com/mediocregopher/radix/v4" 17 | "github.com/mitchellh/mapstructure" 18 | ) 19 | 20 | type redisDBConnectionProducer struct { 21 | Host string `json:"host"` 22 | Port int `json:"port"` 23 | Username string `json:"username"` 24 | Password string `json:"password"` 25 | TLS bool `json:"tls"` 26 | InsecureTLS bool `json:"insecure_tls"` 27 | CACert string `json:"ca_cert"` 28 | 29 | Initialized bool 30 | rawConfig map[string]interface{} 31 | Type string 32 | client radix.Client 33 | Addr string 34 | sync.Mutex 35 | } 36 | 37 | func (c *redisDBConnectionProducer) secretValues() map[string]string { 38 | return map[string]string{ 39 | c.Password: "[password]", 40 | c.Username: "[username]", 41 | } 42 | } 43 | 44 | func (c *redisDBConnectionProducer) Init(ctx context.Context, initConfig map[string]interface{}, verifyConnection bool) (saveConfig map[string]interface{}, err error) { 45 | c.Lock() 46 | defer c.Unlock() 47 | 48 | c.rawConfig = initConfig 49 | 50 | decoderConfig := &mapstructure.DecoderConfig{ 51 | Result: c, 52 | WeaklyTypedInput: true, 53 | TagName: "json", 54 | } 55 | 56 | decoder, err := mapstructure.NewDecoder(decoderConfig) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | err = decoder.Decode(initConfig) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | switch { 67 | case len(c.Host) == 0: 68 | return nil, fmt.Errorf("host cannot be empty") 69 | case c.Port == 0: 70 | return nil, fmt.Errorf("port cannot be empty") 71 | case len(c.Username) == 0: 72 | return nil, fmt.Errorf("username cannot be empty") 73 | case len(c.Password) == 0: 74 | return nil, fmt.Errorf("password cannot be empty") 75 | } 76 | 77 | c.Addr = net.JoinHostPort(c.Host, strconv.Itoa(c.Port)) 78 | 79 | if c.TLS { 80 | if len(c.CACert) == 0 { 81 | return nil, fmt.Errorf("ca_cert cannot be empty") 82 | } 83 | } 84 | 85 | c.Initialized = true 86 | 87 | if verifyConnection { 88 | if _, err := c.Connection(ctx); err != nil { 89 | c.close() 90 | return nil, fmt.Errorf("error verifying connection: %w", err) 91 | } 92 | } 93 | 94 | return initConfig, nil 95 | } 96 | 97 | func (c *redisDBConnectionProducer) Initialize(ctx context.Context, config map[string]interface{}, verifyConnection bool) error { 98 | _, err := c.Init(ctx, config, verifyConnection) 99 | return err 100 | } 101 | 102 | func (c *redisDBConnectionProducer) Connection(ctx context.Context) (interface{}, error) { 103 | // This is intentionally not grabbing the lock since the calling functions (e.g. CreateUser) 104 | // are claiming it. (The locking patterns could be refactored to be more consistent/clear.) 105 | 106 | if !c.Initialized { 107 | return nil, connutil.ErrNotInitialized 108 | } 109 | 110 | if c.client != nil { 111 | return c.client, nil 112 | } 113 | var err error 114 | var poolConfig radix.PoolConfig 115 | 116 | if c.TLS { 117 | rootCAs := x509.NewCertPool() 118 | ok := rootCAs.AppendCertsFromPEM([]byte(c.CACert)) 119 | if !ok { 120 | return nil, fmt.Errorf("failed to parse root certificate") 121 | } 122 | poolConfig = radix.PoolConfig{ 123 | Dialer: radix.Dialer{ 124 | AuthUser: c.Username, 125 | AuthPass: c.Password, 126 | NetDialer: &tls.Dialer{ 127 | Config: &tls.Config{ 128 | RootCAs: rootCAs, 129 | InsecureSkipVerify: c.InsecureTLS, 130 | }, 131 | }, 132 | }, 133 | } 134 | } else { 135 | poolConfig = radix.PoolConfig{ 136 | Dialer: radix.Dialer{ 137 | AuthUser: c.Username, 138 | AuthPass: c.Password, 139 | }, 140 | } 141 | } 142 | 143 | client, err := poolConfig.New(ctx, "tcp", c.Addr) 144 | if err != nil { 145 | return nil, err 146 | } 147 | c.client = client 148 | 149 | return c.client, nil 150 | } 151 | 152 | // close terminates the database connection without locking 153 | func (c *redisDBConnectionProducer) close() error { 154 | if c.client != nil { 155 | if err := c.client.Close(); err != nil { 156 | return err 157 | } 158 | } 159 | 160 | c.client = nil 161 | return nil 162 | } 163 | 164 | // Close terminates the database connection with locking 165 | func (c *redisDBConnectionProducer) Close() error { 166 | c.Lock() 167 | defer c.Unlock() 168 | 169 | return c.close() 170 | } 171 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hashicorp/vault-plugin-database-redis 2 | 3 | go 1.25.1 4 | 5 | require ( 6 | github.com/hashicorp/errwrap v1.1.0 7 | github.com/hashicorp/go-hclog v1.6.3 8 | github.com/hashicorp/vault/sdk v0.19.0 9 | github.com/mediocregopher/radix/v4 v4.1.4 10 | github.com/mitchellh/mapstructure v1.5.0 11 | github.com/ory/dockertest/v3 v3.12.0 12 | ) 13 | 14 | require ( 15 | cloud.google.com/go/auth v0.14.1 // indirect 16 | cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect 17 | cloud.google.com/go/cloudsqlconn v1.4.3 // indirect 18 | cloud.google.com/go/compute/metadata v0.6.0 // indirect 19 | dario.cat/mergo v1.0.1 // indirect 20 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect 21 | github.com/Microsoft/go-winio v0.6.2 // indirect 22 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect 23 | github.com/armon/go-metrics v0.4.1 // indirect 24 | github.com/armon/go-radix v1.0.0 // indirect 25 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 26 | github.com/containerd/continuity v0.4.5 // indirect 27 | github.com/containerd/errdefs v1.0.0 // indirect 28 | github.com/containerd/errdefs/pkg v0.3.0 // indirect 29 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 30 | github.com/distribution/reference v0.6.0 // indirect 31 | github.com/docker/cli v27.4.1+incompatible // indirect 32 | github.com/docker/docker v28.3.3+incompatible // indirect 33 | github.com/docker/go-connections v0.5.0 // indirect 34 | github.com/docker/go-units v0.5.0 // indirect 35 | github.com/fatih/color v1.18.0 // indirect 36 | github.com/felixge/httpsnoop v1.0.4 // indirect 37 | github.com/go-logr/logr v1.4.2 // indirect 38 | github.com/go-logr/stdr v1.2.2 // indirect 39 | github.com/go-viper/mapstructure/v2 v2.4.0 // indirect 40 | github.com/gogo/protobuf v1.3.2 // indirect 41 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect 42 | github.com/golang/protobuf v1.5.4 // indirect 43 | github.com/golang/snappy v0.0.4 // indirect 44 | github.com/google/certificate-transparency-go v1.3.1 // indirect 45 | github.com/google/s2a-go v0.1.9 // indirect 46 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 47 | github.com/google/uuid v1.6.0 // indirect 48 | github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect 49 | github.com/googleapis/gax-go/v2 v2.14.1 // indirect 50 | github.com/hashicorp/go-hmac-drbg v0.0.0-20210916214228-a6e5a68489f6 // indirect 51 | github.com/hashicorp/go-immutable-radix v1.3.1 // indirect 52 | github.com/hashicorp/go-kms-wrapping/v2 v2.0.18 // indirect 53 | github.com/hashicorp/go-metrics v0.5.4 // indirect 54 | github.com/hashicorp/go-multierror v1.1.1 // indirect 55 | github.com/hashicorp/go-plugin v1.6.1 // indirect 56 | github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 // indirect 57 | github.com/hashicorp/go-secure-stdlib/cryptoutil v0.1.1 // indirect 58 | github.com/hashicorp/go-secure-stdlib/mlock v0.1.3 // indirect 59 | github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect 60 | github.com/hashicorp/go-secure-stdlib/permitpool v1.0.0 // indirect 61 | github.com/hashicorp/go-secure-stdlib/plugincontainer v0.4.2 // indirect 62 | github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect 63 | github.com/hashicorp/go-sockaddr v1.0.7 // indirect 64 | github.com/hashicorp/go-uuid v1.0.3 // indirect 65 | github.com/hashicorp/go-version v1.7.0 // indirect 66 | github.com/hashicorp/golang-lru v1.0.2 // indirect 67 | github.com/hashicorp/yamux v0.1.2 // indirect 68 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect 69 | github.com/jackc/pgconn v1.14.3 // indirect 70 | github.com/jackc/pgio v1.0.0 // indirect 71 | github.com/jackc/pgpassfile v1.0.0 // indirect 72 | github.com/jackc/pgproto3/v2 v2.3.3 // indirect 73 | github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect 74 | github.com/jackc/pgtype v1.14.3 // indirect 75 | github.com/jackc/pgx/v4 v4.18.3 // indirect 76 | github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531 // indirect 77 | github.com/mattn/go-colorable v0.1.14 // indirect 78 | github.com/mattn/go-isatty v0.0.20 // indirect 79 | github.com/mitchellh/copystructure v1.2.0 // indirect 80 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 81 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 82 | github.com/moby/docker-image-spec v1.3.1 // indirect 83 | github.com/moby/sys/user v0.4.0 // indirect 84 | github.com/moby/term v0.5.0 // indirect 85 | github.com/oklog/run v1.1.0 // indirect 86 | github.com/opencontainers/go-digest v1.0.0 // indirect 87 | github.com/opencontainers/image-spec v1.1.0 // indirect 88 | github.com/opencontainers/runc v1.2.6 // indirect 89 | github.com/petermattis/goid v0.0.0-20250721140440-ea1c0173183e // indirect 90 | github.com/pierrec/lz4 v2.6.1+incompatible // indirect 91 | github.com/pkg/errors v0.9.1 // indirect 92 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 93 | github.com/robfig/cron/v3 v3.0.1 // indirect 94 | github.com/ryanuber/go-glob v1.0.0 // indirect 95 | github.com/sasha-s/go-deadlock v0.3.5 // indirect 96 | github.com/sirupsen/logrus v1.9.3 // indirect 97 | github.com/stretchr/testify v1.10.0 // indirect 98 | github.com/tilinna/clock v1.0.2 // indirect 99 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect 100 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect 101 | github.com/xeipuuv/gojsonschema v1.2.0 // indirect 102 | go.opencensus.io v0.24.0 // indirect 103 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 104 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect 105 | go.opentelemetry.io/otel v1.35.0 // indirect 106 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect 107 | go.opentelemetry.io/otel/metric v1.35.0 // indirect 108 | go.opentelemetry.io/otel/trace v1.35.0 // indirect 109 | go.opentelemetry.io/proto/otlp v1.3.1 // indirect 110 | go.uber.org/atomic v1.11.0 // indirect 111 | golang.org/x/crypto v0.40.0 // indirect 112 | golang.org/x/net v0.42.0 // indirect 113 | golang.org/x/oauth2 v0.28.0 // indirect 114 | golang.org/x/sys v0.34.0 // indirect 115 | golang.org/x/text v0.27.0 // indirect 116 | golang.org/x/time v0.10.0 // indirect 117 | google.golang.org/api v0.221.0 // indirect 118 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6 // indirect 119 | google.golang.org/grpc v1.70.0 // indirect 120 | google.golang.org/protobuf v1.36.5 // indirect 121 | gopkg.in/yaml.v2 v2.4.0 // indirect 122 | gopkg.in/yaml.v3 v3.0.1 // indirect 123 | ) 124 | -------------------------------------------------------------------------------- /redis.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package redis 5 | 6 | import ( 7 | "context" 8 | "encoding/json" 9 | "errors" 10 | "fmt" 11 | "strings" 12 | "time" 13 | 14 | "github.com/hashicorp/errwrap" 15 | hclog "github.com/hashicorp/go-hclog" 16 | dbplugin "github.com/hashicorp/vault/sdk/database/dbplugin/v5" 17 | "github.com/hashicorp/vault/sdk/database/helper/credsutil" 18 | "github.com/mediocregopher/radix/v4" 19 | "github.com/mediocregopher/radix/v4/resp/resp3" 20 | ) 21 | 22 | const ( 23 | redisTypeName = "redis" 24 | defaultRedisUserRule = `["~*", "+@read"]` 25 | defaultTimeout = 20000 * time.Millisecond 26 | maxKeyLength = 64 27 | ) 28 | 29 | var _ dbplugin.Database = &RedisDB{} 30 | 31 | // Type that combines the custom plugins Redis database connection configuration options and the Vault CredentialsProducer 32 | // used for generating user information for the Redis database. 33 | type RedisDB struct { 34 | *redisDBConnectionProducer 35 | credsutil.CredentialsProducer 36 | } 37 | 38 | // New implements builtinplugins.BuiltinFactory 39 | func New() (interface{}, error) { 40 | db := new() 41 | // Wrap the plugin with middleware to sanitize errors 42 | dbType := dbplugin.NewDatabaseErrorSanitizerMiddleware(db, db.secretValues) 43 | return dbType, nil 44 | } 45 | 46 | func new() *RedisDB { 47 | connProducer := &redisDBConnectionProducer{} 48 | connProducer.Type = redisTypeName 49 | 50 | db := &RedisDB{ 51 | redisDBConnectionProducer: connProducer, 52 | } 53 | 54 | return db 55 | } 56 | 57 | func (c *RedisDB) Initialize(ctx context.Context, req dbplugin.InitializeRequest) (dbplugin.InitializeResponse, error) { 58 | err := c.redisDBConnectionProducer.Initialize(ctx, req.Config, req.VerifyConnection) 59 | if err != nil { 60 | return dbplugin.InitializeResponse{}, err 61 | } 62 | resp := dbplugin.InitializeResponse{ 63 | Config: req.Config, 64 | } 65 | return resp, nil 66 | } 67 | 68 | func (c *RedisDB) NewUser(ctx context.Context, req dbplugin.NewUserRequest) (dbplugin.NewUserResponse, error) { 69 | // Grab the lock 70 | c.Lock() 71 | defer c.Unlock() 72 | 73 | username, err := credsutil.GenerateUsername( 74 | credsutil.DisplayName(req.UsernameConfig.DisplayName, maxKeyLength), 75 | credsutil.RoleName(req.UsernameConfig.RoleName, maxKeyLength)) 76 | if err != nil { 77 | return dbplugin.NewUserResponse{}, fmt.Errorf("failed to generate username: %w", err) 78 | } 79 | username = strings.ToUpper(username) 80 | 81 | db, err := c.getConnection(ctx) 82 | if err != nil { 83 | return dbplugin.NewUserResponse{}, fmt.Errorf("failed to get connection: %w", err) 84 | } 85 | 86 | err = newUser(ctx, db, username, req) 87 | if err != nil { 88 | return dbplugin.NewUserResponse{}, err 89 | } 90 | 91 | resp := dbplugin.NewUserResponse{ 92 | Username: username, 93 | } 94 | 95 | return resp, nil 96 | } 97 | 98 | func (c *RedisDB) UpdateUser(ctx context.Context, req dbplugin.UpdateUserRequest) (dbplugin.UpdateUserResponse, error) { 99 | if req.Password != nil { 100 | err := c.changeUserPassword(ctx, req.Username, req.Password.NewPassword) 101 | return dbplugin.UpdateUserResponse{}, err 102 | } 103 | return dbplugin.UpdateUserResponse{}, nil 104 | } 105 | 106 | func (c *RedisDB) DeleteUser(ctx context.Context, req dbplugin.DeleteUserRequest) (dbplugin.DeleteUserResponse, error) { 107 | c.Lock() 108 | defer c.Unlock() 109 | 110 | db, err := c.getConnection(ctx) 111 | if err != nil { 112 | return dbplugin.DeleteUserResponse{}, fmt.Errorf("failed to make connection: %w", err) 113 | } 114 | 115 | // Close the database connection to ensure no new connections come in 116 | defer func() { 117 | if err := c.close(); err != nil { 118 | logger := hclog.New(&hclog.LoggerOptions{}) 119 | logger.Error("defer close failed", "error", err) 120 | } 121 | }() 122 | 123 | var response string 124 | 125 | err = db.Do(ctx, radix.Cmd(&response, "ACL", "DELUSER", req.Username)) 126 | if err != nil { 127 | return dbplugin.DeleteUserResponse{}, err 128 | } 129 | 130 | return dbplugin.DeleteUserResponse{}, nil 131 | } 132 | 133 | func newUser(ctx context.Context, db radix.Client, username string, req dbplugin.NewUserRequest) error { 134 | statements := removeEmpty(req.Statements.Commands) 135 | if len(statements) == 0 { 136 | statements = append(statements, defaultRedisUserRule) 137 | } 138 | 139 | aclargs := []string{"SETUSER", username, "ON", ">" + req.Password} 140 | 141 | var args []string 142 | err := json.Unmarshal([]byte(statements[0]), &args) 143 | if err != nil { 144 | return errwrap.Wrapf("error unmarshalling REDIS rules in the creation statement JSON: {{err}}", err) 145 | } 146 | 147 | aclargs = append(aclargs, args...) 148 | var response string 149 | 150 | err = db.Do(ctx, radix.Cmd(&response, "ACL", aclargs...)) 151 | if err != nil { 152 | return err 153 | } 154 | 155 | return nil 156 | } 157 | 158 | func (c *RedisDB) changeUserPassword(ctx context.Context, username, password string) error { 159 | c.Lock() 160 | defer c.Unlock() 161 | 162 | db, err := c.getConnection(ctx) 163 | if err != nil { 164 | return err 165 | } 166 | 167 | // Close the database connection to ensure no new connections come in 168 | defer func() { 169 | if err := c.close(); err != nil { 170 | logger := hclog.New(&hclog.LoggerOptions{}) 171 | logger.Error("defer close failed", "error", err) 172 | } 173 | }() 174 | 175 | var response resp3.ArrayHeader 176 | mn := radix.Maybe{Rcv: &response} 177 | var redisErr resp3.SimpleError 178 | err = db.Do(ctx, radix.Cmd(&mn, "ACL", "GETUSER", username)) 179 | if errors.As(err, &redisErr) { 180 | return fmt.Errorf("redis error returned: %s", redisErr.Error()) 181 | } 182 | 183 | if err != nil { 184 | return fmt.Errorf("reset of passwords for user %s failed in changeUserPassword: %w", username, err) 185 | } 186 | 187 | if mn.Null { 188 | return fmt.Errorf("changeUserPassword for user %s failed, user not found!", username) 189 | } 190 | 191 | var sresponse string 192 | err = db.Do(ctx, radix.Cmd(&sresponse, "ACL", "SETUSER", username, "RESETPASS", ">"+password)) 193 | if err != nil { 194 | return err 195 | } 196 | 197 | return nil 198 | } 199 | 200 | func removeEmpty(strs []string) []string { 201 | var newStrs []string 202 | for _, str := range strs { 203 | str = strings.TrimSpace(str) 204 | if str == "" { 205 | continue 206 | } 207 | newStrs = append(newStrs, str) 208 | } 209 | 210 | return newStrs 211 | } 212 | 213 | func computeTimeout(ctx context.Context) (timeout time.Duration) { 214 | deadline, ok := ctx.Deadline() 215 | if ok { 216 | return time.Until(deadline) 217 | } 218 | return defaultTimeout 219 | } 220 | 221 | func (c *RedisDB) getConnection(ctx context.Context) (radix.Client, error) { 222 | db, err := c.Connection(ctx) 223 | if err != nil { 224 | return nil, err 225 | } 226 | return db.(radix.Client), nil 227 | } 228 | 229 | func (c *RedisDB) Type() (string, error) { 230 | return redisTypeName, nil 231 | } 232 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vault-plugin-database-redis 2 | 3 | A [Vault](https://www.vaultproject.io) plugin for Redis 4 | 5 | This project uses the database plugin interface introduced in Vault version 0.7.1. 6 | 7 | The plugin supports the generation of static and dynamic user roles and root credential rotation on a stand alone redis server. 8 | 9 | ## Build 10 | 11 | Use `make dev` to build a development version of this plugin. 12 | 13 | **Please note:** In case of the following errors, while creating Redis connection in Vault, please build this plugin with `CGO_ENABLED=0 go build -ldflags='-extldflags=-static' -o vault-plugin-database-redis ./cmd/vault-plugin-database-redis/` command. More details on this error can be found [here](https://github.com/hashicorp/vault-plugin-database-redis/issues/1#issuecomment-1078415041). 14 | ````bash 15 | Error writing data to database/config/my-redis: Error making API request. 16 | 17 | URL: PUT http://127.0.0.1:8200/v1/database/config/my-redis 18 | Code: 400. Errors: 19 | 20 | * error creating database object: invalid database version: 2 errors occurred: 21 | * fork/exec /config/plugin/vault-plugin-database-redis: no such file or directory 22 | * fork/exec /config/plugin/vault-plugin-database-redis: no such file or directory 23 | ```` 24 | 25 | ## Testing 26 | To run tests, `go test` will first set up the docker.io/redis:latest database image, then execute a set of basic tests against it. To test against different redis images, for example 5.0-buster, set the environment variable `REDIS_VERSION=5.0-buster`. If you want to run the tests against a local redis installation or an already running redis container, set the environment variable `TEST_REDIS_HOST` before executing. 27 | 28 | **Note:** The tests assume that the redis database instance has a default user with the following ACL settings `user default on >default-pa55w0rd ~* +@all`. If it doesn't, you will need to align the Administrator username and password with the pre-set values in the `redis_test.go` file. 29 | 30 | Set `VAULT_ACC=1` to execute all of the tests including the acceptance tests, or run just a subset of tests by using a command like `go test -run TestDriver/Init` for example. 31 | 32 | ## Installation 33 | 34 | The Vault plugin system is documented on the [Vault documentation site](https://www.vaultproject.io/docs/internals/plugins.html). 35 | 36 | You will need to define a plugin directory using the `plugin_directory` configuration directive, then place the 37 | `vault-plugin-database-redis` executable generated above, into the directory. 38 | 39 | **Please note:** This plugin is incompatible with Vault versions before 1.6.0 due to an update of the database plugin interface. You will be able to register the plugin in the plugins catalog with an older version of Vault but when you try to initialize the plugin to connect to a database instance you will get this error. 40 | ````bash 41 | Error writing data to database/config/my-redis: Error making API request. 42 | 43 | URL: PUT http://127.0.0.1:8200/v1/database/config/my-redis 44 | Code: 400. Errors: 45 | 46 | * error creating database object: Incompatible API version with plugin. Plugin version: 5, Client versions: [3 4] 47 | ```` 48 | 49 | Sample commands for registering and starting to use the plugin: 50 | 51 | ```bash 52 | $ SHA256=$(shasum -a 256 plugins/vault-plugin-database-redis | cut -d' ' -f1) 53 | 54 | $ vault secrets enable database 55 | 56 | $ vault write sys/plugins/catalog/database/vault-plugin-database-redis sha256=$SHA256 \ 57 | command=vault-plugin-database-redis 58 | ``` 59 | 60 | At this stage you are now ready to initialize the plugin to connect to the redis db using unencrypted or encrypted communications. 61 | 62 | Prior to initializing the plugin, ensure that you have created an administration account. Vault will use the user specified here to create/update/revoke database credentials. That user must have the appropriate rule `+@admin` to perform actions upon other database users. 63 | 64 | ### Plugin Initialization 65 | 66 | #### Standalone REDIS Server. 67 | 68 | ```bash 69 | $ vault write database/config/my-redis plugin_name="vault-plugin-database-redis" \ 70 | host="localhost" port=6379 username="Administrator" password="password" \ 71 | allowed_roles="my-redis-*-role" 72 | 73 | # You should consider rotating the admin password. Note that if you do, the new password will never be made available 74 | # through Vault, so you should create a vault-specific database admin user for this. 75 | $ vault write -force database/rotate-root/my-redis 76 | 77 | ``` 78 | 79 | ### Dynamic Role Creation 80 | 81 | When you create roles, you need to provide a JSON string containing the Redis ACL rules which are documented [here](https://redis.io/commands/acl-cat) or in the output of the `ACL CAT` redis command. 82 | 83 | ```bash 84 | # if a creation_statement is not provided the user account will default to a read only user, '["~*", "+@read"]' that can read any key. 85 | $ vault write database/roles/my-redis-admin-role db_name=my-redis \ 86 | default_ttl="5m" max_ttl="1h" creation_statements='["+@admin"]' 87 | 88 | $ vault write database/roles/my-redis-read-foo-role db_name=my-redis \ 89 | default_ttl="5m" max_ttl="1h" creation_statements='["~foo", "+@read"]' 90 | Success! Data written to: database/roles/my-redis-read-foo-role 91 | ``` 92 | 93 | To retrieve the credentials for the dynamic accounts 94 | 95 | ```bash 96 | 97 | $vault read database/creds/my-redis-admin-role 98 | Key Value 99 | --- ----- 100 | lease_id database/creds/my-redis-admin-role/OxCTXJcxQ2F4lReWPjbezSnA 101 | lease_duration 5m 102 | lease_renewable true 103 | password dACqHsav6-attdv1glGZ 104 | username V_TOKEN_MY-REDIS-ADMIN-ROLE_YASUQUF3GVVD0ZWTEMK4_1608481717 105 | 106 | $ vault read database/creds/my-redis-read-foo-role 107 | Key Value 108 | --- ----- 109 | lease_id database/creds/my-redis-read-foo-role/Yn99BrX4t0NkLyifm4NmsEUB 110 | lease_duration 5m 111 | lease_renewable true 112 | password ZN6gdTKszk7oc9Oztc-o 113 | username V_TOKEN_MY-REDIS-READ-FOO-ROLE_PUAINND1FC5XQGRC0HIF_1608481734 114 | 115 | ``` 116 | 117 | ### Static Role Creation 118 | 119 | In order to use static roles, the user must already exist in the Redis ACL list. The example below assumes that there is an existing user with the name "vault-edu". If the user does not exist you will receive the following error. 120 | 121 | ```bash 122 | Error writing data to database/static-roles/static-account: Error making API request. 123 | 124 | URL: PUT http://127.0.0.1:8200/v1/database/static-roles/static-account 125 | Code: 400. Errors: 126 | 127 | * cannot update static account username 128 | 129 | ``` 130 | 131 | ```bash 132 | $ vault write database/static-roles/static-account db_name=insecure-redis \ 133 | username="vault-edu" rotation_period="5m" 134 | Success! Data written to: database/static-roles/static-account 135 | ```` 136 | 137 | To retrieve the credentials for the vault-edu user 138 | 139 | ```bash 140 | $ vault read database/static-creds/static-account 141 | Key Value 142 | --- ----- 143 | last_vault_rotation 2020-12-20T10:39:49.647822-06:00 144 | password ylKNgqa3NPVAioBf-0S5 145 | rotation_period 5m 146 | ttl 3m59s 147 | username vault-edu 148 | ``` 149 | 150 | ## Spring Cloud Vault Integration 151 | 152 | > Tested on [spring-cloud-vault:3.1.0](https://docs.spring.io/spring-cloud-vault/docs/3.1.0/reference/html) 153 | 154 | In order to enable integration with `Spring Cloud Vault` and therefore supply dynamically-generated Redis credentials to Spring applications, we can use `org.springframework.cloud:spring-cloud-vault-config-databases` with [Multiple Databases](https://docs.spring.io/spring-cloud-vault/docs/3.1.0/reference/html/#vault.config.backends.databases) configuration approach. 155 | 156 | Sample `application.yml` configuration (not-related sections are omitted): 157 | 158 | ```yaml 159 | spring: 160 | cloud: 161 | vault: 162 | host: 127.0.0.1 163 | port: 8200 164 | authentication: TOKEN 165 | token: ${VAULT_TOKEN} 166 | databases: 167 | redis: 168 | enabled: true 169 | role: my-redis-role 170 | backend: database 171 | username-property: spring.redis.username 172 | password-property: spring.redis.password 173 | config: 174 | import: vault:// 175 | ``` 176 | 177 | **Please note:** Spring Cloud Vault does not support `max_ttl` yet, thus we have to set it up to `0` when creating configurations. More details can be found [here](https://docs.spring.io/spring-cloud-vault/docs/3.1.0/reference/html/#vault.config.backends.databases). 178 | 179 | ## Developing 180 | 181 | A set of make targets are provided for quick and easy iterations when developing. These steps assume there is a Vault 182 | server running locally and accessible via the `vault` CLI. See this [documentation](https://github.com/hashicorp/vault#developing-vault) 183 | on how to get started with Vault. 184 | 185 | 1. `make setup-env` will start a Redis docker container and initialize a test user with the username `default` and password `default-pa55w0rd` 186 | 2. `source ./bootstrap/terraform/local_environment_setup.sh` will export the necessary environment variables generated from the setup step 187 | 3. `make configure` will build the plugin, register it in your local Vault server and run sample commands to verify everything is working 188 | 4. `make testacc` will run the acceptance tests against the Redis container created during the environment setup 189 | 5. `make teardown-env` will stop the Redis docker container with any resources generated alongside it such as network configs 190 | 191 | When iterating, you can reload any local code changes with `make configure` as many times as desired to test the latest 192 | modifications via the Vault CLI or API. 193 | 194 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 HashiCorp, Inc. 2 | 3 | Mozilla Public License Version 2.0 4 | ================================== 5 | 6 | 1. Definitions 7 | -------------- 8 | 9 | 1.1. "Contributor" 10 | means each individual or legal entity that creates, contributes to 11 | the creation of, or owns Covered Software. 12 | 13 | 1.2. "Contributor Version" 14 | means the combination of the Contributions of others (if any) used 15 | by a Contributor and that particular Contributor's Contribution. 16 | 17 | 1.3. "Contribution" 18 | means Covered Software of a particular Contributor. 19 | 20 | 1.4. "Covered Software" 21 | means Source Code Form to which the initial Contributor has attached 22 | the notice in Exhibit A, the Executable Form of such Source Code 23 | Form, and Modifications of such Source Code Form, in each case 24 | including portions thereof. 25 | 26 | 1.5. "Incompatible With Secondary Licenses" 27 | means 28 | 29 | (a) that the initial Contributor has attached the notice described 30 | in Exhibit B to the Covered Software; or 31 | 32 | (b) that the Covered Software was made available under the terms of 33 | version 1.1 or earlier of the License, but not also under the 34 | terms of a Secondary License. 35 | 36 | 1.6. "Executable Form" 37 | means any form of the work other than Source Code Form. 38 | 39 | 1.7. "Larger Work" 40 | means a work that combines Covered Software with other material, in 41 | a separate file or files, that is not Covered Software. 42 | 43 | 1.8. "License" 44 | means this document. 45 | 46 | 1.9. "Licensable" 47 | means having the right to grant, to the maximum extent possible, 48 | whether at the time of the initial grant or subsequently, any and 49 | all of the rights conveyed by this License. 50 | 51 | 1.10. "Modifications" 52 | means any of the following: 53 | 54 | (a) any file in Source Code Form that results from an addition to, 55 | deletion from, or modification of the contents of Covered 56 | Software; or 57 | 58 | (b) any new file in Source Code Form that contains any Covered 59 | Software. 60 | 61 | 1.11. "Patent Claims" of a Contributor 62 | means any patent claim(s), including without limitation, method, 63 | process, and apparatus claims, in any patent Licensable by such 64 | Contributor that would be infringed, but for the grant of the 65 | License, by the making, using, selling, offering for sale, having 66 | made, import, or transfer of either its Contributions or its 67 | Contributor Version. 68 | 69 | 1.12. "Secondary License" 70 | means either the GNU General Public License, Version 2.0, the GNU 71 | Lesser General Public License, Version 2.1, the GNU Affero General 72 | Public License, Version 3.0, or any later versions of those 73 | licenses. 74 | 75 | 1.13. "Source Code Form" 76 | means the form of the work preferred for making modifications. 77 | 78 | 1.14. "You" (or "Your") 79 | means an individual or a legal entity exercising rights under this 80 | License. For legal entities, "You" includes any entity that 81 | controls, is controlled by, or is under common control with You. For 82 | purposes of this definition, "control" means (a) the power, direct 83 | or indirect, to cause the direction or management of such entity, 84 | whether by contract or otherwise, or (b) ownership of more than 85 | fifty percent (50%) of the outstanding shares or beneficial 86 | ownership of such entity. 87 | 88 | 2. License Grants and Conditions 89 | -------------------------------- 90 | 91 | 2.1. Grants 92 | 93 | Each Contributor hereby grants You a world-wide, royalty-free, 94 | non-exclusive license: 95 | 96 | (a) under intellectual property rights (other than patent or trademark) 97 | Licensable by such Contributor to use, reproduce, make available, 98 | modify, display, perform, distribute, and otherwise exploit its 99 | Contributions, either on an unmodified basis, with Modifications, or 100 | as part of a Larger Work; and 101 | 102 | (b) under Patent Claims of such Contributor to make, use, sell, offer 103 | for sale, have made, import, and otherwise transfer either its 104 | Contributions or its Contributor Version. 105 | 106 | 2.2. Effective Date 107 | 108 | The licenses granted in Section 2.1 with respect to any Contribution 109 | become effective for each Contribution on the date the Contributor first 110 | distributes such Contribution. 111 | 112 | 2.3. Limitations on Grant Scope 113 | 114 | The licenses granted in this Section 2 are the only rights granted under 115 | this License. No additional rights or licenses will be implied from the 116 | distribution or licensing of Covered Software under this License. 117 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 118 | Contributor: 119 | 120 | (a) for any code that a Contributor has removed from Covered Software; 121 | or 122 | 123 | (b) for infringements caused by: (i) Your and any other third party's 124 | modifications of Covered Software, or (ii) the combination of its 125 | Contributions with other software (except as part of its Contributor 126 | Version); or 127 | 128 | (c) under Patent Claims infringed by Covered Software in the absence of 129 | its Contributions. 130 | 131 | This License does not grant any rights in the trademarks, service marks, 132 | or logos of any Contributor (except as may be necessary to comply with 133 | the notice requirements in Section 3.4). 134 | 135 | 2.4. Subsequent Licenses 136 | 137 | No Contributor makes additional grants as a result of Your choice to 138 | distribute the Covered Software under a subsequent version of this 139 | License (see Section 10.2) or under the terms of a Secondary License (if 140 | permitted under the terms of Section 3.3). 141 | 142 | 2.5. Representation 143 | 144 | Each Contributor represents that the Contributor believes its 145 | Contributions are its original creation(s) or it has sufficient rights 146 | to grant the rights to its Contributions conveyed by this License. 147 | 148 | 2.6. Fair Use 149 | 150 | This License is not intended to limit any rights You have under 151 | applicable copyright doctrines of fair use, fair dealing, or other 152 | equivalents. 153 | 154 | 2.7. Conditions 155 | 156 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 157 | in Section 2.1. 158 | 159 | 3. Responsibilities 160 | ------------------- 161 | 162 | 3.1. Distribution of Source Form 163 | 164 | All distribution of Covered Software in Source Code Form, including any 165 | Modifications that You create or to which You contribute, must be under 166 | the terms of this License. You must inform recipients that the Source 167 | Code Form of the Covered Software is governed by the terms of this 168 | License, and how they can obtain a copy of this License. You may not 169 | attempt to alter or restrict the recipients' rights in the Source Code 170 | Form. 171 | 172 | 3.2. Distribution of Executable Form 173 | 174 | If You distribute Covered Software in Executable Form then: 175 | 176 | (a) such Covered Software must also be made available in Source Code 177 | Form, as described in Section 3.1, and You must inform recipients of 178 | the Executable Form how they can obtain a copy of such Source Code 179 | Form by reasonable means in a timely manner, at a charge no more 180 | than the cost of distribution to the recipient; and 181 | 182 | (b) You may distribute such Executable Form under the terms of this 183 | License, or sublicense it under different terms, provided that the 184 | license for the Executable Form does not attempt to limit or alter 185 | the recipients' rights in the Source Code Form under this License. 186 | 187 | 3.3. Distribution of a Larger Work 188 | 189 | You may create and distribute a Larger Work under terms of Your choice, 190 | provided that You also comply with the requirements of this License for 191 | the Covered Software. If the Larger Work is a combination of Covered 192 | Software with a work governed by one or more Secondary Licenses, and the 193 | Covered Software is not Incompatible With Secondary Licenses, this 194 | License permits You to additionally distribute such Covered Software 195 | under the terms of such Secondary License(s), so that the recipient of 196 | the Larger Work may, at their option, further distribute the Covered 197 | Software under the terms of either this License or such Secondary 198 | License(s). 199 | 200 | 3.4. Notices 201 | 202 | You may not remove or alter the substance of any license notices 203 | (including copyright notices, patent notices, disclaimers of warranty, 204 | or limitations of liability) contained within the Source Code Form of 205 | the Covered Software, except that You may alter any license notices to 206 | the extent required to remedy known factual inaccuracies. 207 | 208 | 3.5. Application of Additional Terms 209 | 210 | You may choose to offer, and to charge a fee for, warranty, support, 211 | indemnity or liability obligations to one or more recipients of Covered 212 | Software. However, You may do so only on Your own behalf, and not on 213 | behalf of any Contributor. You must make it absolutely clear that any 214 | such warranty, support, indemnity, or liability obligation is offered by 215 | You alone, and You hereby agree to indemnify every Contributor for any 216 | liability incurred by such Contributor as a result of warranty, support, 217 | indemnity or liability terms You offer. You may include additional 218 | disclaimers of warranty and limitations of liability specific to any 219 | jurisdiction. 220 | 221 | 4. Inability to Comply Due to Statute or Regulation 222 | --------------------------------------------------- 223 | 224 | If it is impossible for You to comply with any of the terms of this 225 | License with respect to some or all of the Covered Software due to 226 | statute, judicial order, or regulation then You must: (a) comply with 227 | the terms of this License to the maximum extent possible; and (b) 228 | describe the limitations and the code they affect. Such description must 229 | be placed in a text file included with all distributions of the Covered 230 | Software under this License. Except to the extent prohibited by statute 231 | or regulation, such description must be sufficiently detailed for a 232 | recipient of ordinary skill to be able to understand it. 233 | 234 | 5. Termination 235 | -------------- 236 | 237 | 5.1. The rights granted under this License will terminate automatically 238 | if You fail to comply with any of its terms. However, if You become 239 | compliant, then the rights granted under this License from a particular 240 | Contributor are reinstated (a) provisionally, unless and until such 241 | Contributor explicitly and finally terminates Your grants, and (b) on an 242 | ongoing basis, if such Contributor fails to notify You of the 243 | non-compliance by some reasonable means prior to 60 days after You have 244 | come back into compliance. Moreover, Your grants from a particular 245 | Contributor are reinstated on an ongoing basis if such Contributor 246 | notifies You of the non-compliance by some reasonable means, this is the 247 | first time You have received notice of non-compliance with this License 248 | from such Contributor, and You become compliant prior to 30 days after 249 | Your receipt of the notice. 250 | 251 | 5.2. If You initiate litigation against any entity by asserting a patent 252 | infringement claim (excluding declaratory judgment actions, 253 | counter-claims, and cross-claims) alleging that a Contributor Version 254 | directly or indirectly infringes any patent, then the rights granted to 255 | You by any and all Contributors for the Covered Software under Section 256 | 2.1 of this License shall terminate. 257 | 258 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 259 | end user license agreements (excluding distributors and resellers) which 260 | have been validly granted by You or Your distributors under this License 261 | prior to termination shall survive termination. 262 | 263 | ************************************************************************ 264 | * * 265 | * 6. Disclaimer of Warranty * 266 | * ------------------------- * 267 | * * 268 | * Covered Software is provided under this License on an "as is" * 269 | * basis, without warranty of any kind, either expressed, implied, or * 270 | * statutory, including, without limitation, warranties that the * 271 | * Covered Software is free of defects, merchantable, fit for a * 272 | * particular purpose or non-infringing. The entire risk as to the * 273 | * quality and performance of the Covered Software is with You. * 274 | * Should any Covered Software prove defective in any respect, You * 275 | * (not any Contributor) assume the cost of any necessary servicing, * 276 | * repair, or correction. This disclaimer of warranty constitutes an * 277 | * essential part of this License. No use of any Covered Software is * 278 | * authorized under this License except under this disclaimer. * 279 | * * 280 | ************************************************************************ 281 | 282 | ************************************************************************ 283 | * * 284 | * 7. Limitation of Liability * 285 | * -------------------------- * 286 | * * 287 | * Under no circumstances and under no legal theory, whether tort * 288 | * (including negligence), contract, or otherwise, shall any * 289 | * Contributor, or anyone who distributes Covered Software as * 290 | * permitted above, be liable to You for any direct, indirect, * 291 | * special, incidental, or consequential damages of any character * 292 | * including, without limitation, damages for lost profits, loss of * 293 | * goodwill, work stoppage, computer failure or malfunction, or any * 294 | * and all other commercial damages or losses, even if such party * 295 | * shall have been informed of the possibility of such damages. This * 296 | * limitation of liability shall not apply to liability for death or * 297 | * personal injury resulting from such party's negligence to the * 298 | * extent applicable law prohibits such limitation. Some * 299 | * jurisdictions do not allow the exclusion or limitation of * 300 | * incidental or consequential damages, so this exclusion and * 301 | * limitation may not apply to You. * 302 | * * 303 | ************************************************************************ 304 | 305 | 8. Litigation 306 | ------------- 307 | 308 | Any litigation relating to this License may be brought only in the 309 | courts of a jurisdiction where the defendant maintains its principal 310 | place of business and such litigation shall be governed by laws of that 311 | jurisdiction, without reference to its conflict-of-law provisions. 312 | Nothing in this Section shall prevent a party's ability to bring 313 | cross-claims or counter-claims. 314 | 315 | 9. Miscellaneous 316 | ---------------- 317 | 318 | This License represents the complete agreement concerning the subject 319 | matter hereof. If any provision of this License is held to be 320 | unenforceable, such provision shall be reformed only to the extent 321 | necessary to make it enforceable. Any law or regulation which provides 322 | that the language of a contract shall be construed against the drafter 323 | shall not be used to construe this License against a Contributor. 324 | 325 | 10. Versions of the License 326 | --------------------------- 327 | 328 | 10.1. New Versions 329 | 330 | Mozilla Foundation is the license steward. Except as provided in Section 331 | 10.3, no one other than the license steward has the right to modify or 332 | publish new versions of this License. Each version will be given a 333 | distinguishing version number. 334 | 335 | 10.2. Effect of New Versions 336 | 337 | You may distribute the Covered Software under the terms of the version 338 | of the License under which You originally received the Covered Software, 339 | or under the terms of any subsequent version published by the license 340 | steward. 341 | 342 | 10.3. Modified Versions 343 | 344 | If you create software not governed by this License, and you want to 345 | create a new license for such software, you may create and use a 346 | modified version of this License if you rename the license and remove 347 | any references to the name of the license steward (except to note that 348 | such modified license differs from this License). 349 | 350 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 351 | Licenses 352 | 353 | If You choose to distribute Source Code Form that is Incompatible With 354 | Secondary Licenses under the terms of this version of the License, the 355 | notice described in Exhibit B of this License must be attached. 356 | 357 | Exhibit A - Source Code Form License Notice 358 | ------------------------------------------- 359 | 360 | This Source Code Form is subject to the terms of the Mozilla Public 361 | License, v. 2.0. If a copy of the MPL was not distributed with this 362 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 363 | 364 | If it is not possible or desirable to put the notice in a particular 365 | file, then You may include the notice in a location (such as a LICENSE 366 | file in a relevant directory) where a recipient would be likely to look 367 | for such a notice. 368 | 369 | You may add additional accurate notices of copyright ownership. 370 | 371 | Exhibit B - "Incompatible With Secondary Licenses" Notice 372 | --------------------------------------------------------- 373 | 374 | This Source Code Form is "Incompatible With Secondary Licenses", as 375 | defined by the Mozilla Public License, v. 2.0. 376 | -------------------------------------------------------------------------------- /redis_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package redis 5 | 6 | import ( 7 | "context" 8 | "crypto/tls" 9 | "crypto/x509" 10 | "fmt" 11 | "os" 12 | "strings" 13 | "testing" 14 | "time" 15 | 16 | "github.com/hashicorp/vault/sdk/database/dbplugin/v5" 17 | "github.com/mediocregopher/radix/v4" 18 | "github.com/ory/dockertest/v3" 19 | dc "github.com/ory/dockertest/v3/docker" 20 | ) 21 | 22 | var pre6dot5 = false // check for Pre 6.5.0 Redis 23 | 24 | const ( 25 | defaultUsername = "default" 26 | defaultPassword = "default-pa55w0rd" 27 | adminUsername = "Administrator" 28 | adminPassword = "password" 29 | aclCat = "+@admin" 30 | testRedisRole = `["%s"]` 31 | testRedisGroup = `["+@all"]` 32 | testRedisRoleAndGroup = `["%s"]` 33 | ) 34 | 35 | var redisTls = false 36 | 37 | func prepareRedisTestContainer(t *testing.T) (func(), string, int) { 38 | if os.Getenv("TEST_REDIS_TLS") != "" { 39 | redisTls = true 40 | } 41 | if os.Getenv("TEST_REDIS_HOST") != "" { 42 | return func() {}, os.Getenv("TEST_REDIS_HOST"), 6379 43 | } 44 | // redver should match a redis repository tag. Default to latest. 45 | redver := os.Getenv("REDIS_VERSION") 46 | if redver == "" { 47 | redver = "latest" 48 | } 49 | 50 | pool, err := dockertest.NewPool("") 51 | if err != nil { 52 | t.Fatalf("Failed to connect to docker: %s", err) 53 | } 54 | 55 | ro := &dockertest.RunOptions{ 56 | Repository: "docker.io/redis", 57 | Tag: redver, 58 | ExposedPorts: []string{"6379"}, 59 | PortBindings: map[dc.Port][]dc.PortBinding{ 60 | "6379": { 61 | {HostIP: "0.0.0.0", HostPort: "6379"}, 62 | }, 63 | }, 64 | } 65 | resource, err := pool.RunWithOptions(ro) 66 | if err != nil { 67 | t.Fatalf("Could not start local redis docker container: %s", err) 68 | } 69 | 70 | cleanup := func() { 71 | err := pool.Retry(func() error { 72 | return pool.Purge(resource) 73 | }) 74 | if err != nil { 75 | if strings.Contains(err.Error(), "No such container") { 76 | return 77 | } 78 | t.Fatalf("Failed to cleanup local container: %s", err) 79 | } 80 | } 81 | 82 | address := "127.0.0.1:6379" 83 | 84 | if err = pool.Retry(func() error { 85 | t.Log("Waiting for the database to start...") 86 | poolConfig := radix.PoolConfig{} 87 | _, err := poolConfig.New(context.Background(), "tcp", address) 88 | if err != nil { 89 | return err 90 | } 91 | 92 | return nil 93 | }); err != nil { 94 | t.Fatalf("Could not connect to redis: %s", err) 95 | cleanup() 96 | } 97 | time.Sleep(3 * time.Second) 98 | return cleanup, "0.0.0.0", 6379 99 | } 100 | 101 | func TestDriver(t *testing.T) { 102 | var err error 103 | var caCert []byte 104 | if os.Getenv("TEST_REDIS_TLS") != "" { 105 | caCertFile := os.Getenv("CA_CERT_FILE") 106 | caCert, err = os.ReadFile(caCertFile) 107 | if err != nil { 108 | t.Fatal(fmt.Errorf("unable to read CA_CERT_FILE at %v: %w", caCertFile, err)) 109 | } 110 | } 111 | 112 | // Spin up redis 113 | cleanup, host, port := prepareRedisTestContainer(t) 114 | defer cleanup() 115 | 116 | err = createUser(host, port, redisTls, caCert, defaultUsername, defaultPassword, "Administrator", "password", 117 | aclCat) 118 | if err != nil { 119 | t.Fatalf("Failed to create Administrator user using 'default' user: %s", err) 120 | } 121 | err = createUser(host, port, redisTls, caCert, adminUsername, adminPassword, "rotate-root", "rotate-rootpassword", 122 | aclCat) 123 | if err != nil { 124 | t.Fatalf("Failed to create rotate-root test user: %s", err) 125 | } 126 | err = createUser(host, port, redisTls, caCert, adminUsername, adminPassword, "vault-edu", "password", 127 | aclCat) 128 | if err != nil { 129 | t.Fatalf("Failed to create vault-edu test user: %s", err) 130 | } 131 | 132 | t.Run("Init", func(t *testing.T) { testRedisDBInitialize_NoTLS(t, host, port) }) 133 | t.Run("Init", func(t *testing.T) { testRedisDBInitialize_TLS(t, host, port) }) 134 | t.Run("Create/Revoke", func(t *testing.T) { testRedisDBCreateUser(t, host, port) }) 135 | t.Run("Create/Revoke", func(t *testing.T) { testRedisDBCreateUser_DefaultRule(t, host, port) }) 136 | t.Run("Create/Revoke", func(t *testing.T) { testRedisDBCreateUser_plusRole(t, host, port) }) 137 | t.Run("Create/Revoke", func(t *testing.T) { testRedisDBCreateUser_groupOnly(t, host, port) }) 138 | t.Run("Create/Revoke", func(t *testing.T) { testRedisDBCreateUser_roleAndGroup(t, host, port) }) 139 | t.Run("Rotate", func(t *testing.T) { testRedisDBRotateRootCredentials(t, host, port) }) 140 | t.Run("Creds", func(t *testing.T) { testRedisDBSetCredentials(t, host, port) }) 141 | t.Run("Secret", func(t *testing.T) { testConnectionProducerSecretValues(t) }) 142 | t.Run("TimeoutCalc", func(t *testing.T) { testComputeTimeout(t) }) 143 | } 144 | 145 | func setupRedisDBInitialize(t *testing.T, connectionDetails map[string]interface{}) (err error) { 146 | initReq := dbplugin.InitializeRequest{ 147 | Config: connectionDetails, 148 | VerifyConnection: true, 149 | } 150 | 151 | db := new() 152 | _, err = db.Initialize(context.Background(), initReq) 153 | if err != nil { 154 | return err 155 | } 156 | 157 | if !db.Initialized { 158 | t.Fatal("Database should be initialized") 159 | } 160 | 161 | err = db.Close() 162 | if err != nil { 163 | t.Fatalf("err: %s", err) 164 | } 165 | return nil 166 | } 167 | 168 | func testRedisDBInitialize_NoTLS(t *testing.T, host string, port int) { 169 | if redisTls { 170 | t.Skip("skipping plain text Init() test in TLS mode") 171 | } 172 | 173 | t.Log("Testing plain text Init()") 174 | 175 | connectionDetails := map[string]interface{}{ 176 | "host": host, 177 | "port": port, 178 | "username": adminUsername, 179 | "password": adminPassword, 180 | } 181 | err := setupRedisDBInitialize(t, connectionDetails) 182 | if err != nil { 183 | t.Fatalf("Testing Init() failed: error: %s", err) 184 | } 185 | } 186 | 187 | func testRedisDBInitialize_TLS(t *testing.T, host string, port int) { 188 | if !redisTls { 189 | t.Skip("skipping TLS Init() test in plain text mode") 190 | } 191 | 192 | CACertFile := os.Getenv("CA_CERT_FILE") 193 | CACert, err := os.ReadFile(CACertFile) 194 | if err != nil { 195 | t.Fatal(fmt.Errorf("unable to read CA_CERT_FILE at %v: %w", CACertFile, err)) 196 | } 197 | 198 | t.Log("Testing TLS Init()") 199 | 200 | connectionDetails := map[string]interface{}{ 201 | "host": host, 202 | "port": port, 203 | "username": adminUsername, 204 | "password": adminPassword, 205 | "tls": true, 206 | "ca_cert": CACert, 207 | "insecure_tls": true, 208 | } 209 | err = setupRedisDBInitialize(t, connectionDetails) 210 | if err != nil { 211 | t.Fatalf("Testing TLS Init() failed: error: %s", err) 212 | } 213 | } 214 | 215 | func testRedisDBCreateUser(t *testing.T, address string, port int) { 216 | if os.Getenv("VAULT_ACC") == "" { 217 | t.SkipNow() 218 | } 219 | 220 | t.Log("Testing CreateUser()") 221 | 222 | connectionDetails := map[string]interface{}{ 223 | "host": address, 224 | "port": port, 225 | "username": adminUsername, 226 | "password": adminPassword, 227 | } 228 | 229 | if redisTls { 230 | CACertFile := os.Getenv("CA_CERT_FILE") 231 | CACert, err := os.ReadFile(CACertFile) 232 | if err != nil { 233 | t.Fatal(fmt.Errorf("unable to read CA_CERT_FILE at %v: %w", CACertFile, err)) 234 | } 235 | 236 | connectionDetails["tls"] = true 237 | connectionDetails["ca_cert"] = CACert 238 | connectionDetails["insecure_tls"] = true 239 | } 240 | 241 | initReq := dbplugin.InitializeRequest{ 242 | Config: connectionDetails, 243 | VerifyConnection: true, 244 | } 245 | 246 | db := new() 247 | _, err := db.Initialize(context.Background(), initReq) 248 | if err != nil { 249 | t.Fatalf("Failed to initialize database: %s", err) 250 | } 251 | 252 | if !db.Initialized { 253 | t.Fatal("Database should be initialized") 254 | } 255 | 256 | password := "y8fva_sdVA3rasf" 257 | 258 | createReq := dbplugin.NewUserRequest{ 259 | UsernameConfig: dbplugin.UsernameMetadata{ 260 | DisplayName: "test", 261 | RoleName: "test", 262 | }, 263 | Statements: dbplugin.Statements{ 264 | Commands: []string{}, 265 | }, 266 | Password: password, 267 | Expiration: time.Now().Add(time.Minute), 268 | } 269 | 270 | userResp, err := db.NewUser(context.Background(), createReq) 271 | if err != nil { 272 | t.Fatalf("err: %s", err) 273 | } 274 | 275 | db.Close() 276 | 277 | if err := checkCredsExist(t, userResp.Username, password, address, port); err != nil { 278 | t.Fatalf("Could not connect with new credentials: %s", err) 279 | } 280 | 281 | err = revokeUser(t, userResp.Username, address, port) 282 | if err != nil { 283 | t.Fatalf("Could not revoke user: %s", userResp.Username) 284 | } 285 | } 286 | 287 | func checkCredsExist(t *testing.T, username, password, address string, port int) error { 288 | if os.Getenv("VAULT_ACC") == "" { 289 | t.SkipNow() 290 | } 291 | 292 | t.Log("Testing checkCredsExist()") 293 | 294 | connectionDetails := map[string]interface{}{ 295 | "host": address, 296 | "port": port, 297 | "username": username, 298 | "password": password, 299 | } 300 | 301 | if redisTls { 302 | CACertFile := os.Getenv("CA_CERT_FILE") 303 | CACert, err := os.ReadFile(CACertFile) 304 | if err != nil { 305 | t.Fatal(fmt.Errorf("unable to read CA_CERT_FILE at %v: %w", CACertFile, err)) 306 | } 307 | 308 | connectionDetails["tls"] = true 309 | connectionDetails["ca_cert"] = CACert 310 | connectionDetails["insecure_tls"] = true 311 | } 312 | 313 | initReq := dbplugin.InitializeRequest{ 314 | Config: connectionDetails, 315 | VerifyConnection: true, 316 | } 317 | 318 | db := new() 319 | _, err := db.Initialize(context.Background(), initReq) 320 | if err != nil { 321 | t.Fatalf("err: %s", err) 322 | } 323 | 324 | if !db.Initialized { 325 | t.Fatal("Database should be initialized") 326 | } 327 | 328 | return nil 329 | } 330 | 331 | func checkRuleAllowed(t *testing.T, username, password, address string, port int, cmd string, rules []string) error { 332 | if os.Getenv("VAULT_ACC") == "" { 333 | t.SkipNow() 334 | } 335 | 336 | t.Log("Testing checkRuleAllowed()") 337 | 338 | connectionDetails := map[string]interface{}{ 339 | "host": address, 340 | "port": port, 341 | "username": username, 342 | "password": password, 343 | } 344 | 345 | if redisTls { 346 | CACertFile := os.Getenv("CA_CERT_FILE") 347 | CACert, err := os.ReadFile(CACertFile) 348 | if err != nil { 349 | t.Fatal(fmt.Errorf("unable to read CA_CERT_FILE at %v: %w", CACertFile, err)) 350 | } 351 | 352 | connectionDetails["tls"] = true 353 | connectionDetails["ca_cert"] = CACert 354 | connectionDetails["insecure_tls"] = true 355 | } 356 | 357 | initReq := dbplugin.InitializeRequest{ 358 | Config: connectionDetails, 359 | VerifyConnection: true, 360 | } 361 | 362 | db := new() 363 | _, err := db.Initialize(context.Background(), initReq) 364 | if err != nil { 365 | t.Fatalf("err: %s", err) 366 | } 367 | 368 | if !db.Initialized { 369 | t.Fatal("Database should be initialized") 370 | } 371 | var response string 372 | err = db.client.Do(context.Background(), radix.Cmd(&response, cmd, rules...)) 373 | 374 | return err 375 | } 376 | 377 | func revokeUser(t *testing.T, username, address string, port int) error { 378 | if os.Getenv("VAULT_ACC") == "" { 379 | t.SkipNow() 380 | } 381 | 382 | t.Log("Testing RevokeUser()") 383 | 384 | connectionDetails := map[string]interface{}{ 385 | "host": address, 386 | "port": port, 387 | "username": adminUsername, 388 | "password": adminPassword, 389 | } 390 | 391 | if redisTls { 392 | CACertFile := os.Getenv("CA_CERT_FILE") 393 | CACert, err := os.ReadFile(CACertFile) 394 | if err != nil { 395 | t.Fatal(fmt.Errorf("unable to read CA_CERT_FILE at %v: %w", CACertFile, err)) 396 | } 397 | 398 | connectionDetails["tls"] = true 399 | connectionDetails["ca_cert"] = CACert 400 | connectionDetails["insecure_tls"] = true 401 | } 402 | 403 | initReq := dbplugin.InitializeRequest{ 404 | Config: connectionDetails, 405 | VerifyConnection: true, 406 | } 407 | 408 | db := new() 409 | _, err := db.Initialize(context.Background(), initReq) 410 | if err != nil { 411 | t.Fatalf("err: %s", err) 412 | } 413 | 414 | if !db.Initialized { 415 | t.Fatal("Database should be initialized") 416 | } 417 | 418 | delUserReq := dbplugin.DeleteUserRequest{Username: username} 419 | 420 | _, err = db.DeleteUser(context.Background(), delUserReq) 421 | if err != nil { 422 | t.Fatalf("err: %s", err) 423 | } 424 | return nil 425 | } 426 | 427 | func testRedisDBCreateUser_DefaultRule(t *testing.T, address string, port int) { 428 | if os.Getenv("VAULT_ACC") == "" { 429 | t.SkipNow() 430 | } 431 | 432 | t.Log("Testing CreateUser_DefaultRule()") 433 | 434 | connectionDetails := map[string]interface{}{ 435 | "host": address, 436 | "port": port, 437 | "username": adminUsername, 438 | "password": adminPassword, 439 | } 440 | 441 | if redisTls { 442 | CACertFile := os.Getenv("CA_CERT_FILE") 443 | CACert, err := os.ReadFile(CACertFile) 444 | if err != nil { 445 | t.Fatal(fmt.Errorf("unable to read CA_CERT_FILE at %v: %w", CACertFile, err)) 446 | } 447 | 448 | connectionDetails["tls"] = true 449 | connectionDetails["ca_cert"] = CACert 450 | connectionDetails["insecure_tls"] = true 451 | } 452 | 453 | initReq := dbplugin.InitializeRequest{ 454 | Config: connectionDetails, 455 | VerifyConnection: true, 456 | } 457 | 458 | db := new() 459 | _, err := db.Initialize(context.Background(), initReq) 460 | if err != nil { 461 | t.Fatalf("err: %s", err) 462 | } 463 | 464 | if !db.Initialized { 465 | t.Fatal("Database should be initialized") 466 | } 467 | 468 | username := "test" 469 | password := "y8fva_sdVA3rasf" 470 | 471 | createReq := dbplugin.NewUserRequest{ 472 | UsernameConfig: dbplugin.UsernameMetadata{ 473 | DisplayName: username, 474 | RoleName: username, 475 | }, 476 | Statements: dbplugin.Statements{ 477 | Commands: []string{}, 478 | }, 479 | Password: password, 480 | Expiration: time.Now().Add(time.Minute), 481 | } 482 | 483 | userResp, err := db.NewUser(context.Background(), createReq) 484 | if err != nil { 485 | t.Fatalf("err: %s", err) 486 | } 487 | 488 | if err := checkCredsExist(t, userResp.Username, password, address, port); err != nil { 489 | t.Fatalf("Could not connect with new credentials: %s", err) 490 | } 491 | rules := []string{"foo"} 492 | if err := checkRuleAllowed(t, userResp.Username, password, address, port, "get", rules); err != nil { 493 | t.Fatalf("get failed with +@read rule: %s", err) 494 | } 495 | 496 | rules = []string{"foo", "bar"} 497 | if err = checkRuleAllowed(t, userResp.Username, password, address, port, "set", rules); err == nil { 498 | t.Fatalf("set did not fail with +@read rule: %s", err) 499 | } 500 | 501 | err = revokeUser(t, userResp.Username, address, port) 502 | if err != nil { 503 | t.Fatalf("Could not revoke user: %s", username) 504 | } 505 | 506 | db.Close() 507 | } 508 | 509 | func testRedisDBCreateUser_plusRole(t *testing.T, address string, port int) { 510 | if os.Getenv("VAULT_ACC") == "" { 511 | t.SkipNow() 512 | } 513 | 514 | t.Log("Testing CreateUser_plusRole()") 515 | 516 | connectionDetails := map[string]interface{}{ 517 | "host": address, 518 | "port": port, 519 | "username": adminUsername, 520 | "password": adminPassword, 521 | "protocol_version": 4, 522 | } 523 | 524 | if redisTls { 525 | CACertFile := os.Getenv("CA_CERT_FILE") 526 | CACert, err := os.ReadFile(CACertFile) 527 | if err != nil { 528 | t.Fatal(fmt.Errorf("unable to read CA_CERT_FILE at %v: %w", CACertFile, err)) 529 | } 530 | 531 | connectionDetails["tls"] = true 532 | connectionDetails["ca_cert"] = CACert 533 | connectionDetails["insecure_tls"] = true 534 | } 535 | 536 | initReq := dbplugin.InitializeRequest{ 537 | Config: connectionDetails, 538 | VerifyConnection: true, 539 | } 540 | 541 | db := new() 542 | _, err := db.Initialize(context.Background(), initReq) 543 | if err != nil { 544 | t.Fatalf("err: %s", err) 545 | } 546 | 547 | if !db.Initialized { 548 | t.Fatal("Database should be initialized") 549 | } 550 | 551 | password := "y8fva_sdVA3rasf" 552 | 553 | createReq := dbplugin.NewUserRequest{ 554 | UsernameConfig: dbplugin.UsernameMetadata{ 555 | DisplayName: "test", 556 | RoleName: "test", 557 | }, 558 | Statements: dbplugin.Statements{ 559 | Commands: []string{fmt.Sprintf(testRedisRole, aclCat)}, 560 | }, 561 | Password: password, 562 | Expiration: time.Now().Add(time.Minute), 563 | } 564 | 565 | userResp, err := db.NewUser(context.Background(), createReq) 566 | if err != nil { 567 | t.Fatalf("err: %s", err) 568 | } 569 | 570 | db.Close() 571 | 572 | if err := checkCredsExist(t, userResp.Username, password, address, port); err != nil { 573 | t.Fatalf("Could not connect with new credentials: %s", err) 574 | } 575 | 576 | err = revokeUser(t, userResp.Username, address, port) 577 | if err != nil { 578 | t.Fatalf("Could not revoke user: %s", userResp.Username) 579 | } 580 | } 581 | 582 | // g1 & g2 must exist in the database. 583 | func testRedisDBCreateUser_groupOnly(t *testing.T, address string, port int) { 584 | if os.Getenv("VAULT_ACC") == "" { 585 | t.SkipNow() 586 | } 587 | 588 | if pre6dot5 { 589 | t.Log("Skipping as groups are not supported pre6.5.0") 590 | t.SkipNow() 591 | } 592 | t.Log("Testing CreateUser_groupOnly()") 593 | 594 | connectionDetails := map[string]interface{}{ 595 | "host": address, 596 | "port": port, 597 | "username": adminUsername, 598 | "password": adminPassword, 599 | "protocol_version": 4, 600 | } 601 | 602 | if redisTls { 603 | CACertFile := os.Getenv("CA_CERT_FILE") 604 | CACert, err := os.ReadFile(CACertFile) 605 | if err != nil { 606 | t.Fatal(fmt.Errorf("unable to read CA_CERT_FILE at %v: %w", CACertFile, err)) 607 | } 608 | 609 | connectionDetails["tls"] = true 610 | connectionDetails["ca_cert"] = CACert 611 | connectionDetails["insecure_tls"] = true 612 | } 613 | 614 | initReq := dbplugin.InitializeRequest{ 615 | Config: connectionDetails, 616 | VerifyConnection: true, 617 | } 618 | 619 | db := new() 620 | _, err := db.Initialize(context.Background(), initReq) 621 | if err != nil { 622 | t.Fatalf("err: %s", err) 623 | } 624 | 625 | if !db.Initialized { 626 | t.Fatal("Database should be initialized") 627 | } 628 | 629 | password := "y8fva_sdVA3rasf" 630 | 631 | createReq := dbplugin.NewUserRequest{ 632 | UsernameConfig: dbplugin.UsernameMetadata{ 633 | DisplayName: "test", 634 | RoleName: "test", 635 | }, 636 | Statements: dbplugin.Statements{ 637 | Commands: []string{fmt.Sprintf(testRedisGroup)}, 638 | }, 639 | Password: password, 640 | Expiration: time.Now().Add(time.Minute), 641 | } 642 | 643 | userResp, err := db.NewUser(context.Background(), createReq) 644 | if err != nil { 645 | t.Fatalf("err: %s", err) 646 | } 647 | 648 | db.Close() 649 | 650 | if err := checkCredsExist(t, userResp.Username, password, address, port); err != nil { 651 | t.Fatalf("Could not connect with new credentials: %s", err) 652 | } 653 | 654 | err = revokeUser(t, userResp.Username, address, port) 655 | if err != nil { 656 | t.Fatalf("Could not revoke user: %s", userResp.Username) 657 | } 658 | } 659 | 660 | func testRedisDBCreateUser_roleAndGroup(t *testing.T, address string, port int) { 661 | if os.Getenv("VAULT_ACC") == "" { 662 | t.SkipNow() 663 | } 664 | 665 | if pre6dot5 { 666 | t.Log("Skipping as groups are not supported pre6.5.0") 667 | t.SkipNow() 668 | } 669 | t.Log("Testing CreateUser_roleAndGroup()") 670 | 671 | connectionDetails := map[string]interface{}{ 672 | "host": address, 673 | "port": port, 674 | "username": adminUsername, 675 | "password": adminPassword, 676 | "protocol_version": 4, 677 | } 678 | 679 | if redisTls { 680 | CACertFile := os.Getenv("CA_CERT_FILE") 681 | CACert, err := os.ReadFile(CACertFile) 682 | if err != nil { 683 | t.Fatal(fmt.Errorf("unable to read CA_CERT_FILE at %v: %w", CACertFile, err)) 684 | } 685 | 686 | connectionDetails["tls"] = true 687 | connectionDetails["ca_cert"] = CACert 688 | connectionDetails["insecure_tls"] = true 689 | } 690 | 691 | initReq := dbplugin.InitializeRequest{ 692 | Config: connectionDetails, 693 | VerifyConnection: true, 694 | } 695 | 696 | db := new() 697 | _, err := db.Initialize(context.Background(), initReq) 698 | if err != nil { 699 | t.Fatalf("err: %s", err) 700 | } 701 | 702 | if !db.Initialized { 703 | t.Fatal("Database should be initialized") 704 | } 705 | 706 | password := "y8fva_sdVA3rasf" 707 | 708 | createReq := dbplugin.NewUserRequest{ 709 | UsernameConfig: dbplugin.UsernameMetadata{ 710 | DisplayName: "test", 711 | RoleName: "test", 712 | }, 713 | Statements: dbplugin.Statements{ 714 | Commands: []string{fmt.Sprintf(testRedisRoleAndGroup, aclCat)}, 715 | }, 716 | Password: password, 717 | Expiration: time.Now().Add(time.Minute), 718 | } 719 | 720 | userResp, err := db.NewUser(context.Background(), createReq) 721 | if err != nil { 722 | t.Fatalf("err: %s", err) 723 | } 724 | 725 | db.Close() 726 | 727 | if err := checkCredsExist(t, userResp.Username, password, address, port); err != nil { 728 | t.Fatalf("Could not connect with new credentials: %s", err) 729 | } 730 | 731 | err = revokeUser(t, userResp.Username, address, port) 732 | if err != nil { 733 | t.Fatalf("Could not revoke user: %s", userResp.Username) 734 | } 735 | } 736 | 737 | func testRedisDBRotateRootCredentials(t *testing.T, address string, port int) { 738 | if os.Getenv("VAULT_ACC") == "" { 739 | t.SkipNow() 740 | } 741 | 742 | t.Log("Testing RotateRootCredentials()") 743 | 744 | connectionDetails := map[string]interface{}{ 745 | "host": address, 746 | "port": port, 747 | "username": "rotate-root", 748 | "password": "rotate-rootpassword", 749 | } 750 | 751 | if redisTls { 752 | CACertFile := os.Getenv("CA_CERT_FILE") 753 | CACert, err := os.ReadFile(CACertFile) 754 | if err != nil { 755 | t.Fatal(fmt.Errorf("unable to read CA_CERT_FILE at %v: %w", CACertFile, err)) 756 | } 757 | 758 | connectionDetails["tls"] = true 759 | connectionDetails["ca_cert"] = CACert 760 | connectionDetails["insecure_tls"] = true 761 | } 762 | 763 | initReq := dbplugin.InitializeRequest{ 764 | Config: connectionDetails, 765 | VerifyConnection: true, 766 | } 767 | 768 | db := new() 769 | _, err := db.Initialize(context.Background(), initReq) 770 | if err != nil { 771 | t.Fatalf("err: %s", err) 772 | } 773 | 774 | if !db.Initialized { 775 | t.Fatal("Database should be initialized") 776 | } 777 | 778 | defer db.Close() 779 | 780 | updateReq := dbplugin.UpdateUserRequest{ 781 | Username: "rotate-root", 782 | Password: &dbplugin.ChangePassword{ 783 | NewPassword: "newpassword", 784 | }, 785 | } 786 | 787 | _, err = db.UpdateUser(context.Background(), updateReq) 788 | if err != nil { 789 | t.Fatalf("err: %s", err) 790 | } 791 | 792 | // defer setting the password back in case the test fails. 793 | defer doRedisDBSetCredentials(t, "rotate-root", "rotate-rootpassword", address, port) 794 | 795 | if err := checkCredsExist(t, db.Username, "newpassword", address, port); err != nil { 796 | t.Fatalf("Could not connect with new RotatedRootcredentials: %s", err) 797 | } 798 | } 799 | 800 | func doRedisDBSetCredentials(t *testing.T, username, password, address string, port int) { 801 | t.Log("Testing SetCredentials()") 802 | 803 | connectionDetails := map[string]interface{}{ 804 | "host": address, 805 | "port": port, 806 | "username": adminUsername, 807 | "password": adminPassword, 808 | } 809 | 810 | if redisTls { 811 | CACertFile := os.Getenv("CA_CERT_FILE") 812 | CACert, err := os.ReadFile(CACertFile) 813 | if err != nil { 814 | t.Fatal(fmt.Errorf("unable to read CA_CERT_FILE at %v: %w", CACertFile, err)) 815 | } 816 | 817 | connectionDetails["tls"] = true 818 | connectionDetails["ca_cert"] = CACert 819 | connectionDetails["insecure_tls"] = true 820 | } 821 | 822 | initReq := dbplugin.InitializeRequest{ 823 | Config: connectionDetails, 824 | VerifyConnection: true, 825 | } 826 | 827 | db := new() 828 | _, err := db.Initialize(context.Background(), initReq) 829 | if err != nil { 830 | t.Fatalf("err: %s", err) 831 | } 832 | 833 | if !db.Initialized { 834 | t.Fatal("Database should be initialized") 835 | } 836 | 837 | // test that SetCredentials fails if the user does not exist... 838 | updateReq := dbplugin.UpdateUserRequest{ 839 | Username: "userThatDoesNotExist", 840 | Password: &dbplugin.ChangePassword{ 841 | NewPassword: "goodPassword", 842 | }, 843 | } 844 | 845 | ctx, cancel := context.WithTimeout(context.Background(), 5000*time.Millisecond) 846 | defer cancel() 847 | _, err = db.UpdateUser(ctx, updateReq) 848 | if err == nil { 849 | t.Fatalf("err: did not error on setting password for userThatDoesNotExist") 850 | } 851 | 852 | updateReq = dbplugin.UpdateUserRequest{ 853 | Username: username, 854 | Password: &dbplugin.ChangePassword{ 855 | NewPassword: password, 856 | }, 857 | } 858 | 859 | _, err = db.UpdateUser(context.Background(), updateReq) 860 | if err != nil { 861 | t.Fatalf("err: %s", err) 862 | } 863 | 864 | db.Close() 865 | 866 | if err := checkCredsExist(t, username, password, address, port); err != nil { 867 | t.Fatalf("Could not connect with rotated credentials: %s", err) 868 | } 869 | } 870 | 871 | func testRedisDBSetCredentials(t *testing.T, address string, port int) { 872 | if os.Getenv("VAULT_ACC") == "" { 873 | t.SkipNow() 874 | } 875 | 876 | doRedisDBSetCredentials(t, "vault-edu", "password", address, port) 877 | } 878 | 879 | func testConnectionProducerSecretValues(t *testing.T) { 880 | t.Log("Testing redisDBConnectionProducer.secretValues()") 881 | 882 | cp := &redisDBConnectionProducer{ 883 | Username: "USR", 884 | Password: "PWD", 885 | } 886 | 887 | if cp.secretValues()["USR"] != "[username]" && 888 | cp.secretValues()["PWD"] != "[password]" { 889 | t.Fatal("redisDBConnectionProducer.secretValues() test failed.") 890 | } 891 | } 892 | 893 | func testComputeTimeout(t *testing.T) { 894 | t.Log("Testing computeTimeout") 895 | if computeTimeout(context.Background()) != defaultTimeout { 896 | t.Fatalf("Background timeout not set to %s milliseconds.", defaultTimeout) 897 | } 898 | ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) 899 | defer cancel() 900 | if computeTimeout(ctx) == defaultTimeout { 901 | t.Fatal("WithTimeout failed") 902 | } 903 | } 904 | 905 | func createUser(hostname string, port int, redisTls bool, CACert []byte, adminuser, adminpassword, username, password, aclRule string) (err error) { 906 | var poolConfig radix.PoolConfig 907 | 908 | if redisTls { 909 | rootCAs := x509.NewCertPool() 910 | ok := rootCAs.AppendCertsFromPEM(CACert) 911 | if !ok { 912 | return fmt.Errorf("failed to parse root certificate") 913 | } 914 | 915 | poolConfig = radix.PoolConfig{ 916 | Dialer: radix.Dialer{ 917 | AuthUser: adminuser, 918 | AuthPass: adminpassword, 919 | NetDialer: &tls.Dialer{ 920 | Config: &tls.Config{ 921 | RootCAs: rootCAs, 922 | InsecureSkipVerify: true, 923 | }, 924 | }, 925 | }, 926 | } 927 | } else { 928 | poolConfig = radix.PoolConfig{ 929 | Dialer: radix.Dialer{ 930 | AuthUser: adminuser, 931 | AuthPass: adminpassword, 932 | }, 933 | } 934 | } 935 | 936 | addr := fmt.Sprintf("%s:%d", hostname, port) 937 | client, err := poolConfig.New(context.Background(), "tcp", addr) 938 | if err != nil { 939 | return err 940 | } 941 | 942 | var response string 943 | err = client.Do(context.Background(), radix.Cmd(&response, "ACL", "SETUSER", username, "on", ">"+password, aclRule)) 944 | 945 | fmt.Printf("Response in createUser: %s\n", response) 946 | 947 | if err != nil { 948 | return err 949 | } 950 | 951 | if client != nil { 952 | if err = client.Close(); err != nil { 953 | return err 954 | } 955 | } 956 | 957 | return nil 958 | } 959 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go/auth v0.14.1 h1:AwoJbzUdxA/whv1qj3TLKwh3XX5sikny2fc40wUl+h0= 4 | cloud.google.com/go/auth v0.14.1/go.mod h1:4JHUxlGXisL0AW8kXPtUF6ztuOksyfUQNFjfsOCXkPM= 5 | cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= 6 | cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= 7 | cloud.google.com/go/cloudsqlconn v1.4.3 h1:/WYFbB1NtMtoMxCbqpzzTFPDkxxlLTPme390KEGaEPc= 8 | cloud.google.com/go/cloudsqlconn v1.4.3/go.mod h1:QL3tuStVOO70txb3rs4G8j5uMfo5ztZii8K3oGD3VYA= 9 | cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= 10 | cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= 11 | dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= 12 | dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 13 | filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= 14 | filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 15 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= 16 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 17 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 18 | github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= 19 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 20 | github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= 21 | github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= 22 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 23 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 24 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= 25 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= 26 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 27 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 28 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 29 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 30 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 31 | github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= 32 | github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= 33 | github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= 34 | github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 35 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 36 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 37 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 38 | github.com/bufbuild/protocompile v0.10.0 h1:+jW/wnLMLxaCEG8AX9lD0bQ5v9h1RUiMKOBOT5ll9dM= 39 | github.com/bufbuild/protocompile v0.10.0/go.mod h1:G9qQIQo0xZ6Uyj6CMNz0saGmx2so+KONo8/KrELABiY= 40 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 41 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 42 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 43 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 44 | github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= 45 | github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= 46 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 47 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 48 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= 49 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= 50 | github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= 51 | github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= 52 | github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= 53 | github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= 54 | github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= 55 | github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= 56 | github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= 57 | github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 58 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 59 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 60 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 61 | github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= 62 | github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 63 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 64 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 65 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 66 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 67 | github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= 68 | github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= 69 | github.com/docker/cli v27.4.1+incompatible h1:VzPiUlRJ/xh+otB75gva3r05isHMo5wXDfPRi5/b4hI= 70 | github.com/docker/cli v27.4.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= 71 | github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= 72 | github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 73 | github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= 74 | github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= 75 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 76 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 77 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 78 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 79 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 80 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 81 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 82 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 83 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 84 | github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= 85 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 86 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 87 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 88 | github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss= 89 | github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= 90 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 91 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 92 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 93 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 94 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 95 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 96 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 97 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 98 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 99 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 100 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 101 | github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= 102 | github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= 103 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 104 | github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= 105 | github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= 106 | github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= 107 | github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= 108 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 109 | github.com/gofrs/uuid v4.3.0+incompatible h1:CaSVZxm5B+7o45rtab4jC2G37WGYX1zQfuU2i6DSvnc= 110 | github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 111 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 112 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 113 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 114 | github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= 115 | github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= 116 | github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= 117 | github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= 118 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 119 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 120 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= 121 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= 122 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 123 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 124 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 125 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 126 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 127 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 128 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 129 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 130 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 131 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 132 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 133 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 134 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 135 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 136 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 137 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 138 | github.com/google/certificate-transparency-go v1.3.1 h1:akbcTfQg0iZlANZLn0L9xOeWtyCIdeoYhKrqi5iH3Go= 139 | github.com/google/certificate-transparency-go v1.3.1/go.mod h1:gg+UQlx6caKEDQ9EElFOujyxEQEfOiQzAt6782Bvi8k= 140 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 141 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 142 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 143 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 144 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 145 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 146 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 147 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 148 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 149 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 150 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 151 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 152 | github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= 153 | github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= 154 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 155 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 156 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 157 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 158 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 159 | github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= 160 | github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= 161 | github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= 162 | github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= 163 | github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= 164 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= 165 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= 166 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 167 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 168 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 169 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 170 | github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= 171 | github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= 172 | github.com/hashicorp/go-hmac-drbg v0.0.0-20210916214228-a6e5a68489f6 h1:kBoJV4Xl5FLtBfnBjDvBxeNSy2IRITSGs73HQsFUEjY= 173 | github.com/hashicorp/go-hmac-drbg v0.0.0-20210916214228-a6e5a68489f6/go.mod h1:y+HSOcOGB48PkUxNyLAiCiY6rEENu+E+Ss4LG8QHwf4= 174 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 175 | github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= 176 | github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 177 | github.com/hashicorp/go-kms-wrapping/v2 v2.0.18 h1:DLfC677GfKEpSAFpEWvl1vXsGpEcSHmbhBaPLrdDQHc= 178 | github.com/hashicorp/go-kms-wrapping/v2 v2.0.18/go.mod h1:t/eaR/mi2mw3klfl1WEAuiLKrlZ/Q8cosmsT+RIPLu0= 179 | github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY= 180 | github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI= 181 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 182 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 183 | github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI= 184 | github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0= 185 | github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= 186 | github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng= 187 | github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= 188 | github.com/hashicorp/go-secure-stdlib/cryptoutil v0.1.1 h1:VaLXp47MqD1Y2K6QVrA9RooQiPyCgAbnfeJg44wKuJk= 189 | github.com/hashicorp/go-secure-stdlib/cryptoutil v0.1.1/go.mod h1:hH8rgXHh9fPSDPerG6WzABHsHF+9ZpLhRI1LPk4JZ8c= 190 | github.com/hashicorp/go-secure-stdlib/mlock v0.1.3 h1:kH3Rhiht36xhAfhuHyWJDgdXXEx9IIZhDGRk24CDhzg= 191 | github.com/hashicorp/go-secure-stdlib/mlock v0.1.3/go.mod h1:ov1Q0oEDjC3+A4BwsG2YdKltrmEw8sf9Pau4V9JQ4Vo= 192 | github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM= 193 | github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0= 194 | github.com/hashicorp/go-secure-stdlib/permitpool v1.0.0 h1:U6y5MXGiDVOOtkWJ6o/tu1TxABnI0yKTQWJr7z6BpNk= 195 | github.com/hashicorp/go-secure-stdlib/permitpool v1.0.0/go.mod h1:ecDb3o+8D4xtP0nTCufJaAVawHavy5M2eZ64Nq/8/LM= 196 | github.com/hashicorp/go-secure-stdlib/plugincontainer v0.4.2 h1:gCNiM4T5xEc4IpT8vM50CIO+AtElr5kO9l2Rxbq+Sz8= 197 | github.com/hashicorp/go-secure-stdlib/plugincontainer v0.4.2/go.mod h1:6ZM4ZdwClyAsiU2uDBmRHCvq0If/03BMbF9U+U7G5pA= 198 | github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= 199 | github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= 200 | github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= 201 | github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= 202 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 203 | github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 204 | github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= 205 | github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 206 | github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= 207 | github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 208 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 209 | github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= 210 | github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 211 | github.com/hashicorp/vault/sdk v0.19.0 h1:cpjxJ5qnEEX7xtVXaFpXpqfiFTs2hzyQiHS7oJRrvMM= 212 | github.com/hashicorp/vault/sdk v0.19.0/go.mod h1:IYPuA9rZdJjmvssaRWhqs6XQNH6g6XBLjIxOdOVYVrM= 213 | github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= 214 | github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= 215 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= 216 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 217 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= 218 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 219 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= 220 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= 221 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= 222 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= 223 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= 224 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= 225 | github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= 226 | github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= 227 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= 228 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= 229 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= 230 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= 231 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= 232 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= 233 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 234 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 235 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= 236 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= 237 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= 238 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 239 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 240 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 241 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 242 | github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= 243 | github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 244 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= 245 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= 246 | github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= 247 | github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= 248 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= 249 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= 250 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= 251 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= 252 | github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= 253 | github.com/jackc/pgtype v1.14.3 h1:h6W9cPuHsRWQFTWUZMAKMgG5jSwQI0Zurzdvlx3Plus= 254 | github.com/jackc/pgtype v1.14.3/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA= 255 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= 256 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= 257 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= 258 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= 259 | github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= 260 | github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= 261 | github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= 262 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 263 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 264 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 265 | github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 266 | github.com/jhump/protoreflect v1.16.0 h1:54fZg+49widqXYQ0b+usAFHbMkBGR4PpXrsHc8+TBDg= 267 | github.com/jhump/protoreflect v1.16.0/go.mod h1:oYPd7nPvcBw/5wlDfm/AVmU9zH9BgqGCI469pGxfj/8= 268 | github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531 h1:hgVxRoDDPtQE68PT4LFvNlPz2nBKd3OMlGKIQ69OmR4= 269 | github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531/go.mod h1:fqTUQpVYBvhCNIsMXGl2GE9q6z94DIP6NtFKXCSTVbg= 270 | github.com/joshlf/testutil v0.0.0-20170608050642-b5d8aa79d93d h1:J8tJzRyiddAFF65YVgxli+TyWBi0f79Sld6rJP6CBcY= 271 | github.com/joshlf/testutil v0.0.0-20170608050642-b5d8aa79d93d/go.mod h1:b+Q3v8Yrg5o15d71PSUraUzYb+jWl6wQMSBXSGS/hv0= 272 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 273 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 274 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 275 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 276 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 277 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 278 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 279 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 280 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 281 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 282 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 283 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 284 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 285 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 286 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 287 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 288 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 289 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 290 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 291 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 292 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 293 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 294 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 295 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 296 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 297 | github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= 298 | github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 299 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= 300 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 301 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 302 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 303 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 304 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 305 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 306 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 307 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 308 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 309 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 310 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 311 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 312 | github.com/mediocregopher/radix/v4 v4.1.4 h1:Uze6DEbEAvL+VHXUEu/EDBTkUk5CLct5h3nVSGpc6Ts= 313 | github.com/mediocregopher/radix/v4 v4.1.4/go.mod h1:ajchozX/6ELmydxWeWM6xCFHVpZ4+67LXHOTOVR0nCE= 314 | github.com/microsoft/go-mssqldb v1.5.0 h1:CgENxkwtOBNj3Jg6T1X209y2blCfTTcwuOlznd2k9fk= 315 | github.com/microsoft/go-mssqldb v1.5.0/go.mod h1:lmWsjHD8XX/Txr0f8ZqgbEZSC+BZjmEQy/Ms+rLrvho= 316 | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= 317 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 318 | github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= 319 | github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= 320 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 321 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 322 | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= 323 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 324 | github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= 325 | github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= 326 | github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= 327 | github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= 328 | github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= 329 | github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= 330 | github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= 331 | github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= 332 | github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= 333 | github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 334 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 335 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 336 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 337 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 338 | github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 339 | github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 340 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 341 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 342 | github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= 343 | github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= 344 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 345 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 346 | github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= 347 | github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= 348 | github.com/opencontainers/runc v1.2.6 h1:P7Hqg40bsMvQGCS4S7DJYhUZOISMLJOB2iGX5COWiPk= 349 | github.com/opencontainers/runc v1.2.6/go.mod h1:dOQeFo29xZKBNeRBI0B19mJtfHv68YgCTh1X+YphA+4= 350 | github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw= 351 | github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE= 352 | github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= 353 | github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 354 | github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= 355 | github.com/petermattis/goid v0.0.0-20250721140440-ea1c0173183e h1:D0bJD+4O3G4izvrQUmzCL80zazlN7EwJ0PPDhpJWC/I= 356 | github.com/petermattis/goid v0.0.0-20250721140440-ea1c0173183e/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= 357 | github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= 358 | github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 359 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 360 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 361 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 362 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 363 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 364 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 365 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 366 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 367 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 368 | github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= 369 | github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= 370 | github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= 371 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 372 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 373 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 374 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 375 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 376 | github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= 377 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= 378 | github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= 379 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 380 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 381 | github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= 382 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 383 | github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 384 | github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= 385 | github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= 386 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 387 | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= 388 | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 389 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= 390 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= 391 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= 392 | github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= 393 | github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= 394 | github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU= 395 | github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U= 396 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 397 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= 398 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 399 | github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= 400 | github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= 401 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 402 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 403 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 404 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 405 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 406 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 407 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 408 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 409 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 410 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 411 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 412 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 413 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 414 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 415 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 416 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 417 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 418 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 419 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 420 | github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= 421 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 422 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 423 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 424 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 425 | github.com/tilinna/clock v1.0.2 h1:6BO2tyAC9JbPExKH/z9zl44FLu1lImh3nDNKA0kgrkI= 426 | github.com/tilinna/clock v1.0.2/go.mod h1:ZsP7BcY7sEEz7ktc0IVy8Us6boDrK8VradlKRUGfOao= 427 | github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= 428 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 429 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= 430 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 431 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= 432 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 433 | github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= 434 | github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= 435 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 436 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 437 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 438 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= 439 | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= 440 | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 441 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 442 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 443 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= 444 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= 445 | go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= 446 | go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= 447 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= 448 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= 449 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 h1:umZgi92IyxfXd/l4kaDhnKgY8rnN/cZcF1LKc6I8OQ8= 450 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0/go.mod h1:4lVs6obhSVRb1EW5FhOuBTyiQhtRtAnnva9vD3yRfq8= 451 | go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= 452 | go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= 453 | go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= 454 | go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= 455 | go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= 456 | go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= 457 | go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= 458 | go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= 459 | go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= 460 | go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= 461 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 462 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 463 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 464 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 465 | go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 466 | go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 467 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 468 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= 469 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 470 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 471 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 472 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 473 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 474 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 475 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 476 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 477 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 478 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 479 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 480 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 481 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 482 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 483 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 484 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 485 | golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 486 | golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= 487 | golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= 488 | golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= 489 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 490 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 491 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 492 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 493 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 494 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 495 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 496 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 497 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 498 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 499 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 500 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 501 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 502 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 503 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 504 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 505 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 506 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 507 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 508 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 509 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 510 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 511 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 512 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 513 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 514 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 515 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 516 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 517 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 518 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 519 | golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= 520 | golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= 521 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 522 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 523 | golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= 524 | golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= 525 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 526 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 527 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 528 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 529 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 530 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 531 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 532 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 533 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 534 | golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= 535 | golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 536 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 537 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 538 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 539 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 540 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 541 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 542 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 543 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 544 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 545 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 546 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 547 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 548 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 549 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 550 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 551 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 552 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 553 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 554 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 555 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 556 | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 557 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 558 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 559 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 560 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 561 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 562 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 563 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 564 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 565 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 566 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 567 | golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 568 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 569 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 570 | golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= 571 | golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 572 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 573 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 574 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 575 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 576 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 577 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 578 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 579 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 580 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 581 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 582 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 583 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 584 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 585 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 586 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 587 | golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= 588 | golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= 589 | golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= 590 | golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 591 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 592 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 593 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 594 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 595 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 596 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 597 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 598 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 599 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 600 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 601 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 602 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 603 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 604 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 605 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 606 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 607 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 608 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 609 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 610 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 611 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 612 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 613 | google.golang.org/api v0.221.0 h1:qzaJfLhDsbMeFee8zBRdt/Nc+xmOuafD/dbdgGfutOU= 614 | google.golang.org/api v0.221.0/go.mod h1:7sOU2+TL4TxUTdbi0gWgAIg7tH5qBXxoyhtL+9x3biQ= 615 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 616 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 617 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 618 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 619 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 620 | google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= 621 | google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= 622 | google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= 623 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6 h1:2duwAxN2+k0xLNpjnHTXoMUgnv6VPSp5fiqTuwSxjmI= 624 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk= 625 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 626 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 627 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 628 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 629 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 630 | google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= 631 | google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= 632 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 633 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 634 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 635 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 636 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 637 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 638 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 639 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 640 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 641 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 642 | google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= 643 | google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 644 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 645 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 646 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 647 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 648 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 649 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 650 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 651 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= 652 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 653 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 654 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 655 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 656 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 657 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 658 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 659 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 660 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 661 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 662 | gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= 663 | gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= 664 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 665 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 666 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 667 | --------------------------------------------------------------------------------