-
Notifications
You must be signed in to change notification settings - Fork 68
feat: Support username/password local broker authentication #3823
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
base: main
Are you sure you want to change the base?
feat: Support username/password local broker authentication #3823
Conversation
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! 🚀 New features to boost your workflow:
|
| None => Err(CertificateError::IoError { | ||
| path: path.as_ref().to_owned(), | ||
| error: std::io::Error::other("File content is empty"), | ||
| }), |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 messageBack 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), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Password is clearer.
| User(ClientAuthUserConfig), | |
| Pass(ClientAuthPassConfig), |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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
| password: String, | |
| password: Zeroizing<String>, |
| } | ||
|
|
||
| // Return username and password if they are set | ||
| pub fn username_and_password(&self) -> Option<(String, String)> { |
There was a problem hiding this comment.
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.
| pub fn username_and_password(&self) -> Option<(String, String)> { | |
| pub fn username_and_password(&self) -> Option<mqtt_channel::config::ClientAuthUserConfig> { |
| 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); | ||
| } |
There was a problem hiding this comment.
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).
| 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(), | ||
| )?; | ||
| } |
There was a problem hiding this comment.
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>
4a67eee to
f3adf15
Compare
Robot Results
|
Signed-off-by: Rina Fujino <rina.fujino.23@gmail.com>
0f9b7df to
34b2eab
Compare
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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 | ||
| ``` | ||
|
|
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| ${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} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| ${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()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| User(ClientAuthPassConfig), | |
| Cred(ClientAuthCredConfig), |
Short for "credentials".
There was a problem hiding this comment.
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.
| 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(), | ||
| )?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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(), | |
| )?; | |
| } | |
| } | |
| }``` |
Proposed changes
Support username/password authentication to the MQTT local broker.
Prepare a
/etc/tedge/.passwordfile. The first line is read as the password.Then, configure these parameters.
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)
Todo:
tedge mqtt pub/subworkstedge-agent&tedge-mapper-c8ywork with mosquitto bridgetedge-agent&tedge-mapper-c8yworks with build-in bridgeTypes of changes
Paste Link to the issue
#3807
Checklist
just prepare-devonce)just formatas mentioned in CODING_GUIDELINESjust checkas mentioned in CODING_GUIDELINESFurther comments