Skip to content

Commit

Permalink
Feature/nukable permission check for resources (#696)
Browse files Browse the repository at this point in the history
* nukable permission check for resources

* nukable permission check for resources
  • Loading branch information
james03160927 authored May 13, 2024
1 parent cc48d3e commit d53472e
Show file tree
Hide file tree
Showing 26 changed files with 310 additions and 44 deletions.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,40 @@ of the file that are supported are listed here.
| network-firewall-resource-policy | NetworkFirewallResourcePolicy | ✅ (Firewall Resource Policy ARN) | ❌ | ❌ | ❌ |


### Resource Deletion and 'IsNukable' Check Option
#### Supported Resources for 'IsNukable' Check
For certain resources, such as `AMI`, `EBS`, `DHCP Option`, and others listed below, we support an option to verify whether the user has sufficient permissions to nuke the resources. If not, it will raise `error: INSUFFICIENT_PERMISSION` error.

Supported resources:
- AMI
- EBS
- DHCP Option
- Egress only Internet Gateway
- Endpoints
- Internet Gatway
- IPAM
- IPAM BYOASN
- IPAM Custom Allocation
- IPAM Pool
- IPAM Resource Discovery
- IPAM Scope
- Key Pair
- Network ACL
- Network Interface
- Subnet
- VPC
- Elastic IP
- Launch Template
- NAT Gateway
- Network Firewall
- Security Group
- SnapShot
- Transit Gateway

#### Unsupported Resources
Please note that the eligibility check for nukability relies on the `DryRun` feature provided by AWS. Regrettably, this feature is not available for all delete APIs of resource types. Hence, the 'eligibility check for nukability' option may not be accessible for all resource types


### How to Use

Once you created your config file, you can run a command like this to nuke resources with your config file:
Expand Down
22 changes: 18 additions & 4 deletions aws/resources/ami.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package resources

import (
"context"
"strings"

awsgo "github.com/aws/aws-sdk-go/aws"
"github.com/gruntwork-io/cloud-nuke/util"
"strings"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
Expand Down Expand Up @@ -54,6 +55,15 @@ func (ami *AMIs) getAll(c context.Context, configObj config.Config) ([]*string,
}
}

// checking the nukable permissions
ami.VerifyNukablePermissions(imageIds, func(id *string) error {
_, err := ami.Client.DeregisterImage(&ec2.DeregisterImageInput{
ImageId: id,
DryRun: awsgo.Bool(true),
})
return err
})

return imageIds, nil
}

