mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-08-09 18:55:14 +00:00
Merge pull request #2 from openappsec/open-appsec-addition
Open appsec addition
This commit is contained in:
171
README.md
171
README.md
@@ -1 +1,172 @@
|
||||
# open-appsec NPM Proxy Manager integration (beta)
|
||||
|
||||
This is the repository for the beta release of the new integration of open-appsec WAF with NGINX Proxy Manager.
|
||||
This will allow NGINX Proxy Manager (NPM) users to protect their web applications and web APIs exposed by NGINX Proxy Manager by easily activating and configuring open-appsec protection for each of the configured Proxy Host objects in NPM directly from the NPM Web UI and also to monitor security events.
|
||||
This new integration of open-appsec WAF with NGINX Proxy Manager not only closes the security gap caused by the soon end-of-life ModSecurity WAF, but provides a modern, strong protection alternative in form of open-appsec, a preemptive, machine-learning based, fully automatic WAF that does not rely on signatures at all.
|
||||
|
||||
### NGINX Proxy Manager
|
||||
Nginx Proxy Manager is a popular open-source project that simplifies the management of NGINX reverse proxy configurations, offering a user-friendly web-based interface for easy setup and maintenance. It was created by “jc21” (https://www.jc21.com).
|
||||
This project is particularly useful for individuals and organizations looking to streamline the deployment of web applications and services by efficiently managing multiple domains and subdomains through a centralized interface.
|
||||
With NGINX Proxy Manager, users can effortlessly create and manage SSL certificates, enabling secure HTTPS connections for their applications, while also providing advanced features such as Let's Encrypt integration for automated certificate renewal.
|
||||
NGINX Proxy Manager (NPM) is based on NGINX and provided as a container image that can be easily deployed in containerized environments like Docker (typically using Docker Compose) or others.
|
||||
NPM itself does not include any WAF solution for effective Threat Prevention against modern attacks or Zero-day attacks.
|
||||
|
||||
Website and Docs: https://nginxproxymanager.com
|
||||
Github: https://github.com/NginxProxyManager
|
||||
|
||||
### open-appsec WAF:
|
||||
|
||||
open-appsec WAF provides automatic, preemptive threat prevention for reverse proxies like NGINX. It is machine learning based, which means it doesn’t require signatures (or updating them) at all. This enables it to provide state-of-the-art threat prevention even for true zero-day attacks while significantly reducing both, administrative effort as well as the amount of false-positives.
|
||||
open-appsec therefore is a great fit to provide advanced threat prevention to the services exposed by NGINX Proxy Manager.
|
||||
|
||||
Website: https://www.openappsec.io
|
||||
Github: https://github.com/openappsec
|
||||
Docs: https://docs.openappsec.io
|
||||
|
||||
### Integration of open-appsec WAF with NGINX Proxy Manager:
|
||||
|
||||
With this integration we are focusing on maximum simplicity for the user to maintain the low entry barrier as a key design principle of the NGINX proxy manager (NPM) project, which we want in the same way to apply also to the addition of open-appsec.
|
||||
|
||||
The actual deployment of NPM with open-appsec is performed using a slightly enhanced docker compose file (see below) which also adds the open-appsec agent container to it, which will perform the actual security inspection.
|
||||
The NGINX proxy manager container deployed as part of the docker compose is using the “open-appsec-npm” image, provided by the open-appsec team, which is based on the regular NPM code but also adds the open-appsec attachment to it as an NGINX module. This attachment enables the connection between the NGINX and the open-appsec agent and provides the HTTP data for inspection to the Agent.
|
||||
The “open-appsec-npm” container also contains various NPM WebUI enhancements and the integration logic allowing the configuration, administration and monitoring of open-appsec.
|
||||
|
||||
You can read more about open-appsec’s Technology here:
|
||||
https://www.openappsec.io/tech
|
||||
|
||||
After successful deployment you can then activate and configure open-appsec directly from the enhanced NPM Web UI interface to which the most relevant configuration options for the open-appsec WAF as well as an option to view the open-appsec logs have been added.
|
||||
|
||||
The resulting architecture with the open-appsec Agent container and the NGINX Proxy Manager container then looks like this:
|
||||
|
||||

|
||||
|
||||
Documentation: https://docs.openappsec.io/integrations/nginx-proxy-manager-integration
|
||||
|
||||
# Deployment Step-by-Step:
|
||||
Before you start, make sure to have a Linux environment with Docker and Docker Compose suitable with version 3.8 available.
|
||||
To deploy NGINX Proxy Manager with open-appsec integration follow the steps below:
|
||||
1. Within the directory which you want to use for the deployment:
|
||||
Create a folder appsec-localconfig which will hold the appsec declarative configuration file (this will be managed by the enhanced NPM WebUI)
|
||||
```
|
||||
mkdir ./appsec-localconfig
|
||||
```
|
||||
2. Download the initial declarative configuration file for open-appsec into that folder.
|
||||
This will be managed from the NPM WebUI.
|
||||
```
|
||||
wget https://raw.githubusercontent.com/openappsec/open-appsec-npm/main/deployment/local_policy.yaml -O ./appsec-localconfig/local_policy.yaml
|
||||
```
|
||||
3. Create a docker-compose.yaml file with the content below, it can be downloaded as follows:
|
||||
```
|
||||
wget https://raw.githubusercontent.com/openappsec/open-appsec-npm/main/deployment/docker-compose.yaml
|
||||
```
|
||||
4. Run docker-compose up to start the deployment of all relevant containers:
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
5. Check if the nginx-proxy-manager-attachment and the appsec-agent containers are up and running:
|
||||
```
|
||||
docker ps
|
||||
```
|
||||
Congratulations, now you are all set and you can login with your web browser to the WebUI of NGINX Proxy Manager with open-appsec integration as follows:
|
||||
```
|
||||
http://[hostname or IP of your host]:81
|
||||
```
|
||||

|
||||
|
||||
|
||||
At first login please use the following default administrator user credentials:
|
||||
|
||||
E-mail address: admin@example.com
|
||||
Password: changeme
|
||||
|
||||
You will then be prompted to provide your own user details and asked to change the password, before being presented with the NGINX Proxy Manager Dashboard view:
|
||||
|
||||

|
||||
|
||||
# Configuration
|
||||
To learn how to use NGINX Proxy Manager (NPM) please see project documentation, as NPM usage and configuration will not be explained here:
|
||||
https://nginx-proxy-manager.com
|
||||
|
||||
Once you created a new Proxy Host within NGINX Proxy Manager WebUI you can now easily enable and configure open-appsec protection (see also screenshot below):
|
||||
1. Enable open-appsec by flipping the “open-appsec” switch to enabled.
|
||||
2. Select the Enforcement Mode, it can be either “Prevent-Learn” or “Detect-Learn”
|
||||
3. Select the minimum confidence level for open-appsec to prevent an attack (only relevant when in prevent mode), it can be either “Critical”, “High” or “Medium” confidence.
|
||||
4. Click “Save”
|
||||
|
||||

|
||||
|
||||
This screenshot for example shows a “Proxy Host” reverse proxy configuration in NPM that will listen to inbound traffic for hostnames “100.25.161.101”, “localhost” or “my.webserver.com”.
|
||||
This “Proxy Host” has open-appsec enabled in “Prevent-Learn” mode and therefore will prevent incoming http or https requests when there’s a minimum confidence level of “High” or higher, as configured for the “Minimum confidence for prevent” setting.
|
||||
Non-malicious traffic will then be proxied using “http” protocol to the configured backend webserver with the IP address “192.168.160.2” on port “80”.
|
||||
|
||||
Note: Changes in the open-appsec configuration performed and saved in the NPM Web UI can take up to 30 seconds before they become effective.
|
||||
|
||||
This was just a very basic overview to get you started, there's many more things you can configure as part of the open-appsec NGINX Proxy Manager integration.
|
||||
|
||||
If you want to check out the open-appsec Security Logs click on the new menu option “Security Log” which allows you to view the open-appsec specific logs directly from the NPM Web UI:
|
||||

|
||||
|
||||
**You find the full documentation including FAQ here:
|
||||
https://docs.openappsec.io/integrations/nginx-proxy-manager-integration/**
|
||||
|
||||
# Compilation Instructions
|
||||
|
||||
**Important: In order to deploy and use the open-appsec NGINX Proxy Manager integration you do not have to compile the code yourself.**
|
||||
We offer a pre-compiled ready-to-use "open-appsec-npm" container (see above in the "Deployment" section).
|
||||
|
||||
## Prerequisites
|
||||
- Linux Machine with Docker engine deployed
|
||||
|
||||
## Additional perquisites for compiling against other NGINX Proxy Manager version
|
||||
Precompiled libraries for open-appsec Attachment for the specific version of NGINX Proxy Manager you want to compile agasint.
|
||||
You find the detailed compilation instructions in this repo: https://github.com/openappsec/attachment
|
||||
|
||||
Make sure to compile the attachment using a container with the exact same OS version as used in the NPM container version you want to build the integration upon and provide also the NGINX version information from that container.
|
||||
libngx_module.so libosrc_compression_utils.so libosrc_nginx_attachment_util.so libosrc_shmem_ipc.so
|
||||
|
||||
## Preparations
|
||||
Clone this repository to your local Linux machine:
|
||||
```
|
||||
git clone https://github.com/openappsec/open-appsec-npm.git
|
||||
```
|
||||
|
||||
Change into the open-appsec-npm directory:
|
||||
```
|
||||
cd open-appsec-npm
|
||||
```
|
||||
### If you are compiling against other NGINX proxy Manager version
|
||||
Replace the files in the ```docker/lib``` folder with the libraries you've precompiled.
|
||||
|
||||
Compilation - Build the Frontend:
|
||||
---
|
||||
```
|
||||
cd scripts/ci
|
||||
bash frontend-build
|
||||
```
|
||||
|
||||
Build the open-appsec-npm Container Image:
|
||||
--
|
||||
```
|
||||
cd ../..
|
||||
docker buildx build --load -t open-appsec-npm -f docker/Dockerfile .
|
||||
```
|
||||
|
||||
**Congratulations, you have successfully built your own open-appsec-npm container.**
|
||||
You can deploy it now by specifying it within the docker-compose.yaml file.
|
||||
You find the deployment instructions above in the "Deployment" section.
|
||||
If you also want to build your own open-appsec Agent container you find the instructions in this repo:
|
||||
https://github.com/openappsec/openappsec
|
||||
|
||||
# Contributing
|
||||
We welcome everyone that wishes to share their knowledge and expertise to enhance and expand this project.
|
||||
|
||||
Please see the [Contributing Guidelines](https://github.com/openappsec/openappsec-npm/blob/main/CONTRIBUTING.md).
|
||||
|
||||
# Final notes
|
||||
|
||||
We hope this integration will be useful for you and provide you easy-to-configure yet highly effective protection based on open-appsec for your web services or web APIs against known and especially unknown, zero day attacks!
|
||||
|
||||
If you have any questions, feedback or need assistance with some issue you can
|
||||
- contact us at info@openappsec.io
|
||||
- contact us using the chat on our project website https://www.openappsec.io
|
||||
- open an issue in the GitHub project
|
||||
|
314
backend/internal/nginx-openappsec.js
Executable file
314
backend/internal/nginx-openappsec.js
Executable file
@@ -0,0 +1,314 @@
|
||||
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, data) => {
|
||||
return access.can('settings:update', row.id)
|
||||
.then(() => {
|
||||
if (config.debug()) {
|
||||
logger.info('Generating openappsec config:', JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
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;
|
152
backend/internal/openappsec-log.js
Executable file
152
backend/internal/openappsec-log.js
Executable file
@@ -0,0 +1,152 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const util = require('util');
|
||||
const error = require('../lib/error');
|
||||
const { APPSEC_LOG_DIR } = require('../lib/constants');
|
||||
|
||||
const internalOpenappsecLog = {
|
||||
|
||||
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,
|
||||
assetName: json.eventSource.assetName,
|
||||
securityAction: json.eventData.securityAction,
|
||||
waapIncidentType: json.eventData.waapIncidentType,
|
||||
httpSourceId: json.eventData.httpSourceId,
|
||||
sourceIP: json.eventData.sourceIP,
|
||||
proxyIP: json.eventData.proxyIP,
|
||||
httpHostName: json.eventData.httpHostName,
|
||||
httpMethod: json.eventData.httpMethod,
|
||||
httpUriPath: json.eventData.httpUriPath,
|
||||
eventTopic: json.eventSource.eventTopic,
|
||||
matchedLocation: json.eventData.matchedLocation,
|
||||
matchedParameter: json.eventData.matchedParameter,
|
||||
matchedSample: json.eventData.matchedSample,
|
||||
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(async () => {
|
||||
const directoryPath = APPSEC_LOG_DIR;
|
||||
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 = [];
|
||||
for (const file of logFiles) {
|
||||
const filePath = path.join(directoryPath, file);
|
||||
const dataLines = await this.processFile(filePath);
|
||||
wrappedObjects.push(...dataLines);
|
||||
}
|
||||
|
||||
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;
|
@@ -4,8 +4,12 @@ const utils = require('../lib/utils');
|
||||
const proxyHostModel = require('../models/proxy_host');
|
||||
const internalHost = require('./host');
|
||||
const internalNginx = require('./nginx');
|
||||
const internalNginxOpenappsec= require('./nginx-openappsec');
|
||||
const internalAuditLog = require('./audit-log');
|
||||
const internalCertificate = require('./certificate');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const yaml = require('js-yaml');
|
||||
|
||||
function omissions () {
|
||||
return ['is_deleted'];
|
||||
@@ -48,9 +52,15 @@ const internalProxyHost = {
|
||||
data.owner_user_id = access.token.getUserId(1);
|
||||
data = internalHost.cleanSslHstsData(data);
|
||||
|
||||
let db_data = _.assign({}, data);
|
||||
// Remove the openappsec fields from data. they are not in the database.
|
||||
delete db_data.use_openappsec;
|
||||
delete db_data.openappsec_mode;
|
||||
delete db_data.minimum_confidence;
|
||||
|
||||
return proxyHostModel
|
||||
.query()
|
||||
.insertAndFetch(data)
|
||||
.insertAndFetch(db_data)
|
||||
.then(utils.omitRow(omissions()));
|
||||
})
|
||||
.then((row) => {
|
||||
@@ -84,6 +94,16 @@ const internalProxyHost = {
|
||||
return row;
|
||||
});
|
||||
})
|
||||
.then(row => {
|
||||
return internalNginxOpenappsec.generateConfig(access, row, data)
|
||||
.then(() => {
|
||||
return row;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Error generating openappsec config: " + err);
|
||||
// throw new error.ConfigurationError(err.message);
|
||||
});
|
||||
})
|
||||
.then((row) => {
|
||||
// Audit log
|
||||
data.meta = _.assign({}, data.meta || {}, row.meta);
|
||||
@@ -159,6 +179,16 @@ const internalProxyHost = {
|
||||
return row;
|
||||
}
|
||||
})
|
||||
.then(row => {
|
||||
return internalNginxOpenappsec.generateConfig(access, row, data)
|
||||
.then(() => {
|
||||
return row;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Error generating openappsec config: " + 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({}, {
|
||||
@@ -167,6 +197,11 @@ const internalProxyHost = {
|
||||
|
||||
data = internalHost.cleanSslHstsData(data, row);
|
||||
|
||||
// Remove the openappsec fields from data. they are not in the database
|
||||
delete data.use_openappsec;
|
||||
delete data.openappsec_mode;
|
||||
delete data.minimum_confidence;
|
||||
|
||||
return proxyHostModel
|
||||
.query()
|
||||
.where({id: data.id})
|
||||
@@ -247,6 +282,22 @@ const internalProxyHost = {
|
||||
if (typeof data.omit !== 'undefined' && data.omit !== null) {
|
||||
row = _.omit(row, data.omit);
|
||||
}
|
||||
return row;
|
||||
})
|
||||
.then((row) => {
|
||||
// add openappsec fields to row
|
||||
try {
|
||||
const configFilePath = internalNginxOpenappsec.getConfigFilePath(access);
|
||||
const openappsecConfig = yaml.load(fs.readFileSync(configFilePath, 'utf8'));
|
||||
let result = internalNginxOpenappsec.getOpenappsecFields(openappsecConfig, row.id);
|
||||
row.use_openappsec = result.use_openappsec;
|
||||
row.openappsec_mode = result.mode;
|
||||
row.minimum_confidence = result.minimum_confidence;
|
||||
}
|
||||
catch (e) {
|
||||
console.log("Error reading openappsec config file: " + e);
|
||||
}
|
||||
|
||||
return row;
|
||||
});
|
||||
},
|
||||
@@ -274,6 +325,14 @@ const internalProxyHost = {
|
||||
.patch({
|
||||
is_deleted: 1
|
||||
})
|
||||
.then(() => {
|
||||
// Delete openappsec config
|
||||
return internalNginxOpenappsec.deleteConfig(access, row)
|
||||
.catch((err) => {
|
||||
throw new error.ConfigurationError(err.message);
|
||||
});
|
||||
|
||||
})
|
||||
.then(() => {
|
||||
// Delete Nginx Config
|
||||
return internalNginx.deleteConfig('proxy_host', row)
|
||||
@@ -430,6 +489,21 @@ const internalProxyHost = {
|
||||
return query.then(utils.omitRows(omissions()));
|
||||
})
|
||||
.then((rows) => {
|
||||
// add openappsec fields to rows
|
||||
try {
|
||||
const configFilePath = internalNginxOpenappsec.getConfigFilePath(access);
|
||||
const openappsecConfig = yaml.load(fs.readFileSync(configFilePath, 'utf8'));
|
||||
rows.map(function (row, idx) {
|
||||
let result = internalNginxOpenappsec.getOpenappsecFields(openappsecConfig, row.id);
|
||||
rows[idx].use_openappsec = result.use_openappsec;
|
||||
rows[idx].openappsec_mode = result.mode;
|
||||
rows[idx].minimum_confidence = result.minimum_confidence;
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
console.log("Error reading openappsec config file: " + e);
|
||||
}
|
||||
|
||||
if (typeof expand !== 'undefined' && expand !== null && expand.indexOf('certificate') !== -1) {
|
||||
return internalHost.cleanAllRowsCertificateMeta(rows);
|
||||
}
|
||||
|
52
backend/internal/setting-openappsec.js
Executable file
52
backend/internal/setting-openappsec.js
Executable file
@@ -0,0 +1,52 @@
|
||||
const fs = require('fs');
|
||||
const error = require('../lib/error');
|
||||
const path = require('path');
|
||||
|
||||
const constants = require('../lib/constants');
|
||||
|
||||
const internalOpenappsecSetting = {
|
||||
configFilePath: path.join(constants.APPSEC_EXT_DIR, constants.APPSEC_CONFIG_FILE_NAME),
|
||||
|
||||
/**
|
||||
* @param {Access} access
|
||||
* @return {Promise}
|
||||
*/
|
||||
getLocalPolicy: (access) => {
|
||||
return access.can('settings:list')
|
||||
.then(() => {
|
||||
try {
|
||||
const filePath = internalOpenappsecSetting.configFilePath
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return;
|
||||
}
|
||||
const fileContent = fs.readFileSync(filePath, 'utf8');
|
||||
const jsonStr = JSON.stringify(fileContent);
|
||||
return jsonStr;
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Access} access
|
||||
* @param {Object} data
|
||||
* @return {Promise}
|
||||
*/
|
||||
updateLocalPolicy: (access, data) => {
|
||||
return access.can('settings:list')
|
||||
.then(() => {
|
||||
const filePath = internalOpenappsecSetting.configFilePath
|
||||
const yamlStr = data.local_policy;
|
||||
fs.writeFileSync(filePath, yamlStr, {encoding: 'utf8'});
|
||||
return true;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
throw new error.ConfigurationError(err.message);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = internalOpenappsecSetting;
|
9
backend/lib/constants.js
Executable file
9
backend/lib/constants.js
Executable file
@@ -0,0 +1,9 @@
|
||||
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',
|
||||
};
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nginx-proxy-manager",
|
||||
"version": "0.0.0",
|
||||
"version": "1.0.0",
|
||||
"description": "A beautiful interface for creating Nginx endpoints",
|
||||
"main": "js/index.js",
|
||||
"dependencies": {
|
||||
@@ -13,6 +13,7 @@
|
||||
"express": "^4.17.3",
|
||||
"express-fileupload": "^1.1.9",
|
||||
"gravatar": "^1.8.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json-schema-ref-parser": "^8.0.0",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"knex": "2.4.2",
|
||||
|
@@ -29,8 +29,10 @@ router.use('/schema', require('./schema'));
|
||||
router.use('/tokens', require('./tokens'));
|
||||
router.use('/users', require('./users'));
|
||||
router.use('/audit-log', require('./audit-log'));
|
||||
router.use('/openappsec-log', require('./openappsec-log'));
|
||||
router.use('/reports', require('./reports'));
|
||||
router.use('/settings', require('./settings'));
|
||||
router.use('/openappsec-settings', require('./openappsec-settings'));
|
||||
router.use('/nginx/proxy-hosts', require('./nginx/proxy_hosts'));
|
||||
router.use('/nginx/redirection-hosts', require('./nginx/redirection_hosts'));
|
||||
router.use('/nginx/dead-hosts', require('./nginx/dead_hosts'));
|
||||
|
35
backend/routes/api/openappsec-log.js
Executable file
35
backend/routes/api/openappsec-log.js
Executable file
@@ -0,0 +1,35 @@
|
||||
const express = require('express');
|
||||
const jwtdecode = require('../../lib/express/jwt-decode');
|
||||
const internalOpenappsecLog = require('../../internal/openappsec-log');
|
||||
|
||||
let router = express.Router({
|
||||
caseSensitive: true,
|
||||
strict: true,
|
||||
mergeParams: true
|
||||
});
|
||||
|
||||
/**
|
||||
* /api/openappsec-log
|
||||
*/
|
||||
router
|
||||
.route('/')
|
||||
.options((req, res) => {
|
||||
res.sendStatus(204);
|
||||
})
|
||||
.all(jwtdecode())
|
||||
|
||||
/**
|
||||
* GET /api/openappsec-log
|
||||
*
|
||||
* Retrieve all logs
|
||||
*/
|
||||
.get((req, res, next) => {
|
||||
return internalOpenappsecLog.getAll(res.locals.access)
|
||||
.then((policy) => {
|
||||
res.status(200)
|
||||
.send(policy);
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
module.exports = router;
|
49
backend/routes/api/openappsec-settings.js
Executable file
49
backend/routes/api/openappsec-settings.js
Executable file
@@ -0,0 +1,49 @@
|
||||
const express = require('express');
|
||||
const jwtdecode = require('../../lib/express/jwt-decode');
|
||||
const internalOpenappsecSetting = require('../../internal/setting-openappsec');
|
||||
|
||||
let router = express.Router({
|
||||
caseSensitive: true,
|
||||
strict: true,
|
||||
mergeParams: true
|
||||
});
|
||||
|
||||
/**
|
||||
* /api/openappsec-settings
|
||||
*/
|
||||
router
|
||||
.route('/')
|
||||
.options((req, res) => {
|
||||
res.sendStatus(204);
|
||||
})
|
||||
.all(jwtdecode())
|
||||
|
||||
/**
|
||||
* GET /api/openappsec-settings
|
||||
*
|
||||
* Retrieve the open-appsec local policy.
|
||||
*/
|
||||
.get((req, res, next) => {
|
||||
return internalOpenappsecSetting.getLocalPolicy(res.locals.access)
|
||||
.then((policy) => {
|
||||
res.status(200)
|
||||
.send(policy);
|
||||
})
|
||||
.catch(next);
|
||||
})
|
||||
|
||||
/**
|
||||
* PUT /api/openappsec-settings
|
||||
*
|
||||
* Update the open-appsec local policy.
|
||||
*/
|
||||
.put((req, res, next) => {
|
||||
return internalOpenappsecSetting.updateLocalPolicy(res.locals.access, req.body)
|
||||
.then((result) => {
|
||||
res.status(200)
|
||||
.send(result);
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
module.exports = router;
|
@@ -137,6 +137,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"openappsec_mode": {
|
||||
"description": "openappsec_mode ID",
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 255
|
||||
},
|
||||
"minimum_confidence": {
|
||||
"description": "minimum_confidence ID",
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 255
|
||||
},
|
||||
"access_list_id": {
|
||||
"description": "Access List ID",
|
||||
"example": 1234,
|
||||
@@ -231,6 +243,11 @@
|
||||
"example": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"use_openappsec": {
|
||||
"description": "Use openappsec",
|
||||
"example": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"caching_enabled": {
|
||||
"description": "Should we cache assets",
|
||||
"example": true,
|
||||
|
@@ -50,6 +50,15 @@
|
||||
"block_exploits": {
|
||||
"$ref": "../definitions.json#/definitions/block_exploits"
|
||||
},
|
||||
"use_openappsec": {
|
||||
"$ref": "../definitions.json#/definitions/use_openappsec"
|
||||
},
|
||||
"openappsec_mode": {
|
||||
"$ref": "../definitions.json#/definitions/openappsec_mode"
|
||||
},
|
||||
"minimum_confidence": {
|
||||
"$ref": "../definitions.json#/definitions/minimum_confidence"
|
||||
},
|
||||
"caching_enabled": {
|
||||
"$ref": "../definitions.json#/definitions/caching_enabled"
|
||||
},
|
||||
@@ -104,6 +113,15 @@
|
||||
},
|
||||
"advanced_config": {
|
||||
"type": "string"
|
||||
},
|
||||
"use_openappsec": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"openappsec_mode": {
|
||||
"type": "string"
|
||||
},
|
||||
"minimum_confidence": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,6 +167,15 @@
|
||||
"block_exploits": {
|
||||
"$ref": "#/definitions/block_exploits"
|
||||
},
|
||||
"use_openappsec": {
|
||||
"$ref": "#/definitions/use_openappsec"
|
||||
},
|
||||
"openappsec_mode": {
|
||||
"$ref": "#/definitions/openappsec_mode"
|
||||
},
|
||||
"minimum_confidence": {
|
||||
"$ref": "#/definitions/minimum_confidence"
|
||||
},
|
||||
"caching_enabled": {
|
||||
"$ref": "#/definitions/caching_enabled"
|
||||
},
|
||||
@@ -239,6 +266,15 @@
|
||||
"block_exploits": {
|
||||
"$ref": "#/definitions/block_exploits"
|
||||
},
|
||||
"use_openappsec": {
|
||||
"$ref": "#/definitions/use_openappsec"
|
||||
},
|
||||
"openappsec_mode": {
|
||||
"$ref": "#/definitions/openappsec_mode"
|
||||
},
|
||||
"minimum_confidence": {
|
||||
"$ref": "#/definitions/minimum_confidence"
|
||||
},
|
||||
"caching_enabled": {
|
||||
"$ref": "#/definitions/caching_enabled"
|
||||
},
|
||||
@@ -312,6 +348,15 @@
|
||||
"block_exploits": {
|
||||
"$ref": "#/definitions/block_exploits"
|
||||
},
|
||||
"use_openappsec": {
|
||||
"$ref": "#/definitions/use_openappsec"
|
||||
},
|
||||
"openappsec_mode": {
|
||||
"$ref": "#/definitions/openappsec_mode"
|
||||
},
|
||||
"minimum_confidence": {
|
||||
"$ref": "#/definitions/minimum_confidence"
|
||||
},
|
||||
"caching_enabled": {
|
||||
"$ref": "#/definitions/caching_enabled"
|
||||
},
|
||||
|
122
backend/templates/local-policy-open-appsec-enabled-for-proxy-host.yaml
Executable file
122
backend/templates/local-policy-open-appsec-enabled-for-proxy-host.yaml
Executable file
@@ -0,0 +1,122 @@
|
||||
# This example is for NPM Proxy Host with open-appsec enabled,
|
||||
# Enforcement Mode set to Prevent/Learn, hostname web.server.com/example
|
||||
|
||||
policies:
|
||||
default:
|
||||
triggers:
|
||||
- appsec-default-log-trigger
|
||||
mode: inactive
|
||||
practices:
|
||||
- webapp-default-practice
|
||||
custom-response: appsec-default-web-user-response
|
||||
specific-rules:
|
||||
- host: web.server.com/example
|
||||
# as set in "Edit Proxy Host" in "Domain Names" field
|
||||
# IMPORTANT LIMITATION: Currently open-appsec declarative with CRD version 1.0 only supports single host entry per specific rule
|
||||
# This will be resolved with new CRDs 2.0
|
||||
name: npm-managed-specific-rule-proxyhost-1
|
||||
# This “name” key will be the actual reference to a specific Reverse Proxy object defined in NPM
|
||||
triggers:
|
||||
- npm-managed-log-trigger-proxyhost-1
|
||||
mode: prevent-learn
|
||||
practices:
|
||||
- npm-managed-practice-proxyhost-1
|
||||
|
||||
practices:
|
||||
- name: webapp-default-practice
|
||||
web-attacks:
|
||||
max-body-size-kb: 1000000
|
||||
max-header-size-bytes: 102400
|
||||
max-object-depth: 40
|
||||
max-url-size-bytes: 32768
|
||||
minimum-confidence: high
|
||||
override-mode: inactive
|
||||
protections:
|
||||
csrf-protection: inactive
|
||||
error-disclosure: inactive
|
||||
non-valid-http-methods: false
|
||||
open-redirect: inactive
|
||||
anti-bot:
|
||||
injected-URIs: []
|
||||
validated-URIs: []
|
||||
override-mode: inactive
|
||||
snort-signatures:
|
||||
configmap: []
|
||||
override-mode: inactive
|
||||
openapi-schema-validation:
|
||||
configmap: []
|
||||
override-mode: inactive
|
||||
|
||||
- name: npm-managed-practice-proxyhost-1
|
||||
web-attacks:
|
||||
max-body-size-kb: 1000000
|
||||
max-header-size-bytes: 102400
|
||||
max-object-depth: 40
|
||||
max-url-size-bytes: 32768
|
||||
minimum-confidence: high
|
||||
override-mode: inactive
|
||||
protections:
|
||||
csrf-protection: inactive
|
||||
error-disclosure: inactive
|
||||
non-valid-http-methods: false
|
||||
open-redirect: inactive
|
||||
anti-bot:
|
||||
injected-URIs: []
|
||||
validated-URIs: []
|
||||
override-mode: inactive
|
||||
snort-signatures:
|
||||
configmap: []
|
||||
override-mode: inactive
|
||||
openapi-schema-validation:
|
||||
configmap: []
|
||||
override-mode: inactive
|
||||
|
||||
log-triggers:
|
||||
- name: appsec-default-log-trigger
|
||||
access-control-logging:
|
||||
allow-events: false
|
||||
drop-events: true
|
||||
additional-suspicious-events-logging:
|
||||
enabled: true
|
||||
minimum-severity: high
|
||||
response-body: false
|
||||
response-code: true
|
||||
appsec-logging:
|
||||
all-web-requests: false
|
||||
detect-events: true
|
||||
prevent-events: true
|
||||
extended-logging:
|
||||
http-headers: false
|
||||
request-body: false
|
||||
url-path: true
|
||||
url-query: true
|
||||
log-destination:
|
||||
cloud: false
|
||||
stdout:
|
||||
format: json
|
||||
- name: npm-managed-log-trigger-proxyhost-1
|
||||
access-control-logging:
|
||||
allow-events: false
|
||||
drop-events: true
|
||||
additional-suspicious-events-logging:
|
||||
enabled: true
|
||||
minimum-severity: high
|
||||
response-body: false
|
||||
appsec-logging:
|
||||
all-web-requests: false
|
||||
detect-events: true
|
||||
prevent-events: true
|
||||
extended-logging:
|
||||
http-headers: false
|
||||
request-body: false
|
||||
url-path: false
|
||||
url-query: false
|
||||
log-destination:
|
||||
cloud: false
|
||||
stdout:
|
||||
format: json
|
||||
|
||||
custom-responses:
|
||||
- name: appsec-default-web-user-response
|
||||
mode: response-code-only
|
||||
http-response-code: 403
|
16
backend/templates/openappsec.conf
Executable file
16
backend/templates/openappsec.conf
Executable file
@@ -0,0 +1,16 @@
|
||||
policies:
|
||||
default:
|
||||
triggers:
|
||||
- appsec-default-log-trigger
|
||||
mode: inactive
|
||||
practices:
|
||||
- webapp-default-practice
|
||||
custom-response: appsec-default-web-user-response
|
||||
specific-rules:
|
||||
- host: {{ domain_names.first }}
|
||||
triggers:
|
||||
- appsec-default-log-trigger
|
||||
mode: {{openappsec_mode}}
|
||||
practices:
|
||||
- webapp-default-practice
|
||||
custom-response: appsec-default-web-user-response
|
@@ -1,10 +1,10 @@
|
||||
version: '3.8'
|
||||
version: '3.3'
|
||||
# docker compose for npm open-appsec integration
|
||||
|
||||
services:
|
||||
appsec-npm:
|
||||
container_name: appsec-npm
|
||||
image: 'ghcr.io/openappsec/appsec-npm:latest'
|
||||
container_name: npm-attachment
|
||||
image: 'ghcr.io/openappsec/nginx-proxy-manager-attachment:latest'
|
||||
ipc: host
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
|
@@ -42,6 +42,7 @@ log-triggers:
|
||||
enabled: true
|
||||
minimum-severity: high
|
||||
response-body: false
|
||||
response-code: true
|
||||
appsec-logging:
|
||||
all-web-requests: false
|
||||
detect-events: true
|
||||
@@ -49,8 +50,8 @@ log-triggers:
|
||||
extended-logging:
|
||||
http-headers: false
|
||||
request-body: false
|
||||
url-path: false
|
||||
url-query: false
|
||||
url-path: true
|
||||
url-query: true
|
||||
log-destination:
|
||||
cloud: false
|
||||
stdout:
|
||||
|
@@ -42,9 +42,20 @@ WORKDIR /app
|
||||
RUN yarn install \
|
||||
&& yarn cache clean
|
||||
|
||||
# add open-appsec attachment
|
||||
RUN mkdir -p /usr/lib/nginx
|
||||
RUN mkdir -p /usr/lib/nginx/modules
|
||||
COPY docker/lib/libngx_module.so /usr/lib/nginx/modules/libngx_module.so
|
||||
COPY docker/lib/libosrc_nginx_attachment_util.so /usr/lib/libosrc_nginx_attachment_util.so
|
||||
COPY docker/lib/libosrc_compression_utils.so /usr/lib/libosrc_compression_utils.so
|
||||
COPY docker/lib/libosrc_shmem_ipc.so /usr/lib/libosrc_shmem_ipc.so
|
||||
|
||||
# add late to limit cache-busting by modifications
|
||||
COPY docker/rootfs /
|
||||
|
||||
# patch nginx.conf for open-appsec attachment
|
||||
RUN sed -i -e '/include \/etc\/nginx\/modules\/\*\.conf/a\load_module /usr/lib/nginx/modules/libngx_module.so;' -e '/http {/a\\tcp_worker_processes auto;' /etc/nginx/nginx.conf
|
||||
|
||||
# Remove frontend service not required for prod, dev nginx config as well
|
||||
RUN rm -rf /etc/s6-overlay/s6-rc.d/user/contents.d/frontend /etc/nginx/conf.d/dev.conf \
|
||||
&& chmod 644 /etc/logrotate.d/nginx-proxy-manager \
|
||||
|
@@ -33,6 +33,8 @@ services:
|
||||
volumes:
|
||||
- npm_data:/data
|
||||
- le_data:/etc/letsencrypt
|
||||
- ../open-appsec-agent-deployment/localconfig:/ext/appsec
|
||||
- ../open-appsec-agent-deployment/logs:/ext/appsec-logs
|
||||
- ../backend:/app
|
||||
- ../frontend:/app/frontend
|
||||
- ../global:/app/global
|
||||
|
BIN
docker/lib/libngx_module.so
Normal file
BIN
docker/lib/libngx_module.so
Normal file
Binary file not shown.
BIN
docker/lib/libosrc_compression_utils.so
Normal file
BIN
docker/lib/libosrc_compression_utils.so
Normal file
Binary file not shown.
BIN
docker/lib/libosrc_nginx_attachment_util.so
Normal file
BIN
docker/lib/libosrc_nginx_attachment_util.so
Normal file
Binary file not shown.
BIN
docker/lib/libosrc_shmem_ipc.so
Normal file
BIN
docker/lib/libosrc_shmem_ipc.so
Normal file
Binary file not shown.
@@ -1808,7 +1808,16 @@ array-unique@^0.3.2:
|
||||
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
|
||||
integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
|
||||
|
||||
asn1.js@^5.2.0, asn1.js@^5.4.1:
|
||||
asn1.js@^4.0.0:
|
||||
version "4.10.1"
|
||||
resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0"
|
||||
integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==
|
||||
dependencies:
|
||||
bn.js "^4.0.0"
|
||||
inherits "^2.0.1"
|
||||
minimalistic-assert "^1.0.0"
|
||||
|
||||
asn1.js@^5.4.1:
|
||||
version "5.4.1"
|
||||
resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
|
||||
integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==
|
||||
@@ -2056,10 +2065,10 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9:
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
|
||||
integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
|
||||
|
||||
bn.js@^5.0.0, bn.js@^5.1.2, bn.js@^5.2.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70"
|
||||
integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==
|
||||
bn.js@^5.1.1, bn.js@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.2.tgz#c9686902d3c9a27729f43ab10f9d79c2004da7b0"
|
||||
integrity sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==
|
||||
|
||||
body-parser@1.19.2, body-parser@^1.19.0:
|
||||
version "1.19.2"
|
||||
@@ -2175,28 +2184,28 @@ browserify-des@^1.0.0, browserify-des@^1.0.2:
|
||||
inherits "^2.0.1"
|
||||
safe-buffer "^5.1.2"
|
||||
|
||||
browserify-rsa@^4.0.0, browserify-rsa@^4.0.1, browserify-rsa@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d"
|
||||
integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==
|
||||
browserify-rsa@^4.0.0, browserify-rsa@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524"
|
||||
integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=
|
||||
dependencies:
|
||||
bn.js "^5.0.0"
|
||||
bn.js "^4.1.0"
|
||||
randombytes "^2.0.1"
|
||||
|
||||
browserify-sign@^4.0.0, browserify-sign@^4.2.1:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.2.tgz#e78d4b69816d6e3dd1c747e64e9947f9ad79bc7e"
|
||||
integrity sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3"
|
||||
integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==
|
||||
dependencies:
|
||||
bn.js "^5.2.1"
|
||||
browserify-rsa "^4.1.0"
|
||||
bn.js "^5.1.1"
|
||||
browserify-rsa "^4.0.1"
|
||||
create-hash "^1.2.0"
|
||||
create-hmac "^1.1.7"
|
||||
elliptic "^6.5.4"
|
||||
elliptic "^6.5.3"
|
||||
inherits "^2.0.4"
|
||||
parse-asn1 "^5.1.6"
|
||||
readable-stream "^3.6.2"
|
||||
safe-buffer "^5.2.1"
|
||||
parse-asn1 "^5.1.5"
|
||||
readable-stream "^3.6.0"
|
||||
safe-buffer "^5.2.0"
|
||||
|
||||
browserify-zlib@^0.2.0:
|
||||
version "0.2.0"
|
||||
@@ -3739,7 +3748,7 @@ electron-to-chromium@^1.3.522, electron-to-chromium@^1.3.719:
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.739.tgz#f07756aa92cabd5a6eec6f491525a64fe62f98b9"
|
||||
integrity sha512-+LPJVRsN7hGZ9EIUUiWCpO7l4E3qBYHNadazlucBfsXBbccDFNKUBAgzE68FnkWGJPwD/AfKhSzL+G+Iqb8A4A==
|
||||
|
||||
elliptic@^6.5.3, elliptic@^6.5.4:
|
||||
elliptic@^6.5.3:
|
||||
version "6.5.4"
|
||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
|
||||
integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
|
||||
@@ -7186,13 +7195,14 @@ parent-module@^1.0.0:
|
||||
dependencies:
|
||||
callsites "^3.0.0"
|
||||
|
||||
parse-asn1@^5.0.0, parse-asn1@^5.1.5, parse-asn1@^5.1.6:
|
||||
version "5.1.6"
|
||||
resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4"
|
||||
integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==
|
||||
parse-asn1@^5.0.0, parse-asn1@^5.1.5:
|
||||
version "5.1.5"
|
||||
resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e"
|
||||
integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==
|
||||
dependencies:
|
||||
asn1.js "^5.2.0"
|
||||
asn1.js "^4.0.0"
|
||||
browserify-aes "^1.0.0"
|
||||
create-hash "^1.1.0"
|
||||
evp_bytestokey "^1.0.0"
|
||||
pbkdf2 "^3.0.3"
|
||||
safe-buffer "^5.1.1"
|
||||
@@ -8056,10 +8066,10 @@ rc@^1.2.8:
|
||||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.5.0, readable-stream@^3.6.0, readable-stream@^3.6.2:
|
||||
version "3.6.2"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
|
||||
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
|
||||
"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.5.0, readable-stream@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
|
||||
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
string_decoder "^1.1.1"
|
||||
|
68
frontend/app-images/open-appsec-logo.svg
Executable file
68
frontend/app-images/open-appsec-logo.svg
Executable file
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="873.41901"
|
||||
height="838.49463"
|
||||
viewBox="0 0 873.41901 838.49463"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
sodipodi:docname="open-appsec-logo.svg"
|
||||
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview2"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.27439024"
|
||||
inkscape:cx="1568.9333"
|
||||
inkscape:cy="351.68889"
|
||||
inkscape:window-width="1680"
|
||||
inkscape:window-height="979"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<defs
|
||||
id="defs1">
|
||||
<clipPath
|
||||
id="clip-Without_CP">
|
||||
<rect
|
||||
width="3444"
|
||||
height="979"
|
||||
id="rect1"
|
||||
x="0"
|
||||
y="0" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g
|
||||
id="Group_1_copy-2"
|
||||
data-name="Group 1 copy"
|
||||
transform="translate(-16.001,-12)">
|
||||
<path
|
||||
id="Shape_3"
|
||||
data-name="Shape 3"
|
||||
d="m 943.882,813.9 c 0,0 -76.84,12.621 -141.535,-1.855 -63.8,-14.276 -115.462,-55.652 -115.462,-55.652 0,0 -87.913,63.539 -176.918,63.072 C 422.06,819 333.048,754.536 333.048,754.536 c 0,0 -41.188,37.694 -102.426,51.942 -66.14,15.389 -152.708,7.42 -152.708,7.42 v 77.913 c 0,0 83.521,12.22 154.57,0 56.763,-9.763 104.289,-44.522 104.289,-44.522 0,0 68.9,50.51 176.918,50.087 95.062,-0.372 171.332,-51.942 171.332,-51.942 0,0 54.106,35.641 113.6,46.377 68.383,12.34 143.4,0 143.4,0 z"
|
||||
transform="translate(-61.913,-46.884)"
|
||||
fill="#ff0b05" />
|
||||
<path
|
||||
id="Shape_2"
|
||||
data-name="Shape 2"
|
||||
d="m 727.624,274.073 c 0,0 21.518,103.827 16.76,192.927 -4.549,85.193 -37.246,157.681 -37.246,157.681 0,0 76.431,26.129 121.05,61.219 43.834,34.471 57.731,76.058 57.731,76.058 0,0 -53.385,9.1 -102.427,-9.275 -51.955,-19.463 -98.7,-55.652 -98.7,-55.652 0,0 -79.692,64.725 -175.057,63.069 C 409,758.354 342.128,697.029 342.128,697.029 c 0,0 -54.86,40.153 -111.738,57.507 -45.1,13.761 -93.115,7.42 -93.115,7.42 0,0 38.01,-96.083 169.469,-133.565 64.982,-18.528 154.034,-27.082 225.338,-25.971 66.292,1.033 117.325,11.13 117.325,11.13 0,0 44.715,-82.589 57.731,-176.232 12.091,-86.981 -14.9,-181.8 -14.9,-181.8 z"
|
||||
transform="translate(-61.682,-46.884)"
|
||||
fill="#8b1548" />
|
||||
<path
|
||||
id="Shape_1"
|
||||
data-name="Shape 1"
|
||||
d="m 730.221,146.072 c 0,0 -28.435,-37.192 -67.042,-59.362 -40.495,-23.253 -89.39,-27.826 -89.39,-27.826 0,0 43.638,22.244 67.043,44.522 17.685,16.834 14.9,31.536 14.9,31.536 0,0 -56.372,-17.173 -109.876,7.42 -103.573,47.607 -119.189,118.725 -119.189,118.725 0,0 48.89,-43.93 85.666,-61.217 45.218,-21.256 76.354,-14.841 76.354,-14.841 a 9.365,9.365 0 0 1 9.311,3.71 c 5.734,7.85 -1.862,16.7 -1.862,16.7 0,0 -61.447,49.466 -72.63,105.739 -16.1,81.044 26.072,170.667 26.072,170.667 0,0 -19.194,-84.193 20.485,-153.971 16.627,-29.239 47.676,-63.819 74.492,-83.478 30.46,-22.331 55.869,-27.826 55.869,-27.826 0,0 36.166,6.686 68.905,55.652 12.261,18.339 26.846,42.126 31.659,66.783 11.488,58.848 1.862,133.565 1.862,133.565 0,0 41.487,-47.813 44.7,-128 2.718,-67.941 -37.246,-113.159 -37.246,-113.159 0,0 -10.414,-12.34 -9.581,-17.736 1.012,-6.55 13.305,-6.38 13.305,-6.38 a 226.339,226.339 0 0 1 59.593,25.971 c 37.294,23.409 76.354,59.362 76.354,59.362 0,0 -31.733,-74.906 -96.839,-116.87 -38.15,-24.592 -122.915,-29.686 -122.915,-29.686 z"
|
||||
transform="translate(-60.555,-46.884)"
|
||||
fill="#ff2885" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.8 KiB |
@@ -1,9 +1,9 @@
|
||||
<% var title = 'Login – Nginx Proxy Manager' %>
|
||||
<%- include partials/header.ejs %>
|
||||
|
||||
<div class="page" id="login" data-version="<%= version %>">
|
||||
<div class="page" id="login" data-version="<%- version %>">
|
||||
<span class="loader"></span>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="/js/login.bundle.js?v=<%= version %>"></script>
|
||||
<script type="text/javascript" src="/js/login.bundle.js?v=<%- version %>"></script>
|
||||
<%- include partials/footer.ejs %>
|
||||
|
@@ -21,6 +21,7 @@
|
||||
<meta name="msapplication-TileColor" content="#333333">
|
||||
<meta name="msapplication-config" content="/images/favicons/browserconfig.xml">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@600&display=swap" rel="stylesheet">
|
||||
<link href="/css/main.css?v=<%= version %>" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
|
@@ -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) {
|
||||
@@ -716,6 +722,17 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
OpenappsecLog: {
|
||||
/**
|
||||
* @param {Array} [expand]
|
||||
* @param {String} [query]
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getAll: function (expand, query) {
|
||||
return getAllObjects('openappsec-log', expand, query);
|
||||
},
|
||||
},
|
||||
|
||||
Reports: {
|
||||
|
||||
/**
|
||||
@@ -753,5 +770,22 @@ module.exports = {
|
||||
delete data.id;
|
||||
return fetch('put', 'settings/' + id, data);
|
||||
}
|
||||
},
|
||||
|
||||
OpenAppsecSettings: {
|
||||
/**
|
||||
* @returns {Promise}
|
||||
*/
|
||||
get: function () {
|
||||
return fetch('get', 'openappsec-settings');
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Object} data
|
||||
* @returns {Promise}
|
||||
*/
|
||||
save: function (data) {
|
||||
return fetch('put', 'openappsec-settings', data);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@@ -407,6 +407,40 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* openappsec Log
|
||||
*/
|
||||
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/page/' + page);
|
||||
|
||||
// Show the view with the data
|
||||
App.UI.showAppContent(new View({
|
||||
page: page,
|
||||
perPage: 50
|
||||
}));
|
||||
});
|
||||
} else {
|
||||
this.showDashboard();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* openappsec Log Metadata
|
||||
*
|
||||
* @param model
|
||||
*/
|
||||
showOpenappsecMeta: function (model) {
|
||||
if (Cache.User.isAdmin()) {
|
||||
require(['./main', './openappsec-log/meta'], function (App, View) {
|
||||
App.UI.showModalDialog(new View({model: model}));
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Settings
|
||||
*/
|
||||
|
@@ -72,6 +72,7 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="custom-switch">
|
||||
@@ -90,6 +91,40 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<hr class="my-3">
|
||||
|
||||
<div class="form-group">
|
||||
<label class="custom-switch">
|
||||
<input type="checkbox" class="custom-switch-input" name="use_openappsec" value="1"<%- use_openappsec ? ' checked' : '' %>>
|
||||
<span class="custom-switch-indicator"></span>
|
||||
<span class="custom-switch-description font-weight-bold">open-appsec</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-6 col-form-label <%- use_openappsec ? '' : ' text-muted' %>">Enforcement Mode</label>
|
||||
<select name="openappsec_mode" class="col-sm-4 form-control custom-select" placeholder="Please Select" <%- use_openappsec ? '' : ' disabled' %>>
|
||||
<option value="detect-learn" <%- openappsec_mode === 'detect-learn' ? 'selected' : '' %>>Detect-Learn</option>
|
||||
<option value="prevent-learn" <%- openappsec_mode === 'prevent-learn' ? 'selected' : '' %>>Prevent-Learn</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-6 col-form-label <%- use_openappsec ? '' : ' text-muted' %>">Minimum confidence for prevent</label>
|
||||
<select name="minimum_confidence" class="col-sm-4 form-control custom-select" placeholder="Please Select" <%- use_openappsec ? '' : ' disabled' %>>
|
||||
<option value="critical" <%- minimum_confidence === 'critical' ? 'selected' : '' %>>Critical</option>
|
||||
<option value="high" <%- minimum_confidence === 'high' ? 'selected' : '' %>>High</option>
|
||||
<option value="medium" <%- minimum_confidence === 'medium' ? 'selected' : '' %>>Medium</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@@ -33,6 +33,9 @@ module.exports = Mn.View.extend({
|
||||
certificate_select: 'select[name="certificate_id"]',
|
||||
access_list_select: 'select[name="access_list_id"]',
|
||||
ssl_forced: 'input[name="ssl_forced"]',
|
||||
use_openappsec: 'input[name="use_openappsec"]',
|
||||
openappsec_mode: 'select[name="openappsec_mode"]',
|
||||
minimum_confidence: 'select[name="minimum_confidence"]',
|
||||
hsts_enabled: 'input[name="hsts_enabled"]',
|
||||
hsts_subdomains: 'input[name="hsts_subdomains"]',
|
||||
http2_support: 'input[name="http2_support"]',
|
||||
@@ -75,6 +78,24 @@ module.exports = Mn.View.extend({
|
||||
inputs.trigger('change');
|
||||
},
|
||||
|
||||
'change @ui.use_openappsec': function () {
|
||||
let checked = this.ui.use_openappsec.prop('checked');
|
||||
this.ui.openappsec_mode
|
||||
.prop('disabled', !checked)
|
||||
.parents('.form-group')
|
||||
.css('opacity', checked ? 1 : 0.5);
|
||||
|
||||
this.ui.minimum_confidence
|
||||
.prop('disabled', !checked)
|
||||
.parents('.form-group')
|
||||
.css('opacity', checked ? 1 : 0.5);
|
||||
|
||||
/*** check this */
|
||||
if (!checked) {
|
||||
this.ui.openappsec_mode.prop('checked', false);
|
||||
}
|
||||
},
|
||||
|
||||
'change @ui.ssl_forced': function () {
|
||||
let checked = this.ui.ssl_forced.prop('checked');
|
||||
this.ui.hsts_enabled
|
||||
@@ -146,14 +167,32 @@ module.exports = Mn.View.extend({
|
||||
}
|
||||
|
||||
let view = this;
|
||||
let data = this.ui.form.serializeJSON();
|
||||
|
||||
// Add locations
|
||||
// enable openappsec inputs for serialization. if we don't do this, serialization of custom locations will be added to the root object instead of the missing root properties with the same name.
|
||||
|
||||
let topHostDisabledElements = this.ui.form.find('#details [name*="openappsec"]:disabled, [name="minimum_confidence"]:disabled');
|
||||
topHostDisabledElements.prop('disabled', false);
|
||||
let data = this.ui.form.serializeJSON();
|
||||
topHostDisabledElements.prop('disabled', true);
|
||||
|
||||
// Check if openappsec is enabled. serializeJSON() will only return its value attribute, not its checked attribute.
|
||||
let use_openappsec = this.ui.use_openappsec.prop('checked');
|
||||
data.use_openappsec = use_openappsec;
|
||||
|
||||
// Add locations using the model defined in file frontend/js/app/nginx/proxy/location.js and the template frontend/js/app/nginx/proxy/location-item.ejs.
|
||||
// input fields with the class 'model' will automatically update the model.
|
||||
data.locations = [];
|
||||
this.locationsCollection.models.forEach((location) => {
|
||||
data.locations.push(location.toJSON());
|
||||
});
|
||||
|
||||
// convert all "false" strings to false booleans in data.
|
||||
Object.keys(data).forEach(key => {
|
||||
if (typeof data[key] === 'string' && data[key].toLowerCase() === 'false') {
|
||||
data[key] = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Serialize collects path from custom locations
|
||||
// This field must be removed from root object
|
||||
delete data.path;
|
||||
@@ -161,6 +200,7 @@ module.exports = Mn.View.extend({
|
||||
// Manipulate
|
||||
data.forward_port = parseInt(data.forward_port, 10);
|
||||
data.block_exploits = !!data.block_exploits;
|
||||
data.use_openappsec = !!data.use_openappsec;
|
||||
data.caching_enabled = !!data.caching_enabled;
|
||||
data.allow_websocket_upgrade = !!data.allow_websocket_upgrade;
|
||||
data.http2_support = !!data.http2_support;
|
||||
@@ -266,6 +306,7 @@ module.exports = Mn.View.extend({
|
||||
|
||||
this.ui.ssl_forced.trigger('change');
|
||||
this.ui.hsts_enabled.trigger('change');
|
||||
this.ui.use_openappsec.trigger('change');
|
||||
|
||||
// Domain names
|
||||
this.ui.domain_names.selectize({
|
||||
|
@@ -16,7 +16,7 @@
|
||||
<div class="col-auto">
|
||||
<div class="selectgroup">
|
||||
<label class="selectgroup-item">
|
||||
<input type="checkbox" class="selectgroup-input">
|
||||
<input id="advanced_config_toggle" type="checkbox" class="selectgroup-input">
|
||||
<span class="selectgroup-button">
|
||||
<i class="fe fe-settings"></i>
|
||||
</span>
|
||||
@@ -56,6 +56,41 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-3"/>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="custom-switch">
|
||||
<input type="checkbox" name="use_openappsec" id="use_openappsec" class="custom-switch-input" value="1"<%- use_openappsec ? ' checked' : '' %>>
|
||||
<span class="custom-switch-indicator"></span>
|
||||
<span class="custom-switch-description font-weight-bold">open-appsec</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-7 col-form-label <%- use_openappsec ? '' : ' text-muted' %>">Enforcement Mode</label>
|
||||
<select name="openappsec_mode" class="col-sm-4 form-control custom-select model" placeholder="Please Select" <%- use_openappsec ? '' : ' disabled' %>>
|
||||
<option value="detect-learn" <%- openappsec_mode === 'detect-learn' ? 'selected' : '' %>>Detect-Learn</option>
|
||||
<option value="prevent-learn" <%- openappsec_mode === 'prevent-learn' ? 'selected' : '' %>>Prevent-Learn</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-7 col-form-label <%- use_openappsec ? '' : ' text-muted' %>">Minimum confidence for prevent</label>
|
||||
<select name="minimum_confidence" class="col-sm-4 form-control custom-select model" placeholder="Please Select" <%- use_openappsec ? '' : ' disabled' %>>
|
||||
<option value="critical" <%- minimum_confidence === 'critical' ? 'selected' : '' %>>Critical</option>
|
||||
<option value="high" <%- minimum_confidence === 'high' ? 'selected' : '' %>>High</option>
|
||||
<option value="medium" <%- minimum_confidence === 'medium' ? 'selected' : '' %>>Medium</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="#" class="card-link location-delete">
|
||||
<i class="fa fa-trash"></i> <%- i18n('locations', 'delete') %>
|
||||
|
@@ -7,7 +7,10 @@ const LocationView = Mn.View.extend({
|
||||
className: 'location_block',
|
||||
|
||||
ui: {
|
||||
toggle: 'input[type="checkbox"]',
|
||||
use_openappsec: 'input[name="use_openappsec"]',
|
||||
openappsec_mode: 'select[name="openappsec_mode"]',
|
||||
minimum_confidence: 'select[name="minimum_confidence"]',
|
||||
toggle: 'input[type="checkbox"]#advanced_config_toggle',
|
||||
config: '.config',
|
||||
delete: '.location-delete'
|
||||
},
|
||||
@@ -21,6 +24,27 @@ const LocationView = Mn.View.extend({
|
||||
}
|
||||
},
|
||||
|
||||
'change @ui.use_openappsec': function () {
|
||||
let checked = this.ui.use_openappsec.prop('checked');
|
||||
this.model.set('use_openappsec', checked);
|
||||
|
||||
this.ui.openappsec_mode
|
||||
.prop('disabled', !checked)
|
||||
.parents('.form-group')
|
||||
.css('opacity', checked ? 1 : 0.5);
|
||||
|
||||
this.ui.minimum_confidence
|
||||
.prop('disabled', !checked)
|
||||
.parents('.form-group')
|
||||
.css('opacity', checked ? 1 : 0.5);
|
||||
|
||||
/*** check this */
|
||||
if (!checked) {
|
||||
this.ui.openappsec_mode.prop('checked', false);
|
||||
}
|
||||
},
|
||||
|
||||
// input fields with the class 'model' will automatically update the model.
|
||||
'change .model': function (e) {
|
||||
const map = {};
|
||||
map[e.target.name] = e.target.value;
|
||||
|
@@ -129,6 +129,7 @@ module.exports = Mn.View.extend({
|
||||
|
||||
// Manipulate
|
||||
data.block_exploits = !!data.block_exploits;
|
||||
data.use_openappsec = !!data.use_openappsec;
|
||||
data.preserve_path = !!data.preserve_path;
|
||||
data.http2_support = !!data.http2_support;
|
||||
data.hsts_enabled = !!data.hsts_enabled;
|
||||
|
16
frontend/js/app/openappsec-log/list-all/item.ejs
Normal file
16
frontend/js/app/openappsec-log/list-all/item.ejs
Normal file
@@ -0,0 +1,16 @@
|
||||
<td class="text-right"><a href="#" class="meta btn btn-secondary btn-sm">open</a></td>
|
||||
<td class="text-nowrap"><%- formatDbDate(eventTime, 'MMM DD YYYY, H:mm') %></td>
|
||||
<%= createSpecificTableCell(eventSeverity) %>
|
||||
<%= createSpecificTableCell(assetName) %>
|
||||
<%= createSpecificTableCell(securityAction) %>
|
||||
<%= createSpecificTableCell(waapIncidentType) %>
|
||||
<%= createSpecificTableCell(httpSourceId) %>
|
||||
<%= createSpecificTableCell(sourceIP) %>
|
||||
<%= createSpecificTableCell(proxyIP) %>
|
||||
<%= createSpecificTableCell(httpHostName) %>
|
||||
<%= createSpecificTableCell(httpMethod) %>
|
||||
<%= createSpecificTableCell(httpResponseCode) %>
|
||||
<%= createSpecificTableCell(httpUriPath) %>
|
||||
<%= createSpecificTableCell(matchedLocation) %>
|
||||
<%= createSpecificTableCell(matchedParameter) %>
|
||||
<%= createSpecificTableCell(matchedSample) %>
|
40
frontend/js/app/openappsec-log/list-all/item.js
Normal file
40
frontend/js/app/openappsec-log/list-all/item.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const Mn = require('backbone.marionette');
|
||||
const Controller = require('../../controller');
|
||||
const template = require('./item.ejs');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
template: template,
|
||||
tagName: 'tr',
|
||||
|
||||
ui: {
|
||||
meta: 'a.meta'
|
||||
},
|
||||
|
||||
events: {
|
||||
'click @ui.meta': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showOpenappsecMeta(this.model);
|
||||
}
|
||||
},
|
||||
|
||||
templateContext: {
|
||||
more: function() {
|
||||
switch (this.object_type) {
|
||||
case 'redirection-host':
|
||||
case 'stream':
|
||||
case 'proxy-host':
|
||||
return this.meta.domain_names.join(', ');
|
||||
}
|
||||
|
||||
return '#' + (this.object_id || '?');
|
||||
},
|
||||
createSpecificTableCell: function(value) {
|
||||
if (value && value.trim() !== '') {
|
||||
return `<td>${value}</td>`;
|
||||
} else {
|
||||
return `<td class="text-center">-</td>`;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
21
frontend/js/app/openappsec-log/list-all/main.ejs
Normal file
21
frontend/js/app/openappsec-log/list-all/main.ejs
Normal file
@@ -0,0 +1,21 @@
|
||||
<thead class="sticky-top bg-white">
|
||||
<th> </th>
|
||||
<th>Time</th>
|
||||
<th>Event Severity</th>
|
||||
<th>Asset Name</th>
|
||||
<th>Security Action</th>
|
||||
<th><span class="text-nowrap">AppSec Incident</span> Type</th>
|
||||
<th>Source Identifier</th>
|
||||
<th>Source IP</th>
|
||||
<th>Proxy IP</th>
|
||||
<th>HTTP Host</th>
|
||||
<th>HTTP Method</th>
|
||||
<th><span class="text-nowrap">HTTP Response</span> Code</th>
|
||||
<th><span class="text-nowrap">HTTP URI</span> Path</th>
|
||||
<th>Matched Location</th>
|
||||
<th>Matched Parameter</th>
|
||||
<th>Matched Sample</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- items -->
|
||||
</tbody>
|
49
frontend/js/app/openappsec-log/list-all/main.js
Normal file
49
frontend/js/app/openappsec-log/list-all/main.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const Mn = require('backbone.marionette');
|
||||
const ItemView = require('./item');
|
||||
const template = require('./main.ejs');
|
||||
|
||||
let TableBody = Mn.CollectionView.extend({
|
||||
tagName: 'tbody',
|
||||
childView: ItemView,
|
||||
|
||||
initialize: function (options) {
|
||||
this.options = new Backbone.Model(options);
|
||||
// this.page = options.page;
|
||||
// this.perPage = options.perPage;
|
||||
this.updatePage();
|
||||
// this.listenTo(this.options, 'change:page', this.updatePage);
|
||||
},
|
||||
|
||||
updatePage: function () {
|
||||
let perPage = this.perPage || this.collection.length;
|
||||
let page = this.page || 1;
|
||||
let models;
|
||||
if (this.perPage && this.page) {
|
||||
models = this.collection.models.slice((page - 1) * perPage, page * perPage);
|
||||
} else {
|
||||
models = this.collection.models;
|
||||
}
|
||||
this.collection.reset(models);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
tagName: 'table',
|
||||
className: 'table table-hover table-outline table-vcenter card-table',
|
||||
template: template,
|
||||
|
||||
regions: {
|
||||
body: {
|
||||
el: 'tbody',
|
||||
replaceElement: true
|
||||
}
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
this.showChildView('body', new TableBody({
|
||||
collection: this.collection,
|
||||
// page: this.options.page,
|
||||
// perPage: this.options.perPage
|
||||
}));
|
||||
}
|
||||
});
|
16
frontend/js/app/openappsec-log/list-important/item.ejs
Normal file
16
frontend/js/app/openappsec-log/list-important/item.ejs
Normal file
@@ -0,0 +1,16 @@
|
||||
<td class="text-right"><a href="#" class="meta btn btn-secondary btn-sm">open</a></td>
|
||||
<td class="text-nowrap"><%- formatDbDate(eventTime, 'MMM DD YYYY, H:mm') %></td>
|
||||
<%= createSpecificTableCell(eventSeverity) %>
|
||||
<%= createSpecificTableCell(assetName) %>
|
||||
<%= createSpecificTableCell(securityAction) %>
|
||||
<%= createSpecificTableCell(waapIncidentType) %>
|
||||
<%= createSpecificTableCell(httpSourceId) %>
|
||||
<%= createSpecificTableCell(sourceIP) %>
|
||||
<%= createSpecificTableCell(proxyIP) %>
|
||||
<%= createSpecificTableCell(httpHostName) %>
|
||||
<%= createSpecificTableCell(httpMethod) %>
|
||||
<%= createSpecificTableCell(httpResponseCode) %>
|
||||
<%= createSpecificTableCell(httpUriPath) %>
|
||||
<%= createSpecificTableCell(matchedLocation) %>
|
||||
<%= createSpecificTableCell(matchedParameter) %>
|
||||
<%= createSpecificTableCell(matchedSample) %>
|
39
frontend/js/app/openappsec-log/list-important/item.js
Normal file
39
frontend/js/app/openappsec-log/list-important/item.js
Normal file
@@ -0,0 +1,39 @@
|
||||
const Mn = require('backbone.marionette');
|
||||
const Controller = require('../../controller');
|
||||
const template = require('./item.ejs');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
template: template,
|
||||
tagName: 'tr',
|
||||
|
||||
ui: {
|
||||
meta: 'a.meta'
|
||||
},
|
||||
|
||||
events: {
|
||||
'click @ui.meta': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showOpenappsecMeta(this.model);
|
||||
}
|
||||
},
|
||||
|
||||
templateContext: {
|
||||
more: function() {
|
||||
switch (this.object_type) {
|
||||
case 'redirection-host':
|
||||
case 'stream':
|
||||
case 'proxy-host':
|
||||
return this.meta.domain_names.join(', ');
|
||||
}
|
||||
|
||||
return '#' + (this.object_id || '?');
|
||||
},
|
||||
createSpecificTableCell: function(value) {
|
||||
if (value && value.trim() !== '') {
|
||||
return `<td>${value}</td>`;
|
||||
} else {
|
||||
return `<td class="text-center">-</td>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
21
frontend/js/app/openappsec-log/list-important/main.ejs
Normal file
21
frontend/js/app/openappsec-log/list-important/main.ejs
Normal file
@@ -0,0 +1,21 @@
|
||||
<thead class="sticky-top bg-white">
|
||||
<th> </th>
|
||||
<th>Time</th>
|
||||
<th>Event Severity</th>
|
||||
<th>Asset Name</th>
|
||||
<th>Security Action</th>
|
||||
<th><span class="text-nowrap">AppSec Incident</span> Type</th>
|
||||
<th>Source Identifier</th>
|
||||
<th>Source IP</th>
|
||||
<th>Proxy IP</th>
|
||||
<th>HTTP Host</th>
|
||||
<th>HTTP Method</th>
|
||||
<th><span class="text-nowrap">HTTP Response</span> Code</th>
|
||||
<th><span class="text-nowrap">HTTP URI</span> Path</th>
|
||||
<th>Matched Location</th>
|
||||
<th>Matched Parameter</th>
|
||||
<th>Matched Sample</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- items -->
|
||||
</tbody>
|
42
frontend/js/app/openappsec-log/list-important/main.js
Normal file
42
frontend/js/app/openappsec-log/list-important/main.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const Mn = require('backbone.marionette');
|
||||
const ItemView = require('./item');
|
||||
const template = require('./main.ejs');
|
||||
|
||||
let TableBody = Mn.CollectionView.extend({
|
||||
tagName: 'tbody',
|
||||
childView: ItemView,
|
||||
|
||||
initialize: function (options) {
|
||||
this.options = new Backbone.Model(options);
|
||||
this.page = options.page;
|
||||
this.perPage = options.perPage;
|
||||
this.updatePage();
|
||||
this.listenTo(this.options, 'change:page', this.updatePage);
|
||||
},
|
||||
|
||||
updatePage: function () {
|
||||
let models = this.collection.models.slice((this.page - 1) * this.perPage, this.page * this.perPage);
|
||||
this.collection.reset(models);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
tagName: 'table',
|
||||
className: 'table table-hover table-outline table-vcenter card-table',
|
||||
template: template,
|
||||
|
||||
regions: {
|
||||
body: {
|
||||
el: 'tbody',
|
||||
replaceElement: true
|
||||
}
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
this.showChildView('body', new TableBody({
|
||||
collection: this.collection,
|
||||
page: this.options.page,
|
||||
perPage: this.options.perPage
|
||||
}));
|
||||
}
|
||||
});
|
@@ -0,0 +1,8 @@
|
||||
<td class="text-right"><a href="#" class="meta btn btn-secondary btn-sm">open</a></td>
|
||||
<td class="text-nowrap"><%- formatDbDate(eventTime, 'MMM DD YYYY, H:mm') %></td>
|
||||
<%= createSpecificTableCell(eventSeverity) %>
|
||||
<%= createSpecificTableCell(eventPriority) %>
|
||||
<%= createSpecificTableCell(eventTopic) %>
|
||||
<%= createSpecificTableCell(eventName) %>
|
||||
<%= createSpecificTableCell(suggestedRemediation) %>
|
||||
<%= createSpecificTableCell(assetName) %>
|
39
frontend/js/app/openappsec-log/list-notifications/item.js
Normal file
39
frontend/js/app/openappsec-log/list-notifications/item.js
Normal file
@@ -0,0 +1,39 @@
|
||||
const Mn = require('backbone.marionette');
|
||||
const Controller = require('../../controller');
|
||||
const template = require('./item.ejs');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
template: template,
|
||||
tagName: 'tr',
|
||||
|
||||
ui: {
|
||||
meta: 'a.meta'
|
||||
},
|
||||
|
||||
events: {
|
||||
'click @ui.meta': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showOpenappsecMeta(this.model);
|
||||
}
|
||||
},
|
||||
|
||||
templateContext: {
|
||||
more: function() {
|
||||
switch (this.object_type) {
|
||||
case 'redirection-host':
|
||||
case 'stream':
|
||||
case 'proxy-host':
|
||||
return this.meta.domain_names.join(', ');
|
||||
}
|
||||
|
||||
return '#' + (this.object_id || '?');
|
||||
},
|
||||
createSpecificTableCell: function(value) {
|
||||
if (value && value.trim() !== '') {
|
||||
return `<td>${value}</td>`;
|
||||
} else {
|
||||
return `<td class="text-center">-</td>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
13
frontend/js/app/openappsec-log/list-notifications/main.ejs
Normal file
13
frontend/js/app/openappsec-log/list-notifications/main.ejs
Normal file
@@ -0,0 +1,13 @@
|
||||
<thead class="sticky-top bg-white">
|
||||
<th> </th>
|
||||
<th>Time</th>
|
||||
<th>Event Severity</th>
|
||||
<th>Event Priority</th>
|
||||
<th>Event Topic</th>
|
||||
<th>Event Name</th>
|
||||
<th>Suggested Remediation if Applicable</th>
|
||||
<th>Asset Name</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- items -->
|
||||
</tbody>
|
42
frontend/js/app/openappsec-log/list-notifications/main.js
Normal file
42
frontend/js/app/openappsec-log/list-notifications/main.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const Mn = require('backbone.marionette');
|
||||
const ItemView = require('./item');
|
||||
const template = require('./main.ejs');
|
||||
|
||||
let TableBody = Mn.CollectionView.extend({
|
||||
tagName: 'tbody',
|
||||
childView: ItemView,
|
||||
|
||||
initialize: function (options) {
|
||||
this.options = new Backbone.Model(options);
|
||||
this.page = options.page;
|
||||
this.perPage = options.perPage;
|
||||
this.updatePage();
|
||||
this.listenTo(this.options, 'change:page', this.updatePage);
|
||||
},
|
||||
|
||||
updatePage: function () {
|
||||
let models = this.collection.models.slice((this.page - 1) * this.perPage, this.page * this.perPage);
|
||||
this.collection.reset(models);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
tagName: 'table',
|
||||
className: 'table table-hover table-outline table-vcenter card-table',
|
||||
template: template,
|
||||
|
||||
regions: {
|
||||
body: {
|
||||
el: 'tbody',
|
||||
replaceElement: true
|
||||
}
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
this.showChildView('body', new TableBody({
|
||||
collection: this.collection,
|
||||
page: this.options.page,
|
||||
perPage: this.options.perPage
|
||||
}));
|
||||
}
|
||||
});
|
77
frontend/js/app/openappsec-log/list/item.ejs
Executable file
77
frontend/js/app/openappsec-log/list/item.ejs
Executable file
@@ -0,0 +1,77 @@
|
||||
|
||||
<td>
|
||||
<div <div class="text-nowrap">
|
||||
<% var sevirityClass = 'bg-success';
|
||||
switch (eventSeverity) {
|
||||
case 'Critical':
|
||||
sevirityClass = 'bg-danger';
|
||||
break;
|
||||
case 'Warning':
|
||||
sevirityClass = 'bg-warning';
|
||||
break;
|
||||
case 'Info':
|
||||
sevirityClass = 'bg-success';
|
||||
//sevirityClass = 'bg-info';
|
||||
break;
|
||||
case 'Debug':
|
||||
sevirityClass = 'bg-success';
|
||||
break;
|
||||
}
|
||||
%>
|
||||
<span class="status-icon <%- sevirityClass %>"></span> <%- eventSeverity %>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<%
|
||||
var items = [];
|
||||
object_type = "test";
|
||||
switch (object_type) {
|
||||
case 'proxy-host':
|
||||
%> <span class="text-success"><i class="fe fe-zap"></i></span> <%
|
||||
items = meta.domain_names;
|
||||
break;
|
||||
case 'redirection-host':
|
||||
%> <span class="text-yellow"><i class="fe fe-shuffle"></i></span> <%
|
||||
items = meta.domain_names;
|
||||
break;
|
||||
case 'stream':
|
||||
%> <span class="text-blue"><i class="fe fe-radio"></i></span> <%
|
||||
items.push(meta.incoming_port);
|
||||
break;
|
||||
case 'dead-host':
|
||||
%> <span class="text-danger"><i class="fe fe-zap-off"></i></span> <%
|
||||
items = meta.domain_names;
|
||||
break;
|
||||
case 'access-list':
|
||||
%> <span class="text-teal"><i class="fe fe-lock"></i></span> <%
|
||||
items.push(meta.name);
|
||||
break;
|
||||
case 'user':
|
||||
%> <span class="text-teal"><i class="fe fe-user"></i></span> <%
|
||||
items.push(meta.name);
|
||||
break;
|
||||
case 'certificate':
|
||||
%> <span class="text-pink"><i class="fe fe-shield"></i></span> <%
|
||||
if (meta.provider === 'letsencrypt') {
|
||||
items = meta.domain_names;
|
||||
} else {
|
||||
//items.push(meta.nice_name);
|
||||
items.push("test");
|
||||
}
|
||||
break;
|
||||
}
|
||||
%><%- eventName %>
|
||||
</div>
|
||||
<div class="small text-muted">
|
||||
<%- formatDbDate(eventTime, 'Do MMMM YYYY, h:mm a') %>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<%- serviceName %>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a href="#" class="meta btn btn-secondary btn-sm">open</a>
|
||||
</td>
|
32
frontend/js/app/openappsec-log/list/item.js
Executable file
32
frontend/js/app/openappsec-log/list/item.js
Executable file
@@ -0,0 +1,32 @@
|
||||
const Mn = require('backbone.marionette');
|
||||
const Controller = require('../../controller');
|
||||
const template = require('./item.ejs');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
template: template,
|
||||
tagName: 'tr',
|
||||
|
||||
ui: {
|
||||
meta: 'a.meta'
|
||||
},
|
||||
|
||||
events: {
|
||||
'click @ui.meta': function (e) {
|
||||
e.preventDefault();
|
||||
Controller.showOpenappsecMeta(this.model);
|
||||
}
|
||||
},
|
||||
|
||||
templateContext: {
|
||||
more: function() {
|
||||
switch (this.object_type) {
|
||||
case 'redirection-host':
|
||||
case 'stream':
|
||||
case 'proxy-host':
|
||||
return this.meta.domain_names.join(', ');
|
||||
}
|
||||
|
||||
return '#' + (this.object_id || '?');
|
||||
}
|
||||
}
|
||||
});
|
9
frontend/js/app/openappsec-log/list/main.ejs
Executable file
9
frontend/js/app/openappsec-log/list/main.ejs
Executable file
@@ -0,0 +1,9 @@
|
||||
<thead>
|
||||
<th>Severity</th>
|
||||
<th>Event</th>
|
||||
<th>Handler</th>
|
||||
<th> </th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- items -->
|
||||
</tbody>
|
42
frontend/js/app/openappsec-log/list/main.js
Executable file
42
frontend/js/app/openappsec-log/list/main.js
Executable file
@@ -0,0 +1,42 @@
|
||||
const Mn = require('backbone.marionette');
|
||||
const ItemView = require('./item');
|
||||
const template = require('./main.ejs');
|
||||
|
||||
let TableBody = Mn.CollectionView.extend({
|
||||
tagName: 'tbody',
|
||||
childView: ItemView,
|
||||
|
||||
initialize: function (options) {
|
||||
this.options = new Backbone.Model(options);
|
||||
this.page = options.page;
|
||||
this.perPage = options.perPage;
|
||||
this.updatePage();
|
||||
this.listenTo(this.options, 'change:page', this.updatePage);
|
||||
},
|
||||
|
||||
updatePage: function () {
|
||||
let models = this.collection.models.slice((this.page - 1) * this.perPage, this.page * this.perPage);
|
||||
this.collection.reset(models);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
tagName: 'table',
|
||||
className: 'table table-hover table-outline table-vcenter card-table',
|
||||
template: template,
|
||||
|
||||
regions: {
|
||||
body: {
|
||||
el: 'tbody',
|
||||
replaceElement: true
|
||||
}
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
this.showChildView('body', new TableBody({
|
||||
collection: this.collection,
|
||||
page: this.options.page,
|
||||
perPage: this.options.perPage
|
||||
}));
|
||||
}
|
||||
});
|
45
frontend/js/app/openappsec-log/main.ejs
Executable file
45
frontend/js/app/openappsec-log/main.ejs
Executable file
@@ -0,0 +1,45 @@
|
||||
<div class="card">
|
||||
<div class="card-status bg-teal"></div>
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Security Log</h3>
|
||||
<div class="card-options">
|
||||
<!-- <form class="search-form" role="search">
|
||||
<div class="input-icon">
|
||||
<span class="input-icon-addon">
|
||||
<i class="fe fe-search"></i>
|
||||
</span>
|
||||
<input name="source-query" type="text" value="" class="form-control form-control-sm" placeholder="<%- i18n('audit-log', 'search') %>" aria-label="<%- i18n('audit-log', 'search') %>">
|
||||
</div>
|
||||
</form> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body no-padding min-100 has-tabs">
|
||||
<div class="px-4">
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="nav-item"><a id="tab1" href="#filter-important" aria-controls="tab1" role="tab"
|
||||
data-toggle="tab" class="nav-link active">Important Events</a></li>
|
||||
<li role="presentation" class="nav-item"><a id="tab2" href="#filter-all" aria-controls="tab2" role="tab" data-toggle="tab"
|
||||
class="nav-link">All Events</a></li>
|
||||
<li role="presentation" class="nav-item"><a id="tab3" href="#filter-notifications" aria-controls="tab3" role="tab"
|
||||
data-toggle="tab" class="nav-link">Notifications</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="tab-content">
|
||||
|
||||
<div role="tabpanel" class="tab-pane active" id="tab1">
|
||||
<div class="dimmer active">
|
||||
<div class="loader"></div>
|
||||
<div class="dimmer-content list-region">
|
||||
<!-- List Region -->
|
||||
</div>
|
||||
<div class="dimmer-content pagination-region">
|
||||
<!-- Pagination Region -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
229
frontend/js/app/openappsec-log/main.js
Executable file
229
frontend/js/app/openappsec-log/main.js
Executable file
@@ -0,0 +1,229 @@
|
||||
const Mn = require('backbone.marionette');
|
||||
const App = require('../main');
|
||||
const OpenappsecLogModel = require('../../models/openappsec-log');
|
||||
const ListViewTab1 = require('./list-important/main');
|
||||
const ListViewTab2 = require('./list-all/main');
|
||||
const ListViewTab3 = require('./list-notifications/main');
|
||||
const template = require('./main.ejs');
|
||||
const paginationTemplate = require('./pagination.ejs');
|
||||
const ErrorView = require('../error/main');
|
||||
const EmptyView = require('../empty/main');
|
||||
const { data } = require('jquery');
|
||||
const Controller = require('../controller');
|
||||
|
||||
let PaginationView = Mn.View.extend({
|
||||
tagName: 'ul',
|
||||
className: 'pagination justify-content-center mt-4 border-top pt-3',
|
||||
|
||||
initialize: function (options) {
|
||||
this.totalPages = parseInt(options.totalPages) || 1;
|
||||
this.currentPage = parseInt(options.currentPage) || 1;
|
||||
this.totalDataLines = parseInt(options.totalDataLines) || 1;
|
||||
this.maxPageLinks = 15;
|
||||
},
|
||||
|
||||
template: paginationTemplate,
|
||||
|
||||
templateContext: function () {
|
||||
return {
|
||||
totalDataLines: this.totalDataLines,
|
||||
totalPages: this.totalPages,
|
||||
currentPage: this.currentPage,
|
||||
maxPageLinks: this.maxPageLinks
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
id: 'openappsec-log',
|
||||
template: template,
|
||||
|
||||
initialize: function (options) {
|
||||
this.options = options;
|
||||
},
|
||||
|
||||
ui: {
|
||||
list_region: '.list-region',
|
||||
dimmer: '.dimmer',
|
||||
search: '.search-form',
|
||||
query: 'input[name="source-query"]',
|
||||
pagination_region: '.pagination-region'
|
||||
},
|
||||
|
||||
fetch: App.Api.OpenappsecLog.getAll,
|
||||
|
||||
showData: function (response, tab = 'tab1') {
|
||||
|
||||
// Filter the response data for each tab
|
||||
const eventSeverities = ["critical", "high"];
|
||||
const tab1Data = response.filter(item => eventSeverities.includes(item.eventSeverity.trim().toLowerCase()));
|
||||
|
||||
const eventNames = ["waap telemetry", "waap attack type telemetry", "ips stats"];
|
||||
const tab2Data = response.filter(item => !eventNames.includes(item.eventName.trim().toLowerCase()));
|
||||
|
||||
const eventLevels = ["action item"];
|
||||
const tab3Data = response.filter(item => eventLevels.includes(item.eventLevel.trim().toLowerCase()));
|
||||
|
||||
// Sort the data for each tab by timestamp in descending order.
|
||||
tab1Data.sort((a, b) => {
|
||||
return new Date(b.eventTime) - new Date(a.eventTime);
|
||||
});
|
||||
|
||||
tab2Data.sort((a, b) => {
|
||||
return new Date(b.eventTime) - new Date(a.eventTime);
|
||||
});
|
||||
|
||||
tab3Data.sort((a, b) => {
|
||||
return new Date(b.eventTime) - new Date(a.eventTime);
|
||||
});
|
||||
|
||||
// Store the lengths of the original collections
|
||||
this.tabCollectionLengths = {
|
||||
tab1: response.length,
|
||||
tab2: response.length,
|
||||
tab3: response.length
|
||||
};
|
||||
|
||||
this.tabCollections = {
|
||||
tab1: new OpenappsecLogModel.Collection(tab1Data),
|
||||
tab2: new OpenappsecLogModel.Collection(tab2Data),
|
||||
tab3: new OpenappsecLogModel.Collection(tab3Data)
|
||||
};
|
||||
|
||||
this.tabPaginationStates = {
|
||||
tab1: { page: 1, perPage: this.options.perPage, totalDataLines: tab1Data.length },
|
||||
tab2: { page: 1, perPage: this.options.perPage, totalDataLines: tab2Data.length },
|
||||
tab3: { page: 1, perPage: this.options.perPage, totalDataLines: tab3Data.length }
|
||||
};
|
||||
|
||||
// Define an object mapping for the ListViews
|
||||
const listViewMapping = {
|
||||
tab1: ListViewTab1,
|
||||
tab2: ListViewTab2,
|
||||
tab3: ListViewTab3
|
||||
};
|
||||
|
||||
// Get the current tab
|
||||
const currentTab = tab; // Replace this with the actual current tab
|
||||
|
||||
// Select the ListView for the current tab
|
||||
const CurrentListView = listViewMapping[currentTab];
|
||||
|
||||
// Show the ListView for the current tab
|
||||
this.showChildView('list_region', new CurrentListView({
|
||||
collection: this.tabCollections[currentTab],
|
||||
// page: 1,
|
||||
// perPage: this.options.perPage
|
||||
page: this.tabPaginationStates[currentTab].page,
|
||||
perPage: this.tabPaginationStates[currentTab].perPage
|
||||
}));
|
||||
|
||||
// this.showChildView('pagination_region', new PaginationView({
|
||||
// totalDataLines: this.tabPaginationStates[currentTab].totalDataLines,
|
||||
// totalPages: Math.ceil(this.tabPaginationStates[currentTab].totalDataLines / this.options.perPage),
|
||||
// currentPage: this.tabPaginationStates[currentTab].page
|
||||
// }));
|
||||
},
|
||||
|
||||
showError: function (err) {
|
||||
this.showChildView('list_region', new ErrorView({
|
||||
code: err.code,
|
||||
message: err.message,
|
||||
retry: function () {
|
||||
App.Controller.showOpenappsecLog();
|
||||
}
|
||||
}));
|
||||
|
||||
console.error(err);
|
||||
},
|
||||
|
||||
showEmpty: function () {
|
||||
this.showChildView('list_region', new EmptyView({
|
||||
title: App.i18n('audit-log', 'empty'),
|
||||
subtitle: App.i18n('audit-log', 'empty-subtitle')
|
||||
}));
|
||||
},
|
||||
|
||||
regions: {
|
||||
list_region: '@ui.list_region',
|
||||
pagination_region: '@ui.pagination_region'
|
||||
},
|
||||
|
||||
events: {
|
||||
'submit @ui.search': function (e) {
|
||||
e.preventDefault();
|
||||
let query = this.ui.query.val();
|
||||
|
||||
this.fetch(['user'], query)
|
||||
.then(response => this.showData(response))
|
||||
.catch(err => {
|
||||
this.showError(err);
|
||||
});
|
||||
},
|
||||
|
||||
'click .nav-link': 'onTabClick',
|
||||
|
||||
'click @ui.pagination_region a': function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
// get the page number from the link
|
||||
const newPage = $(e.currentTarget).attr('href').split('/').pop();
|
||||
Controller.navigate('/openappsec-log/page/' + newPage, { trigger: true });
|
||||
}
|
||||
},
|
||||
|
||||
onTabClick: function(event) {
|
||||
event.preventDefault();
|
||||
const selectedTab = event.target.id;
|
||||
let view = this;
|
||||
let query = this.ui.query.val() || '';
|
||||
view.ui.dimmer.addClass('active');
|
||||
view.fetch(['user'], query)
|
||||
.then(response => {
|
||||
if (!view.isDestroyed() && response) {
|
||||
view.showData(response, selectedTab);
|
||||
} else {
|
||||
view.showEmpty();
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
view.showError(err);
|
||||
})
|
||||
.then(() => {
|
||||
view.ui.dimmer.removeClass('active');
|
||||
});
|
||||
|
||||
// this.showChildView('list_region', new ListView({
|
||||
// collection: this.tabCollections[selectedTab],
|
||||
// page: this.tabPaginationStates[selectedTab].page,
|
||||
// perPage: this.tabPaginationStates[selectedTab].perPage
|
||||
// }));
|
||||
|
||||
// this.showChildView('pagination_region', new PaginationView({
|
||||
// totalDataLines: this.tabCollections[selectedTab].length,
|
||||
// totalPages: Math.ceil(this.tabCollections[selectedTab].length / this.options.perPage),
|
||||
// currentPage: this.tabPaginationStates[selectedTab].page
|
||||
// }));
|
||||
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
let view = this;
|
||||
let query = this.ui.query.val() || '';
|
||||
|
||||
view.fetch(['user'], query)
|
||||
.then(response => {
|
||||
if (!view.isDestroyed() && response) {
|
||||
view.showData(response);
|
||||
} else {
|
||||
view.showEmpty();
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
view.showError(err);
|
||||
})
|
||||
.then(() => {
|
||||
view.ui.dimmer.removeClass('active');
|
||||
});
|
||||
}
|
||||
});
|
27
frontend/js/app/openappsec-log/meta.ejs
Executable file
27
frontend/js/app/openappsec-log/meta.ejs
Executable file
@@ -0,0 +1,27 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><%- eventName %></h5>
|
||||
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-2">
|
||||
<div class="tag tag-dark">
|
||||
Handler
|
||||
<span class="tag-addon tag-orange"><%- serviceName %></span>
|
||||
</div>
|
||||
<div class="tag tag-dark">
|
||||
Severity
|
||||
<span class="tag-addon tag-teal"><%- eventSeverity %></span>
|
||||
</div>
|
||||
<div class="tag tag-dark">
|
||||
<%- i18n('audit-log', 'date') %>
|
||||
<span class="tag-addon tag-primary"><%- formatDbDate(eventTime, 'Do MMMM YYYY, h:mm a') %></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<pre><%- JSON.stringify(meta, null, 2) %></pre>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'close') %></button>
|
||||
</div>
|
||||
</div>
|
7
frontend/js/app/openappsec-log/meta.js
Executable file
7
frontend/js/app/openappsec-log/meta.js
Executable file
@@ -0,0 +1,7 @@
|
||||
const Mn = require('backbone.marionette');
|
||||
const template = require('./meta.ejs');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
template: template,
|
||||
className: 'modal-dialog wide'
|
||||
});
|
23
frontend/js/app/openappsec-log/pagination.ejs
Normal file
23
frontend/js/app/openappsec-log/pagination.ejs
Normal file
@@ -0,0 +1,23 @@
|
||||
<li class="page-item <%= currentPage === 1 ? "disabled" : "" %>">
|
||||
<a class="page-link" href="/openappsec-log/page/1">First</a>
|
||||
</li>
|
||||
<li class="page-item <%= currentPage === 1 ? "disabled" : "" %>">
|
||||
<a class="page-link" href="/openappsec-log/page/<%= currentPage - 1 %>">Previous</a>
|
||||
</li>
|
||||
<% let startPage = Math.max(1, currentPage - Math.floor(maxPageLinks / 2)); %>
|
||||
<% let endPage = Math.min(startPage + maxPageLinks - 1, totalPages); %>
|
||||
<% startPage = Math.max(1, endPage - maxPageLinks + 1); %>
|
||||
<% for (let i = startPage; i <= endPage; i++) { %>
|
||||
<li class="page-item <%= i === currentPage ? "active" : "" %>">
|
||||
<a class="page-link" href="/openappsec-log/page/<%= i %>"><%= i %></a>
|
||||
</li>
|
||||
<% } %>
|
||||
<li class="page-item <%= currentPage === totalPages ? "disabled" : "" %>">
|
||||
<a class="page-link" href="/openappsec-log/page/<%= currentPage + 1 %>">Next</a>
|
||||
</li>
|
||||
<li class="page-item <%= currentPage === totalPages ? "disabled" : "" %>">
|
||||
<a class="page-link" href="/openappsec-log/page/<%= totalPages %>">Last</a>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link">Total lines: <%= totalDataLines %></span>
|
||||
</li>
|
@@ -13,6 +13,8 @@ module.exports = AppRouter.default.extend({
|
||||
'nginx/access': 'showNginxAccess',
|
||||
'nginx/certificates': 'showNginxCertificates',
|
||||
'audit-log': 'showAuditLog',
|
||||
'openappsec-log': 'showOpenappsecLogPage',
|
||||
'openappsec-log/page/:number': 'showOpenappsecLogPage',
|
||||
'settings': 'showSettings',
|
||||
'*default': 'showDashboard'
|
||||
}
|
||||
|
@@ -3,12 +3,55 @@
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><%- i18n('settings', 'title') %></h3>
|
||||
</div>
|
||||
<div class="card-body no-padding min-100">
|
||||
<div class="card-body no-padding min-100 has-tabs">
|
||||
|
||||
<div class="px-4">
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active">Nginx Proxy Manager</a></li>
|
||||
<li role="presentation" class="nav-item"><a href="#open-appsec" aria-controls="tab2" role="tab" data-toggle="tab" class="nav-link">open-appsec Advanced</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="tab-content">
|
||||
|
||||
<!-- npm -->
|
||||
<div role="tabpanel" class="tab-pane active" id="details">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12">
|
||||
<div class="dimmer active">
|
||||
<div class="loader"></div>
|
||||
<div class="dimmer-content list-region">
|
||||
<!-- List Region -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- open-appsec -->
|
||||
<div role="tabpanel" class="tab-pane" id="open-appsec">
|
||||
<div class="p-4">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label class="form-label">open-appsec Configuration File</label>
|
||||
<textarea id="local_policy" name="local_policy" rows="15" class="form-control text-monospace" placeholder="# <%- i18n('settings', 'local-policy-warning') %>"></textarea>
|
||||
</div>
|
||||
<div id="lp_success_info" class="alert alert-success success" role="alert">Local Policy Updated</div>
|
||||
<div id="lp_error_info" class="alert alert-danger" role="alert">error</div>
|
||||
<button type="button" class="btn btn-teal save"><%- i18n('str', 'save-settings') %></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -5,11 +5,18 @@ const ListView = require('./list/main');
|
||||
const ErrorView = require('../error/main');
|
||||
const template = require('./main.ejs');
|
||||
|
||||
require('jquery-serializejson');
|
||||
|
||||
module.exports = Mn.View.extend({
|
||||
id: 'settings',
|
||||
template: template,
|
||||
|
||||
ui: {
|
||||
local_policy_field: '#open-appsec form #local_policy',
|
||||
lp_success_info: '#open-appsec form #lp_success_info',
|
||||
lp_error_info: '#open-appsec form #lp_error_info',
|
||||
form: '#open-appsec form',
|
||||
save: 'button.save',
|
||||
list_region: '.list-region',
|
||||
add: '.add-item',
|
||||
dimmer: '.dimmer'
|
||||
@@ -19,9 +26,57 @@ module.exports = Mn.View.extend({
|
||||
list_region: '@ui.list_region'
|
||||
},
|
||||
|
||||
events: {
|
||||
'click @ui.save': function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
this.ui.lp_success_info.hide();
|
||||
this.ui.lp_error_info.hide();
|
||||
|
||||
let data = this.ui.form.serializeJSON();
|
||||
console.log(data);
|
||||
App.Api.OpenAppsecSettings.save(data)
|
||||
.then(response => {
|
||||
this.showSuccess();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
this.showError(err);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
showSuccess: function () {
|
||||
this.ui.lp_success_info.show();
|
||||
setTimeout(() => {
|
||||
this.ui.lp_success_info.fadeOut();
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
showError: function (err) {
|
||||
this.ui.lp_error_info.show();
|
||||
this.ui.lp_error_info.html(err.message);
|
||||
setTimeout(() => {
|
||||
this.ui.lp_error_info.fadeOut();
|
||||
}, 3000);
|
||||
},
|
||||
|
||||
onRender: function () {
|
||||
let view = this;
|
||||
|
||||
this.ui.lp_success_info.hide();
|
||||
this.ui.lp_error_info.hide();
|
||||
|
||||
App.Api.OpenAppsecSettings.get()
|
||||
.then(response => {
|
||||
if (!view.isDestroyed() && response) {
|
||||
view.ui.local_policy_field.val(response);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
App.Api.Settings.getAll()
|
||||
.then(response => {
|
||||
if (!view.isDestroyed() && response && response.length) {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<div class="container">
|
||||
<div class="mx-3 mx-md-5" style="width: 95vw;">
|
||||
<div class="d-flex">
|
||||
<button class="navbar-toggler d-lg-none mr-2" type="button" data-toggle="collapse" data-target="#menu">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
@@ -6,6 +6,8 @@
|
||||
<a class="navbar-brand" href="/">
|
||||
<img src="/images/favicons/favicon-32x32.png" border="0"> <%- i18n('main', 'app') %>
|
||||
</a>
|
||||
<div class="d-flex align-items-center order-lg-2 ml-auto">
|
||||
<span class="small mr-3 text-nowrap">Secured by:</span><img src="/images/open-appsec-logo.svg" border="0" width="29" class="mr-2"> <span class="mr-sm-0 mr-md-4 my-0 align-middle text-nowrap" style="font-family: Open Sans;">open-appsec</span>
|
||||
|
||||
<div class="d-flex align-items-center order-lg-2 ml-auto">
|
||||
<div class="dropdown">
|
||||
|
@@ -7,8 +7,8 @@
|
||||
<!-- Menu View -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-3 my-md-5">
|
||||
<div id="app-content" class="container">
|
||||
<div class="m-3 my-md-5">
|
||||
<div id="app-content">
|
||||
<!-- App View -->
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<div class="container">
|
||||
<div class="mx-3 mx-md-5">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg order-lg-first">
|
||||
<ul class="nav nav-tabs border-0 flex-column flex-lg-row">
|
||||
@@ -42,6 +42,9 @@
|
||||
<li class="nav-item">
|
||||
<a href="/audit-log" class="nav-link"><i class="fe fe-book-open"></i> <%- i18n('audit-log', 'title') %></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/openappsec-log" class="nav-link"><i class="fe fe-book-open"></i>Security Log</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/settings" class="nav-link"><i class="fe fe-settings"></i> <%- i18n('settings', 'title') %></a>
|
||||
</li>
|
||||
|
@@ -12,6 +12,7 @@
|
||||
"roles": "Roles",
|
||||
"created-on": "Created: {date}",
|
||||
"save": "Save",
|
||||
"save-settings": "Save Settings",
|
||||
"cancel": "Cancel",
|
||||
"close": "Close",
|
||||
"enable": "Enable",
|
||||
@@ -42,7 +43,7 @@
|
||||
},
|
||||
"main": {
|
||||
"app": "Nginx Proxy Manager",
|
||||
"version": "v{version}",
|
||||
"version": "v{version}-beta.1",
|
||||
"welcome": "Welcome to Nginx Proxy Manager",
|
||||
"logged-in": "You are logged in as {name}",
|
||||
"unknown-error": "Error loading stuff. Please reload the app.",
|
||||
@@ -290,7 +291,8 @@
|
||||
"default-site-404": "404 Page",
|
||||
"default-site-444": "No Response (444)",
|
||||
"default-site-html": "Custom Page",
|
||||
"default-site-redirect": "Redirect"
|
||||
"default-site-redirect": "Redirect",
|
||||
"local-policy-warning": "open-appsec YAML-based configuration file"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,10 +6,25 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<div class="text-center p-6">
|
||||
<div class="text-center p-1">
|
||||
<div class="text-center">
|
||||
<img src="/images/logo-text-vertical-grey.png" alt="Logo" />
|
||||
<div class="text-center text-muted mt-5">
|
||||
<%- i18n('main', 'version', {version: getVersion()}) %>
|
||||
<div class="text-center text-muted my-5">
|
||||
<%- i18n('main', 'version' , {version: getVersion()}) %>
|
||||
</div>
|
||||
|
||||
<div class="d-flex position-relative justify-content-center">
|
||||
<div class="position-absolute" style="left: 55px;">
|
||||
<span class="small pr-1">Secured by:</span>
|
||||
</div>
|
||||
<div class="mx-auto">
|
||||
<img src="/images/open-appsec-logo.svg" border="0" width="29">
|
||||
</div>
|
||||
<div class="position-absolute" style="right: 35px;">
|
||||
<span class="pl-2 align-middle" style="font-family: Open Sans;">open-appsec</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -36,7 +36,8 @@ module.exports = Mn.View.extend({
|
||||
templateContext: {
|
||||
i18n: i18n,
|
||||
getVersion: function () {
|
||||
return $('#login').data('version');
|
||||
const version = $('#login').data('version');
|
||||
return version;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
41
frontend/js/models/openappsec-log.js
Executable file
41
frontend/js/models/openappsec-log.js
Executable file
@@ -0,0 +1,41 @@
|
||||
const Backbone = require('backbone');
|
||||
|
||||
const model = Backbone.Model.extend({
|
||||
idAttribute: 'id',
|
||||
|
||||
defaults: function () {
|
||||
return {
|
||||
name: '',
|
||||
eventSeverity: '',
|
||||
assetName: '',
|
||||
securityAction: '',
|
||||
waapIncidentType: '',
|
||||
httpSourceId: '',
|
||||
sourceIP: '',
|
||||
// 'Proxy-IP': '',
|
||||
proxyIP: '',
|
||||
httpHostName: '',
|
||||
httpMethod: '',
|
||||
// 'HTTP-Response-Code': '',
|
||||
httpResponseCode: '',
|
||||
httpUriPath: '',
|
||||
// 'Protection-Name': '',
|
||||
protectionName: '',
|
||||
matchedLocation: '',
|
||||
matchedParameter: '',
|
||||
matchedSample: '',
|
||||
eventPriority: '',
|
||||
eventTopic: '',
|
||||
eventName: '',
|
||||
// Suggested Remediation if Applicable
|
||||
suggestedRemediation: ''
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
Model: model,
|
||||
Collection: Backbone.Collection.extend({
|
||||
model: model
|
||||
})
|
||||
};
|
@@ -10,12 +10,17 @@ const model = Backbone.Model.extend({
|
||||
advanced_config: '',
|
||||
forward_scheme: 'http',
|
||||
forward_host: '',
|
||||
forward_port: '80'
|
||||
forward_port: '80',
|
||||
use_openappsec: false,
|
||||
openappsec_mode: 'detect-learn',
|
||||
minimum_confidence: 'high'
|
||||
}
|
||||
},
|
||||
|
||||
toJSON() {
|
||||
const r = Object.assign({}, this.attributes);
|
||||
// convert use_openappsec to boolean
|
||||
r.use_openappsec = !!r.use_openappsec;
|
||||
delete r.opened;
|
||||
return r;
|
||||
},
|
||||
|
@@ -20,6 +20,9 @@ const model = Backbone.Model.extend({
|
||||
caching_enabled: false,
|
||||
allow_websocket_upgrade: false,
|
||||
block_exploits: false,
|
||||
use_openappsec: false,
|
||||
openappsec_mode: "detect-learn",
|
||||
minimum_confidence: "high",
|
||||
http2_support: false,
|
||||
advanced_config: '',
|
||||
enabled: true,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nginx-proxy-manager",
|
||||
"version": "0.0.0",
|
||||
"version": "1.0.0",
|
||||
"description": "A beautiful interface for creating Nginx endpoints",
|
||||
"main": "js/index.js",
|
||||
"devDependencies": {
|
||||
|
@@ -39,4 +39,13 @@ a:hover {
|
||||
|
||||
.col-login {
|
||||
max-width: 48rem;
|
||||
}
|
||||
|
||||
.page {
|
||||
width: fit-content;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
table.card-table thead th {
|
||||
vertical-align: top;
|
||||
}
|
@@ -676,16 +676,6 @@ asn1.js@^4.0.0:
|
||||
inherits "^2.0.1"
|
||||
minimalistic-assert "^1.0.0"
|
||||
|
||||
asn1.js@^5.2.0:
|
||||
version "5.4.1"
|
||||
resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
|
||||
integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==
|
||||
dependencies:
|
||||
bn.js "^4.0.0"
|
||||
inherits "^2.0.1"
|
||||
minimalistic-assert "^1.0.0"
|
||||
safer-buffer "^2.1.0"
|
||||
|
||||
asn1@~0.2.3:
|
||||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
||||
@@ -1376,10 +1366,10 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9:
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
|
||||
integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
|
||||
|
||||
bn.js@^5.0.0, bn.js@^5.2.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70"
|
||||
integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==
|
||||
bn.js@^5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.2.tgz#c9686902d3c9a27729f43ab10f9d79c2004da7b0"
|
||||
integrity sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==
|
||||
|
||||
boolbase@~1.0.0:
|
||||
version "1.0.0"
|
||||
@@ -1472,7 +1462,7 @@ browserify-des@^1.0.0:
|
||||
inherits "^2.0.1"
|
||||
safe-buffer "^5.1.2"
|
||||
|
||||
browserify-rsa@^4.0.0:
|
||||
browserify-rsa@^4.0.0, browserify-rsa@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524"
|
||||
integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=
|
||||
@@ -1480,28 +1470,20 @@ browserify-rsa@^4.0.0:
|
||||
bn.js "^4.1.0"
|
||||
randombytes "^2.0.1"
|
||||
|
||||
browserify-rsa@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d"
|
||||
integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==
|
||||
dependencies:
|
||||
bn.js "^5.0.0"
|
||||
randombytes "^2.0.1"
|
||||
|
||||
browserify-sign@^4.0.0:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.2.tgz#e78d4b69816d6e3dd1c747e64e9947f9ad79bc7e"
|
||||
integrity sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3"
|
||||
integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==
|
||||
dependencies:
|
||||
bn.js "^5.2.1"
|
||||
browserify-rsa "^4.1.0"
|
||||
bn.js "^5.1.1"
|
||||
browserify-rsa "^4.0.1"
|
||||
create-hash "^1.2.0"
|
||||
create-hmac "^1.1.7"
|
||||
elliptic "^6.5.4"
|
||||
elliptic "^6.5.3"
|
||||
inherits "^2.0.4"
|
||||
parse-asn1 "^5.1.6"
|
||||
readable-stream "^3.6.2"
|
||||
safe-buffer "^5.2.1"
|
||||
parse-asn1 "^5.1.5"
|
||||
readable-stream "^3.6.0"
|
||||
safe-buffer "^5.2.0"
|
||||
|
||||
browserify-zlib@^0.2.0:
|
||||
version "0.2.0"
|
||||
@@ -2451,7 +2433,7 @@ electron-to-chromium@^1.3.47:
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.522.tgz#4a6485ad187ffd31913bba0747d0e36405f405d6"
|
||||
integrity sha512-67V62Z4CFOiAtox+o+tosGfVk0QX4DJgH609tjT8QymbJZVAI/jWnAthnr8c5hnRNziIRwkc9EMQYejiVz3/9Q==
|
||||
|
||||
elliptic@^6.5.3, elliptic@^6.5.4:
|
||||
elliptic@^6.5.3:
|
||||
version "6.5.4"
|
||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
|
||||
integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
|
||||
@@ -4889,7 +4871,7 @@ parent-module@^1.0.0:
|
||||
dependencies:
|
||||
callsites "^3.0.0"
|
||||
|
||||
parse-asn1@^5.0.0:
|
||||
parse-asn1@^5.0.0, parse-asn1@^5.1.5:
|
||||
version "5.1.5"
|
||||
resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e"
|
||||
integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==
|
||||
@@ -4901,17 +4883,6 @@ parse-asn1@^5.0.0:
|
||||
pbkdf2 "^3.0.3"
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
parse-asn1@^5.1.6:
|
||||
version "5.1.6"
|
||||
resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4"
|
||||
integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==
|
||||
dependencies:
|
||||
asn1.js "^5.2.0"
|
||||
browserify-aes "^1.0.0"
|
||||
evp_bytestokey "^1.0.0"
|
||||
pbkdf2 "^3.0.3"
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
parse-json@^5.0.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
|
||||
@@ -5337,15 +5308,6 @@ readable-stream@^3.1.1, readable-stream@^3.6.0:
|
||||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
readable-stream@^3.6.2:
|
||||
version "3.6.2"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
|
||||
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
readdirp@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
|
||||
@@ -5650,7 +5612,7 @@ rxjs@^6.6.0:
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0:
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
Reference in New Issue
Block a user