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

File scanning connector to handle multiple AV tools #1514

Merged
merged 34 commits into from
Nov 1, 2024

Conversation

ARADDCC002
Copy link
Member

No description provided.

@ARADDCC002 ARADDCC002 marked this pull request as draft September 11, 2024 16:09
@ARADDCC002 ARADDCC002 marked this pull request as ready for review September 27, 2024 12:59
@ARADDCC002 ARADDCC002 changed the title wip for file scanning connector File scanning connector to handle multiple AV tools Oct 1, 2024
avScan: { state: ScanState.Error },
})
if (config.ui.avScanning.enabled) {
const fileScanConnectors = getFileScanningConnectors()
Copy link
Member

Choose a reason for hiding this comment

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

It would be good if this new connector type could have the same interface as other connectors and this could just be scanners.scan(file). Ideally we want to encapsulate the initialisation and the calling of each individual scanner inside the connector logic, not here in the service layer every time we want to use the scanners

}

async init() {
if (!av) {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this check is necessary because the init() function is only called to initialise the av scanner

try {
av = await new NodeClam().init({ clamdscan: config.avScanning.clamdscan })
} catch (error) {
throw ConfigurationError('Cannot retrieve Clam AV config', {
Copy link
Member

Choose a reason for hiding this comment

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

Have you tested what happens if the Clam AV service isn't running? I assume this throw would be called. If so, the error message needs updating

}

async scan(file: FileInterfaceDoc) {
const avStream = av.passthrough()
Copy link
Member

Choose a reason for hiding this comment

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

It's a bit of a pain but to be safe, you should probably check that av has been initialised at the start of functions like this when using the AV scanner and throw an error recommending that init() should be called first if av has not been assigned.

if (!file.avScan.find((result) => result.toolName === toolName)) {
const updatedAvScanArray = [...file.avScan, { toolName: toolName, state: ScanState.InProgress }]
file.avScan = updatedAvScanArray
await FileModel.updateOne(
Copy link
Member

Choose a reason for hiding this comment

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

In my opinion, these scanners should not be responsible for storing the scan information in the DB, they should just be an interface with a particular scanning and this method should take in a file stream and return a scan result object. Ther service layer should be responsible for getting the file stream and processing the scan results.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is a bit tricky as it's async and the service that calls these connectors already returns the response to the user. We'd need to call the scanners, wait for the results, store them, then return a 200 response to the user from the backend.

Copy link
Member

Choose a reason for hiding this comment

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

Just because we want a process to run asynchronously doesn't mean all the implementation needs to be in a single method. Use a .then() on the asynchronous call to provide the implementation for how we will process the results once the scanners are complete.

isInfected: { type: Boolean },
viruses: [{ type: String }],
},
avScan: [
Copy link
Member

Choose a reason for hiding this comment

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

Don't forget, this will require a migration. I don't think a migration was done when this field was originally added so this migration will have to account for when this field doesn't exist

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think it does but feel free to double check. From testing it looks like it will return single results as arrays anyway

Copy link
Member

Choose a reason for hiding this comment

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

From looking at this diff it looks like the previous type of avScan was an object and now avScan is an array? That's why I thought this would require a migration

Copy link
Member Author

Choose a reason for hiding this comment

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

It becomes an array of the same type of object as it was before and it seems to handle it automatically thankfully it seems

@@ -205,6 +204,10 @@ module.exports = {
audit: {
kind: 'silly',
},

fileScanners: {
kinds: ['clamAV'],
Copy link
Member

Choose a reason for hiding this comment

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

By default, we shouldn't have any scanners configured. This instead should be done in the docker_compose.cjs

})
}
})

Copy link
Member

Choose a reason for hiding this comment

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

I would recommend call init() on all the connectors in fileScanConnectors here or in each case where you add a new connector to the list. This way, you can pick up on configuration errors early on

}
})

const wrapper = new FileScanningWrapper(fileScanConnectors)
Copy link
Member

Choose a reason for hiding this comment

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

In the other index.ts files for the other connectors we also have a caching mechanism we'll probably also want here. Something that checks if wrapper has already been assigned and returns it if it has

}

async scan(file: FileInterfaceDoc): Promise<FileScanResult[]> {
await this.init()
Copy link
Member

Choose a reason for hiding this comment

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

See comment on index.ts about moving this to there

avScan: { state: ScanState.Error },
})
})
if (runFileScanners().info()) {
Copy link
Member

Choose a reason for hiding this comment

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

This isn't a major thing but could we get this to look more like the other connectors eg: const auth = await authorisation.file(user, model, file, FileAction.Upload) so this would become if (scanners.info()) {

export const getFilescanningInfo = [
bodyParser.json(),
async (req: Request, res: Response<GetFileScanningInfoResponse>) => {
return res.json({ scanners: runFileScanners().info() })
Copy link
Member

Choose a reason for hiding this comment

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

See comment on file.ts to make this be return res.json({ scanners: scanners.info() })

}

return file
}

export async function updateFileWithResults(file: FileInterface, results: FileScanResult[]) {
for (const result of results) {
Copy link
Member

Choose a reason for hiding this comment

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

I'd be interested to look to see if Mongo can handle this logic for you and add new elements to the array if a an element with a tool doesn't exist and update the existing element if a element with that tool does exist

await FileModel.updateOne(
{ _id: file._id },
{
$set: { avScan: updatedAvScanArray },
Copy link
Member

Choose a reason for hiding this comment

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

Here you should use $push and then you won't need to have updatedAvScanArray

} else if (
file.avScan.some(
(scanResult) =>
scanResult.state === ScanState.NotScanned ||
Copy link
Member

Choose a reason for hiding this comment

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

Can this not be scanResult.state !== ScanState.Complete?

Copy link
Member

@JR40159 JR40159 left a comment

Choose a reason for hiding this comment

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

When setting the scanners connectors to an empty array, the information still appears in the UI?

@@ -27,16 +34,16 @@ vi.mock('../../src/connectors/authorisation/index.js', async () => ({
default: authMock,
}))

// const fileScanConnectors: BaseFileScanningConnector[] = [new ClamAvFileScanningConnector()]
Copy link
Member

Choose a reason for hiding this comment

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

?

@@ -193,6 +192,10 @@ data:
audit: {
kind: '{{ .Values.connectors.audit.kind }}',
},

fileScanners: {
kinds: '{{ .Values.connectors.fileScanners.kinds }}',
Copy link
Member

Choose a reason for hiding this comment

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

This should be an array?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is just a reference though isn't it? I don't think we define if it's an array or not here

@ARADDCC002 ARADDCC002 merged commit 1692d87 into main Nov 1, 2024
15 checks passed
@ARADDCC012 ARADDCC012 deleted the feature/BAI-1458-add-file-scanning-connector branch November 5, 2024 12:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants