eclipse-mosquitto / mosquitto

Eclipse Mosquitto - An open source MQTT broker
https://mosquitto.org
Other
9.07k stars 2.39k forks source link

Feature: Dynamic security control #1779

Closed ralight closed 3 years ago

ralight commented 4 years ago

Mosquitto Dynamic Security

This document describes a topic based mechanism for controlling security in Mosquitto. JSON commands are published to topics like $CONTROL/<feature>/v1

Users

When a client connects to Mosquitto, it can optionally provide a username. The username maps the client to a user on the broker, if it exists. Multiple clients can make use of the same username, and hence the same broker user.

Groups

Broker users can be defined as belonging to zero or more broker groups.

Security Policies

Security policies can be applied to a user or a group, and define what that user/group is allowed to do, for example what topics it may or may not publish or subscribe to, or whether it is allowed to access different administrative features.

If a user is in a group, and both the user and the group have separate policies applied, then the user policy will override the group policy.

Users can have their groups assigned a priority. This means that if they are in multiple groups, the order in which policies are applied can be defined. Policy will be applied starting with the group with the lowest priority, moving to groups with higher priorities, and finally a user policy if defined.

If a user is not a member of any groups, and does not have a user policy, then all access will be denied.

Policy Features

Security policy management

Actions:

Topic: $CONTROL/security-policy/v1

User/group management

Actions:

Topic: $CONTROL/user-management/v1

Bridge control

Future

Listener control

Future

General preferences

Future, e.g. max_keepalive.

Security policy management

Topic: $CONTROL/security-policy/v1

Commands are JSON payloads as defined below. There can be any number of commands in a single message. The below schema defines all of the available commands.

{
    "commands": [
        {
            "command": "addPolicy",         # Create a new policy
            "policyName": "",
            "policy":{
                # Full policy description, see below
            },
            "users":["one", "two"],
            "groups":["g-one", "g-two"]
        },
        {
            "command": "deletePolicy",      # Delete an existing policy
            "policyName": ""
        },
        {
            "command": "replacePolicy",     # Completely replace an existing policy
            "policyName": "",
            "policy":{
                # Full policy description, see below
            },
            "users":[],
            "groups":[]
        },
        {
            "command": "getPolicy",
            "policyName": ""                # If empty or omitted, retrieve all policies
        },
        {
            "command": "setUserPolicy",
            "policyName": ""                # If empty or omitted then set the group policy to
                                            # null.
        },
        {
            "command": "setGroupPolicy",
            "policyName": ""                # If empty or omitted then set the group policy to 
                                            # null.
        },
        {
            "command": "listPolicies"       # Retrieve a list of policy names
        },
        {
            "command": "setUserDefaultPolicy",
                                            # Sets the policy that new users will automatically 
                                            # be assigned to if no other policy is specified.
            "policyName": ""
        },
        {
            "command": "setGroupDefaultPolicy",
                                            # Set the policy to automatically assign a new 
                                            # group to.
            "policyName": ""
        },
        {
            "command": "addPolicyFeature",  # Add a new feature to a policy. Features are 
                                            # different areas of policy control, e.g. user 
                                            # management or policy management.# This *could* be 
                                            # controlled through access to $CONTROL/ topics,
                                            # but an explicit feature control is clearer.
                                            # The normal topic access control does not allow 
                                            # changing access to the $CONTROL topics.
            "policyName": "",
            "featureName": ""
        },
        {
            "command": "removePolicyFeature",
                                            # Remove a feature from a policy
            "policyName": "",
            "featureName": ""
        },
        {
            "command": "addTopicAccessControl",
            "policyName": "",               # Add the access to this policy
            "type": "publish-write",        # This affects messages being sent from a client to 
                                            # the broker, i.e. it operates on PUBLISH only
            "topicFilter": "",              # This is a normal MQTT topic filter that can contain 
                                            # embedded %c %u type arguments for replacement with 
                                            # client id and username.
            "maxQos": 2,                    # The maximum QoS allowed when publishing.
            "allowRetain": true,            # Whether retained messages are allowed to be
                                            # published.
            "maxPayloadSize": 1000,         # The maximum payload size allowed, in bytes.
            "allow": false                  # Whether access to this topic is allowed.
        },
        {
            "command": "addTopicAccessControl",
            "policyName": "",
            "type": "publish-read",         # This ACL affects messages that are due to be 
                                            # delivered from the broker to a client, i.e. it 
                                            # operates on PUBLISH only.
            "topicFilter": "",              # This is a normal MQTT topic filter that can contain 
                                            # embedded %c %u type arguments for replacement with 
                                            # client id and username.
        },
        {
            "command": "addTopicAccessControl",
            "policyName": "",
            "type": "subscribe-",           # This ACL affects the actual subscription process, 
                                            # i.e. it operates on SUBSCRIBE only
            "topicFilter": "",              # This is a topic filter that can use MQTT wildcards 
                                            # to determine which subscription patterns are 
                                            # allowed. For example, setting '#' here would allow/
                                            # deny access to every single subscription.
            "maxQos": 2,                    # The maximum QoS allowed, if subscription is allowed.
            "allow": true                   # Is access allowed or not.
        },
        {
            "command": "addTopicAccessControl",
            "policyName": "",
            "type": "subscribe-fixed",      # This ACL affects the actual subscription process,
                                            # i.e. it operates on SUBSCRIBE only.
            "topicFilter": "",              # This is an explicit string that is allowed/denied
                                            # for subscription. For example, setting '#' here
                                            # would allow/deny access to subscriptions to the '#'
                                            # topic filter only.
            "maxQos": 2,                    # The maximum QoS allowed, if subscription is allowed.
            "allow": true                   # Is access allowed or not.
        }
    ]
}

