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 MQTT client authentication #1864

Merged
merged 5 commits into from
Apr 21, 2023
Merged

Conversation

Bravo555
Copy link
Contributor

Proposed changes

This commit adds 2 new config options: mqtt.client.auth.cert_file and mqtt.client.auth.key_file to facilitate MQTT client authentication. The client certificate needs to be signed by the same CA as the server certificate for the authentication to work, that's why new settings were added instead of using a thin-edge device certificate located in /etc/tedge/device-certs, which is self-signed and adding the option of using CA to tedge cert command will not be trivial.

Note: this PR is the second part of #1785 and requires server authentication to be present, which is why it contains commits from the server authentication PR (#1816). I anticipate #1816 to be merged before this PR which might need some more discussion and fixes, which is why I'm putting it as a draft. When #1816 is merged, the first 2 commits should disappear, leaving this PR with only commits related to client authentication.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Improvement (general improvements like code refactoring that doesn't explicitly fix a bug or add any new functionality)
  • Documentation Update (if none of the other choices apply)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Paste Link to the issue

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA (in all commits with git commit -s)
  • I ran cargo fmt as mentioned in CODING_GUIDELINES
  • I used cargo clippy as mentioned in CODING_GUIDELINES
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation (if appropriate)

Further comments

Some more thought needs to be put into how to integrate client authentication with device certificates generated by the tedge cert create command. The certificates generated by the command are currently self-signed, but for client authentication to work, both server and client need to use certificates signed by the same CA. A CA certificate is usually located on another device, to which the subject sends a CSR and receives back a signed certificate. So unless we want to keep it as it is now, where we use a certificate different from thin-edge device certificate, and which needs to be deployed by the user manually, the tedge cert command would have to be heavily modified.

@Bravo555 Bravo555 marked this pull request as ready for review March 30, 2023 10:07
@Bravo555 Bravo555 temporarily deployed to Test Pull Request March 30, 2023 13:26 — with GitHub Actions Inactive
@github-actions
Copy link
Contributor

github-actions bot commented Mar 30, 2023

Robot Results

✅ Passed ❌ Failed ⏭️ Skipped Total Pass %
173 0 5 173 100

Passed Tests

