@@ -17,12 +17,14 @@ import (
1717 "github.com/aws/aws-sdk-go-v2/service/ec2"
1818 awstypes "github.com/aws/aws-sdk-go-v2/service/ec2/types"
1919 "github.com/hashicorp/aws-sdk-go-base/v2/tfawserr"
20+ "github.com/hashicorp/terraform-plugin-log/tflog"
2021 "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
2122 "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
2223 "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
2324 "github.com/hashicorp/terraform-provider-aws/internal/conns"
2425 "github.com/hashicorp/terraform-provider-aws/internal/enum"
2526 "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag"
27+ "github.com/hashicorp/terraform-provider-aws/internal/logging"
2628 "github.com/hashicorp/terraform-provider-aws/internal/provider/sdkv2/importer"
2729 "github.com/hashicorp/terraform-provider-aws/internal/retry"
2830 tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices"
@@ -389,13 +391,15 @@ func resourceVPCDelete(ctx context.Context, d *schema.ResourceData, meta any) di
389391 var diags diag.Diagnostics
390392 conn := meta .(* conns.AWSClient ).EC2Client (ctx )
391393
392- log .Printf ("[INFO] Deleting EC2 VPC: %s" , d .Id ())
394+ ctx = tflog .SetField (ctx , logging .KeyResourceId , d .Id ())
395+ tflog .Info (ctx , "Deleting EC2 VPC" )
396+
393397 input := ec2.DeleteVpcInput {
394398 VpcId : aws .String (d .Id ()),
395399 }
396- _ , err := tfresource . RetryWhenAWSErrCodeEquals ( ctx , d . Timeout ( schema . TimeoutDelete ), func ( ctx context. Context ) ( any , error ) {
397- return conn . DeleteVpc ( ctx , & input )
398- }, errCodeDependencyViolation )
400+
401+ // First attempt at deletion.
402+ _ , err := conn . DeleteVpc ( ctx , & input )
399403
400404 if tfawserr .ErrCodeEquals (err , errCodeInvalidVPCIDNotFound ) {
401405 return diags
@@ -407,6 +411,52 @@ func resourceVPCDelete(ctx context.Context, d *schema.ResourceData, meta any) di
407411 return diags
408412 }
409413
414+ // Defers checking for GuardDuty-managed resources until we get a DependencyViolation error so that no new permissions,
415+ // such as ec2:DescribeVpcEndpoints, are required for users who do not have GuardDuty monitoring enabled for their VPCs.
416+ var guardDutyDiags diag.Diagnostics
417+ if tfawserr .ErrCodeEquals (err , errCodeDependencyViolation ) {
418+ tflog .Debug (ctx , "VPC deletion failed with DependencyViolation, checking for GuardDuty resources" , map [string ]any {
419+ "error" : err ,
420+ })
421+
422+ endpointsErr := detectAndDeleteGuardDutyVPCEndpoints (ctx , conn , d .Id ())
423+ if endpointsErr != nil {
424+ if isUnauthorizedError (endpointsErr ) {
425+ guardDutyDiags = sdkdiag .AppendWarningf (guardDutyDiags ,
426+ "While deleting EC2 VPC %q, the provider was unable to do check for or dissociate GuardDuty-managed resources.\n " +
427+ "If GuardDuty monitoring is enabled for this VPC, the missing permissions will prevent deletion of the Subnet\n \n " +
428+ "Error: %s" , d .Id (), endpointsErr .Error (),
429+ )
430+ } else {
431+ return sdkdiag .AppendErrorf (diags , "deleting GuardDuty VPC endpoints for EC2 VPC %q: %s" , d .Id (), endpointsErr )
432+ }
433+ }
434+
435+ sgErr := detectAndDeleteGuardDutySecurityGroups (ctx , conn , d .Id ())
436+ if sgErr != nil {
437+ if isUnauthorizedError (sgErr ) {
438+ guardDutyDiags = sdkdiag .AppendWarningf (guardDutyDiags ,
439+ "While deleting EC2 VPC %q, the provider was unable to do check for or dissociate GuardDuty-managed resources.\n " +
440+ "If GuardDuty monitoring is enabled for this VPC, the missing permissions will prevent deletion of the Subnet\n \n " +
441+ "Error: %s" , d .Id (), sgErr .Error (),
442+ )
443+ } else {
444+ return sdkdiag .AppendErrorf (diags , "deleting GuardDuty VPC security groups for EC2 VPC %q: %s" , d .Id (), sgErr )
445+ }
446+ }
447+
448+ // Retry the deletion now that GuardDuty resources have been cleaned up.
449+ _ , err = tfresource .RetryWhenAWSErrCodeEquals (ctx , d .Timeout (schema .TimeoutDelete ), func (ctx context.Context ) (any , error ) {
450+ return conn .DeleteVpc (ctx , & input )
451+ }, errCodeDependencyViolation )
452+ }
453+
454+ // Only append GuardDuty-related warnings if we're still seeing a DependencyViolation:
455+ // If there's no longer a DependencyViolation, any GuardDuty-related warings are not relevant.
456+ if tfawserr .ErrCodeEquals (err , errCodeDependencyViolation ) {
457+ diags = append (diags , guardDutyDiags ... )
458+ }
459+
410460 if err != nil {
411461 return sdkdiag .AppendErrorf (diags , "deleting EC2 VPC (%s): %s" , d .Id (), err )
412462 }
@@ -756,3 +806,116 @@ func resourceVPCFlatten(ctx context.Context, client *conns.AWSClient, vpc *awsty
756806func vpcARN (ctx context.Context , c * conns.AWSClient , accountID , vpcID string ) string {
757807 return c .RegionalARNWithAccount (ctx , names .EC2 , accountID , "vpc/" + vpcID )
758808}
809+
810+ func detectAndDeleteGuardDutySecurityGroups (ctx context.Context , conn * ec2.Client , vpcID string ) error {
811+ tflog .Debug (ctx , "Detecting GuardDuty security groups in VPC" )
812+
813+ sgs , err := findGuardDutySecurityGroupsForVPC (ctx , conn , vpcID )
814+ if err != nil {
815+ if isUnauthorizedError (err ) {
816+ return err
817+ }
818+ return fmt .Errorf ("listing GuardDuty security groups for VPC %q: %w" , vpcID , err )
819+ }
820+
821+ if len (sgs ) == 0 {
822+ tflog .Debug (ctx , "No GuardDuty security groups found in VPC" )
823+ return nil
824+ }
825+
826+ tflog .Debug (ctx , "Found GuardDuty security group(s) in VPC" , map [string ]any {
827+ "count" : len (sgs ),
828+ })
829+
830+ for _ , sg := range sgs {
831+ groupID := aws .ToString (sg .GroupId )
832+ ctx := tflog .SetField (ctx , "group_id" , groupID )
833+
834+ tflog .Debug (ctx , "Deleting GuardDuty security group" )
835+
836+ deleteInput := ec2.DeleteSecurityGroupInput {
837+ GroupId : aws .String (groupID ),
838+ }
839+ _ , err := conn .DeleteSecurityGroup (ctx , & deleteInput )
840+ if err != nil {
841+ if isUnauthorizedError (err ) {
842+ return err
843+ }
844+ return fmt .Errorf ("deleting GuardDuty security group %q: %w" , groupID , err )
845+ }
846+
847+ tflog .Debug (ctx , "Successfully deleted GuardDuty security group" )
848+ }
849+
850+ return nil
851+ }
852+
853+ func detectAndDeleteGuardDutyVPCEndpoints (ctx context.Context , conn * ec2.Client , vpcID string ) error {
854+ tflog .Debug (ctx , "Checking for GuardDuty VPC endpoints for deletion" )
855+
856+ endpoints , err := findGuardDutyVPCEndpoints (ctx , conn , vpcID )
857+ if err != nil {
858+ if isUnauthorizedError (err ) {
859+ return err
860+ }
861+ return fmt .Errorf ("listing GuardDuty VPC endpoints for VPC %q: %w" , vpcID , err )
862+ }
863+
864+ if len (endpoints ) == 0 {
865+ tflog .Debug (ctx , "No GuardDuty VPC endpoints found" )
866+ return nil
867+ }
868+
869+ tflog .Debug (ctx , "Found GuardDuty VPC endpoints" , map [string ]any {
870+ "count" : len (endpoints ),
871+ })
872+
873+ for _ , endpoint := range endpoints {
874+ endpointID := aws .ToString (endpoint .VpcEndpointId )
875+ ctx := tflog .SetField (ctx , "endpoint_id" , endpointID )
876+
877+ tflog .Debug (ctx , "Deleting GuardDuty VPC endpoint" )
878+
879+ deleteInput := ec2.DeleteVpcEndpointsInput {
880+ VpcEndpointIds : []string {endpointID },
881+ }
882+ _ , err := conn .DeleteVpcEndpoints (ctx , & deleteInput )
883+ if err != nil {
884+ if isUnauthorizedError (err ) {
885+ return err
886+ }
887+ if tfawserr .ErrCodeEquals (err , errCodeInvalidVPCEndpointIdNotFound ) {
888+ tflog .Debug (ctx , "GuardDuty VPC endpoint not found during deletion" )
889+ continue
890+ }
891+ return fmt .Errorf ("deleting GuardDuty VPC endpoint %q in VPC %q: %w" , endpointID , vpcID , err )
892+ }
893+
894+ if err := waitVPCEndpointDeleted (ctx , conn , endpointID , vpcEndpointDeletionTimeout ); err != nil {
895+ if tfawserr .ErrCodeEquals (err , errCodeInvalidVPCEndpointIdNotFound ) {
896+ tflog .Debug (ctx , "GuardDuty VPC endpoint not found while waiting for deleted state" )
897+ continue
898+ }
899+ return fmt .Errorf ("waiting for GuardDuty VPC endpoint %q to reach deleted state in VPC %q: %w" , endpointID , vpcID , err )
900+ }
901+
902+ tflog .Debug (ctx , "Successfully deleted GuardDuty VPC endpoint" )
903+ }
904+
905+ return nil
906+ }
907+
908+ func guardDutySecurityGroupNameForVPC (vpcID string ) string {
909+ return guardDutySecurityGroupPrefix + vpcID
910+ }
911+
912+ func findGuardDutySecurityGroupsForVPC (ctx context.Context , conn * ec2.Client , vpcID string ) ([]awstypes.SecurityGroup , error ) {
913+ groupName := guardDutySecurityGroupNameForVPC (vpcID )
914+ return findSecurityGroups (ctx , conn , & ec2.DescribeSecurityGroupsInput {
915+ Filters : newAttributeFilterList (map [string ]string {
916+ "vpc-id" : vpcID ,
917+ "group-name" : groupName ,
918+ "tag:" + guardDutyManagedTagKey : guardDutyManagedTagValue ,
919+ }),
920+ })
921+ }
0 commit comments