Skip to content

Commit

Permalink
WIP (summary later)
Browse files Browse the repository at this point in the history
Signed-off-by: Zoey <[email protected]>
  • Loading branch information
Zoey2936 committed Jan 3, 2025
1 parent b091ad0 commit d79a7de
Show file tree
Hide file tree
Showing 73 changed files with 815 additions and 1,971 deletions.
30 changes: 17 additions & 13 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ 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:368-python
FROM zoeyvid/nginx-quic:371-python
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
ARG CRS_VER=v4.9.0
ARG CRS_VER=v4.10.0
COPY rootfs /
COPY --from=strip-backend /app /app

Expand Down Expand Up @@ -113,20 +113,20 @@ COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/templa
COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/lib/crowdsec.lua /usr/local/nginx/lib/lua/crowdsec.lua
COPY --from=crowdsec /src/crowdsec-nginx-bouncer/lua-mod/lib/plugins /usr/local/nginx/lib/lua/plugins
COPY --from=frontend /app/dist /html/frontend
COPY --from=zoeyvid/certbot-docker:69 /usr/local /usr/local

LABEL com.centurylinklabs.watchtower.monitor-only="true"
ENV NODE_ENV=production \
NODE_CONFIG_DIR=/data/etc/npm \
DB_SQLITE_FILE=/data/etc/npm/database.sqlite

ENV ACME_SERVER="https://acme-v02.api.letsencrypt.org/directory" \
ACME_MUST_STAPLE=true \
ENV NODE_ENV=production \

Check warning on line 119 in Dockerfile

View workflow job for this annotation

GitHub Actions / build

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "ACME_KEY_TYPE") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
TV=1 \
ACME_SERVER="https://acme-v02.api.letsencrypt.org/directory" \
ACME_MUST_STAPLE=false \
ACME_OCSP_STAPLING=false \
ACME_KEY_TYPE=rsa \
ACME_SERVER_TLS_VERIFY=true \
PUID=0 \
PGID=0 \
NIBEP=48693 \
GOAIWSP=48683 \
NIBEP=48683 \
GOAIWSP=48693 \
NPM_PORT=81 \
GOA_PORT=91 \
IPV4_BINDING=0.0.0.0 \
Expand All @@ -136,16 +136,19 @@ ENV ACME_SERVER="https://acme-v02.api.letsencrypt.org/directory" \
NPM_IPV6_BINDING=[::] \
GOA_IPV6_BINDING=[::] \
DISABLE_IPV6=false \
NPM_DISABLE_IPV6=false \
GOA_DISABLE_IPV6=false \
NPM_LISTEN_LOCALHOST=false \
GOA_LISTEN_LOCALHOST=false \
DEFAULT_CERT_ID=0 \
HTTP_PORT=80 \
HTTPS_PORT=443 \
DISABLE_HTTP=false \
DISABLE_H3_QUIC=false \
NGINX_QUIC_BPF=false \
NGINX_ACCESS_LOG=false \
NGINX_LOG_NOT_FOUND=false \
NGINX_404_REDIRECT=false \
NGINX_HSTS_SUBDMAINS=true \
X_FRAME_OPTIONS=deny \
NGINX_DISABLE_PROXY_BUFFERING=false \
DISABLE_NGINX_BEAUTIFIER=false \
CLEAN=true \
Expand All @@ -158,7 +161,8 @@ ENV ACME_SERVER="https://acme-v02.api.letsencrypt.org/directory" \
GOA=false \
GOACLA="--agent-list --real-os --double-decode --anonymize-ip --anonymize-level=1 --keep-last=30 --with-output-resolver --no-query-string" \
PHP82=false \
PHP83=false
PHP83=false \
PHP84=false

WORKDIR /app
ENTRYPOINT ["tini", "--", "entrypoint.sh"]
Expand Down
51 changes: 22 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,10 @@ running at home or otherwise, including free TLS, without having to know too muc

