Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(lib-dynamodb): fix use of log filters in conjunction with DynamDBDocumentClient #4249

Merged
merged 2 commits into from
Dec 5, 2022
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class AnyCommand extends DynamoDBDocumentClientCommand<{}, {}, {}, {}, {}> {
protected readonly clientCommand = {
middlewareStack: {
argCaptor: this.argCaptor,
add(fn, config) {
addRelativeTo(fn, config) {
this.argCaptor.push([fn, config]);
},
},
Expand All @@ -41,7 +41,8 @@ describe("DynamoDBDocumentClientCommand", () => {
expect(options).toEqual({
name: "DocumentMarshall",
override: true,
step: "initialize",
relation: "before",
toMiddleware: "serializerMiddleware",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isn't strictly necessary since the serializer in in the serialize stage and this was in the initialize stage, but more explicit

});
}
{
Expand All @@ -50,7 +51,8 @@ describe("DynamoDBDocumentClientCommand", () => {
expect(options).toEqual({
name: "DocumentUnmarshall",
override: true,
step: "deserialize",
relation: "before",
toMiddleware: "deserializerMiddleware",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

safer to be explicit about this, but it was already in this order

});
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
DeserializeHandler,
DeserializeHandlerArguments,
DeserializeHandlerOutput,
HandlerExecutionContext,
InitializeHandler,
InitializeHandlerArguments,
InitializeHandlerOutput,
Expand All @@ -29,35 +30,64 @@ export abstract class DynamoDBDocumentClientCommand<

public abstract middlewareStack: MiddlewareStack<Input | BaseInput, Output | BaseOutput>;

private static defaultLogFilterOverrides = {
overrideInputFilterSensitiveLog(...args: any[]) {},
overrideOutputFilterSensitiveLog(...args: any[]) {},
};

protected addMarshallingMiddleware(configuration: DynamoDBDocumentClientResolvedConfig): void {
const { marshallOptions, unmarshallOptions } = configuration.translateConfig || {};

this.clientCommand.middlewareStack.add(
(next: InitializeHandler<Input | BaseInput, Output | BaseOutput>) =>
this.clientCommand.middlewareStack.addRelativeTo(
(next: InitializeHandler<Input | BaseInput, Output | BaseOutput>, context: HandlerExecutionContext) =>
async (
args: InitializeHandlerArguments<Input | BaseInput>
): Promise<InitializeHandlerOutput<Output | BaseOutput>> => {
args.input = marshallInput(this.input, this.inputKeyNodes, marshallOptions);
context.dynamoDbDocumentClientOptions =
context.dynamoDbDocumentClientOptions || DynamoDBDocumentClientCommand.defaultLogFilterOverrides;

const input = args.input;
context.dynamoDbDocumentClientOptions.overrideInputFilterSensitiveLog = () => {
return context.inputFilterSensitiveLog?.(input);
};
return next(args);
},
{
name: "DocumentMarshall",
step: "initialize",
relation: "before",
toMiddleware: "serializerMiddleware",
override: true,
}
);
this.clientCommand.middlewareStack.add(
(next: DeserializeHandler<Input | BaseInput, Output | BaseOutput>) =>
this.clientCommand.middlewareStack.addRelativeTo(
(next: DeserializeHandler<Input | BaseInput, Output | BaseOutput>, context: HandlerExecutionContext) =>
async (
args: DeserializeHandlerArguments<Input | BaseInput>
): Promise<DeserializeHandlerOutput<Output | BaseOutput>> => {
const deserialized = await next(args);

/**
* The original filter function is based on the shape of the
* base DynamoDB type, whereas the returned output will be
* unmarshalled. Therefore the filter log needs to be modified
* to act on the original output structure.
*/
const output = deserialized.output;
context.dynamoDbDocumentClientOptions =
context.dynamoDbDocumentClientOptions || DynamoDBDocumentClientCommand.defaultLogFilterOverrides;

context.dynamoDbDocumentClientOptions.overrideOutputFilterSensitiveLog = () => {
return context.outputFilterSensitiveLog?.(output);
};

deserialized.output = unmarshallOutput(deserialized.output, this.outputKeyNodes, unmarshallOptions);
return deserialized;
},
{
name: "DocumentUnmarshall",
step: "deserialize",
relation: "before",
toMiddleware: "deserializerMiddleware",
override: true,
}
);
Expand Down
51 changes: 50 additions & 1 deletion packages/middleware-logger/src/loggerMiddleware.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Logger, MiddlewareStack } from "@aws-sdk/types";
import { HandlerExecutionContext, Logger, MiddlewareStack } from "@aws-sdk/types";

import { getLoggerPlugin, loggerMiddleware, loggerMiddlewareOptions } from "./loggerMiddleware";

Expand Down Expand Up @@ -114,6 +114,55 @@ describe("loggerMiddleware", () => {
});
});

it("should use override log filters for DynamoDBDocumentClient if present", async () => {
mockNext.mockResolvedValueOnce(mockResponse);

const logger = { info: jest.fn() } as unknown as Logger;
const clientName = "mockClientName";
const commandName = "mockCommandName";

const mockInputLog = { inputKey: "inputKey", inputSensitiveKey: "SENSITIVE_VALUE" };
const inputFilterSensitiveLog = jest.fn().mockReturnValueOnce(mockInputLog);
const mockOutputLog = { outputKey: "outputKey", outputSensitiveKey: "SENSITIVE_VALUE" };
const outputFilterSensitiveLog = jest.fn().mockReturnValueOnce(mockOutputLog);

const context: HandlerExecutionContext = {
logger,
clientName,
commandName,
dynamoDbDocumentClientOptions: {
overrideInputFilterSensitiveLog: inputFilterSensitiveLog,
overrideOutputFilterSensitiveLog: outputFilterSensitiveLog,
},
inputFilterSensitiveLog() {
throw new Error("should not be called");
},
outputFilterSensitiveLog() {
throw new Error("should not be called");
},
};

const response = await loggerMiddleware()(mockNext, context)(mockArgs);
expect(mockNext).toHaveBeenCalledTimes(1);
expect(response).toStrictEqual(mockResponse);

expect(inputFilterSensitiveLog).toHaveBeenCalledTimes(1);
expect(inputFilterSensitiveLog).toHaveBeenCalledWith(mockArgs.input);

const { $metadata, ...outputWithoutMetadata } = mockOutput;
expect(outputFilterSensitiveLog).toHaveBeenCalledTimes(1);
expect(outputFilterSensitiveLog).toHaveBeenCalledWith(outputWithoutMetadata);

expect(logger.info).toHaveBeenCalledTimes(1);
expect(logger.info).toHaveBeenCalledWith({
clientName,
commandName,
input: mockInputLog,
output: mockOutputLog,
metadata: $metadata,
});
});

it("header x-amzn-request-id as requestId if x-amzn-requestid is not present", async () => {
const requestIdBackup = "requestIdBackup";
const customResponse = {
Expand Down
17 changes: 13 additions & 4 deletions packages/middleware-logger/src/loggerMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,30 @@ export const loggerMiddleware =
context: HandlerExecutionContext
): InitializeHandler<any, Output> =>
async (args: InitializeHandlerArguments<any>): Promise<InitializeHandlerOutput<Output>> => {
const { clientName, commandName, inputFilterSensitiveLog, logger, outputFilterSensitiveLog } = context;

const response = await next(args);
const {
clientName,
commandName,
logger,
inputFilterSensitiveLog,
outputFilterSensitiveLog,
dynamoDbDocumentClientOptions = {},
} = context;

const { overrideInputFilterSensitiveLog, overrideOutputFilterSensitiveLog } = dynamoDbDocumentClientOptions;

if (!logger) {
return response;
}

if (typeof logger.info === "function") {
const { $metadata, ...outputWithoutMetadata } = response.output;

logger.info({
clientName,
commandName,
input: inputFilterSensitiveLog(args.input),
output: outputFilterSensitiveLog(outputWithoutMetadata),
input: (overrideInputFilterSensitiveLog ?? inputFilterSensitiveLog)(args.input),
output: (overrideOutputFilterSensitiveLog ?? outputFilterSensitiveLog)(outputWithoutMetadata),
metadata: $metadata,
});
}
Expand Down
8 changes: 8 additions & 0 deletions packages/types/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,14 @@ export interface HandlerExecutionContext {
*/
authSchemes?: AuthScheme[];

/**
* Used by DynamoDbDocumentClient.
*/
dynamoDbDocumentClientOptions?: Partial<{
overrideInputFilterSensitiveLog(...args: any[]): string | void;
overrideOutputFilterSensitiveLog(...args: any[]): string | void;
}>;

[key: string]: any;
}

Expand Down