From 4446f265cb11d9ed634a04487ceaf3294c7e4e07 Mon Sep 17 00:00:00 2001 From: Philipp Kunz Date: Mon, 3 Feb 2025 13:55:15 +0100 Subject: [PATCH] fix(clamav.manager): Improve log handling and add timeout for log reception in ClamAV manager tests --- changelog.md | 7 ++ package.json | 3 +- pnpm-lock.yaml | 10 +++ readme.md | 125 ++++++++++++++++++++++++++++++++--- test/test.clamav.manager.ts | 50 +++++++++----- ts/00_commitinfo_data.ts | 2 +- ts/classes.clamav.manager.ts | 7 ++ 7 files changed, 175 insertions(+), 29 deletions(-) diff --git a/changelog.md b/changelog.md index e607144..2ca100a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## 2025-02-03 - 1.1.1 - fix(clamav.manager) +Improve log handling and add timeout for log reception in ClamAV manager tests + +- Refined the log receiving mechanism in ClamAV manager tests to use promises for better control over log receipt timing. +- Introduced a timeout mechanism in the log receiving test case to avoid indefinite waiting. +- Fixed the test case setup to accurately reflect log receipt and database information verification. + ## 2025-02-03 - 1.1.0 - feat(ClamAvService) Add ClamAV Manager with Docker container management capabilities. diff --git a/package.json b/package.json index 28095cc..e4dadd0 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "@git.zone/tsrun": "^1.2.46", "@git.zone/tstest": "^1.0.44", "@push.rocks/tapbundle": "^5.0.15", - "@types/node": "^20.8.7" + "@types/node": "^20.8.7", + "typescript": "^5.7.3" }, "dependencies": { "@push.rocks/smartfile": "^11.1.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6edd114..5048cbd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,6 +39,9 @@ importers: '@types/node': specifier: ^20.8.7 version: 20.17.12 + typescript: + specifier: ^5.7.3 + version: 5.7.3 packages: @@ -3935,6 +3938,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + engines: {node: '>=14.17'} + hasBin: true + uglify-js@3.19.3: resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} engines: {node: '>=0.8.0'} @@ -9602,6 +9610,8 @@ snapshots: typescript@5.6.3: {} + typescript@5.7.3: {} + uglify-js@3.19.3: {} uint8array-extras@1.4.0: {} diff --git a/readme.md b/readme.md index aa81205..8c2d8c1 100644 --- a/readme.md +++ b/readme.md @@ -1,28 +1,72 @@ # @push.rocks/smartantivirus -A package for performing antivirus testing, especially suitable for use with ClamAV. +A package for performing antivirus testing with ClamAV, featuring both direct daemon communication and Docker container management. + +## Features + +- **Docker Integration**: Automatically manages ClamAV containers for easy setup and testing +- **Real-time Logging**: Captures and processes ClamAV logs with type-safe event handling +- **Database Management**: Supports automatic database updates and version tracking +- **Flexible Scanning**: Scan strings, buffers, and files for malware +- **Health Monitoring**: Built-in service readiness checks and connection verification ## Install -Installing `@push.rocks/smartantivirus` is straightforward. You'll need Node.js and npm installed on your machine to get started. Once they are ready, you can add the `@push.rocks/smartantivirus` package to your project by running the following command: +Installing `@push.rocks/smartantivirus` is straightforward. You'll need Node.js and npm installed on your machine: ```bash npm install @push.rocks/smartantivirus ``` -This will add the package to your project's dependencies and allow you to integrate antivirus scanning capabilities directly into your application. +### Prerequisites + +- Node.js and npm +- Docker (for container-based usage) +- ClamAV daemon (for direct daemon usage) ## Usage -The `@push.rocks/smartantivirus` package provides tools to easily integrate antivirus scanning capabilities into your Node.js application by interfacing with the ClamAV daemon. Below is a comprehensive guide on how to use the features of this library. +The package provides two main ways to use ClamAV: -### Setting Up the ClamAV Daemon +1. **Docker-based Usage** (Recommended): Uses `ClamAVManager` to automatically handle container lifecycle +2. **Direct Daemon Usage**: Uses `ClamAvService` to communicate with an existing ClamAV daemon -Before using this package, make sure you have ClamAV installed and running on your system. You can find installation instructions for various operating systems on the [ClamAV official website](https://www.clamav.net/documents/installing-clamav). +Below is a comprehensive guide on how to use both approaches. -After installing ClamAV, start the ClamAV daemon (`clamd`). Make sure it is configured to listen on a port accessible to your Node.js application. You can configure this in the `clamd.conf` file, typically located in `/etc/clamav/clamd.conf`. +### Docker-based Usage with ClamAVManager -### Basic Usage +The `ClamAVManager` class provides a high-level interface for managing ClamAV in Docker containers: + +```typescript +import { ClamAVManager } from '@push.rocks/smartantivirus'; + +async function main() { + // Create a new manager instance + const manager = new ClamAVManager(); + + // Start the ClamAV container + await manager.startContainer(); + + // Listen for log events + manager.on('log', (event) => { + console.log(`[ClamAV ${event.type}] ${event.message}`); + }); + + // Get database information + const dbInfo = await manager.getDatabaseInfo(); + console.log('Database Info:', dbInfo); + + // Update virus definitions + await manager.updateDatabase(); + + // When done, stop the container + await manager.stopContainer(); +} + +main().catch(console.error); +``` + +### Direct Daemon Usage with ClamAvService The primary interface provided by the package is the `ClamAvService` class. It allows you to scan data in memory or verify the connection to the ClamAV daemon. @@ -89,11 +133,24 @@ scanBufferExample(); ### Error Handling and Debugging -The methods of `ClamAvService` throw errors if there are issues with communication or processing data. Wrap your code in try-catch blocks and use appropriate logging to handle errors gracefully. +Both `ClamAVManager` and `ClamAvService` provide comprehensive error handling: ```typescript try { - const scanResult = await clamService.scanString('Some suspicious string...'); + // Using ClamAVManager + const manager = new ClamAVManager(); + await manager.startContainer(); + + // Listen for errors in logs + manager.on('log', (event) => { + if (event.type === 'error') { + console.error(`ClamAV Error: ${event.message}`); + } + }); + + // Using ClamAvService + const service = new ClamAvService(); + const scanResult = await service.scanString('Some suspicious string...'); console.log(`Infection Status: ${scanResult.isInfected ? 'Infected' : 'Clean'}`); if (scanResult.isInfected) { console.log(`Reason: ${scanResult.reason}`); @@ -115,6 +172,52 @@ The tests include creating and utilizing a `ClamAvService` instance and attempts ### Advanced Usage and Integration -Beyond scanning strings and buffers, you can implement additional advanced use cases based on your specific application needs, such as integrating into web services or automating file scans in cloud environments. Consider building upon provided functionalities and adapting them to meet the requirements of your application architecture. +#### Container Configuration + +The `ClamAVManager` supports customizing the Docker container: + +```typescript +const manager = new ClamAVManager(); + +// Container properties are configurable +console.log(manager.containerName); // 'clamav-daemon' +console.log(manager.port); // 3310 +``` + +#### Log Management + +Access and process ClamAV logs: + +```typescript +const manager = new ClamAVManager(); + +// Get all logs +const logs = manager.getLogs(); + +// Filter logs by type +const errorLogs = logs.filter(log => log.type === 'error'); +const updateLogs = logs.filter(log => log.type === 'update'); +``` + +#### Health Checks + +Monitor ClamAV service health: + +```typescript +const manager = new ClamAVManager(); + +// Service automatically checks readiness during initialization +await manager.startContainer(); // Includes readiness checks + +// Get database status +const dbInfo = await manager.getDatabaseInfo(); +console.log('Database Version:', dbInfo); +``` + +You can build upon these functionalities to implement advanced use cases such as: +- Automated virus scanning in CI/CD pipelines +- Real-time file monitoring in web applications +- Cloud-based malware detection services +- Integration with security information and event management (SIEM) systems With the help of Node.js worker threads or external task queues like RabbitMQ, you can distribute scanning tasks efficiently within high-traffic environments. diff --git a/test/test.clamav.manager.ts b/test/test.clamav.manager.ts index 33347dc..faf02e5 100644 --- a/test/test.clamav.manager.ts +++ b/test/test.clamav.manager.ts @@ -1,7 +1,9 @@ import { expect, tap } from '../ts/plugins.js'; -import { type ClamAVLogEvent, ClamAVManager } from '../ts/classes.clamav.manager.js'; +import type { ClamAVLogEvent } from '../ts/classes.clamav.manager.js'; import { setupClamAV, cleanupClamAV, getManager } from './helpers/clamav.helper.js'; +type ClamAVManager = Awaited>; + let manager: ClamAVManager; tap.test('setup', async () => { @@ -10,31 +12,47 @@ tap.test('setup', async () => { }); tap.test('should have initialized container and receive logs', async () => { - let logReceived = false; - - // Add event listener for logs - manager.on('log', (event: ClamAVLogEvent) => { - console.log(`[Test] Received log event: ${event.type} - ${event.message}`); - logReceived = true; + // Create a promise that resolves when we receive a log + const logPromise = new Promise((resolve) => { + // First check if we already have logs + const existingLogs = manager.getLogs(); + if (existingLogs.length > 0) { + console.log('[Test] Found existing logs:', existingLogs.map(log => `${log.type}: ${log.message}`).join('\n')); + resolve(); + return; + } + + // If no existing logs, wait for new ones + const handler = (event: ClamAVLogEvent) => { + console.log(`[Test] Received log event: ${event.type} - ${event.message}`); + manager.removeListener('log', handler); + resolve(); + }; + manager.on('log', handler); }); - // Wait for logs - const maxWaitTime = 5000; - const startTime = Date.now(); - - while (!logReceived && Date.now() - startTime < maxWaitTime) { - await new Promise(resolve => setTimeout(resolve, 100)); + // Wait for logs with timeout + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error('Timeout waiting for logs')), 30000); + }); + + try { + await Promise.race([logPromise, timeoutPromise]); + } catch (error) { + console.error('Error waiting for logs:', error); + throw error; } - expect(logReceived).toBeTruthy('No logs received within timeout period'); + console.log('Log received check passed'); // Verify container is running by checking if we can get database info try { const dbInfo = await manager.getDatabaseInfo(); - expect(dbInfo).toBeTruthy('Container should be running and able to get database info'); + expect(dbInfo).toBeTruthy(); + console.log('Database info check passed'); } catch (error) { console.error('Error getting database info:', error); - expect.fail('Failed to get database info - container may not be fully initialized'); + throw new Error('Failed to get database info - container may not be fully initialized'); } }); diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 5164342..a772e61 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartantivirus', - version: '1.1.0', + version: '1.1.1', description: 'A Node.js package for integrating antivirus scanning capabilities using ClamAV, allowing in-memory file and data scanning.' } diff --git a/ts/classes.clamav.manager.ts b/ts/classes.clamav.manager.ts index d064a84..d437d3a 100644 --- a/ts/classes.clamav.manager.ts +++ b/ts/classes.clamav.manager.ts @@ -11,11 +11,16 @@ export class ClamAVManager extends EventEmitter { private containerName = 'clamav-daemon'; private imageTag = 'clamav/clamav:latest'; private port = 3310; + private logs: ClamAVLogEvent[] = []; constructor() { super(); } + public getLogs(): ClamAVLogEvent[] { + return this.logs; + } + /** * Start the ClamAV container if it's not already running */ @@ -157,6 +162,7 @@ export class ClamAVManager extends EventEmitter { type: this.determineLogType(line) }; + this.logs.push(event); this.emit('log', event); console.log(`[ClamAV ${event.type}] ${event.message}`); }); @@ -169,6 +175,7 @@ export class ClamAVManager extends EventEmitter { type: 'error' }; + this.logs.push(event); this.emit('log', event); console.error(`[ClamAV error] ${event.message}`); });