Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow disabling Authorization headers for bundle downloads #174

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions docs/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ Optional options:

Defaults to ``message``.

``disable_download_auth_header=<boolean>``
Whether to send Authorization header for download requests.
Defaults to ``false``

.. _keyring-section:

**[device] section**
Expand Down
12 changes: 12 additions & 0 deletions docs/using.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
^^^^^^^^^^^^^^^

Expand Down
37 changes: 19 additions & 18 deletions include/config-file.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down
20 changes: 13 additions & 7 deletions src/config-file.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@
#include <stdlib.h>


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
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion src/hawkbit-client.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions test-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ requests
pydbus
pygobject
pexpect
scapy
45 changes: 45 additions & 0 deletions test/sniffer.py
Original file line number Diff line number Diff line change
@@ -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])
24 changes: 24 additions & 0 deletions test/test_requests.py
Original file line number Diff line number Diff line change
@@ -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