Skip to main content

Security

The login in the URL or credentials sends a user name and a password; tls_ca trusts a certificate authority and selects TLS, and tls_client_auth adds a client certificate.

The user name and the password travel in the CONNECT packet. A server that refuses them fails startup at once, naming the refusal. A URL such as mqtt://user:password@host:1884 logs in with the user name and the password it carries. They are percent-decoded, as a URL writes them, so p%40ss is the password p@ss. The URL logs in only when it carries both a non-empty user name and a password. credentials is the explicit choice and wins over a login in the URL.

TLS takes PEM bytes, so the service reads its files itself and hands their contents over. tls_ca is the certificate authority that signed the server's certificate. The client verifies the server against it and against no other store. So a TLS connection needs it: an mqtts:// URL without tls_ca fails startup, naming the missing setting. tls_ca also selects TLS on an mqtt:// URL.

tls_client_auth takes the service's own certificate and key, for a server that verifies each client by its certificate, as managed MQTT services commonly do. A server may ask for both: a client certificate on a TLS listener, and a user name and a password inside the session.

The login and the handshake happen on the server, so a TestApp test runs the same app in process without them. The settings are still checked as at the connect: an mqtts:// URL without tls_ca fails TestApp::start too. The generated AsyncAPI document reports the host and the port, never the credentials.

Example​

The service logs in with its user name and the password the environment gives it.

/// A server that asks every client for a user name and a password.
fn password_broker(url: &str, client_id: &str, username: &str, password: &str) -> MqttBroker {
MqttBroker::new(url, client_id).credentials(username, password)
}

#[ruststream::app]
fn app() -> impl App<State = ()> {
let password = env::var("MQTT_PASSWORD").unwrap_or_default();
service(password_broker(
"mqtt://localhost:1884",
"telemetry-svc",
"telemetry-svc",
&password,
))
}

A deployment that hands over one connection string passes it as it is.

/// The deployment hands over one connection string with the login in it, such as
/// `mqtt://telemetry-svc:p%40ss@broker.internal:1884` for the password `p@ss`.
fn url_broker(url: &str, client_id: &str) -> MqttBroker {
MqttBroker::new(url, client_id)
}

On a TLS listener that verifies clients by their certificates, the service hands over the three PEM files of its deployment.

/// A TLS listener that verifies the service by its certificate. `dir` holds the PEM files the
/// deployment mounts: the authority that signed the server's certificate, and the service's own
/// certificate and key.
fn tls_broker(url: &str, client_id: &str, dir: &Path) -> io::Result<MqttBroker> {
Ok(MqttBroker::new(url, client_id)
.tls_ca(fs::read(dir.join("ca.crt"))?)
.tls_client_auth(
fs::read(dir.join("client.crt"))?,
fs::read(dir.join("client.key"))?,
))
}