Skip to content

Commit

Permalink
dep updates/add openappsec env option/add NGINX_WORKER_PROCESSES env/…
Browse files Browse the repository at this point in the history
…support ocsp stapling for custom certs/allow empty port

Signed-off-by: Zoey <[email protected]>
  • Loading branch information
renovate[bot] authored and Zoey2936 committed Jan 23, 2025
1 parent eea4015 commit 5387842
Show file tree
Hide file tree
Showing 19 changed files with 189 additions and 68 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/dependency-updates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jobs:
git ls-remote --tags https://github.com/crowdsecurity/cs-nginx-bouncer \
| cut -d/ -f3 \
| sort -V \
| grep -v rc \
| tail -1 \
| sed -E "s/\^\{\}//"
)"
Expand All @@ -45,6 +46,7 @@ jobs:
git ls-remote --tags https://github.com/coreruleset/coreruleset \
| cut -d/ -f3 \
| sort -V \
| grep -v rc \
| tail -1 \
| sed -E "s/\^\{\}//"
)"
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ dist/
downloads/
eggs/
.eggs/
lib/
#lib/
lib64/
parts/
sdist/
Expand Down
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ RUN apk upgrade --no-cache -a && \
sed -i "s|APPSEC_PROCESS_TIMEOUT=.*|APPSEC_PROCESS_TIMEOUT=10000|g" /src/crowdsec-nginx-bouncer/lua-mod/config_example.conf


