Skip to content

Conversation

@rina23q
Copy link
Member

@rina23q rina23q commented Oct 17, 2025

Proposed changes

Support username/password authentication to the MQTT local broker.


Prepare a /etc/tedge/.password file. The first line is read as the password.

testpassword

Then, configure these parameters.

tedge config set mqtt.client.auth.username testuser
tedge config set mqtt.client.auth.password_file /etc/tedge/.password

If encrypted connection is activated in the broker, configure any secure port (e.g. 8883) and give the CA certificate file or the directory (below is example)

tedge config set mqtt.client.port 8883
tedge config set mqtt.client.auth.ca_file /etc/mosquitto/certs/ca.crt

Todo:

  • Confirm tedge mqtt pub/sub works
  • Confirm tedge-agent & tedge-mapper-c8y work with mosquitto bridge
  • Confirm tedge-agent & tedge-mapper-c8y works with build-in bridge
  • Confirm it works with non-c8y cloud
  • Write system tests
  • Extend the user guide

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

#3807

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA (in all commits with git commit -s. You can activate automatic signing by running just prepare-dev once)
  • I ran just format as mentioned in CODING_GUIDELINES
  • I used just check 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

@codecov
Copy link

codecov bot commented Oct 17, 2025

Codecov Report

❌ Patch coverage is 8.41121% with 98 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/common/mqtt_channel/src/config.rs 0.00% 54 Missing ⚠️
..._config/src/tedge_toml/tedge_config/mqtt_config.rs 26.66% 21 Missing and 1 partial ⚠️
crates/core/tedge/src/cli/mqtt/publish.rs 11.11% 6 Missing and 2 partials ⚠️
crates/core/tedge/src/cli/mqtt/subscribe.rs 0.00% 8 Missing ⚠️
crates/extensions/tedge_mqtt_bridge/src/lib.rs 0.00% 4 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment on lines 443 to 446
None => Err(CertificateError::IoError {
path: path.as_ref().to_owned(),
error: std::io::Error::other("File content is empty"),
}),
Copy link
Member Author

Choose a reason for hiding this comment

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

This error has to be removed as the MQTT specification states that you can send a username without password. However, both username and password are required for MqttOptions::set_credentials() in rumqttc, so I'm not sure how empty password is addressed in rumqttc.

Copy link
Member Author

@rina23q rina23q Oct 24, 2025

Choose a reason for hiding this comment

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

I checked how MqttOptions::set_cedentials() behaves in the packet level.

In the MQTT specs level, there are username flag and password flag. So, "empty password" and "password not set" are different. The former, the password flag is set but password is empty string. The latter, the password flag itself isn't set.

"Empty password" exmaple:

mosquitto_pub -h 127.0.0.1 -u testuser -P "" -t test -p 1883 -m message

"Password not set" example:

mosquitto_pub -h 127.0.0.1 -u testuser -t test -p 1883 -m message

Back to rumqttc. Their API offers only set_credentials(username, password). I provided the empty string to the second argument as below.

    mqttoptions.set_credentials("username", "");

Then, I ran the rumqttc client and confirmed the password flag was not set in the CONNECT packet. This means, rumqttc treats "empty password" equals to "password not set".

