Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae9ff8dcc0 | ||
|
|
bd2be82f5a | ||
|
|
8835180037 | ||
|
|
be5cb745ab | ||
|
|
08d1e99e8b | ||
|
|
a0cb3b8fee | ||
|
|
d5262e8a15 |
No files matched your search
@@ -9,15 +9,16 @@ require (
|
|||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.38.2
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.38.2
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.21.2
|
github.com/aws/aws-sdk-go-v2/service/sts v1.21.2
|
||||||
github.com/aws/smithy-go v1.14.1
|
github.com/aws/smithy-go v1.14.1
|
||||||
github.com/google/go-cmp v0.5.9
|
github.com/google/go-cmp v0.6.0
|
||||||
github.com/google/uuid v1.3.1
|
github.com/google/uuid v1.4.0
|
||||||
github.com/hashicorp/boundary/sdk v0.0.41-0.20231117205527-08045ddb050e
|
github.com/hashicorp/boundary/sdk v0.0.43-0.20240717182311-a20aae98794a
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1
|
||||||
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.0.0
|
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.0.0
|
||||||
github.com/hashicorp/terraform-json v0.14.0
|
github.com/hashicorp/terraform-json v0.14.0
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.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.31.0
|
google.golang.org/protobuf v1.33.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -44,7 +45,6 @@ require (
|
|||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-hclog v1.5.0 // indirect
|
github.com/hashicorp/go-hclog v1.5.0 // 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/hashicorp/go-version v1.5.0 // indirect
|
github.com/hashicorp/go-version v1.5.0 // indirect
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
@@ -55,10 +55,12 @@ require (
|
|||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/zclconf/go-cty v1.10.0 // indirect
|
github.com/zclconf/go-cty v1.10.0 // indirect
|
||||||
golang.org/x/crypto v0.14.0 // indirect
|
golang.org/x/crypto v0.18.0 // indirect
|
||||||
golang.org/x/net v0.17.0 // indirect
|
golang.org/x/net v0.20.0 // indirect
|
||||||
golang.org/x/sys v0.13.0 // indirect
|
golang.org/x/sys v0.16.0 // indirect
|
||||||
golang.org/x/text v0.13.0 // indirect
|
golang.org/x/text v0.14.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/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
@@ -74,12 +74,12 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
|||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hashicorp/boundary/sdk v0.0.41-0.20231117205527-08045ddb050e h1:PzZklmCjnaqQq9JdZRI9SVPSd9xo0iXLlmOHoQX68aM=
|
github.com/hashicorp/boundary/sdk v0.0.43-0.20240717182311-a20aae98794a h1:SwVze6sYE5o+J9qdcgj29auY1k6O8oSo9DC2+2Gb9rw=
|
||||||
github.com/hashicorp/boundary/sdk v0.0.41-0.20231117205527-08045ddb050e/go.mod h1:+XTDYf9YNeKIbGOPJwy7hlO2Le4zgzCtHCG/u+z4THI=
|
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=
|
||||||
@@ -144,8 +144,8 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw
|
|||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||||
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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/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=
|
||||||
@@ -179,8 +179,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
|
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
|
||||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||||
github.com/rogpeppe/go-internal v1.6.2 h1:aIihoIOHCiLZHxyoNQ+ABL4NKhFTgKLBdMLyEAh98m0=
|
|
||||||
github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
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/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4=
|
github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4=
|
||||||
@@ -209,8 +210,8 @@ go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ
|
|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
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.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
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=
|
||||||
@@ -223,8 +224,8 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
|
|||||||
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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/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.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -238,14 +239,14 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/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.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@@ -258,19 +259,22 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
|||||||
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/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
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.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
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=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||||
|
|||||||
@@ -34,8 +34,9 @@ type CredentialAttributes struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetCredentialsConfig parses values out of a protobuf struct secrets and returns a
|
// GetCredentialsConfig parses values out of a protobuf struct secrets and returns a
|
||||||
// CredentialsConfig used for configuring an AWS session. An error is returned if
|
// CredentialsConfig used for configuring an AWS session. An status error is returned
|
||||||
// any unrecognized fields are found in the protobuf struct input.
|
// with an InvalidArgument code if any unrecognized fields are found in the protobuf
|
||||||
|
// struct input.
|
||||||
func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes, required bool) (*awsutil.CredentialsConfig, error) {
|
func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes, required bool) (*awsutil.CredentialsConfig, error) {
|
||||||
// initialize secrets if it is nil
|
// initialize secrets if it is nil
|
||||||
// secrets can be nil because static credentials are optional
|
// secrets can be nil because static credentials are optional
|
||||||
@@ -116,13 +117,18 @@ func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes,
|
|||||||
opts = append(opts, awsutil.WithRoleTags(attrs.RoleTags))
|
opts = append(opts, awsutil.WithRoleTags(attrs.RoleTags))
|
||||||
}
|
}
|
||||||
|
|
||||||
return awsutil.NewCredentialsConfig(opts...)
|
cfg, err := awsutil.NewCredentialsConfig(opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.BadRequestStatus(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCredentialAttributes parses values out of a protobuf struct input and returns a
|
// GetCredentialAttributes parses values out of a protobuf struct input and returns a
|
||||||
// CredentialAttributes used for configuring an AWS session. An error is returned if
|
// CredentialAttributes used for configuring an AWS session. An status error is returned
|
||||||
// any of the following fields are missing from the protobuf struct input or have
|
// with an InvalidArgument code if any of the following fields are missing from the protobuf
|
||||||
// invalid value types: region, disableCredentialRotation
|
// struct input or have invalid value types: region, disableCredentialRotation
|
||||||
func GetCredentialAttributes(in *structpb.Struct) (*CredentialAttributes, error) {
|
func GetCredentialAttributes(in *structpb.Struct) (*CredentialAttributes, error) {
|
||||||
badFields := make(map[string]string)
|
badFields := make(map[string]string)
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,14 @@ package credential
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
stderr "errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
|
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
|
||||||
|
"github.com/hashicorp/boundary-plugin-aws/internal/errors"
|
||||||
"github.com/hashicorp/boundary-plugin-aws/internal/values"
|
"github.com/hashicorp/boundary-plugin-aws/internal/values"
|
||||||
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
@@ -71,7 +72,7 @@ func WithStateTestOpts(opts []awsutil.Option) AwsCredentialPersistedStateOption
|
|||||||
func WithCredentialsConfig(x *awsutil.CredentialsConfig) AwsCredentialPersistedStateOption {
|
func WithCredentialsConfig(x *awsutil.CredentialsConfig) AwsCredentialPersistedStateOption {
|
||||||
return func(s *AwsCredentialPersistedState) error {
|
return func(s *AwsCredentialPersistedState) error {
|
||||||
if s.CredentialsConfig != nil {
|
if s.CredentialsConfig != nil {
|
||||||
return errors.New("credentials config already set")
|
return stderr.New("credentials config already set")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.CredentialsConfig = x
|
s.CredentialsConfig = x
|
||||||
@@ -83,7 +84,7 @@ func WithCredentialsConfig(x *awsutil.CredentialsConfig) AwsCredentialPersistedS
|
|||||||
func WithCredsLastRotatedTime(t time.Time) AwsCredentialPersistedStateOption {
|
func WithCredsLastRotatedTime(t time.Time) AwsCredentialPersistedStateOption {
|
||||||
return func(s *AwsCredentialPersistedState) error {
|
return func(s *AwsCredentialPersistedState) error {
|
||||||
if !s.CredsLastRotatedTime.IsZero() {
|
if !s.CredsLastRotatedTime.IsZero() {
|
||||||
return errors.New("last rotation time already set")
|
return stderr.New("last rotation time already set")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.CredsLastRotatedTime = t
|
s.CredsLastRotatedTime = t
|
||||||
@@ -106,13 +107,15 @@ func NewAwsCredentialPersistedState(opts ...AwsCredentialPersistedStateOption) (
|
|||||||
|
|
||||||
// ValidateCreds takes the credentials configuration from the persisted state
|
// ValidateCreds takes the credentials configuration from the persisted state
|
||||||
// and runs sts.GetCallerIdentity for the current credentials, which is done
|
// and runs sts.GetCallerIdentity for the current credentials, which is done
|
||||||
// to check that the credentials are valid.
|
// to check that the credentials are valid. This method returns a status error
|
||||||
|
// with PluginError details.
|
||||||
func (s *AwsCredentialPersistedState) ValidateCreds(ctx context.Context) error {
|
func (s *AwsCredentialPersistedState) ValidateCreds(ctx context.Context) error {
|
||||||
if s.CredentialsConfig == nil {
|
if s.CredentialsConfig == nil {
|
||||||
return errors.New("missing credentials config")
|
return errors.BadRequestStatus("missing credentials config")
|
||||||
}
|
}
|
||||||
if _, err := s.CredentialsConfig.GetCallerIdentity(ctx, s.testOpts...); err != nil {
|
if _, err := s.CredentialsConfig.GetCallerIdentity(ctx, s.testOpts...); err != nil {
|
||||||
return fmt.Errorf("error validating credentials: %w", err)
|
st, _ := errors.ParseAWSError(err, "validating credentials")
|
||||||
|
return st.Err()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -121,18 +124,19 @@ func (s *AwsCredentialPersistedState) ValidateCreds(ctx context.Context) error {
|
|||||||
// then deletes the old access key. If deletion of the old access key is successful, the new access key/secret key are
|
// then deletes the old access key. If deletion of the old access key is successful, the new access key/secret key are
|
||||||
// written into the credentials config and the persisted state. On any error, the old credentials are not overwritten.
|
// written into the credentials config and the persisted state. On any error, the old credentials are not overwritten.
|
||||||
// This ensures that any generated new secret key never leaves this function in case of an error, even though it will
|
// This ensures that any generated new secret key never leaves this function in case of an error, even though it will
|
||||||
// still result in an extraneous access key existing.
|
// still result in an extraneous access key existing. This method returns a status error with PluginError details.
|
||||||
func (s *AwsCredentialPersistedState) RotateCreds(ctx context.Context) error {
|
func (s *AwsCredentialPersistedState) RotateCreds(ctx context.Context) error {
|
||||||
if s.CredentialsConfig == nil {
|
if s.CredentialsConfig == nil {
|
||||||
return errors.New("missing credentials config")
|
return errors.BadRequestStatus("missing credentials config")
|
||||||
}
|
}
|
||||||
if GetCredentialType(s.CredentialsConfig) != StaticAWS {
|
if GetCredentialType(s.CredentialsConfig) != StaticAWS {
|
||||||
return errors.New("invalid credential type")
|
return errors.BadRequestStatus("invalid credential type")
|
||||||
}
|
}
|
||||||
if err := s.CredentialsConfig.RotateKeys(ctx, append([]awsutil.Option{
|
if err := s.CredentialsConfig.RotateKeys(ctx, append([]awsutil.Option{
|
||||||
awsutil.WithValidityCheckTimeout(rotationWaitTimeout),
|
awsutil.WithValidityCheckTimeout(rotationWaitTimeout),
|
||||||
}, s.testOpts...)...); err != nil {
|
}, s.testOpts...)...); err != nil {
|
||||||
return fmt.Errorf("error rotating credentials: %w", err)
|
st, _ := errors.ParseAWSError(err, "error rotating credentials")
|
||||||
|
return st.Err()
|
||||||
}
|
}
|
||||||
s.CredsLastRotatedTime = time.Now()
|
s.CredsLastRotatedTime = time.Now()
|
||||||
return nil
|
return nil
|
||||||
@@ -140,13 +144,14 @@ func (s *AwsCredentialPersistedState) RotateCreds(ctx context.Context) error {
|
|||||||
|
|
||||||
// ReplaceCreds replaces the access key in the state with a new key.
|
// ReplaceCreds replaces the access key in the state with a new key.
|
||||||
// If the existing key was rotated at any point in time, it is
|
// If the existing key was rotated at any point in time, it is
|
||||||
// deleted first, otherwise it's left alone.
|
// deleted first, otherwise it's left alone. This method returns a
|
||||||
|
// status error with PluginError details.
|
||||||
func (s *AwsCredentialPersistedState) ReplaceCreds(ctx context.Context, credentialsConfig *awsutil.CredentialsConfig) error {
|
func (s *AwsCredentialPersistedState) ReplaceCreds(ctx context.Context, credentialsConfig *awsutil.CredentialsConfig) error {
|
||||||
if credentialsConfig == nil {
|
if credentialsConfig == nil {
|
||||||
return errors.New("missing new credentials config")
|
return errors.BadRequestStatus("missing new credentials config")
|
||||||
}
|
}
|
||||||
if s.CredentialsConfig == nil {
|
if s.CredentialsConfig == nil {
|
||||||
return errors.New("missing credentials config")
|
return errors.BadRequestStatus("missing credentials config")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete old/existing credentials. This action is only possible for static credential types.
|
// Delete old/existing credentials. This action is only possible for static credential types.
|
||||||
@@ -166,27 +171,29 @@ func (s *AwsCredentialPersistedState) ReplaceCreds(ctx context.Context, credenti
|
|||||||
|
|
||||||
// DeleteCreds deletes the credentials in the state. The access key
|
// DeleteCreds deletes the credentials in the state. The access key
|
||||||
// ID, secret access key, and rotation time fields are zeroed out in
|
// ID, secret access key, and rotation time fields are zeroed out in
|
||||||
// the state just to ensure that they cannot be re-used after.
|
// the state just to ensure that they cannot be re-used after. This
|
||||||
|
// method returns a status error with PluginError details.
|
||||||
func (s *AwsCredentialPersistedState) DeleteCreds(ctx context.Context) error {
|
func (s *AwsCredentialPersistedState) DeleteCreds(ctx context.Context) error {
|
||||||
if s.CredentialsConfig == nil {
|
if s.CredentialsConfig == nil {
|
||||||
return errors.New("missing credentials config")
|
return errors.BadRequestStatus("missing credentials config")
|
||||||
}
|
}
|
||||||
if GetCredentialType(s.CredentialsConfig) != StaticAWS {
|
if GetCredentialType(s.CredentialsConfig) != StaticAWS {
|
||||||
return errors.New("invalid credential type")
|
return errors.BadRequestStatus("invalid credential type")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.CredentialsConfig.DeleteAccessKey(ctx, s.CredentialsConfig.AccessKey, s.testOpts...); err != nil {
|
if err := s.CredentialsConfig.DeleteAccessKey(ctx, s.CredentialsConfig.AccessKey, s.testOpts...); err != nil {
|
||||||
// Determine if the deletion error was due to a missing
|
// Determine if the deletion error was due to a missing
|
||||||
// resource. If it was, just pass it.
|
// resource. If it was, just pass it.
|
||||||
var awsErr *iamTypes.NoSuchEntityException
|
var awsErr *iamTypes.NoSuchEntityException
|
||||||
if errors.As(err, &awsErr) && awsErr.ErrorCode() == "NoSuchEntity" {
|
if stderr.As(err, &awsErr) && awsErr.ErrorCode() == "NoSuchEntity" {
|
||||||
s.CredentialsConfig = nil
|
s.CredentialsConfig = nil
|
||||||
s.CredsLastRotatedTime = time.Time{}
|
s.CredsLastRotatedTime = time.Time{}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise treat it as an actual error.
|
// Otherwise treat it as an actual error.
|
||||||
return err
|
st, _ := errors.ParseAWSError(err, "failed to delete access key")
|
||||||
|
return st.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
s.CredentialsConfig = nil
|
s.CredentialsConfig = nil
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go-v2/service/iam"
|
"github.com/aws/aws-sdk-go-v2/service/iam"
|
||||||
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
|
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
|
||||||
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -340,14 +343,16 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
|
|||||||
|
|
||||||
func TestAwsCatalogPersistedState_ValidateCreds(t *testing.T) {
|
func TestAwsCatalogPersistedState_ValidateCreds(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
in *AwsCredentialPersistedState
|
in *AwsCredentialPersistedState
|
||||||
expectedErr string
|
expectedStatusCode codes.Code
|
||||||
|
expectedErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "missing credentials config",
|
name: "missing credentials config",
|
||||||
in: &AwsCredentialPersistedState{},
|
in: &AwsCredentialPersistedState{},
|
||||||
expectedErr: "missing credentials config",
|
expectedStatusCode: codes.InvalidArgument,
|
||||||
|
expectedErr: "missing credentials config",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "validation error",
|
name: "validation error",
|
||||||
@@ -365,7 +370,8 @@ func TestAwsCatalogPersistedState_ValidateCreds(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedErr: fmt.Sprintf("error validating credentials: %s", testGetCallerIdentityErr),
|
expectedStatusCode: codes.Unknown,
|
||||||
|
expectedErr: "aws service unknown: unknown error: validating credentials",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "good",
|
name: "good",
|
||||||
@@ -383,10 +389,12 @@ func TestAwsCatalogPersistedState_ValidateCreds(t *testing.T) {
|
|||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
tc := tc
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
require := require.New(t)
|
require, assert := require.New(t), assert.New(t)
|
||||||
err := tc.in.ValidateCreds(context.Background())
|
err := tc.in.ValidateCreds(context.Background())
|
||||||
if tc.expectedErr != "" {
|
if tc.expectedErr != "" {
|
||||||
require.EqualError(err, tc.expectedErr)
|
require.Error(err)
|
||||||
|
assert.ErrorContains(err, tc.expectedErr)
|
||||||
|
assert.Equal(tc.expectedStatusCode, status.Code(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,11 +410,13 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
|
|||||||
exepected *awsutil.CredentialsConfig
|
exepected *awsutil.CredentialsConfig
|
||||||
expectedNonZeroRotatedTime bool
|
expectedNonZeroRotatedTime bool
|
||||||
expectedErr string
|
expectedErr string
|
||||||
|
expectedStatusCode codes.Code
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "missing credentials config",
|
name: "missing credentials config",
|
||||||
in: &AwsCredentialPersistedState{},
|
in: &AwsCredentialPersistedState{},
|
||||||
expectedErr: "missing credentials config",
|
expectedErr: "missing credentials config",
|
||||||
|
expectedStatusCode: codes.InvalidArgument,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "cannot delete dynamic credential type",
|
name: "cannot delete dynamic credential type",
|
||||||
@@ -417,7 +427,8 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
|
|||||||
},
|
},
|
||||||
CredsLastRotatedTime: time.Now(),
|
CredsLastRotatedTime: time.Now(),
|
||||||
},
|
},
|
||||||
expectedErr: "invalid credential type",
|
expectedErr: "invalid credential type",
|
||||||
|
expectedStatusCode: codes.InvalidArgument,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "cannot delete unknown credential type",
|
name: "cannot delete unknown credential type",
|
||||||
@@ -428,7 +439,8 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
|
|||||||
},
|
},
|
||||||
CredsLastRotatedTime: time.Now(),
|
CredsLastRotatedTime: time.Now(),
|
||||||
},
|
},
|
||||||
expectedErr: "invalid credential type",
|
expectedErr: "invalid credential type",
|
||||||
|
expectedStatusCode: codes.InvalidArgument,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "rotation error",
|
name: "rotation error",
|
||||||
@@ -446,7 +458,8 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedErr: fmt.Sprintf("error rotating credentials: error calling CreateAccessKey: error calling iam.GetUser: %s", testGetUserErr),
|
expectedErr: "aws service unknown: unknown error: error rotating credentials",
|
||||||
|
expectedStatusCode: codes.Unknown,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "good",
|
name: "good",
|
||||||
@@ -496,10 +509,12 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
|
|||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
tc := tc
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
require := require.New(t)
|
require, assert := require.New(t), assert.New(t)
|
||||||
err := tc.in.RotateCreds(context.Background())
|
err := tc.in.RotateCreds(context.Background())
|
||||||
if tc.expectedErr != "" {
|
if tc.expectedErr != "" {
|
||||||
require.EqualError(err, tc.expectedErr)
|
require.Error(err)
|
||||||
|
assert.ErrorContains(err, tc.expectedErr)
|
||||||
|
assert.Equal(tc.expectedStatusCode, status.Code(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,17 +536,20 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) {
|
|||||||
expected *awsutil.CredentialsConfig
|
expected *awsutil.CredentialsConfig
|
||||||
expectedDeleteCalled bool
|
expectedDeleteCalled bool
|
||||||
expectedErr string
|
expectedErr string
|
||||||
|
expectedStatusCode codes.Code
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "missing new credentials config",
|
name: "missing new credentials config",
|
||||||
in: &AwsCredentialPersistedState{},
|
in: &AwsCredentialPersistedState{},
|
||||||
expectedErr: "missing new credentials config",
|
expectedErr: "missing new credentials config",
|
||||||
|
expectedStatusCode: codes.InvalidArgument,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing credentials config",
|
name: "missing credentials config",
|
||||||
in: &AwsCredentialPersistedState{},
|
in: &AwsCredentialPersistedState{},
|
||||||
credentialConfig: &awsutil.CredentialsConfig{},
|
credentialConfig: &awsutil.CredentialsConfig{},
|
||||||
expectedErr: "missing credentials config",
|
expectedErr: "missing credentials config",
|
||||||
|
expectedStatusCode: codes.InvalidArgument,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "deletion error",
|
name: "deletion error",
|
||||||
@@ -549,8 +567,9 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
credentialConfig: &awsutil.CredentialsConfig{},
|
credentialConfig: &awsutil.CredentialsConfig{},
|
||||||
expectedErr: fmt.Sprintf("error deleting old access key: %s", testDeleteAccessKeyErr),
|
expectedErr: "aws service unknown: unknown error: failed to delete access key",
|
||||||
|
expectedStatusCode: codes.Unknown,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "good with delete of old rotated",
|
name: "good with delete of old rotated",
|
||||||
@@ -604,11 +623,12 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) {
|
|||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
tc := tc
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
require := require.New(t)
|
require, assert := require.New(t), assert.New(t)
|
||||||
state.Reset()
|
state.Reset()
|
||||||
err := tc.in.ReplaceCreds(context.Background(), tc.credentialConfig)
|
err := tc.in.ReplaceCreds(context.Background(), tc.credentialConfig)
|
||||||
if tc.expectedErr != "" {
|
if tc.expectedErr != "" {
|
||||||
require.EqualError(err, tc.expectedErr)
|
require.Error(err)
|
||||||
|
assert.ErrorContains(err, tc.expectedErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,14 +643,16 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) {
|
|||||||
|
|
||||||
func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
|
func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
in *AwsCredentialPersistedState
|
in *AwsCredentialPersistedState
|
||||||
expectedErr string
|
expectedErr string
|
||||||
|
expectedStatusCode codes.Code
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "missing credentials config",
|
name: "missing credentials config",
|
||||||
in: &AwsCredentialPersistedState{},
|
in: &AwsCredentialPersistedState{},
|
||||||
expectedErr: "missing credentials config",
|
expectedErr: "missing credentials config",
|
||||||
|
expectedStatusCode: codes.InvalidArgument,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "cannot delete dynamic credential type",
|
name: "cannot delete dynamic credential type",
|
||||||
@@ -641,7 +663,8 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
|
|||||||
},
|
},
|
||||||
CredsLastRotatedTime: time.Now(),
|
CredsLastRotatedTime: time.Now(),
|
||||||
},
|
},
|
||||||
expectedErr: "invalid credential type",
|
expectedErr: "invalid credential type",
|
||||||
|
expectedStatusCode: codes.InvalidArgument,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "cannot delete unknown credential type",
|
name: "cannot delete unknown credential type",
|
||||||
@@ -652,7 +675,8 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
|
|||||||
},
|
},
|
||||||
CredsLastRotatedTime: time.Now(),
|
CredsLastRotatedTime: time.Now(),
|
||||||
},
|
},
|
||||||
expectedErr: "invalid credential type",
|
expectedErr: "invalid credential type",
|
||||||
|
expectedStatusCode: codes.InvalidArgument,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "deletion error",
|
name: "deletion error",
|
||||||
@@ -669,7 +693,8 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedErr: fmt.Sprintf("error deleting old access key: %s", testDeleteAccessKeyErr),
|
expectedErr: "aws service unknown: unknown error: failed to delete access key",
|
||||||
|
expectedStatusCode: codes.Unknown,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "deletion error, but OK because key was just gone",
|
name: "deletion error, but OK because key was just gone",
|
||||||
@@ -708,10 +733,11 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
|
|||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
tc := tc
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
require := require.New(t)
|
require, assert := require.New(t), assert.New(t)
|
||||||
err := tc.in.DeleteCreds(context.Background())
|
err := tc.in.DeleteCreds(context.Background())
|
||||||
if tc.expectedErr != "" {
|
if tc.expectedErr != "" {
|
||||||
require.EqualError(err, tc.expectedErr)
|
require.Error(err)
|
||||||
|
assert.ErrorContains(err, tc.expectedErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+193
-1
@@ -4,12 +4,63 @@
|
|||||||
package errors
|
package errors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws/retry"
|
||||||
|
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
|
||||||
|
"github.com/aws/smithy-go"
|
||||||
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// awsErrorAccessDenied is returned when the credentials do not have
|
||||||
|
// the required permissions to do a specific action on a resource.
|
||||||
|
// This error will persist until the IAM Role Policy is updated by
|
||||||
|
// the owner. This error cannot be resolved by retrying the request.
|
||||||
|
awsErrorAccessDenied = "AccessDenied"
|
||||||
|
|
||||||
|
// awsErrorInvalidAccessKeyId is returned when the credential does
|
||||||
|
// not exist. This error will persist until the credentials attached
|
||||||
|
// to the storage bucket is updated. This error can occur when the
|
||||||
|
// given credentials are deleted, deactivated, or rotated. This
|
||||||
|
// error cannot be resolved by retrying the request.
|
||||||
|
awsErrorInvalidAccessKeyId = "InvalidAccessKeyId"
|
||||||
|
|
||||||
|
// awsErrorExpiredToken is returned when the provided credential is
|
||||||
|
// expired. This error can only be returned by a dynamic credential
|
||||||
|
// type. This error can resolve itself by retrying the request.
|
||||||
|
awsErrorExpiredToken = "ExpiredToken"
|
||||||
|
|
||||||
|
// NoSuchBucket is returned when the specified S3 storage bucket does
|
||||||
|
// not exist
|
||||||
|
awsErrorNoSuchBucket = "NoSuchBucket"
|
||||||
|
|
||||||
|
// BadDigest is returned when an S3 PUT OBJECT request's calculated
|
||||||
|
// checksum does not match the checksum which was sent by the client
|
||||||
|
awsErrorBadDigest = "BadDigest"
|
||||||
|
|
||||||
|
// NoSuchKey is returned when attempting to interact with an S3 object
|
||||||
|
// whose key does not exist
|
||||||
|
awsErrorNoSuchKey = "NoSuchKey"
|
||||||
|
|
||||||
|
// The InvalidObjectState error is returned when trying to
|
||||||
|
// access an object that was moved into cold storage.
|
||||||
|
awsErrorInvalidObjectState = "InvalidObjectState"
|
||||||
|
|
||||||
|
// RequestTimeout is returned when an http request takes longer than allowed
|
||||||
|
awsErrorRequestTimeout = "RequestTimeout"
|
||||||
|
|
||||||
|
// RequestTimeoutException is returned when an http request takes longer than allowed
|
||||||
|
awsErrorRequestTimeoutException = "RequestTimeoutException"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InvalidArgumentError returns an grpc invalid argument status error.
|
// InvalidArgumentError returns an grpc invalid argument status error.
|
||||||
@@ -22,5 +73,146 @@ func InvalidArgumentError(msg string, f map[string]string) error {
|
|||||||
sort.Strings(fieldMsgs)
|
sort.Strings(fieldMsgs)
|
||||||
msg = fmt.Sprintf("%s: [%s]", msg, strings.Join(fieldMsgs, ", "))
|
msg = fmt.Sprintf("%s: [%s]", msg, strings.Join(fieldMsgs, ", "))
|
||||||
}
|
}
|
||||||
return status.Error(codes.InvalidArgument, msg)
|
return BadRequestStatus(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseAWSError converts an aws service error into a RPC status. This method
|
||||||
|
// will handle parsing the following aws error codes: throttling, connections,
|
||||||
|
// and credentials. This method will fallback to parsing the http status code
|
||||||
|
// when it cannot match an aws error code. This method does not handle specific
|
||||||
|
// service type errors such as S3 or EC2.
|
||||||
|
func ParseAWSError(err error, msg string) (st *status.Status, permission *pb.Permission) {
|
||||||
|
if err == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the service name of the aws api
|
||||||
|
serviceName := "unknown"
|
||||||
|
var oe *smithy.OperationError
|
||||||
|
if errors.As(err, &oe) {
|
||||||
|
serviceName = oe.Service()
|
||||||
|
}
|
||||||
|
|
||||||
|
// The unknown plugin error code will be used
|
||||||
|
// when the aws service returns an error that
|
||||||
|
// has failed to be parsed. By default, this
|
||||||
|
// error code is retryable.
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if st == nil {
|
||||||
|
statusMsg := fmt.Sprintf("aws service %s: unknown error: %s", serviceName, msg)
|
||||||
|
st = status.New(codes.Unknown, statusMsg)
|
||||||
|
}
|
||||||
|
if permission == nil {
|
||||||
|
permission = &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, CheckedAt: timestamppb.Now()}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// evaluate against aws throttling error codes
|
||||||
|
throttleErr := retry.ThrottleErrorCode{
|
||||||
|
Codes: retry.DefaultThrottleErrorCodes,
|
||||||
|
}
|
||||||
|
if throttleErr.IsErrorThrottle(err).Bool() {
|
||||||
|
statusMsg := fmt.Sprintf("aws service %s: throttling error: %s", serviceName, msg)
|
||||||
|
return status.New(codes.Unavailable, statusMsg), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse some specific aws error codes that we have special cred states for
|
||||||
|
var apiErr smithy.APIError
|
||||||
|
if errors.As(err, &apiErr) {
|
||||||
|
switch apiErr.ErrorCode() {
|
||||||
|
case awsErrorAccessDenied:
|
||||||
|
fallthrough
|
||||||
|
case awsErrorInvalidAccessKeyId:
|
||||||
|
fallthrough
|
||||||
|
case awsErrorExpiredToken:
|
||||||
|
statusMsg := fmt.Sprintf("aws service %s: invalid credentials error: %s", serviceName, msg)
|
||||||
|
return status.New(codes.PermissionDenied, statusMsg), &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_ERROR,
|
||||||
|
ErrorDetails: apiErr.ErrorMessage(),
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
}
|
||||||
|
case awsErrorNoSuchBucket:
|
||||||
|
statusMsg := fmt.Sprintf("aws %s error: %s", serviceName, msg)
|
||||||
|
return status.New(codes.NotFound, statusMsg), &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_ERROR,
|
||||||
|
ErrorDetails: apiErr.ErrorMessage(),
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
}
|
||||||
|
case awsErrorBadDigest:
|
||||||
|
statusMsg := fmt.Sprintf("aws %s error: %s", serviceName, msg)
|
||||||
|
return status.New(codes.Aborted, statusMsg), nil
|
||||||
|
case awsErrorNoSuchKey:
|
||||||
|
fallthrough
|
||||||
|
case awsErrorInvalidObjectState:
|
||||||
|
statusMsg := fmt.Sprintf("aws %s error: %s", serviceName, msg)
|
||||||
|
return status.New(codes.NotFound, statusMsg), nil
|
||||||
|
case awsErrorRequestTimeout:
|
||||||
|
fallthrough
|
||||||
|
case awsErrorRequestTimeoutException:
|
||||||
|
statusMsg := fmt.Sprintf("aws %s error: %s", serviceName, msg)
|
||||||
|
return status.New(codes.DeadlineExceeded, statusMsg), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// default to evaluating the http status code
|
||||||
|
if httpErr, ok := err.(*awshttp.ResponseError); ok {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
_, err = buf.ReadFrom(httpErr.Response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return status.New(codes.Internal, err.Error()), nil
|
||||||
|
}
|
||||||
|
defer httpErr.Response.Body.Close()
|
||||||
|
switch httpErr.HTTPStatusCode() {
|
||||||
|
case http.StatusBadRequest:
|
||||||
|
statusMsg := fmt.Sprintf("aws service %s: bad request error: %s", serviceName, msg)
|
||||||
|
return status.New(codes.InvalidArgument, statusMsg), nil
|
||||||
|
case http.StatusUnauthorized:
|
||||||
|
statusMsg := fmt.Sprintf("aws service %s: invalid credentials error: %s", serviceName, msg)
|
||||||
|
return status.New(codes.PermissionDenied, statusMsg), &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_ERROR,
|
||||||
|
ErrorDetails: buf.String(),
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
}
|
||||||
|
case http.StatusForbidden:
|
||||||
|
statusMsg := fmt.Sprintf("aws service %s: invalid credentials error: %s", serviceName, msg)
|
||||||
|
return status.New(codes.PermissionDenied, statusMsg), &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_ERROR,
|
||||||
|
ErrorDetails: buf.String(),
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
}
|
||||||
|
case http.StatusNotFound:
|
||||||
|
statusMsg := fmt.Sprintf("aws service %s: resource not found error: %s", serviceName, msg)
|
||||||
|
return status.New(codes.NotFound, statusMsg), nil
|
||||||
|
case http.StatusTooManyRequests:
|
||||||
|
statusMsg := fmt.Sprintf("aws service %s: throttling error: %s", serviceName, msg)
|
||||||
|
return status.New(codes.Unavailable, statusMsg), nil
|
||||||
|
case http.StatusInternalServerError:
|
||||||
|
fallthrough
|
||||||
|
case http.StatusBadGateway:
|
||||||
|
fallthrough
|
||||||
|
case http.StatusServiceUnavailable:
|
||||||
|
statusMsg := fmt.Sprintf("aws service %s: connectivity error: %s", serviceName, msg)
|
||||||
|
return status.New(codes.Unavailable, statusMsg), nil
|
||||||
|
case http.StatusRequestTimeout:
|
||||||
|
fallthrough
|
||||||
|
case http.StatusGatewayTimeout:
|
||||||
|
statusMsg := fmt.Sprintf("aws service %s: timeout error: %s", serviceName, msg)
|
||||||
|
return status.New(codes.DeadlineExceeded, statusMsg), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return st, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BadRequestStatus returns a status error with an invalid
|
||||||
|
// argument code
|
||||||
|
func BadRequestStatus(format string, args ...any) error {
|
||||||
|
return status.New(codes.InvalidArgument, fmt.Sprintf(format, args...)).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnknownStatus returns a status error with an internal
|
||||||
|
// error code
|
||||||
|
func UnknownStatus(format string, args ...any) error {
|
||||||
|
return status.New(codes.Internal, fmt.Sprintf(format, args...)).Err()
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,346 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_ParseAWSError(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
expectedStatusCode codes.Code
|
||||||
|
expectedStatusMsg string
|
||||||
|
expectedPermission *pb.Permission
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil-error",
|
||||||
|
expectedStatusCode: codes.OK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default-to-unknwon",
|
||||||
|
err: fmt.Errorf("what can this error be?"),
|
||||||
|
expectedStatusCode: codes.Unknown,
|
||||||
|
expectedStatusMsg: "aws service unknown: unknown error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "http-status-code-bad-request",
|
||||||
|
err: TestAwsHttpResponseError(http.StatusBadRequest, "bad request"),
|
||||||
|
expectedStatusCode: codes.InvalidArgument,
|
||||||
|
expectedStatusMsg: "aws service unknown: bad request error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "http-status-code-unauthorized",
|
||||||
|
err: TestAwsHttpResponseError(http.StatusUnauthorized, "unauthorized request"),
|
||||||
|
expectedStatusCode: codes.PermissionDenied,
|
||||||
|
expectedStatusMsg: "aws service unknown: invalid credentials error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_ERROR,
|
||||||
|
ErrorDetails: "unauthorized request",
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "http-status-code-forbidden",
|
||||||
|
err: TestAwsHttpResponseError(http.StatusForbidden, "forbidden request"),
|
||||||
|
expectedStatusCode: codes.PermissionDenied,
|
||||||
|
expectedStatusMsg: "aws service unknown: invalid credentials error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_ERROR,
|
||||||
|
ErrorDetails: "forbidden request",
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "http-status-code-not-found",
|
||||||
|
err: TestAwsHttpResponseError(http.StatusNotFound, "not found request"),
|
||||||
|
expectedStatusCode: codes.NotFound,
|
||||||
|
expectedStatusMsg: "aws service unknown: resource not found error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "http-status-code-too-many-requests",
|
||||||
|
err: TestAwsHttpResponseError(http.StatusTooManyRequests, "too many requests"),
|
||||||
|
expectedStatusCode: codes.Unavailable,
|
||||||
|
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "http-status-code-request-timeout",
|
||||||
|
err: TestAwsHttpResponseError(http.StatusRequestTimeout, "request timeout"),
|
||||||
|
expectedStatusCode: codes.DeadlineExceeded,
|
||||||
|
expectedStatusMsg: "aws service unknown: timeout error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "http-status-code-internal-server-error",
|
||||||
|
err: TestAwsHttpResponseError(http.StatusInternalServerError, "internal server error"),
|
||||||
|
expectedStatusCode: codes.Unavailable,
|
||||||
|
expectedStatusMsg: "aws service unknown: connectivity error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "http-status-code-bad-gateway",
|
||||||
|
err: TestAwsHttpResponseError(http.StatusBadGateway, "bad gateway"),
|
||||||
|
expectedStatusCode: codes.Unavailable,
|
||||||
|
expectedStatusMsg: "aws service unknown: connectivity error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "http-status-code-service-unavailable",
|
||||||
|
err: TestAwsHttpResponseError(http.StatusServiceUnavailable, "service unavailable"),
|
||||||
|
expectedStatusCode: codes.Unavailable,
|
||||||
|
expectedStatusMsg: "aws service unknown: connectivity error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "http-status-code-gateway-timeout",
|
||||||
|
err: TestAwsHttpResponseError(http.StatusGatewayTimeout, "gateway timeout"),
|
||||||
|
expectedStatusCode: codes.DeadlineExceeded,
|
||||||
|
expectedStatusMsg: "aws service unknown: timeout error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-throttling",
|
||||||
|
err: TestAwsError("Throttling", "throttling error"),
|
||||||
|
expectedStatusCode: codes.Unavailable,
|
||||||
|
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-throttling-exception",
|
||||||
|
err: TestAwsError("ThrottlingException", "throttling exception"),
|
||||||
|
expectedStatusCode: codes.Unavailable,
|
||||||
|
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-throttled-exception",
|
||||||
|
err: TestAwsError("ThrottledException", "throttled exception"),
|
||||||
|
expectedStatusCode: codes.Unavailable,
|
||||||
|
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-request-throttled-exception",
|
||||||
|
err: TestAwsError("RequestThrottledException", "request throttled exception"),
|
||||||
|
expectedStatusCode: codes.Unavailable,
|
||||||
|
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-request-too-many-requests-exception",
|
||||||
|
err: TestAwsError("TooManyRequestsException", "too many requests exception"),
|
||||||
|
expectedStatusCode: codes.Unavailable,
|
||||||
|
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-request-provisioned-throughput-exceeded-exception",
|
||||||
|
err: TestAwsError("ProvisionedThroughputExceededException", "provisioned throughput exceeded exception"),
|
||||||
|
expectedStatusCode: codes.Unavailable,
|
||||||
|
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-request-transaction-in-progress-exception",
|
||||||
|
err: TestAwsError("TransactionInProgressException", "transaction in progress exception"),
|
||||||
|
expectedStatusCode: codes.Unavailable,
|
||||||
|
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-request-limit-exceeded",
|
||||||
|
err: TestAwsError("RequestLimitExceeded", "request limit exceeded"),
|
||||||
|
expectedStatusCode: codes.Unavailable,
|
||||||
|
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-request-bandwidth-limit-exceeded",
|
||||||
|
err: TestAwsError("BandwidthLimitExceeded", "bandwidth limit exceeded"),
|
||||||
|
expectedStatusCode: codes.Unavailable,
|
||||||
|
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-request-limit-exceeded-exception",
|
||||||
|
err: TestAwsError("LimitExceededException", "limit exceeded exception"),
|
||||||
|
expectedStatusCode: codes.Unavailable,
|
||||||
|
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-request-throttled",
|
||||||
|
err: TestAwsError("RequestThrottled", "request throttled"),
|
||||||
|
expectedStatusCode: codes.Unavailable,
|
||||||
|
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-request-slow-down",
|
||||||
|
err: TestAwsError("SlowDown", "slow down"),
|
||||||
|
expectedStatusCode: codes.Unavailable,
|
||||||
|
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-request-prior-request-not-complete",
|
||||||
|
err: TestAwsError("PriorRequestNotComplete", "prior request not complete"),
|
||||||
|
expectedStatusCode: codes.Unavailable,
|
||||||
|
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-request-ec2-throttled-exception",
|
||||||
|
err: TestAwsError("EC2ThrottledException", "ec2 throttled exception"),
|
||||||
|
expectedStatusCode: codes.Unavailable,
|
||||||
|
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-request-timeout",
|
||||||
|
err: TestAwsError("RequestTimeout", "request timeout"),
|
||||||
|
expectedStatusCode: codes.DeadlineExceeded,
|
||||||
|
expectedStatusMsg: "aws unknown error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-request-timeout-exception",
|
||||||
|
err: TestAwsError("RequestTimeoutException", "request timeout exception"),
|
||||||
|
expectedStatusCode: codes.DeadlineExceeded,
|
||||||
|
expectedStatusMsg: "aws unknown error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-request-access-denied",
|
||||||
|
err: TestAwsError(awsErrorAccessDenied, "not authorized to perform action"),
|
||||||
|
expectedStatusCode: codes.PermissionDenied,
|
||||||
|
expectedStatusMsg: "aws service unknown: invalid credentials error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_ERROR,
|
||||||
|
ErrorDetails: "not authorized to perform action",
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-request-invalid-access-key-id",
|
||||||
|
err: TestAwsError(awsErrorInvalidAccessKeyId, "The AWS Access Key Id you provided does not exist in our records."),
|
||||||
|
expectedStatusCode: codes.PermissionDenied,
|
||||||
|
expectedStatusMsg: "aws service unknown: invalid credentials error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_ERROR,
|
||||||
|
ErrorDetails: "The AWS Access Key Id you provided does not exist in our records.",
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-request-expired-token",
|
||||||
|
err: TestAwsError(awsErrorExpiredToken, "The security token included in the request is expired."),
|
||||||
|
expectedStatusCode: codes.PermissionDenied,
|
||||||
|
expectedStatusMsg: "aws service unknown: invalid credentials error: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_ERROR,
|
||||||
|
ErrorDetails: "The security token included in the request is expired.",
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range cases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
require, assert := require.New(t), assert.New(t)
|
||||||
|
|
||||||
|
actualStatus, permission := ParseAWSError(tt.err, "test")
|
||||||
|
if tt.expectedStatusCode == codes.OK {
|
||||||
|
require.Nil(actualStatus)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(tt.expectedStatusCode, actualStatus.Code())
|
||||||
|
require.Equal(tt.expectedStatusMsg, actualStatus.Message())
|
||||||
|
require.Len(actualStatus.Details(), 0)
|
||||||
|
CheckSimilarPermission(assert, tt.expectedPermission, permission, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_BadRequestStatus(t *testing.T) {
|
||||||
|
require, assert := require.New(t), assert.New(t)
|
||||||
|
err := BadRequestStatus("test: %s", "hello world")
|
||||||
|
assert.ErrorContains(err, "test: hello world")
|
||||||
|
assert.Equal(status.Code(err), codes.InvalidArgument)
|
||||||
|
_, ok := status.FromError(err)
|
||||||
|
require.True(ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_UnknownStatus(t *testing.T) {
|
||||||
|
require, assert := require.New(t), assert.New(t)
|
||||||
|
err := UnknownStatus("test: %s", "hello world")
|
||||||
|
assert.ErrorContains(err, "test: hello world")
|
||||||
|
assert.Equal(status.Code(err), codes.Internal)
|
||||||
|
_, ok := status.FromError(err)
|
||||||
|
require.True(ok)
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
|
||||||
|
"github.com/aws/smithy-go"
|
||||||
|
smithyhttp "github.com/aws/smithy-go/transport/http"
|
||||||
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestAwsHttpResponseError returns an aws http response error
|
||||||
|
func TestAwsHttpResponseError(code int, msg string) error {
|
||||||
|
return &awshttp.ResponseError{
|
||||||
|
ResponseError: &smithyhttp.ResponseError{
|
||||||
|
Response: &smithyhttp.Response{
|
||||||
|
Response: &http.Response{
|
||||||
|
StatusCode: code,
|
||||||
|
Body: io.NopCloser(strings.NewReader(msg)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAwsError returns an generic api error
|
||||||
|
func TestAwsError(code, msg string) error {
|
||||||
|
return &smithy.GenericAPIError{
|
||||||
|
Code: code,
|
||||||
|
Message: msg,
|
||||||
|
Fault: smithy.FaultServer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckSimilarPermission(assert *assert.Assertions, expected, actual *pb.Permission, substituteNilWithDefaultOk bool) {
|
||||||
|
if expected == nil && substituteNilWithDefaultOk {
|
||||||
|
expected = &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_OK,
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected != nil {
|
||||||
|
assert.NotNil(actual)
|
||||||
|
if actual != nil {
|
||||||
|
assert.Equal(expected.State, actual.State, "StateType mismatch")
|
||||||
|
assert.Equal(expected.ErrorDetails, actual.ErrorDetails)
|
||||||
|
assert.NotNil(actual.CheckedAt)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert.Equal(expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -132,7 +132,7 @@ func TestPluginOnCreateCatalogErr(t *testing.T) {
|
|||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
expectedErrContains: fmt.Sprintf("error during credential rotation: error rotating credentials: error calling CreateAccessKey: error calling iam.GetUser: %s", testGetUserErr),
|
expectedErrContains: "aws service unknown: unknown error: error rotating credentials",
|
||||||
expectedErrCode: codes.InvalidArgument,
|
expectedErrCode: codes.InvalidArgument,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -163,7 +163,7 @@ func TestPluginOnCreateCatalogErr(t *testing.T) {
|
|||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
expectedErrContains: fmt.Sprintf("error during credential validation: error validating credentials: %s", testGetCallerIdentityErr),
|
expectedErrContains: "aws service unknown: unknown error: validating credentials",
|
||||||
expectedErrCode: codes.InvalidArgument,
|
expectedErrCode: codes.InvalidArgument,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -342,7 +342,7 @@ func TestPluginOnUpdateCatalogErr(t *testing.T) {
|
|||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
expectedErrContains: fmt.Sprintf("error attempting to replace credentials: error deleting old access key: %s", testDeleteAccessKeyErr),
|
expectedErrContains: "aws service unknown: unknown error: failed to delete access key",
|
||||||
expectedErrCode: codes.InvalidArgument,
|
expectedErrCode: codes.InvalidArgument,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -375,7 +375,7 @@ func TestPluginOnUpdateCatalogErr(t *testing.T) {
|
|||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
expectedErrContains: fmt.Sprintf("error during credential rotation: error rotating credentials: error calling CreateAccessKey: error calling iam.GetUser: %s", testGetUserErr),
|
expectedErrContains: "aws service unknown: unknown error: error rotating credentials",
|
||||||
expectedErrCode: codes.InvalidArgument,
|
expectedErrCode: codes.InvalidArgument,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -466,7 +466,7 @@ func TestPluginOnDeleteCatalogErr(t *testing.T) {
|
|||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
expectedErrContains: fmt.Sprintf("error removing rotated credentials during catalog deletion: error deleting old access key: %s", testDeleteAccessKeyErr),
|
expectedErrContains: "aws service unknown: unknown error: failed to delete access key",
|
||||||
expectedErrCode: codes.Aborted,
|
expectedErrCode: codes.Aborted,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
cred "github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
cred "github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
||||||
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
@@ -19,6 +21,7 @@ func TestGetStorageAttributes(t *testing.T) {
|
|||||||
in *structpb.Struct
|
in *structpb.Struct
|
||||||
expected *StorageAttributes
|
expected *StorageAttributes
|
||||||
expectedErrContains string
|
expectedErrContains string
|
||||||
|
expectedDetails *pb.StorageBucketCredentialState
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "missing region",
|
name: "missing region",
|
||||||
@@ -100,13 +103,17 @@ func TestGetStorageAttributes(t *testing.T) {
|
|||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
tc := tc
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
require := require.New(t)
|
require, assert := require.New(t), assert.New(t)
|
||||||
|
|
||||||
actual, err := getStorageAttributes(tc.in)
|
actual, err := getStorageAttributes(tc.in)
|
||||||
if tc.expectedErrContains != "" {
|
if tc.expectedErrContains != "" {
|
||||||
require.Error(err)
|
require.Error(err)
|
||||||
require.Contains(err.Error(), tc.expectedErrContains)
|
require.Contains(err.Error(), tc.expectedErrContains)
|
||||||
require.Equal(status.Code(err), codes.InvalidArgument)
|
require.Equal(codes.InvalidArgument, status.Code(err))
|
||||||
|
|
||||||
|
st, ok := status.FromError(err)
|
||||||
|
require.True(ok)
|
||||||
|
assert.Len(st.Details(), 0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/boundary-plugin-aws/internal/errors"
|
||||||
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
s3StatusErrMsg = "aws s3 error: %s"
|
||||||
|
|
||||||
|
// s3ErrorBadDigest is returned when there is a checksum
|
||||||
|
// mismatch between the request and the s3 service. This
|
||||||
|
// error code is returned for the PutObject request.
|
||||||
|
s3ErrorBadDigest = "BadDigest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setStatePermission(req any, permission *pb.Permission) *pb.StorageBucketCredentialState {
|
||||||
|
state := &pb.StorageBucketCredentialState{
|
||||||
|
State: &pb.Permissions{},
|
||||||
|
}
|
||||||
|
switch req.(type) {
|
||||||
|
case pb.HeadObjectRequest:
|
||||||
|
state.State.Read = permission
|
||||||
|
case *pb.HeadObjectRequest:
|
||||||
|
state.State.Read = permission
|
||||||
|
case pb.GetObjectRequest:
|
||||||
|
state.State.Read = permission
|
||||||
|
case *pb.GetObjectRequest:
|
||||||
|
state.State.Read = permission
|
||||||
|
case pb.PutObjectRequest:
|
||||||
|
state.State.Write = permission
|
||||||
|
case *pb.PutObjectRequest:
|
||||||
|
state.State.Write = permission
|
||||||
|
case pb.DeleteObjectsRequest:
|
||||||
|
state.State.Delete = permission
|
||||||
|
case *pb.DeleteObjectsRequest:
|
||||||
|
state.State.Delete = permission
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseS3Error will convert an S3 api error into a RPC status. If the
|
||||||
|
// error does not match against an s3 error type, it will fallback to
|
||||||
|
// evaluating against generic aws service errors. This includes throttling,
|
||||||
|
// credentials, and connectivity error types.
|
||||||
|
//
|
||||||
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
|
||||||
|
func parseS3Error(err error, msg string, req any) *status.Status {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
st, p := errors.ParseAWSError(err, msg)
|
||||||
|
if p != nil {
|
||||||
|
state := setStatePermission(req, p)
|
||||||
|
if st, err = st.WithDetails(state); err != nil {
|
||||||
|
st = status.New(codes.Internal, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return st
|
||||||
|
}
|
||||||
|
|
||||||
|
func checksumMistmatchStatus() error {
|
||||||
|
return status.New(codes.Aborted, "mismatched checksum").Err()
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/boundary-plugin-aws/internal/errors"
|
||||||
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_ParseS3Error(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
req any
|
||||||
|
err error
|
||||||
|
expectedStatusCode codes.Code
|
||||||
|
expectedStatusMsg string
|
||||||
|
expectedDetails *pb.StorageBucketCredentialState
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil-error",
|
||||||
|
expectedStatusCode: codes.OK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no-such-bucket",
|
||||||
|
req: &pb.GetObjectRequest{},
|
||||||
|
err: TestAwsS3Error("NoSuchBucket", "GetObject", "no such bucket"),
|
||||||
|
expectedStatusCode: codes.NotFound,
|
||||||
|
expectedStatusMsg: "aws s3 error: test",
|
||||||
|
expectedDetails: &pb.StorageBucketCredentialState{
|
||||||
|
State: &pb.Permissions{
|
||||||
|
Read: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_ERROR,
|
||||||
|
ErrorDetails: "no such bucket",
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no-such-key",
|
||||||
|
req: &pb.GetObjectRequest{},
|
||||||
|
err: TestAwsS3Error("NoSuchKey", "GetObject", "no such key"),
|
||||||
|
expectedStatusCode: codes.NotFound,
|
||||||
|
expectedStatusMsg: "aws s3 error: test",
|
||||||
|
expectedDetails: &pb.StorageBucketCredentialState{
|
||||||
|
State: &pb.Permissions{
|
||||||
|
Read: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not-found",
|
||||||
|
req: &pb.GetObjectRequest{},
|
||||||
|
err: errors.TestAwsHttpResponseError(404, "not found"),
|
||||||
|
expectedStatusCode: codes.NotFound,
|
||||||
|
// cannot embed services within mock http errors, therefore unknown service
|
||||||
|
expectedStatusMsg: "aws service unknown: resource not found error: test",
|
||||||
|
expectedDetails: &pb.StorageBucketCredentialState{
|
||||||
|
State: &pb.Permissions{
|
||||||
|
Read: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid-object-state",
|
||||||
|
req: &pb.GetObjectRequest{},
|
||||||
|
err: TestAwsS3Error("InvalidObjectState", "GetObject", "invalid object state"),
|
||||||
|
expectedStatusCode: codes.NotFound,
|
||||||
|
expectedStatusMsg: "aws s3 error: test",
|
||||||
|
expectedDetails: &pb.StorageBucketCredentialState{
|
||||||
|
State: &pb.Permissions{
|
||||||
|
Read: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad-digest",
|
||||||
|
req: &pb.PutObjectRequest{},
|
||||||
|
err: TestAwsS3Error(s3ErrorBadDigest, "PutObject", "checksum mismatch"),
|
||||||
|
expectedStatusCode: codes.Aborted,
|
||||||
|
expectedStatusMsg: "aws s3 error: test",
|
||||||
|
expectedDetails: &pb.StorageBucketCredentialState{
|
||||||
|
State: &pb.Permissions{
|
||||||
|
Write: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fallback-throttle-error",
|
||||||
|
req: &pb.GetObjectRequest{},
|
||||||
|
err: TestAwsS3Error("Throttling", "action", "throttling exception"),
|
||||||
|
expectedStatusCode: codes.Unavailable,
|
||||||
|
expectedStatusMsg: "aws service s3: throttling error: test",
|
||||||
|
expectedDetails: &pb.StorageBucketCredentialState{
|
||||||
|
State: &pb.Permissions{
|
||||||
|
Read: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fallback-connectivity-error",
|
||||||
|
req: &pb.GetObjectRequest{},
|
||||||
|
err: TestAwsS3Error("RequestTimeout", "action", "request timeout"),
|
||||||
|
expectedStatusCode: codes.DeadlineExceeded,
|
||||||
|
expectedStatusMsg: "aws s3 error: test",
|
||||||
|
expectedDetails: &pb.StorageBucketCredentialState{
|
||||||
|
State: &pb.Permissions{
|
||||||
|
Read: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fallback-credentials-error",
|
||||||
|
req: &pb.GetObjectRequest{},
|
||||||
|
err: TestAwsS3Error("AccessDenied", "action", "access denied"),
|
||||||
|
expectedStatusCode: codes.PermissionDenied,
|
||||||
|
expectedStatusMsg: "aws service s3: invalid credentials error: test",
|
||||||
|
expectedDetails: &pb.StorageBucketCredentialState{
|
||||||
|
State: &pb.Permissions{
|
||||||
|
Read: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_ERROR,
|
||||||
|
ErrorDetails: "access denied",
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range cases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
require, assert := require.New(t), assert.New(t)
|
||||||
|
|
||||||
|
actualStatus := parseS3Error(tt.err, "test", tt.req)
|
||||||
|
if tt.expectedStatusCode == codes.OK {
|
||||||
|
require.Nil(actualStatus)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(tt.expectedStatusCode, actualStatus.Code())
|
||||||
|
require.Equal(tt.expectedStatusMsg, actualStatus.Message())
|
||||||
|
require.Len(actualStatus.Details(), 1)
|
||||||
|
|
||||||
|
foundDetail := false
|
||||||
|
for _, detail := range actualStatus.Details() {
|
||||||
|
switch errDetail := detail.(type) {
|
||||||
|
case *pb.StorageBucketCredentialState:
|
||||||
|
foundDetail = true
|
||||||
|
errors.CheckSimilarPermission(assert, tt.expectedDetails.State.Read, errDetail.State.Read, false)
|
||||||
|
errors.CheckSimilarPermission(assert, tt.expectedDetails.State.Write, errDetail.State.Write, false)
|
||||||
|
errors.CheckSimilarPermission(assert, tt.expectedDetails.State.Delete, errDetail.State.Delete, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.True(foundDetail, "did not find error details with status")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+128
-111
@@ -9,19 +9,21 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
"github.com/aws/aws-sdk-go-v2/aws/retry"
|
|
||||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
cred "github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
cred "github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
||||||
|
"github.com/hashicorp/boundary-plugin-aws/internal/errors"
|
||||||
"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"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
@@ -46,15 +48,15 @@ type StoragePlugin struct {
|
|||||||
func (p *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCreateStorageBucketRequest) (*pb.OnCreateStorageBucketResponse, error) {
|
func (p *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCreateStorageBucketRequest) (*pb.OnCreateStorageBucketResponse, error) {
|
||||||
bucket := req.GetBucket()
|
bucket := req.GetBucket()
|
||||||
if bucket == nil {
|
if bucket == nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, "bucket is required")
|
return nil, errors.BadRequestStatus("bucket is required")
|
||||||
}
|
}
|
||||||
if bucket.BucketName == "" {
|
if bucket.BucketName == "" {
|
||||||
return nil, status.Error(codes.InvalidArgument, "bucketName is required")
|
return nil, errors.BadRequestStatus("bucketName is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
attrs := bucket.GetAttributes()
|
attrs := bucket.GetAttributes()
|
||||||
if attrs == nil {
|
if attrs == nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, "attributes is required")
|
return nil, errors.BadRequestStatus("attributes is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
storageAttributes, err := getStorageAttributes(attrs)
|
storageAttributes, err := getStorageAttributes(attrs)
|
||||||
@@ -74,13 +76,13 @@ func (p *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCre
|
|||||||
}, p.testCredStateOpts...)...,
|
}, p.testCredStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error setting up persisted state: %s", err)
|
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to rotate AWS static credentials
|
// Try to rotate AWS static credentials
|
||||||
if cred.GetCredentialType(credConfig) == cred.StaticAWS && !storageAttributes.DisableCredentialRotation {
|
if cred.GetCredentialType(credConfig) == cred.StaticAWS && !storageAttributes.DisableCredentialRotation {
|
||||||
if err := credState.RotateCreds(ctx); err != nil {
|
if err := credState.RotateCreds(ctx); err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error during credential rotation: %s", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,17 +92,17 @@ func (p *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCre
|
|||||||
}, p.testStorageStateOpts...)...,
|
}, p.testStorageStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error setting up persisted state: %s", err)
|
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// perform dry run to ensure we can interact with the bucket as expected.
|
// perform dry run to ensure we can interact with the bucket as expected.
|
||||||
if err := dryRunValidation(ctx, storageState, storageAttributes, bucket); err != nil {
|
if st := dryRunValidation(ctx, storageState, storageAttributes, bucket); st != nil {
|
||||||
return nil, err
|
return nil, st.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
persistedProto, err := storageState.toProto()
|
persistedProto, err := storageState.toProto()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
return nil, errors.BadRequestStatus(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return &pb.OnCreateStorageBucketResponse{
|
return &pb.OnCreateStorageBucketResponse{
|
||||||
@@ -115,19 +117,19 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
|
|||||||
// fetching the new copy to keep things a little less messy.
|
// fetching the new copy to keep things a little less messy.
|
||||||
newBucket := req.GetNewBucket()
|
newBucket := req.GetNewBucket()
|
||||||
if newBucket == nil {
|
if newBucket == nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, "new bucket is required")
|
return nil, errors.BadRequestStatus("new bucket is required")
|
||||||
}
|
}
|
||||||
if newBucket.BucketName == "" {
|
if newBucket.BucketName == "" {
|
||||||
return nil, status.Error(codes.InvalidArgument, "new bucketName is required")
|
return nil, errors.BadRequestStatus("new bucketName is required")
|
||||||
}
|
}
|
||||||
currentBucket := req.GetCurrentBucket()
|
currentBucket := req.GetCurrentBucket()
|
||||||
if currentBucket == nil {
|
if currentBucket == nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, "current bucket is required")
|
return nil, errors.BadRequestStatus("current bucket is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
oldAttrs := currentBucket.GetAttributes()
|
oldAttrs := currentBucket.GetAttributes()
|
||||||
if oldAttrs == nil {
|
if oldAttrs == nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, "current bucket attributes is required")
|
return nil, errors.BadRequestStatus("current bucket attributes is required")
|
||||||
}
|
}
|
||||||
oldStorageAttributes, err := getStorageAttributes(oldAttrs)
|
oldStorageAttributes, err := getStorageAttributes(oldAttrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -135,7 +137,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
|
|||||||
}
|
}
|
||||||
newAttrs := newBucket.GetAttributes()
|
newAttrs := newBucket.GetAttributes()
|
||||||
if newAttrs == nil {
|
if newAttrs == nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, "new bucket attributes is required")
|
return nil, errors.BadRequestStatus("new bucket attributes is required")
|
||||||
}
|
}
|
||||||
newStorageAttributes, err := getStorageAttributes(newAttrs)
|
newStorageAttributes, err := getStorageAttributes(newAttrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -147,7 +149,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
|
|||||||
oldStorageAttributes.CredentialAttributes,
|
oldStorageAttributes.CredentialAttributes,
|
||||||
p.testCredStateOpts...)
|
p.testCredStateOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error loading persisted state: %s", err)
|
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the incoming credentials are valid and return any errors to the
|
// Verify the incoming credentials are valid and return any errors to the
|
||||||
@@ -170,7 +172,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
|
|||||||
}, p.testCredStateOpts...)...,
|
}, p.testCredStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error setting up new credential persisted state: %s", err)
|
return nil, errors.BadRequestStatus("error setting up new credential persisted state: %s", err)
|
||||||
}
|
}
|
||||||
newStorageState, err := newAwsStoragePersistedState(
|
newStorageState, err := newAwsStoragePersistedState(
|
||||||
append([]awsStoragePersistedStateOption{
|
append([]awsStoragePersistedStateOption{
|
||||||
@@ -178,10 +180,10 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
|
|||||||
}, p.testStorageStateOpts...)...,
|
}, p.testStorageStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error loading persisted state: %s", err)
|
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
||||||
}
|
}
|
||||||
if err := dryRunValidation(ctx, newStorageState, newStorageAttributes, newBucket); err != nil {
|
if st := dryRunValidation(ctx, newStorageState, newStorageAttributes, newBucket); st != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "failed to validate incoming credentials: %s", err)
|
return nil, st.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the existing credential state.
|
// Replace the existing credential state.
|
||||||
@@ -189,7 +191,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
|
|||||||
// and deletes the credentials if we are managing them
|
// and deletes the credentials if we are managing them
|
||||||
// (ie: if we've rotated them before).
|
// (ie: if we've rotated them before).
|
||||||
if err := credState.ReplaceCreds(ctx, updatedCredentials); err != nil {
|
if err := credState.ReplaceCreds(ctx, updatedCredentials); err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error attempting to replace credentials: %s", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,7 +201,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
|
|||||||
// This is not allowed.
|
// This is not allowed.
|
||||||
if newStorageAttributes.DisableCredentialRotation && newBucket.GetSecrets() == nil {
|
if newStorageAttributes.DisableCredentialRotation && newBucket.GetSecrets() == nil {
|
||||||
if !credState.CredsLastRotatedTime.IsZero() {
|
if !credState.CredsLastRotatedTime.IsZero() {
|
||||||
return nil, status.Error(codes.FailedPrecondition, "cannot disable rotation for already-rotated credentials")
|
return nil, errors.BadRequestStatus("cannot disable rotation for already-rotated credentials")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +209,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
|
|||||||
// freshly replaced credentials, we can rotate here.
|
// freshly replaced credentials, we can rotate here.
|
||||||
if !newStorageAttributes.DisableCredentialRotation && credState.CredsLastRotatedTime.IsZero() {
|
if !newStorageAttributes.DisableCredentialRotation && credState.CredsLastRotatedTime.IsZero() {
|
||||||
if err := credState.RotateCreds(ctx); err != nil {
|
if err := credState.RotateCreds(ctx); err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error during credential rotation: %s", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,17 +220,17 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
|
|||||||
}, p.testStorageStateOpts...)...,
|
}, p.testStorageStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error loading persisted state: %s", err)
|
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// perform dry run to ensure we can interact with the bucket as expected.
|
// perform dry run to ensure we can interact with the bucket as expected.
|
||||||
if err := dryRunValidation(ctx, storageState, newStorageAttributes, newBucket); err != nil {
|
if st := dryRunValidation(ctx, storageState, newStorageAttributes, newBucket); st != nil {
|
||||||
return nil, err
|
return nil, st.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
persistedProto, err := storageState.toProto()
|
persistedProto, err := storageState.toProto()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
return nil, errors.BadRequestStatus(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return &pb.OnUpdateStorageBucketResponse{
|
return &pb.OnUpdateStorageBucketResponse{
|
||||||
@@ -240,12 +242,12 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
|
|||||||
func (p *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDeleteStorageBucketRequest) (*pb.OnDeleteStorageBucketResponse, error) {
|
func (p *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDeleteStorageBucketRequest) (*pb.OnDeleteStorageBucketResponse, error) {
|
||||||
bucket := req.GetBucket()
|
bucket := req.GetBucket()
|
||||||
if bucket == nil {
|
if bucket == nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, "bucket is required")
|
return nil, errors.BadRequestStatus("bucket is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
attrs := bucket.GetAttributes()
|
attrs := bucket.GetAttributes()
|
||||||
if attrs == nil {
|
if attrs == nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, "attributes is required")
|
return nil, errors.BadRequestStatus("attributes is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
storageAttributes, err := getStorageAttributes(attrs)
|
storageAttributes, err := getStorageAttributes(attrs)
|
||||||
@@ -264,7 +266,7 @@ func (p *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDel
|
|||||||
storageAttributes.CredentialAttributes,
|
storageAttributes.CredentialAttributes,
|
||||||
p.testCredStateOpts...)
|
p.testCredStateOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error loading persisted state: %s", err)
|
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = newAwsStoragePersistedState(
|
_, err = newAwsStoragePersistedState(
|
||||||
@@ -273,7 +275,7 @@ func (p *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDel
|
|||||||
}, p.testStorageStateOpts...)...,
|
}, p.testStorageStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error loading persisted state: %s", err)
|
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to delete static credentials
|
// try to delete static credentials
|
||||||
@@ -283,7 +285,7 @@ func (p *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDel
|
|||||||
// credentials to ensure that it has the proper permissions to do
|
// credentials to ensure that it has the proper permissions to do
|
||||||
// it.
|
// it.
|
||||||
if err := credState.DeleteCreds(ctx); err != nil {
|
if err := credState.DeleteCreds(ctx); err != nil {
|
||||||
return nil, status.Errorf(codes.Aborted, "error removing rotated credentials during storage bucket deletion: %s", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -294,21 +296,21 @@ func (p *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDel
|
|||||||
// HeadObject is called to get the metadata of an object.
|
// HeadObject is called to get the metadata of an object.
|
||||||
func (p *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectRequest) (*pb.HeadObjectResponse, error) {
|
func (p *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectRequest) (*pb.HeadObjectResponse, error) {
|
||||||
if req.GetKey() == "" {
|
if req.GetKey() == "" {
|
||||||
return nil, status.Error(codes.InvalidArgument, "key is required")
|
return nil, errors.BadRequestStatus("key is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
bucket := req.GetBucket()
|
bucket := req.GetBucket()
|
||||||
if bucket == nil {
|
if bucket == nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, "bucket is required")
|
return nil, errors.BadRequestStatus("bucket is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if bucket.BucketName == "" {
|
if bucket.BucketName == "" {
|
||||||
return nil, status.Error(codes.InvalidArgument, "bucketName is required")
|
return nil, errors.BadRequestStatus("bucketName is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
attrs := bucket.GetAttributes()
|
attrs := bucket.GetAttributes()
|
||||||
if attrs == nil {
|
if attrs == nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, "attributes is required")
|
return nil, errors.BadRequestStatus("attributes is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
storageAttributes, err := getStorageAttributes(attrs)
|
storageAttributes, err := getStorageAttributes(attrs)
|
||||||
@@ -328,7 +330,7 @@ func (p *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReques
|
|||||||
}, p.testCredStateOpts...)...,
|
}, p.testCredStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error setting up persisted state: %s", err)
|
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
storageState, err := newAwsStoragePersistedState(
|
storageState, err := newAwsStoragePersistedState(
|
||||||
@@ -337,7 +339,7 @@ func (p *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReques
|
|||||||
}, p.testStorageStateOpts...)...,
|
}, p.testStorageStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error setting up persisted state: %s", err)
|
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := []s3Option{}
|
opts := []s3Option{}
|
||||||
@@ -346,7 +348,7 @@ func (p *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReques
|
|||||||
}
|
}
|
||||||
s3Client, err := storageState.S3Client(ctx, opts...)
|
s3Client, err := storageState.S3Client(ctx, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error getting S3 client: %s", err)
|
return nil, errors.BadRequestStatus("error getting S3 client: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
objectKey := path.Join(bucket.GetBucketPrefix(), req.GetKey())
|
objectKey := path.Join(bucket.GetBucketPrefix(), req.GetKey())
|
||||||
@@ -355,7 +357,7 @@ func (p *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReques
|
|||||||
Key: aws.String(objectKey),
|
Key: aws.String(objectKey),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, parseAWSErrCode("error getting head object from s3", err)
|
return nil, parseS3Error(err, "failed to head object", req).Err()
|
||||||
}
|
}
|
||||||
return &pb.HeadObjectResponse{
|
return &pb.HeadObjectResponse{
|
||||||
ContentLength: resp.ContentLength,
|
ContentLength: resp.ContentLength,
|
||||||
@@ -367,16 +369,16 @@ func (p *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReques
|
|||||||
func (p *StoragePlugin) ValidatePermissions(ctx context.Context, req *pb.ValidatePermissionsRequest) (*pb.ValidatePermissionsResponse, error) {
|
func (p *StoragePlugin) ValidatePermissions(ctx context.Context, req *pb.ValidatePermissionsRequest) (*pb.ValidatePermissionsResponse, error) {
|
||||||
bucket := req.GetBucket()
|
bucket := req.GetBucket()
|
||||||
if bucket == nil {
|
if bucket == nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, "bucket is required")
|
return nil, errors.BadRequestStatus("bucket is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if bucket.BucketName == "" {
|
if bucket.BucketName == "" {
|
||||||
return nil, status.Error(codes.InvalidArgument, "bucketName is required")
|
return nil, errors.BadRequestStatus("bucketName is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
attrs := bucket.GetAttributes()
|
attrs := bucket.GetAttributes()
|
||||||
if attrs == nil {
|
if attrs == nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, "attributes is required")
|
return nil, errors.BadRequestStatus("attributes is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
storageAttributes, err := getStorageAttributes(attrs)
|
storageAttributes, err := getStorageAttributes(attrs)
|
||||||
@@ -396,7 +398,7 @@ func (p *StoragePlugin) ValidatePermissions(ctx context.Context, req *pb.Validat
|
|||||||
}, p.testCredStateOpts...)...,
|
}, p.testCredStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error setting up persisted state: %s", err)
|
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
storageState, err := newAwsStoragePersistedState(
|
storageState, err := newAwsStoragePersistedState(
|
||||||
@@ -405,12 +407,12 @@ func (p *StoragePlugin) ValidatePermissions(ctx context.Context, req *pb.Validat
|
|||||||
}, p.testStorageStateOpts...)...,
|
}, p.testStorageStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error setting up persisted state: %s", err)
|
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// perform dry run to ensure we can interact with the bucket as expected.
|
// perform dry run to ensure we can interact with the bucket as expected.
|
||||||
if err := dryRunValidation(ctx, storageState, storageAttributes, bucket); err != nil {
|
if st := dryRunValidation(ctx, storageState, storageAttributes, bucket); st != nil {
|
||||||
return nil, err
|
return nil, st.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
return &pb.ValidatePermissionsResponse{}, nil
|
return &pb.ValidatePermissionsResponse{}, nil
|
||||||
@@ -420,21 +422,21 @@ func (p *StoragePlugin) ValidatePermissions(ctx context.Context, req *pb.Validat
|
|||||||
// GetObject is a blocking call until the stream has been recieved in full.
|
// GetObject is a blocking call until the stream has been recieved in full.
|
||||||
func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePluginService_GetObjectServer) error {
|
func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePluginService_GetObjectServer) error {
|
||||||
if req.GetKey() == "" {
|
if req.GetKey() == "" {
|
||||||
return status.Error(codes.InvalidArgument, "key is required")
|
return errors.BadRequestStatus("key is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
bucket := req.GetBucket()
|
bucket := req.GetBucket()
|
||||||
if bucket == nil {
|
if bucket == nil {
|
||||||
return status.Error(codes.InvalidArgument, "bucket is required")
|
return errors.BadRequestStatus("bucket is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if bucket.GetBucketName() == "" {
|
if bucket.GetBucketName() == "" {
|
||||||
return status.Error(codes.InvalidArgument, "bucketName is required")
|
return errors.BadRequestStatus("bucketName is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
attrs := bucket.GetAttributes()
|
attrs := bucket.GetAttributes()
|
||||||
if attrs == nil {
|
if attrs == nil {
|
||||||
return status.Error(codes.InvalidArgument, "attributes is required")
|
return errors.BadRequestStatus("attributes is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
storageAttributes, err := getStorageAttributes(attrs)
|
storageAttributes, err := getStorageAttributes(attrs)
|
||||||
@@ -454,7 +456,7 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu
|
|||||||
}, p.testCredStateOpts...)...,
|
}, p.testCredStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.Errorf(codes.InvalidArgument, "error setting up persisted state: %s", err)
|
return errors.BadRequestStatus("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
storageState, err := newAwsStoragePersistedState(
|
storageState, err := newAwsStoragePersistedState(
|
||||||
@@ -463,7 +465,7 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu
|
|||||||
}, p.testStorageStateOpts...)...,
|
}, p.testStorageStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.Errorf(codes.InvalidArgument, "error setting up persisted state: %s", err)
|
return errors.BadRequestStatus("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := []s3Option{}
|
opts := []s3Option{}
|
||||||
@@ -472,7 +474,7 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu
|
|||||||
}
|
}
|
||||||
s3Client, err := storageState.S3Client(stream.Context(), opts...)
|
s3Client, err := storageState.S3Client(stream.Context(), opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.Errorf(codes.InvalidArgument, "error getting S3 client: %s", err)
|
return errors.BadRequestStatus("error getting S3 client: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
objectKey := path.Join(bucket.GetBucketPrefix(), req.GetKey())
|
objectKey := path.Join(bucket.GetBucketPrefix(), req.GetKey())
|
||||||
@@ -481,7 +483,7 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu
|
|||||||
Key: aws.String(objectKey),
|
Key: aws.String(objectKey),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return parseAWSErrCode("error getting object from s3", err)
|
return parseS3Error(err, "failed to get object", req).Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
@@ -494,13 +496,13 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu
|
|||||||
buffer := make([]byte, chunkSize)
|
buffer := make([]byte, chunkSize)
|
||||||
n, err := reader.Read(buffer)
|
n, err := reader.Read(buffer)
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
return status.Errorf(codes.Internal, "error reading chunk from s3: %s", err)
|
return errors.UnknownStatus("error reading chunk from s3: %s", err)
|
||||||
}
|
}
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
if err := stream.Send(&pb.GetObjectResponse{
|
if err := stream.Send(&pb.GetObjectResponse{
|
||||||
FileChunk: buffer[:n],
|
FileChunk: buffer[:n],
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return status.Errorf(codes.Internal, "error sending chunk to client: %s", err)
|
return errors.UnknownStatus("error sending chunk to client: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
@@ -514,38 +516,38 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu
|
|||||||
// PutObject is called when putting objects into an s3 bucket.
|
// PutObject is called when putting objects into an s3 bucket.
|
||||||
func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest) (*pb.PutObjectResponse, error) {
|
func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest) (*pb.PutObjectResponse, error) {
|
||||||
if req == nil {
|
if req == nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "request is required")
|
return nil, errors.BadRequestStatus("request is required")
|
||||||
}
|
}
|
||||||
if req.GetKey() == "" {
|
if req.GetKey() == "" {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "key is required")
|
return nil, errors.BadRequestStatus("key is required")
|
||||||
}
|
}
|
||||||
bucket := req.GetBucket()
|
bucket := req.GetBucket()
|
||||||
if bucket == nil {
|
if bucket == nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, "bucket is required")
|
return nil, errors.BadRequestStatus("bucket is required")
|
||||||
}
|
}
|
||||||
if bucket.GetBucketName() == "" {
|
if bucket.GetBucketName() == "" {
|
||||||
return nil, status.Error(codes.InvalidArgument, "bucketName is required")
|
return nil, errors.BadRequestStatus("bucketName is required")
|
||||||
}
|
}
|
||||||
attrs := bucket.GetAttributes()
|
attrs := bucket.GetAttributes()
|
||||||
if attrs == nil {
|
if attrs == nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, "attributes is required")
|
return nil, errors.BadRequestStatus("attributes is required")
|
||||||
}
|
}
|
||||||
if req.GetPath() == "" {
|
if req.GetPath() == "" {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "path is required")
|
return nil, errors.BadRequestStatus("path is required")
|
||||||
}
|
}
|
||||||
file, err := os.Open(req.GetPath())
|
file, err := os.Open(req.GetPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "failed to open file")
|
return nil, errors.BadRequestStatus("failed to open file")
|
||||||
}
|
}
|
||||||
info, err := file.Stat()
|
info, err := file.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "failed to read file info")
|
return nil, errors.BadRequestStatus("failed to read file info")
|
||||||
}
|
}
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "path is not a file")
|
return nil, errors.BadRequestStatus("path is not a file")
|
||||||
}
|
}
|
||||||
if info.Size() == 0 {
|
if info.Size() == 0 {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "file is empty")
|
return nil, errors.BadRequestStatus("file is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
storageAttributes, err := getStorageAttributes(attrs)
|
storageAttributes, err := getStorageAttributes(attrs)
|
||||||
@@ -565,7 +567,7 @@ func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest)
|
|||||||
}, p.testCredStateOpts...)...,
|
}, p.testCredStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error setting up persisted state: %s", err)
|
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
storageState, err := newAwsStoragePersistedState(
|
storageState, err := newAwsStoragePersistedState(
|
||||||
@@ -574,7 +576,7 @@ func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest)
|
|||||||
}, p.testStorageStateOpts...)...,
|
}, p.testStorageStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error setting up persisted state: %s", err)
|
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := []s3Option{}
|
opts := []s3Option{}
|
||||||
@@ -583,18 +585,18 @@ func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest)
|
|||||||
}
|
}
|
||||||
s3Client, err := storageState.S3Client(ctx, opts...)
|
s3Client, err := storageState.S3Client(ctx, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error getting S3 client: %s", err)
|
return nil, errors.BadRequestStatus("error getting S3 client: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hash := sha256.New()
|
hash := sha256.New()
|
||||||
if _, err := io.Copy(hash, file); err != nil {
|
if _, err := io.Copy(hash, file); err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "failed to calcualte hash")
|
return nil, errors.UnknownStatus("failed to calcualte hash")
|
||||||
}
|
}
|
||||||
checksum := base64.StdEncoding.EncodeToString(hash.Sum(nil))
|
checksum := base64.StdEncoding.EncodeToString(hash.Sum(nil))
|
||||||
|
|
||||||
_, err = file.Seek(0, io.SeekStart)
|
_, err = file.Seek(0, io.SeekStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "failed to rewind file pointer")
|
return nil, errors.UnknownStatus("failed to rewind file pointer")
|
||||||
}
|
}
|
||||||
|
|
||||||
objectKey := path.Join(bucket.GetBucketPrefix(), req.GetKey())
|
objectKey := path.Join(bucket.GetBucketPrefix(), req.GetKey())
|
||||||
@@ -606,17 +608,17 @@ func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest)
|
|||||||
ChecksumSHA256: aws.String(checksum),
|
ChecksumSHA256: aws.String(checksum),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, parseAWSErrCode("error putting object into s3", err)
|
return nil, parseS3Error(err, "failed to put object", req).Err()
|
||||||
}
|
}
|
||||||
if resp.ChecksumSHA256 == nil {
|
if resp.ChecksumSHA256 == nil {
|
||||||
return nil, status.Errorf(codes.Internal, "missing checksum response from aws")
|
return nil, errors.UnknownStatus("missing checksum response from aws")
|
||||||
}
|
}
|
||||||
if checksum != *resp.ChecksumSHA256 {
|
if checksum != *resp.ChecksumSHA256 {
|
||||||
return nil, status.Errorf(codes.Internal, "mismatched checksum")
|
return nil, checksumMistmatchStatus()
|
||||||
}
|
}
|
||||||
decodedChecksum, err := base64.StdEncoding.DecodeString(*resp.ChecksumSHA256)
|
decodedChecksum, err := base64.StdEncoding.DecodeString(*resp.ChecksumSHA256)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "failed to decode checksum value from aws")
|
return nil, errors.UnknownStatus("failed to decode checksum value from aws")
|
||||||
}
|
}
|
||||||
return &pb.PutObjectResponse{
|
return &pb.PutObjectResponse{
|
||||||
ChecksumSha_256: decodedChecksum,
|
ChecksumSha_256: decodedChecksum,
|
||||||
@@ -626,21 +628,21 @@ func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest)
|
|||||||
// DeleteObjects is used to delete one or many objects from an s3 bucket.
|
// DeleteObjects is used to delete one or many objects from an s3 bucket.
|
||||||
func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjectsRequest) (*pb.DeleteObjectsResponse, error) {
|
func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjectsRequest) (*pb.DeleteObjectsResponse, error) {
|
||||||
if req.GetKeyPrefix() == "" {
|
if req.GetKeyPrefix() == "" {
|
||||||
return nil, status.Error(codes.InvalidArgument, "key prefix is required")
|
return nil, errors.BadRequestStatus("key prefix is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
bucket := req.GetBucket()
|
bucket := req.GetBucket()
|
||||||
if bucket == nil {
|
if bucket == nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, "bucket is required")
|
return nil, errors.BadRequestStatus("bucket is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if bucket.GetBucketName() == "" {
|
if bucket.GetBucketName() == "" {
|
||||||
return nil, status.Error(codes.InvalidArgument, "bucketName is required")
|
return nil, errors.BadRequestStatus("bucketName is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
attrs := bucket.GetAttributes()
|
attrs := bucket.GetAttributes()
|
||||||
if attrs == nil {
|
if attrs == nil {
|
||||||
return nil, status.Error(codes.InvalidArgument, "attributes is required")
|
return nil, errors.BadRequestStatus("attributes is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
storageAttributes, err := getStorageAttributes(attrs)
|
storageAttributes, err := getStorageAttributes(attrs)
|
||||||
@@ -660,7 +662,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
|
|||||||
}, p.testCredStateOpts...)...,
|
}, p.testCredStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error setting up persisted state: %s", err)
|
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
storageState, err := newAwsStoragePersistedState(
|
storageState, err := newAwsStoragePersistedState(
|
||||||
@@ -669,7 +671,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
|
|||||||
}, p.testStorageStateOpts...)...,
|
}, p.testStorageStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error setting up persisted state: %s", err)
|
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := []s3Option{}
|
opts := []s3Option{}
|
||||||
@@ -678,7 +680,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
|
|||||||
}
|
}
|
||||||
client, err := storageState.S3Client(ctx, opts...)
|
client, err := storageState.S3Client(ctx, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.InvalidArgument, "error getting S3 client: %s", err)
|
return nil, errors.BadRequestStatus("error getting S3 client: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix := path.Join(bucket.GetBucketPrefix(), req.GetKeyPrefix())
|
prefix := path.Join(bucket.GetBucketPrefix(), req.GetKeyPrefix())
|
||||||
@@ -694,7 +696,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
|
|||||||
Key: aws.String(prefix),
|
Key: aws.String(prefix),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, parseAWSErrCode("error deleting S3 object", err)
|
return nil, parseS3Error(err, "failed to delete object", req).Err()
|
||||||
}
|
}
|
||||||
return &pb.DeleteObjectsResponse{
|
return &pb.DeleteObjectsResponse{
|
||||||
ObjectsDeleted: uint32(1),
|
ObjectsDeleted: uint32(1),
|
||||||
@@ -714,7 +716,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
|
|||||||
ContinuationToken: conToken,
|
ContinuationToken: conToken,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, parseAWSErrCode("error iterating S3 bucket contents", err)
|
return nil, parseS3Error(err, "failed to list objects", req).Err()
|
||||||
}
|
}
|
||||||
truncated = res.IsTruncated
|
truncated = res.IsTruncated
|
||||||
conToken = res.NextContinuationToken
|
conToken = res.NextContinuationToken
|
||||||
@@ -736,7 +738,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, parseAWSErrCode("error deleting S3 object(s)", err)
|
return nil, parseS3Error(err, "failed to delete objects", req).Err()
|
||||||
}
|
}
|
||||||
deleted += len(res.Deleted)
|
deleted += len(res.Deleted)
|
||||||
}
|
}
|
||||||
@@ -752,15 +754,15 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
|
|||||||
// PutObject permissions. This function will read the same file it had uploaded to validate
|
// PutObject permissions. This function will read the same file it had uploaded to validate
|
||||||
// it has GetObject permissions. This function will read the metadata of the same file it
|
// it has GetObject permissions. This function will read the metadata of the same file it
|
||||||
// had uploaded to validate it has GetObjectAttributes permissions.
|
// had uploaded to validate it has GetObjectAttributes permissions.
|
||||||
func dryRunValidation(ctx context.Context, state *awsStoragePersistedState, attrs *StorageAttributes, bucket *storagebuckets.StorageBucket) error {
|
func dryRunValidation(ctx context.Context, state *awsStoragePersistedState, attrs *StorageAttributes, bucket *storagebuckets.StorageBucket) *status.Status {
|
||||||
if state == nil {
|
if state == nil {
|
||||||
return status.Error(codes.InvalidArgument, "persisted state is required")
|
return status.New(codes.InvalidArgument, "persisted state is required")
|
||||||
}
|
}
|
||||||
if attrs == nil {
|
if attrs == nil {
|
||||||
return status.Error(codes.InvalidArgument, "attributes is required")
|
return status.New(codes.InvalidArgument, "attributes is required")
|
||||||
}
|
}
|
||||||
if bucket == nil {
|
if bucket == nil {
|
||||||
return status.Error(codes.InvalidArgument, "bucket is required")
|
return status.New(codes.InvalidArgument, "bucket is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := []s3Option{}
|
opts := []s3Option{}
|
||||||
@@ -770,61 +772,76 @@ func dryRunValidation(ctx context.Context, state *awsStoragePersistedState, attr
|
|||||||
|
|
||||||
client, err := state.S3Client(ctx, opts...)
|
client, err := state.S3Client(ctx, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.Errorf(codes.InvalidArgument, "error getting S3 client: %s", err)
|
return status.New(codes.InvalidArgument, fmt.Sprintf("error getting S3 client: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errs *multierror.Error
|
||||||
|
permissions := &pb.Permissions{}
|
||||||
|
// we track the codes returned by ParseAWSError and pick the most severe one by using the max func
|
||||||
|
|
||||||
objectKey := path.Join(bucket.GetBucketPrefix(), uuid.New().String())
|
objectKey := path.Join(bucket.GetBucketPrefix(), uuid.New().String())
|
||||||
if _, err := client.PutObject(ctx, &s3.PutObjectInput{
|
if _, err := client.PutObject(ctx, &s3.PutObjectInput{
|
||||||
Bucket: aws.String(bucket.GetBucketName()),
|
Bucket: aws.String(bucket.GetBucketName()),
|
||||||
Key: aws.String(objectKey),
|
Key: aws.String(objectKey),
|
||||||
Body: bytes.NewReader([]byte("hashicorp boundary aws plugin access test")),
|
Body: bytes.NewReader([]byte("hashicorp boundary aws plugin access test")),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return parseAWSErrCode("error failed to put object", err)
|
var st *status.Status
|
||||||
|
st, permissions.Write = errors.ParseAWSError(err, "failed to put object")
|
||||||
|
errs = multierror.Append(errs, st.Err())
|
||||||
|
} else {
|
||||||
|
permissions.Write = &pb.Permission{State: pb.StateType_STATE_TYPE_OK, CheckedAt: timestamppb.Now()}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := client.GetObject(ctx, &s3.GetObjectInput{
|
if _, err := client.GetObject(ctx, &s3.GetObjectInput{
|
||||||
Bucket: aws.String(bucket.GetBucketName()),
|
Bucket: aws.String(bucket.GetBucketName()),
|
||||||
Key: aws.String(objectKey),
|
Key: aws.String(objectKey),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return parseAWSErrCode("error failed to get object", err)
|
var st *status.Status
|
||||||
|
st, permissions.Read = errors.ParseAWSError(err, "failed to get object")
|
||||||
|
errs = multierror.Append(errs, st.Err())
|
||||||
|
} else {
|
||||||
|
permissions.Read = &pb.Permission{State: pb.StateType_STATE_TYPE_OK, CheckedAt: timestamppb.Now()}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := client.HeadObject(ctx, &s3.HeadObjectInput{
|
if _, err := client.HeadObject(ctx, &s3.HeadObjectInput{
|
||||||
Bucket: aws.String(bucket.GetBucketName()),
|
Bucket: aws.String(bucket.GetBucketName()),
|
||||||
Key: aws.String(objectKey),
|
Key: aws.String(objectKey),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return parseAWSErrCode("error failed to get head object", err)
|
var st *status.Status
|
||||||
|
st, permissions.Read = errors.ParseAWSError(err, "failed to head object")
|
||||||
|
errs = multierror.Append(errs, st.Err())
|
||||||
}
|
}
|
||||||
|
|
||||||
if res, err := client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{
|
if res, err := client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{
|
||||||
Bucket: aws.String(bucket.GetBucketName()),
|
Bucket: aws.String(bucket.GetBucketName()),
|
||||||
Prefix: aws.String(objectKey),
|
Prefix: aws.String(objectKey),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return parseAWSErrCode("error failed to list objects", err)
|
var st *status.Status
|
||||||
|
st, permissions.Read = errors.ParseAWSError(err, "failed to list object")
|
||||||
|
errs = multierror.Append(errs, st.Err())
|
||||||
} else if res == nil || len(res.Contents) != 1 || *res.Contents[0].Key != objectKey {
|
} else if res == nil || len(res.Contents) != 1 || *res.Contents[0].Key != objectKey {
|
||||||
return status.Errorf(codes.InvalidArgument, "list response did not contain the expected key: %+v", res)
|
return status.New(codes.Aborted, fmt.Sprintf("list response did not contain the expected key: %+v", res))
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
if _, err := client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
||||||
Bucket: aws.String(bucket.GetBucketName()),
|
Bucket: aws.String(bucket.GetBucketName()),
|
||||||
Key: aws.String(objectKey),
|
Key: aws.String(objectKey),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return parseAWSErrCode("error failed to delete object", err)
|
var st *status.Status
|
||||||
|
st, permissions.Delete = errors.ParseAWSError(err, "failed to delete object")
|
||||||
|
errs = multierror.Append(errs, st.Err())
|
||||||
|
} else {
|
||||||
|
permissions.Delete = &pb.Permission{State: pb.StateType_STATE_TYPE_OK, CheckedAt: timestamppb.Now()}
|
||||||
|
}
|
||||||
|
|
||||||
|
if errs != nil {
|
||||||
|
st := status.New(codes.FailedPrecondition, fmt.Sprintf("failed to validate provided aws credentials: %v", errs.Unwrap()))
|
||||||
|
state := &pb.StorageBucketCredentialState{State: permissions}
|
||||||
|
if st, err = st.WithDetails(state); err != nil {
|
||||||
|
st = status.New(codes.Internal, err.Error())
|
||||||
|
}
|
||||||
|
return st
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseAWSErrCode detects if an aws error is a throttle error.
|
|
||||||
// If the error is a throttle error, then an Unavailable status code
|
|
||||||
// is returned. Otherwise the default status code is an Internal error.
|
|
||||||
func parseAWSErrCode(msg string, err error) error {
|
|
||||||
errCode := codes.Internal
|
|
||||||
retryErr := retry.ThrottleErrorCode{
|
|
||||||
Codes: retry.DefaultThrottleErrorCodes,
|
|
||||||
}
|
|
||||||
if retryErr.IsErrorThrottle(err).Bool() {
|
|
||||||
errCode = codes.Unavailable
|
|
||||||
}
|
|
||||||
return status.Errorf(errCode, "%s: %s", msg, err)
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -84,7 +84,6 @@ func newAwsStoragePersistedState(opts ...awsStoragePersistedStateOption) (*awsSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *awsStoragePersistedState) toProto() (*storagebuckets.StorageBucketPersisted, error) {
|
func (s *awsStoragePersistedState) toProto() (*storagebuckets.StorageBucketPersisted, error) {
|
||||||
|
|
||||||
data, err := structpb.NewStruct(s.ToMap())
|
data, err := structpb.NewStruct(s.ToMap())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error converting state to structpb.Struct: %w", err)
|
return nil, fmt.Errorf("error converting state to structpb.Struct: %w", err)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||||
|
"github.com/aws/smithy-go"
|
||||||
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
||||||
"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"
|
||||||
@@ -28,25 +29,8 @@ const (
|
|||||||
testGetUserErr = "test error for GetUser"
|
testGetUserErr = "test error for GetUser"
|
||||||
testGetCallerIdentityErr = "test error for GetCallerIdentity"
|
testGetCallerIdentityErr = "test error for GetCallerIdentity"
|
||||||
testDeleteAccessKeyErr = "test error for DeleteAccessKey"
|
testDeleteAccessKeyErr = "test error for DeleteAccessKey"
|
||||||
testGetObjectErr = "test error for GetObject"
|
|
||||||
testPutObjectErr = "test error for PutObject"
|
|
||||||
testHeadObjectErr = "test error for HeadObject"
|
|
||||||
testListObjectV2Err = "test error for ListObjectV2"
|
|
||||||
testDeleteObjectErr = "test error for DeleteObject"
|
|
||||||
testDeleteObjectsErr = "test error for DeleteObjects"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// throttleErr is a mocked error used for testing the aws s3 client
|
|
||||||
type throttleErr struct{}
|
|
||||||
|
|
||||||
func (e *throttleErr) ErrorCode() string {
|
|
||||||
return "ThrottlingException"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *throttleErr) Error() string {
|
|
||||||
return "ThrottlingException"
|
|
||||||
}
|
|
||||||
|
|
||||||
type testMockS3State struct {
|
type testMockS3State struct {
|
||||||
GetObjectCalled bool
|
GetObjectCalled bool
|
||||||
GetObjectInputParams *s3.GetObjectInput
|
GetObjectInputParams *s3.GetObjectInput
|
||||||
@@ -414,3 +398,16 @@ func validSTSMock() []credential.AwsCredentialPersistedStateOption {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestAwsS3Error returns an s3 api error
|
||||||
|
func TestAwsS3Error(code, op, msg string) error {
|
||||||
|
return &smithy.OperationError{
|
||||||
|
ServiceID: "s3",
|
||||||
|
OperationName: op,
|
||||||
|
Err: &smithy.GenericAPIError{
|
||||||
|
Code: code,
|
||||||
|
Message: msg,
|
||||||
|
Fault: smithy.FaultServer,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user