Skip to content

Commit

Permalink
[backend/frontend] Improve auditing to split file read and file downl…
Browse files Browse the repository at this point in the history
…oad (#5005)
  • Loading branch information
richard-julien committed Dec 2, 2023
1 parent d410e98 commit b535aa7
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,7 @@ const useSearchEntities = ({
'read',
'search',
'enrich',
'download',
'import',
'export',
'login',
Expand Down
31 changes: 30 additions & 1 deletion opencti-platform/opencti-graphql/src/database/file-storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ import conf, { booleanConf, ENABLED_FILE_INDEX_MANAGER, logApp } from '../config
import { now, sinceNowInMinutes } from '../utils/format';
import { DatabaseError, FunctionalError } from '../config/errors';
import { createWork, deleteWorkForFile, deleteWorkForSource, loadExportWorksAsProgressFiles } from '../domain/work';
import { buildPagination } from './utils';
import { buildPagination, isEmptyField, isNotEmptyField } from './utils';
import { connectorsForImport } from './repository';
import { pushToConnector } from './rabbitmq';
import { telemetry } from '../config/tracing';
import { elDeleteFilesByIds } from './file-search';
import { isAttachmentProcessorEnabled } from './engine';
import { internalLoadById } from './middleware-loader';
import { SYSTEM_USER } from '../utils/access';
import { buildContextDataForFile, publishUserAction } from '../listener/UserActionListener';

// Minio configuration
const clientEndpoint = conf.get('minio:endpoint');
Expand Down Expand Up @@ -204,6 +207,32 @@ const guessMimeType = (fileId) => {
}
return mimeType;
};

export const checkFileAccess = async (context, user, scope, loadedFile) => {
const { entity_id, filename } = loadedFile.metaData;
if (isEmptyField(entity_id)) {
return true;
}
const userInstancePromise = internalLoadById(context, user, entity_id);
const systemInstancePromise = internalLoadById(context, SYSTEM_USER, entity_id);
const [instance, systemInstance] = await Promise.all([userInstancePromise, systemInstancePromise]);
if (isEmptyField(instance)) {
if (isNotEmptyField(systemInstance)) {
const data = buildContextDataForFile(systemInstance, loadedFile.id, filename);
await publishUserAction({
user,
event_type: 'file',
event_scope: scope,
event_access: 'extended',
status: 'error',
context_data: data
});
}
throw FunctionalError('FILE_ACCESS', { id: entity_id, file: loadedFile.id });
}
return true;
};

export const isFileObjectExcluded = (id) => {
const fileName = getFileName(id);
return excludedFiles.map((e) => e.toLowerCase()).includes(fileName.toLowerCase());
Expand Down
23 changes: 21 additions & 2 deletions opencti-platform/opencti-graphql/src/http/httpPlatform.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import contentDisposition from 'content-disposition';
import { basePath, booleanConf, DEV_MODE, logApp, OPENCTI_SESSION } from '../config/conf';
import passport, { empty, isStrategyActivated, STRATEGY_CERT } from '../config/providers';
import { authenticateUser, authenticateUserFromRequest, loginFromProvider, userWithOrigin } from '../domain/user';
import { downloadFile, getFileContent, loadFile } from '../database/file-storage';
import { checkFileAccess, downloadFile, getFileContent, loadFile } from '../database/file-storage';
import createSseMiddleware from '../graphql/sseMiddleware';
import initTaxiiApi from './httpTaxii';
import initHttpRollingFeeds from './httpRollingFeed';
Expand Down Expand Up @@ -51,6 +51,19 @@ const extractRefererPathFromReq = (req) => {
return undefined;
};

const publishFileDownload = async (executeContext, auth, file) => {
const { filename, entity_id } = file.metaData;
const entity = entity_id ? await internalLoadById(executeContext, auth, entity_id) : undefined;
const data = buildContextDataForFile(entity, file.id, filename);
await publishUserAction({
user: auth,
event_type: 'file',
event_access: 'extended',
event_scope: 'download',
context_data: data
});
};

const publishFileRead = async (executeContext, auth, file) => {
const { filename, entity_id } = file.metaData;
const entity = entity_id ? await internalLoadById(executeContext, auth, entity_id) : undefined;
Expand Down Expand Up @@ -152,7 +165,9 @@ const createApp = async (app) => {
}
const { file } = req.params;
const data = await loadFile(auth, file);
await publishFileRead(executeContext, auth, data);
await checkFileAccess(executeContext, auth, 'download', data);
// If file is attach to a specific instance, we need to contr
await publishFileDownload(executeContext, auth, data);
const stream = await downloadFile(file);
res.attachment(file);
stream.pipe(res);
Expand All @@ -173,6 +188,7 @@ const createApp = async (app) => {
}
const { file } = req.params;
const data = await loadFile(auth, file);
await checkFileAccess(executeContext, auth, 'read', data);
await publishFileRead(executeContext, auth, data);
res.set('Content-disposition', contentDisposition(data.name, { type: 'inline' }));
res.set({ 'Content-Security-Policy': 'sandbox' });
Expand Down Expand Up @@ -202,6 +218,7 @@ const createApp = async (app) => {
}
const { file } = req.params;
const data = await loadFile(auth, file);
await checkFileAccess(executeContext, auth, 'read', data);
const { mimetype } = data.metaData;
if (mimetype === 'text/markdown') {
const markDownData = await getFileContent(file);
Expand Down Expand Up @@ -231,6 +248,8 @@ const createApp = async (app) => {
}
const { file } = req.params;
const data = await loadFile(auth, file);
await checkFileAccess(executeContext, auth, 'download', data);
await publishFileDownload(executeContext, auth, data);
const { filename } = data.metaData;
const archive = archiver.create('zip-encrypted', { zlib: { level: 8 }, encryptionMethod: 'aes256', password: nconf.get('app:artifact_zip_password') });
archive.append(await downloadFile(file), { name: `${filename}.zip` });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export interface UserFileActionContextData {
}
export interface UserFileAction extends BasicUserAction {
event_type: 'file'
event_scope: 'read' | 'create' | 'delete'
event_scope: 'read' | 'create' | 'delete' | 'download'
context_data: UserFileActionContextData
}
// endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,16 @@ const initActivityManager = () => {
}
}
if (action.event_type === 'file') {
const isFailAction = action.status === 'error';
const prefixMessage = isFailAction ? 'failure ' : '';
if (action.event_scope === 'read') {
const { file_name, entity_name } = action.context_data;
const message = `downloads from \`${entity_name}\` the file \`${file_name}\``;
const message = `${prefixMessage} reads from \`${entity_name}\` the file \`${file_name}\``;
await activityLogger(action, message);
}
if (action.event_scope === 'download') {
const { file_name, entity_name } = action.context_data;
const message = `${prefixMessage} downloads from \`${entity_name}\` the file \`${file_name}\``;
await activityLogger(action, message);
}
if (action.event_scope === 'create') {
Expand Down
2 changes: 1 addition & 1 deletion opencti-platform/opencti-graphql/src/resolvers/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const logResolvers = {
raw_data: (log, _, __) => JSON.stringify(log, null, 2),
context_uri: (log, _, __) => (log.context_data.id ? `/dashboard/id/${log.context_data.id}` : undefined),
event_status: (log, _, __) => log.event_status ?? 'success',
event_type: (log, _, __) => log.event_scope ?? log.event_type, // Retro compatibility
event_scope: (log, _, __) => log.event_scope ?? log.event_type, // Retro compatibility
},
// Backward compatibility
ContextData: {
Expand Down

0 comments on commit b535aa7

Please sign in to comment.