Compare commits

..
26 changed files with 1635 additions and 1188 deletions

No files matched your search

+34 -34
View File
@@ -1,56 +1,56 @@
module github.com/hashicorp/boundary-plugin-aws module github.com/hashicorp/boundary-plugin-aws
go 1.24.0 go 1.25.7
require ( require (
github.com/aws/aws-sdk-go-v2 v1.41.5 github.com/aws/aws-sdk-go-v2 v1.41.7
github.com/aws/aws-sdk-go-v2/service/ec2 v1.193.0 github.com/aws/aws-sdk-go-v2/service/ec2 v1.254.0
github.com/aws/aws-sdk-go-v2/service/iam v1.52.2 github.com/aws/aws-sdk-go-v2/service/iam v1.47.6
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0
github.com/aws/aws-sdk-go-v2/service/sts v1.41.2 github.com/aws/aws-sdk-go-v2/service/sts v1.38.5
github.com/aws/smithy-go v1.24.2 github.com/aws/smithy-go v1.25.1
github.com/google/go-cmp v0.7.0 github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/hashicorp/boundary/sdk v0.0.51 github.com/hashicorp/boundary/sdk v0.0.59
github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.1.2 github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.1.2
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0
github.com/hashicorp/terraform-json v0.23.0 github.com/hashicorp/terraform-json v0.23.0
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.11.1
google.golang.org/grpc v1.79.3 google.golang.org/grpc v1.79.3
google.golang.org/protobuf v1.36.10 google.golang.org/protobuf v1.36.11
) )
require ( require (
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 // indirect
github.com/aws/aws-sdk-go-v2/config v1.32.2 // indirect github.com/aws/aws-sdk-go-v2/config v1.28.5 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.2 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.46 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.2 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.5 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.18.0 // indirect github.com/fatih/color v1.18.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/eventlogger v0.2.6-0.20231025104552-802587e608f0 // indirect github.com/hashicorp/eventlogger v0.2.11 // indirect
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 // indirect github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-kms-wrapping/v2 v2.0.16 // indirect github.com/hashicorp/go-kms-wrapping/v2 v2.0.20 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.6 // indirect github.com/hashicorp/go-sockaddr v1.0.7 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
@@ -59,11 +59,11 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/zclconf/go-cty v1.15.0 // indirect github.com/zclconf/go-cty v1.15.0 // indirect
golang.org/x/crypto v0.46.0 // indirect golang.org/x/crypto v0.48.0 // indirect
golang.org/x/net v0.48.0 // indirect golang.org/x/net v0.49.0 // indirect
golang.org/x/sys v0.39.0 // indirect golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.32.0 // indirect golang.org/x/text v0.34.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
+104 -132
View File
@@ -8,48 +8,68 @@ 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/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY= github.com/aws/aws-sdk-go-v2 v1.39.1 h1:fWZhGAwVRK/fAN2tmt7ilH4PPAE11rDj7HytrmbZ2FE=
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= github.com/aws/aws-sdk-go-v2 v1.39.1/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o= github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc=
github.com/aws/aws-sdk-go-v2/config v1.32.2 h1:4liUsdEpUUPZs5WVapsJLx5NPmQhQdez7nYFcovrytk= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E=
github.com/aws/aws-sdk-go-v2/config v1.32.2/go.mod h1:l0hs06IFz1eCT+jTacU/qZtC33nvcnLADAPL/XyrkZI= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00=
github.com/aws/aws-sdk-go-v2/credentials v1.19.2 h1:qZry8VUyTK4VIo5aEdUcBjPZHL2v4FyQ3QEOaWcFLu4= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 h1:gx1AwW1Iyk9Z9dD9F4akX5gnN3QZwUB20GGKH/I+Rho=
github.com/aws/aws-sdk-go-v2/credentials v1.19.2/go.mod h1:YUqm5a1/kBnoK+/NY5WEiMocZihKSo15/tJdmdXnM5g= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10/go.mod h1:qqY157uZoqm5OXq/amuaBJyC9hgBCBQnsaWnPe905GY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA= github.com/aws/aws-sdk-go-v2/config v1.28.5 h1:Za41twdCXbuyyWv9LndXxZZv3QhTG1DinqlFsSuvtI0=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14/go.mod h1:Dadl9QO0kHgbrH1GRqGiZdYtW5w+IXXaBNCHTIaheM4= github.com/aws/aws-sdk-go-v2/config v1.28.5/go.mod h1:4VsPbHP8JdcdUDmbTVgNL/8w9SqOkM5jyY8ljIxLO3o=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4= github.com/aws/aws-sdk-go-v2/credentials v1.17.46 h1:AU7RcriIo2lXjUfHFnFKYsLCwgbz1E7Mm95ieIRDNUg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c= github.com/aws/aws-sdk-go-v2/credentials v1.17.46/go.mod h1:1FmYyLGL08KQXQ6mcTlifyFXfJVCNJTVGuQP4m0d/UA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 h1:sDSXIrlsFSFJtWKLQS4PUWRvrT580rrnuLydJrCQ/yA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps= 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/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8 h1:6bgAZgRyT4RoFWhxS+aoGMFyE0cD1bSzFnEEi4bFPGI=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= 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/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.193.0 h1:RhSoBFT5/8tTmIseJUXM6INTXTQDF8+0oyxWBnozIms= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8 h1:HhJYoES3zOz34yWEpGENqJvRVPqpmJyR3+AFg9ybhdY=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.193.0/go.mod h1:mzj8EEjIHSN2oZRXiw1Dd+uB4HZTl7hC8nBzX9IZMWw= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8/go.mod h1:JnA+hPWeYAVbDssp83tv+ysAG8lTfLVXvSsyKg/7xNA=
github.com/aws/aws-sdk-go-v2/service/iam v1.52.2 h1:li0ooCUfHIivHn8nB3LstP6HgdNefwu5gnXE4MLVz/U= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo=
github.com/aws/aws-sdk-go-v2/service/iam v1.52.2/go.mod h1:PuHz5kGh1jtsNpjezdYhRp7xgn6DzCNJJfQt7O7U9Aw= 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/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= 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/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.8 h1:1/bT9kDdLQzfZ1e6J6hpW+SfNDd6xrV8F3M2CuGyUz8=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM= 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/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24/go.mod h1:X5ZJyfwVrWA96GzPmUCWFQaEARPR7gCrpq2E92PJwAE=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM= github.com/aws/aws-sdk-go-v2/service/ec2 v1.254.0 h1:fTLR6dLDTGChAjecRPlVrKeznT0rVdzR4yn9Z68MTGk=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ= 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/s3 v1.97.3 h1:HwxWTbTrIHm5qY+CAEur0s/figc3qwvLWsNkF4RPToo= github.com/aws/aws-sdk-go-v2/service/iam v1.47.6 h1:EWehQXACWr+6hzfZPwZChlfoVhiUCfLHE0Xh3kAfzWQ=
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM= 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/signin v1.0.2 h1:MxMBdKTYBjPQChlJhi4qlEueqB1p1KcbTEa7tD5aqPs= 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/signin v1.0.2/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk= 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/sso v1.30.5 h1:ksUT5KtgpZd3SAiFJNJ0AFEJVva3gjBmN7eXUZjzUwQ= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZLt5ci0ozzgkVo8BJGwvqNaZbTWb3UcucAateA=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.5/go.mod h1:av+ArJpoYf3pgyrj6tcehSFW+y9/QvAY8kMooR9bZCw= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9/go.mod h1:w7wZ/s9qK7c8g4al+UyoF1Sp/Z45UwMGcqIzLWVQHWk=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10 h1:GtsxyiF3Nd3JahRBJbxLCCdYW9ltGQYrFWg8XdkGDd8= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.8 h1:tIN8MFT1z5STK5kTdOT1TCfMN/bn5fSEnlKsTL8qBOU=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10/go.mod h1:/j67Z5XBVDx8nZVp9EuFM9/BS5dvBznbqILGuu73hug= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.8/go.mod h1:VKS56txtNWjKI8FqD/hliL0BcshyF4ZaLBa1rm2Y+5s=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.2 h1:a5UTtD4mHBU3t0o6aHQZFJTNKVfxFWfPX7J0Lr7G+uY= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 h1:ieLCO1JxUWuxTZ1cRd0GAaeX7O6cIxnwk7tc1LsQhC4=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.2/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15/go.mod h1:e3IzZvQ3kAWNykvE0Tr0RDZCMFInMvhku3qNpcIQXhM=
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8 h1:M6JI2aGFEzYxsF6CXIuRBnkge9Wf9a2xU39rNeXgu10=
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= 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=
github.com/aws/aws-sdk-go-v2/service/sso v1.24.6/go.mod h1:WJSZH2ZvepM6t6jwu4w/Z45Eoi75lPN7DcydSRtJg6Y=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 h1:K0OQAsDywb0ltlFrZm0JHPY3yZp/S9OaoLU33S7vPS8=
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= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -65,60 +85,57 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/boundary/sdk v0.0.51 h1:a7XpHNTYaeBXRhJkc8MC0siPtfJELYH/ibjw/0UbJEg= github.com/hashicorp/boundary/sdk v0.0.59 h1:XNniO0rwgihLdF1+4qDHKN3gdta3AU1JteZResCJ8Oc=
github.com/hashicorp/boundary/sdk v0.0.51/go.mod h1:gSrMTppD30IPVsUqs4SwQowu+OlR5/+58draey++SkQ= github.com/hashicorp/boundary/sdk v0.0.59/go.mod h1:VYwVGAEaHTzihRXpzKZL6EhWBhll59Iul3PGLVL63ao=
github.com/hashicorp/cli v1.1.7 h1:/fZJ+hNdwfTSfsxMBa9WWMlfjUZbX8/LnUxgAd7lCVU=
github.com/hashicorp/cli v1.1.7/go.mod h1:e6Mfpga9OCT1vqzFuoGZiiF/KaG9CbUfO5s3ghU3YgU=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/eventlogger v0.2.6-0.20231025104552-802587e608f0 h1:f9oX8/3zxiQrfrWnBeyjDm4S02GAU02OBtCRoZOUwlo= github.com/hashicorp/eventlogger v0.2.11 h1:lkK8ARM/DrCeL5deH2yVS6p40337dESi+77p9pIfLqo=
github.com/hashicorp/eventlogger v0.2.6-0.20231025104552-802587e608f0/go.mod h1://CHt6/j+Q2lc0NlUB5af4aS2M0c0aVBg9/JfcpAyhM= github.com/hashicorp/eventlogger v0.2.11/go.mod h1:Rmc3MEopz7jUnLokZVTWAsXaEP5rqd20ObGS+pcau3U=
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 h1:iAb287bq0TaWTnhDYuN/zVqdD2EwanQg9ncVelC60Xc= github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 h1:iAb287bq0TaWTnhDYuN/zVqdD2EwanQg9ncVelC60Xc=
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0/go.mod h1:tMywUTIvdB/FXhwm6HMTt61C8/eODY6gitCHhXtyojg= github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0/go.mod h1:tMywUTIvdB/FXhwm6HMTt61C8/eODY6gitCHhXtyojg=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.7 h1:gM4OwbF16Cmfxt2QMkoGMQbRTfYFZLvDMPgU3rM3KIo= github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.8 h1:/GIUjn9GkFXMk/8/irRdbdtmx8CcyeyWdVy/E5LvzyA=
github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.7/go.mod h1:7ZMHVluyqgHgEuTADeDzFNWoA9mnyPfdiK8Tk2Bct1c= github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.8/go.mod h1:JDc9UOD4EVRDIwPVethJcT5Ibi/Nas6eQDPtA60iwP0=
github.com/hashicorp/go-kms-wrapping/v2 v2.0.16 h1:WZeXfD26QMWYC35at25KgE021SF9L3u9UMHK8fJAdV0= github.com/hashicorp/go-kms-wrapping/v2 v2.0.20 h1:afreZ1WJd0WI7v4NsMZ1aL7V/T59sxPuKmQDgGUja20=
github.com/hashicorp/go-kms-wrapping/v2 v2.0.16/go.mod h1:ZiKZctjRTLEppuRwrttWkp71VYMbTTCkazK4xT7U/NQ= github.com/hashicorp/go-kms-wrapping/v2 v2.0.20/go.mod h1:NeK2Ul15t1zutp/dZzt28XQrGZHosbxE/QLNNfaWObM=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA=
github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8=
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 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/awsutil/v2 v2.1.2/go.mod h1:6+rVulOPNCQbL3Xv2iLCqM0JmU2WO2wRzP1C6hBKeB8=
github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng= github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng=
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.11 h1:uPW2Wn0YlmI9RGSkZpcIplnVRwJ7BCiGpk1vnF2TMw4= github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.13 h1:TayxZ5drfMP0G6T++WvnLESGLOWeHtdWDWWTAi2e3Qk=
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.11/go.mod h1:uis9dCmOzXuOaRyXq+1Foh31kcvXKoWogjNnhfjHfW8= github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.13/go.mod h1:NDRQ/F3DXTylqjORAP0cA+puH/JFrLlT+NlqxHK2/e8=
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.9 h1:0S0ctJ7Ra8O7ap+/3fZUnzJ3VzJyirWS/WnNCuOYtZY= github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.10 h1:2iDz+t0JLl1W0tJhvmhsh/UBgT1JgC8Qxz8HxYMWXQo=
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.9/go.mod h1:TNNdgtjLgVDbrgFcyCKrlAicIl3dZF94swJltyGUX2M= github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.10/go.mod h1:eZkXE+osawMrAWR4wJRmyKauUwH6mNGbjFuiDujnbPk=
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM= github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM=
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0= github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=
github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.6 h1:ZYv2XA+tEfFXIToR2jmBgVqQU9gERt0APbWqmUoNGnY= github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.8 h1:pgSicufBI+MvaIG9Keykb3k9B3sWtDNjmsoyrl2/8Qw=
github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.6/go.mod h1:ggFN8dlaLWS2R1gymBbCrvXM/bkZP7hEAa4seqDwhyg= github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.8/go.mod h1:sBcjk+paCXCMR9HHLcrYSfPz2FsskD8MQuotKiyM2aA=
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 h1:SMGUnbpAcat8rIKHkBPjfv81yC46a8eCNZ2hsR2l1EI= github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 h1:SMGUnbpAcat8rIKHkBPjfv81yC46a8eCNZ2hsR2l1EI=
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1/go.mod h1:Ch/bf00Qnx77MZd49JRgHYqHQjtEmTgGU2faufpVZb0= github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1/go.mod h1:Ch/bf00Qnx77MZd49JRgHYqHQjtEmTgGU2faufpVZb0=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3 h1:xbrxd0U9XQW8qL1BAz2XrAjAF/P2vcqUTAues9c24B8= github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3 h1:xbrxd0U9XQW8qL1BAz2XrAjAF/P2vcqUTAues9c24B8=
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3/go.mod h1:LWq2Sy8UoKKuK4lFuCNWSjJj57MhNNf2zzBWMtkAIX4= github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3/go.mod h1:LWq2Sy8UoKKuK4lFuCNWSjJj57MhNNf2zzBWMtkAIX4=
github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I= github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=
github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
@@ -127,20 +144,16 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI= github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI=
github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c= github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f h1:E87tDTVS5W65euzixn7clSzK66puSt1H4I5SC0EmHH4= github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f h1:E87tDTVS5W65euzixn7clSzK66puSt1H4I5SC0EmHH4=
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f/go.mod h1:3J2qVK16Lq8V+wfiL2lPeDZ7UWMxk5LemerHa1p6N00= github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f/go.mod h1:3J2qVK16Lq8V+wfiL2lPeDZ7UWMxk5LemerHa1p6N00=
github.com/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 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 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= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@@ -151,8 +164,6 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng=
github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
@@ -168,7 +179,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
@@ -178,12 +188,9 @@ github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= 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= 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 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
@@ -198,69 +205,34 @@ go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2W
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= 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 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= 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-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= 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 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 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= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48=
+1 -1
View File
@@ -130,7 +130,7 @@ func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes,
cfg, err := awsutil.NewCredentialsConfig(opts...) cfg, err := awsutil.NewCredentialsConfig(opts...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("%s", err.Error()) return nil, errors.BadRequestStatus(err.Error())
} }
return cfg, nil return cfg, nil
+73 -10
View File
@@ -16,6 +16,7 @@ import (
"github.com/aws/aws-sdk-go-v2/aws/retry" "github.com/aws/aws-sdk-go-v2/aws/retry"
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http" awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/aws/smithy-go" "github.com/aws/smithy-go"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin" pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
@@ -52,6 +53,10 @@ const (
// whose key does not exist // whose key does not exist
awsErrorNoSuchKey = "NoSuchKey" awsErrorNoSuchKey = "NoSuchKey"
// NotFound is returned when attempting to interact with an S3 object
// whose key does not exist
awsErrorNotFound = "NotFound"
// The InvalidObjectState error is returned when trying to // The InvalidObjectState error is returned when trying to
// access an object that was moved into cold storage. // access an object that was moved into cold storage.
awsErrorInvalidObjectState = "InvalidObjectState" awsErrorInvalidObjectState = "InvalidObjectState"
@@ -73,7 +78,7 @@ func InvalidArgumentError(msg string, f map[string]string) error {
sort.Strings(fieldMsgs) sort.Strings(fieldMsgs)
msg = fmt.Sprintf("%s: [%s]", msg, strings.Join(fieldMsgs, ", ")) msg = fmt.Sprintf("%s: [%s]", msg, strings.Join(fieldMsgs, ", "))
} }
return BadRequestStatus("%s", msg) return BadRequestStatus(msg)
} }
// ParseAWSError converts an aws service error into a RPC status. This method // ParseAWSError converts an aws service error into a RPC status. This method
@@ -118,7 +123,50 @@ func ParseAWSError(op string, err error) (st *status.Status, permission *pb.Perm
return status.New(codes.Unavailable, statusMsg), nil return status.New(codes.Unavailable, statusMsg), nil
} }
// parse some specific aws error codes that we have special cred states for // match error type against NotFound
var notFoundErr *types.NotFound
if errors.As(err, &notFoundErr) {
statusMsg := fmt.Sprintf("aws service %s: %s", serviceName, msg)
return status.New(codes.NotFound, statusMsg), nil
}
// match error type against NoSuchkey
var noSuchKeyErr *types.NoSuchKey
if errors.As(err, &noSuchKeyErr) {
statusMsg := fmt.Sprintf("aws service %s: %s", serviceName, msg)
return status.New(codes.NotFound, statusMsg), nil
}
// match error type against InvalidObjectState
var invalidObjectStateErr *types.InvalidObjectState
if errors.As(err, &invalidObjectStateErr) {
statusMsg := fmt.Sprintf("aws service %s: %s", serviceName, msg)
return status.New(codes.NotFound, statusMsg), nil
}
// match error type against NoSuchBucket
var noSuchBucketErr *types.NoSuchBucket
if errors.As(err, &noSuchBucketErr) {
statusMsg := fmt.Sprintf("aws service %s: %s", serviceName, msg)
return status.New(codes.NotFound, statusMsg), &pb.Permission{
State: pb.StateType_STATE_TYPE_ERROR,
ErrorDetails: noSuchBucketErr.ErrorMessage(),
CheckedAt: timestamppb.Now(),
}
}
// match error type against AccessDenied
var accessDeniedErr *types.AccessDenied
if errors.As(err, &accessDeniedErr) {
statusMsg := fmt.Sprintf("aws service %s: invalid credentials: %s", serviceName, msg)
return status.New(codes.PermissionDenied, statusMsg), &pb.Permission{
State: pb.StateType_STATE_TYPE_ERROR,
ErrorDetails: accessDeniedErr.ErrorMessage(),
CheckedAt: timestamppb.Now(),
}
}
// fallback to evaluating the smithy APIError code
var apiErr smithy.APIError var apiErr smithy.APIError
if errors.As(err, &apiErr) { if errors.As(err, &apiErr) {
switch apiErr.ErrorCode() { switch apiErr.ErrorCode() {
@@ -145,6 +193,8 @@ func ParseAWSError(op string, err error) (st *status.Status, permission *pb.Perm
return status.New(codes.Aborted, statusMsg), nil return status.New(codes.Aborted, statusMsg), nil
case awsErrorNoSuchKey: case awsErrorNoSuchKey:
fallthrough fallthrough
case awsErrorNotFound:
fallthrough
case awsErrorInvalidObjectState: case awsErrorInvalidObjectState:
statusMsg := fmt.Sprintf("aws service %s: %s", serviceName, msg) statusMsg := fmt.Sprintf("aws service %s: %s", serviceName, msg)
return status.New(codes.NotFound, statusMsg), nil return status.New(codes.NotFound, statusMsg), nil
@@ -156,8 +206,9 @@ func ParseAWSError(op string, err error) (st *status.Status, permission *pb.Perm
} }
} }
// default to evaluating the http status code // fallback to evaluating the http status code
if httpErr, ok := err.(*awshttp.ResponseError); ok { var httpErr *awshttp.ResponseError
if errors.As(err, &httpErr) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
_, err = buf.ReadFrom(httpErr.Response.Body) _, err = buf.ReadFrom(httpErr.Response.Body)
if err != nil { if err != nil {
@@ -206,14 +257,26 @@ func ParseAWSError(op string, err error) (st *status.Status, permission *pb.Perm
return st, nil return st, nil
} }
// BadRequestStatus returns a status error with an invalid // BadRequestStatusf returns a status error with an invalid
// argument code // argument code and a formatted error message.
func BadRequestStatus(format string, args ...any) error { func BadRequestStatusf(format string, args ...any) error {
return status.New(codes.InvalidArgument, fmt.Sprintf(format, args...)).Err() return status.New(codes.InvalidArgument, fmt.Sprintf(format, args...)).Err()
} }
// UnknownStatus returns a status error with an internal // BadRequestStatus returns a status error with an invalid
// error code // argument code and the provided message.
func UnknownStatus(format string, args ...any) error { func BadRequestStatus(msg string) error {
return status.New(codes.InvalidArgument, msg).Err()
}
// UnknownStatusf returns a status error with an internal
// error code and a formatted error message.
func UnknownStatusf(format string, args ...any) error {
return status.New(codes.Internal, fmt.Sprintf(format, args...)).Err() return status.New(codes.Internal, fmt.Sprintf(format, args...)).Err()
} }
// UnknownStatus returns a status error with an internal
// error code and the provided message.
func UnknownStatus(msg string) error {
return status.New(codes.Internal, msg).Err()
}
+82 -4
View File
@@ -134,6 +134,17 @@ func Test_ParseAWSError(t *testing.T) {
State: pb.StateType_STATE_TYPE_UNKNOWN, State: pb.StateType_STATE_TYPE_UNKNOWN,
}, },
}, },
{
name: "wrapped-http-status-code-forbidden",
err: fmt.Errorf("wrap: %w", TestAwsHttpResponseError(http.StatusForbidden, "forbidden request")),
expectedStatusCode: codes.PermissionDenied,
expectedStatusMsg: "aws service unknown: invalid credentials: test",
expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_ERROR,
ErrorDetails: "forbidden request",
CheckedAt: timestamppb.Now(),
},
},
{ {
name: "aws-throttling", name: "aws-throttling",
err: TestAwsError("Throttling", "throttling error"), err: TestAwsError("Throttling", "throttling error"),
@@ -311,6 +322,55 @@ func Test_ParseAWSError(t *testing.T) {
CheckedAt: timestamppb.Now(), CheckedAt: timestamppb.Now(),
}, },
}, },
{
name: "aws-s3-typed-not-found",
err: TestAwsS3NotFoundError("object was not found"),
expectedStatusCode: codes.NotFound,
expectedStatusMsg: "aws service unknown: test",
expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN,
},
},
{
name: "aws-s3-typed-no-such-key",
err: TestAwsS3NoSuchKeyError("key does not exist"),
expectedStatusCode: codes.NotFound,
expectedStatusMsg: "aws service unknown: test",
expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN,
},
},
{
name: "aws-s3-typed-invalid-object-state",
err: TestAwsS3InvalidObjectStateError("object is archived"),
expectedStatusCode: codes.NotFound,
expectedStatusMsg: "aws service unknown: test",
expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN,
},
},
{
name: "aws-s3-typed-no-such-bucket",
err: TestAwsS3NoSuchBucketError("bucket does not exist"),
expectedStatusCode: codes.NotFound,
expectedStatusMsg: "aws service unknown: test",
expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_ERROR,
ErrorDetails: "bucket does not exist",
CheckedAt: timestamppb.Now(),
},
},
{
name: "aws-s3-typed-access-denied",
err: TestAwsS3AccessDeniedError("access denied"),
expectedStatusCode: codes.PermissionDenied,
expectedStatusMsg: "aws service unknown: invalid credentials: test",
expectedPermission: &pb.Permission{
State: pb.StateType_STATE_TYPE_ERROR,
ErrorDetails: "access denied",
CheckedAt: timestamppb.Now(),
},
},
} }
for _, tt := range cases { for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@@ -330,18 +390,36 @@ func Test_ParseAWSError(t *testing.T) {
} }
} }
func Test_BadRequestStatus(t *testing.T) { func Test_BadRequestStatusf(t *testing.T) {
require, assert := require.New(t), assert.New(t) require, assert := require.New(t), assert.New(t)
err := BadRequestStatus("test: %s", "hello world") err := BadRequestStatusf("test: %s", "hello world")
assert.ErrorContains(err, "test: hello world") assert.ErrorContains(err, "test: hello world")
assert.Equal(status.Code(err), codes.InvalidArgument) assert.Equal(status.Code(err), codes.InvalidArgument)
_, ok := status.FromError(err) _, ok := status.FromError(err)
require.True(ok) require.True(ok)
} }
func Test_UnknownStatus(t *testing.T) { func Test_BadRequestStatus(t *testing.T) {
require, assert := require.New(t), assert.New(t) require, assert := require.New(t), assert.New(t)
err := UnknownStatus("test: %s", "hello world") err := BadRequestStatus("test: hello world")
assert.ErrorContains(err, "test: hello world")
assert.Equal(status.Code(err), codes.InvalidArgument)
_, ok := status.FromError(err)
require.True(ok)
}
func Test_UnknownStatusf(t *testing.T) {
require, assert := require.New(t), assert.New(t)
err := UnknownStatusf("test: %s", "hello world")
assert.ErrorContains(err, "test: hello world")
assert.Equal(status.Code(err), codes.Internal)
_, ok := status.FromError(err)
require.True(ok)
}
func Test_UnknownStatus(t *testing.T) {
require, assert := require.New(t), assert.New(t)
err := UnknownStatusf("test: hello world")
assert.ErrorContains(err, "test: hello world") assert.ErrorContains(err, "test: hello world")
assert.Equal(status.Code(err), codes.Internal) assert.Equal(status.Code(err), codes.Internal)
_, ok := status.FromError(err) _, ok := status.FromError(err)
+26
View File
@@ -9,6 +9,7 @@ import (
"strings" "strings"
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http" awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/aws/smithy-go" "github.com/aws/smithy-go"
smithyhttp "github.com/aws/smithy-go/transport/http" smithyhttp "github.com/aws/smithy-go/transport/http"
pb "github.com/hashicorp/boundary/sdk/pbs/plugin" pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
@@ -39,6 +40,31 @@ func TestAwsError(code, msg string) error {
} }
} }
// TestAwsS3NotFoundError returns an S3 NotFound error.
func TestAwsS3NotFoundError(msg string) error {
return &types.NotFound{Message: &msg}
}
// TestAwsS3NoSuchKeyError returns an S3 NoSuchKey error.
func TestAwsS3NoSuchKeyError(msg string) error {
return &types.NoSuchKey{Message: &msg}
}
// TestAwsS3InvalidObjectStateError returns an S3 InvalidObjectState error.
func TestAwsS3InvalidObjectStateError(msg string) error {
return &types.InvalidObjectState{Message: &msg}
}
// TestAwsS3NoSuchBucketError returns an S3 NoSuchBucket error.
func TestAwsS3NoSuchBucketError(msg string) error {
return &types.NoSuchBucket{Message: &msg}
}
// TestAwsS3AccessDeniedError returns an S3 AccessDenied error.
func TestAwsS3AccessDeniedError(msg string) error {
return &types.AccessDenied{Message: &msg}
}
func CheckSimilarPermission(assert *assert.Assertions, expected, actual *pb.Permission, substituteNilWithDefaultOk bool) { func CheckSimilarPermission(assert *assert.Assertions, expected, actual *pb.Permission, substituteNilWithDefaultOk bool) {
if expected == nil && substituteNilWithDefaultOk { if expected == nil && substituteNilWithDefaultOk {
expected = &pb.Permission{ expected = &pb.Permission{
+1 -40
View File
@@ -16,36 +16,6 @@ boundary host-catalogs create plugin \
-secret secret_access_key='SECRET' -secret secret_access_key='SECRET'
``` ```
To create a host catalog that returns addresses only from the primary network
interface:
```
boundary host-catalogs create plugin \
-scope-id p_1234567890 \
-name "Example Plugin-Based Host Catalog" \
-description "Description for plugin-based host catalog" \
-plugin-name aws \
-attr region=REGION \
-attr primary_interface_only=true \
-secret access_key_id='KEY' \
-secret secret_access_key='SECRET'
```
To create a host catalog that excludes public IPv4 addresses while keeping
private IPv4 addresses:
```
boundary host-catalogs create plugin \
-scope-id p_1234567890 \
-name "Example Plugin-Based Host Catalog" \
-description "Description for plugin-based host catalog" \
-plugin-name aws \
-attr region=REGION \
-attr exclude_public_ips=true \
-secret access_key_id='KEY' \
-secret secret_access_key='SECRET'
```
To create a host set, filtering the host set based on tag keys `foo` or `bar` To create a host set, filtering the host set based on tag keys `foo` or `bar`
(either tag can be present), ensuring that any targets set to this host set only (either tag can be present), ensuring that any targets set to this host set only
connect to external addresses in the `54.0.0.0/8` class A subnet: connect to external addresses in the `54.0.0.0/8` class A subnet:
@@ -105,6 +75,7 @@ a configured IAM user for this provider:
"Resource": "*" "Resource": "*"
} }
] ]
}
``` ```
* `iam:GetUser`, `iam:CreateAccessKey`, and `iam:DeleteAccessKey`, configured to * `iam:GetUser`, `iam:CreateAccessKey`, and `iam:DeleteAccessKey`, configured to
@@ -145,16 +116,6 @@ The following `attributes` are valid on an AWS host catalog resource:
provider. provider.
- `role_tags` (object): The key-value pair tags configured for the `AssumeRole` - `role_tags` (object): The key-value pair tags configured for the `AssumeRole`
provider. provider.
- `primary_interface_only` (bool): If `true`, only addresses from the primary
ENI are returned. When `false` or omitted, addresses from all attached ENIs
are considered.
- `exclude_private_ips` (bool): If `true`, private IPv4 addresses are omitted.
- `exclude_public_ips` (bool): If `true`, public IPv4 addresses are omitted.
If none of the exclusion flags are set, the plugin preserves the historical
behavior and returns all discovered address classes. The exclusion flags apply
to both IPv4 and IPv6 addresses based on whether the IP is classified as public
or private.
The following `secrets` are required on an AWS host catalog resource: The following `secrets` are required on an AWS host catalog resource:
+10 -44
View File
@@ -23,17 +23,10 @@ type CatalogAttributes struct {
// DualStack is used for configuring how the aws client will resolve requests. // DualStack is used for configuring how the aws client will resolve requests.
DualStack bool DualStack bool
// PrimaryInterfaceOnly restricts discovery to the instance's primary ENI. // InstanceAddressesOnly specifies whether only the instance addresses (private IPv4/DNS,
PrimaryInterfaceOnly bool // public IPv4/DNS, and IPv6) are synced, or all addresses, including those from
// secondary ENIs, are synced.
// ExcludePrivateIps controls whether private IPv4 addresses are omitted. InstanceAddressesOnly bool
ExcludePrivateIps bool
// ExcludePublicIps controls whether public IPv4 addresses are omitted.
ExcludePublicIps bool
// ExcludeIpv6 controls whether IPv6 addresses are omitted.
ExcludeIpv6 bool
} }
func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) { func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) {
@@ -52,35 +45,11 @@ func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) {
} }
delete(unknownFields, ConstAwsDualStack) delete(unknownFields, ConstAwsDualStack)
primaryInterfaceOnly, err := values.GetBoolValue(in, ConstPrimaryInterfaceOnly, false) instanceAddressesOnly, err := values.GetBoolValue(in, ConstInstanceAddressesOnly, false)
if err != nil { if err != nil {
badFields[fmt.Sprintf("attributes.%s", ConstPrimaryInterfaceOnly)] = err.Error() badFields[fmt.Sprintf("attributes.%s", ConstInstanceAddressesOnly)] = err.Error()
}
delete(unknownFields, ConstPrimaryInterfaceOnly)
excludePrivateIPs, err := values.GetBoolValue(in, ConstExcludePrivateIps, false)
if err != nil {
badFields[fmt.Sprintf("attributes.%s", ConstExcludePrivateIps)] = err.Error()
}
delete(unknownFields, ConstExcludePrivateIps)
excludePublicIPs, err := values.GetBoolValue(in, ConstExcludePublicIps, false)
if err != nil {
badFields[fmt.Sprintf("attributes.%s", ConstExcludePublicIps)] = err.Error()
}
delete(unknownFields, ConstExcludePublicIps)
excludeIpv6, err := values.GetBoolValue(in, ConstExcludeIpv6, false)
if err != nil {
badFields[fmt.Sprintf("attributes.%s", ConstExcludeIpv6)] = err.Error()
}
delete(unknownFields, ConstExcludeIpv6)
if excludePrivateIPs && excludePublicIPs && excludeIpv6 {
badFields[fmt.Sprintf("attributes.%s", ConstExcludePrivateIps)] = "cannot be combined with exclude_public_ips and exclude_ipv6"
badFields[fmt.Sprintf("attributes.%s", ConstExcludePublicIps)] = "cannot be combined with exclude_private_ips and exclude_ipv6"
badFields[fmt.Sprintf("attributes.%s", ConstExcludeIpv6)] = "cannot be combined with exclude_private_ips and exclude_public_ips"
} }
delete(unknownFields, ConstInstanceAddressesOnly)
for s := range unknownFields { for s := range unknownFields {
switch s { switch s {
@@ -107,12 +76,9 @@ func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) {
} }
return &CatalogAttributes{ return &CatalogAttributes{
CredentialAttributes: credAttributes, CredentialAttributes: credAttributes,
DualStack: dualStack, DualStack: dualStack,
PrimaryInterfaceOnly: primaryInterfaceOnly, InstanceAddressesOnly: instanceAddressesOnly,
ExcludePrivateIps: excludePrivateIPs,
ExcludePublicIps: excludePublicIPs,
ExcludeIpv6: excludeIpv6,
}, nil }, nil
} }
+4 -88
View File
@@ -60,11 +60,11 @@ func TestGetCatalogAttributes(t *testing.T) {
}, },
}, },
{ {
name: "with primary interface only", name: "withInstanceAddressesOnly",
in: &structpb.Struct{ in: &structpb.Struct{
Fields: map[string]*structpb.Value{ Fields: map[string]*structpb.Value{
"region": structpb.NewStringValue("us-west-2"), "region": structpb.NewStringValue("us-west-2"),
"primary_interface_only": structpb.NewBoolValue(true), "instance_addresses_only": structpb.NewBoolValue(true),
}, },
}, },
expected: &CatalogAttributes{ expected: &CatalogAttributes{
@@ -72,93 +72,9 @@ func TestGetCatalogAttributes(t *testing.T) {
Region: "us-west-2", Region: "us-west-2",
DisableCredentialRotation: false, DisableCredentialRotation: false,
}, },
PrimaryInterfaceOnly: true, InstanceAddressesOnly: true,
}, },
}, },
{
name: "with exclude public ip",
in: &structpb.Struct{
Fields: map[string]*structpb.Value{
"region": structpb.NewStringValue("us-west-2"),
"exclude_public_ips": structpb.NewBoolValue(true),
},
},
expected: &CatalogAttributes{
CredentialAttributes: &credential.CredentialAttributes{
Region: "us-west-2",
DisableCredentialRotation: false,
},
ExcludePublicIps: true,
},
},
{
name: "with exclude private ip",
in: &structpb.Struct{
Fields: map[string]*structpb.Value{
"region": structpb.NewStringValue("us-west-2"),
"exclude_private_ips": structpb.NewBoolValue(true),
},
},
expected: &CatalogAttributes{
CredentialAttributes: &credential.CredentialAttributes{
Region: "us-west-2",
DisableCredentialRotation: false,
},
ExcludePrivateIps: true,
},
},
{
name: "with mixed attributes",
in: &structpb.Struct{
Fields: map[string]*structpb.Value{
"region": structpb.NewStringValue("us-west-2"),
"dual_stack": structpb.NewBoolValue(true),
"exclude_private_ips": structpb.NewBoolValue(true),
"exclude_ipv6": structpb.NewBoolValue(true),
"primary_interface_only": structpb.NewBoolValue(true),
},
},
expected: &CatalogAttributes{
CredentialAttributes: &credential.CredentialAttributes{
Region: "us-west-2",
DisableCredentialRotation: false,
},
DualStack: true,
ExcludePrivateIps: true,
ExcludeIpv6: true,
PrimaryInterfaceOnly: true,
},
},
{
name: "can exclude public and private IPs when IPv6 remains enabled",
in: &structpb.Struct{
Fields: map[string]*structpb.Value{
"region": structpb.NewStringValue("us-west-2"),
"exclude_private_ips": structpb.NewBoolValue(true),
"exclude_public_ips": structpb.NewBoolValue(true),
},
},
expected: &CatalogAttributes{
CredentialAttributes: &credential.CredentialAttributes{
Region: "us-west-2",
DisableCredentialRotation: false,
},
ExcludePrivateIps: true,
ExcludePublicIps: true,
},
},
{
name: "cannot exclude public private and IPv6 together",
in: &structpb.Struct{
Fields: map[string]*structpb.Value{
"region": structpb.NewStringValue("us-west-2"),
"exclude_private_ips": structpb.NewBoolValue(true),
"exclude_public_ips": structpb.NewBoolValue(true),
"exclude_ipv6": structpb.NewBoolValue(true),
},
},
expectedErrContains: "attributes.exclude_ipv6: cannot be combined with exclude_private_ips and exclude_public_ips, attributes.exclude_private_ips: cannot be combined with exclude_public_ips and exclude_ipv6, attributes.exclude_public_ips: cannot be combined with exclude_private_ips and exclude_ipv6",
},
{ {
name: "default", name: "default",
in: &structpb.Struct{ in: &structpb.Struct{
+4 -11
View File
@@ -14,15 +14,8 @@ const (
// ConstAwsDualStack is the key for the dualstack flag in the aws s3 client. // ConstAwsDualStack is the key for the dualstack flag in the aws s3 client.
ConstAwsDualStack = "dual_stack" ConstAwsDualStack = "dual_stack"
// ConstPrimaryInterfaceOnly limits host discovery to the primary ENI. // ConstInstanceAddressesOnly is the key (instance_addresses_only) that specifies whether
ConstPrimaryInterfaceOnly = "primary_interface_only" // only the instance addresses (private IPv4/DNS, public IPv4/DNS, and IPv6) are synced,
// or all addresses, including those from secondary ENIs, are synced.
// ConstExcludePrivateIps excludes private IPv4 addresses. ConstInstanceAddressesOnly = "instance_addresses_only"
ConstExcludePrivateIps = "exclude_private_ips"
// ConstExcludePublicIps excludes public IPv4 addresses associated to ENI IPs.
ConstExcludePublicIps = "exclude_public_ips"
// ConstExcludeIpv6 excludes IPv6 addresses.
ConstExcludeIpv6 = "exclude_ipv6"
) )
+40 -60
View File
@@ -1,8 +1,8 @@
package host
// Copyright IBM Corp. 2021, 2026 // Copyright IBM Corp. 2021, 2026
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
package host
import ( import (
"context" "context"
"fmt" "fmt"
@@ -63,7 +63,7 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo
}, p.testCredStateOpts...)..., }, p.testCredStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err) return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
} }
// Try to rotate AWS static credentials // Try to rotate AWS static credentials
@@ -79,7 +79,7 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo
}, p.testCatalogStateOpts...)..., }, p.testCatalogStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err) return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
} }
// perform dry run to ensure we can interact with AWS as expected. // perform dry run to ensure we can interact with AWS as expected.
@@ -93,7 +93,7 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo
persistedProto, err := catalogState.toProto() persistedProto, err := catalogState.toProto()
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("%s", err.Error()) return nil, errors.BadRequestStatusf("error converting persisted state to proto: %s", err)
} }
return &pb.OnCreateCatalogResponse{ return &pb.OnCreateCatalogResponse{
@@ -138,7 +138,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
oldCatalogAttributes.DualStack, oldCatalogAttributes.DualStack,
p.testCredStateOpts...) p.testCredStateOpts...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
} }
// Verify the incoming credentials are valid and return any errors to the // Verify the incoming credentials are valid and return any errors to the
@@ -161,7 +161,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
}, p.testCredStateOpts...)..., }, p.testCredStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error setting up new credential persisted state: %s", err) return nil, errors.BadRequestStatusf("error setting up new credential persisted state: %s", err)
} }
newCatalogState, err := newAwsCatalogPersistedState( newCatalogState, err := newAwsCatalogPersistedState(
append([]awsCatalogPersistedStateOption{ append([]awsCatalogPersistedStateOption{
@@ -169,7 +169,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
}, p.testCatalogStateOpts...)..., }, p.testCatalogStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
} }
opts := []ec2Option{} opts := []ec2Option{}
if newCatalogAttributes.DualStack { if newCatalogAttributes.DualStack {
@@ -213,7 +213,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
}, p.testCatalogStateOpts...)..., }, p.testCatalogStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
} }
// perform dry run to ensure we can interact with AWS as expected. // perform dry run to ensure we can interact with AWS as expected.
@@ -227,7 +227,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
persistedProto, err := catalogState.toProto() persistedProto, err := catalogState.toProto()
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("%s", err.Error()) return nil, errors.BadRequestStatusf("error converting persisted state to proto: %s", err)
} }
return &pb.OnUpdateCatalogResponse{ return &pb.OnUpdateCatalogResponse{
@@ -264,7 +264,7 @@ func (p *HostPlugin) OnDeleteCatalog(ctx context.Context, req *pb.OnDeleteCatalo
catalogAttributes.DualStack, catalogAttributes.DualStack,
p.testCredStateOpts...) p.testCredStateOpts...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
} }
_, err = newAwsCatalogPersistedState( _, err = newAwsCatalogPersistedState(
@@ -273,7 +273,7 @@ func (p *HostPlugin) OnDeleteCatalog(ctx context.Context, req *pb.OnDeleteCatalo
}, p.testCatalogStateOpts...)..., }, p.testCatalogStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
} }
// try to delete static credentials // try to delete static credentials
@@ -341,7 +341,7 @@ func (p *HostPlugin) OnCreateSet(ctx context.Context, req *pb.OnCreateSetRequest
catalogAttributes.DualStack, catalogAttributes.DualStack,
p.testCredStateOpts...) p.testCredStateOpts...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
} }
catalogState, err := newAwsCatalogPersistedState( catalogState, err := newAwsCatalogPersistedState(
@@ -350,7 +350,7 @@ func (p *HostPlugin) OnCreateSet(ctx context.Context, req *pb.OnCreateSetRequest
}, p.testCatalogStateOpts...)..., }, p.testCatalogStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
} }
set := req.GetSet() set := req.GetSet()
@@ -368,7 +368,7 @@ func (p *HostPlugin) OnCreateSet(ctx context.Context, req *pb.OnCreateSetRequest
describeInstanceFilters, err := buildFilters(setAttrs) describeInstanceFilters, err := buildFilters(setAttrs)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error building set filters: %s", err) return nil, errors.BadRequestStatusf("error building set filters: %s", err)
} }
opts := []ec2Option{} opts := []ec2Option{}
@@ -404,7 +404,7 @@ func (p *HostPlugin) OnUpdateSet(ctx context.Context, req *pb.OnUpdateSetRequest
catalogAttributes.DualStack, catalogAttributes.DualStack,
p.testCredStateOpts...) p.testCredStateOpts...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
} }
catalogState, err := newAwsCatalogPersistedState( catalogState, err := newAwsCatalogPersistedState(
@@ -413,7 +413,7 @@ func (p *HostPlugin) OnUpdateSet(ctx context.Context, req *pb.OnUpdateSetRequest
}, p.testCatalogStateOpts...)..., }, p.testCatalogStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
} }
// As with catalog, we don't need to really look at the old host // As with catalog, we don't need to really look at the old host
@@ -433,7 +433,7 @@ func (p *HostPlugin) OnUpdateSet(ctx context.Context, req *pb.OnUpdateSetRequest
describeInstanceFilters, err := buildFilters(setAttrs) describeInstanceFilters, err := buildFilters(setAttrs)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error building set filters: %s", err) return nil, errors.BadRequestStatusf("error building set filters: %s", err)
} }
opts := []ec2Option{} opts := []ec2Option{}
@@ -477,7 +477,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
catalogAttributes.DualStack, catalogAttributes.DualStack,
p.testCredStateOpts...) p.testCredStateOpts...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
} }
catalogState, err := newAwsCatalogPersistedState( catalogState, err := newAwsCatalogPersistedState(
@@ -486,7 +486,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
}, p.testCatalogStateOpts...)..., }, p.testCatalogStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
} }
sets := req.GetSets() sets := req.GetSets()
@@ -510,7 +510,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
} }
if set.GetAttributes() == nil { if set.GetAttributes() == nil {
return nil, errors.BadRequestStatus("set %s attributes are required", set.GetId()) return nil, errors.BadRequestStatusf("set %s attributes are required", set.GetId())
} }
setAttrs, err := getSetAttributes(set.GetAttributes()) setAttrs, err := getSetAttributes(set.GetAttributes())
if err != nil { if err != nil {
@@ -519,7 +519,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
input, err := buildDescribeInstancesInput(setAttrs, false) input, err := buildDescribeInstancesInput(setAttrs, false)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error building DescribeInstances parameters for host set id %q: %s", set.GetId(), err) return nil, errors.BadRequestStatusf("error building DescribeInstances parameters for host set id %q: %s", set.GetId(), err)
} }
queries[i] = hostSetQuery{ queries[i] = hostSetQuery{
Id: set.GetId(), Id: set.GetId(),
@@ -533,7 +533,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
} }
ec2Client, err := catalogState.EC2Client(ctx, opts...) ec2Client, err := catalogState.EC2Client(ctx, opts...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error getting EC2 client: %s", err) return nil, errors.BadRequestStatusf("error getting EC2 client: %s", err)
} }
// Run all queries now and assemble output. // Run all queries now and assemble output.
@@ -541,7 +541,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
for i, query := range queries { for i, query := range queries {
output, err := ec2Client.DescribeInstances(ctx, query.Input) output, err := ec2Client.DescribeInstances(ctx, query.Input)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error running DescribeInstances for host set id %q: %s", query.Id, err) return nil, errors.BadRequestStatusf("error running DescribeInstances for host set id %q: %s", query.Id, err)
} }
queries[i].Output = output queries[i].Output = output
@@ -552,7 +552,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
for _, instance := range reservation.Instances { for _, instance := range reservation.Instances {
host, err := awsInstanceToHost(instance, catalogAttributes) host, err := awsInstanceToHost(instance, catalogAttributes)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error processing host results for host set id %q: %s", 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) queries[i].OutputHosts = append(queries[i].OutputHosts, host)
@@ -653,7 +653,7 @@ func awsInstanceToHost(instance types.Instance, catalogAttributes *CatalogAttrib
return nil, fmt.Errorf("response integrity error: missing instance id") return nil, fmt.Errorf("response integrity error: missing instance id")
} }
if catalogAttributes == nil { if catalogAttributes == nil {
return nil, fmt.Errorf("catalog attributes are required") return nil, fmt.Errorf("response integrity error: missing catalog attributes")
} }
result := new(pb.ListHostsResponseHost) result := new(pb.ListHostsResponseHost)
@@ -669,46 +669,28 @@ func awsInstanceToHost(instance types.Instance, catalogAttributes *CatalogAttrib
} }
} }
if !catalogAttributes.ExcludePrivateIps { // First let's get the instance's Private IPv4/DNS, Public IPv4/DNS, and IPv6 addresses
result.IpAddresses = appendDistinct(result.IpAddresses, instance.PrivateIpAddress) result.IpAddresses = appendDistinct(result.IpAddresses, instance.PrivateIpAddress, instance.PublicIpAddress, instance.Ipv6Address)
result.DnsNames = appendDistinct(result.DnsNames, instance.PrivateDnsName) result.DnsNames = appendDistinct(result.DnsNames, instance.PrivateDnsName, instance.PublicDnsName)
}
if !catalogAttributes.ExcludePublicIps { if catalogAttributes.InstanceAddressesOnly {
result.IpAddresses = appendDistinct(result.IpAddresses, instance.PublicIpAddress) // return early as we already have the instance addresses
result.DnsNames = appendDistinct(result.DnsNames, instance.PublicDnsName) return result, nil
} }
// Now go through all of the interfaces and log the IP address of // Now go through all of the interfaces and sync the IP address of
// every interface. // every interface.
for _, iface := range instance.NetworkInterfaces { for _, iface := range instance.NetworkInterfaces {
if catalogAttributes.PrimaryInterfaceOnly {
if iface.Attachment == nil || aws.ToInt32(iface.Attachment.DeviceIndex) != 0 {
// We only want to sync the primary interface so skip any others
continue
}
}
// Populate default IP addresses/DNS name similar to how we do // Populate default IP addresses/DNS name similar to how we do
// for the entire instance. // for the entire instance.
if !catalogAttributes.ExcludePrivateIps { result.IpAddresses = appendDistinct(result.IpAddresses, iface.PrivateIpAddress)
result.IpAddresses = appendDistinct(result.IpAddresses, iface.PrivateIpAddress) result.DnsNames = appendDistinct(result.DnsNames, iface.PrivateDnsName)
result.DnsNames = appendDistinct(result.DnsNames, iface.PrivateDnsName)
}
// Iterate through the private IP addresses and log the // Iterate through the private IP addresses and log the
// information. // information.
for _, addr := range iface.PrivateIpAddresses { for _, addr := range iface.PrivateIpAddresses {
if !catalogAttributes.ExcludePrivateIps { result.IpAddresses = appendDistinct(result.IpAddresses, addr.PrivateIpAddress)
result.IpAddresses = appendDistinct(result.IpAddresses, addr.PrivateIpAddress) result.DnsNames = appendDistinct(result.DnsNames, addr.PrivateDnsName)
result.DnsNames = appendDistinct(result.DnsNames, addr.PrivateDnsName)
}
if catalogAttributes.ExcludePublicIps {
// skip adding public addresses if we are excluding them
continue
}
// PublicIP for iface is only available via an association to the private IP
if addr.Association != nil { if addr.Association != nil {
result.IpAddresses = appendDistinct(result.IpAddresses, addr.Association.PublicIp) result.IpAddresses = appendDistinct(result.IpAddresses, addr.Association.PublicIp)
result.DnsNames = appendDistinct(result.DnsNames, addr.Association.PublicDnsName) result.DnsNames = appendDistinct(result.DnsNames, addr.Association.PublicDnsName)
@@ -716,10 +698,8 @@ func awsInstanceToHost(instance types.Instance, catalogAttributes *CatalogAttrib
} }
// Add the IPv6 addresses. // Add the IPv6 addresses.
if !catalogAttributes.ExcludeIpv6 { for _, addr := range iface.Ipv6Addresses {
for _, addr := range iface.Ipv6Addresses { result.IpAddresses = appendDistinct(result.IpAddresses, addr.Ipv6Address)
result.IpAddresses = appendDistinct(result.IpAddresses, addr.Ipv6Address)
}
} }
} }
+53 -658
View File
@@ -2387,16 +2387,25 @@ func TestBuildDescribeInstancesInput(t *testing.T) {
func TestAwsInstanceToHost(t *testing.T) { func TestAwsInstanceToHost(t *testing.T) {
cases := []struct { cases := []struct {
name string name string
instance types.Instance instance types.Instance
catalogAttributes *CatalogAttributes catalogAttrs *CatalogAttributes
expected *pb.ListHostsResponseHost expected *pb.ListHostsResponseHost
expectedErr string expectedErr string
}{ }{
{ {
name: "missing instance id", name: "missing instance id",
instance: types.Instance{}, instance: types.Instance{},
expectedErr: "response integrity error: missing instance id", catalogAttrs: &CatalogAttributes{},
expectedErr: "response integrity error: missing instance id",
},
{
name: "missing catalog attributes",
instance: types.Instance{
InstanceId: aws.String("foobar"),
},
catalogAttrs: nil,
expectedErr: "response integrity error: missing catalog attributes",
}, },
{ {
name: "good, single IP w/public addr", name: "good, single IP w/public addr",
@@ -2423,6 +2432,7 @@ func TestAwsInstanceToHost(t *testing.T) {
}, },
}, },
}, },
catalogAttrs: &CatalogAttributes{},
expected: &pb.ListHostsResponseHost{ expected: &pb.ListHostsResponseHost{
ExternalId: "foobar", ExternalId: "foobar",
IpAddresses: []string{"10.0.0.1", "1.1.1.1"}, IpAddresses: []string{"10.0.0.1", "1.1.1.1"},
@@ -2448,6 +2458,7 @@ func TestAwsInstanceToHost(t *testing.T) {
}, },
}, },
}, },
catalogAttrs: &CatalogAttributes{},
expected: &pb.ListHostsResponseHost{ expected: &pb.ListHostsResponseHost{
ExternalId: "foobar", ExternalId: "foobar",
IpAddresses: []string{"10.0.0.1"}, IpAddresses: []string{"10.0.0.1"},
@@ -2489,6 +2500,7 @@ func TestAwsInstanceToHost(t *testing.T) {
}, },
}, },
}, },
catalogAttrs: &CatalogAttributes{},
expected: &pb.ListHostsResponseHost{ expected: &pb.ListHostsResponseHost{
ExternalId: "foobar", ExternalId: "foobar",
IpAddresses: []string{"10.0.0.1", "1.1.1.1", "10.0.0.2"}, IpAddresses: []string{"10.0.0.1", "1.1.1.1", "10.0.0.2"},
@@ -2534,6 +2546,7 @@ func TestAwsInstanceToHost(t *testing.T) {
}, },
}, },
}, },
catalogAttrs: &CatalogAttributes{},
expected: &pb.ListHostsResponseHost{ expected: &pb.ListHostsResponseHost{
ExternalId: "foobar", ExternalId: "foobar",
IpAddresses: []string{"10.0.0.1", "1.1.1.1", "10.0.0.2", "1.1.1.2"}, IpAddresses: []string{"10.0.0.1", "1.1.1.1", "10.0.0.2", "1.1.1.2"},
@@ -2569,6 +2582,7 @@ func TestAwsInstanceToHost(t *testing.T) {
}, },
}, },
}, },
catalogAttrs: &CatalogAttributes{},
expected: &pb.ListHostsResponseHost{ expected: &pb.ListHostsResponseHost{
ExternalId: "foobar", ExternalId: "foobar",
IpAddresses: []string{"10.0.0.1", "1.1.1.1", "10.0.0.2"}, IpAddresses: []string{"10.0.0.1", "1.1.1.1", "10.0.0.2"},
@@ -2583,6 +2597,7 @@ func TestAwsInstanceToHost(t *testing.T) {
PrivateDnsName: aws.String("test.example.internal"), PrivateDnsName: aws.String("test.example.internal"),
PublicIpAddress: aws.String("1.1.1.1"), PublicIpAddress: aws.String("1.1.1.1"),
PublicDnsName: aws.String("test.example.com"), PublicDnsName: aws.String("test.example.com"),
Ipv6Address: aws.String("instance::ipv6::address"),
NetworkInterfaces: []types.InstanceNetworkInterface{ NetworkInterfaces: []types.InstanceNetworkInterface{
{ {
PrivateIpAddress: aws.String("10.0.0.1"), PrivateIpAddress: aws.String("10.0.0.1"),
@@ -2596,25 +2611,27 @@ func TestAwsInstanceToHost(t *testing.T) {
}, },
Ipv6Addresses: []types.InstanceIpv6Address{ Ipv6Addresses: []types.InstanceIpv6Address{
{Ipv6Address: nil}, // Just coverage for nil assertion which is skipped {Ipv6Address: nil}, // Just coverage for nil assertion which is skipped
{Ipv6Address: aws.String("2001:db8::1")}, {Ipv6Address: aws.String("some::fake::address")},
}, },
}, },
}, },
}, },
catalogAttrs: &CatalogAttributes{},
expected: &pb.ListHostsResponseHost{ expected: &pb.ListHostsResponseHost{
ExternalId: "foobar", ExternalId: "foobar",
IpAddresses: []string{"10.0.0.1", "1.1.1.1", "2001:db8::1"}, IpAddresses: []string{"10.0.0.1", "1.1.1.1", "instance::ipv6::address", "some::fake::address"},
DnsNames: []string{"test.example.internal", "test.example.com"}, DnsNames: []string{"test.example.internal", "test.example.com"},
}, },
}, },
{ {
name: "good, single IP w/public addr", name: "good, instance addresses only excludes interface secondary addresses",
instance: types.Instance{ instance: types.Instance{
InstanceId: aws.String("foobar"), InstanceId: aws.String("foobar"),
PrivateIpAddress: aws.String("10.0.0.1"), PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"), PrivateDnsName: aws.String("test.example.internal"),
PublicIpAddress: aws.String("1.1.1.1"), PublicIpAddress: aws.String("1.1.1.1"),
PublicDnsName: aws.String("test.example.com"), PublicDnsName: aws.String("test.example.com"),
Ipv6Address: aws.String("instance::ipv6::address"),
NetworkInterfaces: []types.InstanceNetworkInterface{ NetworkInterfaces: []types.InstanceNetworkInterface{
{ {
PrivateIpAddress: aws.String("10.0.0.1"), PrivateIpAddress: aws.String("10.0.0.1"),
@@ -2628,13 +2645,34 @@ func TestAwsInstanceToHost(t *testing.T) {
PrivateIpAddress: aws.String("10.0.0.1"), PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"), PrivateDnsName: aws.String("test.example.internal"),
}, },
{
PrivateIpAddress: aws.String("10.0.0.2"),
PrivateDnsName: aws.String("test2.example.internal"),
},
}, },
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("iface::ipv6::address")}},
},
{
PrivateIpAddress: aws.String("10.0.1.1"),
PrivateDnsName: aws.String("secondary.example.internal"),
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{
Association: &types.InstanceNetworkInterfaceAssociation{
PublicIp: aws.String("2.2.2.2"),
PublicDnsName: aws.String("secondary.example.com"),
},
PrivateIpAddress: aws.String("10.0.1.1"),
PrivateDnsName: aws.String("secondary.example.internal"),
},
},
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("secondary::ipv6::address")}},
}, },
}, },
}, },
catalogAttrs: &CatalogAttributes{InstanceAddressesOnly: true},
expected: &pb.ListHostsResponseHost{ expected: &pb.ListHostsResponseHost{
ExternalId: "foobar", ExternalId: "foobar",
IpAddresses: []string{"10.0.0.1", "1.1.1.1"}, IpAddresses: []string{"10.0.0.1", "1.1.1.1", "instance::ipv6::address"},
DnsNames: []string{"test.example.internal", "test.example.com"}, DnsNames: []string{"test.example.internal", "test.example.com"},
}, },
}, },
@@ -2668,6 +2706,7 @@ func TestAwsInstanceToHost(t *testing.T) {
}, },
}, },
}, },
catalogAttrs: &CatalogAttributes{},
expected: &pb.ListHostsResponseHost{ expected: &pb.ListHostsResponseHost{
ExternalId: "foobar", ExternalId: "foobar",
ExternalName: "test-instance-actual-name", ExternalName: "test-instance-actual-name",
@@ -2705,6 +2744,7 @@ func TestAwsInstanceToHost(t *testing.T) {
}, },
}, },
}, },
catalogAttrs: &CatalogAttributes{},
expected: &pb.ListHostsResponseHost{ expected: &pb.ListHostsResponseHost{
ExternalId: "foobar", ExternalId: "foobar",
ExternalName: "test-instance-actual-name", ExternalName: "test-instance-actual-name",
@@ -2712,663 +2752,18 @@ func TestAwsInstanceToHost(t *testing.T) {
DnsNames: []string{"test.example.internal", "test.example.com"}, DnsNames: []string{"test.example.internal", "test.example.com"},
}, },
}, },
{
name: "primary_interface_only keeps all addresses on primary ENI",
catalogAttributes: &CatalogAttributes{
PrimaryInterfaceOnly: true,
},
instance: types.Instance{
InstanceId: aws.String("foobar"),
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
PublicIpAddress: aws.String("1.1.1.1"),
PublicDnsName: aws.String("test.example.com"),
NetworkInterfaces: []types.InstanceNetworkInterface{
{
Attachment: &types.InstanceNetworkInterfaceAttachment{
DeviceIndex: aws.Int32(0),
},
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{
Primary: aws.Bool(true),
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
},
// Secondary IPs on primary ENI (e.g. VPC CNI pod IPs).
{
Primary: aws.Bool(false),
PrivateIpAddress: aws.String("10.0.0.42"),
PrivateDnsName: aws.String("pod-a.example.internal"),
},
{
Primary: aws.Bool(false),
PrivateIpAddress: aws.String("10.0.0.43"),
PrivateDnsName: aws.String("pod-b.example.internal"),
},
},
},
// Secondary ENI attached by VPC CNI for additional pod IPs.
{
Attachment: &types.InstanceNetworkInterfaceAttachment{
DeviceIndex: aws.Int32(1),
},
PrivateIpAddress: aws.String("10.0.0.99"),
PrivateDnsName: aws.String("eni1.example.internal"),
},
},
},
expected: &pb.ListHostsResponseHost{
ExternalId: "foobar",
IpAddresses: []string{"10.0.0.1", "1.1.1.1", "10.0.0.42", "10.0.0.43"},
DnsNames: []string{"test.example.internal", "test.example.com", "pod-a.example.internal", "pod-b.example.internal"},
},
},
{
name: "primary_interface_only keeps IPv6 on primary ENI",
catalogAttributes: &CatalogAttributes{
PrimaryInterfaceOnly: true,
},
instance: types.Instance{
InstanceId: aws.String("foobar"),
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
NetworkInterfaces: []types.InstanceNetworkInterface{
// Secondary ENI listed first to verify ordering does not matter.
{
Attachment: &types.InstanceNetworkInterfaceAttachment{
DeviceIndex: aws.Int32(1),
},
PrivateIpAddress: aws.String("10.0.0.99"),
Ipv6Addresses: []types.InstanceIpv6Address{
{Ipv6Address: aws.String("2001:db8::2")},
},
},
{
Attachment: &types.InstanceNetworkInterfaceAttachment{
DeviceIndex: aws.Int32(0),
},
PrivateIpAddress: aws.String("10.0.0.1"),
Ipv6Addresses: []types.InstanceIpv6Address{
{Ipv6Address: aws.String("2001:db8::3")},
{Ipv6Address: aws.String("2001:db8::4")},
},
},
},
},
expected: &pb.ListHostsResponseHost{
ExternalId: "foobar",
IpAddresses: []string{"10.0.0.1", "2001:db8::3", "2001:db8::4"},
DnsNames: []string{"test.example.internal"},
},
},
{
name: "primary_interface_only without interfaces still returns top-level IPs",
catalogAttributes: &CatalogAttributes{
PrimaryInterfaceOnly: true,
},
instance: types.Instance{
InstanceId: aws.String("foobar"),
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
PublicIpAddress: aws.String("1.1.1.1"),
PublicDnsName: aws.String("test.example.com"),
},
expected: &pb.ListHostsResponseHost{
ExternalId: "foobar",
IpAddresses: []string{"10.0.0.1", "1.1.1.1"},
DnsNames: []string{"test.example.internal", "test.example.com"},
},
},
{
name: "exclude_public_ips excludes public IPv4 sources but keeps IPv6",
catalogAttributes: &CatalogAttributes{
ExcludePublicIps: true,
},
instance: types.Instance{
InstanceId: aws.String("i-1234"),
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("host.internal"),
PublicIpAddress: aws.String("1.2.3.4"),
PublicDnsName: aws.String("host.public"),
NetworkInterfaces: []types.InstanceNetworkInterface{
{
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("host.internal"),
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("host.internal"),
Association: &types.InstanceNetworkInterfaceAssociation{
PublicIp: aws.String("1.2.3.4"),
PublicDnsName: aws.String("host.public"),
},
},
},
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("2001:db8::1")}},
},
},
},
expected: &pb.ListHostsResponseHost{
ExternalId: "i-1234",
IpAddresses: []string{"10.0.0.1", "2001:db8::1"},
DnsNames: []string{"host.internal"},
},
},
{
name: "exclude_private_ips excludes private IPv4 sources but keeps IPv6",
catalogAttributes: &CatalogAttributes{
ExcludePrivateIps: true,
},
instance: types.Instance{
InstanceId: aws.String("i-1234"),
PublicIpAddress: aws.String("1.2.3.4"),
PublicDnsName: aws.String("host.public"),
NetworkInterfaces: []types.InstanceNetworkInterface{
{
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{Association: &types.InstanceNetworkInterfaceAssociation{PublicIp: aws.String("1.2.3.4"), PublicDnsName: aws.String("host.public")}},
},
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("2001:db8::1")}},
},
{
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{Association: &types.InstanceNetworkInterfaceAssociation{PublicIp: aws.String("1.2.3.5"), PublicDnsName: aws.String("svc.public")}},
},
},
},
},
expected: &pb.ListHostsResponseHost{
ExternalId: "i-1234",
IpAddresses: []string{"1.2.3.4", "2001:db8::1", "1.2.3.5"},
DnsNames: []string{"host.public", "svc.public"},
},
},
{
name: "exclude_public_ips keeps IPv6 addresses",
catalogAttributes: &CatalogAttributes{
ExcludePublicIps: true,
},
instance: types.Instance{
InstanceId: aws.String("i-1234"),
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("host.internal"),
NetworkInterfaces: []types.InstanceNetworkInterface{
{
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("host.internal"),
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("fd00::1")}},
},
},
},
expected: &pb.ListHostsResponseHost{
ExternalId: "i-1234",
IpAddresses: []string{"10.0.0.1", "fd00::1"},
DnsNames: []string{"host.internal"},
},
},
{
name: "exclude_ipv6 omits IPv6 while preserving IPv4 and DNS",
catalogAttributes: &CatalogAttributes{
ExcludeIpv6: true,
},
instance: types.Instance{
InstanceId: aws.String("i-1234"),
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("host.internal"),
PublicIpAddress: aws.String("1.2.3.4"),
PublicDnsName: aws.String("host.public"),
NetworkInterfaces: []types.InstanceNetworkInterface{
{
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("host.internal"),
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("host.internal"),
Association: &types.InstanceNetworkInterfaceAssociation{
PublicIp: aws.String("1.2.3.4"),
PublicDnsName: aws.String("host.public"),
},
},
},
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("2001:db8::1")}},
},
},
},
expected: &pb.ListHostsResponseHost{
ExternalId: "i-1234",
IpAddresses: []string{"10.0.0.1", "1.2.3.4"},
DnsNames: []string{"host.internal", "host.public"},
},
},
{
name: "exclude_public_ips with exclude_ipv6 keeps only private IPv4 sources",
catalogAttributes: &CatalogAttributes{
ExcludePublicIps: true,
ExcludeIpv6: true,
},
instance: types.Instance{
InstanceId: aws.String("i-1234"),
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("host.internal"),
PublicIpAddress: aws.String("1.2.3.4"),
PublicDnsName: aws.String("host.public"),
NetworkInterfaces: []types.InstanceNetworkInterface{
{
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("host.internal"),
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("host.internal"),
Association: &types.InstanceNetworkInterfaceAssociation{
PublicIp: aws.String("1.2.3.4"),
PublicDnsName: aws.String("host.public"),
},
},
{
PrivateIpAddress: aws.String("10.0.0.2"),
PrivateDnsName: aws.String("svc.internal"),
},
},
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("2001:db8::1")}},
},
},
},
expected: &pb.ListHostsResponseHost{
ExternalId: "i-1234",
IpAddresses: []string{"10.0.0.1", "10.0.0.2"},
DnsNames: []string{"host.internal", "svc.internal"},
},
},
{
name: "exclude_private_ips with exclude_ipv6 keeps only public IPv4 sources",
catalogAttributes: &CatalogAttributes{
ExcludePrivateIps: true,
ExcludeIpv6: true,
},
instance: types.Instance{
InstanceId: aws.String("i-1234"),
PublicIpAddress: aws.String("1.2.3.4"),
PublicDnsName: aws.String("host.public"),
NetworkInterfaces: []types.InstanceNetworkInterface{
{
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{
Association: &types.InstanceNetworkInterfaceAssociation{
PublicIp: aws.String("1.2.3.4"),
PublicDnsName: aws.String("host.public"),
},
},
{
Association: &types.InstanceNetworkInterfaceAssociation{
PublicIp: aws.String("1.2.3.5"),
PublicDnsName: aws.String("svc.public"),
},
},
},
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("fd00::1")}},
},
},
},
expected: &pb.ListHostsResponseHost{
ExternalId: "i-1234",
IpAddresses: []string{"1.2.3.4", "1.2.3.5"},
DnsNames: []string{"host.public", "svc.public"},
},
},
{
name: "primary_interface_only with exclude_ipv6 drops IPv6 from primary ENI",
catalogAttributes: &CatalogAttributes{
PrimaryInterfaceOnly: true,
ExcludeIpv6: true,
},
instance: types.Instance{
InstanceId: aws.String("foobar"),
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
PublicIpAddress: aws.String("1.1.1.1"),
PublicDnsName: aws.String("test.example.com"),
NetworkInterfaces: []types.InstanceNetworkInterface{
{
Attachment: &types.InstanceNetworkInterfaceAttachment{
DeviceIndex: aws.Int32(0),
},
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("test.example.internal"),
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{
PrivateIpAddress: aws.String("10.0.0.42"),
PrivateDnsName: aws.String("pod-a.example.internal"),
},
},
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("2001:db8::3")}},
},
{
Attachment: &types.InstanceNetworkInterfaceAttachment{
DeviceIndex: aws.Int32(1),
},
PrivateIpAddress: aws.String("10.0.0.99"),
PrivateDnsName: aws.String("eni1.example.internal"),
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("2001:db8::4")}},
},
},
},
expected: &pb.ListHostsResponseHost{
ExternalId: "foobar",
IpAddresses: []string{"10.0.0.1", "1.1.1.1", "10.0.0.42"},
DnsNames: []string{"test.example.internal", "test.example.com", "pod-a.example.internal"},
},
},
{
name: "exclude_ipv6 on IPv6-only interface leaves no addresses",
catalogAttributes: &CatalogAttributes{
ExcludeIpv6: true,
},
instance: types.Instance{
InstanceId: aws.String("i-1234"),
NetworkInterfaces: []types.InstanceNetworkInterface{
{
Ipv6Addresses: []types.InstanceIpv6Address{
{Ipv6Address: aws.String("2001:db8::1")},
{Ipv6Address: aws.String("fd00::1")},
},
},
},
},
expected: &pb.ListHostsResponseHost{
ExternalId: "i-1234",
},
},
{
name: "deduplicates repeated addresses and DNS names across instance and interfaces",
instance: types.Instance{
InstanceId: aws.String("i-1234"),
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("host.internal"),
PublicIpAddress: aws.String("1.2.3.4"),
PublicDnsName: aws.String("host.public"),
NetworkInterfaces: []types.InstanceNetworkInterface{
{
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("host.internal"),
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("host.internal"),
Association: &types.InstanceNetworkInterfaceAssociation{
PublicIp: aws.String("1.2.3.4"),
PublicDnsName: aws.String("host.public"),
},
},
},
Ipv6Addresses: []types.InstanceIpv6Address{
{Ipv6Address: aws.String("2001:db8::1")},
{Ipv6Address: aws.String("2001:db8::1")},
},
},
{
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("host.internal"),
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{
Association: &types.InstanceNetworkInterfaceAssociation{
PublicIp: aws.String("1.2.3.4"),
PublicDnsName: aws.String("host.public"),
},
},
},
Ipv6Addresses: []types.InstanceIpv6Address{
{Ipv6Address: aws.String("2001:db8::1")},
},
},
},
},
expected: &pb.ListHostsResponseHost{
ExternalId: "i-1234",
IpAddresses: []string{"10.0.0.1", "1.2.3.4", "2001:db8::1"},
DnsNames: []string{"host.internal", "host.public"},
},
},
{
name: "primary_interface_only skips interfaces without attachment metadata",
catalogAttributes: &CatalogAttributes{
PrimaryInterfaceOnly: true,
},
instance: types.Instance{
InstanceId: aws.String("i-1234"),
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("host.internal"),
PublicIpAddress: aws.String("1.2.3.4"),
PublicDnsName: aws.String("host.public"),
NetworkInterfaces: []types.InstanceNetworkInterface{
{
PrivateIpAddress: aws.String("10.0.0.99"),
PrivateDnsName: aws.String("missing-attachment.internal"),
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{
PrivateIpAddress: aws.String("10.0.0.99"),
PrivateDnsName: aws.String("missing-attachment.internal"),
},
},
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("2001:db8::99")}},
},
},
},
expected: &pb.ListHostsResponseHost{
ExternalId: "i-1234",
IpAddresses: []string{"10.0.0.1", "1.2.3.4"},
DnsNames: []string{"host.internal", "host.public"},
},
},
{
name: "exclude_private_ips removes private DNS names even when public addresses remain",
catalogAttributes: &CatalogAttributes{
ExcludePrivateIps: true,
},
instance: types.Instance{
InstanceId: aws.String("i-1234"),
PublicIpAddress: aws.String("1.2.3.4"),
PublicDnsName: aws.String("host.public"),
NetworkInterfaces: []types.InstanceNetworkInterface{
{
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("host.internal"),
PrivateIpAddresses: []types.InstancePrivateIpAddress{
{
PrivateIpAddress: aws.String("10.0.0.1"),
PrivateDnsName: aws.String("host.internal"),
Association: &types.InstanceNetworkInterfaceAssociation{
PublicIp: aws.String("1.2.3.4"),
PublicDnsName: aws.String("host.public"),
},
},
{
PrivateIpAddress: aws.String("10.0.0.2"),
PrivateDnsName: aws.String("svc.internal"),
Association: &types.InstanceNetworkInterfaceAssociation{
PublicIp: aws.String("1.2.3.5"),
PublicDnsName: aws.String("svc.public"),
},
},
},
},
},
},
expected: &pb.ListHostsResponseHost{
ExternalId: "i-1234",
IpAddresses: []string{"1.2.3.4", "1.2.3.5"},
DnsNames: []string{"host.public", "svc.public"},
},
},
} }
for _, tc := range cases { for _, tc := range cases {
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
require := require.New(t) require := require.New(t)
attrs := tc.catalogAttributes actual, err := awsInstanceToHost(tc.instance, tc.catalogAttrs)
if attrs == nil {
attrs = &CatalogAttributes{}
}
actual, err := awsInstanceToHost(tc.instance, attrs)
if tc.expectedErr != "" { if tc.expectedErr != "" {
require.EqualError(err, tc.expectedErr) require.EqualError(err, tc.expectedErr)
return return
} }
require.NoError(err)
require.Equal(tc.expected, actual)
})
}
}
func TestGetCatalogAttributesAddressSelection(t *testing.T) {
cases := []struct {
name string
in *structpb.Struct
expectedErr string
expected *CatalogAttributes
}{
{
name: "valid selectors",
in: &structpb.Struct{
Fields: map[string]*structpb.Value{
"region": structpb.NewStringValue("us-west-2"),
"primary_interface_only": structpb.NewBoolValue(true),
"exclude_public_ips": structpb.NewBoolValue(true),
"exclude_ipv6": structpb.NewBoolValue(true),
},
},
expected: &CatalogAttributes{
CredentialAttributes: &credential.CredentialAttributes{
Region: "us-west-2",
DisableCredentialRotation: false,
},
PrimaryInterfaceOnly: true,
ExcludePublicIps: true,
ExcludeIpv6: true,
},
},
{
name: "exclude_private_ips can be combined with exclude_ipv6",
in: &structpb.Struct{
Fields: map[string]*structpb.Value{
"region": structpb.NewStringValue("us-west-2"),
"exclude_private_ips": structpb.NewBoolValue(true),
"exclude_ipv6": structpb.NewBoolValue(true),
},
},
expected: &CatalogAttributes{
CredentialAttributes: &credential.CredentialAttributes{
Region: "us-west-2",
DisableCredentialRotation: false,
},
ExcludePrivateIps: true,
ExcludeIpv6: true,
},
},
{
name: "exclude_public_ips can be combined with exclude_ipv6",
in: &structpb.Struct{
Fields: map[string]*structpb.Value{
"region": structpb.NewStringValue("us-west-2"),
"exclude_public_ips": structpb.NewBoolValue(true),
"exclude_ipv6": structpb.NewBoolValue(true),
},
},
expected: &CatalogAttributes{
CredentialAttributes: &credential.CredentialAttributes{
Region: "us-west-2",
DisableCredentialRotation: false,
},
ExcludePublicIps: true,
ExcludeIpv6: true,
},
},
{
name: "exclude_private_ips and exclude_public_ips can be combined when IPv6 remains enabled",
in: &structpb.Struct{
Fields: map[string]*structpb.Value{
"region": structpb.NewStringValue("us-west-2"),
"exclude_private_ips": structpb.NewBoolValue(true),
"exclude_public_ips": structpb.NewBoolValue(true),
},
},
expected: &CatalogAttributes{
CredentialAttributes: &credential.CredentialAttributes{
Region: "us-west-2",
DisableCredentialRotation: false,
},
ExcludePrivateIps: true,
ExcludePublicIps: true,
},
},
{
name: "exclude_private_ips and exclude_public_ips cannot also exclude_ipv6",
in: &structpb.Struct{
Fields: map[string]*structpb.Value{
"region": structpb.NewStringValue("us-west-2"),
"exclude_private_ips": structpb.NewBoolValue(true),
"exclude_public_ips": structpb.NewBoolValue(true),
"exclude_ipv6": structpb.NewBoolValue(true),
},
},
expectedErr: "attributes.exclude_ipv6: cannot be combined with exclude_private_ips and exclude_public_ips, attributes.exclude_private_ips: cannot be combined with exclude_public_ips and exclude_ipv6, attributes.exclude_public_ips: cannot be combined with exclude_private_ips and exclude_ipv6",
},
{
name: "exclude_ipv6 alone is accepted",
in: &structpb.Struct{
Fields: map[string]*structpb.Value{
"region": structpb.NewStringValue("us-west-2"),
"exclude_ipv6": structpb.NewBoolValue(true),
},
},
expected: &CatalogAttributes{
CredentialAttributes: &credential.CredentialAttributes{
Region: "us-west-2",
DisableCredentialRotation: false,
},
ExcludeIpv6: true,
},
},
{
name: "exclude_ipv6 requires a boolean",
in: &structpb.Struct{
Fields: map[string]*structpb.Value{
"region": structpb.NewStringValue("us-west-2"),
"exclude_ipv6": structpb.NewStringValue("true"),
},
},
expectedErr: "attributes.exclude_ipv6",
},
{
name: "no selectors configured preserves historical behavior",
in: &structpb.Struct{
Fields: map[string]*structpb.Value{
"region": structpb.NewStringValue("us-west-2"),
},
},
expected: &CatalogAttributes{
CredentialAttributes: &credential.CredentialAttributes{
Region: "us-west-2",
DisableCredentialRotation: false,
},
},
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
actual, err := getCatalogAttributes(tc.in)
if tc.expectedErr != "" {
require.Error(err)
require.Contains(err.Error(), tc.expectedErr)
return
}
require.NoError(err) require.NoError(err)
require.Equal(tc.expected, actual) require.Equal(tc.expected, actual)
}) })
+4
View File
@@ -40,6 +40,10 @@ func setStatePermission(req any, permission *pb.Permission) *pb.StorageBucketCre
state.State.Delete = permission state.State.Delete = permission
case *pb.DeleteObjectsRequest: case *pb.DeleteObjectsRequest:
state.State.Delete = permission state.State.Delete = permission
case pb.ListObjectsRequest:
state.State.Read = permission
case *pb.ListObjectsRequest:
state.State.Read = permission
} }
return state return state
} }
+215 -29
View File
@@ -9,6 +9,7 @@ import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
stderr "errors"
"fmt" "fmt"
"io" "io"
"os" "os"
@@ -76,7 +77,7 @@ func (p *StoragePlugin) getClient(ctx context.Context,
// No storage bucket ID to key cache on, create new client and return // No storage bucket ID to key cache on, create new client and return
client, err := storageState.s3Client(ctx, opt...) client, err := storageState.s3Client(ctx, opt...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error creating S3 client: %s", err) return nil, errors.BadRequestStatusf("error creating S3 client: %s", err)
} }
return client, nil return client, nil
} }
@@ -92,7 +93,7 @@ func (p *StoragePlugin) getClient(ctx context.Context,
defer p.clients.Unlock() defer p.clients.Unlock()
client, err := storageState.s3Client(ctx, opt...) client, err := storageState.s3Client(ctx, opt...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error creating S3 client: %s", err) return nil, errors.BadRequestStatusf("error creating S3 client: %s", err)
} }
p.clients.cache[storageBucketId] = client p.clients.cache[storageBucketId] = client
return client, nil return client, nil
@@ -118,7 +119,7 @@ func (p *StoragePlugin) getClient(ctx context.Context,
var err error var err error
client, err = storageState.s3Client(ctx, opt...) client, err = storageState.s3Client(ctx, opt...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error creating S3 client: %s", err) return nil, errors.BadRequestStatusf("error creating S3 client: %s", err)
} }
p.clients.cache[storageBucketId] = client p.clients.cache[storageBucketId] = client
} }
@@ -138,7 +139,7 @@ func (p *StoragePlugin) call(
s3Client, err := p.getClient(ctx, storageBucketId, storageState, opts...) s3Client, err := p.getClient(ctx, storageBucketId, storageState, opts...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error getting S3 client: %s", err) return nil, errors.BadRequestStatusf("error getting S3 client: %s", err)
} }
firstAttempt := true firstAttempt := true
for { for {
@@ -151,7 +152,7 @@ func (p *StoragePlugin) call(
opts = append(opts, WithCacheRefresh(true)) opts = append(opts, WithCacheRefresh(true))
s3Client, err = p.getClient(ctx, storageBucketId, storageState, opts...) s3Client, err = p.getClient(ctx, storageBucketId, storageState, opts...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error refreshing S3 client: %s", err) return nil, errors.BadRequestStatusf("error refreshing S3 client: %s", err)
} }
continue continue
} }
@@ -218,7 +219,7 @@ func (p *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCre
}, p.testCredStateOpts...)..., }, p.testCredStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err) return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
} }
// Try to rotate AWS static credentials // Try to rotate AWS static credentials
@@ -234,7 +235,7 @@ func (p *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCre
}, p.testStorageStateOpts...)..., }, p.testStorageStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err) return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
} }
// perform dry run to ensure we can interact with the bucket as expected. // perform dry run to ensure we can interact with the bucket as expected.
@@ -244,7 +245,7 @@ func (p *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCre
persistedProto, err := storageState.toProto() persistedProto, err := storageState.toProto()
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("%s", err.Error()) return nil, errors.BadRequestStatus(err.Error())
} }
return &pb.OnCreateStorageBucketResponse{ return &pb.OnCreateStorageBucketResponse{
@@ -292,7 +293,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
oldStorageAttributes.DualStack, oldStorageAttributes.DualStack,
p.testCredStateOpts...) p.testCredStateOpts...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
} }
// Verify the incoming credentials are valid and return any errors to the // Verify the incoming credentials are valid and return any errors to the
@@ -315,7 +316,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
}, p.testCredStateOpts...)..., }, p.testCredStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error setting up new credential persisted state: %s", err) return nil, errors.BadRequestStatusf("error setting up new credential persisted state: %s", err)
} }
newStorageState, err := newAwsStoragePersistedState( newStorageState, err := newAwsStoragePersistedState(
append([]awsStoragePersistedStateOption{ append([]awsStoragePersistedStateOption{
@@ -323,7 +324,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
}, p.testStorageStateOpts...)..., }, p.testStorageStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
} }
if st := dryRunValidation(ctx, newStorageState, newStorageAttributes, newBucket); st != nil { if st := dryRunValidation(ctx, newStorageState, newStorageAttributes, newBucket); st != nil {
return nil, st.Err() return nil, st.Err()
@@ -363,7 +364,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
}, p.testStorageStateOpts...)..., }, p.testStorageStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
} }
// perform dry run to ensure we can interact with the bucket as expected. // perform dry run to ensure we can interact with the bucket as expected.
@@ -373,7 +374,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
persistedProto, err := storageState.toProto() persistedProto, err := storageState.toProto()
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("%s", err.Error()) return nil, errors.BadRequestStatus(err.Error())
} }
return &pb.OnUpdateStorageBucketResponse{ return &pb.OnUpdateStorageBucketResponse{
@@ -410,7 +411,7 @@ func (p *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDel
storageAttributes.DualStack, storageAttributes.DualStack,
p.testCredStateOpts...) p.testCredStateOpts...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
} }
_, err = newAwsStoragePersistedState( _, err = newAwsStoragePersistedState(
@@ -419,7 +420,7 @@ func (p *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDel
}, p.testStorageStateOpts...)..., }, p.testStorageStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error loading persisted state: %s", err) return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
} }
// try to delete static credentials // try to delete static credentials
@@ -474,7 +475,7 @@ func (p *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReques
}, p.testCredStateOpts...)..., }, p.testCredStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err) return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
} }
storageState, err := newAwsStoragePersistedState( storageState, err := newAwsStoragePersistedState(
@@ -483,7 +484,7 @@ func (p *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReques
}, p.testStorageStateOpts...)..., }, p.testStorageStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err) return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
} }
opts := []s3Option{} opts := []s3Option{}
@@ -546,7 +547,7 @@ func (p *StoragePlugin) ValidatePermissions(ctx context.Context, req *pb.Validat
}, p.testCredStateOpts...)..., }, p.testCredStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err) return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
} }
storageState, err := newAwsStoragePersistedState( storageState, err := newAwsStoragePersistedState(
@@ -555,7 +556,7 @@ func (p *StoragePlugin) ValidatePermissions(ctx context.Context, req *pb.Validat
}, p.testStorageStateOpts...)..., }, p.testStorageStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err) return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
} }
// perform dry run to ensure we can interact with the bucket as expected. // perform dry run to ensure we can interact with the bucket as expected.
@@ -604,7 +605,7 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu
}, p.testCredStateOpts...)..., }, p.testCredStateOpts...)...,
) )
if err != nil { if err != nil {
return errors.BadRequestStatus("error setting up persisted state: %s", err) return errors.BadRequestStatusf("error setting up persisted state: %s", err)
} }
storageState, err := newAwsStoragePersistedState( storageState, err := newAwsStoragePersistedState(
@@ -613,7 +614,7 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu
}, p.testStorageStateOpts...)..., }, p.testStorageStateOpts...)...,
) )
if err != nil { if err != nil {
return errors.BadRequestStatus("error setting up persisted state: %s", err) return errors.BadRequestStatusf("error setting up persisted state: %s", err)
} }
opts := []s3Option{} opts := []s3Option{}
@@ -647,13 +648,13 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu
buffer := make([]byte, chunkSize) buffer := make([]byte, chunkSize)
n, err := reader.Read(buffer) n, err := reader.Read(buffer)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return errors.UnknownStatus("error reading chunk from s3: %s", err) return errors.UnknownStatusf("error reading chunk from s3: %s", err)
} }
if n > 0 { if n > 0 {
if err := stream.Send(&pb.GetObjectResponse{ if err := stream.Send(&pb.GetObjectResponse{
FileChunk: buffer[:n], FileChunk: buffer[:n],
}); err != nil { }); err != nil {
return errors.UnknownStatus("error sending chunk to client: %s", err) return errors.UnknownStatusf("error sending chunk to client: %s", err)
} }
} }
if err == io.EOF { if err == io.EOF {
@@ -718,7 +719,7 @@ func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest)
}, p.testCredStateOpts...)..., }, p.testCredStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err) return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
} }
storageState, err := newAwsStoragePersistedState( storageState, err := newAwsStoragePersistedState(
@@ -727,7 +728,7 @@ func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest)
}, p.testStorageStateOpts...)..., }, p.testStorageStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err) return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
} }
opts := []s3Option{} opts := []s3Option{}
@@ -817,7 +818,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
}, p.testCredStateOpts...)..., }, p.testCredStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err) return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
} }
storageState, err := newAwsStoragePersistedState( storageState, err := newAwsStoragePersistedState(
@@ -826,7 +827,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
}, p.testStorageStateOpts...)..., }, p.testStorageStateOpts...)...,
) )
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err) return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
} }
opts := []s3Option{} opts := []s3Option{}
@@ -861,7 +862,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
objects := []types.ObjectIdentifier{} objects := []types.ObjectIdentifier{}
client, err := p.getClient(ctx, bucket.GetId(), storageState, opts...) client, err := p.getClient(ctx, bucket.GetId(), storageState, opts...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error getting S3 client: %s", err) return nil, errors.BadRequestStatusf("error getting S3 client: %s", err)
} }
var conToken *string var conToken *string
truncated, firstAttempt := true, true truncated, firstAttempt := true, true
@@ -880,7 +881,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
opts = append(opts, WithCacheRefresh(true)) opts = append(opts, WithCacheRefresh(true))
client, err = p.getClient(ctx, bucket.GetId(), storageState, opts...) client, err = p.getClient(ctx, bucket.GetId(), storageState, opts...)
if err != nil { if err != nil {
return nil, errors.BadRequestStatus("error refreshing S3 client: %s", err) return nil, errors.BadRequestStatusf("error refreshing S3 client: %s", err)
} }
continue continue
} }
@@ -917,6 +918,191 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
}, nil }, nil
} }
// ListObjects retrieves files and directories from an S3 bucket under a given prefix.
// If the "recursive" flag in the request is false (default), it returns only the top-level objects directly under the prefix.
// If the "recursive" is true, it returns all objects under the prefix recursively.
func (p *StoragePlugin) ListObjects(ctx context.Context, req *pb.ListObjectsRequest) (*pb.ListObjectsResponse, error) {
bucket := req.GetBucket()
if bucket == nil {
return nil, errors.BadRequestStatus("bucket is required")
}
if bucket.BucketName == "" {
return nil, errors.BadRequestStatus("bucketName is required")
}
attrs := bucket.GetAttributes()
if attrs == nil {
return nil, errors.BadRequestStatus("attributes is required")
}
storageAttributes, err := getStorageAttributes(attrs)
if err != nil {
return nil, err
}
credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes, false)
if err != nil {
return nil, err
}
// Set up the creds in our persisted state.
credState, err := cred.NewAwsCredentialPersistedState(
append([]cred.AwsCredentialPersistedStateOption{
cred.WithCredentialsConfig(credConfig),
}, p.testCredStateOpts...)...,
)
if err != nil {
return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
}
storageState, err := newAwsStoragePersistedState(
append([]awsStoragePersistedStateOption{
withCredentials(credState),
}, p.testStorageStateOpts...)...,
)
if err != nil {
return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
}
var opts []s3Option
if storageAttributes.EndpointUrl != "" {
opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl))
}
s3Client, err := storageState.s3Client(ctx, opts...)
if err != nil {
return nil, errors.BadRequestStatusf("error getting S3 client: %s", err)
}
prefix := path.Join(bucket.GetBucketPrefix(), req.GetKeyPrefix())
if strings.HasSuffix(req.GetKeyPrefix(), "/") {
// path.Join ends by "cleaning" the path, including removing a trailing slash, if
// it exists. given that a slash is used to denote a folder, it is required here.
prefix += "/"
}
var objects []*pb.Object
if req.GetRecursive() {
objects, err = listObjectsRecursive(ctx, s3Client, bucket.GetBucketName(), prefix, req)
} else {
objects, err = listObjectsTopLevel(ctx, s3Client, bucket.GetBucketName(), prefix, req)
}
if err != nil {
return nil, err
}
return &pb.ListObjectsResponse{
Objects: objects,
}, nil
}
// listObjectsTopLevel lists only the objects that exist directly under the specified prefix (This does not traverse subdirectories)
// It uses the S3 "Delimiter" option to group results so that subdirectories appear as CommonPrefixes.
func listObjectsTopLevel(ctx context.Context, s3Client S3API, bucketName, prefix string, req *pb.ListObjectsRequest) ([]*pb.Object, error) {
var objects []*pb.Object
paginator := s3.NewListObjectsV2Paginator(s3Client, &s3.ListObjectsV2Input{
Bucket: aws.String(bucketName),
Prefix: aws.String(prefix),
Delimiter: aws.String("/"),
})
for paginator.HasMorePages() {
if err := ctx.Err(); err != nil {
switch {
case stderr.Is(err, context.Canceled):
return nil, status.Error(codes.Canceled, "request canceled")
case stderr.Is(err, context.DeadlineExceeded):
return nil, status.Error(codes.DeadlineExceeded, "request deadline exceeded")
default:
return nil, status.Error(codes.Internal, "unknown context error")
}
}
page, err := paginator.NextPage(ctx)
if err != nil {
return nil, parseS3Error("failed to list objects", err, req).Err()
}
for _, c := range page.Contents {
if c.Key != nil {
objects = append(objects, &pb.Object{Key: *c.Key, IsDir: false})
}
}
for _, cp := range page.CommonPrefixes {
if cp.Prefix != nil {
objects = append(objects, &pb.Object{Key: *cp.Prefix, IsDir: true})
}
}
}
return objects, nil
}
// listObjectsRecursive lists all objects under the specified prefix, traversing all subdirectories.
// It does not use the S3 "Delimiter" option, so every object key that matches the prefix is returned.
// This mode is useful for fully scanning all objects in a folder tree.
func listObjectsRecursive(ctx context.Context, s3Client S3API, bucketName, prefix string, req *pb.ListObjectsRequest) ([]*pb.Object, error) {
var objects []*pb.Object
folderSet := make(map[string]struct{})
paginator := s3.NewListObjectsV2Paginator(s3Client, &s3.ListObjectsV2Input{
Bucket: aws.String(bucketName),
Prefix: aws.String(prefix),
})
for paginator.HasMorePages() {
if err := ctx.Err(); err != nil {
switch {
case stderr.Is(err, context.Canceled):
return nil, status.Error(codes.Canceled, "request canceled")
case stderr.Is(err, context.DeadlineExceeded):
return nil, status.Error(codes.DeadlineExceeded, "request deadline exceeded")
default:
return nil, status.Error(codes.Internal, "unknown context error")
}
}
page, err := paginator.NextPage(ctx)
if err != nil {
return nil, parseS3Error("failed to list objects", err, req).Err()
}
for _, c := range page.Contents {
// We intentionally ignore "empty objects" that simulate folders.
// S3 is not a traditional file system and does not have real directories.
// Folders are an abstraction; they are represented by zero-byte objects
// whose keys end with a slash (e.g., "myfolder/"). These objects exist
// solely as placeholders to give the appearance of directories in S3 tools.
// Here, we only include actual objects with content (non-zero length).
if c.Key != nil && !strings.HasSuffix(*c.Key, "/") {
objects = append(objects, &pb.Object{Key: *c.Key, IsDir: false})
}
// Extract all parent folder paths from the object key.
// We walk up the directory hierarchy using path.Dir,
// adding each parent folder to folderSet until we reach
// the root ("/" or ".") or leave the current prefix subtree.
// This ensures all intermediate "folders" appear in the result even if
// no explicit zero-byte object exists for them.
dir := path.Dir(*c.Key)
for dir != "." && dir != "/" {
folder := dir + "/"
if !strings.HasPrefix(folder, prefix) {
break // left the subtree, stop climbing up
}
if folder != prefix {
folderSet[folder] = struct{}{}
}
dir = path.Dir(dir)
}
}
}
// Add folders
for folder := range folderSet {
objects = append(objects, &pb.Object{
Key: folder,
IsDir: true,
})
}
return objects, nil
}
// dryRunValidation validates that the IAM role policy attached to the given secrets of the // dryRunValidation validates that the IAM role policy attached to the given secrets of the
// storage bucket has the minimum required permissions needed for this plugin to function // storage bucket has the minimum required permissions needed for this plugin to function
// as expected. This function will create an object in the s3 bucket to validate it has // as expected. This function will create an object in the s3 bucket to validate it has
+388
View File
@@ -4202,3 +4202,391 @@ func TestS3ClientCache(t *testing.T) {
assert.Equal(cred3.AccessKey, client.Credentials().AccessKeyID) assert.Equal(cred3.AccessKey, client.Credentials().AccessKeyID)
assert.Equal(cred3.SecretKey, client.Credentials().SecretAccessKey) assert.Equal(cred3.SecretKey, client.Credentials().SecretAccessKey)
} }
func TestStoragePlugin_ListObjects(t *testing.T) {
validRequest := func() *pb.ListObjectsRequest {
return &pb.ListObjectsRequest{
KeyPrefix: "foo/bar/",
Bucket: &storagebuckets.StorageBucket{
BucketName: "foo",
Secrets: credential.MockStaticCredentialSecrets(),
Attributes: &structpb.Struct{
Fields: map[string]*structpb.Value{
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
},
},
},
}
}
cases := []struct {
name string
req *pb.ListObjectsRequest
credOpts []credential.AwsCredentialPersistedStateOption
storageOpts []awsStoragePersistedStateOption
ctx context.Context
expectedObjects []*pb.Object
expectedErrContains string
expectedErrCode codes.Code
expectedDetails *pb.StorageBucketCredentialState
}{
{
name: "nil bucket",
req: &pb.ListObjectsRequest{KeyPrefix: "foo/bar"},
expectedErrContains: "bucket is required",
expectedErrCode: codes.InvalidArgument,
},
{
name: "empty bucket name",
req: &pb.ListObjectsRequest{
KeyPrefix: "foo/bar",
Bucket: &storagebuckets.StorageBucket{},
},
expectedErrContains: "bucketName is required",
expectedErrCode: codes.InvalidArgument,
},
{
name: "nil attributes",
req: &pb.ListObjectsRequest{
KeyPrefix: "foo/bar",
Bucket: &storagebuckets.StorageBucket{
BucketName: "foo",
},
},
expectedErrContains: "attributes is required",
expectedErrCode: codes.InvalidArgument,
},
{
name: "error reading attributes",
req: &pb.ListObjectsRequest{
KeyPrefix: "foo/bar",
Bucket: &storagebuckets.StorageBucket{
BucketName: "foo",
Attributes: new(structpb.Struct),
},
},
expectedErrContains: "missing required value \"region\"",
expectedErrCode: codes.InvalidArgument,
},
{
name: "credential persisted state setup error",
req: validRequest(),
credOpts: []credential.AwsCredentialPersistedStateOption{
func(s *credential.AwsCredentialPersistedState) error {
return errors.New(testOptionErr)
},
},
expectedErrContains: fmt.Sprintf("error setting up persisted state: %s", testOptionErr),
expectedErrCode: codes.InvalidArgument,
},
{
name: "storage persisted state setup error",
req: validRequest(),
storageOpts: []awsStoragePersistedStateOption{
func(s *awsStoragePersistedState) error {
return errors.New(testOptionErr)
},
},
expectedErrContains: fmt.Sprintf("error setting up persisted state: %s", testOptionErr),
expectedErrCode: codes.InvalidArgument,
},
{
name: "listObjectsV2 access denied error",
req: validRequest(),
credOpts: validSTSMock(),
storageOpts: []awsStoragePersistedStateOption{
withTestS3APIFunc(
newTestMockS3(
nil,
testMockS3WithListObjectsV2Error(TestAwsS3Error("AccessDenied", "ListObjectsV2", "not authorized")),
),
),
},
expectedErrContains: "aws service s3: invalid credentials: failed to list objects",
expectedErrCode: codes.PermissionDenied,
expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{
Read: &pb.Permission{
State: pb.StateType_STATE_TYPE_ERROR,
ErrorDetails: "not authorized",
CheckedAt: timestamppb.Now(),
},
},
},
},
{
name: "listObjectsV2 throttling error",
req: validRequest(),
credOpts: validSTSMock(),
storageOpts: []awsStoragePersistedStateOption{
withTestS3APIFunc(
newTestMockS3(
nil,
testMockS3WithListObjectsV2Error(TestAwsS3Error("ThrottlingException", "ListObjectsV2", "throttling exception")),
),
),
},
expectedErrContains: "aws service s3: throttling: failed to list objects",
expectedErrCode: codes.Unavailable,
expectedDetails: &pb.StorageBucketCredentialState{
State: &pb.Permissions{
Read: &pb.Permission{
State: pb.StateType_STATE_TYPE_UNKNOWN,
CheckedAt: timestamppb.Now(),
},
},
},
},
{
name: "context canceled - non recursive",
req: validRequest(),
ctx: func() context.Context {
c, cancel := context.WithCancel(context.Background())
cancel()
return c
}(),
credOpts: validSTSMock(),
storageOpts: []awsStoragePersistedStateOption{
withTestS3APIFunc(
newTestMockS3(
nil,
// Return an error that simulates context cancellation being detected at the AWS level
testMockS3WithListObjectsV2Error(context.Canceled),
),
),
},
expectedErrContains: "request canceled",
expectedErrCode: codes.Canceled,
},
{
name: "context deadline exceeded - non recursive",
req: validRequest(),
ctx: func() context.Context {
c, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
time.Sleep(2 * time.Nanosecond) // Ensure deadline is exceeded
cancel()
return c
}(),
credOpts: validSTSMock(),
storageOpts: []awsStoragePersistedStateOption{
withTestS3APIFunc(
newTestMockS3(
nil,
// Return an error that simulates deadline exceeded being detected at the AWS level
testMockS3WithListObjectsV2Error(context.DeadlineExceeded),
),
),
},
expectedErrContains: "request deadline exceeded",
expectedErrCode: codes.DeadlineExceeded,
},
{
name: "context canceled - recursive",
req: func() *pb.ListObjectsRequest {
r := validRequest()
r.Recursive = true
return r
}(),
ctx: func() context.Context {
c, cancel := context.WithCancel(context.Background())
cancel() // Cancel immediately
return c
}(),
credOpts: validSTSMock(),
storageOpts: []awsStoragePersistedStateOption{
withTestS3APIFunc(
newTestMockS3(
nil,
// Return an error that simulates context cancellation being detected at the AWS level
testMockS3WithListObjectsV2Error(context.Canceled),
),
),
},
expectedErrContains: "request canceled",
expectedErrCode: codes.Canceled,
},
{
name: "context deadline exceeded - recursive",
req: func() *pb.ListObjectsRequest {
r := validRequest()
r.Recursive = true
return r
}(),
ctx: func() context.Context {
c, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
// Ensure the deadline is exceeded
time.Sleep(2 * time.Nanosecond)
cancel()
return c
}(),
credOpts: validSTSMock(),
storageOpts: []awsStoragePersistedStateOption{
withTestS3APIFunc(
newTestMockS3(
nil,
// Return an error that simulates deadline exceeded being detected at the AWS level
testMockS3WithListObjectsV2Error(context.DeadlineExceeded),
),
),
},
expectedErrContains: "request deadline exceeded",
expectedErrCode: codes.DeadlineExceeded,
},
{
name: "success - single page top-level",
req: validRequest(),
credOpts: validSTSMock(),
storageOpts: []awsStoragePersistedStateOption{
withTestS3APIFunc(
newTestMockS3(
nil,
testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{
CommonPrefixes: []s3types.CommonPrefix{
{Prefix: aws.String("foo/bar/key/")},
},
Contents: []s3types.Object{
{Key: aws.String("foo/bar/file1.bsr")},
{Key: aws.String("foo/bar/file2.bsr")},
},
}),
),
),
},
expectedObjects: []*pb.Object{
{Key: "foo/bar/key/", IsDir: true},
{Key: "foo/bar/file1.bsr", IsDir: false},
{Key: "foo/bar/file2.bsr", IsDir: false},
},
},
{
name: "success - recursive listing with nested dirs",
req: func() *pb.ListObjectsRequest {
r := validRequest()
r.Recursive = true
return r
}(),
credOpts: validSTSMock(),
storageOpts: []awsStoragePersistedStateOption{
withTestS3APIFunc(
newTestMockS3(
nil,
testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{
Contents: []s3types.Object{
{Key: aws.String("foo/bar/file1.bsr")},
{Key: aws.String("foo/bar/nested/file2.bsr")},
{Key: aws.String("foo/bar/nested/deeper/file3.bsr")},
},
}),
),
),
},
expectedObjects: []*pb.Object{
{Key: "foo/bar/file1.bsr", IsDir: false},
{Key: "foo/bar/nested/", IsDir: true},
{Key: "foo/bar/nested/file2.bsr", IsDir: false},
{Key: "foo/bar/nested/deeper/", IsDir: true},
{Key: "foo/bar/nested/deeper/file3.bsr", IsDir: false},
},
},
{
name: "success - recursive multi-page",
req: func() *pb.ListObjectsRequest {
r := validRequest()
r.Recursive = true
return r
}(),
credOpts: validSTSMock(),
storageOpts: []awsStoragePersistedStateOption{
withTestS3APIFunc(
newTestMockS3(
nil,
testMockS3WithListObjectsV2OutputFunc(func(input *s3.ListObjectsV2Input) *s3.ListObjectsV2Output {
if input.ContinuationToken == nil {
return &s3.ListObjectsV2Output{
Contents: []s3types.Object{
{Key: aws.String("foo/bar/page1/file1.bsr")},
},
NextContinuationToken: aws.String("token123"),
IsTruncated: aws.Bool(true),
}
}
if aws.ToString(input.ContinuationToken) == "token123" {
return &s3.ListObjectsV2Output{
Contents: []s3types.Object{
{Key: aws.String("foo/bar/page2/file2.bsr")},
},
}
}
return &s3.ListObjectsV2Output{}
}),
),
),
},
expectedObjects: []*pb.Object{
{Key: "foo/bar/page1/", IsDir: true},
{Key: "foo/bar/page1/file1.bsr", IsDir: false},
{Key: "foo/bar/page2/", IsDir: true},
{Key: "foo/bar/page2/file2.bsr", IsDir: false},
},
},
{
name: "success - recursive empty",
req: func() *pb.ListObjectsRequest {
r := validRequest()
r.Recursive = true
return r
}(),
credOpts: validSTSMock(),
storageOpts: []awsStoragePersistedStateOption{
withTestS3APIFunc(
newTestMockS3(nil, testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{})),
),
},
expectedObjects: []*pb.Object(nil),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
require, assert := require.New(t), assert.New(t)
p := &StoragePlugin{
testCredStateOpts: tc.credOpts,
testStorageStateOpts: tc.storageOpts,
}
ctx := tc.ctx
if ctx == nil {
ctx = context.Background()
}
resp, err := p.ListObjects(ctx, tc.req)
if tc.expectedErrContains != "" {
require.Error(err)
assert.Contains(err.Error(), tc.expectedErrContains)
assert.Equal(tc.expectedErrCode, status.Code(err))
st, ok := status.FromError(err)
require.True(ok)
if tc.expectedDetails != nil {
var state *pb.StorageBucketCredentialState
for _, detail := range st.Details() {
if errDetail, ok := detail.(*pb.StorageBucketCredentialState); ok {
state = errDetail
break
}
}
require.NotNil(state)
internal.CheckSimilarPermission(assert, tc.expectedDetails.State.Read, state.State.Read, false)
internal.CheckSimilarPermission(assert, tc.expectedDetails.State.Write, state.State.Write, false)
internal.CheckSimilarPermission(assert, tc.expectedDetails.State.Delete, state.State.Delete, false)
} else {
assert.Len(st.Details(), 0)
}
return
}
require.NoError(err)
require.NotNil(resp)
require.ElementsMatch(resp.Objects, tc.expectedObjects)
})
}
}
+107 -9
View File
@@ -22,8 +22,6 @@ import (
const expectedEc2InstanceCount = 5 const expectedEc2InstanceCount = 5
var expectedTags = []string{"foo", "bar", "baz"}
func TestHostPlugin(t *testing.T) { func TestHostPlugin(t *testing.T) {
region := os.Getenv("AWS_REGION") region := os.Getenv("AWS_REGION")
if region == "" { if region == "" {
@@ -80,6 +78,14 @@ func TestHostPlugin(t *testing.T) {
require.NoError(err) require.NoError(err)
require.Len(ec2InstanceTags, expectedEc2InstanceCount) require.Len(ec2InstanceTags, expectedEc2InstanceCount)
rawExpectedTags, err := tf.GetOutputSlice("instance_tag_keys")
require.NoError(err)
require.Len(rawExpectedTags, 3)
expectedTags := make([]string, 0, len(rawExpectedTags))
for _, rawExpectedTag := range rawExpectedTags {
expectedTags = append(expectedTags, rawExpectedTag.(string))
}
// Start the workflow now. Set up the host catalog. Note that this // Start the workflow now. Set up the host catalog. Note that this
// will cause the state to go out of drift above in the sense that // will cause the state to go out of drift above in the sense that
// the access key ID/secret access key will no longer be valid. We // the access key ID/secret access key will no longer be valid. We
@@ -143,13 +149,13 @@ func TestHostPlugin(t *testing.T) {
} }
cases := [][]string{ cases := [][]string{
{"foo"}, {expectedTags[0]},
{"bar"}, {expectedTags[1]},
{"baz"}, {expectedTags[2]},
{"foo", "bar"}, {expectedTags[0], expectedTags[1]},
{"foo", "baz"}, {expectedTags[0], expectedTags[2]},
{"bar", "baz"}, {expectedTags[1], expectedTags[2]},
{"foo", "bar", "baz"}, {expectedTags[0], expectedTags[1], expectedTags[2]},
} }
for _, tc := range cases { for _, tc := range cases {
@@ -159,6 +165,9 @@ func TestHostPlugin(t *testing.T) {
testPluginListHosts(ctx, t, p, region, keyid, secret, tc, expectedTagInstancesMap) testPluginListHosts(ctx, t, p, region, keyid, secret, tc, expectedTagInstancesMap)
// TODO: add OnDeleteSet if it needs to be implemented // TODO: add OnDeleteSet if it needs to be implemented
} }
// finally lets test instance_addresses_only catalog attribute correctly syncs only instance addresses
testInstanceAddressesOnlyListHosts(ctx, t, p, tf, region, keyid, secret)
} }
func testPluginOnCreateCatalog(ctx context.Context, t *testing.T, p *host.HostPlugin, region, accessKeyId, secretAccessKey string, rotate bool) (string, string) { func testPluginOnCreateCatalog(ctx context.Context, t *testing.T, p *host.HostPlugin, region, accessKeyId, secretAccessKey string, rotate bool) (string, string) {
@@ -444,3 +453,92 @@ func testPluginListHosts(ctx context.Context, t *testing.T, p *host.HostPlugin,
// Success // Success
t.Logf("testing ListHosts: success (region=%s, tags=%v, expected/actual=(len=%d, ids=%s))", region, tags, len(actualInstances), actualInstances) t.Logf("testing ListHosts: success (region=%s, tags=%v, expected/actual=(len=%d, ids=%s))", region, tags, len(actualInstances), actualInstances)
} }
func testInstanceAddressesOnlyListHosts(ctx context.Context, t *testing.T, p *host.HostPlugin, tf *TestTerraformer, region, accessKeyId, secretAccessKey string) {
t.Helper()
t.Logf("testing testInstanceAddressesOnlyListHosts")
// Get the instance details from tf output
instanceID, err := tf.GetOutputString("multi_address_instance_id")
require.NoError(t, err)
primaryAddresses, err := tf.GetOutputMap("multi_address_primary_addresses")
require.NoError(t, err)
secondaryAddresses, err := tf.GetOutputMap("multi_address_secondary_addresses")
require.NoError(t, err)
var primary, allAddresses []string
for _, addr := range primaryAddresses {
primary = append(primary, addr.(string))
}
allAddresses = append(allAddresses, primary...)
for _, addr := range secondaryAddresses {
allAddresses = append(allAddresses, addr.(string))
}
cases := []struct {
name string
instanceAddressesOnly bool
expectedIPs []string
}{
{
name: "all addresses",
expectedIPs: allAddresses,
},
{
name: "instance addresses only",
instanceAddressesOnly: true,
expectedIPs: primary,
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
catalogAttrMap := map[string]any{
credential.ConstRegion: region,
credential.ConstDisableCredentialRotation: true,
}
if tc.instanceAddressesOnly {
catalogAttrMap[host.ConstInstanceAddressesOnly] = true
}
catalogAttrs, err := structpb.NewStruct(catalogAttrMap)
require.NoError(err)
setAttrs, err := structpb.NewStruct(map[string]any{
host.ConstDescribeInstancesFilters: []any{fmt.Sprintf("instance-id=%s", instanceID)},
})
require.NoError(err)
reqPersistedSecrets, err := structpb.NewStruct(map[string]any{
credential.ConstAccessKeyId: accessKeyId,
credential.ConstSecretAccessKey: secretAccessKey,
credential.ConstCredsLastRotatedTime: (time.Time{}).Format(time.RFC3339Nano),
})
require.NoError(err)
request := &pb.ListHostsRequest{
Catalog: &hostcatalogs.HostCatalog{
Attrs: &hostcatalogs.HostCatalog_Attributes{
Attributes: catalogAttrs,
},
},
Sets: []*hostsets.HostSet{{
Id: "hostset-0",
Attrs: &hostsets.HostSet_Attributes{Attributes: setAttrs},
}},
Persisted: &pb.HostCatalogPersisted{
Secrets: reqPersistedSecrets,
},
}
response, err := p.ListHosts(ctx, request)
require.NoError(err)
require.Len(response.GetHosts(), 1)
hostResp := response.GetHosts()[0]
require.Equal(instanceID, hostResp.ExternalId)
require.ElementsMatch(hostResp.IpAddresses, tc.expectedIPs)
})
}
}
+207
View File
@@ -5,6 +5,7 @@ package testing
import ( import (
"context" "context"
"fmt"
"io" "io"
"net" "net"
"os" "os"
@@ -85,6 +86,12 @@ func TestStoragePlugin(t *testing.T) {
iamSecretAccessKeyMissingDeleteObject, err := tf.GetOutputString("iam_secret_access_key_missing_delete_obj") iamSecretAccessKeyMissingDeleteObject, err := tf.GetOutputString("iam_secret_access_key_missing_delete_obj")
require.NoError(err) require.NoError(err)
s3PaginationTestObjectCount, err := tf.GetOutput("s3_pagination_test_object_count")
require.NoError(err)
objectCountFloat, ok := s3PaginationTestObjectCount.(float64)
require.True(ok)
objectCount := int(objectCountFloat)
// Start the workflow now. Set up the storage bucket. Note that this // Start the workflow now. Set up the storage bucket. Note that this
// will cause the state to go out of drift above in the sense that // will cause the state to go out of drift above in the sense that
// the access key ID/secret access key will no longer be valid. We // the access key ID/secret access key will no longer be valid. We
@@ -138,6 +145,11 @@ func TestStoragePlugin(t *testing.T) {
keyid, secret = iamAccessKeyIds[5].(string), iamSecretAccessKeys[5].(string) keyid, secret = iamAccessKeyIds[5].(string), iamSecretAccessKeys[5].(string)
testPluginObjectMethods(ctx, t, p, bucketName, region, keyid, secret) testPluginObjectMethods(ctx, t, p, bucketName, region, keyid, secret)
// ********************
// * ListObjects
// ********************
testListObjects(ctx, t, p, objectCount, bucketName, region, keyid, secret)
// ******************** // ********************
// * Validate Permissions // * Validate Permissions
// ******************** // ********************
@@ -514,3 +526,198 @@ func testPluginObjectMethods(ctx context.Context, t *testing.T, p *storage.Stora
require.NoError(conn.Close()) require.NoError(conn.Close())
grpcServer.Stop() grpcServer.Stop()
} }
// testListObjects verifies top-level and recursive listing by matching the exact keys and types of returned objects.
func testListObjects(ctx context.Context, t *testing.T, p *storage.StoragePlugin, objectCount int, bucketName, region, accessKeyId, secretAccessKey string) {
t.Helper()
t.Logf("testing ListObjects (bucket=%s, region=%s, expectedObjects=%d, creds=%t)",
bucketName,
region,
objectCount,
accessKeyId != "" && secretAccessKey != "",
)
r := require.New(t)
reqAttrs, err := structpb.NewStruct(map[string]any{
credential.ConstRegion: region,
credential.ConstDisableCredentialRotation: true,
})
r.NoError(err)
reqSecrets, err := structpb.NewStruct(map[string]any{
credential.ConstAccessKeyId: accessKeyId,
credential.ConstSecretAccessKey: secretAccessKey,
})
r.NoError(err)
bucket := &storagebuckets.StorageBucket{
BucketName: bucketName,
Attributes: reqAttrs,
Secrets: reqSecrets,
}
// Test Scenario 1: Non-Paginated Listing
t.Run("NonPaginatedListing", func(t *testing.T) {
// Subtest for the non-recursive (top-level) option
t.Run("TopLevel", func(t *testing.T) {
r := require.New(t)
t.Logf("testing ListObjects for a small set with recursive=false")
req := &pb.ListObjectsRequest{
Bucket: bucket,
KeyPrefix: "list-objects/",
Recursive: false,
}
resp, err := p.ListObjects(ctx, req)
r.NoError(err)
r.NotNil(resp)
expectedObjects := []*pb.Object{
{Key: "list-objects/file1.txt", IsDir: false},
{Key: "list-objects/data.json", IsDir: false},
{Key: "list-objects/nested-dir/", IsDir: true},
{Key: "list-objects/empty-nested-dir/", IsDir: true},
}
r.ElementsMatch(resp.Objects, expectedObjects)
})
// Subtest for the recursive option
t.Run("Recursive", func(t *testing.T) {
r := require.New(t)
t.Logf("testing ListObjects for a small set with recursive=true")
req := &pb.ListObjectsRequest{
Bucket: bucket,
KeyPrefix: "list-objects/",
Recursive: true,
}
resp, err := p.ListObjects(ctx, req)
r.NoError(err)
r.NotNil(resp)
expectedObjects := []*pb.Object{
{Key: "list-objects/file1.txt", IsDir: false},
{Key: "list-objects/data.json", IsDir: false},
{Key: "list-objects/nested-dir/file2.txt", IsDir: false},
{Key: "list-objects/nested-dir/", IsDir: true},
{Key: "list-objects/empty-nested-dir/", IsDir: true},
}
r.ElementsMatch(resp.Objects, expectedObjects)
})
})
// Test Scenario 2: Paginated Top-Level Listing
t.Run("TopLevelListing", func(t *testing.T) {
// Subtest for the root prefix
t.Run("AtRoot", func(t *testing.T) {
r := require.New(t)
t.Logf("testing ListObjects with recursive=false at root prefix")
req := &pb.ListObjectsRequest{
Bucket: bucket,
KeyPrefix: "list-objects-paginated/",
Recursive: false,
}
resp, err := p.ListObjects(ctx, req)
r.NoError(err)
r.NotNil(resp)
var expectedObjects []*pb.Object
for i := 0; i < objectCount; i++ {
key := fmt.Sprintf("list-objects-paginated/root-file-%d.txt", i)
expectedObjects = append(expectedObjects, &pb.Object{Key: key, IsDir: false})
}
for i := 0; i < objectCount; i++ {
keyEmpty := fmt.Sprintf("list-objects-paginated/empty-dir-%d/", i)
expectedObjects = append(expectedObjects, &pb.Object{Key: keyEmpty, IsDir: true})
keyNested := fmt.Sprintf("list-objects-paginated/nested-dir-%d/", i)
expectedObjects = append(expectedObjects, &pb.Object{Key: keyNested, IsDir: true})
}
r.ElementsMatch(resp.Objects, expectedObjects)
})
// Subtest for a nested prefix
t.Run("AtNestedPrefix", func(t *testing.T) {
r := require.New(t)
t.Logf("testing ListObjects with recursive=false at a nested prefix")
req := &pb.ListObjectsRequest{
Bucket: bucket,
KeyPrefix: "list-objects-paginated/nested-dir-7/",
Recursive: false,
}
resp, err := p.ListObjects(ctx, req)
r.NoError(err)
r.NotNil(resp)
expectedObjects := []*pb.Object{
{Key: "list-objects-paginated/nested-dir-7/data-file-7.json", IsDir: false},
}
r.ElementsMatch(resp.Objects, expectedObjects)
})
})
// Test Scenario 3: Paginated Recursive Listing
t.Run("RecursiveListing", func(t *testing.T) {
r := require.New(t)
t.Logf("testing ListObjects with recursive=true")
req := &pb.ListObjectsRequest{
Bucket: bucket,
KeyPrefix: "list-objects-paginated/",
Recursive: true,
}
resp, err := p.ListObjects(ctx, req)
r.NoError(err)
r.NotNil(resp)
var expectedObjects []*pb.Object
for i := 0; i < objectCount; i++ {
keyFile := fmt.Sprintf("list-objects-paginated/root-file-%d.txt", i)
expectedObjects = append(expectedObjects, &pb.Object{Key: keyFile, IsDir: false})
keyNestedFile := fmt.Sprintf("list-objects-paginated/nested-dir-%d/data-file-%d.json", i, i)
expectedObjects = append(expectedObjects, &pb.Object{Key: keyNestedFile, IsDir: false})
}
for i := 0; i < objectCount; i++ {
keyNestedDir := fmt.Sprintf("list-objects-paginated/nested-dir-%d/", i)
expectedObjects = append(expectedObjects, &pb.Object{Key: keyNestedDir, IsDir: true})
keyEmptyDir := fmt.Sprintf("list-objects-paginated/empty-dir-%d/", i)
expectedObjects = append(expectedObjects, &pb.Object{Key: keyEmptyDir, IsDir: true})
keyNestedEmptyDir := fmt.Sprintf("list-objects-paginated/empty-dir-%d/dir-%d/", i, i)
expectedObjects = append(expectedObjects, &pb.Object{Key: keyNestedEmptyDir, IsDir: true})
}
r.ElementsMatch(resp.Objects, expectedObjects)
})
// Test Scenario 4: Edge cases
t.Run("EdgeCases", func(t *testing.T) {
// Subtest for a prefix that doesn't exist
t.Run("NonExistentPrefix", func(t *testing.T) {
r := require.New(t)
t.Logf("testing ListObjects for a non-existent prefix")
req := &pb.ListObjectsRequest{
Bucket: bucket,
KeyPrefix: "this/prefix/does/not/exist/",
Recursive: false,
}
resp, err := p.ListObjects(ctx, req)
r.NoError(err)
r.NotNil(resp)
r.Empty(resp.Objects)
})
// Subtest for listing inside a known empty directory
t.Run("InsideEmptyDirectory", func(t *testing.T) {
r := require.New(t)
t.Logf("testing ListObjects inside an empty directory")
req := &pb.ListObjectsRequest{
Bucket: bucket,
KeyPrefix: "list-objects-paginated/empty-dirs/dir-10/",
Recursive: false,
}
resp, err := p.ListObjects(ctx, req)
r.NoError(err)
r.NotNil(resp)
r.Empty(resp.Objects)
})
})
}
+21 -14
View File
@@ -1,24 +1,20 @@
# Copyright IBM Corp. 2021, 2026 # Copyright IBM Corp. 2021, 2026
# SPDX-License-Identifier: MPL-2.0 # SPDX-License-Identifier: MPL-2.0
variable "iam_user_prefix" {
default = "boundary-plugin-host-aws-test-iam-user"
}
variable "iam_user_count" {
default = 6
}
resource "random_id" "aws_iam_user_name" { resource "random_id" "aws_iam_user_name" {
count = var.iam_user_count count = var.iam_user_count
prefix = "${var.iam_user_prefix}-${count.index}" prefix = "demo-${local.hashicorp_email}-boundary-iam-user"
byte_length = 4 byte_length = 4
} }
resource "aws_iam_user" "user" { resource "aws_iam_user" "user" {
count = var.iam_user_count count = var.iam_user_count
name = random_id.aws_iam_user_name[count.index].dec name = random_id.aws_iam_user_name[count.index].dec
force_destroy = true force_destroy = true
permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser"
tags = {
"boundary-demo" = local.hashicorp_email
}
} }
resource "aws_iam_access_key" "user_initial_key" { resource "aws_iam_access_key" "user_initial_key" {
@@ -26,8 +22,13 @@ resource "aws_iam_access_key" "user_initial_key" {
user = aws_iam_user.user[count.index].name user = aws_iam_user.user[count.index].name
} }
resource "random_id" "aws_ec2_policy_name" {
prefix = "BoundaryPluginHost"
byte_length = 4
}
resource "aws_iam_policy" "ec2_describeinstances" { resource "aws_iam_policy" "ec2_describeinstances" {
name = "BoundaryPluginHostAwsTestDescribeInstancesPolicy" name = random_id.aws_ec2_policy_name.dec
policy = <<EOF policy = <<EOF
{ {
@@ -51,9 +52,15 @@ resource "aws_iam_user_policy_attachment" "user_ec2_describeinstances_attachment
policy_arn = aws_iam_policy.ec2_describeinstances.arn policy_arn = aws_iam_policy.ec2_describeinstances.arn
} }
resource "random_id" "aws_iam_policy_name" {
count = var.iam_user_count
prefix = "BoundaryPluginCredentials"
byte_length = 4
}
resource "aws_iam_policy" "user_self_manage_policy" { resource "aws_iam_policy" "user_self_manage_policy" {
count = var.iam_user_count count = var.iam_user_count
name = "BoundaryPluginHostAwsTestUserSelfManagePolicy-${count.index}" name = random_id.aws_iam_policy_name[count.index].dec
policy = <<EOF policy = <<EOF
{ {
+1 -1
View File
@@ -5,7 +5,7 @@ terraform {
required_providers { required_providers {
aws = { aws = {
source = "hashicorp/aws" source = "hashicorp/aws"
version = "~> 3.0" version = "~> 6.0"
} }
} }
} }
+49
View File
@@ -0,0 +1,49 @@
# Copyright IBM Corp. 2021, 2026
# SPDX-License-Identifier: MPL-2.0
data "aws_caller_identity" "current" {}
variable "iam_user_count" {
default = 6
}
# We are using a shared account for testing so make sure the stale ec2
# machine tags don't match for this test run
resource "random_id" "foo_key" {
prefix = "foo"
byte_length = 4
}
resource "random_id" "bar_key" {
prefix = "bar"
byte_length = 4
}
resource "random_id" "baz_key" {
prefix = "baz"
byte_length = 4
}
locals {
hashicorp_email = split(":", data.aws_caller_identity.current.user_id)[1]
instance_tags = [
{
"${random_id.foo_key.dec}" = "true"
},
{
"${random_id.foo_key.dec}" = "true"
"${random_id.bar_key.dec}" = "true"
},
{
"${random_id.bar_key.dec}" = "true"
},
{
"${random_id.bar_key.dec}" = "true"
"${random_id.baz_key.dec}" = "true"
},
{
"${random_id.baz_key.dec}" = "true"
},
]
}
+131 -28
View File
@@ -1,28 +1,6 @@
# Copyright IBM Corp. 2021, 2026 # Copyright IBM Corp. 2021, 2026
# SPDX-License-Identifier: MPL-2.0 # SPDX-License-Identifier: MPL-2.0
variable "instance_tags" {
default = [
{
"foo" = "true",
},
{
"foo" = "true",
"bar" = "true",
},
{
"bar" = "true",
},
{
"bar" = "true",
"baz" = "true",
},
{
"baz" = "true",
},
]
}
data "aws_availability_zones" "azs" {} data "aws_availability_zones" "azs" {}
data "aws_ami" "ubuntu" { data "aws_ami" "ubuntu" {
@@ -42,22 +20,117 @@ data "aws_ami" "ubuntu" {
} }
resource "aws_vpc" "vpc" { resource "aws_vpc" "vpc" {
cidr_block = "10.0.0.0/16" cidr_block = "10.0.0.0/16"
assign_generated_ipv6_cidr_block = true
} }
resource "aws_subnet" "subnet" { resource "aws_subnet" "subnet" {
vpc_id = aws_vpc.vpc.id vpc_id = aws_vpc.vpc.id
cidr_block = aws_vpc.vpc.cidr_block cidr_block = aws_vpc.vpc.cidr_block
availability_zone = data.aws_availability_zones.azs.names[0] ipv6_cidr_block = cidrsubnet(aws_vpc.vpc.ipv6_cidr_block, 8, 0)
availability_zone = data.aws_availability_zones.azs.names[0]
map_public_ip_on_launch = true
assign_ipv6_address_on_creation = true
}
# Need to add a gateway for public IP addr assignments
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.vpc.id
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
route {
ipv6_cidr_block = "::/0"
gateway_id = aws_internet_gateway.igw.id
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.subnet.id
route_table_id = aws_route_table.public.id
} }
resource "aws_instance" "instances" { resource "aws_instance" "instances" {
count = length(var.instance_tags) count = length(local.instance_tags)
ami = data.aws_ami.ubuntu.id ami = data.aws_ami.ubuntu.id
instance_type = "t3.nano" instance_type = "t3.nano"
subnet_id = aws_subnet.subnet.id subnet_id = aws_subnet.subnet.id
tags = var.instance_tags[count.index] tags = local.instance_tags[count.index]
}
resource "aws_instance" "multi_address_instance" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.nano"
subnet_id = aws_subnet.subnet.id
secondary_private_ips = ["10.0.10.10"]
ipv6_address_count = 1
associate_public_ip_address = true
tags = {
Name = "boundary-multi-address-instance"
"boundary-demo" = local.hashicorp_email
}
}
resource "aws_network_interface" "multi_address_secondary_eni" {
subnet_id = aws_subnet.subnet.id
private_ips = ["10.0.20.10"]
ipv6_addresses = [cidrhost(aws_subnet.subnet.ipv6_cidr_block, 32)]
tags = {
Name = "boundary-multi-address-secondary-eni"
"boundary-demo" = local.hashicorp_email
}
}
resource "aws_network_interface_attachment" "multi_address_secondary_eni" {
instance_id = aws_instance.multi_address_instance.id
network_interface_id = aws_network_interface.multi_address_secondary_eni.id
device_index = 1
}
resource "aws_eip" "multi_address_primary" {
domain = "vpc"
network_interface = aws_instance.multi_address_instance.primary_network_interface_id
associate_with_private_ip = aws_instance.multi_address_instance.private_ip
depends_on = [aws_internet_gateway.igw, aws_route_table_association.public]
tags = {
Name = "boundary-multi-address-primary-eip"
"boundary-demo" = local.hashicorp_email
}
}
resource "aws_eip" "multi_address_primary_secondary_private" {
domain = "vpc"
network_interface = aws_instance.multi_address_instance.primary_network_interface_id
associate_with_private_ip = "10.0.10.10"
depends_on = [aws_internet_gateway.igw, aws_route_table_association.public, aws_instance.multi_address_instance]
tags = {
Name = "boundary-multi-address-primary-secondary-eip"
"boundary-demo" = local.hashicorp_email
}
}
resource "aws_eip" "multi_address_secondary_eni" {
domain = "vpc"
network_interface = aws_network_interface.multi_address_secondary_eni.id
associate_with_private_ip = "10.0.20.10"
depends_on = [aws_internet_gateway.igw, aws_route_table_association.public, aws_network_interface_attachment.multi_address_secondary_eni]
tags = {
Name = "boundary-multi-address-secondary-eni-eip"
"boundary-demo" = local.hashicorp_email
}
} }
output "instance_ids" { output "instance_ids" {
@@ -75,3 +148,33 @@ output "instance_tags" {
for _, r in aws_instance.instances : r.id => r.tags for _, r in aws_instance.instances : r.id => r.tags
} }
} }
output "instance_tag_keys" {
value = [
random_id.foo_key.dec,
random_id.bar_key.dec,
random_id.baz_key.dec,
]
}
output "multi_address_instance_id" {
value = aws_instance.multi_address_instance.id
}
output "multi_address_primary_addresses" {
value = {
private_ip = aws_instance.multi_address_instance.private_ip
public_ip = aws_eip.multi_address_primary.public_ip
ipv6 = tolist(aws_instance.multi_address_instance.ipv6_addresses)[0]
}
}
output "multi_address_secondary_addresses" {
value = {
primary_eni_private_ip = "10.0.10.10"
primary_eni_public_ip = aws_eip.multi_address_primary_secondary_private.public_ip
secondary_eni_private_ip = aws_network_interface.multi_address_secondary_eni.private_ip
secondary_eni_public_ip = aws_eip.multi_address_secondary_eni.public_ip
secondary_eni_ipv6 = tolist(aws_network_interface.multi_address_secondary_eni.ipv6_addresses)[0]
}
}
+22 -20
View File
@@ -34,10 +34,18 @@ resource "aws_iam_policy" "valid" {
"s3:GetObject", "s3:GetObject",
"s3:DeleteObject", "s3:DeleteObject",
"s3:GetObjectAttributes", "s3:GetObjectAttributes",
"s3:ListBucket" "s3:ListBucket",
"s3:DeleteObject"
], ],
"Effect": "Allow", "Effect": "Allow",
"Resource": "${aws_s3_bucket.test.arn}*" "Resource": "${aws_s3_bucket.test.arn}*"
},
{
"Action": [
"s3:ListBucket"
],
"Effect": "Allow",
"Resource": "${aws_s3_bucket.test.arn}"
} }
] ]
@@ -196,7 +204,7 @@ resource "aws_iam_role_policy_attachment" "missing_delete_obj" {
# Static Credential Testing # Static Credential Testing
resource "random_id" "user_name" { resource "random_id" "user_name" {
count = var.iam_user_count count = var.iam_user_count
prefix = "demo-${local.hashicorp_email}-boundary-iam-user" prefix = "demo-${local.hashicorp_email}" # Do Not Remove/Edit. This is a requirement to obtain access in creating a iam user in the dev account.
byte_length = 4 byte_length = 4
} }
@@ -204,10 +212,8 @@ resource "aws_iam_user" "test" {
count = var.iam_user_count count = var.iam_user_count
name = random_id.user_name[count.index].dec name = random_id.user_name[count.index].dec
force_destroy = true force_destroy = true
permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser" permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser" # Do Not Remove/Edit. This is a requirement to obtain access in creating a iam user in the dev account.
tags = { tags = local.tags
"boundary-demo" = local.hashicorp_email
}
} }
resource "aws_iam_access_key" "test" { resource "aws_iam_access_key" "test" {
@@ -277,17 +283,15 @@ resource "aws_iam_user_policy_attachment" "missing_delete_obj" {
# Edge case for missing GetObject permission # Edge case for missing GetObject permission
resource "random_id" "missing_get_obj" { resource "random_id" "missing_get_obj" {
prefix = "demo-${local.hashicorp_email}-boundary-iam-user" prefix = "demo-${local.hashicorp_email}" # Do Not Remove/Edit. This is a requirement to obtain access in creating a iam user in the dev account.
byte_length = 4 byte_length = 4
} }
resource "aws_iam_user" "missing_get_obj" { resource "aws_iam_user" "missing_get_obj" {
name = random_id.missing_get_obj.dec name = random_id.missing_get_obj.dec
force_destroy = true force_destroy = true
permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser" permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser" # Do Not Remove/Edit. This is a requirement to obtain access in creating a iam user in the dev account.
tags = { tags = local.tags
"boundary-demo" = local.hashicorp_email
}
} }
resource "aws_iam_access_key" "missing_get_obj" { resource "aws_iam_access_key" "missing_get_obj" {
@@ -302,17 +306,15 @@ resource "aws_iam_user_policy_attachment" "missing_get_obj" {
# Edge case for missing PutObject permission # Edge case for missing PutObject permission
resource "random_id" "missing_put_obj" { resource "random_id" "missing_put_obj" {
prefix = "demo-${local.hashicorp_email}-boundary-iam-user" prefix = "demo-${local.hashicorp_email}" # Do Not Remove/Edit. This is a requirement to obtain access in creating a iam user in the dev account.
byte_length = 4 byte_length = 4
} }
resource "aws_iam_user" "missing_put_obj" { resource "aws_iam_user" "missing_put_obj" {
name = random_id.missing_put_obj.dec name = random_id.missing_put_obj.dec
force_destroy = true force_destroy = true
permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser" permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser" # Do Not Remove/Edit. This is a requirement to obtain access in creating a iam user in the dev account.
tags = { tags = local.tags
"boundary-demo" = local.hashicorp_email
}
} }
resource "aws_iam_access_key" "missing_put_obj" { resource "aws_iam_access_key" "missing_put_obj" {
+1 -1
View File
@@ -5,7 +5,7 @@ terraform {
required_providers { required_providers {
aws = { aws = {
source = "hashicorp/aws" source = "hashicorp/aws"
version = "~> 3.0" version = "~> 6.0"
} }
} }
} }
+9 -4
View File
@@ -6,19 +6,19 @@ output "bucket_name" {
} }
output "valid_policy_arn" { output "valid_policy_arn" {
value = aws_iam_role.valid.arn value = aws_iam_role.valid.arn
} }
output "missing_put_obj_policy_arn" { output "missing_put_obj_policy_arn" {
value = aws_iam_role.missing_put_obj.arn value = aws_iam_role.missing_put_obj.arn
} }
output "missing_get_obj_policy_arn" { output "missing_get_obj_policy_arn" {
value = aws_iam_role.missing_get_obj.arn value = aws_iam_role.missing_get_obj.arn
} }
output "missing_delete_obj_policy_arn" { output "missing_delete_obj_policy_arn" {
value = aws_iam_role.missing_delete_obj.arn value = aws_iam_role.missing_delete_obj.arn
} }
output "iam_user_names" { output "iam_user_names" {
@@ -68,3 +68,8 @@ output "iam_secret_access_key_missing_delete_obj" {
value = aws_iam_access_key.missing_delete_obj.secret value = aws_iam_access_key.missing_delete_obj.secret
sensitive = true sensitive = true
} }
output "s3_pagination_test_object_count" {
description = "The number of S3 objects created for each pagination test scenario."
value = local.s3_pagination_test_object_count
}
+8
View File
@@ -15,3 +15,11 @@ resource "aws_s3_bucket_public_access_block" "secure_access" {
ignore_public_acls = true ignore_public_acls = true
restrict_public_buckets = true restrict_public_buckets = true
} }
resource "aws_s3_object" "s3_files" {
for_each = local.s3_files
bucket = aws_s3_bucket.test.id
key = each.key
content = each.value
tags = local.tags
}
+40
View File
@@ -19,4 +19,44 @@ locals {
Repo = "boundary-plugin-aws" Repo = "boundary-plugin-aws"
Environment = "Demo" Environment = "Demo"
} }
# total number of objects of each type to create
s3_pagination_test_object_count = 350
# Dynamically build a flat map of S3 objects:
# - root-level text files
# - nested JSON files
# - placeholder "empty directories"
# - some static files in list-objects directory
# This map is consumed directly by an aws_s3_object resource
s3_files = merge(
# Root-level text files
{
for i in range(local.s3_pagination_test_object_count) :
"list-objects-paginated/root-file-${i}.txt" => "This is a root-level file #${i}."
},
# Nested JSON files inside sub-directories
{
for i in range(local.s3_pagination_test_object_count) :
"list-objects-paginated/nested-dir-${i}/data-file-${i}.json" =>
jsonencode({
id = i,
message = "This is a nested JSON file #${i}."
})
},
# "Empty" directories (in S3 these are just placeholder objects ending with `/`)
{
for i in range(local.s3_pagination_test_object_count) :
"list-objects-paginated/empty-dir-${i}/dir-${i}/" => "" # zero-length object to mimic directory
},
# Static example files in list-objects directory
{
"list-objects/file1.txt" = "This is the content for file 1."
"list-objects/data.json" = jsonencode({ key = "value", items = [1, 2, 3] })
"list-objects/nested-dir/file2.txt" = "This is a nested file."
"list-objects/empty-nested-dir/" = ""
}
)
} }