Search code examples
authenticationpluginsmqttmqtt-vernemq

Enabling multiple authentication/authorization plugins in Vernemq


When it comes to authentication/authorization in Vernemq, I expected that multiple enabled plugins would be checked in order to determine if a connect/pub/sub is permitted or not.

However, I am experiencing different results in my deployment.

If only one of the auth plugins - either vmq.acl or vmq.diversity - is enabled, it works as expected. But, with both plugins enabled, only vmq.diversity seems to be working.

What I would like to ask is:

  1. Is this the expected behaviour of vernemq?

  2. If not, how can multiple auth plugins be enabled?

  3. With multiple auth plugins enabled, in what order are they checked?

The vernemq service is run via docker compose as described below.

# docker-compose.yaml

services:
  vernemq:
    image: vernemq/vernemq
    container_name: vernemq_broker
    environment:
      - DOCKER_VERNEMQ_ACCEPT_EULA=yes

      - DOCKER_VERNEMQ_ALLOW_ANONYMOUS=off

      - DOCKER_VERNEMQ_PLUGINS__VMQ_PASSWD=on
      - DOCKER_VERNEMQ_PLUGINS__VMQ_ACL=on
      - DOCKER_VERNEMQ_VMQ_ACL__ACL_RELOAD_INTERVAL=10

      - DOCKER_VERNEMQ_PLUGINS__VMQ_DIVERSITY=on
      
      - DOCKER_VERNEMQ_VMQ_DIVERSITY__AUTH_POSTGRES__ENABLED=on
      - DOCKER_VERNEMQ_VMQ_DIVERSITY__POSTGRES__HOST=auth_db
      - DOCKER_VERNEMQ_VMQ_DIVERSITY__POSTGRES__PORT=5432
      - DOCKER_VERNEMQ_VMQ_DIVERSITY__POSTGRES__USER=postgres
      - DOCKER_VERNEMQ_VMQ_DIVERSITY__POSTGRES__PASSWORD=password
      - DOCKER_VERNEMQ_VMQ_DIVERSITY__POSTGRES__DATABASE=postgres
      - DOCKER_VERNEMQ_VMQ_DIVERSITY__POSTGRES__PASSWORD_HASH_METHOD=crypt
      
      # insecure approach to setting password - for testing/development only
      - DOCKER_VERNEMQ_USER_file_user1=file_user1_password
      - DOCKER_VERNEMQ_USER_file_user2=file_user2_password

      - DOCKER_VERNEMQ_LOG__CONSOLE__LEVEL=debuga
    volumes:
      - ./vernemq/vmq.acl:/etc/vernemq/vmq.acl
    ports:
      - 1883:1883
    depends_on:
      - auth_db
  auth_db:
    image: postgres:latest
    container_name: vernemq_auth_db
    environment:
      - POSTGRES_PASSWORD=password
      - POSTGRES_USER=postgres
      - POSTGRES_DB=postgres
    volumes:
      - ./auth_db/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql
    ports:
      - 5432:5432
    expose:
      - 5432
# vmq.acl

user file-user1
topic private/file/#
topic public/#

user file-user2
topic private/file/#
topic public/#

The authentication database is initialised with the script:

-- init-db.sql

CREATE EXTENSION pgcrypto;
CREATE TABLE IF NOT EXISTS vmq_auth_acl (
    mountpoint character varying(10) NOT NULL,
    client_id character varying(128) NOT NULL,
    username character varying(128) NOT NULL,
    password character varying(128),
    publish_acl json,
    subscribe_acl json,
    CONSTRAINT vmq_auth_acl_primary_key PRIMARY KEY (mountpoint, client_id, username)
);

CREATE OR REPLACE PROCEDURE insert_user(user_ref text)
LANGUAGE SQL AS 
$$ 
  WITH x AS (
    SELECT ''::text AS mountpoint,
    CONCAT('db-', user_ref, '-client-id')::text AS client_id, -- eg: 'db-user1-client-id'
    CONCAT('db-', user_ref)::text AS username, -- eg: 'db-user1'
    CONCAT('db-', user_ref, '-password')::text AS password, -- eg: 'db-user1-password'
    gen_salt('bf')::text AS salt,
    '[{"pattern": "private/db/#"}, {"pattern": "public/#"}]'::json AS publish_acl,
    '[{"pattern": "private/db/#"}, {"pattern": "public/#"}]'::json AS subscribe_acl
  )
  INSERT INTO vmq_auth_acl (
    mountpoint,
    client_id,
    username,
    password,
    publish_acl,
    subscribe_acl
  )
  SELECT
    x.mountpoint,
    x.client_id,
    x.username,
    crypt(x.password, x.salt),
    publish_acl,
    subscribe_acl
  FROM x ON CONFLICT (mountpoint, client_id, username) DO NOTHING;
$$;
--
--
CALL insert_user('user1'); -- inserts row for user 'db-user1'
CALL insert_user('user2'); -- inserts row for user 'db-user2'

With the above configuration and files, there are the following users defined under the following plugins.

  1. file-based authentication - vmq.passwd plugin

    • file-user1
    • file-user2
  2. authentication via postgres database - vmq.diversity plugin

    • db-user1
    • db-user2

When both plugins are enabled, only the user defined in the database are able to authenticate and connect.


Solution

  • In general, an authentication plugin (implementing the auth_on_register-hook) will give an internal response to the broker. If the plugin authenticates the client, the response will be ok. The response can also be next to tell the broker that the current plugin cannot authenticate the client, so the broker should just go check the next plugin.

    This was mostly intended a way to allow users to chain their own plugins. It was not implemented for the file-based plugin, for reasons of compatibility to the Mosquitto format. You can easily implement a chain in a vmq_diversity plugin, though, as you can adapt the Lua scripts.

    1. Is this the expected behaviour of vernemq?
    

    Right now, yes, see above explanation

    2. If not, how can multiple auth plugins be enabled?
    

    By making the plugins return next instead of an authentication error. Verne will then just go through all the plugins until it runs out of plugins to ask. You will always see the typical plugin_chain_exhausted logged as a reason for a rejected client. Let me know when you need more help with that.

    3. With multiple auth plugins enabled, in what order are they checked?
    

    They are checked in load order (whether you load the plugins internally via vernemq.conf file or externally via the plugin enable command). The order should be visible in the vmq-admin plugin show command too.