@@ -15,6 +15,9 @@ import (
15
15
"github.com/aws/aws-sdk-go/service/cloudformation"
16
16
"github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface"
17
17
18
+ "github.com/aws/aws-sdk-go-v2/service/iam"
19
+ iamtypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
20
+
18
21
liberrors "github.com/ekristen/libnuke/pkg/errors"
19
22
"github.com/ekristen/libnuke/pkg/registry"
20
23
"github.com/ekristen/libnuke/pkg/resource"
@@ -36,6 +39,7 @@ func init() {
36
39
Lister : & CloudFormationStackLister {},
37
40
Settings : []string {
38
41
"DisableDeletionProtection" ,
42
+ "CreateRoleToDeleteStack" ,
39
43
},
40
44
})
41
45
}
@@ -46,6 +50,7 @@ func (l *CloudFormationStackLister) List(_ context.Context, o interface{}) ([]re
46
50
opts := o .(* nuke.ListerOpts )
47
51
48
52
svc := cloudformation .New (opts .Session )
53
+ iamSvc := iam .NewFromConfig (* opts .Config )
49
54
50
55
params := & cloudformation.DescribeStacksInput {}
51
56
resources := make ([]resource.Resource , 0 )
@@ -56,11 +61,26 @@ func (l *CloudFormationStackLister) List(_ context.Context, o interface{}) ([]re
56
61
return nil , err
57
62
}
58
63
for _ , stack := range resp .Stacks {
59
- resources = append ( resources , & CloudFormationStack {
64
+ newResource := & CloudFormationStack {
60
65
svc : svc ,
61
- stack : stack ,
66
+ iamSvc : iamSvc ,
67
+ logger : opts .Logger ,
62
68
maxDeleteAttempts : CloudformationMaxDeleteAttempt ,
63
- })
69
+ Name : stack .StackName ,
70
+ Status : stack .StackStatus ,
71
+ description : stack .Description ,
72
+ parentID : stack .ParentId ,
73
+ roleARN : stack .RoleARN ,
74
+ CreationTime : stack .CreationTime ,
75
+ LastUpdatedTime : stack .LastUpdatedTime ,
76
+ Tags : stack .Tags ,
77
+ }
78
+
79
+ if newResource .LastUpdatedTime == nil {
80
+ newResource .LastUpdatedTime = newResource .CreationTime
81
+ }
82
+
83
+ resources = append (resources , newResource )
64
84
}
65
85
66
86
if resp .NextToken == nil {
@@ -75,58 +95,106 @@ func (l *CloudFormationStackLister) List(_ context.Context, o interface{}) ([]re
75
95
76
96
type CloudFormationStack struct {
77
97
svc cloudformationiface.CloudFormationAPI
78
- stack * cloudformation.Stack
79
- maxDeleteAttempts int
98
+ iamSvc * iam.Client
80
99
settings * settings.Setting
100
+ logger * logrus.Entry
101
+ Name * string
102
+ Status * string
103
+ CreationTime * time.Time
104
+ LastUpdatedTime * time.Time
105
+ Tags []* cloudformation.Tag
106
+ description * string
107
+ parentID * string
108
+ roleARN * string
109
+ maxDeleteAttempts int
81
110
}
82
111
83
- func (cfs * CloudFormationStack ) Filter () error {
84
- if ptr .ToString (cfs . stack . Description ) == "DO NOT MODIFY THIS STACK! This stack is managed by Config Conformance Packs." {
112
+ func (r * CloudFormationStack ) Filter () error {
113
+ if ptr .ToString (r . description ) == "DO NOT MODIFY THIS STACK! This stack is managed by Config Conformance Packs." {
85
114
return fmt .Errorf ("stack is managed by Config Conformance Packs" )
86
115
}
87
116
return nil
88
117
}
89
118
90
- func (cfs * CloudFormationStack ) Settings (setting * settings.Setting ) {
91
- cfs .settings = setting
119
+ func (r * CloudFormationStack ) Settings (setting * settings.Setting ) {
120
+ r .settings = setting
92
121
}
93
122
94
- func (cfs * CloudFormationStack ) Remove (_ context.Context ) error {
95
- return cfs .removeWithAttempts (0 )
123
+ func (r * CloudFormationStack ) Remove (ctx context.Context ) error {
124
+ return r .removeWithAttempts (ctx , 0 )
96
125
}
97
126
98
- func (cfs * CloudFormationStack ) removeWithAttempts (attempt int ) error {
99
- if err := cfs .doRemove (); err != nil {
100
- // TODO: pass logrus in via ListerOpts so that it can be used here instead of global
127
+ func (r * CloudFormationStack ) createRole (ctx context.Context ) error {
128
+ roleParts := strings .Split (ptr .ToString (r .roleARN ), "/" )
129
+ _ , err := r .iamSvc .CreateRole (ctx , & iam.CreateRoleInput {
130
+ RoleName : ptr .String (roleParts [len (roleParts )- 1 ]),
131
+ AssumeRolePolicyDocument : ptr .String (`{
132
+ "Version": "2012-10-17",
133
+ "Statement": [
134
+ {
135
+ "Effect": "Allow",
136
+ "Principal": {
137
+ "Service": "cloudformation.amazonaws.com"
138
+ },
139
+ "Action": "sts:AssumeRole"
140
+ }
141
+ ]
142
+ }` ),
143
+ Tags : []iamtypes.Tag {
144
+ {
145
+ Key : ptr .String ("Managed" ),
146
+ Value : ptr .String ("aws-nuke" ),
147
+ },
148
+ },
149
+ })
150
+
151
+ return err
152
+ }
101
153
102
- logrus .Errorf ("CloudFormationStack stackName=%s attempt=%d maxAttempts=%d delete failed: %s" ,
103
- * cfs .stack .StackName , attempt , cfs .maxDeleteAttempts , err .Error ())
154
+ func (r * CloudFormationStack ) removeWithAttempts (ctx context.Context , attempt int ) error {
155
+ if err := r .doRemove (); err != nil {
156
+ r .logger .Errorf ("CloudFormationStack stackName=%s attempt=%d maxAttempts=%d delete failed: %s" ,
157
+ * r .Name , attempt , r .maxDeleteAttempts , err .Error ())
104
158
105
159
var awsErr awserr.Error
106
160
ok := errors .As (err , & awsErr )
107
- if ok && awsErr .Code () == "ValidationError" &&
108
- awsErr .Message () == "Stack [" + * cfs .stack .StackName + "] cannot be deleted while TerminationProtection is enabled" {
109
- // check if the setting for the resource is set to allow deletion protection to be disabled
110
- if cfs .settings .GetBool ("DisableDeletionProtection" ) {
111
- logrus .Infof ("CloudFormationStack stackName=%s attempt=%d maxAttempts=%d updating termination protection" ,
112
- * cfs .stack .StackName , attempt , cfs .maxDeleteAttempts )
113
- _ , err = cfs .svc .UpdateTerminationProtection (& cloudformation.UpdateTerminationProtectionInput {
114
- EnableTerminationProtection : aws .Bool (false ),
115
- StackName : cfs .stack .StackName ,
116
- })
117
- if err != nil {
161
+ if ok && awsErr .Code () == "ValidationError" {
162
+ if awsErr .Message () == fmt .Sprintf ("Role %s is invalid or cannot be assumed" , * r .roleARN ) {
163
+ if r .settings .GetBool ("CreateRoleToDeleteStack" ) {
164
+ r .logger .Infof ("CloudFormationStack stackName=%s attempt=%d maxAttempts=%d creating role to delete stack" ,
165
+ * r .Name , attempt , r .maxDeleteAttempts )
166
+ if err := r .createRole (ctx ); err != nil {
167
+ return err
168
+ }
169
+ } else {
170
+ r .logger .Warnf ("CloudFormationStack stackName=%s attempt=%d maxAttempts=%d set feature flag to create role to delete stack" ,
171
+ * r .Name , attempt , r .maxDeleteAttempts )
172
+ return err
173
+ }
174
+ } else if awsErr .Message () == fmt .Sprintf ("Stack [%s] cannot be deleted while TerminationProtection is enabled" , * r .Name ) {
175
+ // check if the setting for the resource is set to allow deletion protection to be disabled
176
+ if r .settings .GetBool ("DisableDeletionProtection" ) {
177
+ r .logger .Infof ("CloudFormationStack stackName=%s attempt=%d maxAttempts=%d updating termination protection" ,
178
+ * r .Name , attempt , r .maxDeleteAttempts )
179
+ _ , err = r .svc .UpdateTerminationProtection (& cloudformation.UpdateTerminationProtectionInput {
180
+ EnableTerminationProtection : aws .Bool (false ),
181
+ StackName : r .Name ,
182
+ })
183
+ if err != nil {
184
+ return err
185
+ }
186
+ } else {
187
+ r .logger .Warnf ("CloudFormationStack stackName=%s attempt=%d maxAttempts=%d set feature flag to disable deletion protection" ,
188
+ * r .Name , attempt , r .maxDeleteAttempts )
118
189
return err
119
190
}
120
- } else {
121
- logrus .Warnf ("CloudFormationStack stackName=%s attempt=%d maxAttempts=%d set feature flag to disable deletion protection" ,
122
- * cfs .stack .StackName , attempt , cfs .maxDeleteAttempts )
123
- return err
124
191
}
125
192
}
126
- if attempt >= cfs .maxDeleteAttempts {
193
+
194
+ if attempt >= r .maxDeleteAttempts {
127
195
return errors .New ("CFS might not be deleted after this run" )
128
196
} else {
129
- return cfs .removeWithAttempts (attempt + 1 )
197
+ return r .removeWithAttempts (ctx , attempt + 1 )
130
198
}
131
199
} else {
132
200
return nil
@@ -148,9 +216,9 @@ func GetParentStack(svc cloudformationiface.CloudFormationAPI, stackID string) (
148
216
return nil , nil //nolint:nilnil
149
217
}
150
218
151
- func (cfs * CloudFormationStack ) doRemove () error { //nolint:gocyclo
152
- if cfs . stack . ParentId != nil {
153
- p , err := GetParentStack (cfs .svc , * cfs . stack . ParentId )
219
+ func (r * CloudFormationStack ) doRemove () error { //nolint:gocyclo
220
+ if r . parentID != nil {
221
+ p , err := GetParentStack (r .svc , * r . parentID )
154
222
if err != nil {
155
223
return err
156
224
}
@@ -160,14 +228,14 @@ func (cfs *CloudFormationStack) doRemove() error { //nolint:gocyclo
160
228
}
161
229
}
162
230
163
- o , err := cfs .svc .DescribeStacks (& cloudformation.DescribeStacksInput {
164
- StackName : cfs . stack . StackName ,
231
+ o , err := r .svc .DescribeStacks (& cloudformation.DescribeStacksInput {
232
+ StackName : r . Name ,
165
233
})
166
234
if err != nil {
167
235
var awsErr awserr.Error
168
236
if errors .As (err , & awsErr ) {
169
237
if awsErr .Code () == "ValidationFailed" && strings .HasSuffix (awsErr .Message (), " does not exist" ) {
170
- logrus . Infof ("CloudFormationStack stackName=%s no longer exists" , * cfs . stack . StackName )
238
+ r . logger . Infof ("CloudFormationStack stackName=%s no longer exists" , * r . Name )
171
239
return nil
172
240
}
173
241
}
@@ -179,16 +247,16 @@ func (cfs *CloudFormationStack) doRemove() error { //nolint:gocyclo
179
247
// stack already deleted, no need to re-delete
180
248
return nil
181
249
} else if * stack .StackStatus == cloudformation .StackStatusDeleteInProgress {
182
- logrus . Infof ("CloudFormationStack stackName=%s delete in progress. Waiting" , * cfs . stack . StackName )
183
- return cfs .svc .WaitUntilStackDeleteComplete (& cloudformation.DescribeStacksInput {
184
- StackName : cfs . stack . StackName ,
250
+ r . logger . Infof ("CloudFormationStack stackName=%s delete in progress. Waiting" , * r . Name )
251
+ return r .svc .WaitUntilStackDeleteComplete (& cloudformation.DescribeStacksInput {
252
+ StackName : r . Name ,
185
253
})
186
254
} else if * stack .StackStatus == cloudformation .StackStatusDeleteFailed {
187
- logrus . Infof ("CloudFormationStack stackName=%s delete failed. Attempting to retain and delete stack" , * cfs . stack . StackName )
255
+ r . logger . Infof ("CloudFormationStack stackName=%s delete failed. Attempting to retain and delete stack" , * r . Name )
188
256
// This means the CFS has undetectable resources.
189
257
// In order to move on with nuking, we retain them in the deletion.
190
- retainableResources , err := cfs .svc .ListStackResources (& cloudformation.ListStackResourcesInput {
191
- StackName : cfs . stack . StackName ,
258
+ retainableResources , err := r .svc .ListStackResources (& cloudformation.ListStackResourcesInput {
259
+ StackName : r . Name ,
192
260
})
193
261
if err != nil {
194
262
return err
@@ -202,71 +270,59 @@ func (cfs *CloudFormationStack) doRemove() error { //nolint:gocyclo
202
270
}
203
271
}
204
272
205
- _ , err = cfs .svc .DeleteStack (& cloudformation.DeleteStackInput {
206
- StackName : cfs . stack . StackName ,
273
+ if _ , err = r .svc .DeleteStack (& cloudformation.DeleteStackInput {
274
+ StackName : r . Name ,
207
275
RetainResources : retain ,
208
- })
209
- if err != nil {
276
+ }); err != nil {
210
277
return err
211
278
}
212
- return cfs .svc .WaitUntilStackDeleteComplete (& cloudformation.DescribeStacksInput {
213
- StackName : cfs .stack .StackName ,
279
+
280
+ return r .svc .WaitUntilStackDeleteComplete (& cloudformation.DescribeStacksInput {
281
+ StackName : r .Name ,
214
282
})
215
283
} else {
216
- if err := cfs .waitForStackToStabilize (* stack .StackStatus ); err != nil {
284
+ if err := r .waitForStackToStabilize (* stack .StackStatus ); err != nil {
217
285
return err
218
- } else if _ , err := cfs .svc .DeleteStack (& cloudformation.DeleteStackInput {
219
- StackName : cfs . stack . StackName ,
286
+ } else if _ , err := r .svc .DeleteStack (& cloudformation.DeleteStackInput {
287
+ StackName : r . Name ,
220
288
}); err != nil {
221
289
return err
222
- } else if err := cfs .svc .WaitUntilStackDeleteComplete (& cloudformation.DescribeStacksInput {
223
- StackName : cfs . stack . StackName ,
290
+ } else if err := r .svc .WaitUntilStackDeleteComplete (& cloudformation.DescribeStacksInput {
291
+ StackName : r . Name ,
224
292
}); err != nil {
225
293
return err
226
294
} else {
227
295
return nil
228
296
}
229
297
}
230
298
}
231
- func (cfs * CloudFormationStack ) waitForStackToStabilize (currentStatus string ) error {
299
+
300
+ func (r * CloudFormationStack ) waitForStackToStabilize (currentStatus string ) error {
232
301
switch currentStatus {
233
302
case cloudformation .StackStatusUpdateInProgress ,
234
303
cloudformation .StackStatusUpdateRollbackCompleteCleanupInProgress ,
235
304
cloudformation .StackStatusUpdateRollbackInProgress :
236
- logrus . Infof ("CloudFormationStack stackName=%s update in progress. Waiting to stabalize" , * cfs . stack . StackName )
305
+ r . logger . Infof ("CloudFormationStack stackName=%s update in progress. Waiting to stabalize" , * r . Name )
237
306
238
- return cfs .svc .WaitUntilStackUpdateComplete (& cloudformation.DescribeStacksInput {
239
- StackName : cfs . stack . StackName ,
307
+ return r .svc .WaitUntilStackUpdateComplete (& cloudformation.DescribeStacksInput {
308
+ StackName : r . Name ,
240
309
})
241
310
case cloudformation .StackStatusCreateInProgress ,
242
311
cloudformation .StackStatusRollbackInProgress :
243
- logrus . Infof ("CloudFormationStack stackName=%s create in progress. Waiting to stabalize" , * cfs . stack . StackName )
312
+ r . logger . Infof ("CloudFormationStack stackName=%s create in progress. Waiting to stabalize" , * r . Name )
244
313
245
- return cfs .svc .WaitUntilStackCreateComplete (& cloudformation.DescribeStacksInput {
246
- StackName : cfs . stack . StackName ,
314
+ return r .svc .WaitUntilStackCreateComplete (& cloudformation.DescribeStacksInput {
315
+ StackName : r . Name ,
247
316
})
248
317
default :
249
318
return nil
250
319
}
251
320
}
252
321
253
- func (cfs * CloudFormationStack ) Properties () types.Properties {
254
- properties := types .NewProperties ()
255
- properties .Set ("Name" , cfs .stack .StackName )
256
- properties .Set ("CreationTime" , cfs .stack .CreationTime .Format (time .RFC3339 ))
257
- if cfs .stack .LastUpdatedTime == nil {
258
- properties .Set ("LastUpdatedTime" , cfs .stack .CreationTime .Format (time .RFC3339 ))
259
- } else {
260
- properties .Set ("LastUpdatedTime" , cfs .stack .LastUpdatedTime .Format (time .RFC3339 ))
261
- }
262
-
263
- for _ , tagValue := range cfs .stack .Tags {
264
- properties .SetTag (tagValue .Key , tagValue .Value )
265
- }
266
-
267
- return properties
322
+ func (r * CloudFormationStack ) Properties () types.Properties {
323
+ return types .NewPropertiesFromStruct (r )
268
324
}
269
325
270
- func (cfs * CloudFormationStack ) String () string {
271
- return * cfs . stack . StackName
326
+ func (r * CloudFormationStack ) String () string {
327
+ return * r . Name
272
328
}
0 commit comments