12 min readUpdated Oct 17, 2023

Get fine-grained access control of your server with AblyD

Get fine-grained access control of your server with AblyD
Thomas CampThomas Camp

It’s fairly common to want to have a server running a process, be it a website, a calculation, or anything else you can imagine.

Often outside of the device’s core functionality though, there are many other things you may be interested in. Information on if the process is still running, what server(s) are running, what errors are occurring in the process, and general information being available externally are common examples of this.

In addition to this, having control of processes can be incredibly powerful. Be it being able to start and stop processes easily without a full server re-deploy, or control actively running processes with ease.

Baking these sorts of controls into a program can be complex though. A typical way would be to define HTTP endpoints that allow for certain actions to be performed. For example, you would have a /health endpoint which can be polled to see if the server and processes are up and running. Alternatively, you can set up a Webhook endpoint somewhere which you can then configure your process to send updates.

However, this all requires setup and additional work. If you set up a server to handle requests, you now need to handle authentication of requests, data formats, feeding requests into your core functionality, and more. If you’re wanting to use webhooks, you need to do much the same, except for removing the authentication validation for having baked-in endpoints within your core process itself.

Enter websocketd

Thankfully there’s an amazing tool for exactly this problem that already exists; websocketd. Websocketd, created by Joe Walnes, is a Go daemon service. In effect it allows you to wrap around a process and makes the stdin and stdout available with WebSockets by hosting a WebSocket server available externally. Each new connection to the server will spin up a new process.

For example, if you had a file which you wanted to be able to be run, my-executable.sh, you could run the following command in the terminal:

websocketd --port=8080 ./my-executable.sh

This would start up a WebSocket Server on port 8080, which a client could connect to. Once a client does, an instance of the executable would start up, with the stdin and stdout re-directed to the WebSocket connection.

By using websocketd, you can now start up a process whenever you want, and have any statistics/errors/information returned over WebSockets. You could then pass this on to other systems as needed or react accordingly.

However, there’s many, many additional features that we could look to add to elevate this functionality further. A couple ideas would be:

  • In-built health information on each process and server
  • Many-to-many communication for each process to allow for relevant clients to consume data from a process
  • Authentication mechanisms to ensure only legitimate clients are able to access and communicate with the server
  • Simply mechanisms for getting information sent to clients without WebSockets support
  • No way to check prior communication or re-establish lost connections

Extending websocketd with Ably

Although all of the above would be hard to implement by simply extending the existing WebSockets implementation, we thankfully have access to something which will handle it all for us; Ably. Ably is a platform that enables communication between any number of systems via a Pub/Sub system, with multi-protocol support, history, the ability to see who’s connected, and more.

By substituting in Ably in place of the WebSocket Server we’re able to get all of these features available to us, in addition to removing the load of hosting the WebSocket server. Instead all the server will need to do is maintain a single connection to Ably, and Ably will handle the rest of the complexity and load.

Through a few changes to the websocketd library, we can amplify its usefulness massively. All of the code will be in Go, and we will be using the Ably Go Client Library to interact with Ably.

Why Go?

Go is an efficient, reliable programming language which is at its core based on the premise of scalability and concurrency. Go effectively implements Pub/Sub communication with its own channels to communicate between goroutines, with multiple goroutines able to both publish and subscribe to these channels. This core design to Go makes it one of the most well-fitted languages for extending this Pub/Sub functionality to networked Pub/Sub communication.

WebSocket connection to using Ably Channels

The first change was to remove all of the WebSocket Server code; we won’t be needing it. Instead we can just instantiate a connection to Ably. You will need an Ably Account with an Ably App to connect to, using an Ably API Key to authenticate. With that, you just need the following code:

client, err := ably.NewRealtime(
    ably.WithKey(‘YOUR_ABLY_API_KEY’),
    ably.WithClientID("ablyDInstanceA")
)

With that we have a connection, and are ready to subscribe and publish to channels to communicate with external clients. You may also notice we’ve specified a ClientID as part of connecting. This will be used by other clients to identify this connection as coming from an AblyD instance. Ideally we can use this to identify multiple instances of AblyD on different servers if we want to extend our implementation to that.

Shifting to channels

Rather than having a connection being established dictating a process starting, we can instead make use of a channel for starting up processes. If we create a channel in Ably called command, we can allow any client to send a message to it to start off a process.

One major benefit of this is it makes it easy for us to add in additional parameters when starting the process. For example, we can send a payload along with the ‘start’ message with the following:

{
    “Args”: “--debug=verbose --other-arg=20}

This can be passed as part of our setup of a process, and allows for unique configurations of our process to be run depending on what’s required. For our AblyD instance to receive these commands, all we need to do is subscribe to the command channel:

