Files
backup-tool/tests/integration/test_repository_signing.py
T

94 lines
3.1 KiB
Python

from __future__ import annotations
from pathlib import Path
from typing import Protocol, cast
import httpx
import pytest
from backup_tool.api.app import create_app
from backup_tool.config import Settings
from backup_tool.db.models import Repository
from backup_tool.repository import initialize
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
PASSWORD = "a secure password"
class SignedRepository(Protocol):
repository_id: str
signing_key_id: str
signing_public_key: str
def settings_for(tmp_path: Path) -> Settings:
key = tmp_path / "master.key"
key.write_bytes(b"m6-test-master-key-material-32-bytes-minimum")
key.chmod(0o600)
repositories = tmp_path / "repositories"
repositories.mkdir()
return Settings(
data_dir=tmp_path,
database_url=f"sqlite+aiosqlite:///{tmp_path / 'metadata.db'}",
repository_roots=(repositories,),
local_source_roots=(tmp_path,),
restore_roots=(tmp_path,),
master_key_file=key,
min_free_bytes=1,
)
def test_repository_initialization_creates_a_bound_ed25519_keypair(
tmp_path: Path,
) -> None:
settings = settings_for(tmp_path)
initialized = cast(SignedRepository, initialize(settings, "main", "none", "none"))
key_path = settings.data_dir / "repository-keys" / f"{initialized.repository_id}.ed25519"
assert key_path.read_bytes()
assert key_path.stat().st_mode & 0o777 == 0o600
private_key = Ed25519PrivateKey.from_private_bytes(key_path.read_bytes())
public_key = private_key.public_key().public_bytes(
serialization.Encoding.Raw,
serialization.PublicFormat.Raw,
)
assert initialized.signing_public_key == public_key.hex()
assert initialized.signing_key_id.startswith("ed25519-")
@pytest.mark.asyncio
async def test_repository_api_persists_its_bound_public_signing_key(
tmp_path: Path,
) -> None:
settings = settings_for(tmp_path)
app = create_app(settings)
from backup_tool.db.models import Base
async with app.state.engine.begin() as connection:
await connection.run_sync(Base.metadata.create_all)
async with httpx.AsyncClient(
transport=httpx.ASGITransport(app=app), base_url="https://test"
) as client:
assert (
await client.post("/api/v2/setup", json={"username": "admin", "password": PASSWORD})
).status_code == 201
created = await client.post(
"/api/v2/repositories",
json={
"name": "main",
"relative_path": "main",
"compression": "none",
"encryption": "none",
},
headers={"X-CSRF-Token": client.cookies["backup_tool_csrf"]},
)
assert created.status_code == 201
async with app.state.sessions() as db:
repository = await db.get(Repository, created.json()["id"])
await app.state.engine.dispose()
assert repository is not None
assert repository.signing_key_id.startswith("ed25519-")
assert len(repository.signing_public_key) == 64