Skip to content

Master 080625 #4578

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

Closed
wants to merge 91 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
81a3895
Update README.md
roybarda Dec 6, 2023
f9ae99e
first open-appsec support
roybarda Dec 6, 2023
41b579d
Supporting open-appsec
roybarda Dec 7, 2023
b66497a
open-appsec-addition
roybarda Dec 7, 2023
01b9edf
Create docker-compose.yaml
orianelou Dec 12, 2023
a856a0c
Create local_policy.yaml
orianelou Dec 13, 2023
3741c52
Update shared volumes config
ramiwi Dec 12, 2023
80deae9
Merge branch 'client-side-log-pagination' into open-appsec-integration
ramiwi Dec 12, 2023
dbd78e5
Filtered log views
ramiwi Dec 13, 2023
ce84a86
Notify policy updates to open-appsec container.
ramiwi Dec 13, 2023
1d81a55
Merge pull request #1 from openappsec/filtered-logs
roybarda Dec 14, 2023
c0171fe
Update log table, login screen UI and package versions
ramiwi Dec 14, 2023
f90f5ed
login screen package version fix
ramiwi Dec 15, 2023
3ae059e
log fields and logo fixes
ramiwi Dec 15, 2023
1c493ea
Update Dockerfile
clutat Dec 18, 2023
84ee15f
Update README.md
clutat Dec 18, 2023
f2564e9
Update README.md
clutat Dec 18, 2023
2a3348e
Update README.md
clutat Dec 18, 2023
256a750
Update docker-compose.yaml - work on initial version
clutat Dec 18, 2023
8631791
wide page with sticky table heads.
ramiwi Dec 18, 2023
69f0ceb
Update API endpoint for applying policy
ramiwi Dec 18, 2023
b105a2c
Add Docker Compose file and local policy configuration
ramiwi Dec 18, 2023
36a0a17
Update README.md - fix directory path in compilation instructions
clutat Dec 19, 2023
24d1e53
Update README.md - fix mkdir docker/lib
clutat Dec 19, 2023
a909cd1
Update local_policy.yaml - adjust default log-trigger
clutat Dec 19, 2023
bea44ab
Update README.md - various updates
clutat Dec 19, 2023
cbf282f
Update UI styles in header, main, and menu components
ramiwi Dec 19, 2023
b22d683
Merge branch 'open-appsec-addition' of https://github.com/openappsec/…
ramiwi Dec 19, 2023
a49e2bc
Sort tab data by timestamp in descending order
ramiwi Dec 19, 2023
1f96171
Update curl command for setting policy
ramiwi Dec 19, 2023
c6c7319
policy path moved to globals
ramiwi Dec 19, 2023
4a8f6b3
Update README.md - add detached -d to docker compose command
clutat Dec 20, 2023
f915c08
Update README.md - various text adjustments
clutat Dec 20, 2023
d34478c
Update README.md
orianelou Dec 20, 2023
b13d8e0
ignore appsec policy update notification command error
ramiwi Dec 20, 2023
ca10cc4
Merge branch 'open-appsec-addition' of https://github.com/openappsec/…
ramiwi Dec 20, 2023
8a0825f
Update README.md - added default admin login info
clutat Dec 20, 2023
9730109
Update README.md - minor formatting changes
clutat Dec 20, 2023
6a3092b
Update README.md
orianelou Dec 20, 2023
f43b024
Fixing version for first release
roybarda Dec 20, 2023
4c25b81
Update README.md
orianelou Dec 20, 2023
ab96034
fixing first release of open-appsec for nginx-proxy-manager
Dec 20, 2023
8ef4a82
Merge branch 'open-appsec-addition' of https://github.com/openappsec/…
Dec 20, 2023
6b94d74
Update README.md
orianelou Dec 20, 2023
26553cc
Update README.md
orianelou Dec 20, 2023
4415447
Merge branch 'develop' into open-appsec-addition
roybarda Dec 20, 2023
98b1e49
Merge pull request #2 from openappsec/open-appsec-addition
orianelou Dec 20, 2023
94df666
Merge pull request #3 from openappsec/develop
roybarda Dec 20, 2023
9099729
Update README.md
orianelou Dec 21, 2023
f79772d
Update README.md
orianelou Dec 27, 2023
bc4a8d6
Update README.md
orianelou Dec 27, 2023
fd2c6fd
fixing html sanitiation
Dec 27, 2023
ccb571a
Update docker-compose.yaml
orianelou Feb 25, 2024
47ed646
Update docker-compose.yaml
orianelou Mar 11, 2024
8086073
Create docker-compose-managed.yaml
orianelou Apr 4, 2024
a3bdf0d
Rename deployment/docker-compose-managed.yaml to deployment/managed-f…
orianelou Apr 4, 2024
a9c94f4
Create docker-compose.yaml
orianelou Apr 4, 2024
5f172fb
Update docker-compose.yaml
orianelou Apr 4, 2024
01f51dd
Merge pull request #11 from openappsec/centrally-managed
orianelou Apr 4, 2024
9241868
Create local_policy.yaml
orianelou Apr 4, 2024
e5c0f64
Create docker-compose.yaml
orianelou Apr 4, 2024
aaf04e6
Delete deployment/managed-from-open-appsec-ui/local_policy.yaml
orianelou Apr 8, 2024
c0d2d00
Update docker-compose.yaml
orianelou Apr 8, 2024
30badbc
fixing let's encrypt cert generation
Apr 25, 2024
86d18e3
Create local_policy.yaml
orianelou May 6, 2024
b9fc2b2
Create docker-compose.yaml
orianelou May 6, 2024
c6b8341
Create local_policy.yaml
orianelou May 6, 2024
c5996a4
Update README.md
orianelou Jun 6, 2024
3381ded
Update README.md
orianelou Jun 6, 2024
c63aa41
Update README.md
roybarda Aug 26, 2024
0a04721
Update README.md
roybarda Aug 26, 2024
6c3c5c1
syncing to latest npm release 2.11.3
Aug 26, 2024
03bcc68
syncing to latest npm release 2.11.3
Aug 26, 2024
245cac0
fixing version
Aug 26, 2024
34f66fe
Merge pull request #25 from openappsec/rebase_npm_26_08_24
roybarda Aug 26, 2024
e60af51
fix redirection-host issue
Oct 13, 2024
14707d5
fix additional logging
Oct 13, 2024
1a51efe
aligning to v2.12.1
Nov 21, 2024
9ddeac3
fixing version number
Nov 21, 2024
03fadba
fixing open-appsec in new release
Nov 21, 2024
1aa7688
adding back original yarn packages
Nov 21, 2024
35d2742
adding central mgmt dockerfile
Nov 21, 2024
7b9e6cf
fixing proxy-host schema to support open-appsec
Dec 2, 2024
d270bfb
supporting schema validation for proxy-host
Dec 2, 2024
5935559
fixing shema
Dec 2, 2024
94290ca
Merge pull request #35 from openappsec/02-12-24
roybarda Dec 2, 2024
61e24c1
aligning to latest open-appsec-npm version
Jan 8, 2025
1f00dc2
Merge pull request #37 from openappsec/07-01-25
roybarda Jan 8, 2025
8b1ccc2
version fix
Jan 8, 2025
effd521
Adding centrally managed based on the new version
Jan 13, 2025
7c4627a
Merging to latest release of nginxproxymanager
Jun 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 142 additions & 82 deletions README.md

