Skip to content

Commit

Permalink
Adding secure bucket construct
Browse files Browse the repository at this point in the history
  • Loading branch information
meleksomai committed Nov 27, 2024
1 parent 0e09ca6 commit 46a6641
Show file tree
Hide file tree
Showing 10 changed files with 522 additions and 0 deletions.
5 changes: 5 additions & 0 deletions packages/cdk-secure-bucket/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
root: true,
// This tells ESLint to load the config from the package `eslint-config-inception`
extends: ["eslint-config-cdk-inception"],
};
5 changes: 5 additions & 0 deletions packages/cdk-secure-bucket/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# @inception-health/cdk-secure-bucket

Provides a construct that extends an s3 bucket. This is different than a lot of our constructs that wrap aws resources.

This bucket implementation handles security best practices. Use can use it instead of a standard s3 bucket by default but you must use it if you're storing PII/PHI.
1 change: 1 addition & 0 deletions packages/cdk-secure-bucket/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require("@inception-health/cdk-jest");
2 changes: 2 additions & 0 deletions packages/cdk-secure-bucket/lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./s3.secure";
export * from "./lifecycle";
36 changes: 36 additions & 0 deletions packages/cdk-secure-bucket/lib/lifecycle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { aws_s3, Duration } from "aws-cdk-lib";

// The lifecycle policy for items should be managed cohesively
export class LifecycleMode {
static readonly NONE = new LifecycleMode();

static readonly BASIC = new LifecycleMode({
enabled: true,
transitions: [
{
storageClass: aws_s3.StorageClass.INFREQUENT_ACCESS,
transitionAfter: Duration.days(360),
},
{
storageClass: aws_s3.StorageClass.GLACIER,
transitionAfter: Duration.days(360 * 3),
},
],
});

static readonly INTELLIGENT = new LifecycleMode({
enabled: true,
transitions: [
{
storageClass: aws_s3.StorageClass.INTELLIGENT_TIERING,
transitionAfter: Duration.days(360),
},
{
storageClass: aws_s3.StorageClass.GLACIER,
transitionAfter: Duration.days(360 * 3),
},
],
});

private constructor(public readonly rule?: aws_s3.LifecycleRule) {}
}
161 changes: 161 additions & 0 deletions packages/cdk-secure-bucket/lib/s3.secure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { aws_kms, aws_s3 } from "aws-cdk-lib";
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";

import { LifecycleMode } from "./lifecycle";

export interface SecureStoreProps
extends Pick<aws_s3.BucketProps, "bucketName"> {
/**
* External KMS key to use for bucket encryption.
*
* The `encryption` property must be specified.
*
*/
encryptionKey: string;

/**
* Type of Bucket lifecycle to use.
*
* - Basic: simple basic lifecycle
* - Intelligent: intelligent lifecycle
* - None: no lifecycle (not recommended)
*
*@defaultValue BASIC
*/
lifecycleMode?: LifecycleMode;

/**
* Bucket removal policy.
*
*@defaultValue RemovalPolicy.RETAIN
*/
removalPolicy?: cdk.RemovalPolicy;
}

const defaultProps = {
lifecycleMode: LifecycleMode.BASIC,
removalPolicy: cdk.RemovalPolicy.RETAIN,
};

