Diffusion and Diffusion Cloud Security: System Authentication

This article is about the system authentication handler in Diffusion and Diffusion Cloud. It is part of a series of articles covering Diffusion and Diffusion Cloud security features. As usual, most of what you will read here applies equally to Diffusion and Diffusion Cloud.

In the previous article, we saw how to control who can create a client session with a server by implementing an authentication handler. The system authentication handler is a configuration-based alternative to writing code.

System authentication configuration

System authentication is implemented as a standard authentication handler. It is always enabled in Diffusion Cloud, and enabled by default in a Diffusion server’s etc/Server.xml file:

<security>
  <authorisation-handler-class></authorisation-handler-class>

  <authentication-handlers>
    <control-authentication-handler handler-name="before-system-handler"/>

    <!-- This line enables system authentication. -->
    <system-authentication-handler/>

    <control-authentication-handler handler-name="after-system-handler"/>
  </authentication-handlers>
</security>

System authentication is configured with a set of principals. Each principal has an associated name, password, and a set of roles that determine the principal’s permissions. The authentication handler can be configured to allow, abstain from authentication, or deny anonymous connections. The configuration is persisted in the file
etc/SystemAuthentication.store file, locally to each server. It can
be updated in several ways:

  • With the server shut down, the file can be edited by hand (available in Diffusion only).
  • Unlike most Diffusion configuration, the configuration can be updated while the server is running through the SystemAuthenticationControl configuration API, which I cover in more detail below. Changes are written back to the SystemAuthentication.store file.
  • Diffusion Cloud administrators can change the configuration using the Diffusion Cloud dashboard. Under the covers, the dashboard uses the SystemAuthenticationControl API to update the configuration.

The file itself consists of a number of simple declarative statements. Here’s part of the default configuration.

;; Edit this file before using it in a production system. This file adds several
;; user accounts with security privileges and the well-known password 'password'.
;; It is installed by default for developer convenience.

allow anonymous connections [ "CLIENT" ]

add principal "client" "password" [ "CLIENT" ]
add principal "control" "password" [ "CLIENT_CONTROL" "TOPIC_CONTROL" "AUTHENTICATION_HANDLER" ]
add principal "admin" "password" [ "ADMINISTRATOR" ]
add principal "operator" "password" [ "OPERATOR" ]

The full syntax is described in the product documentation, but should be fairly self-explanatory. Each add principal line defines a principal, providing the principal’s name, password, and a list of roles to grant to the principal. The allow anonymous connections line permits sessions to be established without providing a principal name and credentials, and grants such sessions the CLIENT role.

Following security best-practice, Diffusion stores cryptographic hashes of passwords, not the passwords themselves. Each hash is further salted with random information to defend against dictionary and rainbow-table attacks. The SystemAuthentication.store file may contain plain text passwords if they have just been added by hand. They will be encrypted when the server is booted. So if you start the server against the above file, it will be transformed into something like the following.

allow anonymous connections [ "CLIENT" ]
add principal "client" encrypted "V1:ADF09FA0B36D13A6A5207B976527181A529DB2BA:1FB66A8D80264D21D291725E8D1E39F856BFC57482BAFDF7671B65E84E98CE97" [ "CLIENT" ]
add principal "control" encrypted "V1:78374FCC677F52A9F5FEF01DA3F908464B324098:939F390CDEAF21B54098BF3AEA3E2C3B2D3FEFDF53B4C4C08632EEB68EB21608" [ "AUTHENTICATION_HANDLER" "TOPIC_CONTROL" "CLIENT_CONTROL" ]
add principal "admin" encrypted "V1:7F052878777F629D753B827A8B88F2AC15D65A95:8FB738D2FD3B4E0C2DB6E9EA60F62B9372830447974DCB4B87EBE0D5FAFC56EC" [ "ADMINISTRATOR" ]
add principal "operator" encrypted "V1:BBC064C7354846D3D2BFFD688EFB54A62D7F0C07:8E700F2A440BF31AB72299AE886A1D1370A52D7AAA8076DFA52EB7B6F04D7570" [ "OPERATOR" ]

Limitations

Why wouldn’t you use the system authentication handler? A few reasons spring to mind. You might want to persist account information to a separate identity store, such as a LDAP database. This is particularly true if you have a large user population, and require a more scalable administration interface. Password-based credentials might be unsuitable, or you might want to implement single sign-on with some other system performing the actual authentication. Or you might want to set session properties based on the authentication result. All of these can be addressed by writing your own authentication handler. As I mentioned in the previous article, you can deploy a custom authentication handler and still use system authentication to manage some of the accounts, for example administration accounts used to access the console and those used to register control authentication handlers.

Using the SystemAuthenticationControl API

The system authentication store can be updated by a suitably privileged client session, using the SystemAuthenticationControl API. To query the store a session needs the VIEW_SECURITY global permission and to update the store it needs the MODIFY_SECURITY global permission. With the default Diffusion and Diffusion Cloud configuration, these permissions are granted to the ADMINISTRATOR role.

Variants of the SystemAuthenticationControl API are available in the Java, JavaScript, C#, Android, and C SDKs. I’ll use the Java API.

Here’s part of the interface:

public interface SystemAuthenticationControl extends SecurityStoreFeature {

    /* QUERY */

    void getSystemAuthentication(ConfigurationCallback callback)
        throws IllegalArgumentException, SessionClosedException;

