Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
249463eb7b | ||
|
|
899c62ce06 | ||
|
|
ce22c1c46a | ||
|
|
2f9163ad0e | ||
|
|
7caff84c06 |
No files matched your search
@@ -4,3 +4,7 @@ plan.tfplan
|
||||
*.log
|
||||
*.out
|
||||
*.html
|
||||
|
||||
# IntelliJ IDEA project files
|
||||
.idea
|
||||
*.iprg
|
||||
+1
-2
@@ -1,5 +1,4 @@
|
||||
# These owners will be the default owners for everything in
|
||||
# the repo, unless a later match takes precedence.
|
||||
|
||||
@hashicorp/boundary
|
||||
|
||||
* @hashicorp/boundary
|
||||
@@ -3,12 +3,12 @@ module github.com/hashicorp/boundary-plugin-aws
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2 v1.20.1
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.22.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/smithy-go v1.14.1
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.2
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.186.0
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.37.2
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.66.1
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.32.2
|
||||
github.com/aws/smithy-go v1.22.0
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/uuid v1.4.0
|
||||
github.com/hashicorp/boundary/sdk v0.0.43-0.20240717182311-a20aae98794a
|
||||
@@ -18,22 +18,22 @@ require (
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
google.golang.org/grpc v1.61.0
|
||||
google.golang.org/protobuf v1.33.0
|
||||
google.golang.org/protobuf v1.35.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.33 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.32 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.38 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.32 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.39 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.33 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.32 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.13.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
@@ -55,10 +55,10 @@ require (
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/zclconf/go-cty v1.10.0 // indirect
|
||||
golang.org/x/crypto v0.18.0 // indirect
|
||||
golang.org/x/net v0.20.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/crypto v0.28.0 // indirect
|
||||
golang.org/x/net v0.30.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe // indirect
|
||||
|
||||
@@ -8,51 +8,52 @@ github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
|
||||
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
|
||||
github.com/aws/aws-sdk-go-v2 v1.20.1 h1:rZBf5DWr7YGrnlTK4kgDQGn1ltqOg5orCYb/UhOFZkg=
|
||||
github.com/aws/aws-sdk-go-v2 v1.20.1/go.mod h1:NU06lETsFm8fUC6ZjhgDpVBcGZTFQ6XM+LZWZxMI4ac=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.12 h1:lN6L3LrYHeZ6xCxaIYtoWCx4GMLk4nRknsh29OMSqHY=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.12/go.mod h1:TDCkEAkMTXxTs0oLBGBKpBZbk3NLh8EvAfF0Q3x8/0c=
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.2 h1:AkNLZEyYMLnx/Q/mSKkcMqwNFXMAvFto9bNsHqcTduI=
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.2/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 h1:pT3hpW0cOHRJx8Y0DfJUEQuqPild8jRGmSFmBgvydr0=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6/go.mod h1:j/I2++U0xX+cr44QjHay4Cvxj6FUbnxrgmqN3H1jTZA=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.33 h1:JKcw5SFxFW/rpM4mOPjv0VQ11E2kxW13F3exWOy7VZU=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.33/go.mod h1:hXO/l9pgY3K5oZJldamP0pbZHdPqqk+4/maa7DSD3cA=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.32 h1:lIH1eKPcCY1ylR4B6PkBGRWMHO3aVenOKJHWiS4/G2w=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.32/go.mod h1:lL8U3v/Y79YRG69WlAho0OHIKUXCyFvSXaIvfo81sls=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.8 h1:DK/9C+UN/X+1+Wm8pqaDksQr2tSLzq+8X1/rI/ZxKEQ=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.8/go.mod h1:ce7BgLQfYr5hQFdy67oX2svto3ufGtm6oBvmsHScI1Q=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.38 h1:c8ed/T9T2K5I+h/JzmF5tpI46+OODQ74dzmdo+QnaMg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.38/go.mod h1:qggunOChCMu9ZF/UkAfhTz25+U2rLVb3ya0Ua6TTfCA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.32 h1:hNeAAymUY5gu11WrrmFb3CVIp9Dar9hbo44yzzcQpzA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 h1:UAsR3xA31QGf79WzpG/ixT9FZvQlh5HY1NRqSHBNOCk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21/go.mod h1:JNr43NFf5L9YaG3eKTm7HQzls9J+A9YYcGI5Quh1r2Y=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.32/go.mod h1:0ZXSqrty4FtQ7p8TEuRde/SZm9X05KT18LAUlR40Ln0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 h1:6jZVETqmYCadGFvrYEQfC5fAQmlo80CeL5psbno6r0s=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21/go.mod h1:1SR0GbLlnN3QUmYaflZNiH1ql+1qrSiB2vwcJ+4UM60=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.39 h1:fc0ukRAiP1syoSGZYu+DaE+FulSYhTiJ8WpVu5jElU4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.39/go.mod h1:WLAW8PT7+JhjZfLSWe7WEJaJu0GNo0cKc2Zyo003RBs=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.1 h1:vUh7dBFNS3oFCtVv6CiYKh5hP9ls8+kIpKLeFruIBLk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.1/go.mod h1:sFMeinkhj/SZKQM8BxtvNtSPjJEo0Xrz+w3g2e4FSKI=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0 h1:NXi4pNJWjAaiI56P1Rl8DC9A4jMNRE00WNBsDua5WRg=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0/go.mod h1:L3ZT0N/vBsw77mOAawXmRnREpEjcHd2v5Hzf7AkIH8M=
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.22.2 h1:DPFxx/6Zwes/MiadlDteVqDKov7yQ5v9vuwfhZuJm1s=
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.22.2/go.mod h1:cQTMNdo/Z5t1DDRsUnx0a2j6cPnytMBidUYZw2zks28=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.13 h1:iV/W5OMBys+66OeXJi/7xIRrKZNsu0ylsLGu+6nbmQE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.13/go.mod h1:ReJb6xYmtGyu9KoFtRreWegbN9dZqvZIIv4vWnhcsyI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.33 h1:QviNkc+vGSuEHx8P+pVNKOdWLXBPIwMFv7p0fphgE4U=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.33/go.mod h1:fABTUmOrAgAalG2i9WJpjBvlnk7UK8YmnYaxN+Q2CwE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.32 h1:dGAseBFEYxth10V23b5e2mAS+tX7oVbfYHD6dnDdAsg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21 h1:7edmS3VOBDhK00b/MwGtGglCm7hhwNYnjJs/PgFdMQE=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21/go.mod h1:Q9o5h4HoIWG8XfzxqiuK/CGUbepCJ8uTlaE3bAbxytQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.186.0 h1:n2l2WeV+lEABrGwG/4MsE0WFEbd3j7yKsmZzbnEm5CY=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.186.0/go.mod h1:kYXaB4FzyhEJjvrJ84oPnMElLiEAjGxxUunVW2tBSng=
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.37.2 h1:E7vCDUFeDN8uOk8Nb2d4E1howWS1TR4HrKABXsvttIs=
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.37.2/go.mod h1:QzMecFrIFYJ1cyxjlUoIFRzYSDX19gdqYUd0Tyws2J8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2 h1:4FMHqLfk0efmTqhXVRL5xYRqlEBNBiRI7N6w4jsEdd4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2/go.mod h1:LWoqeWlK9OZeJxsROW2RqrSPvQHKTpp69r/iDjwsSaw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.32/go.mod h1:4jwAWKEkCR0anWk5+1RbfSg1R5Gzld7NLiuaq5bTR/Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.1 h1:PT6PBCycRwhpEW5hJnRiceCeoWJ+r3bdgXtV+VKG7Pk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.1/go.mod h1:TqoxCLwT2nrxrBGA+z7t6OWM7LBkgRckK3gOjYE+7JA=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.38.2 h1:v346f1h8sUBKXnEbrv43L37MTBlFHyKXQPIZHNAaghA=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.38.2/go.mod h1:cwCATiyNrXK9P2FsWdZ89g9mpsYv2rhk0UA/KByl5fY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 h1:s7NA1SOw8q/5c0wr8477yOPp0z+uBaXBnLE0XYb0POA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2/go.mod h1:fnjjWyAW/Pj5HYOxl9LJqWtEwS7W2qgcRLWP+uWbss0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2 h1:t7iUP9+4wdc5lt3E41huP+GvQZJD38WLsgVp4iOtAjg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2/go.mod h1:/niFCtmuQNxqx9v8WAPq5qh7EH25U4BF6tjoyq9bObM=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.66.1 h1:MkQ4unegQEStiQYmfFj+Aq5uTp265ncSmm0XTQwDwi0=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.66.1/go.mod h1:cB6oAuus7YXRZhWCc1wIwPywwZ1XwweNp2TVAEGYeB8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.13.2 h1:A2RlEMo4SJSwbNoUUgkxTAEMduAy/8wG3eB2b2lP4gY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.13.2/go.mod h1:ju+nNXUunfIFamXUIZQiICjnO/TPlOmWcYhZcSy7xaE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.2 h1:OJELEgyaT2kmaBGZ+myyZbTTLobfe3ox3FSh5eYK9Qs=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.2/go.mod h1:ubDBBaDFs1GHijSOTi8ljppML15GLG0HxhILtbjNNYQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.21.2 h1:ympg1+Lnq33XLhcK/xTG4yZHPs1Oyxu+6DEWbl7qOzA=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.21.2/go.mod h1:FQ/DQcOfESELfJi5ED+IPPAjI5xC6nxtSolVVB773jM=
|
||||
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/aws/smithy-go v1.14.1 h1:EFKMUmH/iHMqLiwoEDx2rRjRQpI1YCn5jTysoaDujFs=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 h1:CiS7i0+FUe+/YY1GvIBLLrR/XNGZ4CtM1Ll0XavNuVo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.32.2/go.mod h1:HtaiBI8CjYoNVde8arShXb94UbQQi9L4EMr6D+xGBwo=
|
||||
github.com/aws/smithy-go v1.14.1/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM=
|
||||
github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
@@ -210,8 +211,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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
@@ -224,8 +225,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-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.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -239,14 +240,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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.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.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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
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-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
@@ -269,8 +270,8 @@ 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/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
|
||||
@@ -37,7 +37,7 @@ type CredentialAttributes struct {
|
||||
// CredentialsConfig used for configuring an AWS session. An status error is returned
|
||||
// with an InvalidArgument code if any unrecognized fields are found in the protobuf
|
||||
// struct input.
|
||||
func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes) (*awsutil.CredentialsConfig, error) {
|
||||
func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes, dualStack bool) (*awsutil.CredentialsConfig, error) {
|
||||
// initialize secrets if it is nil
|
||||
// secrets can be nil because static credentials are optional
|
||||
if secrets == nil {
|
||||
@@ -117,6 +117,17 @@ func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes)
|
||||
opts = append(opts, awsutil.WithRoleTags(attrs.RoleTags))
|
||||
}
|
||||
|
||||
if dualStack {
|
||||
opts = append(opts, []awsutil.Option{
|
||||
awsutil.WithIamEndpointResolver(&iamEndpointResolver{
|
||||
dualStack: dualStack,
|
||||
}),
|
||||
awsutil.WithStsEndpointResolver(&stsEndpointResolver{
|
||||
dualStack: dualStack,
|
||||
}),
|
||||
}...)
|
||||
}
|
||||
|
||||
cfg, err := awsutil.NewCredentialsConfig(opts...)
|
||||
if err != nil {
|
||||
return nil, errors.BadRequestStatus(err.Error())
|
||||
|
||||
@@ -106,6 +106,7 @@ func TestGetCredentialsConfig(t *testing.T) {
|
||||
name string
|
||||
secrets *structpb.Struct
|
||||
attrs *CredentialAttributes
|
||||
dualStack bool
|
||||
expected *awsutil.CredentialsConfig
|
||||
expectedErrContains string
|
||||
}{
|
||||
@@ -135,6 +136,24 @@ func TestGetCredentialsConfig(t *testing.T) {
|
||||
Region: "us-west-2",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with dualstack",
|
||||
secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"),
|
||||
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||
},
|
||||
},
|
||||
dualStack: true,
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expected: &awsutil.CredentialsConfig{
|
||||
AccessKey: "AKIAfoobar",
|
||||
SecretKey: "bazqux",
|
||||
Region: "us-west-2",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unknown fields",
|
||||
secrets: &structpb.Struct{
|
||||
@@ -233,7 +252,7 @@ func TestGetCredentialsConfig(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
actual, err := GetCredentialsConfig(tc.secrets, tc.attrs)
|
||||
actual, err := GetCredentialsConfig(tc.secrets, tc.attrs, tc.dualStack)
|
||||
if tc.expectedErrContains != "" {
|
||||
require.Error(err)
|
||||
require.Contains(err.Error(), tc.expectedErrContains)
|
||||
@@ -249,6 +268,13 @@ func TestGetCredentialsConfig(t *testing.T) {
|
||||
require.Equal(tc.expected.RoleExternalId, actual.RoleExternalId)
|
||||
require.Equal(tc.expected.RoleSessionName, actual.RoleSessionName)
|
||||
require.Equal(tc.expected.RoleTags, actual.RoleTags)
|
||||
if tc.dualStack {
|
||||
require.NotNil(actual.IAMEndpointResolver)
|
||||
require.NotNil(actual.STSEndpointResolver)
|
||||
} else {
|
||||
require.Nil(actual.IAMEndpointResolver)
|
||||
require.Nil(actual.STSEndpointResolver)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/iam"
|
||||
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
smithyEndpoints "github.com/aws/smithy-go/endpoints"
|
||||
"github.com/hashicorp/boundary-plugin-aws/internal/errors"
|
||||
"github.com/hashicorp/boundary-plugin-aws/internal/values"
|
||||
"github.com/hashicorp/go-secure-stdlib/awsutil/v2"
|
||||
@@ -114,7 +117,7 @@ func (s *AwsCredentialPersistedState) ValidateCreds(ctx context.Context) error {
|
||||
return errors.BadRequestStatus("missing credentials config")
|
||||
}
|
||||
if _, err := s.CredentialsConfig.GetCallerIdentity(ctx, s.testOpts...); err != nil {
|
||||
st, _ := errors.ParseAWSError(err, "validating credentials")
|
||||
st, _ := errors.ParseAWSError("validating credentials", err)
|
||||
return st.Err()
|
||||
}
|
||||
return nil
|
||||
@@ -135,7 +138,7 @@ func (s *AwsCredentialPersistedState) RotateCreds(ctx context.Context) error {
|
||||
if err := s.CredentialsConfig.RotateKeys(ctx, append([]awsutil.Option{
|
||||
awsutil.WithValidityCheckTimeout(rotationWaitTimeout),
|
||||
}, s.testOpts...)...); err != nil {
|
||||
st, _ := errors.ParseAWSError(err, "error rotating credentials")
|
||||
st, _ := errors.ParseAWSError("rotating credentials", err)
|
||||
return st.Err()
|
||||
}
|
||||
s.CredsLastRotatedTime = time.Now()
|
||||
@@ -192,7 +195,7 @@ func (s *AwsCredentialPersistedState) DeleteCreds(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// Otherwise treat it as an actual error.
|
||||
st, _ := errors.ParseAWSError(err, "failed to delete access key")
|
||||
st, _ := errors.ParseAWSError("deleting credentials", err)
|
||||
return st.Err()
|
||||
}
|
||||
|
||||
@@ -237,7 +240,7 @@ func (s *AwsCredentialPersistedState) ToMap() map[string]any {
|
||||
|
||||
// AwsCredentialPersistedStateFromProto parses values out of a protobuf struct input
|
||||
// and returns a AwsCredentialPersistedState used for configuring an AWS session.
|
||||
func AwsCredentialPersistedStateFromProto(secrets *structpb.Struct, attrs *CredentialAttributes, opts ...AwsCredentialPersistedStateOption) (*AwsCredentialPersistedState, error) {
|
||||
func AwsCredentialPersistedStateFromProto(secrets *structpb.Struct, attrs *CredentialAttributes, dualStack bool, opts ...AwsCredentialPersistedStateOption) (*AwsCredentialPersistedState, error) {
|
||||
// initialize secrets if it is nil
|
||||
// secrets can be nil because static credentials are optional
|
||||
if secrets == nil {
|
||||
@@ -252,17 +255,17 @@ func AwsCredentialPersistedStateFromProto(secrets *structpb.Struct, attrs *Crede
|
||||
|
||||
accessKeyId, err := values.GetStringValue(secrets, ConstAccessKeyId, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("persisted state integrity error: %w", err)
|
||||
return nil, fmt.Errorf("persisted state integrity: %w", err)
|
||||
}
|
||||
|
||||
secretAccessKey, err := values.GetStringValue(secrets, ConstSecretAccessKey, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("persisted state integrity error: %w", err)
|
||||
return nil, fmt.Errorf("persisted state integrity: %w", err)
|
||||
}
|
||||
|
||||
credsLastRotatedTime, err := values.GetTimeValue(secrets, ConstCredsLastRotatedTime)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("persisted state integrity error: %w", err)
|
||||
return nil, fmt.Errorf("persisted state integrity: %w", err)
|
||||
}
|
||||
|
||||
s, err := NewAwsCredentialPersistedState(opts...)
|
||||
@@ -292,6 +295,17 @@ func AwsCredentialPersistedStateFromProto(secrets *structpb.Struct, attrs *Crede
|
||||
if len(attrs.RoleTags) != 0 {
|
||||
awsOpts = append(awsOpts, awsutil.WithRoleTags(attrs.RoleTags))
|
||||
}
|
||||
if dualStack {
|
||||
awsOpts = append(awsOpts, []awsutil.Option{
|
||||
awsutil.WithIamEndpointResolver(&iamEndpointResolver{
|
||||
dualStack: dualStack,
|
||||
}),
|
||||
awsutil.WithStsEndpointResolver(&stsEndpointResolver{
|
||||
dualStack: dualStack,
|
||||
}),
|
||||
}...)
|
||||
}
|
||||
|
||||
credentialsConfig, err := awsutil.NewCredentialsConfig(awsOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -321,3 +335,23 @@ func GetCredentialType(cc *awsutil.CredentialsConfig) CredentialType {
|
||||
|
||||
return Unknown
|
||||
}
|
||||
|
||||
type iamEndpointResolver struct {
|
||||
dualStack bool
|
||||
}
|
||||
|
||||
func (e *iamEndpointResolver) ResolveEndpoint(ctx context.Context, params iam.EndpointParameters) (resolver smithyEndpoints.Endpoint, err error) {
|
||||
params.UseDualStack = aws.Bool(e.dualStack)
|
||||
resolver, err = iam.NewDefaultEndpointResolverV2().ResolveEndpoint(ctx, params)
|
||||
return
|
||||
}
|
||||
|
||||
type stsEndpointResolver struct {
|
||||
dualStack bool
|
||||
}
|
||||
|
||||
func (e *stsEndpointResolver) ResolveEndpoint(ctx context.Context, params sts.EndpointParameters) (resolver smithyEndpoints.Endpoint, err error) {
|
||||
params.UseDualStack = aws.Bool(e.dualStack)
|
||||
resolver, err = sts.NewDefaultEndpointResolverV2().ResolveEndpoint(ctx, params)
|
||||
return
|
||||
}
|
||||
@@ -112,6 +112,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
|
||||
name string
|
||||
secrets *structpb.Struct
|
||||
attrs *CredentialAttributes
|
||||
dualStack bool
|
||||
opts []AwsCredentialPersistedStateOption
|
||||
expected *AwsCredentialPersistedState
|
||||
expectedErr string
|
||||
@@ -174,6 +175,26 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with dualstack static credentials",
|
||||
secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
ConstAccessKeyId: structpb.NewStringValue("foobar"),
|
||||
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||
},
|
||||
},
|
||||
dualStack: true,
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expected: &AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
Region: "us-west-2",
|
||||
AccessKey: "foobar",
|
||||
SecretKey: "bazqux",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bad last rotated time",
|
||||
secrets: &structpb.Struct{
|
||||
@@ -186,7 +207,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
|
||||
attrs: &CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
expectedErr: "persisted state integrity error: could not parse time in value \"creds_last_rotated_time\": parsing time \"notatime\" as \"2006-01-02T15:04:05.999999999Z07:00\": cannot parse \"notatime\" as \"2006\"",
|
||||
expectedErr: "persisted state integrity: could not parse time in value \"creds_last_rotated_time\": parsing time \"notatime\" as \"2006-01-02T15:04:05.999999999Z07:00\": cannot parse \"notatime\" as \"2006\"",
|
||||
},
|
||||
{
|
||||
name: "option error",
|
||||
@@ -318,7 +339,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
actual, err := AwsCredentialPersistedStateFromProto(tc.secrets, tc.attrs, tc.opts...)
|
||||
actual, err := AwsCredentialPersistedStateFromProto(tc.secrets, tc.attrs, tc.dualStack, tc.opts...)
|
||||
if tc.expectedErr != "" {
|
||||
require.EqualError(err, tc.expectedErr)
|
||||
return
|
||||
@@ -336,6 +357,13 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
|
||||
require.Equal(tc.expected.CredentialsConfig.RoleExternalId, actual.CredentialsConfig.RoleExternalId)
|
||||
require.Equal(tc.expected.CredentialsConfig.RoleSessionName, actual.CredentialsConfig.RoleSessionName)
|
||||
require.Equal(tc.expected.CredentialsConfig.RoleTags, actual.CredentialsConfig.RoleTags)
|
||||
if tc.dualStack {
|
||||
require.NotNil(actual.CredentialsConfig.STSEndpointResolver)
|
||||
require.NotNil(actual.CredentialsConfig.IAMEndpointResolver)
|
||||
} else {
|
||||
require.Nil(actual.CredentialsConfig.STSEndpointResolver)
|
||||
require.Nil(actual.CredentialsConfig.IAMEndpointResolver)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -458,7 +486,7 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
|
||||
),
|
||||
},
|
||||
},
|
||||
expectedErr: "aws service unknown: unknown error: error rotating credentials",
|
||||
expectedErr: "aws service unknown: unknown error: rotating credentials",
|
||||
expectedStatusCode: codes.Unknown,
|
||||
},
|
||||
{
|
||||
@@ -568,7 +596,7 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) {
|
||||
},
|
||||
},
|
||||
credentialConfig: &awsutil.CredentialsConfig{},
|
||||
expectedErr: "aws service unknown: unknown error: failed to delete access key",
|
||||
expectedErr: "aws service unknown: unknown error: deleting credentials",
|
||||
expectedStatusCode: codes.Unknown,
|
||||
},
|
||||
{
|
||||
@@ -693,7 +721,7 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
|
||||
),
|
||||
},
|
||||
},
|
||||
expectedErr: "aws service unknown: unknown error: failed to delete access key",
|
||||
expectedErr: "aws service unknown: unknown error: deleting credentials",
|
||||
expectedStatusCode: codes.Unknown,
|
||||
},
|
||||
{
|
||||
|
||||
+15
-14
@@ -81,11 +81,12 @@ func InvalidArgumentError(msg string, f map[string]string) error {
|
||||
// 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) {
|
||||
func ParseAWSError(op string, err error) (st *status.Status, permission *pb.Permission) {
|
||||
if err == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("%s: %v", op, err)
|
||||
// find the service name of the aws api
|
||||
serviceName := "unknown"
|
||||
var oe *smithy.OperationError
|
||||
@@ -113,7 +114,7 @@ func ParseAWSError(err error, msg string) (st *status.Status, permission *pb.Per
|
||||
Codes: retry.DefaultThrottleErrorCodes,
|
||||
}
|
||||
if throttleErr.IsErrorThrottle(err).Bool() {
|
||||
statusMsg := fmt.Sprintf("aws service %s: throttling error: %s", serviceName, msg)
|
||||
statusMsg := fmt.Sprintf("aws service %s: throttling: %s", serviceName, msg)
|
||||
return status.New(codes.Unavailable, statusMsg), nil
|
||||
}
|
||||
|
||||
@@ -126,31 +127,31 @@ func ParseAWSError(err error, msg string) (st *status.Status, permission *pb.Per
|
||||
case awsErrorInvalidAccessKeyId:
|
||||
fallthrough
|
||||
case awsErrorExpiredToken:
|
||||
statusMsg := fmt.Sprintf("aws service %s: invalid credentials error: %s", serviceName, msg)
|
||||
statusMsg := fmt.Sprintf("aws service %s: invalid credentials: %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)
|
||||
statusMsg := fmt.Sprintf("aws service %s: %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)
|
||||
statusMsg := fmt.Sprintf("aws service %s: %s", serviceName, msg)
|
||||
return status.New(codes.Aborted, statusMsg), nil
|
||||
case awsErrorNoSuchKey:
|
||||
fallthrough
|
||||
case awsErrorInvalidObjectState:
|
||||
statusMsg := fmt.Sprintf("aws %s error: %s", serviceName, msg)
|
||||
statusMsg := fmt.Sprintf("aws service %s: %s", serviceName, msg)
|
||||
return status.New(codes.NotFound, statusMsg), nil
|
||||
case awsErrorRequestTimeout:
|
||||
fallthrough
|
||||
case awsErrorRequestTimeoutException:
|
||||
statusMsg := fmt.Sprintf("aws %s error: %s", serviceName, msg)
|
||||
statusMsg := fmt.Sprintf("aws service %s: %s", serviceName, msg)
|
||||
return status.New(codes.DeadlineExceeded, statusMsg), nil
|
||||
}
|
||||
}
|
||||
@@ -165,39 +166,39 @@ func ParseAWSError(err error, msg string) (st *status.Status, permission *pb.Per
|
||||
defer httpErr.Response.Body.Close()
|
||||
switch httpErr.HTTPStatusCode() {
|
||||
case http.StatusBadRequest:
|
||||
statusMsg := fmt.Sprintf("aws service %s: bad request error: %s", serviceName, msg)
|
||||
statusMsg := fmt.Sprintf("aws service %s: bad request: %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)
|
||||
statusMsg := fmt.Sprintf("aws service %s: invalid credentials: %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)
|
||||
statusMsg := fmt.Sprintf("aws service %s: invalid credentials: %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)
|
||||
statusMsg := fmt.Sprintf("aws service %s: resource not found: %s", serviceName, msg)
|
||||
return status.New(codes.NotFound, statusMsg), nil
|
||||
case http.StatusTooManyRequests:
|
||||
statusMsg := fmt.Sprintf("aws service %s: throttling error: %s", serviceName, msg)
|
||||
statusMsg := fmt.Sprintf("aws service %s: throttling: %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)
|
||||
statusMsg := fmt.Sprintf("aws service %s: connectivity: %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)
|
||||
statusMsg := fmt.Sprintf("aws service %s: timeout: %s", serviceName, msg)
|
||||
return status.New(codes.DeadlineExceeded, statusMsg), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "http-status-code-bad-request",
|
||||
err: TestAwsHttpResponseError(http.StatusBadRequest, "bad request"),
|
||||
expectedStatusCode: codes.InvalidArgument,
|
||||
expectedStatusMsg: "aws service unknown: bad request error: test",
|
||||
expectedStatusMsg: "aws service unknown: bad request: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -53,7 +53,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "http-status-code-unauthorized",
|
||||
err: TestAwsHttpResponseError(http.StatusUnauthorized, "unauthorized request"),
|
||||
expectedStatusCode: codes.PermissionDenied,
|
||||
expectedStatusMsg: "aws service unknown: invalid credentials error: test",
|
||||
expectedStatusMsg: "aws service unknown: invalid credentials: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_ERROR,
|
||||
ErrorDetails: "unauthorized request",
|
||||
@@ -64,7 +64,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "http-status-code-forbidden",
|
||||
err: TestAwsHttpResponseError(http.StatusForbidden, "forbidden request"),
|
||||
expectedStatusCode: codes.PermissionDenied,
|
||||
expectedStatusMsg: "aws service unknown: invalid credentials error: test",
|
||||
expectedStatusMsg: "aws service unknown: invalid credentials: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_ERROR,
|
||||
ErrorDetails: "forbidden request",
|
||||
@@ -75,7 +75,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
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",
|
||||
expectedStatusMsg: "aws service unknown: resource not found: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -84,7 +84,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "http-status-code-too-many-requests",
|
||||
err: TestAwsHttpResponseError(http.StatusTooManyRequests, "too many requests"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedStatusMsg: "aws service unknown: throttling: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -93,7 +93,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "http-status-code-request-timeout",
|
||||
err: TestAwsHttpResponseError(http.StatusRequestTimeout, "request timeout"),
|
||||
expectedStatusCode: codes.DeadlineExceeded,
|
||||
expectedStatusMsg: "aws service unknown: timeout error: test",
|
||||
expectedStatusMsg: "aws service unknown: timeout: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -102,7 +102,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "http-status-code-internal-server-error",
|
||||
err: TestAwsHttpResponseError(http.StatusInternalServerError, "internal server error"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: connectivity error: test",
|
||||
expectedStatusMsg: "aws service unknown: connectivity: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -111,7 +111,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "http-status-code-bad-gateway",
|
||||
err: TestAwsHttpResponseError(http.StatusBadGateway, "bad gateway"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: connectivity error: test",
|
||||
expectedStatusMsg: "aws service unknown: connectivity: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -120,7 +120,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "http-status-code-service-unavailable",
|
||||
err: TestAwsHttpResponseError(http.StatusServiceUnavailable, "service unavailable"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: connectivity error: test",
|
||||
expectedStatusMsg: "aws service unknown: connectivity: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -129,7 +129,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "http-status-code-gateway-timeout",
|
||||
err: TestAwsHttpResponseError(http.StatusGatewayTimeout, "gateway timeout"),
|
||||
expectedStatusCode: codes.DeadlineExceeded,
|
||||
expectedStatusMsg: "aws service unknown: timeout error: test",
|
||||
expectedStatusMsg: "aws service unknown: timeout: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -138,7 +138,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "aws-throttling",
|
||||
err: TestAwsError("Throttling", "throttling error"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedStatusMsg: "aws service unknown: throttling: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -147,7 +147,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "aws-throttling-exception",
|
||||
err: TestAwsError("ThrottlingException", "throttling exception"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedStatusMsg: "aws service unknown: throttling: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -156,7 +156,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "aws-throttled-exception",
|
||||
err: TestAwsError("ThrottledException", "throttled exception"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedStatusMsg: "aws service unknown: throttling: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -165,7 +165,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "aws-request-throttled-exception",
|
||||
err: TestAwsError("RequestThrottledException", "request throttled exception"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedStatusMsg: "aws service unknown: throttling: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -174,7 +174,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "aws-request-too-many-requests-exception",
|
||||
err: TestAwsError("TooManyRequestsException", "too many requests exception"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedStatusMsg: "aws service unknown: throttling: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -183,7 +183,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "aws-request-provisioned-throughput-exceeded-exception",
|
||||
err: TestAwsError("ProvisionedThroughputExceededException", "provisioned throughput exceeded exception"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedStatusMsg: "aws service unknown: throttling: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -192,7 +192,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "aws-request-transaction-in-progress-exception",
|
||||
err: TestAwsError("TransactionInProgressException", "transaction in progress exception"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedStatusMsg: "aws service unknown: throttling: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -201,7 +201,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "aws-request-limit-exceeded",
|
||||
err: TestAwsError("RequestLimitExceeded", "request limit exceeded"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedStatusMsg: "aws service unknown: throttling: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -210,7 +210,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "aws-request-bandwidth-limit-exceeded",
|
||||
err: TestAwsError("BandwidthLimitExceeded", "bandwidth limit exceeded"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedStatusMsg: "aws service unknown: throttling: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -219,7 +219,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "aws-request-limit-exceeded-exception",
|
||||
err: TestAwsError("LimitExceededException", "limit exceeded exception"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedStatusMsg: "aws service unknown: throttling: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -228,7 +228,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "aws-request-throttled",
|
||||
err: TestAwsError("RequestThrottled", "request throttled"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedStatusMsg: "aws service unknown: throttling: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -237,7 +237,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "aws-request-slow-down",
|
||||
err: TestAwsError("SlowDown", "slow down"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedStatusMsg: "aws service unknown: throttling: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -246,7 +246,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "aws-request-prior-request-not-complete",
|
||||
err: TestAwsError("PriorRequestNotComplete", "prior request not complete"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedStatusMsg: "aws service unknown: throttling: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -255,7 +255,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "aws-request-ec2-throttled-exception",
|
||||
err: TestAwsError("EC2ThrottledException", "ec2 throttled exception"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service unknown: throttling error: test",
|
||||
expectedStatusMsg: "aws service unknown: throttling: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -264,7 +264,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "aws-request-timeout",
|
||||
err: TestAwsError("RequestTimeout", "request timeout"),
|
||||
expectedStatusCode: codes.DeadlineExceeded,
|
||||
expectedStatusMsg: "aws unknown error: test",
|
||||
expectedStatusMsg: "aws service unknown: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -273,7 +273,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "aws-request-timeout-exception",
|
||||
err: TestAwsError("RequestTimeoutException", "request timeout exception"),
|
||||
expectedStatusCode: codes.DeadlineExceeded,
|
||||
expectedStatusMsg: "aws unknown error: test",
|
||||
expectedStatusMsg: "aws service unknown: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
@@ -282,7 +282,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
name: "aws-request-access-denied",
|
||||
err: TestAwsError(awsErrorAccessDenied, "not authorized to perform action"),
|
||||
expectedStatusCode: codes.PermissionDenied,
|
||||
expectedStatusMsg: "aws service unknown: invalid credentials error: test",
|
||||
expectedStatusMsg: "aws service unknown: invalid credentials: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_ERROR,
|
||||
ErrorDetails: "not authorized to perform action",
|
||||
@@ -293,7 +293,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
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",
|
||||
expectedStatusMsg: "aws service unknown: invalid credentials: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_ERROR,
|
||||
ErrorDetails: "The AWS Access Key Id you provided does not exist in our records.",
|
||||
@@ -304,7 +304,7 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
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",
|
||||
expectedStatusMsg: "aws service unknown: invalid credentials: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_ERROR,
|
||||
ErrorDetails: "The security token included in the request is expired.",
|
||||
@@ -316,14 +316,14 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require, assert := require.New(t), assert.New(t)
|
||||
|
||||
actualStatus, permission := ParseAWSError(tt.err, "test")
|
||||
actualStatus, permission := ParseAWSError("test", tt.err)
|
||||
if tt.expectedStatusCode == codes.OK {
|
||||
require.Nil(actualStatus)
|
||||
return
|
||||
}
|
||||
|
||||
require.Equal(tt.expectedStatusCode, actualStatus.Code())
|
||||
require.Equal(tt.expectedStatusMsg, actualStatus.Message())
|
||||
require.Contains(actualStatus.Message(), tt.expectedStatusMsg)
|
||||
require.Len(actualStatus.Details(), 0)
|
||||
CheckSimilarPermission(assert, tt.expectedPermission, permission, true)
|
||||
})
|
||||
|
||||
@@ -108,6 +108,14 @@ The following `attributes` are valid on an AWS host catalog resource:
|
||||
be performed. See the [Credential Rotation](../../../README.md#credential-rotation) readme for more information.
|
||||
- `region` (string): The region to configure the host catalog for. All host sets
|
||||
in this catalog will be configured for this region.
|
||||
- `role_arn` (string): The AWS Role ARN used for `AssumeRole` authentication. If
|
||||
provided, `disable_credential_rotation` must be also set to `true.
|
||||
- `role_external_id` (string): The external id configured for the `AssumeRole`
|
||||
provider.
|
||||
- `role_session_name` (string): The session name configured for the `AssumeRole`
|
||||
provider.
|
||||
- `role_tags` (object): The key-value pair tags configured for the `AssumeRole`
|
||||
provider.
|
||||
|
||||
The following `secrets` are required on an AWS host catalog resource:
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@ import (
|
||||
// map.
|
||||
type CatalogAttributes struct {
|
||||
*cred.CredentialAttributes
|
||||
|
||||
// DualStack is used for configuring how the aws client will resolve requests.
|
||||
DualStack bool
|
||||
}
|
||||
|
||||
func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) {
|
||||
@@ -31,6 +34,12 @@ func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dualStack, err := values.GetBoolValue(in, ConstAwsDualStack, false)
|
||||
if err != nil {
|
||||
badFields[fmt.Sprintf("attributes.%s", ConstAwsDualStack)] = err.Error()
|
||||
}
|
||||
delete(unknownFields, ConstAwsDualStack)
|
||||
|
||||
for s := range unknownFields {
|
||||
switch s {
|
||||
// Ignore knownFields from CredentialAttributes
|
||||
@@ -57,6 +66,7 @@ func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) {
|
||||
|
||||
return &CatalogAttributes{
|
||||
CredentialAttributes: credAttributes,
|
||||
DualStack: dualStack,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,22 @@ func TestGetCatalogAttributes(t *testing.T) {
|
||||
},
|
||||
expectedErrContains: "attributes.bar: unrecognized field, attributes.foo: unrecognized field",
|
||||
},
|
||||
{
|
||||
name: "with dual stack",
|
||||
in: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"region": structpb.NewStringValue("us-west-2"),
|
||||
"dual_stack": structpb.NewBoolValue(true),
|
||||
},
|
||||
},
|
||||
expected: &CatalogAttributes{
|
||||
CredentialAttributes: &credential.CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
DisableCredentialRotation: false,
|
||||
},
|
||||
DualStack: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "default",
|
||||
in: &structpb.Struct{
|
||||
|
||||
@@ -10,4 +10,7 @@ const (
|
||||
// ConstInstanceNameTagKey denotes the special AWS tag key that contains an EC2
|
||||
// instance's name. This field is to be matched literally.
|
||||
ConstInstanceNameTagKey = "Name"
|
||||
|
||||
// ConstAwsDualStack is the key for the dualstack flag in the aws s3 client.
|
||||
ConstAwsDualStack = "dual_stack"
|
||||
)
|
||||
@@ -51,7 +51,7 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
credConfig, err := credential.GetCredentialsConfig(catalog.GetSecrets(), catalogAttributes.CredentialAttributes)
|
||||
credConfig, err := credential.GetCredentialsConfig(catalog.GetSecrets(), catalogAttributes.CredentialAttributes, catalogAttributes.DualStack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -83,7 +83,11 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo
|
||||
}
|
||||
|
||||
// perform dry run to ensure we can interact with AWS as expected.
|
||||
if st := dryRunValidation(ctx, catalogState); st != nil {
|
||||
opts := []ec2Option{}
|
||||
if catalogAttributes.DualStack {
|
||||
opts = append(opts, WithDualStack(catalogAttributes.DualStack))
|
||||
}
|
||||
if st := dryRunValidation(ctx, catalogState, opts); st != nil {
|
||||
return nil, st.Err()
|
||||
}
|
||||
|
||||
@@ -131,6 +135,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
|
||||
credState, err := credential.AwsCredentialPersistedStateFromProto(
|
||||
req.GetPersisted().GetSecrets(),
|
||||
oldCatalogAttributes.CredentialAttributes,
|
||||
oldCatalogAttributes.DualStack,
|
||||
p.testCredStateOpts...)
|
||||
if err != nil {
|
||||
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
||||
@@ -139,7 +144,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
|
||||
// Verify the incoming credentials are valid and return any errors to the
|
||||
// user if they're not. Note this doesn't validate the credentials against
|
||||
// AWS - it only does logical validation on the fields.
|
||||
updatedCredentials, err := credential.GetCredentialsConfig(newCatalog.GetSecrets(), newCatalogAttributes.CredentialAttributes)
|
||||
updatedCredentials, err := credential.GetCredentialsConfig(newCatalog.GetSecrets(), newCatalogAttributes.CredentialAttributes, newCatalogAttributes.DualStack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -166,7 +171,11 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
|
||||
if err != nil {
|
||||
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
||||
}
|
||||
if st := dryRunValidation(ctx, newCatalogState); st != nil {
|
||||
opts := []ec2Option{}
|
||||
if newCatalogAttributes.DualStack {
|
||||
opts = append(opts, WithDualStack(newCatalogAttributes.DualStack))
|
||||
}
|
||||
if st := dryRunValidation(ctx, newCatalogState, opts); st != nil {
|
||||
return nil, st.Err()
|
||||
}
|
||||
|
||||
@@ -208,7 +217,11 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
|
||||
}
|
||||
|
||||
// perform dry run to ensure we can interact with AWS as expected.
|
||||
if st := dryRunValidation(ctx, catalogState); st != nil {
|
||||
opts := []ec2Option{}
|
||||
if newCatalogAttributes.DualStack {
|
||||
opts = append(opts, WithDualStack(newCatalogAttributes.DualStack))
|
||||
}
|
||||
if st := dryRunValidation(ctx, catalogState, opts); st != nil {
|
||||
return nil, st.Err()
|
||||
}
|
||||
|
||||
@@ -248,6 +261,7 @@ func (p *HostPlugin) OnDeleteCatalog(ctx context.Context, req *pb.OnDeleteCatalo
|
||||
credState, err := credential.AwsCredentialPersistedStateFromProto(
|
||||
req.GetPersisted().GetSecrets(),
|
||||
catalogAttributes.CredentialAttributes,
|
||||
catalogAttributes.DualStack,
|
||||
p.testCredStateOpts...)
|
||||
if err != nil {
|
||||
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
||||
@@ -324,6 +338,7 @@ func (p *HostPlugin) OnCreateSet(ctx context.Context, req *pb.OnCreateSetRequest
|
||||
credState, err := credential.AwsCredentialPersistedStateFromProto(
|
||||
req.GetPersisted().GetSecrets(),
|
||||
catalogAttributes.CredentialAttributes,
|
||||
catalogAttributes.DualStack,
|
||||
p.testCredStateOpts...)
|
||||
if err != nil {
|
||||
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
||||
@@ -356,7 +371,11 @@ func (p *HostPlugin) OnCreateSet(ctx context.Context, req *pb.OnCreateSetRequest
|
||||
return nil, errors.BadRequestStatus("error building set filters: %s", err)
|
||||
}
|
||||
|
||||
if st := dryRunValidation(ctx, catalogState, describeInstanceFilters...); st != nil {
|
||||
opts := []ec2Option{}
|
||||
if catalogAttributes.DualStack {
|
||||
opts = append(opts, WithDualStack(catalogAttributes.DualStack))
|
||||
}
|
||||
if st := dryRunValidation(ctx, catalogState, opts, describeInstanceFilters...); st != nil {
|
||||
return nil, st.Err()
|
||||
}
|
||||
return &pb.OnCreateSetResponse{}, nil
|
||||
@@ -382,6 +401,7 @@ func (p *HostPlugin) OnUpdateSet(ctx context.Context, req *pb.OnUpdateSetRequest
|
||||
credState, err := credential.AwsCredentialPersistedStateFromProto(
|
||||
req.GetPersisted().GetSecrets(),
|
||||
catalogAttributes.CredentialAttributes,
|
||||
catalogAttributes.DualStack,
|
||||
p.testCredStateOpts...)
|
||||
if err != nil {
|
||||
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
||||
@@ -416,7 +436,11 @@ func (p *HostPlugin) OnUpdateSet(ctx context.Context, req *pb.OnUpdateSetRequest
|
||||
return nil, errors.BadRequestStatus("error building set filters: %s", err)
|
||||
}
|
||||
|
||||
if st := dryRunValidation(ctx, catalogState, describeInstanceFilters...); st != nil {
|
||||
opts := []ec2Option{}
|
||||
if catalogAttributes.DualStack {
|
||||
opts = append(opts, WithDualStack(catalogAttributes.DualStack))
|
||||
}
|
||||
if st := dryRunValidation(ctx, catalogState, opts, describeInstanceFilters...); st != nil {
|
||||
return nil, st.Err()
|
||||
}
|
||||
|
||||
@@ -450,6 +474,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
|
||||
credState, err := credential.AwsCredentialPersistedStateFromProto(
|
||||
req.GetPersisted().GetSecrets(),
|
||||
catalogAttributes.CredentialAttributes,
|
||||
catalogAttributes.DualStack,
|
||||
p.testCredStateOpts...)
|
||||
if err != nil {
|
||||
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
||||
@@ -502,7 +527,11 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
|
||||
}
|
||||
}
|
||||
|
||||
ec2Client, err := catalogState.EC2Client(ctx)
|
||||
opts := []ec2Option{}
|
||||
if catalogAttributes.DualStack {
|
||||
opts = append(opts, WithDualStack(catalogAttributes.DualStack))
|
||||
}
|
||||
ec2Client, err := catalogState.EC2Client(ctx, opts...)
|
||||
if err != nil {
|
||||
return nil, errors.BadRequestStatus("error getting EC2 client: %s", err)
|
||||
}
|
||||
@@ -673,12 +702,12 @@ func awsInstanceToHost(instance types.Instance) (*pb.ListHostsResponseHost, erro
|
||||
// credentials, the host listing functionality as well as the filters, if any
|
||||
// are passed in. This function can therefore be used for both host catalog and
|
||||
// host set validation.
|
||||
func dryRunValidation(ctx context.Context, state *awsCatalogPersistedState, filters ...types.Filter) *status.Status {
|
||||
func dryRunValidation(ctx context.Context, state *awsCatalogPersistedState, ec2Opts []ec2Option, filters ...types.Filter) *status.Status {
|
||||
if state == nil {
|
||||
return status.New(codes.InvalidArgument, "persisted state is required")
|
||||
}
|
||||
|
||||
ec2Client, err := state.EC2Client(ctx)
|
||||
ec2Client, err := state.EC2Client(ctx, ec2Opts...)
|
||||
if err != nil {
|
||||
return status.New(codes.InvalidArgument, fmt.Sprintf("error getting EC2 client: %s", err))
|
||||
}
|
||||
|
||||
@@ -683,7 +683,7 @@ func TestPluginOnCreateCatalogErr(t *testing.T) {
|
||||
),
|
||||
}),
|
||||
},
|
||||
expectedErrContains: "error rotating credentials",
|
||||
expectedErrContains: "rotating credentials",
|
||||
expectedErrCode: codes.Unknown,
|
||||
},
|
||||
{
|
||||
@@ -1089,7 +1089,7 @@ func TestPluginOnUpdateCatalogErr(t *testing.T) {
|
||||
),
|
||||
}),
|
||||
},
|
||||
expectedErrContains: "failed to delete access key",
|
||||
expectedErrContains: "deleting credentials",
|
||||
expectedErrCode: codes.Unknown,
|
||||
},
|
||||
{
|
||||
@@ -1167,7 +1167,7 @@ func TestPluginOnUpdateCatalogErr(t *testing.T) {
|
||||
),
|
||||
}),
|
||||
},
|
||||
expectedErrContains: "error rotating credentials",
|
||||
expectedErrContains: "rotating credentials",
|
||||
expectedErrCode: codes.Unknown,
|
||||
},
|
||||
{
|
||||
@@ -1298,7 +1298,7 @@ func TestPluginOnDeleteCatalogErr(t *testing.T) {
|
||||
),
|
||||
}),
|
||||
},
|
||||
expectedErrContains: "aws service unknown: unknown error: failed to delete access key",
|
||||
expectedErrContains: "aws service unknown: unknown error: deleting credentials:",
|
||||
expectedErrCode: codes.Unknown,
|
||||
},
|
||||
}
|
||||
@@ -2747,8 +2747,8 @@ func TestAppendDistinct(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDryRunValidation(t *testing.T) {
|
||||
t.Run("nilState", func(t *testing.T) {
|
||||
st := dryRunValidation(context.Background(), nil)
|
||||
t.Run("nil credential state", func(t *testing.T) {
|
||||
st := dryRunValidation(context.Background(), nil, nil)
|
||||
require.NotNil(t, st)
|
||||
require.Equal(t, codes.InvalidArgument.String(), st.Code().String())
|
||||
require.Equal(t, "persisted state is required", st.Message())
|
||||
@@ -2762,7 +2762,7 @@ func TestDryRunValidation(t *testing.T) {
|
||||
testEC2APIFunc: func(...aws.Config) (EC2API, error) {
|
||||
return nil, fmt.Errorf("oops ec2 client err")
|
||||
},
|
||||
})
|
||||
}, []ec2Option{})
|
||||
require.NotNil(t, st)
|
||||
require.Equal(t, codes.InvalidArgument.String(), st.Code().String())
|
||||
require.Equal(t, "error getting EC2 client: oops ec2 client err", st.Message())
|
||||
@@ -2777,7 +2777,7 @@ func TestDryRunValidation(t *testing.T) {
|
||||
},
|
||||
},
|
||||
testEC2APIFunc: newTestMockEC2(nil, testMockEC2WithDescribeInstancesError(fmt.Errorf("oops describe instances error"))),
|
||||
})
|
||||
}, []ec2Option{})
|
||||
require.NotNil(t, st)
|
||||
require.Equal(t, codes.FailedPrecondition.String(), st.Code().String())
|
||||
require.Equal(t, "aws describe instances failed: oops describe instances error", st.Message())
|
||||
@@ -2792,7 +2792,7 @@ func TestDryRunValidation(t *testing.T) {
|
||||
},
|
||||
},
|
||||
testEC2APIFunc: newTestMockEC2(nil, testMockEC2WithDescribeInstancesOutput(&ec2.DescribeInstancesOutput{})),
|
||||
})
|
||||
}, []ec2Option{})
|
||||
require.Nil(t, st)
|
||||
})
|
||||
}
|
||||
@@ -7,12 +7,10 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
||||
smithyEndpoints "github.com/aws/smithy-go/endpoints"
|
||||
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
||||
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
@@ -22,17 +20,6 @@ type EC2API interface {
|
||||
DescribeInstances(context.Context, *ec2.DescribeInstancesInput, ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error)
|
||||
}
|
||||
|
||||
var customClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
},
|
||||
}
|
||||
|
||||
type awsCatalogPersistedState struct {
|
||||
*credential.AwsCredentialPersistedState
|
||||
|
||||
@@ -86,7 +73,11 @@ func (s *awsCatalogPersistedState) toProto() (*pb.HostCatalogPersisted, error) {
|
||||
|
||||
// EC2Client returns a configured EC2 client based on the session
|
||||
// information stored in the state.
|
||||
func (s *awsCatalogPersistedState) EC2Client(ctx context.Context) (EC2API, error) {
|
||||
func (s *awsCatalogPersistedState) EC2Client(ctx context.Context, opt ...ec2Option) (EC2API, error) {
|
||||
opts, err := getOpts(opt...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing options when fetching EC2 client: %w", err)
|
||||
}
|
||||
awsCfg, err := s.GenerateCredentialChain(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting AWS configuration when fetching EC2 client: %w", err)
|
||||
@@ -94,8 +85,58 @@ func (s *awsCatalogPersistedState) EC2Client(ctx context.Context) (EC2API, error
|
||||
if awsCfg == nil {
|
||||
return nil, fmt.Errorf("nil aws configuration")
|
||||
}
|
||||
var ec2Opts []func(*ec2.Options)
|
||||
if opts.withDualStack {
|
||||
ec2Opts = append(ec2Opts, ec2.WithEndpointResolverV2(&endpointResolver{
|
||||
dualStack: opts.withDualStack,
|
||||
}))
|
||||
}
|
||||
if s.testEC2APIFunc != nil {
|
||||
return s.testEC2APIFunc(*awsCfg)
|
||||
}
|
||||
return ec2.NewFromConfig(*awsCfg), nil
|
||||
return ec2.NewFromConfig(*awsCfg, ec2Opts...), nil
|
||||
}
|
||||
|
||||
// getOpts iterates the inbound ec2Options and returns a struct
|
||||
func getOpts(opt ...ec2Option) (ec2Options, error) {
|
||||
opts := getDefaultEc2Options()
|
||||
for _, o := range opt {
|
||||
if o == nil {
|
||||
continue
|
||||
}
|
||||
if err := o(&opts); err != nil {
|
||||
return ec2Options{}, err
|
||||
}
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
// ec2Option - how ec2Options are passed as arguments
|
||||
type ec2Option func(*ec2Options) error
|
||||
|
||||
// options = how options are represented
|
||||
type ec2Options struct {
|
||||
withDualStack bool
|
||||
}
|
||||
|
||||
func getDefaultEc2Options() ec2Options {
|
||||
return ec2Options{}
|
||||
}
|
||||
|
||||
// WithDualStack sets the dual stack resolver
|
||||
func WithDualStack(with bool) ec2Option {
|
||||
return func(o *ec2Options) error {
|
||||
o.withDualStack = with
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type endpointResolver struct {
|
||||
dualStack bool
|
||||
}
|
||||
|
||||
func (e *endpointResolver) ResolveEndpoint(ctx context.Context, params ec2.EndpointParameters) (resolver smithyEndpoints.Endpoint, err error) {
|
||||
params.UseDualStack = aws.Bool(e.dualStack)
|
||||
resolver, err = ec2.NewDefaultEndpointResolverV2().ResolveEndpoint(ctx, params)
|
||||
return
|
||||
}
|
||||
@@ -19,6 +19,9 @@ type StorageAttributes struct {
|
||||
|
||||
// EndpointUrl is used for configuring how the aws client will resolve requests.
|
||||
EndpointUrl string
|
||||
|
||||
// DualStack is used for configuring how the aws client will resolve requests.
|
||||
DualStack bool
|
||||
}
|
||||
|
||||
func getStorageAttributes(in *structpb.Struct) (*StorageAttributes, error) {
|
||||
@@ -37,6 +40,12 @@ func getStorageAttributes(in *structpb.Struct) (*StorageAttributes, error) {
|
||||
}
|
||||
delete(unknownFields, ConstAwsEndpointUrl)
|
||||
|
||||
dualStack, err := values.GetBoolValue(in, ConstAwsDualStack, false)
|
||||
if err != nil {
|
||||
badFields[fmt.Sprintf("attributes.%s", ConstAwsDualStack)] = err.Error()
|
||||
}
|
||||
delete(unknownFields, ConstAwsDualStack)
|
||||
|
||||
for s := range unknownFields {
|
||||
switch s {
|
||||
// Ignore knownFields from CredentialAttributes
|
||||
@@ -64,5 +73,6 @@ func getStorageAttributes(in *structpb.Struct) (*StorageAttributes, error) {
|
||||
return &StorageAttributes{
|
||||
CredentialAttributes: credAttributes,
|
||||
EndpointUrl: endpointUrl,
|
||||
DualStack: dualStack,
|
||||
}, nil
|
||||
}
|
||||
@@ -41,6 +41,21 @@ func TestGetStorageAttributes(t *testing.T) {
|
||||
},
|
||||
expectedErrContains: "attributes.bar: unrecognized field, attributes.foo: unrecognized field",
|
||||
},
|
||||
{
|
||||
name: "good w/ dualstack",
|
||||
in: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"region": structpb.NewStringValue("us-west-2"),
|
||||
"dual_stack": structpb.NewBoolValue(true),
|
||||
},
|
||||
},
|
||||
expected: &StorageAttributes{
|
||||
CredentialAttributes: &cred.CredentialAttributes{
|
||||
Region: "us-west-2",
|
||||
},
|
||||
DualStack: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "good w/ endpoint",
|
||||
in: &structpb.Struct{
|
||||
|
||||
@@ -7,6 +7,9 @@ const (
|
||||
// ConstAwsEndpointUrl is the key for the endpoint url in the aws s3 client.
|
||||
ConstAwsEndpointUrl = "endpoint_url"
|
||||
|
||||
// ConstAwsDualStack is the key for the dualstack flag in the aws s3 client.
|
||||
ConstAwsDualStack = "dual_stack"
|
||||
|
||||
// defaultStreamChunkSize is the recommened chunk size for sending data through a stream
|
||||
defaultStreamChunkSize = 65536 // 64 KiB
|
||||
)
|
||||
@@ -50,12 +50,12 @@ func setStatePermission(req any, permission *pb.Permission) *pb.StorageBucketCre
|
||||
// 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 {
|
||||
func parseS3Error(op string, err error, req any) *status.Status {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
st, p := errors.ParseAWSError(err, msg)
|
||||
st, p := errors.ParseAWSError(op, err)
|
||||
if p != nil {
|
||||
state := setStatePermission(req, p)
|
||||
if st, err = st.WithDetails(state); err != nil {
|
||||
|
||||
@@ -34,7 +34,7 @@ func Test_ParseS3Error(t *testing.T) {
|
||||
req: &pb.GetObjectRequest{},
|
||||
err: TestAwsS3Error("NoSuchBucket", "GetObject", "no such bucket"),
|
||||
expectedStatusCode: codes.NotFound,
|
||||
expectedStatusMsg: "aws s3 error: test",
|
||||
expectedStatusMsg: "aws service s3: test:",
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
Read: &pb.Permission{
|
||||
@@ -50,7 +50,7 @@ func Test_ParseS3Error(t *testing.T) {
|
||||
req: &pb.GetObjectRequest{},
|
||||
err: TestAwsS3Error("NoSuchKey", "GetObject", "no such key"),
|
||||
expectedStatusCode: codes.NotFound,
|
||||
expectedStatusMsg: "aws s3 error: test",
|
||||
expectedStatusMsg: "aws service s3: test:",
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
Read: &pb.Permission{
|
||||
@@ -66,7 +66,7 @@ func Test_ParseS3Error(t *testing.T) {
|
||||
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",
|
||||
expectedStatusMsg: "aws service unknown: resource not found: test:",
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
Read: &pb.Permission{
|
||||
@@ -81,7 +81,7 @@ func Test_ParseS3Error(t *testing.T) {
|
||||
req: &pb.GetObjectRequest{},
|
||||
err: TestAwsS3Error("InvalidObjectState", "GetObject", "invalid object state"),
|
||||
expectedStatusCode: codes.NotFound,
|
||||
expectedStatusMsg: "aws s3 error: test",
|
||||
expectedStatusMsg: "aws service s3: test:",
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
Read: &pb.Permission{
|
||||
@@ -96,7 +96,7 @@ func Test_ParseS3Error(t *testing.T) {
|
||||
req: &pb.PutObjectRequest{},
|
||||
err: TestAwsS3Error(s3ErrorBadDigest, "PutObject", "checksum mismatch"),
|
||||
expectedStatusCode: codes.Aborted,
|
||||
expectedStatusMsg: "aws s3 error: test",
|
||||
expectedStatusMsg: "aws service s3: test:",
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
Write: &pb.Permission{
|
||||
@@ -111,7 +111,7 @@ func Test_ParseS3Error(t *testing.T) {
|
||||
req: &pb.GetObjectRequest{},
|
||||
err: TestAwsS3Error("Throttling", "action", "throttling exception"),
|
||||
expectedStatusCode: codes.Unavailable,
|
||||
expectedStatusMsg: "aws service s3: throttling error: test",
|
||||
expectedStatusMsg: "aws service s3: throttling: test:",
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
Read: &pb.Permission{
|
||||
@@ -126,7 +126,7 @@ func Test_ParseS3Error(t *testing.T) {
|
||||
req: &pb.GetObjectRequest{},
|
||||
err: TestAwsS3Error("RequestTimeout", "action", "request timeout"),
|
||||
expectedStatusCode: codes.DeadlineExceeded,
|
||||
expectedStatusMsg: "aws s3 error: test",
|
||||
expectedStatusMsg: "aws service s3: test:",
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
Read: &pb.Permission{
|
||||
@@ -141,7 +141,7 @@ func Test_ParseS3Error(t *testing.T) {
|
||||
req: &pb.GetObjectRequest{},
|
||||
err: TestAwsS3Error("AccessDenied", "action", "access denied"),
|
||||
expectedStatusCode: codes.PermissionDenied,
|
||||
expectedStatusMsg: "aws service s3: invalid credentials error: test",
|
||||
expectedStatusMsg: "aws service s3: invalid credentials: test:",
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
Read: &pb.Permission{
|
||||
@@ -157,14 +157,14 @@ func Test_ParseS3Error(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require, assert := require.New(t), assert.New(t)
|
||||
|
||||
actualStatus := parseS3Error(tt.err, "test", tt.req)
|
||||
actualStatus := parseS3Error("test", tt.err, tt.req)
|
||||
if tt.expectedStatusCode == codes.OK {
|
||||
require.Nil(actualStatus)
|
||||
return
|
||||
}
|
||||
|
||||
require.Equal(tt.expectedStatusCode, actualStatus.Code())
|
||||
require.Equal(tt.expectedStatusMsg, actualStatus.Message())
|
||||
require.Contains(actualStatus.Message(), tt.expectedStatusMsg)
|
||||
require.Len(actualStatus.Details(), 1)
|
||||
|
||||
foundDetail := false
|
||||
|
||||
@@ -64,7 +64,7 @@ func (p *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCre
|
||||
return nil, err
|
||||
}
|
||||
|
||||
credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes)
|
||||
credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes, storageAttributes.DualStack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -147,6 +147,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
|
||||
credState, err := cred.AwsCredentialPersistedStateFromProto(
|
||||
req.GetPersisted().GetData(),
|
||||
oldStorageAttributes.CredentialAttributes,
|
||||
oldStorageAttributes.DualStack,
|
||||
p.testCredStateOpts...)
|
||||
if err != nil {
|
||||
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
||||
@@ -155,7 +156,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
|
||||
// Verify the incoming credentials are valid and return any errors to the
|
||||
// user if they're not. Note this doesn't validate the credentials against
|
||||
// AWS - it only does logical validation on the fields.
|
||||
updatedCredentials, err := cred.GetCredentialsConfig(newBucket.GetSecrets(), newStorageAttributes.CredentialAttributes)
|
||||
updatedCredentials, err := cred.GetCredentialsConfig(newBucket.GetSecrets(), newStorageAttributes.CredentialAttributes, newStorageAttributes.DualStack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -264,6 +265,7 @@ func (p *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDel
|
||||
credState, err := cred.AwsCredentialPersistedStateFromProto(
|
||||
req.GetPersisted().GetData(),
|
||||
storageAttributes.CredentialAttributes,
|
||||
storageAttributes.DualStack,
|
||||
p.testCredStateOpts...)
|
||||
if err != nil {
|
||||
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
||||
@@ -318,7 +320,7 @@ func (p *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReques
|
||||
return nil, err
|
||||
}
|
||||
|
||||
credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes)
|
||||
credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes, storageAttributes.DualStack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -346,6 +348,9 @@ func (p *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReques
|
||||
if storageAttributes.EndpointUrl != "" {
|
||||
opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl))
|
||||
}
|
||||
if storageAttributes.DualStack {
|
||||
opts = append(opts, WithDualStack(storageAttributes.DualStack))
|
||||
}
|
||||
s3Client, err := storageState.S3Client(ctx, opts...)
|
||||
if err != nil {
|
||||
return nil, errors.BadRequestStatus("error getting S3 client: %s", err)
|
||||
@@ -357,10 +362,10 @@ func (p *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReques
|
||||
Key: aws.String(objectKey),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, parseS3Error(err, "failed to head object", req).Err()
|
||||
return nil, parseS3Error("head object", err, req).Err()
|
||||
}
|
||||
return &pb.HeadObjectResponse{
|
||||
ContentLength: resp.ContentLength,
|
||||
ContentLength: aws.ToInt64(resp.ContentLength),
|
||||
LastModified: timestamppb.New(*resp.LastModified),
|
||||
}, nil
|
||||
}
|
||||
@@ -386,7 +391,7 @@ func (p *StoragePlugin) ValidatePermissions(ctx context.Context, req *pb.Validat
|
||||
return nil, err
|
||||
}
|
||||
|
||||
credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes)
|
||||
credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes, storageAttributes.DualStack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -444,7 +449,7 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu
|
||||
return err
|
||||
}
|
||||
|
||||
credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes)
|
||||
credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes, storageAttributes.DualStack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -472,6 +477,9 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu
|
||||
if storageAttributes.EndpointUrl != "" {
|
||||
opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl))
|
||||
}
|
||||
if storageAttributes.DualStack {
|
||||
opts = append(opts, WithDualStack(storageAttributes.DualStack))
|
||||
}
|
||||
s3Client, err := storageState.S3Client(stream.Context(), opts...)
|
||||
if err != nil {
|
||||
return errors.BadRequestStatus("error getting S3 client: %s", err)
|
||||
@@ -483,7 +491,7 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu
|
||||
Key: aws.String(objectKey),
|
||||
})
|
||||
if err != nil {
|
||||
return parseS3Error(err, "failed to get object", req).Err()
|
||||
return parseS3Error("get object", err, req).Err()
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
@@ -555,7 +563,7 @@ func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes)
|
||||
credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes, storageAttributes.DualStack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -583,6 +591,9 @@ func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest)
|
||||
if storageAttributes.EndpointUrl != "" {
|
||||
opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl))
|
||||
}
|
||||
if storageAttributes.DualStack {
|
||||
opts = append(opts, WithDualStack(storageAttributes.DualStack))
|
||||
}
|
||||
s3Client, err := storageState.S3Client(ctx, opts...)
|
||||
if err != nil {
|
||||
return nil, errors.BadRequestStatus("error getting S3 client: %s", err)
|
||||
@@ -608,7 +619,7 @@ func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest)
|
||||
ChecksumSHA256: aws.String(checksum),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, parseS3Error(err, "failed to put object", req).Err()
|
||||
return nil, parseS3Error("put object", err, req).Err()
|
||||
}
|
||||
if resp.ChecksumSHA256 == nil {
|
||||
return nil, errors.UnknownStatus("missing checksum response from aws")
|
||||
@@ -650,7 +661,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
|
||||
return nil, err
|
||||
}
|
||||
|
||||
credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes)
|
||||
credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes, storageAttributes.DualStack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -696,7 +707,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
|
||||
Key: aws.String(prefix),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, parseS3Error(err, "failed to delete object", req).Err()
|
||||
return nil, parseS3Error("delete object", err, req).Err()
|
||||
}
|
||||
return &pb.DeleteObjectsResponse{
|
||||
ObjectsDeleted: uint32(1),
|
||||
@@ -712,13 +723,13 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
|
||||
res, err := client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{
|
||||
Bucket: aws.String(bucket.GetBucketName()),
|
||||
Prefix: aws.String(prefix),
|
||||
MaxKeys: maxkeys,
|
||||
MaxKeys: aws.Int32(maxkeys),
|
||||
ContinuationToken: conToken,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, parseS3Error(err, "failed to list objects", req).Err()
|
||||
return nil, parseS3Error("list objects", err, req).Err()
|
||||
}
|
||||
truncated = res.IsTruncated
|
||||
truncated = aws.ToBool(res.IsTruncated)
|
||||
conToken = res.NextContinuationToken
|
||||
for _, o := range res.Contents {
|
||||
objects = append(objects, types.ObjectIdentifier{
|
||||
@@ -738,7 +749,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, parseS3Error(err, "failed to delete objects", req).Err()
|
||||
return nil, parseS3Error("delete objects", err, req).Err()
|
||||
}
|
||||
deleted += len(res.Deleted)
|
||||
}
|
||||
@@ -769,6 +780,9 @@ func dryRunValidation(ctx context.Context, state *awsStoragePersistedState, attr
|
||||
if attrs.EndpointUrl != "" {
|
||||
opts = append(opts, WithEndpoint(attrs.EndpointUrl))
|
||||
}
|
||||
if attrs.DualStack {
|
||||
opts = append(opts, WithDualStack(attrs.DualStack))
|
||||
}
|
||||
|
||||
client, err := state.S3Client(ctx, opts...)
|
||||
if err != nil {
|
||||
@@ -786,7 +800,7 @@ func dryRunValidation(ctx context.Context, state *awsStoragePersistedState, attr
|
||||
Body: bytes.NewReader([]byte("hashicorp boundary aws plugin access test")),
|
||||
}); err != nil {
|
||||
var st *status.Status
|
||||
st, permissions.Write = errors.ParseAWSError(err, "failed to put object")
|
||||
st, permissions.Write = errors.ParseAWSError("put object", err)
|
||||
errs = multierror.Append(errs, st.Err())
|
||||
} else {
|
||||
permissions.Write = &pb.Permission{State: pb.StateType_STATE_TYPE_OK, CheckedAt: timestamppb.Now()}
|
||||
@@ -797,7 +811,7 @@ func dryRunValidation(ctx context.Context, state *awsStoragePersistedState, attr
|
||||
Key: aws.String(objectKey),
|
||||
}); err != nil {
|
||||
var st *status.Status
|
||||
st, permissions.Read = errors.ParseAWSError(err, "failed to get object")
|
||||
st, permissions.Read = errors.ParseAWSError("get object", err)
|
||||
errs = multierror.Append(errs, st.Err())
|
||||
} else {
|
||||
permissions.Read = &pb.Permission{State: pb.StateType_STATE_TYPE_OK, CheckedAt: timestamppb.Now()}
|
||||
@@ -808,7 +822,7 @@ func dryRunValidation(ctx context.Context, state *awsStoragePersistedState, attr
|
||||
Key: aws.String(objectKey),
|
||||
}); err != nil {
|
||||
var st *status.Status
|
||||
st, permissions.Read = errors.ParseAWSError(err, "failed to head object")
|
||||
st, permissions.Read = errors.ParseAWSError("head object", err)
|
||||
errs = multierror.Append(errs, st.Err())
|
||||
}
|
||||
|
||||
@@ -817,7 +831,7 @@ func dryRunValidation(ctx context.Context, state *awsStoragePersistedState, attr
|
||||
Prefix: aws.String(objectKey),
|
||||
}); err != nil {
|
||||
var st *status.Status
|
||||
st, permissions.Read = errors.ParseAWSError(err, "failed to list object")
|
||||
st, permissions.Read = errors.ParseAWSError("list object", err)
|
||||
errs = multierror.Append(errs, st.Err())
|
||||
} else if res == nil || len(res.Contents) != 1 || *res.Contents[0].Key != objectKey {
|
||||
permissions.Read = &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, CheckedAt: timestamppb.Now()}
|
||||
@@ -829,7 +843,7 @@ func dryRunValidation(ctx context.Context, state *awsStoragePersistedState, attr
|
||||
Key: aws.String(objectKey),
|
||||
}); err != nil {
|
||||
var st *status.Status
|
||||
st, permissions.Delete = errors.ParseAWSError(err, "failed to delete object")
|
||||
st, permissions.Delete = errors.ParseAWSError("delete object", err)
|
||||
errs = multierror.Append(errs, st.Err())
|
||||
} else {
|
||||
permissions.Delete = &pb.Permission{State: pb.StateType_STATE_TYPE_OK, CheckedAt: timestamppb.Now()}
|
||||
|
||||
@@ -153,7 +153,7 @@ func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) {
|
||||
),
|
||||
}),
|
||||
},
|
||||
expectedErrContains: "aws service unknown: unknown error: error rotating credentials",
|
||||
expectedErrContains: "aws service unknown: unknown error: rotating credentials:",
|
||||
expectedErrCode: codes.Unknown,
|
||||
expectedDetails: nil,
|
||||
},
|
||||
@@ -178,7 +178,7 @@ func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) {
|
||||
}),
|
||||
)),
|
||||
},
|
||||
expectedErrContains: "aws service s3: invalid credentials error: failed to put object",
|
||||
expectedErrContains: "aws service s3: invalid credentials: put object:",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -211,7 +211,7 @@ func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) {
|
||||
}),
|
||||
)),
|
||||
},
|
||||
expectedErrContains: "aws service s3: throttling error: failed to put object",
|
||||
expectedErrContains: "aws service s3: throttling: put object:",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -245,7 +245,7 @@ func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: invalid credentials error: failed to get object",
|
||||
expectedErrContains: "aws service s3: invalid credentials: get object:",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -280,7 +280,7 @@ func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: throttling error: failed to get object",
|
||||
expectedErrContains: "aws service s3: throttling: get object:",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -314,7 +314,7 @@ func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: invalid credentials error: failed to head object",
|
||||
expectedErrContains: "aws service s3: invalid credentials: head object:",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -349,7 +349,7 @@ func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: throttling error: failed to head object",
|
||||
expectedErrContains: "aws service s3: throttling: head object:",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -384,7 +384,7 @@ func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: invalid credentials error: failed to delete object",
|
||||
expectedErrContains: "aws service s3: invalid credentials: delete object:",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -857,7 +857,7 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
|
||||
),
|
||||
}),
|
||||
},
|
||||
expectedErrContains: "aws service unknown: unknown error: failed to delete access key",
|
||||
expectedErrContains: "aws service unknown: unknown error: deleting credentials:",
|
||||
expectedErrCode: codes.Unknown,
|
||||
},
|
||||
{
|
||||
@@ -896,7 +896,7 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
|
||||
),
|
||||
}),
|
||||
},
|
||||
expectedErrContains: "aws service unknown: unknown error: error rotating credentials",
|
||||
expectedErrContains: "aws service unknown: unknown error: rotating credentials",
|
||||
expectedErrCode: codes.Unknown,
|
||||
},
|
||||
{
|
||||
@@ -920,7 +920,7 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
|
||||
testMockS3WithPutObjectError(TestAwsS3Error("AccessDenied", "PutObject", "not authorized")),
|
||||
)),
|
||||
},
|
||||
expectedErrContains: "aws service s3: invalid credentials error: failed to put object",
|
||||
expectedErrContains: "aws service s3: invalid credentials: put object:",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -953,7 +953,7 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
|
||||
testMockS3WithPutObjectError(TestAwsS3Error("ThrottlingException", "PutObject", "throttling exception")),
|
||||
)),
|
||||
},
|
||||
expectedErrContains: "aws service s3: throttling error: failed to put object",
|
||||
expectedErrContains: "aws service s3: throttling: put object",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -987,7 +987,7 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: invalid credentials error: failed to get object",
|
||||
expectedErrContains: "aws service s3: invalid credentials: get object",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -1022,7 +1022,7 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: throttling error: failed to get object",
|
||||
expectedErrContains: "aws service s3: throttling: get object",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -1056,7 +1056,7 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: invalid credentials error: failed to head object",
|
||||
expectedErrContains: "aws service s3: invalid credentials: head object",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -1091,7 +1091,7 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: throttling error: failed to head object",
|
||||
expectedErrContains: "aws service s3: throttling: head object",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -1255,7 +1255,7 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: invalid credentials error: failed to head object",
|
||||
expectedErrContains: "aws service s3: invalid credentials: head object",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -1363,7 +1363,7 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: invalid credentials error: failed to put object",
|
||||
expectedErrContains: "aws service s3: invalid credentials: put object",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -1486,7 +1486,7 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
|
||||
),
|
||||
}),
|
||||
},
|
||||
expectedErrContains: "aws service unknown: unknown error: failed to delete access key",
|
||||
expectedErrContains: "aws service unknown: unknown error: deleting credentials",
|
||||
expectedErrCode: codes.Unknown,
|
||||
},
|
||||
{
|
||||
@@ -1599,7 +1599,7 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: invalid credentials error: failed to put object",
|
||||
expectedErrContains: "aws service s3: invalid credentials: put object",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -1738,7 +1738,7 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
|
||||
),
|
||||
}),
|
||||
},
|
||||
expectedErrContains: "aws service unknown: unknown error: error rotating credentials",
|
||||
expectedErrContains: "aws service unknown: unknown error: rotating credentials",
|
||||
expectedErrCode: codes.Unknown,
|
||||
},
|
||||
{
|
||||
@@ -1973,7 +1973,7 @@ func TestStoragePlugin_OnDeleteStorageBucket(t *testing.T) {
|
||||
),
|
||||
}),
|
||||
},
|
||||
expectedErrContains: "aws service unknown: unknown error: failed to delete access key",
|
||||
expectedErrContains: "aws service unknown: unknown error: deleting credentials",
|
||||
expectedErrCode: codes.Unknown,
|
||||
},
|
||||
{
|
||||
@@ -2172,7 +2172,7 @@ func TestStoragePlugin_HeadObject(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: invalid credentials error: failed to head object",
|
||||
expectedErrContains: "aws service s3: invalid credentials: head object",
|
||||
expectedErrCode: codes.PermissionDenied,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -2196,7 +2196,7 @@ func TestStoragePlugin_HeadObject(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: throttling error: failed to head object",
|
||||
expectedErrContains: "aws service s3: throttling: head object",
|
||||
expectedErrCode: codes.Unavailable,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -2219,7 +2219,7 @@ func TestStoragePlugin_HeadObject(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws s3 error: failed to head object",
|
||||
expectedErrContains: "aws service s3: head object",
|
||||
expectedErrCode: codes.NotFound,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -2241,7 +2241,7 @@ func TestStoragePlugin_HeadObject(t *testing.T) {
|
||||
newTestMockS3(
|
||||
nil,
|
||||
testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{
|
||||
ContentLength: 1024,
|
||||
ContentLength: aws.Int64(1024),
|
||||
LastModified: aws.Time(createTime(t, "2006-01-02T15:04:05Z")),
|
||||
}),
|
||||
),
|
||||
@@ -2272,7 +2272,7 @@ func TestStoragePlugin_HeadObject(t *testing.T) {
|
||||
newTestMockS3(
|
||||
nil,
|
||||
testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{
|
||||
ContentLength: 1024,
|
||||
ContentLength: aws.Int64(1024),
|
||||
LastModified: aws.Time(createTime(t, "2006-01-02T15:04:05Z")),
|
||||
}),
|
||||
),
|
||||
@@ -2425,7 +2425,7 @@ func TestStoragePlugin_ValidatePermissions(t *testing.T) {
|
||||
}),
|
||||
)),
|
||||
},
|
||||
expectedErrContains: "aws service s3: invalid credentials error: failed to put object",
|
||||
expectedErrContains: "aws service s3: invalid credentials: put object",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -2458,7 +2458,7 @@ func TestStoragePlugin_ValidatePermissions(t *testing.T) {
|
||||
}),
|
||||
)),
|
||||
},
|
||||
expectedErrContains: "aws service s3: throttling error: failed to put object",
|
||||
expectedErrContains: "aws service s3: throttling: put object",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -2492,7 +2492,7 @@ func TestStoragePlugin_ValidatePermissions(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: invalid credentials error: failed to get object",
|
||||
expectedErrContains: "aws service s3: invalid credentials: get object",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -2527,7 +2527,7 @@ func TestStoragePlugin_ValidatePermissions(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: throttling error: failed to get object",
|
||||
expectedErrContains: "aws service s3: throttling: get object",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -2561,7 +2561,7 @@ func TestStoragePlugin_ValidatePermissions(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: invalid credentials error: failed to head object",
|
||||
expectedErrContains: "aws service s3: invalid credentials: head object",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -2596,7 +2596,7 @@ func TestStoragePlugin_ValidatePermissions(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: throttling error: failed to head object",
|
||||
expectedErrContains: "aws service s3: throttling: head object",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -2683,7 +2683,7 @@ func TestStoragePlugin_ValidatePermissions(t *testing.T) {
|
||||
}),
|
||||
)),
|
||||
},
|
||||
expectedErrContains: "aws service s3: invalid credentials error: failed to put object",
|
||||
expectedErrContains: "aws service s3: invalid credentials: put object",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -2851,7 +2851,7 @@ func TestStoragePlugin_GetObject(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: invalid credentials error: failed to get object",
|
||||
expectedErrContains: "aws service s3: invalid credentials: get object",
|
||||
expectedErrCode: codes.PermissionDenied,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -2875,7 +2875,7 @@ func TestStoragePlugin_GetObject(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws s3 error: failed to get object",
|
||||
expectedErrContains: "aws service s3: get object",
|
||||
expectedErrCode: codes.NotFound,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -2898,7 +2898,7 @@ func TestStoragePlugin_GetObject(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws s3 error: failed to get object",
|
||||
expectedErrContains: "aws service s3: get object",
|
||||
expectedErrCode: codes.NotFound,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -2921,7 +2921,7 @@ func TestStoragePlugin_GetObject(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws s3 error: failed to get object",
|
||||
expectedErrContains: "aws service s3: get object",
|
||||
expectedErrCode: codes.NotFound,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -2945,7 +2945,7 @@ func TestStoragePlugin_GetObject(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: throttling error: failed to get object",
|
||||
expectedErrContains: "aws service s3: throttling: get object",
|
||||
expectedErrCode: codes.Unavailable,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -3300,7 +3300,7 @@ func TestStoragePlugin_PutObject(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: invalid credentials error: failed to put object",
|
||||
expectedErrContains: "aws service s3: invalid credentials: put object",
|
||||
expectedErrCode: codes.PermissionDenied,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -3324,7 +3324,7 @@ func TestStoragePlugin_PutObject(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws s3 error: failed to put object",
|
||||
expectedErrContains: "aws service s3: put object",
|
||||
expectedErrCode: codes.NotFound,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -3348,7 +3348,7 @@ func TestStoragePlugin_PutObject(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: throttling error: failed to put object",
|
||||
expectedErrContains: "aws service s3: throttling: put object",
|
||||
expectedErrCode: codes.Unavailable,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -3640,7 +3640,7 @@ func TestStoragePlugin_DeleteObjects(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: invalid credentials error: failed to delete object",
|
||||
expectedErrContains: "aws service s3: invalid credentials: delete object",
|
||||
expectedErrCode: codes.PermissionDenied,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -3664,7 +3664,7 @@ func TestStoragePlugin_DeleteObjects(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: invalid credentials error: failed to list objects",
|
||||
expectedErrContains: "aws service s3: invalid credentials: list objects",
|
||||
expectedErrCode: codes.PermissionDenied,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -3685,7 +3685,7 @@ func TestStoragePlugin_DeleteObjects(t *testing.T) {
|
||||
newTestMockS3(
|
||||
nil,
|
||||
testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{
|
||||
IsTruncated: false,
|
||||
IsTruncated: aws.Bool(false),
|
||||
Contents: []s3types.Object{
|
||||
{
|
||||
Key: aws.String("abc/abc"),
|
||||
@@ -3696,7 +3696,7 @@ func TestStoragePlugin_DeleteObjects(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: invalid credentials error: failed to delete objects",
|
||||
expectedErrContains: "aws service s3: invalid credentials: delete objects",
|
||||
expectedErrCode: codes.PermissionDenied,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -3720,7 +3720,7 @@ func TestStoragePlugin_DeleteObjects(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: throttling error: failed to delete object",
|
||||
expectedErrContains: "aws service s3: throttling: delete object",
|
||||
expectedErrCode: codes.Unavailable,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -3743,7 +3743,7 @@ func TestStoragePlugin_DeleteObjects(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: throttling error: failed to list objects",
|
||||
expectedErrContains: "aws service s3: throttling: list objects",
|
||||
expectedErrCode: codes.Unavailable,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -3763,7 +3763,7 @@ func TestStoragePlugin_DeleteObjects(t *testing.T) {
|
||||
newTestMockS3(
|
||||
nil,
|
||||
testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{
|
||||
IsTruncated: false,
|
||||
IsTruncated: aws.Bool(false),
|
||||
Contents: []s3types.Object{
|
||||
s3types.Object{
|
||||
Key: aws.String("abc/abc"),
|
||||
@@ -3774,7 +3774,7 @@ func TestStoragePlugin_DeleteObjects(t *testing.T) {
|
||||
),
|
||||
),
|
||||
},
|
||||
expectedErrContains: "aws service s3: throttling error: failed to delete objects",
|
||||
expectedErrContains: "aws service s3: throttling: delete objects",
|
||||
expectedErrCode: codes.Unavailable,
|
||||
expectedDetails: &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
@@ -3808,7 +3808,7 @@ func TestStoragePlugin_DeleteObjects(t *testing.T) {
|
||||
newTestMockS3(
|
||||
nil,
|
||||
testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{
|
||||
IsTruncated: false,
|
||||
IsTruncated: aws.Bool(false),
|
||||
Contents: []s3types.Object{
|
||||
{Key: aws.String("abc/abc1")},
|
||||
{Key: aws.String("abc/abc2")},
|
||||
@@ -3836,7 +3836,7 @@ func TestStoragePlugin_DeleteObjects(t *testing.T) {
|
||||
newTestMockS3(
|
||||
nil,
|
||||
testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{
|
||||
IsTruncated: false,
|
||||
IsTruncated: aws.Bool(false),
|
||||
Contents: []s3types.Object{},
|
||||
}),
|
||||
// no deleted response since it shouldn't be called
|
||||
|
||||
@@ -7,10 +7,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
@@ -29,17 +26,6 @@ type S3API interface {
|
||||
ListObjectsV2(ctx context.Context, params *s3.ListObjectsV2Input, optFns ...func(*s3.Options)) (*s3.ListObjectsV2Output, error)
|
||||
}
|
||||
|
||||
var customClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
},
|
||||
}
|
||||
|
||||
type awsStoragePersistedState struct {
|
||||
*credential.AwsCredentialPersistedState
|
||||
|
||||
@@ -106,9 +92,10 @@ func (s *awsStoragePersistedState) S3Client(ctx context.Context, opt ...s3Option
|
||||
return nil, fmt.Errorf("nil aws configuration")
|
||||
}
|
||||
var s3Opts []func(*s3.Options)
|
||||
if opts.withEndpoint != "" {
|
||||
if opts.withEndpoint != "" || opts.withDualStack {
|
||||
s3Opts = append(s3Opts, s3.WithEndpointResolverV2(&endpointResolver{
|
||||
endpoint: opts.withEndpoint,
|
||||
endpoint: opts.withEndpoint,
|
||||
dualStack: opts.withDualStack,
|
||||
}))
|
||||
}
|
||||
if s.testS3APIFunc != nil {
|
||||
@@ -136,7 +123,8 @@ type s3Option func(*s3Options) error
|
||||
|
||||
// options = how options are represented
|
||||
type s3Options struct {
|
||||
withEndpoint string
|
||||
withEndpoint string
|
||||
withDualStack bool
|
||||
}
|
||||
|
||||
func getDefaultS3Options() s3Options {
|
||||
@@ -151,18 +139,27 @@ func WithEndpoint(with string) s3Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithDualStack sets the dual stack resolver
|
||||
func WithDualStack(with bool) s3Option {
|
||||
return func(o *s3Options) error {
|
||||
o.withDualStack = with
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type endpointResolver struct {
|
||||
endpoint string
|
||||
endpoint string
|
||||
dualStack bool
|
||||
}
|
||||
|
||||
func (e *endpointResolver) ResolveEndpoint(ctx context.Context, params s3.EndpointParameters) (resolver smithyEndpoints.Endpoint, err error) {
|
||||
var uri *url.URL
|
||||
params.UseDualStack = aws.Bool(e.dualStack)
|
||||
resolver, err = s3.NewDefaultEndpointResolverV2().ResolveEndpoint(ctx, params)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if e.endpoint == "" {
|
||||
// TODO: not sure if we should set an error or ignore the missing endpoint value and continue with the default resolver
|
||||
return
|
||||
}
|
||||
uri, err = url.Parse(e.endpoint)
|
||||
@@ -170,7 +167,6 @@ func (e *endpointResolver) ResolveEndpoint(ctx context.Context, params s3.Endpoi
|
||||
return
|
||||
}
|
||||
if uri == nil {
|
||||
// TODO: not sure if we should set an error or ignore the missing uri value and continue with the default resolver
|
||||
return
|
||||
}
|
||||
resolver = smithyEndpoints.Endpoint{
|
||||
|
||||
+112
-7
@@ -17,12 +17,16 @@ import (
|
||||
"github.com/hashicorp/boundary-plugin-aws/plugin/service/storage"
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets"
|
||||
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
// NOTE: this test does not vaildate against dynamic credentials because
|
||||
// this credential type can only be tested on an EC2 instance.
|
||||
func TestStoragePlugin(t *testing.T) {
|
||||
region := os.Getenv("AWS_REGION")
|
||||
if region == "" {
|
||||
@@ -75,6 +79,12 @@ func TestStoragePlugin(t *testing.T) {
|
||||
iamSecretAccessKeyMissingPutObject, err := tf.GetOutputString("iam_secret_access_key_missing_put_obj")
|
||||
require.NoError(err)
|
||||
|
||||
iamAccessKeyMissingDeleteObject, err := tf.GetOutputString("iam_access_key_missing_delete_obj")
|
||||
require.NoError(err)
|
||||
|
||||
iamSecretAccessKeyMissingDeleteObject, err := tf.GetOutputString("iam_secret_access_key_missing_delete_obj")
|
||||
require.NoError(err)
|
||||
|
||||
// Start the workflow now. Set up the storage bucket. Note that this
|
||||
// will cause the state to go out of drift above in the sense that
|
||||
// the access key ID/secret access key will no longer be valid. We
|
||||
@@ -121,7 +131,7 @@ func TestStoragePlugin(t *testing.T) {
|
||||
testPluginOnDeleteStorageBucket(ctx, t, p, bucketName, region, keyid, secret, true)
|
||||
|
||||
// ********************
|
||||
// * Object Methods: PutObject, GetObject, HeadObject
|
||||
// * Object Methods: PutObject, GetObject, HeadObject, DeleteObjects
|
||||
// ********************
|
||||
//
|
||||
// Reassign the keyid and secret first.
|
||||
@@ -132,11 +142,59 @@ func TestStoragePlugin(t *testing.T) {
|
||||
// * Validate Permissions
|
||||
// ********************
|
||||
//
|
||||
var expectedSBCState *pb.StorageBucketCredentialState
|
||||
|
||||
// Validate error is returned for missing get object permission
|
||||
testPluginValidatePermissions(ctx, t, p, bucketName, region, iamAccessKeyMissingGetObject, iamSecretAccessKeyMissingGetObject)
|
||||
expectedSBCState = &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
Write: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_OK,
|
||||
},
|
||||
Read: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_ERROR,
|
||||
},
|
||||
Delete: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_OK,
|
||||
},
|
||||
},
|
||||
}
|
||||
testPluginValidatePermissions(ctx, t, p, bucketName, region, iamAccessKeyMissingGetObject, iamSecretAccessKeyMissingGetObject, expectedSBCState)
|
||||
|
||||
// Validate error is returned for missing put object permission
|
||||
testPluginValidatePermissions(ctx, t, p, bucketName, region, iamAccessKeyMissingPutObject, iamSecretAccessKeyMissingPutObject)
|
||||
expectedSBCState = &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
Write: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_ERROR,
|
||||
},
|
||||
Read: &pb.Permission{
|
||||
// Since we cannot write an object to S3,
|
||||
// we cannot test against the ability to read from S3.
|
||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||
},
|
||||
Delete: &pb.Permission{
|
||||
// We can still try deleting an object that does
|
||||
// not exist in the S3 bucket to validate access.
|
||||
State: pb.StateType_STATE_TYPE_OK,
|
||||
},
|
||||
},
|
||||
}
|
||||
testPluginValidatePermissions(ctx, t, p, bucketName, region, iamAccessKeyMissingPutObject, iamSecretAccessKeyMissingPutObject, expectedSBCState)
|
||||
|
||||
// Validate error is returned for missing delete object permission
|
||||
expectedSBCState = &pb.StorageBucketCredentialState{
|
||||
State: &pb.Permissions{
|
||||
Write: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_OK,
|
||||
},
|
||||
Read: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_OK,
|
||||
},
|
||||
Delete: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_ERROR,
|
||||
},
|
||||
},
|
||||
}
|
||||
testPluginValidatePermissions(ctx, t, p, bucketName, region, iamAccessKeyMissingDeleteObject, iamSecretAccessKeyMissingDeleteObject, expectedSBCState)
|
||||
}
|
||||
|
||||
func testPluginOnCreateStorageBucket(ctx context.Context, t *testing.T, p *storage.StoragePlugin, bucketName, region, accessKeyId, secretAccessKey string, rotate bool) (string, string) {
|
||||
@@ -289,10 +347,12 @@ func testPluginOnDeleteStorageBucket(ctx context.Context, t *testing.T, p *stora
|
||||
}
|
||||
}
|
||||
|
||||
func testPluginValidatePermissions(ctx context.Context, t *testing.T, p *storage.StoragePlugin, bucketName, region, accessKeyId, secretAccessKey string) {
|
||||
func testPluginValidatePermissions(ctx context.Context, t *testing.T, p *storage.StoragePlugin, bucketName, region, accessKeyId, secretAccessKey string, expectedState *pb.StorageBucketCredentialState) {
|
||||
t.Helper()
|
||||
t.Logf("testing ValidatePermissions")
|
||||
require := require.New(t)
|
||||
require, assert := require.New(t), assert.New(t)
|
||||
|
||||
require.NotNil(expectedState)
|
||||
|
||||
reqAttrs, err := structpb.NewStruct(map[string]any{
|
||||
credential.ConstRegion: region,
|
||||
@@ -315,6 +375,51 @@ func testPluginValidatePermissions(ctx context.Context, t *testing.T, p *storage
|
||||
|
||||
_, err = p.ValidatePermissions(ctx, req)
|
||||
require.Error(err)
|
||||
|
||||
var actualSBCState *pb.StorageBucketCredentialState
|
||||
if st, ok := status.FromError(err); ok {
|
||||
for _, detail := range st.Details() {
|
||||
if statusDetail, ok := detail.(*pb.StorageBucketCredentialState); ok {
|
||||
actualSBCState = statusDetail
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
require.NotNil(actualSBCState)
|
||||
require.NotNil(actualSBCState.GetState())
|
||||
|
||||
expectedReadState := expectedState.GetState().GetRead()
|
||||
if expectedReadState != nil {
|
||||
actualReadState := actualSBCState.GetState().GetRead()
|
||||
require.NotNil(actualReadState)
|
||||
assert.Equal(expectedReadState.GetState(), actualReadState.GetState())
|
||||
assert.NotEmpty(actualReadState.GetCheckedAt())
|
||||
if expectedReadState.GetState() == pb.StateType_STATE_TYPE_ERROR {
|
||||
assert.NotEmpty(actualReadState.GetErrorDetails())
|
||||
}
|
||||
}
|
||||
|
||||
expectedWriteState := expectedState.GetState().GetWrite()
|
||||
if expectedWriteState != nil {
|
||||
actualWriteState := actualSBCState.GetState().GetWrite()
|
||||
require.NotNil(actualWriteState)
|
||||
assert.Equal(expectedWriteState.GetState(), actualWriteState.GetState())
|
||||
assert.NotEmpty(actualWriteState.GetCheckedAt())
|
||||
if expectedWriteState.GetState() == pb.StateType_STATE_TYPE_ERROR {
|
||||
assert.NotEmpty(actualWriteState.GetErrorDetails())
|
||||
}
|
||||
}
|
||||
|
||||
expectedDeleteState := expectedState.GetState().GetDelete()
|
||||
if expectedDeleteState != nil {
|
||||
actualDeleteState := actualSBCState.GetState().GetDelete()
|
||||
require.NotNil(actualDeleteState)
|
||||
assert.Equal(expectedDeleteState.GetState(), actualDeleteState.GetState())
|
||||
assert.NotEmpty(actualDeleteState.GetCheckedAt())
|
||||
if expectedDeleteState.GetState() == pb.StateType_STATE_TYPE_ERROR {
|
||||
assert.NotEmpty(actualDeleteState.GetErrorDetails())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testPluginObjectMethods(ctx context.Context, t *testing.T, p *storage.StoragePlugin, bucketName, region, accessKeyId, secretAccessKey string) {
|
||||
@@ -369,7 +474,7 @@ func testPluginObjectMethods(ctx context.Context, t *testing.T, p *storage.Stora
|
||||
|
||||
// Need to create a GRPC server to test GetObject
|
||||
t.Logf("testing GetObject")
|
||||
lis, err := net.Listen("tcp", "localhost:2030")
|
||||
lis, err := net.Listen("tcp", "[::1]:2030")
|
||||
require.NoError(err)
|
||||
grpcServer := grpc.NewServer()
|
||||
pb.RegisterStoragePluginServiceServer(grpcServer, &storage.StoragePlugin{})
|
||||
@@ -377,7 +482,7 @@ func testPluginObjectMethods(ctx context.Context, t *testing.T, p *storage.Stora
|
||||
grpcServer.Serve(lis)
|
||||
}()
|
||||
|
||||
conn, err := grpc.Dial("localhost:2030", grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
conn, err := grpc.Dial("[::1]:2030", grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
require.NoError(err)
|
||||
defer conn.Close()
|
||||
client := pb.NewStoragePluginServiceClient(conn)
|
||||
|
||||
Vendored
+97
-12
@@ -32,7 +32,9 @@ resource "aws_iam_policy" "valid" {
|
||||
"Action": [
|
||||
"s3:PutObject",
|
||||
"s3:GetObject",
|
||||
"s3:GetObjectAttributes"
|
||||
"s3:DeleteObject",
|
||||
"s3:GetObjectAttributes",
|
||||
"s3:ListBucket"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Resource": "${aws_s3_bucket.test.arn}*"
|
||||
@@ -79,7 +81,9 @@ resource "aws_iam_policy" "missing_put_obj" {
|
||||
{
|
||||
"Action": [
|
||||
"s3:GetObject",
|
||||
"s3:GetObjectAttributes"
|
||||
"s3:GetObjectAttributes",
|
||||
"s3:ListBucket",
|
||||
"s3:DeleteObject"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Resource": "${aws_s3_bucket.test.arn}*"
|
||||
@@ -125,7 +129,7 @@ resource "aws_iam_policy" "missing_get_obj" {
|
||||
{
|
||||
"Action": [
|
||||
"s3:PutObject",
|
||||
"s3:GetObjectAttributes"
|
||||
"s3:DeleteObject"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Resource": "${aws_s3_bucket.test.arn}*"
|
||||
@@ -141,18 +145,69 @@ resource "aws_iam_role_policy_attachment" "missing_get_obj" {
|
||||
policy_arn = aws_iam_policy.missing_get_obj.arn
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "missing_delete_obj" {
|
||||
name = "${random_id.prefix.dec}-missing-delete-obj"
|
||||
tags = local.tags
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Action = "sts:AssumeRole"
|
||||
Effect = "Allow"
|
||||
Sid = ""
|
||||
Principal = {
|
||||
AWS: data.aws_caller_identity.current.user_id
|
||||
}
|
||||
},
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
resource "aws_iam_policy" "missing_delete_obj" {
|
||||
name = "${random_id.prefix.dec}-missing-delete-obj"
|
||||
tags = local.tags
|
||||
|
||||
policy = <<EOT
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Action": [
|
||||
"s3:PutObject",
|
||||
"s3:GetObject",
|
||||
"s3:GetObjectAttributes",
|
||||
"s3:ListBucket"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Resource": "${aws_s3_bucket.test.arn}*"
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
EOT
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "missing_delete_obj" {
|
||||
role = aws_iam_role.missing_delete_obj.name
|
||||
policy_arn = aws_iam_policy.missing_delete_obj.arn
|
||||
}
|
||||
|
||||
# Static Credential Testing
|
||||
resource "random_id" "user_name" {
|
||||
count = var.iam_user_count
|
||||
prefix = random_id.prefix.dec
|
||||
prefix = "demo-${local.hashicorp_email}-boundary-iam-user"
|
||||
byte_length = 4
|
||||
}
|
||||
|
||||
resource "aws_iam_user" "test" {
|
||||
count = var.iam_user_count
|
||||
name = random_id.user_name[count.index].dec
|
||||
force_destroy = true
|
||||
tags = local.tags
|
||||
count = var.iam_user_count
|
||||
name = random_id.user_name[count.index].dec
|
||||
force_destroy = true
|
||||
permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser"
|
||||
tags = {
|
||||
"boundary-demo" = local.hashicorp_email
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_access_key" "test" {
|
||||
@@ -195,17 +250,44 @@ resource "aws_iam_user_policy_attachment" "test_credentials" {
|
||||
policy_arn = aws_iam_policy.credentials[count.index].arn
|
||||
}
|
||||
|
||||
# Edge case for missing DeleteObject permission
|
||||
resource "random_id" "missing_delete_obj" {
|
||||
prefix = "demo-${local.hashicorp_email}-boundary-iam-user"
|
||||
byte_length = 4
|
||||
}
|
||||
|
||||
resource "aws_iam_user" "missing_delete_obj" {
|
||||
name = random_id.missing_delete_obj.dec
|
||||
force_destroy = true
|
||||
permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser"
|
||||
tags = {
|
||||
"boundary-demo" = local.hashicorp_email
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_access_key" "missing_delete_obj" {
|
||||
user = aws_iam_user.missing_delete_obj.name
|
||||
}
|
||||
|
||||
resource "aws_iam_user_policy_attachment" "missing_delete_obj" {
|
||||
user = aws_iam_user.missing_delete_obj.name
|
||||
policy_arn = aws_iam_policy.missing_delete_obj.arn
|
||||
}
|
||||
|
||||
# Edge case for missing GetObject permission
|
||||
|
||||
resource "random_id" "missing_get_obj" {
|
||||
prefix = random_id.prefix.dec
|
||||
prefix = "demo-${local.hashicorp_email}-boundary-iam-user"
|
||||
byte_length = 4
|
||||
}
|
||||
|
||||
resource "aws_iam_user" "missing_get_obj" {
|
||||
name = random_id.missing_get_obj.dec
|
||||
force_destroy = true
|
||||
tags = local.tags
|
||||
permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser"
|
||||
tags = {
|
||||
"boundary-demo" = local.hashicorp_email
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_access_key" "missing_get_obj" {
|
||||
@@ -220,14 +302,17 @@ resource "aws_iam_user_policy_attachment" "missing_get_obj" {
|
||||
# Edge case for missing PutObject permission
|
||||
|
||||
resource "random_id" "missing_put_obj" {
|
||||
prefix = random_id.prefix.dec
|
||||
prefix = "demo-${local.hashicorp_email}-boundary-iam-user"
|
||||
byte_length = 4
|
||||
}
|
||||
|
||||
resource "aws_iam_user" "missing_put_obj" {
|
||||
name = random_id.missing_put_obj.dec
|
||||
force_destroy = true
|
||||
tags = local.tags
|
||||
permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser"
|
||||
tags = {
|
||||
"boundary-demo" = local.hashicorp_email
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_access_key" "missing_put_obj" {
|
||||
|
||||
Vendored
+15
-1
@@ -13,10 +13,14 @@ output "missing_put_obj_policy_arn" {
|
||||
value = aws_iam_role.missing_put_obj.arn
|
||||
}
|
||||
|
||||
output "missing_put_get_policy_arn" {
|
||||
output "missing_get_obj_policy_arn" {
|
||||
value = aws_iam_role.missing_get_obj.arn
|
||||
}
|
||||
|
||||
output "missing_delete_obj_policy_arn" {
|
||||
value = aws_iam_role.missing_delete_obj.arn
|
||||
}
|
||||
|
||||
output "iam_user_names" {
|
||||
value = aws_iam_user.test.*.name
|
||||
}
|
||||
@@ -53,4 +57,14 @@ output "iam_access_key_missing_get_obj" {
|
||||
output "iam_secret_access_key_missing_get_obj" {
|
||||
value = aws_iam_access_key.missing_get_obj.secret
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "iam_access_key_missing_delete_obj" {
|
||||
value = aws_iam_access_key.missing_delete_obj.id
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "iam_secret_access_key_missing_delete_obj" {
|
||||
value = aws_iam_access_key.missing_delete_obj.secret
|
||||
sensitive = true
|
||||
}
|
||||
Vendored
+9
-1
@@ -4,6 +4,14 @@
|
||||
resource "aws_s3_bucket" "test" {
|
||||
bucket = random_id.prefix.dec
|
||||
force_destroy = true
|
||||
acl = "private"
|
||||
tags = local.tags
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_public_access_block" "secure_access" {
|
||||
bucket = aws_s3_bucket.test.id
|
||||
|
||||
block_public_acls = true
|
||||
block_public_policy = true
|
||||
ignore_public_acls = true
|
||||
restrict_public_buckets = true
|
||||
}
|
||||
Vendored
+6
-4
@@ -4,7 +4,7 @@
|
||||
data "aws_caller_identity" "current" {}
|
||||
|
||||
resource "random_id" "prefix" {
|
||||
prefix = "storage-e2e-test"
|
||||
prefix = "aws-plugin-e2e-test"
|
||||
byte_length = 4
|
||||
}
|
||||
|
||||
@@ -13,8 +13,10 @@ variable "iam_user_count" {
|
||||
}
|
||||
|
||||
locals {
|
||||
hashicorp_email = split(":", data.aws_caller_identity.current.user_id)[1]
|
||||
tags = {
|
||||
user_id = split(":", data.aws_caller_identity.current.user_id)[1]
|
||||
repo = "boundary-plugin-aws"
|
||||
User = split(":", data.aws_caller_identity.current.user_id)[1]
|
||||
Repo = "boundary-plugin-aws"
|
||||
Environment = "Demo"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user