Name ⏱️ Duration Suite
Define Child device 1 ID 0.029 s C8Y Child Alarms Rpi
Normal case when the child device does not exist on c8y cloud 3.329 s C8Y Child Alarms Rpi
Normal case when the child device already exists 1.187 s C8Y Child Alarms Rpi
Reconciliation when the new alarm message arrives, restart the mapper 2.3970000000000002 s C8Y Child Alarms Rpi
Reconciliation when the alarm that is cleared 5.924 s C8Y Child Alarms Rpi
Prerequisite Parent 83.684 s Child Conf Mgmt Plugin
Prerequisite Child 0.322 s Child Conf Mgmt Plugin
Child device bootstrapping 15.224 s Child Conf Mgmt Plugin
Snapshot from device 61.68 s Child Conf Mgmt Plugin
Child device config update 63.086 s Child Conf Mgmt Plugin
Configuration types should be detected on file change (without restarting service) 125.827 s Inotify Crate
Check lock file existence in default folder 3.024 s Lock File
Check PID number in lock file 3.026 s Lock File
Check PID number in lock file after restarting the services 3.4 s Lock File
Check starting same service twice 1.92 s Lock File
Switch off lock file creation 2.011 s Lock File
Successful firmware operation 83.767 s Firmware Operation
Install with empty firmware name 62.59 s Firmware Operation
Prerequisite Parent 25.59 s Firmware Operation Child Device
Prerequisite Child 8.567 s Firmware Operation Child Device
Child device firmware update 6.7620000000000005 s Firmware Operation Child Device
Child device firmware update with cache 6.368 s Firmware Operation Child Device
Update Inventory data via inventory.json 1.7189999999999999 s Inventory Update
Retrieve a JWT tokens 130.69 s Jwt Request
Main device registration 3.5060000000000002 s Device Registration
Child device registration 3.591 s Device Registration
Supports restarting the device 142.833 s Restart Device
Update tedge version from previous using Cumulocity 79.726 s Tedge Self Update
Test if all c8y services are up 128.704 s Service Monitoring
Test if all c8y services are down 129.372 s Service Monitoring
Test if all c8y services are using configured service type 121.784 s Service Monitoring
Test if all c8y services using default service type when service type configured as empty 99.988 s Service Monitoring
Check health status of tedge-mapper-c8y service on broker stop start 37.74 s Service Monitoring
Check health status of tedge-mapper-c8y service on broker restart 39.043 s Service Monitoring
Check health status of child device service 31.417 s Service Monitoring
Successful shell command with output 4.339 s Shell Operation
Check Successful shell command with literal double quotes output 3.862 s Shell Operation
Execute multiline shell command 3.791 s Shell Operation
Failed shell command 3.645 s Shell Operation
Software list should be populated during startup 52.167 s Software
Install software via Cumulocity 69.207 s Software
Software list should only show currently installed software and not candidates 64.996 s Software
Child devices support sending simple measurements 4.069 s Child Device Telemetry
Child devices support sending custom measurements 1.33 s Child Device Telemetry
Child devices support sending custom events 1.3980000000000001 s Child Device Telemetry
Child devices support sending custom events overriding the type 1.451 s Child Device Telemetry
Child devices support sending custom alarms #1699 1.4849999999999999 s Child Device Telemetry
Child devices support sending inventory data via c8y topic 1.5 s Child Device Telemetry
Main device support sending inventory data via c8y topic 1.729 s Child Device Telemetry
Child device supports sending custom child device measurements directly to c8y 1.8 s Child Device Telemetry
Main device supports sending custom child device measurements directly to c8y 1.754 s Child Device Telemetry
Check retained alarms 58.911 s Raise Alarms
Validate updated data path used by tedge-agent 0.872 s Data Path Config
Validate updated data path used by c8y-firmware-plugin 11.573 s Data Path Config
Stop tedge-agent service 0.238 s Log Path Config
Customize the log path 0.117 s Log Path Config
Initialize tedge-agent 0.189 s Log Path Config
Check created folders 0.09 s Log Path Config
Remove created custom folders 0.116 s Log Path Config
Install thin-edge via apt 36.658 s Install Apt
Install latest via script (from current branch) 25.376 s Install Tedge
Install specific version via script (from current branch) 31.166 s Install Tedge
Install latest tedge via script (from main branch) 22.197 s Install Tedge
Install then uninstall latest tedge via script (from main branch) 54.631 s Install Tedge
Support starting and stopping services 50.762 s Service-Control
Supports a reconnect 71.907 s Test-Commands
Supports disconnect then connect 48.749 s Test-Commands
Update unknown setting 35.15 s Test-Commands
Update known setting 42.154 s Test-Commands
Stop c8y-configuration-plugin 60.296 s Health C8Y-Configuration-Plugin
Update the service file 0.149 s Health C8Y-Configuration-Plugin
Reload systemd files 0.487 s Health C8Y-Configuration-Plugin
Start c8y-configuration-plugin 0.134 s Health C8Y-Configuration-Plugin
Start watchdog service 10.231 s Health C8Y-Configuration-Plugin
Check PID of c8y-configuration-plugin 0.154 s Health C8Y-Configuration-Plugin
Kill the PID 0.459 s Health C8Y-Configuration-Plugin
Recheck PID of c8y-configuration-plugin 6.624 s Health C8Y-Configuration-Plugin
Compare PID change 0.004 s Health C8Y-Configuration-Plugin
Stop watchdog service 0.109 s Health C8Y-Configuration-Plugin
Remove entry from service file 0.086 s Health C8Y-Configuration-Plugin
Stop c8y-log-plugin 60.346 s Health C8Y-Log-Plugin
Update the service file 0.102 s Health C8Y-Log-Plugin
Reload systemd files 0.449 s Health C8Y-Log-Plugin
Start c8y-log-plugin 0.166 s Health C8Y-Log-Plugin
Start watchdog service 10.15 s Health C8Y-Log-Plugin
Check PID of c8y-log-plugin 0.106 s Health C8Y-Log-Plugin
Kill the PID 0.308 s Health C8Y-Log-Plugin
Recheck PID of c8y-log-plugin 6.464 s Health C8Y-Log-Plugin
Compare PID change 0.004 s Health C8Y-Log-Plugin
Stop watchdog service 0.108 s Health C8Y-Log-Plugin
Remove entry from service file 0.091 s Health C8Y-Log-Plugin
Stop tedge-mapper 0.24 s Health Tedge Mapper C8Y
Update the service file 0.177 s Health Tedge Mapper C8Y
Reload systemd files 1.174 s Health Tedge Mapper C8Y
Start tedge-mapper 0.201 s Health Tedge Mapper C8Y
Start watchdog service 10.333 s Health Tedge Mapper C8Y
Check PID of tedge-mapper 0.144 s Health Tedge Mapper C8Y
Kill the PID 0.171 s Health Tedge Mapper C8Y
Recheck PID of tedge-mapper 6.52 s Health Tedge Mapper C8Y
Compare PID change 0.001 s Health Tedge Mapper C8Y
Stop watchdog service 0.193 s Health Tedge Mapper C8Y
Remove entry from service file 0.145 s Health Tedge Mapper C8Y
Stop tedge-agent 0.271 s Health Tedge-Agent
Update the service file 0.139 s Health Tedge-Agent
Reload systemd files 0.492 s Health Tedge-Agent
Start tedge-agent 0.171 s Health Tedge-Agent
Start watchdog service 10.276 s Health Tedge-Agent
Check PID of tedge-mapper 0.091 s Health Tedge-Agent
Kill the PID 0.193 s Health Tedge-Agent
Recheck PID of tedge-agent 6.323 s Health Tedge-Agent
Compare PID change 0.002 s Health Tedge-Agent
Stop watchdog service 0.116 s Health Tedge-Agent
Remove entry from service file 0.138 s Health Tedge-Agent
Stop tedge-mapper-az 0.148 s Health Tedge-Mapper-Az
Update the service file 0.131 s Health Tedge-Mapper-Az
Reload systemd files 0.428 s Health Tedge-Mapper-Az
Start tedge-mapper-az 0.144 s Health Tedge-Mapper-Az
Start watchdog service 10.195 s Health Tedge-Mapper-Az
Check PID of tedge-mapper-az 0.094 s Health Tedge-Mapper-Az
Kill the PID 0.184 s Health Tedge-Mapper-Az
Recheck PID of tedge-agent 6.674 s Health Tedge-Mapper-Az
Compare PID change 0.001 s Health Tedge-Mapper-Az
Stop watchdog service 0.233 s Health Tedge-Mapper-Az
Remove entry from service file 0.123 s Health Tedge-Mapper-Az
Stop tedge-mapper-collectd 0.253 s Health Tedge-Mapper-Collectd
Update the service file 0.281 s Health Tedge-Mapper-Collectd
Reload systemd files 0.775 s Health Tedge-Mapper-Collectd
Start tedge-mapper-collectd 0.242 s Health Tedge-Mapper-Collectd
Start watchdog service 10.478 s Health Tedge-Mapper-Collectd
Check PID of tedge-mapper-collectd 0.106 s Health Tedge-Mapper-Collectd
Kill the PID 0.177 s Health Tedge-Mapper-Collectd
Recheck PID of tedge-mapper-collectd 6.799 s Health Tedge-Mapper-Collectd
Compare PID change 0.001 s Health Tedge-Mapper-Collectd
Stop watchdog service 0.3 s Health Tedge-Mapper-Collectd
Remove entry from service file 0.208 s Health Tedge-Mapper-Collectd
tedge-collectd-mapper health status 6.222 s Health Tedge-Mapper-Collectd
c8y-log-plugin health status 5.566 s MQTT health endpoints
c8y-configuration-plugin health status 5.767 s MQTT health endpoints
Publish on a local insecure broker 0.553 s Basic Pub Sub
Publish on a local secure broker 2.892 s Basic Pub Sub
Publish on a local secure broker with client authentication 2.209 s Basic Pub Sub
Wrong package name 0.152 s Improve Tedge Apt Plugin Error Messages
Wrong version 0.182 s Improve Tedge Apt Plugin Error Messages
Wrong type 0.376 s Improve Tedge Apt Plugin Error Messages
tedge_connect_test_positive 0.406 s Tedge Connect Test
tedge_connect_test_negative 1.706 s Tedge Connect Test
tedge_connect_test_sm_services 9.18 s Tedge Connect Test
tedge_disconnect_test_sm_services 1.407 s Tedge Connect Test
Install thin-edge.io 25.103 s Call Tedge
call tedge -V 0.107 s Call Tedge
call tedge -h 0.197 s Call Tedge
call tedge -h -V 0.097 s Call Tedge
call tedge help 0.085 s Call Tedge
tedge config list 0.284 s Call Tedge Config List
tedge config list --all 0.297 s Call Tedge Config List
set/unset device.type 1.686 s Call Tedge Config List
set/unset device.key.path 1.212 s Call Tedge Config List
set/unset device.cert.path 0.907 s Call Tedge Config List
set/unset c8y.root.cert.path 0.852 s Call Tedge Config List
set/unset c8y.smartrest.templates 0.703 s Call Tedge Config List
set/unset az.root.cert.path 0.524 s Call Tedge Config List
set/unset az.mapper.timestamp 0.488 s Call Tedge Config List
set/unset mqtt.bind_address 0.762 s Call Tedge Config List
set/unset mqtt.port 0.62 s Call Tedge Config List
set/unset tmp.path 0.464 s Call Tedge Config List
set/unset logs.path 0.548 s Call Tedge Config List
set/unset run.path 0.855 s Call Tedge Config List
Get Put Delete 7.036 s Http File Transfer Api
Set keys should return value on stdout 0.214 s Tedge Config Get
Unset keys should not return anything on stdout and warnings on stderr 0.757 s Tedge Config Get
Invalid keys should not return anything on stdout and warnings on stderr 0.482 s Tedge Config Get
Set configuration via environment variables 1.784 s Tedge Config Get
Set unknown configuration via environment variables 0.204 s Tedge Config Get

