From 2cbcb8ab2cafae1c494e3893e7ebe34c17ba4fbb Mon Sep 17 00:00:00 2001 From: catborise Date: Thu, 5 Nov 2026 17:29:51 +0300 Subject: [PATCH] fix: the views that take a password or a key keep it out of error reports No view used sensitive_post_parameters, so once ADMINS is set the error mail of a failing POST would carry the password or key in plain text. The user create, edit and password views, the password change, the compute forms, the host secrets, the VM's root password and its console settings now mark their POST data sensitive. --- accounts/views.py | 2 ++ admin/views.py | 4 +++ computes/views.py | 3 +++ instances/views.py | 3 +++ virtsecrets/views.py | 2 ++ webvirtcloud/tests/test_sensitive_posts.py | 30 ++++++++++++++++++++++ 6 files changed, 44 insertions(+) create mode 100644 webvirtcloud/tests/test_sensitive_posts.py diff --git a/accounts/views.py b/accounts/views.py index 02be62b7..86ae8ebc 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -20,6 +20,7 @@ from django.utils.http import url_has_allowed_host_and_scheme from django.utils.translation import gettext_lazy as _ from django.views.decorators.cache import never_cache from django.views.decorators.http import require_POST +from django.views.decorators.debug import sensitive_post_parameters from accounts.apps import admin_password_path from computes.utils import lock_ownership @@ -186,6 +187,7 @@ def account(request, user_id): ) +@sensitive_post_parameters() @login_required def change_password(request): must_change = UserAttributes.objects.filter( diff --git a/admin/views.py b/admin/views.py index c32a49b8..a9bb11cb 100644 --- a/admin/views.py +++ b/admin/views.py @@ -15,6 +15,7 @@ from django.utils import timezone from django.utils.http import url_has_allowed_host_and_scheme, urlencode from django.utils.translation import gettext_lazy as _ from django.views.decorators.http import require_POST +from django.views.decorators.debug import sensitive_post_parameters from instances.models import InstanceTombstone from instances.report import workbook from logs.audit import audit, changed_fields, snapshot @@ -124,6 +125,7 @@ def user_list(request): ) +@sensitive_post_parameters() @superuser_only def user_create(request): user_form = forms.UserCreateForm(request.POST or None) @@ -149,6 +151,7 @@ def user_create(request): ) +@sensitive_post_parameters() @superuser_only def user_update(request, pk): user = get_object_or_404(User, pk=pk) @@ -190,6 +193,7 @@ def user_update(request, pk): ) +@sensitive_post_parameters() @superuser_only def user_update_password(request, pk): user = get_object_or_404(User, pk=pk) diff --git a/computes/views.py b/computes/views.py index 69d759c3..99b15830 100644 --- a/computes/views.py +++ b/computes/views.py @@ -8,6 +8,7 @@ from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils import timezone from django.utils.translation import gettext_lazy as _ +from django.views.decorators.debug import sensitive_post_parameters from instances.models import Instance from instances.utils import read_list_rows from libvirt import libvirtError @@ -145,6 +146,7 @@ def instances(request, compute_id): ) +@sensitive_post_parameters() @superuser_only def compute_create(request, FormClass): form = FormClass(request.POST or None) @@ -157,6 +159,7 @@ def compute_create(request, FormClass): return render(request, "computes/form.html", {"form": form}) +@sensitive_post_parameters() @superuser_only def compute_update(request, compute_id): compute = get_object_or_404(Compute, pk=compute_id) diff --git a/instances/views.py b/instances/views.py index a169a37f..da159006 100755 --- a/instances/views.py +++ b/instances/views.py @@ -38,6 +38,7 @@ from django.utils import timezone from django.utils.translation import gettext as _ from django.utils.translation import gettext_lazy, gettext_noop from django.views.decorators.http import require_POST +from django.views.decorators.debug import sensitive_post_parameters from libvirt import (VIR_DOMAIN_PAUSED, VIR_DOMAIN_RUNNING, VIR_DOMAIN_SHUTOFF, @@ -1091,6 +1092,7 @@ def access_error(request, err): return text +@sensitive_post_parameters() @require_POST @serialize_instance_mutation def set_root_pass(request, pk): @@ -2320,6 +2322,7 @@ def clone(request, pk): return redirect(reverse("instances:instance", args=[pk])) +@sensitive_post_parameters() @require_POST @serialize_instance_mutation def update_console(request, pk): diff --git a/virtsecrets/views.py b/virtsecrets/views.py index 97b2a91d..576dfd80 100644 --- a/virtsecrets/views.py +++ b/virtsecrets/views.py @@ -3,6 +3,7 @@ from computes.models import Compute from django.contrib import messages from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, render +from django.views.decorators.debug import sensitive_post_parameters from instances.models import Instance from libvirt import libvirtError from logs.audit import audit @@ -11,6 +12,7 @@ from vrtManager.virtsecrets import wvmSecrets from virtsecrets.forms import USAGE_TEXTS, AddSecret +@sensitive_post_parameters() @superuser_only def secrets(request, compute_id): """The host's secrets with the VMs and pools that use them; one created, diff --git a/webvirtcloud/tests/test_sensitive_posts.py b/webvirtcloud/tests/test_sensitive_posts.py new file mode 100644 index 00000000..1b797f07 --- /dev/null +++ b/webvirtcloud/tests/test_sensitive_posts.py @@ -0,0 +1,30 @@ +"""The views that take a password or a key mark their POST data sensitive: +an error report (mail_admins) leaves it out.""" + +from unittest.mock import patch + +from django.contrib.auth import get_user_model +from django.test import TestCase +from django.urls import reverse + +from computes.models import Compute +from instances.models import Instance + + +class SensitivePostTestCase(TestCase): + def test_each_view_that_takes_a_secret_hides_its_post_data(self): + admin = get_user_model().objects.create_superuser("sens-admin", "s@example.com", "pw") + compute = Compute.objects.create(name="sens-c", hostname="192.0.2.90", login="root", password="", type=1) + vm = Instance.objects.create(compute=compute, name="sens-vm", uuid="aaaaaaaa-0000-0000-0000-000000000091") + self.client.force_login(admin) + self.client.raise_request_exception = False + urls = [reverse("admin:user_create"), reverse("admin:user_update", args=[admin.pk]), + reverse("admin:user_update_password", args=[admin.pk]), reverse("accounts:change_password"), + reverse("add_tcp_host"), reverse("compute_update", args=[compute.pk]), + reverse("virtsecrets", args=[compute.pk]), + reverse("instances:rootpasswd", args=[vm.pk]), reverse("instances:update_console", args=[vm.pk])] + with patch("instances.models.wvmInstance"), patch("virtsecrets.views.wvmSecrets"): + for url in urls: + with self.subTest(url): + response = self.client.post(url, {"password": "S3cret"}) + self.assertEqual(response.wsgi_request.sensitive_post_parameters, "__ALL__")