1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00

fix(settings): ALLOWED_HOSTS lists the server's names instead of accepting any host

This commit is contained in:
catborise 2026-10-07 15:46:45 +03:00
parent c1381318f1
commit 9f5e8fe1c4
8 changed files with 90 additions and 4 deletions

View file

@ -84,10 +84,23 @@ Run WebVirtCloud in a container with persistent volumes for data and SSH keys:
git clone https://github.com/retspen/webvirtcloud
cd webvirtcloud
# 2. Start services:
# 2. Create docker-compose.override.yml (below) with your settings
# 3. Start services:
docker compose up -d
```
Local settings go in `docker-compose.override.yml`, which Compose merges into
`docker-compose.yml` and which `git pull` never touches. At least list the
names/IPs the panel is reached by:
```yaml
services:
webvirtcloud:
environment:
- ALLOWED_HOSTS=wvc.example.com,192.0.2.10
```
Access the panel at `http://<server-ip>` and noVNC console at port `6080`.
## Manual Installation
@ -106,6 +119,13 @@ python3 conf/runit/secret_generator.py > data/secret_key
chmod 600 data/secret_key
```
### Allowed hosts
The template answers only to localhost. After copying it in step 3 below, list
the names/IPs the panel and the cloud-init datasource are reached by in
`webvirtcloud/settings.py`, e.g.
`ALLOWED_HOSTS = ["wvc.example.com", "192.0.2.10", "localhost", "127.0.0.1", "[::1]"]`.
### Ubuntu 20.04 / 22.04 / 24.04 LTS & Debian 11 / 12
```bash
@ -442,6 +462,15 @@ of `webvirtcloud/settings.py.template` into your `webvirtcloud/settings.py`, set
(`sudo nginx -t && sudo systemctl reload nginx`). Re-running the installer
replaces that file, including any TLS directives you added to it.
`ALLOWED_HOSTS` no longer defaults to `"*"`: the template allows localhost, the
installer adds the server's name and IPs, and the `ALLOWED_HOSTS` env var
(comma-separated) adds more. Docker: set `ALLOWED_HOSTS` in
`docker-compose.override.yml` to the names/IPs you open the panel and the cloud-init datasource with; requests
for any other host get 400. Bare metal (your settings.py is kept): replace
`ALLOWED_HOSTS = ["*"]` with the names/IPs the panel and the datasource are
reached by, and copy the `if extra_hosts := ...` lines below it from the
template so the env var works.
```bash
# Go to Installation Directory
cd /srv/webvirtcloud
@ -453,6 +482,18 @@ python3 manage.py collectstatic --noinput
sudo systemctl restart supervisor # supervisord on RHEL / openSUSE
```
Docker: each new container generates `settings.py` from the template and runs
the migrations at start; the database and the secret key stay in the `data`
volume. Keep your variables (at least `ALLOWED_HOSTS`) in
`docker-compose.override.yml` (see Docker Deployment), then:
```bash
cd webvirtcloud
docker compose cp webvirtcloud:/srv/webvirtcloud/data/db.sqlite3 ./db.sqlite3.bak
git pull
docker compose up -d --build
```
> **Note on Settings Upgrade:**
> When upgrading from earlier versions using `drf-yasg`, update your `webvirtcloud/settings.py`:
> 1. In `INSTALLED_APPS`, replace `'drf_yasg'` with `'drf_spectacular'` and `'drf_spectacular_sidecar'`.

View file

@ -58,3 +58,16 @@ wvc_install_nginx() {
cp "$snippet" "$etc/conf.d/webvirtcloud.conf"
return 0
}
# Python list literal for ALLOWED_HOSTS: the FQDN, the loopbacks and the given
# IPs. IPv6 addresses are bracketed, as Django compares them.
wvc_allowed_hosts() {
local host out="" seen=" "
for host in "$1" localhost 127.0.0.1 ::1 "${@:2}"; do
case "$host" in *:*) host="[$host]" ;; esac
case "$seen" in *" $host "*) continue ;; esac
seen+="$host "
out+="${out:+, }'$host'"
done
echo "[$out]"
}

View file