@didier-wenzek
Copy link
Contributor

The client certificate needs to be signed by the same CA as the server certificate for the authentication to work,

There is a misunderstanding here. This would be a too strong constraint. See mosquitto documentation: This means that any CA certificates you include in cafile or capath will be able to issue client certificates that are valid for connecting to your broker.

It means that to accept connections from some client, one must add in the mosquitto capath directory the signing certificate of this client certificate. Note that (still according mosquitto documentation):

     For capath to work correctly,
     the certificates files must have ".pem" as the file ending
     and you must run "openssl rehash <path to capath>" each time you add/remove a certificate.

that's why new settings were added instead of using a thin-edge device certificate located in /etc/tedge/device-certs,

These two settings are required but for another reason. One needs to use different certificates and signing authority to authenticate the device in the cloud and to authenticate a local process or child-device.

@Bravo555
Copy link
Contributor Author

There is a misunderstanding here. This would be a too strong constraint. See mosquitto documentation: This means that any CA certificates you include in cafile or capath will be able to issue client certificates that are valid for connecting to your broker.

It means that to accept connections from some client, one must add in the mosquitto capath directory the signing certificate of this client certificate. Note that (still according mosquitto documentation):

You're right, CAs trusted by the broker and the clients can actually be different.

These two settings are required but for another reason. One needs to use different certificates and signing authority to authenticate the device in the cloud and to authenticate a local process or child-device.