User/group management

Topic: $CONTROL/user-management/v1

{
    "commands": [
        {
            "command": "addUser",
            "username": "",                 # Required
            "password": "",                 # Required, must not be empty.
            "clientid": "",                 # Optional, if empty then any client id is valid for 
                                            # this username. If not empty, then only a specific 
                                            # clientid may use this username.
            "policyName": "",               # Optional, if empty or omitted then use the default 
                                            # user policy
        },
        {
            "command": "deleteUser",
            "username": ""
        },
        {
            "command": "setUserPassword",   # Change the password for a user
            "username": "",                 # Required
            "password": ""                  # Must not be empty
        },
        {
            "command": "addGroup",          # Create a new group
            "groupname": "",
            "policyName": "",               # Optional, if empty or omitted then use the default 
                                            # group policy
        }
        {
            "command": "addUserToGroup",    # Add a user to a group
            "username": "",                 # Required, must not be empty
            "group": ""                     # Required, must not be empty
        },
        {
            "command": "removeUserFromGroup",
            "username": "",                 # Remove a user from a group
            "group": ""
        },
        {
            "command": "listUsers",         # Return a list of users
            "verbose": false                # If false, just a list of usernames, if true then 
                                            # include policies.
        },
        {
            "command": "listGroups"         # Return a list of groups
            "verbose": false                # If false, just return a list of groups. If true,
                                            # return a list of groups with users and policies
        },
        {
            "command": "listGroupUsers",    # Return a list of users in a group
            "group": ""
        },
        {
            "command": "kickClient",        # Remove an active session
            "username": "",                 # One of username or clientid must
            "clientid": ""                  # be present, but not both
        }
    ]
}

Policy storage

How policies are stored on disk.

{
    "policies": [
        {
            "policyName":"",
            "features":[
                {
                    "name": "user-management",
                    "allow": true
                },
                {
                    "name": "security-policy",
                    "allow": true
                }
            ],
            "topics":[
                {
                   "type": "publish-write",
                   "topicFilter": "",
                   "maxQos": 2,
                   "allowRetain": true,
                   "maxPayloadSize": 1000,
                   "allow": false
                }
            ]
        }
    ]
}

User and group storage

{
    "users": [
        {
            "clientid": "",
            "username": "",
            "password": "",
            "banned": false,                # Future
            "connectionRate": 0,            # Future
            "messageRate": 0,               # Future
            "policyName": "",
            "groups": [
                {
                    "name": "",
                    "priority": 0              # Determines the order in which policies are applied.
                },
            ]
        }
    ],
    "groups": [
        {
            "groupname": "",
            "policyName": "",
            "users": [
                "one", "two"
            ]
        }
    ]
}
ralight commented 4 years ago

Added group priority.

chainhead commented 4 years ago