    interface ConfigurationCallback extends Callback {
        void onReply(SystemAuthenticationConfiguration configuration);
    }

    interface SystemAuthenticationConfiguration {
        Collection<SystemPrincipal> getPrincipals();
        AnonymousConnectionAction getAnonymousAction();
        Set<String> getRolesForAnonymousSessions();
    }

    interface SystemPrincipal {
        String getName();
        Set<String> getAssignedRoles();
    }

    enum AnonymousConnectionAction { ALLOW, DENY, ABSTAIN; }

    /* UPDATE */

    // Inherited from SecurityStoreFeature
    void updateStore(
        String commandScript,
        UpdateStoreCallback callback)
        throws IllegalArgumentException, SessionClosedException;

    ScriptBuilder scriptBuilder();
}

The query interface retries the entire model, returning it as a SystemAuthenticationConfiguration object. This provides all of the current information in the store, except for the passwords.

The update API is different in style. It accepts a command script, using the same language as the configuration file. The script is interpreted as a set of changes to the existing configuration file. As well as the add principal and [allow|deny|abstain] anonymous connection commands, the language includes operations to remove principals, and verify passwords. Here’s various examples:

; Disable anonymous connections.
deny anonymous connections

; Add a new principal.
add principal "riley" "LaV&aga" [ "CLIENT" ]

; Change the roles associated with the principal 'operator'.
assign roles "operator" [ "OPERATOR" "CLIENT" ]

; Check that the password of the "client" principal is "password".
verify password "client" "password"

When the updateStore method is called, the server attempts to execute each command in order against a copy of the store. If any command fails, none of the changes will be applied. If all commands succeed, the changes will be applied.

Here’s an example script that safely changes a password. Assume that the client has prompted the user for the existing and new passwords.

verify password "riley" "LaV&aga"
set password "riley" "3lana"

Finally, the scriptBuilder method returns a builder which provides a fluent Java API for creating scripts.

A utility to change passwords

We now know enough to write a simple command line utility for changing the password of a system authentication account. I’ve written such a utility, and its available from the GitHub repository accompanying this blog.

Now, let’s look at the heart of the utility – the changePassword method that performs the password change. The method is passed the control session, the name of the principal to change, the principal’s expected password, and the new password.

private static void changePassword(
    Session session,
    String targetPrincipal,
    String expectedPassword,
    String newPassword) {

It uses the SystemAuthenticationControl script builder to create a script.

final SystemAuthenticationControl control =
    session.feature(SystemAuthenticationControl.class);

final String script = control.scriptBuilder()
    .verifyPassword(targetPrincipal, expectedPassword)
    .setPassword(targetPrincipal, newPassword)
    .script();

System.out.printf("About to apply the script: %n%s%n", script);

Then calls updateStore to make the change.

    final ChangePasswordCallback result = new ChangePasswordCallback();

    control.updateStore(script, result);

    result.waitForResponse();
}

Let’s try it out. You can run the utility against a local Diffusion installation with the default configuration.

The utility will also run against a Diffusion Cloud server, but you’ll have to change the URL, admin password, and create a target principal to change. (You can of course change the password of the admin account – just don’t forget it!). If you do use Diffusion Cloud, we recommend you use TLS (SSL) to connect to protect the passwords. Your URL should be of the form wss://myservice.reappt.io. We don’t use TLS in the examples below to avoid complicating the example to trust the certificate of the local Diffusion server.

First let’s find out how to pass the command line arguments. The utility uses args4j, and prints a usage message if it can’t pass the command line.

OK, so here goes:

% java -jar target/changepassword.jar -u ws://localhost:8080 -a admin -ap password -t client -e password -n s3cr3t
[client multiplexer] INFO com.pushtechnology.diffusion.multiplexer.impl.AbstractMultiplexer - Multiplexer 'client multiplexer' started.
About to apply the script:
verify password 'client' 'password'
set password 'client' 's3cr3t'
Password change succeeded

It worked! What if we try again?

% java -jar target/changepassword.jar -u ws://localhost:8080 -a admin -ap password -t client -e password -n s3cr3t
[client multiplexer] INFO com.pushtechnology.diffusion.multiplexer.impl.AbstractMultiplexer - Multiplexer 'client multiplexer' started.
About to apply the script:
verify password 'client' 'password'
set password 'client' 's3cr3t'
Password change failed: [Password verification for client failed at [1:0]]

The change failed because the password of the client principal is no longer “password”. The [1:0] in the error message tells us that the failure occurred at line 1, column 0 of the script, which corresponds to the verify command.

That’s all for now

I hope this article helps you understand and use system authentication in Diffusion and Diffusion Cloud. Further information and examples can be found in the product documentation.

Bonus for Emacs users

To help with formatting examples for the blog, I wrote an Emacs mode to provide syntax highlighting for SystemAutentication.store files. It can be downloaded from GitHub.


Further reading

BLOG

Exploring Generative AI: Opportunity or Potential Headache?

March 25, 2024

Read More about Exploring Generative AI: Opportunity or Potential Headache?/span>

The Diffusion Data logo

BLOG

Creating a WebSocket Server for PubSub

June 28, 2024

Read More about Creating a WebSocket Server for PubSub/span>

The Diffusion Data logo

BLOG

React PubSub using Diffusion Websocket Server

July 08, 2024

Read More about React PubSub using Diffusion Websocket Server/span>