Okay, then tedge cert create is probably not relevant. What prompted me to ask about it was that we have quite a lot of configuration options and I'm not sure whether or not I'm needlessly duplicating them instead of using some other options. E.g. for root trust store, e.g. I wondered instead of adding a key in mqtt.client, paths to trusted certificates should not be global, with some ability to override, e.g. with an options to add more paths or certificates in certain contexts, like HTTP or MQTT.

Copy link
Contributor

@didier-wenzek didier-wenzek left a comment

Choose a reason for hiding this comment

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

That sounds correct. One point is missing though: this needs to be added to mqtt_channel too and used by the misc tedge daemons.

@@ -10,25 +10,45 @@ Force Tags theme:mqtt
*** Test Cases ***
Publish on a local insecure broker
Start Service mosquitto
Execute Command tedge mqtt pub topic message
Execute Command tedge mqtt pub topic1 message1
Copy link
Contributor

Choose a reason for hiding this comment

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

I would add a check that the message is actually received - here and for the two other tests using authentication

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To verify that the message was received, I was thinking of starting tedge mqtt pub and tedge mqtt sub commands, but as tedge sub blocks, I don't really know how should I handle it using our ThinEdgeIO Robot Framework library. Could you give me some pointers? Because from the glimpse into the library, I don't see any way to start a background process on the test device.

Copy link
Contributor

Choose a reason for hiding this comment

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

@Bravo555 Sorry I'm a bit late...There is a mqtt logger which is running in the integration test device where you can use the Should Have MQTT Messages keyword. The mqtt logger is running all the time, so no need to explicitly do a mqtt sub.

Here is an example:

@{listen}=    Should Have MQTT Messages    topic=tedge/${CHILD_SN}/commands/req/config_update        date_from=-5s

crates/core/tedge/src/cli/mqtt/cli.rs Show resolved Hide resolved
@didier-wenzek
Copy link
Contributor

didier-wenzek commented Mar 30, 2023

Okay, then tedge cert create is probably not relevant. What prompted me to ask about it was that we have quite a lot of configuration options and I'm not sure whether or not I'm needlessly duplicating them instead of using some other options. E.g. for root trust store, e.g. I wondered instead of adding a key in mqtt.client, paths to trusted certificates should not be global, with some ability to override, e.g. with an options to add more paths or certificates in certain contexts, like HTTP or MQTT.

Indeed, we start to have quite a lot of MQTT related options !

$ tedge config list --all | grep mqtt                                   
mqtt.bind_address=127.0.0.1
mqtt.client.host=localhost
mqtt.client.port=1883
mqtt.port=1883
mqtt.client.ca_file=
mqtt.client.ca_path=
mqtt.client.auth.cert_file=
mqtt.client.auth.key_file=
mqtt.external.port=
mqtt.external.bind_address=
mqtt.external.bind_interface=
mqtt.external.capath=
mqtt.external.certfile=
mqtt.external.keyfile=

