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

Fix/allow key access #20

Merged
merged 5 commits into from
Oct 27, 2023
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
15 changes: 0 additions & 15 deletions src/ApiStack.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Stack, StackProps } from 'aws-cdk-lib';
import { AnyPrincipal, Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam';
import { Key } from 'aws-cdk-lib/aws-kms';
import { ITopic, Topic } from 'aws-cdk-lib/aws-sns';
import { StringParameter } from 'aws-cdk-lib/aws-ssm';
import { Construct } from 'constructs';
Expand Down Expand Up @@ -39,27 +38,13 @@ class SNSTopic extends Construct {
constructor(scope: Construct, id: string, props: SNSTopicProps) {
super(scope, id);

let masterKey = (props.keyProtected !== false) ? this.encryptionKey() : undefined;

this.topic = new Topic(this, 'submissions', {
displayName: 'submissions',
masterKey,
});

this.allowCrossAccountAccess(props.publishingAccountIds);
}

/**
* Returns the customer manager key for the project
*
* @returns the encryption key
*/
private encryptionKey() {
let masterKey = undefined;
masterKey = Key.fromKeyArn(this, 'key', StringParameter.valueForStringParameter(this, Statics.ssmDataKeyArn));
return masterKey;
}

/**
* Allow cross account access to this topic
*
Expand Down
34 changes: 1 addition & 33 deletions src/StorageStack.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Duration, Stack, StackProps } from 'aws-cdk-lib';
import { AttributeType, BillingMode, Table, TableEncryption } from 'aws-cdk-lib/aws-dynamodb';
import { Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam';
import { Key } from 'aws-cdk-lib/aws-kms';
import { Bucket, BucketEncryption, ObjectOwnership } from 'aws-cdk-lib/aws-s3';
import { Secret } from 'aws-cdk-lib/aws-secretsmanager';
Expand Down Expand Up @@ -53,16 +52,13 @@ export class StorageStack extends Stack {
this.addParameters();
}

private key(crossAccountIds?: string[]) {
const crossAccountPrincipalArns = this.crossAccountIdArns(crossAccountIds);
private key() {
const key = new Key(this, 'kmskey', {
enableKeyRotation: true,
description: 'encryption key for user data',
alias: `${Statics.projectName}/user-data`,
});

this.allowCrossAccountKeyAccess(crossAccountPrincipalArns, key);

// Store key arn to be used in other stacks/projects
new StringParameter(this, 'key', {
stringValue: key.keyArn,
Expand All @@ -71,34 +67,6 @@ export class StorageStack extends Stack {

return key;
}

private allowCrossAccountKeyAccess(crossAccountPrincipalArns: string[] | null, key: Key) {
if (crossAccountPrincipalArns) {
key.addToResourcePolicy(new PolicyStatement({
effect: Effect.ALLOW,
actions: [
'kms:GenerateDataKey',
'kms:Decrypt',
],
resources: [key.keyArn],
conditions: {
ArnLike: {
'aws:PrincipalArn': crossAccountPrincipalArns,
},
},
}), false);
}
}

private crossAccountIdArns(crossAccountIds: string[] | undefined) {
if (crossAccountIds && crossAccountIds.length > 0) {
return crossAccountIds.map(
(accountId) => `arn:aws:iam::${accountId}:role/storesubmissions-lambda-role`,
);
}
return null;
}

private addArnToParameterStore(id: string, arn: string, name: string) {
new StringParameter(this, id, {
stringValue: arn,
Expand Down
144 changes: 144 additions & 0 deletions src/StorageStack.ts.orig
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { Duration, Stack, StackProps } from 'aws-cdk-lib';
import { AttributeType, BillingMode, Table, TableEncryption } from 'aws-cdk-lib/aws-dynamodb';
import { Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam';
import { Key } from 'aws-cdk-lib/aws-kms';
import { Bucket, BucketEncryption, ObjectOwnership } from 'aws-cdk-lib/aws-s3';
import { Secret } from 'aws-cdk-lib/aws-secretsmanager';
import { StringParameter } from 'aws-cdk-lib/aws-ssm';
import { Construct } from 'constructs';
import { Configurable } from './Configuration';
import { Statics } from './statics';


interface StorageStackProps extends StackProps, Configurable {};

/**
* Contains all API-related resources.
*/
export class StorageStack extends Stack {
constructor(scope: Construct, id: string, props: StorageStackProps) {
super(scope, id, props);

const key = this.key();
/**
* This bucket will receive submission attachments
* (Submission PDF, uploads) for each submission.
*/
const bucket = new Bucket(this, 'submission-attachments', {
eventBridgeEnabled: true,
enforceSSL: true,
encryption: BucketEncryption.KMS,
objectOwnership: ObjectOwnership.BUCKET_OWNER_ENFORCED,
lifecycleRules: [
{
expiration: Duration.days(365),
},
],
encryptionKey: key,
});
this.addArnToParameterStore('bucketParam', bucket.bucketArn, Statics.ssmSubmissionBucketArn);
this.addArnToParameterStore('bucketNameParam', bucket.bucketName, Statics.ssmSubmissionBucketName);

const table = new Table(this, 'submissions', {
partitionKey: { name: 'pk', type: AttributeType.STRING },
sortKey: { name: 'sk', type: AttributeType.STRING },
billingMode: BillingMode.PAY_PER_REQUEST,
timeToLiveAttribute: 'ttl',
encryptionKey: key,
encryption: TableEncryption.CUSTOMER_MANAGED,
});
this.addArnToParameterStore('tableParam', table.tableArn, Statics.ssmSubmissionTableArn);
this.addArnToParameterStore('tableNameParam', table.tableName, Statics.ssmSubmissionTableName);

this.addParameters();
}

private key(crossAccountIds?: string[]) {
const crossAccountPrincipalArns = this.crossAccountIdArns(crossAccountIds);
const key = new Key(this, 'kmskey', {
enableKeyRotation: true,
description: 'encryption key for user data',
alias: `${Statics.projectName}/user-data`,
});

this.allowCrossAccountKeyAccess(crossAccountPrincipalArns, key);

// Store key arn to be used in other stacks/projects
new StringParameter(this, 'key', {
stringValue: key.keyArn,
parameterName: Statics.ssmDataKeyArn,
});

return key;
}
<<<<<<< HEAD
=======

private allowCrossAccountKeyAccess(crossAccountPrincipalArns: string[] | null, key: Key) {
if (crossAccountPrincipalArns) {
key.addToResourcePolicy(new PolicyStatement({
effect: Effect.ALLOW,
actions: [
'kms:GenerateDataKey',
'kms:Decrypt',
],
resources: [key.keyArn],
conditions: {
ArnLike: {
'aws:PrincipalArn': crossAccountPrincipalArns,
},
},
}), false);
}
}

private crossAccountIdArns(crossAccountIds: string[] | undefined) {
if (crossAccountIds && crossAccountIds.length > 0) {
return crossAccountIds.map(
(accountId) => `arn:aws:iam::${accountId}:role/storesubmissions-lambda-role`,
);
}
return null;
}

>>>>>>> development
private addArnToParameterStore(id: string, arn: string, name: string) {
new StringParameter(this, id, {
stringValue: arn,
parameterName: name,
});
}

/**
* Add general parameters, the values of which should be added later
*/
private addParameters() {
new StringParameter(this, 'submissionTopicArn', {
stringValue: '-',
parameterName: Statics.ssmSubmissionTopicArn,
});

new StringParameter(this, 'sourceBucketArn', {
stringValue: '-',
parameterName: Statics.ssmSourceBucketArn,
description: 'ARN for the source bucket, to allow copying submission files',
});

new StringParameter(this, 'sourceKeyArn', {
stringValue: '-',
parameterName: Statics.ssmSourceKeyArn,
description: 'ARN for the source bucket encryption key, to allow copying submission files',
});

new StringParameter(this, 'formIoBaseUrl', {
stringValue: '-',
parameterName: Statics.ssmFormIoBaseUrl,
description: 'Base url for retrieving form config. Includes stage path.',
});

new Secret(this, 'formIoApiKey', {
secretName: Statics.secretFormIoApiKey,
description: 'FormIO Api token for retrieving form config',
});
}
}
Loading