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

Add option to authenticate via client certificates #38

Closed
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Setup target (device) configuration file:
target_name = test-target
auth_token = bhVahL1Il1shie2aj2poojeChee6ahShu
#gateway_token = bhVahL1Il1shie2aj2poojeChee6ahShu
#client_cert = /path/to/client_certificate.pem
#client_key = /path/to/client_certificate.key
bundle_download_location = /tmp/bundle.raucb
retry_wait = 60
connect_timeout = 20
Expand Down
6 changes: 6 additions & 0 deletions config.conf.example
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ timeout = 60
# reboot after a successful update
post_update_reboot = false

# path to client certificate
client_cert =

# path to private key of client certificate
client_key =

# debug, info, message, critical, error, fatal
log_level = message

Expand Down
66 changes: 64 additions & 2 deletions docs/using.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Using the RAUC hawkbit Updater
==============================

Authentication
--------------
Authentication via tokens
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Authentication via tokens
Authentication via Tokens

-------------------------

As described on the `hawkBit Authentication page <https://www.eclipse.org/hawkbit/concepts/authentication/>`_
in the "DDI API Authentication Modes" section, a device can be authenticated
Expand All @@ -22,6 +22,68 @@ Although gateway token is very handy during development or testing, it's
recommended to use this token with care because it can be used to
authenticate any device.

Authentication via Certificates
-------------------------------

As can be seen in the system configuration settings of hawkBit, there is a
third option to authenticate the targets. An "Allow targets to authenticate via
a certificate authenticated by a reverse proxy" option. To use this
authentication method a TLS reverse proxy server needs to be set up.
The client and reverse proxy server perform a "SSL-handshake" that means the
client validates the server certificate of the reverse proxy server with its
list of trusted certificates.

The clients request has:

- to have a TLS connection to the reverse proxy server
- to contain the client certificate
- to have the common name of the server certificate match the server
name set in the configuration file as "hawkbit_server"

The purpose of the reverse proxy is to:

- disband the TLS connection
- check if sent client certificate is valid
- extract the common name and fingerprint of the client certificate
- forward the common name and fingerprint as HTTP headers to the
hawkBit server

When the hawkBit server receives the request it checks if:

- sent common name matches with the controller ID of the target
- sent fingerprint(s) matches the expected fingerprint(s) which is set
in the system configuration settings of hawkBit
Comment on lines +38 to +55
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for indentation here.


The client certificate will only be used if no tokens are set and a valid path
to a certificate and its key is given in the configuration file.

Here an example of how the configuration file might look like:

[client]
hawkbit_server = CN_server_certificate:443
ssl = true
ssl_verify = true
Comment on lines +64 to +65
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if these options are false?

tenant_id = DEFAULT
target_name = test-target
auth_token =
#gateway_token = bhVahL1Il1shie2aj2poojeChee6ahShu
#client_cert = /path/to/client_certificate.pem
#client_key = /path/to/client_certificate.key
Comment on lines +70 to +71
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these options commented?

bundle_download_location = /tmp/bundle.raucb
retry_wait = 60
connect_timeout = 20
timeout = 60
log_level = debug
post_update_reboot = false

[device]
product = Terminator
model = T-1000
serialnumber = 8922673153
hw_revision = 2
key1 = value
key2 = value

Plain Bundle Support
--------------------

