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

Enable creation of strong names for .NET assemblies. #643

Merged
merged 10 commits into from Sep 11, 2018
3 changes: 3 additions & 0 deletions buildspec.yaml
Expand Up @@ -4,6 +4,9 @@ phases:
install:
commands:
- /bin/bash ./install.sh
pre_build:
commands:
- /bin/bash ./fetch-dotnet-snk.sh
build:
commands:
- /bin/bash ./build.sh
Expand Down
61 changes: 61 additions & 0 deletions fetch-dotnet-snk.sh
@@ -0,0 +1,61 @@
#!/bin/bash
set -euo pipefail

# This script retrieves the .snk file needed to create strong names for .NET assemblies.

function echo_usage() {
echo "USAGE: Set the following environment variables, then run ./fetch-dotnet-snk.sh with no arguments."
echo -e "\tDOTNET_STRONG_NAME_ENABLED=true"
echo -e "\tDOTNET_STRONG_NAME_ROLE_ARN=<ARN of a role with access to the secret. You must have iam:AssumeRole permissions for this role.>"
echo -e "\tDOTNET_STRONG_NAME_SECRET_REGION=<The AWS region (i.e. us-east-2) in which in the secret is stored.>"
echo -e "\tDOTNET_STRONG_NAME_SECRET_ID=<The name (i.e. production/my/key) or ARN of the secret containing the .snk file.>"
}

if [ -z ${DOTNET_STRONG_NAME_ENABLED:-} ]; then
echo "Environment variable DOTNET_STRONG_NAME_ENABLED is not set. Skipping strong-name signing."
exit 0
fi

echo "Retrieving SNK..."

apt update -y
apt install jq -y

if [ -z ${DOTNET_STRONG_NAME_ROLE_ARN:-} ]; then
echo "Strong name signing is enabled, but DOTNET_STRONG_NAME_ROLE_ARN is not set."
echo_usage
exit 1
fi

if [ -z ${DOTNET_STRONG_NAME_SECRET_REGION:-}]; then
echo "Strong name signing is enabled, but DOTNET_STRONG_NAME_SECRET_REGION is not set."
echo_usage
exit 1
fi

if [ -z ${DOTNET_STRONG_NAME_SECRET_ID:-} ]; then
echo "Strong name signing is enabled, but DOTNET_STRONG_NAME_SECRET_ID is not set."
echo_usage
exit 1
fi

ROLE=$(aws sts assume-role --region ${DOTNET_STRONG_NAME_SECRET_REGION:-} --role-arn ${DOTNET_STRONG_NAME_ROLE_ARN:-} --role-session-name "jsii-dotnet-snk")
export AWS_ACCESS_KEY_ID=$(echo $ROLE | jq -r .Credentials.AccessKeyId)
export AWS_SECRET_ACCESS_KEY=$(echo $ROLE | jq -r .Credentials.SecretAccessKey)
export AWS_SESSION_TOKEN=$(echo $ROLE | jq .Credentials.SessionToken)

SNK_SECRET=$(aws secretsmanager get-secret-value --region ${DOTNET_STRONG_NAME_SECRET_REGION:-} --secret-id ${DOTNET_STRONG_NAME_SECRET_ID:-})
TMP_DIR=$(mktemp -d)
TMP_KEY="$TMP_DIR/key.snk"
echo $SNK_SECRET | jq -r .SecretBinary | base64 --decode > $TMP_KEY

for PACKAGE_PATH in packages/@aws-cdk/*; do
JSII_PROPERTY=$(cat "$PACKAGE_PATH/package.json" | jq -r .jsii)
if [ -z $JSII_PROPERTY ]; then
continue
fi

cp $TMP_KEY $PACKAGE_PATH
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of copying this file everywhere, maybe the .NET build can consult an environment variable for it's location?

Copy link
Contributor Author

@mpiroc mpiroc Aug 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Using an environment variable is an extra level of indirection that we don't need (we already have too many [levels of indirection]!). There's no cost to copying the file everywhere.
  • I'm trying to minimize any necessary changes if we ever decide to move away from a monorepo. While an environment variable would still work with one package per repo, it seems like overkill.

Copy link
Contributor Author

@mpiroc mpiroc Aug 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another issue with using an environment variable is: How would it be set? The temporary directory is created by fetch-dotnet-snk.sh, but build.sh is a sibling process of fetch-dotnet-snk.sh, not a descendant. A process can't modify its parent's environment variables.

done

rm -rf $TMP_DIR
17 changes: 12 additions & 5 deletions packages/@aws-cdk/aws-dynamodb/lib/table.ts
Expand Up @@ -26,22 +26,28 @@ export interface TableProps {
*/
tableName?: string;

