Skip to content

Commit 24f6c4a

Browse files
authored
s3 access point support (#650)
* s3 access point support * s3 access point support
1 parent 31edbae commit 24f6c4a

13 files changed

+723
-0
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ Cloud-nuke suppports 🔎 inspecting and 🔥💀 deleting the following AWS res
6868
| Lambda | Functions |
6969
| SQS | Queues |
7070
| S3 | Buckets |
71+
| S3 | Access Points |
72+
| S3 | Object Lambda Access Points |
73+
| S3 | Multi Region Access Points |
7174
| VPC | Default VPCs |
7275
| VPC | Default rules in the un-deletable default security group |
7376
| VPC | NAT Gateways |
@@ -553,6 +556,9 @@ of the file that are supported are listed here.
553556
| rds-parameter-group | RdsParameterGroup | ✅ (Group Name) | ❌ | ❌ |
554557
| rds-subnet-group | DBSubnetGroups | ✅ (DB Subnet Group Name) | ❌ | ❌ |
555558
| s3 | s3 | ✅ (Bucket Name) | ✅ (Creation Time) | ✅ |
559+
| s3-ap | s3AccessPoint | ✅ (Access point Name) | ❌ | ❌ |
560+
| s3-olap | S3ObjectLambdaAccessPoint | ✅ (Object Lambda Access point Name) | ❌ | ❌ |
561+
| s3-mrap | S3MultiRegionAccessPoint | ✅ (Multi region Access point Name) | ✅ (Creation Time) | ❌ |
556562
| ses-configuration-set | SesConfigurationset | ✅ (Configuration set name) | ❌ | ❌ |
557563
| ses-email-template | SesEmailTemplates | ✅ (Template Name) | ✅ (Creation Time) | ❌ |
558564
| ses-identity | SesIdentity | ✅ (Identity -Mail/Domain) | ❌ | ❌ |

aws/resource_registry.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ func getRegisteredRegionalResources() []AwsResource {
103103
&resources.RdsParameterGroup{},
104104
&resources.RedshiftClusters{},
105105
&resources.S3Buckets{},
106+
&resources.S3AccessPoint{},
107+
&resources.S3ObjectLambdaAccessPoint{},
108+
&resources.S3MultiRegionAccessPoint{},
106109
&resources.SageMakerNotebookInstances{},
107110
&resources.SecretsManagerSecrets{},
108111
&resources.SecurityHub{},

aws/resources/s3_access_point.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package resources
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/aws/aws-sdk-go/aws"
8+
"github.com/aws/aws-sdk-go/service/s3control"
9+
"github.com/gruntwork-io/cloud-nuke/config"
10+
"github.com/gruntwork-io/cloud-nuke/logging"
11+
"github.com/gruntwork-io/cloud-nuke/report"
12+
"github.com/gruntwork-io/cloud-nuke/util"
13+
"github.com/gruntwork-io/go-commons/errors"
14+
)
15+
16+
func (ap *S3AccessPoint) getAll(c context.Context, configObj config.Config) ([]*string, error) {
17+
accountID, ok := c.Value(util.AccountIdKey).(string)
18+
if !ok {
19+
logging.Errorf("unable to read the account-id from context")
20+
return nil, errors.WithStackTrace(fmt.Errorf("unable to lookup the account id"))
21+
}
22+
23+
// set the account id in object as this is mandatory to nuke an access point
24+
ap.AccountID = aws.String(accountID)
25+
26+
var accessPoints []*string
27+
err := ap.Client.ListAccessPointsPages(&s3control.ListAccessPointsInput{
28+
AccountId: ap.AccountID,
29+
}, func(lapo *s3control.ListAccessPointsOutput, lastPage bool) bool {
30+
for _, accessPoint := range lapo.AccessPointList {
31+
if configObj.S3AccessPoint.ShouldInclude(config.ResourceValue{
32+
Name: accessPoint.Name,
33+
}) {
34+
accessPoints = append(accessPoints, accessPoint.Name)
35+
}
36+
}
37+
return !lastPage
38+
})
39+
return accessPoints, errors.WithStackTrace(err)
40+
}
41+
42+
func (ap *S3AccessPoint) nukeAll(identifiers []*string) error {
43+
if len(identifiers) == 0 {
44+
logging.Debugf("No Access point(s) to nuke in region %s", ap.Region)
45+
return nil
46+
}
47+
48+
logging.Debugf("Deleting all Access points in region %s", ap.Region)
49+
var deleted []*string
50+
51+
for _, id := range identifiers {
52+
53+
_, err := ap.Client.DeleteAccessPoint(&s3control.DeleteAccessPointInput{
54+
AccountId: ap.AccountID,
55+
Name: id,
56+
})
57+
58+
// Record status of this resource
59+
e := report.Entry{
60+
Identifier: aws.StringValue(id),
61+
ResourceType: "S3 Access point",
62+
Error: err,
63+
}
64+
report.Record(e)
65+
66+
if err != nil {
67+
logging.Debugf("[Failed] %s", err)
68+
} else {
69+
deleted = append(deleted, id)
70+
logging.Debugf("Deleted S3 access point: %s", aws.StringValue(id))
71+
}
72+
}
73+
74+
logging.Debugf("[OK] %d S3 Access point(s) deleted in %s", len(deleted), ap.Region)
75+
76+
return nil
77+
}

aws/resources/s3_access_point_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package resources
2+
3+
import (
4+
"context"
5+
"regexp"
6+
"testing"
7+
8+
"github.com/aws/aws-sdk-go/aws"
9+
"github.com/aws/aws-sdk-go/service/s3control"
10+
"github.com/aws/aws-sdk-go/service/s3control/s3controliface"
11+
"github.com/gruntwork-io/cloud-nuke/config"
12+
"github.com/gruntwork-io/cloud-nuke/telemetry"
13+
"github.com/gruntwork-io/cloud-nuke/util"
14+
"github.com/stretchr/testify/require"
15+
)
16+
17+
type mocks3AccessPoint struct {
18+
s3controliface.S3ControlAPI
19+
ListAccessPointsOutput s3control.ListAccessPointsOutput
20+
DeleteAccessPointOutput s3control.DeleteAccessPointOutput
21+
}
22+
23+
func (m mocks3AccessPoint) ListAccessPointsPages(_ *s3control.ListAccessPointsInput, fn func(*s3control.ListAccessPointsOutput, bool) bool) error {
24+
fn(&m.ListAccessPointsOutput, true)
25+
return nil
26+
}
27+
func (m mocks3AccessPoint) DeleteAccessPoint(_ *s3control.DeleteAccessPointInput) (*s3control.DeleteAccessPointOutput, error) {
28+
return &m.DeleteAccessPointOutput, nil
29+
}
30+
31+
func TestS3AccessPoint_GetAll(t *testing.T) {
32+
telemetry.InitTelemetry("cloud-nuke", "")
33+
t.Parallel()
34+
35+
testName01 := "test-access-point-01"
36+
testName02 := "test-access-point-02"
37+
38+
ctx := context.Background()
39+
ctx = context.WithValue(ctx, util.AccountIdKey, "test-account-id")
40+
41+
ap := S3AccessPoint{
42+
Client: mocks3AccessPoint{
43+
ListAccessPointsOutput: s3control.ListAccessPointsOutput{
44+
AccessPointList: []*s3control.AccessPoint{
45+
{
46+
Name: aws.String(testName01),
47+
},
48+
{
49+
Name: aws.String(testName02),
50+
},
51+
},
52+
},
53+
},
54+
AccountID: aws.String("test-account-id"),
55+
}
56+
57+
tests := map[string]struct {
58+
configObj config.ResourceType
59+
expected []string
60+
}{
61+
"emptyFilter": {
62+
configObj: config.ResourceType{},
63+
expected: []string{testName01, testName02},
64+
},
65+
"nameExclusionFilter": {
66+
configObj: config.ResourceType{
67+
ExcludeRule: config.FilterRule{
68+
NamesRegExp: []config.Expression{{
69+
RE: *regexp.MustCompile(testName01),
70+
}}},
71+
},
72+
expected: []string{testName02},
73+
},
74+
}
75+
for name, tc := range tests {
76+
t.Run(name, func(t *testing.T) {
77+
78+
names, err := ap.getAll(ctx, config.Config{
79+
S3AccessPoint: tc.configObj,
80+
})
81+
require.NoError(t, err)
82+
require.Equal(t, tc.expected, aws.StringValueSlice(names))
83+
})
84+
}
85+
}
86+
87+
func TestS3AccessPoint_NukeAll(t *testing.T) {
88+
telemetry.InitTelemetry("cloud-nuke", "")
89+
t.Parallel()
90+
91+
rc := S3AccessPoint{
92+
Client: mocks3AccessPoint{
93+
DeleteAccessPointOutput: s3control.DeleteAccessPointOutput{},
94+
},
95+
}
96+
97+
err := rc.nukeAll([]*string{aws.String("test")})
98+
require.NoError(t, err)
99+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package resources
2+
3+
import (
4+
"context"
5+
6+
awsgo "github.com/aws/aws-sdk-go/aws"
7+
"github.com/aws/aws-sdk-go/aws/session"
8+
"github.com/aws/aws-sdk-go/service/s3control"
9+
"github.com/aws/aws-sdk-go/service/s3control/s3controliface"
10+
"github.com/gruntwork-io/cloud-nuke/config"
11+
"github.com/gruntwork-io/go-commons/errors"
12+
)
13+
14+
type S3AccessPoint struct {
15+
BaseAwsResource
16+
Client s3controliface.S3ControlAPI
17+
Region string
18+
AccessPoints []string
19+
AccountID *string
20+
}
21+
22+
func (ap *S3AccessPoint) Init(session *session.Session) {
23+
ap.Client = s3control.New(session)
24+
}
25+
26+
func (ap *S3AccessPoint) ResourceName() string {
27+
return "s3-ap"
28+
}
29+
30+
func (ap *S3AccessPoint) ResourceIdentifiers() []string {
31+
return ap.AccessPoints
32+
}
33+
34+
func (ap *S3AccessPoint) MaxBatchSize() int {
35+
return 5
36+
}
37+
38+
func (ap *S3AccessPoint) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) {
39+
identifiers, err := ap.getAll(c, configObj)
40+
if err != nil {
41+
return nil, err
42+
}
43+
44+
ap.AccessPoints = awsgo.StringValueSlice(identifiers)
45+
return ap.AccessPoints, nil
46+
}
47+
48+
func (ap *S3AccessPoint) Nuke(identifiers []string) error {
49+
if err := ap.nukeAll(awsgo.StringSlice(identifiers)); err != nil {
50+
return errors.WithStackTrace(err)
51+
}
52+
53+
return nil
54+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package resources
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/aws/aws-sdk-go/aws"
8+
"github.com/aws/aws-sdk-go/service/s3control"
9+
"github.com/gruntwork-io/cloud-nuke/config"
10+
"github.com/gruntwork-io/cloud-nuke/logging"
11+
"github.com/gruntwork-io/cloud-nuke/report"
12+
"github.com/gruntwork-io/cloud-nuke/util"
13+
"github.com/gruntwork-io/go-commons/errors"
14+
)
15+
16+
func (ap *S3MultiRegionAccessPoint) getAll(c context.Context, configObj config.Config) ([]*string, error) {
17+
accountID, ok := c.Value(util.AccountIdKey).(string)
18+
if !ok {
19+
logging.Errorf("unable to read the account-id from context")
20+
return nil, errors.WithStackTrace(fmt.Errorf("unable to lookup the account id"))
21+
}
22+
23+
// set the account id in object as this is mandatory to nuke an access point
24+
ap.AccountID = aws.String(accountID)
25+
26+
var accessPoints []*string
27+
err := ap.Client.ListMultiRegionAccessPointsPages(&s3control.ListMultiRegionAccessPointsInput{
28+
AccountId: ap.AccountID,
29+
}, func(lapo *s3control.ListMultiRegionAccessPointsOutput, lastPage bool) bool {
30+
for _, accessPoint := range lapo.AccessPoints {
31+
if configObj.S3MultiRegionAccessPoint.ShouldInclude(config.ResourceValue{
32+
Name: accessPoint.Name,
33+
Time: accessPoint.CreatedAt,
34+
}) {
35+
accessPoints = append(accessPoints, accessPoint.Name)
36+
}
37+
}
38+
return !lastPage
39+
})
40+
if err != nil {
41+
logging.Errorf("[FAILED] Multi region access point listing - %v", err)
42+
}
43+
return accessPoints, errors.WithStackTrace(err)
44+
}
45+
46+
func (ap *S3MultiRegionAccessPoint) nukeAll(identifiers []*string) error {
47+
if len(identifiers) == 0 {
48+
logging.Debugf("No Multi region access point(s) to nuke in region %s", ap.Region)
49+
return nil
50+
}
51+
52+
logging.Debugf("Deleting all Multi region access points in region %s", ap.Region)
53+
var deleted []*string
54+
55+
for _, id := range identifiers {
56+
57+
_, err := ap.Client.DeleteMultiRegionAccessPoint(&s3control.DeleteMultiRegionAccessPointInput{
58+
AccountId: ap.AccountID,
59+
Details: &s3control.DeleteMultiRegionAccessPointInput_{
60+
Name: id,
61+
},
62+
})
63+
64+
// Record status of this resource
65+
e := report.Entry{
66+
Identifier: aws.StringValue(id),
67+
ResourceType: "S3 Multi Region Access point",
68+
Error: err,
69+
}
70+
report.Record(e)
71+
72+
if err != nil {
73+
logging.Debugf("[Failed] %s", err)
74+
} else {
75+
deleted = append(deleted, id)
76+
logging.Debugf("Deleted S3 Multi region access point: %s", aws.StringValue(id))
77+
}
78+
}
79+
80+
logging.Debugf("[OK] %d S3 Multi region access point(s) deleted in %s", len(deleted), ap.Region)
81+
82+
return nil
83+
}

0 commit comments

Comments
 (0)