ablyCommandChannel.Subscribe(context.Background(), "start", func(msg *ably.Message) {
    // Start up
}

Likewise we can have a channel created for each process for the input and the output of a process. With that a client now has all of the functionality we originally had from the WebSocket server. Now let’s take it further!

The benefit of Pub/Sub


Due to Ably being a Pub/Sub system, we can have any number of devices consuming from a process, meaning if you have multiple systems which will need them they can all get it from a single Ably Channel with nothing extra needed to be done from the server's side. If you need the data to go to multiple analytics tools and databases, you can just consume it from a single channel. You can also filter by message name, meaning that each tool can only consider relevant messages to its functionality.

History

Ably channels allow clients to retrieve past messages through History. This means that if a consumer of data somehow has a lapse in connectivity, or needs to retrieve data that's been sent through previously as it starts up, then that is all supported innately with no additional work required.

Protocols + Serverless

In general WebSockets are a great means of communicating between systems, however it’s not always the right answer. Perhaps based on outputs from a process we’ll want to activate a Serverless Function, or store logs into a database. Equally, we may need to share data with services which only support other protocols.

Due to Ably being protocol agnostic, it doesn’t matter that we’re publishing into it over WebSockets. A client can subscribe to an Ably channel over WebSockets, MQTT, SSE, or even make REST requests, and Ably handles the conversion and intricacies of each protocol to allow for seamless communication.

Additionally with Ably’s Integrations functionality, it’s possible to send messages from channels to Serverless Functions, Webhook endpoints and more. With all of this functionality being available inherently within Ably Channels, there’s nothing extra required to be done when developing code, just subscribe to a channel using the protocol of your choice or set up an Integration from the Ably site and you’re ready to go.

Authentication


Ably also comes with a fully-fledged authentication system, allowing for fine-grained control on what channels clients can use, and what actions they can perform on them.

This means you can ensure that services which should only be able to read data in from certain actions only have access to those channels with subscribe-only permissions. Likewise, services which should only be able to make certain requests or changes will only have access to publish to the appropriate command channels.

With full JWT support it's incredibly easy to also keep credentials secure and short-lived, keeping these communication channels as restricted and secure as possible.

Presence

One major tool available with Ably which we can use in AblyD is the ability to see who is active in a channel. When an AblyD session is started on a server it will join the Presence of the output channel, meaning that anyone who can access the channel can check if the server is available.

This Presence State also has a data element attached, which can be used to indicate the current state of the server. For example, it can specify Active, Available, Busy, Closing, etc. Changes to the presence set of a channel can be subscribed to using realtime protocols, meaning clients can have instantaneous feedback on the status of processes. An example of such an event would be:

{
    "action": "present",
    "id": "e-nKgn7VHj:1:0",
    "timestamp": 1627403743288,
    "clientId": "ablyDProcessA",
    "connectionId": "e-nKgn7VHj",
    "data": {
        "MaxInstances": 20,
        "Instances": {
            "79597": "Running"
        }
    }
}

This can allow for other devices to have further insight into the server's and processes’s health and status, all again coming as part of AblyD.

Examples

Basic subscription over WebSockets

With all that said, let’s try using it. Firstly, you’ll need to clone it from GitHub. Within the repository, there’s a sample executable you can run inside of /examples/bash/count.sh. This file is a very simple bash script that will print out numbers 1 to 10:

for ((COUNT = 1; COUNT <= 10; COUNT++))
do
echo $COUNT
sleep 0.5
done

Within the AblyD folder we can run the following to start it up. Make sure to replace YOUR_API_KEY with your Ably API key from the Ably App you’ll be using.

$ ./ablyD --apikey=YOUR_API_KEY ./examples/bash/count.sh

This should start up AblyD with the process to run being our count.sh file. It’s waiting for a start message to be published to the command channel.

Also in the same folder there’s a file called count.html which contains a basic page for interacting with our server through Ably. There is a button you can press to request a process start up, and responses from the stdout of the server will appear below the button.

It’s as simple as that! We can have multiple instances all being run at once, we could have multiple clients subscribed to the same output from the server, and also use more complicated scripts processes that require input.

AblyD Client Library

To keep interacting with and managing AblyD instances as simple as possible, we also made a simple library which wraps around our Ably JS library which provides simple abstractions of the logic to keep things easy. We’ve made it available on npm, so to get it you just need to use:

npm install ablyd-client

To start and listen to changes for an instance, you would just need:

const AblydClient = require('ablyd-client');

let client = new AblydClient(authDetails);
client.startNewProcess((err, newProcess) => {
    newProcess.subscribe((msg) => {
        console.log(msg.data);
    });
});

Assuming you have an AblyD instance running, you should see messages appearing in your console!

Sending messages to webhook endpoints

With our simple use-cases made, let’s spice it up. With Ably we’re able to set up Integration Rules, which allow for any messages sent on a channel to be automatically sent on to an external service. This could be a Serverless Event, a webhook, or any number of other options.

We can set these rules up programmatically using the Ably Control API. With the ability to create dynamic rules, we can create a program which will generate an Integration Rule, and then start a process in our AblyD instance. Any messages published by the AblyD instance to its output channel will be automatically sent to the webhook we define within our rule.

You can find this as a demo with further details in the examples/webhook folder of the AblyD GitHub repository.

Summary

If you have any need to have external control or access to processes running on a device, then AblyD will likely be a useful tool for you. With a whole array of functionalities such as pub/sub, history and presence, AblyD can be incredibly powerful for extending your functionalities and having secure, powerful control and insight to your processes.

AblyD is available with Homebrew with the following command:

$ brew install ably-labs/homebrew-tap/ablyd    

If you have any questions or requests for features and tools, please raise an issue on the Github repository or get in touch with us. I’ll be looking to use this core functionality to create an orchestration tool which can detect server load or processes dying and handle replacements of each.

If you have any fun ideas or projects you’d like to use this in, please show them off to us at @ablyrealtime!

More Go resources

Join the Ably newsletter today

1000s of industry pioneers trust Ably for monthly insights on the realtime data economy.
Enter your email