-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
bugfix(rtv): Use "/" instead of "@" to delimit package and name in SS…
…M parameter "@" is not allowed in SSM parameter keys. Obvsiouly this has never been tested properly, so add an integration test. Fixes #151
- Loading branch information
Elad Ben-Israel
committed
Jun 30, 2018
1 parent
8c81c7d
commit f06cb8f
Showing
6 changed files
with
322 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# Runtime Values | ||
|
||
The CDK allows apps to advertise values from __construction time__ to __runtime | ||
code__. For example, consider code in a Lambda function which needs to know the | ||
URL of the SQS queue created as part of your CDK app. | ||
|
||
Runtime values are advertised as textual SSM parameters with the following key: | ||
|
||
``` | ||
/rtv/<stack-name>/<package>/<name> | ||
``` | ||
|
||
Therefore, in order to advertise a value you will need to: | ||
|
||
1. Make the current stack name available as an environment variable to your | ||
runtime code. The convention is to use `RTV_STACK_NAME`. | ||
2. Use the `RuntimeValue` construct in order to create the SSM parameter and | ||
specify least-privilege permissions. | ||
|
||
For example, say we want to publish a queue's URL to a lambda function. | ||
|
||
### Construction Code | ||
|
||
```ts | ||
import { RuntimeValue } from '@aws-cdk/rtv' | ||
|
||
const queue = new Queue(this, 'MyQueue', { /* props.... */ }); | ||
const fn = new Lambda(this, 'MyFunction', { /* props... */ }); | ||
const fleet = new Fleet(this, 'MyFleet', { /* props... */ }); | ||
|
||
// this line defines an AWS::SSM::Parameter resource with the | ||
// key "/rtv/<stack-name>/com.myorg/MyQueueURL" and the actual queue URL as value | ||
const queueUrlRtv = new RuntimeValue(this, 'QueueRTV', { | ||
package: 'com.myorg', | ||
name: 'MyQueueURL', | ||
value: queue.queueUrl | ||
}); | ||
|
||
// this line adds read permissions for this SSM parameter to the policies associated with | ||
// the IAM roles of the Lambda function and the EC2 fleet | ||
queueUrlRtv.grantRead(fn.role); | ||
queueUrlRtv.grantRead(fleet.role); | ||
|
||
// adds the `RTV_STACK_NAME` to the environment of the lambda function | ||
// and the fleet (via user-data) | ||
fn.env(RuntimeValue.ENV_NAME, RuntimeValue.ENV_VALUE); | ||
fleet.env(RuntimeValue.ENV_NAME, RuntimeValue.ENV_VALUE); | ||
``` | ||
|
||
### Runtime Code | ||
|
||
Then, your runtime code will need to use the SSM Parameter Store AWS SDK in | ||
order to format the SSM parameter key and read the value. In future releases, we | ||
will provide runtime libraries to make this easy. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,47 +1,94 @@ | ||
import { Arn, AwsStackName, Construct, FnConcat, PolicyStatement } from '@aws-cdk/core'; | ||
import { Arn, AwsStackName, Construct, FnConcat, PolicyStatement, Token } from '@aws-cdk/core'; | ||
import { IIdentityResource } from '@aws-cdk/iam'; | ||
import { ssm } from '@aws-cdk/resources'; | ||
|
||
export interface RuntimeValueProps { | ||
/** | ||
* A namespace for the runtime value. | ||
* It is recommended to use the name of the library/package that advertises this value. | ||
*/ | ||
package: string; | ||
|
||
/** | ||
* The value to advertise. Can be either a primitive value or a token. | ||
*/ | ||
value: any; | ||
} | ||
|
||
/** | ||
* Defines a value published from construction code which needs to be accessible | ||
* by runtime code. | ||
*/ | ||
export class RuntimeValue extends Construct { | ||
|
||
/** | ||
* The recommended name of the environment variable to use to set the stack name | ||
* from which the runtime value is published. | ||
*/ | ||
public static readonly ENV_NAME = 'RTV_STACK_NAME'; | ||
|
||
/** | ||
* The value to assign to the `RTV_STACK_NAME` environment variable. | ||
*/ | ||
public static readonly ENV_VALUE = new AwsStackName(); | ||
|
||
/** | ||
* IAM actions needed to read a value from an SSM parameter. | ||
*/ | ||
private static readonly SSM_READ_ACTIONS = [ | ||
'ssm:DescribeParameters', | ||
'ssm:GetParameters', | ||
'ssm:GetParameter' | ||
]; | ||
|
||
public readonly parameterName: any; | ||
/** | ||
* The name of the runtime parameter. | ||
*/ | ||
public readonly parameterName: ParameterName; | ||
|
||
/** | ||
* The ARN fo the SSM parameter used for this runtime value. | ||
*/ | ||
public readonly parameterArn: Arn; | ||
|
||
constructor(parent: Construct, name: string, props: RuntimeValueProps) { | ||
super(parent, name); | ||
|
||
this.parameterName = new FnConcat('/rtv/', new AwsStackName(), '/', props.package, '@', name); | ||
this.parameterName = new FnConcat('/rtv/', new AwsStackName(), '/', props.package, '/', name); | ||
|
||
new ssm.ParameterResource(this, 'Parameter', { | ||
parameterName: this.parameterName, | ||
type: 'String', | ||
value: props.value, | ||
}); | ||
} | ||
|
||
get arn() { | ||
return Arn.fromComponents({ | ||
this.parameterArn = Arn.fromComponents({ | ||
service: 'ssm', | ||
resource: 'parameter', | ||
resourceName: this.parameterName | ||
}); | ||
} | ||
|
||
public grantReadPermissions(principal: IIdentityResource) { | ||
/** | ||
* Grants a principal read permissions on this runtime value. | ||
* @param principal The principal (e.g. Role, User, Group) | ||
*/ | ||
public grantRead(principal?: IIdentityResource) { | ||
|
||
// sometimes "role" is optional, so we want `rtv.grantRead(role)` to be a no-op | ||
if (!principal) { | ||
return; | ||
} | ||
|
||
principal.addToPolicy(new PolicyStatement() | ||
.addResource(this.arn) | ||
.addResource(this.parameterArn) | ||
.addActions(...RuntimeValue.SSM_READ_ACTIONS)); | ||
} | ||
} | ||
|
||
/** | ||
* The full name of the runtime value's SSM parameter. | ||
*/ | ||
export class ParameterName extends Token { | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
164 changes: 164 additions & 0 deletions
164
packages/@aws-cdk/rtv/test/integ.rtv.lambda.expected.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
{ | ||
"Resources": { | ||
"MyQueueE6CA6235": { | ||
"Type": "AWS::SQS::Queue" | ||
}, | ||
"MyFunctionServiceRole3C357FF2": { | ||
"Type": "AWS::IAM::Role", | ||
"Properties": { | ||
"AssumeRolePolicyDocument": { | ||
"Statement": [ | ||
{ | ||
"Action": "sts:AssumeRole", | ||
"Effect": "Allow", | ||
"Principal": { | ||
"Service": "lambda.amazonaws.com" | ||
} | ||
} | ||
], | ||
"Version": "2012-10-17" | ||
}, | ||
"ManagedPolicyArns": [ | ||
{ | ||
"Fn::Join": [ | ||
"", | ||
[ | ||
"arn", | ||
":", | ||
{ | ||
"Ref": "AWS::Partition" | ||
}, | ||
":", | ||
"iam", | ||
":", | ||
"", | ||
":", | ||
"aws", | ||
":", | ||
"policy", | ||
"/", | ||
"service-role/AWSLambdaBasicExecutionRole" | ||
] | ||
] | ||
} | ||
] | ||
} | ||
}, | ||
"MyFunctionServiceRoleDefaultPolicyB705ABD4": { | ||
"Type": "AWS::IAM::Policy", | ||
"Properties": { | ||
"PolicyDocument": { | ||
"Statement": [ | ||
{ | ||
"Action": [ | ||
"ssm:DescribeParameters", | ||
"ssm:GetParameters", | ||
"ssm:GetParameter" | ||
], | ||
"Effect": "Allow", | ||
"Resource": { | ||
"Fn::Join": [ | ||
"", | ||
[ | ||
"arn", | ||
":", | ||
{ | ||
"Ref": "AWS::Partition" | ||
}, | ||
":", | ||
"ssm", | ||
":", | ||
{ | ||
"Ref": "AWS::Region" | ||
}, | ||
":", | ||
{ | ||
"Ref": "AWS::AccountId" | ||
}, | ||
":", | ||
"parameter", | ||
"/", | ||
{ | ||
"Fn::Join": [ | ||
"", | ||
[ | ||
"/rtv/", | ||
{ | ||
"Ref": "AWS::StackName" | ||
}, | ||
"/", | ||
"com.myorg", | ||
"/", | ||
"MyQueueURL" | ||
] | ||
] | ||
} | ||
] | ||
] | ||
} | ||
} | ||
], | ||
"Version": "2012-10-17" | ||
}, | ||
"PolicyName": "MyFunctionServiceRoleDefaultPolicyB705ABD4", | ||
"Roles": [ | ||
{ | ||
"Ref": "MyFunctionServiceRole3C357FF2" | ||
} | ||
] | ||
} | ||
}, | ||
"MyFunction3BAA72D1": { | ||
"Type": "AWS::Lambda::Function", | ||
"Properties": { | ||
"Code": { | ||
"ZipFile": "exports.handler = function runtimeCode(_event, _context, callback) {\n return callback();\n}" | ||
}, | ||
"Environment": { | ||
"Variables": { | ||
"RTV_STACK_NAME": { | ||
"Ref": "AWS::StackName" | ||
} | ||
} | ||
}, | ||
"Handler": "index.handler", | ||
"Role": { | ||
"Fn::GetAtt": [ | ||
"MyFunctionServiceRole3C357FF2", | ||
"Arn" | ||
] | ||
}, | ||
"Runtime": "nodejs6.10", | ||
"Timeout": 30 | ||
}, | ||
"DependsOn": [ | ||
"MyFunctionServiceRole3C357FF2", | ||
"MyFunctionServiceRoleDefaultPolicyB705ABD4" | ||
] | ||
}, | ||
"MyQueueURLParameterA4918D6E": { | ||
"Type": "AWS::SSM::Parameter", | ||
"Properties": { | ||
"Type": "String", | ||
"Value": { | ||
"Ref": "MyQueueE6CA6235" | ||
}, | ||
"Name": { | ||
"Fn::Join": [ | ||
"", | ||
[ | ||
"/rtv/", | ||
{ | ||
"Ref": "AWS::StackName" | ||
}, | ||
"/", | ||
"com.myorg", | ||
"/", | ||
"MyQueueURL" | ||
] | ||
] | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { App, Stack } from '@aws-cdk/core'; | ||
import { InlineJavaScriptLambda } from '@aws-cdk/lambda'; | ||
import { Queue } from '@aws-cdk/sqs'; | ||
import { RuntimeValue } from '../lib'; | ||
|
||
function runtimeCode(_event: any, _context: any, callback: any) { | ||
return callback(); | ||
} | ||
|
||
class TestStack extends Stack { | ||
constructor(parent: App, name: string) { | ||
super(parent, name); | ||
|
||
const queue = new Queue(this, 'MyQueue'); | ||
const fn = new InlineJavaScriptLambda(this, 'MyFunction', { | ||
handler: { fn: runtimeCode }, | ||
}); | ||
|
||
// this line defines an AWS::SSM::Parameter resource with the | ||
// key "/rtv/<stack-name>/com.myorg/MyQueueURL" and the actual queue URL as value | ||
const queueUrlRtv = new RuntimeValue(this, 'MyQueueURL', { | ||
package: 'com.myorg', | ||
value: queue.queueUrl | ||
}); | ||
|
||
// this line adds read permissions for this SSM parameter to the policies associated with | ||
// the IAM roles of the Lambda function and the EC2 fleet | ||
queueUrlRtv.grantRead(fn.role); | ||
|
||
// adds the `RTV_STACK_NAME` to the environment of the lambda function | ||
// and the fleet (via user-data) | ||
fn.addEnvironment(RuntimeValue.ENV_NAME, RuntimeValue.ENV_VALUE); | ||
} | ||
} | ||
|
||
const app = new App(process.argv); | ||
|
||
new TestStack(app, 'aws-cdk-rtv-lambda'); | ||
|
||
process.stdout.write(app.run()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters