Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
125caec99f | ||
|
|
4d6d789de3 | ||
|
|
6f0df81d2b | ||
|
|
65914e30b0 | ||
|
|
e484c09479 | ||
|
|
4126a0dbfc | ||
|
|
09dd7f8a4d | ||
|
|
4c4a901d33 |
No files matched your search
@@ -1,6 +1,6 @@
|
||||
module github.com/hashicorp/boundary-plugin-aws
|
||||
|
||||
go 1.26.0
|
||||
go 1.25.7
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.7
|
||||
@@ -14,12 +14,12 @@ require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hashicorp/boundary/sdk v0.0.59
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.1.3
|
||||
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.1.2
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0
|
||||
github.com/hashicorp/terraform-json v0.23.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
google.golang.org/grpc v1.83.2
|
||||
google.golang.org/grpc v1.79.3
|
||||
google.golang.org/protobuf v1.36.11
|
||||
)
|
||||
|
||||
@@ -59,11 +59,11 @@ require (
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/zclconf/go-cty v1.15.0 // indirect
|
||||
golang.org/x/crypto v0.56.0 // indirect
|
||||
golang.org/x/net v0.58.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/text v0.41.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
@@ -8,8 +8,12 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
||||
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.1 h1:fWZhGAwVRK/fAN2tmt7ilH4PPAE11rDj7HytrmbZ2FE=
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.1/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 h1:gx1AwW1Iyk9Z9dD9F4akX5gnN3QZwUB20GGKH/I+Rho=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10/go.mod h1:qqY157uZoqm5OXq/amuaBJyC9hgBCBQnsaWnPe905GY=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.5 h1:Za41twdCXbuyyWv9LndXxZZv3QhTG1DinqlFsSuvtI0=
|
||||
@@ -18,26 +22,42 @@ github.com/aws/aws-sdk-go-v2/credentials v1.17.46 h1:AU7RcriIo2lXjUfHFnFKYsLCwgb
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.46/go.mod h1:1FmYyLGL08KQXQ6mcTlifyFXfJVCNJTVGuQP4m0d/UA=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 h1:sDSXIrlsFSFJtWKLQS4PUWRvrT580rrnuLydJrCQ/yA=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20/go.mod h1:WZ/c+w0ofps+/OUqMwWgnfrgzZH1DZO1RIkktICsqnY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8 h1:6bgAZgRyT4RoFWhxS+aoGMFyE0cD1bSzFnEEi4bFPGI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8/go.mod h1:KcGkXFVU8U28qS4KvLEcPxytPZPBcRawaH2Pf/0jptE=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8 h1:HhJYoES3zOz34yWEpGENqJvRVPqpmJyR3+AFg9ybhdY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8/go.mod h1:JnA+hPWeYAVbDssp83tv+ysAG8lTfLVXvSsyKg/7xNA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23/go.mod h1:15DfR2nw+CRHIk0tqNyifu3G1YdAOy68RftkhMDDwYk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.8 h1:1/bT9kDdLQzfZ1e6J6hpW+SfNDd6xrV8F3M2CuGyUz8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.8/go.mod h1:RbdwTONAIi59ej/+1H+QzZORt5bcyAtbrS7FQb2pvz0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24/go.mod h1:X5ZJyfwVrWA96GzPmUCWFQaEARPR7gCrpq2E92PJwAE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.254.0 h1:fTLR6dLDTGChAjecRPlVrKeznT0rVdzR4yn9Z68MTGk=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.254.0/go.mod h1:V0jbRy1/IPapnkqgXSwVOFB+u5pnCwd9S+R3pKWULC4=
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.47.6 h1:EWehQXACWr+6hzfZPwZChlfoVhiUCfLHE0Xh3kAfzWQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.47.6/go.mod h1:qRXgEBWPIltrWHQwU+HkyBvwh1QgeigFcaCGCIVrWk0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZLt5ci0ozzgkVo8BJGwvqNaZbTWb3UcucAateA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9/go.mod h1:w7wZ/s9qK7c8g4al+UyoF1Sp/Z45UwMGcqIzLWVQHWk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.8 h1:tIN8MFT1z5STK5kTdOT1TCfMN/bn5fSEnlKsTL8qBOU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.8/go.mod h1:VKS56txtNWjKI8FqD/hliL0BcshyF4ZaLBa1rm2Y+5s=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 h1:ieLCO1JxUWuxTZ1cRd0GAaeX7O6cIxnwk7tc1LsQhC4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15/go.mod h1:e3IzZvQ3kAWNykvE0Tr0RDZCMFInMvhku3qNpcIQXhM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8 h1:M6JI2aGFEzYxsF6CXIuRBnkge9Wf9a2xU39rNeXgu10=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8/go.mod h1:Fw+MyTwlwjFsSTE31mH211Np+CUslml8mzc0AFEG09s=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 h1:pbrxO/kuIwgEsOPLkaHu0O+m4fNgLU8B3vxQ+72jTPw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23/go.mod h1:/CMNUqoj46HpS3MNRDEDIwcgEnrtZlKRaHNaHxIFpNA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.8 h1:AgYCo1Rb8XChJXA871BXHDNxNWOTAr6V5YdsRIBbgv0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.8/go.mod h1:Au9dvIGm1Hbqnt29d3VakOCQuN9l0WrkDDTRq8biWS4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 h1:03xatSQO4+AM1lTAbnRg5OK528EUg744nW7F73U8DKw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23/go.mod h1:M8l3mwgx5ToK7wot2sBBce/ojzgnPzZXUV445gTSyE8=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.88.2 h1:T7b3qniouutV5Wwa9B1q7gW+Y8s1B3g9RE9qa7zLBIM=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.88.2/go.mod h1:tW9TsLb6t1eaTdBE6LITyJW1m/+DjQPU78Q/jT2FJu8=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0 h1:etqBTKY581iwLL/H/S2sVgk3C9lAsTJFeXWFDsDcWOU=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0/go.mod h1:L2dcoOgS2VSgbPLvpak2NyUPsO1TBN7M45Z4H7DlRc4=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 h1:3zu537oLmsPfDMyjnUS2g+F2vITgy5pB74tHI+JBNoM=
|
||||
@@ -46,6 +66,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 h1:K0OQAsDywb0ltlFrZm0JHPY3
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5/go.mod h1:ORITg+fyuMoeiQFiVGoqB3OydVTLkClw/ljbblMq6Cc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 h1:+LVB0xBqEgjQoqr9bGZbRzvg212B0f17JdflleJRNR4=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.5/go.mod h1:xoaxeqnnUaZjPjaICgIy5B+MHCSb/ZSOn4MvkFNOUA0=
|
||||
github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE=
|
||||
github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
||||
github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI=
|
||||
github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
|
||||
@@ -94,8 +116,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA=
|
||||
github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8=
|
||||
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.1.3 h1:mLe7IMysLSedhuu00W7AXsbfb90AA7UKfZmw3OkJ8n4=
|
||||
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.1.3/go.mod h1:6+rVulOPNCQbL3Xv2iLCqM0JmU2WO2wRzP1C6hBKeB8=
|
||||
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.1.2 h1:KlDrZWx/wYpnUcFPB0s9uhPjyNZoeIfhrV2MN/Ly76s=
|
||||
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.1.2/go.mod h1:6+rVulOPNCQbL3Xv2iLCqM0JmU2WO2wRzP1C6hBKeB8=
|
||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng=
|
||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
|
||||
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.13 h1:TayxZ5drfMP0G6T++WvnLESGLOWeHtdWDWWTAi2e3Qk=
|
||||
@@ -173,40 +195,40 @@ github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ
|
||||
github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
|
||||
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
|
||||
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
|
||||
golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I=
|
||||
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
||||
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
|
||||
google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -112,35 +112,10 @@ func NewAwsCredentialPersistedState(opts ...AwsCredentialPersistedStateOption) (
|
||||
// and runs sts.GetCallerIdentity for the current credentials, which is done
|
||||
// to check that the credentials are valid. This method returns a status error
|
||||
// with PluginError details.
|
||||
//
|
||||
// When RoleARN is set and no static key is present (ambient role path),
|
||||
// credentials are eagerly retrieved before calling GetCallerIdentity. This
|
||||
// forces sts:AssumeRole to be called immediately so that any role errors
|
||||
// (e.g. no such role, access denied) surface here rather than being deferred
|
||||
// to the first real AWS API call.
|
||||
func (s *AwsCredentialPersistedState) ValidateCreds(ctx context.Context) error {
|
||||
if s.CredentialsConfig == nil {
|
||||
return errors.BadRequestStatus("missing credentials config")
|
||||
}
|
||||
|
||||
// For the ambient role path (RoleARN set, no static key), eagerly resolve
|
||||
// the credential chain so that sts:AssumeRole is called now. Without this,
|
||||
// the AssumeRoleProvider inside the chain is lazy and GetCallerIdentity
|
||||
// would succeed even with an invalid role ARN (it uses the mock/ambient
|
||||
// identity directly rather than the assumed one).
|
||||
cc := s.CredentialsConfig
|
||||
if cc.RoleARN != "" && cc.AccessKey == "" {
|
||||
cfg, err := s.GenerateCredentialChain(ctx)
|
||||
if err != nil {
|
||||
st, _ := errors.ParseAWSError("validating credentials", err)
|
||||
return st.Err()
|
||||
}
|
||||
if _, err := cfg.Credentials.Retrieve(ctx); err != nil {
|
||||
st, _ := errors.ParseAWSError("validating credentials", err)
|
||||
return st.Err()
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := s.CredentialsConfig.GetCallerIdentity(ctx, s.testOpts...); err != nil {
|
||||
st, _ := errors.ParseAWSError("validating credentials", err)
|
||||
return st.Err()
|
||||
|
||||
@@ -29,11 +29,6 @@ const (
|
||||
// the owner. This error cannot be resolved by retrying the request.
|
||||
awsErrorAccessDenied = "AccessDenied"
|
||||
|
||||
// awsErrorUnauthorizedOperation is returned by EC2 when the caller is not
|
||||
// authorized to perform the operation. This includes DryRun permission
|
||||
// checks that fail (the counterpart to DryRunOperation success).
|
||||
awsErrorUnauthorizedOperation = "UnauthorizedOperation"
|
||||
|
||||
// awsErrorInvalidAccessKeyId is returned when the credential does
|
||||
// not exist. This error will persist until the credentials attached
|
||||
// to the storage bucket is updated. This error can occur when the
|
||||
@@ -71,26 +66,8 @@ const (
|
||||
|
||||
// RequestTimeoutException is returned when an http request takes longer than allowed
|
||||
awsErrorRequestTimeoutException = "RequestTimeoutException"
|
||||
|
||||
// dryRunOperationErrorCode is returned by AWS when a DryRun request would have
|
||||
// succeeded. It is a success signal disguised as an API error (typically HTTP 412).
|
||||
dryRunOperationErrorCode = "DryRunOperation"
|
||||
)
|
||||
|
||||
// IsDryRunSuccess reports whether err is the AWS DryRunOperation success signal.
|
||||
// A true result means the dry-run validated credentials, permissions, and parameters;
|
||||
// it must not be treated as a failure.
|
||||
func IsDryRunSuccess(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
var apiErr smithy.APIError
|
||||
if !errors.As(err, &apiErr) {
|
||||
return false
|
||||
}
|
||||
return apiErr.ErrorCode() == dryRunOperationErrorCode
|
||||
}
|
||||
|
||||
// InvalidArgumentError returns an grpc invalid argument status error.
|
||||
func InvalidArgumentError(msg string, f map[string]string) error {
|
||||
var fieldMsgs []string
|
||||
@@ -195,8 +172,6 @@ func ParseAWSError(op string, err error) (st *status.Status, permission *pb.Perm
|
||||
switch apiErr.ErrorCode() {
|
||||
case awsErrorAccessDenied:
|
||||
fallthrough
|
||||
case awsErrorUnauthorizedOperation:
|
||||
fallthrough
|
||||
case awsErrorInvalidAccessKeyId:
|
||||
fallthrough
|
||||
case awsErrorExpiredToken:
|
||||
|
||||
@@ -300,17 +300,6 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
CheckedAt: timestamppb.Now(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "aws-request-unauthorized-operation",
|
||||
err: TestAwsError(awsErrorUnauthorizedOperation, "You are not authorized to perform this operation."),
|
||||
expectedStatusCode: codes.PermissionDenied,
|
||||
expectedStatusMsg: "aws service unknown: invalid credentials: test",
|
||||
expectedPermission: &pb.Permission{
|
||||
State: pb.StateType_STATE_TYPE_ERROR,
|
||||
ErrorDetails: "You are not authorized to perform this operation.",
|
||||
CheckedAt: timestamppb.Now(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "aws-request-invalid-access-key-id",
|
||||
err: TestAwsError(awsErrorInvalidAccessKeyId, "The AWS Access Key Id you provided does not exist in our records."),
|
||||
@@ -401,46 +390,6 @@ func Test_ParseAWSError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_IsDryRunSuccess(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
err error
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "plain error",
|
||||
err: fmt.Errorf("not an aws error"),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "other api error",
|
||||
err: TestAwsError(awsErrorAccessDenied, "not authorized"),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "dry run operation",
|
||||
err: TestAwsError(dryRunOperationErrorCode, "Request would have succeeded, but DryRun flag is set"),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "wrapped dry run operation",
|
||||
err: fmt.Errorf("describe instances: %w", TestAwsError(dryRunOperationErrorCode, "Request would have succeeded, but DryRun flag is set")),
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, IsDryRunSuccess(tt.err))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_BadRequestStatusf(t *testing.T) {
|
||||
require, assert := require.New(t), assert.New(t)
|
||||
err := BadRequestStatusf("test: %s", "hello world")
|
||||
|
||||
+63
-106
@@ -5,6 +5,7 @@ package host
|
||||
|
||||
import (
|
||||
"context"
|
||||
goerrors "errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
stsTypes "github.com/aws/aws-sdk-go-v2/service/sts/types"
|
||||
"github.com/aws/smithy-go"
|
||||
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
||||
"github.com/hashicorp/boundary-plugin-aws/internal/errors"
|
||||
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||
@@ -42,8 +44,8 @@ type hostSetQuery struct {
|
||||
OutputHosts []*pb.ListHostsResponseHost
|
||||
}
|
||||
|
||||
const errBuildDescribeInstancesInput = "error building DescribeInstances input: %s"
|
||||
const errDryRunFailed = "error performing DescribeInstances dry run: %s"
|
||||
const defaultSessionName = "boundary-default"
|
||||
const dryRunOperationErrorCode = "DryRunOperation"
|
||||
|
||||
// Ensure that we are implementing HostPluginServiceServer
|
||||
var _ pb.HostPluginServiceServer = (*HostPlugin)(nil)
|
||||
@@ -86,15 +88,6 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo
|
||||
}
|
||||
}
|
||||
|
||||
// Validate assumed-role credentials at catalog-creation time so that an
|
||||
// invalid role_arn is rejected immediately rather than silently falling
|
||||
// back to ambient credentials.
|
||||
if credential.GetCredentialType(credState.CredentialsConfig) == credential.DynamicAWS {
|
||||
if err := credState.ValidateCreds(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
catalogState, err := newAwsCatalogPersistedState(
|
||||
append([]awsCatalogPersistedStateOption{
|
||||
withCredentials(credState),
|
||||
@@ -104,21 +97,13 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo
|
||||
return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
|
||||
}
|
||||
|
||||
// perform dry run to ensure we can interact with AWS as expected.
|
||||
opts := []ec2Option{}
|
||||
if catalogAttributes.DualStack {
|
||||
opts = append(opts, WithDualStack(catalogAttributes.DualStack))
|
||||
}
|
||||
input, err := buildDescribeInstancesInput(&SetAttributes{}, true)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Unknown, errBuildDescribeInstancesInput, err)
|
||||
}
|
||||
// Run a DescribeInstances request with DryRun set to true to ensure
|
||||
// we can interact with AWS resources as expected.
|
||||
if _, err := checkHosts(ctx, catalogState, input, opts, catalogAttributes.TargetAccount); err != nil {
|
||||
if _, ok := status.FromError(err); ok {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf(errDryRunFailed, err)
|
||||
if st := dryRunValidation(ctx, catalogState, opts); st != nil {
|
||||
return nil, st.Err()
|
||||
}
|
||||
|
||||
persistedProto, err := catalogState.toProto()
|
||||
@@ -193,15 +178,6 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
|
||||
if err != nil {
|
||||
return nil, errors.BadRequestStatusf("error setting up new credential persisted state: %s", err)
|
||||
}
|
||||
|
||||
// Validate assumed-role credentials when the role_arn changes so that
|
||||
// an invalid ARN is caught before replacing the existing credential state.
|
||||
if credential.GetCredentialType(newCredState.CredentialsConfig) == credential.DynamicAWS {
|
||||
if err := newCredState.ValidateCreds(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
newCatalogState, err := newAwsCatalogPersistedState(
|
||||
append([]awsCatalogPersistedStateOption{
|
||||
withCredentials(newCredState),
|
||||
@@ -210,22 +186,12 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
|
||||
if err != nil {
|
||||
return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
|
||||
}
|
||||
|
||||
opts := []ec2Option{}
|
||||
if newCatalogAttributes.DualStack {
|
||||
opts = append(opts, WithDualStack(newCatalogAttributes.DualStack))
|
||||
}
|
||||
input, err := buildDescribeInstancesInput(&SetAttributes{}, true)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Unknown, errBuildDescribeInstancesInput, err)
|
||||
}
|
||||
// Run a DescribeInstances request with DryRun set to true to ensure
|
||||
// we can interact with AWS resources as expected.
|
||||
if _, err := checkHosts(ctx, newCatalogState, input, opts, newCatalogAttributes.TargetAccount); err != nil {
|
||||
if _, ok := status.FromError(err); ok {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf(errDryRunFailed, err)
|
||||
if st := dryRunValidation(ctx, newCatalogState, opts); st != nil {
|
||||
return nil, st.Err()
|
||||
}
|
||||
|
||||
// Replace the existing credential state.
|
||||
@@ -265,21 +231,13 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
|
||||
return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
|
||||
}
|
||||
|
||||
// perform dry run to ensure we can interact with AWS as expected.
|
||||
opts := []ec2Option{}
|
||||
if newCatalogAttributes.DualStack {
|
||||
opts = append(opts, WithDualStack(newCatalogAttributes.DualStack))
|
||||
}
|
||||
input, err := buildDescribeInstancesInput(&SetAttributes{}, true)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Unknown, errBuildDescribeInstancesInput, err)
|
||||
}
|
||||
// Run a DescribeInstances request with DryRun set to true to ensure
|
||||
// we can interact with AWS resources as expected.
|
||||
if _, err := checkHosts(ctx, catalogState, input, opts, newCatalogAttributes.TargetAccount); err != nil {
|
||||
if _, ok := status.FromError(err); ok {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf(errDryRunFailed, err)
|
||||
if st := dryRunValidation(ctx, catalogState, opts); st != nil {
|
||||
return nil, st.Err()
|
||||
}
|
||||
|
||||
persistedProto, err := catalogState.toProto()
|
||||
@@ -423,23 +381,18 @@ func (p *HostPlugin) OnCreateSet(ctx context.Context, req *pb.OnCreateSetRequest
|
||||
return nil, err
|
||||
}
|
||||
|
||||
describeInstanceFilters, err := buildFilters(setAttrs)
|
||||
if err != nil {
|
||||
return nil, errors.BadRequestStatusf("error building set filters: %s", err)
|
||||
}
|
||||
|
||||
opts := []ec2Option{}
|
||||
if catalogAttributes.DualStack {
|
||||
opts = append(opts, WithDualStack(catalogAttributes.DualStack))
|
||||
}
|
||||
input, err := buildDescribeInstancesInput(setAttrs, true)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Unknown, errBuildDescribeInstancesInput, err)
|
||||
if st := dryRunValidation(ctx, catalogState, opts, describeInstanceFilters...); st != nil {
|
||||
return nil, st.Err()
|
||||
}
|
||||
// Run a DescribeInstances request with DryRun set to true to ensure
|
||||
// we can interact with AWS resources as expected.
|
||||
if _, err := checkHosts(ctx, catalogState, input, opts, catalogAttributes.TargetAccount); err != nil {
|
||||
if _, ok := status.FromError(err); ok {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf(errDryRunFailed, err)
|
||||
}
|
||||
|
||||
return &pb.OnCreateSetResponse{}, nil
|
||||
}
|
||||
|
||||
@@ -493,21 +446,17 @@ func (p *HostPlugin) OnUpdateSet(ctx context.Context, req *pb.OnUpdateSetRequest
|
||||
return nil, err
|
||||
}
|
||||
|
||||
describeInstanceFilters, err := buildFilters(setAttrs)
|
||||
if err != nil {
|
||||
return nil, errors.BadRequestStatusf("error building set filters: %s", err)
|
||||
}
|
||||
|
||||
opts := []ec2Option{}
|
||||
if catalogAttributes.DualStack {
|
||||
opts = append(opts, WithDualStack(catalogAttributes.DualStack))
|
||||
}
|
||||
input, err := buildDescribeInstancesInput(setAttrs, true)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Unknown, errBuildDescribeInstancesInput, err)
|
||||
}
|
||||
// Run a DescribeInstances request with DryRun set to true to ensure
|
||||
// we can interact with AWS resources as expected.
|
||||
if _, err := checkHosts(ctx, catalogState, input, opts, catalogAttributes.TargetAccount); err != nil {
|
||||
if _, ok := status.FromError(err); ok {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf(errDryRunFailed, err)
|
||||
if st := dryRunValidation(ctx, catalogState, opts, describeInstanceFilters...); st != nil {
|
||||
return nil, st.Err()
|
||||
}
|
||||
|
||||
return &pb.OnUpdateSetResponse{}, nil
|
||||
@@ -596,14 +545,10 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
|
||||
for i, query := range queries {
|
||||
output, err := checkHosts(ctx, catalogState, query.Input, opts, catalogAttributes.TargetAccount)
|
||||
if err != nil {
|
||||
// Mapped AWS failures are already gRPC statuses from checkHosts.
|
||||
if _, ok := status.FromError(err); ok {
|
||||
return nil, err
|
||||
}
|
||||
if catalogAttributes.TargetAccount != nil {
|
||||
return nil, status.Errorf(codes.Unknown, "unable to list hosts for host set %q: %v", query.Id, err)
|
||||
return nil, errors.BadRequestStatusf("unable to list hosts for host set %q: %s", query.Id, err)
|
||||
}
|
||||
return nil, status.Errorf(codes.Unknown, "error retrieving host results for host set id %q: %v", query.Id, err)
|
||||
return nil, errors.BadRequestStatusf("error retrieving host results for host set id %q: %s", query.Id, err)
|
||||
}
|
||||
|
||||
queries[i].Output = output
|
||||
@@ -617,7 +562,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
|
||||
for _, instance := range reservation.Instances {
|
||||
host, err := awsInstanceToHost(instance, catalogAttributes)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Unknown, "error processing host results for host set id %q: %v", query.Id, err)
|
||||
return nil, errors.BadRequestStatusf("error processing host results for host set id %q: %s", query.Id, err)
|
||||
}
|
||||
|
||||
queries[i].OutputHosts = append(queries[i].OutputHosts, host)
|
||||
@@ -666,11 +611,11 @@ func buildFilters(attrs *SetAttributes) ([]types.Filter, error) {
|
||||
splitFilter := strings.Split(filterAttr, "=")
|
||||
switch {
|
||||
case len(splitFilter) != 2:
|
||||
return nil, errors.BadRequestStatusf("expected filter %q to contain a single equal sign", filterAttr)
|
||||
return nil, fmt.Errorf("expected filter %q to contain a single equal sign", filterAttr)
|
||||
case len(splitFilter[0]) == 0:
|
||||
return nil, errors.BadRequestStatusf("filter %q contains an empty filter key", filterAttr)
|
||||
return nil, fmt.Errorf("filter %q contains an empty filter key", filterAttr)
|
||||
case len(splitFilter[1]) == 0:
|
||||
return nil, errors.BadRequestStatusf("filter %q contains an empty value", filterAttr)
|
||||
return nil, fmt.Errorf("filter %q contains an empty value", filterAttr)
|
||||
}
|
||||
|
||||
filterKey, filterValue := splitFilter[0], splitFilter[1]
|
||||
@@ -699,13 +644,9 @@ func buildFilters(attrs *SetAttributes) ([]types.Filter, error) {
|
||||
}
|
||||
|
||||
func buildDescribeInstancesInput(attrs *SetAttributes, dryRun bool) (*ec2.DescribeInstancesInput, error) {
|
||||
if attrs == nil {
|
||||
attrs = &SetAttributes{}
|
||||
}
|
||||
|
||||
filters, err := buildFilters(attrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("error building filters: %w", err)
|
||||
}
|
||||
|
||||
return &ec2.DescribeInstancesInput{
|
||||
@@ -792,31 +733,25 @@ func checkHosts(ctx context.Context,
|
||||
// use the principal account to execute the DescribeInstances command
|
||||
ec2Client, err = principalCatalogState.EC2Client(ctx, opts...)
|
||||
if err != nil {
|
||||
if st, _ := errors.ParseAWSError("EC2 client setup", err); st.Code() != codes.Unknown {
|
||||
return nil, st.Err()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// use the target account to execute the DescribeInstances command
|
||||
ec2Client, err = ec2ClientForTarget(ctx, principalCatalogState, target, opts)
|
||||
if err != nil {
|
||||
if _, ok := status.FromError(err); ok {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("EC2 client setup failed for target account: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
output, err := ec2Client.DescribeInstances(ctx, input)
|
||||
if err != nil {
|
||||
if errors.IsDryRunSuccess(err) {
|
||||
// DryRunOperation: request would have succeeded; not a failure.
|
||||
var awsErr smithy.APIError
|
||||
ok := goerrors.As(err, &awsErr)
|
||||
if ok && awsErr.ErrorCode() == dryRunOperationErrorCode {
|
||||
// successful DryRun validation, no response necessary, no error thrown
|
||||
return nil, nil
|
||||
}
|
||||
if st, _ := errors.ParseAWSError("DescribeInstances", err); st.Code() != codes.Unknown {
|
||||
return nil, st.Err()
|
||||
}
|
||||
|
||||
if target != nil {
|
||||
return nil, fmt.Errorf("EC2 DescribeInstances failed for target account: %w", err)
|
||||
}
|
||||
@@ -839,9 +774,6 @@ func ec2ClientForTarget(
|
||||
// principal identity).
|
||||
principalCfg, err := principalCatalogState.GenerateCredentialChain(ctx)
|
||||
if err != nil {
|
||||
if st, _ := errors.ParseAWSError("EC2 client setup for target account", err); st.Code() != codes.Unknown {
|
||||
return nil, st.Err()
|
||||
}
|
||||
return nil, fmt.Errorf("principal AWS configuration could not be loaded: %w", err)
|
||||
}
|
||||
if principalCfg == nil {
|
||||
@@ -855,6 +787,9 @@ func ec2ClientForTarget(
|
||||
provider := stscreds.NewAssumeRoleProvider(stsClient, target.RoleArn, func(o *stscreds.AssumeRoleOptions) {
|
||||
if target.RoleSessionName != "" {
|
||||
o.RoleSessionName = target.RoleSessionName
|
||||
} else {
|
||||
// STS may reject the AssumeRole request if session name is not provided.
|
||||
o.RoleSessionName = defaultSessionName
|
||||
}
|
||||
if target.RoleExternalId != "" {
|
||||
o.ExternalID = &target.RoleExternalId
|
||||
@@ -893,6 +828,28 @@ func ec2ClientForTarget(
|
||||
return ec2.NewFromConfig(targetCfg, ec2Opts...), nil
|
||||
}
|
||||
|
||||
// dryRunValidation performs an AWS DescribeInstances call to verify the state's
|
||||
// credentials, the host listing functionality as well as the filters, if any
|
||||
// are passed in. This function can therefore be used for both host catalog and
|
||||
// host set validation.
|
||||
func dryRunValidation(ctx context.Context, state *awsCatalogPersistedState, ec2Opts []ec2Option, filters ...types.Filter) *status.Status {
|
||||
if state == nil {
|
||||
return status.New(codes.InvalidArgument, "persisted state is required")
|
||||
}
|
||||
|
||||
ec2Client, err := state.EC2Client(ctx, ec2Opts...)
|
||||
if err != nil {
|
||||
return status.New(codes.InvalidArgument, fmt.Sprintf("error getting EC2 client: %s", err))
|
||||
}
|
||||
|
||||
_, err = ec2Client.DescribeInstances(ctx, &ec2.DescribeInstancesInput{Filters: filters})
|
||||
if err != nil {
|
||||
return status.New(codes.FailedPrecondition, fmt.Sprintf("aws describe instances failed: %s", err))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// appendDistinct will append the elements to the slice
|
||||
// if an element is not nil, empty, and does not exist in slice.
|
||||
func appendDistinct(slice []string, elems ...*string) []string {
|
||||
|
||||
@@ -16,11 +16,8 @@ import (
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/iam"
|
||||
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
stsTypes "github.com/aws/aws-sdk-go-v2/service/sts/types"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
||||
awserrors "github.com/hashicorp/boundary-plugin-aws/internal/errors"
|
||||
"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"
|
||||
@@ -31,38 +28,6 @@ import (
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
// testRoleARN is a syntactically valid ARN used for role_arn test cases.
|
||||
// All STS calls against it are handled by injected mocks — no real AWS
|
||||
// account is contacted.
|
||||
const testRoleARN = "arn:aws:iam::123456789012:role/BoundaryTestRole"
|
||||
|
||||
// validAssumeRoleOutput returns a mock AssumeRoleOutput with synthetic
|
||||
// temporary credentials for use with WithSTSAPIFunc.
|
||||
func validAssumeRoleOutput() *sts.AssumeRoleOutput {
|
||||
return &sts.AssumeRoleOutput{
|
||||
Credentials: &stsTypes.Credentials{
|
||||
AccessKeyId: aws.String("ASIAmockassumedkey"),
|
||||
SecretAccessKey: aws.String("mockassumedsecret"),
|
||||
SessionToken: aws.String("mockassumedsessiontoken"),
|
||||
Expiration: aws.Time(time.Now().Add(time.Hour)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// catalogAttrsStruct builds a catalog attributes structpb for the given
|
||||
// region. roleArn is written only when non-empty. disableRotation must be
|
||||
// set explicitly to avoid implicit rotation behaviour in tests.
|
||||
func catalogAttrsStruct(region, roleArn string, disableRotation bool) *structpb.Struct {
|
||||
fields := map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue(region),
|
||||
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(disableRotation),
|
||||
}
|
||||
if roleArn != "" {
|
||||
fields[credential.ConstRoleArn] = structpb.NewStringValue(roleArn)
|
||||
}
|
||||
return &structpb.Struct{Fields: fields}
|
||||
}
|
||||
|
||||
|
||||
func TestPluginOnCreateCatalogSuccess(t *testing.T) {
|
||||
tests := []struct {
|
||||
@@ -173,13 +138,17 @@ func TestPluginOnCreateCatalogSuccess(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
// role_arn set, no static keys. AssumeRole mock succeeds.
|
||||
// Persisted secrets are empty — dynamic credentials are not stored.
|
||||
name: "usingAssumeRole",
|
||||
req: &pb.OnCreateCatalogRequest{
|
||||
Catalog: &hostcatalogs.HostCatalog{
|
||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||
Attributes: catalogAttrsStruct("us-east-1", testRoleARN, true),
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-east-1"),
|
||||
credential.ConstRoleArn: structpb.NewStringValue("arn:0123:test:rolearn"),
|
||||
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -189,13 +158,6 @@ func TestPluginOnCreateCatalogSuccess(t *testing.T) {
|
||||
testMockEC2WithDescribeInstancesOutput(&ec2.DescribeInstancesOutput{}),
|
||||
)),
|
||||
},
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutil.Option{
|
||||
awsutil.WithSTSAPIFunc(awsutil.NewMockSTS(
|
||||
awsutil.WithAssumeRoleOutput(validAssumeRoleOutput()),
|
||||
)),
|
||||
}),
|
||||
},
|
||||
expRsp: &pb.OnCreateCatalogResponse{Persisted: &pb.HostCatalogPersisted{Secrets: &structpb.Struct{}}},
|
||||
},
|
||||
}
|
||||
@@ -353,20 +315,6 @@ func TestPluginOnUpdateCatalogSuccess(t *testing.T) {
|
||||
testMockEC2WithDescribeInstancesOutput(&ec2.DescribeInstancesOutput{}),
|
||||
)),
|
||||
},
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutil.Option{
|
||||
awsutil.WithSTSAPIFunc(awsutil.NewMockSTS(
|
||||
awsutil.WithAssumeRoleOutput(&sts.AssumeRoleOutput{
|
||||
Credentials: &stsTypes.Credentials{
|
||||
AccessKeyId: aws.String("ASIAfoobar"),
|
||||
SecretAccessKey: aws.String("secretkeyfoobar"),
|
||||
SessionToken: aws.String("sessiontokenfoobar"),
|
||||
Expiration: aws.Time(time.Now().Add(time.Hour)),
|
||||
},
|
||||
}),
|
||||
)),
|
||||
}),
|
||||
},
|
||||
expRsp: &pb.OnUpdateCatalogResponse{
|
||||
Persisted: &pb.HostCatalogPersisted{Secrets: &structpb.Struct{}},
|
||||
},
|
||||
@@ -473,16 +421,6 @@ func TestPluginOnUpdateCatalogSuccess(t *testing.T) {
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutil.Option{
|
||||
awsutil.WithIAMAPIFunc(awsutil.NewMockIAM()),
|
||||
awsutil.WithSTSAPIFunc(awsutil.NewMockSTS(
|
||||
awsutil.WithAssumeRoleOutput(&sts.AssumeRoleOutput{
|
||||
Credentials: &stsTypes.Credentials{
|
||||
AccessKeyId: aws.String("ASIAfoobar"),
|
||||
SecretAccessKey: aws.String("secretkeyfoobar"),
|
||||
SessionToken: aws.String("sessiontokenfoobar"),
|
||||
Expiration: aws.Time(time.Now().Add(time.Hour)),
|
||||
},
|
||||
}),
|
||||
)),
|
||||
}),
|
||||
},
|
||||
expRsp: &pb.OnUpdateCatalogResponse{
|
||||
@@ -804,72 +742,8 @@ func TestPluginOnCreateCatalogErr(t *testing.T) {
|
||||
testMockEC2WithDescribeInstancesError(errors.New(testDescribeInstancesError)),
|
||||
)),
|
||||
},
|
||||
// checkHosts returns the DescribeInstances error as-is rather than
|
||||
// wrapping it in a gRPC FailedPrecondition status like the old
|
||||
// dryRunValidation did. A plain Go error maps to codes.Unknown.
|
||||
expectedErrContains: testDescribeInstancesError,
|
||||
expectedErrCode: codes.Unknown,
|
||||
},
|
||||
{
|
||||
name: "dry run status error preserves gRPC code",
|
||||
req: &pb.OnCreateCatalogRequest{
|
||||
Catalog: &hostcatalogs.HostCatalog{
|
||||
Secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
||||
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||
},
|
||||
},
|
||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
catalogOpts: []awsCatalogPersistedStateOption{
|
||||
withTestEC2APIFunc(newTestMockEC2(
|
||||
nil,
|
||||
testMockEC2WithDescribeInstancesError(awserrors.TestAwsError("AccessDenied", "not authorized to perform ec2:DescribeInstances")),
|
||||
)),
|
||||
},
|
||||
// checkHosts wraps the AWS error via ParseAWSError into a gRPC
|
||||
// PermissionDenied status. OnCreateCatalog's dry-run error handler
|
||||
// should preserve that status code, not re-wrap it as Unknown.
|
||||
expectedErrContains: "invalid credentials",
|
||||
expectedErrCode: codes.PermissionDenied,
|
||||
},
|
||||
{
|
||||
// BUG FIX: role_arn set, no static keys. AssumeRole mock returns
|
||||
// an error. ValidateCreds now eagerly calls Retrieve() which forces
|
||||
// sts:AssumeRole, so the error surfaces here at creation time.
|
||||
// Previously this succeeded silently via the ambient credential chain.
|
||||
name: "role_arn AssumeRole error — correctly rejected",
|
||||
req: &pb.OnCreateCatalogRequest{
|
||||
Catalog: &hostcatalogs.HostCatalog{
|
||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||
Attributes: catalogAttrsStruct("us-east-1", testRoleARN, true),
|
||||
},
|
||||
},
|
||||
},
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutil.Option{
|
||||
awsutil.WithSTSAPIFunc(awsutil.NewMockSTS(
|
||||
awsutil.WithAssumeRoleError(errors.New("simulated AssumeRole failure")),
|
||||
)),
|
||||
}),
|
||||
},
|
||||
catalogOpts: []awsCatalogPersistedStateOption{
|
||||
withTestEC2APIFunc(newTestMockEC2(
|
||||
nil,
|
||||
testMockEC2WithDescribeInstancesOutput(&ec2.DescribeInstancesOutput{}),
|
||||
)),
|
||||
},
|
||||
expectedErrContains: "simulated AssumeRole failure",
|
||||
expectedErrCode: codes.Unknown,
|
||||
expectedErrContains: "aws describe instances failed: DescribeInstances error",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1120,11 +994,8 @@ func TestPluginOnUpdateCatalogErr(t *testing.T) {
|
||||
testMockEC2WithDescribeInstancesError(errors.New(testDescribeInstancesError)),
|
||||
)),
|
||||
},
|
||||
//checkHosts returns the DesribeInstances errror as-is rather than wrapping it
|
||||
// in a gRpc FailedPrecodndition status like the old dryRunValidiation did.
|
||||
// A plain Go error maps to codeds.Unknown.
|
||||
expectedErrContains: testDescribeInstancesError,
|
||||
expectedErrCode: codes.Unknown,
|
||||
expectedErrContains: "aws describe instances failed: DescribeInstances error",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
},
|
||||
{
|
||||
name: "new incoming dynamic credential update dry run error",
|
||||
@@ -1159,27 +1030,14 @@ func TestPluginOnUpdateCatalogErr(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
// ValidateCreds is called for DynamicAWS credentials before the dry-run
|
||||
// DescribeInstances. Provide a mock STS that returns a successful AssumeRole
|
||||
// so the validate step passes and execution reaches the EC2 mock.
|
||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||
credential.WithStateTestOpts([]awsutil.Option{
|
||||
awsutil.WithSTSAPIFunc(awsutil.NewMockSTS(
|
||||
awsutil.WithAssumeRoleOutput(validAssumeRoleOutput()),
|
||||
)),
|
||||
}),
|
||||
},
|
||||
catalogOpts: []awsCatalogPersistedStateOption{
|
||||
withTestEC2APIFunc(newTestMockEC2(
|
||||
nil,
|
||||
testMockEC2WithDescribeInstancesError(errors.New(testDescribeInstancesError)),
|
||||
)),
|
||||
},
|
||||
// checkHosts returns the DescribeInstances error as-is rather than wrapping it
|
||||
// in a gRPC FailedPrecondition status like the old dryRunValidiation did.
|
||||
// A plain Go error maps to codes.Unknown.
|
||||
expectedErrContains: testDescribeInstancesError,
|
||||
expectedErrCode: codes.Unknown,
|
||||
expectedErrContains: "aws describe instances failed: DescribeInstances error",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
},
|
||||
{
|
||||
name: "replace creds error",
|
||||
@@ -1353,53 +1211,8 @@ func TestPluginOnUpdateCatalogErr(t *testing.T) {
|
||||
testMockEC2WithDescribeInstancesError(fmt.Errorf("oops there was an error")),
|
||||
)),
|
||||
},
|
||||
// checkHosts returns the DescribeInstances error as-is rather than wrapping it
|
||||
// in a gRPC FailedPrecondition status like the old dryRunValidiation did.
|
||||
// A plain Go error maps to codes.Unknown.
|
||||
expectedErrContains: "oops there was an error",
|
||||
expectedErrCode: codes.Unknown,
|
||||
},
|
||||
{
|
||||
name: "final dry run gRPC status error preserves code",
|
||||
req: &pb.OnUpdateCatalogRequest{
|
||||
CurrentCatalog: &hostcatalogs.HostCatalog{
|
||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
NewCatalog: &hostcatalogs.HostCatalog{
|
||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Persisted: &pb.HostCatalogPersisted{
|
||||
Secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
||||
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||
credential.ConstCredsLastRotatedTime: structpb.NewStringValue("2006-01-02T15:04:05+07:00"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
catalogOpts: []awsCatalogPersistedStateOption{
|
||||
withTestEC2APIFunc(newTestMockEC2(
|
||||
nil,
|
||||
testMockEC2WithDescribeInstancesError(awserrors.TestAwsError("AccessDenied", "not authorized to perform ec2:DescribeInstances")),
|
||||
)),
|
||||
},
|
||||
// ParseAWSError maps AccessDenied to PermissionDenied. The dry-run
|
||||
// error handler should preserve that gRPC status, not wrap it as Unknown.
|
||||
expectedErrContains: "invalid credentials",
|
||||
expectedErrCode: codes.PermissionDenied,
|
||||
expectedErrContains: "aws describe instances failed: oops there was an error",
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1752,57 +1565,8 @@ func TestPluginOnCreateSetErr(t *testing.T) {
|
||||
testMockEC2WithDescribeInstancesError(errors.New(testDescribeInstancesError)),
|
||||
)),
|
||||
},
|
||||
expectedErrContains: testDescribeInstancesError,
|
||||
expectedErrCode: codes.Unknown,
|
||||
},
|
||||
{
|
||||
name: "dry run gRPC status error preserves code",
|
||||
req: &pb.OnCreateSetRequest{
|
||||
Catalog: &hostcatalogs.HostCatalog{
|
||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Persisted: &pb.HostCatalogPersisted{
|
||||
Secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
||||
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Set: &hostsets.HostSet{
|
||||
Id: "foobar",
|
||||
Attrs: &hostsets.HostSet_Attributes{
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
ConstDescribeInstancesFilters: structpb.NewListValue(
|
||||
&structpb.ListValue{
|
||||
Values: []*structpb.Value{
|
||||
structpb.NewStringValue("tag-key=foo"),
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
catalogOpts: []awsCatalogPersistedStateOption{
|
||||
withTestEC2APIFunc(newTestMockEC2(
|
||||
nil,
|
||||
testMockEC2WithDescribeInstancesError(awserrors.TestAwsError("AccessDenied", "not authorized to perform ec2:DescribeInstances")),
|
||||
)),
|
||||
},
|
||||
// ParseAWSError maps AccessDenied to PermissionDenied. The dry-run
|
||||
// error handler should preserve that gRPC status, not wrap it as Unknown.
|
||||
expectedErrContains: "invalid credentials",
|
||||
expectedErrCode: codes.PermissionDenied,
|
||||
expectedErrContains: fmt.Sprintf("aws describe instances failed: %s", testDescribeInstancesError),
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2064,56 +1828,8 @@ func TestPluginOnUpdateSetErr(t *testing.T) {
|
||||
testMockEC2WithDescribeInstancesError(errors.New(testDescribeInstancesError)),
|
||||
)),
|
||||
},
|
||||
expectedErrContains: testDescribeInstancesError,
|
||||
expectedErrCode: codes.Unknown,
|
||||
},
|
||||
{
|
||||
name: "dry run gRPC status error preserves code",
|
||||
req: &pb.OnUpdateSetRequest{
|
||||
Catalog: &hostcatalogs.HostCatalog{
|
||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Persisted: &pb.HostCatalogPersisted{
|
||||
Secrets: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
||||
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||
},
|
||||
},
|
||||
},
|
||||
NewSet: &hostsets.HostSet{
|
||||
Attrs: &hostsets.HostSet_Attributes{
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
ConstDescribeInstancesFilters: structpb.NewListValue(
|
||||
&structpb.ListValue{
|
||||
Values: []*structpb.Value{
|
||||
structpb.NewStringValue("tag-key=foo"),
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
catalogOpts: []awsCatalogPersistedStateOption{
|
||||
withTestEC2APIFunc(newTestMockEC2(
|
||||
nil,
|
||||
testMockEC2WithDescribeInstancesError(awserrors.TestAwsError("AccessDenied", "not authorized to perform ec2:DescribeInstances")),
|
||||
)),
|
||||
},
|
||||
// ParseAWSError maps AccessDenied to PermissionDenied. The dry-run
|
||||
// error handler should preserve that gRPC status, not wrap it as Unknown.
|
||||
expectedErrContains: "invalid credentials",
|
||||
expectedErrCode: codes.PermissionDenied,
|
||||
expectedErrContains: fmt.Sprintf("aws describe instances failed: %s", testDescribeInstancesError),
|
||||
expectedErrCode: codes.FailedPrecondition,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2571,7 +2287,7 @@ func TestPluginListHostsErr(t *testing.T) {
|
||||
)),
|
||||
},
|
||||
expectedErrContains: fmt.Sprintf("error retrieving host results for host set id \"foobar\": %s", testDescribeInstancesError),
|
||||
expectedErrCode: codes.Unknown,
|
||||
expectedErrCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "target attributes missing target_role_arn",
|
||||
@@ -2637,7 +2353,7 @@ func TestPluginListHostsErr(t *testing.T) {
|
||||
}),
|
||||
},
|
||||
expectedErrContains: "unable to list hosts for host set \"foobar\": EC2 client setup failed for target account: test EC2 client error",
|
||||
expectedErrCode: codes.Unknown,
|
||||
expectedErrCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "target DescribeInstances error",
|
||||
@@ -2665,7 +2381,7 @@ func TestPluginListHostsErr(t *testing.T) {
|
||||
)),
|
||||
},
|
||||
expectedErrContains: "unable to list hosts for host set \"foobar\": EC2 DescribeInstances failed for target account: DescribeInstances error",
|
||||
expectedErrCode: codes.Unknown,
|
||||
expectedErrCode: codes.InvalidArgument,
|
||||
},
|
||||
{
|
||||
name: "awsInstanceToHost error",
|
||||
@@ -2726,115 +2442,7 @@ func TestPluginListHostsErr(t *testing.T) {
|
||||
)),
|
||||
},
|
||||
expectedErrContains: "error processing host results for host set id \"foobar\": response integrity error: missing instance id",
|
||||
expectedErrCode: codes.Unknown,
|
||||
},
|
||||
{
|
||||
name: "DescribeInstances AccessDenied",
|
||||
req: &pb.ListHostsRequest{
|
||||
Catalog: &hostcatalogs.HostCatalog{
|
||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Persisted: testListHostsPersisted(),
|
||||
Sets: []*hostsets.HostSet{
|
||||
testListHostsSet("foobar", "tag-key=foo"),
|
||||
},
|
||||
},
|
||||
catalogOpts: []awsCatalogPersistedStateOption{
|
||||
withTestEC2APIFunc(newTestMockEC2(
|
||||
nil,
|
||||
testMockEC2WithDescribeInstancesError(awserrors.TestAwsError("AccessDenied", "not authorized to perform ec2:DescribeInstances")),
|
||||
)),
|
||||
},
|
||||
expectedErrContains: "invalid credentials",
|
||||
expectedErrCode: codes.PermissionDenied,
|
||||
},
|
||||
{
|
||||
name: "DescribeInstances UnauthorizedOperation",
|
||||
req: &pb.ListHostsRequest{
|
||||
Catalog: &hostcatalogs.HostCatalog{
|
||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Persisted: testListHostsPersisted(),
|
||||
Sets: []*hostsets.HostSet{
|
||||
testListHostsSet("foobar", "tag-key=foo"),
|
||||
},
|
||||
},
|
||||
catalogOpts: []awsCatalogPersistedStateOption{
|
||||
withTestEC2APIFunc(newTestMockEC2(
|
||||
nil,
|
||||
testMockEC2WithDescribeInstancesError(awserrors.TestAwsError("UnauthorizedOperation", "You are not authorized to perform this operation.")),
|
||||
)),
|
||||
},
|
||||
expectedErrContains: "invalid credentials",
|
||||
expectedErrCode: codes.PermissionDenied,
|
||||
},
|
||||
{
|
||||
name: "target DescribeInstances AccessDenied",
|
||||
req: &pb.ListHostsRequest{
|
||||
Catalog: &hostcatalogs.HostCatalog{
|
||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
credential.ConstTargetRoleArn: structpb.NewStringValue("arn:aws:iam::222222222222:role/Target"),
|
||||
credential.ConstTargetRegion: structpb.NewStringValue("eu-west-1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Persisted: testListHostsPersisted(),
|
||||
Sets: []*hostsets.HostSet{
|
||||
testListHostsSet("foobar", "tag-key=foo"),
|
||||
},
|
||||
},
|
||||
catalogOpts: []awsCatalogPersistedStateOption{
|
||||
withTestEC2APIFunc(newTestMockEC2(
|
||||
nil,
|
||||
testMockEC2WithDescribeInstancesError(awserrors.TestAwsError("AccessDenied", "not authorized to perform ec2:DescribeInstances")),
|
||||
)),
|
||||
},
|
||||
expectedErrContains: "invalid credentials",
|
||||
expectedErrCode: codes.PermissionDenied,
|
||||
},
|
||||
{
|
||||
name: "target EC2 client AccessDenied",
|
||||
req: &pb.ListHostsRequest{
|
||||
Catalog: &hostcatalogs.HostCatalog{
|
||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||
credential.ConstTargetRoleArn: structpb.NewStringValue("arn:aws:iam::222222222222:role/Target"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Persisted: testListHostsPersisted(),
|
||||
Sets: []*hostsets.HostSet{
|
||||
testListHostsSet("foobar", "tag-key=foo"),
|
||||
},
|
||||
},
|
||||
catalogOpts: []awsCatalogPersistedStateOption{
|
||||
withTestEC2APIFunc(func(...aws.Config) (EC2API, error) {
|
||||
return nil, awserrors.TestAwsError("AccessDenied", "not authorized to assume role")
|
||||
}),
|
||||
},
|
||||
// ec2ClientForTarget returns testEC2APIFunc errors unparsed; checkHosts
|
||||
// only preserves existing gRPC statuses, so this surfaces as Unknown.
|
||||
expectedErrContains: "unable to list hosts for host set \"foobar\": EC2 client setup failed for target account: api error AccessDenied: not authorized to assume role",
|
||||
expectedErrCode: codes.Unknown,
|
||||
expectedErrCode: codes.InvalidArgument,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -3025,21 +2633,6 @@ func TestBuildDescribeInstancesInput(t *testing.T) {
|
||||
require.Equal(tc.expected, actual)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("nil attrs, dry run", func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
actual, err := buildDescribeInstancesInput(nil, true)
|
||||
require.NoError(err)
|
||||
require.Equal(&ec2.DescribeInstancesInput{
|
||||
DryRun: aws.Bool(true),
|
||||
Filters: []types.Filter{
|
||||
{
|
||||
Name: aws.String("instance-state-name"),
|
||||
Values: []string{string(types.InstanceStateNameRunning)},
|
||||
},
|
||||
},
|
||||
}, actual)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAwsInstanceToHost(t *testing.T) {
|
||||
@@ -3478,3 +3071,54 @@ func TestAppendDistinct(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDryRunValidation(t *testing.T) {
|
||||
t.Run("nil credential state", func(t *testing.T) {
|
||||
st := dryRunValidation(context.Background(), nil, nil)
|
||||
require.NotNil(t, st)
|
||||
require.Equal(t, codes.InvalidArgument.String(), st.Code().String())
|
||||
require.Equal(t, "persisted state is required", st.Message())
|
||||
})
|
||||
|
||||
t.Run("ec2ClientErr", func(t *testing.T) {
|
||||
st := dryRunValidation(context.Background(), &awsCatalogPersistedState{
|
||||
AwsCredentialPersistedState: &credential.AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{},
|
||||
},
|
||||
testEC2APIFunc: func(...aws.Config) (EC2API, error) {
|
||||
return nil, fmt.Errorf("oops ec2 client err")
|
||||
},
|
||||
}, []ec2Option{})
|
||||
require.NotNil(t, st)
|
||||
require.Equal(t, codes.InvalidArgument.String(), st.Code().String())
|
||||
require.Equal(t, "error getting EC2 client: oops ec2 client err", st.Message())
|
||||
})
|
||||
|
||||
t.Run("describeInstancesErr", func(t *testing.T) {
|
||||
st := dryRunValidation(context.Background(), &awsCatalogPersistedState{
|
||||
AwsCredentialPersistedState: &credential.AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
AccessKey: "AKIAfoo",
|
||||
SecretKey: "baz",
|
||||
},
|
||||
},
|
||||
testEC2APIFunc: newTestMockEC2(nil, testMockEC2WithDescribeInstancesError(fmt.Errorf("oops describe instances error"))),
|
||||
}, []ec2Option{})
|
||||
require.NotNil(t, st)
|
||||
require.Equal(t, codes.FailedPrecondition.String(), st.Code().String())
|
||||
require.Equal(t, "aws describe instances failed: oops describe instances error", st.Message())
|
||||
})
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
st := dryRunValidation(context.Background(), &awsCatalogPersistedState{
|
||||
AwsCredentialPersistedState: &credential.AwsCredentialPersistedState{
|
||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
||||
AccessKey: "AKIAfoo",
|
||||
SecretKey: "baz",
|
||||
},
|
||||
},
|
||||
testEC2APIFunc: newTestMockEC2(nil, testMockEC2WithDescribeInstancesOutput(&ec2.DescribeInstancesOutput{})),
|
||||
}, []ec2Option{})
|
||||
require.Nil(t, st)
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user