feat: harden gateway with policy engine, secure tools, and governance docs
This commit is contained in:
127
tests/test_policy.py
Normal file
127
tests/test_policy.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""Tests for YAML policy engine."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from aegis_gitea_mcp.config import get_settings, reset_settings
|
||||
from aegis_gitea_mcp.policy import PolicyEngine, PolicyError
|
||||
|
||||
|
||||
def _set_base_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("GITEA_URL", "https://gitea.example.com")
|
||||
monkeypatch.setenv("GITEA_TOKEN", "token-12345")
|
||||
monkeypatch.setenv("MCP_API_KEYS", "a" * 64)
|
||||
|
||||
|
||||
def test_default_policy_allows_read_and_denies_write(
|
||||
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||
) -> None:
|
||||
"""Default policy should allow reads and deny writes when write mode is disabled."""
|
||||
_set_base_env(monkeypatch)
|
||||
reset_settings()
|
||||
_ = get_settings()
|
||||
|
||||
engine = PolicyEngine.from_yaml_file(tmp_path / "does-not-exist.yaml")
|
||||
read_decision = engine.authorize("list_repositories", is_write=False)
|
||||
write_decision = engine.authorize("create_issue", is_write=True, repository="owner/repo")
|
||||
|
||||
assert read_decision.allowed
|
||||
assert not write_decision.allowed
|
||||
|
||||
|
||||
def test_policy_global_deny(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
"""Global deny should reject matching tool names."""
|
||||
_set_base_env(monkeypatch)
|
||||
policy_path = tmp_path / "policy.yaml"
|
||||
policy_path.write_text(
|
||||
"""
|
||||
defaults:
|
||||
read: allow
|
||||
write: deny
|
||||
tools:
|
||||
deny:
|
||||
- list_repositories
|
||||
""".strip() + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
reset_settings()
|
||||
_ = get_settings()
|
||||
engine = PolicyEngine.from_yaml_file(policy_path)
|
||||
|
||||
decision = engine.authorize("list_repositories", is_write=False)
|
||||
assert not decision.allowed
|
||||
assert "denied" in decision.reason
|
||||
|
||||
|
||||
def test_repository_path_restriction(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
"""Repository path allow-list should block unknown paths."""
|
||||
_set_base_env(monkeypatch)
|
||||
policy_path = tmp_path / "policy.yaml"
|
||||
policy_path.write_text(
|
||||
"""
|
||||
repositories:
|
||||
acme/app:
|
||||
tools:
|
||||
allow:
|
||||
- get_file_contents
|
||||
paths:
|
||||
allow:
|
||||
- src/*
|
||||
""".strip() + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
reset_settings()
|
||||
_ = get_settings()
|
||||
engine = PolicyEngine.from_yaml_file(policy_path)
|
||||
|
||||
allowed = engine.authorize(
|
||||
"get_file_contents",
|
||||
is_write=False,
|
||||
repository="acme/app",
|
||||
target_path="src/main.py",
|
||||
)
|
||||
denied = engine.authorize(
|
||||
"get_file_contents",
|
||||
is_write=False,
|
||||
repository="acme/app",
|
||||
target_path="docs/readme.md",
|
||||
)
|
||||
|
||||
assert allowed.allowed
|
||||
assert not denied.allowed
|
||||
|
||||
|
||||
def test_invalid_policy_structure(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
"""Invalid policy YAML should raise PolicyError."""
|
||||
_set_base_env(monkeypatch)
|
||||
policy_path = tmp_path / "policy.yaml"
|
||||
policy_path.write_text("repositories: []\n", encoding="utf-8")
|
||||
|
||||
reset_settings()
|
||||
_ = get_settings()
|
||||
|
||||
with pytest.raises(PolicyError):
|
||||
PolicyEngine.from_yaml_file(policy_path)
|
||||
|
||||
|
||||
def test_write_mode_repository_whitelist(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
"""Write mode should require repo whitelist and honor configured repository entries."""
|
||||
_set_base_env(monkeypatch)
|
||||
monkeypatch.setenv("WRITE_MODE", "true")
|
||||
monkeypatch.setenv("WRITE_REPOSITORY_WHITELIST", "acme/app")
|
||||
|
||||
policy_path = tmp_path / "policy.yaml"
|
||||
policy_path.write_text("defaults:\n write: allow\n", encoding="utf-8")
|
||||
|
||||
reset_settings()
|
||||
_ = get_settings()
|
||||
engine = PolicyEngine.from_yaml_file(policy_path)
|
||||
|
||||
allowed = engine.authorize("create_issue", is_write=True, repository="acme/app")
|
||||
denied = engine.authorize("create_issue", is_write=True, repository="acme/other")
|
||||
|
||||
assert allowed.allowed
|
||||
assert denied.allowed is False
|
||||
Reference in New Issue
Block a user