mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +00:00
fix(virtsecrets): a secret's value never reaches the page
The edit dialog of a secret was filled with its current value, so every readable secret's value was in the secrets page's HTML. A private secret, or one with no value yet, cannot be read: its field held the text "None", which is valid base64, and saving the dialog unchanged set the secret to the three bytes it decodes to. The dialog now only writes a new value: the field starts empty, is a password field and is required. The page no longer reads secret values, and get_secret_value, used only for that, is gone.
This commit is contained in:
parent
ce7c772aae
commit
4ece22230e
5 changed files with 35 additions and 16 deletions
|
|
@ -96,7 +96,7 @@
|
|||
<label class="col-sm-3 col-form-label" for="secret_value_{{ secret.uuid }}">{% trans "Value" %}</label>
|
||||
<input type="hidden" name="uuid" value="{{ secret.uuid }}">
|
||||
<div class="col-sm-6">
|
||||
<input type="text" id="secret_value_{{ secret.uuid }}" name="value" class="form-control" value="{{ secret.value }}" required pattern="[A-Za-z0-9+\/]*={0,2}">
|
||||
<input type="password" id="secret_value_{{ secret.uuid }}" name="value" class="form-control" autocomplete="new-password" required pattern="[A-Za-z0-9+\/]*={0,2}">
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- /.modal-content -->
|
||||
|
|
|
|||
|
|
@ -35,12 +35,9 @@ class SecretValueTests(SimpleTestCase):
|
|||
secret = get.return_value.secretLookupByUUIDString.return_value
|
||||
return conn, secret
|
||||
|
||||
def test_the_shown_value_written_back_keeps_the_secret(self):
|
||||
def test_a_base64_value_is_stored_as_its_bytes(self):
|
||||
conn, secret = self.secrets()
|
||||
secret.value.return_value = b"pw\x00\xff"
|
||||
shown = conn.get_secret_value("uuid")
|
||||
self.assertEqual(shown, "cHcA/w==") # base64 text, what the edit dialog shows
|
||||
conn.set_secret_value("uuid", shown)
|
||||
conn.set_secret_value("uuid", "cHcA/w==")
|
||||
secret.setValue.assert_called_once_with(b"pw\x00\xff")
|
||||
|
||||
def test_a_value_that_is_not_base64_is_refused(self):
|
||||
|
|
|
|||
31
virtsecrets/tests/test_secrets_page.py
Normal file
31
virtsecrets/tests/test_secrets_page.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
"""The secrets page never holds a secret's value: the edit dialog only
|
||||
writes a new one."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from computes.models import Compute
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from libvirt import VIR_SECRET_USAGE_TYPE_CEPH
|
||||
from lxml import html
|
||||
|
||||
|
||||
class SecretsPageTests(TestCase):
|
||||
def test_the_page_holds_no_secret_value(self):
|
||||
compute = Compute.objects.create(name="secrets-page", hostname="localhost", login="", password="", type=4)
|
||||
self.client.force_login(get_user_model().objects.create_superuser("page", "page@example.com", "pw"))
|
||||
with patch("virtsecrets.views.wvmSecrets") as conn:
|
||||
conn.return_value.get_secrets.return_value = ["0e5b1f0a-8f3e-4c1a-9d2b-5c6d7e8f9a0b"]
|
||||
secret = conn.return_value.get_secret.return_value
|
||||
secret.usageID.return_value = "client.libvirt"
|
||||
secret.UUIDString.return_value = "0e5b1f0a-8f3e-4c1a-9d2b-5c6d7e8f9a0b"
|
||||
secret.usageType.return_value = VIR_SECRET_USAGE_TYPE_CEPH
|
||||
secret.value.return_value = b"cephx key"
|
||||
conn.return_value.get_secret_value.return_value = "Y2VwaHgga2V5"
|
||||
response = self.client.get(reverse("virtsecrets", args=[compute.id]))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertFalse(b"Y2VwaHgga2V5" in response.content, "the secret's value is on the page")
|
||||
field = html.fromstring(response.content).get_element_by_id("secret_value_0e5b1f0a-8f3e-4c1a-9d2b-5c6d7e8f9a0b")
|
||||
self.assertIsNone(field.get("value"))
|
||||
self.assertEqual(field.get("type"), "password")
|
||||
|
|
@ -42,16 +42,11 @@ def secrets(request, compute_id):
|
|||
|
||||
for uuid in secrets:
|
||||
secrt = conn.get_secret(uuid)
|
||||
try:
|
||||
secrt_value = conn.get_secret_value(uuid)
|
||||
except libvirtError:
|
||||
secrt_value = None
|
||||
secrets_all.append(
|
||||
{
|
||||
"usage": secrt.usageID(),
|
||||
"uuid": secrt.UUIDString(),
|
||||
"usageType": secret_usage_types[secrt.usageType()],
|
||||
"value": secrt_value,
|
||||
}
|
||||
)
|
||||
if request.method == "POST":
|
||||
|
|
|
|||
|
|
@ -15,12 +15,8 @@ class wvmSecrets(wvmConnect):
|
|||
etree.SubElement(usage, USAGE_ELEMENTS[secret_type]).text = data
|
||||
self.wvm.secretDefineXML(etree.tostring(secret, encoding="unicode"))
|
||||
|
||||
def get_secret_value(self, uuid):
|
||||
"""The value as base64 text, the form set_secret_value takes."""
|
||||
secrt = self.get_secret(uuid)
|
||||
return base64.b64encode(secrt.value()).decode()
|
||||
|
||||
def set_secret_value(self, uuid, value):
|
||||
"""value: the new value as base64 text."""
|
||||
secrt = self.get_secret(uuid)
|
||||
# validate: a value that is not base64 is refused, not decoded to other bytes
|
||||
secrt.setValue(base64.b64decode(value, validate=True))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue