Skip to content

Commit 817a21a

Browse files
authored
feat(amplify): add compute role support for Amplify branches (#34708)
### Issue # (if applicable) N/A ### Reason for this change Amplify supports branch-level compute role setting. But current L2 Construct doesn't support it. ### Description of changes Add `computeRole` property for `Branch` construct. ### Describe any new or updated permissions being added N/A ### Description of how you validated changes Add a unit test and an integ test. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent ff9140f commit 817a21a

14 files changed

+546
-5
lines changed

packages/@aws-cdk/aws-amplify-alpha/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,15 @@ const amplifyApp = new amplify.App(this, 'MyApp', {
268268
});
269269
```
270270

271+
It is also possible to override the compute role for a specific branch by setting `computeRole` in `Branch`:
272+
273+
```ts
274+
declare const computeRole: iam.Role;
275+
declare const amplifyApp: amplify.App
276+
277+
const branch = amplifyApp.addBranch("dev", { computeRole });
278+
```
279+
271280
## Cache Config
272281

273282
Amplify uses Amazon CloudFront to manage the caching configuration for your hosted applications. A cache configuration is applied to each app to optimize for the best performance.

packages/@aws-cdk/aws-amplify-alpha/lib/app.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { CfnApp } from 'aws-cdk-lib/aws-amplify';
66
import { BasicAuth } from './basic-auth';
77
import { Branch, BranchOptions } from './branch';
88
import { Domain, DomainOptions } from './domain';
9-
import { renderEnvironmentVariables } from './utils';
9+
import { renderEnvironmentVariables, isServerSideRendered } from './utils';
1010
import { addConstructMetadata, MethodMetadata } from 'aws-cdk-lib/core/lib/metadata-resource';
1111
import { propertyInjectable } from 'aws-cdk-lib/core/lib/prop-injectable';
1212

@@ -237,6 +237,11 @@ export class App extends Resource implements IApp, iam.IGrantable {
237237
*/
238238
public readonly computeRole?: iam.IRole;
239239

240+
/**
241+
* The platform of the app
242+
*/
243+
public readonly platform?: Platform;
244+
240245
private readonly customRules: CustomRule[];
241246
private readonly environmentVariables: { [name: string]: string };
242247
private readonly autoBranchEnvironmentVariables: { [name: string]: string };
@@ -256,7 +261,8 @@ export class App extends Resource implements IApp, iam.IGrantable {
256261
this.grantPrincipal = role;
257262

258263
let computedRole: iam.IRole | undefined;
259-
const isSSR = props.platform === Platform.WEB_COMPUTE || props.platform === Platform.WEB_DYNAMIC;
264+
const appPlatform = props.platform || Platform.WEB;
265+
const isSSR = isServerSideRendered(appPlatform);
260266

261267
if (props.computeRole) {
262268
if (!isSSR) {
@@ -272,6 +278,8 @@ export class App extends Resource implements IApp, iam.IGrantable {
272278

273279
const sourceCodeProviderOptions = props.sourceCodeProvider?.bind(this);
274280

281+
this.platform = appPlatform;
282+
275283
const app = new CfnApp(this, 'Resource', {
276284
accessToken: sourceCodeProviderOptions?.accessToken?.unsafeUnwrap(), // Safe usage
277285
autoBranchCreationConfig: props.autoBranchCreation && {
@@ -302,7 +310,7 @@ export class App extends Resource implements IApp, iam.IGrantable {
302310
oauthToken: sourceCodeProviderOptions?.oauthToken?.unsafeUnwrap(), // Safe usage
303311
repository: sourceCodeProviderOptions?.repository,
304312
customHeaders: props.customResponseHeaders ? renderCustomResponseHeaders(props.customResponseHeaders) : undefined,
305-
platform: props.platform || Platform.WEB,
313+
platform: appPlatform,
306314
});
307315

308316
this.appId = app.attrAppId;

packages/@aws-cdk/aws-amplify-alpha/lib/branch.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ import {
99
Duration,
1010
NestedStack,
1111
Stack,
12+
ValidationError,
1213
} from 'aws-cdk-lib/core';
1314
import { Provider } from 'aws-cdk-lib/custom-resources';
1415
import { Construct } from 'constructs';
1516
import { CfnBranch } from 'aws-cdk-lib/aws-amplify';
16-
import { IApp } from './app';
17+
import { App, IApp } from './app';
1718
import { BasicAuth } from './basic-auth';
18-
import { renderEnvironmentVariables } from './utils';
19+
import { renderEnvironmentVariables, isServerSideRendered } from './utils';
1920
import { AssetDeploymentIsCompleteFunction, AssetDeploymentOnEventFunction } from '../custom-resource-handlers/dist/aws-amplify-alpha/asset-deployment-provider.generated';
2021
import { addConstructMetadata, MethodMetadata } from 'aws-cdk-lib/core/lib/metadata-resource';
2122
import { propertyInjectable } from 'aws-cdk-lib/core/lib/prop-injectable';
@@ -137,6 +138,16 @@ export interface BranchOptions {
137138
* @default None - Default setting is no skew protection.
138139
*/
139140
readonly skewProtection?: boolean;
141+
142+
/**
143+
* The IAM role to assign to a branch of an SSR app.
144+
* The SSR Compute role allows the Amplify Hosting compute service to securely access specific AWS resources based on the role's permissions.
145+
*
146+
* This role overrides the app-level compute role.
147+
*
148+
* @default undefined - No specific role for the branch. If the app has a compute role, it will be inherited.
149+
*/
150+
readonly computeRole?: iam.IRole;
140151
}
141152

142153
/**
@@ -183,6 +194,15 @@ export class Branch extends Resource implements IBranch {
183194
// Enhanced CDK Analytics Telemetry
184195
addConstructMetadata(this, props);
185196

197+
if (props.app instanceof App) {
198+
const platform = props.app.platform;
199+
const isSSR = isServerSideRendered(platform);
200+
201+
if (props.computeRole && !isSSR) {
202+
throw new ValidationError('`computeRole` can only be specified for branches of apps with `Platform.WEB_COMPUTE` or `Platform.WEB_DYNAMIC`.', this);
203+
}
204+
}
205+
186206
this.environmentVariables = props.environmentVariables || {};
187207

188208
const branchName = props.branchName || id;
@@ -199,6 +219,7 @@ export class Branch extends Resource implements IBranch {
199219
stage: props.stage,
200220
enablePerformanceMode: props.performanceMode,
201221
enableSkewProtection: props.skewProtection,
222+
computeRoleArn: props.computeRole?.roleArn,
202223
});
203224

204225
this.arn = branch.attrArn;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
import { Platform } from './app';
2+
13
export function renderEnvironmentVariables(vars: { [name: string]: string }) {
24
return Object.entries(vars).map(([name, value]) => ({ name, value }));
35
}
6+
7+
/**
8+
* Utility function to check if the platform is a server-side rendering platform
9+
*/
10+
export function isServerSideRendered(platform?: Platform): boolean {
11+
return platform === Platform.WEB_COMPUTE || platform === Platform.WEB_DYNAMIC;
12+
}

packages/@aws-cdk/aws-amplify-alpha/test/branch.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Template } from 'aws-cdk-lib/assertions';
33
import { Asset } from 'aws-cdk-lib/aws-s3-assets';
44
import { SecretValue, Stack } from 'aws-cdk-lib';
55
import * as amplify from '../lib';
6+
import * as iam from 'aws-cdk-lib/aws-iam';
67

78
let stack: Stack;
89
let app: amplify.App;
@@ -154,3 +155,37 @@ test('with skew protection', () => {
154155
EnableSkewProtection: true,
155156
});
156157
});
158+
159+
test('with compute role', () => {
160+
// WHEN
161+
const computeRole = new iam.Role(stack, 'ComputeRole', {
162+
assumedBy: new iam.ServicePrincipal('amplify.amazonaws.com'),
163+
});
164+
165+
const ssrApp = new amplify.App(stack, 'SsrApp', {
166+
platform: amplify.Platform.WEB_COMPUTE,
167+
});
168+
169+
ssrApp.addBranch('main', { computeRole });
170+
171+
// THEN
172+
Template.fromStack(stack).hasResourceProperties('AWS::Amplify::Branch', {
173+
ComputeRoleArn: stack.resolve(computeRole.roleArn),
174+
});
175+
});
176+
177+
test('throws error when compute role provided for WEB platform', () => {
178+
// WHEN
179+
const computeRole = new iam.Role(stack, 'ComputeRoleWeb', {
180+
assumedBy: new iam.ServicePrincipal('amplify.amazonaws.com'),
181+
});
182+
183+
const webApp = new amplify.App(stack, 'WebApp', {
184+
platform: amplify.Platform.WEB,
185+
});
186+
187+
// THEN
188+
expect(() => {
189+
webApp.addBranch('main', { computeRole });
190+
}).toThrow(/`computeRole` can only be specified for branches of apps with `Platform.WEB_COMPUTE` or `Platform.WEB_DYNAMIC`./);
191+
});

packages/@aws-cdk/aws-amplify-alpha/test/integ.branch-compute-role.js.snapshot/amplifybranchcomputeroleDefaultTestDeployAssert73C754F8.assets.json

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk/aws-amplify-alpha/test/integ.branch-compute-role.js.snapshot/amplifybranchcomputeroleDefaultTestDeployAssert73C754F8.template.json

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk/aws-amplify-alpha/test/integ.branch-compute-role.js.snapshot/cdk-amplify-branch-compute-role.assets.json

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
{
2+
"Resources": {
3+
"ComputeRole65BDBE3E": {
4+
"Type": "AWS::IAM::Role",
5+
"Properties": {
6+
"AssumeRolePolicyDocument": {
7+
"Statement": [
8+
{
9+
"Action": "sts:AssumeRole",
10+
"Effect": "Allow",
11+
"Principal": {
12+
"Service": "amplify.amazonaws.com"
13+
}
14+
}
15+
],
16+
"Version": "2012-10-17"
17+
}
18+
}
19+
},
20+
"AppRole1AF9B530": {
21+
"Type": "AWS::IAM::Role",
22+
"Properties": {
23+
"AssumeRolePolicyDocument": {
24+
"Statement": [
25+
{
26+
"Action": "sts:AssumeRole",
27+
"Effect": "Allow",
28+
"Principal": {
29+
"Service": "amplify.amazonaws.com"
30+
}
31+
}
32+
],
33+
"Version": "2012-10-17"
34+
}
35+
}
36+
},
37+
"AppComputeRole426920E4": {
38+
"Type": "AWS::IAM::Role",
39+
"Properties": {
40+
"AssumeRolePolicyDocument": {
41+
"Statement": [
42+
{
43+
"Action": "sts:AssumeRole",
44+
"Effect": "Allow",
45+
"Principal": {
46+
"Service": "amplify.amazonaws.com"
47+
}
48+
}
49+
],
50+
"Version": "2012-10-17"
51+
}
52+
}
53+
},
54+
"AppF1B96344": {
55+
"Type": "AWS::Amplify::App",
56+
"Properties": {
57+
"BasicAuthConfig": {
58+
"EnableBasicAuth": false
59+
},
60+
"CacheConfig": {
61+
"Type": "AMPLIFY_MANAGED_NO_COOKIES"
62+
},
63+
"ComputeRoleArn": {
64+
"Fn::GetAtt": [
65+
"AppComputeRole426920E4",
66+
"Arn"
67+
]
68+
},
69+
"IAMServiceRole": {
70+
"Fn::GetAtt": [
71+
"AppRole1AF9B530",
72+
"Arn"
73+
]
74+
},
75+
"Name": "App",
76+
"Platform": "WEB_COMPUTE"
77+
}
78+
},
79+
"AppmainF505BAED": {
80+
"Type": "AWS::Amplify::Branch",
81+
"Properties": {
82+
"AppId": {
83+
"Fn::GetAtt": [
84+
"AppF1B96344",
85+
"AppId"
86+
]
87+
},
88+
"BranchName": "main",
89+
"ComputeRoleArn": {
90+
"Fn::GetAtt": [
91+
"ComputeRole65BDBE3E",
92+
"Arn"
93+
]
94+
},
95+
"EnableAutoBuild": true,
96+
"EnablePullRequestPreview": true
97+
}
98+
}
99+
},
100+
"Parameters": {
101+
"BootstrapVersion": {
102+
"Type": "AWS::SSM::Parameter::Value<String>",
103+
"Default": "/cdk-bootstrap/hnb659fds/version",
104+
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
105+
}
106+
},
107+
"Rules": {
108+
"CheckBootstrapVersion": {
109+
"Assertions": [
110+
{
111+
"Assert": {
112+
"Fn::Not": [
113+
{
114+
"Fn::Contains": [
115+
[
116+
"1",
117+
"2",
118+
"3",
119+
"4",
120+
"5"
121+
],
122+
{
123+
"Ref": "BootstrapVersion"
124+
}
125+
]
126+
}
127+
]
128+
},
129+
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
130+
}
131+
]
132+
}
133+
}
134+
}

0 commit comments

Comments
 (0)