Users

  1. Who is the ab initio user who sets out rest of the configuration for user, group, policy? And, how is this ab initio user configured?
  2. If a client using client-side authentication passes a user name, which user name 'wins' - use_identity_as_username or the user name in the CONNECT message?
  3. Have you considered JWT as a 'header' that a client can send when publishing or sending SUBSCRIBE for the first time? This will probably be too verbose for IoT scenarios.
ckrey commented 4 years ago

I assume the repsonses to the commands will follow the Request/Response scheme as described in the MQTTv5 specification. The client will specify a Response Topic and may pass some Correlation Data. Will there be a default Response Topic?

ralight commented 4 years ago

@chainhead

  1. I envisage having a way of running Mosquitto that would generate an initial user and report the new random password, but haven't fully considered that yet.
  2. The current behaviour is that if use_identity_as_username is set, then any username specified in CONNECT is discarded. I would expect that behaviour to remain the same.
  3. No I hadn't. It's not something I have experience with, could you explain how you think it would be useful?
ralight commented 4 years ago

@ckrey There are few ways I've been thinking about dealing with responses. First off, I don't really want to restrict these features to MQTT v5 only so have to consider MQTT v3 as well. That implies that a default response topic is required.

The two ways I was thinking about this are:

  1. Have separate command and response topics, so $CONTROL/user-management/v1/cmd and $CONTROL/user-management/v1/response for example, where the response topic is a default that can be overridden. The default could also be $CONTROL/user-management/v1/response/<client-id>.
  2. Use $CONTROL/user-management/v1 for both commands and responses. This would mean being a bit cleverer with topic control. By this I mean we could process the commands but not send them on to any other subscribing clients, and the response could be sent to the requesting client only, regardless of other subscriptions.

In both cases I'm a little bit nervous about letting the client define their own response topic because of the possibility of sending sensitive data to public/topic. Perhaps the response topic would have to reside in $CONTROL/user-management/v1/response/#.

chainhead commented 4 years ago

With respect to JWT, firstly, it is a solution for authorization and not authentication. Secondly, it is a tiny piece of JSON that is sent by the client as a set of 'claims'. These claims are verified by the server. The issuance of the JWT itself is typically handled by a different system e.g. Microsoft AD, Keycloak, etc.

For a MQTT broker, couple of use cases could be as follows:

  1. Claims related to expiry e.g. a device can publish not before or not after such and such date (Unix epoch in JWT terms).
  2. Claims related to resource (topic string) and action (pub/sub).

One related question. Are you basing the security construct on user name and password? Why not client identifier? Perhaps, alongside username password? I mention this because, the client identifier is mandatory as per the spec (which you know already. :D )

chainhead commented 4 years ago

Just thinking...

Should the configuration of security policy be via MQTT messages at all? I mean, how do we "police the police" given that, the entity publishing the configuration commands should also come under access control.

Using a configuration file with 'hot' reload is not a bad option at all. (Although, I have no idea if it is possible). Better yet, have the configuration driven off of a access control solution e.g. Microsoft AD, Keycloak, etc. (Ok, the latter option is more enterprise suited.)

yanosz commented 4 years ago

That sounds great. I think its a good idea to rework mosquittos authentication. Projects such as https://github.com/iegomez/mosquitto-go-auth and https://github.com/jpmens/mosquitto-auth-plug seemingly filled some gap in the last years.

For me, authentication and authorization typically invovles integration. Typically, mqtt is used in a distributed setups, that also involve some protocols. I'd suggest:

Providing a clear separation between permissions, roles, users and groups. I think that this is vital, but I don't find it in your concept. Permissions define what somebody can do. Roles aggregate Permissions. Users (or accounts) are managed in some kind of repository, whereas Groups aggregate users:

For instance: hsimpson is user in group sector-7g-employees, whereas subscribe-radiation-alert is a permission, that could be aggregated in a role called safety-inspector.

Typically, users and groups are provided by 3rd party systems (or text files for KISS), whereas permissions and roles are defined in mosquitto. Mapping takes place.

One can think of a lot: You can map permissions to users (e.g. Homer Simpson is allowed to subscribe to radiation alerts), group to permission (e.g. all employees in section 7g get radiation alerts), user to role (Homer Simpson is a safety inspector) and group to roles (all employees in group sector 7g play the role of safety inspectors) … poor Springfield.

But… that’s quiet complex. IMHO, typically, it suffices to map groups to roles by names. E.g. that would require Homer Simpson to be member of a Group called safety-inspects as well. I guess that this kind of mapping suffices for mosquitto but some edge-case could require some explicit mappings. However, mapping permissions typically results in long and almost unreadable definitions.

