diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 731b9cf3187ace58601e9a21cdd923fb91097ecd..f0410d1689fce55427f859e4267bfd27d271423b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -47,19 +47,11 @@ docker: DEBIAN_FRONTEND: noninteractive BUILDAH_ISOLATION: chroot image: - name: debian:bullseye-slim + name: debian:bookworm-slim entrypoint: [""] before_script: - apt update - - apt install -y buildah qemu-user-static ca-certificates qemu-user-static - - | - cat << EOF > /etc/containers/storage.conf - [storage] - driver = "overlay" - [storage.options.overlay] - mount_program = "/usr/bin/fuse-overlayfs" - mountopt = "nodev,fsync=0" - EOF + - apt install -y buildah qemu-user-static ca-certificates binfmt-support - update-binfmts --enable qemu-aarch64 script: - buildah login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY diff --git a/Dockerfile b/Dockerfile index 5d8244f8990d514363c95c76abf3e32194bfec46..458b35e6772b4c6abe6e5d5e6593fe0594bba7a5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ ARG DEBIAN_FRONTEND=noninteractive -FROM debian:bullseye-slim as builder +FROM debian:bookworm-slim as builder ENV LC_ALL=C.UTF-8 RUN apt-get update \ && apt-get install -y python3 \ @@ -11,7 +11,7 @@ RUN cd /tmp/proxy && python3 setup.py install # Build server container -FROM debian:bullseye-slim as server +FROM debian:bookworm-slim as server LABEL maintainer Andrej Shadura <andrew.shadura@collabora.co.uk> ENV LC_ALL=C.UTF-8 diff --git a/obs_proxy/server.py b/obs_proxy/server.py index 16c04626ad4307b8d419203a24d6c908a984165c..d77cfa29f63b994ab1362d71f774a021d1b8dc8c 100755 --- a/obs_proxy/server.py +++ b/obs_proxy/server.py @@ -91,25 +91,23 @@ def require_auth(f): http_port=port, ) if port == config.server.port: - auth_header = ctx.headers.get("Authorization") - if not auth_header or not ( - auth_header.startswith("Basic") or auth_header.startswith("Bearer") - ): + if not ctx.authorization: return 'auth required', 401, {'WWW-Authenticate': 'Basic'} - # Quart parses Basic auth for us - if ctx.authorization: + # Quart parses auth for us + if ctx.authorization.type == "basic": if ( ctx.authorization["username"] != config.auth.username or ctx.authorization["password"] != config.auth.password ): logger.debug(f"{ctx.authorization = }, {config.auth = }") abort(403) - else: - _, token = auth_header.split(' ', maxsplit=1) - if token != config.auth.token: - logger.debug(f"{auth_header = }, {config.auth = }") + elif ctx.authorization.type == "bearer": + if ctx.authorization.token != config.auth.token: + logger.debug(f"{ctx.authorization = }, {config.auth = }") abort(403) + else: + abort(403) return await f(*args, **kwargs) diff --git a/obs_proxy/utils.py b/obs_proxy/utils.py index cf09f28d4f5a67c6a506bfbb3335988c4fe61f28..9c75393709c83c9e9046ed410b6a07395c043612 100644 --- a/obs_proxy/utils.py +++ b/obs_proxy/utils.py @@ -114,6 +114,8 @@ def configure_logging(): # Too verbose for us ws_logger = logging.getLogger("websockets.client") ws_logger.setLevel(logging.INFO) + httpcore_logger = logging.getLogger("httpcore") + httpcore_logger.setLevel(logging.INFO) http_logger = logging.getLogger("http.server") http_logger.addHandler(handler) diff --git a/setup.cfg b/setup.cfg index 2e4e7d529fcf60484407ed3989f206988a3a8651..60fe92b5ccd40c761969c7fd5ba5449979423f05 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,6 +27,7 @@ install_requires = httpx ~= 0.22 json-rpc ~= 1.12 Quart ~= 0.18 + Werkzeug ~= 2.3 websockets ~= 10.0 xmltodict ~= 0.13 aiofiles ~= 0.8 diff --git a/tests/test_auth.py b/tests/test_auth.py new file mode 100644 index 0000000000000000000000000000000000000000..9bcc9893a596b657eb06eb81745ff16d4c9299df --- /dev/null +++ b/tests/test_auth.py @@ -0,0 +1,81 @@ +import logging +from http import HTTPStatus +from typing import Generator + +import pytest +from pytest import LogCaptureFixture # noqa: PT013 +from quart import Quart + +from obs_proxy.config import config +from obs_proxy.server import require_auth + + +@pytest.fixture +def app(caplog: LogCaptureFixture, tmp_path) -> Generator[Quart, None, None]: + caplog.set_level(logging.DEBUG) + app = Quart(__name__) + + @app.route('/ping') + @require_auth + async def ping(): + return "ok" + + return app + + +@pytest.fixture +def test_app(app: Quart) -> Generator[Quart, None, None]: + return app.test_app() + + +@pytest.mark.asyncio +async def test_userpass(test_app: Quart): + config.auth.username = "foobar" + config.auth.password = "barfoo" + client = test_app.test_client() + + response = await client.get( + '/ping', + scope_base={ + "server": ("127.0.0.1", config.server.port), + }, + ) + + assert response.status_code == HTTPStatus.UNAUTHORIZED + + response = await client.get( + '/ping', + auth=(config.auth.username, config.auth.password), + scope_base={ + "server": ("127.0.0.1", config.server.port), + }, + ) + + assert response.status_code == HTTPStatus.OK + + +@pytest.mark.asyncio +async def test_token(test_app: Quart): + config.auth.token = "barfoobarbaz" + client = test_app.test_client() + + response = await client.get( + '/ping', + scope_base={ + "server": ("127.0.0.1", config.server.port), + }, + ) + + assert response.status_code == HTTPStatus.UNAUTHORIZED + + response = await client.get( + '/ping', + headers={ + "Authorization": f"Bearer {config.auth.token}", + }, + scope_base={ + "server": ("127.0.0.1", config.server.port), + }, + ) + + assert response.status_code == HTTPStatus.OK