Skip to content

Commit

Permalink
Add user-agent to requests (#176)
Browse files Browse the repository at this point in the history
* Add default 'mosquitto' user-agent to http/jwt requests

* Add libmosquitto version ofr http and jwt remote user agent.

Add more room for extended versions.

Co-authored-by: Ignacio Gómez <[email protected]>
  • Loading branch information
kfdm and iegomez committed Jul 12, 2021
1 parent cff71ff commit 5dc063f
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 77 deletions.
69 changes: 34 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -855,32 +855,32 @@ There are no requirements, as the tests create (and later delete) the DB and tab

The `jwt` backend is for auth with a JWT remote API, a local DB, a JavaScript VM interpreter or an ACL file. Global otions for JWT are:

| Option | default | Mandatory | Meaning |
| ------------------------- | ----------------- | :---------: | ------------------------------------------------------- |
| jwt_mode | | Y | local, remote, js, files |
| jwt_parse_token | false | N | Parse token in remote/js modes |
| jwt_secret | | Y/N | JWT secret, required for local mode, optional otherwise |
| jwt_userfield | | N | When `Username`, expect `username` as part of claims |
| jwt_skip_user_expiration | false | N | Skip token expiration in user/superuser checks |
| jwt_skip_acl_expiration | false | N | Skip token expiration in ACL checks |

| Option | default | Mandatory | Meaning |
| ------------------------ | --------- | :-------: | ------------------------------------------------------- |
| jwt_mode | | Y | local, remote, js, files |
| jwt_parse_token | false | N | Parse token in remote/js modes |
| jwt_secret | | Y/N | JWT secret, required for local mode, optional otherwise |
| jwt_userfield | | N | When `Username`, expect `username` as part of claims |
| jwt_skip_user_expiration | false | N | Skip token expiration in user/superuser checks |
| jwt_skip_acl_expiration | false | N | Skip token expiration in ACL checks |
| jwt_user_agent | mosquitto | N | User agent for requests |

#### Remote mode

The following `auth_opt_` options are supported by the `jwt` backend when remote is set to true:

| Option | default | Mandatory | Meaning |
| ------------------------- | ----------------- | :---------: | ------------------------------------------- |
| jwt_host | | Y | API server host name or ip |
| jwt_port | | Y | TCP port number |
| jwt_getuser_uri | | Y | URI for check username/password |
| jwt_superuser_uri | | N | URI for check superuser |
| jwt_aclcheck_uri | | Y | URI for check acl |
| jwt_with_tls | false | N | Use TLS on connect |
| jwt_verify_peer | false | N | Whether to verify peer for tls |
| jwt_response_mode | status | N | Response type (status, json, text) |
| jwt_params_mode | json | N | Data type (json, form) |

| Option | default | Mandatory | Meaning |
| ----------------- | --------- | :-------: | ---------------------------------- |
| jwt_host | | Y | API server host name or ip |
| jwt_port | | Y | TCP port number |
| jwt_getuser_uri | | Y | URI for check username/password |
| jwt_superuser_uri | | N | URI for check superuser |
| jwt_aclcheck_uri | | Y | URI for check acl |
| jwt_with_tls | false | N | Use TLS on connect |
| jwt_verify_peer | false | N | Whether to verify peer for tls |
| jwt_response_mode | status | N | Response type (status, json, text) |
| jwt_params_mode | json | N | Data type (json, form) |
| jwt_user_agent | mosquitto | N | User agent for requests |

URIs (like jwt_getuser_uri) are expected to be in the form `/path`. For example, if jwt_with_tls is `false`, jwt_host is `localhost`, jwt_port `3000` and jwt_getuser_uri is `/user`, mosquitto will send a POST request to `http:https://localhost:3000/user` to get a response to check against. How data is sent (either json encoded or as form values) and received (as a simple http status code, a json encoded response or plain text), is given by options jwt_response_mode and jwt_params_mode.

Expand Down Expand Up @@ -1120,20 +1120,19 @@ The `http` backend is very similar to the JWT one, but instead of a jwt token it

The following `auth_opt_` options are supported:


| Option | default | Mandatory | Meaning |
| ------------------ | ----------------- | :---------: | ---------- |
| http_host | | Y | IP address,will skip dns lookup |
| http_port | | Y | TCP port number |
| http_getuser_uri | | Y | URI for check username/password |
| http_superuser_uri | | N | URI for check superuser |
| http_aclcheck_uri | | Y | URI for check acl |
| http_with_tls | false | N | Use TLS on connect |
| http_verify_peer | false | N | Whether to verify peer for tls |
| http_response_mode | status | N | Response type (status, json, text)|
| http_params_mode | json | N | Data type (json, form) |
| http_timeout | 5 | N | Timeout in seconds |

| Option | default | Mandatory | Meaning |
| ------------------ | --------- | :-------: | ---------------------------------- |
| http_host | | Y | IP address,will skip dns lookup |
| http_port | | Y | TCP port number |
| http_getuser_uri | | Y | URI for check username/password |
| http_superuser_uri | | N | URI for check superuser |
| http_aclcheck_uri | | Y | URI for check acl |
| http_with_tls | false | N | Use TLS on connect |
| http_verify_peer | false | N | Whether to verify peer for tls |
| http_response_mode | status | N | Response type (status, json, text) |
| http_params_mode | json | N | Data type (json, form) |
| http_timeout | 5 | N | Timeout in seconds |
| http_user_agent | mosquitto | N | User Agent to use in requests |

#### Response mode

Expand Down
7 changes: 6 additions & 1 deletion auth-plugin.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,12 @@ int mosquitto_auth_plugin_init(void **user_data, struct mosquitto_auth_opt *auth
GoSlice keysSlice = {keys, auth_opt_count, auth_opt_count};
GoSlice valuesSlice = {values, auth_opt_count, auth_opt_count};

AuthPluginInit(keysSlice, valuesSlice, opts_count);
char versionArray[10];
sprintf(versionArray, "%i.%i.%i", LIBMOSQUITTO_MAJOR, LIBMOSQUITTO_MINOR, LIBMOSQUITTO_REVISION);

GoString version = {versionArray, strlen(versionArray)};

AuthPluginInit(keysSlice, valuesSlice, opts_count, version);
return MOSQ_ERR_SUCCESS;
}

Expand Down
13 changes: 8 additions & 5 deletions backends/backends.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ const (
aclCheck = "acl"
userCheck = "user"
superuserCheck = "superuser"

// other constants
defaultUserAgent = "mosquitto"
)

// AllowedBackendsOptsPrefix serves as a check for allowed backends and a map from backend to expected opts prefix.
Expand All @@ -66,7 +69,7 @@ var allowedBackendsOptsPrefix = map[string]string{
}

// Initialize sets general options, tries to build the backends and register their checkers.
func Initialize(authOpts map[string]string, logLevel log.Level) (*Backends, error) {
func Initialize(authOpts map[string]string, logLevel log.Level, version string) (*Backends, error) {

b := &Backends{
backends: make(map[string]Backend),
Expand Down Expand Up @@ -99,7 +102,7 @@ func Initialize(authOpts map[string]string, logLevel log.Level) (*Backends, erro
}
}

err := b.addBackends(authOpts, logLevel, backends)
err := b.addBackends(authOpts, logLevel, backends, version)
if err != nil {
return nil, err
}
Expand All @@ -114,7 +117,7 @@ func Initialize(authOpts map[string]string, logLevel log.Level) (*Backends, erro
return b, nil
}

func (b *Backends) addBackends(authOpts map[string]string, logLevel log.Level, backends []string) error {
func (b *Backends) addBackends(authOpts map[string]string, logLevel log.Level, backends []string, version string) error {
for _, bename := range backends {
var beIface Backend
var err error
Expand All @@ -130,7 +133,7 @@ func (b *Backends) addBackends(authOpts map[string]string, logLevel log.Level, b
b.backends[postgresBackend] = beIface.(Postgres)
}
case jwtBackend:
beIface, err = NewJWT(authOpts, logLevel, hasher)
beIface, err = NewJWT(authOpts, logLevel, hasher, version)
if err != nil {
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
Expand Down Expand Up @@ -162,7 +165,7 @@ func (b *Backends) addBackends(authOpts map[string]string, logLevel log.Level, b
b.backends[mysqlBackend] = beIface.(Mysql)
}
case httpBackend:
beIface, err = NewHTTP(authOpts, logLevel)
beIface, err = NewHTTP(authOpts, logLevel, version)
if err != nil {
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
Expand Down
26 changes: 14 additions & 12 deletions backends/backends_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,26 @@ func TestBackends(t *testing.T) {
passwordHash := "PBKDF2$sha512$100000$2WQHK5rjNN+oOT+TZAsWAw==$TDf4Y6J+9BdnjucFQ0ZUWlTwzncTjOOeE00W4Qm8lfPQyPCZACCjgfdK353jdGFwJjAf6vPAYaba9+z4GWK7Gg=="
clientid := "clientid"

version := "2.0.0"

Convey("Missing or empty backends option should result in an error", t, func() {
authOpts["backends"] = ""

_, err := Initialize(authOpts, log.DebugLevel)
_, err := Initialize(authOpts, log.DebugLevel, version)
So(err, ShouldNotBeNil)
So(err.Error(), ShouldEqual, "missing or blank option backends")

delete(authOpts, "backends")

_, err = Initialize(authOpts, log.DebugLevel)
_, err = Initialize(authOpts, log.DebugLevel, version)
So(err, ShouldNotBeNil)
So(err.Error(), ShouldEqual, "missing or blank option backends")
})

Convey("An unknown backend should result in an error", t, func() {
authOpts["backends"] = "unknown"

_, err := Initialize(authOpts, log.DebugLevel)
_, err := Initialize(authOpts, log.DebugLevel, version)
So(err, ShouldNotBeNil)
So(err.Error(), ShouldEqual, "unknown backend unknown")
})
Expand All @@ -70,7 +72,7 @@ func TestBackends(t *testing.T) {
authOpts["files_register"] = "user"
authOpts["redis_register"] = "unknown"

_, err := Initialize(authOpts, log.DebugLevel)
_, err := Initialize(authOpts, log.DebugLevel, version)
So(err, ShouldNotBeNil)
So(err.Error(), ShouldEqual, "unsupported check unknown found for backend redis")
})
Expand All @@ -89,7 +91,7 @@ func TestBackends(t *testing.T) {
username = "test1"
redis.conn.Set(ctx, username, passwordHash, 0)

b, err := Initialize(authOpts, log.DebugLevel)
b, err := Initialize(authOpts, log.DebugLevel, version)
So(err, ShouldBeNil)

// Redis only contains test1, while files has a bunch of more users.
Expand Down Expand Up @@ -140,7 +142,7 @@ func TestBackends(t *testing.T) {
// Insert a user to test auth
redis.conn.Set(ctx, username, passwordHash, 0)

b, err := Initialize(authOpts, log.DebugLevel)
b, err := Initialize(authOpts, log.DebugLevel, version)
So(err, ShouldBeNil)

tt1, err1 := b.AuthUnpwdCheck(username, password, clientid)
Expand Down Expand Up @@ -197,7 +199,7 @@ func TestBackends(t *testing.T) {
// Set it as superuser.
redis.conn.Set(ctx, fmt.Sprintf("%s:su", username), "true", 0)

b, err := Initialize(authOpts, log.DebugLevel)
b, err := Initialize(authOpts, log.DebugLevel, version)
So(err, ShouldBeNil)

tt1, err1 := b.AuthUnpwdCheck(username, password, clientid)
Expand Down Expand Up @@ -242,7 +244,7 @@ func TestBackends(t *testing.T) {
redis.conn.Set(ctx, username, passwordHash, 0)
redis.conn.Set(ctx, fmt.Sprintf("%s:su", username), "true", 0)

b, err := Initialize(authOpts, log.DebugLevel)
b, err := Initialize(authOpts, log.DebugLevel, version)
So(err, ShouldBeNil)

tt1, err1 := b.AuthUnpwdCheck(username, password, clientid)
Expand Down Expand Up @@ -287,7 +289,7 @@ func TestBackends(t *testing.T) {
redis.conn.Set(ctx, username, passwordHash, 0)
redis.conn.Set(ctx, fmt.Sprintf("%s:su", username), "true", 0)

b, err := Initialize(authOpts, log.DebugLevel)
b, err := Initialize(authOpts, log.DebugLevel, version)
So(err, ShouldBeNil)

tt1, err1 := b.AuthUnpwdCheck(username, password, clientid)
Expand Down Expand Up @@ -331,7 +333,7 @@ func TestBackends(t *testing.T) {
// Set it as superuser.
redis.conn.Set(ctx, fmt.Sprintf("%s:su", username), "true", 0)

b, err := Initialize(authOpts, log.DebugLevel)
b, err := Initialize(authOpts, log.DebugLevel, version)
So(err, ShouldBeNil)

tt1, err1 := b.AuthUnpwdCheck(username, password, clientid)
Expand Down Expand Up @@ -376,7 +378,7 @@ func TestBackends(t *testing.T) {
redis.conn.Set(ctx, username, passwordHash, 0)
redis.conn.Set(ctx, fmt.Sprintf("%s:su", username), "true", 0)

b, err := Initialize(authOpts, log.DebugLevel)
b, err := Initialize(authOpts, log.DebugLevel, version)
So(err, ShouldBeNil)

tt1, err1 := b.AuthUnpwdCheck(username, password, clientid)
Expand Down Expand Up @@ -421,7 +423,7 @@ func TestBackends(t *testing.T) {
redis.conn.Set(ctx, username, passwordHash, 0)
redis.conn.Set(ctx, fmt.Sprintf("%s:su", username), "true", 0)

b, err := Initialize(authOpts, log.DebugLevel)
b, err := Initialize(authOpts, log.DebugLevel, version)
So(err, ShouldBeNil)

tt1, err1 := b.AuthUnpwdCheck(username, password, clientid)
Expand Down
9 changes: 8 additions & 1 deletion backends/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type HTTP struct {
UserUri string
SuperuserUri string
AclUri string
UserAgent string
Host string
Port string
WithTLS bool
Expand All @@ -34,7 +35,7 @@ type HTTPResponse struct {
Error string `json:"error"`
}

func NewHTTP(authOpts map[string]string, logLevel log.Level) (HTTP, error) {
func NewHTTP(authOpts map[string]string, logLevel log.Level, version string) (HTTP, error) {

log.SetLevel(logLevel)

Expand Down Expand Up @@ -79,6 +80,11 @@ func NewHTTP(authOpts map[string]string, logLevel log.Level) (HTTP, error) {
missingOpts += " http_aclcheck_uri"
}

http.UserAgent = fmt.Sprintf("%s-%s", defaultUserAgent, version)
if userAgent, ok := authOpts["http_user_agent"]; ok {
http.UserAgent = userAgent
}

if host, ok := authOpts["http_host"]; ok {
http.Host = host
} else {
Expand Down Expand Up @@ -224,6 +230,7 @@ func (o HTTP) httpRequest(uri, username string, dataMap map[string]interface{},
}

req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", o.UserAgent)

resp, err = o.Client.Do(req)
}
Expand Down
Loading

0 comments on commit 5dc063f

Please sign in to comment.