Compare commits

..
8 Commits
Author SHA1 Message Date
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
24 changed files with 1806 additions and 427 deletions

No files matched your search

+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
# the repo, unless a later match takes precedence.
@hashicorp/boundary
* @hashicorp/boundary
+2
View File
@@ -1,3 +1,5 @@
Copyright (c) 2024 HashiCorp, Inc.
Mozilla Public License Version 2.0
==================================
+9
View File
@@ -0,0 +1,9 @@
This product includes software developed at MinIO, Inc.
(https://min.io/).
The Boundary MinIO Plugin contains unmodified
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.
+32 -52
View File
@@ -1,35 +1,36 @@
module github.com/hashicorp/boundary-plugin-minio
go 1.22.2
go 1.23.4
require (
github.com/google/go-cmp v0.6.0
github.com/google/uuid v1.5.0
github.com/hashicorp/boundary/sdk v0.0.43-0.20240717182311-a20aae98794a
github.com/google/uuid v1.6.0
github.com/hashicorp/boundary-plugin-minio/madmin v0.1.0
github.com/hashicorp/boundary/sdk v0.0.49
github.com/hashicorp/go-multierror v1.1.1
github.com/minio/madmin-go/v3 v3.0.48
github.com/minio/minio-go/v7 v7.0.67
github.com/ory/dockertest/v3 v3.10.0
github.com/stretchr/testify v1.8.4
github.com/minio/minio-go/v7 v7.0.83
github.com/ory/dockertest/v3 v3.11.0
github.com/stretchr/testify v1.9.0
google.golang.org/grpc v1.61.0
google.golang.org/protobuf v1.33.0
)
require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
dario.cat/mergo v1.0.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/containerd/continuity v0.3.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/containerd/continuity v0.4.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v20.10.17+incompatible // indirect
github.com/docker/docker v24.0.9+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/docker/cli v26.1.4+incompatible // indirect
github.com/docker/docker v27.5.0+incompatible // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/goccy/go-json v0.10.4 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
@@ -37,56 +38,35 @@ require (
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-uuid v1.0.3 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/lufia/plan9stats v0.0.0-20230110061619-bbe2e5e100de // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // 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/mapstructure v1.5.0 // indirect
github.com/mitchellh/pointerstructure v1.2.1 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/opencontainers/runc v1.1.12 // indirect
github.com/philhofer/fwd v1.1.2 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/opencontainers/runc v1.1.14 // indirect
github.com/pkg/errors v0.9.1 // 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/prometheus/client_model v0.4.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/rogpeppe/go-internal v1.10.0 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/secure-io/sio-go v0.3.1 // indirect
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sirupsen/logrus v1.9.3 // 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/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.7.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+70 -157
View File
@@ -1,70 +1,70 @@
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
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-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
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/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
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/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
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/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
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/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
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.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg=
github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8=
github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M=
github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0=
github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/cli v26.1.4+incompatible h1:I8PHdc0MtxEADqYJZvhBrW9bo8gawKwwenxRM7/rLu8=
github.com/docker/cli v26.1.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v27.5.0+incompatible h1:um++2NcQtGRTz5eEgO6aJimo6/JxrTXC941hd05JO6U=
github.com/docker/docker v27.5.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
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/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.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/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/boundary/sdk v0.0.43-0.20240717182311-a20aae98794a h1:SwVze6sYE5o+J9qdcgj29auY1k6O8oSo9DC2+2Gb9rw=
github.com/hashicorp/boundary/sdk v0.0.43-0.20240717182311-a20aae98794a/go.mod h1:9iOT7kDM6mYcSkKxNuZlv8rP7U5BG1kXoevjLLL8lNQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/boundary-plugin-minio/madmin v0.1.0 h1:VH/D4t0L3wmVPn7a1DDqF4nSOTGuWktXLnDny98y63Y=
github.com/hashicorp/boundary-plugin-minio/madmin v0.1.0/go.mod h1:y0Q45edl67+Q8CVtnMpDeneusJv8ypMXWrKDyTA82qI=
github.com/hashicorp/boundary/sdk v0.0.49 h1:XOb6mSKyrU/wI20+5xTYBHQUP7eIeKcLxKSCpCs4yzM=
github.com/hashicorp/boundary/sdk v0.0.49/go.mod h1:IHP79to8aIi22FiY58pgBqJL96/U9D8ZAUhS2DdC+Us=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -115,42 +115,32 @@ github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
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/go.mod h1:3J2qVK16Lq8V+wfiL2lPeDZ7UWMxk5LemerHa1p6N00=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/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/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid/v2 v2.0.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.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2 h1:hRGSmZu7j271trc9sneMrpOW7GN5ngLm8YUZIPzf394=
github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lufia/plan9stats v0.0.0-20230110061619-bbe2e5e100de h1:V53FWzU6KAZVi1tPp5UIsMoUWJ2/PNwYIDXnu7QuBCE=
github.com/lufia/plan9stats v0.0.0-20230110061619-bbe2e5e100de/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
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/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.0.67/go.mod h1:+UXocnUeZ3wHvVh5s95gcrA4YjMIbccT6ubB+1m054A=
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/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/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=
@@ -164,26 +154,20 @@ github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQh
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/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
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/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss=
github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8=
github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg=
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w=
github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA=
github.com/ory/dockertest/v3 v3.11.0 h1:OiHcxKAvSDUwsEVh2BjxQQc/5EHz9n0va9awCtNGuyA=
github.com/ory/dockertest/v3 v3.11.0/go.mod h1:VIPxS1gwT9NpPOrfD3rACs8Y9Z7yhzO4SB194iUDnUI=
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=
@@ -191,68 +175,36 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
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/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
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/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
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/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/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/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.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.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/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
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/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
@@ -260,79 +212,44 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
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/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-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/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
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.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
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-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-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-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-20210616094352-59db8d763f22/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/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
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=
@@ -355,16 +272,12 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48=
+250
View File
@@ -0,0 +1,250 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package client
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"github.com/hashicorp/boundary-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 (c) HashiCorp, Inc.
// 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 (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package client_test
import (
"context"
"testing"
"github.com/hashicorp/boundary-plugin-minio/internal/client"
internaltest "github.com/hashicorp/boundary-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)
}
+9 -12
View File
@@ -11,7 +11,7 @@ import (
"testing"
"time"
"github.com/minio/madmin-go/v3"
"github.com/hashicorp/boundary-plugin-minio/internal/client"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/ory/dockertest/v3"
@@ -46,8 +46,8 @@ type MinioServer struct {
// Client is a MinIO SDK client, created with the default service account
// credentials.
Client *minio.Client
// AdminClient is a Madmin client created with the root account credentials.
AdminClient *madmin.AdminClient
// AdminClient is a client created with the root account credentials.
AdminClient *client.Client
}
type Option func(testing.TB, *options)
@@ -165,23 +165,20 @@ func NewMinioServer(t testing.TB, inOpts ...Option) *MinioServer {
}
// Create service account.
acl, err := madmin.NewWithOptions(server.ApiAddr, &madmin.Options{
Creds: credentials.NewStaticV4(server.RootUsername, server.RootPassword, ""),
Secure: false,
})
c, err := client.New(server.ApiAddr, server.RootUsername, server.RootPassword)
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",
Description: "MinIO credentials for Boundary MinIO plugin testing",
})
require.NoError(t, err)
server.ServiceAccountAccessKeyId = creds.AccessKey
server.ServiceAccountSecretAccessKey = creds.SecretKey
server.ServiceAccountAccessKeyId = creds.AccessKeyId
server.ServiceAccountSecretAccessKey = creds.SecretAccessKey
cl, err := minio.New(server.ApiAddr, &minio.Options{
Creds: credentials.NewStaticV4(creds.AccessKey, creds.SecretKey, ""),
Creds: credentials.NewStaticV4(creds.AccessKeyId, creds.SecretAccessKey, ""),
Secure: false,
})
require.NoError(t, err)
+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",
}
}
+168
View File
@@ -0,0 +1,168 @@
//
// 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 := int64(len(salt)+1+len(nonce)+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 github.com/hashicorp/boundary-plugin-minio/madmin
go 1.23
require (
github.com/minio/minio-go/v7 v7.0.83
github.com/secure-io/sio-go v0.3.1
golang.org/x/crypto v0.32.0
)
require golang.org/x/sys v0.29.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.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
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.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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)
}
+39 -54
View File
@@ -17,10 +17,10 @@ import (
"sync"
"github.com/google/uuid"
"github.com/hashicorp/boundary-plugin-minio/internal/client"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"github.com/hashicorp/go-multierror"
madmin "github.com/minio/madmin-go/v3"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"google.golang.org/grpc/codes"
@@ -162,11 +162,11 @@ func (sp *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUp
if !sec.LastRotatedTime.IsZero() {
sc := sec.Clone()
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 {
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 {
return fmt.Errorf("failed to delete minio service account: %w", err)
}
@@ -261,12 +261,10 @@ func (sp *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDe
if err != nil {
return nil, err
}
ac, err := newMadminClient(sa, sec)
ac, err := client.New(sa.EndpointUrl, sec.AccessKeyId, sec.SecretAccessKey, client.WithUseSsl(sa.UseSSL))
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)
if err != nil {
return nil, status.Errorf(codes.Unknown, "failed to delete minio service account: %v", err)
@@ -359,13 +357,13 @@ func (sp *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReque
Region: sa.Region,
})
if err != nil {
return nil, errorToStatus(codes.Unknown, fmt.Sprintf("failed to create minio sdk client: %v", err), err, req).Err()
return nil, errorToStatus(codes.Unknown, "failed to create minio sdk client", err, req).Err()
}
objKey := path.Join(bucket.GetBucketPrefix(), req.GetKey())
oi, err := cl.StatObject(ctx, bucket.GetBucketName(), objKey, minio.StatObjectOptions{})
if err != nil {
return nil, errorToStatus(codes.Unknown, fmt.Sprintf("failed to stat object: %v", err), err, req).Err()
return nil, errorToStatus(codes.Unknown, "failed to stat object", err, req).Err()
}
return &pb.HeadObjectResponse{
@@ -404,13 +402,13 @@ func (sp *StoragePlugin) GetObject(req *pb.GetObjectRequest, objServer pb.Storag
Region: sa.Region,
})
if err != nil {
return errorToStatus(codes.Unknown, fmt.Sprintf("failed to create minio sdk client: %v", err), err, req).Err()
return errorToStatus(codes.Unknown, "failed to create minio sdk client", err, req).Err()
}
objKey := path.Join(bucket.GetBucketPrefix(), req.GetKey())
obj, err := cl.GetObject(objServer.Context(), bucket.GetBucketName(), objKey, minio.GetObjectOptions{})
if err != nil {
return errorToStatus(codes.Unknown, fmt.Sprintf("failed to get object: %v", err), err, req).Err()
return errorToStatus(codes.Unknown, "failed to get object", err, req).Err()
}
defer obj.Close()
@@ -493,7 +491,7 @@ func (sp *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest
Region: sa.Region,
})
if err != nil {
return nil, errorToStatus(codes.Internal, fmt.Sprintf("failed to create minio sdk client: %v", err), err, req).Err()
return nil, errorToStatus(codes.Internal, "failed to create minio sdk client", err, req).Err()
}
hash := sha256.New()
@@ -507,15 +505,9 @@ func (sp *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest
}
key := path.Join(bucket.GetBucketPrefix(), req.GetKey())
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,
},
DisableMultipart: true,
})
res, err := cl.PutObject(ctx, bucket.GetBucketName(), key, file, info.Size(), minio.PutObjectOptions{})
if err != nil {
return nil, errorToStatus(codes.Internal, fmt.Sprintf("failed to put object into minio: %v", err), err, req).Err()
return nil, errorToStatus(codes.Internal, "failed to put object into minio", err, req).Err()
}
resChecksum := res.ChecksumSHA256
@@ -569,7 +561,7 @@ func (sp *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObject
Region: sa.Region,
})
if err != nil {
return nil, errorToStatus(codes.Unknown, fmt.Sprintf("failed to create minio sdk client: %v", err), err, req).Err()
return nil, errorToStatus(codes.Unknown, "failed to create minio sdk client", err, req).Err()
}
prefix := path.Join(bucket.GetBucketPrefix(), req.GetKeyPrefix())
@@ -581,7 +573,7 @@ func (sp *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObject
if !req.Recursive {
if err := cl.RemoveObject(ctx, bucket.GetBucketName(), prefix, minio.RemoveObjectOptions{}); err != nil {
return nil, errorToStatus(codes.Unknown, fmt.Sprintf("error deleting minio object: %v", err), err, req).Err()
return nil, errorToStatus(codes.Unknown, "error deleting minio object", err, req).Err()
}
return &pb.DeleteObjectsResponse{
ObjectsDeleted: uint32(1),
@@ -594,7 +586,7 @@ func (sp *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObject
Recursive: true,
}) {
if obj.Err != nil {
return nil, errorToStatus(codes.Unknown, fmt.Sprintf("error iterating minio bucket contents: %v", obj.Err), obj.Err, req).Err()
return nil, errorToStatus(codes.Unknown, "error iterating minio bucket contents", obj.Err, req).Err()
}
objects = append(objects, obj)
}
@@ -611,7 +603,7 @@ func (sp *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObject
removed := 0
for res := range cl.RemoveObjectsWithResult(ctx, bucket.GetBucketName(), emitter, minio.RemoveObjectsOptions{}) {
if res.Err != nil {
return nil, errorToStatus(codes.Unknown, fmt.Sprintf("error deleting minio object(s): %v", res.Err), res.Err, req).Err()
return nil, errorToStatus(codes.Unknown, "error deleting minio object(s)", res.Err, req).Err()
}
removed++
}
@@ -762,23 +754,15 @@ func dryRun(ctx context.Context, cl *minio.Client, bucket *storagebuckets.Storag
return permissions, combinedErrors
}
func newMadminClient(sa *StorageAttributes, sec *StorageSecrets) (*madmin.AdminClient, error) {
return madmin.NewWithOptions(sa.EndpointUrl, &madmin.Options{
Creds: credentials.NewStaticV4(sec.AccessKeyId, sec.SecretAccessKey, ""),
Secure: sa.UseSSL,
})
}
// ensureServiceAccount ensures the credentials we received belong to a MinIO
// ensureServiceAccount ensugres the credentials we received belong to a MinIO
// service account. This plugin does not support using user credentials for its
// configuration.
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 {
return fmt.Errorf("failed to create madmin client: %w", err)
return status.Errorf(codes.InvalidArgument, "failed to create client: %v", err)
}
if _, err = cl.InfoServiceAccount(ctx, sec.AccessKeyId); err != nil {
if err := ac.EnsureServiceAccount(ctx, sec.AccessKeyId); err != nil {
return fmt.Errorf("failed to obtain service account info: %w", err)
}
@@ -817,49 +801,50 @@ func errorToPermission(err error) *pb.Permission {
// 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, "timeout exceeded when communicating with storage service provider")
return status.New(codes.DeadlineExceeded, fmt.Sprintf("%s: timeout exceeded when communicating with storage service provider", msg))
case "SlowDown":
return status.New(codes.Unavailable, "received a throttling response from storage service provider")
return status.New(codes.Unavailable, fmt.Sprintf("%s: received a throttling response from storage service provider", msg))
case "BadDigest":
return status.New(codes.Aborted, "storage checksum mismatch")
return status.New(codes.Aborted, fmt.Sprintf("%s: storage checksum mismatch", msg))
case "NoSuchBucket":
return status.New(codes.NotFound, "specified storage bucket does not exist")
return status.New(codes.NotFound, fmt.Sprintf("%s: specified storage bucket does not exist", msg))
case "NoSuchKey":
return status.New(codes.NotFound, "specified object does not exist")
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, "received a bad request response from storage service provider")
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
st := status.New(code, msg)
if err == nil {
return st
}
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:
case pb.HeadObjectRequest, *pb.HeadObjectRequest:
state.State.Read = permission
case pb.GetObjectRequest:
case pb.GetObjectRequest, *pb.GetObjectRequest:
state.State.Read = permission
case pb.PutObjectRequest:
case pb.PutObjectRequest, *pb.PutObjectRequest:
state.State.Write = permission
case pb.DeleteObjectsRequest:
case pb.DeleteObjectsRequest, *pb.DeleteObjectsRequest:
state.State.Delete = permission
}
st, err = st.WithDetails(state)
st, err := status.New(code, fmt.Sprintf("%s: %s", msg, err)).WithDetails(state)
if err != nil {
st = status.New(code, msg)
st = status.New(code, fmt.Sprintf("%s: %s", msg, err))
}
return st
}
+338 -130
View File
@@ -19,12 +19,12 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/uuid"
"github.com/hashicorp/boundary-plugin-minio/internal/client"
internaltest "github.com/hashicorp/boundary-plugin-minio/internal/testing"
"github.com/hashicorp/boundary-plugin-minio/internal/values"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets"
"github.com/hashicorp/boundary/sdk/pbs/plugin"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"github.com/minio/madmin-go/v3"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/stretchr/testify/assert"
@@ -141,11 +141,11 @@ type getObjectServerMock struct {
}
// Context returns the object server's context.
func (s *getObjectServerMock) Context() context.Context {
if s.ctx == nil {
func (g *getObjectServerMock) Context() context.Context {
if g.ctx == nil {
return context.Background()
}
return s.ctx
return g.ctx
}
// Send streams a response message to the client.
@@ -249,14 +249,14 @@ func TestOnCreateStorageBucket(t *testing.T) {
},
Secrets: &structpb.Struct{
Fields: func() map[string]*structpb.Value {
creds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
creds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{
Policy: policyDenyPutObject,
})
require.NoError(t, err)
return map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue(creds.AccessKey),
ConstSecretAccessKey: structpb.NewStringValue(creds.SecretKey),
ConstAccessKeyId: structpb.NewStringValue(creds.AccessKeyId),
ConstSecretAccessKey: structpb.NewStringValue(creds.SecretAccessKey),
}
}(),
},
@@ -272,7 +272,7 @@ func TestOnCreateStorageBucket(t *testing.T) {
{
name: "credRotationFail",
req: func() *plugin.OnCreateStorageBucketRequest {
creds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
creds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{
Policy: json.RawMessage(`
{
"Statement": [
@@ -301,8 +301,8 @@ func TestOnCreateStorageBucket(t *testing.T) {
},
Secrets: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue(creds.AccessKey),
ConstSecretAccessKey: structpb.NewStringValue(creds.SecretKey),
ConstAccessKeyId: structpb.NewStringValue(creds.AccessKeyId),
ConstSecretAccessKey: structpb.NewStringValue(creds.SecretAccessKey),
},
},
},
@@ -396,7 +396,7 @@ func TestOnCreateStorageBucket(t *testing.T) {
{
name: "successWithRotation",
req: func() *plugin.OnCreateStorageBucketRequest {
creds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{})
creds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{})
require.NoError(t, err)
return &plugin.OnCreateStorageBucketRequest{
@@ -410,8 +410,8 @@ func TestOnCreateStorageBucket(t *testing.T) {
},
Secrets: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue(creds.AccessKey),
ConstSecretAccessKey: structpb.NewStringValue(creds.SecretKey),
ConstAccessKeyId: structpb.NewStringValue(creds.AccessKeyId),
ConstSecretAccessKey: structpb.NewStringValue(creds.SecretAccessKey),
},
},
},
@@ -484,10 +484,10 @@ func TestOnCreateStorageBucket(t *testing.T) {
require.NoError(t, err)
require.False(t, rspLastRotatedTime.IsZero())
_, err = server.AdminClient.InfoServiceAccount(ctx, inAccessKeyId)
err = server.AdminClient.EnsureServiceAccount(ctx, inAccessKeyId)
require.ErrorContains(t, err, "specified service account is not found")
_, err = server.AdminClient.InfoServiceAccount(ctx, rspAccessKeyId)
err = server.AdminClient.EnsureServiceAccount(ctx, rspAccessKeyId)
require.NoError(t, err)
} else {
require.Empty(t, cmp.Diff(tt.expRsp, rsp, protocmp.Transform(),
@@ -840,14 +840,14 @@ func TestOnUpdateStorageBucket(t *testing.T) {
},
Secrets: &structpb.Struct{
Fields: func() map[string]*structpb.Value {
creds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
creds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{
Policy: policyDenyPutObject,
})
require.NoError(t, err)
return map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue(creds.AccessKey),
ConstSecretAccessKey: structpb.NewStringValue(creds.SecretKey),
ConstAccessKeyId: structpb.NewStringValue(creds.AccessKeyId),
ConstSecretAccessKey: structpb.NewStringValue(creds.SecretAccessKey),
}
}(),
},
@@ -907,7 +907,7 @@ func TestOnUpdateStorageBucket(t *testing.T) {
},
},
Secrets: func() *structpb.Struct {
creds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
creds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{
Policy: json.RawMessage(`
{
"Statement": [
@@ -927,8 +927,8 @@ func TestOnUpdateStorageBucket(t *testing.T) {
return &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue(creds.AccessKey),
ConstSecretAccessKey: structpb.NewStringValue(creds.SecretKey),
ConstAccessKeyId: structpb.NewStringValue(creds.AccessKeyId),
ConstSecretAccessKey: structpb.NewStringValue(creds.SecretAccessKey),
},
}
}(),
@@ -1119,9 +1119,9 @@ func TestOnUpdateStorageBucket_SuccessDisableCredRotationAndNewCreds(t *testing.
err := server.Client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})
require.NoError(t, err)
persistedCreds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{})
persistedCreds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{})
require.NoError(t, err)
newCreds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{})
newCreds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{})
require.NoError(t, err)
req := &plugin.OnUpdateStorageBucketRequest{
@@ -1137,8 +1137,8 @@ func TestOnUpdateStorageBucket_SuccessDisableCredRotationAndNewCreds(t *testing.
Persisted: &storagebuckets.StorageBucketPersisted{
Data: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue(persistedCreds.AccessKey),
ConstSecretAccessKey: structpb.NewStringValue(persistedCreds.SecretKey),
ConstAccessKeyId: structpb.NewStringValue(persistedCreds.AccessKeyId),
ConstSecretAccessKey: structpb.NewStringValue(persistedCreds.SecretAccessKey),
ConstLastRotatedTime: structpb.NewStringValue(time.Now().Format(time.RFC3339Nano)),
},
},
@@ -1153,8 +1153,8 @@ func TestOnUpdateStorageBucket_SuccessDisableCredRotationAndNewCreds(t *testing.
},
Secrets: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue(newCreds.AccessKey),
ConstSecretAccessKey: structpb.NewStringValue(newCreds.SecretKey),
ConstAccessKeyId: structpb.NewStringValue(newCreds.AccessKeyId),
ConstSecretAccessKey: structpb.NewStringValue(newCreds.SecretAccessKey),
},
},
},
@@ -1170,25 +1170,21 @@ func TestOnUpdateStorageBucket_SuccessDisableCredRotationAndNewCreds(t *testing.
outAccessKeyId, err := values.GetStringValue(rsp.GetPersisted().GetData(), ConstAccessKeyId, true)
require.NoError(t, err)
require.Equal(t, newCreds.AccessKey, outAccessKeyId)
require.Equal(t, newCreds.AccessKeyId, outAccessKeyId)
outSecretAccessKey, err := values.GetStringValue(rsp.GetPersisted().GetData(), ConstSecretAccessKey, true)
require.NoError(t, err)
require.Equal(t, newCreds.SecretKey, outSecretAccessKey)
require.Equal(t, newCreds.SecretAccessKey, outSecretAccessKey)
lastRotated, err := values.GetTimeValue(rsp.GetPersisted().GetData(), ConstLastRotatedTime)
require.NoError(t, err)
require.True(t, lastRotated.IsZero())
_, err = server.AdminClient.InfoServiceAccount(ctx, persistedCreds.AccessKey)
err = server.AdminClient.EnsureServiceAccount(ctx, persistedCreds.AccessKeyId)
require.ErrorContains(t, err, "specified service account is not found")
_, err = server.AdminClient.InfoServiceAccount(ctx, newCreds.AccessKey)
err = server.AdminClient.EnsureServiceAccount(ctx, newCreds.AccessKeyId)
require.NoError(t, err)
lsa, err := server.AdminClient.ListServiceAccounts(ctx, server.RootUsername)
require.NoError(t, err)
require.Len(t, lsa.Accounts, 2) // Creds in `server` + newCreds.
}
func TestOnUpdateStorageBucket_SuccessEnableCredRotationWithNewCreds(t *testing.T) {
@@ -1199,10 +1195,10 @@ func TestOnUpdateStorageBucket_SuccessEnableCredRotationWithNewCreds(t *testing.
err := server.Client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})
require.NoError(t, err)
persistedCreds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{})
persistedCreds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{})
require.NoError(t, err)
newCreds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{})
newCreds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{})
require.NoError(t, err)
req := &plugin.OnUpdateStorageBucketRequest{
@@ -1218,8 +1214,8 @@ func TestOnUpdateStorageBucket_SuccessEnableCredRotationWithNewCreds(t *testing.
Persisted: &storagebuckets.StorageBucketPersisted{
Data: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue(persistedCreds.AccessKey),
ConstSecretAccessKey: structpb.NewStringValue(persistedCreds.SecretKey),
ConstAccessKeyId: structpb.NewStringValue(persistedCreds.AccessKeyId),
ConstSecretAccessKey: structpb.NewStringValue(persistedCreds.SecretAccessKey),
},
},
},
@@ -1233,8 +1229,8 @@ func TestOnUpdateStorageBucket_SuccessEnableCredRotationWithNewCreds(t *testing.
},
Secrets: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue(newCreds.AccessKey),
ConstSecretAccessKey: structpb.NewStringValue(newCreds.SecretKey),
ConstAccessKeyId: structpb.NewStringValue(newCreds.AccessKeyId),
ConstSecretAccessKey: structpb.NewStringValue(newCreds.SecretAccessKey),
},
},
},
@@ -1251,30 +1247,26 @@ func TestOnUpdateStorageBucket_SuccessEnableCredRotationWithNewCreds(t *testing.
outAccessKeyId, err := values.GetStringValue(rsp.GetPersisted().GetData(), ConstAccessKeyId, true)
require.NoError(t, err)
require.NotEqual(t, persistedCreds.AccessKey, outAccessKeyId)
require.NotEqual(t, newCreds.AccessKey, outAccessKeyId)
require.NotEqual(t, persistedCreds.AccessKeyId, outAccessKeyId)
require.NotEqual(t, newCreds.AccessKeyId, outAccessKeyId)
outSecretAccessKey, err := values.GetStringValue(rsp.GetPersisted().GetData(), ConstSecretAccessKey, true)
require.NoError(t, err)
require.NotEqual(t, persistedCreds.SecretKey, outSecretAccessKey)
require.NotEqual(t, newCreds.SecretKey, outSecretAccessKey)
require.NotEqual(t, persistedCreds.SecretAccessKey, outSecretAccessKey)
require.NotEqual(t, newCreds.SecretAccessKey, outSecretAccessKey)
lastRotated, err := values.GetTimeValue(rsp.GetPersisted().GetData(), ConstLastRotatedTime)
require.NoError(t, err)
require.False(t, lastRotated.IsZero())
_, err = server.AdminClient.InfoServiceAccount(ctx, persistedCreds.AccessKey)
err = server.AdminClient.EnsureServiceAccount(ctx, persistedCreds.AccessKeyId)
require.NoError(t, err)
_, err = server.AdminClient.InfoServiceAccount(ctx, newCreds.AccessKey)
err = server.AdminClient.EnsureServiceAccount(ctx, newCreds.AccessKeyId)
require.ErrorContains(t, err, "specified service account is not found")
_, err = server.AdminClient.InfoServiceAccount(ctx, outAccessKeyId)
err = server.AdminClient.EnsureServiceAccount(ctx, outAccessKeyId)
require.NoError(t, err)
lsa, err := server.AdminClient.ListServiceAccounts(ctx, server.RootUsername)
require.NoError(t, err)
require.Len(t, lsa.Accounts, 3) // Creds in `server` + persistedCreds + newly rotated creds.
}
func TestOnUpdateStorageBucket_SuccessNoCredRotationChangeDisabledNewCreds(t *testing.T) {
@@ -1285,10 +1277,10 @@ func TestOnUpdateStorageBucket_SuccessNoCredRotationChangeDisabledNewCreds(t *te
err := server.Client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})
require.NoError(t, err)
persistedCreds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{})
persistedCreds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{})
require.NoError(t, err)
newCreds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{})
newCreds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{})
require.NoError(t, err)
bucketAttrs := &structpb.Struct{
@@ -1305,8 +1297,8 @@ func TestOnUpdateStorageBucket_SuccessNoCredRotationChangeDisabledNewCreds(t *te
Persisted: &storagebuckets.StorageBucketPersisted{
Data: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue(persistedCreds.AccessKey),
ConstSecretAccessKey: structpb.NewStringValue(persistedCreds.SecretKey),
ConstAccessKeyId: structpb.NewStringValue(persistedCreds.AccessKeyId),
ConstSecretAccessKey: structpb.NewStringValue(persistedCreds.SecretAccessKey),
},
},
},
@@ -1315,8 +1307,8 @@ func TestOnUpdateStorageBucket_SuccessNoCredRotationChangeDisabledNewCreds(t *te
Attributes: bucketAttrs,
Secrets: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue(newCreds.AccessKey),
ConstSecretAccessKey: structpb.NewStringValue(newCreds.SecretKey),
ConstAccessKeyId: structpb.NewStringValue(newCreds.AccessKeyId),
ConstSecretAccessKey: structpb.NewStringValue(newCreds.SecretAccessKey),
},
},
},
@@ -1332,21 +1324,17 @@ func TestOnUpdateStorageBucket_SuccessNoCredRotationChangeDisabledNewCreds(t *te
outAccessKeyId, err := values.GetStringValue(rsp.GetPersisted().GetData(), ConstAccessKeyId, true)
require.NoError(t, err)
require.Equal(t, newCreds.AccessKey, outAccessKeyId)
require.Equal(t, newCreds.AccessKeyId, outAccessKeyId)
outSecretAccessKey, err := values.GetStringValue(rsp.GetPersisted().GetData(), ConstSecretAccessKey, true)
require.NoError(t, err)
require.Equal(t, newCreds.SecretKey, outSecretAccessKey)
require.Equal(t, newCreds.SecretAccessKey, outSecretAccessKey)
_, err = server.AdminClient.InfoServiceAccount(ctx, persistedCreds.AccessKey)
err = server.AdminClient.EnsureServiceAccount(ctx, persistedCreds.AccessKeyId)
require.NoError(t, err)
_, err = server.AdminClient.InfoServiceAccount(ctx, newCreds.AccessKey)
err = server.AdminClient.EnsureServiceAccount(ctx, newCreds.AccessKeyId)
require.NoError(t, err)
lsa, err := server.AdminClient.ListServiceAccounts(ctx, server.RootUsername)
require.NoError(t, err)
require.Len(t, lsa.Accounts, 3) // Creds in `server` + persistedCreds + newCreds
}
func TestOnUpdateStorageBucket_SuccessNoCredRotationChangeEnabledNewCreds(t *testing.T) {
@@ -1357,10 +1345,10 @@ func TestOnUpdateStorageBucket_SuccessNoCredRotationChangeEnabledNewCreds(t *tes
err := server.Client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})
require.NoError(t, err)
persistedCreds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{})
persistedCreds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{})
require.NoError(t, err)
newCreds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{})
newCreds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{})
require.NoError(t, err)
bucketAttrs := &structpb.Struct{
@@ -1377,8 +1365,8 @@ func TestOnUpdateStorageBucket_SuccessNoCredRotationChangeEnabledNewCreds(t *tes
Persisted: &storagebuckets.StorageBucketPersisted{
Data: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue(persistedCreds.AccessKey),
ConstSecretAccessKey: structpb.NewStringValue(persistedCreds.SecretKey),
ConstAccessKeyId: structpb.NewStringValue(persistedCreds.AccessKeyId),
ConstSecretAccessKey: structpb.NewStringValue(persistedCreds.SecretAccessKey),
ConstLastRotatedTime: structpb.NewStringValue(time.Now().Format(time.RFC3339Nano)),
},
},
@@ -1388,8 +1376,8 @@ func TestOnUpdateStorageBucket_SuccessNoCredRotationChangeEnabledNewCreds(t *tes
Attributes: bucketAttrs,
Secrets: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue(newCreds.AccessKey),
ConstSecretAccessKey: structpb.NewStringValue(newCreds.SecretKey),
ConstAccessKeyId: structpb.NewStringValue(newCreds.AccessKeyId),
ConstSecretAccessKey: structpb.NewStringValue(newCreds.SecretAccessKey),
},
},
},
@@ -1406,30 +1394,26 @@ func TestOnUpdateStorageBucket_SuccessNoCredRotationChangeEnabledNewCreds(t *tes
outAccessKeyId, err := values.GetStringValue(rsp.GetPersisted().GetData(), ConstAccessKeyId, true)
require.NoError(t, err)
require.NotEqual(t, persistedCreds.AccessKey, outAccessKeyId)
require.NotEqual(t, newCreds.AccessKey, outAccessKeyId)
require.NotEqual(t, persistedCreds.AccessKeyId, outAccessKeyId)
require.NotEqual(t, newCreds.AccessKeyId, outAccessKeyId)
outSecretAccessKey, err := values.GetStringValue(rsp.GetPersisted().GetData(), ConstSecretAccessKey, true)
require.NoError(t, err)
require.NotEqual(t, persistedCreds.SecretKey, outSecretAccessKey)
require.NotEqual(t, newCreds.SecretKey, outSecretAccessKey)
require.NotEqual(t, persistedCreds.SecretAccessKey, outSecretAccessKey)
require.NotEqual(t, newCreds.SecretAccessKey, outSecretAccessKey)
lastRotated, err := values.GetTimeValue(rsp.GetPersisted().GetData(), ConstLastRotatedTime)
require.NoError(t, err)
require.False(t, lastRotated.IsZero())
_, err = server.AdminClient.InfoServiceAccount(ctx, persistedCreds.AccessKey)
err = server.AdminClient.EnsureServiceAccount(ctx, persistedCreds.AccessKeyId)
require.ErrorContains(t, err, "specified service account is not found")
_, err = server.AdminClient.InfoServiceAccount(ctx, newCreds.AccessKey)
err = server.AdminClient.EnsureServiceAccount(ctx, newCreds.AccessKeyId)
require.ErrorContains(t, err, "specified service account is not found")
_, err = server.AdminClient.InfoServiceAccount(ctx, outAccessKeyId)
err = server.AdminClient.EnsureServiceAccount(ctx, outAccessKeyId)
require.NoError(t, err)
lsa, err := server.AdminClient.ListServiceAccounts(ctx, server.RootUsername)
require.NoError(t, err)
require.Len(t, lsa.Accounts, 2) // Creds in `server` + newly rotated creds.
}
func TestOnCreateStorageBucket_NoChangesCredRotationDisabled(t *testing.T) {
@@ -1440,7 +1424,7 @@ func TestOnCreateStorageBucket_NoChangesCredRotationDisabled(t *testing.T) {
err := server.Client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})
require.NoError(t, err)
persistedCreds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{})
persistedCreds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{})
require.NoError(t, err)
bucketAttrs := &structpb.Struct{
@@ -1457,8 +1441,8 @@ func TestOnCreateStorageBucket_NoChangesCredRotationDisabled(t *testing.T) {
Persisted: &storagebuckets.StorageBucketPersisted{
Data: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue(persistedCreds.AccessKey),
ConstSecretAccessKey: structpb.NewStringValue(persistedCreds.SecretKey),
ConstAccessKeyId: structpb.NewStringValue(persistedCreds.AccessKeyId),
ConstSecretAccessKey: structpb.NewStringValue(persistedCreds.SecretAccessKey),
ConstLastRotatedTime: structpb.NewStringValue(time.Time{}.Format(time.RFC3339Nano)),
},
},
@@ -1477,12 +1461,8 @@ func TestOnCreateStorageBucket_NoChangesCredRotationDisabled(t *testing.T) {
require.Empty(t, cmp.Diff(req.GetPersisted(), rsp.GetPersisted(), protocmp.Transform()))
_, err = server.AdminClient.InfoServiceAccount(ctx, persistedCreds.AccessKey)
err = server.AdminClient.EnsureServiceAccount(ctx, persistedCreds.AccessKeyId)
require.NoError(t, err)
lsa, err := server.AdminClient.ListServiceAccounts(ctx, server.RootUsername)
require.NoError(t, err)
require.Len(t, lsa.Accounts, 2) // Creds in `server` + persistedCreds
}
func TestOnCreateStorageBucket_NoChangesCredRotationEnabled(t *testing.T) {
@@ -1493,7 +1473,7 @@ func TestOnCreateStorageBucket_NoChangesCredRotationEnabled(t *testing.T) {
err := server.Client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})
require.NoError(t, err)
persistedCreds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{})
persistedCreds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{})
require.NoError(t, err)
bucketAttrs := &structpb.Struct{
@@ -1510,8 +1490,8 @@ func TestOnCreateStorageBucket_NoChangesCredRotationEnabled(t *testing.T) {
Persisted: &storagebuckets.StorageBucketPersisted{
Data: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue(persistedCreds.AccessKey),
ConstSecretAccessKey: structpb.NewStringValue(persistedCreds.SecretKey),
ConstAccessKeyId: structpb.NewStringValue(persistedCreds.AccessKeyId),
ConstSecretAccessKey: structpb.NewStringValue(persistedCreds.SecretAccessKey),
ConstLastRotatedTime: structpb.NewStringValue(time.Now().Format(time.RFC3339Nano)),
},
},
@@ -1530,12 +1510,8 @@ func TestOnCreateStorageBucket_NoChangesCredRotationEnabled(t *testing.T) {
require.Empty(t, cmp.Diff(req.GetPersisted(), rsp.GetPersisted(), protocmp.Transform()))
_, err = server.AdminClient.InfoServiceAccount(ctx, persistedCreds.AccessKey)
err = server.AdminClient.EnsureServiceAccount(ctx, persistedCreds.AccessKeyId)
require.NoError(t, err)
lsa, err := server.AdminClient.ListServiceAccounts(ctx, server.RootUsername)
require.NoError(t, err)
require.Len(t, lsa.Accounts, 2) // Creds in `server` + persistedCreds
}
func TestOnDeleteStorageBucket(t *testing.T) {
@@ -1611,7 +1587,7 @@ func TestOnDeleteStorageBucket(t *testing.T) {
{
name: "successNoRotation",
in: func() *pb.OnDeleteStorageBucketRequest {
creds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{})
creds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{})
require.NoError(t, err)
return &pb.OnDeleteStorageBucketRequest{
@@ -1627,8 +1603,8 @@ func TestOnDeleteStorageBucket(t *testing.T) {
Persisted: &storagebuckets.StorageBucketPersisted{
Data: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue(creds.AccessKey),
ConstSecretAccessKey: structpb.NewStringValue(creds.SecretKey),
ConstAccessKeyId: structpb.NewStringValue(creds.AccessKeyId),
ConstSecretAccessKey: structpb.NewStringValue(creds.SecretAccessKey),
ConstLastRotatedTime: structpb.NewStringValue(time.Time{}.Format(time.RFC3339Nano)),
},
},
@@ -1640,7 +1616,7 @@ func TestOnDeleteStorageBucket(t *testing.T) {
{
name: "successWithRotation",
in: func() *pb.OnDeleteStorageBucketRequest {
creds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{})
creds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{})
require.NoError(t, err)
return &pb.OnDeleteStorageBucketRequest{
@@ -1656,8 +1632,8 @@ func TestOnDeleteStorageBucket(t *testing.T) {
Persisted: &storagebuckets.StorageBucketPersisted{
Data: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue(creds.AccessKey),
ConstSecretAccessKey: structpb.NewStringValue(creds.SecretKey),
ConstAccessKeyId: structpb.NewStringValue(creds.AccessKeyId),
ConstSecretAccessKey: structpb.NewStringValue(creds.SecretAccessKey),
ConstLastRotatedTime: structpb.NewStringValue(time.Now().Format(time.RFC3339Nano)),
},
},
@@ -1683,7 +1659,7 @@ func TestOnDeleteStorageBucket(t *testing.T) {
accessKeyIn, err := values.GetStringValue(tt.in.GetPersisted().GetData(), ConstAccessKeyId, true)
require.NoError(t, err)
_, err = server.AdminClient.InfoServiceAccount(ctx, accessKeyIn)
err = server.AdminClient.EnsureServiceAccount(ctx, accessKeyIn)
if tt.expCredsDeleted {
require.ErrorContains(t, err, "specified service account is not found")
} else {
@@ -1756,14 +1732,14 @@ func TestValidatePermissions(t *testing.T) {
},
Secrets: &structpb.Struct{
Fields: func() map[string]*structpb.Value {
creds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
creds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{
Policy: policyDenyPutObject,
})
require.NoError(t, err)
return map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue(creds.AccessKey),
ConstSecretAccessKey: structpb.NewStringValue(creds.SecretKey),
ConstAccessKeyId: structpb.NewStringValue(creds.AccessKeyId),
ConstSecretAccessKey: structpb.NewStringValue(creds.SecretAccessKey),
}
}(),
},
@@ -2130,8 +2106,8 @@ func TestGetObject(t *testing.T) {
Key: "object-name",
}, []byte{}
},
expErrMsg: "failed to get object: Bucket name contains invalid characters",
expErrCode: codes.Unknown,
expErrMsg: "Bucket name contains invalid characters",
expErrCode: codes.InvalidArgument,
},
{
name: "readObjectErr",
@@ -2216,7 +2192,7 @@ func TestGetObject(t *testing.T) {
in: func(t *testing.T) (*pb.GetObjectRequest, []byte) {
objName := uuid.New().String()
data := []byte{}
var data []byte
for i := 0; i < 8192; i += len(objName) {
data = append(data, []byte(objName)...)
}
@@ -2252,7 +2228,7 @@ func TestGetObject(t *testing.T) {
in: func(t *testing.T) (*pb.GetObjectRequest, []byte) {
objName := uuid.New().String()
data := []byte{}
var data []byte
for i := 0; i < 8192; i += len(objName) {
data = append(data, []byte(objName)...)
}
@@ -2285,7 +2261,7 @@ func TestGetObject(t *testing.T) {
in: func(t *testing.T) (*pb.GetObjectRequest, []byte) {
objName := uuid.New().String()
data := []byte{}
var data []byte
for i := 0; i < 8192; i += len(objName) {
data = append(data, []byte(objName)...)
}
@@ -2320,7 +2296,7 @@ func TestGetObject(t *testing.T) {
in: func(t *testing.T) (*pb.GetObjectRequest, []byte) {
objName := uuid.New().String()
data := []byte{}
var data []byte
for i := 0; i < 102_400; i += len(objName) {
data = append(data, []byte(objName)...)
}
@@ -2352,7 +2328,7 @@ func TestGetObject(t *testing.T) {
in: func(t *testing.T) (*pb.GetObjectRequest, []byte) {
objName := uuid.New().String()
data := []byte{}
var data []byte
for i := 0; i < 10_420; i += len(objName) {
data = append(data, []byte(objName)...)
}
@@ -2550,7 +2526,7 @@ func TestPutObject(t *testing.T) {
if err != nil {
return nil, err
}
file.Close()
_ = file.Close()
return []byte{}, nil
},
err: "file is empty",
@@ -2604,7 +2580,9 @@ func TestPutObject(t *testing.T) {
if err != nil {
return nil, err
}
defer file.Close()
defer func() {
_ = file.Close()
}()
data := []byte("test file data")
@@ -2640,7 +2618,9 @@ func TestPutObject(t *testing.T) {
if err != nil {
return nil, err
}
defer file.Close()
defer func() {
_ = file.Close()
}()
data := []byte("test file data for bucket with prefix")
@@ -2675,7 +2655,9 @@ func TestPutObject(t *testing.T) {
if err != nil {
return nil, err
}
defer file.Close()
defer func() {
_ = file.Close()
}()
data := make([]byte, 30e6)
n, err := rand.Reader.Read(data)
@@ -2732,7 +2714,9 @@ func TestPutObject(t *testing.T) {
require.NoError(err)
obj, err := cl.GetObject(ctx, bucketName, path.Join(tt.req.Bucket.GetBucketPrefix(), tt.req.GetKey()), minio.GetObjectOptions{})
require.NoError(err)
defer obj.Close()
defer func() {
_ = obj.Close()
}()
fmt.Printf("path: %v\n", tt.req.Path)
contentLen := len(content)
@@ -3062,13 +3046,13 @@ func TestDryRun(t *testing.T) {
{
name: "putObjectFail",
minioClFn: func(t *testing.T, s *internaltest.MinioServer) *minio.Client {
creds, err := s.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
creds, err := s.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{
Policy: policyDenyPutObject,
})
require.NoError(t, err)
cl, err := minio.New(s.ApiAddr, &minio.Options{
Creds: credentials.NewStaticV4(creds.AccessKey, creds.SecretKey, ""),
Creds: credentials.NewStaticV4(creds.AccessKeyId, creds.SecretAccessKey, ""),
Secure: false,
})
require.NoError(t, err)
@@ -3088,7 +3072,7 @@ func TestDryRun(t *testing.T) {
},
expPermissions: &pb.Permissions{
Write: &pb.Permission{State: pb.StateType_STATE_TYPE_ERROR, ErrorDetails: "Access Denied."},
// The write permission is denied, so the any read operation will return with a no such key error.
// The write permission is denied, so any read operation will return with a no such key error.
Read: &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, ErrorDetails: "The specified key does not exist."},
Delete: &pb.Permission{State: pb.StateType_STATE_TYPE_OK},
},
@@ -3096,13 +3080,13 @@ func TestDryRun(t *testing.T) {
{
name: "statObjectFail",
minioClFn: func(t *testing.T, s *internaltest.MinioServer) *minio.Client {
creds, err := s.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
creds, err := s.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{
Policy: policyDenyStatObject,
})
require.NoError(t, err)
cl, err := minio.New(s.ApiAddr, &minio.Options{
Creds: credentials.NewStaticV4(creds.AccessKey, creds.SecretKey, ""),
Creds: credentials.NewStaticV4(creds.AccessKeyId, creds.SecretAccessKey, ""),
Secure: false,
})
require.NoError(t, err)
@@ -3129,13 +3113,13 @@ func TestDryRun(t *testing.T) {
{
name: "listObjectsFail",
minioClFn: func(t *testing.T, s *internaltest.MinioServer) *minio.Client {
creds, err := s.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
creds, err := s.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{
Policy: policyDenyListBucket,
})
require.NoError(t, err)
cl, err := minio.New(s.ApiAddr, &minio.Options{
Creds: credentials.NewStaticV4(creds.AccessKey, creds.SecretKey, ""),
Creds: credentials.NewStaticV4(creds.AccessKeyId, creds.SecretAccessKey, ""),
Secure: false,
})
require.NoError(t, err)
@@ -3162,13 +3146,13 @@ func TestDryRun(t *testing.T) {
{
name: "removeObjectFail",
minioClFn: func(t *testing.T, s *internaltest.MinioServer) *minio.Client {
creds, err := s.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
creds, err := s.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{
Policy: policyDenyDeleteObject,
})
require.NoError(t, err)
cl, err := minio.New(s.ApiAddr, &minio.Options{
Creds: credentials.NewStaticV4(creds.AccessKey, creds.SecretKey, ""),
Creds: credentials.NewStaticV4(creds.AccessKeyId, creds.SecretAccessKey, ""),
Secure: false,
})
require.NoError(t, err)
@@ -3314,3 +3298,227 @@ func TestEnsureServiceAccount(t *testing.T) {
})
}
}
func TestErrorToStatus(t *testing.T) {
t.Run("nilErr", func(t *testing.T) {
st := errorToStatus(codes.Internal, "failed to do the thing", nil, nil)
require.NotNil(t, st)
require.Contains(t, st.String(), "failed to do the thing: no error from storage service provider")
require.Equal(t, codes.Internal, st.Code())
})
t.Run("requestTimeout", func(t *testing.T) {
st := errorToStatus(codes.Internal, "failed to do the thing", minio.ErrorResponse{Code: "RequestTimeout"}, nil)
require.NotNil(t, st)
require.Contains(t, st.String(), "failed to do the thing: timeout exceeded when communicating with storage service provider")
require.Equal(t, codes.DeadlineExceeded, st.Code())
})
t.Run("slowDown", func(t *testing.T) {
st := errorToStatus(codes.Internal, "failed to do the thing", minio.ErrorResponse{Code: "SlowDown"}, nil)
require.NotNil(t, st)
require.Contains(t, st.String(), "failed to do the thing: received a throttling response from storage service provider")
require.Equal(t, codes.Unavailable, st.Code())
})
t.Run("badDigest", func(t *testing.T) {
st := errorToStatus(codes.Internal, "failed to do the thing", minio.ErrorResponse{Code: "BadDigest"}, nil)
require.NotNil(t, st)
require.Contains(t, st.String(), "failed to do the thing: storage checksum mismatch")
require.Equal(t, codes.Aborted, st.Code())
})
t.Run("noSuchBucket", func(t *testing.T) {
st := errorToStatus(codes.Internal, "failed to do the thing", minio.ErrorResponse{Code: "NoSuchBucket"}, nil)
require.NotNil(t, st)
require.Contains(t, st.String(), "failed to do the thing: specified storage bucket does not exist")
require.Equal(t, codes.NotFound, st.Code())
})
t.Run("noSuchKey", func(t *testing.T) {
st := errorToStatus(codes.Internal, "failed to do the thing", minio.ErrorResponse{Code: "NoSuchKey"}, nil)
require.NotNil(t, st)
require.Contains(t, st.String(), "failed to do the thing: specified object does not exist")
require.Equal(t, codes.NotFound, st.Code())
})
t.Run("statusCode400", func(t *testing.T) {
st := errorToStatus(codes.Internal, "failed to do the thing", minio.ErrorResponse{StatusCode: 400, Message: "oops there was an error doing the thing"}, nil)
require.NotNil(t, st)
require.Contains(t, st.String(), "failed to do the thing: received a bad request response from storage service provider: oops there was an error doing the thing")
require.Equal(t, codes.InvalidArgument, st.Code())
})
t.Run("permissionsErr", func(t *testing.T) {
tests := []struct {
name string
code codes.Code
errMsg string
err error
reqType any
expStatusContains string
statusDetailsCheckFn func(t *testing.T, p *pb.Permissions)
}{
{
name: "headObject",
code: codes.Internal,
errMsg: "head object failed",
err: minio.ErrorResponse{StatusCode: 401, Code: "AccessDenied", Message: "object access denied"},
reqType: pb.HeadObjectRequest{},
expStatusContains: "head object failed: object access denied",
statusDetailsCheckFn: func(t *testing.T, in *pb.Permissions) {
require.Empty(t, cmp.Diff(
in.GetRead(),
&pb.Permission{State: pb.StateType_STATE_TYPE_ERROR, ErrorDetails: "object access denied"},
protocmp.Transform(),
protocmp.IgnoreFields(in.GetRead(), "checked_at"),
))
},
},
{
name: "headObjectReqPtr",
code: codes.Internal,
errMsg: "head object failed",
err: minio.ErrorResponse{StatusCode: 401, Code: "AccessDenied", Message: "object access denied"},
reqType: &pb.HeadObjectRequest{},
expStatusContains: "head object failed: object access denied",
statusDetailsCheckFn: func(t *testing.T, in *pb.Permissions) {
require.Empty(t, cmp.Diff(
in.GetRead(),
&pb.Permission{State: pb.StateType_STATE_TYPE_ERROR, ErrorDetails: "object access denied"},
protocmp.Transform(),
protocmp.IgnoreFields(in.GetRead(), "checked_at"),
))
},
},
{
name: "getObject",
code: codes.Internal,
errMsg: "get object failed",
err: minio.ErrorResponse{StatusCode: 401, Code: "AccessDenied", Message: "object access denied"},
reqType: pb.GetObjectRequest{},
expStatusContains: "get object failed: object access denied",
statusDetailsCheckFn: func(t *testing.T, in *pb.Permissions) {
require.Empty(t, cmp.Diff(
in.GetRead(),
&pb.Permission{State: pb.StateType_STATE_TYPE_ERROR, ErrorDetails: "object access denied"},
protocmp.Transform(),
protocmp.IgnoreFields(in.GetRead(), "checked_at"),
))
},
},
{
name: "getObjectReqPtr",
code: codes.Internal,
errMsg: "get object failed",
err: minio.ErrorResponse{StatusCode: 401, Code: "AccessDenied", Message: "object access denied"},
reqType: &pb.GetObjectRequest{},
expStatusContains: "get object failed: object access denied",
statusDetailsCheckFn: func(t *testing.T, in *pb.Permissions) {
require.Empty(t, cmp.Diff(
in.GetRead(),
&pb.Permission{State: pb.StateType_STATE_TYPE_ERROR, ErrorDetails: "object access denied"},
protocmp.Transform(),
protocmp.IgnoreFields(in.GetRead(), "checked_at"),
))
},
},
{
name: "putObject",
code: codes.Internal,
errMsg: "put object failed",
err: minio.ErrorResponse{StatusCode: 401, Code: "AccessDenied", Message: "object access denied"},
reqType: pb.PutObjectRequest{},
expStatusContains: "put object failed: object access denied",
statusDetailsCheckFn: func(t *testing.T, in *pb.Permissions) {
require.Empty(t, cmp.Diff(
in.GetWrite(),
&pb.Permission{State: pb.StateType_STATE_TYPE_ERROR, ErrorDetails: "object access denied"},
protocmp.Transform(),
protocmp.IgnoreFields(in.GetRead(), "checked_at"),
))
},
},
{
name: "putObjectReqPtr",
code: codes.Internal,
errMsg: "put object failed",
err: minio.ErrorResponse{StatusCode: 401, Code: "AccessDenied", Message: "object access denied"},
reqType: &pb.PutObjectRequest{},
expStatusContains: "put object failed: object access denied",
statusDetailsCheckFn: func(t *testing.T, in *pb.Permissions) {
require.Empty(t, cmp.Diff(
in.GetWrite(),
&pb.Permission{State: pb.StateType_STATE_TYPE_ERROR, ErrorDetails: "object access denied"},
protocmp.Transform(),
protocmp.IgnoreFields(in.GetRead(), "checked_at"),
))
},
},
{
name: "deleteObjects",
code: codes.Internal,
errMsg: "delete objects failed",
err: minio.ErrorResponse{StatusCode: 401, Code: "AccessDenied", Message: "object access denied"},
reqType: pb.DeleteObjectsRequest{},
expStatusContains: "delete objects failed: object access denied",
statusDetailsCheckFn: func(t *testing.T, in *pb.Permissions) {
require.Empty(t, cmp.Diff(
in.GetDelete(),
&pb.Permission{State: pb.StateType_STATE_TYPE_ERROR, ErrorDetails: "object access denied"},
protocmp.Transform(),
protocmp.IgnoreFields(in.GetRead(), "checked_at"),
))
},
},
{
name: "deleteObjectsReqPtr",
code: codes.Internal,
errMsg: "delete objects failed",
err: minio.ErrorResponse{StatusCode: 401, Code: "AccessDenied", Message: "object access denied"},
reqType: &pb.DeleteObjectsRequest{},
expStatusContains: "delete objects failed: object access denied",
statusDetailsCheckFn: func(t *testing.T, in *pb.Permissions) {
require.Empty(t, cmp.Diff(
in.GetDelete(),
&pb.Permission{State: pb.StateType_STATE_TYPE_ERROR, ErrorDetails: "object access denied"},
protocmp.Transform(),
protocmp.IgnoreFields(in.GetRead(), "checked_at"),
))
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
st := errorToStatus(tt.code, tt.errMsg, tt.err, tt.reqType)
require.NotNil(t, st)
require.Contains(t, st.String(), tt.expStatusContains)
require.Equal(t, codes.Internal, tt.code)
require.Len(t, st.Details(), 1)
require.IsType(t, st.Details()[0], &pb.StorageBucketCredentialState{})
state := st.Details()[0].(*pb.StorageBucketCredentialState)
require.NotNil(t, state.GetState())
if tt.statusDetailsCheckFn != nil {
tt.statusDetailsCheckFn(t, state.GetState())
}
})
}
})
t.Run("unknownErr", func(t *testing.T) {
st := errorToStatus(codes.Internal, "failed to do the thing", fmt.Errorf("oops the thing failed but it's just a normal go error"), nil)
require.NotNil(t, st)
require.Contains(t, st.String(), "failed to do the thing: oops the thing failed but it's just a normal go error")
require.Equal(t, codes.Internal, st.Code())
require.Len(t, st.Details(), 1)
require.Empty(t, cmp.Diff(
&pb.StorageBucketCredentialState{State: &pb.Permissions{}},
st.Details()[0],
protocmp.Transform(),
))
})
}
+8 -8
View File
@@ -10,7 +10,7 @@ import (
"sync"
"time"
"github.com/minio/madmin-go/v3"
"github.com/hashicorp/boundary-plugin-minio/internal/client"
)
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)
}
ac, err := newMadminClient(sa, inSec)
ac, err := client.New(sa.EndpointUrl, inSec.AccessKeyId, inSec.SecretAccessKey, client.WithUseSsl(sa.UseSSL))
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))
newCreds, err := ac.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
newCreds, err := ac.AddServiceAccount(ctx, client.AddServiceAccountReq{
Name: "Boundary MinIO Service Account",
Description: fmt.Sprintf("Boundary-managed MinIO service account for storage bucket '%s'", bucketName),
Policy: policy,
@@ -71,16 +71,16 @@ func rotateCredentials(ctx context.Context, bucketName string, sa *StorageAttrib
}
newSec := &StorageSecrets{
AccessKeyId: newCreds.AccessKey,
SecretAccessKey: newCreds.SecretKey,
AccessKeyId: newCreds.AccessKeyId,
SecretAccessKey: newCreds.SecretAccessKey,
LastRotatedTime: time.Now(),
}
inCl := inSec.Clone()
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 {
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)
if err != nil {
+13 -13
View File
@@ -8,8 +8,8 @@ import (
"encoding/json"
"testing"
"github.com/hashicorp/boundary-plugin-minio/internal/client"
internaltest "github.com/hashicorp/boundary-plugin-minio/internal/testing"
"github.com/minio/madmin-go/v3"
"github.com/minio/minio-go/v7"
"github.com/stretchr/testify/require"
)
@@ -39,7 +39,7 @@ func TestRotateCredentials(t *testing.T) {
{
name: "addServiceAccountFail",
inSec: func() *StorageSecrets {
creds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
creds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{
Policy: json.RawMessage(`
{
"Statement": [
@@ -58,8 +58,8 @@ func TestRotateCredentials(t *testing.T) {
require.NoError(t, err)
return &StorageSecrets{
AccessKeyId: creds.AccessKey,
SecretAccessKey: creds.SecretKey,
AccessKeyId: creds.AccessKeyId,
SecretAccessKey: creds.SecretAccessKey,
}
}(),
expErrMsg: "failed to create new minio service account: Access Denied",
@@ -67,12 +67,12 @@ func TestRotateCredentials(t *testing.T) {
{
name: "credentialDeletionFail",
inSec: func() *StorageSecrets {
creds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{})
creds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{})
require.NoError(t, err)
return &StorageSecrets{
AccessKeyId: creds.AccessKey,
SecretAccessKey: creds.SecretKey,
AccessKeyId: creds.AccessKeyId,
SecretAccessKey: creds.SecretAccessKey,
}
}(),
credDelErr: true,
@@ -80,12 +80,12 @@ func TestRotateCredentials(t *testing.T) {
{
name: "success",
inSec: func() *StorageSecrets {
creds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{})
creds, err := server.AdminClient.AddServiceAccount(ctx, client.AddServiceAccountReq{})
require.NoError(t, err)
return &StorageSecrets{
AccessKeyId: creds.AccessKey,
SecretAccessKey: creds.SecretKey,
AccessKeyId: creds.AccessKeyId,
SecretAccessKey: creds.SecretAccessKey,
}
}(),
},
@@ -117,7 +117,7 @@ func TestRotateCredentials(t *testing.T) {
// called. We then make the delete call and finally assert that the
// credential was removed if there was no error when attempting to
// delete.
_, err = server.AdminClient.InfoServiceAccount(ctx, tt.inSec.AccessKeyId)
err = server.AdminClient.EnsureServiceAccount(ctx, tt.inSec.AccessKeyId)
require.NoError(t, err)
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") // 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)
return
} 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).
}
_, 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")
})
}