Compare commits

...
Author SHA1 Message Date
Emilia Grant bca65035ab add .env and .envrc to .gitignore 2025-12-01 11:58:35 -05:00
Emilia Grant f1d71fc740 fix non-const printf format strings for go 1.24 2025-12-01 11:58:09 -05:00
Emilia Grant 5b68421df4 allow shared creds 2025-12-01 11:57:56 -05:00
Katrina Schwark 5d098ac71d Add PCI review checklist to PR template (#61) 2025-06-30 11:57:23 -06:00
dependabot[bot] 949976ec19 build(deps): bump golang.org/x/net from 0.36.0 to 0.38.0 (#60)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.36.0 to 0.38.0.
- [Commits](https://github.com/golang/net/compare/v0.36.0...v0.38.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.38.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-16 16:23:17 -07:00
dependabot[bot] 49f692e7a8 build(deps): bump golang.org/x/net from 0.33.0 to 0.36.0 (#59)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.33.0 to 0.36.0.
- [Commits](https://github.com/golang/net/compare/v0.33.0...v0.36.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-17 09:58:09 -07:00
daniandDanielle Miu a75ac95465 Implement NormalizeStorageBucketData and begin normalizing endpoint URLs (#58)
Co-authored-by: Danielle Miu <29378233+DanielleMiu@users.noreply.github.com>
2025-03-14 15:19:20 +00:00
Irena Rindos 7fd6d3f3e5 chore: update deps (#57) 2025-01-07 12:45:10 -05:00
Louis Ruch bdd27e2562 feat(storage): Add s3Client cache (#55)
* feat(storage): Add s3Client cache

* Update awsutil deps
2024-11-25 21:32:37 -08:00
Johan Brandhorst-Satzkorn f17f05ec82 Update CODEOWNERS (#54)
Make it effective with a wildcard
2024-11-18 16:23:19 -08:00
Damian Debkowski 899c62ce06 feat: support dual stack requests for aws clients (#52)
* feat: enable dual stack in aws clients

* refactor: optionally set custom endpoint resolver

* feat: update sts & iam client to utilize dualstack

* update aws dependencies to remove role policy bug
2024-10-28 11:50:18 -07:00
Louis Ruch ce22c1c46a Refactor error messages to include aws error details (#53)
* refactor error messages to include aws error details

* update deps
2024-10-21 09:55:51 -07:00
Hugo 2f9163ad0e docs(host): Add newly supported attributes 2024-10-03 18:19:49 +01:00
Damian Debkowski 7caff84c06 test: validate ipv6 compatibility (#50)
* test: validate ipv6 compatability

* test: update test to include checking against SBC state
2024-09-25 12:04:00 -07:00
33 changed files with 1364 additions and 457 deletions

No files matched your search

+11
View File
@@ -0,0 +1,11 @@
## PCI review checklist
<!-- heimdall_github_prtemplate:grc-pci_dss-2024-01-05 -->
- [ ] I have documented a clear reason for, and description of, the change I am making.
- [ ] If applicable, I've documented a plan to revert these changes if they require more than reverting the pull request.
- [ ] If applicable, I've documented the impact of any changes to security controls.
Examples of changes to security controls include using new access control methods, adding or removing logging pipelines, etc.
+9
View File
@@ -4,3 +4,12 @@ plan.tfplan
*.log *.log
*.out *.out
*.html *.html
# IntelliJ IDEA project files
.idea
*.iprg
.DS_Store
.env
.envrc
+1 -2
View File
@@ -1,5 +1,4 @@
# These owners will be the default owners for everything in # These owners will be the default owners for everything in
# the repo, unless a later match takes precedence. # the repo, unless a later match takes precedence.
@hashicorp/boundary * @hashicorp/boundary
+47 -44
View File
@@ -1,66 +1,69 @@
module github.com/hashicorp/boundary-plugin-aws module github.com/hashicorp/boundary-plugin-aws
go 1.21 go 1.24.0
require ( require (
github.com/aws/aws-sdk-go-v2 v1.20.1 github.com/aws/aws-sdk-go-v2 v1.40.0
github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0 github.com/aws/aws-sdk-go-v2/service/ec2 v1.193.0
github.com/aws/aws-sdk-go-v2/service/iam v1.22.2 github.com/aws/aws-sdk-go-v2/service/iam v1.52.2
github.com/aws/aws-sdk-go-v2/service/s3 v1.38.2 github.com/aws/aws-sdk-go-v2/service/s3 v1.69.0
github.com/aws/aws-sdk-go-v2/service/sts v1.21.2 github.com/aws/aws-sdk-go-v2/service/sts v1.41.2
github.com/aws/smithy-go v1.14.1 github.com/aws/smithy-go v1.24.0
github.com/google/go-cmp v0.6.0 github.com/google/go-cmp v0.6.0
github.com/google/uuid v1.4.0 github.com/google/uuid v1.6.0
github.com/hashicorp/boundary/sdk v0.0.43-0.20240717182311-a20aae98794a github.com/hashicorp/boundary/sdk v0.0.51
github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.0.0 github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.1.2
github.com/hashicorp/terraform-json v0.14.0 github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0
github.com/hashicorp/terraform-json v0.23.0
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.10.0
google.golang.org/grpc v1.61.0 google.golang.org/grpc v1.68.0
google.golang.org/protobuf v1.33.0 google.golang.org/protobuf v1.36.1
) )
require ( require (
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.12 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/aws/aws-sdk-go-v2/config v1.18.33 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.32 // indirect github.com/aws/aws-sdk-go-v2/config v1.32.2 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.8 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.19.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.38 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 // 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.4.14 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.39 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.1 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.13 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.33 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 // 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/checksum v1.4.5 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.1 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.13.2 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.2 // indirect github.com/aws/aws-sdk-go-v2/service/signin v1.0.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.15.0 // indirect github.com/fatih/color v1.18.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/eventlogger v0.2.6-0.20231025104552-802587e608f0 // indirect github.com/hashicorp/eventlogger v0.2.6-0.20231025104552-802587e608f0 // indirect
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 // indirect github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-kms-wrapping/v2 v2.0.14 // indirect github.com/hashicorp/go-kms-wrapping/v2 v2.0.16 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.6 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.5.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/pointerstructure v1.2.1 // indirect github.com/mitchellh/pointerstructure v1.2.1 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/zclconf/go-cty v1.10.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect
golang.org/x/crypto v0.18.0 // indirect github.com/zclconf/go-cty v1.15.0 // indirect
golang.org/x/net v0.20.0 // indirect golang.org/x/crypto v0.36.0 // indirect
golang.org/x/sys v0.16.0 // indirect golang.org/x/net v0.38.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/sys v0.38.0 // indirect
google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect golang.org/x/text v0.23.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
+94 -133
View File
@@ -4,55 +4,52 @@ github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7Y
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= 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/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.40.0 h1:/WMUA0kjhZExjOQN2z3oLALDREea1A7TobfuiBrKlwc=
github.com/aws/aws-sdk-go-v2 v1.20.1 h1:rZBf5DWr7YGrnlTK4kgDQGn1ltqOg5orCYb/UhOFZkg= github.com/aws/aws-sdk-go-v2 v1.40.0/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE=
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.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8=
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.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc=
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/config v1.32.2 h1:4liUsdEpUUPZs5WVapsJLx5NPmQhQdez7nYFcovrytk=
github.com/aws/aws-sdk-go-v2/config v1.18.33 h1:JKcw5SFxFW/rpM4mOPjv0VQ11E2kxW13F3exWOy7VZU= github.com/aws/aws-sdk-go-v2/config v1.32.2/go.mod h1:l0hs06IFz1eCT+jTacU/qZtC33nvcnLADAPL/XyrkZI=
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.19.2 h1:qZry8VUyTK4VIo5aEdUcBjPZHL2v4FyQ3QEOaWcFLu4=
github.com/aws/aws-sdk-go-v2/credentials v1.13.32 h1:lIH1eKPcCY1ylR4B6PkBGRWMHO3aVenOKJHWiS4/G2w= github.com/aws/aws-sdk-go-v2/credentials v1.19.2/go.mod h1:YUqm5a1/kBnoK+/NY5WEiMocZihKSo15/tJdmdXnM5g=
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.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA=
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.18.14/go.mod h1:Dadl9QO0kHgbrH1GRqGiZdYtW5w+IXXaBNCHTIaheM4=
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.4.14 h1:PZHqQACxYb8mYgms4RZbhZG0a7dPW06xOjmaH0EJC/I=
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.4.14/go.mod h1:VymhrMJUWs69D8u0/lZ7jSB6WgaG/NqHi3gX0aYf6U0=
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/endpoints/v2 v2.7.14 h1:bOS19y6zlJwagBfHxs0ESzr1XCOU2KXJCWcq3E2vfjY=
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.7.14/go.mod h1:1ipeGBMAxZ0xcTm6y6paC2C/J6f6OO7LBODV9afuAyM=
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/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.32 h1:hNeAAymUY5gu11WrrmFb3CVIp9Dar9hbo44yzzcQpzA= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
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/v4a v1.3.24 h1:JX70yGKLj25+lMC5Yyh8wBtvB01GDilyRuJvXJ4piD0=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.39 h1:fc0ukRAiP1syoSGZYu+DaE+FulSYhTiJ8WpVu5jElU4= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24/go.mod h1:+Ln60j9SUTD0LEwnhEB0Xhg61DHqplBrbZpLgyjoEHg=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.39/go.mod h1:WLAW8PT7+JhjZfLSWe7WEJaJu0GNo0cKc2Zyo003RBs= github.com/aws/aws-sdk-go-v2/service/ec2 v1.193.0 h1:RhSoBFT5/8tTmIseJUXM6INTXTQDF8+0oyxWBnozIms=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.1 h1:vUh7dBFNS3oFCtVv6CiYKh5hP9ls8+kIpKLeFruIBLk= github.com/aws/aws-sdk-go-v2/service/ec2 v1.193.0/go.mod h1:mzj8EEjIHSN2oZRXiw1Dd+uB4HZTl7hC8nBzX9IZMWw=
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/iam v1.52.2 h1:li0ooCUfHIivHn8nB3LstP6HgdNefwu5gnXE4MLVz/U=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0 h1:NXi4pNJWjAaiI56P1Rl8DC9A4jMNRE00WNBsDua5WRg= github.com/aws/aws-sdk-go-v2/service/iam v1.52.2/go.mod h1:PuHz5kGh1jtsNpjezdYhRp7xgn6DzCNJJfQt7O7U9Aw=
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/internal/accept-encoding v1.13.3 h1:x2Ibm/Af8Fi+BH+Hsn9TXGdT+hKbDd5XOTZxTMxDk7o=
github.com/aws/aws-sdk-go-v2/service/iam v1.22.2 h1:DPFxx/6Zwes/MiadlDteVqDKov7yQ5v9vuwfhZuJm1s= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3/go.mod h1:IW1jwyrQgMdhisceG8fQLmQIydcT/jWY21rFhzgaKwo=
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/checksum v1.4.5 h1:gvZOjQKPxFXy1ft3QnEyXmT+IqneM9QAUWlM3r0mfqw=
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/checksum v1.4.5/go.mod h1:DLWnfvIcm9IET/mmjdxeXbBKmTCm0ZB8p1za9BVteM8=
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/presigned-url v1.13.14 h1:FIouAnCE46kyYqyhs0XEBDFFSREtdnr8HQuLPQPLCrY=
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/presigned-url v1.13.14/go.mod h1:UTwDc5COa5+guonQU8qBikJo1ZJ4ln2r1MkF7Dqag1E=
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/s3shared v1.18.5 h1:P1doBzv5VEg1ONxnJss1Kh5ZG/ewoIE4MQtKKc6Crgg=
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/s3shared v1.18.5/go.mod h1:NOP+euMW7W3Ukt28tAxPuoWao4rhhqJD3QEBk7oCg7w=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.32 h1:dGAseBFEYxth10V23b5e2mAS+tX7oVbfYHD6dnDdAsg= github.com/aws/aws-sdk-go-v2/service/s3 v1.69.0 h1:Q2ax8S21clKOnHhhr933xm3JxdJebql+R7aNo7p7GBQ=
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/s3 v1.69.0/go.mod h1:ralv4XawHjEMaHOWnTFushl0WRqim/gQWesAMF6hTow=
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/signin v1.0.2 h1:MxMBdKTYBjPQChlJhi4qlEueqB1p1KcbTEa7tD5aqPs=
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/signin v1.0.2/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk=
github.com/aws/aws-sdk-go-v2/service/s3 v1.38.2 h1:v346f1h8sUBKXnEbrv43L37MTBlFHyKXQPIZHNAaghA= github.com/aws/aws-sdk-go-v2/service/sso v1.30.5 h1:ksUT5KtgpZd3SAiFJNJ0AFEJVva3gjBmN7eXUZjzUwQ=
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/sso v1.30.5/go.mod h1:av+ArJpoYf3pgyrj6tcehSFW+y9/QvAY8kMooR9bZCw=
github.com/aws/aws-sdk-go-v2/service/sso v1.13.2 h1:A2RlEMo4SJSwbNoUUgkxTAEMduAy/8wG3eB2b2lP4gY= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10 h1:GtsxyiF3Nd3JahRBJbxLCCdYW9ltGQYrFWg8XdkGDd8=
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.35.10/go.mod h1:/j67Z5XBVDx8nZVp9EuFM9/BS5dvBznbqILGuu73hug=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.2 h1:OJELEgyaT2kmaBGZ+myyZbTTLobfe3ox3FSh5eYK9Qs= github.com/aws/aws-sdk-go-v2/service/sts v1.41.2 h1:a5UTtD4mHBU3t0o6aHQZFJTNKVfxFWfPX7J0Lr7G+uY=
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.41.2/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso=
github.com/aws/aws-sdk-go-v2/service/sts v1.21.2 h1:ympg1+Lnq33XLhcK/xTG4yZHPs1Oyxu+6DEWbl7qOzA= github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
github.com/aws/aws-sdk-go-v2/service/sts v1.21.2/go.mod h1:FQ/DQcOfESELfJi5ED+IPPAjI5xC6nxtSolVVB773jM= github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
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/smithy-go v1.14.1/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -60,26 +57,20 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/boundary/sdk v0.0.43-0.20240717182311-a20aae98794a h1:SwVze6sYE5o+J9qdcgj29auY1k6O8oSo9DC2+2Gb9rw= github.com/hashicorp/boundary/sdk v0.0.51 h1:a7XpHNTYaeBXRhJkc8MC0siPtfJELYH/ibjw/0UbJEg=
github.com/hashicorp/boundary/sdk v0.0.43-0.20240717182311-a20aae98794a/go.mod h1:9iOT7kDM6mYcSkKxNuZlv8rP7U5BG1kXoevjLLL8lNQ= github.com/hashicorp/boundary/sdk v0.0.51/go.mod h1:gSrMTppD30IPVsUqs4SwQowu+OlR5/+58draey++SkQ=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -89,18 +80,18 @@ github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0/go.mod h1:tMywUTIvdB/FXhwm6HMTt61C8/eODY6gitCHhXtyojg= github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0/go.mod h1:tMywUTIvdB/FXhwm6HMTt61C8/eODY6gitCHhXtyojg=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.5 h1:jrnDfQm2hCQ0/hEselgqzV4fK16gpZoY0OWGZpVPNHM= github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.7 h1:gM4OwbF16Cmfxt2QMkoGMQbRTfYFZLvDMPgU3rM3KIo=
github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.5/go.mod h1:psh1qKep5ukvuNobFY/hCybuudlkkACpmazOsCgX5Rg= github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.7/go.mod h1:7ZMHVluyqgHgEuTADeDzFNWoA9mnyPfdiK8Tk2Bct1c=
github.com/hashicorp/go-kms-wrapping/v2 v2.0.14 h1:1ZuhfnZgRnLK8S0KovJkoTCRIQId5pv3sDR7pG5VQBw= github.com/hashicorp/go-kms-wrapping/v2 v2.0.16 h1:WZeXfD26QMWYC35at25KgE021SF9L3u9UMHK8fJAdV0=
github.com/hashicorp/go-kms-wrapping/v2 v2.0.14/go.mod h1:0dWtzl2ilqKpavgM3id/kFK9L3tjo6fS4OhbVPSYpnQ= github.com/hashicorp/go-kms-wrapping/v2 v2.0.16/go.mod h1:ZiKZctjRTLEppuRwrttWkp71VYMbTTCkazK4xT7U/NQ=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-plugin v1.5.2 h1:aWv8eimFqWlsEiMrYZdPYl+FdHaBJSN4AWwGWfT1G2Y= github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg=
github.com/hashicorp/go-plugin v1.5.2/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4= github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0=
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.0.0 h1:ca5TSI4AgaOncPpyzLDtCGjVEtKukONpeM95vFxXCOQ= github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.1.2 h1:KlDrZWx/wYpnUcFPB0s9uhPjyNZoeIfhrV2MN/Ly76s=
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.0.0/go.mod h1:7CUvZtfTp2U0CYQCLzMtS2ngckjAZePSfwrE2aeDP1M= github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.1.2/go.mod h1:6+rVulOPNCQbL3Xv2iLCqM0JmU2WO2wRzP1C6hBKeB8=
github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng= github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng=
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
@@ -108,8 +99,8 @@ github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.11 h1:uPW2Wn0YlmI9RGSkZ
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.11/go.mod h1:uis9dCmOzXuOaRyXq+1Foh31kcvXKoWogjNnhfjHfW8= github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.11/go.mod h1:uis9dCmOzXuOaRyXq+1Foh31kcvXKoWogjNnhfjHfW8=
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.9 h1:0S0ctJ7Ra8O7ap+/3fZUnzJ3VzJyirWS/WnNCuOYtZY= github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.9 h1:0S0ctJ7Ra8O7ap+/3fZUnzJ3VzJyirWS/WnNCuOYtZY=
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.9/go.mod h1:TNNdgtjLgVDbrgFcyCKrlAicIl3dZF94swJltyGUX2M= github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.9/go.mod h1:TNNdgtjLgVDbrgFcyCKrlAicIl3dZF94swJltyGUX2M=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 h1:iBt4Ew4XEGLfh6/bPk4rSYmuZJGizr6/x/AEizP0CQc= github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8/go.mod h1:aiJI+PIApBRQG7FZTEBx5GiiX+HbOHilUdNxUZi4eV0= github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=
github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.6 h1:ZYv2XA+tEfFXIToR2jmBgVqQU9gERt0APbWqmUoNGnY= github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.6 h1:ZYv2XA+tEfFXIToR2jmBgVqQU9gERt0APbWqmUoNGnY=
github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.6/go.mod h1:ggFN8dlaLWS2R1gymBbCrvXM/bkZP7hEAa4seqDwhyg= github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.6/go.mod h1:ggFN8dlaLWS2R1gymBbCrvXM/bkZP7hEAa4seqDwhyg=
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 h1:SMGUnbpAcat8rIKHkBPjfv81yC46a8eCNZ2hsR2l1EI= github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 h1:SMGUnbpAcat8rIKHkBPjfv81yC46a8eCNZ2hsR2l1EI=
@@ -119,17 +110,17 @@ github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3 h1:xbrxd0U9XQW8qL1BAz2XrAjAF/P2vcqUTAues9c24B8= github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3 h1:xbrxd0U9XQW8qL1BAz2XrAjAF/P2vcqUTAues9c24B8=
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3/go.mod h1:LWq2Sy8UoKKuK4lFuCNWSjJj57MhNNf2zzBWMtkAIX4= github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3/go.mod h1:LWq2Sy8UoKKuK4lFuCNWSjJj57MhNNf2zzBWMtkAIX4=
github.com/hashicorp/go-sockaddr v1.0.5 h1:dvk7TIXCZpmfOlM+9mlcrWmWjw/wlKT+VDq2wMvfPJU= github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I=
github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e17dKDpqV7s= github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI=
github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM= github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
@@ -138,10 +129,6 @@ github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f h1:E87tDTVS5W65euzixn7clSzK66puSt1H4I5SC0EmHH4= github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f h1:E87tDTVS5W65euzixn7clSzK66puSt1H4I5SC0EmHH4=
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f/go.mod h1:3J2qVK16Lq8V+wfiL2lPeDZ7UWMxk5LemerHa1p6N00= github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f/go.mod h1:3J2qVK16Lq8V+wfiL2lPeDZ7UWMxk5LemerHa1p6N00=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
@@ -150,22 +137,18 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng= github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng=
github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4= github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
@@ -184,49 +167,38 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ=
github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0= github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -237,16 +209,13 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -257,28 +226,20 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac h1:ZL/Teoy/ZGnzyrqK/Optxxp2pmVh+fmJ97slxSRyzUg= google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k= google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe h1:0poefMBYvYbs7g5UkjS6HcxBPaTRAmznle9jnxYoAI8= google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe h1:bQnxqljG/wqi4NTXu2+DJ3n7APcEA882QZ1JvhQAq9o= google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+13 -2
View File
@@ -37,7 +37,7 @@ type CredentialAttributes struct {
// CredentialsConfig used for configuring an AWS session. An status error is returned // 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 // with an InvalidArgument code if any unrecognized fields are found in the protobuf
// struct input. // 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 // initialize secrets if it is nil
// secrets can be nil because static credentials are optional // secrets can be nil because static credentials are optional
if secrets == nil { if secrets == nil {
@@ -117,9 +117,20 @@ func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes)
opts = append(opts, awsutil.WithRoleTags(attrs.RoleTags)) 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...) cfg, err := awsutil.NewCredentialsConfig(opts...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus(err.Error()) return nil, errors.BadRequestStatus("%s", err.Error())
} }
return cfg, nil return cfg, nil
+27 -1
View File
@@ -106,6 +106,7 @@ func TestGetCredentialsConfig(t *testing.T) {
name string name string
secrets *structpb.Struct secrets *structpb.Struct
attrs *CredentialAttributes attrs *CredentialAttributes
dualStack bool
expected *awsutil.CredentialsConfig expected *awsutil.CredentialsConfig
expectedErrContains string expectedErrContains string
}{ }{
@@ -135,6 +136,24 @@ func TestGetCredentialsConfig(t *testing.T) {
Region: "us-west-2", 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", name: "unknown fields",
secrets: &structpb.Struct{ secrets: &structpb.Struct{
@@ -233,7 +252,7 @@ func TestGetCredentialsConfig(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
require := require.New(t) require := require.New(t)
actual, err := GetCredentialsConfig(tc.secrets, tc.attrs) actual, err := GetCredentialsConfig(tc.secrets, tc.attrs, tc.dualStack)
if tc.expectedErrContains != "" { if tc.expectedErrContains != "" {
require.Error(err) require.Error(err)
require.Contains(err.Error(), tc.expectedErrContains) 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.RoleExternalId, actual.RoleExternalId)
require.Equal(tc.expected.RoleSessionName, actual.RoleSessionName) require.Equal(tc.expected.RoleSessionName, actual.RoleSessionName)
require.Equal(tc.expected.RoleTags, actual.RoleTags) 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)
}
}) })
} }
} }
+42 -14
View File
@@ -11,7 +11,10 @@ import (
"time" "time"
"github.com/aws/aws-sdk-go-v2/aws" "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" 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/errors"
"github.com/hashicorp/boundary-plugin-aws/internal/values" "github.com/hashicorp/boundary-plugin-aws/internal/values"
"github.com/hashicorp/go-secure-stdlib/awsutil/v2" "github.com/hashicorp/go-secure-stdlib/awsutil/v2"
@@ -114,7 +117,7 @@ func (s *AwsCredentialPersistedState) ValidateCreds(ctx context.Context) error {
return errors.BadRequestStatus("missing credentials config") return errors.BadRequestStatus("missing credentials config")
} }
if _, err := s.CredentialsConfig.GetCallerIdentity(ctx, s.testOpts...); err != nil { if _, err := s.CredentialsConfig.GetCallerIdentity(ctx, s.testOpts...); err != nil {
st, _ := errors.ParseAWSError(err, "validating credentials") st, _ := errors.ParseAWSError("validating credentials", err)
return st.Err() return st.Err()
} }
return nil return nil
@@ -135,7 +138,7 @@ func (s *AwsCredentialPersistedState) RotateCreds(ctx context.Context) error {
if err := s.CredentialsConfig.RotateKeys(ctx, append([]awsutil.Option{ if err := s.CredentialsConfig.RotateKeys(ctx, append([]awsutil.Option{
awsutil.WithValidityCheckTimeout(rotationWaitTimeout), awsutil.WithValidityCheckTimeout(rotationWaitTimeout),
}, s.testOpts...)...); err != nil { }, s.testOpts...)...); err != nil {
st, _ := errors.ParseAWSError(err, "error rotating credentials") st, _ := errors.ParseAWSError("rotating credentials", err)
return st.Err() return st.Err()
} }
s.CredsLastRotatedTime = time.Now() s.CredsLastRotatedTime = time.Now()
@@ -192,7 +195,7 @@ func (s *AwsCredentialPersistedState) DeleteCreds(ctx context.Context) error {
} }
// Otherwise treat it as an actual 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() return st.Err()
} }
@@ -203,13 +206,7 @@ func (s *AwsCredentialPersistedState) DeleteCreds(ctx context.Context) error {
// GenerateCredentialChain returns a AWS configuration for the credentials in the state. // GenerateCredentialChain returns a AWS configuration for the credentials in the state.
func (s *AwsCredentialPersistedState) GenerateCredentialChain(ctx context.Context) (*aws.Config, error) { func (s *AwsCredentialPersistedState) GenerateCredentialChain(ctx context.Context) (*aws.Config, error) {
// Default `WithSharedCredentials` is `true` - This disables the ability to return s.CredentialsConfig.GenerateCredentialChain(ctx, s.testOpts...)
// read credentials from environment variables because we set the default
// profile options, so we need to set it to false (see
// awsutil@v2.(*CredentialsConfig).generateAwsConfigOptions) for extra
// details.
opts := append(s.testOpts, awsutil.WithSharedCredentials(false))
return s.CredentialsConfig.GenerateCredentialChain(ctx, opts...)
} }
// ToMap returns a map of the credentials stored in the persisted state. // ToMap returns a map of the credentials stored in the persisted state.
@@ -237,7 +234,7 @@ func (s *AwsCredentialPersistedState) ToMap() map[string]any {
// AwsCredentialPersistedStateFromProto parses values out of a protobuf struct input // AwsCredentialPersistedStateFromProto parses values out of a protobuf struct input
// and returns a AwsCredentialPersistedState used for configuring an AWS session. // 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 // initialize secrets if it is nil
// secrets can be nil because static credentials are optional // secrets can be nil because static credentials are optional
if secrets == nil { if secrets == nil {
@@ -252,17 +249,17 @@ func AwsCredentialPersistedStateFromProto(secrets *structpb.Struct, attrs *Crede
accessKeyId, err := values.GetStringValue(secrets, ConstAccessKeyId, false) accessKeyId, err := values.GetStringValue(secrets, ConstAccessKeyId, false)
if err != nil { 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) secretAccessKey, err := values.GetStringValue(secrets, ConstSecretAccessKey, false)
if err != nil { 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) credsLastRotatedTime, err := values.GetTimeValue(secrets, ConstCredsLastRotatedTime)
if err != nil { 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...) s, err := NewAwsCredentialPersistedState(opts...)
@@ -292,6 +289,17 @@ func AwsCredentialPersistedStateFromProto(secrets *structpb.Struct, attrs *Crede
if len(attrs.RoleTags) != 0 { if len(attrs.RoleTags) != 0 {
awsOpts = append(awsOpts, awsutil.WithRoleTags(attrs.RoleTags)) 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...) credentialsConfig, err := awsutil.NewCredentialsConfig(awsOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -321,3 +329,23 @@ func GetCredentialType(cc *awsutil.CredentialsConfig) CredentialType {
return Unknown 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
}
+33 -5
View File
@@ -112,6 +112,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
name string name string
secrets *structpb.Struct secrets *structpb.Struct
attrs *CredentialAttributes attrs *CredentialAttributes
dualStack bool
opts []AwsCredentialPersistedStateOption opts []AwsCredentialPersistedStateOption
expected *AwsCredentialPersistedState expected *AwsCredentialPersistedState
expectedErr string 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", name: "bad last rotated time",
secrets: &structpb.Struct{ secrets: &structpb.Struct{
@@ -186,7 +207,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
attrs: &CredentialAttributes{ attrs: &CredentialAttributes{
Region: "us-west-2", 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", name: "option error",
@@ -318,7 +339,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
require := require.New(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 != "" { if tc.expectedErr != "" {
require.EqualError(err, tc.expectedErr) require.EqualError(err, tc.expectedErr)
return return
@@ -336,6 +357,13 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
require.Equal(tc.expected.CredentialsConfig.RoleExternalId, actual.CredentialsConfig.RoleExternalId) require.Equal(tc.expected.CredentialsConfig.RoleExternalId, actual.CredentialsConfig.RoleExternalId)
require.Equal(tc.expected.CredentialsConfig.RoleSessionName, actual.CredentialsConfig.RoleSessionName) require.Equal(tc.expected.CredentialsConfig.RoleSessionName, actual.CredentialsConfig.RoleSessionName)
require.Equal(tc.expected.CredentialsConfig.RoleTags, actual.CredentialsConfig.RoleTags) 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, expectedStatusCode: codes.Unknown,
}, },
{ {
@@ -568,7 +596,7 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) {
}, },
}, },
credentialConfig: &awsutil.CredentialsConfig{}, 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, 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, expectedStatusCode: codes.Unknown,
}, },
{ {
+16 -15
View File
@@ -73,7 +73,7 @@ func InvalidArgumentError(msg string, f map[string]string) error {
sort.Strings(fieldMsgs) sort.Strings(fieldMsgs)
msg = fmt.Sprintf("%s: [%s]", msg, strings.Join(fieldMsgs, ", ")) msg = fmt.Sprintf("%s: [%s]", msg, strings.Join(fieldMsgs, ", "))
} }
return BadRequestStatus(msg) return BadRequestStatus("%s", msg)
} }
// ParseAWSError converts an aws service error into a RPC status. This method // ParseAWSError converts an aws service error into a RPC status. This method
@@ -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 // 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 // when it cannot match an aws error code. This method does not handle specific
// service type errors such as S3 or EC2. // 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 { if err == nil {
return nil, nil return nil, nil
} }
msg := fmt.Sprintf("%s: %v", op, err)
// find the service name of the aws api // find the service name of the aws api
serviceName := "unknown" serviceName := "unknown"
var oe *smithy.OperationError var oe *smithy.OperationError
@@ -113,7 +114,7 @@ func ParseAWSError(err error, msg string) (st *status.Status, permission *pb.Per
Codes: retry.DefaultThrottleErrorCodes, Codes: retry.DefaultThrottleErrorCodes,
} }
if throttleErr.IsErrorThrottle(err).Bool() { 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 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: case awsErrorInvalidAccessKeyId:
fallthrough fallthrough
case awsErrorExpiredToken: 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{ return status.New(codes.PermissionDenied, statusMsg), &pb.Permission{
State: pb.StateType_STATE_TYPE_ERROR, State: pb.StateType_STATE_TYPE_ERROR,
ErrorDetails: apiErr.ErrorMessage(), ErrorDetails: apiErr.ErrorMessage(),
CheckedAt: timestamppb.Now(), CheckedAt: timestamppb.Now(),
} }
case awsErrorNoSuchBucket: 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{ return status.New(codes.NotFound, statusMsg), &pb.Permission{
State: pb.StateType_STATE_TYPE_ERROR, State: pb.StateType_STATE_TYPE_ERROR,
ErrorDetails: apiErr.ErrorMessage(), ErrorDetails: apiErr.ErrorMessage(),
CheckedAt: timestamppb.Now(), CheckedAt: timestamppb.Now(),
} }
case awsErrorBadDigest: 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 return status.New(codes.Aborted, statusMsg), nil
case awsErrorNoSuchKey: case awsErrorNoSuchKey:
fallthrough fallthrough
case awsErrorInvalidObjectState: 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 return status.New(codes.NotFound, statusMsg), nil
case awsErrorRequestTimeout: case awsErrorRequestTimeout:
fallthrough fallthrough
case awsErrorRequestTimeoutException: 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 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() defer httpErr.Response.Body.Close()
switch httpErr.HTTPStatusCode() { switch httpErr.HTTPStatusCode() {
case http.StatusBadRequest: 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 return status.New(codes.InvalidArgument, statusMsg), nil
case http.StatusUnauthorized: 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{ return status.New(codes.PermissionDenied, statusMsg), &pb.Permission{
State: pb.StateType_STATE_TYPE_ERROR, State: pb.StateType_STATE_TYPE_ERROR,
ErrorDetails: buf.String(), ErrorDetails: buf.String(),
CheckedAt: timestamppb.Now(), CheckedAt: timestamppb.Now(),
} }
case http.StatusForbidden: 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{ return status.New(codes.PermissionDenied, statusMsg), &pb.Permission{
State: pb.StateType_STATE_TYPE_ERROR, State: pb.StateType_STATE_TYPE_ERROR,
ErrorDetails: buf.String(), ErrorDetails: buf.String(),
CheckedAt: timestamppb.Now(), CheckedAt: timestamppb.Now(),
} }
case http.StatusNotFound: 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 return status.New(codes.NotFound, statusMsg), nil
case http.StatusTooManyRequests: 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 return status.New(codes.Unavailable, statusMsg), nil
case http.StatusInternalServerError: case http.StatusInternalServerError:
fallthrough fallthrough
case http.StatusBadGateway: case http.StatusBadGateway:
fallthrough fallthrough
case http.StatusServiceUnavailable: 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 return status.New(codes.Unavailable, statusMsg), nil
case http.StatusRequestTimeout: case http.StatusRequestTimeout:
fallthrough fallthrough
case http.StatusGatewayTimeout: 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 return status.New(codes.DeadlineExceeded, statusMsg), nil
} }
} }
+31 -31
View File
@@ -44,7 +44,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "http-status-code-bad-request", name: "http-status-code-bad-request",
err: TestAwsHttpResponseError(http.StatusBadRequest, "bad request"), err: TestAwsHttpResponseError(http.StatusBadRequest, "bad request"),
expectedStatusCode: codes.InvalidArgument, expectedStatusCode: codes.InvalidArgument,
expectedStatusMsg: "aws service unknown: bad request error: test", expectedStatusMsg: "aws service unknown: bad request: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -53,7 +53,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "http-status-code-unauthorized", name: "http-status-code-unauthorized",
err: TestAwsHttpResponseError(http.StatusUnauthorized, "unauthorized request"), err: TestAwsHttpResponseError(http.StatusUnauthorized, "unauthorized request"),
expectedStatusCode: codes.PermissionDenied, expectedStatusCode: codes.PermissionDenied,
expectedStatusMsg: "aws service unknown: invalid credentials error: test", expectedStatusMsg: "aws service unknown: invalid credentials: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_ERROR, State: pb.StateType_STATE_TYPE_ERROR,
ErrorDetails: "unauthorized request", ErrorDetails: "unauthorized request",
@@ -64,7 +64,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "http-status-code-forbidden", name: "http-status-code-forbidden",
err: TestAwsHttpResponseError(http.StatusForbidden, "forbidden request"), err: TestAwsHttpResponseError(http.StatusForbidden, "forbidden request"),
expectedStatusCode: codes.PermissionDenied, expectedStatusCode: codes.PermissionDenied,
expectedStatusMsg: "aws service unknown: invalid credentials error: test", expectedStatusMsg: "aws service unknown: invalid credentials: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_ERROR, State: pb.StateType_STATE_TYPE_ERROR,
ErrorDetails: "forbidden request", ErrorDetails: "forbidden request",
@@ -75,7 +75,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "http-status-code-not-found", name: "http-status-code-not-found",
err: TestAwsHttpResponseError(http.StatusNotFound, "not found request"), err: TestAwsHttpResponseError(http.StatusNotFound, "not found request"),
expectedStatusCode: codes.NotFound, expectedStatusCode: codes.NotFound,
expectedStatusMsg: "aws service unknown: resource not found error: test", expectedStatusMsg: "aws service unknown: resource not found: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -84,7 +84,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "http-status-code-too-many-requests", name: "http-status-code-too-many-requests",
err: TestAwsHttpResponseError(http.StatusTooManyRequests, "too many requests"), err: TestAwsHttpResponseError(http.StatusTooManyRequests, "too many requests"),
expectedStatusCode: codes.Unavailable, expectedStatusCode: codes.Unavailable,
expectedStatusMsg: "aws service unknown: throttling error: test", expectedStatusMsg: "aws service unknown: throttling: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -93,7 +93,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "http-status-code-request-timeout", name: "http-status-code-request-timeout",
err: TestAwsHttpResponseError(http.StatusRequestTimeout, "request timeout"), err: TestAwsHttpResponseError(http.StatusRequestTimeout, "request timeout"),
expectedStatusCode: codes.DeadlineExceeded, expectedStatusCode: codes.DeadlineExceeded,
expectedStatusMsg: "aws service unknown: timeout error: test", expectedStatusMsg: "aws service unknown: timeout: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -102,7 +102,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "http-status-code-internal-server-error", name: "http-status-code-internal-server-error",
err: TestAwsHttpResponseError(http.StatusInternalServerError, "internal server error"), err: TestAwsHttpResponseError(http.StatusInternalServerError, "internal server error"),
expectedStatusCode: codes.Unavailable, expectedStatusCode: codes.Unavailable,
expectedStatusMsg: "aws service unknown: connectivity error: test", expectedStatusMsg: "aws service unknown: connectivity: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -111,7 +111,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "http-status-code-bad-gateway", name: "http-status-code-bad-gateway",
err: TestAwsHttpResponseError(http.StatusBadGateway, "bad gateway"), err: TestAwsHttpResponseError(http.StatusBadGateway, "bad gateway"),
expectedStatusCode: codes.Unavailable, expectedStatusCode: codes.Unavailable,
expectedStatusMsg: "aws service unknown: connectivity error: test", expectedStatusMsg: "aws service unknown: connectivity: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -120,7 +120,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "http-status-code-service-unavailable", name: "http-status-code-service-unavailable",
err: TestAwsHttpResponseError(http.StatusServiceUnavailable, "service unavailable"), err: TestAwsHttpResponseError(http.StatusServiceUnavailable, "service unavailable"),
expectedStatusCode: codes.Unavailable, expectedStatusCode: codes.Unavailable,
expectedStatusMsg: "aws service unknown: connectivity error: test", expectedStatusMsg: "aws service unknown: connectivity: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -129,7 +129,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "http-status-code-gateway-timeout", name: "http-status-code-gateway-timeout",
err: TestAwsHttpResponseError(http.StatusGatewayTimeout, "gateway timeout"), err: TestAwsHttpResponseError(http.StatusGatewayTimeout, "gateway timeout"),
expectedStatusCode: codes.DeadlineExceeded, expectedStatusCode: codes.DeadlineExceeded,
expectedStatusMsg: "aws service unknown: timeout error: test", expectedStatusMsg: "aws service unknown: timeout: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -138,7 +138,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "aws-throttling", name: "aws-throttling",
err: TestAwsError("Throttling", "throttling error"), err: TestAwsError("Throttling", "throttling error"),
expectedStatusCode: codes.Unavailable, expectedStatusCode: codes.Unavailable,
expectedStatusMsg: "aws service unknown: throttling error: test", expectedStatusMsg: "aws service unknown: throttling: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -147,7 +147,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "aws-throttling-exception", name: "aws-throttling-exception",
err: TestAwsError("ThrottlingException", "throttling exception"), err: TestAwsError("ThrottlingException", "throttling exception"),
expectedStatusCode: codes.Unavailable, expectedStatusCode: codes.Unavailable,
expectedStatusMsg: "aws service unknown: throttling error: test", expectedStatusMsg: "aws service unknown: throttling: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -156,7 +156,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "aws-throttled-exception", name: "aws-throttled-exception",
err: TestAwsError("ThrottledException", "throttled exception"), err: TestAwsError("ThrottledException", "throttled exception"),
expectedStatusCode: codes.Unavailable, expectedStatusCode: codes.Unavailable,
expectedStatusMsg: "aws service unknown: throttling error: test", expectedStatusMsg: "aws service unknown: throttling: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -165,7 +165,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "aws-request-throttled-exception", name: "aws-request-throttled-exception",
err: TestAwsError("RequestThrottledException", "request throttled exception"), err: TestAwsError("RequestThrottledException", "request throttled exception"),
expectedStatusCode: codes.Unavailable, expectedStatusCode: codes.Unavailable,
expectedStatusMsg: "aws service unknown: throttling error: test", expectedStatusMsg: "aws service unknown: throttling: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -174,7 +174,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "aws-request-too-many-requests-exception", name: "aws-request-too-many-requests-exception",
err: TestAwsError("TooManyRequestsException", "too many requests exception"), err: TestAwsError("TooManyRequestsException", "too many requests exception"),
expectedStatusCode: codes.Unavailable, expectedStatusCode: codes.Unavailable,
expectedStatusMsg: "aws service unknown: throttling error: test", expectedStatusMsg: "aws service unknown: throttling: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -183,7 +183,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "aws-request-provisioned-throughput-exceeded-exception", name: "aws-request-provisioned-throughput-exceeded-exception",
err: TestAwsError("ProvisionedThroughputExceededException", "provisioned throughput exceeded exception"), err: TestAwsError("ProvisionedThroughputExceededException", "provisioned throughput exceeded exception"),
expectedStatusCode: codes.Unavailable, expectedStatusCode: codes.Unavailable,
expectedStatusMsg: "aws service unknown: throttling error: test", expectedStatusMsg: "aws service unknown: throttling: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -192,7 +192,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "aws-request-transaction-in-progress-exception", name: "aws-request-transaction-in-progress-exception",
err: TestAwsError("TransactionInProgressException", "transaction in progress exception"), err: TestAwsError("TransactionInProgressException", "transaction in progress exception"),
expectedStatusCode: codes.Unavailable, expectedStatusCode: codes.Unavailable,
expectedStatusMsg: "aws service unknown: throttling error: test", expectedStatusMsg: "aws service unknown: throttling: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -201,7 +201,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "aws-request-limit-exceeded", name: "aws-request-limit-exceeded",
err: TestAwsError("RequestLimitExceeded", "request limit exceeded"), err: TestAwsError("RequestLimitExceeded", "request limit exceeded"),
expectedStatusCode: codes.Unavailable, expectedStatusCode: codes.Unavailable,
expectedStatusMsg: "aws service unknown: throttling error: test", expectedStatusMsg: "aws service unknown: throttling: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -210,7 +210,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "aws-request-bandwidth-limit-exceeded", name: "aws-request-bandwidth-limit-exceeded",
err: TestAwsError("BandwidthLimitExceeded", "bandwidth limit exceeded"), err: TestAwsError("BandwidthLimitExceeded", "bandwidth limit exceeded"),
expectedStatusCode: codes.Unavailable, expectedStatusCode: codes.Unavailable,
expectedStatusMsg: "aws service unknown: throttling error: test", expectedStatusMsg: "aws service unknown: throttling: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -219,7 +219,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "aws-request-limit-exceeded-exception", name: "aws-request-limit-exceeded-exception",
err: TestAwsError("LimitExceededException", "limit exceeded exception"), err: TestAwsError("LimitExceededException", "limit exceeded exception"),
expectedStatusCode: codes.Unavailable, expectedStatusCode: codes.Unavailable,
expectedStatusMsg: "aws service unknown: throttling error: test", expectedStatusMsg: "aws service unknown: throttling: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -228,7 +228,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "aws-request-throttled", name: "aws-request-throttled",
err: TestAwsError("RequestThrottled", "request throttled"), err: TestAwsError("RequestThrottled", "request throttled"),
expectedStatusCode: codes.Unavailable, expectedStatusCode: codes.Unavailable,
expectedStatusMsg: "aws service unknown: throttling error: test", expectedStatusMsg: "aws service unknown: throttling: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -237,7 +237,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "aws-request-slow-down", name: "aws-request-slow-down",
err: TestAwsError("SlowDown", "slow down"), err: TestAwsError("SlowDown", "slow down"),
expectedStatusCode: codes.Unavailable, expectedStatusCode: codes.Unavailable,
expectedStatusMsg: "aws service unknown: throttling error: test", expectedStatusMsg: "aws service unknown: throttling: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -246,7 +246,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "aws-request-prior-request-not-complete", name: "aws-request-prior-request-not-complete",
err: TestAwsError("PriorRequestNotComplete", "prior request not complete"), err: TestAwsError("PriorRequestNotComplete", "prior request not complete"),
expectedStatusCode: codes.Unavailable, expectedStatusCode: codes.Unavailable,
expectedStatusMsg: "aws service unknown: throttling error: test", expectedStatusMsg: "aws service unknown: throttling: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -255,7 +255,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "aws-request-ec2-throttled-exception", name: "aws-request-ec2-throttled-exception",
err: TestAwsError("EC2ThrottledException", "ec2 throttled exception"), err: TestAwsError("EC2ThrottledException", "ec2 throttled exception"),
expectedStatusCode: codes.Unavailable, expectedStatusCode: codes.Unavailable,
expectedStatusMsg: "aws service unknown: throttling error: test", expectedStatusMsg: "aws service unknown: throttling: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -264,7 +264,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "aws-request-timeout", name: "aws-request-timeout",
err: TestAwsError("RequestTimeout", "request timeout"), err: TestAwsError("RequestTimeout", "request timeout"),
expectedStatusCode: codes.DeadlineExceeded, expectedStatusCode: codes.DeadlineExceeded,
expectedStatusMsg: "aws unknown error: test", expectedStatusMsg: "aws service unknown: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -273,7 +273,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "aws-request-timeout-exception", name: "aws-request-timeout-exception",
err: TestAwsError("RequestTimeoutException", "request timeout exception"), err: TestAwsError("RequestTimeoutException", "request timeout exception"),
expectedStatusCode: codes.DeadlineExceeded, expectedStatusCode: codes.DeadlineExceeded,
expectedStatusMsg: "aws unknown error: test", expectedStatusMsg: "aws service unknown: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
@@ -282,7 +282,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "aws-request-access-denied", name: "aws-request-access-denied",
err: TestAwsError(awsErrorAccessDenied, "not authorized to perform action"), err: TestAwsError(awsErrorAccessDenied, "not authorized to perform action"),
expectedStatusCode: codes.PermissionDenied, expectedStatusCode: codes.PermissionDenied,
expectedStatusMsg: "aws service unknown: invalid credentials error: test", expectedStatusMsg: "aws service unknown: invalid credentials: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_ERROR, State: pb.StateType_STATE_TYPE_ERROR,
ErrorDetails: "not authorized to perform action", ErrorDetails: "not authorized to perform action",
@@ -293,7 +293,7 @@ func Test_ParseAWSError(t *testing.T) {
name: "aws-request-invalid-access-key-id", name: "aws-request-invalid-access-key-id",
err: TestAwsError(awsErrorInvalidAccessKeyId, "The AWS Access Key Id you provided does not exist in our records."), err: TestAwsError(awsErrorInvalidAccessKeyId, "The AWS Access Key Id you provided does not exist in our records."),
expectedStatusCode: codes.PermissionDenied, expectedStatusCode: codes.PermissionDenied,
expectedStatusMsg: "aws service unknown: invalid credentials error: test", expectedStatusMsg: "aws service unknown: invalid credentials: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_ERROR, State: pb.StateType_STATE_TYPE_ERROR,
ErrorDetails: "The AWS Access Key Id you provided does not exist in our records.", 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", name: "aws-request-expired-token",
err: TestAwsError(awsErrorExpiredToken, "The security token included in the request is expired."), err: TestAwsError(awsErrorExpiredToken, "The security token included in the request is expired."),
expectedStatusCode: codes.PermissionDenied, expectedStatusCode: codes.PermissionDenied,
expectedStatusMsg: "aws service unknown: invalid credentials error: test", expectedStatusMsg: "aws service unknown: invalid credentials: test",
expectedPermission: &pb.Permission{ expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_ERROR, State: pb.StateType_STATE_TYPE_ERROR,
ErrorDetails: "The security token included in the request is expired.", 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) { t.Run(tt.name, func(t *testing.T) {
require, assert := require.New(t), assert.New(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 { if tt.expectedStatusCode == codes.OK {
require.Nil(actualStatus) require.Nil(actualStatus)
return return
} }
require.Equal(tt.expectedStatusCode, actualStatus.Code()) require.Equal(tt.expectedStatusCode, actualStatus.Code())
require.Equal(tt.expectedStatusMsg, actualStatus.Message()) require.Contains(actualStatus.Message(), tt.expectedStatusMsg)
require.Len(actualStatus.Details(), 0) require.Len(actualStatus.Details(), 0)
CheckSimilarPermission(assert, tt.expectedPermission, permission, true) CheckSimilarPermission(assert, tt.expectedPermission, permission, true)
}) })
+1 -1
View File
@@ -32,6 +32,6 @@ type AwsPlugin struct {
func NewAwsPlugin() *AwsPlugin { func NewAwsPlugin() *AwsPlugin {
return &AwsPlugin{ return &AwsPlugin{
HostPlugin: &host.HostPlugin{}, HostPlugin: &host.HostPlugin{},
StoragePlugin: &storage.StoragePlugin{}, StoragePlugin: storage.New(),
} }
} }
+8
View File
@@ -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. 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 - `region` (string): The region to configure the host catalog for. All host sets
in this catalog will be configured for this region. 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: The following `secrets` are required on an AWS host catalog resource:
+10
View File
@@ -19,6 +19,9 @@ import (
// map. // map.
type CatalogAttributes struct { type CatalogAttributes struct {
*cred.CredentialAttributes *cred.CredentialAttributes
// DualStack is used for configuring how the aws client will resolve requests.
DualStack bool
} }
func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) { func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) {
@@ -31,6 +34,12 @@ func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) {
return nil, err 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 { for s := range unknownFields {
switch s { switch s {
// Ignore knownFields from CredentialAttributes // Ignore knownFields from CredentialAttributes
@@ -57,6 +66,7 @@ func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) {
return &CatalogAttributes{ return &CatalogAttributes{
CredentialAttributes: credAttributes, CredentialAttributes: credAttributes,
DualStack: dualStack,
}, nil }, nil
} }
+16
View File
@@ -43,6 +43,22 @@ func TestGetCatalogAttributes(t *testing.T) {
}, },
expectedErrContains: "attributes.bar: unrecognized field, attributes.foo: unrecognized field", 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", name: "default",
in: &structpb.Struct{ in: &structpb.Struct{
+3
View File
@@ -10,4 +10,7 @@ const (
// ConstInstanceNameTagKey denotes the special AWS tag key that contains an EC2 // ConstInstanceNameTagKey denotes the special AWS tag key that contains an EC2
// instance's name. This field is to be matched literally. // instance's name. This field is to be matched literally.
ConstInstanceNameTagKey = "Name" ConstInstanceNameTagKey = "Name"
// ConstAwsDualStack is the key for the dualstack flag in the aws s3 client.
ConstAwsDualStack = "dual_stack"
) )
+41 -12
View File
@@ -51,7 +51,7 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo
if err != nil { if err != nil {
return nil, err return nil, err
} }
credConfig, err := credential.GetCredentialsConfig(catalog.GetSecrets(), catalogAttributes.CredentialAttributes) credConfig, err := credential.GetCredentialsConfig(catalog.GetSecrets(), catalogAttributes.CredentialAttributes, catalogAttributes.DualStack)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -83,13 +83,17 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo
} }
// perform dry run to ensure we can interact with AWS as expected. // 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() return nil, st.Err()
} }
persistedProto, err := catalogState.toProto() persistedProto, err := catalogState.toProto()
if err != nil { if err != nil {
return nil, errors.BadRequestStatus(err.Error()) return nil, errors.BadRequestStatus("%s", err.Error())
} }
return &pb.OnCreateCatalogResponse{ return &pb.OnCreateCatalogResponse{
@@ -131,6 +135,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
credState, err := credential.AwsCredentialPersistedStateFromProto( credState, err := credential.AwsCredentialPersistedStateFromProto(
req.GetPersisted().GetSecrets(), req.GetPersisted().GetSecrets(),
oldCatalogAttributes.CredentialAttributes, oldCatalogAttributes.CredentialAttributes,
oldCatalogAttributes.DualStack,
p.testCredStateOpts...) p.testCredStateOpts...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) 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 // 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 // user if they're not. Note this doesn't validate the credentials against
// AWS - it only does logical validation on the fields. // 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 { if err != nil {
return nil, err return nil, err
} }
@@ -166,7 +171,11 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) 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() return nil, st.Err()
} }
@@ -208,13 +217,17 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
} }
// perform dry run to ensure we can interact with AWS as expected. // 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() return nil, st.Err()
} }
persistedProto, err := catalogState.toProto() persistedProto, err := catalogState.toProto()
if err != nil { if err != nil {
return nil, errors.BadRequestStatus(err.Error()) return nil, errors.BadRequestStatus("%s", err.Error())
} }
return &pb.OnUpdateCatalogResponse{ return &pb.OnUpdateCatalogResponse{
@@ -248,6 +261,7 @@ func (p *HostPlugin) OnDeleteCatalog(ctx context.Context, req *pb.OnDeleteCatalo
credState, err := credential.AwsCredentialPersistedStateFromProto( credState, err := credential.AwsCredentialPersistedStateFromProto(
req.GetPersisted().GetSecrets(), req.GetPersisted().GetSecrets(),
catalogAttributes.CredentialAttributes, catalogAttributes.CredentialAttributes,
catalogAttributes.DualStack,
p.testCredStateOpts...) p.testCredStateOpts...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) 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( credState, err := credential.AwsCredentialPersistedStateFromProto(
req.GetPersisted().GetSecrets(), req.GetPersisted().GetSecrets(),
catalogAttributes.CredentialAttributes, catalogAttributes.CredentialAttributes,
catalogAttributes.DualStack,
p.testCredStateOpts...) p.testCredStateOpts...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) 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) 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 nil, st.Err()
} }
return &pb.OnCreateSetResponse{}, nil return &pb.OnCreateSetResponse{}, nil
@@ -382,6 +401,7 @@ func (p *HostPlugin) OnUpdateSet(ctx context.Context, req *pb.OnUpdateSetRequest
credState, err := credential.AwsCredentialPersistedStateFromProto( credState, err := credential.AwsCredentialPersistedStateFromProto(
req.GetPersisted().GetSecrets(), req.GetPersisted().GetSecrets(),
catalogAttributes.CredentialAttributes, catalogAttributes.CredentialAttributes,
catalogAttributes.DualStack,
p.testCredStateOpts...) p.testCredStateOpts...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) 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) 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 nil, st.Err()
} }
@@ -450,6 +474,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
credState, err := credential.AwsCredentialPersistedStateFromProto( credState, err := credential.AwsCredentialPersistedStateFromProto(
req.GetPersisted().GetSecrets(), req.GetPersisted().GetSecrets(),
catalogAttributes.CredentialAttributes, catalogAttributes.CredentialAttributes,
catalogAttributes.DualStack,
p.testCredStateOpts...) p.testCredStateOpts...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) 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 { if err != nil {
return nil, errors.BadRequestStatus("error getting EC2 client: %s", err) 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 // 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 // are passed in. This function can therefore be used for both host catalog and
// host set validation. // 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 { if state == nil {
return status.New(codes.InvalidArgument, "persisted state is required") return status.New(codes.InvalidArgument, "persisted state is required")
} }
ec2Client, err := state.EC2Client(ctx) ec2Client, err := state.EC2Client(ctx, ec2Opts...)
if err != nil { if err != nil {
return status.New(codes.InvalidArgument, fmt.Sprintf("error getting EC2 client: %s", err)) return status.New(codes.InvalidArgument, fmt.Sprintf("error getting EC2 client: %s", err))
} }
+9 -9
View File
@@ -683,7 +683,7 @@ func TestPluginOnCreateCatalogErr(t *testing.T) {
), ),
}), }),
}, },
expectedErrContains: "error rotating credentials", expectedErrContains: "rotating credentials",
expectedErrCode: codes.Unknown, expectedErrCode: codes.Unknown,
}, },
{ {
@@ -1089,7 +1089,7 @@ func TestPluginOnUpdateCatalogErr(t *testing.T) {
), ),
}), }),
}, },
expectedErrContains: "failed to delete access key", expectedErrContains: "deleting credentials",
expectedErrCode: codes.Unknown, expectedErrCode: codes.Unknown,
}, },
{ {
@@ -1167,7 +1167,7 @@ func TestPluginOnUpdateCatalogErr(t *testing.T) {
), ),
}), }),
}, },
expectedErrContains: "error rotating credentials", expectedErrContains: "rotating credentials",
expectedErrCode: codes.Unknown, 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, expectedErrCode: codes.Unknown,
}, },
} }
@@ -2747,8 +2747,8 @@ func TestAppendDistinct(t *testing.T) {
} }
func TestDryRunValidation(t *testing.T) { func TestDryRunValidation(t *testing.T) {
t.Run("nilState", func(t *testing.T) { t.Run("nil credential state", func(t *testing.T) {
st := dryRunValidation(context.Background(), nil) st := dryRunValidation(context.Background(), nil, nil)
require.NotNil(t, st) require.NotNil(t, st)
require.Equal(t, codes.InvalidArgument.String(), st.Code().String()) require.Equal(t, codes.InvalidArgument.String(), st.Code().String())
require.Equal(t, "persisted state is required", st.Message()) require.Equal(t, "persisted state is required", st.Message())
@@ -2762,7 +2762,7 @@ func TestDryRunValidation(t *testing.T) {
testEC2APIFunc: func(...aws.Config) (EC2API, error) { testEC2APIFunc: func(...aws.Config) (EC2API, error) {
return nil, fmt.Errorf("oops ec2 client err") return nil, fmt.Errorf("oops ec2 client err")
}, },
}) }, []ec2Option{})
require.NotNil(t, st) require.NotNil(t, st)
require.Equal(t, codes.InvalidArgument.String(), st.Code().String()) require.Equal(t, codes.InvalidArgument.String(), st.Code().String())
require.Equal(t, "error getting EC2 client: oops ec2 client err", st.Message()) 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"))), testEC2APIFunc: newTestMockEC2(nil, testMockEC2WithDescribeInstancesError(fmt.Errorf("oops describe instances error"))),
}) }, []ec2Option{})
require.NotNil(t, st) require.NotNil(t, st)
require.Equal(t, codes.FailedPrecondition.String(), st.Code().String()) require.Equal(t, codes.FailedPrecondition.String(), st.Code().String())
require.Equal(t, "aws describe instances failed: oops describe instances error", st.Message()) 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{})), testEC2APIFunc: newTestMockEC2(nil, testMockEC2WithDescribeInstancesOutput(&ec2.DescribeInstancesOutput{})),
}) }, []ec2Option{})
require.Nil(t, st) require.Nil(t, st)
}) })
} }
+57 -16
View File
@@ -7,12 +7,10 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"net"
"net/http"
"time"
"github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/aws/aws-sdk-go-v2/service/ec2"
smithyEndpoints "github.com/aws/smithy-go/endpoints"
"github.com/hashicorp/boundary-plugin-aws/internal/credential" "github.com/hashicorp/boundary-plugin-aws/internal/credential"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin" pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"google.golang.org/protobuf/types/known/structpb" "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) 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 { type awsCatalogPersistedState struct {
*credential.AwsCredentialPersistedState *credential.AwsCredentialPersistedState
@@ -86,7 +73,11 @@ func (s *awsCatalogPersistedState) toProto() (*pb.HostCatalogPersisted, error) {
// EC2Client returns a configured EC2 client based on the session // EC2Client returns a configured EC2 client based on the session
// information stored in the state. // 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) awsCfg, err := s.GenerateCredentialChain(ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting AWS configuration when fetching EC2 client: %w", err) 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 { if awsCfg == nil {
return nil, fmt.Errorf("nil aws configuration") 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 { if s.testEC2APIFunc != nil {
return s.testEC2APIFunc(*awsCfg) 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
} }
+10
View File
@@ -19,6 +19,9 @@ type StorageAttributes struct {
// EndpointUrl is used for configuring how the aws client will resolve requests. // EndpointUrl is used for configuring how the aws client will resolve requests.
EndpointUrl string EndpointUrl string
// DualStack is used for configuring how the aws client will resolve requests.
DualStack bool
} }
func getStorageAttributes(in *structpb.Struct) (*StorageAttributes, error) { func getStorageAttributes(in *structpb.Struct) (*StorageAttributes, error) {
@@ -37,6 +40,12 @@ func getStorageAttributes(in *structpb.Struct) (*StorageAttributes, error) {
} }
delete(unknownFields, ConstAwsEndpointUrl) 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 { for s := range unknownFields {
switch s { switch s {
// Ignore knownFields from CredentialAttributes // Ignore knownFields from CredentialAttributes
@@ -64,5 +73,6 @@ func getStorageAttributes(in *structpb.Struct) (*StorageAttributes, error) {
return &StorageAttributes{ return &StorageAttributes{
CredentialAttributes: credAttributes, CredentialAttributes: credAttributes,
EndpointUrl: endpointUrl, EndpointUrl: endpointUrl,
DualStack: dualStack,
}, nil }, nil
} }
+15
View File
@@ -41,6 +41,21 @@ func TestGetStorageAttributes(t *testing.T) {
}, },
expectedErrContains: "attributes.bar: unrecognized field, attributes.foo: unrecognized field", 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", name: "good w/ endpoint",
in: &structpb.Struct{ in: &structpb.Struct{
+3
View File
@@ -7,6 +7,9 @@ const (
// ConstAwsEndpointUrl is the key for the endpoint url in the aws s3 client. // ConstAwsEndpointUrl is the key for the endpoint url in the aws s3 client.
ConstAwsEndpointUrl = "endpoint_url" 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 is the recommened chunk size for sending data through a stream
defaultStreamChunkSize = 65536 // 64 KiB defaultStreamChunkSize = 65536 // 64 KiB
) )
+2 -2
View File
@@ -50,12 +50,12 @@ func setStatePermission(req any, permission *pb.Permission) *pb.StorageBucketCre
// credentials, and connectivity error types. // credentials, and connectivity error types.
// //
// https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html // 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 { if err == nil {
return nil return nil
} }
st, p := errors.ParseAWSError(err, msg) st, p := errors.ParseAWSError(op, err)
if p != nil { if p != nil {
state := setStatePermission(req, p) state := setStatePermission(req, p)
if st, err = st.WithDetails(state); err != nil { if st, err = st.WithDetails(state); err != nil {
+10 -10
View File
@@ -34,7 +34,7 @@ func Test_ParseS3Error(t *testing.T) {
req: &pb.GetObjectRequest{}, req: &pb.GetObjectRequest{},
err: TestAwsS3Error("NoSuchBucket", "GetObject", "no such bucket"), err: TestAwsS3Error("NoSuchBucket", "GetObject", "no such bucket"),
expectedStatusCode: codes.NotFound, expectedStatusCode: codes.NotFound,
expectedStatusMsg: "aws s3 error: test", expectedStatusMsg: "aws service s3: test:",
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
Read: &pb.Permission{ Read: &pb.Permission{
@@ -50,7 +50,7 @@ func Test_ParseS3Error(t *testing.T) {
req: &pb.GetObjectRequest{}, req: &pb.GetObjectRequest{},
err: TestAwsS3Error("NoSuchKey", "GetObject", "no such key"), err: TestAwsS3Error("NoSuchKey", "GetObject", "no such key"),
expectedStatusCode: codes.NotFound, expectedStatusCode: codes.NotFound,
expectedStatusMsg: "aws s3 error: test", expectedStatusMsg: "aws service s3: test:",
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
Read: &pb.Permission{ Read: &pb.Permission{
@@ -66,7 +66,7 @@ func Test_ParseS3Error(t *testing.T) {
err: errors.TestAwsHttpResponseError(404, "not found"), err: errors.TestAwsHttpResponseError(404, "not found"),
expectedStatusCode: codes.NotFound, expectedStatusCode: codes.NotFound,
// cannot embed services within mock http errors, therefore unknown service // 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{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
Read: &pb.Permission{ Read: &pb.Permission{
@@ -81,7 +81,7 @@ func Test_ParseS3Error(t *testing.T) {
req: &pb.GetObjectRequest{}, req: &pb.GetObjectRequest{},
err: TestAwsS3Error("InvalidObjectState", "GetObject", "invalid object state"), err: TestAwsS3Error("InvalidObjectState", "GetObject", "invalid object state"),
expectedStatusCode: codes.NotFound, expectedStatusCode: codes.NotFound,
expectedStatusMsg: "aws s3 error: test", expectedStatusMsg: "aws service s3: test:",
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
Read: &pb.Permission{ Read: &pb.Permission{
@@ -96,7 +96,7 @@ func Test_ParseS3Error(t *testing.T) {
req: &pb.PutObjectRequest{}, req: &pb.PutObjectRequest{},
err: TestAwsS3Error(s3ErrorBadDigest, "PutObject", "checksum mismatch"), err: TestAwsS3Error(s3ErrorBadDigest, "PutObject", "checksum mismatch"),
expectedStatusCode: codes.Aborted, expectedStatusCode: codes.Aborted,
expectedStatusMsg: "aws s3 error: test", expectedStatusMsg: "aws service s3: test:",
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
Write: &pb.Permission{ Write: &pb.Permission{
@@ -111,7 +111,7 @@ func Test_ParseS3Error(t *testing.T) {
req: &pb.GetObjectRequest{}, req: &pb.GetObjectRequest{},
err: TestAwsS3Error("Throttling", "action", "throttling exception"), err: TestAwsS3Error("Throttling", "action", "throttling exception"),
expectedStatusCode: codes.Unavailable, expectedStatusCode: codes.Unavailable,
expectedStatusMsg: "aws service s3: throttling error: test", expectedStatusMsg: "aws service s3: throttling: test:",
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
Read: &pb.Permission{ Read: &pb.Permission{
@@ -126,7 +126,7 @@ func Test_ParseS3Error(t *testing.T) {
req: &pb.GetObjectRequest{}, req: &pb.GetObjectRequest{},
err: TestAwsS3Error("RequestTimeout", "action", "request timeout"), err: TestAwsS3Error("RequestTimeout", "action", "request timeout"),
expectedStatusCode: codes.DeadlineExceeded, expectedStatusCode: codes.DeadlineExceeded,
expectedStatusMsg: "aws s3 error: test", expectedStatusMsg: "aws service s3: test:",
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
Read: &pb.Permission{ Read: &pb.Permission{
@@ -141,7 +141,7 @@ func Test_ParseS3Error(t *testing.T) {
req: &pb.GetObjectRequest{}, req: &pb.GetObjectRequest{},
err: TestAwsS3Error("AccessDenied", "action", "access denied"), err: TestAwsS3Error("AccessDenied", "action", "access denied"),
expectedStatusCode: codes.PermissionDenied, expectedStatusCode: codes.PermissionDenied,
expectedStatusMsg: "aws service s3: invalid credentials error: test", expectedStatusMsg: "aws service s3: invalid credentials: test:",
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
Read: &pb.Permission{ Read: &pb.Permission{
@@ -157,14 +157,14 @@ func Test_ParseS3Error(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
require, assert := require.New(t), assert.New(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 { if tt.expectedStatusCode == codes.OK {
require.Nil(actualStatus) require.Nil(actualStatus)
return return
} }
require.Equal(tt.expectedStatusCode, actualStatus.Code()) require.Equal(tt.expectedStatusCode, actualStatus.Code())
require.Equal(tt.expectedStatusMsg, actualStatus.Message()) require.Contains(actualStatus.Message(), tt.expectedStatusMsg)
require.Len(actualStatus.Details(), 1) require.Len(actualStatus.Details(), 1)
foundDetail := false foundDetail := false
+234 -62
View File
@@ -14,6 +14,7 @@ import (
"os" "os"
"path" "path"
"strings" "strings"
"sync"
"github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3"
@@ -24,14 +25,24 @@ import (
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin" pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-secure-stdlib/parseutil"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
) )
// Ensure that we are implementing StoragePluginServiceServer // Ensure that we are implementing StoragePluginServiceServer
var _ pb.StoragePluginServiceServer = (*StoragePlugin)(nil) var _ pb.StoragePluginServiceServer = (*StoragePlugin)(nil)
// clientCache caches previously initialized S3API clients created with a RoleARN attribute.
type clientCache struct {
// cache is a map of cached s3 clients. The key of the map
// is the public id of the storage bucket
cache map[string]S3API
sync.RWMutex
}
// StoragePlugin implements the StoragePluginServiceServer interface for the // StoragePlugin implements the StoragePluginServiceServer interface for the
// AWS storage service plugin. // AWS storage service plugin.
type StoragePlugin struct { type StoragePlugin struct {
@@ -42,6 +53,137 @@ type StoragePlugin struct {
// testStorageStateOpts are passed in to the stored state to control test behavior // testStorageStateOpts are passed in to the stored state to control test behavior
testStorageStateOpts []awsStoragePersistedStateOption testStorageStateOpts []awsStoragePersistedStateOption
clients *clientCache
}
// New creates a new StoragePlugin
func New() *StoragePlugin {
c := &clientCache{
cache: make(map[string]S3API),
}
return &StoragePlugin{
clients: c,
}
}
// getClient returns an S3API client for the given storage bucket id.
func (p *StoragePlugin) getClient(ctx context.Context,
storageBucketId string,
storageState *awsStoragePersistedState,
opt ...s3Option) (S3API, error) {
if storageBucketId == "" {
// No storage bucket ID to key cache on, create new client and return
client, err := storageState.s3Client(ctx, opt...)
if err != nil {
return nil, errors.BadRequestStatus("error creating S3 client: %s", err)
}
return client, nil
}
opts, err := getOpts(opt...)
if err != nil {
return nil, fmt.Errorf("error parsing options in cache: %w", err)
}
if opts.withCacheRefresh {
// Forced cache refresh
p.clients.Lock()
defer p.clients.Unlock()
client, err := storageState.s3Client(ctx, opt...)
if err != nil {
return nil, errors.BadRequestStatus("error creating S3 client: %s", err)
}
p.clients.cache[storageBucketId] = client
return client, nil
}
p.clients.RLock()
client, ok := p.clients.cache[storageBucketId]
p.clients.RUnlock()
if !ok || client.Credentials().Expired() {
// We got a cache miss or the credentials has expired, time to refresh
p.clients.Lock()
defer p.clients.Unlock()
// Check cache again in case another caller updated it since we got the lock
client, ok = p.clients.cache[storageBucketId]
if ok && !client.Credentials().Expired() {
// Got a cache hit with valid credentials, return cached client
return client, nil
}
// Create new client and cache it
var err error
client, err = storageState.s3Client(ctx, opt...)
if err != nil {
return nil, errors.BadRequestStatus("error creating S3 client: %s", err)
}
p.clients.cache[storageBucketId] = client
}
return client, nil
}
type s3Caller func(client S3API) (any, error)
// call gets the s3client and passes it to the s3caller function, if a permission error is returned
// it forces a cache refresh and tries again.
func (p *StoragePlugin) call(
ctx context.Context,
fn s3Caller,
storageBucketId string,
storageState *awsStoragePersistedState,
opts ...s3Option) (any, error) {
s3Client, err := p.getClient(ctx, storageBucketId, storageState, opts...)
if err != nil {
return nil, errors.BadRequestStatus("error getting S3 client: %s", err)
}
firstAttempt := true
for {
resp, err := fn(s3Client)
if err != nil {
st, _ := errors.ParseAWSError("", err)
if st.Code() == codes.PermissionDenied && firstAttempt {
// We got a permission error, this is the first time so refresh cache and try again
firstAttempt = false
opts = append(opts, WithCacheRefresh(true))
s3Client, err = p.getClient(ctx, storageBucketId, storageState, opts...)
if err != nil {
return nil, errors.BadRequestStatus("error refreshing S3 client: %s", err)
}
continue
}
return nil, err
}
return resp, nil
}
}
// NormalizeStorageBucketData is called by boundary before calling OnCreate or OnUpdate
//
// aws storage supports a total of 8 attributes, 6 as part of credential.CredentialAttributes
// and 2 more from storage.StorageAttributes. of these, only endpoint_url is normalized.
func (sp *StoragePlugin) NormalizeStorageBucketData(ctx context.Context, req *pb.NormalizeStorageBucketDataRequest) (*pb.NormalizeStorageBucketDataResponse, error) {
sa, err := getStorageAttributes(req.GetAttributes())
if err != nil {
return nil, err
}
fields := req.GetAttributes().GetFields()
if _, ok := fields[ConstAwsEndpointUrl]; ok {
if sa.EndpointUrl, err = parseutil.NormalizeAddr(sa.EndpointUrl); err != nil {
return nil, status.New(codes.InvalidArgument, fmt.Sprintf("failed to normalize provided %s: %s", ConstAwsEndpointUrl, err.Error())).Err()
}
fields[ConstAwsEndpointUrl] = structpb.NewStringValue(sa.EndpointUrl)
}
return &pb.NormalizeStorageBucketDataResponse{
Attributes: &structpb.Struct{
Fields: fields,
},
}, nil
} }
// OnCreateStorageBucket is called when a storage bucket is created. // OnCreateStorageBucket is called when a storage bucket is created.
@@ -64,7 +206,7 @@ func (p *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCre
return nil, err return nil, err
} }
credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes) credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes, storageAttributes.DualStack)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -102,7 +244,7 @@ func (p *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCre
persistedProto, err := storageState.toProto() persistedProto, err := storageState.toProto()
if err != nil { if err != nil {
return nil, errors.BadRequestStatus(err.Error()) return nil, errors.BadRequestStatus("%s", err.Error())
} }
return &pb.OnCreateStorageBucketResponse{ return &pb.OnCreateStorageBucketResponse{
@@ -147,6 +289,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
credState, err := cred.AwsCredentialPersistedStateFromProto( credState, err := cred.AwsCredentialPersistedStateFromProto(
req.GetPersisted().GetData(), req.GetPersisted().GetData(),
oldStorageAttributes.CredentialAttributes, oldStorageAttributes.CredentialAttributes,
oldStorageAttributes.DualStack,
p.testCredStateOpts...) p.testCredStateOpts...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
@@ -155,7 +298,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
// Verify the incoming credentials are valid and return any errors to the // 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 // user if they're not. Note this doesn't validate the credentials against
// AWS - it only does logical validation on the fields. // 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 { if err != nil {
return nil, err return nil, err
} }
@@ -230,7 +373,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
persistedProto, err := storageState.toProto() persistedProto, err := storageState.toProto()
if err != nil { if err != nil {
return nil, errors.BadRequestStatus(err.Error()) return nil, errors.BadRequestStatus("%s", err.Error())
} }
return &pb.OnUpdateStorageBucketResponse{ return &pb.OnUpdateStorageBucketResponse{
@@ -264,6 +407,7 @@ func (p *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDel
credState, err := cred.AwsCredentialPersistedStateFromProto( credState, err := cred.AwsCredentialPersistedStateFromProto(
req.GetPersisted().GetData(), req.GetPersisted().GetData(),
storageAttributes.CredentialAttributes, storageAttributes.CredentialAttributes,
storageAttributes.DualStack,
p.testCredStateOpts...) p.testCredStateOpts...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
@@ -318,7 +462,7 @@ func (p *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReques
return nil, err return nil, err
} }
credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes) credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes, storageAttributes.DualStack)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -346,21 +490,25 @@ func (p *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReques
if storageAttributes.EndpointUrl != "" { if storageAttributes.EndpointUrl != "" {
opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl)) opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl))
} }
s3Client, err := storageState.S3Client(ctx, opts...) if storageAttributes.DualStack {
if err != nil { opts = append(opts, WithDualStack(storageAttributes.DualStack))
return nil, errors.BadRequestStatus("error getting S3 client: %s", err)
} }
objectKey := path.Join(bucket.GetBucketPrefix(), req.GetKey()) objectKey := path.Join(bucket.GetBucketPrefix(), req.GetKey())
resp, err := s3Client.HeadObject(ctx, &s3.HeadObjectInput{ headCall := func(s3Client S3API) (any, error) {
Bucket: aws.String(bucket.GetBucketName()), return s3Client.HeadObject(ctx, &s3.HeadObjectInput{
Key: aws.String(objectKey), Bucket: aws.String(bucket.GetBucketName()),
}) Key: aws.String(objectKey),
if err != nil { })
return nil, parseS3Error(err, "failed to head object", req).Err()
} }
headResp, err := p.call(ctx, headCall, bucket.GetId(), storageState, opts...)
if err != nil {
return nil, parseS3Error("head object", err, req).Err()
}
resp := headResp.(*s3.HeadObjectOutput)
return &pb.HeadObjectResponse{ return &pb.HeadObjectResponse{
ContentLength: resp.ContentLength, ContentLength: aws.ToInt64(resp.ContentLength),
LastModified: timestamppb.New(*resp.LastModified), LastModified: timestamppb.New(*resp.LastModified),
}, nil }, nil
} }
@@ -386,7 +534,7 @@ func (p *StoragePlugin) ValidatePermissions(ctx context.Context, req *pb.Validat
return nil, err return nil, err
} }
credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes) credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes, storageAttributes.DualStack)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -444,7 +592,7 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu
return err return err
} }
credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes) credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes, storageAttributes.DualStack)
if err != nil { if err != nil {
return err return err
} }
@@ -472,19 +620,22 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu
if storageAttributes.EndpointUrl != "" { if storageAttributes.EndpointUrl != "" {
opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl)) opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl))
} }
s3Client, err := storageState.S3Client(stream.Context(), opts...) if storageAttributes.DualStack {
if err != nil { opts = append(opts, WithDualStack(storageAttributes.DualStack))
return errors.BadRequestStatus("error getting S3 client: %s", err)
} }
objectKey := path.Join(bucket.GetBucketPrefix(), req.GetKey()) objectKey := path.Join(bucket.GetBucketPrefix(), req.GetKey())
resp, err := s3Client.GetObject(stream.Context(), &s3.GetObjectInput{ getCall := func(s3Client S3API) (any, error) {
Bucket: aws.String(bucket.GetBucketName()), return s3Client.GetObject(stream.Context(), &s3.GetObjectInput{
Key: aws.String(objectKey), Bucket: aws.String(bucket.GetBucketName()),
}) Key: aws.String(objectKey),
if err != nil { })
return parseS3Error(err, "failed to get object", req).Err()
} }
getResp, err := p.call(stream.Context(), getCall, bucket.GetId(), storageState, opts...)
if err != nil {
return parseS3Error("get object", err, req).Err()
}
resp := getResp.(*s3.GetObjectOutput)
defer resp.Body.Close() defer resp.Body.Close()
reader := bufio.NewReader(resp.Body) reader := bufio.NewReader(resp.Body)
@@ -555,7 +706,7 @@ func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest)
return nil, err return nil, err
} }
credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes) credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes, storageAttributes.DualStack)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -583,9 +734,8 @@ func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest)
if storageAttributes.EndpointUrl != "" { if storageAttributes.EndpointUrl != "" {
opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl)) opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl))
} }
s3Client, err := storageState.S3Client(ctx, opts...) if storageAttributes.DualStack {
if err != nil { opts = append(opts, WithDualStack(storageAttributes.DualStack))
return nil, errors.BadRequestStatus("error getting S3 client: %s", err)
} }
hash := sha256.New() hash := sha256.New()
@@ -598,18 +748,23 @@ func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest)
if err != nil { if err != nil {
return nil, errors.UnknownStatus("failed to rewind file pointer") return nil, errors.UnknownStatus("failed to rewind file pointer")
} }
objectKey := path.Join(bucket.GetBucketPrefix(), req.GetKey()) objectKey := path.Join(bucket.GetBucketPrefix(), req.GetKey())
resp, err := s3Client.PutObject(ctx, &s3.PutObjectInput{
Bucket: aws.String(bucket.GetBucketName()), putCall := func(s3Client S3API) (any, error) {
Key: aws.String(objectKey), return s3Client.PutObject(ctx, &s3.PutObjectInput{
Body: file, Bucket: aws.String(bucket.GetBucketName()),
ChecksumAlgorithm: types.ChecksumAlgorithmSha256, Key: aws.String(objectKey),
ChecksumSHA256: aws.String(checksum), Body: file,
}) ChecksumAlgorithm: types.ChecksumAlgorithmSha256,
if err != nil { ChecksumSHA256: aws.String(checksum),
return nil, parseS3Error(err, "failed to put object", req).Err() })
} }
putResp, err := p.call(ctx, putCall, bucket.GetId(), storageState, opts...)
if err != nil {
return nil, parseS3Error("put object", err, req).Err()
}
resp := putResp.(*s3.PutObjectOutput)
if resp.ChecksumSHA256 == nil { if resp.ChecksumSHA256 == nil {
return nil, errors.UnknownStatus("missing checksum response from aws") return nil, errors.UnknownStatus("missing checksum response from aws")
} }
@@ -650,7 +805,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
return nil, err return nil, err
} }
credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes) credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes, storageAttributes.DualStack)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -678,10 +833,6 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
if storageAttributes.EndpointUrl != "" { if storageAttributes.EndpointUrl != "" {
opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl)) opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl))
} }
client, err := storageState.S3Client(ctx, opts...)
if err != nil {
return nil, errors.BadRequestStatus("error getting S3 client: %s", err)
}
prefix := path.Join(bucket.GetBucketPrefix(), req.GetKeyPrefix()) prefix := path.Join(bucket.GetBucketPrefix(), req.GetKeyPrefix())
if strings.HasSuffix(req.GetKeyPrefix(), "/") { if strings.HasSuffix(req.GetKeyPrefix(), "/") {
@@ -691,12 +842,15 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
} }
if !req.Recursive { if !req.Recursive {
_, err := client.DeleteObject(ctx, &s3.DeleteObjectInput{ deleteCall := func(s3Client S3API) (any, error) {
Bucket: aws.String(bucket.GetBucketName()), return s3Client.DeleteObject(ctx, &s3.DeleteObjectInput{
Key: aws.String(prefix), Bucket: aws.String(bucket.GetBucketName()),
}) Key: aws.String(prefix),
})
}
_, err := p.call(ctx, deleteCall, bucket.GetId(), storageState, opts...)
if err != nil { if err != nil {
return nil, parseS3Error(err, "failed to delete object", req).Err() return nil, parseS3Error("delete object", err, req).Err()
} }
return &pb.DeleteObjectsResponse{ return &pb.DeleteObjectsResponse{
ObjectsDeleted: uint32(1), ObjectsDeleted: uint32(1),
@@ -704,21 +858,36 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
} }
const maxkeys = 1000 const maxkeys = 1000
objects := []types.ObjectIdentifier{} objects := []types.ObjectIdentifier{}
client, err := p.getClient(ctx, bucket.GetId(), storageState, opts...)
if err != nil {
return nil, errors.BadRequestStatus("error getting S3 client: %s", err)
}
var conToken *string var conToken *string
truncated := true truncated, firstAttempt := true, true
for truncated { for truncated {
res, err := client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ res, err := client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{
Bucket: aws.String(bucket.GetBucketName()), Bucket: aws.String(bucket.GetBucketName()),
Prefix: aws.String(prefix), Prefix: aws.String(prefix),
MaxKeys: maxkeys, MaxKeys: aws.Int32(maxkeys),
ContinuationToken: conToken, ContinuationToken: conToken,
}) })
if err != nil { if err != nil {
return nil, parseS3Error(err, "failed to list objects", req).Err() st, _ := errors.ParseAWSError("", err)
if st.Code() == codes.PermissionDenied && firstAttempt {
// This is first attempt and we get a permission error, refresh cache and try again
firstAttempt = false
opts = append(opts, WithCacheRefresh(true))
client, err = p.getClient(ctx, bucket.GetId(), storageState, opts...)
if err != nil {
return nil, errors.BadRequestStatus("error refreshing S3 client: %s", err)
}
continue
}
return nil, parseS3Error("list objects", err, req).Err()
} }
truncated = res.IsTruncated truncated = aws.ToBool(res.IsTruncated)
conToken = res.NextContinuationToken conToken = res.NextContinuationToken
for _, o := range res.Contents { for _, o := range res.Contents {
objects = append(objects, types.ObjectIdentifier{ objects = append(objects, types.ObjectIdentifier{
@@ -738,7 +907,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
}, },
}) })
if err != nil { 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) deleted += len(res.Deleted)
} }
@@ -769,8 +938,11 @@ func dryRunValidation(ctx context.Context, state *awsStoragePersistedState, attr
if attrs.EndpointUrl != "" { if attrs.EndpointUrl != "" {
opts = append(opts, WithEndpoint(attrs.EndpointUrl)) opts = append(opts, WithEndpoint(attrs.EndpointUrl))
} }
if attrs.DualStack {
opts = append(opts, WithDualStack(attrs.DualStack))
}
client, err := state.S3Client(ctx, opts...) client, err := state.s3Client(ctx, opts...)
if err != nil { if err != nil {
return status.New(codes.InvalidArgument, fmt.Sprintf("error getting S3 client: %s", err)) return status.New(codes.InvalidArgument, fmt.Sprintf("error getting S3 client: %s", err))
} }
@@ -786,7 +958,7 @@ func dryRunValidation(ctx context.Context, state *awsStoragePersistedState, attr
Body: bytes.NewReader([]byte("hashicorp boundary aws plugin access test")), Body: bytes.NewReader([]byte("hashicorp boundary aws plugin access test")),
}); err != nil { }); err != nil {
var st *status.Status 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()) errs = multierror.Append(errs, st.Err())
} else { } else {
permissions.Write = &pb.Permission{State: pb.StateType_STATE_TYPE_OK, CheckedAt: timestamppb.Now()} permissions.Write = &pb.Permission{State: pb.StateType_STATE_TYPE_OK, CheckedAt: timestamppb.Now()}
@@ -797,7 +969,7 @@ func dryRunValidation(ctx context.Context, state *awsStoragePersistedState, attr
Key: aws.String(objectKey), Key: aws.String(objectKey),
}); err != nil { }); err != nil {
var st *status.Status 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()) errs = multierror.Append(errs, st.Err())
} else { } else {
permissions.Read = &pb.Permission{State: pb.StateType_STATE_TYPE_OK, CheckedAt: timestamppb.Now()} permissions.Read = &pb.Permission{State: pb.StateType_STATE_TYPE_OK, CheckedAt: timestamppb.Now()}
@@ -808,7 +980,7 @@ func dryRunValidation(ctx context.Context, state *awsStoragePersistedState, attr
Key: aws.String(objectKey), Key: aws.String(objectKey),
}); err != nil { }); err != nil {
var st *status.Status 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()) errs = multierror.Append(errs, st.Err())
} }
@@ -817,7 +989,7 @@ func dryRunValidation(ctx context.Context, state *awsStoragePersistedState, attr
Prefix: aws.String(objectKey), Prefix: aws.String(objectKey),
}); err != nil { }); err != nil {
var st *status.Status 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()) errs = multierror.Append(errs, st.Err())
} else if res == nil || len(res.Contents) != 1 || *res.Contents[0].Key != objectKey { } else if res == nil || len(res.Contents) != 1 || *res.Contents[0].Key != objectKey {
permissions.Read = &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, CheckedAt: timestamppb.Now()} permissions.Read = &pb.Permission{State: pb.StateType_STATE_TYPE_UNKNOWN, CheckedAt: timestamppb.Now()}
@@ -829,7 +1001,7 @@ func dryRunValidation(ctx context.Context, state *awsStoragePersistedState, attr
Key: aws.String(objectKey), Key: aws.String(objectKey),
}); err != nil { }); err != nil {
var st *status.Status 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()) errs = multierror.Append(errs, st.Err())
} else { } else {
permissions.Delete = &pb.Permission{State: pb.StateType_STATE_TYPE_OK, CheckedAt: timestamppb.Now()} permissions.Delete = &pb.Permission{State: pb.StateType_STATE_TYPE_OK, CheckedAt: timestamppb.Now()}
+337 -52
View File
@@ -26,6 +26,7 @@ import (
"github.com/hashicorp/boundary-plugin-aws/internal/credential" "github.com/hashicorp/boundary-plugin-aws/internal/credential"
internal "github.com/hashicorp/boundary-plugin-aws/internal/errors" internal "github.com/hashicorp/boundary-plugin-aws/internal/errors"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets"
"github.com/hashicorp/boundary/sdk/pbs/plugin"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin" pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"github.com/hashicorp/go-secure-stdlib/awsutil/v2" "github.com/hashicorp/go-secure-stdlib/awsutil/v2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -36,6 +37,204 @@ import (
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
) )
func TestStoragePlugin_NormalizeStorageBucketData(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
req *plugin.NormalizeStorageBucketDataRequest
expected *plugin.NormalizeStorageBucketDataResponse
err string
}{
{
name: "missing attributes",
req: &plugin.NormalizeStorageBucketDataRequest{},
err: "attributes.region: missing required value \"region\"",
},
{
name: "empty attributes",
req: &plugin.NormalizeStorageBucketDataRequest{
Attributes: &structpb.Struct{},
},
err: "attributes.region: missing required value \"region\"",
},
{
name: "missing required field",
req: &plugin.NormalizeStorageBucketDataRequest{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAwsEndpointUrl: structpb.NewStringValue("http://127.0.0.1:9000"),
},
},
},
err: "attributes.region: missing required value \"region\"",
},
{
name: "missing endpoint_url",
req: &plugin.NormalizeStorageBucketDataRequest{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
credential.ConstRegion: structpb.NewStringValue("somewhere"),
},
},
},
expected: &plugin.NormalizeStorageBucketDataResponse{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
credential.ConstRegion: structpb.NewStringValue("somewhere"),
},
},
},
},
{
name: "valid endpoint_url returned unchanged",
req: &plugin.NormalizeStorageBucketDataRequest{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAwsEndpointUrl: structpb.NewStringValue("http://127.0.0.1:9000"),
credential.ConstRegion: structpb.NewStringValue("somewhere"),
},
},
},
expected: &plugin.NormalizeStorageBucketDataResponse{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAwsEndpointUrl: structpb.NewStringValue("http://127.0.0.1:9000"),
credential.ConstRegion: structpb.NewStringValue("somewhere"),
},
},
},
},
{
name: "valid ipv6 endpoint_url returned normalized",
req: &plugin.NormalizeStorageBucketDataRequest{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAwsEndpointUrl: structpb.NewStringValue("https://[2001:BEEF:0:0:0:1:0:0001]"),
credential.ConstRegion: structpb.NewStringValue("somewhere"),
},
},
},
expected: &plugin.NormalizeStorageBucketDataResponse{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAwsEndpointUrl: structpb.NewStringValue("https://[2001:beef::1:0:1]"),
credential.ConstRegion: structpb.NewStringValue("somewhere"),
},
},
},
},
{
name: "valid ipv6 endpoint_url with port returned normalized",
req: &plugin.NormalizeStorageBucketDataRequest{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAwsEndpointUrl: structpb.NewStringValue("https://[2001:BEEF:0:0:0:1:0:0001]:9000"),
credential.ConstRegion: structpb.NewStringValue("somewhere"),
},
},
},
expected: &plugin.NormalizeStorageBucketDataResponse{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAwsEndpointUrl: structpb.NewStringValue("https://[2001:beef::1:0:1]:9000"),
credential.ConstRegion: structpb.NewStringValue("somewhere"),
},
},
},
},
{
name: "invalid ipv6 endpoint_url returns normalization error",
req: &plugin.NormalizeStorageBucketDataRequest{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAwsEndpointUrl: structpb.NewStringValue("https://[2001:BEEF:0:0:0:1:0:0001"),
credential.ConstRegion: structpb.NewStringValue("somewhere"),
},
},
},
err: "failed to normalize provided endpoint_url: failed to parse address",
},
{
name: "invalid ipv6 endpoint_url returns normalization error 2",
req: &plugin.NormalizeStorageBucketDataRequest{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAwsEndpointUrl: structpb.NewStringValue("https://[2001:BEEF:0:0:0:1:0:0001]:"),
credential.ConstRegion: structpb.NewStringValue("somewhere"),
},
},
},
err: "failed to normalize provided endpoint_url: url has malformed host: missing port value after colon",
},
{
name: "valid all fields returns normalized",
req: &plugin.NormalizeStorageBucketDataRequest{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAwsEndpointUrl: structpb.NewStringValue("http://[2001:BEEF:0:0:0:1:0:0001]:9000/test/path"),
credential.ConstRegion: structpb.NewStringValue("somewhere ❤"),
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(false),
credential.ConstRoleArn: structpb.NewStringValue("an arn"),
credential.ConstRoleExternalId: structpb.NewStringValue("id"),
credential.ConstRoleSessionName: structpb.NewStringValue("sesh"),
ConstAwsDualStack: structpb.NewBoolValue(false),
credential.ConstRoleTags: structpb.NewStructValue(&structpb.Struct{
Fields: map[string]*structpb.Value{
"foo": structpb.NewStringValue("bar"),
},
}),
},
},
},
expected: &plugin.NormalizeStorageBucketDataResponse{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAwsEndpointUrl: structpb.NewStringValue("http://[2001:beef::1:0:1]:9000/test/path"),
credential.ConstRegion: structpb.NewStringValue("somewhere ❤"),
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(false),
credential.ConstRoleArn: structpb.NewStringValue("an arn"),
credential.ConstRoleExternalId: structpb.NewStringValue("id"),
credential.ConstRoleSessionName: structpb.NewStringValue("sesh"),
ConstAwsDualStack: structpb.NewBoolValue(false),
credential.ConstRoleTags: structpb.NewStructValue(&structpb.Struct{
Fields: map[string]*structpb.Value{
"foo": structpb.NewStringValue("bar"),
},
}),
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
sp := new(StoragePlugin)
res, err := sp.NormalizeStorageBucketData(ctx, tt.req)
if tt.err != "" {
require.ErrorContains(err, tt.err)
return
}
if err != nil {
if st, ok := status.FromError(err); ok {
require.Nil(err, st.Message())
}
}
require.Nil(err)
for key, val := range tt.expected.GetAttributes().GetFields() {
v, ok := res.GetAttributes().GetFields()[key]
assert.True(ok, "missing expected value: %q", key)
if ok {
assert.Equal(val, v, "expected[%q]: %v, but got: %v", key, val, v)
}
}
})
}
}
func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) { func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) {
validRequest := func() *pb.OnCreateStorageBucketRequest { validRequest := func() *pb.OnCreateStorageBucketRequest {
return &pb.OnCreateStorageBucketRequest{ return &pb.OnCreateStorageBucketRequest{
@@ -153,7 +352,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, expectedErrCode: codes.Unknown,
expectedDetails: nil, expectedDetails: nil,
}, },
@@ -178,7 +377,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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -211,7 +410,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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -245,7 +444,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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -280,7 +479,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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -314,7 +513,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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -349,7 +548,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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -384,7 +583,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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -857,7 +1056,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, expectedErrCode: codes.Unknown,
}, },
{ {
@@ -896,7 +1095,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, expectedErrCode: codes.Unknown,
}, },
{ {
@@ -920,7 +1119,7 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
testMockS3WithPutObjectError(TestAwsS3Error("AccessDenied", "PutObject", "not authorized")), 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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -953,7 +1152,7 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
testMockS3WithPutObjectError(TestAwsS3Error("ThrottlingException", "PutObject", "throttling exception")), 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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -987,7 +1186,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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -1022,7 +1221,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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -1056,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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -1091,7 +1290,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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -1255,7 +1454,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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -1363,7 +1562,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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -1486,7 +1685,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, expectedErrCode: codes.Unknown,
}, },
{ {
@@ -1599,7 +1798,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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -1738,7 +1937,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, expectedErrCode: codes.Unknown,
}, },
{ {
@@ -1973,7 +2172,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, expectedErrCode: codes.Unknown,
}, },
{ {
@@ -2172,7 +2371,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, expectedErrCode: codes.PermissionDenied,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -2196,7 +2395,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, expectedErrCode: codes.Unavailable,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -2219,7 +2418,7 @@ func TestStoragePlugin_HeadObject(t *testing.T) {
), ),
), ),
}, },
expectedErrContains: "aws s3 error: failed to head object", expectedErrContains: "aws service s3: head object",
expectedErrCode: codes.NotFound, expectedErrCode: codes.NotFound,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -2241,7 +2440,7 @@ func TestStoragePlugin_HeadObject(t *testing.T) {
newTestMockS3( newTestMockS3(
nil, nil,
testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{ testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{
ContentLength: 1024, ContentLength: aws.Int64(1024),
LastModified: aws.Time(createTime(t, "2006-01-02T15:04:05Z")), LastModified: aws.Time(createTime(t, "2006-01-02T15:04:05Z")),
}), }),
), ),
@@ -2272,7 +2471,7 @@ func TestStoragePlugin_HeadObject(t *testing.T) {
newTestMockS3( newTestMockS3(
nil, nil,
testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{ testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{
ContentLength: 1024, ContentLength: aws.Int64(1024),
LastModified: aws.Time(createTime(t, "2006-01-02T15:04:05Z")), LastModified: aws.Time(createTime(t, "2006-01-02T15:04:05Z")),
}), }),
), ),
@@ -2425,7 +2624,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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -2458,7 +2657,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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -2492,7 +2691,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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -2527,7 +2726,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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -2561,7 +2760,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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -2596,7 +2795,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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -2683,7 +2882,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, expectedErrCode: codes.FailedPrecondition,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -2851,7 +3050,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, expectedErrCode: codes.PermissionDenied,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -2875,7 +3074,7 @@ func TestStoragePlugin_GetObject(t *testing.T) {
), ),
), ),
}, },
expectedErrContains: "aws s3 error: failed to get object", expectedErrContains: "aws service s3: get object",
expectedErrCode: codes.NotFound, expectedErrCode: codes.NotFound,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -2898,7 +3097,7 @@ func TestStoragePlugin_GetObject(t *testing.T) {
), ),
), ),
}, },
expectedErrContains: "aws s3 error: failed to get object", expectedErrContains: "aws service s3: get object",
expectedErrCode: codes.NotFound, expectedErrCode: codes.NotFound,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -2921,7 +3120,7 @@ func TestStoragePlugin_GetObject(t *testing.T) {
), ),
), ),
}, },
expectedErrContains: "aws s3 error: failed to get object", expectedErrContains: "aws service s3: get object",
expectedErrCode: codes.NotFound, expectedErrCode: codes.NotFound,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -2945,7 +3144,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, expectedErrCode: codes.Unavailable,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -3300,7 +3499,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, expectedErrCode: codes.PermissionDenied,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -3324,7 +3523,7 @@ func TestStoragePlugin_PutObject(t *testing.T) {
), ),
), ),
}, },
expectedErrContains: "aws s3 error: failed to put object", expectedErrContains: "aws service s3: put object",
expectedErrCode: codes.NotFound, expectedErrCode: codes.NotFound,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -3348,7 +3547,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, expectedErrCode: codes.Unavailable,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -3640,7 +3839,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, expectedErrCode: codes.PermissionDenied,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -3664,7 +3863,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, expectedErrCode: codes.PermissionDenied,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -3685,7 +3884,7 @@ func TestStoragePlugin_DeleteObjects(t *testing.T) {
newTestMockS3( newTestMockS3(
nil, nil,
testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{ testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{
IsTruncated: false, IsTruncated: aws.Bool(false),
Contents: []s3types.Object{ Contents: []s3types.Object{
{ {
Key: aws.String("abc/abc"), Key: aws.String("abc/abc"),
@@ -3696,7 +3895,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, expectedErrCode: codes.PermissionDenied,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -3720,7 +3919,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, expectedErrCode: codes.Unavailable,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -3743,7 +3942,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, expectedErrCode: codes.Unavailable,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -3763,7 +3962,7 @@ func TestStoragePlugin_DeleteObjects(t *testing.T) {
newTestMockS3( newTestMockS3(
nil, nil,
testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{ testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{
IsTruncated: false, IsTruncated: aws.Bool(false),
Contents: []s3types.Object{ Contents: []s3types.Object{
s3types.Object{ s3types.Object{
Key: aws.String("abc/abc"), Key: aws.String("abc/abc"),
@@ -3774,7 +3973,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, expectedErrCode: codes.Unavailable,
expectedDetails: &pb.StorageBucketCredentialState{ expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{ State: &pb.Permissions{
@@ -3808,7 +4007,7 @@ func TestStoragePlugin_DeleteObjects(t *testing.T) {
newTestMockS3( newTestMockS3(
nil, nil,
testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{ testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{
IsTruncated: false, IsTruncated: aws.Bool(false),
Contents: []s3types.Object{ Contents: []s3types.Object{
{Key: aws.String("abc/abc1")}, {Key: aws.String("abc/abc1")},
{Key: aws.String("abc/abc2")}, {Key: aws.String("abc/abc2")},
@@ -3836,7 +4035,7 @@ func TestStoragePlugin_DeleteObjects(t *testing.T) {
newTestMockS3( newTestMockS3(
nil, nil,
testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{ testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{
IsTruncated: false, IsTruncated: aws.Bool(false),
Contents: []s3types.Object{}, Contents: []s3types.Object{},
}), }),
// no deleted response since it shouldn't be called // no deleted response since it shouldn't be called
@@ -3917,3 +4116,89 @@ func TestStoragePlugin_DeleteObjects(t *testing.T) {
}) })
} }
} }
func TestS3ClientCache(t *testing.T) {
ctx := context.Background()
assert, require := assert.New(t), require.New(t)
cred1, err := awsutil.NewCredentialsConfig(
awsutil.WithRegion("us-east-1"),
awsutil.WithAccessKey("cred1"),
awsutil.WithSecretKey("secret1"),
)
require.NoError(err)
cred2, err := awsutil.NewCredentialsConfig(
awsutil.WithRegion("us-east-1"),
awsutil.WithAccessKey("cred2"),
awsutil.WithSecretKey("secret2"),
)
require.NoError(err)
cred3, err := awsutil.NewCredentialsConfig(
awsutil.WithRegion("us-east-1"),
awsutil.WithAccessKey("cred3"),
awsutil.WithSecretKey("secret3"),
)
require.NoError(err)
p := New()
cred, err := credential.NewAwsCredentialPersistedState(
credential.WithCredentialsConfig(cred1),
)
require.NoError(err)
state, err := newAwsStoragePersistedState(withCredentials(cred))
require.NoError(err)
// Cache should be empty before first call
assert.Empty(p.clients.cache)
client, err := p.getClient(ctx, "id", state)
require.NoError(err)
require.NotNil(client)
assert.Equal(cred1.AccessKey, client.Credentials().AccessKeyID)
assert.Equal(cred1.SecretKey, client.Credentials().SecretAccessKey)
// Update state with new credentials
cred, err = credential.NewAwsCredentialPersistedState(
credential.WithCredentialsConfig(cred2),
)
require.NoError(err)
state, err = newAwsStoragePersistedState(withCredentials(cred))
require.NoError(err)
// Client is cached and creds valid so it should still grab initial client
client, err = p.getClient(ctx, "id", state)
require.NoError(err)
require.NotNil(client)
assert.Equal(cred1.AccessKey, client.Credentials().AccessKeyID)
assert.Equal(cred1.SecretKey, client.Credentials().SecretAccessKey)
// Force credentials to expire
c := client.(*s3Client)
c.creds.CanExpire = true
p.clients.Lock()
p.clients.cache["id"] = c
p.clients.Unlock()
// Getting client should now create new client on cred2 since the credentials have expired
client, err = p.getClient(ctx, "id", state)
require.NoError(err)
require.NotNil(client)
assert.Equal(cred2.AccessKey, client.Credentials().AccessKeyID)
assert.Equal(cred2.SecretKey, client.Credentials().SecretAccessKey)
// Update state with new credentials again
cred, err = credential.NewAwsCredentialPersistedState(
credential.WithCredentialsConfig(cred3),
)
require.NoError(err)
state, err = newAwsStoragePersistedState(withCredentials(cred))
require.NoError(err)
// Getting client and force cache refresh should return cred3 based client
client, err = p.getClient(ctx, "id", state, WithCacheRefresh(true))
require.NoError(err)
require.NotNil(client)
assert.Equal(cred3.AccessKey, client.Credentials().AccessKeyID)
assert.Equal(cred3.SecretKey, client.Credentials().SecretAccessKey)
}
+45 -21
View File
@@ -7,10 +7,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"net"
"net/http"
"net/url" "net/url"
"time"
"github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3"
@@ -27,17 +24,16 @@ type S3API interface {
PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error) PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error)
DeleteObjects(ctx context.Context, params *s3.DeleteObjectsInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectsOutput, error) DeleteObjects(ctx context.Context, params *s3.DeleteObjectsInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectsOutput, error)
ListObjectsV2(ctx context.Context, params *s3.ListObjectsV2Input, optFns ...func(*s3.Options)) (*s3.ListObjectsV2Output, error) ListObjectsV2(ctx context.Context, params *s3.ListObjectsV2Input, optFns ...func(*s3.Options)) (*s3.ListObjectsV2Output, error)
Credentials() aws.Credentials
} }
var customClient = &http.Client{ type s3Client struct {
Transport: &http.Transport{ *s3.Client
Proxy: http.ProxyFromEnvironment, creds aws.Credentials
DialContext: (&net.Dialer{ }
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second, func (c *s3Client) Credentials() aws.Credentials {
DualStack: true, return c.creds
}).DialContext,
},
} }
type awsStoragePersistedState struct { type awsStoragePersistedState struct {
@@ -91,9 +87,9 @@ func (s *awsStoragePersistedState) toProto() (*storagebuckets.StorageBucketPersi
return &storagebuckets.StorageBucketPersisted{Data: data}, nil return &storagebuckets.StorageBucketPersisted{Data: data}, nil
} }
// S3Client returns a configured S3 client based on the session // s3Client returns a configured S3 client based on the session
// information stored in the state. // information stored in the state.
func (s *awsStoragePersistedState) S3Client(ctx context.Context, opt ...s3Option) (S3API, error) { func (s *awsStoragePersistedState) s3Client(ctx context.Context, opt ...s3Option) (S3API, error) {
opts, err := getOpts(opt...) opts, err := getOpts(opt...)
if err != nil { if err != nil {
return nil, fmt.Errorf("error parsing options when fetching S3 client: %w", err) return nil, fmt.Errorf("error parsing options when fetching S3 client: %w", err)
@@ -106,15 +102,25 @@ func (s *awsStoragePersistedState) S3Client(ctx context.Context, opt ...s3Option
return nil, fmt.Errorf("nil aws configuration") return nil, fmt.Errorf("nil aws configuration")
} }
var s3Opts []func(*s3.Options) var s3Opts []func(*s3.Options)
if opts.withEndpoint != "" { if opts.withEndpoint != "" || opts.withDualStack {
s3Opts = append(s3Opts, s3.WithEndpointResolverV2(&endpointResolver{ s3Opts = append(s3Opts, s3.WithEndpointResolverV2(&endpointResolver{
endpoint: opts.withEndpoint, endpoint: opts.withEndpoint,
dualStack: opts.withDualStack,
})) }))
} }
if s.testS3APIFunc != nil { if s.testS3APIFunc != nil {
return s.testS3APIFunc(*awsCfg) return s.testS3APIFunc(*awsCfg)
} }
return s3.NewFromConfig(*awsCfg, s3Opts...), nil
// Retrieve the credentials provider from the client
credsProvider := awsCfg.Credentials
creds, err := credsProvider.Retrieve(ctx)
if err != nil {
return nil, fmt.Errorf("failed to retrieve credentials: %v\n", err)
}
c := s3.NewFromConfig(*awsCfg, s3Opts...)
return &s3Client{Client: c, creds: creds}, nil
} }
// getOpts iterates the inbound s3Options and returns a struct // getOpts iterates the inbound s3Options and returns a struct
@@ -136,13 +142,23 @@ type s3Option func(*s3Options) error
// options = how options are represented // options = how options are represented
type s3Options struct { type s3Options struct {
withEndpoint string withEndpoint string
withDualStack bool
withCacheRefresh bool
} }
func getDefaultS3Options() s3Options { func getDefaultS3Options() s3Options {
return s3Options{} return s3Options{}
} }
// WithCacheRefresh controls if the cache should be forced to refresh
func WithCacheRefresh(refresh bool) s3Option {
return func(o *s3Options) error {
o.withCacheRefresh = refresh
return nil
}
}
// WithEndpoint contains the endpoint to use // WithEndpoint contains the endpoint to use
func WithEndpoint(with string) s3Option { func WithEndpoint(with string) s3Option {
return func(o *s3Options) error { return func(o *s3Options) error {
@@ -151,18 +167,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 { type endpointResolver struct {
endpoint string endpoint string
dualStack bool
} }
func (e *endpointResolver) ResolveEndpoint(ctx context.Context, params s3.EndpointParameters) (resolver smithyEndpoints.Endpoint, err error) { func (e *endpointResolver) ResolveEndpoint(ctx context.Context, params s3.EndpointParameters) (resolver smithyEndpoints.Endpoint, err error) {
var uri *url.URL var uri *url.URL
params.UseDualStack = aws.Bool(e.dualStack)
resolver, err = s3.NewDefaultEndpointResolverV2().ResolveEndpoint(ctx, params) resolver, err = s3.NewDefaultEndpointResolverV2().ResolveEndpoint(ctx, params)
if err != nil { if err != nil {
return return
} }
if e.endpoint == "" { 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 return
} }
uri, err = url.Parse(e.endpoint) uri, err = url.Parse(e.endpoint)
@@ -170,7 +195,6 @@ func (e *endpointResolver) ResolveEndpoint(ctx context.Context, params s3.Endpoi
return return
} }
if uri == nil { 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 return
} }
resolver = smithyEndpoints.Endpoint{ resolver = smithyEndpoints.Endpoint{
+1 -1
View File
@@ -64,7 +64,7 @@ func TestAwsStoragePersistedStateS3Client(t *testing.T) {
) )
require.NoError(err) require.NoError(err)
clientRaw, err := state.S3Client(context.Background(), tc.s3Opts...) clientRaw, err := state.s3Client(context.Background(), tc.s3Opts...)
require.NoError(err) require.NoError(err)
require.NotNil(clientRaw) require.NotNil(clientRaw)
client, ok := clientRaw.(*testMockS3) client, ok := clientRaw.(*testMockS3)
+112 -7
View File
@@ -17,12 +17,16 @@ import (
"github.com/hashicorp/boundary-plugin-aws/plugin/service/storage" "github.com/hashicorp/boundary-plugin-aws/plugin/service/storage"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin" pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/structpb" "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) { func TestStoragePlugin(t *testing.T) {
region := os.Getenv("AWS_REGION") region := os.Getenv("AWS_REGION")
if region == "" { if region == "" {
@@ -75,6 +79,12 @@ func TestStoragePlugin(t *testing.T) {
iamSecretAccessKeyMissingPutObject, err := tf.GetOutputString("iam_secret_access_key_missing_put_obj") iamSecretAccessKeyMissingPutObject, err := tf.GetOutputString("iam_secret_access_key_missing_put_obj")
require.NoError(err) 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 // 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 // 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 // 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) 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. // Reassign the keyid and secret first.
@@ -132,11 +142,59 @@ func TestStoragePlugin(t *testing.T) {
// * Validate Permissions // * Validate Permissions
// ******************** // ********************
// //
var expectedSBCState *pb.StorageBucketCredentialState
// Validate error is returned for missing get object permission // 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 // 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) { 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.Helper()
t.Logf("testing ValidatePermissions") 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{ reqAttrs, err := structpb.NewStruct(map[string]any{
credential.ConstRegion: region, credential.ConstRegion: region,
@@ -315,6 +375,51 @@ func testPluginValidatePermissions(ctx context.Context, t *testing.T, p *storage
_, err = p.ValidatePermissions(ctx, req) _, err = p.ValidatePermissions(ctx, req)
require.Error(err) 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) { 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 // Need to create a GRPC server to test GetObject
t.Logf("testing GetObject") t.Logf("testing GetObject")
lis, err := net.Listen("tcp", "localhost:2030") lis, err := net.Listen("tcp", "[::1]:2030")
require.NoError(err) require.NoError(err)
grpcServer := grpc.NewServer() grpcServer := grpc.NewServer()
pb.RegisterStoragePluginServiceServer(grpcServer, &storage.StoragePlugin{}) pb.RegisterStoragePluginServiceServer(grpcServer, &storage.StoragePlugin{})
@@ -377,7 +482,7 @@ func testPluginObjectMethods(ctx context.Context, t *testing.T, p *storage.Stora
grpcServer.Serve(lis) 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) require.NoError(err)
defer conn.Close() defer conn.Close()
client := pb.NewStoragePluginServiceClient(conn) client := pb.NewStoragePluginServiceClient(conn)
+97 -12
View File
@@ -32,7 +32,9 @@ resource "aws_iam_policy" "valid" {
"Action": [ "Action": [
"s3:PutObject", "s3:PutObject",
"s3:GetObject", "s3:GetObject",
"s3:GetObjectAttributes" "s3:DeleteObject",
"s3:GetObjectAttributes",
"s3:ListBucket"
], ],
"Effect": "Allow", "Effect": "Allow",
"Resource": "${aws_s3_bucket.test.arn}*" "Resource": "${aws_s3_bucket.test.arn}*"
@@ -79,7 +81,9 @@ resource "aws_iam_policy" "missing_put_obj" {
{ {
"Action": [ "Action": [
"s3:GetObject", "s3:GetObject",
"s3:GetObjectAttributes" "s3:GetObjectAttributes",
"s3:ListBucket",
"s3:DeleteObject"
], ],
"Effect": "Allow", "Effect": "Allow",
"Resource": "${aws_s3_bucket.test.arn}*" "Resource": "${aws_s3_bucket.test.arn}*"
@@ -125,7 +129,7 @@ resource "aws_iam_policy" "missing_get_obj" {
{ {
"Action": [ "Action": [
"s3:PutObject", "s3:PutObject",
"s3:GetObjectAttributes" "s3:DeleteObject"
], ],
"Effect": "Allow", "Effect": "Allow",
"Resource": "${aws_s3_bucket.test.arn}*" "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 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 # Static Credential Testing
resource "random_id" "user_name" { resource "random_id" "user_name" {
count = var.iam_user_count count = var.iam_user_count
prefix = random_id.prefix.dec prefix = "demo-${local.hashicorp_email}-boundary-iam-user"
byte_length = 4 byte_length = 4
} }
resource "aws_iam_user" "test" { resource "aws_iam_user" "test" {
count = var.iam_user_count count = var.iam_user_count
name = random_id.user_name[count.index].dec name = random_id.user_name[count.index].dec
force_destroy = true 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" "test" { 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 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 # Edge case for missing GetObject permission
resource "random_id" "missing_get_obj" { resource "random_id" "missing_get_obj" {
prefix = random_id.prefix.dec prefix = "demo-${local.hashicorp_email}-boundary-iam-user"
byte_length = 4 byte_length = 4
} }
resource "aws_iam_user" "missing_get_obj" { resource "aws_iam_user" "missing_get_obj" {
name = random_id.missing_get_obj.dec name = random_id.missing_get_obj.dec
force_destroy = true 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" { 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 # Edge case for missing PutObject permission
resource "random_id" "missing_put_obj" { resource "random_id" "missing_put_obj" {
prefix = random_id.prefix.dec prefix = "demo-${local.hashicorp_email}-boundary-iam-user"
byte_length = 4 byte_length = 4
} }
resource "aws_iam_user" "missing_put_obj" { resource "aws_iam_user" "missing_put_obj" {
name = random_id.missing_put_obj.dec name = random_id.missing_put_obj.dec
force_destroy = true 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" { resource "aws_iam_access_key" "missing_put_obj" {
+15 -1
View File
@@ -13,10 +13,14 @@ output "missing_put_obj_policy_arn" {
value = aws_iam_role.missing_put_obj.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 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" { output "iam_user_names" {
value = aws_iam_user.test.*.name value = aws_iam_user.test.*.name
} }
@@ -54,3 +58,13 @@ output "iam_secret_access_key_missing_get_obj" {
value = aws_iam_access_key.missing_get_obj.secret value = aws_iam_access_key.missing_get_obj.secret
sensitive = true 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
}
+9 -1
View File
@@ -4,6 +4,14 @@
resource "aws_s3_bucket" "test" { resource "aws_s3_bucket" "test" {
bucket = random_id.prefix.dec bucket = random_id.prefix.dec
force_destroy = true force_destroy = true
acl = "private"
tags = local.tags 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
}
+5 -3
View File
@@ -4,7 +4,7 @@
data "aws_caller_identity" "current" {} data "aws_caller_identity" "current" {}
resource "random_id" "prefix" { resource "random_id" "prefix" {
prefix = "storage-e2e-test" prefix = "aws-plugin-e2e-test"
byte_length = 4 byte_length = 4
} }
@@ -13,8 +13,10 @@ variable "iam_user_count" {
} }
locals { locals {
hashicorp_email = split(":", data.aws_caller_identity.current.user_id)[1]
tags = { tags = {
user_id = split(":", data.aws_caller_identity.current.user_id)[1] User = split(":", data.aws_caller_identity.current.user_id)[1]
repo = "boundary-plugin-aws" Repo = "boundary-plugin-aws"
Environment = "Demo"
} }
} }