2
0

support hypercorn server_names against dns rebind attacks, add cookie sessions to flash messages

This commit is contained in:
Stefan Bühler 2022-04-06 18:59:07 +02:00
parent d1050d2ee4
commit 4ef792e97d
9 changed files with 71 additions and 30 deletions

View File

@ -1,7 +1,11 @@
---
secret: mysecret
comm-secret: mysecret
cookie-secret: mysecret
controllers:
- capport-controller1.example.com
- capport-controller2.example.com
session-timeout: 3600 # in seconds
venue-info-url: 'https://example.com'
server-names:
- localhost
- ...

View File

@ -131,7 +131,8 @@ async def _run_hub(*, task_status=trio.TASK_STATUS_IGNORED) -> None:
@app.before_serving
async def init():
global config
config = Config.load()
config = Config.load_default_once()
app.secret_key = config.cookie_secret
capport.utils.cli.init_logger(config)
await app.nursery.start(_run_hub)
@ -152,6 +153,7 @@ async def login():
address = get_client_ip()
mac = await get_client_mac(address)
await user_login(address, mac)
await quart.flash('Logged in')
return quart.redirect('/', code=303)
@ -159,6 +161,7 @@ async def login():
async def logout():
mac = await get_client_mac()
await user_logout(mac)
await quart.flash('Logged out')
return quart.redirect('/', code=303)

View File

@ -0,0 +1,12 @@
from __future__ import annotations
import capport.config
_config = capport.config.Config.load_default_once()
worker_class = 'trio'
if _config.server_names:
server_names = _config.server_names
elif not _config.debug:
raise Exception("production setup requires server-names in config (list of accepted hostnames in http requests)")

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>{% block title %}Captive Portal Universität Stuttgart{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
{% block content %}{% endblock %}
{% for message in get_flashed_messages() %}
<div class="flash">{{ message }}</div>
{% endfor %}
</body>
</html>

View File

@ -1,13 +1,8 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Captive Portal Universität Stuttgart</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
{% extends "base.html" %}
{% block content %}
{% if not state.mac %}
It seems you're accessing this site from outside the network this captive portal is running for.
<p>It seems you're accessing this site from outside the network this captive portal is running for.</p>
<p>Your clients IP address is {{ state.address }}</p>
{% elif state.captive %}
To get access to the internet please accept our usage guidelines by clicking this button:
<form method="POST" action="/login"><button type="submit">Accept</button></form>
@ -18,5 +13,4 @@
<br>
Your current session will last for {{ state.allowed_remaining }} seconds.
{% endif %}
</body>
</html>
{% endblock %}

View File

@ -339,7 +339,7 @@ class Hub:
await trio.sleep_forever()
def _calc_authentication(self, ssl_binding: bytes, server_side: bool) -> bytes:
m = hmac.new(self._config.secret.encode('utf8'), digestmod=hashlib.sha256)
m = hmac.new(self._config.comm_secret.encode('utf8'), digestmod=hashlib.sha256)
if server_side:
m.update(b'server$')
else:

View File

@ -7,16 +7,28 @@ import typing
import yaml
_cached_config: typing.Optional[Config] = None
@dataclasses.dataclass
class Config:
controllers: typing.List[str]
secret: str
server_names: typing.List[str]
comm_secret: str
cookie_secret: str
venue_info_url: typing.Optional[str]
session_timeout: int # in seconds
debug: bool
@staticmethod
def load(filename: typing.Optional[str]=None) -> 'Config':
def load_default_once() -> Config:
global _cached_config
if not _cached_config:
_cached_config = Config.load()
return _cached_config
@staticmethod
def load(filename: typing.Optional[str]=None) -> Config:
if filename is None:
for name in ('capport.yaml', '/etc/capport.yaml'):
if os.path.exists(name):
@ -27,7 +39,9 @@ class Config:
controllers = list(map(str, data['controllers']))
return Config(
controllers=controllers,
secret=str(data['secret']),
server_names=data.get('server-names', []),
comm_secret=str(data.get('comm-secret', None) or data['secret']),
cookie_secret=str(data['cookie-secret']),
venue_info_url=str(data.get('venue-info-url')),
session_timeout=data.get('session-timeout', 3600),
debug=data.get('debug', False)

View File

@ -48,7 +48,7 @@ async def amain(config: capport.config.Config) -> None:
def main() -> None:
config = capport.config.Config.load()
config = capport.config.Config.load_default_once()
capport.utils.cli.init_logger(config)
try:
trio.run(amain, config)

View File

@ -5,4 +5,4 @@ set -e
base=$(dirname "$(readlink -f "$0")")
cd "${base}"
exec ./venv/bin/hypercorn -k trio capport.api "$@"
exec ./venv/bin/hypercorn --config python:capport.api.hypercorn_conf capport.api "$@"