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

Use "/" instead of "@" in SSM parameter key #208

Merged
merged 2 commits into from
Jul 2, 2018
Merged
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
54 changes: 54 additions & 0 deletions packages/@aws-cdk/rtv/README.md
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.
63 changes: 55 additions & 8 deletions packages/@aws-cdk/rtv/lib/rtv.ts
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 {

}
12 changes: 9 additions & 3 deletions packages/@aws-cdk/rtv/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
"prepare": "jsii && tslint -p . && pkglint",
"watch": "jsii -w",
"lint": "tsc && tslint -p . --force",
"test": "nyc nodeunit test/test.*.js",
"pkglint": "pkglint -f"
"test": "nyc nodeunit test/test.*.js && cdk-integ-assert",
"pkglint": "pkglint -f",
"integ": "cdk-integ"
},
"keywords": [
"aws",
Expand All @@ -33,7 +34,12 @@
},
"license": "LicenseRef-LICENSE",
"devDependencies": {
"pkglint": "^0.7.1"
"pkglint": "^0.7.1",
"aws-cdk": "^0.7.2-beta",
"@aws-cdk/assert": "^0.7.2-beta",
"@aws-cdk/ec2": "^0.7.2-beta",
"@aws-cdk/sqs": "^0.7.2-beta",
"@aws-cdk/lambda": "^0.7.2-beta"
},
"dependencies": {
"@aws-cdk/core": "^0.7.2-beta",
Expand Down
164 changes: 164 additions & 0 deletions packages/@aws-cdk/rtv/test/integ.rtv.lambda.expected.json
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"
]
]
}
}
}
}
}
40 changes: 40 additions & 0 deletions packages/@aws-cdk/rtv/test/integ.rtv.lambda.ts
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());
2 changes: 1 addition & 1 deletion packages/@aws-cdk/rtv/test/test.rtv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,6 @@ class RuntimeValueTest extends Construct {
new RuntimeValue(this, 'MyQueueName', { package: RTV_PACKAGE, value: queue.queueName })
];

runtimeValues.forEach(rtv => rtv.grantReadPermissions(role));
runtimeValues.forEach(rtv => rtv.grantRead(role));
}
}