Compare commits

...
Author SHA1 Message Date
Hugo 7fa6b7e7a3 wip: Dynamic Creds tests 2023-08-23 12:23:36 +01:00
Damian Debkowski d970294995 refactor: switch from awsutil to awsutilv2 2023-08-08 23:14:11 -07:00
18 changed files with 799 additions and 607 deletions

No files matched your search

+22 -20
View File
@@ -4,37 +4,40 @@ go 1.20
require ( require (
github.com/aws/aws-sdk-go v1.44.80 github.com/aws/aws-sdk-go v1.44.80
github.com/aws/aws-sdk-go-v2 v1.18.0 github.com/aws/aws-sdk-go-v2 v1.20.1
github.com/aws/aws-sdk-go-v2/config v1.18.19
github.com/aws/aws-sdk-go-v2/credentials v1.13.18
github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0 github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0
github.com/aws/aws-sdk-go-v2/service/s3 v1.31.1 github.com/aws/aws-sdk-go-v2/service/iam v1.22.2
github.com/aws/smithy-go v1.13.5 github.com/aws/aws-sdk-go-v2/service/s3 v1.38.2
github.com/aws/aws-sdk-go-v2/service/sts v1.21.2
github.com/aws/smithy-go v1.14.1
github.com/google/go-cmp v0.5.9 github.com/google/go-cmp v0.5.9
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/hashicorp/boundary/sdk v0.0.33 github.com/hashicorp/boundary/sdk v0.0.33
github.com/hashicorp/go-secure-stdlib/awsutil v0.2.2 github.com/hashicorp/go-secure-stdlib/awsutil v0.2.2
github.com/hashicorp/terraform-json v0.14.0 github.com/hashicorp/terraform-json v0.14.0
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/stretchr/testify v1.8.2 github.com/stretchr/testify v1.8.4
google.golang.org/grpc v1.53.0 google.golang.org/grpc v1.53.0
google.golang.org/protobuf v1.28.1 google.golang.org/protobuf v1.28.1
) )
replace github.com/hashicorp/go-secure-stdlib/awsutil v0.2.2 => github.com/hashicorp/go-secure-stdlib/awsutilv2 v0.0.0-20230809054404-9029211590fd
require ( require (
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.12 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1 // indirect github.com/aws/aws-sdk-go-v2/config v1.18.33 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.13.32 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.8 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.38 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.23 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.32 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.3.39 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.26 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.13 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.33 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.6 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.32 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.18.7 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.13.2 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.14.1 // indirect github.com/fatih/color v1.14.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
@@ -53,7 +56,6 @@ require (
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/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/zclconf/go-cty v1.10.0 // indirect github.com/zclconf/go-cty v1.10.0 // indirect
golang.org/x/crypto v0.8.0 // indirect golang.org/x/crypto v0.8.0 // indirect
+42 -51
View File
@@ -4,51 +4,53 @@ github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.44.80 h1:jEXGecSgPdvM5KnyDsSgFhZSm7WwaTp4h544Im4SfhI= github.com/aws/aws-sdk-go v1.44.80 h1:jEXGecSgPdvM5KnyDsSgFhZSm7WwaTp4h544Im4SfhI=
github.com/aws/aws-sdk-go v1.44.80/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.44.80/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go-v2 v1.17.7/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2 v1.18.0 h1:882kkTpSFhdgYRKVZ/VCgf7sd0ru57p2JCxz4/oN5RY=
github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs= github.com/aws/aws-sdk-go-v2 v1.20.1 h1:rZBf5DWr7YGrnlTK4kgDQGn1ltqOg5orCYb/UhOFZkg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10/go.mod h1:VeTZetY5KRJLuD/7fkQXMU6Mw7H5m/KP2J5Iy9osMno= github.com/aws/aws-sdk-go-v2 v1.20.1/go.mod h1:NU06lETsFm8fUC6ZjhgDpVBcGZTFQ6XM+LZWZxMI4ac=
github.com/aws/aws-sdk-go-v2/config v1.18.19 h1:AqFK6zFNtq4i1EYu+eC7lcKHYnZagMn6SW171la0bGw= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.12 h1:lN6L3LrYHeZ6xCxaIYtoWCx4GMLk4nRknsh29OMSqHY=
github.com/aws/aws-sdk-go-v2/config v1.18.19/go.mod h1:XvTmGMY8d52ougvakOv1RpiTLPz9dlG/OQHsKU/cMmY= 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/credentials v1.13.18 h1:EQMdtHwz0ILTW1hoP+EwuWhwCG1hD6l3+RWFQABET4c= github.com/aws/aws-sdk-go-v2/config v1.18.33 h1:JKcw5SFxFW/rpM4mOPjv0VQ11E2kxW13F3exWOy7VZU=
github.com/aws/aws-sdk-go-v2/credentials v1.13.18/go.mod h1:vnwlwjIe+3XJPBYKu1et30ZPABG3VaXJYr8ryohpIyM= 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/feature/ec2/imds v1.13.1 h1:gt57MN3liKiyGopcqgNzJb2+d9MJaKT/q1OksHNXVE4= github.com/aws/aws-sdk-go-v2/credentials v1.13.32 h1:lIH1eKPcCY1ylR4B6PkBGRWMHO3aVenOKJHWiS4/G2w=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1/go.mod h1:lfUx8puBRdM5lVVMQlwt2v+ofiG/X6Ms+dy0UkG/kXw= github.com/aws/aws-sdk-go-v2/credentials v1.13.32/go.mod h1:lL8U3v/Y79YRG69WlAho0OHIKUXCyFvSXaIvfo81sls=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31/go.mod h1:QT0BqUvX1Bh2ABdTGnjqEjvjzrCfIniM9Sc8zn9Yndo= 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/internal/configsources v1.1.33 h1:kG5eQilShqmJbv11XL1VpyDbaEJzWxd4zRiCG30GSn4= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.8/go.mod h1:ce7BgLQfYr5hQFdy67oX2svto3ufGtm6oBvmsHScI1Q=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25/go.mod h1:zBHOPwhBc3FlQjQJE/D3IfPWiWaQmT06Vq9aNukDo0k= 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.4.27 h1:vFQlirhuM8lLlpI7imKOMsjdQLuN9CPi+k44F/OFVsk= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.38/go.mod h1:qggunOChCMu9ZF/UkAfhTz25+U2rLVb3ya0Ua6TTfCA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32 h1:p5luUImdIqywn6JpQsW3tq5GNOxKmOnEpybzPx+d1lk= 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.3.32/go.mod h1:XGhIBZDEgfqmFIugclZ6FU7v75nHhBDtzuB4xB/tEi4= 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.0.23 h1:DWYZIsyqagnWL00f8M/SOr9fN063OEQWn9LLTbdYXsk= 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.0.23/go.mod h1:uIiFgURZbACBEQJfqTZPb/jxO7R+9LeoHUFudtIdeQI= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.39/go.mod h1:WLAW8PT7+JhjZfLSWe7WEJaJu0GNo0cKc2Zyo003RBs=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.1 h1:vUh7dBFNS3oFCtVv6CiYKh5hP9ls8+kIpKLeFruIBLk=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.1/go.mod h1:sFMeinkhj/SZKQM8BxtvNtSPjJEo0Xrz+w3g2e4FSKI=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0 h1:NXi4pNJWjAaiI56P1Rl8DC9A4jMNRE00WNBsDua5WRg= github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0 h1:NXi4pNJWjAaiI56P1Rl8DC9A4jMNRE00WNBsDua5WRg=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0/go.mod h1:L3ZT0N/vBsw77mOAawXmRnREpEjcHd2v5Hzf7AkIH8M= github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0/go.mod h1:L3ZT0N/vBsw77mOAawXmRnREpEjcHd2v5Hzf7AkIH8M=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 h1:y2+VQzC6Zh2ojtV2LoC0MNwHWc6qXv/j2vrQtlftkdA= 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.9.11/go.mod h1:iV4q2hsqtNECrfmlXyord9u4zyuFEJX9eLgLpSPzWA8= 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.1.26 h1:CeuSeq/8FnYpPtnuIeLQEEvDv9zUjneuYi8EghMBdwQ= 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.1.26/go.mod h1:2UqAAwMUXKeRkAHIlDJqvMVgOWkUi/AUXPk/YIe+Dg4= 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.9.25/go.mod h1:/95IA+0lMnzW6XzqYJRpjjsAbKEORVeO0anQqjd2CNU= 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.9.27 h1:0iKliEXAcCa2qVtRs7Ot5hItA2MsufrphbRFlz1Owxo= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.33/go.mod h1:fABTUmOrAgAalG2i9WJpjBvlnk7UK8YmnYaxN+Q2CwE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.0 h1:e2ooMhpYGhDnBfSvIyusvAwX7KexuZaHbQY2Dyei7VU= 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/internal/s3shared v1.14.0/go.mod h1:bh2E0CXKZsQN+faiKVqC40vfNMAWheoULBCnEgO9K+8= 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.31.1 h1:PJH4I+qYjPXclKRbVCW47iYUvtXEh1u6YmDhn5J8VQE= 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/s3 v1.31.1/go.mod h1:ncltU6n4Nof5uJttDtcNQ537uNuwYqsZZQcpkd2/GUQ= 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/sso v1.12.6 h1:5V7DWLBd7wTELVz5bPpwzYy/sikk0gsgZfj40X+l5OI= github.com/aws/aws-sdk-go-v2/service/s3 v1.38.2 h1:v346f1h8sUBKXnEbrv43L37MTBlFHyKXQPIZHNAaghA=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.6/go.mod h1:Y1VOmit/Fn6Tz1uFAeCO6Q7M2fmfXSCLeL5INVYsLuY= 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/ssooidc v1.14.6 h1:B8cauxOH1W1v7rd8RdI/MWnoR4Ze0wIHWrb90qczxj4= 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.14.6/go.mod h1:Lh/bc9XUf8CfOY6Jp5aIkQtN+j1mc+nExc+KXj9jx2s= 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/sts v1.18.7 h1:bWNgNdRko2x6gqa0blfATqAZKZokPIeM1vfmQt2pnvM= 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.18.7/go.mod h1:JuTnSoeePXmMVe9G8NcjjwgOKEfZ4cOjMuT2IBT/2eI= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.2/go.mod h1:ubDBBaDFs1GHijSOTi8ljppML15GLG0HxhILtbjNNYQ=
github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= github.com/aws/aws-sdk-go-v2/service/sts v1.21.2 h1:ympg1+Lnq33XLhcK/xTG4yZHPs1Oyxu+6DEWbl7qOzA=
github.com/aws/aws-sdk-go-v2/service/sts v1.21.2/go.mod h1:FQ/DQcOfESELfJi5ED+IPPAjI5xC6nxtSolVVB773jM=
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/aws/smithy-go v1.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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -57,7 +59,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
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.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -93,8 +94,8 @@ github.com/hashicorp/go-kms-wrapping/v2 v2.0.9/go.mod h1:NtMaPhqSlfQ72XWDD2g80o8
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.4.9 h1:ESiK220/qE0aGxWdzKIvRH69iLiuN/PjoLTm69RoWtU= github.com/hashicorp/go-plugin v1.4.9 h1:ESiK220/qE0aGxWdzKIvRH69iLiuN/PjoLTm69RoWtU=
github.com/hashicorp/go-secure-stdlib/awsutil v0.2.2 h1:kWg2vyKl7BRXrNxYziqDJ55n+vtOQ1QsGORjzoeB+uM= github.com/hashicorp/go-secure-stdlib/awsutilv2 v0.0.0-20230809054404-9029211590fd h1:46A3lfywoivyHBOXlXioGDSDo3UJj2aW2ncnSCSJ7bs=
github.com/hashicorp/go-secure-stdlib/awsutil v0.2.2/go.mod h1:oKHSQs4ivIfZ3fbXGQOop1XuDfdSb8RIsWTGaAanSfg= github.com/hashicorp/go-secure-stdlib/awsutilv2 v0.0.0-20230809054404-9029211590fd/go.mod h1:XnoCjGsUi+UKk0+0P5TIS2rGKdw7O9ehSJy+kIuffBY=
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/configutil/v2 v2.0.9 h1:YZ01/aiARPneDUryzjiPGvr3pmjCSC+1WBZKI1P7I7k= github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.9 h1:YZ01/aiARPneDUryzjiPGvr3pmjCSC+1WBZKI1P7I7k=
@@ -118,7 +119,6 @@ github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f h1:E87tDTVS5W65euzixn7clSzK66puSt1H4I5SC0EmHH4= github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f h1:E87tDTVS5W65euzixn7clSzK66puSt1H4I5SC0EmHH4=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 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 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 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
@@ -126,7 +126,6 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC
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=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -153,12 +152,10 @@ github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8oh
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.6.2 h1:aIihoIOHCiLZHxyoNQ+ABL4NKhFTgKLBdMLyEAh98m0= github.com/rogpeppe/go-internal v1.6.2 h1:aIihoIOHCiLZHxyoNQ+ABL4NKhFTgKLBdMLyEAh98m0=
github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
@@ -167,17 +164,12 @@ github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdk
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
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.1/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.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= 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/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/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
@@ -204,7 +196,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
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-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-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+40 -19
View File
@@ -9,7 +9,7 @@ import (
"github.com/aws/aws-sdk-go/aws/endpoints" "github.com/aws/aws-sdk-go/aws/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" awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
"google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/structpb"
) )
@@ -37,7 +37,7 @@ type CredentialAttributes struct {
// GetCredentialsConfig parses values out of a protobuf struct secrets and returns a // GetCredentialsConfig parses values out of a protobuf struct secrets and returns a
// CredentialsConfig used for configuring an AWS session. An error is returned if // CredentialsConfig used for configuring an AWS session. An error is returned if
// any unrecognized fields are found in the protobuf struct input. // any unrecognized fields are found in the protobuf struct input.
func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes, required bool) (*awsutil.CredentialsConfig, error) { func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes, required bool) (*awsutilv2.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 {
@@ -55,6 +55,10 @@ func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes,
} }
delete(unknownFields, ConstAccessKeyId) delete(unknownFields, ConstAccessKeyId)
if accessKey != "" && GetCredentialType(accessKey) != Static {
badFields[fmt.Sprintf("secrets.%s", ConstAccessKeyId)] = "requires long-term access key beginning with \"AKIA\" prefix"
}
secretKey, err := values.GetStringValue(secrets, ConstSecretAccessKey, required) secretKey, err := values.GetStringValue(secrets, ConstSecretAccessKey, required)
if err != nil { if err != nil {
badFields[fmt.Sprintf("secrets.%s", ConstSecretAccessKey)] = err.Error() badFields[fmt.Sprintf("secrets.%s", ConstSecretAccessKey)] = err.Error()
@@ -68,36 +72,53 @@ func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes,
badFields[fmt.Sprintf("secrets.%s", s)] = "unrecognized field" badFields[fmt.Sprintf("secrets.%s", s)] = "unrecognized field"
} }
opts := []awsutilv2.Option{}
// logic for parsing the credential type
// supported types:
// - user static credential
// - ec2 assume role dynamic credential
// - enviornment variables credential
switch {
// static credentials and dynamic credentials cannot be used together
case accessKey != "" && secretKey != "" && attrs.RoleArn != "":
badFields[fmt.Sprintf("secrets.%s", ConstAccessKeyId)] = "conflicts with role_arn value"
badFields[fmt.Sprintf("secrets.%s", ConstSecretAccessKey)] = "conflicts with role_arn value"
badFields[fmt.Sprintf("secrets.%s", ConstRoleArn)] = "conflicts with access_key_id and secret_access_key values"
// static credential is missing it's secret_access_key
case accessKey != "" && secretKey == "":
badFields[fmt.Sprintf("secrets.%s", ConstSecretAccessKey)] = "missing required value"
// static credential is missing it's access_key_id
case accessKey == "" && secretKey != "":
badFields[fmt.Sprintf("secrets.%s", ConstAccessKeyId)] = "missing required value"
// add static credentials
case accessKey != "" && secretKey != "":
opts = append(opts,
awsutilv2.WithAccessKey(accessKey),
awsutilv2.WithSecretKey(secretKey),
)
// add dynamic credentials
case attrs.RoleArn != "":
opts = append(opts, awsutilv2.WithRoleArn(attrs.RoleArn))
}
if len(badFields) > 0 { if len(badFields) > 0 {
return nil, errors.InvalidArgumentError("Error in the secrets provided", badFields) return nil, errors.InvalidArgumentError("Error in the secrets provided", badFields)
} }
opts := []awsutil.Option{
awsutil.WithEnvironmentCredentials(true),
}
if accessKey != "" && secretKey != "" {
opts = append(opts,
awsutil.WithAccessKey(accessKey),
awsutil.WithSecretKey(secretKey),
)
}
if attrs.Region != "" { if attrs.Region != "" {
opts = append(opts, awsutil.WithRegion(attrs.Region)) opts = append(opts, awsutilv2.WithRegion(attrs.Region))
}
if attrs.RoleArn != "" {
opts = append(opts, awsutil.WithRoleArn(attrs.RoleArn))
} }
if attrs.RoleExternalId != "" { if attrs.RoleExternalId != "" {
opts = append(opts, awsutil.WithRoleExternalId(attrs.RoleExternalId)) opts = append(opts, awsutilv2.WithRoleExternalId(attrs.RoleExternalId))
} }
if attrs.RoleSessionName != "" { if attrs.RoleSessionName != "" {
opts = append(opts, awsutil.WithRoleSessionName(attrs.RoleSessionName)) opts = append(opts, awsutilv2.WithRoleSessionName(attrs.RoleSessionName))
} }
if len(attrs.RoleTags) != 0 { if len(attrs.RoleTags) != 0 {
opts = append(opts, awsutil.WithRoleTags(attrs.RoleTags)) opts = append(opts, awsutilv2.WithRoleTags(attrs.RoleTags))
} }
return awsutil.NewCredentialsConfig(opts...) return awsutilv2.NewCredentialsConfig(opts...)
} }
// GetCredentialAttributes parses values out of a protobuf struct input and returns a // GetCredentialAttributes parses values out of a protobuf struct input and returns a
+29 -26
View File
@@ -6,7 +6,7 @@ package credential
import ( import (
"testing" "testing"
"github.com/hashicorp/go-secure-stdlib/awsutil" awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
@@ -114,31 +114,44 @@ func TestGetCredentialsConfig(t *testing.T) {
secrets *structpb.Struct secrets *structpb.Struct
required bool required bool
attrs *CredentialAttributes attrs *CredentialAttributes
expected *awsutil.CredentialsConfig expected *awsutilv2.CredentialsConfig
expectedErrContains string expectedErrContains string
}{ }{
{ {
name: "no secrets", name: "no credentials",
attrs: &CredentialAttributes{ attrs: &CredentialAttributes{
Region: "us-west-2", Region: "us-west-2",
}, },
expected: &awsutil.CredentialsConfig{ expected: &awsutilv2.CredentialsConfig{
Region: "us-west-2", Region: "us-west-2",
}, },
}, },
{ {
name: "with static credentials", name: "with invalid static credentials",
secrets: &structpb.Struct{ secrets: &structpb.Struct{
Fields: map[string]*structpb.Value{ Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue("foobar"), ConstAccessKeyId: structpb.NewStringValue("ASIAfoobar"),
ConstSecretAccessKey: structpb.NewStringValue("bazqux"), ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
}, },
}, },
attrs: &CredentialAttributes{ attrs: &CredentialAttributes{
Region: "us-west-2", Region: "us-west-2",
}, },
expected: &awsutil.CredentialsConfig{ expectedErrContains: `secrets.access_key_id: requires long-term access key beginning with "AKIA" prefix`,
AccessKey: "foobar", },
{
name: "with static credentials",
secrets: &structpb.Struct{
Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"),
ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
},
},
attrs: &CredentialAttributes{
Region: "us-west-2",
},
expected: &awsutilv2.CredentialsConfig{
AccessKey: "AKIAfoobar",
SecretKey: "bazqux", SecretKey: "bazqux",
Region: "us-west-2", Region: "us-west-2",
}, },
@@ -160,7 +173,7 @@ func TestGetCredentialsConfig(t *testing.T) {
name: "missing secret key", name: "missing secret key",
secrets: &structpb.Struct{ secrets: &structpb.Struct{
Fields: map[string]*structpb.Value{ Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue("foobar"), ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"),
}, },
}, },
required: true, required: true,
@@ -173,7 +186,7 @@ func TestGetCredentialsConfig(t *testing.T) {
name: "unknown fields", name: "unknown fields",
secrets: &structpb.Struct{ secrets: &structpb.Struct{
Fields: map[string]*structpb.Value{ Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue("foobar"), ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"),
ConstSecretAccessKey: structpb.NewStringValue("bazqux"), ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
"foo": structpb.NewBoolValue(true), "foo": structpb.NewBoolValue(true),
"bar": structpb.NewBoolValue(true), "bar": structpb.NewBoolValue(true),
@@ -188,7 +201,7 @@ func TestGetCredentialsConfig(t *testing.T) {
name: "valid ignore creds_last_rotated_time", name: "valid ignore creds_last_rotated_time",
secrets: &structpb.Struct{ secrets: &structpb.Struct{
Fields: map[string]*structpb.Value{ Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue("foobar"), ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"),
ConstSecretAccessKey: structpb.NewStringValue("bazqux"), ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
ConstCredsLastRotatedTime: structpb.NewStringValue("2006-01-02T15:04:05+07:00"), ConstCredsLastRotatedTime: structpb.NewStringValue("2006-01-02T15:04:05+07:00"),
}, },
@@ -196,8 +209,8 @@ func TestGetCredentialsConfig(t *testing.T) {
attrs: &CredentialAttributes{ attrs: &CredentialAttributes{
Region: "us-west-2", Region: "us-west-2",
}, },
expected: &awsutil.CredentialsConfig{ expected: &awsutilv2.CredentialsConfig{
AccessKey: "foobar", AccessKey: "AKIAfoobar",
SecretKey: "bazqux", SecretKey: "bazqux",
Region: "us-west-2", Region: "us-west-2",
}, },
@@ -216,7 +229,7 @@ func TestGetCredentialsConfig(t *testing.T) {
"foo": "bar", "foo": "bar",
}, },
}, },
expected: &awsutil.CredentialsConfig{ expected: &awsutilv2.CredentialsConfig{
Region: "us-west-2", Region: "us-west-2",
RoleARN: "arn:aws:iam::123456789012:role/S3Access", RoleARN: "arn:aws:iam::123456789012:role/S3Access",
RoleExternalId: "1234567890", RoleExternalId: "1234567890",
@@ -230,7 +243,7 @@ func TestGetCredentialsConfig(t *testing.T) {
name: "with static credential & assume role", name: "with static credential & assume role",
secrets: &structpb.Struct{ secrets: &structpb.Struct{
Fields: map[string]*structpb.Value{ Fields: map[string]*structpb.Value{
ConstAccessKeyId: structpb.NewStringValue("foobar"), ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"),
ConstSecretAccessKey: structpb.NewStringValue("bazqux"), ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
}, },
}, },
@@ -243,17 +256,7 @@ func TestGetCredentialsConfig(t *testing.T) {
"foo": "bar", "foo": "bar",
}, },
}, },
expected: &awsutil.CredentialsConfig{ expectedErrContains: "secrets.access_key_id: conflicts with role_arn value, secrets.role_arn: conflicts with access_key_id and secret_access_key values, secrets.secret_access_key: conflicts with role_arn value",
AccessKey: "foobar",
SecretKey: "bazqux",
Region: "us-west-2",
RoleARN: "arn:aws:iam::123456789012:role/S3Access",
RoleExternalId: "1234567890",
RoleSessionName: "test-session",
RoleTags: map[string]string{
"foo": "bar",
},
},
}, },
} }
+68 -44
View File
@@ -4,19 +4,27 @@
package credential package credential
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
"time" "time"
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go/aws/session" iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/boundary-plugin-aws/internal/values" "github.com/hashicorp/boundary-plugin-aws/internal/values"
"github.com/hashicorp/go-secure-stdlib/awsutil" awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
"google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/structpb"
) )
type CredentialType int
const (
Unknown CredentialType = iota
Static
Dynamic
)
// rotationWaitTimeout controls the time we wait for credential rotation to // rotationWaitTimeout controls the time we wait for credential rotation to
// succeed. This is important to ensure that rotated credentials can be used // succeed. This is important to ensure that rotated credentials can be used
// right away. // right away.
@@ -27,18 +35,18 @@ type AwsCredentialPersistedStateOption func(s *AwsCredentialPersistedState) erro
// AwsCredentialPersistedState is the persisted state for the AWS credential. // AwsCredentialPersistedState is the persisted state for the AWS credential.
type AwsCredentialPersistedState struct { type AwsCredentialPersistedState struct {
// CredentialsConfig is the credential configuration for the AWS credential. // CredentialsConfig is the credential configuration for the AWS credential.
CredentialsConfig *awsutil.CredentialsConfig CredentialsConfig *awsutilv2.CredentialsConfig
// CredsLastRotatedTime is the last rotation of aws secrets for the AWS credential. // CredsLastRotatedTime is the last rotation of aws secrets for the AWS credential.
CredsLastRotatedTime time.Time CredsLastRotatedTime time.Time
// testOpts are options that should be used for testing only // testOpts are options that should be used for testing only
testOpts []awsutil.Option testOpts []awsutilv2.Option
} }
// WithStateTestOpts enables unit testing different edge cases // WithStateTestOpts enables unit testing different edge cases
// when using CredentialsConfig. This should never be used in // when using CredentialsConfig. This should never be used in
// production code. This should only be used in unit tests. // production code. This should only be used in unit tests.
func WithStateTestOpts(opts []awsutil.Option) AwsCredentialPersistedStateOption { func WithStateTestOpts(opts []awsutilv2.Option) AwsCredentialPersistedStateOption {
return func(s *AwsCredentialPersistedState) error { return func(s *AwsCredentialPersistedState) error {
s.testOpts = opts s.testOpts = opts
return nil return nil
@@ -46,7 +54,7 @@ func WithStateTestOpts(opts []awsutil.Option) AwsCredentialPersistedStateOption
} }
// WithCredentialsConfig sets the value for CredentialsConfig in the credential persisted state. // WithCredentialsConfig sets the value for CredentialsConfig in the credential persisted state.
func WithCredentialsConfig(x *awsutil.CredentialsConfig) AwsCredentialPersistedStateOption { func WithCredentialsConfig(x *awsutilv2.CredentialsConfig) AwsCredentialPersistedStateOption {
return func(s *AwsCredentialPersistedState) error { return func(s *AwsCredentialPersistedState) error {
if s.CredentialsConfig != nil { if s.CredentialsConfig != nil {
return errors.New("credentials config already set") return errors.New("credentials config already set")
@@ -85,11 +93,11 @@ func NewAwsCredentialPersistedState(opts ...AwsCredentialPersistedStateOption) (
// ValidateCreds takes the credentials configuration from the persisted state // ValidateCreds takes the credentials configuration from the persisted state
// and runs sts.GetCallerIdentity for the current credentials, which is done // and runs sts.GetCallerIdentity for the current credentials, which is done
// to check that the credentials are valid. // to check that the credentials are valid.
func (s *AwsCredentialPersistedState) ValidateCreds() error { func (s *AwsCredentialPersistedState) ValidateCreds(ctx context.Context) error {
if s.CredentialsConfig == nil { if s.CredentialsConfig == nil {
return errors.New("missing credentials config") return errors.New("missing credentials config")
} }
if _, err := s.CredentialsConfig.GetCallerIdentity(s.testOpts...); err != nil { if _, err := s.CredentialsConfig.GetCallerIdentity(ctx, s.testOpts...); err != nil {
return fmt.Errorf("error validating credentials: %w", err) return fmt.Errorf("error validating credentials: %w", err)
} }
return nil return nil
@@ -100,12 +108,15 @@ func (s *AwsCredentialPersistedState) ValidateCreds() error {
// written into the credentials config and the persisted state. On any error, the old credentials are not overwritten. // written into the credentials config and the persisted state. On any error, the old credentials are not overwritten.
// This ensures that any generated new secret key never leaves this function in case of an error, even though it will // This ensures that any generated new secret key never leaves this function in case of an error, even though it will
// still result in an extraneous access key existing. // still result in an extraneous access key existing.
func (s *AwsCredentialPersistedState) RotateCreds() error { func (s *AwsCredentialPersistedState) RotateCreds(ctx context.Context) error {
if s.CredentialsConfig == nil { if s.CredentialsConfig == nil {
return errors.New("missing credentials config") return errors.New("missing credentials config")
} }
if err := s.CredentialsConfig.RotateKeys(append([]awsutil.Option{ if GetCredentialType(s.CredentialsConfig.AccessKey) != Static {
awsutil.WithValidityCheckTimeout(rotationWaitTimeout), return errors.New("invalid credential type")
}
if err := s.CredentialsConfig.RotateKeys(ctx, append([]awsutilv2.Option{
awsutilv2.WithValidityCheckTimeout(rotationWaitTimeout),
}, s.testOpts...)...); err != nil { }, s.testOpts...)...); err != nil {
return fmt.Errorf("error rotating credentials: %w", err) return fmt.Errorf("error rotating credentials: %w", err)
} }
@@ -116,19 +127,21 @@ func (s *AwsCredentialPersistedState) RotateCreds() error {
// ReplaceCreds replaces the access key in the state with a new key. // ReplaceCreds replaces the access key in the state with a new key.
// If the existing key was rotated at any point in time, it is // If the existing key was rotated at any point in time, it is
// deleted first, otherwise it's left alone. // deleted first, otherwise it's left alone.
func (s *AwsCredentialPersistedState) ReplaceCreds(credentialsConfig *awsutil.CredentialsConfig) error { func (s *AwsCredentialPersistedState) ReplaceCreds(ctx context.Context, credentialsConfig *awsutilv2.CredentialsConfig) error {
if credentialsConfig == nil { if credentialsConfig == nil {
return errors.New("missing new credentials config") return errors.New("missing new credentials config")
} }
if s.CredentialsConfig == nil { if s.CredentialsConfig == nil {
return errors.New("missing credentials config") return errors.New("missing credentials config")
} }
if GetCredentialType(s.CredentialsConfig.AccessKey) != Static {
return errors.New("invalid credential type")
}
// Delete old/existing credentials. This action is only possible for static credential types.
// This is done with the same credentials to ensure that it has the proper permissions to do it.
if !s.CredsLastRotatedTime.IsZero() { if !s.CredsLastRotatedTime.IsZero() {
// Delete old/existing credentials. This is done with the same if err := s.DeleteCreds(ctx); err != nil {
// credentials to ensure that it has the proper permissions to do
// it.
if err := s.DeleteCreds(); err != nil {
return err return err
} }
} }
@@ -142,21 +155,22 @@ func (s *AwsCredentialPersistedState) ReplaceCreds(credentialsConfig *awsutil.Cr
// DeleteCreds deletes the credentials in the state. The access key // DeleteCreds deletes the credentials in the state. The access key
// ID, secret access key, and rotation time fields are zeroed out in // ID, secret access key, and rotation time fields are zeroed out in
// the state just to ensure that they cannot be re-used after. // the state just to ensure that they cannot be re-used after.
func (s *AwsCredentialPersistedState) DeleteCreds() error { func (s *AwsCredentialPersistedState) DeleteCreds(ctx context.Context) error {
if s.CredentialsConfig == nil { if s.CredentialsConfig == nil {
return errors.New("missing credentials config") return errors.New("missing credentials config")
} }
if GetCredentialType(s.CredentialsConfig.AccessKey) != Static {
return errors.New("invalid credential type")
}
if err := s.CredentialsConfig.DeleteAccessKey(s.CredentialsConfig.AccessKey, s.testOpts...); err != nil { if err := s.CredentialsConfig.DeleteAccessKey(ctx, s.CredentialsConfig.AccessKey, s.testOpts...); err != nil {
// Determine if the deletion error was due to a missing // Determine if the deletion error was due to a missing
// resource. If it was, just pass it. // resource. If it was, just pass it.
var awsErr awserr.Error var awsErr *iamTypes.NoSuchEntityException
if errors.As(err, &awsErr) { if errors.As(err, &awsErr) && awsErr.ErrorCode() == "NoSuchEntity" {
if awsErr.Code() == iam.ErrCodeNoSuchEntityException { s.CredentialsConfig = nil
s.CredentialsConfig = nil s.CredsLastRotatedTime = time.Time{}
s.CredsLastRotatedTime = time.Time{} return nil
return nil
}
} }
// Otherwise treat it as an actual error. // Otherwise treat it as an actual error.
@@ -168,10 +182,9 @@ func (s *AwsCredentialPersistedState) DeleteCreds() error {
return nil return nil
} }
// GetSession returns a configured AWS session for the credentials in // GenerateCredentialChain returns a AWS configuration for the credentials in the state.
// the state. func (s *AwsCredentialPersistedState) GenerateCredentialChain(ctx context.Context) (*aws.Config, error) {
func (s *AwsCredentialPersistedState) GetSession() (*session.Session, error) { return s.CredentialsConfig.GenerateCredentialChain(ctx, s.testOpts...)
return s.CredentialsConfig.GetSession(s.testOpts...)
} }
// ToMap returns a map of the credentials stored in the persisted state. // ToMap returns a map of the credentials stored in the persisted state.
@@ -224,29 +237,29 @@ func AwsCredentialPersistedStateFromProto(secrets *structpb.Struct, attrs *Crede
return nil, err return nil, err
} }
awsOpts := append([]awsutil.Option{}, s.testOpts...) awsOpts := append([]awsutilv2.Option{}, s.testOpts...)
if accessKeyId != "" && secretAccessKey != "" { if accessKeyId != "" && secretAccessKey != "" {
awsOpts = append(awsOpts, awsOpts = append(awsOpts,
awsutil.WithAccessKey(accessKeyId), awsutilv2.WithAccessKey(accessKeyId),
awsutil.WithSecretKey(secretAccessKey), awsutilv2.WithSecretKey(secretAccessKey),
) )
} }
if attrs.Region != "" { if attrs.Region != "" {
awsOpts = append(awsOpts, awsutil.WithRegion(attrs.Region)) awsOpts = append(awsOpts, awsutilv2.WithRegion(attrs.Region))
} }
if attrs.RoleArn != "" { if attrs.RoleArn != "" {
awsOpts = append(awsOpts, awsutil.WithRoleArn(attrs.RoleArn)) awsOpts = append(awsOpts, awsutilv2.WithRoleArn(attrs.RoleArn))
} }
if attrs.RoleExternalId != "" { if attrs.RoleExternalId != "" {
awsOpts = append(awsOpts, awsutil.WithRoleExternalId(attrs.RoleExternalId)) awsOpts = append(awsOpts, awsutilv2.WithRoleExternalId(attrs.RoleExternalId))
} }
if attrs.RoleSessionName != "" { if attrs.RoleSessionName != "" {
awsOpts = append(awsOpts, awsutil.WithRoleSessionName(attrs.RoleSessionName)) awsOpts = append(awsOpts, awsutilv2.WithRoleSessionName(attrs.RoleSessionName))
} }
if len(attrs.RoleTags) != 0 { if len(attrs.RoleTags) != 0 {
awsOpts = append(awsOpts, awsutil.WithRoleTags(attrs.RoleTags)) awsOpts = append(awsOpts, awsutilv2.WithRoleTags(attrs.RoleTags))
} }
credentialsConfig, err := awsutil.NewCredentialsConfig(awsOpts...) credentialsConfig, err := awsutilv2.NewCredentialsConfig(awsOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -256,18 +269,29 @@ func AwsCredentialPersistedStateFromProto(secrets *structpb.Struct, attrs *Crede
return s, nil return s, nil
} }
// HasDynamicCredentials returns true if the access key is a dynamic credential type. // GetCredentialType returns the credential type based on the given AWS AccessKey.
// //
// https://docs.aws.amazon.com/IAM/latest/UserGuide/security-creds.html#sec-access-keys-and-secret-access-keys // https://docs.aws.amazon.com/IAM/latest/UserGuide/security-creds.html#sec-access-keys-and-secret-access-keys
// Access key IDs beginning with ASIA are temporary credentials access keys that you create using AWS STS operations. // Access key IDs beginning with ASIA are temporary credentials access keys that you create using AWS STS operations.
//
// https://docs.aws.amazon.com/IAM/latest/UserGuide/security-creds.html#sec-access-keys-and-secret-access-keys
// Access key IDs beginning with AKIA are long-term access keys for an IAM user or an AWS account root user.
func GetCredentialType(accessKey string) CredentialType {
if strings.HasPrefix(accessKey, "ASIA") {
return Dynamic
}
if strings.HasPrefix(accessKey, "AKIA") {
return Static
}
return Unknown
}
// HasDynamicCredentials returns true if the access key is a dynamic credential type.
func HasDynamicCredentials(accessKey string) bool { func HasDynamicCredentials(accessKey string) bool {
return strings.HasPrefix(accessKey, "ASIA") return strings.HasPrefix(accessKey, "ASIA")
} }
// HasStaticCredentials returns true if the access key is a static credential type. // HasStaticCredentials returns true if the access key is a static credential type.
//
// https://docs.aws.amazon.com/IAM/latest/UserGuide/security-creds.html#sec-access-keys-and-secret-access-keys
// Access key IDs beginning with AKIA are long-term access keys for an IAM user or an AWS account root user.
func HasStaticCredentials(accessKey string) bool { func HasStaticCredentials(accessKey string) bool {
return strings.HasPrefix(accessKey, "AKIA") return strings.HasPrefix(accessKey, "AKIA")
} }
+176 -107
View File
@@ -4,15 +4,16 @@
package credential package credential
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"testing" "testing"
"time" "time"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go-v2/service/iam"
"github.com/aws/aws-sdk-go/service/iam" iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
"github.com/hashicorp/go-secure-stdlib/awsutil" awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/structpb"
) )
@@ -39,7 +40,7 @@ func TestNewAwsCredentialPersistedState(t *testing.T) {
name: "with credentials config", name: "with credentials config",
opts: []AwsCredentialPersistedStateOption{ opts: []AwsCredentialPersistedStateOption{
WithCredentialsConfig( WithCredentialsConfig(
&awsutil.CredentialsConfig{ &awsutilv2.CredentialsConfig{
AccessKey: "foobar", AccessKey: "foobar",
SecretKey: "bazqux", SecretKey: "bazqux",
Region: "us-west-2", Region: "us-west-2",
@@ -47,7 +48,7 @@ func TestNewAwsCredentialPersistedState(t *testing.T) {
), ),
}, },
expected: &AwsCredentialPersistedState{ expected: &AwsCredentialPersistedState{
CredentialsConfig: &awsutil.CredentialsConfig{ CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "foobar", AccessKey: "foobar",
SecretKey: "bazqux", SecretKey: "bazqux",
Region: "us-west-2", Region: "us-west-2",
@@ -66,8 +67,8 @@ func TestNewAwsCredentialPersistedState(t *testing.T) {
{ {
name: "double set credentials config", name: "double set credentials config",
opts: []AwsCredentialPersistedStateOption{ opts: []AwsCredentialPersistedStateOption{
WithCredentialsConfig(&awsutil.CredentialsConfig{}), WithCredentialsConfig(&awsutilv2.CredentialsConfig{}),
WithCredentialsConfig(&awsutil.CredentialsConfig{}), WithCredentialsConfig(&awsutilv2.CredentialsConfig{}),
}, },
expectedErr: "credentials config already set", expectedErr: "credentials config already set",
}, },
@@ -122,7 +123,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
Region: "us-west-2", Region: "us-west-2",
}, },
expected: &AwsCredentialPersistedState{ expected: &AwsCredentialPersistedState{
CredentialsConfig: &awsutil.CredentialsConfig{ CredentialsConfig: &awsutilv2.CredentialsConfig{
Region: "us-west-2", Region: "us-west-2",
}, },
}, },
@@ -134,7 +135,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
Region: "us-west-2", Region: "us-west-2",
}, },
expected: &AwsCredentialPersistedState{ expected: &AwsCredentialPersistedState{
CredentialsConfig: &awsutil.CredentialsConfig{ CredentialsConfig: &awsutilv2.CredentialsConfig{
Region: "us-west-2", Region: "us-west-2",
}, },
}, },
@@ -146,7 +147,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
Region: "us-west-2", Region: "us-west-2",
}, },
expected: &AwsCredentialPersistedState{ expected: &AwsCredentialPersistedState{
CredentialsConfig: &awsutil.CredentialsConfig{ CredentialsConfig: &awsutilv2.CredentialsConfig{
Region: "us-west-2", Region: "us-west-2",
}, },
}, },
@@ -163,7 +164,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
Region: "us-west-2", Region: "us-west-2",
}, },
expected: &AwsCredentialPersistedState{ expected: &AwsCredentialPersistedState{
CredentialsConfig: &awsutil.CredentialsConfig{ CredentialsConfig: &awsutilv2.CredentialsConfig{
Region: "us-west-2", Region: "us-west-2",
AccessKey: "foobar", AccessKey: "foobar",
SecretKey: "bazqux", SecretKey: "bazqux",
@@ -216,7 +217,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
Region: "us-west-2", Region: "us-west-2",
}, },
expected: &AwsCredentialPersistedState{ expected: &AwsCredentialPersistedState{
CredentialsConfig: &awsutil.CredentialsConfig{ CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "foobar", AccessKey: "foobar",
SecretKey: "bazqux", SecretKey: "bazqux",
Region: "us-west-2", Region: "us-west-2",
@@ -243,7 +244,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
Region: "us-west-2", Region: "us-west-2",
}, },
expected: &AwsCredentialPersistedState{ expected: &AwsCredentialPersistedState{
CredentialsConfig: &awsutil.CredentialsConfig{ CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "foobar", AccessKey: "foobar",
SecretKey: "bazqux", SecretKey: "bazqux",
Region: "us-west-2", Region: "us-west-2",
@@ -263,7 +264,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
}, },
}, },
expected: &AwsCredentialPersistedState{ expected: &AwsCredentialPersistedState{
CredentialsConfig: &awsutil.CredentialsConfig{ CredentialsConfig: &awsutilv2.CredentialsConfig{
Region: "us-west-2", Region: "us-west-2",
RoleARN: "arn:aws:iam::123456789012:role/S3Access", RoleARN: "arn:aws:iam::123456789012:role/S3Access",
RoleExternalId: "1234567890", RoleExternalId: "1234567890",
@@ -293,7 +294,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) {
}, },
}, },
expected: &AwsCredentialPersistedState{ expected: &AwsCredentialPersistedState{
CredentialsConfig: &awsutil.CredentialsConfig{ CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "foobar", AccessKey: "foobar",
SecretKey: "bazqux", SecretKey: "bazqux",
Region: "us-west-2", Region: "us-west-2",
@@ -351,15 +352,15 @@ func TestAwsCatalogPersistedState_ValidateCreds(t *testing.T) {
{ {
name: "validation error", name: "validation error",
in: &AwsCredentialPersistedState{ in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutil.CredentialsConfig{ CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "foobar", AccessKey: "foobar",
SecretKey: "bazqux", SecretKey: "bazqux",
Region: "us-west-2", Region: "us-west-2",
}, },
testOpts: []awsutil.Option{ testOpts: []awsutilv2.Option{
awsutil.WithSTSAPIFunc( awsutilv2.WithSTSAPIFunc(
awsutil.NewMockSTS( awsutilv2.NewMockSTS(
awsutil.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)), awsutilv2.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)),
), ),
), ),
}, },
@@ -369,12 +370,12 @@ func TestAwsCatalogPersistedState_ValidateCreds(t *testing.T) {
{ {
name: "good", name: "good",
in: &AwsCredentialPersistedState{ in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutil.CredentialsConfig{ CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "foobar", AccessKey: "foobar",
SecretKey: "bazqux", SecretKey: "bazqux",
Region: "us-west-2", Region: "us-west-2",
}, },
testOpts: []awsutil.Option{awsutil.WithSTSAPIFunc(awsutil.NewMockSTS())}, testOpts: []awsutilv2.Option{awsutilv2.WithSTSAPIFunc(awsutilv2.NewMockSTS())},
}, },
}, },
} }
@@ -383,7 +384,7 @@ func TestAwsCatalogPersistedState_ValidateCreds(t *testing.T) {
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
require := require.New(t) require := require.New(t)
err := tc.in.ValidateCreds() err := tc.in.ValidateCreds(context.Background())
if tc.expectedErr != "" { if tc.expectedErr != "" {
require.EqualError(err, tc.expectedErr) require.EqualError(err, tc.expectedErr)
return return
@@ -398,7 +399,7 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
cases := []struct { cases := []struct {
name string name string
in *AwsCredentialPersistedState in *AwsCredentialPersistedState
exepected *awsutil.CredentialsConfig exepected *awsutilv2.CredentialsConfig
expectedNonZeroRotatedTime bool expectedNonZeroRotatedTime bool
expectedErr string expectedErr string
}{ }{
@@ -407,50 +408,72 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
in: &AwsCredentialPersistedState{}, in: &AwsCredentialPersistedState{},
expectedErr: "missing credentials config", expectedErr: "missing credentials config",
}, },
{
name: "cannot delete dynamic credential type",
in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "ASIAfoobar",
SecretKey: "bazqux",
},
CredsLastRotatedTime: time.Now(),
},
expectedErr: "invalid credential type",
},
{
name: "cannot delete unknown credential type",
in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "DNEfoobar",
SecretKey: "bazqux",
},
CredsLastRotatedTime: time.Now(),
},
expectedErr: "invalid credential type",
},
{ {
name: "rotation error", name: "rotation error",
in: &AwsCredentialPersistedState{ in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutil.CredentialsConfig{ CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "foobar", AccessKey: "AKIAfoobar",
SecretKey: "bazqux", SecretKey: "bazqux",
Region: "us-west-2", Region: "us-west-2",
}, },
testOpts: []awsutil.Option{ testOpts: []awsutilv2.Option{
awsutil.WithIAMAPIFunc( awsutilv2.WithIAMAPIFunc(
awsutil.NewMockIAM( awsutilv2.NewMockIAM(
awsutil.WithGetUserError(errors.New(testGetUserErr)), awsutilv2.WithGetUserError(errors.New(testGetUserErr)),
), ),
), ),
}, },
}, },
expectedErr: fmt.Sprintf("error rotating credentials: error calling CreateAccessKey: error calling aws.GetUser: %s", testGetUserErr), expectedErr: fmt.Sprintf("error rotating credentials: error calling CreateAccessKey: error calling iam.GetUser: %s", testGetUserErr),
}, },
{ {
name: "good", name: "good",
in: &AwsCredentialPersistedState{ in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutil.CredentialsConfig{ CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "foobar", AccessKey: "AKIAfoobar",
SecretKey: "bazqux", SecretKey: "bazqux",
Region: "us-west-2", Region: "us-west-2",
}, },
testOpts: []awsutil.Option{ testOpts: []awsutilv2.Option{
awsutil.WithSTSAPIFunc( awsutilv2.WithSTSAPIFunc(
awsutil.NewMockSTS(), awsutilv2.NewMockSTS(),
), ),
awsutil.WithIAMAPIFunc( awsutilv2.WithIAMAPIFunc(
awsutil.NewMockIAM( awsutilv2.NewMockIAM(
awsutil.WithGetUserOutput( awsutilv2.WithGetUserOutput(
&iam.GetUserOutput{ &iam.GetUserOutput{
User: &iam.User{ User: &iamTypes.User{
Arn: aws.String("arn:aws:iam::123456789012:user/JohnDoe"), Arn: aws.String("arn:aws:iam::123456789012:user/JohnDoe"),
UserId: aws.String("AIDAJQABLZS4A3QDU576Q"), UserId: aws.String("AIDAJQABLZS4A3QDU576Q"),
UserName: aws.String("JohnDoe"), UserName: aws.String("JohnDoe"),
}, },
}, },
), ),
awsutil.WithCreateAccessKeyOutput( awsutilv2.WithCreateAccessKeyOutput(
&iam.CreateAccessKeyOutput{ &iam.CreateAccessKeyOutput{
AccessKey: &iam.AccessKey{ AccessKey: &iamTypes.AccessKey{
AccessKeyId: aws.String("one"), AccessKeyId: aws.String("one"),
SecretAccessKey: aws.String("two"), SecretAccessKey: aws.String("two"),
UserName: aws.String("JohnDoe"), UserName: aws.String("JohnDoe"),
@@ -461,7 +484,7 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
), ),
}, },
}, },
exepected: &awsutil.CredentialsConfig{ exepected: &awsutilv2.CredentialsConfig{
AccessKey: "one", AccessKey: "one",
SecretKey: "two", SecretKey: "two",
Region: "us-west-2", Region: "us-west-2",
@@ -474,7 +497,7 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) {
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
require := require.New(t) require := require.New(t)
err := tc.in.RotateCreds() err := tc.in.RotateCreds(context.Background())
if tc.expectedErr != "" { if tc.expectedErr != "" {
require.EqualError(err, tc.expectedErr) require.EqualError(err, tc.expectedErr)
return return
@@ -494,8 +517,8 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) {
cases := []struct { cases := []struct {
name string name string
in *AwsCredentialPersistedState in *AwsCredentialPersistedState
credentialConfig *awsutil.CredentialsConfig credentialConfig *awsutilv2.CredentialsConfig
expected *awsutil.CredentialsConfig expected *awsutilv2.CredentialsConfig
expectedDeleteCalled bool expectedDeleteCalled bool
expectedErr string expectedErr string
}{ }{
@@ -507,47 +530,71 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) {
{ {
name: "missing credentials config", name: "missing credentials config",
in: &AwsCredentialPersistedState{}, in: &AwsCredentialPersistedState{},
credentialConfig: &awsutil.CredentialsConfig{}, credentialConfig: &awsutilv2.CredentialsConfig{},
expectedErr: "missing credentials config", expectedErr: "missing credentials config",
}, },
{
name: "cannot delete dynamic credential type",
in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "ASIAfoobar",
SecretKey: "bazqux",
},
CredsLastRotatedTime: time.Now(),
},
credentialConfig: &awsutilv2.CredentialsConfig{},
expectedErr: "invalid credential type",
},
{
name: "cannot delete unknown credential type",
in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "DNEfoobar",
SecretKey: "bazqux",
},
CredsLastRotatedTime: time.Now(),
},
credentialConfig: &awsutilv2.CredentialsConfig{},
expectedErr: "invalid credential type",
},
{ {
name: "deletion error", name: "deletion error",
in: &AwsCredentialPersistedState{ in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutil.CredentialsConfig{ CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "foobar", AccessKey: "AKIAfoobar",
SecretKey: "bazqux", SecretKey: "bazqux",
}, },
CredsLastRotatedTime: time.Now(), CredsLastRotatedTime: time.Now(),
testOpts: []awsutil.Option{ testOpts: []awsutilv2.Option{
awsutil.WithIAMAPIFunc( awsutilv2.WithIAMAPIFunc(
awsutil.NewMockIAM( awsutilv2.NewMockIAM(
awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)), awsutilv2.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
), ),
), ),
}, },
}, },
credentialConfig: &awsutil.CredentialsConfig{}, credentialConfig: &awsutilv2.CredentialsConfig{},
expectedErr: fmt.Sprintf("error deleting old access key: %s", testDeleteAccessKeyErr), expectedErr: fmt.Sprintf("error deleting old access key: %s", testDeleteAccessKeyErr),
}, },
{ {
name: "good with delete of old rotated", name: "good with delete of old rotated",
in: &AwsCredentialPersistedState{ in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutil.CredentialsConfig{ CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "foobar", AccessKey: "AKIAfoobar",
SecretKey: "bazqux", SecretKey: "bazqux",
}, },
CredsLastRotatedTime: time.Now(), CredsLastRotatedTime: time.Now(),
testOpts: []awsutil.Option{ testOpts: []awsutilv2.Option{
awsutil.WithIAMAPIFunc( awsutilv2.WithIAMAPIFunc(
newTestMockIAM(state), newTestMockIAM(state),
), ),
}, },
}, },
credentialConfig: &awsutil.CredentialsConfig{ credentialConfig: &awsutilv2.CredentialsConfig{
AccessKey: "one", AccessKey: "one",
SecretKey: "two", SecretKey: "two",
}, },
expected: &awsutil.CredentialsConfig{ expected: &awsutilv2.CredentialsConfig{
AccessKey: "one", AccessKey: "one",
SecretKey: "two", SecretKey: "two",
}, },
@@ -556,21 +603,21 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) {
{ {
name: "good without delete of old rotated", name: "good without delete of old rotated",
in: &AwsCredentialPersistedState{ in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutil.CredentialsConfig{ CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "foobar", AccessKey: "AKIAfoobar",
SecretKey: "bazqux", SecretKey: "bazqux",
}, },
testOpts: []awsutil.Option{ testOpts: []awsutilv2.Option{
awsutil.WithIAMAPIFunc( awsutilv2.WithIAMAPIFunc(
newTestMockIAM(state), newTestMockIAM(state),
), ),
}, },
}, },
credentialConfig: &awsutil.CredentialsConfig{ credentialConfig: &awsutilv2.CredentialsConfig{
AccessKey: "one", AccessKey: "one",
SecretKey: "two", SecretKey: "two",
}, },
expected: &awsutil.CredentialsConfig{ expected: &awsutilv2.CredentialsConfig{
AccessKey: "one", AccessKey: "one",
SecretKey: "two", SecretKey: "two",
}, },
@@ -583,7 +630,7 @@ func TestAwsCatalogPersistedState_ReplaceCreds(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)
state.Reset() state.Reset()
err := tc.in.ReplaceCreds(tc.credentialConfig) err := tc.in.ReplaceCreds(context.Background(), tc.credentialConfig)
if tc.expectedErr != "" { if tc.expectedErr != "" {
require.EqualError(err, tc.expectedErr) require.EqualError(err, tc.expectedErr)
return return
@@ -610,16 +657,38 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
expectedErr: "missing credentials config", expectedErr: "missing credentials config",
}, },
{ {
name: "deletion error", name: "cannot delete dynamic credential type",
in: &AwsCredentialPersistedState{ in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutil.CredentialsConfig{ CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "foobar", AccessKey: "ASIAfoobar",
SecretKey: "bazqux", SecretKey: "bazqux",
}, },
testOpts: []awsutil.Option{ CredsLastRotatedTime: time.Now(),
awsutil.WithIAMAPIFunc( },
awsutil.NewMockIAM( expectedErr: "invalid credential type",
awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)), },
{
name: "cannot delete unknown credential type",
in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "DNEfoobar",
SecretKey: "bazqux",
},
CredsLastRotatedTime: time.Now(),
},
expectedErr: "invalid credential type",
},
{
name: "deletion error",
in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "AKIAfoobar",
SecretKey: "bazqux",
},
testOpts: []awsutilv2.Option{
awsutilv2.WithIAMAPIFunc(
awsutilv2.NewMockIAM(
awsutilv2.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
), ),
), ),
}, },
@@ -629,15 +698,15 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
{ {
name: "deletion error, but OK because key was just gone", name: "deletion error, but OK because key was just gone",
in: &AwsCredentialPersistedState{ in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutil.CredentialsConfig{ CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "foobar", AccessKey: "AKIAfoobar",
SecretKey: "bazqux", SecretKey: "bazqux",
}, },
testOpts: []awsutil.Option{ testOpts: []awsutilv2.Option{
awsutil.WithIAMAPIFunc( awsutilv2.WithIAMAPIFunc(
awsutil.NewMockIAM( awsutilv2.NewMockIAM(
awsutil.WithDeleteAccessKeyError( awsutilv2.WithDeleteAccessKeyError(
awserr.New(iam.ErrCodeNoSuchEntityException, "", nil), &iamTypes.NoSuchEntityException{},
), ),
), ),
), ),
@@ -647,13 +716,13 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
{ {
name: "good", name: "good",
in: &AwsCredentialPersistedState{ in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutil.CredentialsConfig{ CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "foobar", AccessKey: "AKIAfoobar",
SecretKey: "bazqux", SecretKey: "bazqux",
}, },
testOpts: []awsutil.Option{ testOpts: []awsutilv2.Option{
awsutil.WithIAMAPIFunc( awsutilv2.WithIAMAPIFunc(
awsutil.NewMockIAM(), awsutilv2.NewMockIAM(),
), ),
}, },
}, },
@@ -664,7 +733,7 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
require := require.New(t) require := require.New(t)
err := tc.in.DeleteCreds() err := tc.in.DeleteCreds(context.Background())
if tc.expectedErr != "" { if tc.expectedErr != "" {
require.EqualError(err, tc.expectedErr) require.EqualError(err, tc.expectedErr)
return return
@@ -677,7 +746,7 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) {
} }
} }
func TestAwsCatalogPersistedState_GetSession(t *testing.T) { func TestAwsCatalogPersistedState_GenerateCredentialChain(t *testing.T) {
require := require.New(t) require := require.New(t)
cases := []struct { cases := []struct {
name string name string
@@ -687,24 +756,24 @@ func TestAwsCatalogPersistedState_GetSession(t *testing.T) {
{ {
name: "error", name: "error",
in: &AwsCredentialPersistedState{ in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutil.CredentialsConfig{ CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "foobar", AccessKey: "foobar",
SecretKey: "bazqux", SecretKey: "bazqux",
}, },
testOpts: []awsutil.Option{awsutil.MockOptionErr(errors.New(testOptionErr))}, testOpts: []awsutilv2.Option{awsutilv2.MockOptionErr(errors.New(testOptionErr))},
}, },
expectedErr: fmt.Sprintf("error reading options in GetSession: %s", testOptionErr), expectedErr: fmt.Sprintf("error reading options in GenerateCredentialChain: %s", testOptionErr),
}, },
{ {
name: "static credentials", name: "static credentials",
in: &AwsCredentialPersistedState{ in: &AwsCredentialPersistedState{
CredentialsConfig: &awsutil.CredentialsConfig{ CredentialsConfig: &awsutilv2.CredentialsConfig{
AccessKey: "AKIA_foobar", AccessKey: "AKIA_foobar",
SecretKey: "bazqux", SecretKey: "bazqux",
}, },
testOpts: []awsutil.Option{ testOpts: []awsutilv2.Option{
awsutil.WithIAMAPIFunc( awsutilv2.WithIAMAPIFunc(
awsutil.NewMockIAM(), awsutilv2.NewMockIAM(),
), ),
}, },
}, },
@@ -712,22 +781,22 @@ func TestAwsCatalogPersistedState_GetSession(t *testing.T) {
{ {
name: "assume role", name: "assume role",
in: &AwsCredentialPersistedState{ in: &AwsCredentialPersistedState{
CredentialsConfig: func() *awsutil.CredentialsConfig { CredentialsConfig: func() *awsutilv2.CredentialsConfig {
config, err := awsutil.NewCredentialsConfig( config, err := awsutilv2.NewCredentialsConfig(
awsutil.WithRegion("us-west-2"), awsutilv2.WithRegion("us-west-2"),
awsutil.WithRoleArn("arn:aws:iam::123456789012:role/S3Access"), awsutilv2.WithRoleArn("arn:aws:iam::123456789012:role/S3Access"),
awsutil.WithRoleExternalId("1234567890"), awsutilv2.WithRoleExternalId("1234567890"),
awsutil.WithRoleSessionName("assume-role"), awsutilv2.WithRoleSessionName("assume-role"),
awsutil.WithRoleTags(map[string]string{ awsutilv2.WithRoleTags(map[string]string{
"foo": "bar", "foo": "bar",
}), }),
) )
require.NoError(err) require.NoError(err)
return config return config
}(), }(),
testOpts: []awsutil.Option{ testOpts: []awsutilv2.Option{
awsutil.WithIAMAPIFunc( awsutilv2.WithIAMAPIFunc(
awsutil.NewMockIAM(), awsutilv2.NewMockIAM(),
), ),
}, },
}, },
@@ -736,7 +805,7 @@ func TestAwsCatalogPersistedState_GetSession(t *testing.T) {
for _, tc := range cases { for _, tc := range cases {
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
_, err := tc.in.GetSession() _, err := tc.in.GenerateCredentialChain(context.Background())
if tc.expectedErr != "" { if tc.expectedErr != "" {
require.EqualError(err, tc.expectedErr) require.EqualError(err, tc.expectedErr)
return return
+12 -11
View File
@@ -4,10 +4,11 @@
package credential package credential
import ( import (
"github.com/aws/aws-sdk-go/aws/session" "context"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/aws/aws-sdk-go/service/iam/iamiface" "github.com/aws/aws-sdk-go-v2/aws"
"github.com/hashicorp/go-secure-stdlib/awsutil" "github.com/aws/aws-sdk-go-v2/service/iam"
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
"google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/structpb"
) )
@@ -27,25 +28,25 @@ func (s *testMockIAMState) Reset() {
} }
type testMockIAM struct { type testMockIAM struct {
iamiface.IAMAPI awsutilv2.IAMClient
State *testMockIAMState State *testMockIAMState
} }
func (m *testMockIAM) DeleteAccessKey(input *iam.DeleteAccessKeyInput) (*iam.DeleteAccessKeyOutput, error) { func (m *testMockIAM) DeleteAccessKey(ctx context.Context, input *iam.DeleteAccessKeyInput, opts ...func(*iam.Options)) (*iam.DeleteAccessKeyOutput, error) {
m.State.DeleteAccessKeyCalled = true m.State.DeleteAccessKeyCalled = true
return m.IAMAPI.DeleteAccessKey(input) return m.IAMClient.DeleteAccessKey(ctx, input, opts...)
} }
func newTestMockIAM(state *testMockIAMState, opts ...awsutil.MockIAMOption) awsutil.IAMAPIFunc { func newTestMockIAM(state *testMockIAMState, opts ...awsutilv2.MockIAMOption) awsutilv2.IAMAPIFunc {
return func(sess *session.Session) (iamiface.IAMAPI, error) { return func(awsConfig *aws.Config) (awsutilv2.IAMClient, error) {
m := &testMockIAM{ m := &testMockIAM{
State: state, State: state,
} }
f := awsutil.NewMockIAM(opts...) f := awsutilv2.NewMockIAM(opts...)
var err error var err error
m.IAMAPI, err = f(sess) m.IAMClient, err = f(awsConfig)
return m, err return m, err
} }
} }
+8 -20
View File
@@ -86,12 +86,12 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo
// Try to rotate static credentials // Try to rotate static credentials
if cred.HasStaticCredentials(credState.CredentialsConfig.AccessKey) { if cred.HasStaticCredentials(credState.CredentialsConfig.AccessKey) {
if !catalogAttributes.DisableCredentialRotation { if !catalogAttributes.DisableCredentialRotation {
if err := credState.RotateCreds(); err != nil { if err := credState.RotateCreds(ctx); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "error during credential rotation: %s", err) return nil, status.Errorf(codes.InvalidArgument, "error during credential rotation: %s", err)
} }
} else { } else {
// Simply validate if we aren't rotating. // Simply validate if we aren't rotating.
if err := credState.ValidateCreds(); err != nil { if err := credState.ValidateCreds(ctx); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "error during credential validation: %s", err) return nil, status.Errorf(codes.InvalidArgument, "error during credential validation: %s", err)
} }
} }
@@ -179,7 +179,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
// Replace the credentials. This checks the timestamp on the last // Replace the credentials. This checks the timestamp on the last
// rotation time as well and deletes the credentials if we are // rotation time as well and deletes the credentials if we are
// managing them (ie: if we've rotated them before). // managing them (ie: if we've rotated them before).
if err := credState.ReplaceCreds(catalogSecrets); err != nil { if err := credState.ReplaceCreds(ctx, catalogSecrets); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "error attempting to replace credentials: %s", err) return nil, status.Errorf(codes.InvalidArgument, "error attempting to replace credentials: %s", err)
} }
} }
@@ -187,7 +187,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
if !catalogAttributes.DisableCredentialRotation && credState.CredsLastRotatedTime.IsZero() { if !catalogAttributes.DisableCredentialRotation && credState.CredsLastRotatedTime.IsZero() {
// If we're enabling rotation now but didn't before, or have // If we're enabling rotation now but didn't before, or have
// freshly replaced credentials, we can rotate here. // freshly replaced credentials, we can rotate here.
if err := credState.RotateCreds(); err != nil { if err := credState.RotateCreds(ctx); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "error during credential rotation: %s", err) return nil, status.Errorf(codes.InvalidArgument, "error during credential rotation: %s", err)
} }
} }
@@ -249,7 +249,7 @@ func (p *HostPlugin) OnDeleteCatalog(ctx context.Context, req *pb.OnDeleteCatalo
// Delete old/existing credentials. This is done with the same // Delete old/existing credentials. This is done with the same
// credentials to ensure that it has the proper permissions to do // credentials to ensure that it has the proper permissions to do
// it. // it.
if err := credState.DeleteCreds(); err != nil { if err := credState.DeleteCreds(ctx); err != nil {
return nil, status.Errorf(codes.Aborted, "error removing rotated credentials during catalog deletion: %s", err) return nil, status.Errorf(codes.Aborted, "error removing rotated credentials during catalog deletion: %s", err)
} }
} }
@@ -332,11 +332,7 @@ func (p *HostPlugin) OnCreateSet(ctx context.Context, req *pb.OnCreateSetRequest
return nil, err return nil, err
} }
opts := []ec2Option{} ec2Client, err := catalogState.EC2Client(ctx)
if catalogAttributes.Region != "" {
opts = append(opts, WithRegion(catalogAttributes.Region))
}
ec2Client, err := catalogState.EC2Client(ctx, opts...)
if err != nil { if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "error getting EC2 client: %s", err) return nil, status.Errorf(codes.InvalidArgument, "error getting EC2 client: %s", err)
} }
@@ -409,11 +405,7 @@ func (p *HostPlugin) OnUpdateSet(ctx context.Context, req *pb.OnUpdateSetRequest
return nil, err return nil, err
} }
opts := []ec2Option{} ec2Client, err := catalogState.EC2Client(ctx)
if catalogAttributes.Region != "" {
opts = append(opts, WithRegion(catalogAttributes.Region))
}
ec2Client, err := catalogState.EC2Client(ctx, opts...)
if err != nil { if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "error getting EC2 client: %s", err) return nil, status.Errorf(codes.InvalidArgument, "error getting EC2 client: %s", err)
} }
@@ -516,11 +508,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
} }
} }
opts := []ec2Option{} ec2Client, err := catalogState.EC2Client(ctx)
if catalogAttributes.Region != "" {
opts = append(opts, WithRegion(catalogAttributes.Region))
}
ec2Client, err := catalogState.EC2Client(ctx, opts...)
if err != nil { if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "error getting EC2 client: %s", err) return nil, status.Errorf(codes.InvalidArgument, "error getting EC2 client: %s", err)
} }
+29 -29
View File
@@ -17,7 +17,7 @@ import (
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin" pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"github.com/hashicorp/go-secure-stdlib/awsutil" awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
@@ -145,15 +145,15 @@ func TestPluginOnCreateCatalogErr(t *testing.T) {
}, },
}, },
credOpts: []credential.AwsCredentialPersistedStateOption{ credOpts: []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutil.Option{ credential.WithStateTestOpts([]awsutilv2.Option{
awsutil.WithIAMAPIFunc( awsutilv2.WithIAMAPIFunc(
awsutil.NewMockIAM( awsutilv2.NewMockIAM(
awsutil.WithGetUserError(errors.New(testGetUserErr)), awsutilv2.WithGetUserError(errors.New(testGetUserErr)),
), ),
), ),
}), }),
}, },
expectedErrContains: fmt.Sprintf("error during credential rotation: error rotating credentials: error calling CreateAccessKey: error calling aws.GetUser: %s", testGetUserErr), expectedErrContains: fmt.Sprintf("error during credential rotation: error rotating credentials: error calling CreateAccessKey: error calling iam.GetUser: %s", testGetUserErr),
expectedErrCode: codes.InvalidArgument, expectedErrCode: codes.InvalidArgument,
}, },
{ {
@@ -176,10 +176,10 @@ func TestPluginOnCreateCatalogErr(t *testing.T) {
}, },
}, },
credOpts: []credential.AwsCredentialPersistedStateOption{ credOpts: []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutil.Option{ credential.WithStateTestOpts([]awsutilv2.Option{
awsutil.WithSTSAPIFunc( awsutilv2.WithSTSAPIFunc(
awsutil.NewMockSTS( awsutilv2.NewMockSTS(
awsutil.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)), awsutilv2.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)),
), ),
), ),
}), }),
@@ -370,10 +370,10 @@ func TestPluginOnUpdateCatalogErr(t *testing.T) {
}, },
}, },
credOpts: []credential.AwsCredentialPersistedStateOption{ credOpts: []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutil.Option{ credential.WithStateTestOpts([]awsutilv2.Option{
awsutil.WithIAMAPIFunc( awsutilv2.WithIAMAPIFunc(
awsutil.NewMockIAM( awsutilv2.NewMockIAM(
awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)), awsutilv2.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
), ),
), ),
}), }),
@@ -403,15 +403,15 @@ func TestPluginOnUpdateCatalogErr(t *testing.T) {
}, },
}, },
credOpts: []credential.AwsCredentialPersistedStateOption{ credOpts: []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutil.Option{ credential.WithStateTestOpts([]awsutilv2.Option{
awsutil.WithIAMAPIFunc( awsutilv2.WithIAMAPIFunc(
awsutil.NewMockIAM( awsutilv2.NewMockIAM(
awsutil.WithGetUserError(errors.New(testGetUserErr)), awsutilv2.WithGetUserError(errors.New(testGetUserErr)),
), ),
), ),
}), }),
}, },
expectedErrContains: fmt.Sprintf("error during credential rotation: error rotating credentials: error calling CreateAccessKey: error calling aws.GetUser: %s", testGetUserErr), expectedErrContains: fmt.Sprintf("error during credential rotation: error rotating credentials: error calling CreateAccessKey: error calling iam.GetUser: %s", testGetUserErr),
expectedErrCode: codes.InvalidArgument, expectedErrCode: codes.InvalidArgument,
}, },
} }
@@ -493,10 +493,10 @@ func TestPluginOnDeleteCatalogErr(t *testing.T) {
}, },
}, },
credOpts: []credential.AwsCredentialPersistedStateOption{ credOpts: []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutil.Option{ credential.WithStateTestOpts([]awsutilv2.Option{
awsutil.WithIAMAPIFunc( awsutilv2.WithIAMAPIFunc(
awsutil.NewMockIAM( awsutilv2.NewMockIAM(
awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)), awsutilv2.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
), ),
), ),
}), }),
@@ -729,8 +729,8 @@ func TestPluginOnCreateSetErr(t *testing.T) {
}, },
}, },
credOpts: []credential.AwsCredentialPersistedStateOption{ credOpts: []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutil.Option{ credential.WithStateTestOpts([]awsutilv2.Option{
awsutil.MockOptionErr(errors.New(testOptionErr)), awsutilv2.MockOptionErr(errors.New(testOptionErr)),
}), }),
}, },
expectedErrContains: fmt.Sprintf("error loading persisted state: error reading options in NewCredentialsConfig: %s", testOptionErr), expectedErrContains: fmt.Sprintf("error loading persisted state: error reading options in NewCredentialsConfig: %s", testOptionErr),
@@ -1098,8 +1098,8 @@ func TestPluginOnUpdateSetErr(t *testing.T) {
}, },
}, },
credOpts: []credential.AwsCredentialPersistedStateOption{ credOpts: []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutil.Option{ credential.WithStateTestOpts([]awsutilv2.Option{
awsutil.MockOptionErr(errors.New(testOptionErr)), awsutilv2.MockOptionErr(errors.New(testOptionErr)),
}), }),
}, },
expectedErrContains: fmt.Sprintf("error loading persisted state: error reading options in NewCredentialsConfig: %s", testOptionErr), expectedErrContains: fmt.Sprintf("error loading persisted state: error reading options in NewCredentialsConfig: %s", testOptionErr),
@@ -1458,8 +1458,8 @@ func TestPluginListHostsErr(t *testing.T) {
}, },
}, },
credOpts: []credential.AwsCredentialPersistedStateOption{ credOpts: []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutil.Option{ credential.WithStateTestOpts([]awsutilv2.Option{
awsutil.MockOptionErr(errors.New(testOptionErr)), awsutilv2.MockOptionErr(errors.New(testOptionErr)),
}), }),
}, },
expectedErrContains: fmt.Sprintf("error loading persisted state: error reading options in NewCredentialsConfig: %s", testOptionErr), expectedErrContains: fmt.Sprintf("error loading persisted state: error reading options in NewCredentialsConfig: %s", testOptionErr),
+7 -68
View File
@@ -12,8 +12,6 @@ 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/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/aws/aws-sdk-go-v2/service/ec2"
"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"
@@ -88,75 +86,16 @@ 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, opt ...ec2Option) (EC2API, error) { func (s *awsCatalogPersistedState) EC2Client(ctx context.Context) (EC2API, error) {
opts, err := getOpts(opt...) awsCfg, err := s.GenerateCredentialChain(ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("error parsing options when fetching EC2 client: %w", err) return nil, fmt.Errorf("error getting AWS configuration when fetching EC2 client: %w", err)
} }
if awsCfg == nil {
awsConfigOpts := []func(*config.LoadOptions) error{} return nil, fmt.Errorf("nil aws configuration")
sess, err := s.GetSession()
if err != nil {
return nil, fmt.Errorf("error getting AWS session when fetching EC2 client: %w", err)
} }
creds, err := sess.Config.Credentials.Get()
if err != nil {
return nil, fmt.Errorf("error getting AWS session when fetching EC2 client: %w", err)
}
customCredentials := config.WithCredentialsProvider(
credentials.NewStaticCredentialsProvider(creds.AccessKeyID, creds.SecretAccessKey, creds.SessionToken),
)
awsConfigOpts = append(awsConfigOpts, customCredentials)
if sess.Config.Region != nil {
awsConfigOpts = append(awsConfigOpts, config.WithRegion(*sess.Config.Region))
}
if opts.withRegion != "" {
awsConfigOpts = append(awsConfigOpts, config.WithRegion(opts.withRegion))
}
cfg, err := config.LoadDefaultConfig(ctx, awsConfigOpts...)
if err != nil {
return nil, fmt.Errorf("error loading aws configuration when fetching EC2 client: %w", err)
}
if s.testEC2APIFunc != nil { if s.testEC2APIFunc != nil {
return s.testEC2APIFunc(cfg) return s.testEC2APIFunc(*awsCfg)
}
cfg.HTTPClient = customClient
return ec2.NewFromConfig(cfg), 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 {
withRegion string
}
func getDefaultEC2Options() ec2Options {
return ec2Options{}
}
// WithRegion contains the region to use
func WithRegion(with string) ec2Option {
return func(o *ec2Options) error {
o.withRegion = with
return nil
} }
return ec2.NewFromConfig(*awsCfg), nil
} }
+3 -3
View File
@@ -8,14 +8,14 @@ import (
"testing" "testing"
"github.com/hashicorp/boundary-plugin-aws/internal/credential" "github.com/hashicorp/boundary-plugin-aws/internal/credential"
"github.com/hashicorp/go-secure-stdlib/awsutil" awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestAwsCatalogPersistedStateEC2Client(t *testing.T) { func TestAwsCatalogPersistedStateEC2Client(t *testing.T) {
require := require.New(t) require := require.New(t)
cred, err := credential.NewAwsCredentialPersistedState( cred, err := credential.NewAwsCredentialPersistedState(
credential.WithCredentialsConfig(&awsutil.CredentialsConfig{ credential.WithCredentialsConfig(&awsutilv2.CredentialsConfig{
Region: "us-west-2", Region: "us-west-2",
AccessKey: "foobar", AccessKey: "foobar",
SecretKey: "barfoo", SecretKey: "barfoo",
@@ -29,7 +29,7 @@ func TestAwsCatalogPersistedStateEC2Client(t *testing.T) {
) )
require.NoError(err) require.NoError(err)
clientRaw, err := state.EC2Client(context.Background(), WithRegion("us-west-2")) clientRaw, err := state.EC2Client(context.Background())
require.NoError(err) require.NoError(err)
require.NotNil(clientRaw) require.NotNil(clientRaw)
client, ok := clientRaw.(*testMockEC2) client, ok := clientRaw.(*testMockEC2)
+75 -63
View File
@@ -21,6 +21,7 @@ import (
cred "github.com/hashicorp/boundary-plugin-aws/internal/credential" cred "github.com/hashicorp/boundary-plugin-aws/internal/credential"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin" pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
@@ -39,6 +40,10 @@ 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
// testAwsUtilV2Opts are passed to awsutilv2 directly (currently used for
// GenerateCredentialChain).
testAwsUtilV2Opts []awsutilv2.Option
} }
// OnCreateStorageBucket is called when a storage bucket is created. // OnCreateStorageBucket is called when a storage bucket is created.
@@ -79,12 +84,12 @@ func (p *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCre
// Try to rotate static credentials // Try to rotate static credentials
if cred.HasStaticCredentials(credState.CredentialsConfig.AccessKey) { if cred.HasStaticCredentials(credState.CredentialsConfig.AccessKey) {
if !storageAttributes.DisableCredentialRotation { if !storageAttributes.DisableCredentialRotation {
if err := credState.RotateCreds(); err != nil { if err := credState.RotateCreds(ctx); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "error during credential rotation: %s", err) return nil, status.Errorf(codes.InvalidArgument, "error during credential rotation: %s", err)
} }
} else { } else {
// Simply validate if we aren't rotating. // Simply validate if we aren't rotating.
if err := credState.ValidateCreds(); err != nil { if err := credState.ValidateCreds(ctx); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "error during credential validation: %s", err) return nil, status.Errorf(codes.InvalidArgument, "error during credential validation: %s", err)
} }
} }
@@ -119,81 +124,100 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
// For storage buckets we only need to really validate what is // For storage buckets we only need to really validate what is
// currently in the new copy of the storage bucket data, so skip // currently in the new copy of the storage bucket data, so skip
// fetching the new copy to keep things a little less messy. // fetching the new copy to keep things a little less messy.
bucket := req.GetNewBucket() newBucket := req.GetNewBucket()
if bucket == nil { if newBucket == nil {
return nil, status.Error(codes.InvalidArgument, "new bucket is required") return nil, status.Error(codes.InvalidArgument, "new bucket is required")
} }
if bucket.BucketName == "" { if newBucket.BucketName == "" {
return nil, status.Error(codes.InvalidArgument, "new bucketName is required") return nil, status.Error(codes.InvalidArgument, "new bucketName is required")
} }
currentBucket := req.GetCurrentBucket()
var updateSecrets bool if currentBucket == nil {
secrets := bucket.GetSecrets() return nil, status.Error(codes.InvalidArgument, "current bucket is required")
if secrets != nil {
// We will be updating secrets this run, but what exactly that
// means will be determined later.
updateSecrets = true
} }
attrs := bucket.GetAttributes() oldAttrs := currentBucket.GetAttributes()
if attrs == nil { if oldAttrs == nil {
return nil, status.Error(codes.InvalidArgument, "current bucket attributes is required")
}
oldStorageAttributes, err := getStorageAttributes(oldAttrs)
if err != nil {
return nil, err
}
newAttrs := newBucket.GetAttributes()
if newAttrs == nil {
return nil, status.Error(codes.InvalidArgument, "new bucket attributes is required") return nil, status.Error(codes.InvalidArgument, "new bucket attributes is required")
} }
newStorageAttributes, err := getStorageAttributes(newAttrs)
storageAttributes, err := getStorageAttributes(attrs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Get the persisted data.
// NOTE: We might need to change this at a later time. I'm not too
// sure *exactly* what scenarios we might encounter that would
// ultimately mean that we would have to handle an empty or missing
// state in update, but what we are ultimately assuming here
// (implicitly through awsStoragePersistedStateFromProto) is that
// the state will exist and be populated. Personally I think this
// is fine and important, but this may change in the future.
credState, err := cred.AwsCredentialPersistedStateFromProto( credState, err := cred.AwsCredentialPersistedStateFromProto(
req.GetPersisted().GetData(), req.GetPersisted().GetData(),
storageAttributes.CredentialAttributes, oldStorageAttributes.CredentialAttributes,
p.testCredStateOpts...) p.testCredStateOpts...)
if err != nil { if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "error loading persisted state: %s", err) return nil, status.Errorf(codes.InvalidArgument, "error loading persisted state: %s", err)
} }
if cred.HasStaticCredentials(credState.CredentialsConfig.AccessKey) { // We will be updating credentials when one of two changes occur:
if storageAttributes.DisableCredentialRotation && !updateSecrets { // 1. the new bucket has secrets for static credentials, in which case we need to delete the old static credentials
// This is a validate check to make sure that we aren't disabling // 2. the new bucket has a different roleARN value for dynamic credentials
// rotation for credentials currently being managed by rotation. if newBucket.GetSecrets() != nil || newStorageAttributes.RoleArn != oldStorageAttributes.RoleArn {
// This is not allowed. updatedCredentials, err := cred.GetCredentialsConfig(newBucket.GetSecrets(), newStorageAttributes.CredentialAttributes, false)
if err != nil {
return nil, err
}
// Replace the existing credential state.
// This checks the timestamp on the last rotation time as well
// and deletes the credentials if we are managing them
// (ie: if we've rotated them before).
if err := credState.ReplaceCreds(ctx, updatedCredentials); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "error attempting to replace credentials: %s", err)
}
}
awsCfg, err := credState.CredentialsConfig.GenerateCredentialChain(ctx, p.testAwsUtilV2Opts...)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to generate aws credential chain: %s", err)
}
awsCredentials, err := awsCfg.Credentials.Retrieve(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to retrieve aws credentials: %s", err)
}
credentialType := cred.GetCredentialType(awsCredentials.AccessKeyID)
switch credentialType {
case cred.Static:
// This is a validate check to make sure that we aren't disabling
// rotation for credentials currently being managed by rotation.
// This is not allowed.
if newStorageAttributes.DisableCredentialRotation && newBucket.GetSecrets() == nil {
if !credState.CredsLastRotatedTime.IsZero() { if !credState.CredsLastRotatedTime.IsZero() {
return nil, status.Error(codes.FailedPrecondition, "cannot disable rotation for already-rotated credentials") return nil, status.Error(codes.FailedPrecondition, "cannot disable rotation for already-rotated credentials")
} }
} }
if updateSecrets { // If we're enabling rotation now but didn't before, or have
storageSecrets, err := cred.GetCredentialsConfig(secrets, storageAttributes.CredentialAttributes, false) // freshly replaced credentials, we can rotate here.
if err != nil { if !newStorageAttributes.DisableCredentialRotation && credState.CredsLastRotatedTime.IsZero() {
return nil, err if err := credState.RotateCreds(ctx); err != nil {
}
// Replace the credentials. This checks the timestamp on the last
// rotation time as well and deletes the credentials if we are
// managing them (ie: if we've rotated them before).
if err := credState.ReplaceCreds(storageSecrets); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "error attempting to replace credentials: %s", err)
}
}
if !storageAttributes.DisableCredentialRotation && credState.CredsLastRotatedTime.IsZero() {
// If we're enabling rotation now but didn't before, or have
// freshly replaced credentials, we can rotate here.
if err := credState.RotateCreds(); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "error during credential rotation: %s", err) return nil, status.Errorf(codes.InvalidArgument, "error during credential rotation: %s", err)
} }
} }
case cred.Dynamic:
// We cannot allow credential rotation for dynamic credentials
if !newStorageAttributes.DisableCredentialRotation {
return nil, status.Errorf(codes.InvalidArgument, "cannot enable credential rotation for dynamic credential type")
}
case cred.Unknown:
fallthrough
default:
return nil, status.Errorf(codes.InvalidArgument, "unknown credential type")
} }
storageState, err := newAwsStoragePersistedState( storageState, err := newAwsStoragePersistedState(
append([]awsStoragePersistedStateOption{ append([]awsStoragePersistedStateOption{
withCredentials(credState), withCredentials(credState),
@@ -204,7 +228,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
} }
// perform dry run to ensure we can interact with the bucket as expected. // perform dry run to ensure we can interact with the bucket as expected.
if err := dryRunValidation(ctx, storageState, storageAttributes, bucket); err != nil { if err := dryRunValidation(ctx, storageState, newStorageAttributes, newBucket); err != nil {
return nil, err return nil, err
} }
@@ -264,7 +288,7 @@ func (p *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDel
// Delete old/existing credentials. This is done with the same // Delete old/existing credentials. This is done with the same
// credentials to ensure that it has the proper permissions to do // credentials to ensure that it has the proper permissions to do
// it. // it.
if err := credState.DeleteCreds(); err != nil { if err := credState.DeleteCreds(ctx); err != nil {
return nil, status.Errorf(codes.Aborted, "error removing rotated credentials during storage bucket deletion: %s", err) return nil, status.Errorf(codes.Aborted, "error removing rotated credentials during storage bucket deletion: %s", err)
} }
} }
@@ -323,9 +347,6 @@ func (p *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReques
} }
opts := []s3Option{} opts := []s3Option{}
if storageAttributes.Region != "" {
opts = append(opts, WithRegion(storageAttributes.Region))
}
if storageAttributes.EndpointUrl != "" { if storageAttributes.EndpointUrl != "" {
opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl)) opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl))
} }
@@ -452,9 +473,6 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu
} }
opts := []s3Option{} opts := []s3Option{}
if storageAttributes.Region != "" {
opts = append(opts, WithRegion(storageAttributes.Region))
}
if storageAttributes.EndpointUrl != "" { if storageAttributes.EndpointUrl != "" {
opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl)) opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl))
} }
@@ -566,9 +584,6 @@ func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest)
} }
opts := []s3Option{} opts := []s3Option{}
if storageAttributes.Region != "" {
opts = append(opts, WithRegion(storageAttributes.Region))
}
if storageAttributes.EndpointUrl != "" { if storageAttributes.EndpointUrl != "" {
opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl)) opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl))
} }
@@ -632,9 +647,6 @@ func dryRunValidation(ctx context.Context, state *awsStoragePersistedState, attr
} }
opts := []s3Option{} opts := []s3Option{}
if attrs.Region != "" {
opts = append(opts, WithRegion(attrs.Region))
}
if attrs.EndpointUrl != "" { if attrs.EndpointUrl != "" {
opts = append(opts, WithEndpoint(attrs.EndpointUrl)) opts = append(opts, WithEndpoint(attrs.EndpointUrl))
} }
+212 -46
View File
@@ -17,12 +17,13 @@ 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"
"github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/boundary-plugin-aws/internal/credential" "github.com/hashicorp/boundary-plugin-aws/internal/credential"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin" pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"github.com/hashicorp/go-secure-stdlib/awsutil" awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
@@ -129,25 +130,25 @@ func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) {
}, },
}, },
credOpts: []credential.AwsCredentialPersistedStateOption{ credOpts: []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutil.Option{ credential.WithStateTestOpts([]awsutilv2.Option{
awsutil.WithIAMAPIFunc( awsutilv2.WithIAMAPIFunc(
awsutil.NewMockIAM( awsutilv2.NewMockIAM(
awsutil.WithGetUserError(errors.New(testGetUserErr)), awsutilv2.WithGetUserError(errors.New(testGetUserErr)),
), ),
), ),
}), }),
}, },
expectedErrContains: fmt.Sprintf("error during credential rotation: error rotating credentials: error calling CreateAccessKey: error calling aws.GetUser: %s", testGetUserErr), expectedErrContains: fmt.Sprintf("error during credential rotation: error rotating credentials: error calling CreateAccessKey: error calling iam.GetUser: %s", testGetUserErr),
expectedErrCode: codes.InvalidArgument, expectedErrCode: codes.InvalidArgument,
}, },
{ {
name: "validation error", name: "validation error",
req: validRequest(), req: validRequest(),
credOpts: []credential.AwsCredentialPersistedStateOption{ credOpts: []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutil.Option{ credential.WithStateTestOpts([]awsutilv2.Option{
awsutil.WithSTSAPIFunc( awsutilv2.WithSTSAPIFunc(
awsutil.NewMockSTS( awsutilv2.NewMockSTS(
awsutil.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)), awsutilv2.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)),
), ),
), ),
}), }),
@@ -279,24 +280,24 @@ func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) {
}, },
credOpts: []credential.AwsCredentialPersistedStateOption{ credOpts: []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts( credential.WithStateTestOpts(
[]awsutil.Option{ []awsutilv2.Option{
awsutil.WithSTSAPIFunc( awsutilv2.WithSTSAPIFunc(
awsutil.NewMockSTS(), awsutilv2.NewMockSTS(),
), ),
awsutil.WithIAMAPIFunc( awsutilv2.WithIAMAPIFunc(
awsutil.NewMockIAM( awsutilv2.NewMockIAM(
awsutil.WithGetUserOutput( awsutilv2.WithGetUserOutput(
&iam.GetUserOutput{ &iam.GetUserOutput{
User: &iam.User{ User: &iamTypes.User{
Arn: aws.String("arn:aws:iam::123456789012:user/JohnDoe"), Arn: aws.String("arn:aws:iam::123456789012:user/JohnDoe"),
UserId: aws.String("AIDAJQABLZS4A3QDU576Q"), UserId: aws.String("AIDAJQABLZS4A3QDU576Q"),
UserName: aws.String("JohnDoe"), UserName: aws.String("JohnDoe"),
}, },
}, },
), ),
awsutil.WithCreateAccessKeyOutput( awsutilv2.WithCreateAccessKeyOutput(
&iam.CreateAccessKeyOutput{ &iam.CreateAccessKeyOutput{
AccessKey: &iam.AccessKey{ AccessKey: &iamTypes.AccessKey{
AccessKeyId: aws.String("one"), AccessKeyId: aws.String("one"),
SecretAccessKey: aws.String("two"), SecretAccessKey: aws.String("two"),
UserName: aws.String("JohnDoe"), UserName: aws.String("JohnDoe"),
@@ -353,6 +354,10 @@ func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) {
} }
} }
// TODO: add tests for changing:
// roleARN values
// access/secret keys
// swithcing between static & dynamic credentials
func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) { func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
validRequest := func() *pb.OnUpdateStorageBucketRequest { validRequest := func() *pb.OnUpdateStorageBucketRequest {
return &pb.OnUpdateStorageBucketRequest{ return &pb.OnUpdateStorageBucketRequest{
@@ -365,6 +370,13 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
}, },
}, },
}, },
CurrentBucket: &storagebuckets.StorageBucket{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
},
},
},
Persisted: &storagebuckets.StorageBucketPersisted{ Persisted: &storagebuckets.StorageBucketPersisted{
Data: credential.MockStaticCredentialSecrets(), Data: credential.MockStaticCredentialSecrets(),
}, },
@@ -392,6 +404,27 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
expectedErrContains: "new bucketName is required", expectedErrContains: "new bucketName is required",
expectedErrCode: codes.InvalidArgument, expectedErrCode: codes.InvalidArgument,
}, },
{
name: "nil current bucket",
req: &pb.OnUpdateStorageBucketRequest{
NewBucket: &storagebuckets.StorageBucket{
BucketName: "foo",
},
},
expectedErrContains: "current bucket is required",
expectedErrCode: codes.InvalidArgument,
},
{
name: "nil current bucket attributes",
req: &pb.OnUpdateStorageBucketRequest{
NewBucket: &storagebuckets.StorageBucket{
BucketName: "foo",
},
CurrentBucket: &storagebuckets.StorageBucket{},
},
expectedErrContains: "current bucket attributes is required",
expectedErrCode: codes.InvalidArgument,
},
{ {
name: "nil new bucket attributes", name: "nil new bucket attributes",
req: &pb.OnUpdateStorageBucketRequest{ req: &pb.OnUpdateStorageBucketRequest{
@@ -399,6 +432,13 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
BucketName: "foo", BucketName: "foo",
Secrets: new(structpb.Struct), Secrets: new(structpb.Struct),
}, },
CurrentBucket: &storagebuckets.StorageBucket{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
},
},
},
}, },
expectedErrContains: "new bucket attributes is required", expectedErrContains: "new bucket attributes is required",
expectedErrCode: codes.InvalidArgument, expectedErrCode: codes.InvalidArgument,
@@ -411,6 +451,13 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
Secrets: new(structpb.Struct), Secrets: new(structpb.Struct),
Attributes: new(structpb.Struct), Attributes: new(structpb.Struct),
}, },
CurrentBucket: &storagebuckets.StorageBucket{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
},
},
},
}, },
expectedErrContains: "missing required value \"region\"", expectedErrContains: "missing required value \"region\"",
expectedErrCode: codes.InvalidArgument, expectedErrCode: codes.InvalidArgument,
@@ -426,6 +473,13 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
}, },
}, },
}, },
CurrentBucket: &storagebuckets.StorageBucket{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
},
},
},
}, },
expectedErrContains: "not a valid region: foobar", expectedErrContains: "not a valid region: foobar",
expectedErrCode: codes.InvalidArgument, expectedErrCode: codes.InvalidArgument,
@@ -441,6 +495,13 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
}, },
}, },
}, },
CurrentBucket: &storagebuckets.StorageBucket{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
},
},
},
Persisted: &storagebuckets.StorageBucketPersisted{ Persisted: &storagebuckets.StorageBucketPersisted{
Data: &structpb.Struct{ Data: &structpb.Struct{
Fields: map[string]*structpb.Value{ Fields: map[string]*structpb.Value{
@@ -471,6 +532,13 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
}, },
}, },
}, },
CurrentBucket: &storagebuckets.StorageBucket{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
},
},
},
Persisted: &storagebuckets.StorageBucketPersisted{ Persisted: &storagebuckets.StorageBucketPersisted{
Data: &structpb.Struct{ Data: &structpb.Struct{
Fields: map[string]*structpb.Value{ Fields: map[string]*structpb.Value{
@@ -502,6 +570,13 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
}, },
}, },
}, },
CurrentBucket: &storagebuckets.StorageBucket{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
},
},
},
Persisted: &storagebuckets.StorageBucketPersisted{ Persisted: &storagebuckets.StorageBucketPersisted{
Data: &structpb.Struct{ Data: &structpb.Struct{
Fields: map[string]*structpb.Value{ Fields: map[string]*structpb.Value{
@@ -513,10 +588,10 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
}, },
}, },
credOpts: []credential.AwsCredentialPersistedStateOption{ credOpts: []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutil.Option{ credential.WithStateTestOpts([]awsutilv2.Option{
awsutil.WithIAMAPIFunc( awsutilv2.WithIAMAPIFunc(
awsutil.NewMockIAM( awsutilv2.NewMockIAM(
awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)), awsutilv2.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
), ),
), ),
}), }),
@@ -535,6 +610,13 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
}, },
}, },
}, },
CurrentBucket: &storagebuckets.StorageBucket{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
},
},
},
Persisted: &storagebuckets.StorageBucketPersisted{ Persisted: &storagebuckets.StorageBucketPersisted{
Data: &structpb.Struct{ Data: &structpb.Struct{
Fields: map[string]*structpb.Value{ Fields: map[string]*structpb.Value{
@@ -545,15 +627,15 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
}, },
}, },
credOpts: []credential.AwsCredentialPersistedStateOption{ credOpts: []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutil.Option{ credential.WithStateTestOpts([]awsutilv2.Option{
awsutil.WithIAMAPIFunc( awsutilv2.WithIAMAPIFunc(
awsutil.NewMockIAM( awsutilv2.NewMockIAM(
awsutil.WithGetUserError(errors.New(testGetUserErr)), awsutilv2.WithGetUserError(errors.New(testGetUserErr)),
), ),
), ),
}), }),
}, },
expectedErrContains: fmt.Sprintf("error during credential rotation: error rotating credentials: error calling CreateAccessKey: error calling aws.GetUser: %s", testGetUserErr), expectedErrContains: fmt.Sprintf("error during credential rotation: error rotating credentials: error calling CreateAccessKey: error calling iam.GetUser: %s", testGetUserErr),
expectedErrCode: codes.InvalidArgument, expectedErrCode: codes.InvalidArgument,
}, },
{ {
@@ -659,6 +741,13 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
}, },
}, },
}, },
CurrentBucket: &storagebuckets.StorageBucket{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
},
},
},
Persisted: &storagebuckets.StorageBucketPersisted{ Persisted: &storagebuckets.StorageBucketPersisted{
Data: &structpb.Struct{ Data: &structpb.Struct{
Fields: map[string]*structpb.Value{ Fields: map[string]*structpb.Value{
@@ -669,24 +758,24 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
}, },
}, },
credOpts: []credential.AwsCredentialPersistedStateOption{ credOpts: []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutil.Option{ credential.WithStateTestOpts([]awsutilv2.Option{
awsutil.WithSTSAPIFunc( awsutilv2.WithSTSAPIFunc(
awsutil.NewMockSTS(), awsutilv2.NewMockSTS(),
), ),
awsutil.WithIAMAPIFunc( awsutilv2.WithIAMAPIFunc(
awsutil.NewMockIAM( awsutilv2.NewMockIAM(
awsutil.WithGetUserOutput( awsutilv2.WithGetUserOutput(
&iam.GetUserOutput{ &iam.GetUserOutput{
User: &iam.User{ User: &iamTypes.User{
Arn: aws.String("arn:aws:iam::123456789012:user/JohnDoe"), Arn: aws.String("arn:aws:iam::123456789012:user/JohnDoe"),
UserId: aws.String("AIDAJQABLZS4A3QDU576Q"), UserId: aws.String("AIDAJQABLZS4A3QDU576Q"),
UserName: aws.String("JohnDoe"), UserName: aws.String("JohnDoe"),
}, },
}, },
), ),
awsutil.WithCreateAccessKeyOutput( awsutilv2.WithCreateAccessKeyOutput(
&iam.CreateAccessKeyOutput{ &iam.CreateAccessKeyOutput{
AccessKey: &iam.AccessKey{ AccessKey: &iamTypes.AccessKey{
AccessKeyId: aws.String("AKIA_one"), AccessKeyId: aws.String("AKIA_one"),
SecretAccessKey: aws.String("two"), SecretAccessKey: aws.String("two"),
UserName: aws.String("JohnDoe"), UserName: aws.String("JohnDoe"),
@@ -760,6 +849,83 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) {
} }
} }
func TestStoragePlugin_OnUpdateStorageBucket_DynamicCredentials(t *testing.T) {
cases := []struct {
name string
req *pb.OnUpdateStorageBucketRequest
credOpts []credential.AwsCredentialPersistedStateOption
storageOpts []awsStoragePersistedStateOption
awsutilV2Opts []awsutilv2.Option
expectedErrContains string
expectedErrCode codes.Code
}{
{
name: "attempt to enable credential rotation",
req: &pb.OnUpdateStorageBucketRequest{
CurrentBucket: &storagebuckets.StorageBucket{
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
},
},
},
NewBucket: &storagebuckets.StorageBucket{
BucketName: "foo",
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(false),
},
},
},
Persisted: &storagebuckets.StorageBucketPersisted{
Data: credential.MockAssumeRoleAttributes("us-west-2", true),
},
},
awsutilV2Opts: []awsutilv2.Option{
awsutilv2.WithCredentialsProvider(
awsutilv2.NewMockCredentialsProvider(
awsutilv2.WithCredentials(
aws.Credentials{
AccessKeyID: "ASIA_one",
SecretAccessKey: "secret_key_123",
SessionToken: "session_token_123",
CanExpire: true,
Expires: time.Now().Add(time.Hour * 2),
},
),
),
),
},
expectedErrContains: "cannot enable credential rotation for dynamic credential type",
expectedErrCode: codes.InvalidArgument,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
p := &StoragePlugin{
testCredStateOpts: tc.credOpts,
testStorageStateOpts: tc.storageOpts,
testAwsUtilV2Opts: tc.awsutilV2Opts,
}
resp, err := p.OnUpdateStorageBucket(context.Background(), tc.req)
if tc.expectedErrContains != "" {
require.Contains(err.Error(), tc.expectedErrContains)
require.Equal(status.Code(err).String(), tc.expectedErrCode.String())
return
}
require.NoError(err)
require.NotNil(resp)
require.NotNil(resp.GetPersisted())
require.NotNil(resp.GetPersisted().GetData())
})
}
}
func TestStoragePlugin_OnDeleteStorageBucket(t *testing.T) { func TestStoragePlugin_OnDeleteStorageBucket(t *testing.T) {
cases := []struct { cases := []struct {
name string name string
@@ -858,10 +1024,10 @@ func TestStoragePlugin_OnDeleteStorageBucket(t *testing.T) {
}, },
}, },
credOpts: []credential.AwsCredentialPersistedStateOption{ credOpts: []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutil.Option{ credential.WithStateTestOpts([]awsutilv2.Option{
awsutil.WithIAMAPIFunc( awsutilv2.WithIAMAPIFunc(
awsutil.NewMockIAM( awsutilv2.NewMockIAM(
awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)), awsutilv2.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)),
), ),
), ),
}), }),
@@ -890,10 +1056,10 @@ func TestStoragePlugin_OnDeleteStorageBucket(t *testing.T) {
}, },
}, },
credOpts: []credential.AwsCredentialPersistedStateOption{ credOpts: []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutil.Option{ credential.WithStateTestOpts([]awsutilv2.Option{
awsutil.WithIAMAPIFunc( awsutilv2.WithIAMAPIFunc(
awsutil.NewMockIAM( awsutilv2.NewMockIAM(
awsutil.WithDeleteAccessKeyError(nil), awsutilv2.WithDeleteAccessKeyError(nil),
), ),
), ),
}), }),
+40 -53
View File
@@ -9,12 +9,12 @@ import (
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"net/url"
"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/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3"
smithyEndpoints "github.com/aws/smithy-go/endpoints"
"github.com/hashicorp/boundary-plugin-aws/internal/credential" "github.com/hashicorp/boundary-plugin-aws/internal/credential"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets"
"google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/structpb"
@@ -97,55 +97,23 @@ func (s *awsStoragePersistedState) S3Client(ctx context.Context, opt ...s3Option
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)
} }
awsCfg, err := s.GenerateCredentialChain(ctx)
awsConfigOpts := []func(*config.LoadOptions) error{}
sess, err := s.GetSession()
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting AWS session when fetching S3 client: %w", err) return nil, fmt.Errorf("error getting AWS configuration when fetching S3 client: %w", err)
} }
creds, err := sess.Config.Credentials.Get() if awsCfg == nil {
if err != nil { return nil, fmt.Errorf("nil aws configuration")
return nil, fmt.Errorf("error getting AWS session when fetching S3 client: %w", err)
}
customCredentials := config.WithCredentialsProvider(
credentials.NewStaticCredentialsProvider(creds.AccessKeyID, creds.SecretAccessKey, creds.SessionToken),
)
awsConfigOpts = append(awsConfigOpts, customCredentials)
if sess.Config.Region != nil {
awsConfigOpts = append(awsConfigOpts, config.WithRegion(*sess.Config.Region))
}
if opts.withRegion != "" {
awsConfigOpts = append(awsConfigOpts, config.WithRegion(opts.withRegion))
} }
var s3Opts []func(*s3.Options)
if opts.withEndpoint != "" { if opts.withEndpoint != "" {
customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) { s3Opts = append(s3Opts, s3.WithEndpointResolverV2(&endpointResolver{
if service == s3.ServiceID { endpoint: opts.withEndpoint,
if opts.withRegion != "" { }))
region = opts.withRegion
}
return aws.Endpoint{
PartitionID: "aws",
URL: opts.withEndpoint,
SigningRegion: region,
}, nil
}
// returning EndpointNotFoundError will allow the service to fallback to it's default resolution
return aws.Endpoint{}, &aws.EndpointNotFoundError{}
})
awsConfigOpts = append(awsConfigOpts, config.WithEndpointResolverWithOptions(customResolver))
} }
cfg, err := config.LoadDefaultConfig(ctx, awsConfigOpts...)
if err != nil {
return nil, fmt.Errorf("error loading aws configuration when fetching S3 client: %w", err)
}
if s.testS3APIFunc != nil { if s.testS3APIFunc != nil {
return s.testS3APIFunc(cfg) return s.testS3APIFunc(*awsCfg)
} }
return s3.NewFromConfig(*awsCfg, s3Opts...), nil
cfg.HTTPClient = customClient
return s3.NewFromConfig(cfg), nil
} }
// getOpts iterates the inbound s3Options and returns a struct // getOpts iterates the inbound s3Options and returns a struct
@@ -167,7 +135,6 @@ type s3Option func(*s3Options) error
// options = how options are represented // options = how options are represented
type s3Options struct { type s3Options struct {
withRegion string
withEndpoint string withEndpoint string
} }
@@ -175,14 +142,6 @@ func getDefaultS3Options() s3Options {
return s3Options{} return s3Options{}
} }
// WithRegion contains the region to use
func WithRegion(with string) s3Option {
return func(o *s3Options) error {
o.withRegion = with
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 {
@@ -190,3 +149,31 @@ func WithEndpoint(with string) s3Option {
return nil return nil
} }
} }
type endpointResolver struct {
endpoint string
}
func (e *endpointResolver) ResolveEndpoint(ctx context.Context, params s3.EndpointParameters) (resolver smithyEndpoints.Endpoint, err error) {
var uri *url.URL
resolver, err = s3.NewDefaultEndpointResolverV2().ResolveEndpoint(ctx, params)
if err != nil {
return
}
if e.endpoint == "" {
// TODO: not sure if we should set an error or ignore the missing endpoint value and continue with the default resolver
return
}
uri, err = url.Parse(e.endpoint)
if err != nil {
return
}
if uri == nil {
// TODO: not sure if we should set an error or ignore the missing uri value and continue with the default resolver
return
}
resolver = smithyEndpoints.Endpoint{
URI: *uri,
}
return
}
+11 -21
View File
@@ -7,43 +7,41 @@ import (
"context" "context"
"testing" "testing"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/hashicorp/boundary-plugin-aws/internal/credential" "github.com/hashicorp/boundary-plugin-aws/internal/credential"
"github.com/hashicorp/go-secure-stdlib/awsutil" awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestAwsStoragePersistedStateS3Client(t *testing.T) { func TestAwsStoragePersistedStateS3Client(t *testing.T) {
cases := []struct { cases := []struct {
name string name string
creds *awsutil.CredentialsConfig creds *awsutilv2.CredentialsConfig
opts []awsStoragePersistedStateOption opts []awsStoragePersistedStateOption
awsOpts []awsutil.Option awsOpts []awsutilv2.Option
s3Opts []s3Option s3Opts []s3Option
expectedRegion string expectedRegion string
expectedEndpoint string expectedEndpoint string
}{ }{
{ {
name: "static credentials", name: "static credentials",
creds: func() *awsutil.CredentialsConfig { creds: func() *awsutilv2.CredentialsConfig {
creds, err := awsutil.NewCredentialsConfig( creds, err := awsutilv2.NewCredentialsConfig(
awsutil.WithRegion("us-west-2"), awsutilv2.WithRegion("us-west-2"),
awsutil.WithAccessKey("foobar"), awsutilv2.WithAccessKey("foobar"),
awsutil.WithSecretKey("barfoo"), awsutilv2.WithSecretKey("barfoo"),
) )
require.NoError(t, err) require.NoError(t, err)
return creds return creds
}(), }(),
awsOpts: []awsutil.Option{ awsOpts: []awsutilv2.Option{
awsutil.WithIAMAPIFunc( awsutilv2.WithIAMAPIFunc(
awsutil.NewMockIAM(), awsutilv2.NewMockIAM(),
), ),
}, },
opts: []awsStoragePersistedStateOption{ opts: []awsStoragePersistedStateOption{
withTestS3APIFunc(newTestMockS3(nil)), withTestS3APIFunc(newTestMockS3(nil)),
}, },
s3Opts: []s3Option{ s3Opts: []s3Option{
WithRegion("us-west-2"),
WithEndpoint("0.0.0.0"), WithEndpoint("0.0.0.0"),
}, },
expectedRegion: "us-west-2", expectedRegion: "us-west-2",
@@ -73,14 +71,6 @@ func TestAwsStoragePersistedStateS3Client(t *testing.T) {
require.True(ok) require.True(ok)
require.NotNil(client) require.NotNil(client)
require.Equal(tc.expectedRegion, client.Region) require.Equal(tc.expectedRegion, client.Region)
if tc.expectedEndpoint != "" {
endpoint, err := client.Endpoint.ResolveEndpoint(s3.ServiceID, client.Region)
require.NoError(err)
require.Equal(tc.expectedEndpoint, endpoint.URL)
require.Equal(tc.expectedRegion, endpoint.SigningRegion)
require.Equal("aws", endpoint.PartitionID)
}
}) })
} }
} }
+11 -12
View File
@@ -13,11 +13,11 @@ import (
"github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go/service/sts" "github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/hashicorp/boundary-plugin-aws/internal/credential" "github.com/hashicorp/boundary-plugin-aws/internal/credential"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin" pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"github.com/hashicorp/go-secure-stdlib/awsutil" awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/structpb"
@@ -69,9 +69,8 @@ func (s *testMockS3State) Reset() {
type testMockS3 struct { type testMockS3 struct {
S3API S3API
State *testMockS3State State *testMockS3State
Region string Region string
Endpoint aws.EndpointResolverWithOptions
// mocked responses for getObject // mocked responses for getObject
GetObjectOutput *s3.GetObjectOutput GetObjectOutput *s3.GetObjectOutput
@@ -147,9 +146,9 @@ func newTestMockS3(state *testMockS3State, opts ...testMockS3Option) s3APIFunc {
if cfg.Region != "" { if cfg.Region != "" {
m.Region = cfg.Region m.Region = cfg.Region
} }
if cfg.EndpointResolverWithOptions != nil { // if cfg. != nil {
m.Endpoint = cfg.EndpointResolverWithOptions // m.Endpoint = cfg.EndpointResolverWithOptions
} // }
} }
return m, nil return m, nil
@@ -288,10 +287,10 @@ func deepCopyPutObjectRequest(v *pb.PutObjectRequest) *pb.PutObjectRequest {
func validSTSMock() []credential.AwsCredentialPersistedStateOption { func validSTSMock() []credential.AwsCredentialPersistedStateOption {
return []credential.AwsCredentialPersistedStateOption{ return []credential.AwsCredentialPersistedStateOption{
credential.WithStateTestOpts([]awsutil.Option{ credential.WithStateTestOpts([]awsutilv2.Option{
awsutil.WithSTSAPIFunc( awsutilv2.WithSTSAPIFunc(
awsutil.NewMockSTS( awsutilv2.NewMockSTS(
awsutil.WithGetCallerIdentityOutput(&sts.GetCallerIdentityOutput{ awsutilv2.WithGetCallerIdentityOutput(&sts.GetCallerIdentityOutput{
Account: aws.String("0123456789"), Account: aws.String("0123456789"),
Arn: aws.String("arn:aws:iam::0123456789:user/test"), Arn: aws.String("arn:aws:iam::0123456789:user/test"),
UserId: aws.String("test"), UserId: aws.String("test"),
+1 -1
View File
@@ -75,7 +75,7 @@ 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)
// Start the workflow now. Set up the host catalog. 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
// will assert this through the returned state. // will assert this through the returned state.
+13 -13
View File
@@ -13,10 +13,10 @@ import (
"testing" "testing"
"time" "time"
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/smithy-go"
"github.com/hashicorp/boundary-plugin-aws/internal/credential" "github.com/hashicorp/boundary-plugin-aws/internal/credential"
"github.com/hashicorp/boundary-plugin-aws/internal/values" "github.com/hashicorp/boundary-plugin-aws/internal/values"
"github.com/hashicorp/go-secure-stdlib/awsutil" awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil"
tfjson "github.com/hashicorp/terraform-json" tfjson "github.com/hashicorp/terraform-json"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/structpb"
@@ -280,9 +280,9 @@ func requireCredentialsInvalid(t *testing.T, accessKeyId, secretAccessKey string
t.Helper() t.Helper()
require := require.New(t) require := require.New(t)
c, err := awsutil.NewCredentialsConfig( c, err := awsutilv2.NewCredentialsConfig(
awsutil.WithAccessKey(accessKeyId), awsutilv2.WithAccessKey(accessKeyId),
awsutil.WithSecretKey(secretAccessKey), awsutilv2.WithSecretKey(secretAccessKey),
) )
require.NoError(err) require.NoError(err)
@@ -293,7 +293,7 @@ func requireCredentialsInvalid(t *testing.T, accessKeyId, secretAccessKey string
defer cancel() defer cancel()
waitErr: waitErr:
for { for {
_, err = c.GetCallerIdentity() _, err = c.GetCallerIdentity(context.Background())
if err != nil { if err != nil {
break break
} }
@@ -308,21 +308,21 @@ waitErr:
} }
require.NotNil(err) require.NotNil(err)
awsErr, ok := err.(awserr.Error) var ae smithy.APIError
require.True(ok) require.True(errors.As(err, &ae))
require.Equal("InvalidClientTokenId", awsErr.Code()) require.Equal("InvalidClientTokenId", ae.ErrorCode())
} }
func requireCredentialsValid(t *testing.T, accessKeyId, secretAccessKey string) { func requireCredentialsValid(t *testing.T, accessKeyId, secretAccessKey string) {
t.Helper() t.Helper()
require := require.New(t) require := require.New(t)
c, err := awsutil.NewCredentialsConfig( c, err := awsutilv2.NewCredentialsConfig(
awsutil.WithAccessKey(accessKeyId), awsutilv2.WithAccessKey(accessKeyId),
awsutil.WithSecretKey(secretAccessKey), awsutilv2.WithSecretKey(secretAccessKey),
) )
require.NoError(err) require.NoError(err)
_, err = c.GetCallerIdentity() _, err = c.GetCallerIdentity(context.Background())
require.NoError(err) require.NoError(err)
} }