Expand Down
2 changes: 2 additions & 0 deletions include/config-file.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ typedef struct Config_ {
gchar* tenant_id; /**< hawkBit tenant id */
gchar* controller_id; /**< hawkBit controller id*/
gchar* bundle_download_location; /**< file to download rauc bundle to */
gchar* client_cert; /**< path to the client certificate */
gchar* client_key; /**< path to the key of the client certificate */
int connect_timeout; /**< connection timeout */
int timeout; /**< reply timeout */
int retry_wait; /**< wait between retries */
Expand Down
17 changes: 12 additions & 5 deletions src/config-file.c
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ Config* load_config_file(const gchar *config_file, GError **error)
g_autoptr(GKeyFile) ini_file = NULL;
gboolean key_auth_token_exists = FALSE;
gboolean key_gateway_token_exists = FALSE;
gboolean key_client_cert_exists = FALSE;
gboolean key_client_key_exists = FALSE;

g_return_val_if_fail(config_file, NULL);
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
Expand All @@ -259,11 +261,14 @@ Config* load_config_file(const gchar *config_file, GError **error)
key_gateway_token_exists = get_key_string(ini_file, "client", "gateway_token",
&config->gateway_token, NULL, NULL);
if (!key_auth_token_exists && !key_gateway_token_exists) {
g_set_error(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
"Neither auth_token nor gateway_token is set in the config.");
return NULL;
}
if (key_auth_token_exists && key_gateway_token_exists) {
g_info("Neither auth_token nor gateway_token is set, using client certificates");
key_client_cert_exists = get_key_string(ini_file, "client", "client_cert", &config->client_cert, NULL, NULL);
key_client_key_exists = get_key_string(ini_file, "client", "client_key", &config->client_key, NULL, NULL);
if (!key_client_cert_exists || !key_client_key_exists) {
g_set_error(error, 1, 4, "Neither a token nor client certificate are set!");
return NULL;
}
} else if (key_auth_token_exists && key_gateway_token_exists) {
Comment on lines -262 to +271
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please read these config options outside of these if and then add checks for all combinations that are not allowed? This should make the logic simpler here.

g_set_error(error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
"Both auth_token and gateway_token are set in the config.");
return NULL;
Expand Down Expand Up @@ -321,6 +326,8 @@ void config_file_free(Config *config)
g_free(config->tenant_id);
g_free(config->auth_token);
g_free(config->gateway_token);
g_free(config->client_cert);
g_free(config->client_key);
g_free(config->bundle_download_location);
if (config->device)
g_hash_table_destroy(config->device);
Expand Down
14 changes: 14 additions & 0 deletions src/hawkbit-client.c
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,13 @@ static gboolean get_binary(const gchar *download_url, const gchar *file, gint64
curl_easy_setopt(curl, CURLOPT_URL, download_url);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 8L);

if (hawkbit_config->client_cert && hawkbit_config->client_key) {
curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "PEM");
curl_easy_setopt(curl, CURLOPT_SSLCERT, hawkbit_config->client_cert);
curl_easy_setopt(curl, CURLOPT_SSLKEY, hawkbit_config->client_key);
}

Comment on lines +265 to +271
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This belongs in set_auth_curl_header() which is used for get_binary() as well as rest_request().

curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, DEFAULT_CURL_DOWNLOAD_BUFFER_SIZE);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_to_file_cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, payload);
Expand Down Expand Up @@ -368,6 +375,13 @@ static gboolean rest_request(enum HTTPMethod method, const gchar *url,
// set up CURL options
set_default_curl_opts(curl);
curl_easy_setopt(curl, CURLOPT_URL, url);

if (hawkbit_config->client_cert && hawkbit_config->client_key) {
curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "PEM");
curl_easy_setopt(curl, CURLOPT_SSLCERT, hawkbit_config->client_cert);
curl_easy_setopt(curl, CURLOPT_SSLKEY, hawkbit_config->client_key);
}

Comment on lines +378 to +384
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above.

curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, HTTPMethod_STRING[method]);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, hawkbit_config->timeout);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb);
Expand Down
2 changes: 1 addition & 1 deletion test/test_basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def test_config_no_auth_token(adjust_config):
assert exitcode == 4
assert out == ''
assert err.strip() == \
'Loading config file failed: Neither auth_token nor gateway_token is set in the config.'
'Loading config file failed: Neither a token nor client certificate are set!'

def test_config_multiple_auth_methods(adjust_config):
"""Test config with auth_token and gateway_token options in client section."""
Expand Down