Safer defaults, read-only role, security headers and syslog TCP limits

- ALLOW_PURGE is now false by default; the UI shows a banner when there is
  no authentication.
- Read-only role: AUTH_VIEWER_USER/AUTH_VIEWER_PASS in local mode, or
  OIDC_ADMIN_GROUP in OIDC mode; changes get 403 and the admin settings
  are greyed out.
- Content-Security-Policy (inline scripts allowed by hash) and other
  security headers; cross-site changes are refused.
- Syslog TCP: at most SYSLOG_TCP_MAX_CONNS connections, closed after
  SYSLOG_TCP_IDLE of silence; HTTP idle timeout.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
cedricandClaude Opus 5.5 committed 2026-10-03 16:30:40 +02:00
1 parent 3466a29692
commit 42f6137391
13 files changed
+543 -50

No files matched your search

+30 -2
View File
@@ -131,6 +131,10 @@ const I18N = {
purgeForbidden: 'Purging is disabled on this server (ALLOW_PURGE=false).',
err_purge_unavailable: 'VictoriaLogs refuses deletions: start it with -delete.enable (see docker-compose.yml)',
err_purge_forbidden: 'Purging is disabled on this server (ALLOW_PURGE=false)',
err_read_only: 'Read-only account: changes are reserved to administrators',
err_cross_site: 'Request refused: it comes from another site',
authOff: 'No authentication: anyone who can reach this page can read the logs and change the settings. Set AUTH_USER / AUTH_PASS or AUTH_MODE=oidc.',
readOnlyNote: 'Read-only account: these settings can only be changed by an administrator.',
err_purge_confirm: 'Type PURGE to confirm',
liveZoomed: 'Live view is not available on a zoomed range',
connZoom: 'live paused (zoom)',
@@ -282,6 +286,10 @@ const I18N = {
purgeForbidden: 'La purge est désactivée sur ce serveur (ALLOW_PURGE=false).',
err_purge_unavailable: 'VictoriaLogs refuse les suppressions : lancez-le avec -delete.enable (voir docker-compose.yml)',
err_purge_forbidden: 'La purge est désactivée sur ce serveur (ALLOW_PURGE=false)',
err_read_only: 'Compte en lecture seule : les modifications sont réservées aux administrateurs',
err_cross_site: 'Requête refusée : elle vient d\'un autre site',
authOff: 'Aucune authentification : toute personne qui atteint cette page peut lire les logs et changer les réglages. Définissez AUTH_USER / AUTH_PASS ou AUTH_MODE=oidc.',
readOnlyNote: 'Compte en lecture seule : seul un administrateur peut modifier ces réglages.',
err_purge_confirm: 'Tapez PURGE pour confirmer',
liveZoomed: 'Le direct n\'est pas disponible sur une plage zoomée',
connZoom: 'direct en pause (zoom)',
@@ -2212,14 +2220,34 @@ $('#settingsDlg').addEventListener('click', (ev) => { if (ev.target === ev.curre
applyLogFont(store.get('logFont', 'system'));
applyLogSize(store.get('logSize', 'medium'));
applyLogDensity(store.get('logDensity', 'normal'));
$('#authWarnClose').addEventListener('click', () => {
$('#authWarn').hidden = true;
store.set('authWarnHidden', '1');
});
applyLang();
$('#range').value = store.get('range', '1h');
if (!$('#range').value) $('#range').value = '1h';
$('#severity').value = store.get('severity', '');
// With a login (local or OIDC), show who is logged in and the log out button.
// Without a login, warn that the UI is open to everyone. With a login (local or OIDC),
// show who is logged in and the log out button, and lock the admin settings of a
// read-only account.
fetch('/auth/me').then((res) => (res.ok ? res.json() : null)).then((me) => {
if (!me || !me.user) return;
if (!me) return;
if (me.mode === 'none' && store.get('authWarnHidden') !== '1') $('#authWarn').hidden = false;
if (me.role === 'viewer') {
document.body.classList.add('read-only');
for (const p of document.querySelectorAll('[data-panel="filters"], [data-panel="sources"], [data-panel="data"]')) {
for (const s of p.querySelectorAll('.set-section')) s.inert = true;
const note = document.createElement('p');
note.className = 'ro-note';
note.dataset.i18n = 'readOnlyNote';
note.textContent = t('readOnlyNote');
p.prepend(note);
}
}
if (!me.user) return;
const btn = $('#logoutBtn');
btn.hidden = false;
btn.dataset.user = me.user;
+6
View File
@@ -97,6 +97,12 @@
<section id="histo" class="histo" data-i18n-aria="histoAria" hidden></section>
<div id="authWarn" class="auth-warn" role="status" hidden>
<span data-i18n="authOff"></span>
<button id="authWarnClose" class="icon-btn" type="button" data-i18n-aria="close">
<svg viewBox="0 0 24 24"><path d="M6 6l12 12M18 6 6 18"/></svg>
</button>
</div>
<div id="error" class="error-banner" hidden></div>
<button id="newPill" class="pill" type="button" hidden></button>
+17 -1
View File
@@ -433,6 +433,22 @@ mark.hit { background: var(--hit); color: inherit; border-radius: 3px; padding:
font-family: var(--mono); font-size: 12.5px; white-space: pre-wrap;
}
.auth-warn {
display: flex; align-items: center; gap: 10px;
margin: 6px 20px 0; padding: 6px 8px 6px 14px;
border: 1px solid color-mix(in srgb, var(--sev-warning) 45%, transparent);
background: color-mix(in srgb, var(--sev-warning) 12%, transparent);
color: var(--text); border-radius: var(--radius); font-size: 12.5px;
}
.auth-warn[hidden] { display: none; }
.auth-warn span { flex: 1; }
.auth-warn .icon-btn { width: 26px; height: 26px; flex: none; }
.ro-note {
margin: 0 0 12px; padding: 8px 12px; border-radius: var(--radius); font-size: 12.5px;
background: color-mix(in srgb, var(--accent) 10%, transparent); color: var(--text);
}
.read-only .set-panel .set-section[inert] { opacity: .55; }
.pill {
position: fixed; top: 70px; left: 50%; transform: translateX(-50%); z-index: 30;
border: 0; border-radius: 999px; padding: 7px 16px;
@@ -662,7 +678,7 @@ input.switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2
#count { flex: 1 1 50%; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.histo { padding: 0 16px 6px; }
.h-leg { display: none; }
.list, .error-banner, .table { margin-left: 16px; margin-right: 16px; }
.list, .error-banner, .auth-warn, .table { margin-left: 16px; margin-right: 16px; }
/* Phones: no columns, two lines per log (layout below); the widths do not apply */
.table { display: block; }
.table .list { display: block; margin: 0; }