/**
* A secure S3 Bucket that follows Inception Health HIPAA policies
*
* References:
* - https://docs.aws.amazon.com/AmazonS3/latest/userguide/security-best-practices.html
*/
export class SecureBucket extends aws_s3.Bucket {
private readonly props: SecureStoreProps;
public readonly kmsKey: aws_kms.IKey;

constructor(scope: Construct, id: string, props: SecureStoreProps) {
const customKmsKey = aws_kms.Key.fromKeyArn(
scope,
`${id}-kms`,
props.encryptionKey,
);
const config = Object.assign({}, defaultProps, props);

// validation
if (!config || !config.lifecycleMode)
throw new Error("Construct requires default lifecycle");

super(scope, id, {
bucketName: props.bucketName,
// ================================================
// Lifecycle Management
// ================================================
lifecycleRules: config.lifecycleMode.rule
? [config.lifecycleMode.rule]
: undefined,
removalPolicy: config.removalPolicy,

//autoDeleteObjects: false, // this is applicable only when removalPolicy` to be set to `RemovalPolicy.DESTROY`

// ================================================
// Inventory Management
// ================================================
// References:
// - https://docs.aws.amazon.com/AmazonS3/latest/userguide/storage-inventory.html
// inventories: ,

// ================================================
// Preventative Security Best Practices
// ================================================
// Step 1- Use Amazon S3 block public access.
blockPublicAccess: aws_s3.BlockPublicAccess.BLOCK_ALL,
// Step 2- Encryption
encryption: aws_s3.BucketEncryption.KMS,
// S3 Bucket Keys decrease the request traffic from Amazon S3 to AWS Key Management Service (AWS KMS) and reduce the cost of SSE-KMS
// https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-key.html
bucketKeyEnabled: true,
encryptionKey: customKmsKey,
// Step 3 - Enforce encryption of data in transit
enforceSSL: true,
// Step 4 - Versioning
// Versioning is a means of keeping multiple variants of an object in the same bucket.
// You can use versioning to preserve, retrieve, and restore every version of every
// object stored in your Amazon S3 bucket. With versioning, you can easily recover
// from both unintended user actions and application failures.
// TODO: Also consider implementing on-going detective controls using
// the s3-bucket-versioning-enabled managed AWS Config rule.
versioned: true,

// Step 5 - Cross-region replication
// TODO: Consider Amazon S3 cross-region replication

// Step 6 - VPC endpoints for Amazon S3 access
//
// TODO: Consider VPC endpoints for Amazon S3 access VPC endpoints for
// Amazon S3 provide multiple ways to control access to your Amazon S3
// data:
// - You can control the requests, users, or groups that are allowed
// through a specific VPC endpoint.
// - You can control which VPCs or VPC endpoints have access to your S3
// buckets by using S3 bucket policies.
// - You can help prevent data exfiltration by using a VPC that does not
// have an internet gateway.

// ================================================
// Monitoring and Auditing Best Practices
// ================================================
// Step 1 - Identify and audit all your Amazon S3 buckets

// Step 3 - Enable Amazon S3 server access logging
// TODO: should we build a custom s3 access log destination
serverAccessLogsPrefix: "accesslogs",
});

// ================================================
// Monitoring and Auditing Best Practices
// ================================================
// Step 2 - Implement monitoring using AWS monitoring tools
// Monitoring is an important part of maintaining the reliability,
// security, availability, and performance of Amazon S3 and your AWS
// solutions. AWS provides several tools and services to help you
// monitor Amazon S3 and your other AWS services. For example, you can
// monitor CloudWatch metrics for Amazon S3, particularly PutRequests,
// GetRequests, 4xxErrors, and DeleteRequests. For more information, see
// Monitoring metrics with Amazon CloudWatch and, Monitoring Amazon S3.
// For a second example, see Example: Amazon S3 Bucket Activity. This
// example describes how to create an Amazon CloudWatch alarm that is
// triggered when an Amazon S3 API call is made to PUT or DELETE bucket
// policy, bucket lifecycle, or bucket replication, or to PUT a bucket ACL.
// const putRequestMetric = this.mm({ id: 'PutRequests' });
// putRequestMetric.

// Step 4 - Use AWS CloudTrail

// Step 5 - Enable AWS Config

// Step 6 - Consider using Amazon Macie with Amazon S3

// Step 7 - Monitor AWS security advisories

// ================================================
// Props
// ================================================
this.props = config;
this.kmsKey = customKmsKey;
}
}
42 changes: 42 additions & 0 deletions packages/cdk-secure-bucket/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "@inception-health/cdk-secure-bucket",
"version": "0.0.1",
"license": "MIT",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist/**"
],
"scripts": {
"build": "tsup lib/index.ts --format esm,cjs --dts",
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
"dev": "tsup lib/index.ts --format esm,cjs --watch --dts",
"lint": "TIMING=1 eslint 'lib/**/*.{js,ts}'",
"lint:fix": "TIMING=1 eslint 'lib/**/*.{js,ts}' --fix",
"test": "jest --coverage",
"test:u": "jest --coverage -u"
},
"peerDependencies": {
"aws-cdk": "2.x.x",
"aws-cdk-lib": "2.x.x",
"constructs": "10.x.x"
},
"devDependencies": {
"@inception-health/cdk-jest": "workspace:*",
"@inception-health/cdk-tsconfig": "workspace:*",
"@types/jest": "29.5.12",
"@types/node": "20.11.19",
"aws-cdk": "2.128.0",
"aws-cdk-lib": "2.128.0",
"constructs": "10.3.0",
"eslint": "8.56.0",
"eslint-config-cdk-inception": "workspace:*",
"jest": "29.7.0",
"ts-jest": "29.1.2",
"tsup": "8.0.2",
"typescript": "5.3.3"
},
"repository": "git@github.com:inception-health/cdk.git",
"sideEffects": false
}
Loading

0 comments on commit 46a6641

Please sign in to comment.