/**
* Whether server-side encryption is enabled.
* @default undefined, server-side encryption is disabled
*/
sseEnabled?: boolean;

/**
* When an item in the table is modified, StreamViewType determines what information
* is written to the stream for this table. Valid values for StreamViewType are:
* @default undefined, streams are disbaled
* @default undefined, streams are disabled
*/
streamSpecification?: StreamViewType;

/**
* AutoScalingProps configuration to configure Read AutoScaling for the DyanmoDB table.
* AutoScalingProps configuration to configure Read AutoScaling for the DynamoDB table.
* This field is optional and this can be achieved via addReadAutoScaling.
* @default undefined, read auto scaling is disabled
*/
readAutoScaling?: AutoScalingProps;

/**
* AutoScalingProps configuration to configure Write AutoScaling for the DyanmoDB table.
* AutoScalingProps configuration to configure Write AutoScaling for the DynamoDB table.
* This field is optional and this can be achieved via addWriteAutoScaling.
* @default undefined, write auto scaling is disabled
*/
Expand Down Expand Up @@ -111,7 +117,8 @@ export class Table extends Construct {
keySchema: this.keySchema,
attributeDefinitions: this.attributeDefinitions,
provisionedThroughput: { readCapacityUnits, writeCapacityUnits },
streamSpecification: props.streamSpecification ? {streamViewType: props.streamSpecification} : undefined
sseSpecification: props.sseEnabled ? { sseEnabled: props.sseEnabled } : undefined,
streamSpecification: props.streamSpecification ? { streamViewType: props.streamSpecification } : undefined
});

