Skip to content

Commit

Permalink
Refactor hashing: add support for bcrypt and argond2id hashers.
Browse files Browse the repository at this point in the history
Fix cache security issue.
  • Loading branch information
iegomez committed Jul 14, 2020
1 parent f5a5cec commit aa487a9
Show file tree
Hide file tree
Showing 30 changed files with 925 additions and 373 deletions.
8 changes: 8 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# This is a comment.
# Each line is a file pattern followed by one or more owners.

# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence,
# @global-owner1 and @global-owner2 will be requested for
# review when someone opens a pull request.
* @iegomez
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ dev-requirements:
go get -u github.com/smartystreets/goconvey

test:
go test ./backends ./cache -v -bench=none -count=1
go test ./backends ./cache ./hashing -v -bench=none -count=1

benchmark:
go test ./backends -v -bench=. -run=^a
Expand Down
151 changes: 136 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ Mosquitto Go Auth is an authentication and authorization plugin for the Mosquitt

### Intro

This is an authentication and authorization plugin for [mosquitto](https://mosquitto.org/), a well known open source MQTT broker. It's written (almost) entirely in Go: it uses cgo to expose mosquitto's auth plugin needed functions, but internally just calls Go to get everything done.
This is an authentication and authorization plugin for [mosquitto](https://mosquitto.org/), a well known open source MQTT broker. It's written (almost) entirely in Go: it uses `cgo` to expose mosquitto's auth plugin needed functions, but internally just calls Go to get everything done.

It is greatly inspired in [jpmens'](https://github.com/jpmens) [mosquitto-auth-plug](https://github.com/jpmens/mosquitto-auth-plug).

It was intended for use with [brocaar's](https://github.com/brocaar) [Loraserver project](https://www.loraserver.io/), and thus Files, Postgres and JWT backends were the first to be developed, but more have been added. These are the backends that this plugin implements right now:
These are the backends that this plugin implements right now:

* Files
* PostgreSQL
Expand All @@ -35,6 +35,7 @@ Please open an issue with the `feature` or `enhancement` tag to request new back
- [Configuration](#configuration)
- [General options](#general-options)
- [Cache](#cache)
- [Hashing](#hashing)
- [Log level](#log-level)
- [Prefixes](#prefixes)
- [Backend options](#backend-options)
Expand Down Expand Up @@ -76,23 +77,23 @@ Please open an issue with the `feature` or `enhancement` tag to request new back
### Requirements

This package uses `Go modules` to manage dependencies, `dep` is no longer supported.
As it interacts with mosquitto, it makes use of Cgo. Also, it (optionally) uses Redis for cache purposes.
As it interacts with `mosquitto`, it makes use of `cgo`. Also, it (optionally) uses Redis for cache purposes.


### Build

Before building, you need to build mosquitto. For completeness, we'll build it with websockets, ssl and srv support.
Before building, you need to build `mosquitto`. For completeness, we'll build it with `websockets`, `tls` and `srv` support.

First, install dependencies (tested on Debian 9 and later, Linux Mint 18 and 19):
First, install dependencies (tested on Debian 9 and later, Linux Mint 18, 19 and 20):

`sudo apt-get install libwebsockets8 libwebsockets-dev libc-ares2 libc-ares-dev openssl uuid uuid-dev`
`sudo apt-get install libwebsockets8 libwebsockets-dev libc-ares2 libc-ares-dev openssl uuid uuid-dev`

Download mosquitto and extract it (**change versions accordingly**):

```
wget http:https://mosquitto.org/files/source/mosquitto-1.6.8.tar.gz
tar xzvf mosquitto-1.6.8.tar.gz
cd mosquitto-1.6.8
tar xzvf mosquitto-1.6.10.tar.gz
cd mosquitto-1.6.10
```

Modify config.mk, setting websockets support. Then build mosquitto, add a mosquitto user and set ownership for /var/log/mosquitto and /var/lib/mosquitto/ (default log and persistence locations).
Expand Down Expand Up @@ -278,6 +279,61 @@ auth_opt_cache_addresses host1:port1,host2:port2,host3:port3

Notice that if `cache_mode` is not provided or isn't equal to `cluster`, cache will default to use a single instance with the common options. If instead the mode is set to `cluster` but no addresses are given, the plugin will default to not use a cache.

#### Hashing

There are 3 options for password hashing available: `PBKDF2` (default), `Bcrypt` and `Argon2ID`. Every backend that needs one -that's all but `grpc`, `http` and `custom`- gets a hasher and whether it uses specific options or general ones depends on the auth opts passed.

Provided options define what hasher each backend will use:
- If there are general hashing options available but no backend ones, then every backend will use those general ones for its hasher.
- If there are no options available in general and none for a given backend either, that backend will use defaults (see `hashing/hashing.go` for default values).
- If there are options for a given backend but no general ones, the backend will use its own hasher and any backend that doesn't register a hasher will use defaults.

You may set the desired general hasher with this option, passing either `pbkdf2`, `bcrypt` or `argon2id` values. When not set, the option will default to `pbkdf2`.

```
auth_opt_hasher pbkdf2
```

Each hasher has specific options. Notice that when using the `pw` utility, these values must match those used to generate the password.

##### PBKDF2

```
auth_opt_salt_size 16 # salt bytes length
auth_opt_iterations 100000 # number of iterations
auth_opt_keylen 64 # key length
auth_opt_algorithm sha512 # hashing algorithm, either sha512 (default) or sha256
auth_opt_salt_encoding # salt encoding, either base64 (default) or utf-8
```

##### Bcrypt

```
auth_opt_cost 10 # key expansion iteration count
```

##### Argon2ID

```
auth_opt_salt_size 16 # salt bytes length
auth_opt_iterations 3 # number of iterations
auth_opt_keylen 64 # key length
auth_opt_memory 4096 # amount of memory (in kibibytes) to use
auth_opt_parallelism 2 # degree of parallelism (i.e. number of threads)
```

**These options may be defined for each backend that needs a hasher by prepending the backend's name to the option, e.g. for setting `argon2id` as `Postgres'` hasher**:

```
auth_opt_pg_hasher argon2id
auth_opt_pg_salt_size 16 # salt bytes length
auth_opt_pg_iterations 3 # number of iterations
auth_opt_pg_keylen 64 # key length
auth_opt_pg_memory 4096 # amount of memory (in kibibytes) to use
auth_opt_pg_parallelism # degree of parallelism (i.e. number of threads)
```

#### Logging

You can set the log level with the `log_level` option. Valid values are: debug, info, warn, error, fatal and panic. If not set, default value is `info`.
Expand All @@ -297,7 +353,7 @@ If `log_dest` or `log_file` are invalid, or if there's an error opening the file

#### Prefixes

Though the plugin may have multiple backends enabled, there's a way to specify which backend must be used for a given user: prefixes. When enabled, `prefixes` allows to check if the username contains a predefined prefix in the form prefix_username and use the configured backend for that prefix. Options to enable and set prefixes are the following:
Though the plugin may have multiple backends enabled, there's a way to specify which backend must be used for a given user: prefixes. When enabled, `prefixes` allow to check if the username contains a predefined prefix in the form prefix_username and use the configured backend for that prefix. Options to enable and set prefixes are the following:

```
auth_opt_check_prefix true
Expand All @@ -319,6 +375,38 @@ auth_opt_disable_superuser true

Any other value or missing option will have `superuser` enabled.

#### ACL access values

Mosquitto 1.5 introduced a new ACL access value, `MOSQ_ACL_SUBSCRIBE`, which is similar to the classic `MOSQ_ACL_READ` value but not quite the same:

```
* MOSQ_ACL_SUBSCRIBE when a client is asking to subscribe to a topic string.
* This differs from MOSQ_ACL_READ in that it allows you to
* deny access to topic strings rather than by pattern. For
* example, you may use MOSQ_ACL_SUBSCRIBE to deny
* subscriptions to '#', but allow all topics in
* MOSQ_ACL_READ. This allows clients to subscribe to any
* topic they want, but not discover what topics are in use
* on the server.
* MOSQ_ACL_READ when a message is about to be sent to a client (i.e. whether
* it can read that topic or not).
```

The main difference is that subscribe is checked at first, when a client connects and tells the broker it wants to subscribe to some topic, while read is checked when an actual message is being published to that topic, which makes it particular.
So in practice you could deny general subscriptions such as # by returning false from the acl check when you receive `MOSQ_ACL_SUBSCRIBE`, but allow any particular one by returning true on `MOSQ_ACL_READ`.
Please take this into consideration when designing your ACL records on every backend.

Also, these are the current available values from `mosquitto`:

```
#define MOSQ_ACL_NONE 0x00
#define MOSQ_ACL_READ 0x01
#define MOSQ_ACL_WRITE 0x02
#define MOSQ_ACL_SUBSCRIBE 0x04
```

If you're using prior versions then `MOSQ_ACL_SUBSCRIBE` is not available and you don't need to worry about it.

#### Backend options

Any other options with a leading ```auth_opt_``` are handed to the plugin and used by the backends.
Expand All @@ -333,23 +421,36 @@ This issue captures these concerns and a basic plan to refactor tests: https://g

### Files

The `files` backend implements the regular password and acl checks as described in mosquitto. Passwords should be in PBKDF2 format (for other backends too), and may be generated using the `pw` utility (built by default when running `make`) included in the plugin (or one of your own). Passwords may also be tested using the [pw-test package](https://github.com/iegomez/pw-test).
The `files` backend implements the regular password and acl checks as described in mosquitto. Passwords should be in `PBKDF2`, `Bcrypt` or `Argon2ID` format (for other backends too), see [Hashing](#hashing) for more details about different hashing strategies. Hashes may be generated using the `pw` utility (built by default when running `make`) included in the plugin (or one of your own). Passwords may also be tested using the [pw-test package](https://github.com/iegomez/pw-test).

Usage of `pw`:

```
Usage of ./pw:
-a string
algorithm: sha256 or sha512 (default "sha512")
-c int
bcrypt ost param (default 10)
-e string
salt encoding (default "base64")
-h string
hasher: pbkdf2, argon2 or bcrypt (default "pbkdf2")
-i int
hash iterations (default 100000)
hash iterations: defaults to 100000 for pbkdf2, please set to a reasonable value for argon2 (default 100000)
-l int
key length, recommended values are 32 for sha256 and 64 for sha512
-m int
memory for argon2 hash (default 4096)
-p string
password
-pl int
parallelism for argon2 (default 2)
-s int
salt size (default 16)
```

For this backend passwords and acls file paths must be given:
For this backend `passwords` and `acls` file paths must be given:

```
auth_opt_password_path /path/to/password_file
Expand Down Expand Up @@ -455,7 +556,7 @@ Queries work pretty much the same as in jpmen's plugin, so here's his discriptio
In the following example, the table has a column `rw` containing 1 for
readonly topics, 2 for writeonly topics and 3 for readwrite topics:

SELECT topic FROM acl WHERE (username = $1) AND (rw = $2 or rw = 3)
SELECT topic FROM acl WHERE (username = $1) AND rw = $2


When option pg_superquery is not present, Superuser check will always return false, hence there'll be no superusers.
Expand All @@ -476,6 +577,9 @@ auth_opt_pg_aclquery select distinct 'application/' || a.id || '/#' from "user"
```

#### Password hashing

For instructions on how to set a backend specific hasher or use the general one, see [Hashing](#hashing).

#### Testing Postgres

Expand Down Expand Up @@ -527,7 +631,7 @@ To allow native passwords, set the option to true:
auth_opt_mysql_allow_native_passwords true
```

Finally, placeholders for mysql differ from those of postgres, changing from $1, $2, etc., to simply ?. So, following the postgres examples, same queries for mysql would look like these:
Finally, placeholders for mysql differ from those of postgres, changing from $1, $2, etc., to simply ?. These are some **example** queries for `mysql`:

User query:

Expand All @@ -545,9 +649,12 @@ SELECT COUNT(*) FROM account WHERE username = ? AND super = 1
Acl query:

```sql
SELECT topic FROM acl WHERE (username = ?) AND rw >= ?
SELECT topic FROM acl WHERE (username = ?) AND rw = ?
```

#### Password hashing

For instructions on how to set a backend specific hasher or use the general one, see [Hashing](#hashing).

#### Testing Mysql

Expand Down Expand Up @@ -616,6 +723,9 @@ sqlite_superquery SELECT COUNT(*) FROM account WHERE username = ? AND super = 1
sqlite_aclquery SELECT topic FROM acl WHERE (username = ?) AND rw >= ?
```

#### Password hashing

For instructions on how to set a backend specific hasher or use the general one, see [Hashing](#hashing).

#### Testing SQLite3

Expand Down Expand Up @@ -813,6 +923,9 @@ When option jwt_superquery is not present, Superuser check will always return fa

When option jwt_aclquery is not present, AclCheck will always return true, hence all authenticated users will be authorized to pub/sub to any topic.

#### Password hashing

When using local mode, a hasher is expected. For instructions on how to set a backend specific hasher or use the general one, see [Hashing](#hashing).

#### Prefixes

Expand Down Expand Up @@ -911,6 +1024,10 @@ When not present, host defaults to "localhost", port to 6379, db to 2 and no pas
If you want to use a Redis Cluster as your backend, you need to set `auth_opt_redis_mode` to `cluster` and provide the different addresses as a list of comma separated `host:port` strings with the `auth_opt_redis_addresses` options.
If `auth_opt_redis_mode` is set to another value or not set, Redis defaults to single instance behaviour. If it is correctly set but no addresses are given, the backend will fail to initialize.

#### Password hashing

For instructions on how to set a backend specific hasher or use the general one, see [Hashing](#hashing).

#### Testing Redis

In order to test the Redis backend, the plugin needs to be able to connect to a redis server located at localhost, on port 6379, without using password and that a database named 2 exists (to avoid messing with the commonly used 0 and 1).
Expand Down Expand Up @@ -990,6 +1107,10 @@ When not set, these options default to:

If you experience any problem connecting to a replica set, please refer to [this issue](https://github.com/iegomez/mosquitto-go-auth/issues/32).

#### Password hashing

For instructions on how to set a backend specific hasher or use the general one, see [Hashing](#hashing).

#### Testing MongoDB

Much like `redis`, to test this backend the plugin needs to be able to connect to a mongodb server located at localhost, on port 27017, without using username or password.
Expand Down
31 changes: 31 additions & 0 deletions backends/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package backends

import (
"time"

"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)

// OpenDatabase opens the database and performs a ping to make sure the
// database is up.
// Taken from brocaar's lora-app-server: https://github.com/brocaar/lora-app-server
func OpenDatabase(dsn, engine string) (*sqlx.DB, error) {

db, err := sqlx.Open(engine, dsn)
if err != nil {
return nil, errors.Wrap(err, "database connection error")
}

for {
if err = db.Ping(); err != nil {
log.Errorf("ping database error, will retry in 2s: %s", err)
time.Sleep(2 * time.Second)
} else {
break
}
}

return db, nil
}
Loading

0 comments on commit aa487a9

Please sign in to comment.