v8.2.3: Add backend header hiding option and improve UDP listener updates

- Introduced `hide_backend_headers` option in Nginx configuration for enhanced security.
- Updated models, templates, and JavaScript to support `hide_backend_headers`.
- Improved UDP listener rendering logic by dynamically fetching and syncing listeners via AJAX.
- Added new database migration to update version to `8.2.3`.
master
Aidaho 2025-08-26 09:24:20 +03:00
parent 20157572c6
commit dccc2b3e20
15 changed files with 103 additions and 24 deletions

View File

@ -0,0 +1,22 @@
from playhouse.migrate import *
from app.modules.db.db_model import connect, Version
migrator = connect(get_migrator=1)
def up():
"""Apply the migration."""
try:
Version.update(version='8.2.3').execute()
except Exception as e:
print(f"Error updating version: {str(e)}")
raise e
def down():
"""Roll back the migration."""
try:
Version.update(version='8.2.2').execute()
except Exception as e:
print(f"Error rolling back migration: {str(e)}")
raise e

View File

@ -135,7 +135,7 @@ location {{ location.location }} {
gzip_proxied any;
{% endif %}
{% if nginx_proxy.security.hide_backend_headers %}
{% if config.security.hide_backend_headers %}
# Hide backend headers
proxy_hide_header X-Powered-By;
proxy_hide_header Server;

View File

@ -9,7 +9,6 @@ nginx_proxy:
# Security configurations
security:
hide_backend_headers: true # Hide backend server headers (e.g., X-Powered-By)
hsts_max_age: 15768000 # HSTS duration in seconds (6 months)
content_security_policy: "default-src 'self'" # Content Security Policy rules

View File

@ -237,7 +237,14 @@ function getNginxFormData($form, form_name) {
let header = {action, name, value};
headers.push(header);
});
let location_config = {location, proxy_connect_timeout, proxy_read_timeout, proxy_send_timeout, headers, upstream};
let location_config = {
location,
proxy_connect_timeout,
proxy_read_timeout,
proxy_send_timeout,
headers,
upstream
};
indexed_array['locations'].push(location_config)
} else if (n['name'] === 'ssl_offloading') {
if ($('input[name="ssl_offloading"]').is(':checked')) {
@ -261,23 +268,31 @@ function getNginxFormData($form, form_name) {
indexed_array[n['name']] = n['value'];
}
});
$('#name_alias_div p').each(function (){
$('#name_alias_div p').each(function () {
let name = $(this).children("input[name='name_alias']").val();
if (name === undefined || name === '') {
return;
}
indexed_array['name_aliases'].push(name);
});
let hide_server_tokens = false;
let security_headers = false;
if ($('#hide_server_tokens').is(':checked')) {
hide_server_tokens = true;
}
if ($('#security_headers').is(':checked')) {
security_headers = true;
}
indexed_array['security'] = {'hide_server_tokens': hide_server_tokens, 'security_headers': security_headers};
$('#'+form_name+' span[name="add_servers"] p').each(function (){
let hide_server_tokens = false;
let security_headers = false;
let hide_backend_headers = false;
if ($('#hide_server_tokens').is(':checked')) {
hide_server_tokens = true;
}
if ($('#security_headers').is(':checked')) {
security_headers = true;
}
if ($('#hide_backend_headers').is(':checked')) {
hide_backend_headers = true;
}
indexed_array['security'] = {
'hide_server_tokens': hide_server_tokens,
'security_headers': security_headers,
'hide_backend_headers': hide_backend_headers
};
$('#' + form_name + ' span[name="add_servers"] p').each(function () {
let server = $(this).children("input[name='servers']").val();
if (server === undefined || server === '') {
return;
@ -290,7 +305,8 @@ function getNginxFormData($form, form_name) {
});
let elementsForDelete = [
'servers', 'server_port', 'max_fails', 'fail_timeout', 'proxy_connect_timeout', 'proxy_read_timeout', 'proxy_send_timeout',
'headers_res', 'header_name', 'header_value', 'upstream', 'server', 'name_alias', 'hide_server_tokens', 'security_headers'
'headers_res', 'header_name', 'header_value', 'upstream', 'server', 'name_alias', 'hide_server_tokens', 'security_headers',
'hide_backend_headers'
]
for (let element of elementsForDelete) {
delete indexed_array[element]

View File

@ -826,6 +826,11 @@ function openNginxSection(section) {
} else {
$('#security_headers').prop("checked", false);
}
if (data.security.hide_backend_headers) {
$('#hide_backend_headers').prop("checked", true);
} else {
$('#hide_backend_headers').prop("checked", false);
}
}
if (data.hsts) {
$('#hsts').prop("checked", true);

View File

@ -615,3 +615,33 @@ function checkUdpBackendStatus(listener_id, backend_ip) {
}
});
}
function getUdpListeners() {
$.ajax({
url: api_prefix + "/udp/listeners",
success: function (data) {
let clusters = document.querySelectorAll('.up-pannel > [id^="listener-"]');
let clusterNumbers = Array.from(clusters).map(div =>
parseInt(div.id.replace('listener-', ''), 10)
);
if (data.status === 'failed') {
toastr.error(data.error);
return;
}
if (data.length > 0) {
const dataIds = data.map(item => item.id);
const idsToDelete = clusterNumbers.filter(id => !dataIds.includes(id));
for (const [key, value] of Object.entries(data)) {
if (!clusterNumbers.includes(value.id)) {
getUDPListener(value.id, true);
} else {
getUDPListener(value.id, false);
}
}
for (let id of idsToDelete) {
$('#listener-' + id).remove();
}
}
}
});
}

View File

@ -120,6 +120,7 @@ Timeouts: Timeouts (proxy_connect_timeout).
{% set enable_sec_headers = lang.words.enable|title() + ' ' + lang.words.security + ' ' + lang.words.headers %}
<p>{{ checkbox('hide_server_tokens', title=lang.add_page.desc.hide_server_tokens, desc=lang.phrases.hide_server_tokens, checked='checked') }}</p>
<p>{{ checkbox('security_headers', title=lang.add_page.desc.security_headers, desc=lang.phrases.security_headers, checked='checked') }}</p>
<p>{{ checkbox('hide_backend_headers', title=lang.add_page.desc.hide_backend_headers, desc=lang.phrases.hide_backend_headers, checked='checked') }}</p>
</div>
</td>
</tr>

View File

@ -345,6 +345,7 @@
"check_interval_title": "Check interval. In seconds.",
"hide_server_tokens": "Hide server tokens",
"security_headers": "Enable security headers",
"hide_backend_headers": "Hide backend server headers",
}
%}
{% set roles = {
@ -358,6 +359,7 @@
"desc": {
"security_headers": "Enable security headers (X-Content-Type-Options, etc.)",
"hide_server_tokens": "Hide Nginx version in headers",
"hide_backend_headers": "Hide backend server headers (e.g., X-Powered-By)",
"port_check": "A basic TCP-layer health check tries to connect to the server's TCP port. The check is valid when the server answers with a SYN/ACK packet.",
"maxconn": "The total number of connections allowed, process-wide. This stops the process from accepting too many connections at once, which safeguards it from running out of memory.",
"server_template": "Create the list of servers from the template",

View File

@ -332,6 +332,7 @@
"check_interval_title": "Intervalo de comprobación. En segundos.",
"hide_server_tokens": "Ocultar tokens del servidor",
"security_headers": "Habilitar encabezados de seguridad",
"hide_backend_headers": "Ocultar los encabezados del servidor backend",
}
%}
{% set roles = {
@ -345,6 +346,7 @@
"desc": {
"security_headers": "Habilitar encabezados de seguridad (X-Content-Type-Options, etc.)",
"hide_server_tokens": "Ocultar la versión de Nginx en los encabezados",
"hide_backend_headers": "Ocultar los encabezados del servidor backend (por ejemplo, X-Powered-By)",
"port_check": "Una verificación básica a nivel TCP intenta conectarse al puerto TCP del servidor. La verificación es válida cuando el servidor responde con un paquete SYN/ACK.",
"maxconn": "Número total de conexiones permitidas a nivel de proceso. Esto evita aceptar demasiadas conexiones a la vez, protegiendo de quedarse sin memoria.",
"server_template": "Crear la lista de servidores desde la plantilla",

View File

@ -332,6 +332,7 @@
"check_interval_title": "Vérifiez l'intervalle. En secondes.",
"hide_server_tokens": "Masquer les jetons du serveur",
"security_headers": "Activer les en-têtes de sécurité",
"hide_backend_headers": "Masquer les en-têtes du serveur principal",
}
%}
{% set roles = {
@ -345,6 +346,7 @@
"desc": {
"security_headers": "Activer les en-têtes de sécurité (X-Content-Type-Options, etc.)",
"hide_server_tokens": "Masquer la version Nginx dans les en-têtes",
"hide_backend_headers": "Masquer les en-têtes du serveur principal (par exemple, X-Powered-By)",
"port_check": "Un contrôle de base de la couche TCP tente de se connecter au port TCP du serveur. Le contrôle est valide lorsque le serveur répond par un paquet SYN/ACK.",
"maxconn": "Le nombre total de connexions autorisées, à l\'échelle du processus. Cela empêche le processus d\'accepter trop de connexions à la fois, ce qui lui évite de manquer de mémoire.",
"server_template": "Créez la liste des serveurs à partir du modèle",

View File

@ -332,6 +332,7 @@
"check_interval_title": "Verifique o intervalo. Em segundos.",
"hide_server_tokens": "Ocultar tokens do servidor",
"security_headers": "Habilitar cabeçalhos de segurança",
"hide_backend_headers": "Ocultar cabeçalhos do servidor de backend",
}
%}
{% set roles = {
@ -345,6 +346,7 @@
"desc": {
"security_headers": "Habilitar cabeçalhos de segurança (X-Content-Type-Options, etc.)",
"hide_server_tokens": "Ocultar versão do Nginx nos cabeçalhos",
"hide_backend_headers": "Ocultar cabeçalhos do servidor de backend (por exemplo, X-Powered-By)",
"port_check": "a verificação de integridade básica do nivel TCP tenta se conectar à porta TCP do servidor. A verificação é válida quando o servidor responde com um pacote SYN/ACK",
"maxconn": "número total de conexões permitidas. Isso impede que o processo aceite muitas conexões de uma só vez, o que o protege de ficar sem memória.",
"server_template": "Criar uma lista de servidores de um modelo",

View File

@ -345,6 +345,7 @@
"check_interval_title": "Интервал проверки. В секундах.",
"hide_server_tokens": "Скрыть токены сервера",
"security_headers": "Включить заголовки безопасности",
"hide_backend_headers": "Скрыть заголовки внутреннего сервера",
}
%}
{% set roles = {
@ -358,6 +359,7 @@
"desc": {
"security_headers": "Включить заголовки безопасности (X-Content-Type-Options и т. д.)",
"hide_server_tokens": "Скрыть версию Nginx в заголовках",
"hide_backend_headers": "Скрыть заголовки внутреннего сервера (например, X-Powered-By)",
"port_check": "Базовая проверка работоспособности на уровне TCP пытается подключиться к TCP-порту сервера. Проверка действительна, когда сервер отвечает пакетом SYN/ACK.",
"maxconn": "Общее количество разрешенных подключений для всего процесса. Это не позволяет процессу одновременно принимать слишком много подключений, что защищает его от нехватки памяти.",
"server_template": "Создать список серверов из шаблона",

View File

@ -332,6 +332,7 @@
"check_interval_title": "检查间隔(以秒为单位)。",
"hide_server_tokens": "隐藏服务器令牌",
"security_headers": "启用安全标头",
"hide_backend_headers": "隐藏后端服务器标头",
}
%}
{% set roles = {
@ -345,6 +346,7 @@
"desc": {
"security_headers": "启用安全标头X-Content-Type-Options 等)",
"hide_server_tokens": "在标头中隐藏 Nginx 版本",
"hide_backend_headers": "隐藏后端服务器标头(例如 X-Powered-By",
"port_check": "基本的 TCP 层健康检查尝试连接到服务器的 TCP 端口。当服务器返回 SYN/ACK 数据包时,检查被认为是成功的。",
"maxconn": "进程允许的总连接数。这可以防止进程接受过多连接,从而防止内存耗尽。",
"server_template": "根据模板创建服务器列表。",

View File

@ -22,11 +22,7 @@
{% if g.user_params['role'] <= 3 %}
<div class="add-button add-button-big" title="{{lang.words.create|title()}} UDP {{ lang.words.listener }}" onclick="createUDPListener();">+ {{lang.words.create|title()}} UDP {{ lang.words.listener }}</div>
{% endif %}
<div class="up-pannel" class="sortable">
{% for listener in listeners %}
<div id="listener-{{listener.id}}" class="div-server-hapwi animated-background"></div>
{% endfor %}
</div>
<div class="up-pannel" class="sortable"></div>
<div id="create-udp-step-1" style="display: none;">
<table class="overview" id="create-udp-step-1-overview"
title="{{lang.words.create|title()}} UDP {{ lang.words.listener }}"
@ -179,9 +175,8 @@
<p><span class="ui-icon ui-icon-alert" style="float:left; margin:3px 12px 20px 0;"></span>{{lang.phrases.are_you_sure}}</p>
</div>
<script>
{% for listener in listeners %}
getUDPListener('{{listener.id}}');
{% endfor %}
getUdpListeners();
setInterval(getUdpListeners, 60000);
</script>
{% endif %}
{% endblock %}

View File

@ -105,7 +105,6 @@ class UDPListener(MethodView):
else:
if not listener_id:
kwargs = {
'listeners': udp_sql.select_listeners(g.user_params['group_id']),
'lang': g.user_params['lang'],
'clusters': ha_sql.select_clusters(g.user_params['group_id']),
'is_needed_tool': common.is_tool('ansible'),