forked from aws/aws-cdk
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsingleton-policy.ts
170 lines (150 loc) · 5.93 KB
/
singleton-policy.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import { Construct } from 'constructs';
import * as iam from '../../../../aws-iam';
import * as cdk from '../../../../core';
/**
* Manages a bunch of singleton-y statements on the policy of an IAM Role.
* Dedicated methods can be used to add specific permissions to the role policy
* using as few statements as possible (adding resources to existing compatible
* statements instead of adding new statements whenever possible).
*
* Statements created outside of this class are not considered when adding new
* permissions.
*/
export class SingletonPolicy extends Construct implements iam.IGrantable {
/**
* Obtain a SingletonPolicy for a given role.
* @param role the Role this policy is bound to.
* @returns the SingletonPolicy for this role.
*/
public static forRole(role: iam.IRole): SingletonPolicy {
const found = role.node.tryFindChild(SingletonPolicy.UUID);
return (found as SingletonPolicy) || new SingletonPolicy(role);
}
private static readonly UUID = '8389e75f-0810-4838-bf64-d6f85a95cf83';
public readonly grantPrincipal: iam.IPrincipal;
private statements: { [key: string]: iam.PolicyStatement } = {};
private constructor(private readonly role: iam.IRole) {
super(role as unknown as Construct, SingletonPolicy.UUID);
this.grantPrincipal = role;
}
public grantExecuteChangeSet(props: { stackName: string; changeSetName: string; region?: string }): void {
this.statementFor({
actions: [
'cloudformation:DescribeStacks',
'cloudformation:DescribeStackEvents',
'cloudformation:DescribeChangeSet',
'cloudformation:ExecuteChangeSet',
],
conditions: { StringEqualsIfExists: { 'cloudformation:ChangeSetName': props.changeSetName } },
}).addResources(this.stackArnFromProps(props));
}
public grantCreateReplaceChangeSet(props: { stackName: string; changeSetName: string; region?: string }): void {
this.statementFor({
actions: [
'cloudformation:CreateChangeSet',
'cloudformation:DeleteChangeSet',
'cloudformation:DescribeChangeSet',
'cloudformation:DescribeStacks',
],
conditions: { StringEqualsIfExists: { 'cloudformation:ChangeSetName': props.changeSetName } },
}).addResources(this.stackArnFromProps(props));
}
public grantCreateUpdateStack(props: { stackName: string; replaceOnFailure?: boolean; region?: string }): void {
const actions = [
'cloudformation:DescribeStack*',
'cloudformation:CreateStack',
'cloudformation:UpdateStack',
'cloudformation:GetTemplate*',
'cloudformation:ValidateTemplate',
'cloudformation:GetStackPolicy',
'cloudformation:SetStackPolicy',
];
if (props.replaceOnFailure) {
actions.push('cloudformation:DeleteStack');
}
this.statementFor({ actions }).addResources(this.stackArnFromProps(props));
}
public grantCreateUpdateStackSet(props: { stackSetName: string; region?: string }): void {
const actions = [
'cloudformation:CreateStackSet',
'cloudformation:UpdateStackSet',
'cloudformation:DescribeStackSet',
'cloudformation:DescribeStackSetOperation',
'cloudformation:ListStackInstances',
'cloudformation:CreateStackInstances',
];
this.statementFor({ actions }).addResources(this.stackSetArnFromProps(props));
}
public grantDeleteStack(props: { stackName: string; region?: string }): void {
this.statementFor({
actions: [
'cloudformation:DescribeStack*',
'cloudformation:DeleteStack',
],
}).addResources(this.stackArnFromProps(props));
}
public grantPassRole(role: iam.IRole | string): void {
this.statementFor({ actions: ['iam:PassRole'] }).addResources(typeof role === 'string' ? role : role.roleArn);
}
private statementFor(template: StatementTemplate): iam.PolicyStatement {
const key = keyFor(template);
if (!(key in this.statements)) {
this.statements[key] = new iam.PolicyStatement({ actions: template.actions });
if (template.conditions) {
this.statements[key].addConditions(template.conditions);
}
this.role.addToPolicy(this.statements[key]);
}
return this.statements[key];
function keyFor(props: StatementTemplate): string {
const actions = `${props.actions.sort().join('\x1F')}`;
const conditions = formatConditions(props.conditions);
return `${actions}\x1D${conditions}`;
function formatConditions(cond?: StatementCondition): string {
if (cond == null) { return ''; }
let result = '';
for (const op of Object.keys(cond).sort()) {
result += `${op}\x1E`;
const condition = cond[op];
for (const attribute of Object.keys(condition).sort()) {
const value = condition[attribute];
result += `${value}\x1F`;
}
}
return result;
}
}
}
private stackArnFromProps(props: { stackName: string; region?: string }): string {
return cdk.Stack.of(this).formatArn({
region: props.region,
service: 'cloudformation',
resource: 'stack',
resourceName: `${props.stackName}/*`,
});
}
private stackSetArnFromProps(props: { stackSetName: string; region?: string }): string {
return cdk.Stack.of(this).formatArn({
region: props.region,
service: 'cloudformation',
resource: 'stackset',
resourceName: `${props.stackSetName}:*`,
});
}
}
export interface StatementTemplate {
actions: string[];
conditions?: StatementCondition;
}
export type StatementCondition = { [op: string]: { [attribute: string]: string } };
export function parseCapabilities(capabilities: cdk.CfnCapabilities[] | undefined): string | undefined {
if (capabilities === undefined) {
return undefined;
} else if (capabilities.length === 1) {
const capability = capabilities.toString();
return (capability === '') ? undefined : capability;
} else if (capabilities.length > 1) {
return capabilities.join(',');
}
return undefined;
}