Large diffs are not rendered by default.

318 changes: 318 additions & 0 deletions backend/internal/nginx-openappsec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
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;
const config = require('../lib/config');
const yaml = require('js-yaml');
const path = require('path');
const constants = require('../lib/constants');

const internalNginxOpenappsec = {

// module constants
CONFIG_TEMPLATE_FILE_NAME: 'local-policy-open-appsec-enabled-for-proxy-host.yaml',
CONFIG_TEMPLATE_DIR: '/app/templates',

// module variables
config: null,
configTemplate: null,

/**
* Generate an open-appsec config file for a proxy host.
*
* @param {Object} access
* @param {Object} row
* @param {Object} data
* @returns {Promise}
*/
generateConfig: (access, row, d) => {
return access.can('settings:update', row.id)
.then(() => {
if (config.debug()) {
logger.info('Generating openappsec config:', JSON.stringify(data, null, 2));
}


const data = row ? { ...row, ...d } : d;
logger.debug('data', JSON.stringify(data));

const openappsecMode = data.use_openappsec == false ? 'inactive' : data.openappsec_mode;

const configTemplateFilePath = path.join(internalNginxOpenappsec.CONFIG_TEMPLATE_DIR, internalNginxOpenappsec.CONFIG_TEMPLATE_FILE_NAME)
const configFilePath = path.join(constants.APPSEC_EXT_DIR, constants.APPSEC_CONFIG_FILE_NAME);

let openappsecConfig = yaml.load(fs.readFileSync(configFilePath, 'utf8'));
let openappsecConfigTemplate = yaml.load(fs.readFileSync(configTemplateFilePath, 'utf8'));

internalNginxOpenappsec.config = openappsecConfig;
internalNginxOpenappsec.configTemplate = openappsecConfigTemplate;

const specificRuleName = 'npm-managed-specific-rule-proxyhost-' + row.id;
const logTriggerName = 'npm-managed-log-trigger-proxyhost-' + row.id;
const practiceName = 'npm-managed-practice-proxyhost-' + row.id;

_.remove(openappsecConfig.policies['specific-rules'], rule => rule.name === specificRuleName || rule.name.startsWith(`${specificRuleName}.`));

data.domain_names.forEach((___domain, index) => {
let ruleName = index > 0 ? `${specificRuleName}.${index}` : specificRuleName;
let specificRuleNode = {
host: ___domain,
name: ruleName,
triggers: [logTriggerName],
mode: openappsecMode,
practices: [practiceName]
};
internalNginxOpenappsec.updateNode('policies', 'specific-rules', specificRuleNode, openappsecMode);
});

internalNginxOpenappsec.updateNode('', 'practices', { name: practiceName, 'web-attacks.override-mode': openappsecMode, 'web-attacks.minimum-confidence': data.minimum_confidence }, openappsecMode);
internalNginxOpenappsec.updateNode('', 'log-triggers', { name: logTriggerName }, openappsecMode);

// remove all openappsec managed ___location config nodes for a proxy host.
let pattern = new RegExp(`^npm-managed.*-${row.id}-.*`);
internalNginxOpenappsec.removeMatchingNodes(openappsecConfig, pattern);

// for each data.___location, create ___location config nodes
data.locations.forEach((___location, index) => {
let locationSpecificRuleName = 'npm-managed-specific-rule-proxyhost-' + row.id + '-' + index;
let locationLogTriggerName = 'npm-managed-log-trigger-proxyhost-' + row.id + '-' + index;
let locationPracticeName = 'npm-managed-practice-proxyhost-' + row.id + '-' + index;

let locationOpenappsecMode = ___location.use_openappsec == false ? 'inactive' : ___location.openappsec_mode;

_.remove(openappsecConfig.policies['specific-rules'], rule => rule.name === locationSpecificRuleName || rule.name.startsWith(`${locationSpecificRuleName}.`));

data.domain_names.forEach((___domain, index) => {
let locationUrl = ___domain + ___location.path;
let ruleName = index > 0 ? `${locationSpecificRuleName}.${index}` : locationSpecificRuleName;

let domainSpecificRuleNode = {
host: locationUrl,
name: ruleName,
triggers: [locationLogTriggerName],
mode: locationOpenappsecMode,
practices: [locationPracticeName]
};
internalNginxOpenappsec.updateNode('policies', 'specific-rules', domainSpecificRuleNode, locationOpenappsecMode, '___location', openappsecMode);
});

internalNginxOpenappsec.updateNode('', 'practices', { name: locationPracticeName, 'web-attacks.override-mode': locationOpenappsecMode, 'web-attacks.minimum-confidence': ___location.minimum_confidence }, locationOpenappsecMode, '___location', openappsecMode);
internalNginxOpenappsec.updateNode('', 'log-triggers', { name: locationLogTriggerName }, locationOpenappsecMode, '___location', openappsecMode);
});

fs.writeFileSync(configFilePath, yaml.dump(openappsecConfig));
},
(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:
<br>Settings -> open-appsec Advanced -> Save Settings`;

return Promise.reject(new Error(errorMessageForUI));
});
});
},

/**
* Remove all openappsec managed config nodes for a proxy host.
*
* @param {Object} access
* @param {Object} row
* @returns {Promise}
*
*/
deleteConfig: (access, row) => {
return access.can('settings:update', row.id)
.then(() => {
const configFilePath = path.join(constants.APPSEC_EXT_DIR, constants.APPSEC_CONFIG_FILE_NAME);
let openappsecConfig = yaml.load(fs.readFileSync(configFilePath, 'utf8'));

// remove all openappsec managed ___location config nodes for a proxy host.
let pattern = new RegExp(`^npm-managed.*-${row.id}`);
internalNginxOpenappsec.removeMatchingNodes(openappsecConfig, pattern);
fs.writeFileSync(configFilePath, yaml.dump(openappsecConfig));
})
.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:
<br>Settings -> open-appsec Advanced -> Save Settings`;

return Promise.reject(new Error(errorMessageForUI));
});
})
.catch((err) => {
logger.error('Error deleting openappsec config:', err);
// throw err; // Propagate the error to the caller
});
},

