From 745763098f23a1b3974f45abb8c37e6ecfddec8a Mon Sep 17 00:00:00 2001 From: Kevin Fardel Date: Thu, 14 Mar 2024 18:12:51 +0100 Subject: [PATCH 1/2] hawkbit-client: don't send auth header in curl for bundle download Add disable_download_auth_header configuration flag. If disable_download_auth_header is set in configuration file, we make bundle download without Authorization header. It can be useful when we download bundle from external storage. Signed-off-by: Kevin Fardel --- docs/reference.rst | 4 ++++ docs/using.rst | 12 ++++++++++++ include/config-file.h | 37 +++++++++++++++++++------------------ src/config-file.c | 20 +++++++++++++------- src/hawkbit-client.c | 3 ++- 5 files changed, 50 insertions(+), 26 deletions(-) diff --git a/docs/reference.rst b/docs/reference.rst index feff426d..e6b06f9b 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -152,6 +152,10 @@ Optional options: Defaults to ``message``. +``disable_download_auth_header=`` + Whether to send Authorization header for download requests. + Defaults to ``false`` + .. _keyring-section: **[device] section** diff --git a/docs/using.rst b/docs/using.rst index 56a4a42d..436e2e17 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -47,6 +47,18 @@ Thus rauc-hawkbit-updater can remove these bundles after installation only if it they are located in a directory belonging to the user executing rauc-hawkbit-updater. +Disable authentication header for downloads +------------------------------------------- + +rauc-hawkbit-updater can download bundle from external storage (hawkbit provide +the download url). rauc-hawkbit-update Native behavior is to provide +authentication header like for other requests to DDI hawkbit API. But for some +external bundle storage (like azure blob storage or aws s3), provide this +header can cause an error AuthenticationFailed. + +To disable it, set ``disable_download_auth_header=true`` in the +:ref:`sec_ref_config_file`. + systemd Example ^^^^^^^^^^^^^^^ diff --git a/include/config-file.h b/include/config-file.h index a2df87fe..3d98104d 100644 --- a/include/config-file.h +++ b/include/config-file.h @@ -12,24 +12,25 @@ * @brief struct that contains the Rauc HawkBit configuration. */ typedef struct Config_ { - gchar* hawkbit_server; /**< hawkBit host or IP and port */ - gboolean ssl; /**< use https or http */ - gboolean ssl_verify; /**< verify https certificate */ - gboolean post_update_reboot; /**< reboot system after successful update */ - gboolean resume_downloads; /**< resume downloads or not */ - gboolean stream_bundle; /**< streaming installation or not */ - gchar* auth_token; /**< hawkBit target security token */ - gchar* gateway_token; /**< hawkBit gateway security token */ - gchar* tenant_id; /**< hawkBit tenant id */ - gchar* controller_id; /**< hawkBit controller id*/ - gchar* bundle_download_location; /**< file to download rauc bundle to */ - int connect_timeout; /**< connection timeout */ - int timeout; /**< reply timeout */ - int retry_wait; /**< wait between retries */ - int low_speed_time; /**< time to be below the speed to trigger low speed abort */ - int low_speed_rate; /**< low speed limit to abort transfer */ - GLogLevelFlags log_level; /**< log level */ - GHashTable* device; /**< Additional attributes sent to hawkBit */ + gchar* hawkbit_server; /**< hawkBit host or IP and port */ + gboolean ssl; /**< use https or http */ + gboolean ssl_verify; /**< verify https certificate */ + gboolean post_update_reboot; /**< reboot system after successful update */ + gboolean resume_downloads; /**< resume downloads or not */ + gboolean stream_bundle; /**< streaming installation or not */ + gchar* auth_token; /**< hawkBit target security token */ + gchar* gateway_token; /**< hawkBit gateway security token */ + gchar* tenant_id; /**< hawkBit tenant id */ + gchar* controller_id; /**< hawkBit controller id*/ + gchar* bundle_download_location; /**< file to download rauc bundle to */ + int connect_timeout; /**< connection timeout */ + int timeout; /**< reply timeout */ + int retry_wait; /**< wait between retries */ + int low_speed_time; /**< time to be below the speed to trigger low speed abort */ + int low_speed_rate; /**< low speed limit to abort transfer */ + GLogLevelFlags log_level; /**< log level */ + GHashTable* device; /**< Additional attributes sent to hawkBit */ + gboolean disable_download_auth_header; /**< Disable security header in download requests */ } Config; /** diff --git a/src/config-file.c b/src/config-file.c index d09aebca..7f860603 100644 --- a/src/config-file.c +++ b/src/config-file.c @@ -12,13 +12,14 @@ #include -static const gint DEFAULT_CONNECTTIMEOUT = 20; // 20 sec. -static const gint DEFAULT_TIMEOUT = 60; // 1 min. -static const gint DEFAULT_RETRY_WAIT = 5 * 60; // 5 min. -static const gboolean DEFAULT_SSL = TRUE; -static const gboolean DEFAULT_SSL_VERIFY = TRUE; -static const gboolean DEFAULT_REBOOT = FALSE; -static const gchar* DEFAULT_LOG_LEVEL = "message"; +static const gint DEFAULT_CONNECTTIMEOUT = 20; // 20 sec. +static const gint DEFAULT_TIMEOUT = 60; // 1 min. +static const gint DEFAULT_RETRY_WAIT = 5 * 60; // 5 min. +static const gboolean DEFAULT_SSL = TRUE; +static const gboolean DEFAULT_SSL_VERIFY = TRUE; +static const gboolean DEFAULT_REBOOT = FALSE; +static const gchar* DEFAULT_LOG_LEVEL = "message"; +static const gboolean DEFAULT_DISABLE_DOWNLOAD_AUTH_HEADER = FALSE; /** * @brief Get string value from key_file for key in group, optional default_value can be specified @@ -310,6 +311,11 @@ Config* load_config_file(const gchar *config_file, GError **error) if (!get_key_bool(ini_file, "client", "post_update_reboot", &config->post_update_reboot, DEFAULT_REBOOT, error)) return NULL; + if (!get_key_bool(ini_file, "client", "disable_download_auth_header", + &config->disable_download_auth_header, + DEFAULT_DISABLE_DOWNLOAD_AUTH_HEADER, error)) + return NULL; + if (config->timeout > 0 && config->connect_timeout > 0 && config->timeout < config->connect_timeout) { g_set_error(error, diff --git a/src/hawkbit-client.c b/src/hawkbit-client.c index dca518bf..0586e404 100644 --- a/src/hawkbit-client.c +++ b/src/hawkbit-client.c @@ -314,7 +314,8 @@ static gboolean get_binary(const gchar *download_url, const gchar *file, curl_of curl_easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, resume_from); - if (!set_auth_curl_header(&headers, error)) + if (!hawkbit_config->disable_download_auth_header && + !set_auth_curl_header(&headers, error)) return FALSE; // set up request headers From cfca7aced64890e77f4971b6b5ae5ca30ffb4580 Mon Sep 17 00:00:00 2001 From: Kevin Fardel Date: Thu, 14 Mar 2024 18:31:03 +0100 Subject: [PATCH 2/2] test: add tests for disable_download_auth_header We use a sniffer to catch packets between rauc-hawkbit-updater and hawkbit container and check that download packets have good headers. Signed-off-by: Kevin Fardel --- README.md | 9 +++++++++ test-requirements.txt | 1 + test/sniffer.py | 45 +++++++++++++++++++++++++++++++++++++++++++ test/test_requests.py | 24 +++++++++++++++++++++++ 4 files changed, 79 insertions(+) create mode 100644 test/sniffer.py create mode 100644 test/test_requests.py diff --git a/README.md b/README.md index 1331f7ca..34e52257 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,15 @@ Run test suite: (venv) $ ./test/wait-for-hawkbit-online && dbus-run-session -- pytest -v ``` +Tests need to be run as root or with cap_net_raw and cap_net_admin +capabilities to be able to sniff rauc-hawkbit-updater packet. + +To set capabilities: + +```shell +setcap cap_net_raw,cap_net_admin=eip /usr/bin/python +``` + Pass `-o log_cli=true` to pytest in order to enable live logging for all test cases. Usage / Options diff --git a/test-requirements.txt b/test-requirements.txt index 2a435681..15dd4937 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -4,3 +4,4 @@ requests pydbus pygobject pexpect +scapy diff --git a/test/sniffer.py b/test/sniffer.py new file mode 100644 index 00000000..75fa94d4 --- /dev/null +++ b/test/sniffer.py @@ -0,0 +1,45 @@ +import os +from scapy.all import * +from scapy.layers.http import HTTPRequest + +from helper import run + + +class Sniffer: + def __init__(self, hawkbit): + interfaces_list = os.listdir('/sys/class/net/') + self.packets = [] + self.sniffer = AsyncSniffer( + filter=f"tcp and port {hawkbit.port}", timeout = 10, + prn=self.packet_handler, iface=interfaces_list + ) + + def packet_handler(self, packet): + raise NotImplementedError + + def run_command_with_sniffer(self, command): + self.sniffer.start() + run(command) + self.sniffer.stop() + + +class DownloadSniffer(Sniffer): + def __init__(self, hawkbit): + """Iniatialize sniffer with download URL as expected url""" + self.expected_url = ( + f"{hawkbit.host}:{hawkbit.port}/DEFAULT/controller/v1/" + f"{hawkbit.id['target']}/softwaremodules/" + f"{hawkbit.id['softwaremodule']}/artifacts/bundle.raucb_0" + ) + super().__init__(hawkbit) + + def packet_handler(self, packet): + """ + Handler trigger when a packet is detected on hawkbit port. + We want to find HTTP packet for a download request. If we find a + request with url matching expected_url we store it in packets list. + """ + if packet.haslayer(HTTPRequest): + url = packet[HTTPRequest].Host.decode() + packet[HTTPRequest].Path.decode() + if url == self.expected_url: + self.packets.append(packet[HTTPRequest]) diff --git a/test/test_requests.py b/test/test_requests.py new file mode 100644 index 00000000..44a93999 --- /dev/null +++ b/test/test_requests.py @@ -0,0 +1,24 @@ +from sniffer import DownloadSniffer + +def test_download_with_auth_header(config, hawkbit, assign_bundle): + """Test curl has authentication header by default.""" + assign_bundle(params={'type': 'downloadonly'}) + + sniffer = DownloadSniffer(hawkbit) + sniffer.run_command_with_sniffer(f"rauc-hawkbit-updater -c {config} -r") + + assert len(sniffer.packets) > 0 + for packet in sniffer.packets: + assert packet.Authorization is not None + +def test_download_without_auth_header(adjust_config, hawkbit, assign_bundle): + """Test curl has no authentication header if we disable it.""" + config = adjust_config({'client': {'disable_download_auth_header': 'true'}}) + assign_bundle(params={'type': 'downloadonly'}) + + sniffer = DownloadSniffer(hawkbit) + sniffer.run_command_with_sniffer(f"rauc-hawkbit-updater -c {config} -r") + + assert len(sniffer.packets) > 0 + for packet in sniffer.packets: + assert packet.Authorization is None