@ -66,5 +66,9 @@ echo "OP_EDIT" > "$etc3/nginx.conf"
wvc_install_nginx "$tmp/main_tpl" "$tmp/snippet" "$etc3"
check "no-orig second run keeps operator edit" "[ \"\$(cat '$etc3/nginx.conf')\" = OP_EDIT ]"
# wvc_allowed_hosts: fqdn + loopbacks + IPs, IPv6 bracketed, no duplicates
check "allowed hosts list" "[ \"\$(wvc_allowed_hosts wvc.example.com 192.0.2.10 2001:db8::1 127.0.0.1)\" = \"['wvc.example.com', 'localhost', '127.0.0.1', '[::1]', '192.0.2.10', '[2001:db8::1]']\" ]"
check "allowed hosts without ips" "[ \"\$(wvc_allowed_hosts wvc.example.com)\" = \"['wvc.example.com', 'localhost', '127.0.0.1', '[::1]']\" ]"
[ "$fail" = 0 ] && echo "ALL PASS" || echo "FAILURES"
exit "$fail"

View file

@ -268,6 +268,7 @@ class ConsoleViewsTestCase(TestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context["ws_path"], "")
@override_settings(ALLOWED_HOSTS=["panel.example.com"])
@patch("console.views.wvmInstance")
def test_console_ws_host_strip_port(self, mock_wvm):
mock_conn = MagicMock()

View file

@ -14,6 +14,9 @@ services:
# - SECRET_KEY=your-custom-secret-key-here
# Optional: comma-separated trusted CSRF origins (e.g. http://wvc.example.com,https://wvc.example.com)
# - CSRF_TRUSTED_ORIGINS=http://localhost,http://127.0.0.1
# Host names/IPs the panel is reached by (comma-separated; "*" accepts any).
# Without it only localhost works. Set it in docker-compose.override.yml.
- ALLOWED_HOSTS=localhost
- TZ=UTC
volumes:
# Persistent SQLite database and runtime data

View file

@ -318,6 +318,9 @@ install_webvirtcloud () {
done
fi
sed -i "s|^\\(CSRF_TRUSTED_ORIGINS = \\).*|\\1\[ \'http://$fqdn\', $host_ip ]|" "$APP_PATH/webvirtcloud/settings.py"
# shellcheck disable=SC2046
allowed_hosts="$(wvc_allowed_hosts "$fqdn" $(hostname -I 2>/dev/null))"
sed -i "s|^ALLOWED_HOSTS = .*|ALLOWED_HOSTS = $allowed_hosts|" "$APP_PATH/webvirtcloud/settings.py"
else
echo "* Keeping existing settings.py (not overwritten)."
fi

View file

@ -29,7 +29,11 @@ DEBUG = False
MAC_OUI = '52:54:10'
ALLOWED_HOSTS = ["*"]
# Host names the panel answers to. The installer writes the server's; add more
# (comma-separated, "*" for any) through the environment.
ALLOWED_HOSTS = ["localhost", "127.0.0.1", "[::1]"]
if extra_hosts := os.getenv("ALLOWED_HOSTS"):
ALLOWED_HOSTS.extend([host.strip() for host in extra_hosts.split(",") if host.strip()])
CSRF_TRUSTED_ORIGINS = ["http://localhost", "http://127.0.0.1", "http://localhost:8000", "http://127.0.0.1:8000"]
if extra_origins := os.getenv("CSRF_TRUSTED_ORIGINS"):

View file

@ -7,11 +7,11 @@ import subprocess
import sys
from django.conf import settings
from django.test import TestCase, override_settings
from django.test import SimpleTestCase, TestCase, override_settings
# Env vars that change the settings; removed from the inherited environment so
# every case starts from the defaults.
CONFIG_VARS = ["WEBVIRTCLOUD_HTTPS"]
CONFIG_VARS = ["WEBVIRTCLOUD_HTTPS", "ALLOWED_HOSTS"]
def _env(env):
@ -68,3 +68,20 @@ class HttpsProfileTestCase(TestCase):
with override_settings(SECURE_SSL_REDIRECT=True):
response = self.client.get("/accounts/login/", HTTP_X_FORWARDED_PROTO="https")
self.assertNotEqual(response.status_code, 301)
class AllowedHostsTestCase(SimpleTestCase):
def hosts(self, env):
return load_settings(env, ["ALLOWED_HOSTS"])["ALLOWED_HOSTS"]
def test_allowed_hosts_default_is_not_any_host(self):
hosts = self.hosts({})
self.assertNotIn("*", hosts)
self.assertIn("localhost", hosts)
def test_allowed_hosts_extended_from_env(self):
hosts = self.hosts({"ALLOWED_HOSTS": " wvc.example.com, ,192.0.2.10 "})
self.assertEqual(hosts[-2:], ["wvc.example.com", "192.0.2.10"])
def test_allowed_hosts_star_escape(self):
self.assertIn("*", self.hosts({"ALLOWED_HOSTS": "*"}))