Skip to content

Commit

Permalink
fix(postgres-manager): failing unit test from cdk-nag (#128)
Browse files Browse the repository at this point in the history
  • Loading branch information
williamputraintan authored Mar 3, 2024
1 parent 4aa5716 commit d846224
Show file tree
Hide file tree
Showing 12 changed files with 112 additions and 64 deletions.
2 changes: 1 addition & 1 deletion config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
} from '../lib/workload/orcabus-stateless-stack';
import { Duration, aws_lambda, RemovalPolicy } from 'aws-cdk-lib';
import { EventSourceProps } from '../lib/workload/stateful/event_source/component';
import { DbAuthType } from '../lib/workload/stateless/postgres_manager/function/utils';
import { DbAuthType } from '../lib/workload/stateless/postgres_manager/function/type';

const regName = 'OrcaBusSchemaRegistry';
const eventBusName = 'OrcaBusMain';
Expand Down
15 changes: 9 additions & 6 deletions lib/workload/orcabus-stateless-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import { Filemanager } from './stateless/filemanager/deploy/lib/filemanager';
import { Queue } from 'aws-cdk-lib/aws-sqs';
import { Secret } from 'aws-cdk-lib/aws-secretsmanager';
import {
PostgresManager,
PostgresManagerStack,
PostgresManagerConfig,
} from './stateless/postgres_manager/construct/postgresManager';
} from './stateless/postgres_manager/deploy/postgres-manager-stack';