The two added by this PR make sense and are related to the former ones

  • mqtt.external.* are used to configure a listener on mosquitto.
  • mqtt.external.port and mqtt.client.port should be the same for the client to connect that listener. Same for the addresses.
  • mqtt.external.capath is where to put the signing certificate of a device certificate mqtt.client.auth.cert_file.
  • mqtt.client.ca_path must contains a copy of the mqtt.external.certfile.

The main issue is more that tedge_conf.toml is mixing unrelated things: configuring mosquitto, configuring the tedge daemons and configuring tedge clients.

PS: Writing this comment, I noticed that something needs to be fixed: the client and external settings use the same names except an extra underscore (capath vs ca_path). @reubenmiller : what's your preference?

@Bravo555
Copy link
Contributor Author

Bravo555 commented Apr 3, 2023

One point is missing though: this needs to be added to mqtt_channel too and used by the misc tedge daemons.

I think there's one issue with mqtt_channel as it is now. Because it itself does not read thin-edge configuration, and uses a config builder, every extension using it has to read configuration and set common options, like hostname, port, or certificate paths. If I'm somewhat correct in thinking that mqtt_channel is supposed to be an abstraction which is used by plugins to connect to thin-edge local broker and reduce some boilerplate resulting from all our plugins being MQTT clients, then it's not performing its task very well because when we add something that changes the behaviour of the broker, all the clients using mqtt_channel have to be updated as well.

I think that instead of starting with Config::default(), which does not take config into account, we should provide a function that reads the thin-edge configuration and constructs mqtt_channel::Config from it. We could then allow plugins to override any fields they want, although It's not clear to me why they would even want to do so. All the information needed to connect to the broker is inside the config file, so I think there's no advantages to forcing clients to handle, or even be aware, of the transport protocol that is being used.

For the time being, I'll take the long road and just update all usages adding server and client authentication (in server authentication PR i updated mqtt_channel, but forgot to update usages). Please tell me if I'm not understanding something wrong and whether or not the kind of changes that I propose here is something worth considering.

@Bravo555 Bravo555 temporarily deployed to Test Pull Request April 3, 2023 15:05 — with GitHub Actions Inactive
@didier-wenzek
Copy link
Contributor

I think there's one issue with mqtt_channel as it is now. Because it itself does not read thin-edge configuration, and uses a config builder, every extension using it has to read configuration and set common options, like hostname, port, or certificate paths. If I'm somewhat correct in thinking that mqtt_channel is supposed to be an abstraction which is used by plugins to connect to thin-edge local broker and reduce some boilerplate resulting from all our plugins being MQTT clients, then it's not performing its task very well because when we add something that changes the behaviour of the broker, all the clients using mqtt_channel have to be updated as well.

I would not be as negative as saying that "when we add something that changes the behaviour of the broker, all the clients using mqtt_channel have to be updated as well." Most of the fixes done regarding the behavior of this library have been done without implying code changes for the tedge services.

However, you are correct in saying that "any new MQTT configuration option impacts all the tedge services" and that the very same boilerplate code has to be added for each of them to extract the MQTT config from tedge config.

I think that instead of starting with Config::default(), which does not take config into account, we should provide a function that reads the thin-edge configuration and constructs mqtt_channel::Config from it. We could then allow plugins to override any fields they want, although It's not clear to me why they would even want to do so. All the information needed to connect to the broker is inside the config file, so I think there's no advantages to forcing clients to handle, or even be aware, of the transport protocol that is being used.

I agree that we need some impl From<TEdgeConfig> for mqtt_channel::Config. However, I think that the mqtt_channel crate is the wrong place for that. This crate should focus on MQTT and be independent on how things are configured. Hence, I would implement this configuration issue in the tedge_config crate.

Copy link
Contributor

@didier-wenzek didier-wenzek left a comment

Choose a reason for hiding this comment

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

The changes on mqtt-channel to add client authentication support are correct.

The missing point is now to use this setting from all the tedge services. This might be an opportunity to add an impl From<TEdgeConfig> for mqtt_channel::Config as discussed here: #1864 (comment). I will let you choose the steps you prefer to go there.

@Bravo555 Bravo555 marked this pull request as draft April 5, 2023 07:17
@Bravo555
Copy link
Contributor Author

Bravo555 commented Apr 5, 2023

In a48bdbc, I did a first try in extracting common MQTT configuration options into one place. Made it just another impl function, because only a part of the configuration is extracted, and it involves calling builder methods on mqtt_channel::Config, so I think From wouldn't be the best fit. Made it in another module because it introduced a dependency on mqtt_channel in tedge_config so we can perhaps gate this module behind a feature flag if necessary.

