From d3f333d3d2a3245036fb26ffd58b71f82ec6fd0c Mon Sep 17 00:00:00 2001 From: holler Date: Thu, 16 Jul 2026 09:21:52 +0200 Subject: [PATCH] Rework Apache vhost for SSL-terminating reverse proxy TLS is terminated at the proxy; Apache receives plain HTTP on :80. Merge all rules into a single *:80 vhost (the :443 vhost was never reached) and make the HTTPS redirect conditional on X-Forwarded-Proto: http. Fixes ERR_TOO_MANY_REDIRECTS. --- apache/conf/omny.conf | 73 +++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 41 deletions(-) diff --git a/apache/conf/omny.conf b/apache/conf/omny.conf index aa800af..effbbbd 100644 --- a/apache/conf/omny.conf +++ b/apache/conf/omny.conf @@ -1,51 +1,33 @@ # ============================================================ -# omny-test.psi.ch — HTTPS test instance +# omny-test.psi.ch — behind an SSL-terminating reverse proxy # -# Based on the admin-provided vhost skeleton, extended with: -# * HTTPS vhost + HTTP->HTTPS redirect (ACME path excepted) -# * DirectoryIndex, -Indexes -# * deny rules for .htpasswd/.gen/.inc/backup files -# * auth-gate rewrite rules +# The proxy terminates HTTPS and forwards requests to this +# Apache as plain HTTP on port 80. Therefore: +# * everything (auth-gate rewrites, deny rules) lives in the +# *:80 vhost — there is no *:443 vhost on this host +# * the HTTP->HTTPS redirect fires ONLY when the proxy +# explicitly reports "X-Forwarded-Proto: http". If the +# header is absent, no redirect happens (loop-proof). # # Requirements (for the administrators): -# * mod_rewrite and mod_ssl enabled. The vhost will -# intentionally FAIL to load without mod_rewrite, rather -# than silently serving everything unauthenticated. +# * mod_rewrite enabled. The vhost will intentionally FAIL +# to load without it, rather than silently serving +# everything unauthenticated. # * PHP execution active for .php files in /var/www/omny. -# * SSLCertificateFile / SSLCertificateKeyFile paths below -# are PLACEHOLDERS (Let's Encrypt layout) — please adjust -# to the actual certificate location for omny-test.psi.ch. +# * IMPORTANT: mod_remoteip configured with the proxy's IP +# (RemoteIPHeader X-Forwarded-For + +# RemoteIPInternalProxy ), so REMOTE_ADDR shows +# the real client. Several endpoints authorize by client +# IP; without mod_remoteip they see only the proxy address. # ============================================================ -# ------------------------------------------------------------ -# Port 80: redirect everything to HTTPS, except ACME challenges -# (exception only matters if certbot webroot renewal is used; -# harmless otherwise) -# ------------------------------------------------------------ ServerName omny-test.psi.ch DocumentRoot /var/www/omny - RewriteEngine On - RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge/ - RewriteRule ^ https://omny-test.psi.ch%{REQUEST_URI} [R=301,L] - # Query strings are preserved automatically on [R] redirects. - - -# ------------------------------------------------------------ -# Port 443: the actual site -# ------------------------------------------------------------ - - ServerName omny-test.psi.ch - DocumentRoot /var/www/omny - ErrorLog ${APACHE_LOG_DIR}/omny-test_error.log CustomLog ${APACHE_LOG_DIR}/omny-test_access.log combined - # Optional hardening (requires mod_headers). Deliberately - # disabled on the test instance; enable on production: - # Header always set Strict-Transport-Security "max-age=31536000" - Options +ExecCGI -Indexes AllowOverride None @@ -67,10 +49,19 @@ RewriteEngine On - # 0. URL-level deny before any rewriting. + # -------------------------------------------------------- + # 0. HTTPS enforcement, loop-proof behind the proxy: + # redirect ONLY if the proxy explicitly reports that the + # original request was plain HTTP. If the proxy does not + # send X-Forwarded-Proto, this never matches -> no loop. + # -------------------------------------------------------- + RewriteCond %{HTTP:X-Forwarded-Proto} =http + RewriteRule ^ https://omny-test.psi.ch%{REQUEST_URI} [R=301,L] + + # 1. URL-level deny before any rewriting. RewriteRule \.(htpasswd|gen)$ - [F,L] - # 1. Guard: forbid direct client requests that pass the + # 2. Guard: forbid direct client requests that pass the # internal _file parameter to the gate scripts (would # otherwise let a logged-in user stream PHP source incl. # TOKEN_SECRET). Internal rewrites are unaffected: the @@ -79,7 +70,7 @@ RewriteCond %{QUERY_STRING} (^|&)_file= RewriteRule ^/(samples/)?auth_gate\.php$ - [F,L] - # 2. Pass-throughs (no cookie check) — first match wins. + # 3. Pass-throughs (no cookie check) — first match wins. RewriteRule ^/login\.php$ - [L] RewriteRule ^/upload\.php$ - [L] RewriteRule ^/session_query\.php$ - [L] @@ -91,11 +82,11 @@ RewriteRule ^/samples/upload\.php$ - [L] RewriteRule ^/samples/newmeasurement\.php$ - [L] - # 3. Normalize /samples -> /samples/ so it hits the samples + # 4. Normalize /samples -> /samples/ so it hits the samples # gate instead of falling through to the main gate. RewriteRule ^/samples$ /samples/ [R=301,L] - # 4. Samples area: fixed users only. + # 5. Samples area: fixed users only. # [END] (not [L]) is essential: Apache re-runs the rule # set after internal redirects; without END this rule # would match its own output and loop. @@ -104,6 +95,6 @@ # (auth_gate.php handles this form via strtok). RewriteRule ^/samples/(.*)$ /samples/auth_gate.php?_file=$1 [END,QSA] - # 5. Everything else: main auth gate. + # 6. Everything else: main auth gate. RewriteRule ^/(.*)$ /auth_gate.php?_file=$1 [END,QSA] - + \ No newline at end of file