94 lines
3.1 KiB
Python
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
|