Skip to content

Commit

Permalink
feat: trigger virus scanner lambda
Browse files Browse the repository at this point in the history
  • Loading branch information
LinHuiqing committed Sep 19, 2023
1 parent e60f0ff commit 946b629
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ export const handleStorageSubmission = [
EncryptSubmissionMiddleware.validateStorageSubmissionParams,
EncryptSubmissionMiddleware.createFormsgAndRetrieveForm,
EncryptSubmissionMiddleware.checkNewBoundaryEnabled,
EncryptSubmissionMiddleware.checkAttachmentQuarantineKeys,
EncryptSubmissionMiddleware.scanAttachments,
EncryptSubmissionMiddleware.validateStorageSubmission,
EncryptSubmissionMiddleware.encryptSubmission,
submitEncryptModeForm,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,9 @@ export class FeatureDisabledError extends ApplicationError {
super(message)
}
}

export class VirusScanFailedError extends ApplicationError {
constructor(message = 'Virus scan failed. Please try again.') {
super(message)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { celebrate, Joi, Segments } from 'celebrate'
import { NextFunction } from 'express'
import { StatusCodes } from 'http-status-codes'
import { chain, omit } from 'lodash'
import { okAsync, ResultAsync } from 'neverthrow'

import { featureFlags } from '../../../../../shared/constants'
import {
Expand All @@ -25,7 +26,10 @@ import * as FormService from '../../form/form.service'
import ParsedResponsesObject from '../ParsedResponsesObject.class'
import { sharedSubmissionParams } from '../submission.constants'
import * as SubmissionService from '../submission.service'
import { isAttachmentResponse } from '../submission.utils'
import {
isAttachmentResponse,
isQuarantinedAttachmentResponse,
} from '../submission.utils'

import {
EncryptedPayloadExistsError,
Expand Down Expand Up @@ -183,7 +187,7 @@ export const checkNewBoundaryEnabled = async (
* Guardrail to prevent virus scanner code from being run if not enabled on frontend.
* TODO (FRM-1232): remove this guardrail when encryption boundary is shifted.
*/
export const checkAttachmentQuarantineKeys = async (
export const scanAttachments = async (
req: StorageSubmissionMiddlewareHandlerRequest,
res: Parameters<StorageSubmissionMiddlewareHandlerType>[1],
next: NextFunction,
Expand All @@ -208,25 +212,49 @@ export const checkAttachmentQuarantineKeys = async (
return next()
}

// // Step 2: If virus scanner is enabled, check if quarantine keys for attachments are present. Quarantine keys
// // should only be present if the virus scanner is enabled on the frontend.
// // If not, skip this middleware.

// let quarantineKeysPresent = false
// Step 2: If virus scanner is enabled, check if storage submission v2.1+. Storage submission v2.1 onwards
// should have virus scanning enabled. If not, skip this middleware.
// Note: Version number is sent by the frontend and should only be >=2.1 if virus scanning is enabled on the frontend.

// for (const response of req.body.responses) {
// if (isQuarantinedAttachmentResponse(response)) quarantineKeysPresent = true
// }
if (req.body.version < 2.1) return next()

// if (!quarantineKeysPresent) return next()

// At this point, virus scanner is enabled and quarantine keys are present. This means that both the FE and BE
// At this point, virus scanner is enabled and storage submission v2.1+. This means that both the FE and BE
// have virus scanning enabled.

// Step 3: Trigger lambda to scan attachments.
triggerVirusScanning('16ca3303-743a-4cec-ab20-3d10042d88ce')
const triggerLambdaResult = await ResultAsync.combine(
req.body.responses.map((response) => {
if (isQuarantinedAttachmentResponse(response)) {
return triggerVirusScanning(response.answer).map((data) => {
logger.info({
message: 'Successfully invoked lambda function',
meta: {
...logMeta,
responseMetadata: data?.$metadata,
},
})
return okAsync(true)
})
}
return okAsync(true)
}),
)

// If any of the lambda invocations failed, return an errored response.
if (triggerLambdaResult.isErr()) {
logger.error({
message: 'Error scanning attachments',
meta: logMeta,
error: triggerLambdaResult.error,
})

return res.status(StatusCodes.INTERNAL_SERVER_ERROR).send({
message:
'Something went wrong while scanning your attachments. Please try again.',
})
}

// Step 4: Retrieve attachments from the clean bucket.
// TODO(FRM-1302): Step 4: Retrieve attachments from the clean bucket.

return next()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import { PRESIGNED_ATTACHMENT_POST_EXPIRY_SECS } from './encrypt-submission.cons
import {
AttachmentSizeLimitExceededError,
InvalidFieldIdError,
VirusScanFailedError,
} from './encrypt-submission.errors'
import {
AttachmentMetadata,
Expand Down Expand Up @@ -580,28 +581,21 @@ export const getQuarantinePresignedPostData = (
}

export const triggerVirusScanning = (quarantineFileKey: string) => {
return AwsConfig.virusScannerLambda.invoke(
{
return ResultAsync.fromPromise(
AwsConfig.virusScannerLambda.invoke({
FunctionName: AwsConfig.virusScannerLambdaFunctionName,
Payload: JSON.stringify({ key: quarantineFileKey }),
},
(error, data) => {
if (error) {
logger.error({
message: 'Error invoking lambda function',
meta: {
action: 'triggerVirusScanning',
},
error,
})
}
logger.info({
message: 'Successfully invoked lambda function',
}),
(error) => {
logger.error({
message: 'Error encountered when invoking virus scanning lambda',
meta: {
action: 'triggerVirusScanning',
data,
},
error,
})

return new VirusScanFailedError()
},
)
}
16 changes: 15 additions & 1 deletion src/app/modules/submission/receiver/receiver.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,22 @@ export const receiveEmailSubmission: ControllerHandler<
export const receiveStorageSubmission: ControllerHandler<
unknown,
{ message: string },
{ responses: FieldResponse[] }
{ responses: FieldResponse[]; version: number }
> = async (req, res, next) => {
if (req.body.version >= 2.1) {
const logMeta = {
action: 'receiveSubmission',
...createReqMeta(req),
}

logger.info({
message: 'Skipping multipart receiver since storage submission v2.1+',
meta: logMeta,
})

return next()
}

return receiveSubmission(
req,
res,
Expand Down
5 changes: 1 addition & 4 deletions src/app/modules/submission/submission.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,7 @@ export const isAttachmentResponse = (
export const isQuarantinedAttachmentResponse = (
response: ParsedClearFormFieldResponse,
): response is ParsedClearAttachmentResponse => {
return (
response.fieldType === BasicField.Attachment &&
response.fileKey !== undefined
)
return response.fieldType === BasicField.Attachment
}

/**
Expand Down
1 change: 0 additions & 1 deletion src/types/api/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { AttachmentResponse, FieldResponse } from '../../../shared/types'
export type ParsedClearAttachmentResponse = AttachmentResponse & {
filename: string
content: Buffer
fileKey?: string
}

export type ParsedClearFormFieldResponse =
Expand Down

0 comments on commit 946b629

Please sign in to comment.