if (props.tableName) { this.addMetadata('aws:cdk:hasPhysicalName', props.tableName); }
Expand Down Expand Up @@ -293,4 +300,4 @@ export enum StreamViewType {
NewAndOldImages = 'NEW_AND_OLD_IMAGES',
/** Only the key attributes of the modified item are written to the stream. */
KeysOnly = 'KEYS_ONLY'
}
}
24 changes: 15 additions & 9 deletions packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.expected.json
Expand Up @@ -3,29 +3,35 @@
"TableCD117FA1": {
"Type": "AWS::DynamoDB::Table",
"Properties": {
"AttributeDefinitions": [
"KeySchema": [
{
"AttributeName": "hashKey",
"AttributeType": "S"
"KeyType": "HASH"
},
{
"AttributeName": "rangeKey",
"AttributeType": "N"
"KeyType": "RANGE"
}
],
"KeySchema": [
"ProvisionedThroughput": {
"ReadCapacityUnits": 5,
"WriteCapacityUnits": 5
},
"AttributeDefinitions": [
{
"AttributeName": "hashKey",
"KeyType": "HASH"
"AttributeType": "S"
},
{
"AttributeName": "rangeKey",
"KeyType": "RANGE"
"AttributeType": "N"
}
],
"ProvisionedThroughput": {
"ReadCapacityUnits": 5,
"WriteCapacityUnits": 5
"SSESpecification": {
"SSEEnabled": true
},
"StreamSpecification": {
"StreamViewType": "KEYS_ONLY"
}
}
}
Expand Down
7 changes: 5 additions & 2 deletions packages/@aws-cdk/aws-dynamodb/test/integ.dynamodb.ts
@@ -1,11 +1,14 @@
import { App, Stack } from '@aws-cdk/cdk';
import { KeyAttributeType, Table } from '../lib';
import { KeyAttributeType, StreamViewType, Table } from '../lib';

const app = new App(process.argv);

const stack = new Stack(app, 'aws-cdk-dynamodb');

const table = new Table(stack, 'Table');
const table = new Table(stack, 'Table', {
sseEnabled: true,
streamSpecification: StreamViewType.KeysOnly
});

table.addPartitionKey('hashKey', KeyAttributeType.String);
table.addSortKey('rangeKey', KeyAttributeType.Number);
Expand Down
40 changes: 36 additions & 4 deletions packages/@aws-cdk/aws-dynamodb/test/test.dynamodb.ts
Expand Up @@ -12,7 +12,7 @@ export = {
test.done();
},

'range key only'(test: Test) {
'hash key only'(test: Test) {
const app = new TestApp();
new Table(app.stack, 'MyTable').addPartitionKey('hashKey', KeyAttributeType.Binary);
const template = app.synthesizeTemplate();
Expand All @@ -33,7 +33,7 @@ export = {
test.done();
},

'range + hash key'(test: Test) {
'hash + range key'(test: Test) {
const app = new TestApp();
new Table(app.stack, 'MyTable').addPartitionKey('hashKey', KeyAttributeType.Binary)
.addSortKey('sortKey', KeyAttributeType.Number);
Expand All @@ -60,7 +60,35 @@ export = {

test.done();
},
'stream is not enabled by default'(test: Test) {
'server-side encryption is not enabled'(test: Test) {
const app = new TestApp();
new Table(app.stack, 'MyTable')
.addPartitionKey('partitionKey', KeyAttributeType.Binary)
.addSortKey('sortKey', KeyAttributeType.Number);
const template = app.synthesizeTemplate();

test.deepEqual(template, {
Resources: {
MyTable794EDED1: {
Type: 'AWS::DynamoDB::Table',
Properties: {
AttributeDefinitions: [
{ AttributeName: 'partitionKey', AttributeType: 'B' },
{ AttributeName: 'sortKey', AttributeType: 'N' }
],
KeySchema: [
{ AttributeName: 'partitionKey', KeyType: 'HASH' },
{ AttributeName: 'sortKey', KeyType: 'RANGE' }
],
ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5 },
}
}
}
});

test.done();
},
'stream is not enabled'(test: Test) {
const app = new TestApp();
new Table(app.stack, 'MyTable')
.addPartitionKey('partitionKey', KeyAttributeType.Binary)
Expand Down Expand Up @@ -200,7 +228,9 @@ export = {
const table = new Table(app.stack, 'MyTable', {
tableName: 'MyTable',
readCapacity: 42,
writeCapacity: 1337
writeCapacity: 1337,
sseEnabled: true,
streamSpecification: StreamViewType.KeysOnly
});
table.addPartitionKey('partitionKey', KeyAttributeType.String);
table.addSortKey('sortKey', KeyAttributeType.Binary);
Expand All @@ -223,6 +253,8 @@ export = {
ReadCapacityUnits: 42,
WriteCapacityUnits: 1337
},
SSESpecification: { SSEEnabled: true },
StreamSpecification: { StreamViewType: 'KEYS_ONLY' },
TableName: 'MyTable',
}
}
Expand Down
27 changes: 27 additions & 0 deletions tools/pkglint/lib/rules.ts
Expand Up @@ -256,6 +256,33 @@ export class JSIIDotNetNamespaceIsRequired extends ValidationRule {
}
}

/**
* Strong-naming all .NET assemblies is required.
*/
export class JSIIDotNetStrongNameIsRequired extends ValidationRule {
public validate(pkg: PackageJson): void {
if (!isJSII(pkg)) { return; }

const signAssembly = deepGet(pkg.json, ['jsii', 'targets', 'dotnet', 'signAssembly']) as boolean | undefined;
const signAssemblyExpected = true;
if (signAssembly !== signAssemblyExpected) {
pkg.report({
message: `.NET packages must have strong-name signing enabled.`,
fix: () => deepSet(pkg.json, ['jsii', 'targets', 'dotnet', 'signAssembly'], signAssemblyExpected)
});
}

const assemblyOriginatorKeyFile = deepGet(pkg.json, ['jsii', 'targets', 'dotnet', 'assemblyOriginatorKeyFile']) as string | undefined;
const assemblyOriginatorKeyFileExpected = "../../key.snk";
if (assemblyOriginatorKeyFile !== assemblyOriginatorKeyFileExpected) {
pkg.report({
message: `.NET packages must use the strong name key fetched by fetch-dotnet-snk.sh`,
fix: () => deepSet(pkg.json, ['jsii', 'targets', 'dotnet', 'assemblyOriginatorKeyFile'], assemblyOriginatorKeyFileExpected)
});
}
}
}

/**
* The package must depend on cdk-build-tools
*/
Expand Down