Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d2654e707 | ||
|
|
759ac083ac | ||
|
|
dad21afc00 | ||
|
|
589e435ff1 | ||
|
|
caa6c4eda2 | ||
|
|
9299172dd1 | ||
|
|
07cb85645b | ||
|
|
a0067502b9 | ||
|
|
bde78ecccd | ||
|
|
eb0c2b4682 | ||
|
|
0214399017 | ||
|
|
2c2bd3429b | ||
|
|
09cece64bc | ||
|
|
4ffe4d227f | ||
|
|
b335b9336b |
No files matched your search
@@ -1,17 +1,18 @@
|
|||||||
module github.com/hashicorp/boundary-plugin-minio
|
module github.com/hashicorp/boundary-plugin-minio
|
||||||
|
|
||||||
go 1.21.5
|
go 1.22.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/go-cmp v0.6.0
|
github.com/google/go-cmp v0.6.0
|
||||||
github.com/google/uuid v1.5.0
|
github.com/google/uuid v1.5.0
|
||||||
github.com/hashicorp/boundary/sdk v0.0.42
|
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/madmin-go/v3 v3.0.48
|
||||||
github.com/minio/minio-go/v7 v7.0.67
|
github.com/minio/minio-go/v7 v7.0.67
|
||||||
github.com/ory/dockertest/v3 v3.10.0
|
github.com/ory/dockertest/v3 v3.10.0
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
google.golang.org/grpc v1.59.0
|
google.golang.org/grpc v1.61.0
|
||||||
google.golang.org/protobuf v1.32.0
|
google.golang.org/protobuf v1.33.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -22,7 +23,7 @@ require (
|
|||||||
github.com/containerd/continuity v0.3.0 // indirect
|
github.com/containerd/continuity v0.3.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/docker/cli v20.10.17+incompatible // indirect
|
github.com/docker/cli v20.10.17+incompatible // indirect
|
||||||
github.com/docker/docker v20.10.7+incompatible // indirect
|
github.com/docker/docker v24.0.9+incompatible // indirect
|
||||||
github.com/docker/go-connections v0.4.0 // indirect
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
github.com/docker/go-units v0.4.0 // indirect
|
github.com/docker/go-units v0.4.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
@@ -35,7 +36,6 @@ require (
|
|||||||
github.com/hashicorp/eventlogger v0.2.6-0.20231025104552-802587e608f0 // indirect
|
github.com/hashicorp/eventlogger v0.2.6-0.20231025104552-802587e608f0 // indirect
|
||||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 // indirect
|
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 // indirect
|
||||||
github.com/hashicorp/go-kms-wrapping/v2 v2.0.14 // indirect
|
github.com/hashicorp/go-kms-wrapping/v2 v2.0.14 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
|
||||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||||
github.com/imdario/mergo v0.3.13 // indirect
|
github.com/imdario/mergo v0.3.13 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
@@ -54,7 +54,7 @@ require (
|
|||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||||
github.com/opencontainers/runc v1.1.5 // indirect
|
github.com/opencontainers/runc v1.1.12 // indirect
|
||||||
github.com/philhofer/fwd v1.1.2 // indirect
|
github.com/philhofer/fwd v1.1.2 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
@@ -76,14 +76,16 @@ require (
|
|||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||||
golang.org/x/crypto v0.17.0 // indirect
|
golang.org/x/crypto v0.22.0 // indirect
|
||||||
golang.org/x/mod v0.9.0 // indirect
|
golang.org/x/mod v0.9.0 // indirect
|
||||||
golang.org/x/net v0.19.0 // indirect
|
golang.org/x/net v0.24.0 // indirect
|
||||||
golang.org/x/sync v0.3.0 // indirect
|
golang.org/x/sync v0.5.0 // indirect
|
||||||
golang.org/x/sys v0.15.0 // indirect
|
golang.org/x/sys v0.19.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
golang.org/x/tools v0.7.0 // indirect
|
golang.org/x/tools v0.7.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // 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/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||||
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
|
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
|
||||||
@@ -17,24 +16,18 @@ github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQ
|
|||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
|
github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
|
||||||
github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||||
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
|
||||||
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
|
||||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
|
||||||
github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg=
|
github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg=
|
||||||
github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM=
|
github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM=
|
||||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
|
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
|
||||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M=
|
github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M=
|
||||||
github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/docker v20.10.7+incompatible h1:Z6O9Nhsjv+ayUEeI1IojKbYcsGdgYSNqxe1s2MYzUhQ=
|
github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0=
|
||||||
github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||||
@@ -43,15 +36,12 @@ 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/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 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
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-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
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 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
|
||||||
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
|
||||||
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||||
@@ -73,8 +63,8 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU
|
|||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hashicorp/boundary/sdk v0.0.42 h1:iRDdnM6/6q9qLtBP7/cpq/FWQGf69TFSsBQFxgGK8Jw=
|
github.com/hashicorp/boundary/sdk v0.0.43-0.20240717182311-a20aae98794a h1:SwVze6sYE5o+J9qdcgj29auY1k6O8oSo9DC2+2Gb9rw=
|
||||||
github.com/hashicorp/boundary/sdk v0.0.42/go.mod h1:QJhm4rky2YMtk9DS5Rj8QJhDKB/6zQbVrm3R0QgdYmQ=
|
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=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
@@ -136,9 +126,8 @@ github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/4
|
|||||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
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.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.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
@@ -175,7 +164,6 @@ github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQh
|
|||||||
github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
|
github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
|
|
||||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
|
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/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-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@@ -183,17 +171,14 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
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 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
|
||||||
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
|
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
|
||||||
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
|
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
|
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
|
||||||
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||||
github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs=
|
github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss=
|
||||||
github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg=
|
github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8=
|
||||||
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
|
||||||
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
|
|
||||||
github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
|
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/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 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
|
||||||
@@ -222,12 +207,10 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR
|
|||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
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 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
||||||
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
||||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||||
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
|
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
|
||||||
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
|
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
|
||||||
github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
|
||||||
github.com/secure-io/sio-go v0.3.1 h1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc=
|
github.com/secure-io/sio-go v0.3.1 h1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc=
|
||||||
github.com/secure-io/sio-go v0.3.1/go.mod h1:+xbkjDzPjwh4Axd07pRKSNriS9SCiYksWnZqdnfpQxs=
|
github.com/secure-io/sio-go v0.3.1/go.mod h1:+xbkjDzPjwh4Axd07pRKSNriS9SCiYksWnZqdnfpQxs=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
|
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
|
||||||
@@ -238,8 +221,6 @@ 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/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
|
||||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||||
@@ -248,7 +229,6 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
|||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
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.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
@@ -256,16 +236,12 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
|
||||||
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
|
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/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 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
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 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
|
||||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
|
||||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||||
@@ -285,8 +261,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-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/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.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
@@ -302,36 +278,28 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
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-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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
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.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.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-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-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-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.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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-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-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-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-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/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-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-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.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -340,8 +308,9 @@ 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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.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.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
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-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.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/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||||
@@ -368,15 +337,18 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo=
|
google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac h1:ZL/Teoy/ZGnzyrqK/Optxxp2pmVh+fmJ97slxSRyzUg=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc=
|
google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k=
|
||||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe h1:0poefMBYvYbs7g5UkjS6HcxBPaTRAmznle9jnxYoAI8=
|
||||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe h1:bQnxqljG/wqi4NTXu2+DJ3n7APcEA882QZ1JvhQAq9o=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
|
||||||
|
google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0=
|
||||||
|
google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package values
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
)
|
)
|
||||||
@@ -37,6 +38,52 @@ func GetStringValue(in *structpb.Struct, k string, required bool) (string, error
|
|||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBoolValue returns a boolean value and no error if the given key is found
|
||||||
|
// in the provided proto struct input. An error is returned if the key is not
|
||||||
|
// found and required is true, or the value type is not a boolean.
|
||||||
|
func GetBoolValue(in *structpb.Struct, k string, required bool) (bool, error) {
|
||||||
|
mv := in.GetFields()
|
||||||
|
v, ok := mv[k]
|
||||||
|
if !ok {
|
||||||
|
if required {
|
||||||
|
return false, fmt.Errorf("missing required value %q", k)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
b, ok := v.AsInterface().(bool)
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("unexpected type for value %q: want bool, got %T", k, v.AsInterface())
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTimeValue returns a time.Time object and no error if the given key is
|
||||||
|
// found in the provided proto struct input. An error is returned if the value
|
||||||
|
// in the struct isn't a string or the value is not parsable by time.Parse. The
|
||||||
|
// expected time layout is RFC3339Nano.
|
||||||
|
func GetTimeValue(in *structpb.Struct, k string) (time.Time, error) {
|
||||||
|
mv := in.GetFields()
|
||||||
|
v, ok := mv[k]
|
||||||
|
if !ok {
|
||||||
|
return time.Time{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tRaw, ok := v.AsInterface().(string)
|
||||||
|
if !ok {
|
||||||
|
return time.Time{}, fmt.Errorf("unexpected type for value %q: want string, got %T", k, v.AsInterface())
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := time.Parse(time.RFC3339Nano, tRaw)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, fmt.Errorf("could not parse time in value %q: %w", k, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
// StructFields returns a map[string]struct{} of the proto struct input.
|
// StructFields returns a map[string]struct{} of the proto struct input.
|
||||||
func StructFields(s *structpb.Struct) map[string]struct{} {
|
func StructFields(s *structpb.Struct) map[string]struct{} {
|
||||||
m := make(map[string]struct{}, len(s.GetFields()))
|
m := make(map[string]struct{}, len(s.GetFields()))
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package values
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
@@ -78,3 +79,132 @@ func TestGetStringValue(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetBoolValue(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
in map[string]any
|
||||||
|
key string
|
||||||
|
required bool
|
||||||
|
expected bool
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "required missing",
|
||||||
|
in: map[string]any{},
|
||||||
|
key: "foo",
|
||||||
|
required: true,
|
||||||
|
expectedErr: "missing required value \"foo\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "optional missing",
|
||||||
|
in: map[string]any{},
|
||||||
|
key: "foo",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-bool value",
|
||||||
|
in: map[string]any{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
expectedErr: "unexpected type for value \"foo\": want bool, got string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "good",
|
||||||
|
in: map[string]any{
|
||||||
|
"foo": true,
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
input, err := structpb.NewStruct(tc.in)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
actual, err := GetBoolValue(input, tc.key, tc.required)
|
||||||
|
if tc.expectedErr != "" {
|
||||||
|
require.EqualError(err, tc.expectedErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal(tc.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTimeValue(t *testing.T) {
|
||||||
|
staticTime := time.Now()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
in map[string]any
|
||||||
|
key string
|
||||||
|
expected time.Time
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing",
|
||||||
|
in: map[string]any{},
|
||||||
|
key: "foo",
|
||||||
|
expected: time.Time{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-time value",
|
||||||
|
in: map[string]any{
|
||||||
|
"foo": 1,
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
expectedErr: "unexpected type for value \"foo\": want string, got float64",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad parse",
|
||||||
|
in: map[string]any{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
expectedErr: "could not parse time in value \"foo\": parsing time \"bar\" as \"2006-01-02T15:04:05.999999999Z07:00\": cannot parse \"bar\" as \"2006\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "good",
|
||||||
|
in: map[string]any{
|
||||||
|
"foo": staticTime.Format(time.RFC3339Nano),
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
expected: func() time.Time {
|
||||||
|
u, err := time.Parse(time.RFC3339Nano, staticTime.Format(time.RFC3339Nano))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return u
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
input, err := structpb.NewStruct(tc.in)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
actual, err := GetTimeValue(input, tc.key)
|
||||||
|
if tc.expectedErr != "" {
|
||||||
|
require.EqualError(err, tc.expectedErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal(tc.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ boundary storage-buckets create \
|
|||||||
-worker-filter '"minio-access" in "/tags/type"' \
|
-worker-filter '"minio-access" in "/tags/type"' \
|
||||||
-attr endpoint_url="https://my-minio-instance.dev/s3-api" \
|
-attr endpoint_url="https://my-minio-instance.dev/s3-api" \
|
||||||
-attr region=REGION \
|
-attr region=REGION \
|
||||||
|
-attr disable_credential_rotation=true \
|
||||||
-secret access_key_id='KEY' \
|
-secret access_key_id='KEY' \
|
||||||
-secret secret_access_key='SECRET'
|
-secret secret_access_key='SECRET'
|
||||||
```
|
```
|
||||||
@@ -24,6 +25,11 @@ The following `attributes` are valid on a MinIO storage bucket resource:
|
|||||||
- `endpoint_url` (string, required): Fully-qualified endpoint pointing to a
|
- `endpoint_url` (string, required): Fully-qualified endpoint pointing to a
|
||||||
MinIO S3 API.
|
MinIO S3 API.
|
||||||
- `region` (string, optional): The region to configure the storage bucket for.
|
- `region` (string, optional): The region to configure the storage bucket for.
|
||||||
|
- `disable_credential_rotation` (bool, optional): Controls whether the plugin
|
||||||
|
will rotate the incoming credentials and manage a new MinIO service account.
|
||||||
|
If this attribute is set to `false`, or not provided, the plugin will rotate
|
||||||
|
the incoming credentials, using them to create a new MinIO service account,
|
||||||
|
then delete the incoming credentials.
|
||||||
|
|
||||||
### Secrets
|
### Secrets
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package storage
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/boundary-plugin-minio/internal/errors"
|
"github.com/hashicorp/boundary-plugin-minio/internal/errors"
|
||||||
"github.com/hashicorp/boundary-plugin-minio/internal/values"
|
"github.com/hashicorp/boundary-plugin-minio/internal/values"
|
||||||
@@ -15,10 +16,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ConstEndpointUrl = "endpoint_url"
|
ConstEndpointUrl = "endpoint_url"
|
||||||
ConstAccessKeyId = "access_key_id"
|
ConstAccessKeyId = "access_key_id"
|
||||||
ConstSecretAccessKey = "secret_access_key"
|
ConstSecretAccessKey = "secret_access_key"
|
||||||
ConstRegion = "region"
|
ConstRegion = "region"
|
||||||
|
ConstDisableCredentialRotation = "disable_credential_rotation"
|
||||||
|
ConstLastRotatedTime = "creds_last_rotated_time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StorageAttributes struct {
|
type StorageAttributes struct {
|
||||||
@@ -31,6 +34,11 @@ type StorageAttributes struct {
|
|||||||
// UseSSL determines if the MinIO SDK will use SSL when communicating with
|
// UseSSL determines if the MinIO SDK will use SSL when communicating with
|
||||||
// the server. This field is determined based on the endpoint URL prefix.
|
// the server. This field is determined based on the endpoint URL prefix.
|
||||||
UseSSL bool
|
UseSSL bool
|
||||||
|
// DisableCredentialRotation is used to configure whether this plugin should
|
||||||
|
// manage the MinIO credentials or not. By default, this field is set to
|
||||||
|
// false. See the plugin's OnCreateStorageBucket, OnUpdateStorageBucket and
|
||||||
|
// OnDeleteStorageBucket for how we use this.
|
||||||
|
DisableCredentialRotation bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type StorageSecrets struct {
|
type StorageSecrets struct {
|
||||||
@@ -40,6 +48,10 @@ type StorageSecrets struct {
|
|||||||
// SecretAccessKey is the MinIO Secret Access Key. This field is required
|
// SecretAccessKey is the MinIO Secret Access Key. This field is required
|
||||||
// and comes from user input.
|
// and comes from user input.
|
||||||
SecretAccessKey string
|
SecretAccessKey string
|
||||||
|
// LastRotatedTime is the time the secrets contained in this object were
|
||||||
|
// last rotated. Will be set to the zero-value (time.Time{}) if the
|
||||||
|
// credentials haven't been rotated.
|
||||||
|
LastRotatedTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// AsMap returns a map StorageAttributes's secret fields as a map.
|
// AsMap returns a map StorageAttributes's secret fields as a map.
|
||||||
@@ -47,6 +59,17 @@ func (sa *StorageSecrets) AsMap() map[string]any {
|
|||||||
return map[string]any{
|
return map[string]any{
|
||||||
ConstAccessKeyId: sa.AccessKeyId,
|
ConstAccessKeyId: sa.AccessKeyId,
|
||||||
ConstSecretAccessKey: sa.SecretAccessKey,
|
ConstSecretAccessKey: sa.SecretAccessKey,
|
||||||
|
ConstLastRotatedTime: sa.LastRotatedTime.Format(time.RFC3339Nano),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone returns a new StorageSecrets object with the same state as the incoming
|
||||||
|
// one.
|
||||||
|
func (sec *StorageSecrets) Clone() *StorageSecrets {
|
||||||
|
return &StorageSecrets{
|
||||||
|
AccessKeyId: sec.AccessKeyId,
|
||||||
|
SecretAccessKey: sec.SecretAccessKey,
|
||||||
|
LastRotatedTime: sec.LastRotatedTime,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +106,12 @@ func getStorageAttributes(inAttributes *structpb.Struct) (*StorageAttributes, er
|
|||||||
}
|
}
|
||||||
delete(unknownAttrFields, ConstRegion)
|
delete(unknownAttrFields, ConstRegion)
|
||||||
|
|
||||||
|
disableCredentialRotation, err := values.GetBoolValue(inAttributes, ConstDisableCredentialRotation, false)
|
||||||
|
if err != nil {
|
||||||
|
badFields[fmt.Sprintf("attributes.%s", ConstDisableCredentialRotation)] = err.Error()
|
||||||
|
}
|
||||||
|
delete(unknownAttrFields, ConstDisableCredentialRotation)
|
||||||
|
|
||||||
for s := range unknownAttrFields {
|
for s := range unknownAttrFields {
|
||||||
badFields[fmt.Sprintf("attributes.%s", s)] = "unrecognized field"
|
badFields[fmt.Sprintf("attributes.%s", s)] = "unrecognized field"
|
||||||
}
|
}
|
||||||
@@ -91,9 +120,10 @@ func getStorageAttributes(inAttributes *structpb.Struct) (*StorageAttributes, er
|
|||||||
return nil, errors.InvalidArgumentError("invalid or unrecognized attributes found", badFields)
|
return nil, errors.InvalidArgumentError("invalid or unrecognized attributes found", badFields)
|
||||||
}
|
}
|
||||||
return &StorageAttributes{
|
return &StorageAttributes{
|
||||||
EndpointUrl: endpointUrl,
|
EndpointUrl: endpointUrl,
|
||||||
Region: region,
|
Region: region,
|
||||||
UseSSL: useSSL,
|
UseSSL: useSSL,
|
||||||
|
DisableCredentialRotation: disableCredentialRotation,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,6 +148,12 @@ func getStorageSecrets(inSecrets *structpb.Struct) (*StorageSecrets, error) {
|
|||||||
}
|
}
|
||||||
delete(unknownSecretFields, ConstSecretAccessKey)
|
delete(unknownSecretFields, ConstSecretAccessKey)
|
||||||
|
|
||||||
|
lastRotated, err := values.GetTimeValue(inSecrets, ConstLastRotatedTime)
|
||||||
|
if err != nil {
|
||||||
|
badFields[fmt.Sprintf("secrets.%s", ConstLastRotatedTime)] = err.Error()
|
||||||
|
}
|
||||||
|
delete(unknownSecretFields, ConstLastRotatedTime)
|
||||||
|
|
||||||
for s := range unknownSecretFields {
|
for s := range unknownSecretFields {
|
||||||
badFields[fmt.Sprintf("secrets.%s", s)] = "unrecognized field"
|
badFields[fmt.Sprintf("secrets.%s", s)] = "unrecognized field"
|
||||||
}
|
}
|
||||||
@@ -128,5 +164,6 @@ func getStorageSecrets(inSecrets *structpb.Struct) (*StorageSecrets, error) {
|
|||||||
return &StorageSecrets{
|
return &StorageSecrets{
|
||||||
AccessKeyId: accessKeyId,
|
AccessKeyId: accessKeyId,
|
||||||
SecretAccessKey: secretAccessKey,
|
SecretAccessKey: secretAccessKey,
|
||||||
|
LastRotatedTime: lastRotated,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ package storage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
@@ -89,18 +90,30 @@ func TestGetStorageAttributes(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expErrMsg: "attributes.endpoint_url: missing required value \"endpoint_url\"",
|
expErrMsg: "attributes.endpoint_url: missing required value \"endpoint_url\"",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "disableCredRotationBadType",
|
||||||
|
inAttributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstEndpointUrl: structpb.NewStringValue("http://bar"),
|
||||||
|
ConstDisableCredentialRotation: structpb.NewStringValue("notbool"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expErrMsg: "attributes.disable_credential_rotation: unexpected type for value \"disable_credential_rotation\": want bool, got string",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "successWithNoSSL",
|
name: "successWithNoSSL",
|
||||||
inAttributes: &structpb.Struct{
|
inAttributes: &structpb.Struct{
|
||||||
Fields: map[string]*structpb.Value{
|
Fields: map[string]*structpb.Value{
|
||||||
ConstEndpointUrl: structpb.NewStringValue("http://foo.bar"),
|
ConstEndpointUrl: structpb.NewStringValue("http://foo.bar"),
|
||||||
ConstRegion: structpb.NewStringValue("us-east-1"),
|
ConstRegion: structpb.NewStringValue("us-east-1"),
|
||||||
|
ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expStorageAttributes: &StorageAttributes{
|
expStorageAttributes: &StorageAttributes{
|
||||||
EndpointUrl: "foo.bar",
|
EndpointUrl: "foo.bar",
|
||||||
Region: "us-east-1",
|
Region: "us-east-1",
|
||||||
UseSSL: false,
|
UseSSL: false,
|
||||||
|
DisableCredentialRotation: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -215,6 +228,17 @@ func TestGetStorageSecrets(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expErrMsg: "secrets.secret_access_key: unexpected type for value \"secret_access_key\": want string, got bool",
|
expErrMsg: "secrets.secret_access_key: unexpected type for value \"secret_access_key\": want string, got bool",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "lastRotatedBadType",
|
||||||
|
inSecrets: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstAccessKeyId: structpb.NewStringValue("foo"),
|
||||||
|
ConstSecretAccessKey: structpb.NewStringValue("bar"),
|
||||||
|
ConstLastRotatedTime: structpb.NewStringValue("nottime"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expErrMsg: "secrets.creds_last_rotated_time: could not parse time in value \"creds_last_rotated_time\"",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "unknownSecret",
|
name: "unknownSecret",
|
||||||
inSecrets: &structpb.Struct{
|
inSecrets: &structpb.Struct{
|
||||||
@@ -239,6 +263,21 @@ func TestGetStorageSecrets(t *testing.T) {
|
|||||||
SecretAccessKey: "bar",
|
SecretAccessKey: "bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "successWithLastRotatedTime",
|
||||||
|
inSecrets: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstAccessKeyId: structpb.NewStringValue("foo"),
|
||||||
|
ConstSecretAccessKey: structpb.NewStringValue("bar"),
|
||||||
|
ConstLastRotatedTime: structpb.NewStringValue(time.Date(2024, 01, 02, 03, 04, 05, 06, time.UTC).Format(time.RFC3339Nano)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expStorageSecrets: &StorageSecrets{
|
||||||
|
AccessKeyId: "foo",
|
||||||
|
SecretAccessKey: "bar",
|
||||||
|
LastRotatedTime: time.Date(2024, 01, 02, 03, 04, 05, 06, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -268,10 +307,12 @@ func TestStorageSecretsAsMap(t *testing.T) {
|
|||||||
in: &StorageSecrets{
|
in: &StorageSecrets{
|
||||||
AccessKeyId: "access_key_id_value",
|
AccessKeyId: "access_key_id_value",
|
||||||
SecretAccessKey: "secret_access_key_value",
|
SecretAccessKey: "secret_access_key_value",
|
||||||
|
LastRotatedTime: time.Date(2024, 01, 02, 03, 04, 05, 06, time.UTC),
|
||||||
},
|
},
|
||||||
expMap: map[string]any{
|
expMap: map[string]any{
|
||||||
"access_key_id": "access_key_id_value",
|
"access_key_id": "access_key_id_value",
|
||||||
"secret_access_key": "secret_access_key_value",
|
"secret_access_key": "secret_access_key_value",
|
||||||
|
"creds_last_rotated_time": "2024-01-02T03:04:05.000000006Z",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -283,3 +324,18 @@ func TestStorageSecretsAsMap(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTestStorageSecretsClone(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
in := &StorageSecrets{
|
||||||
|
AccessKeyId: "foo",
|
||||||
|
SecretAccessKey: "bar",
|
||||||
|
LastRotatedTime: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
secCl := in.Clone()
|
||||||
|
require.Equal(t, in.AccessKeyId, secCl.AccessKeyId)
|
||||||
|
require.Equal(t, in.AccessKeyId, secCl.AccessKeyId)
|
||||||
|
require.Equal(t, in.LastRotatedTime, secCl.LastRotatedTime)
|
||||||
|
require.NotSame(t, in, secCl)
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
@@ -12,10 +13,13 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets"
|
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets"
|
||||||
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
madmin "github.com/minio/madmin-go/v3"
|
madmin "github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
@@ -27,6 +31,10 @@ import (
|
|||||||
|
|
||||||
var _ pb.StoragePluginServiceServer = (*StoragePlugin)(nil)
|
var _ pb.StoragePluginServiceServer = (*StoragePlugin)(nil)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultGetObjectChunkSize = uint32(65536)
|
||||||
|
)
|
||||||
|
|
||||||
// StoragePlugin implements the StoragePluginServiceServer interface for the
|
// StoragePlugin implements the StoragePluginServiceServer interface for the
|
||||||
// MinIO storage service plugin.
|
// MinIO storage service plugin.
|
||||||
type StoragePlugin struct {
|
type StoragePlugin struct {
|
||||||
@@ -53,9 +61,14 @@ func (sp *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCr
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ensureServiceAccount(ctx, sa, sec)
|
deleteOldCredsFn := func() error { return nil }
|
||||||
if err != nil {
|
if !sa.DisableCredentialRotation {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "failed to ensure service account: %v", err)
|
newSec, fn, err := rotateCredentials(ctx, bucket.GetBucketName(), sa, sec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "failed to rotate minio credentials: %v", err)
|
||||||
|
}
|
||||||
|
sec = newSec
|
||||||
|
deleteOldCredsFn = fn
|
||||||
}
|
}
|
||||||
|
|
||||||
cl, err := minio.New(sa.EndpointUrl, &minio.Options{
|
cl, err := minio.New(sa.EndpointUrl, &minio.Options{
|
||||||
@@ -67,15 +80,28 @@ func (sp *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCr
|
|||||||
return nil, status.Errorf(codes.Unknown, "failed to create minio sdk client: %v", err)
|
return nil, status.Errorf(codes.Unknown, "failed to create minio sdk client: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = dryRun(ctx, cl, bucket)
|
p, dryRunErr := dryRun(ctx, cl, bucket)
|
||||||
if err != nil {
|
if dryRunErr.Len() > 0 {
|
||||||
return nil, status.Errorf(codes.Unknown, "failed to verify provided minio environment: %v", err)
|
st := status.New(codes.InvalidArgument, fmt.Sprintf("failed to verify provided minio environment: %v", dryRunErr.Unwrap()))
|
||||||
|
|
||||||
|
st, err = st.WithDetails(&pb.StorageBucketCredentialState{State: p})
|
||||||
|
if err != nil {
|
||||||
|
st = status.New(codes.Unknown, fmt.Sprintf("failed to verify provided minio environment: %v", dryRunErr.Unwrap()))
|
||||||
|
}
|
||||||
|
return nil, st.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
persistedData, err := structpb.NewStruct(sec.AsMap())
|
persistedData, err := structpb.NewStruct(sec.AsMap())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "failed to convert storage attributes to protobuf struct: %v", err)
|
return nil, status.Errorf(codes.Internal, "failed to convert storage attributes to protobuf struct: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Given that at this point everything is OK with the bucket, we shouldn't
|
||||||
|
// let a failure to delete the old credentials fail the entire process. If
|
||||||
|
// we ever establish a way to log non-critical errors in the plugin, log
|
||||||
|
// this.
|
||||||
|
_ = deleteOldCredsFn()
|
||||||
|
|
||||||
return &pb.OnCreateStorageBucketResponse{
|
return &pb.OnCreateStorageBucketResponse{
|
||||||
Persisted: &storagebuckets.StorageBucketPersisted{Data: persistedData},
|
Persisted: &storagebuckets.StorageBucketPersisted{Data: persistedData},
|
||||||
}, nil
|
}, nil
|
||||||
@@ -90,24 +116,22 @@ func (sp *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUp
|
|||||||
if newBucket.BucketName == "" {
|
if newBucket.BucketName == "" {
|
||||||
return nil, status.Error(codes.InvalidArgument, "new bucketName is required")
|
return nil, status.Error(codes.InvalidArgument, "new bucketName is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
oldBucket := req.GetCurrentBucket()
|
oldBucket := req.GetCurrentBucket()
|
||||||
if oldBucket == nil {
|
if oldBucket == nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, "current bucket is required")
|
return nil, status.Error(codes.InvalidArgument, "current bucket is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
// current attributes
|
|
||||||
osa, err := getStorageAttributes(oldBucket.GetAttributes())
|
osa, err := getStorageAttributes(oldBucket.GetAttributes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// new attributes and secrets
|
|
||||||
nsa, err := getStorageAttributes(newBucket.GetAttributes())
|
nsa, err := getStorageAttributes(newBucket.GetAttributes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sec, err := getStorageSecrets(newBucket.GetSecrets())
|
sec, err := getStorageSecrets(req.GetPersisted().GetData())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -123,8 +147,64 @@ func (sp *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUp
|
|||||||
return nil, status.Errorf(codes.InvalidArgument, "cannot update attribute EndpointUrl")
|
return nil, status.Errorf(codes.InvalidArgument, "cannot update attribute EndpointUrl")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = ensureServiceAccount(ctx, nsa, sec); err != nil {
|
if nsa.DisableCredentialRotation && newBucket.GetSecrets() == nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "failed to ensure service account: %s", err)
|
if !sec.LastRotatedTime.IsZero() {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "cannot disable rotation for already-rotated credentials")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRotatedCredsFn := func() error { return nil }
|
||||||
|
deleteOldPersistedCredsFn := func() error { return nil }
|
||||||
|
if newBucket.GetSecrets() != nil {
|
||||||
|
// Preemptively check if we're managing the current persisted
|
||||||
|
// credentials and schedule them for later deletion if we are, since in
|
||||||
|
// both rotation and non-rotation cases below, we'll be replacing these.
|
||||||
|
if !sec.LastRotatedTime.IsZero() {
|
||||||
|
sc := sec.Clone()
|
||||||
|
deleteOldPersistedCredsFn = sync.OnceValue(func() error {
|
||||||
|
cl, err := newMadminClient(nsa, sc)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create new minio admin client: %w", err)
|
||||||
|
}
|
||||||
|
err = cl.DeleteServiceAccount(ctx, sc.AccessKeyId)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete minio service account: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
newSec, err := getStorageSecrets(newBucket.GetSecrets())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !nsa.DisableCredentialRotation {
|
||||||
|
// We have new incoming credentials and we're also rotating,
|
||||||
|
// therefore we use incoming credentials to create a new service
|
||||||
|
// account, then delete the incoming credentials. The existing
|
||||||
|
// persisted credentials have already been scheduled for deletion
|
||||||
|
// above (if applicable), so we don't need to do it here.
|
||||||
|
rotatedSec, fn, err := rotateCredentials(ctx, newBucket.GetBucketName(), nsa, newSec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "failed to rotate minio credentials: %v", err)
|
||||||
|
}
|
||||||
|
sec = rotatedSec
|
||||||
|
deleteRotatedCredsFn = fn
|
||||||
|
} else {
|
||||||
|
// We have new incoming credentials but we're not rotating. Set them
|
||||||
|
// directly for validation below. The existing persisted credentials
|
||||||
|
// have already been scheduled for deletion above (if applicable),
|
||||||
|
// so we don't need to do it here.
|
||||||
|
sec = newSec
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if osa.DisableCredentialRotation && !nsa.DisableCredentialRotation {
|
||||||
|
// Handle the case where the user enables credential rotation but
|
||||||
|
// provides no new credentials. This is an error.
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "credential rotation enabled with no new credentials provided")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cl, err := minio.New(nsa.EndpointUrl, &minio.Options{
|
cl, err := minio.New(nsa.EndpointUrl, &minio.Options{
|
||||||
@@ -136,8 +216,16 @@ func (sp *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUp
|
|||||||
return nil, status.Errorf(codes.InvalidArgument, "failed to create minio sdk client: %s", err)
|
return nil, status.Errorf(codes.InvalidArgument, "failed to create minio sdk client: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = dryRun(ctx, cl, newBucket); err != nil {
|
if p, dryRunErr := dryRun(ctx, cl, newBucket); dryRunErr != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "failed to verify provided minio environment: %s", err)
|
if dryRunErr.Len() > 0 {
|
||||||
|
st := status.New(codes.InvalidArgument, fmt.Sprintf("failed to verify provided minio environment: %v", dryRunErr.Unwrap()))
|
||||||
|
|
||||||
|
st, err = st.WithDetails(&pb.StorageBucketCredentialState{State: p})
|
||||||
|
if err != nil {
|
||||||
|
st = status.New(codes.InvalidArgument, fmt.Sprintf("failed to verify provided minio environment: %v", dryRunErr.Unwrap()))
|
||||||
|
}
|
||||||
|
return nil, st.Err()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
persisted, err := structpb.NewStruct(sec.AsMap())
|
persisted, err := structpb.NewStruct(sec.AsMap())
|
||||||
@@ -145,14 +233,46 @@ func (sp *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUp
|
|||||||
return nil, status.Errorf(codes.InvalidArgument, "failed to convert storage attributes to protobuf struct: %s", err)
|
return nil, status.Errorf(codes.InvalidArgument, "failed to convert storage attributes to protobuf struct: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Given that at this point everything is OK with the bucket, we shouldn't
|
||||||
|
// let a failure to delete credentials fail the entire process. If we ever
|
||||||
|
// establish a way to log non-critical errors in the plugin, log this.
|
||||||
|
_ = deleteRotatedCredsFn()
|
||||||
|
_ = deleteOldPersistedCredsFn()
|
||||||
|
|
||||||
return &pb.OnUpdateStorageBucketResponse{
|
return &pb.OnUpdateStorageBucketResponse{
|
||||||
Persisted: &storagebuckets.StorageBucketPersisted{Data: persisted},
|
Persisted: &storagebuckets.StorageBucketPersisted{Data: persisted},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnDeleteStorageBucket is a hook that runs when a storage bucket is deleted.
|
// OnDeleteStorageBucket is a hook that runs when a storage bucket is deleted.
|
||||||
// Since this plugin manages no state at the moment, this function is a no-op.
|
|
||||||
func (sp *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDeleteStorageBucketRequest) (*pb.OnDeleteStorageBucketResponse, error) {
|
func (sp *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDeleteStorageBucketRequest) (*pb.OnDeleteStorageBucketResponse, error) {
|
||||||
|
bucket := req.GetBucket()
|
||||||
|
if bucket == nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "no storage bucket information found")
|
||||||
|
}
|
||||||
|
|
||||||
|
sec, err := getStorageSecrets(req.GetPersisted().GetData())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sec.LastRotatedTime.IsZero() {
|
||||||
|
sa, err := getStorageAttributes(bucket.GetAttributes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ac, err := newMadminClient(sa, sec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "failed to create minio admin 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &pb.OnDeleteStorageBucketResponse{}, nil
|
return &pb.OnDeleteStorageBucketResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,9 +297,13 @@ func (sp *StoragePlugin) ValidatePermissions(ctx context.Context, req *pb.Valida
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ensureServiceAccount(ctx, sa, sec)
|
if !sec.LastRotatedTime.IsZero() {
|
||||||
if err != nil {
|
// since these are existing secrets, we only want to validate the
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "failed to ensure service account: %v", err)
|
// service account if the credentials have been rotated previously
|
||||||
|
err = ensureServiceAccount(ctx, sa, sec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "failed to ensure service account: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cl, err := minio.New(sa.EndpointUrl, &minio.Options{
|
cl, err := minio.New(sa.EndpointUrl, &minio.Options{
|
||||||
@@ -191,9 +315,15 @@ func (sp *StoragePlugin) ValidatePermissions(ctx context.Context, req *pb.Valida
|
|||||||
return nil, status.Errorf(codes.Unknown, "failed to create minio sdk client: %v", err)
|
return nil, status.Errorf(codes.Unknown, "failed to create minio sdk client: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = dryRun(ctx, cl, bucket)
|
p, dryRunErr := dryRun(ctx, cl, bucket)
|
||||||
if err != nil {
|
if dryRunErr.Unwrap() != nil {
|
||||||
return nil, status.Errorf(codes.Unknown, "failed to verify provided minio environment: %v", err)
|
st := status.New(codes.FailedPrecondition, fmt.Sprintf("failed to verify provided minio environment: %v", dryRunErr.Unwrap()))
|
||||||
|
|
||||||
|
st, err = st.WithDetails(&pb.StorageBucketCredentialState{State: p})
|
||||||
|
if err != nil {
|
||||||
|
st = status.New(codes.Unknown, fmt.Sprintf("failed to verify provided minio environment: %v", dryRunErr.Unwrap()))
|
||||||
|
}
|
||||||
|
return nil, st.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
return &pb.ValidatePermissionsResponse{}, nil
|
return &pb.ValidatePermissionsResponse{}, nil
|
||||||
@@ -229,13 +359,13 @@ func (sp *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReque
|
|||||||
Region: sa.Region,
|
Region: sa.Region,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Unknown, "failed to create minio sdk client: %v", err)
|
return nil, errorToStatus(codes.Unknown, "failed to create minio sdk client", err, req).Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
objKey := path.Join(bucket.GetBucketPrefix(), req.GetKey())
|
objKey := path.Join(bucket.GetBucketPrefix(), req.GetKey())
|
||||||
oi, err := cl.StatObject(ctx, bucket.GetBucketName(), objKey, minio.StatObjectOptions{})
|
oi, err := cl.StatObject(ctx, bucket.GetBucketName(), objKey, minio.StatObjectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Unknown, "failed to stat object: %v", err)
|
return nil, errorToStatus(codes.Unknown, "failed to stat object", err, req).Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
return &pb.HeadObjectResponse{
|
return &pb.HeadObjectResponse{
|
||||||
@@ -246,7 +376,71 @@ func (sp *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReque
|
|||||||
|
|
||||||
// GetObject is a hook that retrieves objects.
|
// GetObject is a hook that retrieves objects.
|
||||||
func (sp *StoragePlugin) GetObject(req *pb.GetObjectRequest, objServer pb.StoragePluginService_GetObjectServer) error {
|
func (sp *StoragePlugin) GetObject(req *pb.GetObjectRequest, objServer pb.StoragePluginService_GetObjectServer) error {
|
||||||
return fmt.Errorf("unimplemented")
|
bucket := req.GetBucket()
|
||||||
|
if bucket == nil {
|
||||||
|
return status.Error(codes.InvalidArgument, "no storage bucket information found")
|
||||||
|
}
|
||||||
|
if bucket.GetBucketName() == "" {
|
||||||
|
return status.Error(codes.InvalidArgument, "storage bucket name is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.GetKey() == "" {
|
||||||
|
return status.Error(codes.InvalidArgument, "object key is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
sa, err := getStorageAttributes(bucket.GetAttributes())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sec, err := getStorageSecrets(bucket.GetSecrets())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cl, err := minio.New(sa.EndpointUrl, &minio.Options{
|
||||||
|
Creds: credentials.NewStaticV4(sec.AccessKeyId, sec.SecretAccessKey, ""),
|
||||||
|
Secure: sa.UseSSL,
|
||||||
|
Region: sa.Region,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return errorToStatus(codes.Unknown, "failed to create minio sdk client", err, req).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
objKey := path.Join(bucket.GetBucketPrefix(), req.GetKey())
|
||||||
|
obj, err := cl.GetObject(objServer.Context(), bucket.GetBucketName(), objKey, minio.GetObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return errorToStatus(codes.Unknown, "failed to get object", err, req).Err()
|
||||||
|
}
|
||||||
|
defer obj.Close()
|
||||||
|
|
||||||
|
chunkSize := req.GetChunkSize()
|
||||||
|
if chunkSize == 0 {
|
||||||
|
chunkSize = defaultGetObjectChunkSize
|
||||||
|
}
|
||||||
|
|
||||||
|
rd := bufio.NewReader(obj)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-objServer.Context().Done():
|
||||||
|
return status.Errorf(codes.Canceled, "server context done while streaming: %v", objServer.Context().Err())
|
||||||
|
default:
|
||||||
|
buffer := make([]byte, chunkSize)
|
||||||
|
n, err := rd.Read(buffer)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return status.Errorf(codes.Unknown, "failed to read chunk from minio object: %v", err)
|
||||||
|
}
|
||||||
|
if n > 0 {
|
||||||
|
err := objServer.Send(&pb.GetObjectResponse{FileChunk: buffer[:n]})
|
||||||
|
if err != nil {
|
||||||
|
return status.Errorf(codes.Unknown, "failed to send chunk to client: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutObject is a hook that reads a file stored on local disk and stores it to
|
// PutObject is a hook that reads a file stored on local disk and stores it to
|
||||||
@@ -299,7 +493,7 @@ func (sp *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest
|
|||||||
Region: sa.Region,
|
Region: sa.Region,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "failed to create minio sdk client: %v", err)
|
return nil, errorToStatus(codes.Internal, "failed to create minio sdk client", err, req).Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
hash := sha256.New()
|
hash := sha256.New()
|
||||||
@@ -313,23 +507,23 @@ func (sp *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest
|
|||||||
}
|
}
|
||||||
|
|
||||||
key := path.Join(bucket.GetBucketPrefix(), req.GetKey())
|
key := path.Join(bucket.GetBucketPrefix(), req.GetKey())
|
||||||
res, err := cl.PutObject(ctx, bucket.GetBucketName(), key, file, info.Size(), minio.PutObjectOptions{
|
res, err := cl.PutObject(ctx, bucket.GetBucketName(), key, file, info.Size(), minio.PutObjectOptions{})
|
||||||
UserMetadata: map[string]string{
|
|
||||||
"x-amz-checksum-algorithm": "SHA256",
|
|
||||||
"x-amz-checksum-sha256": checksum,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "failed to put object into minio: %v", err)
|
return nil, errorToStatus(codes.Internal, "failed to put object into minio", err, req).Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.ChecksumSHA256 == "" {
|
resChecksum := res.ChecksumSHA256
|
||||||
return nil, status.Error(codes.Internal, "missing checksum response from minio")
|
if resChecksum == "" {
|
||||||
|
// We were successful in putting the object but we didn't get a file
|
||||||
|
// SHA256 checksum from the server. Given that the call was successful,
|
||||||
|
// assume everything went ok and force the checksum to match so we can
|
||||||
|
// send it correctly back to Boundary.
|
||||||
|
resChecksum = checksum
|
||||||
}
|
}
|
||||||
if checksum != res.ChecksumSHA256 {
|
if checksum != resChecksum {
|
||||||
return nil, status.Error(codes.Internal, "mismatched checksum")
|
return nil, status.Error(codes.Internal, "mismatched checksum")
|
||||||
}
|
}
|
||||||
decodedChecksum, err := base64.StdEncoding.DecodeString(res.ChecksumSHA256)
|
decodedChecksum, err := base64.StdEncoding.DecodeString(resChecksum)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "failed to decode checksum value from minio: %v", err)
|
return nil, status.Errorf(codes.Internal, "failed to decode checksum value from minio: %v", err)
|
||||||
}
|
}
|
||||||
@@ -341,40 +535,225 @@ func (sp *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest
|
|||||||
// DeleteObjects deletes one or many files in an external object store via a
|
// DeleteObjects deletes one or many files in an external object store via a
|
||||||
// provided key prefix.
|
// provided key prefix.
|
||||||
func (sp *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjectsRequest) (*pb.DeleteObjectsResponse, error) {
|
func (sp *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjectsRequest) (*pb.DeleteObjectsResponse, error) {
|
||||||
return nil, fmt.Errorf("unimplemented")
|
bucket := req.GetBucket()
|
||||||
|
if bucket == nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "no storage bucket information found")
|
||||||
|
}
|
||||||
|
if bucket.GetBucketName() == "" {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "storage bucket name is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.GetKeyPrefix() == "" {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "key prefix is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
sa, err := getStorageAttributes(bucket.GetAttributes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sec, err := getStorageSecrets(bucket.GetSecrets())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cl, err := minio.New(sa.EndpointUrl, &minio.Options{
|
||||||
|
Creds: credentials.NewStaticV4(sec.AccessKeyId, sec.SecretAccessKey, ""),
|
||||||
|
Secure: sa.UseSSL,
|
||||||
|
Region: sa.Region,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorToStatus(codes.Unknown, "failed to create minio sdk client", err, req).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := path.Join(bucket.GetBucketPrefix(), req.GetKeyPrefix())
|
||||||
|
if strings.HasSuffix(req.GetKeyPrefix(), "/") {
|
||||||
|
// path.Join ends by "cleaning" the path, including removing a trailing slash, if
|
||||||
|
// it exists. given that a slash is used to denote a folder, it is required here.
|
||||||
|
prefix += "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
if !req.Recursive {
|
||||||
|
if err := cl.RemoveObject(ctx, bucket.GetBucketName(), prefix, minio.RemoveObjectOptions{}); err != nil {
|
||||||
|
return nil, errorToStatus(codes.Unknown, "error deleting minio object", err, req).Err()
|
||||||
|
}
|
||||||
|
return &pb.DeleteObjectsResponse{
|
||||||
|
ObjectsDeleted: uint32(1),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
objects := []minio.ObjectInfo{}
|
||||||
|
for obj := range cl.ListObjects(ctx, bucket.GetBucketName(), minio.ListObjectsOptions{
|
||||||
|
Prefix: prefix,
|
||||||
|
Recursive: true,
|
||||||
|
}) {
|
||||||
|
if obj.Err != nil {
|
||||||
|
return nil, errorToStatus(codes.Unknown, "error iterating minio bucket contents", obj.Err, req).Err()
|
||||||
|
}
|
||||||
|
objects = append(objects, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
emitter := make(chan minio.ObjectInfo)
|
||||||
|
go func() {
|
||||||
|
defer close(emitter)
|
||||||
|
// we could loop the listobjects respose here, but we want to check all the errs ahead of time
|
||||||
|
for _, obj := range objects {
|
||||||
|
emitter <- obj
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
removed := 0
|
||||||
|
for res := range cl.RemoveObjectsWithResult(ctx, bucket.GetBucketName(), emitter, minio.RemoveObjectsOptions{}) {
|
||||||
|
if res.Err != nil {
|
||||||
|
return nil, errorToStatus(codes.Unknown, "error deleting minio object(s)", res.Err, req).Err()
|
||||||
|
}
|
||||||
|
removed++
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.DeleteObjectsResponse{
|
||||||
|
ObjectsDeleted: uint32(removed),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dryRun(ctx context.Context, cl *minio.Client, bucket *storagebuckets.StorageBucket) error {
|
// validateWritePermission checks if the plugin has write permission for an object.
|
||||||
objectKey := path.Join(bucket.GetBucketPrefix(), uuid.New().String())
|
func validateWritePermission(ctx context.Context, cl *minio.Client, bucket *storagebuckets.StorageBucket, objectKey string) (*pb.Permission, *multierror.Error) {
|
||||||
|
const op = "storage.validateWritePermission"
|
||||||
|
var combinedErrors *multierror.Error
|
||||||
|
|
||||||
|
if cl == nil {
|
||||||
|
err := fmt.Errorf("%s: minio client is nil", op)
|
||||||
|
return &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, ErrorDetails: err.Error(), CheckedAt: timestamppb.Now()}, multierror.Append(combinedErrors, err)
|
||||||
|
}
|
||||||
|
if bucket == nil {
|
||||||
|
err := fmt.Errorf("%s: storage bucket is nil", op)
|
||||||
|
return &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, ErrorDetails: err.Error(), CheckedAt: timestamppb.Now()}, multierror.Append(combinedErrors, err)
|
||||||
|
}
|
||||||
|
|
||||||
rd := bytes.NewReader([]byte("hashicorp boundary minio plugin access test"))
|
rd := bytes.NewReader([]byte("hashicorp boundary minio plugin access test"))
|
||||||
_, err := cl.PutObject(ctx, bucket.GetBucketName(), objectKey, rd, rd.Size(), minio.PutObjectOptions{})
|
_, err := cl.PutObject(ctx, bucket.GetBucketName(), objectKey, rd, rd.Size(), minio.PutObjectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to put object at %q: %w", objectKey, err)
|
combinedErrors = multierror.Append(combinedErrors, fmt.Errorf("failed to put object at %q: %w", objectKey, err))
|
||||||
|
return errorToPermission(err), combinedErrors
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = cl.StatObject(ctx, bucket.GetBucketName(), objectKey, minio.StatObjectOptions{})
|
return &pb.Permission{State: pb.StateType_STATE_TYPE_OK, CheckedAt: timestamppb.Now()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateReadPermission checks if the plugin has read permission for an object.
|
||||||
|
func validateReadPermission(ctx context.Context, cl *minio.Client, bucket *storagebuckets.StorageBucket, objectKey string) (*pb.Permission, *multierror.Error) {
|
||||||
|
const op = "storage.validateReadPermission"
|
||||||
|
var combinedErrors *multierror.Error
|
||||||
|
|
||||||
|
if cl == nil {
|
||||||
|
err := fmt.Errorf("%s: minio client is nil", op)
|
||||||
|
return &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, ErrorDetails: err.Error(), CheckedAt: timestamppb.Now()}, multierror.Append(combinedErrors, err)
|
||||||
|
}
|
||||||
|
if bucket == nil {
|
||||||
|
err := fmt.Errorf("%s: storage bucket is nil", op)
|
||||||
|
return &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, ErrorDetails: err.Error(), CheckedAt: timestamppb.Now()}, multierror.Append(combinedErrors, err)
|
||||||
|
}
|
||||||
|
if objectKey == "" {
|
||||||
|
err := fmt.Errorf("%s: object key is empty", op)
|
||||||
|
return &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, ErrorDetails: err.Error(), CheckedAt: timestamppb.Now()}, multierror.Append(combinedErrors, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
permission := &pb.Permission{State: pb.StateType_STATE_TYPE_OK}
|
||||||
|
_, err := cl.StatObject(ctx, bucket.GetBucketName(), objectKey, minio.StatObjectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to stat object at %q: %w", objectKey, err)
|
permission = errorToPermission(err)
|
||||||
|
combinedErrors = multierror.Append(combinedErrors, fmt.Errorf("failed to stat object at %q: %w", objectKey, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = cl.GetObject(ctx, bucket.GetBucketName(), objectKey, minio.GetObjectOptions{})
|
_, err = cl.GetObject(ctx, bucket.GetBucketName(), objectKey, minio.GetObjectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get object at %q: %w", objectKey, err)
|
p := errorToPermission(err)
|
||||||
|
if permission.GetState() == pb.StateType_STATE_TYPE_OK {
|
||||||
|
permission = p
|
||||||
|
} else {
|
||||||
|
if p != nil && p.GetErrorDetails() != "" {
|
||||||
|
permission.ErrorDetails = fmt.Sprintf("%s, %s", permission.GetErrorDetails(), p.GetErrorDetails())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
combinedErrors = multierror.Append(combinedErrors, fmt.Errorf("failed to get object at %q: %w", objectKey, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
oiCh := cl.ListObjects(ctx, bucket.GetBucketName(), minio.ListObjectsOptions{Recursive: true})
|
oiCh := cl.ListObjects(ctx, bucket.GetBucketName(), minio.ListObjectsOptions{Recursive: true})
|
||||||
for oi := range oiCh {
|
for oi := range oiCh {
|
||||||
if oi.Err != nil {
|
if oi.Err != nil {
|
||||||
return fmt.Errorf("failed to list objects in bucket %q: %w", bucket.GetBucketName(), oi.Err)
|
if permission.GetState() == pb.StateType_STATE_TYPE_OK {
|
||||||
|
permission = errorToPermission(oi.Err)
|
||||||
|
} else {
|
||||||
|
p := errorToPermission(err)
|
||||||
|
if p != nil && p.GetErrorDetails() != "" {
|
||||||
|
permission.ErrorDetails = fmt.Sprintf("%s, %s", permission.GetErrorDetails(), p.GetErrorDetails())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
combinedErrors = multierror.Append(fmt.Errorf("failed to list objects in bucket %q: %w", bucket.GetBucketName(), oi.Err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cl.RemoveObject(ctx, bucket.GetBucketName(), objectKey, minio.RemoveObjectOptions{})
|
if combinedErrors != nil {
|
||||||
if err != nil {
|
return permission, combinedErrors
|
||||||
return fmt.Errorf("failed to remove object at %q: %w", objectKey, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
permission.CheckedAt = timestamppb.Now()
|
||||||
|
return permission, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateDeletePermission checks if the plugin has delete permission for an object.
|
||||||
|
func validateDeletePermission(ctx context.Context, cl *minio.Client, bucket *storagebuckets.StorageBucket, objectKey string) (*pb.Permission, *multierror.Error) {
|
||||||
|
const op = "storage.validateDeletePermission"
|
||||||
|
|
||||||
|
var combinedErrors *multierror.Error
|
||||||
|
|
||||||
|
if cl == nil {
|
||||||
|
err := fmt.Errorf("%s: minio client is nil", op)
|
||||||
|
combinedErrors = multierror.Append(combinedErrors, err)
|
||||||
|
return &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, ErrorDetails: err.Error(), CheckedAt: timestamppb.Now()}, combinedErrors
|
||||||
|
}
|
||||||
|
if bucket == nil {
|
||||||
|
err := fmt.Errorf("%s: storage bucket is nil", op)
|
||||||
|
combinedErrors = multierror.Append(combinedErrors, err)
|
||||||
|
return &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, ErrorDetails: err.Error(), CheckedAt: timestamppb.Now()}, combinedErrors
|
||||||
|
}
|
||||||
|
if objectKey == "" {
|
||||||
|
err := fmt.Errorf("%s: object key is empty", op)
|
||||||
|
combinedErrors = multierror.Append(combinedErrors, err)
|
||||||
|
return &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, ErrorDetails: err.Error(), CheckedAt: timestamppb.Now()}, combinedErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cl.RemoveObject(ctx, bucket.GetBucketName(), objectKey, minio.RemoveObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
combinedErrors = multierror.Append(combinedErrors, fmt.Errorf("failed to remove object at %q: %w", objectKey, err))
|
||||||
|
return errorToPermission(err), combinedErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.Permission{State: pb.StateType_STATE_TYPE_OK, CheckedAt: timestamppb.Now()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dryRun(ctx context.Context, cl *minio.Client, bucket *storagebuckets.StorageBucket) (*pb.Permissions, *multierror.Error) {
|
||||||
|
objectKey := path.Join(bucket.GetBucketPrefix(), uuid.New().String())
|
||||||
|
permissions := &pb.Permissions{
|
||||||
|
Write: &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, CheckedAt: timestamppb.Now()},
|
||||||
|
Read: &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, CheckedAt: timestamppb.Now()},
|
||||||
|
Delete: &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, CheckedAt: timestamppb.Now()},
|
||||||
|
}
|
||||||
|
|
||||||
|
var combinedErrors *multierror.Error
|
||||||
|
|
||||||
|
writePermission, writeErr := validateWritePermission(ctx, cl, bucket, objectKey)
|
||||||
|
combinedErrors = multierror.Append(combinedErrors, writeErr)
|
||||||
|
permissions.Write = writePermission
|
||||||
|
|
||||||
|
readPermission, readErr := validateReadPermission(ctx, cl, bucket, objectKey)
|
||||||
|
combinedErrors = multierror.Append(combinedErrors, readErr)
|
||||||
|
permissions.Read = readPermission
|
||||||
|
|
||||||
|
deletePermission, deleteErr := validateDeletePermission(ctx, cl, bucket, objectKey)
|
||||||
|
combinedErrors = multierror.Append(combinedErrors, deleteErr)
|
||||||
|
permissions.Delete = deletePermission
|
||||||
|
|
||||||
|
return permissions, combinedErrors
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMadminClient(sa *StorageAttributes, sec *StorageSecrets) (*madmin.AdminClient, error) {
|
func newMadminClient(sa *StorageAttributes, sec *StorageSecrets) (*madmin.AdminClient, error) {
|
||||||
@@ -399,3 +778,83 @@ func ensureServiceAccount(ctx context.Context, sa *StorageAttributes, sec *Stora
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// errorToPermission converts a MinIO error to a plugin permission.
|
||||||
|
// errorToPermission only handles a subset of MinIO errors that have to do with
|
||||||
|
// permissions.
|
||||||
|
// If the MinIO error is not recognized, it returns a permission with an unknown state.
|
||||||
|
func errorToPermission(err error) *pb.Permission {
|
||||||
|
if err == nil {
|
||||||
|
return &pb.Permission{}
|
||||||
|
}
|
||||||
|
|
||||||
|
errResponse := minio.ToErrorResponse(err)
|
||||||
|
switch errResponse.Code {
|
||||||
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#ErrorCodeList
|
||||||
|
case "AccessDenied":
|
||||||
|
fallthrough
|
||||||
|
case "InvalidSecurity":
|
||||||
|
fallthrough
|
||||||
|
case "InvalidAccessKeyId":
|
||||||
|
fallthrough
|
||||||
|
case "NoSuchBucket":
|
||||||
|
return &pb.Permission{State: pb.StateType_STATE_TYPE_ERROR, ErrorDetails: errResponse.Error(), CheckedAt: timestamppb.Now()}
|
||||||
|
case "InvalidObjectState":
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
return &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, ErrorDetails: errResponse.Error(), CheckedAt: timestamppb.Now()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorToStatus converts an error to a gRPC status with the plugin permission
|
||||||
|
// attached.
|
||||||
|
// If the permission error is not recognized, a gRPC status will be returned
|
||||||
|
// with an code and error message passed in.
|
||||||
|
func errorToStatus(code codes.Code, msg string, err error, req any) *status.Status {
|
||||||
|
if err == nil {
|
||||||
|
// If we see this status, it means this function was called with a nil
|
||||||
|
// err. This is odd and should probably be investigated.
|
||||||
|
return status.New(code, fmt.Sprintf("%s: no error from storage service provider", msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
errResponse := minio.ToErrorResponse(err)
|
||||||
|
switch errResponse.Code {
|
||||||
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#ErrorCodeList
|
||||||
|
case "RequestTimeout":
|
||||||
|
return status.New(codes.DeadlineExceeded, fmt.Sprintf("%s: timeout exceeded when communicating with storage service provider", msg))
|
||||||
|
case "SlowDown":
|
||||||
|
return status.New(codes.Unavailable, fmt.Sprintf("%s: received a throttling response from storage service provider", msg))
|
||||||
|
case "BadDigest":
|
||||||
|
return status.New(codes.Aborted, fmt.Sprintf("%s: storage checksum mismatch", msg))
|
||||||
|
case "NoSuchBucket":
|
||||||
|
return status.New(codes.NotFound, fmt.Sprintf("%s: specified storage bucket does not exist", msg))
|
||||||
|
case "NoSuchKey":
|
||||||
|
return status.New(codes.NotFound, fmt.Sprintf("%s: specified object does not exist", msg))
|
||||||
|
}
|
||||||
|
if errResponse.StatusCode == 400 {
|
||||||
|
// catch-all for various different bad-request related errors
|
||||||
|
return status.New(codes.InvalidArgument, fmt.Sprintf("%s: received a bad request response from storage service provider: %v", msg, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// for all other errors, we can parse as permission state errors
|
||||||
|
permission := errorToPermission(err)
|
||||||
|
state := &pb.StorageBucketCredentialState{State: &pb.Permissions{}}
|
||||||
|
|
||||||
|
// we can use the request type to determine what kind of response we should return
|
||||||
|
switch req.(type) {
|
||||||
|
case pb.HeadObjectRequest, *pb.HeadObjectRequest:
|
||||||
|
state.State.Read = permission
|
||||||
|
case pb.GetObjectRequest, *pb.GetObjectRequest:
|
||||||
|
state.State.Read = permission
|
||||||
|
case pb.PutObjectRequest, *pb.PutObjectRequest:
|
||||||
|
state.State.Write = permission
|
||||||
|
case pb.DeleteObjectsRequest, *pb.DeleteObjectsRequest:
|
||||||
|
state.State.Delete = permission
|
||||||
|
}
|
||||||
|
|
||||||
|
st, err := status.New(code, fmt.Sprintf("%s: %s", msg, err)).WithDetails(state)
|
||||||
|
if err != nil {
|
||||||
|
st = status.New(code, fmt.Sprintf("%s: %s", msg, err))
|
||||||
|
}
|
||||||
|
return st
|
||||||
|
}
|
||||||
+2153
-101
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,91 @@
|
|||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/madmin-go/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultPolicyTemplate = `{
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Action": [
|
||||||
|
"s3:GetObject",
|
||||||
|
"s3:GetObjectAttributes",
|
||||||
|
"s3:PutObject",
|
||||||
|
"s3:DeleteObject"
|
||||||
|
],
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Resource": "arn:aws:s3:::%[1]s/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Action": "s3:ListBucket",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Resource": "arn:aws:s3:::%[1]s"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Action": [
|
||||||
|
"admin:CreateServiceAccount",
|
||||||
|
"admin:RemoveServiceAccount"
|
||||||
|
],
|
||||||
|
"Effect": "Allow"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Version": "2012-10-17"
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
|
||||||
|
type deleteInputCredsFn func() error
|
||||||
|
|
||||||
|
// rotateCredentials uses the incoming service account credentials to generate a
|
||||||
|
// new service account. It returns a new StorageSecrets object with the new
|
||||||
|
// service account credentials and a callback function to delete the service
|
||||||
|
// account provided in the input.
|
||||||
|
func rotateCredentials(ctx context.Context, bucketName string, sa *StorageAttributes, inSec *StorageSecrets) (*StorageSecrets, deleteInputCredsFn, error) {
|
||||||
|
err := ensureServiceAccount(ctx, sa, inSec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to ensure minio service account credentials: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ac, err := newMadminClient(sa, inSec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to create minio admin client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
policy := json.RawMessage(fmt.Sprintf(defaultPolicyTemplate, bucketName))
|
||||||
|
newCreds, err := ac.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
||||||
|
Name: "Boundary MinIO Service Account",
|
||||||
|
Description: fmt.Sprintf("Boundary-managed MinIO service account for storage bucket '%s'", bucketName),
|
||||||
|
Policy: policy,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to create new minio service account: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newSec := &StorageSecrets{
|
||||||
|
AccessKeyId: newCreds.AccessKey,
|
||||||
|
SecretAccessKey: newCreds.SecretKey,
|
||||||
|
LastRotatedTime: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
inCl := inSec.Clone()
|
||||||
|
return newSec, sync.OnceValue(func() error {
|
||||||
|
ac, err := newMadminClient(sa, inCl)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create minio admin client: %w", err)
|
||||||
|
}
|
||||||
|
err = ac.DeleteServiceAccount(ctx, inCl.AccessKeyId)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete minio service account: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRotateCredentials(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
server := internaltest.NewMinioServer(t)
|
||||||
|
|
||||||
|
bucketName := "test-bucket"
|
||||||
|
err := server.Client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
inSec *StorageSecrets
|
||||||
|
expErrMsg string
|
||||||
|
credDelErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "notUsingServiceAccount",
|
||||||
|
inSec: &StorageSecrets{
|
||||||
|
AccessKeyId: server.RootUsername,
|
||||||
|
SecretAccessKey: server.RootPassword,
|
||||||
|
},
|
||||||
|
expErrMsg: "failed to ensure minio service account credentials: failed to obtain service account info: The specified service account is not found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "addServiceAccountFail",
|
||||||
|
inSec: func() *StorageSecrets {
|
||||||
|
creds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
||||||
|
Policy: json.RawMessage(`
|
||||||
|
{
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Action": [
|
||||||
|
"admin:CreateServiceAccount",
|
||||||
|
"admin:RemoveServiceAccount"
|
||||||
|
],
|
||||||
|
"Effect": "Deny"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Version": "2012-10-17"
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return &StorageSecrets{
|
||||||
|
AccessKeyId: creds.AccessKey,
|
||||||
|
SecretAccessKey: creds.SecretKey,
|
||||||
|
}
|
||||||
|
}(),
|
||||||
|
expErrMsg: "failed to create new minio service account: Access Denied",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "credentialDeletionFail",
|
||||||
|
inSec: func() *StorageSecrets {
|
||||||
|
creds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return &StorageSecrets{
|
||||||
|
AccessKeyId: creds.AccessKey,
|
||||||
|
SecretAccessKey: creds.SecretKey,
|
||||||
|
}
|
||||||
|
}(),
|
||||||
|
credDelErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "success",
|
||||||
|
inSec: func() *StorageSecrets {
|
||||||
|
creds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return &StorageSecrets{
|
||||||
|
AccessKeyId: creds.AccessKey,
|
||||||
|
SecretAccessKey: creds.SecretKey,
|
||||||
|
}
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
sa := &StorageAttributes{
|
||||||
|
EndpointUrl: server.ApiAddr,
|
||||||
|
UseSSL: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
outSec, delFn, err := rotateCredentials(context.Background(), bucketName, sa, tt.inSec)
|
||||||
|
if tt.expErrMsg != "" {
|
||||||
|
require.ErrorContains(t, err, tt.expErrMsg)
|
||||||
|
require.Nil(t, outSec)
|
||||||
|
require.Nil(t, delFn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, outSec)
|
||||||
|
require.NotNil(t, delFn)
|
||||||
|
require.NotEqual(t, outSec.AccessKeyId, tt.inSec.AccessKeyId)
|
||||||
|
require.NotEqual(t, outSec.SecretAccessKey, tt.inSec.SecretAccessKey)
|
||||||
|
require.NotEmpty(t, outSec.LastRotatedTime)
|
||||||
|
|
||||||
|
// Ensure old creds still exist since the callback hasn't been
|
||||||
|
// 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)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if tt.credDelErr {
|
||||||
|
sa.EndpointUrl = "localhost:1"
|
||||||
|
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)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
require.NoError(t, delFn())
|
||||||
|
require.NoError(t, delFn()) // 2nd call should return the same state (due to sync.Once).
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = server.AdminClient.InfoServiceAccount(ctx, tt.inSec.AccessKeyId)
|
||||||
|
require.ErrorContains(t, err, "service account is not found")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user