diff --git a/create-a-container/routers/containers.js b/create-a-container/routers/containers.js
index 805b55fa..5debdf38 100644
--- a/create-a-container/routers/containers.js
+++ b/create-a-container/routers/containers.js
@@ -8,6 +8,7 @@ const serviceMap = require('../data/services.json');
const { isApiRequest } = require('../utils/http');
const { parseDockerRef, getImageConfig, extractImageMetadata } = require('../utils/docker-registry');
const { manageDnsRecords } = require('../utils/cloudflare-dns');
+const { isValidHostname } = require('../utils');
/**
* Normalize a Docker image reference to full format: host/org/image:tag
@@ -287,6 +288,12 @@ router.post('/', async (req, res) => {
}
// ---------------------------
+ // Hostname must be lowercase before validation
+ if (hostname) hostname = hostname.trim().toLowerCase();
+ if (!isValidHostname(hostname)) {
+ throw new Error('Invalid hostname: must be 1–63 characters, only lowercase letters, digits, and hyphens, and must start and end with a letter or digit');
+ }
+
const currentUser = req.session?.user || req.user?.username || 'api-user';
let envVarsJson = null;
diff --git a/create-a-container/server.js b/create-a-container/server.js
index 6a04fe15..9053f29c 100644
--- a/create-a-container/server.js
+++ b/create-a-container/server.js
@@ -87,9 +87,14 @@ async function main() {
next();
});
app.use(express.static('public'));
+
+ // We rate limit unsucessful (4xx/5xx statuses) to only 10 per 5 minutes, this
+ // should allow legitimate users a few tries to login or experiment without
+ // allowing bad-actors to abuse requests.
app.use(RateLimit({
- windowMs: 15 * 60 * 1000, // 15 minutes
- max: 100, // limit each IP to 100 requests per windowMs
+ windowMs: 5 * 60 * 1000,
+ max: 10,
+ skipSuccessfulRequests: true,
}));
// Set version info once at startup in app.locals
diff --git a/create-a-container/utils/index.js b/create-a-container/utils/index.js
index cfb0c503..65f53eaa 100644
--- a/create-a-container/utils/index.js
+++ b/create-a-container/utils/index.js
@@ -55,6 +55,16 @@ function getVersionInfo() {
}
}
+/**
+ * Validate that a hostname is a legal DNS subdomain label (RFC 1123).
+ * @param {string} hostname
+ * @returns {boolean}
+ */
+function isValidHostname(hostname) {
+ if (typeof hostname !== 'string') return false;
+ return /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/.test(hostname);
+}
+
/**
* Helper to validate that a redirect URL is a safe relative path.
* @param {string} url - the URL to validate
@@ -74,6 +84,7 @@ function isSafeRelativeUrl(url) {
module.exports = {
ProxmoxApi,
run,
+ isValidHostname,
isSafeRelativeUrl,
getVersionInfo
};
diff --git a/create-a-container/views/containers/form.ejs b/create-a-container/views/containers/form.ejs
index af72f82a..a55efc7b 100644
--- a/create-a-container/views/containers/form.ejs
+++ b/create-a-container/views/containers/form.ejs
@@ -28,7 +28,10 @@ const breadcrumbLabel = isEdit ? 'Edit' : 'New';
- <%= isEdit ? 'style="background-color: #e9ecef;"' : '' %>>
+ readonly style="background-color: #e9ecef;"<% } else { %>required pattern="[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?" title="Lowercase letters, digits, and hyphens only. Must start and end with a letter or digit (max 63 chars)."<% } %>>
+ <% if (!isEdit) { %>
+ Lowercase letters, digits, and hyphens. Must start and end with a letter or digit.
+ <% } %>
@@ -649,6 +652,14 @@ const breadcrumbLabel = isEdit ? 'Edit' : 'New';
addEnvVarRow();
});
+ // Auto-lowercase hostname input
+ const hostnameInput = document.getElementById('hostname');
+ if (!hostnameInput.readOnly) {
+ hostnameInput.addEventListener('input', () => {
+ hostnameInput.value = hostnameInput.value.toLowerCase();
+ });
+ }
+
// Initialize existing environment variables
if (existingEnvVars && typeof existingEnvVars === 'object') {
for (const [key, value] of Object.entries(existingEnvVars)) {
diff --git a/create-a-container/views/nginx-conf.ejs b/create-a-container/views/nginx-conf.ejs
index 6a23f463..ec6a591f 100644
--- a/create-a-container/views/nginx-conf.ejs
+++ b/create-a-container/views/nginx-conf.ejs
@@ -33,6 +33,26 @@ http {
modsecurity on;
modsecurity_rules_file /etc/nginx/modsecurity_includes.conf;
modsecurity_transaction_id "$request_id";
+
+ # Internal error page server on a unix socket. Named locations proxy here
+ # with proxy_method GET so that NGINX's static file module will serve the
+ # HTML regardless of the original request method (POST, PUT, etc.).
+ # ModSecurity is disabled to prevent re-evaluation of the original request.
+ upstream error_pages {
+ server unix:/run/nginx-error-pages.sock;
+ }
+
+ server {
+ listen unix:/run/nginx-error-pages.sock;
+ modsecurity off;
+ ssi on;
+
+ root /opt/opensource-server/error-pages;
+
+ location /403.html { }
+ location /404.html { }
+ location /502.html { }
+ }
server {
listen 80;
@@ -75,17 +95,22 @@ http {
error_page 403 @403;
location @403 {
- ssi on;
- root /opt/opensource-server/error-pages;
- try_files /403.html =403;
+ rewrite ^ /403.html break;
+ proxy_method GET;
+ proxy_pass_request_body off;
+ proxy_set_header Content-Length "";
+ proxy_pass http://error_pages;
}
<%_ if (httpServices.length === 0) { _%>
error_page 502 @502;
location @502 {
- root /opt/opensource-server/error-pages;
- try_files /502.html =502;
+ rewrite ^ /502.html break;
+ proxy_method GET;
+ proxy_pass_request_body off;
+ proxy_set_header Content-Length "";
+ proxy_pass http://error_pages;
}
location / {
@@ -117,11 +142,32 @@ http {
client_max_body_size 2G;
}
<%_ } else { _%>
+ error_page 403 @403;
error_page 404 @404;
+ error_page 502 @502;
+
+ location @403 {
+ rewrite ^ /403.html break;
+ proxy_method GET;
+ proxy_pass_request_body off;
+ proxy_set_header Content-Length "";
+ proxy_pass http://error_pages;
+ }
location @404 {
- root /opt/opensource-server/error-pages;
- try_files /404.html =404;
+ rewrite ^ /404.html break;
+ proxy_method GET;
+ proxy_pass_request_body off;
+ proxy_set_header Content-Length "";
+ proxy_pass http://error_pages;
+ }
+
+ location @502 {
+ rewrite ^ /502.html break;
+ proxy_method GET;
+ proxy_pass_request_body off;
+ proxy_set_header Content-Length "";
+ proxy_pass http://error_pages;
}
return 404;
@@ -163,16 +209,21 @@ http {
error_page 403 @403;
location @403 {
- ssi on;
- root /opt/opensource-server/error-pages;
- try_files /403.html =403;
+ rewrite ^ /403.html break;
+ proxy_method GET;
+ proxy_pass_request_body off;
+ proxy_set_header Content-Length "";
+ proxy_pass http://error_pages;
}
error_page 502 @502;
location @502 {
- root /opt/opensource-server/error-pages;
- try_files /502.html =502;
+ rewrite ^ /502.html break;
+ proxy_method GET;
+ proxy_pass_request_body off;
+ proxy_set_header Content-Length "";
+ proxy_pass http://error_pages;
}
# Proxy settings
@@ -243,16 +294,21 @@ http {
error_page 403 @403;
location @403 {
- ssi on;
- root /opt/opensource-server/error-pages;
- try_files /403.html =403;
+ rewrite ^ /403.html break;
+ proxy_method GET;
+ proxy_pass_request_body off;
+ proxy_set_header Content-Length "";
+ proxy_pass http://error_pages;
}
error_page 404 @404;
location @404 {
- root /opt/opensource-server/error-pages;
- try_files /404.html =404;
+ rewrite ^ /404.html break;
+ proxy_method GET;
+ proxy_pass_request_body off;
+ proxy_set_header Content-Length "";
+ proxy_pass http://error_pages;
}
# Return 404 for all requests
@@ -294,16 +350,21 @@ http {
error_page 403 @403;
location @403 {
- ssi on;
- root /opt/opensource-server/error-pages;
- try_files /403.html =403;
+ rewrite ^ /403.html break;
+ proxy_method GET;
+ proxy_pass_request_body off;
+ proxy_set_header Content-Length "";
+ proxy_pass http://error_pages;
}
error_page 502 @502;
location @502 {
- root /opt/opensource-server/error-pages;
- try_files /502.html =502;
+ rewrite ^ /502.html break;
+ proxy_method GET;
+ proxy_pass_request_body off;
+ proxy_set_header Content-Length "";
+ proxy_pass http://error_pages;
}
# Proxy to documentation site
diff --git a/images/agent/Dockerfile b/images/agent/Dockerfile
index f6c53cf4..52ae5105 100644
--- a/images/agent/Dockerfile
+++ b/images/agent/Dockerfile
@@ -19,6 +19,14 @@ RUN sed -i \
&& sed -i -e 's/IncludeOptional/Include/' /usr/share/modsecurity-crs/owasp-crs.load \
&& sed -i -e 's/^SecRuleEngine .*$/SecRuleEngine On/' /etc/nginx/modsecurity.conf
+# Logrotate overrides for NGINX and ModSecurity to work around a logrotate
+# repoen bug at https://github.com/owasp-modsecurity/ModSecurity-nginx/issues/351
+COPY ./images/agent/nginx.logrotate /etc/logrotate.d/nginx
+
+# Apply custom ModSecurity configurations. See the blame on that file for
+# details on what's been changed from stock.
+COPY ./images/agent/crs-setup.conf /etc/modsecurity/crs/crs-setup.conf
+
# Install DNSMasq and configure it to only get it's config from our pull-config
RUN sed -i \
-e 's/^CONFIG_DIR=\(.*\)$/#CONFIG_DIR=\1/' \
diff --git a/images/agent/crs-setup.conf b/images/agent/crs-setup.conf
new file mode 100644
index 00000000..ee2bfb77
--- /dev/null
+++ b/images/agent/crs-setup.conf
@@ -0,0 +1,885 @@
+# ------------------------------------------------------------------------
+# OWASP ModSecurity Core Rule Set ver.3.3.7
+# Copyright (c) 2006-2020 Trustwave and contributors. All rights reserved.
+# Copyright (c) 2021-2024 Core Rule Set project. All rights reserved.
+#
+# The OWASP ModSecurity Core Rule Set is distributed under
+# Apache Software License (ASL) version 2
+# Please see the enclosed LICENSE file for full details.
+# ------------------------------------------------------------------------
+
+
+#
+# -- [[ Introduction ]] --------------------------------------------------------
+#
+# The OWASP ModSecurity Core Rule Set (CRS) is a set of generic attack
+# detection rules that provide a base level of protection for any web
+# application. They are written for the open source, cross-platform
+# ModSecurity Web Application Firewall.
+#
+# See also:
+# https://coreruleset.org/
+# https://github.com/SpiderLabs/owasp-modsecurity-crs
+# https://www.owasp.org/index.php/Category:OWASP_ModSecurity_Core_Rule_Set_Project
+#
+
+
+#
+# -- [[ System Requirements ]] -------------------------------------------------
+#
+# CRS requires ModSecurity version 2.8.0 or above.
+# We recommend to always use the newest ModSecurity version.
+#
+# The configuration directives/settings in this file are used to control
+# the OWASP ModSecurity CRS. These settings do **NOT** configure the main
+# ModSecurity settings (modsecurity.conf) such as SecRuleEngine,
+# SecRequestBodyAccess, SecAuditEngine, SecDebugLog, and XML processing.
+#
+# The CRS assumes that modsecurity.conf has been loaded. It is bundled with
+# ModSecurity. If you don't have it, you can get it from:
+# 2.x: https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v2/master/modsecurity.conf-recommended
+# 3.x: https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v3/master/modsecurity.conf-recommended
+#
+# The order of file inclusion in your webserver configuration should always be:
+# 1. modsecurity.conf
+# 2. crs-setup.conf (this file)
+# 3. rules/*.conf (the CRS rule files)
+#
+# Please refer to the INSTALL file for detailed installation instructions.
+#
+
+
+#
+# -- [[ Mode of Operation: Anomaly Scoring vs. Self-Contained ]] ---------------
+#
+# The CRS can run in two modes:
+#
+# -- [[ Anomaly Scoring Mode (default) ]] --
+# In CRS3, anomaly mode is the default and recommended mode, since it gives the
+# most accurate log information and offers the most flexibility in setting your
+# blocking policies. It is also called "collaborative detection mode".
+# In this mode, each matching rule increases an 'anomaly score'.
+# At the conclusion of the inbound rules, and again at the conclusion of the
+# outbound rules, the anomaly score is checked, and the blocking evaluation
+# rules apply a disruptive action, by default returning an error 403.
+#
+# -- [[ Self-Contained Mode ]] --
+# In this mode, rules apply an action instantly. This was the CRS2 default.
+# It can lower resource usage, at the cost of less flexibility in blocking policy
+# and less informative audit logs (only the first detected threat is logged).
+# Rules inherit the disruptive action that you specify (i.e. deny, drop, etc).
+# The first rule that matches will execute this action. In most cases this will
+# cause evaluation to stop after the first rule has matched, similar to how many
+# IDSs function.
+#
+# -- [[ Alert Logging Control ]] --
+# In the mode configuration, you must also adjust the desired logging options.
+# There are three common options for dealing with logging. By default CRS enables
+# logging to the webserver error log (or Event viewer) plus detailed logging to
+# the ModSecurity audit log (configured under SecAuditLog in modsecurity.conf).
+#
+# - To log to both error log and ModSecurity audit log file, use: "log,auditlog"
+# - To log *only* to the ModSecurity audit log file, use: "nolog,auditlog"
+# - To log *only* to the error log file, use: "log,noauditlog"
+#
+# Examples for the various modes follow.
+# You must leave one of the following options enabled.
+# Note that you must specify the same line for phase:1 and phase:2.
+#
+
+# Default: Anomaly Scoring mode, log to error log, log to ModSecurity audit log
+# - By default, offending requests are blocked with an error 403 response.
+# - To change the disruptive action, see RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example
+# and review section 'Changing the Disruptive Action for Anomaly Mode'.
+# - In Apache, you can use ErrorDocument to show a friendly error page or
+# perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html
+#
+SecDefaultAction "phase:1,log,auditlog,pass"
+SecDefaultAction "phase:2,log,auditlog,pass"
+
+# Example: Anomaly Scoring mode, log only to ModSecurity audit log
+# - By default, offending requests are blocked with an error 403 response.
+# - To change the disruptive action, see RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example
+# and review section 'Changing the Disruptive Action for Anomaly Mode'.
+# - In Apache, you can use ErrorDocument to show a friendly error page or
+# perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html
+#
+# SecDefaultAction "phase:1,nolog,auditlog,pass"
+# SecDefaultAction "phase:2,nolog,auditlog,pass"
+
+# Example: Self-contained mode, return error 403 on blocking
+# - In this configuration the default disruptive action becomes 'deny'. After a
+# rule triggers, it will stop processing the request and return an error 403.
+# - You can also use a different error status, such as 404, 406, et cetera.
+# - In Apache, you can use ErrorDocument to show a friendly error page or
+# perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html
+#
+# SecDefaultAction "phase:1,log,auditlog,deny,status:403"
+# SecDefaultAction "phase:2,log,auditlog,deny,status:403"
+
+# Example: Self-contained mode, redirect back to homepage on blocking
+# - In this configuration the 'tag' action includes the Host header data in the
+# log. This helps to identify which virtual host triggered the rule (if any).
+# - Note that this might cause redirect loops in some situations; for example
+# if a Cookie or User-Agent header is blocked, it will also be blocked when
+# the client subsequently tries to access the homepage. You can also redirect
+# to another custom URL.
+# SecDefaultAction "phase:1,log,auditlog,redirect:'http://%{request_headers.host}/',tag:'Host: %{request_headers.host}'"
+# SecDefaultAction "phase:2,log,auditlog,redirect:'http://%{request_headers.host}/',tag:'Host: %{request_headers.host}'"
+
+
+#
+# -- [[ Paranoia Level Initialization ]] ---------------------------------------
+#
+# The Paranoia Level (PL) setting allows you to choose the desired level
+# of rule checks that will add to your anomaly scores.
+#
+# With each paranoia level increase, the CRS enables additional rules
+# giving you a higher level of security. However, higher paranoia levels
+# also increase the possibility of blocking some legitimate traffic due to
+# false alarms (also named false positives or FPs). If you use higher
+# paranoia levels, it is likely that you will need to add some exclusion
+# rules for certain requests and applications receiving complex input.
+#
+# - A paranoia level of 1 is default. In this level, most core rules
+# are enabled. PL1 is advised for beginners, installations
+# covering many different sites and applications, and for setups
+# with standard security requirements.
+# At PL1 you should face FPs rarely. If you encounter FPs, please
+# open an issue on the CRS GitHub site and don't forget to attach your
+# complete Audit Log record for the request with the issue.
+# - Paranoia level 2 includes many extra rules, for instance enabling
+# many regexp-based SQL and XSS injection protections, and adding
+# extra keywords checked for code injections. PL2 is advised
+# for moderate to experienced users desiring more complete coverage
+# and for installations with elevated security requirements.
+# PL2 comes with some FPs which you need to handle.
+# - Paranoia level 3 enables more rules and keyword lists, and tweaks
+# limits on special characters used. PL3 is aimed at users experienced
+# at the handling of FPs and at installations with a high security
+# requirement.
+# - Paranoia level 4 further restricts special characters.
+# The highest level is advised for experienced users protecting
+# installations with very high security requirements. Running PL4 will
+# likely produce a very high number of FPs which have to be
+# treated before the site can go productive.
+#
+# All rules will log their PL to the audit log;
+# example: [tag "paranoia-level/2"]. This allows you to deduct from the
+# audit log how the WAF behavior is affected by paranoia level.
+#
+# It is important to also look into the variable
+# tx.enforce_bodyproc_urlencoded (Enforce Body Processor URLENCODED)
+# defined below. Enabling it closes a possible bypass of CRS.
+#
+# Uncomment this rule to change the default:
+#
+#SecAction \
+# "id:900000,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:tx.paranoia_level=1"
+
+
+# It is possible to execute rules from a higher paranoia level but not include
+# them in the anomaly scoring. This allows you to take a well-tuned system on
+# paranoia level 1 and add rules from paranoia level 2 without having to fear
+# the new rules would lead to false positives that raise your score above the
+# threshold.
+# This optional feature is enabled by uncommenting the following rule and
+# setting the tx.executing_paranoia_level.
+# Technically, rules up to the level defined in tx.executing_paranoia_level
+# will be executed, but only the rules up to tx.paranoia_level affect the
+# anomaly scores.
+# By default, tx.executing_paranoia_level is set to tx.paranoia_level.
+# tx.executing_paranoia_level must not be lower than tx.paranoia_level.
+#
+# Please notice that setting tx.executing_paranoia_level to a higher paranoia
+# level results in a performance impact that is equally high as setting
+# tx.paranoia_level to said level.
+#
+#SecAction \
+# "id:900001,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:tx.executing_paranoia_level=1"
+
+
+#
+# -- [[ Enforce Body Processor URLENCODED ]] -----------------------------------
+#
+# ModSecurity selects the body processor based on the Content-Type request
+# header. But clients are not always setting the Content-Type header for their
+# request body payloads. This will leave ModSecurity with limited vision into
+# the payload. The variable tx.enforce_bodyproc_urlencoded lets you force the
+# URLENCODED body processor in these situations. This is off by default, as it
+# implies a change of the behaviour of ModSecurity beyond CRS (the body
+# processor applies to all rules, not only CRS) and because it may lead to
+# false positives already on paranoia level 1. However, enabling this variable
+# closes a possible bypass of CRS so it should be considered.
+#
+# Uncomment this rule to change the default:
+#
+#SecAction \
+# "id:900010,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:tx.enforce_bodyproc_urlencoded=1"
+
+
+#
+# -- [[ Anomaly Mode Severity Levels ]] ----------------------------------------
+#
+# Each rule in the CRS has an associated severity level.
+# These are the default scoring points for each severity level.
+# These settings will be used to increment the anomaly score if a rule matches.
+# You may adjust these points to your liking, but this is usually not needed.
+#
+# - CRITICAL severity: Anomaly Score of 5.
+# Mostly generated by the application attack rules (93x and 94x files).
+# - ERROR severity: Anomaly Score of 4.
+# Generated mostly from outbound leakage rules (95x files).
+# - WARNING severity: Anomaly Score of 3.
+# Generated mostly by malicious client rules (91x files).
+# - NOTICE severity: Anomaly Score of 2.
+# Generated mostly by the protocol rules (92x files).
+#
+# In anomaly mode, these scores are cumulative.
+# So it's possible for a request to hit multiple rules.
+#
+# (Note: In this file, we use 'phase:1' to set CRS configuration variables.
+# In general, 'phase:request' is used. However, we want to make absolutely sure
+# that all configuration variables are set before the CRS rules are processed.)
+#
+#SecAction \
+# "id:900100,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:tx.critical_anomaly_score=5,\
+# setvar:tx.error_anomaly_score=4,\
+# setvar:tx.warning_anomaly_score=3,\
+# setvar:tx.notice_anomaly_score=2"
+
+
+#
+# -- [[ Anomaly Mode Blocking Threshold Levels ]] ------------------------------
+#
+# Here, you can specify at which cumulative anomaly score an inbound request,
+# or outbound response, gets blocked.
+#
+# Most detected inbound threats will give a critical score of 5.
+# Smaller violations, like violations of protocol/standards, carry lower scores.
+#
+# [ At default value ]
+# If you keep the blocking thresholds at the defaults, the CRS will work
+# similarly to previous CRS versions: a single critical rule match will cause
+# the request to be blocked and logged.
+#
+# [ Using higher values ]
+# If you want to make the CRS less sensitive, you can increase the blocking
+# thresholds, for instance to 7 (which would require multiple rule matches
+# before blocking) or 10 (which would require at least two critical alerts - or
+# a combination of many lesser alerts), or even higher. However, increasing the
+# thresholds might cause some attacks to bypass the CRS rules or your policies.
+#
+# [ New deployment strategy: Starting high and decreasing ]
+# It is a common practice to start a fresh CRS installation with elevated
+# anomaly scoring thresholds (>100) and then lower the limits as your
+# confidence in the setup grows. You may also look into the Sampling
+# Percentage section below for a different strategy to ease into a new
+# CRS installation.
+#
+# [ Anomaly Threshold / Paranoia Level Quadrant ]
+#
+# High Anomaly Limit | High Anomaly Limit
+# Low Paranoia Level | High Paranoia Level
+# -> Fresh Site | -> Experimental Site
+# ------------------------------------------------------
+# Low Anomaly Limit | Low Anomaly Limit
+# Low Paranoia Level | High Paranoia Level
+# -> Standard Site | -> High Security Site
+#
+# Uncomment this rule to change the defaults:
+#
+#SecAction \
+# "id:900110,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:tx.inbound_anomaly_score_threshold=5,\
+# setvar:tx.outbound_anomaly_score_threshold=4"
+
+#
+# -- [[ Application Specific Rule Exclusions ]] ----------------------------------------
+#
+# Some well-known applications may undertake actions that appear to be
+# malicious. This includes actions such as allowing HTML or Javascript within
+# parameters. In such cases the CRS aims to prevent false positives by allowing
+# administrators to enable prebuilt, application specific exclusions on an
+# application by application basis.
+# These application specific exclusions are distinct from the rules that would
+# be placed in the REQUEST-900-EXCLUSION-RULES-BEFORE-CRS configuration file as
+# they are prebuilt for specific applications. The 'REQUEST-900' file is
+# designed for users to add their own custom exclusions. Note, using these
+# application specific exclusions may loosen restrictions of the CRS,
+# especially if used with an application they weren't designed for. As a result
+# they should be applied with care.
+# To use this functionality you must specify a supported application. To do so
+# uncomment rule 900130. In addition to uncommenting the rule you will need to
+# specify which application(s) you'd like to enable exclusions for. Only a
+# (very) limited set of applications are currently supported, please use the
+# filenames prefixed with 'REQUEST-903' to guide you in your selection.
+# Such filenames use the following convention:
+# REQUEST-903.9XXX-{APPNAME}-EXCLUSIONS-RULES.conf
+#
+# It is recommended if you run multiple web applications on your site to limit
+# the effects of the exclusion to only the path where the excluded webapp
+# resides using a rule similar to the following example:
+# SecRule REQUEST_URI "@beginsWith /wordpress/" setvar:tx.crs_exclusions_wordpress=1
+
+#
+# Modify and uncomment this rule to select which application:
+#
+#SecAction \
+# "id:900130,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:tx.crs_exclusions_cpanel=1,\
+# setvar:tx.crs_exclusions_drupal=1,\
+# setvar:tx.crs_exclusions_dokuwiki=1,\
+# setvar:tx.crs_exclusions_nextcloud=1,\
+# setvar:tx.crs_exclusions_wordpress=1,\
+# setvar:tx.crs_exclusions_xenforo=1"
+
+#
+# -- [[ HTTP Policy Settings ]] ------------------------------------------------
+#
+# This section defines your policies for the HTTP protocol, such as:
+# - allowed HTTP versions, HTTP methods, allowed request Content-Types
+# - forbidden file extensions (e.g. .bak, .sql) and request headers (e.g. Proxy)
+#
+# These variables are used in the following rule files:
+# - REQUEST-911-METHOD-ENFORCEMENT.conf
+# - REQUEST-912-DOS-PROTECTION.conf
+# - REQUEST-920-PROTOCOL-ENFORCEMENT.conf
+
+# HTTP methods that a client is allowed to use.
+# Default: GET HEAD POST OPTIONS
+# Example: for RESTful APIs, add the following methods: PUT PATCH DELETE
+# Example: for WebDAV, add the following methods: CHECKOUT COPY DELETE LOCK
+# MERGE MKACTIVITY MKCOL MOVE PROPFIND PROPPATCH PUT UNLOCK
+# Uncomment this rule to change the default.
+#
+# We add PUT PATCH and DELETE per the above to allow standard RESTful API
+# semantics.
+SecAction \
+ "id:900200,\
+ phase:1,\
+ nolog,\
+ pass,\
+ t:none,\
+ setvar:'tx.allowed_methods=GET HEAD POST OPTIONS PUT PATCH DELETE'"
+
+# Content-Types that a client is allowed to send in a request.
+# Default: |application/x-www-form-urlencoded| |multipart/form-data| |text/xml|
+# |application/xml| |application/soap+xml| |application/json|
+#
+# Please note, that the rule where CRS uses this variable (920420) evaluates it with operator
+# `@within`, which is case sensitive, but uses t:lowercase. You must add your whole custom
+# Content-Type with lowercase.
+#
+# Bypass Warning: some applications may not rely on the content-type request header in order
+# to parse the request body. This could make an attacker able to send malicious URLENCODED/JSON/XML
+# payloads without being detected by the WAF. Allowing request content-type that doesn't activate any
+# body processor (for example: "text/plain", "application/x-amf", "application/octet-stream", etc..)
+# could lead to a WAF bypass. For example, a malicious JSON payload submitted with a "text/plain"
+# content type may still be interpreted as JSON by a backend application but would not trigger the
+# JSON body parser at the WAF, leading to a bypass.
+#
+# When additional JSON content types are legitimately used in a deployment,
+# e.g. application/cloudevents+json, it is extremely important to ensure that a
+# rule exists to enable the engine's JSON body processor for these additional
+# JSON content types. Failure to do so can lead to a request body bypass. The
+# default JSON rule in modsecurity.conf-recommended (200001) will only activate
+# the JSON body processor for the specific content type application/json. The
+# optional modsecurity.conf-recommended rule 200006 can be used to enable the
+# JSON body processor for a wide variety of JSON content types.
+#
+# To prevent blocking request with not allowed content-type by default, you can create an exclusion
+# rule that removes rule 920420. For example:
+# SecRule REQUEST_HEADERS:Content-Type "@rx ^text/plain" \
+# "id:1234,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# ctl:ruleRemoveById=920420,\
+# chain"
+# SecRule REQUEST_URI "@rx ^/foo/bar" "t:none"
+#
+# Uncomment this rule to change the default.
+#
+# We add `application/x-protobuf` used by the VictoriaMetrics API for write requests.
+# See https://github.com/mieweb/opensource-server/issues/214
+SecAction \
+ "id:900220,\
+ phase:1,\
+ nolog,\
+ pass,\
+ t:none,\
+ setvar:'tx.allowed_request_content_type=|application/x-www-form-urlencoded| |multipart/form-data| |text/xml| |application/xml| |application/soap+xml| |application/json| |application/x-protobuf|'"
+
+# Allowed HTTP versions.
+# Default: HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0
+# Example for legacy clients: HTTP/0.9 HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0
+# Note that some web server versions use 'HTTP/2', some 'HTTP/2.0', so
+# we include both version strings by default.
+# Uncomment this rule to change the default.
+#
+# We add HTTP/3 and HTTP/3.0 to the list of allowed HTTP versions to support QUIC connections
+# via the ngx_http_v3_module. See https://github.com/mieweb/opensource-server/issues/211
+SecAction \
+ "id:900230,\
+ phase:1,\
+ nolog,\
+ pass,\
+ t:none,\
+ setvar:'tx.allowed_http_versions=HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0 HTTP/3 HTTP/3.0'"
+
+# Forbidden file extensions.
+# Guards against unintended exposure of development/configuration files.
+# Default: .asa/ .asax/ .ascx/ .axd/ .backup/ .bak/ .bat/ .cdx/ .cer/ .cfg/ .cmd/ .com/ .config/ .conf/ .cs/ .csproj/ .csr/ .dat/ .db/ .dbf/ .dll/ .dos/ .htr/ .htw/ .ida/ .idc/ .idq/ .inc/ .ini/ .key/ .licx/ .lnk/ .log/ .mdb/ .old/ .pass/ .pdb/ .pol/ .printer/ .pwd/ .rdb/ .resources/ .resx/ .sql/ .swp/ .sys/ .vb/ .vbs/ .vbproj/ .vsdisco/ .webinfo/ .xsd/ .xsx/
+# Example: .bak/ .config/ .conf/ .db/ .ini/ .log/ .old/ .pass/ .pdb/ .rdb/ .sql/
+# Uncomment this rule to change the default.
+#SecAction \
+# "id:900240,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:'tx.restricted_extensions=.asa/ .asax/ .ascx/ .axd/ .backup/ .bak/ .bat/ .cdx/ .cer/ .cfg/ .cmd/ .com/ .config/ .conf/ .cs/ .csproj/ .csr/ .dat/ .db/ .dbf/ .dll/ .dos/ .htr/ .htw/ .ida/ .idc/ .idq/ .inc/ .ini/ .key/ .licx/ .lnk/ .log/ .mdb/ .old/ .pass/ .pdb/ .pol/ .printer/ .pwd/ .rdb/ .resources/ .resx/ .sql/ .swp/ .sys/ .vb/ .vbs/ .vbproj/ .vsdisco/ .webinfo/ .xsd/ .xsx/'"
+
+# Forbidden request headers.
+# Header names should be lowercase, enclosed by /slashes/ as delimiters.
+# Default: /accept-charset/ /content-encoding/ /proxy/ /lock-token/ /content-range/ /if/
+#
+# Note: Accept-Charset is a deprecated header that should not be used by clients and
+# ignored by servers. It can be used for a response WAF bypass, by asking for a charset
+# that the WAF cannot decode.
+# Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset
+#
+# Note: Content-Encoding is used to list any encodings that have been applied to the
+# original payload. It is only used for compression, which isn't supported by CRS by
+# default since it blocks newlines and null bytes inside the request body. Most
+# compression algorithms require at least null bytes per RFC. Blocking it shouldn't
+# break anything and increases security since ModSecurity is incapable of properly
+# scanning compressed request bodies.
+#
+# Note: Blocking Proxy header prevents 'httpoxy' vulnerability: https://httpoxy.org
+#
+# Uncomment this rule to change the default.
+#SecAction \
+# "id:900250,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:'tx.restricted_headers=/accept-charset/ /content-encoding/ /proxy/ /lock-token/ /content-range/ /if/'"
+
+# File extensions considered static files.
+# Extensions include the dot, lowercase, enclosed by /slashes/ as delimiters.
+# Used in DoS protection rule. See section "Anti-Automation / DoS Protection".
+# Default: /.jpg/ /.jpeg/ /.png/ /.gif/ /.js/ /.css/ /.ico/ /.svg/ /.webp/
+# Uncomment this rule to change the default.
+#SecAction \
+# "id:900260,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:'tx.static_extensions=/.jpg/ /.jpeg/ /.png/ /.gif/ /.js/ /.css/ /.ico/ /.svg/ /.webp/'"
+
+# Content-Types charsets that a client is allowed to send in a request.
+# Default: utf-8|iso-8859-1|iso-8859-15|windows-1252
+# Uncomment this rule to change the default.
+# Use "|" to separate multiple charsets like in the rule defining
+# tx.allowed_request_content_type.
+#SecAction \
+# "id:900280,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:'tx.allowed_request_content_type_charset=utf-8|iso-8859-1|iso-8859-15|windows-1252'"
+
+#
+# -- [[ HTTP Argument/Upload Limits ]] -----------------------------------------
+#
+# Here you can define optional limits on HTTP get/post parameters and uploads.
+# This can help to prevent application specific DoS attacks.
+#
+# These values are checked in REQUEST-920-PROTOCOL-ENFORCEMENT.conf.
+# Beware of blocking legitimate traffic when enabling these limits.
+#
+
+# Block request if number of arguments is too high
+# Default: unlimited
+# Example: 255
+# Uncomment this rule to set a limit.
+#SecAction \
+# "id:900300,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:tx.max_num_args=255"
+
+# Block request if the length of any argument name is too high
+# Default: unlimited
+# Example: 100
+# Uncomment this rule to set a limit.
+#SecAction \
+# "id:900310,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:tx.arg_name_length=100"
+
+# Block request if the length of any argument value is too high
+# Default: unlimited
+# Example: 400
+# Uncomment this rule to set a limit.
+#SecAction \
+# "id:900320,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:tx.arg_length=400"
+
+# Block request if the total length of all combined arguments is too high
+# Default: unlimited
+# Example: 64000
+# Uncomment this rule to set a limit.
+#SecAction \
+# "id:900330,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:tx.total_arg_length=64000"
+
+# Block request if the file size of any individual uploaded file is too high
+# Default: unlimited
+# Example: 1048576
+# Uncomment this rule to set a limit.
+#SecAction \
+# "id:900340,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:tx.max_file_size=1048576"
+
+# Block request if the total size of all combined uploaded files is too high
+# Default: unlimited
+# Example: 1048576
+# Uncomment this rule to set a limit.
+#SecAction \
+# "id:900350,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:tx.combined_file_sizes=1048576"
+
+
+#
+# -- [[ Easing In / Sampling Percentage ]] -------------------------------------
+#
+# Adding the Core Rule Set to an existing productive site can lead to false
+# positives, unexpected performance issues and other undesired side effects.
+#
+# It can be beneficial to test the water first by enabling the CRS for a
+# limited number of requests only and then, when you have solved the issues (if
+# any) and you have confidence in the setup, to raise the ratio of requests
+# being sent into the ruleset.
+#
+# Adjust the percentage of requests that are funnelled into the Core Rules by
+# setting TX.sampling_percentage below. The default is 100, meaning that every
+# request gets checked by the CRS. The selection of requests, which are going
+# to be checked, is based on a pseudo random number generated by ModSecurity.
+#
+# If a request is allowed to pass without being checked by the CRS, there is no
+# entry in the audit log (for performance reasons), but an error log entry is
+# written. If you want to disable the error log entry, then issue the
+# following directive somewhere after the inclusion of the CRS
+# (E.g., RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf).
+#
+# SecRuleUpdateActionById 901150 "nolog"
+#
+# ATTENTION: If this TX.sampling_percentage is below 100, then some of the
+# requests will bypass the Core Rules completely and you lose the ability to
+# protect your service with ModSecurity.
+#
+# Uncomment this rule to enable this feature:
+#
+#SecAction "id:900400,\
+# phase:1,\
+# pass,\
+# nolog,\
+# setvar:tx.sampling_percentage=100"
+
+
+#
+# -- [[ Project Honey Pot HTTP Blacklist ]] ------------------------------------
+#
+# Optionally, you can check the client IP address against the Project Honey Pot
+# HTTPBL (dnsbl.httpbl.org). In order to use this, you need to register to get a
+# free API key. Set it here with SecHttpBlKey.
+#
+# Project Honeypot returns multiple different malicious IP types.
+# You may specify which you want to block by enabling or disabling them below.
+#
+# Ref: https://www.projecthoneypot.org/httpbl.php
+# Ref: https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual#wiki-SecHttpBlKey
+#
+# Uncomment these rules to use this feature:
+#
+#SecHttpBlKey XXXXXXXXXXXXXXXXX
+#SecAction "id:900500,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:tx.block_search_ip=1,\
+# setvar:tx.block_suspicious_ip=1,\
+# setvar:tx.block_harvester_ip=1,\
+# setvar:tx.block_spammer_ip=1"
+
+
+#
+# -- [[ GeoIP Database ]] ------------------------------------------------------
+#
+# There are some rulesets that inspect geolocation data of the client IP address
+# (geoLookup). The CRS uses geoLookup to implement optional country blocking.
+#
+# To use geolocation, we make use of the MaxMind GeoIP database.
+# This database is not included with the CRS and must be downloaded.
+#
+# There are two formats for the GeoIP database. ModSecurity v2 uses GeoLite (.dat files),
+# and ModSecurity v3 uses GeoLite2 (.mmdb files).
+#
+# If you use ModSecurity 3, MaxMind provides a binary for updating GeoLite2 files,
+# see https://github.com/maxmind/geoipupdate.
+#
+# Download the package for your OS, and read https://dev.maxmind.com/geoip/geoipupdate/
+# for configuration options.
+#
+# Warning: GeoLite (not GeoLite2) databases are considered legacy, and not being updated anymore.
+# See https://support.maxmind.com/geolite-legacy-discontinuation-notice/ for more info.
+#
+# Therefore, if you use ModSecurity v2, you need to regenerate updated .dat files
+# from CSV files first.
+#
+# You can achieve this using https://github.com/sherpya/geolite2legacy
+# Pick the zip files from maxmind site:
+# https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country-CSV.zip
+#
+# Follow the guidelines for installing the tool and run:
+# ./geolite2legacy.py -i GeoLite2-Country-CSV.zip \
+# -f geoname2fips.csv -o /usr/share/GeoliteCountry.dat
+#
+# Update the database regularly, see Step 3 of the configuration link above.
+#
+# By default, when you execute `sudo geoipupdate` on Linux, files from the free database
+# will be downloaded to `/usr/share/GeoIP` (both v1 and v2).
+#
+# Then choose from:
+# - `GeoLite2-Country.mmdb` (if you are using ModSecurity v3)
+# - `GeoLiteCountry.dat` (if you are using ModSecurity v2)
+#
+# Ref: http://blog.spiderlabs.com/2010/10/detecting-malice-with-modsecurity-geolocation-data.html
+# Ref: http://blog.spiderlabs.com/2010/11/detecting-malice-with-modsecurity-ip-forensics.html
+#
+# Uncomment only one of the next rules here to use this feature.
+# Choose the one depending on the ModSecurity version you are using, and change the path accordingly:
+#
+# For ModSecurity v3:
+#SecGeoLookupDB /usr/share/GeoIP/GeoLite2-Country.mmdb
+# For ModSecurity v2 (points to the converted one):
+#SecGeoLookupDB /usr/share/GeoIP/GeoLiteCountry.dat
+
+#
+# -=[ Block Countries ]=-
+#
+# Rules in the IP Reputation file can check the client against a list of high
+# risk country codes. These countries have to be defined in the variable
+# tx.high_risk_country_codes via their ISO 3166 two-letter country code:
+# https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements
+#
+# If you are sure that you are not getting any legitimate requests from a given
+# country, then you can disable all access from that country via this variable.
+# The rule performing the test has the rule id 910100.
+#
+# This rule requires SecGeoLookupDB to be enabled and the GeoIP database to be
+# downloaded (see the section "GeoIP Database" above.)
+#
+# By default, the list is empty. A list used by some sites was the following:
+# setvar:'tx.high_risk_country_codes=UA ID YU LT EG RO BG TR RU PK MY CN'"
+#
+# Uncomment this rule to use this feature:
+#
+#SecAction \
+# "id:900600,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:'tx.high_risk_country_codes='"
+
+
+#
+# -- [[ Anti-Automation / DoS Protection ]] ------------------------------------
+#
+# Optional DoS protection against clients making requests too quickly.
+#
+# When a client is making more than 100 requests (excluding static files) within
+# 60 seconds, this is considered a 'burst'. After two bursts, the client is
+# blocked for 600 seconds.
+#
+# Requests to static files are not counted towards DoS; they are listed in the
+# 'tx.static_extensions' setting, which you can change in this file (see
+# section "HTTP Policy Settings").
+#
+# For a detailed description, see rule file REQUEST-912-DOS-PROTECTION.conf.
+#
+# Uncomment this rule to use this feature:
+#
+#SecAction \
+# "id:900700,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:'tx.dos_burst_time_slice=60',\
+# setvar:'tx.dos_counter_threshold=100',\
+# setvar:'tx.dos_block_timeout=600'"
+
+
+#
+# -- [[ Check UTF-8 encoding ]] ------------------------------------------------
+#
+# The CRS can optionally check request contents for invalid UTF-8 encoding.
+# We only want to apply this check if UTF-8 encoding is actually used by the
+# site; otherwise it will result in false positives.
+#
+# Uncomment this rule to use this feature:
+#
+#SecAction \
+# "id:900950,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:tx.crs_validate_utf8_encoding=1"
+
+
+#
+# -- [[ Blocking Based on IP Reputation ]] ------------------------------------
+#
+# Blocking based on reputation is permanent in the CRS. Unlike other rules,
+# which look at the individual request, the blocking of IPs is based on
+# a persistent record in the IP collection, which remains active for a
+# certain amount of time.
+#
+# There are two ways an individual client can become flagged for blocking:
+# - External information (RBL, GeoIP, etc.)
+# - Internal information (Core Rules)
+#
+# The record in the IP collection carries a flag, which tags requests from
+# individual clients with a flag named IP.reput_block_flag.
+# But the flag alone is not enough to have a client blocked. There is also
+# a global switch named tx.do_reput_block. This is off by default. If you set
+# it to 1 (=On), requests from clients with the IP.reput_block_flag will
+# be blocked for a certain duration.
+#
+# Variables
+# ip.reput_block_flag Blocking flag for the IP collection record
+# ip.reput_block_reason Reason (= rule message) that caused to blocking flag
+# tx.do_reput_block Switch deciding if we really block based on flag
+# tx.reput_block_duration Setting to define the duration of a block
+#
+# It may be important to know, that all the other core rules are skipped for
+# requests, when it is clear that they carry the blocking flag in question.
+#
+# Uncomment this rule to use this feature:
+#
+#SecAction \
+# "id:900960,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:tx.do_reput_block=1"
+#
+# Uncomment this rule to change the blocking time:
+# Default: 300 (5 minutes)
+#
+#SecAction \
+# "id:900970,\
+# phase:1,\
+# nolog,\
+# pass,\
+# t:none,\
+# setvar:tx.reput_block_duration=300"
+
+
+#
+# -- [[ Collection timeout ]] --------------------------------------------------
+#
+# Set the SecCollectionTimeout directive from the ModSecurity default (1 hour)
+# to a lower setting which is appropriate to most sites.
+# This increases performance by cleaning out stale collection (block) entries.
+#
+# This value should be greater than or equal to:
+# tx.reput_block_duration (see section "Blocking Based on IP Reputation") and
+# tx.dos_block_timeout (see section "Anti-Automation / DoS Protection").
+#
+# Ref: https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual#wiki-SecCollectionTimeout
+
+# Please keep this directive uncommented.
+# Default: 600 (10 minutes)
+SecCollectionTimeout 600
+
+
+#
+# -- [[ End of setup ]] --------------------------------------------------------
+#
+# The CRS checks the tx.crs_setup_version variable to ensure that the setup
+# has been loaded. If you are not planning to use this setup template,
+# you must manually set the tx.crs_setup_version variable before including
+# the CRS rules/* files.
+#
+# The variable is a numerical representation of the CRS version number.
+# E.g., v3.0.0 is represented as 300.
+#
+SecAction \
+ "id:900990,\
+ phase:1,\
+ pass,\
+ t:none,\
+ nolog,\
+ setvar:tx.crs_setup_version=337"
diff --git a/images/agent/nginx.logrotate b/images/agent/nginx.logrotate
new file mode 100644
index 00000000..ef5a5585
--- /dev/null
+++ b/images/agent/nginx.logrotate
@@ -0,0 +1,32 @@
+/var/log/nginx/access.log
+/var/log/nginx/error.log
+/var/log/nginx/stream-access.log
+{
+ daily
+ missingok
+ rotate 14
+ compress
+ delaycompress
+ notifempty
+ create 0640 www-data adm
+ sharedscripts
+ prerotate
+ if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
+ run-parts /etc/logrotate.d/httpd-prerotate; \
+ fi \
+ endscript
+ postrotate
+ invoke-rc.d nginx rotate >/dev/null 2>&1
+ endscript
+}
+
+/var/log/nginx/modsec_audit.log
+{
+ daily
+ missingok
+ rotate 14
+ compress
+ delaycompress
+ notifempty
+ copytruncate
+}