@@ -2,14 +2,21 @@ package resources
22
33import (
44 "context"
5+ "encoding/json"
6+ "fmt"
57 "strings"
68
79 "github.com/gotidy/ptr"
810
11+ "github.com/aws/aws-sdk-go-v2/aws"
12+ "github.com/aws/aws-sdk-go-v2/service/iam"
13+ iamtypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
914 "github.com/aws/aws-sdk-go-v2/service/ssmquicksetup"
15+ "github.com/aws/aws-sdk-go-v2/service/sts"
1016
1117 "github.com/ekristen/libnuke/pkg/registry"
1218 "github.com/ekristen/libnuke/pkg/resource"
19+ "github.com/ekristen/libnuke/pkg/settings"
1320 "github.com/ekristen/libnuke/pkg/types"
1421
1522 "github.com/ekristen/aws-nuke/v3/pkg/nuke"
@@ -23,6 +30,9 @@ func init() {
2330 Scope : nuke .Account ,
2431 Resource : & SSMQuickSetupConfigurationManager {},
2532 Lister : & SSMQuickSetupConfigurationManagerLister {},
33+ Settings : []string {
34+ "CreateRoleToDelete" ,
35+ },
2636 })
2737}
2838
@@ -40,19 +50,26 @@ func (l *SSMQuickSetupConfigurationManagerLister) List(ctx context.Context, o in
4050
4151 for _ , p := range res .ConfigurationManagersList {
4252 resources = append (resources , & SSMQuickSetupConfigurationManager {
43- svc : svc ,
44- ARN : p .ManagerArn ,
45- Name : p .Name ,
53+ svc : svc ,
54+ iamSvc : iam .NewFromConfig (* opts .Config ),
55+ stsSvc : sts .NewFromConfig (* opts .Config ),
56+ ARN : p .ManagerArn ,
57+ Name : p .Name ,
58+ createdRoles : make (map [string ]bool ), // Track which roles we created
4659 })
4760 }
4861
4962 return resources , nil
5063}
5164
5265type SSMQuickSetupConfigurationManager struct {
53- svc * ssmquicksetup.Client
54- ARN * string
55- Name * string
66+ svc * ssmquicksetup.Client
67+ iamSvc * iam.Client
68+ stsSvc * sts.Client
69+ ARN * string
70+ Name * string
71+ settings * settings.Setting
72+ createdRoles map [string ]bool // Track roles created during deletion process
5673}
5774
5875// GetName returns the name of the resource or the last part of the ARN if not set so that the stringer resource has
@@ -66,10 +83,43 @@ func (r *SSMQuickSetupConfigurationManager) GetName() string {
6683 return parts [len (parts )- 1 ]
6784}
6885
86+ func (r * SSMQuickSetupConfigurationManager ) Settings (setting * settings.Setting ) {
87+ r .settings = setting
88+ }
89+
6990func (r * SSMQuickSetupConfigurationManager ) Remove (ctx context.Context ) error {
7091 _ , err := r .svc .DeleteConfigurationManager (ctx , & ssmquicksetup.DeleteConfigurationManagerInput {
7192 ManagerArn : r .ARN ,
7293 })
94+
95+ // Check if we got the specific error about role access and if CreateRoleToDelete setting is enabled
96+ if err != nil && r .settings .GetBool ("CreateRoleToDelete" ) {
97+ if roleName := r .extractRoleNameFromError (err ); roleName != "" {
98+ // Initialize createdRoles map if nil
99+ if r .createdRoles == nil {
100+ r .createdRoles = make (map [string ]bool )
101+ }
102+
103+ // Create the specific role mentioned in the error message
104+ if createErr := r .createRoleFromError (ctx , roleName ); createErr != nil {
105+ return createErr
106+ }
107+
108+ // Mark this role as created by us
109+ r .createdRoles [roleName ] = true
110+
111+ // Retry the deletion after creating role
112+ _ , err = r .svc .DeleteConfigurationManager (ctx , & ssmquicksetup.DeleteConfigurationManagerInput {
113+ ManagerArn : r .ARN ,
114+ })
115+ }
116+ }
117+
118+ // If deletion was successful and we created roles, clean them up
119+ if err == nil && len (r .createdRoles ) > 0 {
120+ r .cleanupCreatedRoles (ctx )
121+ }
122+
73123 return err
74124}
75125
@@ -80,3 +130,258 @@ func (r *SSMQuickSetupConfigurationManager) Properties() types.Properties {
80130func (r * SSMQuickSetupConfigurationManager ) String () string {
81131 return r .GetName ()
82132}
133+
134+ // cleanupCreatedRoles removes all roles that were created during the deletion process
135+ func (r * SSMQuickSetupConfigurationManager ) cleanupCreatedRoles (ctx context.Context ) {
136+ // Clean up roles in order: execution roles first, then admin roles
137+ // This ensures we don't have dependency issues
138+ var adminRoles []string
139+ var execRoles []string
140+
141+ for roleName := range r .createdRoles {
142+ if strings .Contains (roleName , "Administration" ) {
143+ adminRoles = append (adminRoles , roleName )
144+ } else if strings .Contains (roleName , "Execution" ) {
145+ execRoles = append (execRoles , roleName )
146+ }
147+ }
148+
149+ // Clean up execution roles first
150+ for _ , roleName := range execRoles {
151+ if err := r .cleanupRole (ctx , roleName , false ); err != nil {
152+ // Log the error but don't fail the overall operation
153+ // The main resource has been successfully deleted
154+ continue
155+ }
156+ }
157+
158+ // Clean up admin roles last
159+ for _ , roleName := range adminRoles {
160+ if err := r .cleanupRole (ctx , roleName , true ); err != nil {
161+ // Log the error but don't fail the overall operation
162+ // The main resource has been successfully deleted
163+ continue
164+ }
165+ }
166+ }
167+
168+ // cleanupRole removes a specific role and its associated policies
169+ func (r * SSMQuickSetupConfigurationManager ) cleanupRole (ctx context.Context , roleName string , isAdminRole bool ) error {
170+ // For admin roles, remove the inline policy first
171+ if isAdminRole {
172+ _ , err := r .iamSvc .DeleteRolePolicy (ctx , & iam.DeleteRolePolicyInput {
173+ RoleName : aws .String (roleName ),
174+ PolicyName : aws .String ("AssumeLocalExecutionRole" ),
175+ })
176+ if err != nil {
177+ return err
178+ }
179+ } else {
180+ // For execution roles, detach the managed policy
181+ policyArn := "arn:aws:iam::aws:policy/AWSQuickSetupDeploymentRolePolicy"
182+ _ , err := r .iamSvc .DetachRolePolicy (ctx , & iam.DetachRolePolicyInput {
183+ RoleName : aws .String (roleName ),
184+ PolicyArn : aws .String (policyArn ),
185+ })
186+ if err != nil {
187+ return err
188+ }
189+ }
190+
191+ // Delete the role
192+ _ , err := r .iamSvc .DeleteRole (ctx , & iam.DeleteRoleInput {
193+ RoleName : aws .String (roleName ),
194+ })
195+
196+ return err
197+ }
198+
199+ // extractRoleNameFromError extracts the role name from the error message
200+ func (r * SSMQuickSetupConfigurationManager ) extractRoleNameFromError (err error ) string {
201+ errStr := err .Error ()
202+
203+ // Look for the pattern "Role ROLE_NAME can't be accessed"
204+ if strings .Contains (errStr , "can't be accessed" ) {
205+ // Find "Role " and extract the role name after it
206+ roleIndex := strings .Index (errStr , "Role " )
207+ if roleIndex != - 1 {
208+ roleStart := roleIndex + 5 // Length of "Role "
209+ roleEnd := strings .Index (errStr [roleStart :], " can't be accessed" )
210+ if roleEnd != - 1 {
211+ return errStr [roleStart : roleStart + roleEnd ]
212+ }
213+ }
214+ }
215+
216+ return ""
217+ }
218+
219+ // createRoleFromError creates the specific role mentioned in the error message
220+ func (r * SSMQuickSetupConfigurationManager ) createRoleFromError (ctx context.Context , roleName string ) error {
221+ // Get current account ID
222+ callerIdentity , err := r .stsSvc .GetCallerIdentity (ctx , & sts.GetCallerIdentityInput {})
223+ if err != nil {
224+ return err
225+ }
226+ accountID := * callerIdentity .Account
227+
228+ // Determine which type of role to create based on the role name
229+ if strings .Contains (roleName , "Administration" ) {
230+ return r .createAdminRole (ctx , roleName , accountID )
231+ } else if strings .Contains (roleName , "Execution" ) {
232+ return r .createExecRole (ctx , roleName , accountID )
233+ }
234+
235+ return nil
236+ }
237+
238+ // createAdminRole creates the LocalAdministrationRole with CloudFormation trust policy
239+ func (r * SSMQuickSetupConfigurationManager ) createAdminRole (ctx context.Context , roleName , accountID string ) error {
240+ // Define trust policy for CloudFormation service with conditions
241+ trustPolicy := map [string ]interface {}{
242+ "Version" : "2012-10-17" ,
243+ "Statement" : []map [string ]interface {}{
244+ {
245+ "Effect" : "Allow" ,
246+ "Principal" : map [string ]interface {}{
247+ "Service" : "cloudformation.amazonaws.com" ,
248+ },
249+ "Action" : "sts:AssumeRole" ,
250+ "Condition" : map [string ]interface {}{
251+ "StringEquals" : map [string ]interface {}{
252+ "aws:SourceAccount" : accountID ,
253+ },
254+ "StringLike" : map [string ]interface {}{
255+ "aws:SourceArn" : fmt .Sprintf ("arn:aws:cloudformation:*:%s:stackset/AWS-QuickSetup-*" , accountID ),
256+ },
257+ },
258+ },
259+ },
260+ }
261+
262+ trustPolicyJSON , err := json .Marshal (trustPolicy )
263+ if err != nil {
264+ return err
265+ }
266+
267+ // Create the role
268+ _ , err = r .iamSvc .CreateRole (ctx , & iam.CreateRoleInput {
269+ RoleName : aws .String (roleName ),
270+ AssumeRolePolicyDocument : aws .String (string (trustPolicyJSON )),
271+ Description : aws .String ("LocalAdministrationRole created by aws-nuke for SSM QuickSetup Configuration Manager deletion" ),
272+ Path : aws .String ("/" ),
273+ Tags : []iamtypes.Tag {
274+ {
275+ Key : aws .String ("CreatedBy" ),
276+ Value : aws .String ("aws-nuke" ),
277+ },
278+ {
279+ Key : aws .String ("Purpose" ),
280+ Value : aws .String ("SSMQuickSetupConfigurationManager-Deletion" ),
281+ },
282+ {
283+ Key : aws .String ("ConfigurationManager" ),
284+ Value : aws .String (r .GetName ()),
285+ },
286+ },
287+ })
288+
289+ if err != nil {
290+ return err
291+ }
292+
293+ // Create inline policy for assuming execution roles (both LocalExecutionRole and LocalDeploymentExecutionRole)
294+ execRoleBaseName := strings .Replace (roleName , "LocalAdministrationRole" , "" , 1 )
295+ policyDocument := map [string ]interface {}{
296+ "Version" : "2012-10-17" ,
297+ "Statement" : []map [string ]interface {}{
298+ {
299+ "Action" : []string {"sts:AssumeRole" },
300+ "Resource" : []string {
301+ fmt .Sprintf ("arn:aws:iam::%s:role/%sLocalExecutionRole" , accountID , execRoleBaseName ),
302+ fmt .Sprintf ("arn:aws:iam::%s:role/%sLocalDeploymentExecutionRole" , accountID , execRoleBaseName ),
303+ },
304+ "Effect" : "Allow" ,
305+ },
306+ },
307+ }
308+
309+ policyJSON , err := json .Marshal (policyDocument )
310+ if err != nil {
311+ return err
312+ }
313+
314+ // Attach inline policy
315+ _ , err = r .iamSvc .PutRolePolicy (ctx , & iam.PutRolePolicyInput {
316+ RoleName : aws .String (roleName ),
317+ PolicyName : aws .String ("AssumeLocalExecutionRole" ),
318+ PolicyDocument : aws .String (string (policyJSON )),
319+ })
320+
321+ return err
322+ }
323+
324+ // createExecRole creates the LocalExecutionRole/LocalDeploymentExecutionRole with LocalAdministrationRole trust policy
325+ func (r * SSMQuickSetupConfigurationManager ) createExecRole (ctx context.Context , roleName , accountID string ) error {
326+ // Derive the corresponding admin role name from the execution role name
327+ var adminRoleName string
328+ if strings .Contains (roleName , "LocalExecutionRole" ) {
329+ adminRoleName = strings .Replace (roleName , "LocalExecutionRole" , "LocalAdministrationRole" , 1 )
330+ } else if strings .Contains (roleName , "LocalDeploymentExecutionRole" ) {
331+ adminRoleName = strings .Replace (roleName , "LocalDeploymentExecutionRole" , "LocalAdministrationRole" , 1 )
332+ }
333+
334+ // Define trust policy for LocalAdministrationRole
335+ trustPolicy := map [string ]interface {}{
336+ "Version" : "2012-10-17" ,
337+ "Statement" : []map [string ]interface {}{
338+ {
339+ "Effect" : "Allow" ,
340+ "Principal" : map [string ]interface {}{
341+ "AWS" : fmt .Sprintf ("arn:aws:iam::%s:role/%s" , accountID , adminRoleName ),
342+ },
343+ "Action" : "sts:AssumeRole" ,
344+ },
345+ },
346+ }
347+
348+ trustPolicyJSON , err := json .Marshal (trustPolicy )
349+ if err != nil {
350+ return err
351+ }
352+
353+ // Create the role
354+ _ , err = r .iamSvc .CreateRole (ctx , & iam.CreateRoleInput {
355+ RoleName : aws .String (roleName ),
356+ AssumeRolePolicyDocument : aws .String (string (trustPolicyJSON )),
357+ Description : aws .String ("LocalExecutionRole created by aws-nuke for SSM QuickSetup Configuration Manager deletion" ),
358+ Path : aws .String ("/" ),
359+ Tags : []iamtypes.Tag {
360+ {
361+ Key : aws .String ("Managed" ),
362+ Value : aws .String ("aws-nuke" ),
363+ },
364+ {
365+ Key : aws .String ("Purpose" ),
366+ Value : aws .String ("SSMQuickSetupConfigurationManager-Deletion" ),
367+ },
368+ {
369+ Key : aws .String ("ConfigurationManager" ),
370+ Value : aws .String (r .GetName ()),
371+ },
372+ },
373+ })
374+
375+ if err != nil {
376+ return err
377+ }
378+
379+ // Attach the AWSQuickSetupDeploymentRolePolicy
380+ policyArn := "arn:aws:iam::aws:policy/AWSQuickSetupDeploymentRolePolicy"
381+ _ , err = r .iamSvc .AttachRolePolicy (ctx , & iam.AttachRolePolicyInput {
382+ RoleName : aws .String (roleName ),
383+ PolicyArn : aws .String (policyArn ),
384+ })
385+
386+ return err
387+ }
0 commit comments