/**
* Update a node in the openappsec config.
* - if the node does not exist, create it.
* - if the node exists, update it.
* - if openappsecMode is 'inactive', delete the node.
*
* @param {String} parentNodePath - path to the parent node. e.g. 'policies'.
* @param {String} nodeName - name of the node. e.g. 'specific-rules', 'practices', 'log-triggers'.
* @param {Object} nodeItemProperties
* @param {String} openappsecMode
* @param {String} nodeType - 'host' or '___location'
* @param {String} hostAppsecMode - to check if the host of a ___location is inactive.
*/
updateNode: function (parentNodePath, nodeName, nodeItemProperties, openappsecMode, nodeType = 'host', hostAppsecMode = '') {
// if no parent node path is specified, use the root of the config object.
const parent = parentNodePath ? _.get(this.config, parentNodePath, this.config) : this.config;

if (!parent) {
console.log('parent is not defined');
return;
}

let nodeItems = _.find(parent[nodeName], { name: nodeItemProperties.name });
if (openappsecMode == 'inactive' && nodeItems) {
_.remove(parent[nodeName], { name: nodeItemProperties.name });
}

if (openappsecMode !== 'inactive' || nodeType === '___location' && hostAppsecMode !== 'inactive') {
if (!nodeItems) {
// create the node from the template if it does not exist.
let templateSearchPath = parentNodePath ? `${parentNodePath}.${nodeName}[0]` : `${nodeName}[0]`;
nodeItems = _.cloneDeep(_.get(this.configTemplate, templateSearchPath));

// update the node with the nodeItemProperties. if the nodeType is '___location' and the openappsecMode is 'inactive', only update the name, host, and the (inactive) mode.
if (nodeType === '___location' && openappsecMode === 'inactive') {
nodeItemProperties = _.pick(nodeItemProperties, ['name', 'host', 'triggers', 'practices', 'mode', 'web-attacks.override-mode']);
}

Object.keys(nodeItemProperties).forEach(key => {
_.set(nodeItems, key, nodeItemProperties[key]);
});
parent[nodeName] = parent[nodeName] || [];
parent[nodeName].push(nodeItems);
} else {
// update the node if it exists.
Object.keys(nodeItemProperties).forEach(key => {
_.set(nodeItems, key, nodeItemProperties[key]);
});
}
}
},

