Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions backend/internal/access-list.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import fs from "node:fs";
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
import batchflow from "batchflow";
import _ from "lodash";
import errs from "../lib/error.js";
Expand All @@ -11,6 +13,9 @@ import proxyHostModel from "../models/proxy_host.js";
import internalAuditLog from "./audit-log.js";
import internalNginx from "./nginx.js";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const omissions = () => {
return ["is_deleted"];
};
Expand Down Expand Up @@ -311,6 +316,13 @@ const internalAccessList = {
// do nothing
}

// delete the geo config file
try {
fs.unlinkSync(`/data/nginx/access_geo/${row.id}.conf`);
} catch (_err) {
// do nothing
}

// 4. audit log
await internalAuditLog.add(access, {
action: "deleted",
Expand Down Expand Up @@ -432,6 +444,7 @@ const internalAccessList = {
* @param {Integer} list.id
* @param {String} list.name
* @param {Array} list.items
* @param {Array} list.clients
* @returns {Promise}
*/
build: async (list) => {
Expand Down Expand Up @@ -482,6 +495,50 @@ const internalAccessList = {
});
});
}

// 4. Build geo config file for IP-based access control (used by Force SSL)
await internalAccessList.buildGeoConfig(list);
},

/**
* Build geo config file for IP-based access control
* This is used to check IP access before Force SSL redirect
* @param {Object} list
* @param {Integer} list.id
* @param {String} list.name
* @param {Array} list.clients
* @returns {Promise}
*/
buildGeoConfig: async (list) => {
const geoDir = '/data/nginx/access_geo';
const geoFile = `${geoDir}/${list.id}.conf`;

// Ensure directory exists
try {
fs.mkdirSync(geoDir, { recursive: true });
} catch (_err) {
// do nothing
}

// Remove existing geo file
try {
fs.unlinkSync(geoFile);
} catch (_err) {
// do nothing
}

// Only create geo config if there are client IP rules
if (list.clients && list.clients.length > 0) {
logger.info(`Building Geo config file #${list.id} for: ${list.name}`);

const renderEngine = utils.getRenderEngine();
const template = fs.readFileSync(`${__dirname}/../templates/access_list_geo.conf`, { encoding: 'utf8' });

const config = await renderEngine.parseAndRender(template, list);
fs.writeFileSync(geoFile, config, { encoding: 'utf8' });

logger.success(`Built Geo config file #${list.id} for: ${list.name}`);
}
}
}

Expand Down
18 changes: 18 additions & 0 deletions backend/templates/_forced_ssl.conf
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
{% if certificate and certificate_id > 0 -%}
{% if ssl_forced == 1 or ssl_forced == true %}
{% if access_list %}{% assign clients_size = access_list.clients | size %}{% else %}{% assign clients_size = 0 %}{% endif %}
{% if access_list_id > 0 and clients_size > 0 %}
# Force SSL (only for allowed IPs - denied IPs will get 403 from access rules)
set $redirect_to_ssl 0;
if ($scheme = "http") {
set $redirect_to_ssl 1;
}
if ($access_list_{{ access_list_id }}_allowed = 0) {
set $redirect_to_ssl 0;
}
if ($request_uri = /.well-known/acme-challenge/test-challenge) {
set $redirect_to_ssl 0;
}
if ($redirect_to_ssl = 1) {
return 301 https://$host$request_uri;
}
{% else %}
# Force SSL
include conf.d/include/force-ssl.conf;
{% endif %}
{% endif %}
{% endif %}
10 changes: 10 additions & 0 deletions backend/templates/access_list_geo.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Geo-based access control for access list {{ id }}
# Used to check IP access before Force SSL redirect
geo $access_list_{{ id }}_allowed {
default 0;
{% for client in clients %}
{% if client.directive == "allow" %}
{{ client.address }} 1;
{% endif %}
{% endfor %}
}
3 changes: 3 additions & 0 deletions docker/rootfs/etc/nginx/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ http {
# Custom
include /data/nginx/custom/http_top[.]conf;

# Access list geo definitions for Force SSL
include /data/nginx/access_geo/*.conf;

# Files generated by NPM
include /etc/nginx/conf.d/*.conf;
include /data/nginx/default_host/*.conf;
Expand Down
1 change: 1 addition & 0 deletions docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/20-paths.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mkdir -p \
/data/nginx/stream \
/data/nginx/dead_host \
/data/nginx/temp \
/data/nginx/access_geo \
/data/letsencrypt-acme-challenge \
/run/nginx \
/tmp/nginx/body \
Expand Down