diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d80e09f7..0ae33661 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -32,7 +32,9 @@ "github.vscode-pull-request-github", "streetsidesoftware.code-spell-checker", "timonwong.shellcheck", - "github.vscode-github-actions" + "github.vscode-github-actions", + "dbaeumer.vscode-eslint", + "vitest.explorer" ], "settings": { "cSpell.words": [ diff --git a/.vscode/eps-cdk-utils.code-workspace b/.vscode/eps-cdk-utils.code-workspace index f79ac31c..2657005e 100644 --- a/.vscode/eps-cdk-utils.code-workspace +++ b/.vscode/eps-cdk-utils.code-workspace @@ -79,7 +79,10 @@ ".vscode" ], "eslint.useFlatConfig": true, - "eslint.format.enable": true + "eslint.format.enable": true, + "[typescript]": { + "editor.defaultFormatter": "dbaeumer.vscode-eslint" + } }, "extensions": { "recommendations": [ diff --git a/packages/cdkConstructs/src/constructs/PythonLambdaFunction.ts b/packages/cdkConstructs/src/constructs/PythonLambdaFunction.ts index 83c8a1b1..fab17a70 100644 --- a/packages/cdkConstructs/src/constructs/PythonLambdaFunction.ts +++ b/packages/cdkConstructs/src/constructs/PythonLambdaFunction.ts @@ -4,7 +4,8 @@ import { ManagedPolicy, PolicyStatement, Role, - IManagedPolicy + IManagedPolicy, + IRole } from "aws-cdk-lib/aws-iam" import { Architecture, @@ -18,6 +19,8 @@ import { import {join} from "node:path" import {createSharedLambdaResources} from "./lambdaSharedResources" import {addSuppressions} from "../utils/helpers" +import {IKey} from "aws-cdk-lib/aws-kms" +import {CfnDeliveryStream} from "aws-cdk-lib/aws-kinesisfirehose" export interface PythonLambdaFunctionProps { /** @@ -42,7 +45,7 @@ export interface PythonLambdaFunctionProps { /** * A map of environment variables to set for the lambda function. */ - readonly environmentVariables?: {[key: string]: string} + readonly environmentVariables?: { [key: string]: string } /** * Optional additional IAM policies to attach to role the lambda executes as. */ @@ -80,16 +83,45 @@ export interface PythonLambdaFunctionProps { * @default Architecture.X86_64 */ readonly architecture?: Architecture - /** - * Any files to exclude from the Lambda asset bundle. - * Defaults to these files - * "tests", - * "pytest.ini", - * ".vscode", - * "__pycache__", - * "*.pyc" - */ + /** + * Any files to exclude from the Lambda asset bundle. + * Defaults to these files + * "tests", + * "pytest.ini", + * ".vscode", + * "__pycache__", + * "*.pyc" + */ readonly excludeFromAsset?: Array + /** + * Optional KMS key for encrypting CloudWatch Logs. + * If not provided, the value is imported from account resources export. + */ + readonly cloudWatchLogsKmsKey?: IKey + /** + * Optional IAM policy for allowing CloudWatch to use the KMS key for encrypting logs. + * If not provided, the value is imported from account resources export. + */ + readonly cloudwatchEncryptionKMSPolicy?: IManagedPolicy + /** + * Optional firehose delivery stream for forwarding logs to Splunk. + * If not provided, the value is imported from account resources export. + */ + readonly splunkDeliveryStream?: CfnDeliveryStream + /** + * Optional IAM role for the subscription filter that forwards logs to Splunk. + * If not provided, the value is imported from account resources export. + */ + readonly splunkSubscriptionFilterRole?: IRole + /** + * Optional IAM policy for allowing lambdas to use Lambda Insights log groups and streams. + * If not provided, the value is imported from account resources export. + */ + readonly lambdaInsightsLogGroupPolicy?: IManagedPolicy + /** + * Whether to create a subscription filter on the Lambda log group to forward logs to Splunk. Defaults to true. + */ + readonly addSplunkSubscriptionFilter?: boolean } @@ -185,14 +217,26 @@ export class PythonLambdaFunction extends Construct { ".vscode", "__pycache__", "*.pyc" - ] + ], + cloudWatchLogsKmsKey, + cloudwatchEncryptionKMSPolicy, + splunkDeliveryStream, + splunkSubscriptionFilterRole, + lambdaInsightsLogGroupPolicy, + addSplunkSubscriptionFilter } = props const {logGroup, role, insightsLayer} = createSharedLambdaResources(this, { functionName, logRetentionInDays, additionalPolicies, - architecture + architecture, + cloudWatchLogsKmsKey, + cloudwatchEncryptionKMSPolicy, + splunkDeliveryStream, + splunkSubscriptionFilterRole, + lambdaInsightsLogGroupPolicy, + addSplunkSubscriptionFilter }) const layersToAdd = [insightsLayer] diff --git a/packages/cdkConstructs/src/constructs/TypescriptLambdaFunction.ts b/packages/cdkConstructs/src/constructs/TypescriptLambdaFunction.ts index ff8c5de7..32e5467f 100644 --- a/packages/cdkConstructs/src/constructs/TypescriptLambdaFunction.ts +++ b/packages/cdkConstructs/src/constructs/TypescriptLambdaFunction.ts @@ -1,6 +1,7 @@ import {Duration} from "aws-cdk-lib" import { IManagedPolicy, + IRole, ManagedPolicy, PolicyStatement, Role @@ -16,6 +17,8 @@ import {Construct} from "constructs" import {join} from "node:path" import {createSharedLambdaResources} from "./lambdaSharedResources" import {addSuppressions} from "../utils/helpers" +import {IKey} from "aws-cdk-lib/aws-kms" +import {CfnDeliveryStream} from "aws-cdk-lib/aws-kinesisfirehose" export interface TypescriptLambdaFunctionProps { /** @@ -84,6 +87,35 @@ export interface TypescriptLambdaFunctionProps { * @default Architecture.X86_64 */ readonly architecture?: Architecture + /** + * Optional KMS key for encrypting CloudWatch Logs. + * If not provided, the value is imported from account resources export. + */ + readonly cloudWatchLogsKmsKey?: IKey + /** + * Optional IAM policy for allowing CloudWatch to use the KMS key for encrypting logs. + * If not provided, the value is imported from account resources export. + */ + readonly cloudwatchEncryptionKMSPolicy?: IManagedPolicy + /** + * Optional firehose delivery stream for forwarding logs to Splunk. + * If not provided, the value is imported from account resources export. + */ + readonly splunkDeliveryStream?: CfnDeliveryStream + /** + * Optional IAM role for the subscription filter that forwards logs to Splunk. + * If not provided, the value is imported from account resources export. + */ + readonly splunkSubscriptionFilterRole?: IRole + /** + * Optional IAM policy for allowing lambdas to use Lambda Insights log groups and streams. + * If not provided, the value is imported from account resources export. + */ + readonly lambdaInsightsLogGroupPolicy?: IManagedPolicy + /** + * Whether to create a subscription filter on the Lambda log group to forward logs to Splunk. Defaults to true. + */ + readonly addSplunkSubscriptionFilter?: boolean } const getDefaultLambdaOptions = ( @@ -202,14 +234,26 @@ export class TypescriptLambdaFunction extends Construct { projectBaseDir, timeoutInSeconds = 50, runtime = Runtime.NODEJS_24_X, - architecture = Architecture.X86_64 + architecture = Architecture.X86_64, + cloudWatchLogsKmsKey, + cloudwatchEncryptionKMSPolicy, + splunkDeliveryStream, + splunkSubscriptionFilterRole, + lambdaInsightsLogGroupPolicy, + addSplunkSubscriptionFilter } = props const {logGroup, role, insightsLayer} = createSharedLambdaResources(this, { functionName, logRetentionInDays, additionalPolicies, - architecture + architecture, + cloudWatchLogsKmsKey, + cloudwatchEncryptionKMSPolicy, + splunkDeliveryStream, + splunkSubscriptionFilterRole, + lambdaInsightsLogGroupPolicy, + addSplunkSubscriptionFilter }) const lambdaFunction = new NodejsFunction(this, functionName, { diff --git a/packages/cdkConstructs/src/constructs/lambdaSharedResources.ts b/packages/cdkConstructs/src/constructs/lambdaSharedResources.ts index c85efc2e..20769cf8 100644 --- a/packages/cdkConstructs/src/constructs/lambdaSharedResources.ts +++ b/packages/cdkConstructs/src/constructs/lambdaSharedResources.ts @@ -1,11 +1,11 @@ import {Construct} from "constructs" import {Fn, RemovalPolicy} from "aws-cdk-lib" import {Architecture, ILayerVersion, LayerVersion} from "aws-cdk-lib/aws-lambda" -import {Key} from "aws-cdk-lib/aws-kms" -import {Stream} from "aws-cdk-lib/aws-kinesis" +import {IKey, Key} from "aws-cdk-lib/aws-kms" import {CfnLogGroup, CfnSubscriptionFilter, LogGroup} from "aws-cdk-lib/aws-logs" import { IManagedPolicy, + IRole, ManagedPolicy, PolicyStatement, Role, @@ -14,12 +14,20 @@ import { import {NagSuppressions} from "cdk-nag" import {LAMBDA_INSIGHTS_LAYER_ARNS} from "../config" import {addSuppressions} from "../utils/helpers" +import {CfnDeliveryStream} from "aws-cdk-lib/aws-kinesisfirehose" +import {Stream} from "aws-cdk-lib/aws-kinesis" export interface SharedLambdaResourceProps { readonly functionName: string readonly logRetentionInDays: number readonly additionalPolicies: Array readonly architecture: Architecture + readonly cloudWatchLogsKmsKey?: IKey + readonly cloudwatchEncryptionKMSPolicy?: IManagedPolicy + readonly splunkDeliveryStream?: CfnDeliveryStream + readonly splunkSubscriptionFilterRole?: IRole + readonly lambdaInsightsLogGroupPolicy?: IManagedPolicy + readonly addSplunkSubscriptionFilter?: boolean } export interface SharedLambdaResources { @@ -30,28 +38,24 @@ export interface SharedLambdaResources { export const createSharedLambdaResources = ( scope: Construct, - { + props: SharedLambdaResourceProps +): SharedLambdaResources => { + const { functionName, logRetentionInDays, additionalPolicies, - architecture - }: SharedLambdaResourceProps -): SharedLambdaResources => { - const cloudWatchLogsKmsKey = Key.fromKeyArn( - scope, "cloudWatchLogsKmsKey", Fn.importValue("account-resources:CloudwatchLogsKmsKeyArn")) - - const cloudwatchEncryptionKMSPolicy = ManagedPolicy.fromManagedPolicyArn( - scope, "cloudwatchEncryptionKMSPolicyArn", Fn.importValue("account-resources:CloudwatchEncryptionKMSPolicyArn")) - - const splunkDeliveryStream = Stream.fromStreamArn( - scope, "SplunkDeliveryStream", Fn.importValue("lambda-resources:SplunkDeliveryStream")) - - const splunkSubscriptionFilterRole = Role.fromRoleArn( - scope, "splunkSubscriptionFilterRole", Fn.importValue("lambda-resources:SplunkSubscriptionFilterRole")) - - const lambdaInsightsLogGroupPolicy = ManagedPolicy.fromManagedPolicyArn( - scope, "lambdaInsightsLogGroupPolicy", Fn.importValue("lambda-resources:LambdaInsightsLogGroupPolicy")) - + architecture, + cloudWatchLogsKmsKey = Key.fromKeyArn( + scope, "cloudWatchLogsKmsKey", Fn.importValue("account-resources:CloudwatchLogsKmsKeyArn")), + cloudwatchEncryptionKMSPolicy = ManagedPolicy.fromManagedPolicyArn( + scope, "cloudwatchEncryptionKMSPolicyArn", Fn.importValue("account-resources:CloudwatchEncryptionKMSPolicyArn")), + splunkDeliveryStream, + splunkSubscriptionFilterRole = Role.fromRoleArn( + scope, "splunkSubscriptionFilterRole", Fn.importValue("lambda-resources:SplunkSubscriptionFilterRole")), + lambdaInsightsLogGroupPolicy = ManagedPolicy.fromManagedPolicyArn( + scope, "lambdaInsightsLogGroupPolicy", Fn.importValue("lambda-resources:LambdaInsightsLogGroupPolicy")), + addSplunkSubscriptionFilter = true + } = props const insightsLambdaLayerArn = architecture === Architecture.ARM_64 ? LAMBDA_INSIGHTS_LAYER_ARNS.arm64 : LAMBDA_INSIGHTS_LAYER_ARNS.x64 @@ -68,12 +72,27 @@ export const createSharedLambdaResources = ( const cfnlogGroup = logGroup.node.defaultChild as CfnLogGroup addSuppressions([cfnlogGroup], ["CW_LOGGROUP_RETENTION_PERIOD_CHECK"]) - new CfnSubscriptionFilter(scope, "LambdaLogsSplunkSubscriptionFilter", { - destinationArn: splunkDeliveryStream.streamArn, - filterPattern: "", - logGroupName: logGroup.logGroupName, - roleArn: splunkSubscriptionFilterRole.roleArn - }) + if (addSplunkSubscriptionFilter) { + // This is in an if statement to ensure correct value is used + // importing and coercing to cfnDeliveryStream causes issues + if (splunkDeliveryStream) { + new CfnSubscriptionFilter(scope, "LambdaLogsSplunkSubscriptionFilter", { + destinationArn: splunkDeliveryStream.attrArn, + filterPattern: "", + logGroupName: logGroup.logGroupName, + roleArn: splunkSubscriptionFilterRole.roleArn + }) + } else { + const splunkDeliveryStreamImport = Stream.fromStreamArn( + scope, "SplunkDeliveryStream", Fn.importValue("lambda-resources:SplunkDeliveryStream")) + new CfnSubscriptionFilter(scope, "LambdaLogsSplunkSubscriptionFilter", { + destinationArn: splunkDeliveryStreamImport.streamArn, + filterPattern: "", + logGroupName: logGroup.logGroupName, + roleArn: splunkSubscriptionFilterRole.roleArn + }) + } + } const putLogsManagedPolicy = new ManagedPolicy(scope, "LambdaPutLogsManagedPolicy", { description: `write to ${functionName} logs`, diff --git a/packages/cdkConstructs/tests/constructs/pythonLambdaFunctionConstruct.test.ts b/packages/cdkConstructs/tests/constructs/pythonLambdaFunctionConstruct.test.ts index 8616b39d..7f618713 100644 --- a/packages/cdkConstructs/tests/constructs/pythonLambdaFunctionConstruct.test.ts +++ b/packages/cdkConstructs/tests/constructs/pythonLambdaFunctionConstruct.test.ts @@ -1,6 +1,11 @@ import {App, assertions, Stack} from "aws-cdk-lib" import {Template, Match} from "aws-cdk-lib/assertions" -import {ManagedPolicy, PolicyStatement, Role} from "aws-cdk-lib/aws-iam" +import { + ManagedPolicy, + PolicyStatement, + Role, + ServicePrincipal +} from "aws-cdk-lib/aws-iam" import {LogGroup} from "aws-cdk-lib/aws-logs" import { Architecture, @@ -17,6 +22,8 @@ import { } from "vitest" import {PythonLambdaFunction} from "../../src/constructs/PythonLambdaFunction" +import {CfnDeliveryStream} from "aws-cdk-lib/aws-kinesisfirehose" +import {Key} from "aws-cdk-lib/aws-kms" describe("pythonFunctionConstruct works correctly", () => { let stack: Stack @@ -374,3 +381,150 @@ describe("pythonFunctionConstruct works correctly with different architecture", }) }) }) + +describe("pythonFunctionConstruct works correctly with addSplunkSubscriptionFilter set to false", () => { + let stack: Stack + let app: App + let template: assertions.Template + + beforeAll(() => { + app = new App() + stack = new Stack(app, "pythonLambdaConstructStack") + new PythonLambdaFunction(stack, "dummyPythonFunction", { + functionName: "testPythonLambda", + projectBaseDir: resolve(__dirname, "../../../.."), + packageBasePath: "packages/cdkConstructs", + handler: "index.handler", + environmentVariables: {foo: "bar"}, + logRetentionInDays: 30, + logLevel: "DEBUG", + addSplunkSubscriptionFilter: false + }) + template = Template.fromStack(stack) + }) + + test("it does not have a subscription filter", () => { + template.resourceCountIs("AWS::Logs::SubscriptionFilter", 0) + }) +}) + +describe("pythonFunctionConstruct works correctly when not using imports", () => { + let stack: Stack + let app: App + let template: assertions.Template + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let lambdaLogGroupResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let cloudWatchLogsKmsKeyResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let lambdaInsightsLogGroupPolicyResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let cloudwatchEncryptionKMSPolicyResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let splunkSubscriptionFilterRoleResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let splunkDeliveryStreamResource: any + + beforeAll(() => { + app = new App() + stack = new Stack(app, "pythonLambdaConstructStack") + const cloudWatchLogsKmsKey = new Key(stack, "cloudWatchLogsKmsKey") + const cloudwatchEncryptionKMSPolicy = new ManagedPolicy(stack, "cloudwatchEncryptionKMSPolicy", { + description: "cloudwatch encryption KMS policy", + statements: [ + new PolicyStatement({ + actions: [ + "kms:Decrypt", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + resources: ["*"] + })] + }) + const splunkDeliveryStream = new CfnDeliveryStream(stack, "SplunkDeliveryStream", { + deliveryStreamName: "SplunkDeliveryStream", + s3DestinationConfiguration: { + bucketArn: "arn:aws:s3:::my-bucket", + roleArn: "arn:aws:iam::123456789012:role/my-role" + } + }) + const splunkSubscriptionFilterRole = new Role(stack, "SplunkSubscriptionFilterRole", { + assumedBy: new ServicePrincipal("logs.amazonaws.com") + }) + const lambdaInsightsLogGroupPolicy = new ManagedPolicy(stack, "LambdaInsightsLogGroupPolicy", { + description: "permissions to create log group and set retention policy for Lambda Insights", + statements: [ + new PolicyStatement({ + actions: [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + resources: [ + "*" + ] + }) + ] + }) + + const functionConstruct = new PythonLambdaFunction(stack, "dummyPythonFunction", { + functionName: "testPythonLambda", + projectBaseDir: resolve(__dirname, "../../../.."), + packageBasePath: "packages/cdkConstructs", + handler: "index.handler", + environmentVariables: {}, + logRetentionInDays: 30, + logLevel: "INFO", + cloudWatchLogsKmsKey: cloudWatchLogsKmsKey, + cloudwatchEncryptionKMSPolicy: cloudwatchEncryptionKMSPolicy, + splunkDeliveryStream: splunkDeliveryStream, + splunkSubscriptionFilterRole: splunkSubscriptionFilterRole, + lambdaInsightsLogGroupPolicy: lambdaInsightsLogGroupPolicy + }) + template = Template.fromStack(stack) + const lambdaLogGroup = functionConstruct.node.tryFindChild("LambdaLogGroup") as LogGroup + lambdaLogGroupResource = stack.resolve(lambdaLogGroup.logGroupName) + cloudWatchLogsKmsKeyResource = stack.resolve(cloudWatchLogsKmsKey.keyId) + lambdaInsightsLogGroupPolicyResource = stack.resolve(lambdaInsightsLogGroupPolicy.managedPolicyArn) + cloudwatchEncryptionKMSPolicyResource = stack.resolve(cloudwatchEncryptionKMSPolicy.managedPolicyArn) + splunkSubscriptionFilterRoleResource = stack.resolve(splunkSubscriptionFilterRole.roleName) + splunkDeliveryStreamResource = stack.resolve(splunkDeliveryStream.ref) + }) + + test("it has the correct cloudWatchLogsKmsKey", () => { + template.hasResourceProperties("AWS::Logs::LogGroup", { + LogGroupName: "/aws/lambda/testPythonLambda", + KmsKeyId: {"Fn::GetAtt": [cloudWatchLogsKmsKeyResource.Ref, "Arn"]}, + RetentionInDays: 30 + }) + }) + + test("it has the correct cloudwatchEncryptionKMSPolicy and lambdaInsightsLogGroupPolicy", () => { + template.hasResourceProperties("AWS::IAM::Role", { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": Match.arrayWith([ + {"Ref": lambdaInsightsLogGroupPolicyResource.Ref}, + {"Ref": cloudwatchEncryptionKMSPolicyResource.Ref} + ]) + }) + }) + test("it has the correct subscription filter", () => { + template.hasResourceProperties("AWS::Logs::SubscriptionFilter", { + LogGroupName: {"Ref": lambdaLogGroupResource.Ref}, + FilterPattern: "", + RoleArn: {"Fn::GetAtt": [splunkSubscriptionFilterRoleResource.Ref, "Arn"]}, + DestinationArn: {"Fn::GetAtt": [splunkDeliveryStreamResource.Ref, "Arn"]} + }) + }) +}) diff --git a/packages/cdkConstructs/tests/constructs/typescriptFunctionConstruct.test.ts b/packages/cdkConstructs/tests/constructs/typescriptFunctionConstruct.test.ts index a4e914fe..b640048c 100644 --- a/packages/cdkConstructs/tests/constructs/typescriptFunctionConstruct.test.ts +++ b/packages/cdkConstructs/tests/constructs/typescriptFunctionConstruct.test.ts @@ -1,5 +1,10 @@ import {App, assertions, Stack} from "aws-cdk-lib" -import {ManagedPolicy, PolicyStatement, Role} from "aws-cdk-lib/aws-iam" +import { + ManagedPolicy, + PolicyStatement, + Role, + ServicePrincipal +} from "aws-cdk-lib/aws-iam" import {LogGroup} from "aws-cdk-lib/aws-logs" import { Architecture, @@ -17,6 +22,8 @@ import { import {TypescriptLambdaFunction} from "../../src/constructs/TypescriptLambdaFunction" import {resolve} from "node:path" +import {Key} from "aws-cdk-lib/aws-kms" +import {CfnDeliveryStream} from "aws-cdk-lib/aws-kinesisfirehose" describe("TypescriptLambdaFunctionConstruct works correctly", () => { let stack: Stack @@ -364,3 +371,156 @@ describe("TypescriptLambdaFunctionConstruct works correctly with different archi }) }) }) + +describe("TypescriptLambdaFunctionConstruct works correctly with addSplunkSubscriptionFilter set to false", () => { + let stack: Stack + let app: App + let template: assertions.Template + + beforeAll(() => { + app = new App() + stack = new Stack(app, "typescriptLambdaConstructStack") + new TypescriptLambdaFunction(stack, "dummyTypescriptFunction", { + functionName: "testTypescriptLambda", + projectBaseDir: resolve(__dirname, "../../../.."), + packageBasePath: "packages/cdkConstructs", + entryPoint: "tests/src/dummyLambda.ts", + environmentVariables: {}, + logRetentionInDays: 30, + logLevel: "INFO", + architecture: Architecture.X86_64, + version: "1.0.0", + commitId: "abcd1234", + addSplunkSubscriptionFilter: false + }) + template = Template.fromStack(stack) + }) + + test("it does not have a subscription filter", () => { + template.resourceCountIs("AWS::Logs::SubscriptionFilter", 0) + }) +}) + +describe("TypescriptLambdaFunctionConstruct works correctly when not using imports", () => { + let stack: Stack + let app: App + let template: assertions.Template + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let lambdaLogGroupResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let cloudWatchLogsKmsKeyResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let lambdaInsightsLogGroupPolicyResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let cloudwatchEncryptionKMSPolicyResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let splunkSubscriptionFilterRoleResource: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let splunkDeliveryStreamResource: any + + beforeAll(() => { + app = new App() + stack = new Stack(app, "typescriptLambdaConstructStack") + const cloudWatchLogsKmsKey = new Key(stack, "cloudWatchLogsKmsKey") + const cloudwatchEncryptionKMSPolicy = new ManagedPolicy(stack, "cloudwatchEncryptionKMSPolicy", { + description: "cloudwatch encryption KMS policy", + statements: [ + new PolicyStatement({ + actions: [ + "kms:Decrypt", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + resources: ["*"] + })] + }) + const splunkDeliveryStream = new CfnDeliveryStream(stack, "SplunkDeliveryStream", { + deliveryStreamName: "SplunkDeliveryStream", + s3DestinationConfiguration: { + bucketArn: "arn:aws:s3:::my-bucket", + roleArn: "arn:aws:iam::123456789012:role/my-role" + } + }) + const splunkSubscriptionFilterRole = new Role(stack, "SplunkSubscriptionFilterRole", { + assumedBy: new ServicePrincipal("logs.amazonaws.com") + }) + const lambdaInsightsLogGroupPolicy = new ManagedPolicy(stack, "LambdaInsightsLogGroupPolicy", { + description: "permissions to create log group and set retention policy for Lambda Insights", + statements: [ + new PolicyStatement({ + actions: [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + resources: [ + "*" + ] + }) + ] + }) + + const functionConstruct = new TypescriptLambdaFunction(stack, "dummyTypescriptFunction", { + functionName: "testTypescriptLambda", + projectBaseDir: resolve(__dirname, "../../../.."), + packageBasePath: "packages/cdkConstructs", + entryPoint: "tests/src/dummyLambda.ts", + environmentVariables: {}, + logRetentionInDays: 30, + logLevel: "INFO", + architecture: Architecture.X86_64, + version: "1.0.0", + commitId: "abcd1234", + cloudWatchLogsKmsKey: cloudWatchLogsKmsKey, + cloudwatchEncryptionKMSPolicy: cloudwatchEncryptionKMSPolicy, + splunkDeliveryStream: splunkDeliveryStream, + splunkSubscriptionFilterRole: splunkSubscriptionFilterRole, + lambdaInsightsLogGroupPolicy: lambdaInsightsLogGroupPolicy + }) + template = Template.fromStack(stack) + const lambdaLogGroup = functionConstruct.node.tryFindChild("LambdaLogGroup") as LogGroup + lambdaLogGroupResource = stack.resolve(lambdaLogGroup.logGroupName) + cloudWatchLogsKmsKeyResource = stack.resolve(cloudWatchLogsKmsKey.keyId) + lambdaInsightsLogGroupPolicyResource = stack.resolve(lambdaInsightsLogGroupPolicy.managedPolicyArn) + cloudwatchEncryptionKMSPolicyResource = stack.resolve(cloudwatchEncryptionKMSPolicy.managedPolicyArn) + splunkSubscriptionFilterRoleResource = stack.resolve(splunkSubscriptionFilterRole.roleName) + splunkDeliveryStreamResource = stack.resolve(splunkDeliveryStream.ref) + }) + + test("it has the correct cloudWatchLogsKmsKey", () => { + template.hasResourceProperties("AWS::Logs::LogGroup", { + LogGroupName: "/aws/lambda/testTypescriptLambda", + KmsKeyId: {"Fn::GetAtt": [cloudWatchLogsKmsKeyResource.Ref, "Arn"]}, + RetentionInDays: 30 + }) + }) + + test("it has the correct cloudwatchEncryptionKMSPolicy and lambdaInsightsLogGroupPolicy", () => { + template.hasResourceProperties("AWS::IAM::Role", { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": Match.arrayWith([ + {"Ref": lambdaInsightsLogGroupPolicyResource.Ref}, + {"Ref": cloudwatchEncryptionKMSPolicyResource.Ref} + ]) + }) + }) + test("it has the correct subscription filter", () => { + template.hasResourceProperties("AWS::Logs::SubscriptionFilter", { + LogGroupName: {"Ref": lambdaLogGroupResource.Ref}, + FilterPattern: "", + RoleArn: {"Fn::GetAtt": [splunkSubscriptionFilterRoleResource.Ref, "Arn"]}, + DestinationArn: {"Fn::GetAtt": [splunkDeliveryStreamResource.Ref, "Arn"]} + }) + }) +})