diff --git a/backend/internal/nginx-openappsec.js b/backend/internal/nginx-openappsec.js
index a8806156..1520fbd2 100755
--- a/backend/internal/nginx-openappsec.js
+++ b/backend/internal/nginx-openappsec.js
@@ -1,3 +1,6 @@
+const util = require('util');
+const execPromise = util.promisify(require('child_process').exec);
+const { exec } = require('child_process');
const _ = require('lodash');
const fs = require('fs');
const logger = require('../logger').nginx;
@@ -100,7 +103,24 @@ const internalNginxOpenappsec = {
(err) => {
logger.error('Error generating openappsec config:', err);
return Promise.reject(err);
+ })
+ .then(() => {
+ // Return the notifyPolicyUpdate promise chain
+ // notify openappsec to apply the policy
+ return internalNginxOpenappsec.notifyPolicyUpdate().catch((errorMessage) => {
+ console.error('Error:', errorMessage);
+ const errorMessageForUI = `Error: Policy couldn’t be applied, open-appsec-agent container is not responding.
+ Check if open-appec-agent container is running, then apply open-appsec Configuration
+ again by clicking here:
+
Settings -> open-appsec Advanced -> Save Settings`;
+
+ return Promise.reject(new Error(errorMessageForUI));
});
+ })
+ .catch((err) => {
+ logger.error('Error generating openappsec config:', err);
+ throw err; // Propagate the error to the caller
+ });
},
/**
@@ -122,9 +142,22 @@ const internalNginxOpenappsec = {
internalNginxOpenappsec.removeMatchingNodes(openappsecConfig, pattern);
fs.writeFileSync(configFilePath, yaml.dump(openappsecConfig));
})
- .catch(err => {
+ .then(() => {
+ // Return the notifyPolicyUpdate promise chain
+ // notify openappsec to apply the policy
+ return internalNginxOpenappsec.notifyPolicyUpdate().catch((errorMessage) => {
+ console.error('---Error:', errorMessage);
+ const errorMessageForUI = `Error: Policy couldn’t be applied, open-appsec-agent container is not responding.
+ Check if open-appec-agent container is running, then apply open-appsec Configuration
+ again by clicking here:
+
Settings -> open-appsec Advanced -> Save Settings`;
+
+ return Promise.reject(new Error(errorMessageForUI));
+ });
+ })
+ .catch((err) => {
logger.error('Error deleting openappsec config:', err);
- return Promise.reject(err);
+ throw err; // Propagate the error to the caller
});
},
@@ -180,6 +213,38 @@ const internalNginxOpenappsec = {
}
},
+ notifyPolicyUpdate: async function() {
+ if (!constants.USE_NOTIFY_POLICY) {
+ console.log('USE_NOTIFY_POLICY is false');
+ return;
+ }
+ let ports = constants.PORTS;
+ console.log(`Notifying openappsec to apply the policy on ports ${ports}`);
+ let lastError = null;
+
+ for (let port of ports) {
+ try {
+ const command = `curl -s -o /dev/null -w "%{http_code}" ${constants.HOSTURL}:${port}/openappsec/apply-policy`;
+ console.log(`command: ${command}`);
+ let { stdout } = await execPromise(command);
+ if (stdout === '200') {
+ console.log(`Policy applied successfully on port ${port}`);
+ return;
+ } else {
+ console.log(`Policy Unexpected response code: ${stdout}`);
+ lastError = new Error(`Unexpected response code: ${stdout}`);
+ }
+ } catch (error) {
+ console.log(`Error notifying openappsec to apply the policy on port ${port}: ${error.message}`);
+ lastError = error;
+ }
+ }
+
+ if (lastError) {
+ throw lastError;
+ }
+ },
+
/**
* Recursively removes nodes from a JavaScript object based on a pattern.
*
diff --git a/backend/internal/openappsec-log.js b/backend/internal/openappsec-log.js
index a695826a..dfdf19e5 100755
--- a/backend/internal/openappsec-log.js
+++ b/backend/internal/openappsec-log.js
@@ -6,84 +6,134 @@ const { APPSEC_LOG_DIR } = require('../lib/constants');
const internalOpenappsecLog = {
- /**
- * All logs
- *
- * @param {Access} access
- * @param {Array} [expand]
- * @param {String} [search_query]
- * @returns {Promise}
- */
- getAll: (access, expand, search_query) => {
+ countTotalLines: async function (directoryPath) {
+ const files = await fs.promises.readdir(directoryPath);
+ const logFiles = files.filter(file => path.extname(file).startsWith('.log'));
+
+ let totalLineCount = 0;
+
+ for (const file of logFiles) {
+ const filePath = path.join(directoryPath, file);
+
+ // Read only the first line of the file
+ const readStream = fs.createReadStream(filePath);
+ const rl = readline.createInterface({ input: readStream });
+ const firstLine = await new Promise(resolve => {
+ rl.on('line', line => {
+ rl.close();
+ resolve(line);
+ });
+ });
+
+ // Check if the first line is a non-data line
+ try {
+ JSON.parse(firstLine);
+ } catch (err) {
+ continue; // Skip this file if the first line is a non-data line
+ }
+
+ // If the first line is a data line, read the rest of the file
+ const content = await fs.promises.readFile(filePath, 'utf8');
+ const lines = content.split('\n');
+ totalLineCount += lines.length;
+ }
+
+ return totalLineCount;
+ },
+
+ processFile: async function (filePath) {
+ const content = await fs.promises.readFile(filePath, 'utf8');
+ const lines = content.split('\n');
+ const dataLines = [];
+
+ for (const line of lines) {
+ try {
+ const json = JSON.parse(line);
+ const groupName = path.basename(filePath, path.extname(filePath));
+ const wrappedObject = {
+ source: groupName,
+ meta: json,
+ serviceName: json.eventSource.serviceName,
+ eventPriority: json.eventPriority,
+ eventSeverity: json.eventSeverity,
+ eventLevel: json.eventLevel,
+ eventTime: json.eventTime,
+ eventName: json.eventName
+ };
+ dataLines.push(wrappedObject);
+ } catch (err) {
+ // Ignore lines that don't contain JSON data
+ }
+ }
+
+ return dataLines;
+ },
+
+
+ getAll: function (access, expand, search_query) {
return access.can('auditlog:list')
- .then(() => {
-
+ .then(async () => {
const directoryPath = APPSEC_LOG_DIR;
-
- const readdir = util.promisify(fs.readdir);
- const readFile = util.promisify(fs.readFile);
+ const files = await fs.promises.readdir(directoryPath);
+ const logFiles = files.filter(file => path.extname(file).startsWith('.log'));
- async function listLogFiles(dir) {
- const files = await readdir(dir);
- const logFiles = files.filter(file => path.extname(file).startsWith('.log'));
+ // Sort the logFiles array
+ logFiles.sort((a, b) => {
+ const baseA = path.basename(a, path.extname(a));
+ const baseB = path.basename(b, path.extname(b));
+ return baseA.localeCompare(baseB, undefined, { numeric: true, sensitivity: 'base' });
+ });
- const sortedLogFiles = logFiles.sort((a, b) => {
- const baseA = path.basename(a, path.extname(a));
- const baseB = path.basename(b, path.extname(b));
-
- if (baseA < baseB) return -1;
- if (baseA > baseB) return 1;
-
- return path.extname(a).localeCompare(path.extname(b));
- });
-
- const groupedFiles = sortedLogFiles.reduce((groups, file) => {
- const fileName = path.basename(file, path.extname(file));
- if (!groups[fileName]) {
- groups[fileName] = [];
- }
- groups[fileName].push(file);
- return groups;
- }, {});
-
- const wrappedObjects = [];
-
- for (const [groupName, files] of Object.entries(groupedFiles)) {
- for (const file of files) {
- try {
- const content = await readFile(path.join(dir, file), 'utf8');
- const lines = content.split('\n');
- for (const line of lines) {
- try {
- const json = JSON.parse(line);
- const wrappedObject = {
- source: groupName,
- meta: json,
- serviceName: json.eventSource.serviceName,
- eventPriority: json.eventPriority,
- eventSeverity: json.eventSeverity,
- eventLevel: json.eventLevel,
- eventTime: json.eventTime,
- eventName: json.eventName
- };
- wrappedObjects.push(wrappedObject);
- } catch (err) {
- // Ignore lines that don't contain JSON data
- }
- }
- } catch (err) {
- console.error(`Failed to read file ${file}: ${err.message}`);
- }
- }
- }
- wrappedObjects.sort((a, b) => new Date(b.eventTime) - new Date(a.eventTime));
- return wrappedObjects;
+ const wrappedObjects = [];
+ for (const file of logFiles) {
+ const filePath = path.join(directoryPath, file);
+ const dataLines = await this.processFile(filePath);
+ wrappedObjects.push(...dataLines);
}
- let groupedFiles = listLogFiles(directoryPath).catch(console.error);
- return groupedFiles;
+ return wrappedObjects;
});
- }
+ },
+
+ getPage: function (access, expand, search_query, page, perPage) {
+ return access.can('auditlog:list')
+ .then(async () => {
+ const directoryPath = APPSEC_LOG_DIR;
+ let totalDataLines = await this.countTotalLines(directoryPath);
+
+ const files = await fs.promises.readdir(directoryPath);
+ const logFiles = files.filter(file => path.extname(file).startsWith('.log'));
+
+ // Sort the logFiles array
+ logFiles.sort((a, b) => {
+ const baseA = path.basename(a, path.extname(a));
+ const baseB = path.basename(b, path.extname(b));
+ return baseA.localeCompare(baseB, undefined, { numeric: true, sensitivity: 'base' });
+ });
+
+ const wrappedObjects = [];
+ let lineCount = 0;
+ let start = (page - 1) * perPage;
+ let end = page * perPage;
+
+ for (const file of logFiles) {
+ if (lineCount >= end) {
+ break;
+ }
+
+ const filePath = path.join(directoryPath, file);
+ const dataLines = await this.processFile(filePath);
+ const pageDataLines = dataLines.slice(start - lineCount, end - lineCount);
+ wrappedObjects.push(...pageDataLines);
+ lineCount += pageDataLines.length;
+ }
+
+ return {
+ data: wrappedObjects,
+ totalDataLines: totalDataLines,
+ };
+ });
+ },
};
module.exports = internalOpenappsecLog;
diff --git a/backend/internal/proxy-host.js b/backend/internal/proxy-host.js
index a83413ca..f75607a4 100644
--- a/backend/internal/proxy-host.js
+++ b/backend/internal/proxy-host.js
@@ -95,9 +95,14 @@ const internalProxyHost = {
});
})
.then(row => {
- internalNginxOpenappsec.generateConfig(access, row, data)
- return row;
- })
+ return internalNginxOpenappsec.generateConfig(access, row, data)
+ .then(() => {
+ return row;
+ })
+ .catch((err) => {
+ throw new error.ConfigurationError(err.message);
+ });
+ })
.then((row) => {
// Audit log
data.meta = _.assign({}, data.meta || {}, row.meta);
@@ -174,10 +179,14 @@ const internalProxyHost = {
}
})
.then(row => {
- internalNginxOpenappsec.generateConfig(access, row, data);
- // internalNginxOpenappsec.updateConfig(row, data)
- return row;
- })
+ return internalNginxOpenappsec.generateConfig(access, row, data)
+ .then(() => {
+ return row;
+ })
+ .catch((err) => {
+ throw new error.ConfigurationError(err.message);
+ });
+ })
.then((row) => {
// Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here.
data = _.assign({}, {
@@ -316,7 +325,11 @@ const internalProxyHost = {
})
.then(() => {
// Delete openappsec config
- internalNginxOpenappsec.deleteConfig(access, row);
+ return internalNginxOpenappsec.deleteConfig(access, row)
+ .catch((err) => {
+ throw new error.ConfigurationError(err.message);
+ });
+
})
.then(() => {
// Delete Nginx Config
diff --git a/backend/lib/constants.js b/backend/lib/constants.js
index d41a6343..7b961865 100755
--- a/backend/lib/constants.js
+++ b/backend/lib/constants.js
@@ -2,4 +2,8 @@ module.exports = {
APPSEC_CONFIG_FILE_NAME: 'local_policy.yaml',
APPSEC_EXT_DIR: '/ext/appsec',
APPSEC_LOG_DIR: '/ext/appsec-logs',
+ USE_NOTIFY_POLICY: true,
+ PORTS: [7777, 7778],
+ HOSTURL: 'http://127.0.0.1',
+ POLICY_PATH: '/etc/cp/conf/local_policy.yaml',
};
\ No newline at end of file
diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml
index a8c68e4d..ce659441 100644
--- a/docker/docker-compose.dev.yml
+++ b/docker/docker-compose.dev.yml
@@ -33,7 +33,8 @@ services:
volumes:
- npm_data:/data
- le_data:/etc/letsencrypt
- - ../localconfig:/ext/appsec
+ - ../open-appsec-agent-deployment/localconfig:/ext/appsec
+ - ../open-appsec-agent-deployment/logs:/ext/appsec-logs
- ../backend:/app
- ../frontend:/app/frontend
- ../global:/app/global
diff --git a/frontend/js/app/api.js b/frontend/js/app/api.js
index 2c198bf5..6829745a 100644
--- a/frontend/js/app/api.js
+++ b/frontend/js/app/api.js
@@ -112,7 +112,7 @@ function makeExpansionString(expand) {
* @param {String} [query]
* @returns {Promise}
*/
-function getAllObjects(path, expand, query) {
+function getAllObjects(path, expand, query, page, perPage) {
let params = [];
if (typeof expand === 'object' && expand !== null && expand.length) {
@@ -123,7 +123,13 @@ function getAllObjects(path, expand, query) {
params.push('query=' + query);
}
- return fetch('get', path + (params.length ? '?' + params.join('&') : ''));
+ if (page && perPage) {
+ params.push('page=' + page);
+ params.push('perPage=' + perPage);
+ }
+
+ let url = path + (params.length ? '?' + params.join('&') : '');
+ return fetch('get', url);
}
function FileUpload(path, fd) {
@@ -724,7 +730,7 @@ module.exports = {
*/
getAll: function (expand, query) {
return getAllObjects('openappsec-log', expand, query);
- }
+ },
},
Reports: {
diff --git a/frontend/js/app/controller.js b/frontend/js/app/controller.js
index eb9677bb..3bb6a94d 100644
--- a/frontend/js/app/controller.js
+++ b/frontend/js/app/controller.js
@@ -410,12 +410,18 @@ module.exports = {
/**
* openappsec Log
*/
- showOpenappsecLog: function () {
+ showOpenappsecLogPage: function (page) {
+ page = parseInt(page) || 1;
let controller = this;
if (Cache.User.isAdmin()) {
require(['./main', './openappsec-log/main'], (App, View) => {
- controller.navigate('/openappsec-log');
- App.UI.showAppContent(new View());
+ controller.navigate('/openappsec-log/page/' + page);
+
+ // Show the view with the data
+ App.UI.showAppContent(new View({
+ page: page,
+ perPage: 50
+ }));
});
} else {
this.showDashboard();
diff --git a/frontend/js/app/openappsec-log/list-all/item.ejs b/frontend/js/app/openappsec-log/list-all/item.ejs
new file mode 100644
index 00000000..087ea989
--- /dev/null
+++ b/frontend/js/app/openappsec-log/list-all/item.ejs
@@ -0,0 +1,20 @@
+
+