When integrating with JWT or SAML (or both) you could also map roles to claims or attribute-values.

I think it's a good idea to rely on plugins for integrating 3rd party systems. Nevertheless, LDAP, SQL and JWT are really popular nowadays. Maybe, it could help to integrate some work from https://github.com/iegomez/mosquitto-go-auth. An plugin is then required to authenticate credentials of an arbitrary chosen format and to retrieve user as well as group information. This is then used by mosquitto (core) to apply permissions and roles.

Edit: I really do like to model of: https://github.com/stffn/declarative_authorization - it's solely authorization, nevertheles. But the ACL is elegant.

ralight commented 4 years ago

@chainhead Thank you for the JWT examples, those kind of capabilities are certainly appealing. Including the client id - yes absolutely if desired.

The current system uses a file with reloadable values and has been cited to me as a reason not to use Mosquitto.

ralight commented 4 years ago

@yanosz Thanks for your comments. You're right that the permissions part isn't as clear as it needs to be. With regards roles/users/groups, I would say that my "policy", which is a collection of permissions (and can include denials), is equivalent to a role. A group can have a policy attached to it, and likewise users can have policies attached to them. A user can be in multiple groups, which means multiple roles. I hadn't planned on having a user being able to have multiple policies attached to them directly, but this could be managed in the current draft by having the user in multiple groups, even if that group only has a single member.

With regards integrations, they are definitely important and must be considered in the design, but I think the first iteration will be limited to being a standalone implementation.

yanosz commented 4 years ago

@ralight thanks for your feedback.

Regarding:

I would say that my "policy", which is a collection of permissions (and can include denials), is equivalent to a role.

I won't go for denials. I prefer to stick to either a 100% grant or 100% denial approach. Having both denial and grants can be a little bit confusing. I've to admit that policies looked to me like permissions in the first place. i guess, they are something in between. Maybe, it could be better to seperate policies from grant / denial (i.e. topic stanzas) and keep references. That would allow to refer to a specific permission in different policies. When just referencing names in policies, the aggregation aspect would be clearer, IMHO. I'm also a little bit confused by "policyName":"" is the policy name empty or is it some kind of regexp-match?

With regards integrations, they are definitely important and must be considered in the design, but I think the first iteration will be limited to being a standalone implementation.

IMHO it'd be helpful to maintain API compatibility with https://github.com/iegomez/mosquitto-go-auth, as long as the 3rd party integration is not complete. It would be ok to deprecate parts on the API in the first iteration, but I guess that many peoply rely on this code.

chainhead commented 4 years ago

The current system uses a file with reloadable values and has been cited to me as a reason not to use Mosquitto.

I wonder why.

Anyway, these are the options I see.

Broker

Delegate all access control to enterprise solutions

Broker relies on a simple YES or NO response from access control systems, e.g. Microsoft AD, Keycloak, etc., for a given combination of principal (username/password, client identifier, etc.), resource (topic strings) and action (publish, subscribe with QoS levels). Since the principal, resource and action are easily available from a client connection, existing clients will not be impacted.

Pros

  1. Not much impact to mosquitto code base (I hope!).
  2. Integration with very well understood access control solutions.
  3. Clean separation of concerns.
  4. No impact to clients.

Cons

  1. How quickly would a change in the access control system 'cascade' to the broker?

Delegate only life-cycle methods to enterprise solutions

The mechanisms to on-board or off-board a principal and also maintain its access (resource and action) is left to the enterprise systems. Whereas, only the verification is done by mosquitto. For example, a JWT is issued by the enterprise system as part of provisioning a device. However, the verification of the token remains with the broker.

Pros

  1. (Including the ones from the previous section) A network hop is reduced by retaining the verification at the broker side. The trade-off being the (slight) overhead of running the verification step.

Cons

  1. Same as in the previous section
  2. In case of JWT, existing clients will have to be modified to send the token in every action.

Implement authn and authz

This is, of course, your proposal.

Pros

  1. Full control

Cons

  1. "Yet another ACL language" to learn.

Client

On the client side, sending a token maybe seen as an overhead by some clients.

abiliojr commented 4 years ago

I want to say that I love the idea of having a no-fuzz access control system available in mosquitto. Is simply a must in today's world, even for weekend projects.