export interface OrcaBusStatelessConfig {
multiSchemaConstructProps: MultiSchemaConstructProps;
Expand Down Expand Up @@ -41,6 +41,10 @@ export interface FilemanagerDependencies {
export class OrcaBusStatelessStack extends cdk.Stack {
private vpc: IVpc;
private lambdaSecurityGroup: ISecurityGroup;

// microservice stacks
microserviceStackArray: cdk.Stack[] = [];

constructor(scope: Construct, id: string, props: cdk.StackProps & OrcaBusStatelessConfig) {
super(scope, id, props);

Expand All @@ -63,8 +67,7 @@ export class OrcaBusStatelessStack extends cdk.Stack {

// hook microservice construct components here
this.createSequenceRunManager();

this.createPostgresManager(props.postgresManagerConfig);
this.microserviceStackArray.push(this.createPostgresManager(props.postgresManagerConfig));

if (props.filemanagerDependencies) {
this.createFilemanager({
Expand All @@ -80,7 +83,7 @@ export class OrcaBusStatelessStack extends cdk.Stack {
}

private createPostgresManager(config: PostgresManagerConfig) {
new PostgresManager(this, 'PostgresManager', {
return new PostgresManagerStack(this, 'PostgresManager', {
...config,
vpc: this.vpc,
lambdaSecurityGroup: this.lambdaSecurityGroup,
Expand Down Expand Up @@ -114,7 +117,7 @@ export class OrcaBusStatelessStack extends cdk.Stack {
dependencies.databaseSecretName
);

new Filemanager(this, 'Filemanager', {
return new Filemanager(this, 'Filemanager', {
buckets: dependencies.eventSourceBuckets,
buildEnvironment: {},
databaseSecret,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Duration } from 'aws-cdk-lib';
import { Duration, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as nodejs from 'aws-cdk-lib/aws-lambda-nodejs';
Expand All @@ -7,7 +7,7 @@ import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as rds from 'aws-cdk-lib/aws-rds';
import * as ssm from 'aws-cdk-lib/aws-ssm';
import { DbAuthType, MicroserviceConfig } from '../function/utils';
import { MicroserviceConfig, DbAuthType } from '../function/type';

export type PostgresManagerConfig = {
masterSecretName: string;
Expand All @@ -16,13 +16,13 @@ export type PostgresManagerConfig = {
clusterResourceIdParameterName: string;
};

export type PostgresManagerStackProps = PostgresManagerConfig & {
export type PostgresManagerProps = PostgresManagerConfig & {
vpc: ec2.IVpc;
lambdaSecurityGroup: ec2.ISecurityGroup;
};

export class PostgresManager extends Construct {
constructor(scope: Construct, id: string, props: PostgresManagerStackProps) {
export class PostgresManagerStack extends Stack {
constructor(scope: Construct, id: string, props: StackProps & PostgresManagerProps) {
super(scope, id);

const { dbClusterIdentifier, microserviceDbConfig } = props;
Expand All @@ -33,12 +33,12 @@ export class PostgresManager extends Construct {
props.masterSecretName
);

const dbClusterResourceId = ssm.StringParameter.valueFromLookup(
const dbClusterResourceId = ssm.StringParameter.valueForStringParameter(
this,
props.clusterResourceIdParameterName
);

const rdsLambdaProps = {
const rdsLambdaProps : nodejs.NodejsFunctionProps = {
timeout: Duration.minutes(5),
depsLockFilePath: __dirname + '/../yarn.lock',
handler: 'handler',
Expand Down Expand Up @@ -98,7 +98,9 @@ export class PostgresManager extends Construct {
new iam.PolicyStatement({
actions: ['secretsmanager:CreateSecret', 'secretsmanager:TagResource'],
effect: iam.Effect.ALLOW,
resources: ['arn:aws:secretsmanager:ap-southeast-2:*:secret:*'],
resources: [
`arn:aws:secretsmanager:ap-southeast-2:${process.env.CDK_DEFAULT_ACCOUNT}:secret:*`,
],
}),
new iam.PolicyStatement({
actions: ['secretsmanager:GetRandomPassword'],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Client } from 'pg';
import {
EventType,
getMicroserviceConfig,
getMicroserviceName,
executeSqlWithLog,
getRdsMasterSecret,
} from './utils';
import { EventType } from './type';

export const handler = async (event: EventType) => {
const microserviceConfig = getMicroserviceConfig();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Client } from 'pg';
import { EventType } from './type';
import {
EventType,
getMicroserviceConfig,
getMicroserviceName,
executeSqlWithLog,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Client } from 'pg';
import {
EventType,
getMicroserviceConfig,
getMicroserviceName,
executeSqlWithLog,
getRdsMasterSecret,
DbAuthType,
} from './utils';
import { DbAuthType, EventType } from './type';

export const handler = async (event: EventType) => {
const microserviceConfig = getMicroserviceConfig();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import {
getMicroserviceName,
getMicroserviceConfig,
getRdsMasterSecret,
DbAuthType,
} from './utils';
import { DbAuthType } from './type';
import { getMicroserviceName, getMicroserviceConfig, getRdsMasterSecret } from './utils';
import {
SecretsManagerClient,
CreateSecretCommandInput,
Expand Down
16 changes: 16 additions & 0 deletions lib/workload/stateless/postgres_manager/function/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* There are 2 ways of connecting from microservice to db
*/
export enum DbAuthType {
RDS_IAM,
USERNAME_PASSWORD,
}

export type EventType = {
microserviceName: string;
};

export type MicroserviceConfig = {
name: string;
authType: DbAuthType;
}[];
18 changes: 1 addition & 17 deletions lib/workload/stateless/postgres_manager/function/utils.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,6 @@
import { Client } from 'pg';
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';

/**
* There are 2 ways of connecting from microservice to db
*/
export enum DbAuthType {
RDS_IAM,
USERNAME_PASSWORD,
}

export type EventType = {
microserviceName: string;
};

export type MicroserviceConfig = {
name: string;
authType: DbAuthType;
}[];
import { MicroserviceConfig, EventType } from './type';

/**
* get microservice config from lambda environment
Expand Down
2 changes: 1 addition & 1 deletion lib/workload/stateless/postgres_manager/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "lambda-with-rds",
"name": "postgres-manager",
"packageManager": "[email protected]",
"dependencies": {
"@aws-sdk/client-secrets-manager": "^3.515.0",
Expand Down
88 changes: 68 additions & 20 deletions test/stateless/stateless-deployment.test.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,94 @@
import { App, Aspects } from 'aws-cdk-lib';
import { App, Aspects, Stack } from 'aws-cdk-lib';
import { Annotations, Match } from 'aws-cdk-lib/assertions';
import { SynthesisMessage } from 'aws-cdk-lib/cx-api';
import { AwsSolutionsChecks } from 'cdk-nag';
import { AwsSolutionsChecks, NagSuppressions } from 'cdk-nag';
import { OrcaBusStatelessStack } from '../../lib/workload/orcabus-stateless-stack';
import { getEnvironmentConfig } from '../../config/constants';

function synthesisMessageToString(sm: SynthesisMessage): string {
return `${sm.entry.data} [${sm.id}]`;
}

// Picking prod environment to test as it contain the sensitive data
const config = getEnvironmentConfig('prod')!;

describe('cdk-nag-stateless-stack', () => {
let stack: OrcaBusStatelessStack;
let app: App;

beforeAll(() => {
app = new App({});
stack = new OrcaBusStatelessStack(app, 'TestStack', {
env: {
account: '12345678',
region: 'ap-southeast-2',
},
...config.stackProps.orcaBusStatelessConfig,
});
Aspects.of(stack).add(new AwsSolutionsChecks());

// Suppressions (if any)
// ...
const app: App = new App({});
const stack: OrcaBusStatelessStack = new OrcaBusStatelessStack(app, 'TestStack', {
env: {
account: '12345678',
region: 'ap-southeast-2',
},
...config.stackProps.orcaBusStatelessConfig,
});

test('cdk-nag AwsSolutions Pack errors', () => {
// stateless stack cdk-nag test
Aspects.of(stack).add(new AwsSolutionsChecks());
test(`OrcaBusStatelessStack: cdk-nag AwsSolutions Pack errors`, () => {
const errors = Annotations.fromStack(stack)
.findError('*', Match.stringLikeRegexp('AwsSolutions-.*'))
.map(synthesisMessageToString);
expect(errors).toHaveLength(0);
});

test('cdk-nag AwsSolutions Pack warnings', () => {
test(`OrcaBusStatelessStack: cdk-nag AwsSolutions Pack warnings`, () => {
const warnings = Annotations.fromStack(stack)
.findWarning('*', Match.stringLikeRegexp('AwsSolutions-.*'))
.map(synthesisMessageToString);
expect(warnings).toHaveLength(0);
});

// microservice cdk-nag test
for (const ms_stack of stack.microserviceStackArray) {
const stackId = ms_stack.node.id;

Aspects.of(ms_stack).add(new AwsSolutionsChecks());

applyNagSuppression(stackId, ms_stack);

test(`${stackId}: cdk-nag AwsSolutions Pack errors`, () => {
const errors = Annotations.fromStack(ms_stack)
.findError('*', Match.stringLikeRegexp('AwsSolutions-.*'))
.map(synthesisMessageToString);
expect(errors).toHaveLength(0);
});

test(`${stackId}: cdk-nag AwsSolutions Pack warnings`, () => {
const warnings = Annotations.fromStack(ms_stack)
.findWarning('*', Match.stringLikeRegexp('AwsSolutions-.*'))
.map(synthesisMessageToString);
expect(warnings).toHaveLength(0);
});
}
});

/**
* apply nag suppression according to the relevant stackId
* @param stackId the stackId
* @param stack
*/
function applyNagSuppression(stackId: string, stack: Stack) {
switch (stackId) {
case 'PostgresManager':
NagSuppressions.addStackSuppressions(stack, [
{ id: 'AwsSolutions-IAM4', reason: 'allow to use AWS managed policy' },
]);

// suppress by resource
NagSuppressions.addResourceSuppressionsByPath(
stack,
`/TestStack/PostgresManager/CreateUserPassPostgresLambda/ServiceRole/DefaultPolicy/Resource`,
[
{
id: 'AwsSolutions-IAM5',
reason:
"'*' is required for secretsmanager:GetRandomPassword and new SM ARN will contain random character",
},
]
);
break;

default:
break;
}
}
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@
"exclude": [
"node_modules",
"cdk.out",
"lib/workload/stateless/metadata_manager"
"lib/workload/stateless/**",
]
}

0 comments on commit d846224

Please sign in to comment.