⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content
Open
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
494 changes: 382 additions & 112 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
"workspaces": [
"packages/hack",
"packages/cdk",
"packages/foo"
"packages/foo",
"packages/process",
"packages/create",
"packages/poll"
],
"devDependencies": {
"@eslint/js": "^9.38.0",
Expand Down
141 changes: 141 additions & 0 deletions packages/cdk/resources/DynamoDb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import {Construct} from "constructs"

import {
AttributeType,
Billing,
TableEncryptionV2,
TableV2
} from "aws-cdk-lib/aws-dynamodb"
import {
AccountRootPrincipal,
AnyPrincipal,
Effect,
ManagedPolicy,
PolicyDocument,
PolicyStatement
} from "aws-cdk-lib/aws-iam"
import {Key} from "aws-cdk-lib/aws-kms"
import {Duration, RemovalPolicy} from "aws-cdk-lib"

export interface DynamodbProps {
readonly stackName: string
}

/**
* Dynamodb tables used for user state information
*/
export class Dynamodb extends Construct {
public readonly processStatus: TableV2
public readonly useProcessStatusKmsKeyPolicy: ManagedPolicy
public readonly processStatusTableWritePolicy: ManagedPolicy
public readonly processStatusTableReadPolicy: ManagedPolicy
//

public constructor(scope: Construct, id: string, props: DynamodbProps) {
super(scope, id)

// KMS key for token mapping table
const processStatusKey = new Key(this, "processStatusKey", {
removalPolicy: RemovalPolicy.DESTROY,
pendingWindow: Duration.days(7),
alias: `${props.stackName}-processStatusKey`,
description: `${props.stackName}-processStatusKey`,
enableKeyRotation: true,
policy: new PolicyDocument({
statements: [
new PolicyStatement({
sid: "Enable IAM User Permissions",
effect: Effect.ALLOW,
actions: [
"kms:*"
],
principals: [
new AccountRootPrincipal
],
resources: ["*"]
})
]
})
})

// Process Status Table
const processStatusTable = new TableV2(this, "processStatusTable", {
partitionKey: {
name: "actionId",
type: AttributeType.STRING
},
tableName: `${props.stackName}-processStatus`,
removalPolicy: RemovalPolicy.DESTROY,
pointInTimeRecoverySpecification: {
pointInTimeRecoveryEnabled: true
},
encryption: TableEncryptionV2.customerManagedKey(processStatusKey),
billing: Billing.onDemand(),
timeToLiveAttribute: "ExpiryTime"
})

// Policy to use token mapping KMS key
const useProcessStatusKmsKey = new ManagedPolicy(this, "UseProcessStatusKMSKeyPolicy", {
statements: [
new PolicyStatement({
actions: [
"kms:DescribeKey",
"kms:GenerateDataKey",
"kms:Encrypt",
"kms:ReEncryptFrom",
"kms:ReEncryptTo",
"kms:Decrypt"
],
resources: [
processStatusKey.keyArn
]
})
]
})

const processStatusReadPolicy = new ManagedPolicy(this, "ProcessStatusReadManagedPolicy", {
statements: [
new PolicyStatement({
actions: [
"dynamodb:GetItem",
"dynamodb:BatchGetItem",
"dynamodb:Scan",
"dynamodb:Query",
"dynamodb:ConditionCheckItem",
"dynamodb:DescribeTable"
],
resources: [
processStatusTable.tableArn,
`${processStatusTable.tableArn}/index/*`
]
})
]
})

const processStatusWritePolicy = new ManagedPolicy(this, "ProcessStatusWriteManagedPolicy", {
statements: [
new PolicyStatement({
actions: [
"dynamodb:PutItem",
"dynamodb:BatchWriteItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem"
],
resources: [
processStatusTable.tableArn,
`${processStatusTable.tableArn}/index/*`
]
})
]
})


// Outputs: assign the created resources to the class properties
this.processStatus = processStatusTable
this.useProcessStatusKmsKeyPolicy = useProcessStatusKmsKey
this.processStatusTableWritePolicy = processStatusWritePolicy
this.processStatusTableReadPolicy = processStatusReadPolicy


}
}
57 changes: 56 additions & 1 deletion packages/cdk/resources/Functions.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import {Construct} from "constructs"
import {TypescriptLambdaFunction} from "@nhsdigital/eps-cdk-constructs"
import { resolve } from "node:path"
import {resolve} from "node:path"
import {Dynamodb} from "./DynamoDb"
const baseDir = resolve(__dirname, "../../..")
// Interface for properties needed to create API functions
export interface ApiFunctionsProps {
readonly serviceName: string
readonly stackName: string
readonly version: string
readonly commitId: string
readonly processingStatusTable: Dynamodb
}

/**
* Class for creating functions and resources needed for API operations
*/
export class ApiFunctions extends Construct {
public readonly fooLambda: TypescriptLambdaFunction
public readonly createLambda: TypescriptLambdaFunction
public readonly processLambda: TypescriptLambdaFunction
public readonly pollLambda: TypescriptLambdaFunction

public constructor(scope: Construct, id: string, props: ApiFunctionsProps) {
super(scope, id)
Expand All @@ -29,7 +34,57 @@ export class ApiFunctions extends Construct {
version: props.version,
commitId: props.commitId
})

const processLambda = new TypescriptLambdaFunction(this, "ProcessLambda", {
functionName: `${props.stackName}-ProcessLambda`,
projectBaseDir: baseDir,
packageBasePath: "packages/process",
entryPoint: "src/handler.ts",
environmentVariables: {},
logRetentionInDays: 30,
logLevel: "DEBUG",
version: props.version,
commitId: props.commitId
})

const createLambda = new TypescriptLambdaFunction(this, "CreateLambda", {
functionName: `${props.stackName}-CreateLambda`,
projectBaseDir: baseDir,
packageBasePath: "packages/create",
entryPoint: "src/handler.ts",
environmentVariables: {
PROCESSING_LAMBDA_NAME: processLambda.function.functionName
},
logRetentionInDays: 30,
logLevel: "DEBUG",
version: props.version,
commitId: props.commitId,
additionalPolicies: [
processLambda.executionPolicy
]
})

const pollLambda = new TypescriptLambdaFunction(this, "PollLambda", {
functionName: `${props.stackName}-PollLambda`,
projectBaseDir: baseDir,
packageBasePath: "packages/poll",
entryPoint: "src/handler.ts",
environmentVariables: {
PROCESSING_STATUS_TABLE_NAME: props.processingStatusTable.processStatus.tableName
},
logRetentionInDays: 30,
logLevel: "DEBUG",
version: props.version,
commitId: props.commitId,
additionalPolicies: [
props.processingStatusTable.processStatusTableReadPolicy,
]
})

// Outputs
this.fooLambda = fooLambda
this.createLambda = createLambda
this.processLambda = processLambda
this.pollLambda = pollLambda
}
}
27 changes: 21 additions & 6 deletions packages/cdk/resources/RestApiGateway/RestApiGatewayMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import {
RestApi
} from "aws-cdk-lib/aws-apigateway"
import {Construct} from "constructs"
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs"
import {NodejsFunction} from "aws-cdk-lib/aws-lambda-nodejs"
// import {NodejsFunction} from "aws-cdk-lib/aws-lambda-nodejs"

