diff --git a/go.mod b/go.mod index 6db3488..4d7e026 100644 --- a/go.mod +++ b/go.mod @@ -1,56 +1,56 @@ module github.com/hashicorp/boundary-plugin-aws -go 1.24.0 +go 1.25.7 require ( - github.com/aws/aws-sdk-go-v2 v1.41.5 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.193.0 - github.com/aws/aws-sdk-go-v2/service/iam v1.52.2 - github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 - github.com/aws/aws-sdk-go-v2/service/sts v1.41.2 - github.com/aws/smithy-go v1.24.2 + github.com/aws/aws-sdk-go-v2 v1.41.7 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.254.0 + github.com/aws/aws-sdk-go-v2/service/iam v1.47.6 + github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0 + github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 + github.com/aws/smithy-go v1.25.1 github.com/google/go-cmp v0.7.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-secure-stdlib/awsutil/v2 v2.1.2 github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 github.com/hashicorp/terraform-json v0.23.0 github.com/mitchellh/mapstructure v1.5.0 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.1 google.golang.org/grpc v1.79.3 - google.golang.org/protobuf v1.36.10 + google.golang.org/protobuf v1.36.11 ) require ( 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/config v1.32.2 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.19.2 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // 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/checksum v1.9.13 // 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/s3shared v1.19.21 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.0.2 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.5 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 // indirect + github.com/aws/aws-sdk-go-v2/config v1.28.5 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.46 // 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.23 // 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.1 // 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.9 // 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.23 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.18.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/go-cleanhttp v0.5.2 // 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-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-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-isatty v0.0.20 // 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/ryanuber/go-glob v1.0.0 // indirect github.com/zclconf/go-cty v1.15.0 // indirect - golang.org/x/crypto v0.46.0 // indirect - golang.org/x/net v0.48.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/text v0.32.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index efc1d00..d849d1f 100644 --- a/go.sum +++ b/go.sum @@ -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/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY= -github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o= -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/config v1.32.2 h1:4liUsdEpUUPZs5WVapsJLx5NPmQhQdez7nYFcovrytk= -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/credentials v1.19.2 h1:qZry8VUyTK4VIo5aEdUcBjPZHL2v4FyQ3QEOaWcFLu4= -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/feature/ec2/imds v1.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA= -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/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4= -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/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A= -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/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.193.0 h1:RhSoBFT5/8tTmIseJUXM6INTXTQDF8+0oyxWBnozIms= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.193.0/go.mod h1:mzj8EEjIHSN2oZRXiw1Dd+uB4HZTl7hC8nBzX9IZMWw= -github.com/aws/aws-sdk-go-v2/service/iam v1.52.2 h1:li0ooCUfHIivHn8nB3LstP6HgdNefwu5gnXE4MLVz/U= -github.com/aws/aws-sdk-go-v2/service/iam v1.52.2/go.mod h1:PuHz5kGh1jtsNpjezdYhRp7xgn6DzCNJJfQt7O7U9Aw= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= -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/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM= -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/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto= -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/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM= -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/s3 v1.97.3 h1:HwxWTbTrIHm5qY+CAEur0s/figc3qwvLWsNkF4RPToo= -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/signin v1.0.2 h1:MxMBdKTYBjPQChlJhi4qlEueqB1p1KcbTEa7tD5aqPs= -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/sso v1.30.5 h1:ksUT5KtgpZd3SAiFJNJ0AFEJVva3gjBmN7eXUZjzUwQ= -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/ssooidc v1.35.10 h1:GtsxyiF3Nd3JahRBJbxLCCdYW9ltGQYrFWg8XdkGDd8= -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/sts v1.41.2 h1:a5UTtD4mHBU3t0o6aHQZFJTNKVfxFWfPX7J0Lr7G+uY= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.2/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso= -github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= -github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/aws/aws-sdk-go-v2 v1.39.1 h1:fWZhGAwVRK/fAN2tmt7ilH4PPAE11rDj7HytrmbZ2FE= +github.com/aws/aws-sdk-go-v2 v1.39.1/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= +github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8= +github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 h1:gx1AwW1Iyk9Z9dD9F4akX5gnN3QZwUB20GGKH/I+Rho= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10/go.mod h1:qqY157uZoqm5OXq/amuaBJyC9hgBCBQnsaWnPe905GY= +github.com/aws/aws-sdk-go-v2/config v1.28.5 h1:Za41twdCXbuyyWv9LndXxZZv3QhTG1DinqlFsSuvtI0= +github.com/aws/aws-sdk-go-v2/config v1.28.5/go.mod h1:4VsPbHP8JdcdUDmbTVgNL/8w9SqOkM5jyY8ljIxLO3o= +github.com/aws/aws-sdk-go-v2/credentials v1.17.46 h1:AU7RcriIo2lXjUfHFnFKYsLCwgbz1E7Mm95ieIRDNUg= +github.com/aws/aws-sdk-go-v2/credentials v1.17.46/go.mod h1:1FmYyLGL08KQXQ6mcTlifyFXfJVCNJTVGuQP4m0d/UA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 h1:sDSXIrlsFSFJtWKLQS4PUWRvrT580rrnuLydJrCQ/yA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20/go.mod h1:WZ/c+w0ofps+/OUqMwWgnfrgzZH1DZO1RIkktICsqnY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8 h1:6bgAZgRyT4RoFWhxS+aoGMFyE0cD1bSzFnEEi4bFPGI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8/go.mod h1:KcGkXFVU8U28qS4KvLEcPxytPZPBcRawaH2Pf/0jptE= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8 h1:HhJYoES3zOz34yWEpGENqJvRVPqpmJyR3+AFg9ybhdY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8/go.mod h1:JnA+hPWeYAVbDssp83tv+ysAG8lTfLVXvSsyKg/7xNA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23/go.mod h1:15DfR2nw+CRHIk0tqNyifu3G1YdAOy68RftkhMDDwYk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.8 h1:1/bT9kDdLQzfZ1e6J6hpW+SfNDd6xrV8F3M2CuGyUz8= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.8/go.mod h1:RbdwTONAIi59ej/+1H+QzZORt5bcyAtbrS7FQb2pvz0= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24/go.mod h1:X5ZJyfwVrWA96GzPmUCWFQaEARPR7gCrpq2E92PJwAE= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.254.0 h1:fTLR6dLDTGChAjecRPlVrKeznT0rVdzR4yn9Z68MTGk= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.254.0/go.mod h1:V0jbRy1/IPapnkqgXSwVOFB+u5pnCwd9S+R3pKWULC4= +github.com/aws/aws-sdk-go-v2/service/iam v1.47.6 h1:EWehQXACWr+6hzfZPwZChlfoVhiUCfLHE0Xh3kAfzWQ= +github.com/aws/aws-sdk-go-v2/service/iam v1.47.6/go.mod h1:qRXgEBWPIltrWHQwU+HkyBvwh1QgeigFcaCGCIVrWk0= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZLt5ci0ozzgkVo8BJGwvqNaZbTWb3UcucAateA= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9/go.mod h1:w7wZ/s9qK7c8g4al+UyoF1Sp/Z45UwMGcqIzLWVQHWk= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.8 h1:tIN8MFT1z5STK5kTdOT1TCfMN/bn5fSEnlKsTL8qBOU= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.8/go.mod h1:VKS56txtNWjKI8FqD/hliL0BcshyF4ZaLBa1rm2Y+5s= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 h1:ieLCO1JxUWuxTZ1cRd0GAaeX7O6cIxnwk7tc1LsQhC4= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15/go.mod h1:e3IzZvQ3kAWNykvE0Tr0RDZCMFInMvhku3qNpcIQXhM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8 h1:M6JI2aGFEzYxsF6CXIuRBnkge9Wf9a2xU39rNeXgu10= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8/go.mod h1:Fw+MyTwlwjFsSTE31mH211Np+CUslml8mzc0AFEG09s= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 h1:pbrxO/kuIwgEsOPLkaHu0O+m4fNgLU8B3vxQ+72jTPw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23/go.mod h1:/CMNUqoj46HpS3MNRDEDIwcgEnrtZlKRaHNaHxIFpNA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.8 h1:AgYCo1Rb8XChJXA871BXHDNxNWOTAr6V5YdsRIBbgv0= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.8/go.mod h1:Au9dvIGm1Hbqnt29d3VakOCQuN9l0WrkDDTRq8biWS4= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 h1:03xatSQO4+AM1lTAbnRg5OK528EUg744nW7F73U8DKw= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23/go.mod h1:M8l3mwgx5ToK7wot2sBBce/ojzgnPzZXUV445gTSyE8= +github.com/aws/aws-sdk-go-v2/service/s3 v1.88.2 h1:T7b3qniouutV5Wwa9B1q7gW+Y8s1B3g9RE9qa7zLBIM= +github.com/aws/aws-sdk-go-v2/service/s3 v1.88.2/go.mod h1:tW9TsLb6t1eaTdBE6LITyJW1m/+DjQPU78Q/jT2FJu8= +github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0 h1:etqBTKY581iwLL/H/S2sVgk3C9lAsTJFeXWFDsDcWOU= +github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0/go.mod h1:L2dcoOgS2VSgbPLvpak2NyUPsO1TBN7M45Z4H7DlRc4= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 h1:3zu537oLmsPfDMyjnUS2g+F2vITgy5pB74tHI+JBNoM= +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/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 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/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 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.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= +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/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/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 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/hashicorp/boundary/sdk v0.0.51 h1:a7XpHNTYaeBXRhJkc8MC0siPtfJELYH/ibjw/0UbJEg= -github.com/hashicorp/boundary/sdk v0.0.51/go.mod h1:gSrMTppD30IPVsUqs4SwQowu+OlR5/+58draey++SkQ= +github.com/hashicorp/boundary/sdk v0.0.59 h1:XNniO0rwgihLdF1+4qDHKN3gdta3AU1JteZResCJ8Oc= +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.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 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.6-0.20231025104552-802587e608f0/go.mod h1://CHt6/j+Q2lc0NlUB5af4aS2M0c0aVBg9/JfcpAyhM= +github.com/hashicorp/eventlogger v0.2.11 h1:lkK8ARM/DrCeL5deH2yVS6p40337dESi+77p9pIfLqo= +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/go.mod h1:tMywUTIvdB/FXhwm6HMTt61C8/eODY6gitCHhXtyojg= 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-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-kms-wrapping/plugin/v2 v2.0.7 h1:gM4OwbF16Cmfxt2QMkoGMQbRTfYFZLvDMPgU3rM3KIo= -github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.7/go.mod h1:7ZMHVluyqgHgEuTADeDzFNWoA9mnyPfdiK8Tk2Bct1c= -github.com/hashicorp/go-kms-wrapping/v2 v2.0.16 h1:WZeXfD26QMWYC35at25KgE021SF9L3u9UMHK8fJAdV0= -github.com/hashicorp/go-kms-wrapping/v2 v2.0.16/go.mod h1:ZiKZctjRTLEppuRwrttWkp71VYMbTTCkazK4xT7U/NQ= +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.8/go.mod h1:JDc9UOD4EVRDIwPVethJcT5Ibi/Nas6eQDPtA60iwP0= +github.com/hashicorp/go-kms-wrapping/v2 v2.0.20 h1:afreZ1WJd0WI7v4NsMZ1aL7V/T59sxPuKmQDgGUja20= +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/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= -github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= +github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA= +github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8= github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.1.2 h1:KlDrZWx/wYpnUcFPB0s9uhPjyNZoeIfhrV2MN/Ly76s= github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.1.2/go.mod h1:6+rVulOPNCQbL3Xv2iLCqM0JmU2WO2wRzP1C6hBKeB8= -github.com/hashicorp/go-secure-stdlib/base62 v0.1.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/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.11/go.mod h1:uis9dCmOzXuOaRyXq+1Foh31kcvXKoWogjNnhfjHfW8= -github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.9 h1:0S0ctJ7Ra8O7ap+/3fZUnzJ3VzJyirWS/WnNCuOYtZY= -github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.9/go.mod h1:TNNdgtjLgVDbrgFcyCKrlAicIl3dZF94swJltyGUX2M= +github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.13 h1:TayxZ5drfMP0G6T++WvnLESGLOWeHtdWDWWTAi2e3Qk= +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.10 h1:2iDz+t0JLl1W0tJhvmhsh/UBgT1JgC8Qxz8HxYMWXQo= +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/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0= -github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.6 h1:ZYv2XA+tEfFXIToR2jmBgVqQU9gERt0APbWqmUoNGnY= -github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.6/go.mod h1:ggFN8dlaLWS2R1gymBbCrvXM/bkZP7hEAa4seqDwhyg= +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.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/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/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/go.mod h1:LWq2Sy8UoKKuK4lFuCNWSjJj57MhNNf2zzBWMtkAIX4= -github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I= -github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= +github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= 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-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/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI= github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c= -github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= -github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +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/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= 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/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/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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 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/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 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/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= 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/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 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/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= 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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= @@ -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/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= -go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= -go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -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= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-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-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -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= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= -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-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0= +google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -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/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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48= diff --git a/internal/credential/attributes.go b/internal/credential/attributes.go index 5f0d24d..a7e0fe2 100644 --- a/internal/credential/attributes.go +++ b/internal/credential/attributes.go @@ -130,7 +130,7 @@ func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes, cfg, err := awsutil.NewCredentialsConfig(opts...) if err != nil { - return nil, errors.BadRequestStatus("%s", err.Error()) + return nil, errors.BadRequestStatus(err.Error()) } return cfg, nil diff --git a/internal/errors/error.go b/internal/errors/error.go index fef8fc1..5e94b8f 100644 --- a/internal/errors/error.go +++ b/internal/errors/error.go @@ -16,6 +16,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws/retry" 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" pb "github.com/hashicorp/boundary/sdk/pbs/plugin" "google.golang.org/protobuf/types/known/timestamppb" @@ -52,6 +53,10 @@ const ( // whose key does not exist 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 // access an object that was moved into cold storage. awsErrorInvalidObjectState = "InvalidObjectState" @@ -73,7 +78,7 @@ func InvalidArgumentError(msg string, f map[string]string) error { sort.Strings(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 @@ -118,7 +123,50 @@ func ParseAWSError(op string, err error) (st *status.Status, permission *pb.Perm 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, ¬FoundErr) { + 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 if errors.As(err, &apiErr) { 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 case awsErrorNoSuchKey: fallthrough + case awsErrorNotFound: + fallthrough case awsErrorInvalidObjectState: statusMsg := fmt.Sprintf("aws service %s: %s", serviceName, msg) 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 - if httpErr, ok := err.(*awshttp.ResponseError); ok { + // fallback to evaluating the http status code + var httpErr *awshttp.ResponseError + if errors.As(err, &httpErr) { buf := new(bytes.Buffer) _, err = buf.ReadFrom(httpErr.Response.Body) if err != nil { @@ -206,14 +257,26 @@ func ParseAWSError(op string, err error) (st *status.Status, permission *pb.Perm return st, nil } -// BadRequestStatus returns a status error with an invalid -// argument code -func BadRequestStatus(format string, args ...any) error { +// BadRequestStatusf returns a status error with an invalid +// argument code and a formatted error message. +func BadRequestStatusf(format string, args ...any) error { return status.New(codes.InvalidArgument, fmt.Sprintf(format, args...)).Err() } -// UnknownStatus returns a status error with an internal -// error code -func UnknownStatus(format string, args ...any) error { +// BadRequestStatus returns a status error with an invalid +// argument code and the provided message. +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() } + +// 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() +} diff --git a/internal/errors/error_test.go b/internal/errors/error_test.go index de1183d..4873001 100644 --- a/internal/errors/error_test.go +++ b/internal/errors/error_test.go @@ -134,6 +134,17 @@ func Test_ParseAWSError(t *testing.T) { 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", err: TestAwsError("Throttling", "throttling error"), @@ -311,6 +322,55 @@ func Test_ParseAWSError(t *testing.T) { 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 { 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) - err := BadRequestStatus("test: %s", "hello world") + err := BadRequestStatusf("test: %s", "hello world") assert.ErrorContains(err, "test: hello world") assert.Equal(status.Code(err), codes.InvalidArgument) _, ok := status.FromError(err) require.True(ok) } -func Test_UnknownStatus(t *testing.T) { +func Test_BadRequestStatus(t *testing.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.Equal(status.Code(err), codes.Internal) _, ok := status.FromError(err) diff --git a/internal/errors/testing.go b/internal/errors/testing.go index 2520044..b63315d 100644 --- a/internal/errors/testing.go +++ b/internal/errors/testing.go @@ -9,6 +9,7 @@ import ( "strings" 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" smithyhttp "github.com/aws/smithy-go/transport/http" 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) { if expected == nil && substituteNilWithDefaultOk { expected = &pb.Permission{ diff --git a/plugin/service/host/plugin.go b/plugin/service/host/plugin.go index 5721e37..b490205 100644 --- a/plugin/service/host/plugin.go +++ b/plugin/service/host/plugin.go @@ -63,7 +63,7 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo }, p.testCredStateOpts...)..., ) 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 @@ -79,7 +79,7 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo }, p.testCatalogStateOpts...)..., ) 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. @@ -93,7 +93,7 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo persistedProto, err := catalogState.toProto() 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{ @@ -138,7 +138,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo oldCatalogAttributes.DualStack, p.testCredStateOpts...) 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 @@ -161,7 +161,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo }, p.testCredStateOpts...)..., ) 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( append([]awsCatalogPersistedStateOption{ @@ -169,7 +169,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo }, p.testCatalogStateOpts...)..., ) 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{} if newCatalogAttributes.DualStack { @@ -213,7 +213,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo }, p.testCatalogStateOpts...)..., ) 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. @@ -227,7 +227,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo persistedProto, err := catalogState.toProto() 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{ @@ -264,7 +264,7 @@ func (p *HostPlugin) OnDeleteCatalog(ctx context.Context, req *pb.OnDeleteCatalo catalogAttributes.DualStack, p.testCredStateOpts...) 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( @@ -273,7 +273,7 @@ func (p *HostPlugin) OnDeleteCatalog(ctx context.Context, req *pb.OnDeleteCatalo }, p.testCatalogStateOpts...)..., ) 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 @@ -341,7 +341,7 @@ func (p *HostPlugin) OnCreateSet(ctx context.Context, req *pb.OnCreateSetRequest catalogAttributes.DualStack, p.testCredStateOpts...) 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( @@ -350,7 +350,7 @@ func (p *HostPlugin) OnCreateSet(ctx context.Context, req *pb.OnCreateSetRequest }, p.testCatalogStateOpts...)..., ) 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() @@ -368,7 +368,7 @@ func (p *HostPlugin) OnCreateSet(ctx context.Context, req *pb.OnCreateSetRequest describeInstanceFilters, err := buildFilters(setAttrs) 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{} @@ -404,7 +404,7 @@ func (p *HostPlugin) OnUpdateSet(ctx context.Context, req *pb.OnUpdateSetRequest catalogAttributes.DualStack, p.testCredStateOpts...) 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( @@ -413,7 +413,7 @@ func (p *HostPlugin) OnUpdateSet(ctx context.Context, req *pb.OnUpdateSetRequest }, p.testCatalogStateOpts...)..., ) 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 @@ -433,7 +433,7 @@ func (p *HostPlugin) OnUpdateSet(ctx context.Context, req *pb.OnUpdateSetRequest describeInstanceFilters, err := buildFilters(setAttrs) 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{} @@ -477,7 +477,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (* catalogAttributes.DualStack, p.testCredStateOpts...) 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( @@ -486,7 +486,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (* }, p.testCatalogStateOpts...)..., ) 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() @@ -510,7 +510,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (* } 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()) if err != nil { @@ -519,7 +519,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (* input, err := buildDescribeInstancesInput(setAttrs, false) 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{ Id: set.GetId(), @@ -533,7 +533,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (* } ec2Client, err := catalogState.EC2Client(ctx, opts...) 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. @@ -541,7 +541,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (* for i, query := range queries { output, err := ec2Client.DescribeInstances(ctx, query.Input) 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 @@ -552,7 +552,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (* for _, instance := range reservation.Instances { host, err := awsInstanceToHost(instance) 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) diff --git a/plugin/service/storage/error.go b/plugin/service/storage/error.go index fcf3f7d..fdab852 100644 --- a/plugin/service/storage/error.go +++ b/plugin/service/storage/error.go @@ -40,6 +40,10 @@ func setStatePermission(req any, permission *pb.Permission) *pb.StorageBucketCre state.State.Delete = permission case *pb.DeleteObjectsRequest: state.State.Delete = permission + case pb.ListObjectsRequest: + state.State.Read = permission + case *pb.ListObjectsRequest: + state.State.Read = permission } return state } diff --git a/plugin/service/storage/plugin.go b/plugin/service/storage/plugin.go index 0bf5019..a6a19ca 100644 --- a/plugin/service/storage/plugin.go +++ b/plugin/service/storage/plugin.go @@ -9,6 +9,7 @@ import ( "context" "crypto/sha256" "encoding/base64" + stderr "errors" "fmt" "io" "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 client, err := storageState.s3Client(ctx, opt...) 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 } @@ -92,7 +93,7 @@ func (p *StoragePlugin) getClient(ctx context.Context, defer p.clients.Unlock() client, err := storageState.s3Client(ctx, opt...) 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 return client, nil @@ -118,7 +119,7 @@ func (p *StoragePlugin) getClient(ctx context.Context, var err error client, err = storageState.s3Client(ctx, opt...) 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 } @@ -138,7 +139,7 @@ func (p *StoragePlugin) call( s3Client, err := p.getClient(ctx, storageBucketId, storageState, opts...) 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 for { @@ -151,7 +152,7 @@ func (p *StoragePlugin) call( opts = append(opts, WithCacheRefresh(true)) s3Client, err = p.getClient(ctx, storageBucketId, storageState, opts...) if err != nil { - return nil, errors.BadRequestStatus("error refreshing S3 client: %s", err) + return nil, errors.BadRequestStatusf("error refreshing S3 client: %s", err) } continue } @@ -218,7 +219,7 @@ func (p *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCre }, p.testCredStateOpts...)..., ) 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 @@ -234,7 +235,7 @@ func (p *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCre }, p.testStorageStateOpts...)..., ) 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. @@ -244,7 +245,7 @@ func (p *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCre persistedProto, err := storageState.toProto() if err != nil { - return nil, errors.BadRequestStatus("%s", err.Error()) + return nil, errors.BadRequestStatus(err.Error()) } return &pb.OnCreateStorageBucketResponse{ @@ -292,7 +293,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd oldStorageAttributes.DualStack, p.testCredStateOpts...) 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 @@ -315,7 +316,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd }, p.testCredStateOpts...)..., ) 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( append([]awsStoragePersistedStateOption{ @@ -323,7 +324,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd }, p.testStorageStateOpts...)..., ) 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 { return nil, st.Err() @@ -363,7 +364,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd }, p.testStorageStateOpts...)..., ) 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. @@ -373,7 +374,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd persistedProto, err := storageState.toProto() if err != nil { - return nil, errors.BadRequestStatus("%s", err.Error()) + return nil, errors.BadRequestStatus(err.Error()) } return &pb.OnUpdateStorageBucketResponse{ @@ -410,7 +411,7 @@ func (p *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDel storageAttributes.DualStack, p.testCredStateOpts...) 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( @@ -419,7 +420,7 @@ func (p *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDel }, p.testStorageStateOpts...)..., ) 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 @@ -474,7 +475,7 @@ func (p *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReques }, p.testCredStateOpts...)..., ) 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( @@ -483,7 +484,7 @@ func (p *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReques }, p.testStorageStateOpts...)..., ) 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{} @@ -546,7 +547,7 @@ func (p *StoragePlugin) ValidatePermissions(ctx context.Context, req *pb.Validat }, p.testCredStateOpts...)..., ) 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( @@ -555,7 +556,7 @@ func (p *StoragePlugin) ValidatePermissions(ctx context.Context, req *pb.Validat }, p.testStorageStateOpts...)..., ) 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. @@ -604,7 +605,7 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu }, p.testCredStateOpts...)..., ) 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( @@ -613,7 +614,7 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu }, p.testStorageStateOpts...)..., ) 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{} @@ -647,13 +648,13 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu buffer := make([]byte, chunkSize) n, err := reader.Read(buffer) 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 err := stream.Send(&pb.GetObjectResponse{ FileChunk: buffer[:n], }); 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 { @@ -718,7 +719,7 @@ func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest) }, p.testCredStateOpts...)..., ) 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( @@ -727,7 +728,7 @@ func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest) }, p.testStorageStateOpts...)..., ) 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{} @@ -817,7 +818,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects }, p.testCredStateOpts...)..., ) 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( @@ -826,7 +827,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects }, p.testStorageStateOpts...)..., ) 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{} @@ -861,7 +862,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects objects := []types.ObjectIdentifier{} client, err := p.getClient(ctx, bucket.GetId(), storageState, opts...) if err != nil { - return nil, errors.BadRequestStatus("error getting S3 client: %s", err) + return nil, errors.BadRequestStatusf("error getting S3 client: %s", err) } var conToken *string truncated, firstAttempt := true, true @@ -880,7 +881,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects opts = append(opts, WithCacheRefresh(true)) client, err = p.getClient(ctx, bucket.GetId(), storageState, opts...) if err != nil { - return nil, errors.BadRequestStatus("error refreshing S3 client: %s", err) + return nil, errors.BadRequestStatusf("error refreshing S3 client: %s", err) } continue } @@ -917,6 +918,191 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects }, 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 // 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 diff --git a/plugin/service/storage/plugin_test.go b/plugin/service/storage/plugin_test.go index fbfa058..0b377ca 100644 --- a/plugin/service/storage/plugin_test.go +++ b/plugin/service/storage/plugin_test.go @@ -4202,3 +4202,391 @@ func TestS3ClientCache(t *testing.T) { assert.Equal(cred3.AccessKey, client.Credentials().AccessKeyID) 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) + }) + } +} diff --git a/testing/e2e_storage_test.go b/testing/e2e_storage_test.go index 1f92e15..5f00f63 100644 --- a/testing/e2e_storage_test.go +++ b/testing/e2e_storage_test.go @@ -5,6 +5,7 @@ package testing import ( "context" + "fmt" "io" "net" "os" @@ -85,6 +86,12 @@ func TestStoragePlugin(t *testing.T) { iamSecretAccessKeyMissingDeleteObject, err := tf.GetOutputString("iam_secret_access_key_missing_delete_obj") 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 // 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 @@ -138,6 +145,11 @@ func TestStoragePlugin(t *testing.T) { keyid, secret = iamAccessKeyIds[5].(string), iamSecretAccessKeys[5].(string) testPluginObjectMethods(ctx, t, p, bucketName, region, keyid, secret) + // ******************** + // * ListObjects + // ******************** + testListObjects(ctx, t, p, objectCount, bucketName, region, keyid, secret) + // ******************** // * Validate Permissions // ******************** @@ -514,3 +526,198 @@ func testPluginObjectMethods(ctx context.Context, t *testing.T, p *storage.Stora require.NoError(conn.Close()) 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) + }) + }) +} diff --git a/testing/testdata/host/main.tf b/testing/testdata/host/main.tf index 42b0e06..1b98e7a 100644 --- a/testing/testdata/host/main.tf +++ b/testing/testdata/host/main.tf @@ -5,7 +5,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 3.0" + version = "~> 6.0" } } } diff --git a/testing/testdata/storage/iam.tf b/testing/testdata/storage/iam.tf index d6ed739..faf20e1 100644 --- a/testing/testdata/storage/iam.tf +++ b/testing/testdata/storage/iam.tf @@ -34,10 +34,18 @@ resource "aws_iam_policy" "valid" { "s3:GetObject", "s3:DeleteObject", "s3:GetObjectAttributes", - "s3:ListBucket" + "s3:ListBucket", + "s3:DeleteObject" ], "Effect": "Allow", "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 resource "random_id" "user_name" { 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 } @@ -204,10 +212,8 @@ resource "aws_iam_user" "test" { count = var.iam_user_count name = random_id.user_name[count.index].dec force_destroy = true - permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser" - tags = { - "boundary-demo" = local.hashicorp_email - } + 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 = local.tags } 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 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 } resource "aws_iam_user" "missing_get_obj" { - name = random_id.missing_get_obj.dec - force_destroy = true - permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser" - tags = { - "boundary-demo" = local.hashicorp_email - } + name = random_id.missing_get_obj.dec + force_destroy = true + 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 = local.tags } 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 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 } resource "aws_iam_user" "missing_put_obj" { - name = random_id.missing_put_obj.dec - force_destroy = true - permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser" - tags = { - "boundary-demo" = local.hashicorp_email - } + name = random_id.missing_put_obj.dec + force_destroy = true + 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 = local.tags } resource "aws_iam_access_key" "missing_put_obj" { diff --git a/testing/testdata/storage/main.tf b/testing/testdata/storage/main.tf index 42b0e06..1b98e7a 100644 --- a/testing/testdata/storage/main.tf +++ b/testing/testdata/storage/main.tf @@ -5,7 +5,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 3.0" + version = "~> 6.0" } } } diff --git a/testing/testdata/storage/output.tf b/testing/testdata/storage/output.tf index 31cc326..6f89a93 100644 --- a/testing/testdata/storage/output.tf +++ b/testing/testdata/storage/output.tf @@ -6,19 +6,19 @@ output "bucket_name" { } output "valid_policy_arn" { - value = aws_iam_role.valid.arn + value = aws_iam_role.valid.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" { - value = aws_iam_role.missing_get_obj.arn + value = aws_iam_role.missing_get_obj.arn } output "missing_delete_obj_policy_arn" { - value = aws_iam_role.missing_delete_obj.arn + value = aws_iam_role.missing_delete_obj.arn } output "iam_user_names" { @@ -67,4 +67,9 @@ output "iam_access_key_missing_delete_obj" { output "iam_secret_access_key_missing_delete_obj" { value = aws_iam_access_key.missing_delete_obj.secret sensitive = true -} \ No newline at end of file +} + +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 +} diff --git a/testing/testdata/storage/s3.tf b/testing/testdata/storage/s3.tf index 03eb0b7..f24c5a9 100644 --- a/testing/testdata/storage/s3.tf +++ b/testing/testdata/storage/s3.tf @@ -9,9 +9,17 @@ resource "aws_s3_bucket" "test" { resource "aws_s3_bucket_public_access_block" "secure_access" { bucket = aws_s3_bucket.test.id - + block_public_acls = true block_public_policy = true ignore_public_acls = true restrict_public_buckets = true -} \ No newline at end of file +} +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 +} diff --git a/testing/testdata/storage/variable.tf b/testing/testdata/storage/variable.tf index aaaa982..9edddc8 100644 --- a/testing/testdata/storage/variable.tf +++ b/testing/testdata/storage/variable.tf @@ -16,7 +16,47 @@ locals { hashicorp_email = split(":", data.aws_caller_identity.current.user_id)[1] tags = { User = split(":", data.aws_caller_identity.current.user_id)[1] - Repo = "boundary-plugin-aws" + Repo = "boundary-plugin-aws" 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/" = "" + } + ) }