From 8a74b98155ca8394096881829eae7ecba04b34ad Mon Sep 17 00:00:00 2001 From: Louis Ruch Date: Thu, 9 Jan 2025 15:36:42 -0800 Subject: [PATCH] chore: update usage of minio admin library --- .gitignore | 102 ++++++++++ LICENSE | 10 + LICENSE-APACHE-2.0 | 202 +++++++++++++++++++ NOTICE | 2 + go.mod | 58 ++---- go.sum | 145 +++----------- internal/client/client.go | 249 ++++++++++++++++++++++++ internal/client/client_test.go | 82 ++++++++ internal/client/service_test.go | 39 ++++ internal/testing/testing.go | 21 +- madmin/LICENSE | 202 +++++++++++++++++++ madmin/api-error-response.go | 63 ++++++ madmin/encrypt.go | 159 +++++++++++++++ madmin/encrypt_test.go | 111 +++++++++++ madmin/fips.go | 32 +++ madmin/go.mod | 11 ++ madmin/go.sum | 15 ++ madmin/no_fips.go | 32 +++ madmin/utils.go | 88 +++++++++ plugin/service/storage/plugin.go | 30 +-- plugin/service/storage/plugin_test.go | 244 +++++++++++------------ plugin/service/storage/rotation.go | 16 +- plugin/service/storage/rotation_test.go | 26 +-- 23 files changed, 1605 insertions(+), 334 deletions(-) create mode 100644 .gitignore create mode 100644 LICENSE-APACHE-2.0 create mode 100644 NOTICE create mode 100644 internal/client/client.go create mode 100644 internal/client/client_test.go create mode 100644 internal/client/service_test.go create mode 100644 madmin/LICENSE create mode 100644 madmin/api-error-response.go create mode 100644 madmin/encrypt.go create mode 100644 madmin/encrypt_test.go create mode 100644 madmin/fips.go create mode 100644 madmin/go.mod create mode 100644 madmin/go.sum create mode 100644 madmin/no_fips.go create mode 100644 madmin/utils.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..32ba98c --- /dev/null +++ b/.gitignore @@ -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] diff --git a/LICENSE b/LICENSE index 8bd3d1c..fc5642b 100644 --- a/LICENSE +++ b/LICENSE @@ -373,3 +373,13 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. + +Additional Licensing Information +-------------------------------- + + This project includes portions of code from third-party libraries, + which are licensed under their own terms: + + * Code from [madmin-go] (https://github.com/minio/madmin-go/tree/v1.7.5) is + licensed under the Apache License 2.0. See `LICENSE-APACHE-2.0` for the full + license text. diff --git a/LICENSE-APACHE-2.0 b/LICENSE-APACHE-2.0 new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE-APACHE-2.0 @@ -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. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..add70c6 --- /dev/null +++ b/NOTICE @@ -0,0 +1,2 @@ +This product includes code from 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. diff --git a/go.mod b/go.mod index 108fea7..26607d2 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,18 @@ module github.com/hashicorp/boundary-plugin-minio -go 1.22.2 +go 1.23.4 + +replace github.com/hashicorp/boundary-plugin-minio/madmin => ./madmin require ( github.com/google/go-cmp v0.6.0 - github.com/google/uuid v1.5.0 + github.com/google/uuid v1.6.0 + github.com/hashicorp/boundary-plugin-minio/madmin v0.0.0-00010101000000-000000000000 github.com/hashicorp/boundary/sdk v0.0.43-0.20240717182311-a20aae98794a 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/minio/minio-go/v7 v7.0.83 github.com/ory/dockertest/v3 v3.10.0 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 google.golang.org/grpc v1.61.0 google.golang.org/protobuf v1.33.0 ) @@ -27,9 +29,9 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.4.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.1 // 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 @@ -38,55 +40,37 @@ require ( 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/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/opencontainers/runc v1.1.14 // indirect - github.com/philhofer/fwd v1.1.2 // 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/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/mod v0.17.0 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // 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 ) diff --git a/go.sum b/go.sum index 71657f3..7e5b156 100644 --- a/go.sum +++ b/go.sum @@ -36,17 +36,16 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp 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-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.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 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.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= -github.com/golang-jwt/jwt/v4 v4.5.1/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= @@ -54,15 +53,12 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw 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/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/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/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -115,17 +111,16 @@ 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= @@ -134,23 +129,14 @@ 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/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= @@ -166,11 +152,6 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx 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/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= @@ -181,8 +162,6 @@ github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2 github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA= 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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -191,34 +170,15 @@ 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= @@ -227,21 +187,11 @@ 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/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 h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= @@ -250,9 +200,6 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 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,66 +207,40 @@ 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/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-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-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= @@ -329,10 +250,8 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK 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/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 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,8 +274,6 @@ 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= diff --git a/internal/client/client.go b/internal/client/client.go new file mode 100644 index 0000000..2f0e25e --- /dev/null +++ b/internal/client/client.go @@ -0,0 +1,249 @@ +// 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" + +// client is used to access Minio admin API +type Client struct { + endpointUrl *url.URL + accessKeyId string + secretAccessKey string +} + +// 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 +} + +// 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 + } +} + +// 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 +} + +// 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 { + Creds Credential `json:"credentials"` +} + +// Credential contains a minio AccessKeyId and SecretAccessKey +type Credential struct { + AccessKeyId string `json:"accessKey,omitempty"` + SecretAccessKey string `json:"secretKey,omitempty"` +} + +// 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 + } + + 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) + creds := &AddServiceAccountResp{} + if err := json.Unmarshal(result, &creds); err != nil { + return Credential{}, fmt.Errorf("failed to parse response: %w", err) + } + + return creds.Creds, 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 +} + +// MinioError contains the error message from failed admin API requests +type MinioError struct { + Message string `json:"message,omitempty"` +} diff --git a/internal/client/client_test.go b/internal/client/client_test.go new file mode 100644 index 0000000..c82802c --- /dev/null +++ b/internal/client/client_test.go @@ -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) + }) +} diff --git a/internal/client/service_test.go b/internal/client/service_test.go new file mode 100644 index 0000000..2bdb434 --- /dev/null +++ b/internal/client/service_test.go @@ -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) +} diff --git a/internal/testing/testing.go b/internal/testing/testing.go index 8af0118..7cda34c 100644 --- a/internal/testing/testing.go +++ b/internal/testing/testing.go @@ -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) diff --git a/madmin/LICENSE b/madmin/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/madmin/LICENSE @@ -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. diff --git a/madmin/api-error-response.go b/madmin/api-error-response.go new file mode 100644 index 0000000..7750008 --- /dev/null +++ b/madmin/api-error-response.go @@ -0,0 +1,63 @@ +// Copyright (c) 2025 HashiCorp, 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. +// +// Modifications made by Hashicorp in 2025 to remove unused functions and structs. + +package madmin + +import ( + "encoding/xml" +) + +/* **** SAMPLE ERROR RESPONSE **** + + + AccessDenied + Access Denied + bucketName + objectName + F19772218238A85A + GuWkjyviSiGHizehqpmsD1ndz5NClSP19DOT+s2mv7gXGQ8/X1lhbDGiIJEXpGFD + +*/ + +// 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 +} + +// ErrInvalidArgument - Invalid argument response. +func ErrInvalidArgument(message string) error { + return ErrorResponse{ + Code: "InvalidArgument", + Message: message, + RequestID: "minio", + } +} diff --git a/madmin/encrypt.go b/madmin/encrypt.go new file mode 100644 index 0000000..ccbe7cc --- /dev/null +++ b/madmin/encrypt.go @@ -0,0 +1,159 @@ +// Copyright (c) HashiCorp, 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. +// + +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 + ) + 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 +) diff --git a/madmin/encrypt_test.go b/madmin/encrypt_test.go new file mode 100644 index 0000000..8aea7cd --- /dev/null +++ b/madmin/encrypt_test.go @@ -0,0 +1,111 @@ +// Copyright (c) 2025 HashiCorp, 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. +// + +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") + } + }) + } +} diff --git a/madmin/fips.go b/madmin/fips.go new file mode 100644 index 0000000..6a51003 --- /dev/null +++ b/madmin/fips.go @@ -0,0 +1,32 @@ +// Copyright (c) 2025 HashiCorp, Inc. +// 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 true } diff --git a/madmin/go.mod b/madmin/go.mod new file mode 100644 index 0000000..b073981 --- /dev/null +++ b/madmin/go.mod @@ -0,0 +1,11 @@ +module github.com/hashicorp/boundary-plugin-minio/madmin + +go 1.23.4 + +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 diff --git a/madmin/go.sum b/madmin/go.sum new file mode 100644 index 0000000..6ee6d84 --- /dev/null +++ b/madmin/go.sum @@ -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= diff --git a/madmin/no_fips.go b/madmin/no_fips.go new file mode 100644 index 0000000..74f4171 --- /dev/null +++ b/madmin/no_fips.go @@ -0,0 +1,32 @@ +// Copyright (c) 2025 HashiCorp, Inc. +// 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 } diff --git a/madmin/utils.go b/madmin/utils.go new file mode 100644 index 0000000..d21b5db --- /dev/null +++ b/madmin/utils.go @@ -0,0 +1,88 @@ +// Copyright (c) 2025 HashiCorp, 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. +// + +package madmin + +import ( + "net" + "net/url" + "strings" + + "github.com/minio/minio-go/v7/pkg/s3utils" +) + +// GetEndpointURL - construct a new endpoint. +// Modifications made by Hashicorp in 2025 to export the function. +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 +} diff --git a/plugin/service/storage/plugin.go b/plugin/service/storage/plugin.go index 1d1145d..a416814 100644 --- a/plugin/service/storage/plugin.go +++ b/plugin/service/storage/plugin.go @@ -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) @@ -756,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) } diff --git a/plugin/service/storage/plugin_test.go b/plugin/service/storage/plugin_test.go index 61e2958..6ae4c3b 100644 --- a/plugin/service/storage/plugin_test.go +++ b/plugin/service/storage/plugin_test.go @@ -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) - require.NoError(t, err) - - lsa, err := server.AdminClient.ListServiceAccounts(ctx, server.RootUsername) + err = server.AdminClient.EnsureServiceAccount(ctx, outAccessKeyId) 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) - require.NoError(t, err) - - lsa, err := server.AdminClient.ListServiceAccounts(ctx, server.RootUsername) + err = server.AdminClient.EnsureServiceAccount(ctx, outAccessKeyId) 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) - require.NoError(t, err) - - lsa, err := server.AdminClient.ListServiceAccounts(ctx, server.RootUsername) + err = server.AdminClient.EnsureServiceAccount(ctx, persistedCreds.AccessKeyId) 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) diff --git a/plugin/service/storage/rotation.go b/plugin/service/storage/rotation.go index d957fa9..acbd8d6 100644 --- a/plugin/service/storage/rotation.go +++ b/plugin/service/storage/rotation.go @@ -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 { diff --git a/plugin/service/storage/rotation_test.go b/plugin/service/storage/rotation_test.go index 4cd1888..8cc928f 100644 --- a/plugin/service/storage/rotation_test.go +++ b/plugin/service/storage/rotation_test.go @@ -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") }) }