Still, I'm not really satisfied with this, because by generating MQTT config from tedge config, now we need to pass &TEdgeConfig everywhere where we previously only needed host and port. This is particularly annoying in tests, and because constructing TEdgeConfig from scratch would be very painful, and because parsing configuration file in tests sounds like a bad idea, I've made it optional in some parts so it could be None in unit tests, but it's not supposed to be permanent. Now I'd like to try and run some tests that make use of all the affected components, and after I've confirmed that it works, I'll try to fix this "how to pass config" issue. My best guess would be to group host, port and tls into one object, but that would introduce nesting to mqtt_channel's config builder which might be a bit awkward to use.

@Bravo555 Bravo555 temporarily deployed to Test Pull Request April 5, 2023 07:42 — with GitHub Actions Inactive
@Bravo555 Bravo555 temporarily deployed to Test Pull Request April 5, 2023 08:14 — with GitHub Actions Inactive
@didier-wenzek
Copy link
Contributor

didier-wenzek commented Apr 6, 2023

In a48bdbc,

This link is broken.

I did a first try in extracting common MQTT configuration options into one place. Made it just another impl function, because only a part of the configuration is extracted, and it involves calling builder methods on mqtt_channel::Config, so I think From wouldn't be the best fit. Made it in another module because it introduced a dependency on mqtt_channel in tedge_config so we can perhaps gate this module behind a feature flag if necessary.

The function TEdgeConfig::mqtt_config() seems perfect. Adding a feature flag might be not necessary as of today all the components have a dependency on MQTT.

Still, I'm not really satisfied with this, because by generating MQTT config from tedge config, now we need to pass &TEdgeConfig everywhere where we previously only needed host and port. This is particularly annoying in tests, and because constructing TEdgeConfig from scratch would be very painful, and because parsing configuration file in tests sounds like a bad idea, I've made it optional in some parts so it could be None in unit tests, but it's not supposed to be permanent. Now I'd like to try and run some tests that make use of all the affected components, and after I've confirmed that it works, I'll try to fix this "how to pass config" issue. My best guess would be to group host, port and tls into one object, but that would introduce nesting to mqtt_channel's config builder which might be a bit awkward to use.

Here, there is something I don't understand. Why &TEdgeConfig is now required where only host and port were previously needed? Why not replace host and port parameters by an mqtt_channel::Config and let the caller use TEdgeConfig::mqtt_config() if required?

I agree that constructing TEdgeConfig from scratch is very painful but I would really avoid to make config parameter optional to ease testing. Test helpers can be added in tedge_test_utils.

Also keep in mind that TEdgeConfig is under refactoring, and that it would be then feasible to write tests helpers as TEdgeConfig::default().with_client_mqtt_port(1234).

Custom Test Teardown

Check if a service using configured service type
[Arguments] ${service_name}
Execute Command tedge config set service.type thinedge
Custom Test Setup
ThinEdgeIO.Start Service ${service_name}
ThinEdgeIO.Restart Service ${service_name}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why restart is needed here?, is it already started by the setup?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The service starts when the connection to Cumulocity is not yet established, and fails with c8y_api::http_proxy: An error occurred while retrieving internal Id, operation will retry in 60 seconds and mapper will reinitialize. This error is mentioned earlier in the file, but bumping the timeout didn't really fix the problem. This did. If not having to restart the service is a hard requirement in this test, then I'll note it and be sure to find a solution that does not involve restarting, later.

Copy link
Contributor

Choose a reason for hiding this comment

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

If not having to restart the service is a hard requirement in this test, then I'll note it and be sure to find a solution that does not involve restarting, later.

Restarting the service is okay.

@Bravo555 Bravo555 temporarily deployed to Test Pull Request April 18, 2023 08:45 — with GitHub Actions Inactive
@Bravo555 Bravo555 marked this pull request as ready for review April 18, 2023 09:07
@Bravo555
Copy link
Contributor Author

So I think it's ready for review, core functionality is implemented, now just some potential tweaks:

  • settings naming: there's mqtt.client.ca_file for server authentication, but mqtt.client.auth.cert_file/key_file for client authentication. All of them are related to authentication, so I think perhaps all of them should be under mqtt.client.auth. Additionally, for cert_file, key_file we could remove _file suffix, at for anybody familiar with authentication, it's evident that we only provide one cert-private key pair. For ca_file, we could perhaps do something else to eliminate the underscore.
  • config organisation: because of current config architecture, I had to include some additional boilerplate for checking invariants like whether or not if client certificate was included, was the private key also included and vice-versa. As described here I think this aspect of validation configuration states should be moved from config clients to config provider.
  • RobotFramework tests: When I started working on this PR, I did a small RobotFramework suite Tests.Mqtt.Basic Pub Sub which verified all the MQTT clients work correctly with each: no authentication, server authentication, server + client authentication. Then, because we ideally want to test all clients, I've edited the test suite so all the tests work with server +client authentication, testing the largest possible surface. Now that all the tests work with authentication, should the original Tests.Mqtt.Basic Pub Sub suite be removed? It's somewhat trivial and things it tests are already tested by the rest of the tests.

