Skip to content
Open
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
77 changes: 77 additions & 0 deletions doc/brigde-authentication-by-plugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
title: 'Mosquitto: using a plugin to authenticate a bridge'
tags:
- Bridge
- MQTT
- Authentication
authors:
- name: Thorsten Wendt
date: 22 December 2025
---

# Introduction

This patch introduces a new way to provide credentials for bridge
authentication.

Currently the only way to provide authentication credentials was to
set __remote\_username__ and __remote\_password__ in the configuration
file. There was no other way to provide these credentials.

But for example on some platforms these credentials are stored in
a TPM. There is no direct way to pull these credentials from TPM and
use it for bridge authentication.

With this patch these credentials can be provided by a **plugin** that
yields _username_/_password_ on bridge setup and will be used for
broker authentication. Customers can provide this **plugin**
without publishing their source code.

# Plugin interface

The plugin have to register for event **MOSQ_EVT_LOAD_BRIDGE_CRED**.

The callback then will be called before the bride is going to send credentials
to the configured broker. The plugin can set the entries for username and password in the
structure **mosquitto_evt_load_bridge_cred** delivered with the parameter _event\_data_.

The callback can return one of the following value

* MOSQ_ERR_SUCCESS - Plugin provided username and password
* MOSQ_ERR_PLUGIN_DEFER - Plugin is not able to provide username/password
* MOSQ_ERR_AUTH_DENIED - Authentication is denied at all
* MOSQ_ERR_NOT_FOUND - There will be no username/password at all
* MOSQ_ERR_NOMEM - Out of memory

# Bridge's behavior

For every bridge the callback(s) of configured auth bridge plugins will be called.

**MOSQ_ERR_SUCCESS:** Username/password is set by the plugin will be used to send it to the broker.

**MOSQ_ERR_PLUGIN_DEFER:** Will call the next plugin if configured otherwise existing remote_username/remote_password will be used to send it to the broker.

**MOSQ_ERR_AUTH_DENIED:** No other plugin will be called and even existing remote_username/remote_password will be ignored. No username/password will be send to the broker.

**MOSQ_ERR_NOT_FOUND:** No further bridge auth plugin will be called even when configured, instead configured remote_username/remote_password will be used to send it to the broker.

**MOSQ_ERR_NOMEM:** Any action to handle bridge configuration will be aborted immediately.

# Examples

Examples can be found in

- plugins/examples/auth-by-bridge/mosquitto_auth_by_bridge.c
- test/broker/c/bridge_auth_v1.c
- test/broker/c/bridge_auth_v2.c

# What's changed inside

The whole algorithm is hooked in function _bridge\_\_new_ in file _bridge.c_.
To implement this new feature the following new constants and a new structure was introduced.

**MOSQ_ERR_AUTH_DENIED** in _enum mosq\_err\_t_.

**MOSQ_EVT_LOAD_BRIDGE_CRED** in _mosquitto\_plugin\_event_ for registering on this new event.

**load_bridge_cred** in structure _plugin\_\_callbacks_ to store auth bridge plugin callbacks.
17 changes: 17 additions & 0 deletions include/mosquitto/broker.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ enum mosquitto_plugin_event {
MOSQ_EVT_CLIENT_OFFLINE = 28,
MOSQ_EVT_PERSIST_WILL_ADD = 29,
MOSQ_EVT_PERSIST_WILL_DELETE = 30,
MOSQ_EVT_LOAD_BRIDGE_CRED = 31,
};

/* Data for the MOSQ_EVT_RELOAD event */
Expand Down Expand Up @@ -375,6 +376,16 @@ struct mosquitto_evt_persist_will_msg {
void *future2[8];
};

/* Data for the MOSQ_EVT_LOAD_BRIDGE_CRED event */
/* NOTE: This interface is currently marked as unstable, which means
* it may change in a future minor release. */
struct mosquitto_evt_load_bridge_cred {
char* username;
char* password;
const char* bridge_name;
const struct bridge_address* bridge_address;
};