- [Quick Setup](#quick-setup)

**Note: NO armv7, route53 and aws cloudfront ip ranges support.** <br>
**Note: Other Databases like MariaDB may work, but are unsupported.** <br>
**Note: watchtower does NOT update NPMplus, you need to do it yourself (it will only pull the image, but not update the container itself).** <br>
**Note: access.log/stream.log, logrotate and goaccess are NOT enabled by default bceuase of GDPR, you can enable them in the compose.yaml.** <br>

**Note: add `net.ipv4.ip_unprivileged_port_start=0` at the end of `/etc/sysctl.conf` to support PUID/PGID in network mode host.** <br>
**Note: Don't forget to open Port 80 (tcp) and 443 (tcp AND udp, http3/quic needs udp) in your firewall (because of network mode host, you also need to open this ports in ufw, if you use ufw).** <br>
**Note: If you don't use network mode host, which I don't recommend, don't forget to also expose port 443/udp (http3/quic needs udp) and to enable IPv6 in Docker see step 1 and 2 [here](https://github.com/nextcloud/all-in-one/blob/main/docker-ipv6-support.md).** <br>
**MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING: please see/read/use the ACME_MUST_STAPLE env option of the compose.yaml** <br>

**Note: no armv7, route53 and aws cloudfront ip ranges support.** <br>
**Note: other Databases like MariaDB/MySQL or PostgreSQL may work, but are unsupported and have no advantage over SQLite.** <br>
**Note: watchtower does not update NPMplus, you need to do it yourself (it will only pull the image, but will not redeploy the container itself).** <br>
**Note: remember to add your domain to the hsts preload list if you use security headers: https://hstspreload.org** <br>

## Project Goal
I created this project to fill a personal need to provide users with an easy way to accomplish reverse
Expand All @@ -37,17 +31,18 @@ so that the barrier for entry here is low.
- Supports HTTP/3 (QUIC) protocol.
- Supports CrowdSec IPS. Please see [here](https://github.com/ZoeyVid/NPMplus#crowdsec) to enable it.
- goaccess included, see compose.yaml to enable, runs by default on https://<ip>:91 (nginx config from [here](https://github.com/xavier-hernandez/goaccess-for-nginxproxymanager/blob/main/resources/nginx/nginx.conf))
- Supports ModSecurity, with coreruleset as an option. You can configure ModSecurity/coreruleset by editing the files in the `/opt/npm/etc/modsecurity` folder (no support from me, you need to write the rules yourself - for CRS I can try to help you).
- Supports ModSecurity, with coreruleset as an option. You can configure ModSecurity/coreruleset by editing the files in the `/opt/npm/etc/modsecurity` folder (no support from me, you need to write the rules yourself - for CoreRuleSet I can try to help you).
- By default NPMplus UI does not work when you proxy NPMplus through NPMplus and you have CoreRuleSet enabled, see below
- ModSecurity by default blocks uploads of big files, you need to edit its config to fix this, but it can use a lot of resources to scan big files by ModSecurity
- ModSecurity overblocking (403 Error) with CRS? Please see [here](https://coreruleset.org/docs/concepts/false_positives_tuning) and edit the `/opt/npm/etc/modsecurity/crs-setup.conf` file.
- 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 affects NPM).
- CRS plugins are supported, you can find a guide in this readme
- ModSecurity overblocking (403 Error) when using CoreRuleSet? Please see [here](https://coreruleset.org/docs/concepts/false_positives_tuning) and edit the `/opt/npm/etc/modsecurity/crs-setup.conf` file.
- 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
- 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
- Faster creation of TLS certificates is achieved by eliminating unnecessary nginx reloads and configuration creations.
- Uses OCSP Stapling for enhanced security (manual certs not supported)
- Supports OCSP Stapling/Must-Staple for enhanced security (manual certs not supported, see compose.yaml for details)
- Resolved dnspod plugin issue
- To migrate manually, delete all dnspod certs and recreate them OR change the credentials file as per the template given [here](https://github.com/ZoeyVid/NPMplus/blob/develop/global/certbot-dns-plugins.js)
- Smaller docker image with alpine-based distribution
Expand All @@ -60,7 +55,7 @@ so that the barrier for entry here is low.
- access.log is disabled by default, unified and moved to `/opt/npm/nginx/access.log`
- Error Log written to console
- `Server` response header hidden
- PHP 8.2/8.3 optional, with option to add extensions; available packages can added using envs in the compose file
- PHP optional, with option to add extensions; available packages can added using envs in the compose file
- Allows different acme servers using env
- Supports up to 99 domains per cert
- Brotli compression can be enabled
Expand All @@ -69,7 +64,6 @@ so that the barrier for entry here is low.
- Automatic database vacuum (only sqlite)
- Automatic cleaning of old invalid certbot certs (set CLEAN to true)
- Password reset (only sqlite) using `docker exec -it npmplus password-reset.js USER_EMAIL PASSWORD`
- Supports TLS for MariaDB/MySQL; set `DB_MYSQL_TLS` env to true. Self-signed certificates can be uploaded to `/opt/npm/etc/npm/ca.crt` and `DB_MYSQL_CA` set to `/data/etc/npm/ca.crt` (not tested, unsupported)
- multi lang support, if you want to add an language, see this commit as an example: https://github.com/ZoeyVid/NPMplus/commit/a026b42329f66b89fe1fbe5e6034df5d3fc2e11f (implementation based on [@lateautumn233](https://github.com/lateautumn233) fork)
- See the compose file for all available options
- many env options optimized for network_mode host (ports/ip bindings)
Expand All @@ -79,17 +73,16 @@ so that the barrier for entry here is low.

## migration
- **NOTE: migrating back to the original is not possible**, so make first a **backup** before migration, so you can use the backup to switch back
- please delete all dnspod certs and recreate them after migration OR you manually change the credentialsfile (see [here](https://github.com/ZoeyVid/npmplus/blob/develop/global/certbot-dns-plugins.json) for the template)
- stop nginx-proxy-manager download the latest compose.yaml, adjust your paths (of /etc/letsencrypt and /data) to the ones you used with nginx-proxy-manager and adjust the env of the compose file how you like it and then deploy it
- you can now remove the /etc/letsencrypt mount, since it was moved to /data while migration and redeploy the compose file
- since this fork uses `network_mode: host` by default (and all guides are written for this mode), please don't forget to open port 80/tcp, 443/tcp and 443/udp (and maybe 81/tcp) in your firewall
- since many buttons changed, please edit every host you have and click save. (Please also resave it, if all buttons/values are fine, to update the host config to fully fit the NPMplus template)
- please delete all certs using dnspod as dns provider and recreate them after migration, since the certbot plugin used was replaced
- stop nginx-proxy-manager download the latest compose.yaml, adjust your paths (of /etc/letsencrypt and /data) to the ones you used with nginx-proxy-manager and adjust the envs of the compose file how you like it and then deploy it
- you can now remove the /etc/letsencrypt mount, since it was moved to /data while migration, and redeploy the compose file
- since many buttons changed, please check if they are still correct for every host you have.
- maybe setup crowdsec (see below)
- please report all (migration) issues you may have

# Quick Setup
1. Install Docker and Docker Compose (or portainer)
- [Docker Install documentation](https://docs.docker.com/engine)
1. Install Docker and Docker Compose (podman or docker rootless may also work)
- [Docker Install documentation](https://docs.docker.com/engine/install)
- [Docker Compose Install documentation](https://docs.docker.com/compose/install/linux)
2. Download this [compose.yaml](https://raw.githubusercontent.com/ZoeyVid/NPMplus/refs/heads/develop/compose.yaml) (or use its content as a portainer stack)
3. adjust TZ and ACME_EMAIL to your values and maybe adjust other env options to your needs.
Expand Down Expand Up @@ -170,8 +163,8 @@ location / {
```
b) Custom Nginx Configuration (advanced tab), which looks the following for file server and **php**:
- Note: the slash at the end of the file path is important
- Note: first enable `PHP82` and/or `PHP83` inside your compose file
- Note: you can replace `fastcgi_pass php82;` with `fastcgi_pass php83;`
- Note: first enable `PHP82`, `PHP83` and/or `PHP84` inside your compose file
- Note: you can replace `fastcgi_pass php82;` with `fastcgi_pass php83;`/`fastcgi_pass php84;`
- Note: to add more php extension using envs you can set in the compose file
```
location / {
Expand All @@ -191,11 +184,11 @@ location / {

### prerun scripts (EXPERT option) - if you don't know what this is, ignore it
run order: entrypoint.sh (prerun scripts) => start.sh => launch.sh <br>
if you need to run scripts before NPMplus launches put them under: `/opt/npm/etc/prerun/*.sh` (please add `#!/bin/sh` / `#!/bin/bash` to the top of the script) <br>
if you need to run scripts before NPMplus launches put them under: `/opt/npm/etc/prerun/*.sh` (please add `#!/usr/bin/env sh` / `#!/usr/bin/env bash` to the top of the script) <br>
you need to create this folder yourself - **NOTE:** I won't help you creating those patches/scripts if you need them you also need to know how to create them

## Contributing
All are welcome to create pull requests for this project.
All are welcome to create pull requests for this project, but this does not mean it will be merged.

# Please report Bugs first to this fork before reporting them to the upstream Repository
## Getting Help
Expand Down
4 changes: 2 additions & 2 deletions backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ async function appStart() {
internalCertificate.initTimer();
internalIpRanges.initTimer();

const server = app.listen(48693, '127.0.0.1', () => {
logger.info('Backend PID ' + process.pid + ' listening on port 48693 ...');
const server = app.listen(Number(process.env.NIBEP), '127.0.0.1', () => {
logger.info('Backend PID ' + process.pid + ' listening on port ' + process.env.NIBEP);

process.on('SIGTERM', () => {
logger.info('PID ' + process.pid + ' received SIGTERM');
Expand Down
11 changes: 0 additions & 11 deletions backend/internal/certificate.js
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,6 @@ const internalCertificate = {
logger.info(`Requesting Certbot certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);

const credentialsLocation = '/data/tls/certbot/credentials/credentials-' + certificate.id;
fs.mkdirSync('/data/tls/certbot/credentials', { recursive: true });
fs.writeFileSync(credentialsLocation, certificate.meta.dns_provider_credentials, { mode: 0o600 });

try {
Expand Down Expand Up @@ -889,16 +888,6 @@ const internalCertificate = {
return utils
.execFile(certbotCommand, [...certbotArgs, 'revoke', '--cert-name', `npm-${certificate.id}`, '--no-delete-after-revoke'])
.then(async (result) => {
fs.rm('/data/tls/certbot/credentials/credentials-' + certificate.id, { force: true }, (err) => {
if (err) {
logger.error('Error deleting credentials:', err.message);
if (throw_errors) {
throw err;
}
} else {
logger.info('Credentials file deleted successfully');
}
});
logger.info(result);
return result;
})
Expand Down
2 changes: 1 addition & 1 deletion backend/internal/ip_ranges.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ const internalIpRanges = {
let template = null;
const filename = '/tmp/ip_ranges.conf';
try {
template = fs.readFileSync(__dirname + '/../templates/ip_ranges.conf', { encoding: 'utf8' });
template = fs.readFileSync('/app/templates/ip_ranges.conf', { encoding: 'utf8' });
} catch (err) {
reject(new error.ConfigurationError(err.message));
return;
Expand Down
41 changes: 19 additions & 22 deletions backend/internal/nginx.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const error = require('../lib/error');
const internalNginx = {
/**
* This will:
* - test the nginx config first to make sure it's OK
* - create / recreate the config for the host
* - test again
* - IF OK: update the meta with online status
Expand All @@ -23,15 +22,7 @@ const internalNginx = {
let combined_meta = {};

return internalNginx
.test()
.then(() => {
// Nginx is OK
// We're deleting this config regardless.
return internalNginx.deleteConfig(host_type, host);
})
.then(() => {
return internalNginx.generateConfig(host_type, host);
})
.generateConfig(host_type, host)
.then(() => {
// Test nginx again and update meta with result
return internalNginx
Expand Down Expand Up @@ -79,30 +70,33 @@ const internalNginx = {
* @returns {Promise}
*/
test: () => {
return utils
.execFile('certbot-ocsp-fetcher.sh', ['-c', '/data/tls/certbot', '-o', '/data/tls/certbot/live', '--no-reload-webserver', '--quiet'])
.then(() => {
return utils.execFile('nginx', ['-tq']);
})
.catch(() => {
return utils.execFile('nginx', ['-tq']);
});
return utils.execFile('nginx', ['-tq']);
},

/**
* @returns {Promise}
*/

reload: () => {
return internalNginx.test().then(() => {
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']).finally(() => {
if (fs.existsSync('/usr/local/nginx/logs/nginx.pid') && fs.readFileSync('/usr/local/nginx/logs/nginx.pid', 'utf8').trim().length > 0) {
logger.info('Reloading Nginx');
return utils.execFile('nginx', ['-s', 'reload']);
} else {
logger.info('Starting Nginx');
utils.execfg('nginx', ['-e', 'stderr']);
}
});
} else {
if (fs.existsSync('/usr/local/nginx/logs/nginx.pid') && fs.readFileSync('/usr/local/nginx/logs/nginx.pid', 'utf8').trim().length > 0) {
logger.info('Reloading Nginx');
return utils.execFile('nginx', ['-s', 'reload']);
} else {
logger.info('Starting Nginx');
utils.execfg('nginx', ['-e', 'stderr']);
}
});
}
},

/**
Expand All @@ -127,7 +121,7 @@ const internalNginx = {
let template;

try {
template = fs.readFileSync(__dirname + '/../templates/_location.conf', { encoding: 'utf8' });
template = fs.readFileSync('/app/templates/_location.conf', { encoding: 'utf8' });
} catch (err) {
reject(new error.ConfigurationError(err.message));
return;
Expand All @@ -146,6 +140,7 @@ const internalNginx = {
locationCopy.forward_host = split.shift();
locationCopy.forward_path = `/${split.join('/')}`;
}
locationCopy.env = process.env;

renderedLocations += await renderEngine.parseAndRender(template, locationCopy);
}
Expand All @@ -172,7 +167,7 @@ const internalNginx = {
const filename = internalNginx.getConfigName(nice_host_type, host.id);

try {
template = fs.readFileSync(__dirname + '/../templates/' + nice_host_type + '.conf', { encoding: 'utf8' });
template = fs.readFileSync('/app/templates/' + nice_host_type + '.conf', { encoding: 'utf8' });
} catch (err) {
reject(new error.ConfigurationError(err.message));
return;
Expand Down Expand Up @@ -206,6 +201,8 @@ const internalNginx = {
locationsPromise = Promise.resolve();
}

host.env = process.env;

locationsPromise.then(() => {
renderEngine
.parseAndRender(template, host)
Expand Down
5 changes: 2 additions & 3 deletions backend/lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,14 @@ const configure = () => {
return;
}

const envSqliteFile = process.env.DB_SQLITE_FILE || '/data/etc/npm/database.sqlite';
logger.info(`Using Sqlite: ${envSqliteFile}`);
logger.info('Using Sqlite: /data/etc/npm/database.sqlite');
instance = {
database: {
engine: 'knex-native',
knex: {
client: 'better-sqlite3',
connection: {
filename: envSqliteFile,
filename: '/data/etc/npm/database.sqlite',
},
useNullAsDefault: true,
},
Expand Down
Loading

0 comments on commit d79a7de

Please sign in to comment.