Answer only to the host names of Bindery
Without a login, a web page that the user visits could reach Bindery under a name of its own that points at the address of Bindery, and read and change everything. The "none" mode now answers only to addresses and to names of a private network. ALLOWED_HOSTS sets the names for each mode.
This commit is contained in:
@@ -10,6 +10,7 @@ import pytest
|
||||
from fake_cwa import FakeCwa
|
||||
from itsdangerous import TimestampSigner
|
||||
from test_build import PNG
|
||||
from werkzeug.security import generate_password_hash
|
||||
|
||||
from bindery import auth, config, orchestrator, web
|
||||
from bindery.store import Store
|
||||
@@ -242,6 +243,51 @@ def test_incomplete_login_settings_refuse_every_request(client, monkeypatch, set
|
||||
assert client.get("/").status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("mode", "allowed", "host", "accepted"),
|
||||
[
|
||||
# Without a login, only an address or a name of a private network.
|
||||
("none", [], "localhost", True),
|
||||
("none", [], "127.0.0.1:8085", True),
|
||||
("none", [], "192.168.1.5:8085", True),
|
||||
("none", [], "[fd00::5]:8085", True),
|
||||
("none", [], "bindery:8085", True),
|
||||
("none", [], "nas.local", True),
|
||||
("none", [], "NAS.Home.Arpa.", True),
|
||||
("none", [], "bindery.example.com", False),
|
||||
("none", [], "rebind.attacker.example:8085", False),
|
||||
("none", [], "notlocal", True),
|
||||
("none", [], "evil.com.local.attacker.example", False),
|
||||
# With a login, each name.
|
||||
("local", [], "bindery.example.com", True),
|
||||
("proxy", [], "bindery.example.com", True),
|
||||
# A list counts in each mode.
|
||||
("none", ["bindery.example.com"], "bindery.example.com:443", True),
|
||||
("none", ["bindery.example.com"], "localhost", False),
|
||||
("none", ["bindery.example.com"], "x.bindery.example.com", False),
|
||||
("local", [".example.com"], "bindery.example.com", True),
|
||||
("local", [".example.com"], "example.com", True),
|
||||
("local", [".example.com"], "badexample.com", False),
|
||||
("local", ["example.com"], "attacker.example", False),
|
||||
("none", ["*"], "anything.example", True),
|
||||
],
|
||||
)
|
||||
def test_bindery_answers_only_to_its_host_names(client, monkeypatch, mode, allowed, host, accepted):
|
||||
monkeypatch.setattr(config, "AUTH_METHOD", mode)
|
||||
monkeypatch.setattr(config, "LOCAL_USERNAME", "reader")
|
||||
monkeypatch.setattr(config, "LOCAL_PASSWORD_HASH", generate_password_hash("pw", method="pbkdf2:sha256:1000"))
|
||||
monkeypatch.setattr(config, "ALLOWED_HOSTS", allowed)
|
||||
for path in ("/", "/api/auth/check", "/static/style.css"):
|
||||
response = client.get(path, headers={"Host": host})
|
||||
assert response.status_code == (200 if accepted else 400), path
|
||||
if not accepted:
|
||||
assert response.json == {
|
||||
"error": f"Bindery does not answer to the host name {host}. Add it to ALLOWED_HOSTS."
|
||||
}
|
||||
# The health check of the container works under each name.
|
||||
assert client.get("/api/health", headers={"Host": host}).status_code == 200
|
||||
|
||||
|
||||
def test_api_key(client, cwa, monkeypatch):
|
||||
monkeypatch.setattr(config, "API_KEY", "k" * 32)
|
||||
assert client.get("/api/stories", headers={"X-Api-Key": "wrong"}).status_code == 401
|
||||
|
||||
Reference in New Issue
Block a user