diff --git a/go.mod b/go.mod index 1091aeb..7d1200a 100644 --- a/go.mod +++ b/go.mod @@ -4,37 +4,40 @@ go 1.20 require ( 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/config v1.18.19 - github.com/aws/aws-sdk-go-v2/credentials v1.13.18 + github.com/aws/aws-sdk-go-v2 v1.20.1 github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0 - github.com/aws/aws-sdk-go-v2/service/s3 v1.31.1 - github.com/aws/smithy-go v1.13.5 + github.com/aws/aws-sdk-go-v2/service/iam v1.22.2 + github.com/aws/aws-sdk-go-v2/service/s3 v1.38.2 + github.com/aws/aws-sdk-go-v2/service/sts v1.21.2 + github.com/aws/smithy-go v1.14.1 github.com/google/go-cmp v0.5.9 github.com/google/uuid v1.3.0 github.com/hashicorp/boundary/sdk v0.0.33 github.com/hashicorp/go-secure-stdlib/awsutil v0.2.2 github.com/hashicorp/terraform-json v0.14.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/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 ( - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.23 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.26 // 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/s3shared v1.14.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.12.6 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.18.7 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.12 // indirect + github.com/aws/aws-sdk-go-v2/config v1.18.33 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.13.32 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.8 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.38 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.32 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.39 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.13 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.33 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.32 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.1 // indirect + github.com/aws/aws-sdk-go-v2/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/fatih/color v1.14.1 // 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/pointerstructure v1.2.1 // 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/zclconf/go-cty v1.10.0 // indirect golang.org/x/crypto v0.8.0 // indirect diff --git a/go.sum b/go.sum index 08e5d0e..6b1bd20 100644 --- a/go.sum +++ b/go.sum @@ -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/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= 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/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/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs= -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/config v1.18.19 h1:AqFK6zFNtq4i1EYu+eC7lcKHYnZagMn6SW171la0bGw= -github.com/aws/aws-sdk-go-v2/config v1.18.19/go.mod h1:XvTmGMY8d52ougvakOv1RpiTLPz9dlG/OQHsKU/cMmY= -github.com/aws/aws-sdk-go-v2/credentials v1.13.18 h1:EQMdtHwz0ILTW1hoP+EwuWhwCG1hD6l3+RWFQABET4c= -github.com/aws/aws-sdk-go-v2/credentials v1.13.18/go.mod h1:vnwlwjIe+3XJPBYKu1et30ZPABG3VaXJYr8ryohpIyM= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1 h1:gt57MN3liKiyGopcqgNzJb2+d9MJaKT/q1OksHNXVE4= -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/internal/configsources v1.1.31/go.mod h1:QT0BqUvX1Bh2ABdTGnjqEjvjzrCfIniM9Sc8zn9Yndo= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 h1:kG5eQilShqmJbv11XL1VpyDbaEJzWxd4zRiCG30GSn4= +github.com/aws/aws-sdk-go-v2 v1.20.1 h1:rZBf5DWr7YGrnlTK4kgDQGn1ltqOg5orCYb/UhOFZkg= +github.com/aws/aws-sdk-go-v2 v1.20.1/go.mod h1:NU06lETsFm8fUC6ZjhgDpVBcGZTFQ6XM+LZWZxMI4ac= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.12 h1:lN6L3LrYHeZ6xCxaIYtoWCx4GMLk4nRknsh29OMSqHY= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.12/go.mod h1:TDCkEAkMTXxTs0oLBGBKpBZbk3NLh8EvAfF0Q3x8/0c= +github.com/aws/aws-sdk-go-v2/config v1.18.33 h1:JKcw5SFxFW/rpM4mOPjv0VQ11E2kxW13F3exWOy7VZU= +github.com/aws/aws-sdk-go-v2/config v1.18.33/go.mod h1:hXO/l9pgY3K5oZJldamP0pbZHdPqqk+4/maa7DSD3cA= +github.com/aws/aws-sdk-go-v2/credentials v1.13.32 h1:lIH1eKPcCY1ylR4B6PkBGRWMHO3aVenOKJHWiS4/G2w= +github.com/aws/aws-sdk-go-v2/credentials v1.13.32/go.mod h1:lL8U3v/Y79YRG69WlAho0OHIKUXCyFvSXaIvfo81sls= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.8 h1:DK/9C+UN/X+1+Wm8pqaDksQr2tSLzq+8X1/rI/ZxKEQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.8/go.mod h1:ce7BgLQfYr5hQFdy67oX2svto3ufGtm6oBvmsHScI1Q= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25/go.mod h1:zBHOPwhBc3FlQjQJE/D3IfPWiWaQmT06Vq9aNukDo0k= -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 h1:c8ed/T9T2K5I+h/JzmF5tpI46+OODQ74dzmdo+QnaMg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.38/go.mod h1:qggunOChCMu9ZF/UkAfhTz25+U2rLVb3ya0Ua6TTfCA= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32 h1:p5luUImdIqywn6JpQsW3tq5GNOxKmOnEpybzPx+d1lk= -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/v4a v1.0.23 h1:DWYZIsyqagnWL00f8M/SOr9fN063OEQWn9LLTbdYXsk= -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/endpoints/v2 v2.4.32 h1:hNeAAymUY5gu11WrrmFb3CVIp9Dar9hbo44yzzcQpzA= +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/ini v1.3.39 h1:fc0ukRAiP1syoSGZYu+DaE+FulSYhTiJ8WpVu5jElU4= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.39/go.mod h1:WLAW8PT7+JhjZfLSWe7WEJaJu0GNo0cKc2Zyo003RBs= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.1 h1:vUh7dBFNS3oFCtVv6CiYKh5hP9ls8+kIpKLeFruIBLk= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.1/go.mod h1:sFMeinkhj/SZKQM8BxtvNtSPjJEo0Xrz+w3g2e4FSKI= github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0 h1:NXi4pNJWjAaiI56P1Rl8DC9A4jMNRE00WNBsDua5WRg= github.com/aws/aws-sdk-go-v2/service/ec2 v1.99.0/go.mod h1:L3ZT0N/vBsw77mOAawXmRnREpEjcHd2v5Hzf7AkIH8M= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 h1:y2+VQzC6Zh2ojtV2LoC0MNwHWc6qXv/j2vrQtlftkdA= -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/internal/checksum v1.1.26 h1:CeuSeq/8FnYpPtnuIeLQEEvDv9zUjneuYi8EghMBdwQ= -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/presigned-url v1.9.25/go.mod h1:/95IA+0lMnzW6XzqYJRpjjsAbKEORVeO0anQqjd2CNU= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 h1:0iKliEXAcCa2qVtRs7Ot5hItA2MsufrphbRFlz1Owxo= +github.com/aws/aws-sdk-go-v2/service/iam v1.22.2 h1:DPFxx/6Zwes/MiadlDteVqDKov7yQ5v9vuwfhZuJm1s= +github.com/aws/aws-sdk-go-v2/service/iam v1.22.2/go.mod h1:cQTMNdo/Z5t1DDRsUnx0a2j6cPnytMBidUYZw2zks28= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.13 h1:iV/W5OMBys+66OeXJi/7xIRrKZNsu0ylsLGu+6nbmQE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.13/go.mod h1:ReJb6xYmtGyu9KoFtRreWegbN9dZqvZIIv4vWnhcsyI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.33 h1:QviNkc+vGSuEHx8P+pVNKOdWLXBPIwMFv7p0fphgE4U= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.33/go.mod h1:fABTUmOrAgAalG2i9WJpjBvlnk7UK8YmnYaxN+Q2CwE= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.0 h1:e2ooMhpYGhDnBfSvIyusvAwX7KexuZaHbQY2Dyei7VU= -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/s3 v1.31.1 h1:PJH4I+qYjPXclKRbVCW47iYUvtXEh1u6YmDhn5J8VQE= -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/sso v1.12.6 h1:5V7DWLBd7wTELVz5bPpwzYy/sikk0gsgZfj40X+l5OI= -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/ssooidc v1.14.6 h1:B8cauxOH1W1v7rd8RdI/MWnoR4Ze0wIHWrb90qczxj4= -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/sts v1.18.7 h1:bWNgNdRko2x6gqa0blfATqAZKZokPIeM1vfmQt2pnvM= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.7/go.mod h1:JuTnSoeePXmMVe9G8NcjjwgOKEfZ4cOjMuT2IBT/2eI= -github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= +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/presigned-url v1.9.32/go.mod h1:4jwAWKEkCR0anWk5+1RbfSg1R5Gzld7NLiuaq5bTR/Y= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.1 h1:PT6PBCycRwhpEW5hJnRiceCeoWJ+r3bdgXtV+VKG7Pk= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.1/go.mod h1:TqoxCLwT2nrxrBGA+z7t6OWM7LBkgRckK3gOjYE+7JA= +github.com/aws/aws-sdk-go-v2/service/s3 v1.38.2 h1:v346f1h8sUBKXnEbrv43L37MTBlFHyKXQPIZHNAaghA= +github.com/aws/aws-sdk-go-v2/service/s3 v1.38.2/go.mod h1:cwCATiyNrXK9P2FsWdZ89g9mpsYv2rhk0UA/KByl5fY= +github.com/aws/aws-sdk-go-v2/service/sso v1.13.2 h1:A2RlEMo4SJSwbNoUUgkxTAEMduAy/8wG3eB2b2lP4gY= +github.com/aws/aws-sdk-go-v2/service/sso v1.13.2/go.mod h1:ju+nNXUunfIFamXUIZQiICjnO/TPlOmWcYhZcSy7xaE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.2 h1:OJELEgyaT2kmaBGZ+myyZbTTLobfe3ox3FSh5eYK9Qs= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.2/go.mod h1:ubDBBaDFs1GHijSOTi8ljppML15GLG0HxhILtbjNNYQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.21.2 h1:ympg1+Lnq33XLhcK/xTG4yZHPs1Oyxu+6DEWbl7qOzA= +github.com/aws/aws-sdk-go-v2/service/sts v1.21.2/go.mod h1:FQ/DQcOfESELfJi5ED+IPPAjI5xC6nxtSolVVB773jM= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aws/smithy-go v1.14.1 h1:EFKMUmH/iHMqLiwoEDx2rRjRQpI1YCn5jTysoaDujFs= +github.com/aws/smithy-go v1.14.1/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= 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= @@ -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.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= 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/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 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/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 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/awsutil v0.2.2/go.mod h1:oKHSQs4ivIfZ3fbXGQOop1XuDfdSb8RIsWTGaAanSfg= +github.com/hashicorp/go-secure-stdlib/awsutilv2 v0.0.0-20230809054404-9029211590fd h1:46A3lfywoivyHBOXlXioGDSDo3UJj2aW2ncnSCSJ7bs= +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.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng= 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/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= 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/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 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.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 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/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/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/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 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/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.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.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.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.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= @@ -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-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-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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= diff --git a/internal/credential/attributes.go b/internal/credential/attributes.go index 689bf90..2bff15f 100644 --- a/internal/credential/attributes.go +++ b/internal/credential/attributes.go @@ -9,7 +9,7 @@ import ( "github.com/aws/aws-sdk-go/aws/endpoints" "github.com/hashicorp/boundary-plugin-aws/internal/errors" "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" ) @@ -37,7 +37,7 @@ type CredentialAttributes struct { // GetCredentialsConfig parses values out of a protobuf struct secrets and returns a // CredentialsConfig used for configuring an AWS session. An error is returned if // any unrecognized fields are found in the protobuf struct input. -func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes, required bool) (*awsutil.CredentialsConfig, error) { +func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes, required bool) (*awsutilv2.CredentialsConfig, error) { // initialize secrets if it is nil // secrets can be nil because static credentials are optional if secrets == nil { @@ -55,6 +55,10 @@ func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes, } 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) if err != nil { 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" } + 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 { 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 != "" { - opts = append(opts, awsutil.WithRegion(attrs.Region)) - } - if attrs.RoleArn != "" { - opts = append(opts, awsutil.WithRoleArn(attrs.RoleArn)) + opts = append(opts, awsutilv2.WithRegion(attrs.Region)) } if attrs.RoleExternalId != "" { - opts = append(opts, awsutil.WithRoleExternalId(attrs.RoleExternalId)) + opts = append(opts, awsutilv2.WithRoleExternalId(attrs.RoleExternalId)) } if attrs.RoleSessionName != "" { - opts = append(opts, awsutil.WithRoleSessionName(attrs.RoleSessionName)) + opts = append(opts, awsutilv2.WithRoleSessionName(attrs.RoleSessionName)) } 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 diff --git a/internal/credential/attributes_test.go b/internal/credential/attributes_test.go index 7bac81c..79bf050 100644 --- a/internal/credential/attributes_test.go +++ b/internal/credential/attributes_test.go @@ -6,7 +6,7 @@ package credential import ( "testing" - "github.com/hashicorp/go-secure-stdlib/awsutil" + awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil" "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -114,31 +114,44 @@ func TestGetCredentialsConfig(t *testing.T) { secrets *structpb.Struct required bool attrs *CredentialAttributes - expected *awsutil.CredentialsConfig + expected *awsutilv2.CredentialsConfig expectedErrContains string }{ { - name: "no secrets", + name: "no credentials", attrs: &CredentialAttributes{ Region: "us-west-2", }, - expected: &awsutil.CredentialsConfig{ + expected: &awsutilv2.CredentialsConfig{ Region: "us-west-2", }, }, { - name: "with static credentials", + name: "with invalid static credentials", secrets: &structpb.Struct{ Fields: map[string]*structpb.Value{ - ConstAccessKeyId: structpb.NewStringValue("foobar"), + ConstAccessKeyId: structpb.NewStringValue("ASIAfoobar"), ConstSecretAccessKey: structpb.NewStringValue("bazqux"), }, }, attrs: &CredentialAttributes{ Region: "us-west-2", }, - expected: &awsutil.CredentialsConfig{ - AccessKey: "foobar", + expectedErrContains: `secrets.access_key_id: requires long-term access key beginning with "AKIA" prefix`, + }, + { + 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", Region: "us-west-2", }, @@ -160,7 +173,7 @@ func TestGetCredentialsConfig(t *testing.T) { name: "missing secret key", secrets: &structpb.Struct{ Fields: map[string]*structpb.Value{ - ConstAccessKeyId: structpb.NewStringValue("foobar"), + ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"), }, }, required: true, @@ -173,7 +186,7 @@ func TestGetCredentialsConfig(t *testing.T) { name: "unknown fields", secrets: &structpb.Struct{ Fields: map[string]*structpb.Value{ - ConstAccessKeyId: structpb.NewStringValue("foobar"), + ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"), ConstSecretAccessKey: structpb.NewStringValue("bazqux"), "foo": structpb.NewBoolValue(true), "bar": structpb.NewBoolValue(true), @@ -188,7 +201,7 @@ func TestGetCredentialsConfig(t *testing.T) { name: "valid ignore creds_last_rotated_time", secrets: &structpb.Struct{ Fields: map[string]*structpb.Value{ - ConstAccessKeyId: structpb.NewStringValue("foobar"), + ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"), ConstSecretAccessKey: structpb.NewStringValue("bazqux"), ConstCredsLastRotatedTime: structpb.NewStringValue("2006-01-02T15:04:05+07:00"), }, @@ -196,8 +209,8 @@ func TestGetCredentialsConfig(t *testing.T) { attrs: &CredentialAttributes{ Region: "us-west-2", }, - expected: &awsutil.CredentialsConfig{ - AccessKey: "foobar", + expected: &awsutilv2.CredentialsConfig{ + AccessKey: "AKIAfoobar", SecretKey: "bazqux", Region: "us-west-2", }, @@ -216,7 +229,7 @@ func TestGetCredentialsConfig(t *testing.T) { "foo": "bar", }, }, - expected: &awsutil.CredentialsConfig{ + expected: &awsutilv2.CredentialsConfig{ Region: "us-west-2", RoleARN: "arn:aws:iam::123456789012:role/S3Access", RoleExternalId: "1234567890", @@ -230,7 +243,7 @@ func TestGetCredentialsConfig(t *testing.T) { name: "with static credential & assume role", secrets: &structpb.Struct{ Fields: map[string]*structpb.Value{ - ConstAccessKeyId: structpb.NewStringValue("foobar"), + ConstAccessKeyId: structpb.NewStringValue("AKIAfoobar"), ConstSecretAccessKey: structpb.NewStringValue("bazqux"), }, }, @@ -243,17 +256,7 @@ func TestGetCredentialsConfig(t *testing.T) { "foo": "bar", }, }, - expected: &awsutil.CredentialsConfig{ - 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", - }, - }, + 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", }, } diff --git a/internal/credential/state.go b/internal/credential/state.go index d25ffd0..98acdd2 100644 --- a/internal/credential/state.go +++ b/internal/credential/state.go @@ -4,19 +4,27 @@ package credential import ( + "context" "errors" "fmt" "strings" "time" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/iam" + "github.com/aws/aws-sdk-go-v2/aws" + iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types" "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" ) +type CredentialType int + +const ( + Unknown CredentialType = iota + Static + Dynamic +) + // rotationWaitTimeout controls the time we wait for credential rotation to // succeed. This is important to ensure that rotated credentials can be used // right away. @@ -27,18 +35,18 @@ type AwsCredentialPersistedStateOption func(s *AwsCredentialPersistedState) erro // AwsCredentialPersistedState is the persisted state for the AWS credential. type AwsCredentialPersistedState struct { // 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 time.Time // testOpts are options that should be used for testing only - testOpts []awsutil.Option + testOpts []awsutilv2.Option } // WithStateTestOpts enables unit testing different edge cases // when using CredentialsConfig. This should never be used in // 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 { s.testOpts = opts return nil @@ -46,7 +54,7 @@ func WithStateTestOpts(opts []awsutil.Option) AwsCredentialPersistedStateOption } // 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 { if s.CredentialsConfig != nil { return errors.New("credentials config already set") @@ -85,11 +93,11 @@ func NewAwsCredentialPersistedState(opts ...AwsCredentialPersistedStateOption) ( // ValidateCreds takes the credentials configuration from the persisted state // and runs sts.GetCallerIdentity for the current credentials, which is done // to check that the credentials are valid. -func (s *AwsCredentialPersistedState) ValidateCreds() error { +func (s *AwsCredentialPersistedState) ValidateCreds(ctx context.Context) error { if s.CredentialsConfig == nil { 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 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. // 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. -func (s *AwsCredentialPersistedState) RotateCreds() error { +func (s *AwsCredentialPersistedState) RotateCreds(ctx context.Context) error { if s.CredentialsConfig == nil { return errors.New("missing credentials config") } - if err := s.CredentialsConfig.RotateKeys(append([]awsutil.Option{ - awsutil.WithValidityCheckTimeout(rotationWaitTimeout), + if GetCredentialType(s.CredentialsConfig.AccessKey) != Static { + return errors.New("invalid credential type") + } + if err := s.CredentialsConfig.RotateKeys(ctx, append([]awsutilv2.Option{ + awsutilv2.WithValidityCheckTimeout(rotationWaitTimeout), }, s.testOpts...)...); err != nil { 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. // If the existing key was rotated at any point in time, it is // 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 { return errors.New("missing new credentials config") } if s.CredentialsConfig == nil { 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() { - // Delete old/existing credentials. This is done with the same - // credentials to ensure that it has the proper permissions to do - // it. - if err := s.DeleteCreds(); err != nil { + if err := s.DeleteCreds(ctx); err != nil { return err } } @@ -142,21 +155,22 @@ func (s *AwsCredentialPersistedState) ReplaceCreds(credentialsConfig *awsutil.Cr // DeleteCreds deletes the credentials in the state. The access key // ID, secret access key, and rotation time fields are zeroed out in // 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 { 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 // resource. If it was, just pass it. - var awsErr awserr.Error - if errors.As(err, &awsErr) { - if awsErr.Code() == iam.ErrCodeNoSuchEntityException { - s.CredentialsConfig = nil - s.CredsLastRotatedTime = time.Time{} - return nil - } + var awsErr *iamTypes.NoSuchEntityException + if errors.As(err, &awsErr) && awsErr.ErrorCode() == "NoSuchEntity" { + s.CredentialsConfig = nil + s.CredsLastRotatedTime = time.Time{} + return nil } // Otherwise treat it as an actual error. @@ -168,10 +182,9 @@ func (s *AwsCredentialPersistedState) DeleteCreds() error { return nil } -// GetSession returns a configured AWS session for the credentials in -// the state. -func (s *AwsCredentialPersistedState) GetSession() (*session.Session, error) { - return s.CredentialsConfig.GetSession(s.testOpts...) +// GenerateCredentialChain returns a AWS configuration for the credentials in the state. +func (s *AwsCredentialPersistedState) GenerateCredentialChain(ctx context.Context) (*aws.Config, error) { + return s.CredentialsConfig.GenerateCredentialChain(ctx, s.testOpts...) } // 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 } - awsOpts := append([]awsutil.Option{}, s.testOpts...) + awsOpts := append([]awsutilv2.Option{}, s.testOpts...) if accessKeyId != "" && secretAccessKey != "" { awsOpts = append(awsOpts, - awsutil.WithAccessKey(accessKeyId), - awsutil.WithSecretKey(secretAccessKey), + awsutilv2.WithAccessKey(accessKeyId), + awsutilv2.WithSecretKey(secretAccessKey), ) } if attrs.Region != "" { - awsOpts = append(awsOpts, awsutil.WithRegion(attrs.Region)) + awsOpts = append(awsOpts, awsutilv2.WithRegion(attrs.Region)) } if attrs.RoleArn != "" { - awsOpts = append(awsOpts, awsutil.WithRoleArn(attrs.RoleArn)) + awsOpts = append(awsOpts, awsutilv2.WithRoleArn(attrs.RoleArn)) } if attrs.RoleExternalId != "" { - awsOpts = append(awsOpts, awsutil.WithRoleExternalId(attrs.RoleExternalId)) + awsOpts = append(awsOpts, awsutilv2.WithRoleExternalId(attrs.RoleExternalId)) } if attrs.RoleSessionName != "" { - awsOpts = append(awsOpts, awsutil.WithRoleSessionName(attrs.RoleSessionName)) + awsOpts = append(awsOpts, awsutilv2.WithRoleSessionName(attrs.RoleSessionName)) } 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 { return nil, err } @@ -256,18 +269,29 @@ func AwsCredentialPersistedStateFromProto(secrets *structpb.Struct, attrs *Crede 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 // 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 { return strings.HasPrefix(accessKey, "ASIA") } // 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 { return strings.HasPrefix(accessKey, "AKIA") } diff --git a/internal/credential/state_test.go b/internal/credential/state_test.go index 02467a9..4febd42 100644 --- a/internal/credential/state_test.go +++ b/internal/credential/state_test.go @@ -4,15 +4,16 @@ package credential import ( + "context" "errors" "fmt" "testing" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/service/iam" - "github.com/hashicorp/go-secure-stdlib/awsutil" + "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" + awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil" "github.com/stretchr/testify/require" "google.golang.org/protobuf/types/known/structpb" ) @@ -39,7 +40,7 @@ func TestNewAwsCredentialPersistedState(t *testing.T) { name: "with credentials config", opts: []AwsCredentialPersistedStateOption{ WithCredentialsConfig( - &awsutil.CredentialsConfig{ + &awsutilv2.CredentialsConfig{ AccessKey: "foobar", SecretKey: "bazqux", Region: "us-west-2", @@ -47,7 +48,7 @@ func TestNewAwsCredentialPersistedState(t *testing.T) { ), }, expected: &AwsCredentialPersistedState{ - CredentialsConfig: &awsutil.CredentialsConfig{ + CredentialsConfig: &awsutilv2.CredentialsConfig{ AccessKey: "foobar", SecretKey: "bazqux", Region: "us-west-2", @@ -66,8 +67,8 @@ func TestNewAwsCredentialPersistedState(t *testing.T) { { name: "double set credentials config", opts: []AwsCredentialPersistedStateOption{ - WithCredentialsConfig(&awsutil.CredentialsConfig{}), - WithCredentialsConfig(&awsutil.CredentialsConfig{}), + WithCredentialsConfig(&awsutilv2.CredentialsConfig{}), + WithCredentialsConfig(&awsutilv2.CredentialsConfig{}), }, expectedErr: "credentials config already set", }, @@ -122,7 +123,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) { Region: "us-west-2", }, expected: &AwsCredentialPersistedState{ - CredentialsConfig: &awsutil.CredentialsConfig{ + CredentialsConfig: &awsutilv2.CredentialsConfig{ Region: "us-west-2", }, }, @@ -134,7 +135,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) { Region: "us-west-2", }, expected: &AwsCredentialPersistedState{ - CredentialsConfig: &awsutil.CredentialsConfig{ + CredentialsConfig: &awsutilv2.CredentialsConfig{ Region: "us-west-2", }, }, @@ -146,7 +147,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) { Region: "us-west-2", }, expected: &AwsCredentialPersistedState{ - CredentialsConfig: &awsutil.CredentialsConfig{ + CredentialsConfig: &awsutilv2.CredentialsConfig{ Region: "us-west-2", }, }, @@ -163,7 +164,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) { Region: "us-west-2", }, expected: &AwsCredentialPersistedState{ - CredentialsConfig: &awsutil.CredentialsConfig{ + CredentialsConfig: &awsutilv2.CredentialsConfig{ Region: "us-west-2", AccessKey: "foobar", SecretKey: "bazqux", @@ -216,7 +217,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) { Region: "us-west-2", }, expected: &AwsCredentialPersistedState{ - CredentialsConfig: &awsutil.CredentialsConfig{ + CredentialsConfig: &awsutilv2.CredentialsConfig{ AccessKey: "foobar", SecretKey: "bazqux", Region: "us-west-2", @@ -243,7 +244,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) { Region: "us-west-2", }, expected: &AwsCredentialPersistedState{ - CredentialsConfig: &awsutil.CredentialsConfig{ + CredentialsConfig: &awsutilv2.CredentialsConfig{ AccessKey: "foobar", SecretKey: "bazqux", Region: "us-west-2", @@ -263,7 +264,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) { }, }, expected: &AwsCredentialPersistedState{ - CredentialsConfig: &awsutil.CredentialsConfig{ + CredentialsConfig: &awsutilv2.CredentialsConfig{ Region: "us-west-2", RoleARN: "arn:aws:iam::123456789012:role/S3Access", RoleExternalId: "1234567890", @@ -293,7 +294,7 @@ func TestAwsCredentialPersistedStateFromProto(t *testing.T) { }, }, expected: &AwsCredentialPersistedState{ - CredentialsConfig: &awsutil.CredentialsConfig{ + CredentialsConfig: &awsutilv2.CredentialsConfig{ AccessKey: "foobar", SecretKey: "bazqux", Region: "us-west-2", @@ -351,15 +352,15 @@ func TestAwsCatalogPersistedState_ValidateCreds(t *testing.T) { { name: "validation error", in: &AwsCredentialPersistedState{ - CredentialsConfig: &awsutil.CredentialsConfig{ + CredentialsConfig: &awsutilv2.CredentialsConfig{ AccessKey: "foobar", SecretKey: "bazqux", Region: "us-west-2", }, - testOpts: []awsutil.Option{ - awsutil.WithSTSAPIFunc( - awsutil.NewMockSTS( - awsutil.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)), + testOpts: []awsutilv2.Option{ + awsutilv2.WithSTSAPIFunc( + awsutilv2.NewMockSTS( + awsutilv2.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)), ), ), }, @@ -369,12 +370,12 @@ func TestAwsCatalogPersistedState_ValidateCreds(t *testing.T) { { name: "good", in: &AwsCredentialPersistedState{ - CredentialsConfig: &awsutil.CredentialsConfig{ + CredentialsConfig: &awsutilv2.CredentialsConfig{ AccessKey: "foobar", SecretKey: "bazqux", 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 t.Run(tc.name, func(t *testing.T) { require := require.New(t) - err := tc.in.ValidateCreds() + err := tc.in.ValidateCreds(context.Background()) if tc.expectedErr != "" { require.EqualError(err, tc.expectedErr) return @@ -398,7 +399,7 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) { cases := []struct { name string in *AwsCredentialPersistedState - exepected *awsutil.CredentialsConfig + exepected *awsutilv2.CredentialsConfig expectedNonZeroRotatedTime bool expectedErr string }{ @@ -407,50 +408,72 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) { in: &AwsCredentialPersistedState{}, 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", in: &AwsCredentialPersistedState{ - CredentialsConfig: &awsutil.CredentialsConfig{ - AccessKey: "foobar", + CredentialsConfig: &awsutilv2.CredentialsConfig{ + AccessKey: "AKIAfoobar", SecretKey: "bazqux", Region: "us-west-2", }, - testOpts: []awsutil.Option{ - awsutil.WithIAMAPIFunc( - awsutil.NewMockIAM( - awsutil.WithGetUserError(errors.New(testGetUserErr)), + testOpts: []awsutilv2.Option{ + awsutilv2.WithIAMAPIFunc( + awsutilv2.NewMockIAM( + 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", in: &AwsCredentialPersistedState{ - CredentialsConfig: &awsutil.CredentialsConfig{ - AccessKey: "foobar", + CredentialsConfig: &awsutilv2.CredentialsConfig{ + AccessKey: "AKIAfoobar", SecretKey: "bazqux", Region: "us-west-2", }, - testOpts: []awsutil.Option{ - awsutil.WithSTSAPIFunc( - awsutil.NewMockSTS(), + testOpts: []awsutilv2.Option{ + awsutilv2.WithSTSAPIFunc( + awsutilv2.NewMockSTS(), ), - awsutil.WithIAMAPIFunc( - awsutil.NewMockIAM( - awsutil.WithGetUserOutput( + awsutilv2.WithIAMAPIFunc( + awsutilv2.NewMockIAM( + awsutilv2.WithGetUserOutput( &iam.GetUserOutput{ - User: &iam.User{ + User: &iamTypes.User{ Arn: aws.String("arn:aws:iam::123456789012:user/JohnDoe"), UserId: aws.String("AIDAJQABLZS4A3QDU576Q"), UserName: aws.String("JohnDoe"), }, }, ), - awsutil.WithCreateAccessKeyOutput( + awsutilv2.WithCreateAccessKeyOutput( &iam.CreateAccessKeyOutput{ - AccessKey: &iam.AccessKey{ + AccessKey: &iamTypes.AccessKey{ AccessKeyId: aws.String("one"), SecretAccessKey: aws.String("two"), UserName: aws.String("JohnDoe"), @@ -461,7 +484,7 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) { ), }, }, - exepected: &awsutil.CredentialsConfig{ + exepected: &awsutilv2.CredentialsConfig{ AccessKey: "one", SecretKey: "two", Region: "us-west-2", @@ -474,7 +497,7 @@ func TestAwsCatalogPersistedState_RotateCreds(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { require := require.New(t) - err := tc.in.RotateCreds() + err := tc.in.RotateCreds(context.Background()) if tc.expectedErr != "" { require.EqualError(err, tc.expectedErr) return @@ -494,8 +517,8 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) { cases := []struct { name string in *AwsCredentialPersistedState - credentialConfig *awsutil.CredentialsConfig - expected *awsutil.CredentialsConfig + credentialConfig *awsutilv2.CredentialsConfig + expected *awsutilv2.CredentialsConfig expectedDeleteCalled bool expectedErr string }{ @@ -507,47 +530,71 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) { { name: "missing credentials config", in: &AwsCredentialPersistedState{}, - credentialConfig: &awsutil.CredentialsConfig{}, + credentialConfig: &awsutilv2.CredentialsConfig{}, 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", in: &AwsCredentialPersistedState{ - CredentialsConfig: &awsutil.CredentialsConfig{ - AccessKey: "foobar", + CredentialsConfig: &awsutilv2.CredentialsConfig{ + AccessKey: "AKIAfoobar", SecretKey: "bazqux", }, CredsLastRotatedTime: time.Now(), - testOpts: []awsutil.Option{ - awsutil.WithIAMAPIFunc( - awsutil.NewMockIAM( - awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)), + testOpts: []awsutilv2.Option{ + awsutilv2.WithIAMAPIFunc( + awsutilv2.NewMockIAM( + awsutilv2.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)), ), ), }, }, - credentialConfig: &awsutil.CredentialsConfig{}, + credentialConfig: &awsutilv2.CredentialsConfig{}, expectedErr: fmt.Sprintf("error deleting old access key: %s", testDeleteAccessKeyErr), }, { name: "good with delete of old rotated", in: &AwsCredentialPersistedState{ - CredentialsConfig: &awsutil.CredentialsConfig{ - AccessKey: "foobar", + CredentialsConfig: &awsutilv2.CredentialsConfig{ + AccessKey: "AKIAfoobar", SecretKey: "bazqux", }, CredsLastRotatedTime: time.Now(), - testOpts: []awsutil.Option{ - awsutil.WithIAMAPIFunc( + testOpts: []awsutilv2.Option{ + awsutilv2.WithIAMAPIFunc( newTestMockIAM(state), ), }, }, - credentialConfig: &awsutil.CredentialsConfig{ + credentialConfig: &awsutilv2.CredentialsConfig{ AccessKey: "one", SecretKey: "two", }, - expected: &awsutil.CredentialsConfig{ + expected: &awsutilv2.CredentialsConfig{ AccessKey: "one", SecretKey: "two", }, @@ -556,21 +603,21 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) { { name: "good without delete of old rotated", in: &AwsCredentialPersistedState{ - CredentialsConfig: &awsutil.CredentialsConfig{ - AccessKey: "foobar", + CredentialsConfig: &awsutilv2.CredentialsConfig{ + AccessKey: "AKIAfoobar", SecretKey: "bazqux", }, - testOpts: []awsutil.Option{ - awsutil.WithIAMAPIFunc( + testOpts: []awsutilv2.Option{ + awsutilv2.WithIAMAPIFunc( newTestMockIAM(state), ), }, }, - credentialConfig: &awsutil.CredentialsConfig{ + credentialConfig: &awsutilv2.CredentialsConfig{ AccessKey: "one", SecretKey: "two", }, - expected: &awsutil.CredentialsConfig{ + expected: &awsutilv2.CredentialsConfig{ AccessKey: "one", SecretKey: "two", }, @@ -583,7 +630,7 @@ func TestAwsCatalogPersistedState_ReplaceCreds(t *testing.T) { t.Run(tc.name, func(t *testing.T) { require := require.New(t) state.Reset() - err := tc.in.ReplaceCreds(tc.credentialConfig) + err := tc.in.ReplaceCreds(context.Background(), tc.credentialConfig) if tc.expectedErr != "" { require.EqualError(err, tc.expectedErr) return @@ -610,16 +657,38 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) { expectedErr: "missing credentials config", }, { - name: "deletion error", + name: "cannot delete dynamic credential type", in: &AwsCredentialPersistedState{ - CredentialsConfig: &awsutil.CredentialsConfig{ - AccessKey: "foobar", + CredentialsConfig: &awsutilv2.CredentialsConfig{ + AccessKey: "ASIAfoobar", SecretKey: "bazqux", }, - testOpts: []awsutil.Option{ - awsutil.WithIAMAPIFunc( - awsutil.NewMockIAM( - awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)), + 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: "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", in: &AwsCredentialPersistedState{ - CredentialsConfig: &awsutil.CredentialsConfig{ - AccessKey: "foobar", + CredentialsConfig: &awsutilv2.CredentialsConfig{ + AccessKey: "AKIAfoobar", SecretKey: "bazqux", }, - testOpts: []awsutil.Option{ - awsutil.WithIAMAPIFunc( - awsutil.NewMockIAM( - awsutil.WithDeleteAccessKeyError( - awserr.New(iam.ErrCodeNoSuchEntityException, "", nil), + testOpts: []awsutilv2.Option{ + awsutilv2.WithIAMAPIFunc( + awsutilv2.NewMockIAM( + awsutilv2.WithDeleteAccessKeyError( + &iamTypes.NoSuchEntityException{}, ), ), ), @@ -647,13 +716,13 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) { { name: "good", in: &AwsCredentialPersistedState{ - CredentialsConfig: &awsutil.CredentialsConfig{ - AccessKey: "foobar", + CredentialsConfig: &awsutilv2.CredentialsConfig{ + AccessKey: "AKIAfoobar", SecretKey: "bazqux", }, - testOpts: []awsutil.Option{ - awsutil.WithIAMAPIFunc( - awsutil.NewMockIAM(), + testOpts: []awsutilv2.Option{ + awsutilv2.WithIAMAPIFunc( + awsutilv2.NewMockIAM(), ), }, }, @@ -664,7 +733,7 @@ func TestAwsCatalogPersistedState_DeleteCreds(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { require := require.New(t) - err := tc.in.DeleteCreds() + err := tc.in.DeleteCreds(context.Background()) if tc.expectedErr != "" { require.EqualError(err, tc.expectedErr) 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) cases := []struct { name string @@ -687,24 +756,24 @@ func TestAwsCatalogPersistedState_GetSession(t *testing.T) { { name: "error", in: &AwsCredentialPersistedState{ - CredentialsConfig: &awsutil.CredentialsConfig{ + CredentialsConfig: &awsutilv2.CredentialsConfig{ AccessKey: "foobar", 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", in: &AwsCredentialPersistedState{ - CredentialsConfig: &awsutil.CredentialsConfig{ + CredentialsConfig: &awsutilv2.CredentialsConfig{ AccessKey: "AKIA_foobar", SecretKey: "bazqux", }, - testOpts: []awsutil.Option{ - awsutil.WithIAMAPIFunc( - awsutil.NewMockIAM(), + testOpts: []awsutilv2.Option{ + awsutilv2.WithIAMAPIFunc( + awsutilv2.NewMockIAM(), ), }, }, @@ -712,22 +781,22 @@ func TestAwsCatalogPersistedState_GetSession(t *testing.T) { { name: "assume role", in: &AwsCredentialPersistedState{ - CredentialsConfig: func() *awsutil.CredentialsConfig { - config, err := awsutil.NewCredentialsConfig( - awsutil.WithRegion("us-west-2"), - awsutil.WithRoleArn("arn:aws:iam::123456789012:role/S3Access"), - awsutil.WithRoleExternalId("1234567890"), - awsutil.WithRoleSessionName("assume-role"), - awsutil.WithRoleTags(map[string]string{ + CredentialsConfig: func() *awsutilv2.CredentialsConfig { + config, err := awsutilv2.NewCredentialsConfig( + awsutilv2.WithRegion("us-west-2"), + awsutilv2.WithRoleArn("arn:aws:iam::123456789012:role/S3Access"), + awsutilv2.WithRoleExternalId("1234567890"), + awsutilv2.WithRoleSessionName("assume-role"), + awsutilv2.WithRoleTags(map[string]string{ "foo": "bar", }), ) require.NoError(err) return config }(), - testOpts: []awsutil.Option{ - awsutil.WithIAMAPIFunc( - awsutil.NewMockIAM(), + testOpts: []awsutilv2.Option{ + awsutilv2.WithIAMAPIFunc( + awsutilv2.NewMockIAM(), ), }, }, @@ -736,7 +805,7 @@ func TestAwsCatalogPersistedState_GetSession(t *testing.T) { for _, tc := range cases { tc := tc t.Run(tc.name, func(t *testing.T) { - _, err := tc.in.GetSession() + _, err := tc.in.GenerateCredentialChain(context.Background()) if tc.expectedErr != "" { require.EqualError(err, tc.expectedErr) return diff --git a/internal/credential/testing.go b/internal/credential/testing.go index d7b753c..0884c63 100644 --- a/internal/credential/testing.go +++ b/internal/credential/testing.go @@ -4,10 +4,11 @@ package credential import ( - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/iam" - "github.com/aws/aws-sdk-go/service/iam/iamiface" - "github.com/hashicorp/go-secure-stdlib/awsutil" + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/iam" + awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil" "google.golang.org/protobuf/types/known/structpb" ) @@ -27,25 +28,25 @@ func (s *testMockIAMState) Reset() { } type testMockIAM struct { - iamiface.IAMAPI + awsutilv2.IAMClient 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 - return m.IAMAPI.DeleteAccessKey(input) + return m.IAMClient.DeleteAccessKey(ctx, input, opts...) } -func newTestMockIAM(state *testMockIAMState, opts ...awsutil.MockIAMOption) awsutil.IAMAPIFunc { - return func(sess *session.Session) (iamiface.IAMAPI, error) { +func newTestMockIAM(state *testMockIAMState, opts ...awsutilv2.MockIAMOption) awsutilv2.IAMAPIFunc { + return func(awsConfig *aws.Config) (awsutilv2.IAMClient, error) { m := &testMockIAM{ State: state, } - f := awsutil.NewMockIAM(opts...) + f := awsutilv2.NewMockIAM(opts...) var err error - m.IAMAPI, err = f(sess) + m.IAMClient, err = f(awsConfig) return m, err } } diff --git a/plugin/service/host/plugin.go b/plugin/service/host/plugin.go index 406a985..6283917 100644 --- a/plugin/service/host/plugin.go +++ b/plugin/service/host/plugin.go @@ -86,12 +86,12 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo // Try to rotate static credentials if cred.HasStaticCredentials(credState.CredentialsConfig.AccessKey) { 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) } } else { // 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) } } @@ -179,7 +179,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo // 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(catalogSecrets); err != nil { + if err := credState.ReplaceCreds(ctx, catalogSecrets); err != nil { 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 we're enabling rotation now but didn't before, or have // 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) } } @@ -249,7 +249,7 @@ func (p *HostPlugin) OnDeleteCatalog(ctx context.Context, req *pb.OnDeleteCatalo // Delete old/existing credentials. This is done with the same // credentials to ensure that it has the proper permissions to do // 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) } } @@ -332,11 +332,7 @@ func (p *HostPlugin) OnCreateSet(ctx context.Context, req *pb.OnCreateSetRequest return nil, err } - opts := []ec2Option{} - if catalogAttributes.Region != "" { - opts = append(opts, WithRegion(catalogAttributes.Region)) - } - ec2Client, err := catalogState.EC2Client(ctx, opts...) + ec2Client, err := catalogState.EC2Client(ctx) if err != nil { 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 } - opts := []ec2Option{} - if catalogAttributes.Region != "" { - opts = append(opts, WithRegion(catalogAttributes.Region)) - } - ec2Client, err := catalogState.EC2Client(ctx, opts...) + ec2Client, err := catalogState.EC2Client(ctx) if err != nil { 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{} - if catalogAttributes.Region != "" { - opts = append(opts, WithRegion(catalogAttributes.Region)) - } - ec2Client, err := catalogState.EC2Client(ctx, opts...) + ec2Client, err := catalogState.EC2Client(ctx) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "error getting EC2 client: %s", err) } diff --git a/plugin/service/host/plugin_test.go b/plugin/service/host/plugin_test.go index 6d40581..66df874 100644 --- a/plugin/service/host/plugin_test.go +++ b/plugin/service/host/plugin_test.go @@ -17,7 +17,7 @@ import ( "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets" 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" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -145,15 +145,15 @@ func TestPluginOnCreateCatalogErr(t *testing.T) { }, }, credOpts: []credential.AwsCredentialPersistedStateOption{ - credential.WithStateTestOpts([]awsutil.Option{ - awsutil.WithIAMAPIFunc( - awsutil.NewMockIAM( - awsutil.WithGetUserError(errors.New(testGetUserErr)), + credential.WithStateTestOpts([]awsutilv2.Option{ + awsutilv2.WithIAMAPIFunc( + awsutilv2.NewMockIAM( + 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, }, { @@ -176,10 +176,10 @@ func TestPluginOnCreateCatalogErr(t *testing.T) { }, }, credOpts: []credential.AwsCredentialPersistedStateOption{ - credential.WithStateTestOpts([]awsutil.Option{ - awsutil.WithSTSAPIFunc( - awsutil.NewMockSTS( - awsutil.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)), + credential.WithStateTestOpts([]awsutilv2.Option{ + awsutilv2.WithSTSAPIFunc( + awsutilv2.NewMockSTS( + awsutilv2.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)), ), ), }), @@ -370,10 +370,10 @@ func TestPluginOnUpdateCatalogErr(t *testing.T) { }, }, credOpts: []credential.AwsCredentialPersistedStateOption{ - credential.WithStateTestOpts([]awsutil.Option{ - awsutil.WithIAMAPIFunc( - awsutil.NewMockIAM( - awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)), + credential.WithStateTestOpts([]awsutilv2.Option{ + awsutilv2.WithIAMAPIFunc( + awsutilv2.NewMockIAM( + awsutilv2.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)), ), ), }), @@ -403,15 +403,15 @@ func TestPluginOnUpdateCatalogErr(t *testing.T) { }, }, credOpts: []credential.AwsCredentialPersistedStateOption{ - credential.WithStateTestOpts([]awsutil.Option{ - awsutil.WithIAMAPIFunc( - awsutil.NewMockIAM( - awsutil.WithGetUserError(errors.New(testGetUserErr)), + credential.WithStateTestOpts([]awsutilv2.Option{ + awsutilv2.WithIAMAPIFunc( + awsutilv2.NewMockIAM( + 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, }, } @@ -493,10 +493,10 @@ func TestPluginOnDeleteCatalogErr(t *testing.T) { }, }, credOpts: []credential.AwsCredentialPersistedStateOption{ - credential.WithStateTestOpts([]awsutil.Option{ - awsutil.WithIAMAPIFunc( - awsutil.NewMockIAM( - awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)), + credential.WithStateTestOpts([]awsutilv2.Option{ + awsutilv2.WithIAMAPIFunc( + awsutilv2.NewMockIAM( + awsutilv2.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)), ), ), }), @@ -729,8 +729,8 @@ func TestPluginOnCreateSetErr(t *testing.T) { }, }, credOpts: []credential.AwsCredentialPersistedStateOption{ - credential.WithStateTestOpts([]awsutil.Option{ - awsutil.MockOptionErr(errors.New(testOptionErr)), + credential.WithStateTestOpts([]awsutilv2.Option{ + awsutilv2.MockOptionErr(errors.New(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{ - credential.WithStateTestOpts([]awsutil.Option{ - awsutil.MockOptionErr(errors.New(testOptionErr)), + credential.WithStateTestOpts([]awsutilv2.Option{ + awsutilv2.MockOptionErr(errors.New(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{ - credential.WithStateTestOpts([]awsutil.Option{ - awsutil.MockOptionErr(errors.New(testOptionErr)), + credential.WithStateTestOpts([]awsutilv2.Option{ + awsutilv2.MockOptionErr(errors.New(testOptionErr)), }), }, expectedErrContains: fmt.Sprintf("error loading persisted state: error reading options in NewCredentialsConfig: %s", testOptionErr), diff --git a/plugin/service/host/state.go b/plugin/service/host/state.go index 534a8a4..523e74d 100644 --- a/plugin/service/host/state.go +++ b/plugin/service/host/state.go @@ -12,8 +12,6 @@ import ( "time" "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/hashicorp/boundary-plugin-aws/internal/credential" 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 // information stored in the state. -func (s *awsCatalogPersistedState) EC2Client(ctx context.Context, opt ...ec2Option) (EC2API, error) { - opts, err := getOpts(opt...) +func (s *awsCatalogPersistedState) EC2Client(ctx context.Context) (EC2API, error) { + awsCfg, err := s.GenerateCredentialChain(ctx) 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) } - - awsConfigOpts := []func(*config.LoadOptions) error{} - sess, err := s.GetSession() - if err != nil { - return nil, fmt.Errorf("error getting AWS session when fetching EC2 client: %w", err) + if awsCfg == nil { + return nil, fmt.Errorf("nil aws configuration") } - 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 { - return s.testEC2APIFunc(cfg) - } - - 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 s.testEC2APIFunc(*awsCfg) } + return ec2.NewFromConfig(*awsCfg), nil } diff --git a/plugin/service/host/state_test.go b/plugin/service/host/state_test.go index 79ca6c1..34e9de1 100644 --- a/plugin/service/host/state_test.go +++ b/plugin/service/host/state_test.go @@ -8,14 +8,14 @@ import ( "testing" "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" ) func TestAwsCatalogPersistedStateEC2Client(t *testing.T) { require := require.New(t) cred, err := credential.NewAwsCredentialPersistedState( - credential.WithCredentialsConfig(&awsutil.CredentialsConfig{ + credential.WithCredentialsConfig(&awsutilv2.CredentialsConfig{ Region: "us-west-2", AccessKey: "foobar", SecretKey: "barfoo", @@ -29,7 +29,7 @@ func TestAwsCatalogPersistedStateEC2Client(t *testing.T) { ) require.NoError(err) - clientRaw, err := state.EC2Client(context.Background(), WithRegion("us-west-2")) + clientRaw, err := state.EC2Client(context.Background()) require.NoError(err) require.NotNil(clientRaw) client, ok := clientRaw.(*testMockEC2) diff --git a/plugin/service/storage/plugin.go b/plugin/service/storage/plugin.go index f0269aa..870bd74 100644 --- a/plugin/service/storage/plugin.go +++ b/plugin/service/storage/plugin.go @@ -79,12 +79,12 @@ func (p *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCre // Try to rotate static credentials if cred.HasStaticCredentials(credState.CredentialsConfig.AccessKey) { 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) } } else { // 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) } } @@ -119,81 +119,90 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd // For storage buckets we only need to really validate what is // currently in the new copy of the storage bucket data, so skip // fetching the new copy to keep things a little less messy. - bucket := req.GetNewBucket() - if bucket == nil { + newBucket := req.GetNewBucket() + if newBucket == nil { 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") } - - var updateSecrets bool - secrets := bucket.GetSecrets() - if secrets != nil { - // We will be updating secrets this run, but what exactly that - // means will be determined later. - updateSecrets = true + currentBucket := req.GetCurrentBucket() + if currentBucket == nil { + return nil, status.Error(codes.InvalidArgument, "current bucket is required") } - attrs := bucket.GetAttributes() - if attrs == nil { + oldAttrs := currentBucket.GetAttributes() + 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") } - - storageAttributes, err := getStorageAttributes(attrs) + newStorageAttributes, err := getStorageAttributes(newAttrs) if err != nil { 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( req.GetPersisted().GetData(), - storageAttributes.CredentialAttributes, + oldStorageAttributes.CredentialAttributes, p.testCredStateOpts...) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "error loading persisted state: %s", err) } - if cred.HasStaticCredentials(credState.CredentialsConfig.AccessKey) { - if storageAttributes.DisableCredentialRotation && !updateSecrets { - // 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. + // We will be updating credentials when one of two changes occur: + // 1. the new bucket has secrets for static credentials, in which case we need to delete the old static credentials + // 2. the new bucket has a different roleARN value for dynamic credentials + if newBucket.GetSecrets() != nil || newStorageAttributes.RoleArn != oldStorageAttributes.RoleArn { + 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) + } + } + + credentialType := cred.GetCredentialType(credState.CredentialsConfig.AccessKey) + 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() { return nil, status.Error(codes.FailedPrecondition, "cannot disable rotation for already-rotated credentials") } } - if updateSecrets { - storageSecrets, err := cred.GetCredentialsConfig(secrets, storageAttributes.CredentialAttributes, false) - if err != nil { - return nil, err - } - - // 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 { + // If we're enabling rotation now but didn't before, or have + // freshly replaced credentials, we can rotate here. + if !newStorageAttributes.DisableCredentialRotation && credState.CredsLastRotatedTime.IsZero() { + if err := credState.RotateCreds(ctx); err != nil { 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( append([]awsStoragePersistedStateOption{ withCredentials(credState), @@ -204,7 +213,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd } // 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 } @@ -264,7 +273,7 @@ func (p *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDel // Delete old/existing credentials. This is done with the same // credentials to ensure that it has the proper permissions to do // 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) } } @@ -323,9 +332,6 @@ func (p *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReques } opts := []s3Option{} - if storageAttributes.Region != "" { - opts = append(opts, WithRegion(storageAttributes.Region)) - } if storageAttributes.EndpointUrl != "" { opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl)) } @@ -452,9 +458,6 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu } opts := []s3Option{} - if storageAttributes.Region != "" { - opts = append(opts, WithRegion(storageAttributes.Region)) - } if storageAttributes.EndpointUrl != "" { opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl)) } @@ -566,9 +569,6 @@ func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest) } opts := []s3Option{} - if storageAttributes.Region != "" { - opts = append(opts, WithRegion(storageAttributes.Region)) - } if storageAttributes.EndpointUrl != "" { opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl)) } @@ -632,9 +632,6 @@ func dryRunValidation(ctx context.Context, state *awsStoragePersistedState, attr } opts := []s3Option{} - if attrs.Region != "" { - opts = append(opts, WithRegion(attrs.Region)) - } if attrs.EndpointUrl != "" { opts = append(opts, WithEndpoint(attrs.EndpointUrl)) } diff --git a/plugin/service/storage/plugin_test.go b/plugin/service/storage/plugin_test.go index b1157df..50add5e 100644 --- a/plugin/service/storage/plugin_test.go +++ b/plugin/service/storage/plugin_test.go @@ -17,12 +17,13 @@ import ( "time" "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/iam" + iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types" "github.com/aws/aws-sdk-go-v2/service/s3" - "github.com/aws/aws-sdk-go/service/iam" "github.com/hashicorp/boundary-plugin-aws/internal/credential" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets" 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" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -129,25 +130,25 @@ func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) { }, }, credOpts: []credential.AwsCredentialPersistedStateOption{ - credential.WithStateTestOpts([]awsutil.Option{ - awsutil.WithIAMAPIFunc( - awsutil.NewMockIAM( - awsutil.WithGetUserError(errors.New(testGetUserErr)), + credential.WithStateTestOpts([]awsutilv2.Option{ + awsutilv2.WithIAMAPIFunc( + awsutilv2.NewMockIAM( + 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, }, { name: "validation error", req: validRequest(), credOpts: []credential.AwsCredentialPersistedStateOption{ - credential.WithStateTestOpts([]awsutil.Option{ - awsutil.WithSTSAPIFunc( - awsutil.NewMockSTS( - awsutil.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)), + credential.WithStateTestOpts([]awsutilv2.Option{ + awsutilv2.WithSTSAPIFunc( + awsutilv2.NewMockSTS( + awsutilv2.WithGetCallerIdentityError(errors.New(testGetCallerIdentityErr)), ), ), }), @@ -279,24 +280,24 @@ func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) { }, credOpts: []credential.AwsCredentialPersistedStateOption{ credential.WithStateTestOpts( - []awsutil.Option{ - awsutil.WithSTSAPIFunc( - awsutil.NewMockSTS(), + []awsutilv2.Option{ + awsutilv2.WithSTSAPIFunc( + awsutilv2.NewMockSTS(), ), - awsutil.WithIAMAPIFunc( - awsutil.NewMockIAM( - awsutil.WithGetUserOutput( + awsutilv2.WithIAMAPIFunc( + awsutilv2.NewMockIAM( + awsutilv2.WithGetUserOutput( &iam.GetUserOutput{ - User: &iam.User{ + User: &iamTypes.User{ Arn: aws.String("arn:aws:iam::123456789012:user/JohnDoe"), UserId: aws.String("AIDAJQABLZS4A3QDU576Q"), UserName: aws.String("JohnDoe"), }, }, ), - awsutil.WithCreateAccessKeyOutput( + awsutilv2.WithCreateAccessKeyOutput( &iam.CreateAccessKeyOutput{ - AccessKey: &iam.AccessKey{ + AccessKey: &iamTypes.AccessKey{ AccessKeyId: aws.String("one"), SecretAccessKey: aws.String("two"), 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) { validRequest := func() *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{ Data: credential.MockStaticCredentialSecrets(), }, @@ -392,6 +404,27 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) { expectedErrContains: "new bucketName is required", 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", req: &pb.OnUpdateStorageBucketRequest{ @@ -399,6 +432,13 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) { BucketName: "foo", 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", expectedErrCode: codes.InvalidArgument, @@ -411,6 +451,13 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) { Secrets: 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\"", 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", 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{ Data: &structpb.Struct{ 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{ Data: &structpb.Struct{ 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{ Data: &structpb.Struct{ Fields: map[string]*structpb.Value{ @@ -513,10 +588,10 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) { }, }, credOpts: []credential.AwsCredentialPersistedStateOption{ - credential.WithStateTestOpts([]awsutil.Option{ - awsutil.WithIAMAPIFunc( - awsutil.NewMockIAM( - awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)), + credential.WithStateTestOpts([]awsutilv2.Option{ + awsutilv2.WithIAMAPIFunc( + awsutilv2.NewMockIAM( + 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{ Data: &structpb.Struct{ Fields: map[string]*structpb.Value{ @@ -545,15 +627,15 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) { }, }, credOpts: []credential.AwsCredentialPersistedStateOption{ - credential.WithStateTestOpts([]awsutil.Option{ - awsutil.WithIAMAPIFunc( - awsutil.NewMockIAM( - awsutil.WithGetUserError(errors.New(testGetUserErr)), + credential.WithStateTestOpts([]awsutilv2.Option{ + awsutilv2.WithIAMAPIFunc( + awsutilv2.NewMockIAM( + 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, }, { @@ -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{ Data: &structpb.Struct{ Fields: map[string]*structpb.Value{ @@ -669,24 +758,24 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) { }, }, credOpts: []credential.AwsCredentialPersistedStateOption{ - credential.WithStateTestOpts([]awsutil.Option{ - awsutil.WithSTSAPIFunc( - awsutil.NewMockSTS(), + credential.WithStateTestOpts([]awsutilv2.Option{ + awsutilv2.WithSTSAPIFunc( + awsutilv2.NewMockSTS(), ), - awsutil.WithIAMAPIFunc( - awsutil.NewMockIAM( - awsutil.WithGetUserOutput( + awsutilv2.WithIAMAPIFunc( + awsutilv2.NewMockIAM( + awsutilv2.WithGetUserOutput( &iam.GetUserOutput{ - User: &iam.User{ + User: &iamTypes.User{ Arn: aws.String("arn:aws:iam::123456789012:user/JohnDoe"), UserId: aws.String("AIDAJQABLZS4A3QDU576Q"), UserName: aws.String("JohnDoe"), }, }, ), - awsutil.WithCreateAccessKeyOutput( + awsutilv2.WithCreateAccessKeyOutput( &iam.CreateAccessKeyOutput{ - AccessKey: &iam.AccessKey{ + AccessKey: &iamTypes.AccessKey{ AccessKeyId: aws.String("AKIA_one"), SecretAccessKey: aws.String("two"), UserName: aws.String("JohnDoe"), @@ -858,10 +947,10 @@ func TestStoragePlugin_OnDeleteStorageBucket(t *testing.T) { }, }, credOpts: []credential.AwsCredentialPersistedStateOption{ - credential.WithStateTestOpts([]awsutil.Option{ - awsutil.WithIAMAPIFunc( - awsutil.NewMockIAM( - awsutil.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)), + credential.WithStateTestOpts([]awsutilv2.Option{ + awsutilv2.WithIAMAPIFunc( + awsutilv2.NewMockIAM( + awsutilv2.WithDeleteAccessKeyError(errors.New(testDeleteAccessKeyErr)), ), ), }), @@ -890,10 +979,10 @@ func TestStoragePlugin_OnDeleteStorageBucket(t *testing.T) { }, }, credOpts: []credential.AwsCredentialPersistedStateOption{ - credential.WithStateTestOpts([]awsutil.Option{ - awsutil.WithIAMAPIFunc( - awsutil.NewMockIAM( - awsutil.WithDeleteAccessKeyError(nil), + credential.WithStateTestOpts([]awsutilv2.Option{ + awsutilv2.WithIAMAPIFunc( + awsutilv2.NewMockIAM( + awsutilv2.WithDeleteAccessKeyError(nil), ), ), }), diff --git a/plugin/service/storage/state.go b/plugin/service/storage/state.go index ed769f3..1a0032f 100644 --- a/plugin/service/storage/state.go +++ b/plugin/service/storage/state.go @@ -9,12 +9,12 @@ import ( "fmt" "net" "net/http" + "net/url" "time" "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" + smithyEndpoints "github.com/aws/smithy-go/endpoints" "github.com/hashicorp/boundary-plugin-aws/internal/credential" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets" "google.golang.org/protobuf/types/known/structpb" @@ -97,55 +97,23 @@ func (s *awsStoragePersistedState) S3Client(ctx context.Context, opt ...s3Option if err != nil { return nil, fmt.Errorf("error parsing options when fetching S3 client: %w", err) } - - awsConfigOpts := []func(*config.LoadOptions) error{} - sess, err := s.GetSession() + awsCfg, err := s.GenerateCredentialChain(ctx) 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 err != nil { - 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)) + if awsCfg == nil { + return nil, fmt.Errorf("nil aws configuration") } + var s3Opts []func(*s3.Options) if opts.withEndpoint != "" { - customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) { - if service == s3.ServiceID { - 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)) + s3Opts = append(s3Opts, s3.WithEndpointResolverV2(&endpointResolver{ + endpoint: opts.withEndpoint, + })) } - 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 { - return s.testS3APIFunc(cfg) + return s.testS3APIFunc(*awsCfg) } - - cfg.HTTPClient = customClient - - return s3.NewFromConfig(cfg), nil + return s3.NewFromConfig(*awsCfg, s3Opts...), nil } // getOpts iterates the inbound s3Options and returns a struct @@ -167,7 +135,6 @@ type s3Option func(*s3Options) error // options = how options are represented type s3Options struct { - withRegion string withEndpoint string } @@ -175,14 +142,6 @@ func getDefaultS3Options() 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 func WithEndpoint(with string) s3Option { return func(o *s3Options) error { @@ -190,3 +149,31 @@ func WithEndpoint(with string) s3Option { 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 +} diff --git a/plugin/service/storage/state_test.go b/plugin/service/storage/state_test.go index 8efa3fc..d212e15 100644 --- a/plugin/service/storage/state_test.go +++ b/plugin/service/storage/state_test.go @@ -7,43 +7,41 @@ import ( "context" "testing" - "github.com/aws/aws-sdk-go-v2/service/s3" "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" ) func TestAwsStoragePersistedStateS3Client(t *testing.T) { cases := []struct { name string - creds *awsutil.CredentialsConfig + creds *awsutilv2.CredentialsConfig opts []awsStoragePersistedStateOption - awsOpts []awsutil.Option + awsOpts []awsutilv2.Option s3Opts []s3Option expectedRegion string expectedEndpoint string }{ { name: "static credentials", - creds: func() *awsutil.CredentialsConfig { - creds, err := awsutil.NewCredentialsConfig( - awsutil.WithRegion("us-west-2"), - awsutil.WithAccessKey("foobar"), - awsutil.WithSecretKey("barfoo"), + creds: func() *awsutilv2.CredentialsConfig { + creds, err := awsutilv2.NewCredentialsConfig( + awsutilv2.WithRegion("us-west-2"), + awsutilv2.WithAccessKey("foobar"), + awsutilv2.WithSecretKey("barfoo"), ) require.NoError(t, err) return creds }(), - awsOpts: []awsutil.Option{ - awsutil.WithIAMAPIFunc( - awsutil.NewMockIAM(), + awsOpts: []awsutilv2.Option{ + awsutilv2.WithIAMAPIFunc( + awsutilv2.NewMockIAM(), ), }, opts: []awsStoragePersistedStateOption{ withTestS3APIFunc(newTestMockS3(nil)), }, s3Opts: []s3Option{ - WithRegion("us-west-2"), WithEndpoint("0.0.0.0"), }, expectedRegion: "us-west-2", @@ -73,14 +71,6 @@ func TestAwsStoragePersistedStateS3Client(t *testing.T) { require.True(ok) require.NotNil(client) 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) - } }) } } diff --git a/plugin/service/storage/testing.go b/plugin/service/storage/testing.go index 50e4521..fb797de 100644 --- a/plugin/service/storage/testing.go +++ b/plugin/service/storage/testing.go @@ -13,11 +13,11 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "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/sdk/pbs/controller/api/resources/storagebuckets" 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" "google.golang.org/grpc" "google.golang.org/protobuf/types/known/structpb" @@ -69,9 +69,8 @@ func (s *testMockS3State) Reset() { type testMockS3 struct { S3API - State *testMockS3State - Region string - Endpoint aws.EndpointResolverWithOptions + State *testMockS3State + Region string // mocked responses for getObject GetObjectOutput *s3.GetObjectOutput @@ -147,9 +146,9 @@ func newTestMockS3(state *testMockS3State, opts ...testMockS3Option) s3APIFunc { if cfg.Region != "" { m.Region = cfg.Region } - if cfg.EndpointResolverWithOptions != nil { - m.Endpoint = cfg.EndpointResolverWithOptions - } + // if cfg. != nil { + // m.Endpoint = cfg.EndpointResolverWithOptions + // } } return m, nil @@ -288,10 +287,10 @@ func deepCopyPutObjectRequest(v *pb.PutObjectRequest) *pb.PutObjectRequest { func validSTSMock() []credential.AwsCredentialPersistedStateOption { return []credential.AwsCredentialPersistedStateOption{ - credential.WithStateTestOpts([]awsutil.Option{ - awsutil.WithSTSAPIFunc( - awsutil.NewMockSTS( - awsutil.WithGetCallerIdentityOutput(&sts.GetCallerIdentityOutput{ + credential.WithStateTestOpts([]awsutilv2.Option{ + awsutilv2.WithSTSAPIFunc( + awsutilv2.NewMockSTS( + awsutilv2.WithGetCallerIdentityOutput(&sts.GetCallerIdentityOutput{ Account: aws.String("0123456789"), Arn: aws.String("arn:aws:iam::0123456789:user/test"), UserId: aws.String("test"), diff --git a/testing/e2e_storage_test.go b/testing/e2e_storage_test.go index dfa8238..f71deef 100644 --- a/testing/e2e_storage_test.go +++ b/testing/e2e_storage_test.go @@ -75,7 +75,7 @@ func TestStoragePlugin(t *testing.T) { iamSecretAccessKeyMissingPutObject, err := tf.GetOutputString("iam_secret_access_key_missing_put_obj") 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 // the access key ID/secret access key will no longer be valid. We // will assert this through the returned state. diff --git a/testing/testing.go b/testing/testing.go index e97cc92..6552d1c 100644 --- a/testing/testing.go +++ b/testing/testing.go @@ -13,10 +13,10 @@ import ( "testing" "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/values" - "github.com/hashicorp/go-secure-stdlib/awsutil" + awsutilv2 "github.com/hashicorp/go-secure-stdlib/awsutil" tfjson "github.com/hashicorp/terraform-json" "github.com/stretchr/testify/require" "google.golang.org/protobuf/types/known/structpb" @@ -280,9 +280,9 @@ func requireCredentialsInvalid(t *testing.T, accessKeyId, secretAccessKey string t.Helper() require := require.New(t) - c, err := awsutil.NewCredentialsConfig( - awsutil.WithAccessKey(accessKeyId), - awsutil.WithSecretKey(secretAccessKey), + c, err := awsutilv2.NewCredentialsConfig( + awsutilv2.WithAccessKey(accessKeyId), + awsutilv2.WithSecretKey(secretAccessKey), ) require.NoError(err) @@ -293,7 +293,7 @@ func requireCredentialsInvalid(t *testing.T, accessKeyId, secretAccessKey string defer cancel() waitErr: for { - _, err = c.GetCallerIdentity() + _, err = c.GetCallerIdentity(context.Background()) if err != nil { break } @@ -308,21 +308,21 @@ waitErr: } require.NotNil(err) - awsErr, ok := err.(awserr.Error) - require.True(ok) - require.Equal("InvalidClientTokenId", awsErr.Code()) + var ae smithy.APIError + require.True(errors.As(err, &ae)) + require.Equal("InvalidClientTokenId", ae.ErrorCode()) } func requireCredentialsValid(t *testing.T, accessKeyId, secretAccessKey string) { t.Helper() require := require.New(t) - c, err := awsutil.NewCredentialsConfig( - awsutil.WithAccessKey(accessKeyId), - awsutil.WithSecretKey(secretAccessKey), + c, err := awsutilv2.NewCredentialsConfig( + awsutilv2.WithAccessKey(accessKeyId), + awsutilv2.WithSecretKey(secretAccessKey), ) require.NoError(err) - _, err = c.GetCallerIdentity() + _, err = c.GetCallerIdentity(context.Background()) require.NoError(err) }