Skip to content

Commit

Permalink
Merge pull request #15 from MarcioMeier/feat/log-subscriber-ssm-param…
Browse files Browse the repository at this point in the history
…eter

feat: add ssm param as input to log group subscriber lambda
  • Loading branch information
MarcioMeier authored May 8, 2024
2 parents 6565beb + 89feee6 commit 9057700
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 24 deletions.
15 changes: 12 additions & 3 deletions docs/base-nodejs-lambda.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Based on [AWS Construct NodeJsFunction](https://docs.aws.amazon.com/cdk/api/v2/d
- explicit private VPC configuration (see props.network)
- source code path standardization to "[basePath]/[lambdaEventType]/[lambdaName]/index.ts" (can be overwritten by explicit props.entry)
- custom CA support for HTTP calls (NodeJS NODE_EXTRA_CA_CERTS). See props.extraCaPubCert
- option to subscribe an Lambda Arn to the log group related to the Lambda function. See props.logGroupSubscriberLambdaArn
- option to subscribe an Lambda Arn to the log group related to the Lambda function. See props.logGroupSubscriberArn
- adds environment STAGE to Lambda. See props.stage

### Usage
Expand Down Expand Up @@ -60,8 +60,17 @@ Based on [AWS Construct NodeJsFunction](https://docs.aws.amazon.com/cdk/api/v2/d

// register an external Lambda to receive all Cloudwatch log events
// created by this Lambda (used to forward logs to Datadog, Splunk etc)
lambdaConfig.logGroupSubscriberLambdaArn =
'arn:aws:lambda:eu-west-1:012345678:function:datadogForwarder';
lambdaConfig.logGroupSubscriberArn = {
type: LogGroupSubscriberArnType.Arn,
value: 'arn:aws:lambda:eu-west-1:012345678:function:datadogForwarder',
};

// You can also provide an AWS Systems Manager Parameter Store name that points
// to the Arn of the Lambda function that will subscribe to the log group
lambdaConfig.logGroupSubscriberArn = {
type: LogGroupSubscriberArnType.Ssm,
value: 'datadog-forwarder-lambda-arn',
};

// instantiate Lambda construct
const func = new BaseNodeJsFunction(stack, 'test-lambda', lambdaConfig);
Expand Down
18 changes: 13 additions & 5 deletions examples/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions examples/src/lambda/cdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
BaseNodeJsProps,
EventType,
vpcFromConfig,
LogGroupSubscriberArnType,
} from 'cdk-practical-constructs';
import { Construct } from 'constructs';

Expand Down Expand Up @@ -38,8 +39,10 @@ export const addLambdaGetTest = (scope: Construct): void => {
customSG.addIngressRule(Peer.ipv4('9.9.9.9/32'), Port.allTraffic(), 'allow ingress');
customSG.addEgressRule(Peer.ipv4('8.8.8.8/32'), Port.allTraffic(), 'allow egress');
lambdaConfig.securityGroups = [customSG];
lambdaConfig.logGroupSubscriberLambdaArn =
'arn:aws:lambda:eu-west-1:012345678:function:tstLogging';
lambdaConfig.logGroupSubscriberArn = {
type: LogGroupSubscriberArnType.Arn,
value: 'arn:aws:lambda:eu-west-1:012345678:function:tstLogging',
};

const func = new BaseNodeJsFunction(scope, 'getTest', lambdaConfig);
if (!func.defaultSecurityGroup) throw new Error('defaultSecurityGroup should be defined');
Expand Down
51 changes: 48 additions & 3 deletions lib/src/lambda/lambda-base.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { Template } from 'aws-cdk-lib/assertions';
import { Peer, Port, SecurityGroup } from 'aws-cdk-lib/aws-ec2';
import { Schedule } from 'aws-cdk-lib/aws-applicationautoscaling';
import { RetentionDays } from 'aws-cdk-lib/aws-logs';
import { StringParameter } from 'aws-cdk-lib/aws-ssm';

import { vpcFromConfig } from '../utils';

import { BaseNodeJsProps, EventType } from './types';
import { BaseNodeJsProps, EventType, LogGroupSubscriberArnType } from './types';
import { BaseNodeJsFunction } from './lambda-base';

describe('lambda-base', () => {
Expand Down Expand Up @@ -45,8 +46,10 @@ describe('lambda-base', () => {
customSG.addIngressRule(Peer.ipv4('9.9.9.9/32'), Port.allTraffic(), 'allow ingress');
customSG.addEgressRule(Peer.ipv4('8.8.8.8/32'), Port.allTraffic(), 'allow egress');
lambdaConfig.securityGroups = [customSG];
lambdaConfig.logGroupSubscriberLambdaArn =
'arn:aws:lambda:eu-west-1:012345678:function:tstLogging';
lambdaConfig.logGroupSubscriberArn = {
type: LogGroupSubscriberArnType.Arn,
value: 'arn:aws:lambda:eu-west-1:012345678:function:tstLogging',
};

const func = new BaseNodeJsFunction(stack, 'test-lambda', lambdaConfig);
if (!func.defaultSecurityGroup) throw new Error('defaultSecurityGroup should be defined');
Expand Down Expand Up @@ -110,6 +113,8 @@ describe('lambda-base', () => {
LogGroupName: {
Ref: 'testlambdadefaultloggroup43FBE067',
},
FilterName: 'all',
FilterPattern: '',
});

template.hasResourceProperties('AWS::Lambda::Permission', {
Expand All @@ -119,6 +124,46 @@ describe('lambda-base', () => {
});
});

it('should allow ssm log group subscriptions', async () => {
const app = new App();
const stack = new Stack(app);

const lambdaConfig: BaseNodeJsProps = {
stage: 'dev',
eventType: EventType.Http,
baseCodePath: 'src/apigateway/__tests__',
logGroupSubscriberArn: {
type: LogGroupSubscriberArnType.Ssm,
value: 'log-forwarder-lambda-arn',
},
};

// eslint-disable-next-line no-new
new StringParameter(stack, 'log-forwarder-lambda', {
parameterName: 'log-forwarder-lambda-arn',
description: 'Cloudwatch log forwarder Lambda ARN',
stringValue: 'arn:aws:lambda:eu-west-1:012345678:function:tstLoggerForwarder',
});

const func = new BaseNodeJsFunction(stack, 'test-lambda', lambdaConfig);
expect(func).toBeDefined();

// execute synth and test results
const template = Template.fromStack(stack);
// console.log(JSON.stringify(template.toJSON(), null, 2));

template.hasResourceProperties('AWS::Logs::SubscriptionFilter', {
DestinationArn: {
Ref: 'SsmParameterValuelogforwarderlambdaarnC96584B6F00A464EAD1953AFF4B05118Parameter',
},
LogGroupName: {
Ref: 'testlambdadefaultloggroup43FBE067',
},
FilterName: 'all',
FilterPattern: '',
});
});

it('no vpc declaration', async () => {
const app = new App();
const stack = new Stack(app);
Expand Down
21 changes: 13 additions & 8 deletions lib/src/lambda/lambda-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ import {
} from 'aws-cdk-lib/aws-applicationautoscaling';
import { ServicePrincipal } from 'aws-cdk-lib/aws-iam';
import { RemovalPolicy } from 'aws-cdk-lib';
import { StringParameter } from 'aws-cdk-lib/aws-ssm';

import { vpcFromConfig } from '../utils';

import { BaseNodeJsProps, LambdaConfig } from './types';
import { BaseNodeJsProps, LambdaConfig, LogGroupSubscriberArnType } from './types';

// CDK L2 constructs
// Docs: https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs.NodejsFunction.html#entry
Expand All @@ -33,7 +34,7 @@ import { BaseNodeJsProps, LambdaConfig } from './types';
* - allowAllOutbound is false by default. Use allowOutboundTo to specify hosts
* - source code path standardization to "[basePath]/[lambdaEventType]/[lambdaName]/index.ts" (can be overwritten by explicit props.entry)
* - custom CA support for HTTP calls (NodeJS NODE_EXTRA_CA_CERTS). See props.extraCaPubCert
* - option to subscribe an Lambda Arn to the log group related to the Lambda function. See props.logGroupSubscriberLambdaArn
* - option to subscribe an Lambda Arn to the log group related to the Lambda function. See props.logGroupSubscriberArn
* - adds environment STAGE to Lambda. See props.stage
*/
export class BaseNodeJsFunction extends Construct {
Expand Down Expand Up @@ -136,10 +137,8 @@ export const getPropsWithDefaults = (
// eslint-disable-next-line prefer-destructuring
createDefaultLogGroup = props.createDefaultLogGroup;
}
if (!createDefaultLogGroup && props.logGroupSubscriberLambdaArn) {
throw new Error(
`'logGroupSubscriberLambdaArn' cannot be used if 'createDefaultLogGroup' is false`,
);
if (!createDefaultLogGroup && props.logGroupSubscriberArn) {
throw new Error(`'logGroupSubscriberArn' cannot be used if 'createDefaultLogGroup' is false`);
}

let { entry } = props;
Expand Down Expand Up @@ -255,14 +254,20 @@ const addLogSubscriber = (
nodeJsFunction: NodejsFunction,
props: LambdaConfig,
): void => {
if (!props.logGroupSubscriberLambdaArn) {
if (!props.logGroupSubscriberArn) {
return;
}

const functionArn =
props.logGroupSubscriberArn.type === LogGroupSubscriberArnType.Arn
? props.logGroupSubscriberArn.value
: StringParameter.valueForStringParameter(scope, props.logGroupSubscriberArn.value);

const logGroupFuncSubscriber = Function.fromFunctionAttributes(
nodeJsFunction,
'logGroupFuncSubscriber',
{
functionArn: props.logGroupSubscriberLambdaArn,
functionArn,
// https://aleksdaranutsa.medium.com/aws-cdk-cloudwatch-logs-subscription-f4de1f9a52bf
sameEnvironment: true,
},
Expand Down
21 changes: 19 additions & 2 deletions lib/src/lambda/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,34 @@ export type LambdaConfig = Omit<
*/
logGroupRemovalPolicy?: RemovalPolicy;
/**
* Register this lambda Arn as a subscriber of the default log group.
* Register a Lambda as a subscriber of the default log group
* @default none
*/
logGroupSubscriberLambdaArn?: string;
logGroupSubscriberArn?: LogGroupSubscriberArn;
/**
* Create an alias named "live" that points to the latest version of this function
* @defaults true
*/
createLiveAlias?: boolean;
};

/**
* Log group subscriber configuration
* It will create a `AWS::Logs::SubscriptionFilter` resource
* This resource will trigger the configured function with all the logs generated in the deployed function
*/
export type LogGroupSubscriberArn = {
type: LogGroupSubscriberArnType;
value: string;
};

export enum LogGroupSubscriberArnType {
/** The Arn of the Lambda function that will subscribe to the log group */
Arn = 'arn',
/** The AWS Systems Manager Parameter Store name that points to the Arn of the Lambda function that will subscribe to the log group */
Ssm = 'ssm',
}

export enum EventType {
Cloudwatch = 'cloudwatch',
Http = 'http',
Expand Down
2 changes: 1 addition & 1 deletion lib/src/wso2/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type Wso2LambdaConfig = Pick<
| 'securityGroups'
| 'extraCaPubCert'
| 'network'
| 'logGroupSubscriberLambdaArn'
| 'logGroupSubscriberArn'
| 'logGroupRetention'
| 'logGroupRemovalPolicy'
>;
Expand Down

0 comments on commit 9057700

Please sign in to comment.