export interface RestApiGatewayMethodsProps {
readonly executePolices: Array<IManagedPolicy>
readonly restAPiGatewayRole: IRole
readonly restApiGateway: RestApi
readonly fooLambda: NodejsFunction
readonly createLambda: NodejsFunction
readonly pollLambda: NodejsFunction
}

/**
Expand All @@ -35,11 +37,24 @@ export class RestApiGatewayMethods extends Construct {

const prescriptionDetailsLambdaResource = props.restApiGateway.root.addResource("foo")
prescriptionDetailsLambdaResource.addMethod("GET", new LambdaIntegration(props.fooLambda, {
credentialsRole: props.restAPiGatewayRole
}), {
authorizationType: AuthorizationType.NONE,
})

credentialsRole: props.restAPiGatewayRole
}), {
authorizationType: AuthorizationType.NONE,
})

const createPrescriptionLambdaResource = props.restApiGateway.root.addResource("create")
createPrescriptionLambdaResource.addMethod("GET", new LambdaIntegration(props.createLambda, {
credentialsRole: props.restAPiGatewayRole
}), {
authorizationType: AuthorizationType.NONE,
})

const pollPrescriptionLambdaResource = props.restApiGateway.root.addResource("poll")
pollPrescriptionLambdaResource.addMethod("GET", new LambdaIntegration(props.pollLambda, {
credentialsRole: props.restAPiGatewayRole
}), {
authorizationType: AuthorizationType.NONE,
})

//Outputs
}
Expand Down
43 changes: 26 additions & 17 deletions packages/cdk/stacks/HackStack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,19 @@ import {StaticContentBucket} from "../resources/StaticContentBucket"
import {Certificate} from "aws-cdk-lib/aws-certificatemanager"
import {Role} from "aws-cdk-lib/aws-iam"
import {HostedZone} from "aws-cdk-lib/aws-route53"
import { Key } from "aws-cdk-lib/aws-kms"
import { Stream } from "aws-cdk-lib/aws-kinesis"
import { ukRegionLogGroups } from "../resources/ukRegionLogGroups"
import { RestApiGateway } from "../resources/RestApiGateway"
import { RestApiGatewayMethods } from "../resources/RestApiGateway/RestApiGatewayMethods"
import { RestApiOrigin, S3BucketOrigin } from "aws-cdk-lib/aws-cloudfront-origins"
import { AccessLevel, AllowedMethods, FunctionEventType, OriginRequestCookieBehavior, OriginRequestHeaderBehavior, OriginRequestPolicy, OriginRequestQueryStringBehavior, ViewerProtocolPolicy } from "aws-cdk-lib/aws-cloudfront"
import { CloudfrontBehaviors } from "../resources/CloudfrontBehaviors"
import { CloudfrontDistribution } from "../resources/CloudfrontDistribution"
import { getConfigFromEnvVar } from "@nhsdigital/eps-cdk-constructs"
import { ApiFunctions } from "../resources/Functions"
import { addNagSuppressions } from "./nagSuppression"
import {Key} from "aws-cdk-lib/aws-kms"
import {Stream} from "aws-cdk-lib/aws-kinesis"
import {ukRegionLogGroups} from "../resources/ukRegionLogGroups"
import {RestApiGateway} from "../resources/RestApiGateway"
import {RestApiGatewayMethods} from "../resources/RestApiGateway/RestApiGatewayMethods"
import {RestApiOrigin, S3BucketOrigin} from "aws-cdk-lib/aws-cloudfront-origins"
import {AccessLevel, AllowedMethods, FunctionEventType, OriginRequestCookieBehavior, OriginRequestHeaderBehavior, OriginRequestPolicy, OriginRequestQueryStringBehavior, ViewerProtocolPolicy} from "aws-cdk-lib/aws-cloudfront"
import {CloudfrontBehaviors} from "../resources/CloudfrontBehaviors"
import {CloudfrontDistribution} from "../resources/CloudfrontDistribution"
import {getConfigFromEnvVar} from "@nhsdigital/eps-cdk-constructs"
import {ApiFunctions} from "../resources/Functions"
import {addNagSuppressions} from "./nagSuppression"
import {Dynamodb} from "../resources/DynamoDb"

export interface HackStackProps extends StackProps {
readonly serviceName: string
Expand All @@ -37,7 +38,7 @@ export interface HackStackProps extends StackProps {
*/