notifyPolicyUpdate: async function() {
if (!constants.USE_NOTIFY_POLICY) {
console.log('USE_NOTIFY_POLICY is false');
return;
}
let ports = constants.PORTS;
let lastError = null;
for (let port of ports) {
try {
const data = `{"policy_path":"${constants.POLICY_PATH}"}`;
const command = `curl -s -o /dev/null -w "%{http_code}" --data '${data}' ${constants.HOSTURL}:${port}/set-apply-policy`;
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.
*
* @param {Object|Array} obj - The object or array to remove nodes from.
* @param {RegExp} pattern - The pattern to match against node names.
*/
removeMatchingNodes: function (obj, pattern) {
_.forEach(obj, (value, key) => {
if (_.isPlainObject(value)) {
if (pattern.test(key)) {
delete obj[key];
} else {
this.removeMatchingNodes(value, pattern);
}
} else if (_.isArray(value)) {
_.remove(value, function (item) {
return _.isPlainObject(item) && pattern.test(item.name);
});
value.forEach(item => {
if (_.isPlainObject(item)) {
this.removeMatchingNodes(item, pattern);
}
});
}
});
},

/**
* Get the openappsec mode, use_openappsec and minimum_confidence for a proxy host.
*
* @param {Object} openappsecConfig - openappsec config object
* @param {Number} rowId - proxy host id
* @returns {Object} { mode, use_openappsec, minimum_confidence }
*/
getOpenappsecFields: (openappsecConfig, rowId) => {
const specificRuleName = 'npm-managed-specific-rule-proxyhost-' + rowId;

const specificRule = _.find(openappsecConfig?.policies['specific-rules'], { name: specificRuleName });
const mode = specificRule?.mode || 'inactive';
const use_openappsec = mode !== 'inactive' && mode !== undefined;

const practiceName = 'npm-managed-practice-proxyhost-' + rowId;
const practice = _.find(openappsecConfig?.practices, { name: practiceName });
const minimum_confidence = practice?.['web-attacks']['minimum-confidence'] || 'high';

return { mode, use_openappsec, minimum_confidence };
},

/**
* get the openappsec config file path.
*/
getConfigFilePath: () => {
const configFilePath = path.join(constants.APPSEC_EXT_DIR, constants.APPSEC_CONFIG_FILE_NAME);
return configFilePath;
},

/**
* A simple wrapper around unlinkSync that writes to the logger
*
* @param {String} filename
*/
deleteFile: (filename) => {
logger.debug('Deleting file: ' + filename);
try {
fs.unlinkSync(filename);
} catch (err) {
logger.debug('Could not delete file:', JSON.stringify(err, null, 2));
}
}

};

module.exports = internalNginxOpenappsec;
Loading