Still, when I think of what other users of mosquitto might like, I am not sure that they will love it that much.

One of the things I like about mosquitto is it's simplicity. Maybe by choice, maybe by accident, but it's the only broker I know that adheres to the UNIX philosophy of:

"do one thing and do it well."

It is my humble opinion that adding the suggested changes, will make mosquitto leave a void in this category. And it will be painful for anyone keeping a "customized" version of the security implementation, as s/he will have to keep a customized version of the broker as a whole.

Again, don't take me wrong, I use mosquitto in my hobby projects and having reinvented the ACL wheel a few times already, I would definitely love to have it OOB.

I know that the original email said "without needing a separate plugin", but I would like to suggest a third option.

If you look at other software packages like NGINX, they offload client authorization to a 3rd party, by making a subrequest (using the same HTTP protocol). I imagine it would be possible to do the same, but over MQTT (maybe over a different, isolated listener using UNIX domain sockets or something?).

In case the overhead is an issue, maybe revisiting the plugin interface can be an alternative. Over the years I've used it and I think is adequate enough. It's only problem is being a second class citizen (or at least I feel it that way).

I believe the API you suggested can be implemented in isolation, and provided to the community as a "reference" that actually does work (with full official support). Everything will still adhere to the "do one thing and do it well", and advanced users will still be able to morph it to their needs.

I want to congratulate @ralight for starting this conversation, as I believe mosquitto can benefit a lot by having security available out of the box. Please count me in if you need help coding it.

gdt commented 4 years ago

I find this all a bit concerning. Using topics to configure access control for topics feels a bit circular, even though I think it could be done correctly.

The question about initial users who can set policy is excellent. Having mosquitto print a password does not seem reasonable. This all needs to work within the context of packaging systems and management schemes (e.g. ansible, but I broadly mean all such schemes including ad hoc ones). I would suggest that this is somewhat like postfresql's access model where a superuser is created from the command line and then sql is used, and it makes sense to look to that. So the user/password (or certificate) of the superuser needs to be put in a config file, or else there needs to be a program that can set that via e.g a priviliged socket.

Another question is how all of this is stored. Unlike normal topics, it seems obvious that it must be persistent. While json is easy for some, it also seems that a command-line program to send the json must be provided so that one can do normal operations like 'create this user'. I can see the allure of dynamic changing of users, but that feels like it is reinventing radius and an authorization system. I personally really like it that I have a config file with all my users and passwords in them. It's easy to understand and easy to change (emacs is my automation system!).

So I would lean to a file-based config - perhaps a separate authn/authz file - as the baseline, and allow plugins to be fancier. One would be a database of some kind, and that would then enable this publish-json mechanism. I would prefer to keep that off - if I don't have an articulated need for dynamic acls, then it's just one more thing to be sure about, and something along the lines of the Principle of Least Privilege says that such dynamic mechanisms must be off by default.

Finally, I would suggest stepping back and writing requirements first. I'm not really sure what problem this is solving for who. If I did understand that I'd probably say something different.

ralight commented 4 years ago

Thanks all for your comments, I haven't replied before because I've been on holiday and then dealing with other things.

ralight commented 4 years ago

@chainhead

Delegate all access control to enterprise solutions

Broker relies on a simple YES or NO response from access control systems, e.g. Microsoft AD, Keycloak, etc., for a given combination of principal (username/password, client identifier, etc.), resource (topic strings) and action (publish, subscribe with QoS levels). Since the principal, resource and action are easily available from a client connection, existing clients will not be impacted.

This is already possible with the current system, through a plugin. As you say, this has a low impact on Mosquitto itself and just needs implementing where desired. I would view that as a parallel solution to what I'm proposing here.

Delegate only life-cycle methods to enterprise solutions

The mechanisms to on-board or off-board a principal and also maintain its access (resource and action) is left to the enterprise systems. Whereas, only the verification is done by mosquitto. For example, a JWT is issued by the enterprise system as part of provisioning a device. However, the verification of the token remains with the broker.

This approach has definite appeal to me. The most obvious way to add a JWT token is with a MQTT v5 user-property, which would limit where it could be used (no MQTT v3 clients), but for some situations this would be fine. There is no reason this couldn't be produced as another parallel solution, and I suspect that I will be looking at it in the future.

ralight commented 4 years ago