Expand All @@ -62,17 +72,21 @@ func (ami *AMIs) nukeAll(imageIds []*string) error {
if len(imageIds) == 0 {
logging.Debugf("No AMI to nuke in region %s", ami.Region)
return nil

}

logging.Debugf("Deleting all AMI in region %s", ami.Region)

deletedCount := 0
for _, imageID := range imageIds {
params := &ec2.DeregisterImageInput{
ImageId: imageID,
if nukable, reason := ami.IsNukable(awsgo.StringValue(imageID)); !nukable {
logging.Debugf("[Skipping] %s nuke because %v", awsgo.StringValue(imageID), reason)
continue
}

_, err := ami.Client.DeregisterImage(params)
_, err := ami.Client.DeregisterImage(&ec2.DeregisterImageInput{
ImageId: imageID,
})

// Record status of this resource
e := report.Entry{
Expand Down
5 changes: 5 additions & 0 deletions aws/resources/base_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ func (br *BaseAwsResource) PrepareContext(parentContext context.Context, resourc
// executed, and the result (error or success) is recorded using the SetNukableStatus method, indicating whether
// the specified action is nukable
func (br *BaseAwsResource) VerifyNukablePermissions(ids []*string, nukableCheckfn func(id *string) error) {
// check if the 'Nukables' map is initialized, and if it's not, initialize it
if br.Nukables == nil {
br.Nukables = make(map[string]error)
}

for _, id := range ids {
// skip if the id is already exists
if _, ok := br.GetNukableStatus(*id); ok {
Expand Down
17 changes: 17 additions & 0 deletions aws/resources/ebs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package resources

import (
"context"

"github.com/aws/aws-sdk-go/aws"
awsgo "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/gruntwork-io/cloud-nuke/config"
Expand Down Expand Up @@ -34,6 +36,15 @@ func (ev *EBSVolumes) getAll(c context.Context, configObj config.Config) ([]*str
}
}

// checking the nukable permissions
ev.VerifyNukablePermissions(volumeIds, func(id *string) error {
_, err := ev.Client.DeleteVolume(&ec2.DeleteVolumeInput{
VolumeId: id,
DryRun: awsgo.Bool(true),
})
return err
})

return volumeIds, nil
}

Expand Down Expand Up @@ -63,6 +74,12 @@ func (ev *EBSVolumes) nukeAll(volumeIds []*string) error {
var deletedVolumeIDs []*string

for _, volumeID := range volumeIds {

if nukable, reason := ev.IsNukable(awsgo.StringValue(volumeID)); !nukable {
logging.Debugf("[Skipping] %s nuke because %v", awsgo.StringValue(volumeID), reason)
continue
}

params := &ec2.DeleteVolumeInput{
VolumeId: volumeID,
}
Expand Down
13 changes: 13 additions & 0 deletions aws/resources/ec2_dhcp_option.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,24 @@ func (v *EC2DhcpOption) getAll(_ context.Context, configObj config.Config) ([]*s
return nil, errors.WithStackTrace(err)
}

// checking the nukable permissions
v.VerifyNukablePermissions(dhcpOptionIds, func(id *string) error {
_, err := v.Client.DeleteDhcpOptions(&ec2.DeleteDhcpOptionsInput{
DhcpOptionsId: id,
DryRun: awsgo.Bool(true),
})
return err
})

return dhcpOptionIds, nil
}

func (v *EC2DhcpOption) nukeAll(identifiers []*string) error {
for _, identifier := range identifiers {
if nukable, reason := v.IsNukable(awsgo.StringValue(identifier)); !nukable {
logging.Debugf("[Skipping] %s nuke because %v", awsgo.StringValue(identifier), reason)
continue
}

err := nukeDhcpOption(v.Client, identifier)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions aws/resources/ec2_egress_only_igw.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ func (egigw *EgressOnlyInternetGateway) nukeAll(ids []*string) error {
// NOTE : We can skip the error checking and return it here, since it is already being checked while displaying the identifiers with the Nukable field.
// Here, `err` refers to the error indicating whether the identifier is eligible for nuke or not (an error which we got from aws when tried to delete the resource with dryRun),
// and it is not a programming error. (edited)
if nukable, err := egigw.IsNukable(*id); !nukable {
logging.Debugf("[Skipping] %s nuke because %v", *id, err)
if nukable, reason := egigw.IsNukable(*id); !nukable {
logging.Debugf("[Skipping] %s nuke because %v", *id, reason)
continue
}

Expand Down
4 changes: 2 additions & 2 deletions aws/resources/ec2_endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ func (e *EC2Endpoints) nukeAll(identifiers []*string) error {
var deletedAddresses []*string

for _, id := range identifiers {
if nukable, err := e.IsNukable(*id); !nukable {
logging.Debugf("[Skipping] %s nuke because %v", *id, err)
if nukable, reason := e.IsNukable(*id); !nukable {
logging.Debugf("[Skipping] %s nuke because %v", *id, reason)
continue
}

Expand Down
4 changes: 2 additions & 2 deletions aws/resources/ec2_internet_gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ func (igw *InternetGateway) nukeAll(identifiers []*string) error {
var deletedGateways []*string

for _, id := range identifiers {
if nukable, err := igw.IsNukable(*id); !nukable {
logging.Debugf("[Skipping] %s nuke because %v", *id, err)
if nukable, reason := igw.IsNukable(*id); !nukable {
logging.Debugf("[Skipping] %s nuke because %v", *id, reason)
continue
}

Expand Down
17 changes: 16 additions & 1 deletion aws/resources/ec2_ipam.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ func (ec2Ipam *EC2IPAMs) getAll(c context.Context, configObj config.Config) ([]*
return nil, errors.WithStackTrace(err)
}

// checking the nukable permissions
ec2Ipam.VerifyNukablePermissions(result, func(id *string) error {
_, err := ec2Ipam.Client.DeleteIpam(&ec2.DeleteIpamInput{
IpamId: id,
Cascade: aws.Bool(true),
DryRun: awsgo.Bool(true),
})
return err
})

return result, nil
}

Expand Down Expand Up @@ -220,7 +230,7 @@ func (ec2Ipam *EC2IPAMs) nukeIPAM(id *string) error {
// items we need delete/detach them before actually deleting it.
// NOTE: The actual IPAM deletion should always be the last one. This way we
// can guarantee that it will fail if we forgot to delete/detach an item.
functions := []func(userName *string) error{
functions := []func(*string) error{
ec2Ipam.nukePublicIPAMPools,
ec2Ipam.deleteIPAM,
}
Expand All @@ -246,6 +256,11 @@ func (ec2Ipam *EC2IPAMs) nukeAll(ids []*string) error {

for _, id := range ids {

if nukable, reason := ec2Ipam.IsNukable(awsgo.StringValue(id)); !nukable {
logging.Debugf("[Skipping] %s nuke because %v", awsgo.StringValue(id), reason)
continue
}

err := ec2Ipam.nukeIPAM(id)

// Record status of this resource
Expand Down
19 changes: 16 additions & 3 deletions aws/resources/ec2_ipam_byoasn.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"

"github.com/aws/aws-sdk-go/aws"
awsgo "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/gruntwork-io/cloud-nuke/config"
"github.com/gruntwork-io/cloud-nuke/logging"
Expand All @@ -26,6 +27,15 @@ func (byoasn *EC2IPAMByoasn) getAll(c context.Context, configObj config.Config)
for _, out := range output.Byoasns {
result = append(result, out.Asn)
}

// checking the nukable permissions
byoasn.VerifyNukablePermissions(result, func(id *string) error {
_, err := byoasn.Client.DisassociateIpamByoasn(&ec2.DisassociateIpamByoasnInput{
Asn: id,
DryRun: awsgo.Bool(true),
})
return err
})
return result, nil
}

Expand All @@ -40,11 +50,14 @@ func (byoasn *EC2IPAMByoasn) nukeAll(asns []*string) error {
var list []*string

for _, id := range asns {
params := &ec2.DisassociateIpamByoasnInput{
Asn: id,
if nukable, reason := byoasn.IsNukable(awsgo.StringValue(id)); !nukable {
logging.Debugf("[Skipping] %s nuke because %v", awsgo.StringValue(id), reason)
continue
}

_, err := byoasn.Client.DisassociateIpamByoasn(params)
_, err := byoasn.Client.DisassociateIpamByoasn(&ec2.DisassociateIpamByoasnInput{
Asn: id,
})

// Record status of this resource
e := report.Entry{
Expand Down
28 changes: 28 additions & 0 deletions aws/resources/ec2_ipam_custom_allocation.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,29 @@ func (cs *EC2IPAMCustomAllocation) getAll(c context.Context, configObj config.Co
}
}

// checking the nukable permissions
cs.VerifyNukablePermissions(result, func(id *string) error {
cidr, err := cs.getPoolAllocationCIDR(id)
if err != nil {
logging.Errorf("[Failed] %s", err)
return err
}

allocationIPAMPoolID, ok := cs.PoolAndAllocationMap[*id]
if !ok {
logging.Errorf("[Failed] %s", fmt.Errorf("unable to find the pool allocation with %s", *id))
return fmt.Errorf("unable to find the pool allocation with %s", *id)
}

_, err = cs.Client.ReleaseIpamPoolAllocation(&ec2.ReleaseIpamPoolAllocationInput{
IpamPoolId: &allocationIPAMPoolID,
IpamPoolAllocationId: id,
Cidr: cidr,
DryRun: awsgo.Bool(true),
})
return err
})

return result, nil
}

Expand All @@ -107,6 +130,11 @@ func (cs *EC2IPAMCustomAllocation) nukeAll(ids []*string) error {

for _, id := range ids {

if nukable, reason := cs.IsNukable(awsgo.StringValue(id)); !nukable {
logging.Debugf("[Skipping] %s nuke because %v", awsgo.StringValue(id), reason)
continue
}

// get the IPamPool details
cidr, err := cs.getPoolAllocationCIDR(id)
if err != nil {
Expand Down
19 changes: 16 additions & 3 deletions aws/resources/ec2_ipam_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ func (ec2Pool *EC2IPAMPool) getAll(c context.Context, configObj config.Config) (
return nil, errors.WithStackTrace(err)
}

// checking the nukable permissions
ec2Pool.VerifyNukablePermissions(result, func(id *string) error {
_, err := ec2Pool.Client.DeleteIpamPool(&ec2.DeleteIpamPoolInput{
IpamPoolId: id,
DryRun: awsgo.Bool(true),
})
return err
})

return result, nil
}

Expand All @@ -78,11 +87,15 @@ func (pool *EC2IPAMPool) nukeAll(ids []*string) error {
var deletedAddresses []*string

for _, id := range ids {
params := &ec2.DeleteIpamPoolInput{
IpamPoolId: id,

if nukable, reason := pool.IsNukable(awsgo.StringValue(id)); !nukable {
logging.Debugf("[Skipping] %s nuke because %v", awsgo.StringValue(id), reason)
continue
}

_, err := pool.Client.DeleteIpamPool(params)
_, err := pool.Client.DeleteIpamPool(&ec2.DeleteIpamPoolInput{
IpamPoolId: id,
})

// Record status of this resource
e := report.Entry{
Expand Down
19 changes: 16 additions & 3 deletions aws/resources/ec2_ipam_resource_discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ func (discovery *EC2IPAMResourceDiscovery) getAll(c context.Context, configObj c
return nil, errors.WithStackTrace(err)
}

// checking the nukable permissions
discovery.VerifyNukablePermissions(result, func(id *string) error {
_, err := discovery.Client.DeleteIpamResourceDiscovery(&ec2.DeleteIpamResourceDiscoveryInput{
IpamResourceDiscoveryId: id,
DryRun: awsgo.Bool(true),
})
return err
})

return result, nil
}

Expand All @@ -77,11 +86,15 @@ func (discovery *EC2IPAMResourceDiscovery) nukeAll(ids []*string) error {
var deletedAddresses []*string

for _, id := range ids {
params := &ec2.DeleteIpamResourceDiscoveryInput{
IpamResourceDiscoveryId: id,

if nukable, reason := discovery.IsNukable(awsgo.StringValue(id)); !nukable {
logging.Debugf("[Skipping] %s nuke because %v", awsgo.StringValue(id), reason)
continue
}

_, err := discovery.Client.DeleteIpamResourceDiscovery(params)
_, err := discovery.Client.DeleteIpamResourceDiscovery(&ec2.DeleteIpamResourceDiscoveryInput{
IpamResourceDiscoveryId: id,
})
// Record status of this resource
e := report.Entry{
Identifier: awsgo.StringValue(id),
Expand Down
Loading

0 comments on commit d53472e

Please sign in to comment.