Skip to content

Commit

Permalink
feat(apprunner): make Service implement IGrantable (aws#26130)
Browse files Browse the repository at this point in the history
Implementing `IGrantable` for cases when it's needed to grant permissions to a `Service` instance.

For example:
```
declare const bucket: IBucket;

const service = new apprunner.Service(this, 'Service', {
    source: apprunner.Source.fromEcrPublic({
        imageConfiguration: { port: 8000 },
        imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest',
    }),
});

bucket.grantRead(service);
```

Closes aws#26089.

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
lpizzinidev authored and bmoffatt committed Jul 28, 2023
1 parent dad54a4 commit 45a1d6c
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 18 deletions.
24 changes: 23 additions & 1 deletion packages/@aws-cdk/aws-apprunner-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ The `Service` construct allows you to create AWS App Runner services with `ECR P
- `Source.fromAsset()` - To define the source from local asset directory.


The `Service` construct implements `IGrantable`.

## ECR Public

To create a `Service` with ECR Public:
Expand Down Expand Up @@ -124,7 +126,27 @@ new apprunner.Service(this, 'Service', {
You are allowed to define `instanceRole` and `accessRole` for the `Service`.

`instanceRole` - The IAM role that provides permissions to your App Runner service. These are permissions that
your code needs when it calls any AWS APIs.
your code needs when it calls any AWS APIs. If not defined, a new instance role will be generated
when required.

To add IAM policy statements to this role, use `addToRolePolicy()`:

```ts
import * as iam from 'aws-cdk-lib/aws-iam';

const service = new apprunner.Service(this, 'Service', {
source: apprunner.Source.fromEcrPublic({
imageConfiguration: { port: 8000 },
imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest',
}),
});

service.addToRolePolicy(new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['s3:GetObject'],
resources: ['*'],
}))
```

`accessRole` - The IAM role that grants the App Runner service access to a source repository. It's required for
ECR image repositories (but not for ECR Public repositories). If not defined, a new access role will be generated
Expand Down
20 changes: 13 additions & 7 deletions packages/@aws-cdk/aws-apprunner-alpha/lib/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,7 @@ export interface ServiceProps {
*
* @see https://docs.aws.amazon.com/apprunner/latest/dg/security_iam_service-with-iam.html#security_iam_service-with-iam-roles-service.instance
*
* @default - no instance role attached.
* @default - generate a new instance role.
*/
readonly instanceRole?: iam.IRole;

Expand Down Expand Up @@ -959,7 +959,7 @@ export abstract class Secret {
/**
* The App Runner Service.
*/
export class Service extends cdk.Resource {
export class Service extends cdk.Resource implements iam.IGrantable {
/**
* Import from service name.
*/
Expand Down Expand Up @@ -993,9 +993,10 @@ export class Service extends cdk.Resource {

return new Import(scope, id);
}
public readonly grantPrincipal: iam.IPrincipal;
private readonly props: ServiceProps;
private accessRole?: iam.IRole;
private instanceRole?: iam.IRole;
private instanceRole: iam.IRole;
private source: SourceConfig;

/**
Expand Down Expand Up @@ -1051,7 +1052,8 @@ export class Service extends cdk.Resource {
this.source = source;
this.props = props;

this.instanceRole = this.props.instanceRole;
this.instanceRole = this.props.instanceRole ?? this.createInstanceRole();
this.grantPrincipal = this.instanceRole;

const environmentVariables = this.getEnvironmentVariables();
const environmentSecrets = this.getEnvironmentSecrets();
Expand Down Expand Up @@ -1117,6 +1119,13 @@ export class Service extends cdk.Resource {
this.serviceName = cdk.Fn.select(1, cdk.Fn.split('/', resourceFullName));
}

/**
* Adds a statement to the instance role.
*/
public addToRolePolicy(statement: iam.PolicyStatement) {
this.instanceRole.addToPrincipalPolicy(statement);
}

/**
* This method adds an environment variable to the App Runner service.
*/
Expand All @@ -1134,9 +1143,6 @@ export class Service extends cdk.Resource {
if (name.startsWith('AWSAPPRUNNER')) {
throw new Error(`Environment secret key ${name} with a prefix of AWSAPPRUNNER is not allowed`);
}
if (!this.instanceRole) {
this.instanceRole = this.createInstanceRole();
}
secret.grantRead(this.instanceRole);
this.secrets.push({ name: name, value: secret.arn });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,39 @@
"ImageRepositoryType": "ECR_PUBLIC"
}
},
"InstanceConfiguration": {},
"InstanceConfiguration": {
"InstanceRoleArn": {
"Fn::GetAtt": [
"Service1InstanceRole8CBC81F1",
"Arn"
]
}
},
"NetworkConfiguration": {
"EgressConfiguration": {
"EgressType": "DEFAULT"
}
},
"ServiceName": "service1"
}
}
},
"Service1InstanceRole8CBC81F1": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "tasks.apprunner.amazonaws.com"
}
}
],
"Version": "2012-10-17"
}
}
}
},
"Outputs": {
"URL1": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,14 @@
"ImageRepositoryType": "ECR"
}
},
"InstanceConfiguration": {},
"InstanceConfiguration": {
"InstanceRoleArn": {
"Fn::GetAtt": [
"Service3InstanceRoleD40BEE82",
"Arn"
]
}
},
"NetworkConfiguration": {
"EgressConfiguration": {
"EgressType": "DEFAULT"
Expand Down Expand Up @@ -211,14 +218,55 @@
"ImageRepositoryType": "ECR"
}
},
"InstanceConfiguration": {},
"InstanceConfiguration": {
"InstanceRoleArn": {
"Fn::GetAtt": [
"Service2InstanceRole3F57F2AA",
"Arn"
]
}
},
"NetworkConfiguration": {
"EgressConfiguration": {
"EgressType": "DEFAULT"
}
}
}
}
},
"Service3InstanceRoleD40BEE82": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "tasks.apprunner.amazonaws.com"
}
}
],
"Version": "2012-10-17"
}
}
},
"Service2InstanceRole3F57F2AA": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "tasks.apprunner.amazonaws.com"
}
}
],
"Version": "2012-10-17"
}
}
}
},
"Outputs": {
"URL3": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@
}
}
},
"InstanceConfiguration": {},
"InstanceConfiguration": {
"InstanceRoleArn": {
"Fn::GetAtt": [
"Service4InstanceRole26B443A0",
"Arn"
]
}
},
"NetworkConfiguration": {
"EgressConfiguration": {
"EgressType": "DEFAULT"
Expand Down Expand Up @@ -50,13 +57,54 @@
}
}
},
"InstanceConfiguration": {},
"InstanceConfiguration": {
"InstanceRoleArn": {
"Fn::GetAtt": [
"Service5InstanceRole94C07D84",
"Arn"
]
}
},
"NetworkConfiguration": {
"EgressConfiguration": {
"EgressType": "DEFAULT"
}
}
}
},
"Service4InstanceRole26B443A0": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "tasks.apprunner.amazonaws.com"
}
}
],
"Version": "2012-10-17"
}
}
},
"Service5InstanceRole94C07D84": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "tasks.apprunner.amazonaws.com"
}
}
],
"Version": "2012-10-17"
}
}
}
},
"Outputs": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,14 @@
"ImageRepositoryType": "ECR_PUBLIC"
}
},
"InstanceConfiguration": {},
"InstanceConfiguration": {
"InstanceRoleArn": {
"Fn::GetAtt": [
"Service6InstanceRole7220D460",
"Arn"
]
}
},
"NetworkConfiguration": {
"EgressConfiguration": {
"EgressType": "VPC",
Expand All @@ -469,7 +476,14 @@
"ImageRepositoryType": "ECR_PUBLIC"
}
},
"InstanceConfiguration": {},
"InstanceConfiguration": {
"InstanceRoleArn": {
"Fn::GetAtt": [
"Service7InstanceRoleFD40F312",
"Arn"
]
}
},
"NetworkConfiguration": {
"EgressConfiguration": {
"EgressType": "VPC",
Expand All @@ -482,7 +496,41 @@
}
}
}
}
},
"Service6InstanceRole7220D460": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "tasks.apprunner.amazonaws.com"
}
}
],
"Version": "2012-10-17"
}
}
},
"Service7InstanceRoleFD40F312": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "tasks.apprunner.amazonaws.com"
}
}
],
"Version": "2012-10-17"
}
}
}
},
"Outputs": {
"URL6": {
Expand Down
Loading

0 comments on commit 45a1d6c

Please sign in to comment.