#[derive(Debug, Clone)]
enum ClientAuthConfig {
Cert(ClientAuthCertConfig),
User(ClientAuthUserConfig),
Copy link
Contributor

Choose a reason for hiding this comment

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

Password is clearer.

Suggested change
User(ClientAuthUserConfig),
Pass(ClientAuthPassConfig),

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed.

By the way, I created this enum because at first I thought user would use either certificate or username/password for authentication. However, after I saw the MQTT specifications, client certificate is in the Transport Layer and username/password is in the Application Layer. Then, I started feeling, we should support the case using both. Can you confirm this?

Copy link
Contributor

Choose a reason for hiding this comment

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

While a broker can accept both, a thin-edge MQTT client only needs to be configured to authenticate itself either using a certificate or a password, not both. Using a certificate is recommended.

Copy link
Contributor

Choose a reason for hiding this comment

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

But the username can still be required when using certificates right? At least that is my experience when using the Azure EventGrid MQTT endpoint (not that this is a cloud related topic, but more to give an example of an MQTT broker being configured a specific way).

Copy link
Member Author

Choose a reason for hiding this comment

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

But the username can still be required when using certificates right?

I think so. For example, mosquitto has use_identity_as_username settings, if it's set to true, the certificate's CN is used as a username. Password won't be set.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok good to know. I don't think we need to go to the level of having a "use_identity_as_username" equivalent in thin-edge.io...I'd much rather let the users configure the values themselves, and then possibly allow users to set a value using variables ${.common_name} (but we can do template evaluation that later).

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll refactor the code so that user has an opportunity to set username (and password if necessary) together with client certificates since it's possible in the MQTT specifications. I believe it's better thin-edge supports whatever the MQTT specifications allow.

#[derive(Debug, Clone)]
struct ClientAuthUserConfig {
username: String,
password: String,
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 consider to use zeroize

Suggested change
password: String,
password: Zeroizing<String>,

}

// Return username and password if they are set
pub fn username_and_password(&self) -> Option<(String, String)> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Better to return a ClientAuthUserConfig as defined in the mqtt_channel::config module.

Suggested change
pub fn username_and_password(&self) -> Option<(String, String)> {
pub fn username_and_password(&self) -> Option<mqtt_channel::config::ClientAuthUserConfig> {

Comment on lines 96 to 100
let auth_config = tedge_config.mqtt_client_auth_config();

if let Some((username, password)) = auth_config.username_and_password() {
local_config.set_credentials(username, password);
}
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 design the config API in such a way that no change is required for the clients connecting the local MQTT bus. So that, all the required auth settings are captured in a single method call to mqtt_client_auth_config whatever their kind (certificates or password based).

Comment on lines -69 to 78
if let Some(client_auth) = cmd.client_auth_config.as_ref() {
config.with_client_auth(&client_auth.cert_file, &client_auth.key_file)?;
if let Some(MqttAuthClientConfig::Cert(client_auth)) = cmd.client_auth_config.as_ref() {
config.with_client_auth_cert_key(&client_auth.cert_file, &client_auth.key_file)?;
}

if let Some(MqttAuthClientConfig::User(client_auth_user)) = cmd.client_auth_config.as_ref() {
config.with_client_auth_user_pass(
&client_auth_user.username,
client_auth_user.password_file.clone(),
)?;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

An MQTT client should be agnostic to how the user is authenticated. I would move this logic into the tedge config so we can write:

if let Some(client_auth) = cmd.client_auth_config.as_ref() {
        config.with_client_auth(client_auth)?;

This will avoid code duplication across tedge mqtt sub, pub, mappers and the agent.

Signed-off-by: Rina Fujino <rina.fujino.23@gmail.com>
Signed-off-by: Rina Fujino <rina.fujino.23@gmail.com>
@rina23q rina23q force-pushed the feature/3807/support-local-client-username-password-authentication branch from 4a67eee to f3adf15 Compare October 22, 2025 15:44
@rina23q rina23q temporarily deployed to Test Pull Request October 22, 2025 15:44 — with GitHub Actions Inactive
@github-actions
Copy link
Contributor

github-actions bot commented Oct 22, 2025

Robot Results

✅ Passed ❌ Failed ⏭️ Skipped Total Pass % ⏱️ Duration
717 0 3 717 100 2h10m9.512111s

@rina23q rina23q marked this pull request as ready for review October 23, 2025 12:33
Signed-off-by: Rina Fujino <rina.fujino.23@gmail.com>
@rina23q rina23q force-pushed the feature/3807/support-local-client-username-password-authentication branch from 0f9b7df to 34b2eab Compare October 23, 2025 13:16
Signed-off-by: Rina Fujino <rina.fujino.23@gmail.com>
2. Server authentication
3. Server + client authentication

In addition, authentication with username and password is supported. See [here](#username-password-authentication) for details.
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd have added that to the above list, especially since No authentication is a part of that list.

## Username/Password authentication {#username-password-authentication}

When a local MQTT broker requires username/password authentication,
the %%te%% components also need to know this username and password.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
the %%te%% components also need to know this username and password.
the %%te%% components need to provide this username and password.

```sh title="file: <file_path>"
YOUR_PASSWORD
```

Copy link
Contributor

@albinsuresh albinsuresh Oct 24, 2025

Choose a reason for hiding this comment

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

Add one line about restricting the access levels(600) to that file as soon as it is created?

Configure MQTT client host=${CONTAINER_1_HOSTNAME} port=1884
Start Service tedge-agent

TLS Setup
Copy link
Contributor

Choose a reason for hiding this comment

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

Optional suggestion: Instead of two variants TLS Setup and No TLS Setup, why not just keep a single Setup keyword that takes tld_enabled as an argument? Especially since these keywords are already taking the use_builtin_bridge argument?

Copy link
Member Author

@rina23q rina23q Oct 24, 2025

Choose a reason for hiding this comment

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

use_builtin_bridge makes really one-liner difference (sudo tedge config set mqtt.bridge.built_in ${use_builtin_bridge}), while TLS/no TLS need more different setup (bootstrap_args, port, set/unset some tedge config keys, different listener file). Then eventually I thought it would be easier to read, if there are two different Setup keys.

[Arguments] ${use_builtin_bridge}

# Parent
${CONTAINER_1}= Setup bootstrap_args=--no-secure register=${True} connect=${False}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
${CONTAINER_1}= Setup bootstrap_args=--no-secure register=${True} connect=${False}
${PARENT_SN}= Setup bootstrap_args=--no-secure register=${True} connect=${False}

We've always used these naming conventions to distinguish the containers easily.

Connect Mapper c8y

# Child
${CONTAINER_2}= Setup bootstrap_args=--no-secure register=${False} connect=${False}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
${CONTAINER_2}= Setup bootstrap_args=--no-secure register=${False} connect=${False}
${CHILD_SN}= Setup bootstrap_args=--no-secure register=${False} connect=${False}

pass_file: P,
) -> Result<&mut Self, CertificateError> {
debug!(target: "MQTT", "Using client username: {username}");
debug!(target: "MQTT", "Using client password: {}", pass_file.as_ref().display());
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
debug!(target: "MQTT", "Using client password: {}", pass_file.as_ref().display());
debug!(target: "MQTT", "Using client password from: {}", pass_file.as_ref().display());

#[derive(Debug, Clone)]
enum ClientAuthConfig {
Cert(ClientAuthCertConfig),
User(ClientAuthPassConfig),
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
User(ClientAuthPassConfig),
Cred(ClientAuthCredConfig),

Short for "credentials".

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, I just saw the older discussion on this. For some reason, it didn't appear in the files view during the review.

Comment on lines +70 to +79
if let Some(MqttAuthClientConfig::Cert(client_auth_cert)) = cmd.client_auth_config.as_ref() {
config
.with_client_auth_cert_key(&client_auth_cert.cert_file, &client_auth_cert.key_file)?;
}

if let Some(MqttAuthClientConfig::User(client_auth_user)) = cmd.client_auth_config.as_ref() {
config.with_client_auth_user_pass(
&client_auth_user.username,
client_auth_user.password_file.clone(),
)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if let Some(MqttAuthClientConfig::Cert(client_auth_cert)) = cmd.client_auth_config.as_ref() {
config
.with_client_auth_cert_key(&client_auth_cert.cert_file, &client_auth_cert.key_file)?;
}
if let Some(MqttAuthClientConfig::User(client_auth_user)) = cmd.client_auth_config.as_ref() {
config.with_client_auth_user_pass(
&client_auth_user.username,
client_auth_user.password_file.clone(),
)?;
if let Some(client_auth_config) = cmd.client_auth_config.as_ref() {
match client_auth_config {
MqttAuthClientConfig::Cert(client_auth_cert) => {
config
.with_client_auth_cert_key(&client_auth_cert.cert_file, &client_auth_cert.key_file)?;
}
MqttAuthClientConfig::User(client_auth_user) => {
config.with_client_auth_user_pass(
&client_auth_user.username,
client_auth_user.password_file.clone(),
)?;
}
}
}```

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.

4 participants