Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(apprunner): apprunner secrets manager #23691

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions packages/@aws-cdk/aws-apprunner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,33 @@ new apprunner.Service(this, 'Service', {
vpcConnector,
});
```

## Secrets Manager

To include an environment variable integrated with AWS Secrets Manager we will be using the `environmentSecrets` attribute.
`instanceRole` attribute is mandatory when using `environmentSecrets`.

```ts
const secret = new secretsmanager.Secret(stack, 'Secret', {
secretObjectValue: { foo: SecretValue.unsafePlainText('mySecretVal') },
});

const role = new iam.Role(stack, 'InstanceRole', {
assumedBy: new iam.ServicePrincipal('tasks.apprunner.amazonaws.com'),
});

secret.grantRead(role);

new Service(stack, 'Service', {
source: apprunner.Source.fromEcrPublic({
imageConfiguration: {
port: 8000,
environmentSecrets: {
mySecretKey: secret.secretArn,
},
},
imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest',
}),
instanceRole: role,
});
```
63 changes: 56 additions & 7 deletions packages/@aws-cdk/aws-apprunner/lib/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,14 @@ interface EnvironmentVariable {
readonly value: string;
}

/**
* The environment secret for the service.
*/
interface EnvironmentSecret {
readonly name: string;
readonly value: string;
}

/**
* Result of binding `Source` into a `Service`.
*/
Expand Down Expand Up @@ -431,7 +439,14 @@ export interface ImageConfiguration {
*
* @default - no environment variables
*/
readonly environment?: { [key: string]: string };
readonly environmentVariables?: { [key: string]: string };

/**
* Environment secrets that are available to your running App Runner service.
*
* @default - no environment secrets
*/
readonly environmentSecrets?: { [key: string]: string };

/**
* An optional command that App Runner runs to start the application in the source image.
Expand Down Expand Up @@ -667,7 +682,14 @@ export interface CodeConfigurationValues {
*
* @default - no environment variables.
*/
readonly environment?: { [key: string]: string };
readonly environmentVariables?: { [key: string]: string };

/**
* The environment secrets that are available to your running App Runner service.
*
* @default - no environment secrets.
*/
readonly environmentSecrets?: { [key: string]: string };

/**
* The command App Runner runs to start your application.
Expand Down Expand Up @@ -782,7 +804,12 @@ export class Service extends cdk.Resource {
/**
* Environment variables for this service
*/
private environment?: { [key: string]: string } = {};
private environmentVariables?: { [key: string]: string } = {};

/**
* Environment secrets for this service
*/
private environmentSecrets?: { [key: string]: string } = {};

/**
* The ARN of the Service.
Expand Down Expand Up @@ -880,31 +907,35 @@ export class Service extends cdk.Resource {

}
private renderCodeConfigurationValues(props: CodeConfigurationValues): any {
this.environment = props.environment;
this.environmentVariables = props.environmentVariables;
this.environmentSecrets = props.environmentSecrets;
return {
port: props.port,
buildCommand: props.buildCommand,
runtime: props.runtime.name,
runtimeEnvironmentVariables: this.renderEnvironmentVariables(),
runtimeEnvironmentSecrets: this.renderEnvironmentSecrets(),
startCommand: props.startCommand,
};
}
private renderImageRepository(): any {
const repo = this.source.imageRepository!;
this.environment = repo.imageConfiguration?.environment;
this.environmentVariables = repo.imageConfiguration?.environmentVariables;
this.environmentSecrets = repo.imageConfiguration?.environmentSecrets;
return Object.assign(repo, {
imageConfiguration: {
port: repo.imageConfiguration?.port?.toString(),
startCommand: repo.imageConfiguration?.startCommand,
runtimeEnvironmentVariables: this.renderEnvironmentVariables(),
runtimeEnvironmentSecrets: this.renderEnvironmentSecrets(),
},
});
}

private renderEnvironmentVariables(): EnvironmentVariable[] | undefined {
if (this.environment) {
if (this.environmentVariables) {
let env: EnvironmentVariable[] = [];
for (const [key, value] of Object.entries(this.environment)) {
for (const [key, value] of Object.entries(this.environmentVariables)) {
if (key.startsWith('AWSAPPRUNNER')) {
throw new Error(`Environment variable key ${key} with a prefix of AWSAPPRUNNER is not allowed`);
}
Expand All @@ -916,6 +947,24 @@ export class Service extends cdk.Resource {
}
}

private renderEnvironmentSecrets(): EnvironmentSecret[] | undefined {
if (this.environmentSecrets) {
if (!this.props.instanceRole) {
throw new Error('Instance Role have to be provided if passing in RuntimeEnvironmentSecrets');
}
let env: EnvironmentSecret[] = [];
for (const [key, value] of Object.entries(this.environmentSecrets)) {
if (key.startsWith('AWSAPPRUNNER')) {
throw new Error(`Environment secret key ${key} with a prefix of AWSAPPRUNNER is not allowed`);
}
env.push({ name: key, value: value });
}
return env;
} else {
return undefined;
}
}

private generateDefaultRole(): iam.Role {
const accessRole = new iam.Role(this, 'AccessRole', {
assumedBy: new iam.ServicePrincipal('build.apprunner.amazonaws.com'),
Expand Down
3 changes: 3 additions & 0 deletions packages/@aws-cdk/aws-apprunner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"@aws-cdk/assertions": "0.0.0",
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/integ-runner": "0.0.0",
"@aws-cdk/integ-tests": "0.0.0",
"@aws-cdk/cfn2ts": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/jest": "^27.5.2"
Expand All @@ -96,6 +97,7 @@
"@aws-cdk/aws-ecr": "0.0.0",
"@aws-cdk/aws-ecr-assets": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-secretsmanager": "0.0.0",
"@aws-cdk/core": "0.0.0",
"constructs": "^10.0.0"
},
Expand All @@ -104,6 +106,7 @@
"@aws-cdk/aws-ecr": "0.0.0",
"@aws-cdk/aws-ecr-assets": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-secretsmanager": "0.0.0",
"@aws-cdk/core": "0.0.0",
"constructs": "^10.0.0"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as iam from '@aws-cdk/aws-iam';
import * as secretsmanager from '@aws-cdk/aws-secretsmanager';
import * as cdk from '@aws-cdk/core';
import { SecretValue } from '@aws-cdk/core';
import * as integ from '@aws-cdk/integ-tests';
import { Service, Source } from '../lib';

const app = new cdk.App();

const stack = new cdk.Stack(app, 'integ-apprunner-secrets-manager');

// Scenario 8: Create the service from ECR public with secrets manager environment variable
const secret = new secretsmanager.Secret(stack, 'Secret', {
secretObjectValue: { foo: SecretValue.unsafePlainText('fooval') },
});

const role = new iam.Role(stack, 'InstanceRole', {
assumedBy: new iam.ServicePrincipal('tasks.apprunner.amazonaws.com'),
});

secret.grantRead(role);

const service8 = new Service(stack, 'Service8', {
source: Source.fromEcrPublic({
imageConfiguration: {
port: 8000,
environmentSecrets: {
foo: secret.secretArn,
},
},
imageIdentifier: 'public.ecr.aws/aws-containers/hello-app-runner:latest',
}),
instanceRole: role,
});

new cdk.CfnOutput(stack, 'URL8', { value: `https://${service8.serviceUrl}` });

new integ.IntegTest(app, 'AppRunnerSecretsManger', {
testCases: [stack],
});

app.synth();
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "29.0.0",
"files": {
"21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": {
"source": {
"path": "AppRunnerSecretsMangerDefaultTestDeployAssert6B977D95.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
}
},
"dockerImages": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"Parameters": {
"BootstrapVersion": {
"Type": "AWS::SSM::Parameter::Value<String>",
"Default": "/cdk-bootstrap/hnb659fds/version",
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
}
},
"Rules": {
"CheckBootstrapVersion": {
"Assertions": [
{
"Assert": {
"Fn::Not": [
{
"Fn::Contains": [
[
"1",
"2",
"3",
"4",
"5"
],
{
"Ref": "BootstrapVersion"
}
]
}
]
},
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
}
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"version":"29.0.0"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "29.0.0",
"files": {
"9ca68c85825bc7081452c4b5a9e83bc17c984f4b1c4abe7dec694d11162e3de2": {
"source": {
"path": "integ-apprunner-secrets-manager.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "9ca68c85825bc7081452c4b5a9e83bc17c984f4b1c4abe7dec694d11162e3de2.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
}
},
"dockerImages": {}
}
Loading