Compare commits

..
36 Commits
Author SHA1 Message Date
shadowdaoandClaude Sonnet 5.5 76d9e31fd4 Resolve dependencies against published threshold sdk v0.1.0
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 10:36:29 -07:00
shadowdaoandClaude Sonnet 5.5 6628c9dbeb Rename module and SDK import paths for Threshold
Fork of hashicorp/boundary-plugin-minio (MPL-2.0). Module path and Boundary SDK
imports now point at repo.anhonesthost.net/CyberCoveLLC. Adds NOTICE and an
About this fork section to the README.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 10:28:07 -07:00
Michael Li 326c0d977f chore: Use docker mirror for minio image (#58) 2026-10-01 14:09:11 -04:00
Irena Rindos b8e764a368 update klaus dep (#57) 2026-10-01 12:02:26 -04:00
Louis Ruch bff5f27aa8 fix(validate): don't list all objects in bucket when validating permissions 2026-08-28 09:32:59 -07:00
Louis Ruch f021286b33 chore(deps): Update deps 2026-08-28 09:32:59 -07:00
Susmitha Girumala e809f96ddf Update github test workflow permissions to read-only (#54)
Set GITHUB_TOKEN to read-only permission by default within test workflow.
2026-07-14 15:31:25 -04:00
Damian Debkowski 2152efbff5 fix: improve http transport (#49) 2026-06-24 07:12:21 -07:00
Manjeet Singh b6162b5a1f feat: Add ListObjects gRPC method
- Evaluate minio errors. If the error is a permission related error, convert the error to a `Permission` proto and attach it to the gRPC status
- Update `dryRun` method to join all errors before returning response. This ensures all the permissions are validated before returning
2026-05-14 14:16:40 -05:00
hashicorp-copywrite[bot] 3df232d1f2 [COMPLIANCE] Add/Update Copyright Headers (#47)
Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
2026-05-11 22:39:20 -07:00
dependabot[bot] f6a04c903e chore(deps): bump go.opentelemetry.io/otel from 1.39.0 to 1.41.0 (#46)
Bumps [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go) from 1.39.0 to 1.41.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.39.0...v1.41.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel
  dependency-version: 1.41.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-11 22:38:55 -07:00
Pradeep Sekar f5fa05320e chore:vuln fixes (#45) 2026-04-02 08:10:07 -04:00
dependabot[bot] d51e1aeabc chore(deps): bump google.golang.org/grpc from 1.67.0 to 1.79.3 (#44)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.67.0 to 1.79.3.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.67.0...v1.79.3)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.79.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-01 18:32:06 -07:00
dependabot[bot] bdd07a83f3 chore(deps): bump github.com/docker/cli (#43)
Bumps [github.com/docker/cli](https://github.com/docker/cli) from 26.1.4+incompatible to 29.2.0+incompatible.
- [Commits](https://github.com/docker/cli/compare/v26.1.4...v29.2.0)

---
updated-dependencies:
- dependency-name: github.com/docker/cli
  dependency-version: 29.2.0+incompatible
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-07 08:11:27 -08:00
Pradeep Sekar e74f1b661a Make arithemetic operations overflow safe (#41) 2026-02-02 21:25:45 -08:00
dependabot[bot] 036c3f2ae5 chore(deps): bump golang.org/x/crypto from 0.36.0 to 0.45.0 (#42)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.36.0 to 0.45.0.
- [Commits](https://github.com/golang/crypto/compare/v0.36.0...v0.45.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.45.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-26 09:02:13 -08:00
dependabot[bot] 0668690b5c chore(deps): bump golang.org/x/crypto from 0.35.0 to 0.45.0 in /madmin (#39)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.35.0 to 0.45.0.
- [Commits](https://github.com/golang/crypto/compare/v0.35.0...v0.45.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.45.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-25 22:12:03 -08:00
oss-core-libraries-dashboard[bot]andhashicorp-copywrite[bot] 5ba7eda865 [COMPLIANCE] Update Copyright and License Headers (Batch 1 of 1) (#40)
Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
2026-01-16 15:53:48 -08:00
dependabot[bot] 36d0745d0a chore(deps): bump github.com/opencontainers/runc from 1.1.14 to 1.2.8 (#38)
Bumps [github.com/opencontainers/runc](https://github.com/opencontainers/runc) from 1.1.14 to 1.2.8.
- [Release notes](https://github.com/opencontainers/runc/releases)
- [Changelog](https://github.com/opencontainers/runc/blob/v1.2.8/CHANGELOG.md)
- [Commits](https://github.com/opencontainers/runc/compare/v1.1.14...v1.2.8)

---
updated-dependencies:
- dependency-name: github.com/opencontainers/runc
  dependency-version: 1.2.8
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-05 12:06:27 -05:00
compliance-pr-automation-bot[bot]andcompliance-pr-automation[bot] 4d0328c3d7 [Compliance] - PR Template Changes Required (#37)
Co-authored-by: compliance-pr-automation[bot] <1425255+compliance-pr-automation-bot[bot]@users.noreply.github.com>
2025-09-10 14:02:11 -04:00
dependabot[bot] 8faa780f36 chore(deps): bump github.com/docker/docker (#36)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 27.5.0+incompatible to 28.0.0+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v27.5.0...v28.0.0)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-version: 28.0.0+incompatible
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-14 13:10:07 -04:00
dependabot[bot] 2b2d9b4dd1 chore(deps): bump golang.org/x/crypto from 0.32.0 to 0.35.0 in /madmin (#34)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.32.0 to 0.35.0.
- [Commits](https://github.com/golang/crypto/compare/v0.32.0...v0.35.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.35.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-21 19:55:45 -07:00
dependabot[bot] 83b7727889 chore(deps): bump golang.org/x/net from 0.36.0 to 0.38.0 (#35)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.36.0 to 0.38.0.
- [Commits](https://github.com/golang/net/compare/v0.36.0...v0.38.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.38.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-16 16:24:42 -07:00
dependabot[bot] 938010f4d3 chore(deps): bump golang.org/x/net from 0.33.0 to 0.36.0 (#33)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.33.0 to 0.36.0.
- [Commits](https://github.com/golang/net/compare/v0.33.0...v0.36.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-19 11:34:30 -07:00
d72a6897e5 Implement NormalizeStorageBucketData and begin normalizing endpoint URLs (#32)
Co-authored-by: Danielle Miu <29378233+DanielleMiu@users.noreply.github.com>
Co-authored-by: Hugo <10965479+hugoghx@users.noreply.github.com>
2025-03-14 14:59:26 +00:00
Louis Ruch 62abbad9d4 copywrite: ignore madmin (#31) 2025-01-28 08:52:20 -08:00
Louis Ruch 974b1e3b22 chore: use madmin v0.1.0 (#29) 2025-01-21 10:01:47 -08:00
Louis Ruch c9e9377b4a chore: update usage of minio admin library (#28)
* chore: update usage of minio admin library
2025-01-21 09:45:04 -08:00
Louis Ruch 7ceb652854 Update CODEOWNERS (#26) 2024-11-18 16:36:31 -08:00
dependabot[bot] 793364163a chore(deps): bump github.com/opencontainers/runc from 1.1.12 to 1.1.14 (#27)
Bumps [github.com/opencontainers/runc](https://github.com/opencontainers/runc) from 1.1.12 to 1.1.14.
- [Release notes](https://github.com/opencontainers/runc/releases)
- [Changelog](https://github.com/opencontainers/runc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/opencontainers/runc/compare/v1.1.12...v1.1.14)

---
updated-dependencies:
- dependency-name: github.com/opencontainers/runc
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 16:29:58 -08:00
dependabot[bot] 93f9ff943d chore(deps): bump github.com/golang-jwt/jwt/v4 from 4.5.0 to 4.5.1 (#25)
Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.5.0 to 4.5.1.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v4.5.0...v4.5.1)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-18 16:26:43 -08:00
hashicorp-copywrite[bot] 7c1523f910 [COMPLIANCE] Update MPL-2.0 LICENSE (#24)
Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
2024-11-18 16:25:53 -08:00
Hugo 7d2654e707 feat(storage): Improve error messages
Current `errorToStatus` is not displaying the underlying error if the
status code is 400. Additionally, we are sending the error into this
function twice: Once in the error message and again in the error itself.

This commit changes the way `errorToStatus` works by sending a message
indicating where the error occured and then the actual error as a
different argument. Additionally, it changes `errorToStatus` to always
print the input message and the error regardless of error type.
2024-10-01 15:13:57 +01:00
Hugo 759ac083ac feat(storage): Drop checksum headers
Some non-MinIO services that can use this plugin do not support
`x-amz-checksum-algorithm` and/or `x-amz-checksum-sha256`.

Important to note that if a SHA256 checksum still comes through in the
response, the existing code will verify that it matches what we have
locally.
2024-10-01 15:13:57 +01:00
dani dad21afc00 Large File Support (#21)
* disable multipart puts and add a large file test

* read random bytes rather than generate from string
2024-08-13 15:59:58 -04:00
daniandElim Tsiagbey 589e435ff1 Worker storage bucket credential state (#18)
* feat: parse minio errors and plugin permission

- Evaluate minio errors. If the error is a permission related error, convert the error to a `Permission` proto and attach it to the gRPC status
- Update `dryRun` method to join all errors before returning response. This ensures all the permissions are validated before returning

* refactor errorToStatus to return storage states

* update oncreate and validate errorcodes on failure

* update go.mod for new sdk version

* update tests to parse state instead of permissions

---------

Co-authored-by: Elim Tsiagbey <elim.tsiagbey@hashicorp.com>
2024-07-17 15:37:45 -04:00
36 changed files with 3188 additions and 614 deletions

No files matched your search

+11
View File
@@ -0,0 +1,11 @@
schema_version = 1
project {
license = "MPL-2.0"
copyright_year = 2025
header_ignore = [
# ignoring Minio's apache licensed code that is used as is in this repo
"madmin/**",
]
}
+11
View File
@@ -0,0 +1,11 @@
## PCI review checklist
<!-- heimdall_github_prtemplate:grc-pci_dss-2024-01-05 -->
- [ ] I have documented a clear reason for, and description of, the change I am making.
- [ ] If applicable, I've documented a plan to revert these changes if they require more than reverting the pull request.
- [ ] If applicable, I've documented the impact of any changes to security controls.
Examples of changes to security controls include using new access control methods, adding or removing logging pipelines, etc.
+4
View File
@@ -4,6 +4,8 @@ on:
- workflow_dispatch - workflow_dispatch
- push - push
permissions: read-all
jobs: jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -18,4 +20,6 @@ jobs:
go-version-file: 'go.mod' go-version-file: 'go.mod'
cache: true cache: true
- name: Run Tests - name: Run Tests
env:
DOCKER_MIRROR: docker.mirror.hashicorp.services
run: go test -v ./... run: go test -v ./...
+102
View File
@@ -0,0 +1,102 @@
# Folders
_obj
_test
.cover
# IntelliJ IDEA project files
.idea
*.ipr
*.iml
*.iws
### Logs ###
*.log
logs/
### direnv ###
.envrc
.direnv/
### Temp directories ###
tmp/
temp/
### Visual Studio ###
.vscode/
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
### Git ###
# Created by git for backups. To disable backups in Git:
# $ git config --global mergetool.keepBackup false
*.orig
# Created by git when using merge tools for conflicts
*.BACKUP.*
*.BASE.*
*.LOCAL.*
*.REMOTE.*
*_BACKUP_*.txt
*_BASE_*.txt
*_LOCAL_*.txt
*_REMOTE_*.txt
### Go ###
# Files that shouldn't be shared
go.work
go.work.sum
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
### Tags ###
# Ignore tags created by etags, ctags, gtags (GNU global) and cscope
TAGS
.TAGS
!TAGS/
tags
.tags
!tags/
gtags.files
GTAGS
GRTAGS
GPATH
GSYMS
cscope.files
cscope.out
cscope.in.out
cscope.po.out
# Log files (if you are creating logs in debug mode, uncomment this)
# *.log
### Vagrant Patch ###
*.box
### Vim ###
# Swap
[._]*.s[a-v][a-z]
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
+1 -1
View File
@@ -1,4 +1,4 @@
# These owners will be the default owners for everything in # These owners will be the default owners for everything in
# the repo, unless a later match takes precedence. # the repo, unless a later match takes precedence.
@hashicorp/boundary * @hashicorp/boundary
+2
View File
@@ -1,3 +1,5 @@
Copyright IBM Corp. 2025, 2026
Mozilla Public License Version 2.0 Mozilla Public License Version 2.0
================================== ==================================
+20
View File
@@ -0,0 +1,20 @@
This product includes software developed at MinIO, Inc.
(https://min.io/).
The Boundary MinIO Plugin contains subcomponents of the Minio madmin-go library v1.7.5
(https://github.com/minio/madmin-go/tree/v1.7.5),
licensed under the Apache License 2.0.
See `madmin/LICENSE` for the full license text.
---
This repository is a community fork of hashicorp/boundary-plugin-minio
(https://github.com/hashicorp/boundary-plugin-minio), maintained for Threshold.
The original work is licensed under the Mozilla Public License 2.0 (see LICENSE).
Original copyright IBM Corp. / HashiCorp, Inc. is retained in all source file
headers and in LICENSE. Modifications are limited to the Go module path and the
import paths of the Boundary SDK/API packages.
This fork is not affiliated with, endorsed by, or supported by HashiCorp or IBM.
+9
View File
@@ -1,3 +1,12 @@
## About this fork
This is a community fork of
[hashicorp/boundary-plugin-minio](https://github.com/hashicorp/boundary-plugin-minio)
for use with Threshold. It is licensed under the MPL-2.0 (see `LICENSE`), and
the original copyright notices are retained. It is not affiliated with,
endorsed by, or supported by HashiCorp or IBM. Changes from upstream are limited
to the Go module path and SDK import paths; see `NOTICE`.
# MinIO Plugin for HashiCorp Boundary # MinIO Plugin for HashiCorp Boundary
This repo contains the [MinIO](https://min.io) plugin for [HashiCorp This repo contains the [MinIO](https://min.io) plugin for [HashiCorp
+64 -65
View File
@@ -1,90 +1,89 @@
module github.com/hashicorp/boundary-plugin-minio module repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-minio
go 1.22.2 go 1.26.9
require ( require (
github.com/google/go-cmp v0.6.0 github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.5.0 github.com/google/uuid v1.6.0
github.com/hashicorp/boundary/sdk v0.0.42 github.com/hashicorp/go-multierror v1.1.1
github.com/minio/madmin-go/v3 v3.0.48 github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0
github.com/minio/minio-go/v7 v7.0.67 github.com/minio/minio-go/v7 v7.2.0
github.com/ory/dockertest/v3 v3.10.0 github.com/ory/dockertest/v3 v3.11.0
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.11.1
google.golang.org/grpc v1.59.0 google.golang.org/grpc v1.85.0-dev.0.20260825072537-93e31b48545e
google.golang.org/protobuf v1.33.0 google.golang.org/protobuf v1.36.12
repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-minio/madmin v0.2.0
repo.anhonesthost.net/CyberCoveLLC/threshold/sdk v0.1.0
) )
require ( require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect dario.cat/mergo v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/containerd/continuity v0.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/containerd/continuity v0.4.3 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v20.10.17+incompatible // indirect github.com/distribution/reference v0.6.0 // indirect
github.com/docker/docker v24.0.9+incompatible // indirect github.com/docker/cli v29.2.0+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-units v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/felixge/httpsnoop v1.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/go-logr/logr v1.4.4 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/eventlogger v0.2.6-0.20231025104552-802587e608f0 // indirect github.com/hashicorp/eventlogger v0.2.11 // indirect
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 // indirect github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 // indirect
github.com/hashicorp/go-kms-wrapping/v2 v2.0.14 // indirect github.com/hashicorp/go-kms-wrapping/v2 v2.0.20 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/imdario/mergo v0.3.13 // indirect github.com/klauspost/compress v1.18.7 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.11 // indirect
github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/crc32 v1.3.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/minio/crc64nvme v1.1.1 // indirect
github.com/lufia/plan9stats v0.0.0-20230110061619-bbe2e5e100de // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/pointerstructure v1.2.1 // indirect github.com/mitchellh/pointerstructure v1.2.1 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/moby/moby/api v1.53.0 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/moby/moby/client v0.2.2 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runc v1.1.12 // indirect github.com/opencontainers/runc v1.3.6 // indirect
github.com/philhofer/fwd v1.1.2 // indirect github.com/philhofer/fwd v1.2.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect github.com/rs/xid v1.6.0 // indirect
github.com/prometheus/client_model v0.4.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/prometheus/prom2json v1.3.3 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/safchain/ethtool v0.3.0 // indirect
github.com/secure-io/sio-go v0.3.1 // indirect github.com/secure-io/sio-go v0.3.1 // indirect
github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/sirupsen/logrus v1.9.4 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/tinylib/msgp v1.6.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/tinylib/msgp v1.1.8 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect github.com/zeebo/xxh3 v1.1.0 // indirect
golang.org/x/crypto v0.22.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
golang.org/x/mod v0.9.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 // indirect
golang.org/x/net v0.24.0 // indirect go.opentelemetry.io/otel v1.45.0 // indirect
golang.org/x/sync v0.3.0 // indirect go.opentelemetry.io/otel/metric v1.45.0 // indirect
golang.org/x/sys v0.19.0 // indirect go.opentelemetry.io/otel/trace v1.45.0 // indirect
golang.org/x/text v0.14.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/tools v0.7.0 // indirect golang.org/x/crypto v0.57.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect golang.org/x/net v0.60.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect golang.org/x/sys v0.48.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect golang.org/x/text v0.42.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260817212433-ac3dfec99bb1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1 // indirect
gopkg.in/ini.v1 v1.67.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
+173 -266
View File
@@ -1,162 +1,145 @@
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM=
github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/cli v29.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/boundary/sdk v0.0.42 h1:iRDdnM6/6q9qLtBP7/cpq/FWQGf69TFSsBQFxgGK8Jw= github.com/hashicorp/cli v1.1.7 h1:/fZJ+hNdwfTSfsxMBa9WWMlfjUZbX8/LnUxgAd7lCVU=
github.com/hashicorp/boundary/sdk v0.0.42/go.mod h1:QJhm4rky2YMtk9DS5Rj8QJhDKB/6zQbVrm3R0QgdYmQ= github.com/hashicorp/cli v1.1.7/go.mod h1:e6Mfpga9OCT1vqzFuoGZiiF/KaG9CbUfO5s3ghU3YgU=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/eventlogger v0.2.6-0.20231025104552-802587e608f0 h1:f9oX8/3zxiQrfrWnBeyjDm4S02GAU02OBtCRoZOUwlo= github.com/hashicorp/eventlogger v0.2.11 h1:lkK8ARM/DrCeL5deH2yVS6p40337dESi+77p9pIfLqo=
github.com/hashicorp/eventlogger v0.2.6-0.20231025104552-802587e608f0/go.mod h1://CHt6/j+Q2lc0NlUB5af4aS2M0c0aVBg9/JfcpAyhM= github.com/hashicorp/eventlogger v0.2.11/go.mod h1:Rmc3MEopz7jUnLokZVTWAsXaEP5rqd20ObGS+pcau3U=
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 h1:iAb287bq0TaWTnhDYuN/zVqdD2EwanQg9ncVelC60Xc= github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 h1:iAb287bq0TaWTnhDYuN/zVqdD2EwanQg9ncVelC60Xc=
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0/go.mod h1:tMywUTIvdB/FXhwm6HMTt61C8/eODY6gitCHhXtyojg= github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0/go.mod h1:tMywUTIvdB/FXhwm6HMTt61C8/eODY6gitCHhXtyojg=
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.5 h1:jrnDfQm2hCQ0/hEselgqzV4fK16gpZoY0OWGZpVPNHM= github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.8 h1:/GIUjn9GkFXMk/8/irRdbdtmx8CcyeyWdVy/E5LvzyA=
github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.5/go.mod h1:psh1qKep5ukvuNobFY/hCybuudlkkACpmazOsCgX5Rg= github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.8/go.mod h1:JDc9UOD4EVRDIwPVethJcT5Ibi/Nas6eQDPtA60iwP0=
github.com/hashicorp/go-kms-wrapping/v2 v2.0.14 h1:1ZuhfnZgRnLK8S0KovJkoTCRIQId5pv3sDR7pG5VQBw= github.com/hashicorp/go-kms-wrapping/v2 v2.0.20 h1:afreZ1WJd0WI7v4NsMZ1aL7V/T59sxPuKmQDgGUja20=
github.com/hashicorp/go-kms-wrapping/v2 v2.0.14/go.mod h1:0dWtzl2ilqKpavgM3id/kFK9L3tjo6fS4OhbVPSYpnQ= github.com/hashicorp/go-kms-wrapping/v2 v2.0.20/go.mod h1:NeK2Ul15t1zutp/dZzt28XQrGZHosbxE/QLNNfaWObM=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-plugin v1.5.2 h1:aWv8eimFqWlsEiMrYZdPYl+FdHaBJSN4AWwGWfT1G2Y= github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA=
github.com/hashicorp/go-plugin v1.5.2/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4= github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8=
github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng= github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng=
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.11 h1:uPW2Wn0YlmI9RGSkZpcIplnVRwJ7BCiGpk1vnF2TMw4= github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.13 h1:TayxZ5drfMP0G6T++WvnLESGLOWeHtdWDWWTAi2e3Qk=
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.11/go.mod h1:uis9dCmOzXuOaRyXq+1Foh31kcvXKoWogjNnhfjHfW8= github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.13/go.mod h1:NDRQ/F3DXTylqjORAP0cA+puH/JFrLlT+NlqxHK2/e8=
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.9 h1:0S0ctJ7Ra8O7ap+/3fZUnzJ3VzJyirWS/WnNCuOYtZY= github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.10 h1:2iDz+t0JLl1W0tJhvmhsh/UBgT1JgC8Qxz8HxYMWXQo=
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.9/go.mod h1:TNNdgtjLgVDbrgFcyCKrlAicIl3dZF94swJltyGUX2M= github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.10/go.mod h1:eZkXE+osawMrAWR4wJRmyKauUwH6mNGbjFuiDujnbPk=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 h1:iBt4Ew4XEGLfh6/bPk4rSYmuZJGizr6/x/AEizP0CQc= github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8/go.mod h1:aiJI+PIApBRQG7FZTEBx5GiiX+HbOHilUdNxUZi4eV0= github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=
github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.6 h1:ZYv2XA+tEfFXIToR2jmBgVqQU9gERt0APbWqmUoNGnY= github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.8 h1:pgSicufBI+MvaIG9Keykb3k9B3sWtDNjmsoyrl2/8Qw=
github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.6/go.mod h1:ggFN8dlaLWS2R1gymBbCrvXM/bkZP7hEAa4seqDwhyg= github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.8/go.mod h1:sBcjk+paCXCMR9HHLcrYSfPz2FsskD8MQuotKiyM2aA=
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 h1:SMGUnbpAcat8rIKHkBPjfv81yC46a8eCNZ2hsR2l1EI= github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 h1:SMGUnbpAcat8rIKHkBPjfv81yC46a8eCNZ2hsR2l1EI=
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1/go.mod h1:Ch/bf00Qnx77MZd49JRgHYqHQjtEmTgGU2faufpVZb0= github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1/go.mod h1:Ch/bf00Qnx77MZd49JRgHYqHQjtEmTgGU2faufpVZb0=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3 h1:xbrxd0U9XQW8qL1BAz2XrAjAF/P2vcqUTAues9c24B8= github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3 h1:xbrxd0U9XQW8qL1BAz2XrAjAF/P2vcqUTAues9c24B8=
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3/go.mod h1:LWq2Sy8UoKKuK4lFuCNWSjJj57MhNNf2zzBWMtkAIX4= github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3/go.mod h1:LWq2Sy8UoKKuK4lFuCNWSjJj57MhNNf2zzBWMtkAIX4=
github.com/hashicorp/go-sockaddr v1.0.5 h1:dvk7TIXCZpmfOlM+9mlcrWmWjw/wlKT+VDq2wMvfPJU= github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=
github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f h1:E87tDTVS5W65euzixn7clSzK66puSt1H4I5SC0EmHH4= github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f h1:E87tDTVS5W65euzixn7clSzK66puSt1H4I5SC0EmHH4=
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f/go.mod h1:3J2qVK16Lq8V+wfiL2lPeDZ7UWMxk5LemerHa1p6N00= github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f/go.mod h1:3J2qVK16Lq8V+wfiL2lPeDZ7UWMxk5LemerHa1p6N00=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2 h1:hRGSmZu7j271trc9sneMrpOW7GN5ngLm8YUZIPzf394= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/lufia/plan9stats v0.0.0-20230110061619-bbe2e5e100de h1:V53FWzU6KAZVi1tPp5UIsMoUWJ2/PNwYIDXnu7QuBCE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/lufia/plan9stats v0.0.0-20230110061619-bbe2e5e100de/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/minio/madmin-go/v3 v3.0.48 h1:a7FZdWJNeWinDibsL6PeGnLB4JFgwO59cIYHWEtYb+Y=
github.com/minio/madmin-go/v3 v3.0.48/go.mod h1:ZDF7kf5fhmxLhbGTqyq5efs4ao0v4eWf7nOuef/ljJs=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.67 h1:BeBvZWAS+kRJm1vGTMJYVjKUNoo0FoEt/wUWdUtfmh8= github.com/minio/minio-go/v7 v7.2.0 h1:RCJM0R1XOsRs+A3x3UCaf3ZYbByDaLjFeAi+YCQEPhs=
github.com/minio/minio-go/v7 v7.0.67/go.mod h1:+UXocnUeZ3wHvVh5s95gcrA4YjMIbccT6ubB+1m054A= github.com/minio/minio-go/v7 v7.2.0/go.mod h1:EU9hENAStx/xXduNdrGO5e4X5vk19NtgB+RIPjZO8o0=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng=
github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
@@ -164,203 +147,127 @@ github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQh
github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/moby/moby/api v1.53.0 h1:PihqG1ncw4W+8mZs69jlwGXdaYBeb5brF6BL7mPIS/w=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/moby/moby/api v1.53.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/moby/moby/client v0.2.2 h1:Pt4hRMCAIlyjL3cr8M5TrXCwKzguebPAc2do2ur7dEM=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/moby/moby/client v0.2.2/go.mod h1:2EkIPVNCqR05CMIzL1mfA07t0HvVUUOl85pasRz/GmQ=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= github.com/opencontainers/runc v1.3.6 h1:SLGIymCtsk80iNPWgbc8dtjI30r+5mTVV+4dN8/17Sk=
github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= github.com/opencontainers/runc v1.3.6/go.mod h1:o1wyv76EDlTkcf0KTFgN8bMWLPvgF/HfX709lDv+rr4=
github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= github.com/ory/dockertest/v3 v3.11.0 h1:OiHcxKAvSDUwsEVh2BjxQQc/5EHz9n0va9awCtNGuyA=
github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= github.com/ory/dockertest/v3 v3.11.0/go.mod h1:VIPxS1gwT9NpPOrfD3rACs8Y9Z7yhzO4SB194iUDnUI=
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/prometheus/prom2json v1.3.3 h1:IYfSMiZ7sSOfliBoo89PcufjWO4eAR0gznGcETyaUgo=
github.com/prometheus/prom2json v1.3.3/go.mod h1:Pv4yIPktEkK7btWsrUTWDDDrnpUrAELaOCj+oFwlgmc=
github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
github.com/secure-io/sio-go v0.3.1 h1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc= github.com/secure-io/sio-go v0.3.1 h1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc=
github.com/secure-io/sio-go v0.3.1/go.mod h1:+xbkjDzPjwh4Axd07pRKSNriS9SCiYksWnZqdnfpQxs= github.com/secure-io/sio-go v0.3.1/go.mod h1:+xbkjDzPjwh4Axd07pRKSNriS9SCiYksWnZqdnfpQxs=
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tinylib/msgp v1.6.1/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 h1:LMuyCAyfalSjDyjdC65nK6N0zoTT63+E/u95X0JovZI=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0/go.mod h1:085m8qbm4hgc8rZWGDEa4vmyyo2c3nPxUslYUKUIU04=
go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU=
go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ=
go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M=
go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s=
go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw=
go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA=
go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o=
go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA=
go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag=
go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= google.golang.org/genproto/googleapis/api v0.0.0-20260817212433-ac3dfec99bb1 h1:lrupDmKL3p5kEX1M92oan027eCKcouzjuPbH6YBK+Rs=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= google.golang.org/genproto/googleapis/api v0.0.0-20260817212433-ac3dfec99bb1/go.mod h1:q/3oV3jAi5vwelxsVAprMBC8BcM2zmNe+IjRGd+9/ks=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1 h1:utmqiUzZAEINbLmRyAt1QjVkMyLQ+lvIwCpLaB1rZfw=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= google.golang.org/grpc v1.85.0-dev.0.20260825072537-93e31b48545e h1:ar+feAPij1Znzug45m6AoShUCc5AaR8FLA3I6LZd94k=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= google.golang.org/grpc v1.85.0-dev.0.20260825072537-93e31b48545e/go.mod h1:7aY/KlAHanMAVIWHnk8wloQsdlfwzDz/PP9vCbJG6CU=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.2 h1:JtOSMb9OuaCZKr7h5D/h6iii14sK0hLbplTc6frx4Ss=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.2/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48= pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-minio/madmin v0.2.0 h1:1pNNc5lPXYWeLNn6KRzKydHEvyKMeLIs3ZauC9h+/1M=
repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-minio/madmin v0.2.0/go.mod h1:RwjAcIDH2aYt3lVdIEv/3XdB5Z2VaTWRhv9EJDFBgW0=
repo.anhonesthost.net/CyberCoveLLC/threshold/sdk v0.1.0 h1:ExuSGqE1pA/1DnS5xrSK7kEhD+4g5hIJC5TgmwH5LAU=
repo.anhonesthost.net/CyberCoveLLC/threshold/sdk v0.1.0/go.mod h1:cWxYF3tEkYEKTY76ub8kL+vdGmopO0YfJqJGGQJov7k=
+250
View File
@@ -0,0 +1,250 @@
// Copyright IBM Corp. 2025, 2026
// SPDX-License-Identifier: MPL-2.0
package client
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-minio/madmin"
"github.com/minio/minio-go/v7/pkg/signer"
)
const adminPrefix = "/minio/admin/v3"
// AddServiceAccountReq contains the values used by the plugin when creating a minio service account
type AddServiceAccountReq struct {
// Policy to apply to service account
Policy json.RawMessage `json:"policy,omitempty"`
// Name for this access key
Name string `json:"name,omitempty"`
// Description for this access key
Description string `json:"description,omitempty"`
}
// AddServiceAccountResp contains the credentials associated with the added service account
type AddServiceAccountResp struct {
Cred Credential `json:"credentials"`
}
// Credential contains a minio AccessKeyId and SecretAccessKey
type Credential struct {
AccessKeyId string `json:"accessKey,omitempty"`
SecretAccessKey string `json:"secretKey,omitempty"`
}
// MinioError contains the error message from failed admin API requests
type MinioError struct {
Message string `json:"message,omitempty"`
}
// Client is used to access Minio admin API
type Client struct {
endpointUrl *url.URL
accessKeyId string
secretAccessKey string
}
// getOpts - iterate the inbound Options and return a struct
func getOpts(opt ...Option) options {
opts := getDefaultOptions()
for _, o := range opt {
o(&opts)
}
return opts
}
// Option - how options are passed as arguments
type Option func(*options)
// options = how options are represented
type options struct {
withUseSsl bool
}
func getDefaultOptions() options {
return options{}
}
// WithUseSsl provides an Option to use SSL.
func WithUseSsl(b bool) Option {
return func(o *options) {
o.withUseSsl = b
}
}
// New creates a Client that can be used to access Minio admin API
func New(endpointUrl, accessKeyId, secretAccessKey string, opt ...Option) (*Client, error) {
opts := getOpts(opt...)
url, err := madmin.GetEndpointURL(endpointUrl, opts.withUseSsl)
if err != nil {
return nil, err
}
return &Client{endpointUrl: url, accessKeyId: accessKeyId, secretAccessKey: secretAccessKey}, nil
}
// DeleteServiceAccount deletes the provided serviceAccount
func (c *Client) DeleteServiceAccount(ctx context.Context, serviceAccount string) error {
// construct the url
url := c.endpointUrl.JoinPath(adminPrefix, "delete-service-account")
query := url.Query()
query.Set("accessKey", serviceAccount)
url.RawQuery = query.Encode()
// create the request
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, url.String(), nil)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
// Get sha256 for request header
sum := sha256.Sum256(nil)
req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum[:]))
// Sign request as documented
// https://min.io/docs/minio/kubernetes/upstream/administration/identity-access-management.html
req = signer.SignV4(*req, c.accessKeyId, c.secretAccessKey, "", "")
// Send the request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
// Check response status code
if resp.StatusCode != http.StatusNoContent {
// expected no content response, unmarshal resp body for error message
respBody, _ := io.ReadAll(resp.Body)
if len(respBody) == 0 {
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
errResp := &MinioError{}
if err := json.Unmarshal(respBody, &errResp); err != nil {
return fmt.Errorf("failed to parse response: %w", err)
}
return errors.New(errResp.Message)
}
return nil
}
// AddServiceAccount adds a service account to the minio server
func (c *Client) AddServiceAccount(ctx context.Context, in AddServiceAccountReq) (Credential, error) {
// construct the url
url := c.endpointUrl.JoinPath(adminPrefix, "add-service-account")
// marshal and encrypt data payload
data, err := json.Marshal(in)
if err != nil {
return Credential{}, err
}
// Minio encrypts streaming data as described https://blog.min.io/frictionless-encryption/
payload, err := madmin.EncryptData(c.secretAccessKey, data)
if err != nil {
return Credential{}, err
}
// create the request
req, err := http.NewRequestWithContext(ctx, http.MethodPut, url.String(), bytes.NewReader(payload))
if err != nil {
return Credential{}, fmt.Errorf("failed to create request: %w", err)
}
// Get sha256 for request header
sum := sha256.Sum256(payload)
req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum[:]))
req.Header.Set("Content-Type", "application/json")
// Sign request as documented
// https://min.io/docs/minio/kubernetes/upstream/administration/identity-access-management.html
req = signer.SignV4(*req, c.accessKeyId, c.secretAccessKey, "", "")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return Credential{}, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
// did not get ok, unmarshal resp body for error message
respBody, _ := io.ReadAll(resp.Body)
if len(respBody) == 0 {
return Credential{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
errResp := &MinioError{}
if err := json.Unmarshal(respBody, &errResp); err != nil {
return Credential{}, fmt.Errorf("failed to parse response: %w", err)
}
return Credential{}, errors.New(errResp.Message)
}
// got resp lets decrypt it
result, err := madmin.DecryptData(c.secretAccessKey, resp.Body)
addResp := &AddServiceAccountResp{}
if err := json.Unmarshal(result, &addResp); err != nil {
return Credential{}, fmt.Errorf("failed to parse response: %w", err)
}
return addResp.Cred, nil
}
// EnsureServiceAccount true if the account is a service account
func (c *Client) EnsureServiceAccount(ctx context.Context, serviceAccount string) error {
// construct the url
url := c.endpointUrl.JoinPath(adminPrefix, "info-service-account")
query := url.Query()
query.Set("accessKey", serviceAccount)
url.RawQuery = query.Encode()
// create the request
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), nil)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
// get sha256 for request header
sum := sha256.Sum256(nil)
req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum[:]))
// sign request as documented
// https://min.io/docs/minio/kubernetes/upstream/administration/identity-access-management.html
req = signer.SignV4(*req, c.accessKeyId, c.secretAccessKey, "", "")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
// did not get ok, unmarshal resp body for error message
respBody, _ := io.ReadAll(resp.Body)
if len(respBody) == 0 {
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
errResp := &MinioError{}
if err := json.Unmarshal(respBody, &errResp); err != nil {
return fmt.Errorf("failed to parse response: %w", err)
}
return errors.New(errResp.Message)
}
return nil
}
+82
View File
@@ -0,0 +1,82 @@
// Copyright IBM Corp. 2025, 2026
// SPDX-License-Identifier: MPL-2.0
package client
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNew(t *testing.T) {
t.Parallel()
assert, require := assert.New(t), require.New(t)
type args struct {
endpointUrl string
accessKeyId string
secretAccessKey string
opt []Option
}
tests := []struct {
name string
args args
wantUrl string
wantErr bool
}{
{
name: "fully qualified path",
args: args{endpointUrl: "https://test.com"},
wantErr: true,
},
{
name: "no ssl",
args: args{
endpointUrl: "test.com",
accessKeyId: "username",
secretAccessKey: "password",
},
wantUrl: "http://test.com",
},
{
name: "with ssl",
args: args{
endpointUrl: "test.com",
accessKeyId: "username",
secretAccessKey: "password",
opt: []Option{WithUseSsl(true)},
},
wantUrl: "https://test.com",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := New(tt.args.endpointUrl, tt.args.accessKeyId, tt.args.secretAccessKey, tt.args.opt...)
if tt.wantErr {
require.Error(err)
assert.Nil(got)
return
}
require.NoError(err)
require.NotNil(got)
assert.Equal(tt.wantUrl, got.endpointUrl.String())
assert.Equal(tt.args.accessKeyId, got.accessKeyId)
assert.Equal(tt.args.secretAccessKey, got.secretAccessKey)
})
}
}
func Test_getOpts(t *testing.T) {
t.Parallel()
t.Run("WithUseSsl", func(t *testing.T) {
testOpts := getDefaultOptions()
assert.Equal(t, false, testOpts.withUseSsl)
testOpts = getOpts()
assert.Equal(t, false, testOpts.withUseSsl)
testOpts = getOpts(WithUseSsl(true))
assert.Equal(t, true, testOpts.withUseSsl)
})
}
+39
View File
@@ -0,0 +1,39 @@
// Copyright IBM Corp. 2025, 2026
// SPDX-License-Identifier: MPL-2.0
package client_test
import (
"context"
"testing"
"repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-minio/internal/client"
internaltest "repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-minio/internal/testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestServiceAccount(t *testing.T) {
t.Parallel()
ctx := context.Background()
assert, require := assert.New(t), require.New(t)
server := internaltest.NewMinioServer(t)
c, err := client.New(server.ApiAddr, server.RootUsername, server.RootPassword)
require.NoError(err)
assert.NotNil(c)
creds, err := c.AddServiceAccount(ctx, client.AddServiceAccountReq{})
require.NoError(err)
// We just added service account it should be valid
err = c.EnsureServiceAccount(ctx, creds.AccessKeyId)
require.NoError(err)
err = c.DeleteServiceAccount(ctx, creds.AccessKeyId)
require.NoError(err)
// Now that we have deleted the service account it should be invalid
err = c.EnsureServiceAccount(ctx, creds.AccessKeyId)
require.Error(err)
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) HashiCorp, Inc. // Copyright IBM Corp. 2025, 2026
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
package errors package errors
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) HashiCorp, Inc. // Copyright IBM Corp. 2025, 2026
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
package errors package errors
+12 -15
View File
@@ -1,4 +1,4 @@
// Copyright (c) HashiCorp, Inc. // Copyright IBM Corp. 2025, 2026
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
package testing package testing
@@ -11,7 +11,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/minio/madmin-go/v3" "repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-minio/internal/client"
"github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials" "github.com/minio/minio-go/v7/pkg/credentials"
"github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3"
@@ -46,8 +46,8 @@ type MinioServer struct {
// Client is a MinIO SDK client, created with the default service account // Client is a MinIO SDK client, created with the default service account
// credentials. // credentials.
Client *minio.Client Client *minio.Client
// AdminClient is a Madmin client created with the root account credentials. // AdminClient is a client created with the root account credentials.
AdminClient *madmin.AdminClient AdminClient *client.Client
} }
type Option func(testing.TB, *options) type Option func(testing.TB, *options)
@@ -58,7 +58,7 @@ type options struct {
skipCleanup bool skipCleanup bool
} }
func defaultOptions(t testing.TB) options { func defaultOptions(_ testing.TB) options {
return options{ return options{
repository: defaultRepository, repository: defaultRepository,
version: defaultVersion, version: defaultVersion,
@@ -139,7 +139,7 @@ func NewMinioServer(t testing.TB, inOpts ...Option) *MinioServer {
server := &MinioServer{ server := &MinioServer{
RootUsername: rootUsername, RootUsername: rootUsername,
RootPassword: rootPassword, RootPassword: rootPassword,
ApiAddr: fmt.Sprintf("localhost:%s", r.GetPort("9000/tcp")), ApiAddr: fmt.Sprintf("[::1]:%s", r.GetPort("9000/tcp")),
} }
// Ensure the instance is healthy. // Ensure the instance is healthy.
@@ -165,23 +165,20 @@ func NewMinioServer(t testing.TB, inOpts ...Option) *MinioServer {
} }
// Create service account. // Create service account.
acl, err := madmin.NewWithOptions(server.ApiAddr, &madmin.Options{ c, err := client.New(server.ApiAddr, server.RootUsername, server.RootPassword)
Creds: credentials.NewStaticV4(server.RootUsername, server.RootPassword, ""),
Secure: false,
})
require.NoError(t, err) require.NoError(t, err)
server.AdminClient = acl server.AdminClient = c
creds, err := acl.AddServiceAccount(context.Background(), madmin.AddServiceAccountReq{ creds, err := c.AddServiceAccount(context.Background(), client.AddServiceAccountReq{
Name: "Boundary MinIO Plugin Test", Name: "Boundary MinIO Plugin Test",
Description: "MinIO credentials for Boundary MinIO plugin testing", Description: "MinIO credentials for Boundary MinIO plugin testing",
}) })
require.NoError(t, err) require.NoError(t, err)
server.ServiceAccountAccessKeyId = creds.AccessKey server.ServiceAccountAccessKeyId = creds.AccessKeyId
server.ServiceAccountSecretAccessKey = creds.SecretKey server.ServiceAccountSecretAccessKey = creds.SecretAccessKey
cl, err := minio.New(server.ApiAddr, &minio.Options{ cl, err := minio.New(server.ApiAddr, &minio.Options{
Creds: credentials.NewStaticV4(creds.AccessKey, creds.SecretKey, ""), Creds: credentials.NewStaticV4(creds.AccessKeyId, creds.SecretAccessKey, ""),
Secure: false, Secure: false,
}) })
require.NoError(t, err) require.NoError(t, err)
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) HashiCorp, Inc. // Copyright IBM Corp. 2025, 2026
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
package values package values
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) HashiCorp, Inc. // Copyright IBM Corp. 2025, 2026
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
package values package values
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+9
View File
@@ -0,0 +1,9 @@
MinIO Cloud Storage, (C) 2014-2020 MinIO, Inc.
This product includes software developed at MinIO, Inc.
(https://min.io/).
The MinIO project contains unmodified/modified subcomponents too with
separate copyright notices and license terms. Your use of the source
code for these subcomponents is subject to the terms and conditions
of Apache License Version 2.0
+137
View File
@@ -0,0 +1,137 @@
//
// MinIO Object Storage (c) 2021 MinIO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package madmin
import (
"encoding/hex"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"io/ioutil"
"net/http"
"unicode/utf8"
)
/* **** SAMPLE ERROR RESPONSE ****
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<BucketName>bucketName</BucketName>
<Key>objectName</Key>
<RequestId>F19772218238A85A</RequestId>
<HostId>GuWkjyviSiGHizehqpmsD1ndz5NClSP19DOT+s2mv7gXGQ8/X1lhbDGiIJEXpGFD</HostId>
</Error>
*/
// ErrorResponse - Is the typed error returned by all API operations.
type ErrorResponse struct {
XMLName xml.Name `xml:"Error" json:"-"`
Code string
Message string
BucketName string
Key string
RequestID string `xml:"RequestId"`
HostID string `xml:"HostId"`
// Region where the bucket is located. This header is returned
// only in HEAD bucket and ListObjects response.
Region string
}
// Error - Returns HTTP error string
func (e ErrorResponse) Error() string {
return e.Message
}
const (
reportIssue = "Please report this issue at https://github.com/minio/minio/issues."
)
// httpRespToErrorResponse returns a new encoded ErrorResponse
// structure as error.
func httpRespToErrorResponse(resp *http.Response) error {
if resp == nil || resp.Body == nil {
msg := "Response is empty. " + reportIssue
return ErrInvalidArgument(msg)
}
defer closeResponse(resp)
// Limit to 100K
body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 100<<10))
if err != nil {
return ErrorResponse{
Code: resp.Status,
Message: fmt.Sprintf("Failed to read server response: %s.", err),
}
}
var errResp ErrorResponse
// Decode the json error
err = json.Unmarshal(body, &errResp)
if err != nil {
// We might get errors as XML, try that.
xmlErr := xml.Unmarshal(body, &errResp)
if xmlErr != nil {
bodyString := string(body)
if !utf8.Valid(body) {
bodyString = hex.EncodeToString(body)
}
if len(bodyString) > 1024 {
bodyString = bodyString[:1021] + "..."
}
return ErrorResponse{
Code: resp.Status,
Message: fmt.Sprintf("Failed to parse server response (%s): %s", err.Error(), bodyString),
}
}
}
return errResp
}
// ToErrorResponse - Returns parsed ErrorResponse struct from body and
// http headers.
//
// For example:
//
// import admin "github.com/minio/madmin-go"
// ...
// ...
// ss, err := adm.ServiceStatus(...)
// if err != nil {
// resp := admin.ToErrorResponse(err)
// }
// ...
func ToErrorResponse(err error) ErrorResponse {
switch err := err.(type) {
case ErrorResponse:
return err
default:
return ErrorResponse{}
}
}
// ErrInvalidArgument - Invalid argument response.
func ErrInvalidArgument(message string) error {
return ErrorResponse{
Code: "InvalidArgument",
Message: message,
RequestID: "minio",
}
}
+169
View File
@@ -0,0 +1,169 @@
//
// MinIO Object Storage (c) 2021 MinIO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package madmin
import (
"bytes"
"crypto/sha256"
"errors"
"io"
"io/ioutil"
"github.com/secure-io/sio-go"
"github.com/secure-io/sio-go/sioutil"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/pbkdf2"
)
// IsEncrypted reports whether data is encrypted.
func IsEncrypted(data []byte) bool {
if len(data) <= 32 {
return false
}
b := data[32]
return b == pbkdf2AESGCM || b == argon2idAESGCM || b == argon2idChaCHa20Poly1305
}
// EncryptData encrypts the data with an unique key
// derived from password using the Argon2id PBKDF.
//
// The returned ciphertext data consists of:
//
// salt | AEAD ID | nonce | encrypted data
// 32 1 8 ~ len(data)
func EncryptData(password string, data []byte) ([]byte, error) {
salt := sioutil.MustRandom(32)
var (
id byte
err error
stream *sio.Stream
)
if FIPSEnabled() {
key := pbkdf2.Key([]byte(password), salt, pbkdf2Cost, 32, sha256.New)
stream, err = sio.AES_256_GCM.Stream(key)
if err != nil {
return nil, err
}
id = pbkdf2AESGCM
} else {
key := argon2.IDKey([]byte(password), salt, argon2idTime, argon2idMemory, argon2idThreads, 32)
if sioutil.NativeAES() {
stream, err = sio.AES_256_GCM.Stream(key)
if err != nil {
return nil, err
}
id = argon2idAESGCM
} else {
stream, err = sio.ChaCha20Poly1305.Stream(key)
if err != nil {
return nil, err
}
id = argon2idChaCHa20Poly1305
}
}
nonce := sioutil.MustRandom(stream.NonceSize())
// ciphertext = salt || AEAD ID | nonce | encrypted data
// cLen calculation: Modified by Hashicorp in 2026 to make arithmetic operations overflow-safe by casting to int64 before addition
cLen := int64(len(salt)) + 1 + int64(len(nonce)) + int64(len(data)) + stream.Overhead(int64(len(data)))
ciphertext := bytes.NewBuffer(make([]byte, 0, cLen)) // pre-alloc correct length
// Prefix the ciphertext with salt, AEAD ID and nonce
ciphertext.Write(salt)
ciphertext.WriteByte(id)
ciphertext.Write(nonce)
w := stream.EncryptWriter(ciphertext, nonce, nil)
if _, err = w.Write(data); err != nil {
return nil, err
}
if err = w.Close(); err != nil {
return nil, err
}
return ciphertext.Bytes(), nil
}
// ErrMaliciousData indicates that the stream cannot be
// decrypted by provided credentials.
var ErrMaliciousData = sio.NotAuthentic
// DecryptData decrypts the data with the key derived
// from the salt (part of data) and the password using
// the PBKDF used in EncryptData. DecryptData returns
// the decrypted plaintext on success.
//
// The data must be a valid ciphertext produced by
// EncryptData. Otherwise, the decryption will fail.
func DecryptData(password string, data io.Reader) ([]byte, error) {
var (
salt [32]byte
id [1]byte
nonce [8]byte // This depends on the AEAD but both used ciphers have the same nonce length.
)
if _, err := io.ReadFull(data, salt[:]); err != nil {
return nil, err
}
if _, err := io.ReadFull(data, id[:]); err != nil {
return nil, err
}
if _, err := io.ReadFull(data, nonce[:]); err != nil {
return nil, err
}
var (
err error
stream *sio.Stream
)
switch {
case id[0] == argon2idAESGCM:
key := argon2.IDKey([]byte(password), salt[:], argon2idTime, argon2idMemory, argon2idThreads, 32)
stream, err = sio.AES_256_GCM.Stream(key)
case id[0] == argon2idChaCHa20Poly1305:
key := argon2.IDKey([]byte(password), salt[:], argon2idTime, argon2idMemory, argon2idThreads, 32)
stream, err = sio.ChaCha20Poly1305.Stream(key)
case id[0] == pbkdf2AESGCM:
key := pbkdf2.Key([]byte(password), salt[:], pbkdf2Cost, 32, sha256.New)
stream, err = sio.AES_256_GCM.Stream(key)
default:
err = errors.New("madmin: invalid encryption algorithm ID")
}
if err != nil {
return nil, err
}
plaintext, err := ioutil.ReadAll(stream.DecryptReader(data, nonce[:], nil))
if err != nil {
return nil, err
}
return plaintext, err
}
const (
argon2idAESGCM = 0x00
argon2idChaCHa20Poly1305 = 0x01
pbkdf2AESGCM = 0x02
)
const (
argon2idTime = 1
argon2idMemory = 64 * 1024
argon2idThreads = 4
pbkdf2Cost = 8192
)
+111
View File
@@ -0,0 +1,111 @@
//
// MinIO Object Storage (c) 2021 MinIO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package madmin
import (
"bytes"
"encoding/hex"
"fmt"
"testing"
)
var encryptDataTests = []struct {
Password string
Data []byte
}{
{Password: "", Data: nil},
{Password: "", Data: make([]byte, 256)},
{Password: `xPl.8/rhR"Q_1xLt`, Data: make([]byte, 32)},
{Password: "m69?yz4W-!k+7p0", Data: make([]byte, 1024*1024)},
{Password: `7h5oU4$te{;K}fgqlI^]`, Data: make([]byte, 256)},
}
func TestEncryptData(t *testing.T) {
for i, test := range encryptDataTests {
i, test := i, test
t.Run(fmt.Sprintf("Test-%d", i), func(t *testing.T) {
ciphertext, err := EncryptData(test.Password, test.Data)
if err != nil {
t.Fatalf("Failed to encrypt data: %v", err)
}
plaintext, err := DecryptData(test.Password, bytes.NewReader(ciphertext))
if err != nil {
t.Fatalf("Failed to decrypt data: %v", err)
}
if !bytes.Equal(plaintext, test.Data) {
t.Fatal("Decrypt plaintext does not match origin data")
}
})
}
}
var decryptDataTests = []struct {
Password string
Data string
}{
{Password: "", Data: "828aa81599df0651c0461adb82283e8b89956baee9f6e719947ef9cddc849028001dc9d3ac0938f66b07bacc9751437e1985f8a9763c240e81"},
{Password: "", Data: "1793c71df6647860437134073c15688cbb15961dc0758c7ee1225e66e79c724c00d790dba9c671eae89da2c736d858286ac9bd027abacc6443" +
"0375cd41b63b67c070c7fba475a8dd66ae65ba905176c48cbe6f734fc74df87343d8ccff54bada4aeb0a04bd021633ebe6c4768e23f5dea142" +
"561d4fe3f90ed59d13dc5fb3a585dadec1742325291b9c81692bdd3420b2428127f8195e0ecd9a1c9237712ed67af7339fbbf7ff3ee1c516e1" +
"f81e69d933e057b30997e7274a2c9698e07c39f0e8d6818858f34c8191871b5a52bea9061806bd029024bfc1d9c1f230904968d6c9e10fddcb" +
"c006ba97356ff243570fd96df07dd6894e215a6b24c4ed730369519289ebd877aff6ccbd2265985e4ab1a2b7930bab9cfb767b97348a639ddf" +
"8db81bf5151da7e8f3d9638a1b86eb1dd78cc6a526f10a414c78638f"},
{Password: `xPl.8/rhR"Q_1xLt`, Data: "b5c016e93b84b473fc8a37af94936563630c36d6df1841d23a86ee51ca161f9e00ac19116b32f643ff6a56a212b265d8c56" +
"195bb0d12ce199e13dfdc5272f80c1564da2c6fc2fa18da91d8062de02af5cdafea491c6f3cae1f"},
{Password: `7h5oU4$te{;K}fgqlI^]`, Data: "c58edf7cfd557b6b655de6f48b1a3049d8d049dadb3a7bfa9ac9ccbb5baf37ec00f83086a26f43b7d6bc9075ad0" +
"38bf5741f118d502ebe94165e4072ba7f98535d6b1e3b6ae67a98115d146d9b4d90e4df4ae82df9cfa17ed7cd42" +
"465181559f7ddf09c98beec521bb4478e0cb73c4e0827af8688ff4e7a07327a10d5a180035e6ddb16d974a85257" +
"981cd9e0360a20f7b4d653190267dfb241148f018ae180568042e864b9e1b5bc05425a3abc2b0324f50c72d5679" +
"8f924405dfc0f8523f4bb564ed65af8e1b1c82a7a0640552ecf81985d95d0993d99172592ddc1393dfa63e8f0b3" +
"d744b2cc4b73384ca4693f0c1aec0e9b00e85f2937e891105d67da8f59c14ca96608e0425c42f9c1e7c2a8b3413" +
"e1381784f9cfe01de7c47cea1f8d7a7d88f5d4aca783cf55332b47f957a6b9a65269d7eb606b877b"},
}
func TestDecryptData(t *testing.T) {
for i, test := range decryptDataTests {
i, test := i, test
t.Run(fmt.Sprintf("Test-%d", i), func(t *testing.T) {
ciphertext, err := hex.DecodeString(test.Data)
if err != nil {
t.Fatalf("Failed to decode ciphertext data: %v", err)
}
_, err = DecryptData(test.Password, bytes.NewReader(ciphertext))
if err != nil {
t.Fatalf("Failed to decrypt data: %v", err)
}
})
}
}
func TestIsDecrypted(t *testing.T) {
for i, test := range decryptDataTests {
i, test := i, test
t.Run(fmt.Sprintf("Test-%d", i), func(t *testing.T) {
ciphertext, err := hex.DecodeString(test.Data)
if err != nil {
t.Fatalf("Failed to decode ciphertext data: %v", err)
}
if !IsEncrypted(ciphertext) {
t.Fatal("Ciphertext is not encrypted")
}
})
}
}
+11
View File
@@ -0,0 +1,11 @@
module repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-minio/madmin
go 1.25.0
require (
github.com/minio/minio-go/v7 v7.0.83
github.com/secure-io/sio-go v0.3.1
golang.org/x/crypto v0.55.0
)
require golang.org/x/sys v0.47.0 // indirect
+15
View File
@@ -0,0 +1,15 @@
github.com/minio/minio-go/v7 v7.0.83 h1:W4Kokksvlz3OKf3OqIlzDNKd4MERlC2oN8YptwJ0+GA=
github.com/minio/minio-go/v7 v7.0.83/go.mod h1:57YXpvc5l3rjPdhqNrDsvVlY0qPI6UTk1bflAe+9doY=
github.com/secure-io/sio-go v0.3.1 h1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc=
github.com/secure-io/sio-go v0.3.1/go.mod h1:+xbkjDzPjwh4Axd07pRKSNriS9SCiYksWnZqdnfpQxs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+31
View File
@@ -0,0 +1,31 @@
// MinIO Object Storage (c) 2021 MinIO, Inc.
//
//
// MinIO Object Storage (c) 2021 MinIO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//go:build !fips
// +build !fips
package madmin
// FIPSEnabled returns true if and only if FIPS 140-2 support
// is enabled.
//
// FIPS 140-2 requires that only specifc cryptographic
// primitives, like AES or SHA-256, are used and that
// those primitives are implemented by a FIPS 140-2
// certified cryptographic module.
func FIPSEnabled() bool { return false }
+119
View File
@@ -0,0 +1,119 @@
//
// MinIO Object Storage (c) 2021 MinIO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package madmin
import (
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"strings"
"github.com/minio/minio-go/v7/pkg/s3utils"
)
// AdminAPIVersion - admin api version used in the request.
const (
AdminAPIVersion = "v3"
AdminAPIVersionV2 = "v2"
adminAPIPrefix = "/" + AdminAPIVersion
kmsAPIVersion = "v1"
kmsAPIPrefix = "/" + kmsAPIVersion
)
// getEndpointURL - construct a new endpoint.
func getEndpointURL(endpoint string, secure bool) (*url.URL, error) {
if strings.Contains(endpoint, ":") {
host, _, err := net.SplitHostPort(endpoint)
if err != nil {
return nil, err
}
if !s3utils.IsValidIP(host) && !s3utils.IsValidDomain(host) {
msg := "Endpoint: " + endpoint + " does not follow ip address or domain name standards."
return nil, ErrInvalidArgument(msg)
}
} else {
if !s3utils.IsValidIP(endpoint) && !s3utils.IsValidDomain(endpoint) {
msg := "Endpoint: " + endpoint + " does not follow ip address or domain name standards."
return nil, ErrInvalidArgument(msg)
}
}
// If secure is false, use 'http' scheme.
scheme := "https"
if !secure {
scheme = "http"
}
// Strip the obvious :443 and :80 from the endpoint
// to avoid the signature mismatch error.
if secure && strings.HasSuffix(endpoint, ":443") {
endpoint = strings.TrimSuffix(endpoint, ":443")
}
if !secure && strings.HasSuffix(endpoint, ":80") {
endpoint = strings.TrimSuffix(endpoint, ":80")
}
// Construct a secured endpoint URL.
endpointURLStr := scheme + "://" + endpoint
endpointURL, err := url.Parse(endpointURLStr)
if err != nil {
return nil, err
}
// Validate incoming endpoint URL.
if err := isValidEndpointURL(endpointURL.String()); err != nil {
return nil, err
}
return endpointURL, nil
}
// Verify if input endpoint URL is valid.
func isValidEndpointURL(endpointURL string) error {
if endpointURL == "" {
return ErrInvalidArgument("Endpoint url cannot be empty.")
}
url, err := url.Parse(endpointURL)
if err != nil {
return ErrInvalidArgument("Endpoint url cannot be parsed.")
}
if url.Path != "/" && url.Path != "" {
return ErrInvalidArgument("Endpoint url cannot have fully qualified paths.")
}
return nil
}
// closeResponse close non nil response with any response Body.
// convenient wrapper to drain any remaining data on response body.
//
// Subsequently this allows golang http RoundTripper
// to re-use the same connection for future requests.
func closeResponse(resp *http.Response) {
// Callers should close resp.Body when done reading from it.
// If resp.Body is not closed, the Client's underlying RoundTripper
// (typically Transport) may not be able to re-use a persistent TCP
// connection to the server for a subsequent "keep-alive" request.
if resp != nil && resp.Body != nil {
// Drain any remaining Body and then close the connection.
// Without this closing connection would disallow re-using
// the same connection for future uses.
// - http://stackoverflow.com/a/17961593/4465767
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
}
}
+9
View File
@@ -0,0 +1,9 @@
package madmin
import "net/url"
// GetEndpointURL is a wrapper added by Hashicorp in 2025 to export the getEndpointURL
// function in utils.go
func GetEndpointURL(endpoint string, secure bool) (*url.URL, error) {
return getEndpointURL(endpoint, secure)
}
+3 -3
View File
@@ -1,11 +1,11 @@
// Copyright (c) HashiCorp, Inc. // Copyright IBM Corp. 2025, 2026
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
package plugin package plugin
import ( import (
"github.com/hashicorp/boundary-plugin-minio/plugin/service/storage" "repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-minio/plugin/service/storage"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin" pb "repo.anhonesthost.net/CyberCoveLLC/threshold/sdk/pbs/plugin"
) )
var ( var (
+3 -3
View File
@@ -1,4 +1,4 @@
// Copyright (c) HashiCorp, Inc. // Copyright IBM Corp. 2025, 2026
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
package storage package storage
@@ -8,8 +8,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/hashicorp/boundary-plugin-minio/internal/errors" "repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-minio/internal/errors"
"github.com/hashicorp/boundary-plugin-minio/internal/values" "repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-minio/internal/values"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/structpb"
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) HashiCorp, Inc. // Copyright IBM Corp. 2025, 2026
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
package storage package storage
+497 -61
View File
@@ -1,4 +1,4 @@
// Copyright (c) HashiCorp, Inc. // Copyright IBM Corp. 2025, 2026
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
package storage package storage
@@ -11,15 +11,19 @@ import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io" "io"
"net/http"
"os" "os"
"path" "path"
"strings" "strings"
"sync" "sync"
"time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets" "repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-minio/internal/client"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin" "repo.anhonesthost.net/CyberCoveLLC/threshold/sdk/pbs/controller/api/resources/storagebuckets"
madmin "github.com/minio/madmin-go/v3" pb "repo.anhonesthost.net/CyberCoveLLC/threshold/sdk/pbs/plugin"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-secure-stdlib/parseutil"
"github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials" "github.com/minio/minio-go/v7/pkg/credentials"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
@@ -31,7 +35,7 @@ import (
var _ pb.StoragePluginServiceServer = (*StoragePlugin)(nil) var _ pb.StoragePluginServiceServer = (*StoragePlugin)(nil)
const ( const (
defaultGetObjectChunkSize = uint32(65536) defaultGetObjectChunkSize = uint32(1 << 20) // 1 MiB
) )
// StoragePlugin implements the StoragePluginServiceServer interface for the // StoragePlugin implements the StoragePluginServiceServer interface for the
@@ -40,6 +44,42 @@ type StoragePlugin struct {
pb.UnimplementedStoragePluginServiceServer pb.UnimplementedStoragePluginServiceServer
} }
// NormalizeStorageBucketData is called by boundary before calling OnCreate or OnUpdate
//
// minio supports 3 attributes:
// - endpoint_url
// - region
// - disable_credential_rotation
//
// of these, only endpoint is required and normalized. region and disable_credential_rotation
// are passed as-is, if they were provided
func (sp *StoragePlugin) NormalizeStorageBucketData(ctx context.Context, req *pb.NormalizeStorageBucketDataRequest) (*pb.NormalizeStorageBucketDataResponse, error) {
sa, err := getStorageAttributes(req.GetAttributes())
if err != nil {
return nil, err
}
fields := req.GetAttributes().GetFields()
// re-add the scheme since getStorageAttributes removes it
if sa.UseSSL {
sa.EndpointUrl = "https://" + sa.EndpointUrl
} else {
sa.EndpointUrl = "http://" + sa.EndpointUrl
}
if sa.EndpointUrl, err = parseutil.NormalizeAddr(sa.EndpointUrl); err != nil {
return nil, status.New(codes.InvalidArgument, fmt.Sprintf("failed to normalize provided %s: %s", ConstEndpointUrl, err.Error())).Err()
}
fields[ConstEndpointUrl] = structpb.NewStringValue(sa.EndpointUrl)
return &pb.NormalizeStorageBucketDataResponse{
Attributes: &structpb.Struct{
Fields: fields,
},
}, nil
}
// OnCreateStorageBucket is a hook that runs when a storage bucket is created. // OnCreateStorageBucket is a hook that runs when a storage bucket is created.
func (sp *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCreateStorageBucketRequest) (*pb.OnCreateStorageBucketResponse, error) { func (sp *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCreateStorageBucketRequest) (*pb.OnCreateStorageBucketResponse, error) {
bucket := req.GetBucket() bucket := req.GetBucket()
@@ -79,9 +119,15 @@ func (sp *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCr
return nil, status.Errorf(codes.Unknown, "failed to create minio sdk client: %v", err) return nil, status.Errorf(codes.Unknown, "failed to create minio sdk client: %v", err)
} }
err = dryRun(ctx, cl, bucket) p, dryRunErr := dryRun(ctx, cl, bucket)
if err != nil { if dryRunErr.Len() > 0 {
return nil, status.Errorf(codes.Unknown, "failed to verify provided minio environment: %v", err) st := status.New(codes.InvalidArgument, fmt.Sprintf("failed to verify provided minio environment: %v", dryRunErr.Unwrap()))
st, err = st.WithDetails(&pb.StorageBucketCredentialState{State: p})
if err != nil {
st = status.New(codes.Unknown, fmt.Sprintf("failed to verify provided minio environment: %v", dryRunErr.Unwrap()))
}
return nil, st.Err()
} }
persistedData, err := structpb.NewStruct(sec.AsMap()) persistedData, err := structpb.NewStruct(sec.AsMap())
@@ -155,11 +201,11 @@ func (sp *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUp
if !sec.LastRotatedTime.IsZero() { if !sec.LastRotatedTime.IsZero() {
sc := sec.Clone() sc := sec.Clone()
deleteOldPersistedCredsFn = sync.OnceValue(func() error { deleteOldPersistedCredsFn = sync.OnceValue(func() error {
cl, err := newMadminClient(nsa, sc) ac, err := client.New(nsa.EndpointUrl, sec.AccessKeyId, sec.SecretAccessKey, client.WithUseSsl(nsa.UseSSL))
if err != nil { if err != nil {
return fmt.Errorf("failed to create new minio admin client: %w", err) return status.Errorf(codes.InvalidArgument, "failed to create client: %v", err)
} }
err = cl.DeleteServiceAccount(ctx, sc.AccessKeyId) err = ac.DeleteServiceAccount(ctx, sc.AccessKeyId)
if err != nil { if err != nil {
return fmt.Errorf("failed to delete minio service account: %w", err) return fmt.Errorf("failed to delete minio service account: %w", err)
} }
@@ -209,8 +255,16 @@ func (sp *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUp
return nil, status.Errorf(codes.InvalidArgument, "failed to create minio sdk client: %s", err) return nil, status.Errorf(codes.InvalidArgument, "failed to create minio sdk client: %s", err)
} }
if err = dryRun(ctx, cl, newBucket); err != nil { if p, dryRunErr := dryRun(ctx, cl, newBucket); dryRunErr != nil {
return nil, status.Errorf(codes.InvalidArgument, "failed to verify provided minio environment: %s", err) if dryRunErr.Len() > 0 {
st := status.New(codes.InvalidArgument, fmt.Sprintf("failed to verify provided minio environment: %v", dryRunErr.Unwrap()))
st, err = st.WithDetails(&pb.StorageBucketCredentialState{State: p})
if err != nil {
st = status.New(codes.InvalidArgument, fmt.Sprintf("failed to verify provided minio environment: %v", dryRunErr.Unwrap()))
}
return nil, st.Err()
}
} }
persisted, err := structpb.NewStruct(sec.AsMap()) persisted, err := structpb.NewStruct(sec.AsMap())
@@ -246,12 +300,10 @@ func (sp *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDe
if err != nil { if err != nil {
return nil, err return nil, err
} }
ac, err := client.New(sa.EndpointUrl, sec.AccessKeyId, sec.SecretAccessKey, client.WithUseSsl(sa.UseSSL))
ac, err := newMadminClient(sa, sec)
if err != nil { if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "failed to create minio admin client: %v", err) return nil, status.Errorf(codes.InvalidArgument, "failed to create client: %v", err)
} }
err = ac.DeleteServiceAccount(ctx, sec.AccessKeyId) err = ac.DeleteServiceAccount(ctx, sec.AccessKeyId)
if err != nil { if err != nil {
return nil, status.Errorf(codes.Unknown, "failed to delete minio service account: %v", err) return nil, status.Errorf(codes.Unknown, "failed to delete minio service account: %v", err)
@@ -300,9 +352,15 @@ func (sp *StoragePlugin) ValidatePermissions(ctx context.Context, req *pb.Valida
return nil, status.Errorf(codes.Unknown, "failed to create minio sdk client: %v", err) return nil, status.Errorf(codes.Unknown, "failed to create minio sdk client: %v", err)
} }
err = dryRun(ctx, cl, bucket) p, dryRunErr := dryRun(ctx, cl, bucket)
if err != nil { if dryRunErr.Unwrap() != nil {
return nil, status.Errorf(codes.Unknown, "failed to verify provided minio environment: %v", err) st := status.New(codes.FailedPrecondition, fmt.Sprintf("failed to verify provided minio environment: %v", dryRunErr.Unwrap()))
st, err = st.WithDetails(&pb.StorageBucketCredentialState{State: p})
if err != nil {
st = status.New(codes.Unknown, fmt.Sprintf("failed to verify provided minio environment: %v", dryRunErr.Unwrap()))
}
return nil, st.Err()
} }
return &pb.ValidatePermissionsResponse{}, nil return &pb.ValidatePermissionsResponse{}, nil
@@ -338,13 +396,13 @@ func (sp *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReque
Region: sa.Region, Region: sa.Region,
}) })
if err != nil { if err != nil {
return nil, status.Errorf(codes.Unknown, "failed to create minio sdk client: %v", err) return nil, errorToStatus(codes.Unknown, "failed to create minio sdk client", err, req).Err()
} }
objKey := path.Join(bucket.GetBucketPrefix(), req.GetKey()) objKey := path.Join(bucket.GetBucketPrefix(), req.GetKey())
oi, err := cl.StatObject(ctx, bucket.GetBucketName(), objKey, minio.StatObjectOptions{}) oi, err := cl.StatObject(ctx, bucket.GetBucketName(), objKey, minio.StatObjectOptions{})
if err != nil { if err != nil {
return nil, status.Errorf(codes.Unknown, "failed to stat object: %v", err) return nil, errorToStatus(codes.Unknown, "failed to stat object", err, req).Err()
} }
return &pb.HeadObjectResponse{ return &pb.HeadObjectResponse{
@@ -377,19 +435,25 @@ func (sp *StoragePlugin) GetObject(req *pb.GetObjectRequest, objServer pb.Storag
return err return err
} }
customTransport, err := newTransport(sa.UseSSL)
if err != nil {
return err
}
cl, err := minio.New(sa.EndpointUrl, &minio.Options{ cl, err := minio.New(sa.EndpointUrl, &minio.Options{
Creds: credentials.NewStaticV4(sec.AccessKeyId, sec.SecretAccessKey, ""), Creds: credentials.NewStaticV4(sec.AccessKeyId, sec.SecretAccessKey, ""),
Secure: sa.UseSSL, Secure: sa.UseSSL,
Region: sa.Region, Region: sa.Region,
Transport: customTransport,
MaxRetries: 20,
}) })
if err != nil { if err != nil {
return status.Errorf(codes.Unknown, "failed to create minio sdk client: %v", err) return errorToStatus(codes.Unknown, "failed to create minio sdk client", err, req).Err()
} }
objKey := path.Join(bucket.GetBucketPrefix(), req.GetKey()) objKey := path.Join(bucket.GetBucketPrefix(), req.GetKey())
obj, err := cl.GetObject(objServer.Context(), bucket.GetBucketName(), objKey, minio.GetObjectOptions{}) obj, err := cl.GetObject(objServer.Context(), bucket.GetBucketName(), objKey, minio.GetObjectOptions{})
if err != nil { if err != nil {
return status.Errorf(codes.Unknown, "failed to get object: %v", err) return errorToStatus(codes.Unknown, "failed to get object", err, req).Err()
} }
defer obj.Close() defer obj.Close()
@@ -422,6 +486,179 @@ func (sp *StoragePlugin) GetObject(req *pb.GetObjectRequest, objServer pb.Storag
} }
} }
// listObjectsRecursive returns recursive list of all files and directories found under a prefix from MinIO.
func listObjectsRecursive(ctx context.Context, cl *minio.Client, bucketName, searchPrefix string) ([]*pb.Object, error) {
var objects []*pb.Object
dirs := make(map[string]struct{})
seenKeys := make(map[string]struct{})
minioObjects := cl.ListObjects(ctx, bucketName, minio.ListObjectsOptions{
Prefix: searchPrefix,
Recursive: true,
})
for objectName := range minioObjects {
// Bail out if MinIO reports an error for this object entry
if objectName.Err != nil {
return nil, objectName.Err
}
key := objectName.Key
isDirFile := strings.HasSuffix(key, "/")
// Skip the directory that is exactly the prefix itself (e.g., "foo/" when prefix == "foo/").
if isDirFile && key == searchPrefix {
continue
}
// Collect parent directories (normalize), but DO NOT mark them seen here.
// This ensures an explicit directory object (e.g., "folder/") will still be appended once.
dirPaths := buildRecursiveDirectoryPaths(key, searchPrefix)
for _, dirPath := range dirPaths {
k := ensureDir(dirPath)
dirs[k] = struct{}{}
}
// De-dupe only on the exact key string.
if _, exists := seenKeys[key]; exists {
continue
}
objects = append(objects, &pb.Object{Key: key, IsDir: isDirFile})
seenKeys[key] = struct{}{}
}
// Append synthesized directories, but only if an explicit object with the same key wasn't added.
for dirKey := range dirs {
if _, exists := seenKeys[dirKey]; exists {
continue
}
objects = append(objects, &pb.Object{Key: dirKey, IsDir: true})
seenKeys[dirKey] = struct{}{}
}
return objects, nil
}
// listObjectsNonRecursive returns only top-level files and directories within the prefix from MinIO.
func listObjectsNonRecursive(ctx context.Context, cl *minio.Client, bucketName, searchPrefix string) ([]*pb.Object, error) {
var objects []*pb.Object
seenKeys := make(map[string]struct{})
minioObjects := cl.ListObjects(ctx, bucketName, minio.ListObjectsOptions{
Prefix: searchPrefix,
Recursive: false,
})
for objectName := range minioObjects {
if objectName.Err != nil {
return nil, objectName.Err
}
key := objectName.Key
isDirFile := strings.HasSuffix(key, "/")
// Skip the prefix directory itself.
if isDirFile && key == searchPrefix {
continue
}
// Determine the top-level entry under the prefix.
relativeFullPath := strings.TrimSuffix(strings.TrimPrefix(key, searchPrefix), "/")
split := strings.Split(relativeFullPath, "/")
if len(split) == 0 {
continue
}
relativeParentPath := searchPrefix + split[0]
// If deeper segments or explicit dir file, normalize to a directory form.
if len(split) > 1 || isDirFile {
relativeParentPath = ensureDir(relativeParentPath)
}
if _, exists := seenKeys[relativeParentPath]; exists {
continue
}
objects = append(objects, &pb.Object{
Key: relativeParentPath,
IsDir: strings.HasSuffix(relativeParentPath, "/"),
})
seenKeys[relativeParentPath] = struct{}{}
}
return objects, nil
}
// buildRecursiveDirectoryPaths collects all parent directory paths from an object key
// that are within the given prefix. This is used to simulate directory structures in object storage.
func buildRecursiveDirectoryPaths(objectKey, prefix string) []string {
var dirPaths []string
prefixDir := strings.TrimSuffix(prefix, "/")
pathParts := strings.Split(objectKey, "/")
// Build all possible parent directory paths from the split parts
for i := 1; i < len(pathParts); i++ {
dirPath := strings.Join(pathParts[:i], "/")
// Only include directories that are deeper than the search prefix
if len(dirPath) > len(prefixDir) && strings.HasPrefix(dirPath+"/", prefix) {
dirPaths = append(dirPaths, dirPath)
}
}
return dirPaths
}
func ensureDir(k string) string {
if strings.HasSuffix(k, "/") {
return k
}
return k + "/"
}
// ListObjects dispatches to recursive or non-recursive versions.
func (sp *StoragePlugin) ListObjects(ctx context.Context, req *pb.ListObjectsRequest) (*pb.ListObjectsResponse, error) {
bucket := req.GetBucket()
if bucket == nil {
return nil, status.Error(codes.InvalidArgument, "no storage bucket information found")
}
if bucket.GetBucketName() == "" {
return nil, status.Error(codes.InvalidArgument, "storage bucket name is required")
}
// Build MinIO client (same pattern as HeadObject/GetObject/PutObject)
sa, err := getStorageAttributes(bucket.GetAttributes())
if err != nil {
return nil, err
}
sec, err := getStorageSecrets(bucket.GetSecrets())
if err != nil {
return nil, err
}
cl, err := minio.New(sa.EndpointUrl, &minio.Options{
Creds: credentials.NewStaticV4(sec.AccessKeyId, sec.SecretAccessKey, ""),
Secure: sa.UseSSL,
Region: sa.Region,
})
if err != nil {
return nil, errorToStatus(codes.Unknown, "failed to create minio client", err, req).Err()
}
prefix := path.Join(bucket.GetBucketPrefix(), req.GetKeyPrefix())
// path.Join cleans the path, including removing a trailing slash, if it exists. Given that a slash is used to denote a folder,
// it is required here. So, if the original keyPrefix had a trailing slash, we need to add it back.
if strings.HasSuffix(req.GetKeyPrefix(), "/") {
prefix = prefix + "/"
}
var objs []*pb.Object
if req.GetRecursive() {
objs, err = listObjectsRecursive(ctx, cl, bucket.GetBucketName(), prefix)
} else {
objs, err = listObjectsNonRecursive(ctx, cl, bucket.GetBucketName(), prefix)
}
if err != nil {
return nil, errorToStatus(codes.Unknown, "failed to list objects", err, req).Err()
}
return &pb.ListObjectsResponse{Objects: objs}, nil
}
// PutObject is a hook that reads a file stored on local disk and stores it to // PutObject is a hook that reads a file stored on local disk and stores it to
// an external object store. // an external object store.
func (sp *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest) (*pb.PutObjectResponse, error) { func (sp *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest) (*pb.PutObjectResponse, error) {
@@ -472,7 +709,7 @@ func (sp *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest
Region: sa.Region, Region: sa.Region,
}) })
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create minio sdk client: %v", err) return nil, errorToStatus(codes.Internal, "failed to create minio sdk client", err, req).Err()
} }
hash := sha256.New() hash := sha256.New()
@@ -486,14 +723,9 @@ func (sp *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest
} }
key := path.Join(bucket.GetBucketPrefix(), req.GetKey()) key := path.Join(bucket.GetBucketPrefix(), req.GetKey())
res, err := cl.PutObject(ctx, bucket.GetBucketName(), key, file, info.Size(), minio.PutObjectOptions{ res, err := cl.PutObject(ctx, bucket.GetBucketName(), key, file, info.Size(), minio.PutObjectOptions{})
UserMetadata: map[string]string{
"x-amz-checksum-algorithm": "SHA256",
"x-amz-checksum-sha256": checksum,
},
})
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, "failed to put object into minio: %v", err) return nil, errorToStatus(codes.Internal, "failed to put object into minio", err, req).Err()
} }
resChecksum := res.ChecksumSHA256 resChecksum := res.ChecksumSHA256
@@ -547,7 +779,7 @@ func (sp *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObject
Region: sa.Region, Region: sa.Region,
}) })
if err != nil { if err != nil {
return nil, status.Errorf(codes.Unknown, "failed to create minio sdk client: %v", err) return nil, errorToStatus(codes.Unknown, "failed to create minio sdk client", err, req).Err()
} }
prefix := path.Join(bucket.GetBucketPrefix(), req.GetKeyPrefix()) prefix := path.Join(bucket.GetBucketPrefix(), req.GetKeyPrefix())
@@ -559,7 +791,7 @@ func (sp *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObject
if !req.Recursive { if !req.Recursive {
if err := cl.RemoveObject(ctx, bucket.GetBucketName(), prefix, minio.RemoveObjectOptions{}); err != nil { if err := cl.RemoveObject(ctx, bucket.GetBucketName(), prefix, minio.RemoveObjectOptions{}); err != nil {
return nil, status.Errorf(codes.Unknown, "error deleting minio object: %v", err) return nil, errorToStatus(codes.Unknown, "error deleting minio object", err, req).Err()
} }
return &pb.DeleteObjectsResponse{ return &pb.DeleteObjectsResponse{
ObjectsDeleted: uint32(1), ObjectsDeleted: uint32(1),
@@ -572,7 +804,7 @@ func (sp *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObject
Recursive: true, Recursive: true,
}) { }) {
if obj.Err != nil { if obj.Err != nil {
return nil, status.Errorf(codes.Unknown, "error iterating minio bucket contents: %v", err) return nil, errorToStatus(codes.Unknown, "error iterating minio bucket contents", obj.Err, req).Err()
} }
objects = append(objects, obj) objects = append(objects, obj)
} }
@@ -589,7 +821,7 @@ func (sp *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObject
removed := 0 removed := 0
for res := range cl.RemoveObjectsWithResult(ctx, bucket.GetBucketName(), emitter, minio.RemoveObjectsOptions{}) { for res := range cl.RemoveObjectsWithResult(ctx, bucket.GetBucketName(), emitter, minio.RemoveObjectsOptions{}) {
if res.Err != nil { if res.Err != nil {
return nil, status.Errorf(codes.Unknown, "error deleting minio object(s): %v", err) return nil, errorToStatus(codes.Unknown, "error deleting minio object(s)", res.Err, req).Err()
} }
removed++ removed++
} }
@@ -599,58 +831,262 @@ func (sp *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObject
}, nil }, nil
} }
func dryRun(ctx context.Context, cl *minio.Client, bucket *storagebuckets.StorageBucket) error { // validateWritePermission checks if the plugin has write permission for an object.
objectKey := path.Join(bucket.GetBucketPrefix(), uuid.New().String()) func validateWritePermission(ctx context.Context, cl *minio.Client, bucket *storagebuckets.StorageBucket, objectKey string) (*pb.Permission, *multierror.Error) {
const op = "storage.validateWritePermission"
var combinedErrors *multierror.Error
if cl == nil {
err := fmt.Errorf("%s: minio client is nil", op)
return &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, ErrorDetails: err.Error(), CheckedAt: timestamppb.Now()}, multierror.Append(combinedErrors, err)
}
if bucket == nil {
err := fmt.Errorf("%s: storage bucket is nil", op)
return &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, ErrorDetails: err.Error(), CheckedAt: timestamppb.Now()}, multierror.Append(combinedErrors, err)
}
rd := bytes.NewReader([]byte("hashicorp boundary minio plugin access test")) rd := bytes.NewReader([]byte("hashicorp boundary minio plugin access test"))
_, err := cl.PutObject(ctx, bucket.GetBucketName(), objectKey, rd, rd.Size(), minio.PutObjectOptions{}) _, err := cl.PutObject(ctx, bucket.GetBucketName(), objectKey, rd, rd.Size(), minio.PutObjectOptions{})
if err != nil { if err != nil {
return fmt.Errorf("failed to put object at %q: %w", objectKey, err) combinedErrors = multierror.Append(combinedErrors, fmt.Errorf("failed to put object at %q: %w", objectKey, err))
return errorToPermission(err), combinedErrors
} }
_, err = cl.StatObject(ctx, bucket.GetBucketName(), objectKey, minio.StatObjectOptions{}) return &pb.Permission{State: pb.StateType_STATE_TYPE_OK, CheckedAt: timestamppb.Now()}, nil
}
// validateReadPermission checks if the plugin has read permission for an object.
func validateReadPermission(ctx context.Context, cl *minio.Client, bucket *storagebuckets.StorageBucket, objectKey string) (*pb.Permission, *multierror.Error) {
const op = "storage.validateReadPermission"
var combinedErrors *multierror.Error
if cl == nil {
err := fmt.Errorf("%s: minio client is nil", op)
return &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, ErrorDetails: err.Error(), CheckedAt: timestamppb.Now()}, multierror.Append(combinedErrors, err)
}
if bucket == nil {
err := fmt.Errorf("%s: storage bucket is nil", op)
return &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, ErrorDetails: err.Error(), CheckedAt: timestamppb.Now()}, multierror.Append(combinedErrors, err)
}
if objectKey == "" {
err := fmt.Errorf("%s: object key is empty", op)
return &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, ErrorDetails: err.Error(), CheckedAt: timestamppb.Now()}, multierror.Append(combinedErrors, err)
}
permission := &pb.Permission{State: pb.StateType_STATE_TYPE_OK}
_, err := cl.StatObject(ctx, bucket.GetBucketName(), objectKey, minio.StatObjectOptions{})
if err != nil { if err != nil {
return fmt.Errorf("failed to stat object at %q: %w", objectKey, err) permission = errorToPermission(err)
combinedErrors = multierror.Append(combinedErrors, fmt.Errorf("failed to stat object at %q: %w", objectKey, err))
} }
_, err = cl.GetObject(ctx, bucket.GetBucketName(), objectKey, minio.GetObjectOptions{}) _, err = cl.GetObject(ctx, bucket.GetBucketName(), objectKey, minio.GetObjectOptions{})
if err != nil { if err != nil {
return fmt.Errorf("failed to get object at %q: %w", objectKey, err) p := errorToPermission(err)
if permission.GetState() == pb.StateType_STATE_TYPE_OK {
permission = p
} else {
if p != nil && p.GetErrorDetails() != "" {
permission.ErrorDetails = fmt.Sprintf("%s, %s", permission.GetErrorDetails(), p.GetErrorDetails())
}
}
combinedErrors = multierror.Append(combinedErrors, fmt.Errorf("failed to get object at %q: %w", objectKey, err))
} }
oiCh := cl.ListObjects(ctx, bucket.GetBucketName(), minio.ListObjectsOptions{Recursive: true}) // List just the object we created above
oiCh := cl.ListObjects(ctx, bucket.GetBucketName(), minio.ListObjectsOptions{Prefix: objectKey, MaxKeys: 1})
for oi := range oiCh { for oi := range oiCh {
if oi.Err != nil { if oi.Err != nil {
return fmt.Errorf("failed to list objects in bucket %q: %w", bucket.GetBucketName(), oi.Err) if permission.GetState() == pb.StateType_STATE_TYPE_OK {
permission = errorToPermission(oi.Err)
} else {
p := errorToPermission(err)
if p != nil && p.GetErrorDetails() != "" {
permission.ErrorDetails = fmt.Sprintf("%s, %s", permission.GetErrorDetails(), p.GetErrorDetails())
}
}
combinedErrors = multierror.Append(fmt.Errorf("failed to list objects in bucket %q: %w", bucket.GetBucketName(), oi.Err))
} }
} }
err = cl.RemoveObject(ctx, bucket.GetBucketName(), objectKey, minio.RemoveObjectOptions{}) if combinedErrors != nil {
if err != nil { return permission, combinedErrors
return fmt.Errorf("failed to remove object at %q: %w", objectKey, err)
} }
return nil permission.CheckedAt = timestamppb.Now()
return permission, nil
} }
func newMadminClient(sa *StorageAttributes, sec *StorageSecrets) (*madmin.AdminClient, error) { // validateDeletePermission checks if the plugin has delete permission for an object.
return madmin.NewWithOptions(sa.EndpointUrl, &madmin.Options{ func validateDeletePermission(ctx context.Context, cl *minio.Client, bucket *storagebuckets.StorageBucket, objectKey string) (*pb.Permission, *multierror.Error) {
Creds: credentials.NewStaticV4(sec.AccessKeyId, sec.SecretAccessKey, ""), const op = "storage.validateDeletePermission"
Secure: sa.UseSSL,
}) var combinedErrors *multierror.Error
if cl == nil {
err := fmt.Errorf("%s: minio client is nil", op)
combinedErrors = multierror.Append(combinedErrors, err)
return &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, ErrorDetails: err.Error(), CheckedAt: timestamppb.Now()}, combinedErrors
}
if bucket == nil {
err := fmt.Errorf("%s: storage bucket is nil", op)
combinedErrors = multierror.Append(combinedErrors, err)
return &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, ErrorDetails: err.Error(), CheckedAt: timestamppb.Now()}, combinedErrors
}
if objectKey == "" {
err := fmt.Errorf("%s: object key is empty", op)
combinedErrors = multierror.Append(combinedErrors, err)
return &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, ErrorDetails: err.Error(), CheckedAt: timestamppb.Now()}, combinedErrors
}
err := cl.RemoveObject(ctx, bucket.GetBucketName(), objectKey, minio.RemoveObjectOptions{})
if err != nil {
combinedErrors = multierror.Append(combinedErrors, fmt.Errorf("failed to remove object at %q: %w", objectKey, err))
return errorToPermission(err), combinedErrors
}
return &pb.Permission{State: pb.StateType_STATE_TYPE_OK, CheckedAt: timestamppb.Now()}, nil
} }
// ensureServiceAccount ensures the credentials we received belong to a MinIO func dryRun(ctx context.Context, cl *minio.Client, bucket *storagebuckets.StorageBucket) (*pb.Permissions, *multierror.Error) {
objectKey := path.Join(bucket.GetBucketPrefix(), uuid.New().String())
permissions := &pb.Permissions{
Write: &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, CheckedAt: timestamppb.Now()},
Read: &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, CheckedAt: timestamppb.Now()},
Delete: &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, CheckedAt: timestamppb.Now()},
}
var combinedErrors *multierror.Error
writePermission, writeErr := validateWritePermission(ctx, cl, bucket, objectKey)
combinedErrors = multierror.Append(combinedErrors, writeErr)
permissions.Write = writePermission
readPermission, readErr := validateReadPermission(ctx, cl, bucket, objectKey)
combinedErrors = multierror.Append(combinedErrors, readErr)
permissions.Read = readPermission
deletePermission, deleteErr := validateDeletePermission(ctx, cl, bucket, objectKey)
combinedErrors = multierror.Append(combinedErrors, deleteErr)
permissions.Delete = deletePermission
return permissions, combinedErrors
}
// ensureServiceAccount ensugres the credentials we received belong to a MinIO
// service account. This plugin does not support using user credentials for its // service account. This plugin does not support using user credentials for its
// configuration. // configuration.
func ensureServiceAccount(ctx context.Context, sa *StorageAttributes, sec *StorageSecrets) error { func ensureServiceAccount(ctx context.Context, sa *StorageAttributes, sec *StorageSecrets) error {
cl, err := newMadminClient(sa, sec) ac, err := client.New(sa.EndpointUrl, sec.AccessKeyId, sec.SecretAccessKey, client.WithUseSsl(sa.UseSSL))
if err != nil { if err != nil {
return fmt.Errorf("failed to create madmin client: %w", err) return status.Errorf(codes.InvalidArgument, "failed to create client: %v", err)
} }
if err := ac.EnsureServiceAccount(ctx, sec.AccessKeyId); err != nil {
if _, err = cl.InfoServiceAccount(ctx, sec.AccessKeyId); err != nil {
return fmt.Errorf("failed to obtain service account info: %w", err) return fmt.Errorf("failed to obtain service account info: %w", err)
} }
return nil return nil
} }
// errorToPermission converts a MinIO error to a plugin permission.
// errorToPermission only handles a subset of MinIO errors that have to do with
// permissions.
// If the MinIO error is not recognized, it returns a permission with an unknown state.
func errorToPermission(err error) *pb.Permission {
if err == nil {
return &pb.Permission{}
}
errResponse := minio.ToErrorResponse(err)
switch errResponse.Code {
// https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#ErrorCodeList
case "AccessDenied":
fallthrough
case "InvalidSecurity":
fallthrough
case "InvalidAccessKeyId":
fallthrough
case "NoSuchBucket":
return &pb.Permission{State: pb.StateType_STATE_TYPE_ERROR, ErrorDetails: errResponse.Error(), CheckedAt: timestamppb.Now()}
case "InvalidObjectState":
fallthrough
default:
return &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, ErrorDetails: errResponse.Error(), CheckedAt: timestamppb.Now()}
}
}
// errorToStatus converts an error to a gRPC status with the plugin permission
// attached.
// If the permission error is not recognized, a gRPC status will be returned
// with an code and error message passed in.
func errorToStatus(code codes.Code, msg string, err error, req any) *status.Status {
if err == nil {
// If we see this status, it means this function was called with a nil
// err. This is odd and should probably be investigated.
return status.New(code, fmt.Sprintf("%s: no error from storage service provider", msg))
}
errResponse := minio.ToErrorResponse(err)
switch errResponse.Code {
// https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#ErrorCodeList
case "RequestTimeout":
return status.New(codes.DeadlineExceeded, fmt.Sprintf("%s: timeout exceeded when communicating with storage service provider", msg))
case "SlowDown":
return status.New(codes.Unavailable, fmt.Sprintf("%s: received a throttling response from storage service provider", msg))
case "BadDigest":
return status.New(codes.Aborted, fmt.Sprintf("%s: storage checksum mismatch", msg))
case "NoSuchBucket":
return status.New(codes.NotFound, fmt.Sprintf("%s: specified storage bucket does not exist", msg))
case "NoSuchKey":
return status.New(codes.NotFound, fmt.Sprintf("%s: specified object does not exist", msg))
}
if errResponse.StatusCode == 400 {
// catch-all for various different bad-request related errors
return status.New(codes.InvalidArgument, fmt.Sprintf("%s: received a bad request response from storage service provider: %v", msg, err))
}
// for all other errors, we can parse as permission state errors
permission := errorToPermission(err)
state := &pb.StorageBucketCredentialState{State: &pb.Permissions{}}
// we can use the request type to determine what kind of response we should return
switch req.(type) {
case pb.HeadObjectRequest, *pb.HeadObjectRequest:
state.State.Read = permission
case pb.GetObjectRequest, *pb.GetObjectRequest:
state.State.Read = permission
case pb.ListObjectsRequest, *pb.ListObjectsRequest:
state.State.Read = permission
case pb.PutObjectRequest, *pb.PutObjectRequest:
state.State.Write = permission
case pb.DeleteObjectsRequest, *pb.DeleteObjectsRequest:
state.State.Delete = permission
}
st, err := status.New(code, fmt.Sprintf("%s: %s", msg, err)).WithDetails(state)
if err != nil {
st = status.New(code, fmt.Sprintf("%s: %s", msg, err))
}
return st
}
// newTransport returns an http Transport object. The http transport object utilizes the
// default transport created by minio. The following transport fields are updated to provide
// more reliable connectivity when handling larger objects:
// MaxResponseHeaderBytes is set to 1MiB to prevent header reads from stalling.
// WriteBufferSize is set to 1MiB, matching the default buffer size used in Boundary storage.
// ReadBufferSize is set to 1MiB, matching the default buffer size used in Boundary storage.
// ForceAttemptHTTP2 is set to true, supporting more resilient multiplexing and avoids HOL blocking on retries.
// IdleConnTimeout is set to 2 minutes.
func newTransport(secure bool) (*http.Transport, error) {
transport, err := minio.DefaultTransport(secure)
if err != nil {
return nil, err
}
transport.MaxResponseHeaderBytes = 1 << 20 // 1 MiB prevents header read from stalling
transport.WriteBufferSize = 1 << 20 // 1 MiB write buffer (default is 4 KiB)
transport.ReadBufferSize = 1 << 20 // 1 MiB read buffer (default is 4 KiB)
transport.ForceAttemptHTTP2 = true // enables HTTP/2 where supported more resilient multiplexing, avoids HOL blocking on retries
transport.IdleConnTimeout = 2 * time.Minute // double the idle connection timeout, default is 1 minute
return transport, nil
}
File diff suppressed because it is too large. Load diff
+9 -9
View File
@@ -1,4 +1,4 @@
// Copyright (c) HashiCorp, Inc. // Copyright IBM Corp. 2025, 2026
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
package storage package storage
@@ -10,7 +10,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/minio/madmin-go/v3" "repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-minio/internal/client"
) )
const ( const (
@@ -55,13 +55,13 @@ func rotateCredentials(ctx context.Context, bucketName string, sa *StorageAttrib
return nil, nil, fmt.Errorf("failed to ensure minio service account credentials: %w", err) return nil, nil, fmt.Errorf("failed to ensure minio service account credentials: %w", err)
} }
ac, err := newMadminClient(sa, inSec) ac, err := client.New(sa.EndpointUrl, inSec.AccessKeyId, inSec.SecretAccessKey, client.WithUseSsl(sa.UseSSL))
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to create minio admin client: %w", err) return nil, nil, fmt.Errorf("failed to create new client: %w", err)
} }
policy := json.RawMessage(fmt.Sprintf(defaultPolicyTemplate, bucketName)) policy := json.RawMessage(fmt.Sprintf(defaultPolicyTemplate, bucketName))
newCreds, err := ac.AddServiceAccount(ctx, madmin.AddServiceAccountReq{ newCreds, err := ac.AddServiceAccount(ctx, client.AddServiceAccountReq{
Name: "Boundary MinIO Service Account", Name: "Boundary MinIO Service Account",
Description: fmt.Sprintf("Boundary-managed MinIO service account for storage bucket '%s'", bucketName), Description: fmt.Sprintf("Boundary-managed MinIO service account for storage bucket '%s'", bucketName),
Policy: policy, Policy: policy,
@@ -71,16 +71,16 @@ func rotateCredentials(ctx context.Context, bucketName string, sa *StorageAttrib
} }
newSec := &StorageSecrets{ newSec := &StorageSecrets{
AccessKeyId: newCreds.AccessKey, AccessKeyId: newCreds.AccessKeyId,
SecretAccessKey: newCreds.SecretKey, SecretAccessKey: newCreds.SecretAccessKey,
LastRotatedTime: time.Now(), LastRotatedTime: time.Now(),
} }
inCl := inSec.Clone() inCl := inSec.Clone()
return newSec, sync.OnceValue(func() error { return newSec, sync.OnceValue(func() error {
ac, err := newMadminClient(sa, inCl) ac, err := client.New(sa.EndpointUrl, inCl.AccessKeyId, inCl.SecretAccessKey, client.WithUseSsl(sa.UseSSL))
if err != nil { if err != nil {
return fmt.Errorf("failed to create minio admin client: %w", err) return fmt.Errorf("failed to create new client: %w", err)
} }
err = ac.DeleteServiceAccount(ctx, inCl.AccessKeyId) err = ac.DeleteServiceAccount(ctx, inCl.AccessKeyId)
if err != nil { if err != nil {
+15 -15
View File
@@ -1,4 +1,4 @@
// Copyright (c) HashiCorp, Inc. // Copyright IBM Corp. 2025, 2026
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
package storage package storage
@@ -8,8 +8,8 @@ import (
"encoding/json" "encoding/json"
"testing" "testing"
internaltest "github.com/hashicorp/boundary-plugin-minio/internal/testing" "repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-minio/internal/client"
"github.com/minio/madmin-go/v3" internaltest "repo.anhonesthost.net/CyberCoveLLC/threshold-plugin-minio/internal/testing"
"github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -39,7 +39,7 @@ func TestRotateCredentials(t *testing.T) {
{ {
name: "addServiceAccountFail", name: "addServiceAccountFail",
inSec: func() *StorageSecrets { inSec: func() *StorageSecrets {
creds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{ creds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{
Policy: json.RawMessage(` Policy: json.RawMessage(`
{ {
"Statement": [ "Statement": [
@@ -58,8 +58,8 @@ func TestRotateCredentials(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
return &StorageSecrets{ return &StorageSecrets{
AccessKeyId: creds.AccessKey, AccessKeyId: creds.AccessKeyId,
SecretAccessKey: creds.SecretKey, SecretAccessKey: creds.SecretAccessKey,
} }
}(), }(),
expErrMsg: "failed to create new minio service account: Access Denied", expErrMsg: "failed to create new minio service account: Access Denied",
@@ -67,12 +67,12 @@ func TestRotateCredentials(t *testing.T) {
{ {
name: "credentialDeletionFail", name: "credentialDeletionFail",
inSec: func() *StorageSecrets { inSec: func() *StorageSecrets {
creds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{}) creds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{})
require.NoError(t, err) require.NoError(t, err)
return &StorageSecrets{ return &StorageSecrets{
AccessKeyId: creds.AccessKey, AccessKeyId: creds.AccessKeyId,
SecretAccessKey: creds.SecretKey, SecretAccessKey: creds.SecretAccessKey,
} }
}(), }(),
credDelErr: true, credDelErr: true,
@@ -80,12 +80,12 @@ func TestRotateCredentials(t *testing.T) {
{ {
name: "success", name: "success",
inSec: func() *StorageSecrets { inSec: func() *StorageSecrets {
creds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{}) creds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{})
require.NoError(t, err) require.NoError(t, err)
return &StorageSecrets{ return &StorageSecrets{
AccessKeyId: creds.AccessKey, AccessKeyId: creds.AccessKeyId,
SecretAccessKey: creds.SecretKey, SecretAccessKey: creds.SecretAccessKey,
} }
}(), }(),
}, },
@@ -117,7 +117,7 @@ func TestRotateCredentials(t *testing.T) {
// called. We then make the delete call and finally assert that the // called. We then make the delete call and finally assert that the
// credential was removed if there was no error when attempting to // credential was removed if there was no error when attempting to
// delete. // delete.
_, err = server.AdminClient.InfoServiceAccount(ctx, tt.inSec.AccessKeyId) err = server.AdminClient.EnsureServiceAccount(ctx, tt.inSec.AccessKeyId)
require.NoError(t, err) require.NoError(t, err)
if tt.credDelErr { if tt.credDelErr {
@@ -125,7 +125,7 @@ func TestRotateCredentials(t *testing.T) {
require.ErrorContains(t, delFn(), "failed to delete minio service account") require.ErrorContains(t, delFn(), "failed to delete minio service account")
require.ErrorContains(t, delFn(), "failed to delete minio service account") // 2nd call should return the same state (due to sync.Once). require.ErrorContains(t, delFn(), "failed to delete minio service account") // 2nd call should return the same state (due to sync.Once).
_, err := server.AdminClient.InfoServiceAccount(ctx, tt.inSec.AccessKeyId) err := server.AdminClient.EnsureServiceAccount(ctx, tt.inSec.AccessKeyId)
require.NoError(t, err) require.NoError(t, err)
return return
} else { } else {
@@ -133,7 +133,7 @@ func TestRotateCredentials(t *testing.T) {
require.NoError(t, delFn()) // 2nd call should return the same state (due to sync.Once). require.NoError(t, delFn()) // 2nd call should return the same state (due to sync.Once).
} }
_, err = server.AdminClient.InfoServiceAccount(ctx, tt.inSec.AccessKeyId) err = server.AdminClient.EnsureServiceAccount(ctx, tt.inSec.AccessKeyId)
require.ErrorContains(t, err, "service account is not found") require.ErrorContains(t, err, "service account is not found")
}) })
} }
+2 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Copyright (c) HashiCorp, Inc. # Copyright IBM Corp. 2025, 2026
# SPDX-License-Identifier: MPL-2.0 # SPDX-License-Identifier: MPL-2.0
# This shell script instantiates a MinIO instance using Docker and ensures it is # This shell script instantiates a MinIO instance using Docker and ensures it is
@@ -82,7 +82,7 @@ docker run -dt --rm \
-e "MINIO_CONSOLE_ADDRESS=:9090" \ -e "MINIO_CONSOLE_ADDRESS=:9090" \
-e 'MINIO_VOLUMES=/mnt/data' \ -e 'MINIO_VOLUMES=/mnt/data' \
--name "$container_name" \ --name "$container_name" \
minio/minio server "${DOCKER_MIRROR:+$DOCKER_MIRROR/}minio/minio" server
echo -e 'Done!' echo -e 'Done!'
echo 'Waiting for MinIO instance to be healthy...' echo 'Waiting for MinIO instance to be healthy...'