@@ -3,6 +3,7 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline';
3
3
import * as iam from '@aws-cdk/aws-iam' ;
4
4
import * as cdk from '@aws-cdk/core' ;
5
5
import { Action } from '../action' ;
6
+ import { parseCapabilities , SingletonPolicy } from './private/singleton-policy' ;
6
7
7
8
// keep this import separate from other imports to reduce chance for merge conflicts with v2-main
8
9
// eslint-disable-next-line no-duplicate-imports, import/order
@@ -512,148 +513,3 @@ export class CloudFormationDeleteStackAction extends CloudFormationDeployAction
512
513
} ;
513
514
}
514
515
}
515
-
516
- /**
517
- * Manages a bunch of singleton-y statements on the policy of an IAM Role.
518
- * Dedicated methods can be used to add specific permissions to the role policy
519
- * using as few statements as possible (adding resources to existing compatible
520
- * statements instead of adding new statements whenever possible).
521
- *
522
- * Statements created outside of this class are not considered when adding new
523
- * permissions.
524
- */
525
- class SingletonPolicy extends Construct implements iam . IGrantable {
526
- /**
527
- * Obtain a SingletonPolicy for a given role.
528
- * @param role the Role this policy is bound to.
529
- * @returns the SingletonPolicy for this role.
530
- */
531
- public static forRole ( role : iam . IRole ) : SingletonPolicy {
532
- const found = role . node . tryFindChild ( SingletonPolicy . UUID ) ;
533
- return ( found as SingletonPolicy ) || new SingletonPolicy ( role ) ;
534
- }
535
-
536
- private static readonly UUID = '8389e75f-0810-4838-bf64-d6f85a95cf83' ;
537
-
538
- public readonly grantPrincipal : iam . IPrincipal ;
539
-
540
- private statements : { [ key : string ] : iam . PolicyStatement } = { } ;
541
-
542
- private constructor ( private readonly role : iam . IRole ) {
543
- super ( role as unknown as cdk . Construct , SingletonPolicy . UUID ) ;
544
- this . grantPrincipal = role ;
545
- }
546
-
547
- public grantExecuteChangeSet ( props : { stackName : string , changeSetName : string , region ?: string } ) : void {
548
- this . statementFor ( {
549
- actions : [
550
- 'cloudformation:DescribeStacks' ,
551
- 'cloudformation:DescribeChangeSet' ,
552
- 'cloudformation:ExecuteChangeSet' ,
553
- ] ,
554
- conditions : { StringEqualsIfExists : { 'cloudformation:ChangeSetName' : props . changeSetName } } ,
555
- } ) . addResources ( this . stackArnFromProps ( props ) ) ;
556
- }
557
-
558
- public grantCreateReplaceChangeSet ( props : { stackName : string , changeSetName : string , region ?: string } ) : void {
559
- this . statementFor ( {
560
- actions : [
561
- 'cloudformation:CreateChangeSet' ,
562
- 'cloudformation:DeleteChangeSet' ,
563
- 'cloudformation:DescribeChangeSet' ,
564
- 'cloudformation:DescribeStacks' ,
565
- ] ,
566
- conditions : { StringEqualsIfExists : { 'cloudformation:ChangeSetName' : props . changeSetName } } ,
567
- } ) . addResources ( this . stackArnFromProps ( props ) ) ;
568
- }
569
-
570
- public grantCreateUpdateStack ( props : { stackName : string , replaceOnFailure ?: boolean , region ?: string } ) : void {
571
- const actions = [
572
- 'cloudformation:DescribeStack*' ,
573
- 'cloudformation:CreateStack' ,
574
- 'cloudformation:UpdateStack' ,
575
- 'cloudformation:GetTemplate*' ,
576
- 'cloudformation:ValidateTemplate' ,
577
- 'cloudformation:GetStackPolicy' ,
578
- 'cloudformation:SetStackPolicy' ,
579
- ] ;
580
- if ( props . replaceOnFailure ) {
581
- actions . push ( 'cloudformation:DeleteStack' ) ;
582
- }
583
- this . statementFor ( { actions } ) . addResources ( this . stackArnFromProps ( props ) ) ;
584
- }
585
-
586
- public grantDeleteStack ( props : { stackName : string , region ?: string } ) : void {
587
- this . statementFor ( {
588
- actions : [
589
- 'cloudformation:DescribeStack*' ,
590
- 'cloudformation:DeleteStack' ,
591
- ] ,
592
- } ) . addResources ( this . stackArnFromProps ( props ) ) ;
593
- }
594
-
595
- public grantPassRole ( role : iam . IRole ) : void {
596
- this . statementFor ( { actions : [ 'iam:PassRole' ] } ) . addResources ( role . roleArn ) ;
597
- }
598
-
599
- private statementFor ( template : StatementTemplate ) : iam . PolicyStatement {
600
- const key = keyFor ( template ) ;
601
- if ( ! ( key in this . statements ) ) {
602
- this . statements [ key ] = new iam . PolicyStatement ( { actions : template . actions } ) ;
603
- if ( template . conditions ) {
604
- this . statements [ key ] . addConditions ( template . conditions ) ;
605
- }
606
- this . role . addToPolicy ( this . statements [ key ] ) ;
607
- }
608
- return this . statements [ key ] ;
609
-
610
- function keyFor ( props : StatementTemplate ) : string {
611
- const actions = `${ props . actions . sort ( ) . join ( '\x1F' ) } ` ;
612
- const conditions = formatConditions ( props . conditions ) ;
613
- return `${ actions } \x1D${ conditions } ` ;
614
-
615
- function formatConditions ( cond ?: StatementCondition ) : string {
616
- if ( cond == null ) { return '' ; }
617
- let result = '' ;
618
- for ( const op of Object . keys ( cond ) . sort ( ) ) {
619
- result += `${ op } \x1E` ;
620
- const condition = cond [ op ] ;
621
- for ( const attribute of Object . keys ( condition ) . sort ( ) ) {
622
- const value = condition [ attribute ] ;
623
- result += `${ value } \x1F` ;
624
- }
625
- }
626
- return result ;
627
- }
628
- }
629
- }
630
-
631
- private stackArnFromProps ( props : { stackName : string , region ?: string } ) : string {
632
- return cdk . Stack . of ( this ) . formatArn ( {
633
- region : props . region ,
634
- service : 'cloudformation' ,
635
- resource : 'stack' ,
636
- resourceName : `${ props . stackName } /*` ,
637
- } ) ;
638
- }
639
- }
640
-
641
- interface StatementTemplate {
642
- actions : string [ ] ;
643
- conditions ?: StatementCondition ;
644
- }
645
-
646
- type StatementCondition = { [ op : string ] : { [ attribute : string ] : string } } ;
647
-
648
- function parseCapabilities ( capabilities : cdk . CfnCapabilities [ ] | undefined ) : string | undefined {
649
- if ( capabilities === undefined ) {
650
- return undefined ;
651
- } else if ( capabilities . length === 1 ) {
652
- const capability = capabilities . toString ( ) ;
653
- return ( capability === '' ) ? undefined : capability ;
654
- } else if ( capabilities . length > 1 ) {
655
- return capabilities . join ( ',' ) ;
656
- }
657
-
658
- return undefined ;
659
- }
0 commit comments