Use htpasswd to secure your node-RED Container
Tags: docker, security, javascript, devops
Published: 2022-06-09 21:10:01

## Securing Node-RED

node-RED containers can be made secure when using Admin Authorization features
using its [`settings.js` file][1] and mounting it to the container. Sounds quite simple,
however unlike other containers that let your configure their environments via environment
variables whereby the stored credentials are in plaintext, node-RED passwords typically are
stored in encrypted format i.e., [bcrypt][2].

## Automation: Encrypted Passwords

There is a possibility of obtaining the hashed (encrypted) password using an ephemeral container
using:

```bash
docker run -it --rm --entrypoint /bin/bash nodered/node-red:2.2.2-12-minima
```
One in the container use:

```bash
bash-5.0$ node-red-admin hash-pw
```
which will prompt you to enter the password and the resultant hash password will be available for you
to use.

This is however, a bit more of manual work and in case where you wish to perform more easy, automated
setups we might need to find a different way.

### Automation Script via `htpasswd`

Apache provide a great CLI tool called `htpasswd` that can encrypt our plaintext passwords
into the required `bcrypt` hashed format which the node-RED UI and Admin Auth backend will decrypt.

For most of the distributions the package name is `apache2-utils` so for Ubuntu/Debian you can install
`htpasswd` using:

```bash
sudo apt-get install apache2-utils
```

### Jumping right into it!

You can go through the `man` pages of `htpasswd` to find how it might suit for your needs. I am cutting 
to the chase as to how I used it.

In order to generate my hashed password that will be compatible with node-RED's decryption backend, I had
to find the total Computation Cost (rounds) needed, which I found out the value was `8` based on some 
code diving using [node-red-admin][3] repository.

```bash
htpasswd -nb -B -C 8 admin testpassword
```
will produce the following result:

```bash
admin:$2y$08$JTOJDi/whZxz8qwsdwCkfedhVOhh00QiKHICdHLImGS8XM2v.fng2
```

### Creating an Environment Variable Template file for node-RED Docker Container

we will make the automation a bit more easier for us, whereby we can use `envsubst` commandline to fill
and environment variable called `NODERED_AUTH_ADMIN` in a file called `node-red.env`. But in order leverage
the power of `envsubst` we will create a template file called `node-red.tpl.env` which has the following 
content:

```bash
cat node-red.tpl.env
NODERED_AUTH_ADMIN='${NODERED_AUTH_ADMIN}'
```

We create simple bash script which will do the following:

1. Accept a plaintext password as an argument to the script
2. use this plaintext password and hash it with `htpasswd`
3. export the variable which has the result of `htpasswd`
4. use the exported variable to replace the `node-red.tpl.env` file to `node-red.env` with the value
5. unset variable


The script is as follows:

```bash
cat nodeREDPwgen.sh
#!/bin/env bash

function nodeREDPW {
    # 1st argument to this script is plaintext password
    local NODERED_AUTH_ADMIN=$(htpasswd -nb -B -C 8 admin "$1")
    export NODERED_AUTH_ADMIN
    touch node-red.env
    envsubst '${NODERED_AUTH_ADMIN}' < node-red.tpl.env > node-red.env
    unset NODERED_AUTH_ADMIN 
}

nodeREDPW $1
```

Example Usage:

```bash
chmod +x nodeREDPwgen.sh

./nodeREDPWgen.sh testpasswd
```

will produce a `node-red.env` file after successful execution with the following content:

```
NODERED_AUTH_ADMIN='admin:$2y$08$/8NVmF2gJ3JDrmv0tX2ud.P7S9gqvB0ds2cRByhnZcRdZOWhTnPN.'
```

### Creating a `settings.js` file for Node-RED Container

copy the [`settings.js`][1] file from the node-red repository and adapt the `adminAuth` object
as follows:

```javascript

  adminAuth: {
    type: "credentials",
    users: [{
      username: process.env.NODERED_AUTH_ADMIN.slice(0, process.env.NODERED_AUTH_ADMIN.indexOf(':')),
      password: process.env.NODERED_AUTH_ADMIN.slice(process.env.NODERED_AUTH_ADMIN.indexOf(':')+1)
    }]
  },
}
```

### Creating a `docker-compose.yml` file

```yaml
services:
  node-red:
    image: nodered/node-red:2.2.2-12-minimal
    container_name: secure_nodered
    env_file:
      - ./node-red.env
    ports:
      - "1880:1880"
    volumes:
      - ./settings.js:/data/settings.js
```

bring the container up:

```bash
docker compose up -d
```
The UI is available at `http://localhost:1880`

## Problem

If you try to login in by typing `testpassword` in the Admin UI, you will get Login Failed! But Why?

### Investigations

It turns out that the password generated by `htpasswd` produces a variant of bcrypt `$2y` where as,
if we were to generate the password using `node-red-admin hash-pw` and added `testpassword` it would
generate a completely different looking password with the variant `$2b`.

Example: `$2b$08$sfPlvPNDVVz/duAbAC2bbuNEVuvrwmU01x7plOAQIem6H187Xxq9G`


### Solution

It turns out that the `$2y` variant of bcrypt has compatibiltity and we could simply try and replace
`$2y` to `$2b` in our `settings.js` as follows:

```javascript
module.exports = {
  // .. removed for brewity
  adminAuth: {
    type: "credentials",
    users: [{
      username: process.env.NODERED_AUTH_ADMIN.slice(0, process.env.NODERED_AUTH_ADMIN.indexOf(':')),
      password: process.env.NODERED_AUTH_ADMIN.slice(process.env.NODERED_AUTH_ADMIN.indexOf(':')+1).replace("$2y$", "$2b$"),
      permissions: "*"
    }]
  },
  // removed for brewity
}
```

notice the `.replace("$2y", "$2b")` at the end of `password` key.

Let's spin the container up again using `docker compose up -d` and try to login with `testpassword` et Voila!!

We are able to login into Node-RED!

I found that __Nick O'Leary__ (founder of Node-RED and the great guy that he is..) already did [some research on this][4],
I just had to bring this all together.

If you have some suggestions, critiques than please get in touch with me and I would be happy to help.

## Resources

[GitHub Gist of the files][5] for the impatient ones!


[1]: https://github.com/node-red/node-red/blob/master/packages/node_modules/node-red/settings.js
[2]: https://en.wikipedia.org/wiki/Bcrypt
[3]: https://github.com/node-red/node-red-admin/blob/master/lib/commands/hash.js#L28
[4]: https://github.com/node-red/node-red-docker/issues/109
[5]: https://gist.github.com/shantanoo-desai/0d5fbc51518f6cdd1e04cbeab8d63c13