2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-10-23 09:27:39 +00:00

Fixes for SITE_URL validity checks (#10619)

* [docker] Allow HTTPS port to be specified for Caddy proxy

* Fix naming collision for INVENTREE_WEB_PORT

* Push InvenTree version first

* Adjust Caddyfile

- Change backup server

* Fix docstring

* Tweak for site URL check:

- Ignore port if SITE_LAX_PROTOCOL_CHECK is set
- Invert logic for readability

* Additional checks for port mismatch

* Adjust middleware checks

- Allow for less strict checking of CSRF_TRUSTED_ORIGINS

* Slight refactor
This commit is contained in:
Oliver
2025-10-20 16:05:37 +11:00
committed by GitHub
parent 5425ace1fa
commit f9ce9e20b2
5 changed files with 78 additions and 20 deletions

View File

@@ -30,9 +30,9 @@
} }
# The default server address is configured in the .env file # The default server address is configured in the .env file
# If not specified, the default address is used - http://inventree.localhost # If not specified, the proxy listens for all http/https traffic
# If you need to listen on multiple addresses, or use a different port, you can modify this section directly # If you need to listen on multiple addresses, or use a different port, you can modify this section directly
{$INVENTREE_SITE_URL:http://inventree.localhost} { {$INVENTREE_SITE_URL:"http://, https://"} {
import log_common inventree import log_common inventree
encode gzip encode gzip

View File

@@ -101,6 +101,7 @@ services:
restart: unless-stopped restart: unless-stopped
# caddy acts as reverse proxy and static file server # caddy acts as reverse proxy and static file server
# You can adjust the ports that the proxy listens on via the .env file
# https://hub.docker.com/_/caddy # https://hub.docker.com/_/caddy
inventree-proxy: inventree-proxy:
container_name: inventree-proxy container_name: inventree-proxy
@@ -109,8 +110,8 @@ services:
depends_on: depends_on:
- inventree-server - inventree-server
ports: ports:
- ${INVENTREE_WEB_PORT:-80}:80 - ${INVENTREE_HTTP_PORT:-80}:80
- 443:443 - ${INVENTREE_HTTPS_PORT:-443}:443
env_file: env_file:
- .env - .env
volumes: volumes:

View File

@@ -239,13 +239,29 @@ class InvenTreeHostSettingsMiddleware(MiddlewareMixin):
accessed_scheme = request._current_scheme_host accessed_scheme = request._current_scheme_host
referer = urlsplit(accessed_scheme) referer = urlsplit(accessed_scheme)
# Ensure that the settings are set correctly with the current request site_url = urlsplit(settings.SITE_URL)
matches = (
(accessed_scheme and not accessed_scheme.startswith(settings.SITE_URL)) # Check if the accessed URL matches the SITE_URL setting
if not settings.SITE_LAX_PROTOCOL_CHECK site_url_match = (
else not is_same_domain(referer.netloc, urlsplit(settings.SITE_URL).netloc) (
# Exact match on domain
is_same_domain(referer.netloc, site_url.netloc)
and referer.scheme == site_url.scheme
)
or (
# Lax protocol match, accessed URL starts with SITE_URL
settings.SITE_LAX_PROTOCOL_CHECK
and accessed_scheme.startswith(settings.SITE_URL)
)
or (
# Lax protocol match, same domain
settings.SITE_LAX_PROTOCOL_CHECK
and referer.hostname == site_url.hostname
)
) )
if matches:
if not site_url_match:
# The accessed URL does not match the SITE_URL setting
if ( if (
isinstance(settings.CSRF_TRUSTED_ORIGINS, list) isinstance(settings.CSRF_TRUSTED_ORIGINS, list)
and len(settings.CSRF_TRUSTED_ORIGINS) > 1 and len(settings.CSRF_TRUSTED_ORIGINS) > 1
@@ -263,17 +279,31 @@ class InvenTreeHostSettingsMiddleware(MiddlewareMixin):
request, 'config_error.html', {'error_message': msg}, status=500 request, 'config_error.html', {'error_message': msg}, status=500
) )
# Check trusted origins trusted_origins_match = (
if not any( # Matching domain found in allowed origins
is_same_domain(referer.netloc, host) any(
for host in [ is_same_domain(referer.netloc, host)
urlsplit(origin).netloc.lstrip('*') for host in [
urlsplit(origin).netloc.lstrip('*')
for origin in settings.CSRF_TRUSTED_ORIGINS
]
)
) or (
# Lax protocol match allowed
settings.SITE_LAX_PROTOCOL_CHECK
and any(
referer.hostname == urlsplit(origin).hostname
for origin in settings.CSRF_TRUSTED_ORIGINS for origin in settings.CSRF_TRUSTED_ORIGINS
] )
): )
# Check trusted origins
if not trusted_origins_match:
msg = f'INVE-E7: The used path `{accessed_scheme}` is not in the TRUSTED_ORIGINS' msg = f'INVE-E7: The used path `{accessed_scheme}` is not in the TRUSTED_ORIGINS'
logger.error(msg) logger.error(msg)
return render( return render(
request, 'config_error.html', {'error_message': msg}, status=500 request, 'config_error.html', {'error_message': msg}, status=500
) )
# All checks passed
return None return None