@Bravo555
Copy link
Contributor Author

I realised that I haven't updated any markdown documentation, but it would probably be good to inform users about this new functionality there. As such, one more commit with documentation will likely be coming.

@Bravo555 Bravo555 temporarily deployed to Test Pull Request April 19, 2023 13:39 — with GitHub Actions Inactive
Copy link
Contributor

@didier-wenzek didier-wenzek left a comment

Choose a reason for hiding this comment

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

Nicely and thoroughly done! Thank you! Approved with some comments.

settings naming: there's mqtt.client.ca_file for server authentication, but mqtt.client.auth.cert_file/key_file for client authentication. All of them are related to authentication, so I think perhaps all of them should be under mqtt.client.auth. Additionally, for cert_file, key_file we could remove _file suffix, at for anybody familiar with authentication, it's evident that we only provide one cert-private key pair. For ca_file, we could perhaps do something else to eliminate the underscore.

  • I would keep the same names as mosquitto e.g certfile and keyfile.
  • We also need to be consistent with the misc mqtt.external.* settings.

config organisation: because of current config architecture, I had to include some additional boilerplate for checking invariants like whether or not if client certificate was included, was the private key also included and vice-versa. As described here I think this aspect of validation configuration states should be moved from config clients to config provider.

Indeed there is some boilerplate, but this okay for now.

RobotFramework tests: When I started working on this PR, I did a small RobotFramework suite Tests.Mqtt.Basic Pub Sub which verified all the MQTT clients work correctly with each: no authentication, server authentication, server + client authentication. Then, because we ideally want to test all clients, I've edited the test suite so all the tests work with server +client authentication, testing the largest possible surface. Now that all the tests work with authentication, should the original Tests.Mqtt.Basic Pub Sub suite be removed? It's somewhat trivial and things it tests are already tested by the rest of the tests.

Are you speaking about these tests?

I would keep them even if redundant. Indeed there are simple and cover well the 3 levels of authentication.

crates/common/mqtt_channel/src/config.rs Outdated Show resolved Hide resolved
crates/core/tedge/src/cli/mqtt/cli.rs Show resolved Hide resolved
crates/core/tedge_mapper/src/c8y/mapper.rs Outdated Show resolved Hide resolved
crates/core/tedge_mapper/src/c8y/mapper.rs Outdated Show resolved Hide resolved
Comment on lines +50 to +56
integration-test:
#!/usr/bin/env bash
ci/build_scripts/build.sh x86_64-unknown-linux-musl
cd tests/RobotFramework
source .venv/bin/activate
invoke build --local
invoke tests
Copy link
Contributor

Choose a reason for hiding this comment

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

It's really a good idea to add the integration tests here! Thanks.

Comment on lines +24 to +33
```conf
listener 8883
certfile PATH_TO_SERVER_CERTIFICATE
keyfile PATH_TO_SERVER_PRIVATE_KEY
```

- `listener 8883`: defines a new listener on port 8883
- `certfile`: points to a certificate that will be used by the broker to
authenticate itself to connecting clients
- `keyfile`: points to a private key of the specified certificate, necessary for
encrypted communication
Copy link
Contributor

Choose a reason for hiding this comment

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

What is document here is correct. However, I would like to stress that preparing such a configuration file is the point of the mqtt.external.* settings of tedge config.

tedge config list --doc | grep mqtt.external    
mqtt.external.port             Mqtt broker port, which is used by the external mqtt clients to publish or subscribe. Example: 8883
mqtt.external.bind_address     IP address / hostname, which the mqtt broker limits incoming connections on. Example: 0.0.0.0
mqtt.external.bind_interface   Name of network interface, which the mqtt broker limits incoming connections on. Example: wlan0
mqtt.external.capath           Path to a file containing the PEM encoded CA certificates that are trusted when checking incoming client certificates. Example: /etc/ssl/certsNote: If the capath is not set, then no certificates are required for the external connections.
mqtt.external.certfile         Path to the certificate file, which is used by external MQTT listenerExample: /etc/tedge/device-certs/tedge-certificate.pemNote: This setting shall be used together with `mqtt.external.keyfile` for external connections.
mqtt.external.keyfile          Path to the private key file, which is used by external MQTT listenerExample: /etc/tedge/device-certs/tedge-private-key.pemNote: This setting shall be used together with `mqtt.external.certfile` for external connections.