@abiliojr @gdt

Both of your replies highlight some aspects of where I haven't included enough of the things that are "obvious" to me in my head, and should have spelled out clearly.

This is absolutely not a proposal to remove the existing security setup and replace it with a new setup. This is a new security mechanism which would sit alongside the existing file based security, and other plugins. As such, it would be entirely optional but hopefully become a popular way of administering users.

@abiliojr - if I understand what you are saying correctly, your concerns are primarily that this would be a default replacement for what exists, and would prevent other security systems being developed. Is that correct? If so, I think I have addressed those points. When I said "without the need for a separate plugin", I meant without the need for a plugin separate to the project - a third party plugin.

@gdt - it took me a long time to come around to using topics for configuration purposes. When a requirement is that it should work remotely, I think it is the best choice however, rather than implementing something entirely new for the same purpose.

In terms of first setup, yes, having the broker print out admin details is not a good idea. Having a tool to add the first user would be a good plan, alternatively you could use mosquitto_passwd to create a user and then use an acl file to grant access, but that is not as nice.

The setup provided by this must of course be persistent, and would be a json file. That could obviously be manipulated by hand, but essentially it would be owned by the broker process and so it wouldn't be recommended. The json messages shown here to control everything are just the interface. I'm not expecting anybody to type them in manually, there definitely needs to be an easy way of producing them. I intend to provide a command line tool, but haven't started thinking about it too much yet.

Addressing requirements - a remotely configurable security mechanism that is self contained, allows on the fly updates to users, groups, and the rights that each of those have. Suitable for deployment in a similar amount of disk/memory overhead as a current Mosquitto installation

richardash1981 commented 4 years ago

Addressing requirements - a remotely configurable security mechanism that is self contained, allows on the fly updates to users, groups, and the rights that each of those have. Suitable for deployment in a similar amount of disk/memory overhead as a current Mosquitto installation

Just to prove that this isn't an abstract idea, I am working on a commercial project which needs pretty much what is being discussed here. Without divulging anything which you can't find publicly about the LV-CAP project:

From our point of view, if we could re-configure the broker ACL by sending Mosquitto a JSON payload each time the Docker orchestrator runs up or stops a Docker container, this would be ideal. From this perspective:

For completeness, the issues using the existing file-and-reload scheme to change settings are:

In the details:

ralight commented 4 years ago

@richardash1981 Thanks for your comments, it's good to hear there are other people that would benefit from this. The systems you have sound quite capable compared to others I've been involved in :)

  • we would want to use configuration files (current ones would be fine) to set up a "base" set of users and their access - this would allow the system to bootstrap itself, and the orchestrator process to connect to the broker.

In this situation you could also have a pre-created copy of the json file the dynamic security would use for storing its config, and just copy that to the appropriate place on provisioning.

  • Not sure I understand the difference between "publish-read" and "subscribe-" rules - for us the ACL file syntax of just "read" and "write" is working fine.

The current ACL files have read and write rules, which act exclusively on PUBLISH messages. That means if you subscribe to #, then you will only receive the messages which you are allowed. The proposed subscribe rules (which are available to plugins now) allow you to deny SUBSCRIBE messages, so you could stop anybody subscribing to the literal #, for example, or to deny subscribing to a pattern of topics like secret/#. The subscribe rules aren't strictly necessary alongside the read and write rules, but if you can deny subscriptions then it means that messages that would have matched don't have to be processed for that client at all, which is an efficiency saving.

abiliojr commented 4 years ago

I'm sorry for the late reply, life kept me away from this topic. Hope is not too late.

@ralight, I think I have a clearer view of your idea. Am I getting it wrong, or part of this can be implemented using the plugin interface? (maybe with some extensions). Then, as a plus, this plugin could serve as a reference design for other people.

If that's the case, can we improve the API in the process? (or leave space for future improvement). Here are some ideas, mostly based on (mis)beliefs and memories:

More important, an improved documentation of the API, telling what can be done with it and it's limitations. Most of the beliefs listed here come from the lack of an official source of truth other than swimming into the code.

@ralight, how do I help?

ralight commented 3 years ago

This was released as part of 2.0.

sionhughes commented 2 years ago

Reading the thread here and the docs on Dynamic Security plugin, it appears that only username + password auth is possible for clients and not client certificates like is possible with an ACL? Or am I missing something in the docs?