From dbbb4c6fa86915d7c02c806e420d7f2a7be3fd2e Mon Sep 17 00:00:00 2001 From: Mathieu Leplatre Date: Thu, 25 May 2023 10:06:47 +0200 Subject: [PATCH] Fixes #1118: Add check for HTTP versions (#1241) * Fix #1118: Add check for HTTP version * Fix 'curl: (48) An unknown option was passed in to libcurl' * Fix issue not caught in unit tests because of mocking :( * Remove useless package in container --- Dockerfile | 8 +++- checks/core/http_versions.py | 32 ++++++++++++++ tests/checks/core/test_http_versions.py | 56 +++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 checks/core/http_versions.py create mode 100644 tests/checks/core/test_http_versions.py diff --git a/Dockerfile b/Dockerfile index 85596eba..49dd47f3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,15 @@ RUN groupadd --gid 10001 app \ && useradd -m -g app --uid 10001 -s /usr/sbin/nologin app RUN apt-get update && \ - apt-get install --yes build-essential curl && \ + apt-get install --yes --no-install-recommends wget build-essential libssl-dev && \ pip install --progress-bar=off -U pip && \ pip install poetry && \ + # curl with http3 support + wget https://curl.se/download/curl-8.1.1.tar.gz && \ + tar -xvf curl-*.tar.gz && cd curl-* && \ + ./configure --with-openssl --disable-shared && make && make install && \ + cd .. && \ + # cleanup apt-get -q --yes autoremove && \ apt-get clean && \ rm -rf /root/.cache diff --git a/checks/core/http_versions.py b/checks/core/http_versions.py new file mode 100644 index 00000000..a7b76753 --- /dev/null +++ b/checks/core/http_versions.py @@ -0,0 +1,32 @@ +""" +URL should support the specified versions. +""" +import subprocess + +from telescope.typings import CheckResult + + +EXPOSED_PARAMETERS = ["url", "versions"] + +CURL_VERSION_FLAGS = ["--http1.0", "--http1.1", "--http2", "--http3"] + + +async def run(url: str, versions: list[str] = ["1", "1.1", "2", "3"]) -> CheckResult: + supported_versions = set() + for flag in CURL_VERSION_FLAGS: + result = subprocess.run( + ["curl", "-sI", flag, url, "-o/dev/null", "-w", "%{http_version}\n"], + capture_output=True, + ) + supported_versions.add(result.stdout.strip().decode()) + + if missing_versions := set(versions).difference(supported_versions): + return False, f"HTTP version(s) {', '.join(missing_versions)} unsupported" + + if extra_versions := supported_versions.difference(set(versions)): + return ( + False, + f"HTTP version(s) {', '.join(extra_versions)} unexpectedly supported", + ) + + return True, list(supported_versions) diff --git a/tests/checks/core/test_http_versions.py b/tests/checks/core/test_http_versions.py new file mode 100644 index 00000000..eb34ec94 --- /dev/null +++ b/tests/checks/core/test_http_versions.py @@ -0,0 +1,56 @@ +from unittest import mock + +import pytest + +from checks.core.http_versions import run + + +MODULE = "checks.core.http_versions" + + +@pytest.fixture +def mocked_curl(): + with mock.patch(f"{MODULE}.subprocess.run") as mocked_curl: + yield mocked_curl + + +async def test_positive(mocked_curl): + mocked_curl.side_effect = [ + mock.Mock(stdout=b"1\n"), + mock.Mock(stdout=b"1.1\n"), + mock.Mock(stdout=b"2\n"), + mock.Mock(stdout=b"3\n"), + ] + + status, data = await run("http://server.local") + + assert status is True + assert sorted(data) == ["1", "1.1", "2", "3"] + + +async def test_negative_missing(mocked_curl): + mocked_curl.side_effect = [ + mock.Mock(stdout=b"1\n"), + mock.Mock(stdout=b"1.1\n"), + mock.Mock(stdout=b"2\n"), + mock.Mock(stdout=b"2\n"), + ] + + status, data = await run("http://server.local") + + assert status is False + assert data == "HTTP version(s) 3 unsupported" + + +async def test_negative_extra(mocked_curl): + mocked_curl.side_effect = [ + mock.Mock(stdout=b"1\n"), + mock.Mock(stdout=b"1.1\n"), + mock.Mock(stdout=b"2\n"), + mock.Mock(stdout=b"3\n"), + ] + + status, data = await run("http://server.local", versions=["1", "1.1", "2"]) + + assert status is False + assert data == "HTTP version(s) 3 unexpectedly supported"