feat(v2): complete v2 reimplementation
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
PASSWORD = "correct horse battery staple"
|
||||
HOST_KEY = "ssh-ed25519 AQID"
|
||||
|
||||
|
||||
async def _setup_headers(client) -> dict[str, str]:
|
||||
response = await client.post("/api/v2/setup", json={"username": "admin", "password": PASSWORD})
|
||||
assert response.status_code == 201
|
||||
return {"X-CSRF-Token": client.cookies["backup_tool_csrf"]}
|
||||
|
||||
|
||||
async def _create_secret(client, headers: dict[str, str], purpose: str) -> str:
|
||||
response = await client.post(
|
||||
"/api/v2/admin/secrets",
|
||||
json={"purpose": purpose, "value": "PRIVATE-KEY-CANARY"},
|
||||
headers=headers,
|
||||
)
|
||||
assert response.status_code == 201
|
||||
assert "PRIVATE-KEY-CANARY" not in response.text
|
||||
return str(response.json()["id"])
|
||||
|
||||
|
||||
def _source(secret_id: str) -> dict[str, object]:
|
||||
return {
|
||||
"name": "remote",
|
||||
"kind": "ssh",
|
||||
"public_config": {
|
||||
"hostname": "backup.example.test",
|
||||
"port": 22,
|
||||
"username": "backup",
|
||||
"host_key": HOST_KEY,
|
||||
"root": "/",
|
||||
},
|
||||
"private_key_secret_id": secret_id,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ssh_source_persists_only_safe_public_config_and_one_key_reference(
|
||||
app_client,
|
||||
) -> None:
|
||||
client, _ = app_client
|
||||
headers = await _setup_headers(client)
|
||||
secret_id = await _create_secret(client, headers, "ssh_private_key")
|
||||
|
||||
created = await client.post("/api/v2/sources", json=_source(secret_id), headers=headers)
|
||||
|
||||
assert created.status_code == 201
|
||||
body = created.json()
|
||||
assert body["kind"] == "ssh"
|
||||
assert body["public_config"] == _source(secret_id)["public_config"]
|
||||
assert "secret" not in body
|
||||
assert "PRIVATE-KEY-CANARY" not in created.text
|
||||
listed = await client.get("/api/v2/sources", headers=headers)
|
||||
assert listed.status_code == 200
|
||||
assert listed.json()["items"] == [body]
|
||||
assert "PRIVATE-KEY-CANARY" not in listed.text
|
||||
probe = await client.post(f"/api/v2/sources/{body['id']}/probe", headers=headers)
|
||||
assert probe.status_code == 409
|
||||
assert probe.json()["code"] == "source_probe_failed"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ssh_source_requires_exactly_one_existing_private_key_secret(
|
||||
app_client,
|
||||
) -> None:
|
||||
client, _ = app_client
|
||||
headers = await _setup_headers(client)
|
||||
wrong_purpose = await _create_secret(client, headers, "ssh")
|
||||
request = _source(wrong_purpose)
|
||||
|
||||
rejected_purpose = await client.post("/api/v2/sources", json=request, headers=headers)
|
||||
assert rejected_purpose.status_code == 422
|
||||
assert rejected_purpose.json()["code"] == "validation_failed"
|
||||
assert "PRIVATE-KEY-CANARY" not in rejected_purpose.text
|
||||
|
||||
missing_secret = await client.post(
|
||||
"/api/v2/sources",
|
||||
json=_source("00000000-0000-0000-0000-000000000000"),
|
||||
headers=headers,
|
||||
)
|
||||
assert missing_secret.status_code == 422
|
||||
assert missing_secret.json()["code"] == "validation_failed"
|
||||
|
||||
valid_secret = await _create_secret(client, headers, "ssh_private_key")
|
||||
extra_secret_reference = {
|
||||
**_source(valid_secret),
|
||||
"secret_refs": [valid_secret, wrong_purpose],
|
||||
}
|
||||
rejected_extra = await client.post(
|
||||
"/api/v2/sources", json=extra_secret_reference, headers=headers
|
||||
)
|
||||
assert rejected_extra.status_code == 422
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"payload",
|
||||
[
|
||||
{"name": "unsupported", "kind": "sftp", "public_config": {}},
|
||||
{"name": "unsupported", "kind": "postgresql", "public_config": {}},
|
||||
{"name": "unsupported", "kind": "mysql", "public_config": {}},
|
||||
{"name": "unsupported", "kind": "shell", "public_config": {}},
|
||||
],
|
||||
)
|
||||
async def test_source_api_rejects_all_non_local_ssh_kinds(
|
||||
app_client, payload: dict[str, object]
|
||||
) -> None:
|
||||
client, _ = app_client
|
||||
headers = await _setup_headers(client)
|
||||
|
||||
response = await client.post("/api/v2/sources", json=payload, headers=headers)
|
||||
|
||||
assert response.status_code == 422
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"public_config",
|
||||
[
|
||||
{
|
||||
"hostname": "backup.example.test",
|
||||
"port": 22,
|
||||
"username": "backup",
|
||||
"root": "/",
|
||||
},
|
||||
{
|
||||
"hostname": "backup.example.test",
|
||||
"port": 0,
|
||||
"username": "backup",
|
||||
"host_key": HOST_KEY,
|
||||
"root": "/",
|
||||
},
|
||||
{
|
||||
"hostname": "backup.example.test",
|
||||
"port": 22,
|
||||
"username": "backup",
|
||||
"host_key": HOST_KEY,
|
||||
"root": "/not-the-chroot",
|
||||
},
|
||||
{
|
||||
"hostname": "backup.example.test",
|
||||
"port": 22,
|
||||
"username": "backup",
|
||||
"host_key": HOST_KEY,
|
||||
"root": "/",
|
||||
"password": "not-supported",
|
||||
},
|
||||
{
|
||||
"hostname": "backup.example.test",
|
||||
"port": 22,
|
||||
"username": "backup",
|
||||
"host_key": HOST_KEY,
|
||||
"root": "/",
|
||||
"remote_command": "not-supported",
|
||||
},
|
||||
],
|
||||
)
|
||||
async def test_ssh_source_rejects_noncanonical_or_unsupported_config(
|
||||
app_client, public_config: dict[str, object]
|
||||
) -> None:
|
||||
client, _ = app_client
|
||||
headers = await _setup_headers(client)
|
||||
secret_id = await _create_secret(client, headers, "ssh_private_key")
|
||||
payload = _source(secret_id)
|
||||
payload["public_config"] = public_config
|
||||
|
||||
response = await client.post("/api/v2/sources", json=payload, headers=headers)
|
||||
|
||||
assert response.status_code == 422
|
||||
Reference in New Issue
Block a user