Copy link
Contributor

Choose a reason for hiding this comment

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

@reubenmiller I think this mqtt.external setting is badly named making things confusing.

Yes I fully agree. It is really just a binding address, it does not matter if it is "internal" or "external"...but yes it can be done as a later stage.


## Generating certificates

You can use the following script to generate all required certificates:
Copy link
Contributor

Choose a reason for hiding this comment

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

@reubenmiller We can consider to extend the tedge cert create command to create such certificates.

Custom Test Teardown

Check if a service using configured service type
[Arguments] ${service_name}
Execute Command tedge config set service.type thinedge
Custom Test Setup
ThinEdgeIO.Start Service ${service_name}
ThinEdgeIO.Restart Service ${service_name}
Copy link
Contributor

Choose a reason for hiding this comment

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

If not having to restart the service is a hard requirement in this test, then I'll note it and be sure to find a solution that does not involve restarting, later.

Restarting the service is okay.

@Bravo555
Copy link
Contributor Author

Added fixups addressing review comments.

Also renamed mqtt.client.ca_file/dir to mqtt.client.auth.cafile/dir so options related to authentication are all grouped together.

@didier-wenzek
Copy link
Contributor

Thank you for the fixups.

Please, rebase and squash the fixup commits. And I will merge this PR.

@Bravo555
Copy link
Contributor Author

@didier-wenzek squashed

@didier-wenzek
Copy link
Contributor

didier-wenzek commented Apr 21, 2023

@didier-wenzek squashed

Unfortunately, two system tests are failing on a certificate issue.

Cargo audit is also failing: did you rebase once your fix merged?

@Bravo555
Copy link
Contributor Author

Bravo555 commented Apr 21, 2023

Cargo audit is also failing: did you rebase once your fix merged?

My bad, forgot to rebase, will fix

This commit adds 2 new config options: `mqtt.client.auth.certfile` and
`mqtt.client.auth.keyfile` to facilitate MQTT client authentication.

The client certificates need to be signed by a CA. To authorise these
clients, the certificate of the CA which signed client certificates
needs to be specified in mosquitto config with cafile or cadir options.
Description of these options can be found in [mosquitto.conf].

These new settings were added because we need different certificates to
identify a device on a remote broker, and to identify plugins or
extensions on a local broker.

Also rewrapped some doc comments to 80 columns in tedge_config_dto.rs

[mosquitto.conf]: https://mosquitto.org/man/mosquitto-conf-5.html

Signed-off-by: Marcel Guzik <marcel.guzik@inetum.com>
This commit adds MQTT authentication support to the `mqtt_channel` crate
used by different plugins to access the local MQTT broker, as well as
changes the call sites in all the different plugins to start with MQTT
config partially filled by settings related to the current broker, e.g.
hostname, port, and authentication settings, which are the same for all
the plugins. After loading the pre-filled config, the client is then
free to provide settings for the client like subscriptions or last_will
messages, or even to override previously set broker settings.

Signed-off-by: Marcel Guzik <marcel.guzik@inetum.com>
To make sure that all the plugins can work properly with server and
client authentication enabled, the RobotFramework test suite setup was
edited to create a secure mosquitto listener on port 8883 with server
and client authentication enabled. This was achieved by adding
--secure/--no-secure options to bootstrap.sh script, with --secure being
the default.

The default port 1883 listener was not removed because it's created by
thin-edge with seemingly no easy way to disable editing mosquitto config
at runtime, thus it would require more source code changes which I've
decided are not worth it at the moment.

Signed-off-by: Marcel Guzik <marcel.guzik@inetum.com>
There was a [bug] in version 0.5.11 of toml crate which caused
serialization to fail if a serialized struct had fields that serialized
to plain value keys after fields that serialized to tables (like vecs or
hashmaps).

In 0.7 this bug is solved and additionally `toml` crate is now a thin
wrapper over `toml-edit` crate, just providing serde compatibility.
`toml-edit` now is able to better edit toml without fully serializing
it, potentially helping with the partial serialization problem described
in this [comment].

[bug]: toml-rs/toml#356
[comment]: thin-edge#1812 (comment)

Signed-off-by: Marcel Guzik <marcel.guzik@inetum.com>
Signed-off-by: Marcel Guzik <marcel.guzik@inetum.com>
@Bravo555 Bravo555 temporarily deployed to Test Pull Request April 21, 2023 14:59 — with GitHub Actions Inactive
@didier-wenzek didier-wenzek merged commit ff0152e into thin-edge:main Apr 21, 2023
15 checks passed
@Bravo555 Bravo555 deleted the mqtt-client-auth branch May 31, 2023 11:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants