(bugfix) Validate Django-managed SSH private keys
Validate uploaded SSH private keys with ssh-keygen before saving them so invalid, malformed, or unsupported key material is rejected in the control panel instead of failing later during rsync. Auto-populate the public key when it is omitted, add an edit flow for existing SSH credentials, and cover create, update, and invalid-key paths with view tests.
This commit is contained in:
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase, override_settings
|
||||
@@ -85,24 +86,66 @@ class ViewTests(TestCase):
|
||||
def test_ssh_credentials_view_creates_key(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
|
||||
response = self.client.post(
|
||||
reverse("create_ssh_credential"),
|
||||
{
|
||||
"name": "backup-key",
|
||||
"private_key": "PRIVATE KEY",
|
||||
"public_key": "PUBLIC KEY",
|
||||
"known_hosts": "web-01.example.test ssh-ed25519 AAAATEST",
|
||||
"notes": "production backup key",
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
with patch("pobsync_backend.forms.validate_ssh_private_key", return_value="DERIVED PUBLIC KEY"):
|
||||
response = self.client.post(
|
||||
reverse("create_ssh_credential"),
|
||||
{
|
||||
"name": "backup-key",
|
||||
"private_key": "PRIVATE KEY",
|
||||
"public_key": "",
|
||||
"known_hosts": "web-01.example.test ssh-ed25519 AAAATEST",
|
||||
"notes": "production backup key",
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
|
||||
self.assertRedirects(response, reverse("ssh_credentials"))
|
||||
self.assertContains(response, "SSH credential saved for backup-key.")
|
||||
self.assertContains(response, "backup-key")
|
||||
credential = SshCredential.objects.get(name="backup-key")
|
||||
self.assertEqual(credential.private_key, "PRIVATE KEY")
|
||||
self.assertEqual(credential.public_key, "PUBLIC KEY")
|
||||
self.assertEqual(credential.private_key, "PRIVATE KEY\n")
|
||||
self.assertEqual(credential.public_key, "DERIVED PUBLIC KEY")
|
||||
|
||||
def test_ssh_credentials_view_rejects_invalid_key(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
|
||||
response = self.client.post(
|
||||
reverse("create_ssh_credential"),
|
||||
{
|
||||
"name": "bad-key",
|
||||
"private_key": "not a private key",
|
||||
"public_key": "",
|
||||
"known_hosts": "",
|
||||
"notes": "",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "Invalid SSH private key")
|
||||
self.assertFalse(SshCredential.objects.exists())
|
||||
|
||||
def test_ssh_credentials_view_updates_existing_key(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
credential = SshCredential.objects.create(name="backup-key", private_key="OLD KEY")
|
||||
|
||||
with patch("pobsync_backend.forms.validate_ssh_private_key", return_value="UPDATED PUBLIC KEY"):
|
||||
response = self.client.post(
|
||||
reverse("edit_ssh_credential", args=[credential.id]),
|
||||
{
|
||||
"name": "backup-key",
|
||||
"private_key": "UPDATED KEY",
|
||||
"public_key": "",
|
||||
"known_hosts": "",
|
||||
"notes": "rotated",
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
|
||||
self.assertRedirects(response, reverse("ssh_credentials"))
|
||||
credential.refresh_from_db()
|
||||
self.assertEqual(credential.private_key, "UPDATED KEY\n")
|
||||
self.assertEqual(credential.public_key, "UPDATED PUBLIC KEY")
|
||||
self.assertEqual(credential.notes, "rotated")
|
||||
|
||||
def test_global_config_form_creates_default_config(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
|
||||
Reference in New Issue
Block a user