View File

@@ -112,6 +112,15 @@ class MiddlewareTests(InvenTreeTestCase):
def test_site_lax_protocol(self): def test_site_lax_protocol(self):
"""Test that the site URL check is correctly working with/without lax protocol check.""" """Test that the site URL check is correctly working with/without lax protocol check."""
# Test that a completely different host fails
with self.settings(
SITE_URL='https://testserver', CSRF_TRUSTED_ORIGINS=['https://testserver']
):
response = self.client.get(
reverse('web'), HTTP_HOST='otherhost.example.com'
)
self.assertContains(response, 'INVE-E7: The visited path', status_code=500)
# Simple setup with proxy # Simple setup with proxy
with self.settings( with self.settings(
SITE_URL='https://testserver', CSRF_TRUSTED_ORIGINS=['https://testserver'] SITE_URL='https://testserver', CSRF_TRUSTED_ORIGINS=['https://testserver']
@@ -128,6 +137,24 @@ class MiddlewareTests(InvenTreeTestCase):
response = self.client.get(reverse('web')) response = self.client.get(reverse('web'))
self.assertContains(response, 'INVE-E7: The visited path', status_code=500) self.assertContains(response, 'INVE-E7: The visited path', status_code=500)
def test_site_url_port(self):
"""URL checks with different ports."""
with self.settings(
SITE_URL='https://testserver:8000',
CSRF_TRUSTED_ORIGINS=['https://testserver:8000'],
):
response = self.client.get(reverse('web'), HTTP_HOST='testserver:8008')
self.do_positive_test(response)
# Try again with strict protocol check
with self.settings(
SITE_URL='https://testserver:8000',
CSRF_TRUSTED_ORIGINS=['https://testserver:8000'],
SITE_LAX_PROTOCOL_CHECK=False,
):
response = self.client.get(reverse('web'), HTTP_HOST='testserver:8008')
self.assertContains(response, 'INVE-E7: The visited path', status_code=500)
def test_site_url_checks_multi(self): def test_site_url_checks_multi(self):
"""Test that the site URL check is correctly working in a multi-site setup.""" """Test that the site URL check is correctly working in a multi-site setup."""
# multi-site setup with trusted origins # multi-site setup with trusted origins
@@ -149,7 +176,7 @@ class MiddlewareTests(InvenTreeTestCase):
) )
self.do_positive_test(response) self.do_positive_test(response)
# A non-trsuted origin must still fail in multi - origin setup # A non-trusted origin must still fail in multi - origin setup
response = self.client.get( response = self.client.get(
'https://not-my-testserver.example.com/web/', 'https://not-my-testserver.example.com/web/',
SERVER_NAME='not-my-testserver.example.com', SERVER_NAME='not-my-testserver.example.com',

View File

@@ -1556,10 +1556,10 @@ Static {get_static_dir(error=False) or NOT_SPECIFIED}
Backup {get_backup_dir(error=False) or NOT_SPECIFIED} Backup {get_backup_dir(error=False) or NOT_SPECIFIED}
Versions: Versions:
Python {python_version()}
Django {InvenTreeVersion.inventreeDjangoVersion()}
InvenTree {InvenTreeVersion.inventreeVersion()} InvenTree {InvenTreeVersion.inventreeVersion()}
API {InvenTreeVersion.inventreeApiVersion()} API {InvenTreeVersion.inventreeApiVersion()}
Python {python_version()}
Django {InvenTreeVersion.inventreeDjangoVersion()}
Node {node if node else NA} Node {node if node else NA}
Yarn {yarn if yarn else NA} Yarn {yarn if yarn else NA}