export class HackStack extends Stack {
public constructor(scope: App, id: string, props: HackStackProps){
public constructor(scope: App, id: string, props: HackStackProps) {
super(scope, id, props)

// Context
Expand Down Expand Up @@ -93,6 +94,9 @@ export class HackStack extends Stack {
wafLogGroupName: `aws-waf-logs-${props.serviceName}-apigw`,
stackName: this.stackName
})
const dyna = new Dynamodb(this, "Dynamodb", {
stackName: props.stackName
})
const apiGateway = new RestApiGateway(this, "ApiGateway", {
serviceName: props.serviceName,
stackName: props.stackName,
Expand All @@ -106,15 +110,19 @@ export class HackStack extends Stack {
serviceName: props.serviceName,
stackName: props.stackName,
version: "1.0.0",
commitId: "abc123"
commitId: "abc123",
processingStatusTable: dyna
})
const apiMethods = new RestApiGatewayMethods(this, "RestApiGatewayMethods", {
executePolices: [
functions.fooLambda.executionPolicy
functions.fooLambda.executionPolicy,
functions.createLambda.executionPolicy
],
restAPiGatewayRole: apiGateway.apiGatewayRole,
restApiGateway: apiGateway.apiGateway,
fooLambda: functions.fooLambda.function
fooLambda: functions.fooLambda.function,
createLambda: functions.createLambda.function,
pollLambda: functions.pollLambda.function,
})
const staticContentBucketOrigin = S3BucketOrigin.withOriginAccessControl(
staticContentBucket.bucket,
Expand All @@ -141,7 +149,7 @@ export class HackStack extends Stack {
apiGatewayRequestPolicy: apiGatewayRequestPolicy,
staticContentBucketOrigin: staticContentBucketOrigin,
})

// --- Distribution
const cloudfrontDistribution = new CloudfrontDistribution(this, "CloudfrontDistribution", {
serviceName: props.serviceName,
Expand Down Expand Up @@ -176,6 +184,7 @@ export class HackStack extends Stack {
]
})


// Outputs

// Exports
Expand Down
Loading
Loading