(release) Harden SSH key edit and delete flow
Make SSH credential management more explicit by adding an edit action in the key overview and requiring name confirmation before deletion. Keep deletion blocked while a key is still selected by hosts or global config, and cover rename, delete confirmation, and in-use protection in view tests. Refs #20 Refs #8
This commit is contained in:
@@ -406,13 +406,46 @@ class ViewTests(TestCase):
|
||||
generate_ssh_key(credential)
|
||||
key_path = Path(credential.key_path)
|
||||
|
||||
response = self.client.post(reverse("delete_ssh_credential", args=[credential.id]), follow=True)
|
||||
response = self.client.post(
|
||||
reverse("delete_ssh_credential", args=[credential.id]),
|
||||
{"confirm_name": credential.name},
|
||||
follow=True,
|
||||
)
|
||||
|
||||
self.assertRedirects(response, reverse("ssh_credentials"))
|
||||
self.assertContains(response, "SSH key deleted: generated-key.")
|
||||
self.assertFalse(SshCredential.objects.exists())
|
||||
self.assertFalse(key_path.exists())
|
||||
|
||||
def test_ssh_credentials_view_requires_delete_confirmation(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
credential = SshCredential.objects.create(name="backup-key")
|
||||
|
||||
response = self.client.post(
|
||||
reverse("delete_ssh_credential", args=[credential.id]),
|
||||
{"confirm_name": "wrong"},
|
||||
follow=True,
|
||||
)
|
||||
|
||||
self.assertRedirects(response, reverse("edit_ssh_credential", args=[credential.id]))
|
||||
self.assertContains(response, "Type backup-key to confirm SSH key deletion.")
|
||||
self.assertTrue(SshCredential.objects.filter(pk=credential.pk).exists())
|
||||
|
||||
def test_ssh_credentials_view_blocks_delete_when_key_is_in_use(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
credential = SshCredential.objects.create(name="backup-key")
|
||||
HostConfig.objects.create(host="web-01", address="web-01.example.test", ssh_credential=credential)
|
||||
|
||||
response = self.client.post(
|
||||
reverse("delete_ssh_credential", args=[credential.id]),
|
||||
{"confirm_name": credential.name},
|
||||
follow=True,
|
||||
)
|
||||
|
||||
self.assertRedirects(response, reverse("edit_ssh_credential", args=[credential.id]))
|
||||
self.assertContains(response, "SSH key backup-key is still in use and cannot be deleted.")
|
||||
self.assertTrue(SshCredential.objects.filter(pk=credential.pk).exists())
|
||||
|
||||
def test_ssh_credentials_view_rejects_invalid_key(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
|
||||
@@ -476,7 +509,7 @@ class ViewTests(TestCase):
|
||||
response = self.client.post(
|
||||
reverse("edit_ssh_credential", args=[credential.id]),
|
||||
{
|
||||
"name": "backup-key",
|
||||
"name": "renamed-backup-key",
|
||||
"private_key": "UPDATED KEY",
|
||||
"public_key": "",
|
||||
"known_hosts": "",
|
||||
@@ -487,6 +520,7 @@ class ViewTests(TestCase):
|
||||
|
||||
self.assertRedirects(response, reverse("ssh_credentials"))
|
||||
credential.refresh_from_db()
|
||||
self.assertEqual(credential.name, "renamed-backup-key")
|
||||
self.assertEqual(credential.private_key, "UPDATED KEY\n")
|
||||
self.assertEqual(credential.public_key, "UPDATED PUBLIC KEY")
|
||||
self.assertEqual(credential.notes, "rotated")
|
||||
|
||||
Reference in New Issue
Block a user