/* Callback definition */
typedef int (*MOSQ_FUNC_generic_callback)(int, void *, void *);
Expand Down Expand Up @@ -489,6 +500,9 @@ mosq_EXPORT int mosquitto_plugin_set_info(
* * MOSQ_EVT_PERSIST_CLIENT_MSG_UPDATE
* Called when a persistence plugin must update a client message
* in its store.
* * MOSQ_EVT_LOAD_BRIDGE_CRED
* Called when a bridge is about to created to get
* username/password from a loaded plugin
*
* cb_func - the callback function
* event_data - event specific data
Expand Down Expand Up @@ -542,6 +556,9 @@ mosq_EXPORT int mosquitto_callback_register(
* * MOSQ_EVT_PERSIST_CLIENT_MSG_ADD
* * MOSQ_EVT_PERSIST_CLIENT_MSG_DELETE
* * MOSQ_EVT_PERSIST_CLIENT_MSG_UPDATE
* * MOSQ_EVT_PERSIST_WILL_ADD
* * MOSQ_EVT_PERSIST_WILL_DELETE
* * MOSQ_EVT_LOAD_BRIDGE_CRED
* cb_func - the callback function
* event_data - event specific data
*
Expand Down
1 change: 1 addition & 0 deletions include/mosquitto/defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ enum mosq_err_t {
MOSQ_ERR_ALREADY_EXISTS = 31,
MOSQ_ERR_PLUGIN_IGNORE = 32,
MOSQ_ERR_HTTP_BAD_ORIGIN = 33,
MOSQ_ERR_AUTH_DENIED = 34,

/* MQTT v5 direct equivalents 128-255 */
MOSQ_ERR_UNSPECIFIED = 128,
Expand Down
1 change: 1 addition & 0 deletions plugins/examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ if(NOT WIN32)
add_subdirectory(client-lifetime-stats)
add_subdirectory(message-timestamp)
endif()
add_subdirectory(auth-by-bridge)
add_subdirectory(auth-by-env)
add_subdirectory(auth-by-ip)
add_subdirectory(client-properties)
Expand Down
3 changes: 3 additions & 0 deletions plugins/examples/auth-by-bridge/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
set (PLUGIN_NAME mosquitto_auth_by_bridge)

add_mosquitto_plugin_no_install("${PLUGIN_NAME}" "${PLUGIN_NAME}.c" "" "libmosquitto_common")
15 changes: 15 additions & 0 deletions plugins/examples/auth-by-bridge/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
R=../../..
include ${R}/config.mk

PLUGIN_NAME=mosquitto_auth_by_bridge
LOCAL_CFLAGS+=
LOCAL_CPPFLAGS+=
LOCAL_LDFLAGS+=
LOCAL_LIBADD+=

all : binary

OBJS:=${PLUGIN_NAME}.o

PLUGIN_NOINST:=1
include ${R}/plugins/plugin.mk
120 changes: 120 additions & 0 deletions plugins/examples/auth-by-bridge/mosquitto_auth_by_bridge.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
Copyright (C) by ebblo Switzerland GmbH, CH-8212 Neuhausen, Switzerland

This project and the accompanying materials are made available under the
terms of the Eclipse Public License 2.0 and 3-Clause BSD License which
accompany this distribution.

The Eclipse Public License is available at
https://www.eclipse.org/legal/epl-2.0
and the 3-Clause BSD License is available at
https://opensource.org/license/BSD-3-Clause

SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause

Contributors:
- Thorsten Wendt <thorsten.wendt@ebblo.com> - Adding means for brigde authentication.
*/

/*
* This is an example plugin showing how to use the authentication
* callback to provide username and password for connecting a bridge to a broker.
*
* This callback is called when a new bridge is going to be established.
*
* The callback have to provide username/passwort. The broker (in bridge mode)
* is going to use these credentials for authentication against the broker.
*
* The following return values are supported:
*
* MOSQ_ERR_SUCCESS - This plugin provided username and password
* MOSQ_ERR_PLUGIN_DEFER - This plugin is not able to provide username/password
* MOSQ_ERR_AUTH_DENIED - Authentication is denied by the plugin and
* MOSQ_ERR_NOMEM - Out of memory
*
*
* Compile with:
* gcc -I<path to mosquitto-repo/include> -fPIC -shared mosquitto_auth_by_bridge.c -o mosquitto_auth_by_bridge.so
*
* Use in config with:
*
* plugin /path/to/mosquitto_auth_by_bridge.so
*
* Note that this only works on Mosquitto 2.1 or later.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "mosquitto.h"

#include <config.h>

#define PLUGIN_NAME "auth-by-bridge"
#define PLUGIN_VERSION "0.0.1"

#define ENV_AUTH_PLUGIN_USER "AUTH_PLUGIN_USER"
#define ENV_AUTH_PLUGIN_PASS "AUTH_PLUGIN_PASS"

MOSQUITTO_PLUGIN_DECLARE_VERSION( 5 );

static mosquitto_plugin_id_t *mosq_pid = NULL;

static int basic_auth_callback(int event, void *event_data, void *userdata)
{
struct mosquitto_evt_load_bridge_cred* ed = event_data;

UNUSED( event );
UNUSED( userdata );

char* env_auth_plugin_user = getenv( ENV_AUTH_PLUGIN_USER );
char* env_auth_plugin_pass = getenv( ENV_AUTH_PLUGIN_PASS );

if ( ( env_auth_plugin_user && strlen( env_auth_plugin_user ) > 0 ) &&
( env_auth_plugin_pass && strlen( env_auth_plugin_pass ) > 0 ) )
{
char* auth_plugin_username = mosquitto_strdup( env_auth_plugin_user );
if ( !auth_plugin_username )
{
mosquitto_log_printf( MOSQ_LOG_ERR, "Out of memory." );
return MOSQ_ERR_NOMEM;
}

char* auth_plugin_password = mosquitto_strdup( env_auth_plugin_pass );
if ( !auth_plugin_password )
{
mosquitto_free( auth_plugin_username );
mosquitto_log_printf( MOSQ_LOG_ERR, "Out of memory." );
return MOSQ_ERR_NOMEM;
}

ed->username = auth_plugin_username;
ed->password = auth_plugin_password;
return MOSQ_ERR_SUCCESS;
}

return MOSQ_ERR_NOT_FOUND;
}


int mosquitto_plugin_init(mosquitto_plugin_id_t *identifier, void **user_data, struct mosquitto_opt *opts, int opt_count)
{
UNUSED( user_data );
UNUSED( opts );
UNUSED( opt_count );

mosq_pid = identifier;
mosquitto_plugin_set_info( identifier, PLUGIN_NAME, PLUGIN_VERSION );
return mosquitto_callback_register( mosq_pid, MOSQ_EVT_LOAD_BRIDGE_CRED, basic_auth_callback, NULL, NULL );
}


/* mosquitto_plugin_cleanup() is optional in 2.1 and later. Use it only if you have your own cleanup to do */
int mosquitto_plugin_cleanup(void *user_data, struct mosquitto_opt *opts, int opt_count)
{
UNUSED( user_data );
UNUSED( opts );
UNUSED( opt_count );

return MOSQ_ERR_SUCCESS;
}
1 change: 1 addition & 0 deletions plugins/examples/auth-by-bridge/test.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
plugin ./mosquitto_auth_by_bridge.so
3 changes: 3 additions & 0 deletions plugins/examples/auth-by-bridge/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh

../../../src/mosquitto -c test.conf -v
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const char evt_topics[][60] = {
TOPIC_BASE "persist/message/client/delete", /* MOSQ_EVT_PERSIST_CLIENT_MSG_DELETE */
TOPIC_BASE "persist/message/client/update", /* MOSQ_EVT_PERSIST_CLIENT_MSG_UPDATE */
TOPIC_BASE "message/out", /* MOSQ_EVT_MESSAGE_OUT */
TOPIC_BASE "init/cred", /* MOSQ_EVT_LOAD_BRIDGE_CRED */
};


Expand Down
58 changes: 57 additions & 1 deletion src/bridge.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,61 @@ static int bridge__connect_step2(struct mosquitto *context);
#endif
static void bridge__packet_cleanup(struct mosquitto *context);

static struct mosquitto *bridge__new(struct mosquitto__bridge *bridge)
static void bridge_auth_credentials(struct mosquitto__bridge* bridge)
{
// If an auth bridge plugin is available load username/password
// for bridge authentication.
struct mosquitto__callback *cb_base, *cb_next;
struct mosquitto__callback* load_bridge_cred = db.config->security_options.plugin_callbacks.load_bridge_cred;
DL_FOREACH_SAFE( load_bridge_cred, cb_base, cb_next )
{
struct mosquitto_evt_load_bridge_cred event_data;
memset(&event_data, 0, sizeof(event_data));
event_data.bridge_name = bridge->name;
event_data.bridge_address = bridge->addresses;

int rc = cb_base->cb(MOSQ_EVT_LOAD_BRIDGE_CRED, &event_data, cb_base->userdata);

/* If bridge auth plugin is returning MOSQ_ERR_AUTH_DENIED username/password
* will be removed and set to NULL */
if (rc == MOSQ_ERR_AUTH_DENIED){
mosquitto_free(bridge->remote_username);
mosquitto_free(bridge->remote_password);
bridge->remote_username = NULL;
bridge->remote_password = NULL;
break;
}

/* If username/password are set by the bridge auth plugin we are
* using them. Any already existing username/password will be
* removed and exchanged by the new ones */
if (rc == MOSQ_ERR_SUCCESS && event_data.username && event_data.password){
mosquitto_free(bridge->remote_username);
mosquitto_free(bridge->remote_password);
bridge->remote_username = event_data.username;
bridge->remote_password = event_data.password;
break;
}

/* If bridge auth plugin is returning MOSQ_ERR_NOT_FOUND no further
* bridge auth plugin will be called even when configured, instead
* configured remote_username/remote_password will be used */
if (rc == MOSQ_ERR_NOT_FOUND){
break;
}

/* If bridge auth plugin is returning MOSQ_ERR_PLUGIN_DEFER the
* next plugin will be asked for username/password, if present */
if (rc == MOSQ_ERR_PLUGIN_DEFER) {
continue;
}

/* On all other return values we are leaving the loop */
break;
}
}

static struct mosquitto* bridge__new(struct mosquitto__bridge* bridge)
{
struct mosquitto *new_context = NULL;
struct mosquitto **bridges;
Expand Down Expand Up @@ -94,6 +148,8 @@ static struct mosquitto *bridge__new(struct mosquitto__bridge *bridge)
new_context->bridge = bridge;
new_context->is_bridge = true;

bridge_auth_credentials(bridge);

new_context->username = bridge->remote_username;
new_context->password = bridge->remote_password;

Expand Down
1 change: 1 addition & 0 deletions src/mosquitto_broker_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ struct plugin__callbacks {
struct mosquitto__callback *persist_retain_msg_delete;
struct mosquitto__callback *persist_will_add;
struct mosquitto__callback *persist_will_delete;
struct mosquitto__callback *load_bridge_cred;
};

/* This is owned by mosquitto__config or mosquitto__listener, and only referred
Expand Down
4 changes: 4 additions & 0 deletions src/plugin_callbacks.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ static const char *get_event_name(enum mosquitto_plugin_event event)
return "persist-will-add";
case MOSQ_EVT_PERSIST_WILL_DELETE:
return "persist-will-delete";
case MOSQ_EVT_LOAD_BRIDGE_CRED:
return "load-bridge-credentials";
}
return "";
}
Expand Down Expand Up @@ -166,6 +168,8 @@ static struct mosquitto__callback **plugin__get_callback_base(struct mosquitto__
return &security_options->plugin_callbacks.persist_will_add;
case MOSQ_EVT_PERSIST_WILL_DELETE:
return &security_options->plugin_callbacks.persist_will_delete;
case MOSQ_EVT_LOAD_BRIDGE_CRED:
return &security_options->plugin_callbacks.load_bridge_cred;
}
return NULL;
}
Expand Down
Loading