FROM zoeyvid/nginx-quic:375-python
FROM zoeyvid/nginx-quic:384-python
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
ENV NODE_ENV=production
ARG CRS_VER=v4.10.0
Expand All @@ -86,6 +86,7 @@ RUN apk upgrade --no-cache -a && \
# curl https://raw.githubusercontent.com/acmesh-official/acme.sh/master/acme.sh | sh -s -- --install-online --home /usr/local/acme.sh --nocron && \
# ln -s /usr/local/acme.sh/acme.sh /usr/local/bin/acme.sh && \
curl https://raw.githubusercontent.com/tomwassenberg/certbot-ocsp-fetcher/refs/heads/main/certbot-ocsp-fetcher -o /usr/local/bin/certbot-ocsp-fetcher.sh && \
sed -i "s|/live||g" /usr/local/bin/certbot-ocsp-fetcher.sh && \
chmod +x /usr/local/bin/certbot-ocsp-fetcher.sh && \
git clone https://github.com/coreruleset/coreruleset --branch "$CRS_VER" /tmp/coreruleset && \
mkdir -v /usr/local/nginx/conf/conf.d/include/coreruleset && \
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ so that the barrier for entry here is low.
- Try to whitelist the Content-Type you are sending (for example, `application/activity+json` for Mastodon and `application/dns-message` for DoH).
- Try to whitelist the HTTP request method you are using (for example, `PUT` is blocked by default, which also blocks NPMplus UI).
- CoreRuleSet plugins are supported, you can find a guide in this readme
- option to load the openappsec attachment module, see compose.yaml for details
- Darkmode button in the footer for comfortable viewing (CSS done by [@theraw](https://github.com/theraw))
- Fixes proxy to https origin when the origin only accepts TLSv1.3
- Only enables TLSv1.2 and TLSv1.3 protocols, also ML-KEM support
Expand Down Expand Up @@ -134,6 +135,13 @@ name: appsec
source: appsec
labels:
type: appsec
# if you use openappsec you can enable this
#---
#source: docker
#container_name:
# - openappsec-agent
#labels:
# type: openappsec
```
4. make sure to use `network_mode: host` in your compose file
5. run `docker exec crowdsec cscli bouncers add npmplus -o raw` and save the output
Expand Down
8 changes: 6 additions & 2 deletions backend/internal/nginx.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,12 @@ const internalNginx = {

reload: () => {
if (process.env.ACME_OCSP_STAPLING === 'true') {
return utils
.execFile('certbot-ocsp-fetcher.sh', ['-c', '/data/tls/certbot', '-o', '/data/tls/certbot/live', '--no-reload-webserver', '--quiet'])
utils
.execFile('certbot-ocsp-fetcher.sh', ['-c', '/data/tls/certbot/live', '-o', '/data/tls/certbot/live', '--no-reload-webserver', '--quiet'])
.catch(() => {})
.finally(() => {
utils.execFile('certbot-ocsp-fetcher.sh', ['-c', '/data/tls/custom', '-o', '/data/tls/custom', '--no-reload-webserver', '--quiet']);
})
.catch(() => {})
.finally(() => {
logger.info('Reloading Nginx');
Expand Down
4 changes: 2 additions & 2 deletions backend/internal/proxy-host.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ const internalProxyHost = {
.then(() => {
return internalProxyHost.get(access, {
id: data.id,
expand: ['certificate', 'owner', 'access_list'],
expand: ['certificate', 'owner', 'access_list.[clients,items]'],
});
})
.then((row) => {
Expand Down Expand Up @@ -413,7 +413,7 @@ const internalProxyHost = {
return access
.can('proxy_hosts:list')
.then((access_data) => {
let query = proxyHostModel.query().where('is_deleted', 0).groupBy('id').allowGraph('[owner,access_list,certificate]').orderBy(castJsonIfNeed('domain_names'), 'ASC');
let query = proxyHostModel.query().where('is_deleted', 0).groupBy('id').allowGraph('[owner,access_list.[clients,items],certificate]').orderBy(castJsonIfNeed('domain_names'), 'ASC');

if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1));
Expand Down
28 changes: 0 additions & 28 deletions backend/lib/utils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const _ = require('lodash');
const fs = require('fs');
const crypto = require('crypto');
const spawn = require('child_process').spawn;
const execFile = require('child_process').execFile;
const { Liquid } = require('liquidjs');
const logger = require('../logger').global;
Expand Down Expand Up @@ -45,33 +44,6 @@ module.exports = {
return stdout;
},

/**
* @param {String} cmd
* @param {Array} args
*/
execfg: function (cmd, args) {
return new Promise((resolve, reject) => {
logger.debug('CMD: ' + cmd + ' ' + (args ? args.join(' ') : ''));
const childProcess = spawn(cmd, args, {
shell: true,
detached: true,
stdio: 'inherit',
});

childProcess.on('error', (err) => {
reject(err);
});

childProcess.on('close', (code) => {
if (code !== 0) {
reject(new Error(`Command '${cmd}' exited with code ${code}`));
} else {
resolve();
}
});
});
},

/**
* Used in objection query builder
*
Expand Down
42 changes: 42 additions & 0 deletions backend/migrations/20250123132545_allow_empty_forwarding_port.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const migrate_name = 'allow_empty_forwarding_port';
const logger = require('../logger').migrate;

/**
* Migrate
*
* @see http://knexjs.org/#Schema
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.up = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Up...');

return knex.schema
.alterTable('proxy_host', (table) => {
table.integer('forward_port').unsigned();
})
.then(function () {
logger.info('[' + migrate_name + '] proxy Table altered');
});
};

/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex /*, Promise */) {
logger.info('[' + migrate_name + '] Migrating Down...');

return knex.schema
.alterTable('stream', (table) => {
table.integer('forwarding_port').notNull().unsigned().alter();
})
.then(function () {
logger.info('[' + migrate_name + '] proxy Table altered');
});
};
8 changes: 4 additions & 4 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@
"archiver": "7.0.1",
"batchflow": "0.4.0",
"bcrypt": "5.1.1",
"better-sqlite3": "11.7.2",
"better-sqlite3": "11.8.1",
"body-parser": "2.0.2",
"compression": "1.7.5",
"express": "4.21.2",
"express-fileupload": "1.5.1",
"gravatar": "1.8.2",
"jsonwebtoken": "9.0.2",
"knex": "3.1.0",
"liquidjs": "10.20.1",
"liquidjs": "10.20.2",
"lodash": "4.17.21",
"moment": "2.30.1",
"mysql2": "3.12.0",
Expand All @@ -34,8 +34,8 @@
"@apidevtools/swagger-parser": "10.1.1",
"@eslint/js": "9.18.0",
"eslint": "9.18.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-prettier": "5.2.1",
"eslint-config-prettier": "10.0.1",
"eslint-plugin-prettier": "5.2.3",
"globals": "15.14.0",
"prettier": "3.4.2"
},
Expand Down
9 changes: 4 additions & 5 deletions backend/schema/components/proxy-host-object.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,10 @@
"$ref": "../common.json#/properties/domain_names"
},
"forward_host": {
"type": "string",
"pattern": "^(([^.]+\\.)+[^.]+)|(\\[[0-9a-f:]+\\])$"
"type": "string"
},
"forward_port": {
"type": "integer",
"type": ["integer", "null"],
"minimum": 1,
"maximum": 65535
},
Expand Down Expand Up @@ -100,7 +99,7 @@
},
"path": {
"type": "string",
"pattern": "^/.+$",
"pattern": "^/",
"minLength": 1
},
"forward_scheme": {
Expand All @@ -114,7 +113,7 @@
},
"forward_path": {
"type": "string",
"pattern": "^($|/.+$)"
"pattern": "^(/.+)?$"
},
"advanced_config": {
"type": "string"
Expand Down
5 changes: 5 additions & 0 deletions backend/templates/_certificates.conf
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,10 @@
# Custom TLS
ssl_certificate /data/tls/custom/npm-{{ certificate_id }}/fullchain.pem;
ssl_certificate_key /data/tls/custom/npm-{{ certificate_id }}/privkey.pem;
{% if env.ACME_OCSP_STAPLING == "true" %}
ssl_stapling on;
ssl_stapling_verify on;
ssl_stapling_file /data/tls/custom/npm-{{ certificate_id }}.der;
{% endif %}
{% endif %}
{% endif %}
78 changes: 75 additions & 3 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ services:
image: docker.io/zoeyvid/npmplus:latest # or ghcr.io/zoeyvid/npmplus:latest
restart: always
network_mode: host
# ipc: service:openappsec-agent # required when you want to use the openappsec attachment module
# privileged: true # required if you set NGINX_QUIC_BPF to true
volumes:
- "/opt/npm:/data"
Expand All @@ -19,6 +20,7 @@ services:
# - "ACME_OCSP_STAPLING=false" # enables ocsp stapling, default true, I recommend you to enable this if your CA supports it, supported by zerossl and google public ca, unsupported by letsencrypt certs created after May 7, 2025 (will create warning in your log, default value will change then)
# - "ACME_KEY_TYPE=rsa" # which key type to use ecdsa or rsa, default and recommended: ecdsa
# - "ACME_SERVER_TLS_VERIFY=false" # enables checking if ACME_SERVER has a valid TLS cert, default true
# - "NGINX_LOAD_OPENAPPSEC_ATTACHMENT_MODULE=true" # loads the openappsec attachment module, you also need to set ipc for NPMplus in this composse file
# - "PUID=1000" # set group id, needs to be a number greater or equal to 99, or equal to 0, default 0 (root)
# - "PGID=1000" # set user id, needs to be a number greater or equal to 99, or equal to 0, default 0 (root), requires PUID to be not 0
# - "NIBEP=48682" # internal port of the NPMplus API, always bound to 127.0.0.1, default 48681, you need to change it, if you want to run multiple npm instances in network mode host
Expand All @@ -45,6 +47,7 @@ services:
# - "NGINX_HSTS_SUBDMAINS=false" # when enabling security headers, also enable hsts for subdomains, default true
# - "X_FRAME_OPTIONS=sameorigin" # value to use for the X-Frame-Options header when enabling security headers, valid is deny, sameorigin and none (means unset), default deny, since this applies to all hosts I recommend you to instead keep the default and only change it for hosts which need it using the advanced config and more_set_headers
# - "NGINX_DISABLE_PROXY_BUFFERING=true" # Disables the proxy_buffering/proxy_request_buffering options of nginx, default false, may not work if you use crowdsec/appsec
# - "NGINX_WORKER_PROCESSES=8" value of worker_processes, default and recommended: auto
# - "DISABLE_NGINX_BEAUTIFIER=true" # disables nginxbeautifier, useful when it fails parsing non-standard configs, default false
# - "FULLCLEAN=true" # Clean unused config folders, default false
# - "SKIP_IP_RANGES=true" # Skip feteching/whitelisting ip ranges from aws and cloudflare, default false
Expand All @@ -68,7 +71,7 @@ services:
# This can be used with DISABLE_HTTP=true, to force HTTPS redirects for every host
# npmplus-caddy:
# container_name: npmplus-caddy
# image: zoeyvid/npmplus:caddy
# image: docker.io/zoeyvid/npmplus:caddy
# restart: always
# network_mode: bridge
# ports:
Expand All @@ -79,7 +82,7 @@ services:
# This can be used with GOA=true, to keep the geopip database updated, you need to change the envs to make it work
# geoipupdate:
# container_name: npmplus-geoipupdate
# image: maxmindinc/geoipupdate
# image: docker.io/maxmindinc/geoipupdate:latest
# restart: always
# network_mode: bridge
# environment:
Expand All @@ -94,7 +97,7 @@ services:
# This can be used to enable crowdsec, see README for a guide
# crowdsec:
# container_name: crowdsec
# image: crowdsecurity/crowdsec
# image: docker.io/crowdsecurity/crowdsec:latest
# restart: always
# network_mode: bridge
# ports:
Expand All @@ -108,3 +111,72 @@ services:
# - "/opt/crowdsec/data:/var/lib/crowdsec/data"
# - "/opt/npm/nginx:/opt/npm/nginx:ro"
# - "/var/run/docker.sock:/var/run/docker.sock:ro"

# This can be used to run openappsec, you also need to set NGINX_LOAD_OPENAPPSEC_ATTACHMENT_MODULE to true and set ipc for NPMplus
# openappsec-agent:
# container_name: openappsec-agent
# image: ghcr.io/openappsec/agent:latest
# restart: always
# ipc: shareable
# volumes:
# - "/opt/openappsec/conf:/etc/cp/conf"
# - "/opt/openappsec/data:/etc/cp/data"
# - "/opt/openappsec/logs:/var/log/nano_agent"
# - "/opt/openappsec/localconf:/ext/appsec" # please put a local_policy.yaml in the /opt/openappsec/localconf folder before deploying
# - "/opt/openappsec/open-appsec-advanced-model.tgz:/advanced-model/open-appsec-advanced-model.tgz" # optional, if you want to use a different model
# environment:
# - "TZ=your-timezone" # needs to be changed
# - "autoPolicyLoad=true"
# - "registered_server=NPMplus"
# - "user_email=your-email" # optional, not sure what they do exactly with it, but it should work fine without it
# - "AGENT_TOKEN=abc" # optional, can be set if you use theier webinterface, if you leave this commented, please uncomment all other openappsec containers below, see: https://docs.openappsec.io/getting-started/using-the-web-ui-saas/create-a-profile
# - "SHARED_STORAGE_HOST=openappsec-shared-storage" # uncomment if you don't set AGENT_TOKEN
# - "LEARNING_HOST=openappsec-smartsync" # uncomment if you don't set AGENT_TOKEN
# - "TUNING_HOST=openappsec-tuning-svc" # uncomment if you don't set AGENT_TOKEN
# command: /cp-nano-agent

# uncomment if you don't set AGENT_TOKEN
# openappsec-smartsync:
# container_name: openappsec-smartsync
# image: ghcr.io/openappsec/smartsync:latest
# restart: always
# environment:
# - "TZ=your-timezone" # needs to be changed
# - "SHARED_STORAGE_HOST=openappsec-shared-storage"
# depends_on:
# - openappsec-shared-storage
# openappsec-shared-storage:
# container_name: openappsec-shared-storage
# image: ghcr.io/openappsec/smartsync-shared-files:latest
# restart: always
# ipc: service:openappsec-agent
# user: root # if you do not want to run this container as "root" user you can comment it out and instead run the following command after the deployment: docker exec -u root openappsec-shared-storage chown -R appuser:appuser /db
# environment:
# - "TZ=your-timezone" # needs to be changed
# volumes:
# - "/opt/openappsec/storage:/db"
# openappsec-tuning-svc:
# container_name: openappsec-tuning-svc
# image: ghcr.io/openappsec/smartsync-tuning:latest
# restart: always
# environment:
# - "TZ=your-timezone" # needs to be changed
# - "SHARED_STORAGE_HOST=openappsec-shared-storage"
# - "QUERY_DB_HOST=openappsec-db"
# - "QUERY_DB_PASSWORD=password" # replace with something secure, should match POSTGRES_PASSWORD from openappsec-db container
# - "QUERY_DB_USER=appsec"
# volumes:
# - "/opt/openappsec/conf:/etc/cp/conf"
# depends_on:
# - openappsec-shared-storage
# - openappsec-db
# openappsec-db:
# image: postgres
# container_name: openappsec-db
# restart: always
# environment:
# - "TZ=your-timezone" # needs to be changed
# - "POSTGRES_PASSWORD=password" # replace with something secure, should match QUERY_DB_PASSWORD from openappsec-tuning-svc container
# - "POSTGRES_USER=appsec"
# volumes:
# - "/opt/openappsec/pgdb:/var/lib/postgresql/data"
2 changes: 1 addition & 1 deletion frontend/js/app/nginx/proxy/form.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
<div class="col-sm-4 col-md-4">
<div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'forward-port') %> <span class="form-required">*</span></label>
<input name="forward_port" type="number" class="form-control text-monospace" placeholder="80" value="<%- forward_port %>" required>
<input name="forward_port" type="number" class="form-control text-monospace" placeholder="80" value="<%- forward_port %>">
</div>
</div>
<div class="col-sm-6 col-md-6">
Expand Down
2 changes: 1 addition & 1 deletion frontend/js/i18n/de-lang.json
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@
"no-wildcard-without-dns": "Zertifikat kann nicht für Wildcard-Domains angefordert werden, wenn keine DNS-Challenge verwendet wird",
"none": "Nur HTTP",
"other": "Individuell",
"passphrase-protection-support-info": "Schlüsseldateien, die mit einer Passphrase geschützt sind, werden nicht unterstützt. OCSP Stapling wird bei Custom Zertifikaten nicht untersützt.",
"passphrase-protection-support-info": "Schlüsseldateien, die mit einer Passphrase geschützt sind, werden nicht unterstützt.",
"processing-info": "Verarbeitung... Dies kann ein paar Minuten dauern.",
"propagation-seconds": "Ausbreitung in Sekunden",
"propagation-seconds-info": "Leer lassen, um den Standardwert des Plugins zu verwenden. Anzahl der Sekunden, die auf die DNS-Verbreitung gewartet werden soll.",
Expand Down
Loading

0 comments on commit 5387842

Please sign in to comment.