Skip to content

Commit 7490b92

Browse files
authored
feat(amplify): add compute role support for Amplify app (#33962)
### Issue # (if applicable) Closes #33882 . ### Reason for this change To support compute role for SSR app. ### Description of changes * Add `computeRole` property. * Changed to automatically create compute role when SSR App is created. ### Describe any new or updated permissions being added N/A ### Description of how you validated changes Add unit tests 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) BREAKING CHANGE: A compute role is created when `platform` is `Platform.WEB_COMPUTE` or `Platform.WEB_DYNAMIC`. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent a0884ed commit 7490b92

18 files changed

+561
-6
lines changed

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,26 @@ const amplifyApp = new amplify.App(this, 'MyApp', {
248248
});
249249
```
250250

251+
## Compute role
252+
253+
This integration, enables you to assign an IAM role to the Amplify SSR Compute service to allow your server-side rendered (SSR) application to securely access specific AWS resources based on the role's permissions.
254+
255+
For example, you can allow your app's SSR compute functions to securely access other AWS services or resources, such as Amazon Bedrock or an Amazon S3 bucket, based on the permissions defined in the assigned IAM role.
256+
257+
For more information, see [Adding an SSR Compute role to allow access to AWS resources](https://docs.aws.amazon.com/amplify/latest/userguide/amplify-SSR-compute-role.html).
258+
259+
By default, a new role is created when `platform` is `Platform.WEB_COMPUTE` or `Platform.WEB_DYNAMIC`.
260+
If you want to assign an IAM role to the APP, set `compute` to the role:
261+
262+
```ts
263+
declare const computeRole: iam.Role;
264+
265+
const amplifyApp = new amplify.App(this, 'MyApp', {
266+
platform: amplify.Platform.WEB_COMPUTE,
267+
computeRole,
268+
});
269+
```
270+
251271
## Cache Config
252272

253273
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: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as codebuild from 'aws-cdk-lib/aws-codebuild';
22
import * as iam from 'aws-cdk-lib/aws-iam';
3-
import { IResource, Lazy, Resource, SecretValue } from 'aws-cdk-lib/core';
3+
import { IResource, Lazy, Resource, SecretValue, ValidationError } from 'aws-cdk-lib/core';
44
import { Construct } from 'constructs';
55
import { CfnApp } from 'aws-cdk-lib/aws-amplify';
66
import { BasicAuth } from './basic-auth';
@@ -176,6 +176,14 @@ export interface AppProps {
176176
* @default CacheConfigType.AMPLIFY_MANAGED
177177
*/
178178
readonly cacheConfigType?: CacheConfigType;
179+
180+
/**
181+
* The IAM role for an SSR app.
182+
* The Compute role allows the Amplify Hosting compute service to securely access specific AWS resources based on the role's permissions.
183+
*
184+
* @default undefined - a new role is created when `platform` is `Platform.WEB_COMPUTE` or `Platform.WEB_DYNAMIC`, otherwise no compute role
185+
*/
186+
readonly computeRole?: iam.IRole;
179187
}
180188

181189
/**
@@ -224,6 +232,11 @@ export class App extends Resource implements IApp, iam.IGrantable {
224232
*/
225233
public readonly grantPrincipal: iam.IPrincipal;
226234

235+
/**
236+
* The IAM role for an SSR app.
237+
*/
238+
public readonly computeRole?: iam.IRole;
239+
227240
private readonly customRules: CustomRule[];
228241
private readonly environmentVariables: { [name: string]: string };
229242
private readonly autoBranchEnvironmentVariables: { [name: string]: string };
@@ -242,6 +255,21 @@ export class App extends Resource implements IApp, iam.IGrantable {
242255
});
243256
this.grantPrincipal = role;
244257

258+
let computedRole: iam.IRole | undefined;
259+
const isSSR = props.platform === Platform.WEB_COMPUTE || props.platform === Platform.WEB_DYNAMIC;
260+
261+
if (props.computeRole) {
262+
if (!isSSR) {
263+
throw new ValidationError('`computeRole` can only be specified for `Platform.WEB_COMPUTE` or `Platform.WEB_DYNAMIC`.', this);
264+
}
265+
computedRole = props.computeRole;
266+
} else if (isSSR) {
267+
computedRole = new iam.Role(this, 'ComputeRole', {
268+
assumedBy: new iam.ServicePrincipal('amplify.amazonaws.com'),
269+
});
270+
}
271+
this.computeRole = computedRole;
272+
245273
const sourceCodeProviderOptions = props.sourceCodeProvider?.bind(this);
246274

247275
const app = new CfnApp(this, 'Resource', {
@@ -265,6 +293,7 @@ export class App extends Resource implements IApp, iam.IGrantable {
265293
: { enableBasicAuth: false },
266294
buildSpec: props.buildSpec && props.buildSpec.toBuildSpec(),
267295
cacheConfig: props.cacheConfigType ? { type: props.cacheConfigType } : undefined,
296+
computeRoleArn: this.computeRole?.roleArn,
268297
customRules: Lazy.any({ produce: () => this.customRules }, { omitEmptyArray: true }),
269298
description: props.description,
270299
environmentVariables: Lazy.any({ produce: () => renderEnvironmentVariables(this.environmentVariables) }, { omitEmptyArray: true }),

packages/@aws-cdk/aws-amplify-alpha/rosetta/default.ts-fixture

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { SecretValue, Stack } from 'aws-cdk-lib';
33
import { Construct } from 'constructs';
44
import * as amplify from '@aws-cdk/aws-amplify-alpha';
55
import * as acm from 'aws-cdk-lib/aws-certificatemanager';
6+
import * as iam from 'aws-cdk-lib/aws-iam';
67

78
class Fixture extends Stack {
89
constructor(scope: Construct, id: string) {

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Template } from 'aws-cdk-lib/assertions';
22
import * as codebuild from 'aws-cdk-lib/aws-codebuild';
33
import * as codecommit from 'aws-cdk-lib/aws-codecommit';
4+
import * as iam from 'aws-cdk-lib/aws-iam';
45
import { SecretValue, Stack } from 'aws-cdk-lib';
56
import * as amplify from '../lib';
67

@@ -478,3 +479,52 @@ test.each([amplify.CacheConfigType.AMPLIFY_MANAGED, amplify.CacheConfigType.AMPL
478479
},
479480
});
480481
});
482+
483+
test('create a SSR app with auto-generate compute role', () => {
484+
// WHEN
485+
new amplify.App(stack, 'App', {
486+
platform: amplify.Platform.WEB_COMPUTE,
487+
});
488+
489+
// THEN
490+
Template.fromStack(stack).hasResourceProperties('AWS::Amplify::App', {
491+
Platform: amplify.Platform.WEB_COMPUTE,
492+
ComputeRoleArn: {
493+
'Fn::GetAtt': ['AppComputeRole426920E4', 'Arn'],
494+
},
495+
});
496+
497+
Template.fromStack(stack).resourceCountIs('AWS::IAM::Role', 2);
498+
});
499+
500+
test('create a SSR app with compute role', () => {
501+
// WHEN
502+
const computeRole = new iam.Role(stack, 'ComputeRole', {
503+
assumedBy: new iam.ServicePrincipal('amplify.amazonaws.com'),
504+
});
505+
506+
new amplify.App(stack, 'App', {
507+
platform: amplify.Platform.WEB_COMPUTE,
508+
computeRole,
509+
});
510+
511+
// THEN
512+
Template.fromStack(stack).hasResourceProperties('AWS::Amplify::App', {
513+
Platform: amplify.Platform.WEB_COMPUTE,
514+
ComputeRoleArn: stack.resolve(computeRole.roleArn),
515+
});
516+
});
517+
518+
test('throws when compute role is set with a non SSR app', () => {
519+
// WHEN
520+
const computeRole = new iam.Role(stack, 'ComputeRole', {
521+
assumedBy: new iam.ServicePrincipal('amplify.amazonaws.com'),
522+
});
523+
524+
expect(() => {
525+
new amplify.App(stack, 'App', {
526+
platform: amplify.Platform.WEB,
527+
computeRole,
528+
});
529+
}).toThrow('`computeRole` can only be specified for `Platform.WEB_COMPUTE` or `Platform.WEB_DYNAMIC`.');
530+
});

packages/@aws-cdk/aws-amplify-alpha/test/integ.app-compute-role.js.snapshot/amplifyappcomputeroleintegDefaultTestDeployAssert140F931C.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.app-compute-role.js.snapshot/amplifyappcomputeroleintegDefaultTestDeployAssert140F931C.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.app-compute-role.js.snapshot/cdk-amplify-app-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,108 @@
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+
"AppF1B96344": {
38+
"Type": "AWS::Amplify::App",
39+
"Properties": {
40+
"BasicAuthConfig": {
41+
"EnableBasicAuth": false
42+
},
43+
"ComputeRoleArn": {
44+
"Fn::GetAtt": [
45+
"ComputeRole65BDBE3E",
46+
"Arn"
47+
]
48+
},
49+
"IAMServiceRole": {
50+
"Fn::GetAtt": [
51+
"AppRole1AF9B530",
52+
"Arn"
53+
]
54+
},
55+
"Name": "App",
56+
"Platform": "WEB_COMPUTE"
57+
}
58+
},
59+
"AppmainF505BAED": {
60+
"Type": "AWS::Amplify::Branch",
61+
"Properties": {
62+
"AppId": {
63+
"Fn::GetAtt": [
64+
"AppF1B96344",
65+
"AppId"
66+
]
67+
},
68+
"BranchName": "main",
69+
"EnableAutoBuild": true,
70+
"EnablePullRequestPreview": true
71+
}
72+
}
73+
},
74+
"Parameters": {
75+
"BootstrapVersion": {
76+
"Type": "AWS::SSM::Parameter::Value<String>",
77+
"Default": "/cdk-bootstrap/hnb659fds/version",
78+
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
79+
}
80+
},
81+
"Rules": {
82+
"CheckBootstrapVersion": {
83+
"Assertions": [
84+
{
85+
"Assert": {
86+
"Fn::Not": [
87+
{
88+
"Fn::Contains": [
89+
[
90+
"1",
91+
"2",
92+
"3",
93+
"4",
94+
"5"
95+
],
96+
{
97+
"Ref": "BootstrapVersion"
98+
}
99+
]
100+
}
101+
]
102+
},
103+
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
104+
}
105+
]
106+
}
107+
}
108+
}

packages/@aws-cdk/aws-amplify-alpha/test/integ.app-compute-role.js.snapshot/cdk.out

Lines changed: 1 addition & 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.app-compute-role.js.snapshot/integ.json

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

0 commit comments

Comments
 (0)