Implement Redis v6 auth (fixes #182)

* Change redis_auth in struct conf to handle old and new auth
* Update cfg.c to understand an array of two strings for redis_auth
* Update pool.c to send both username and password
master
Jessie Murray 4 years ago committed by Nicolas Favre-Felix
parent 7fdfef0d8d
commit 02d60dc548

@ -106,7 +106,7 @@ f0a2763fd456
* WebSocket support (Currently using the “hixie-76” specification).
* Connects to Redis using a TCP or UNIX socket.
* Restricted commands by IP range (CIDR subnet + mask) or HTTP Basic Auth, returning 403 errors.
* Possible Redis authentication in the config file.
* Support for Redis authentication in the config file: set `redis_auth` to a single string to use a password value, or to an array of two strings to use username+password auth ([new in Redis 6.0](https://redis.io/commands/auth)).
* Environment variables can be used as values in the config file, starting with `$` and in all caps (e.g. `$REDIS_HOST`).
* Pub/Sub using `Transfer-Encoding: chunked`, works with JSONP as well. Webdis can be used as a Comet server.
* Drop privileges on startup.

@ -16,6 +16,14 @@
static struct acl *
conf_parse_acls(json_t *jtab);
#define ACL_ERROR_PREFIX "Config error with 'redis_auth': "
#define ACL_ERROR_SUFFIX ". Starting with auth disabled.\n"
static struct auth *
conf_auth_legacy(char *password);
static struct auth *
conf_auth_username_password(json_t *jarray);
int
conf_str_allcaps(const char *s, const size_t sz) {
size_t i;
@ -100,8 +108,14 @@ conf_read(const char *filename) {
conf->redis_port = (int)json_integer_value(jtmp);
} else if(strcmp(json_object_iter_key(kv), "redis_port") == 0 && json_typeof(jtmp) == JSON_STRING) {
conf->redis_port = atoi_free(conf_string_or_envvar(json_string_value(jtmp)));
} else if(strcmp(json_object_iter_key(kv), "redis_auth") == 0 && json_typeof(jtmp) == JSON_STRING) {
conf->redis_auth = conf_string_or_envvar(json_string_value(jtmp));
} else if(strcmp(json_object_iter_key(kv), "redis_auth") == 0) {
if(json_typeof(jtmp) == JSON_STRING) {
conf->redis_auth = conf_auth_legacy(conf_string_or_envvar(json_string_value(jtmp)));
} else if(json_typeof(jtmp) == JSON_ARRAY) {
conf->redis_auth = conf_auth_username_password(jtmp);
} else {
fprintf(stderr, ACL_ERROR_PREFIX "expected a string or an array of two strings" ACL_ERROR_SUFFIX);
}
} else if(strcmp(json_object_iter_key(kv), "http_host") == 0 && json_typeof(jtmp) == JSON_STRING) {
free(conf->http_host);
conf->http_host = conf_string_or_envvar(json_string_value(jtmp));
@ -294,3 +308,47 @@ conf_free(struct conf *conf) {
free(conf);
}
static struct auth *
conf_auth_legacy(char *password) {
struct auth *ret = calloc(1, sizeof(struct auth));
if(!ret) {
free(password);
fprintf(stderr, ACL_ERROR_PREFIX "failed to allocate memory for credentials" ACL_ERROR_SUFFIX);
return NULL;
}
ret->use_legacy_auth = 1;
ret->password = password; /* already transformed to include decoded env vars */
return ret;
}
static struct auth *
conf_auth_username_password(json_t *jarray) {
size_t array_size = json_array_size(jarray);
if(array_size != 2) {
fprintf(stderr, ACL_ERROR_PREFIX "expected two values, found %zu" ACL_ERROR_SUFFIX, array_size);
return NULL;
}
json_t *jusername = json_array_get(jarray, 0);
json_t *jpassword = json_array_get(jarray, 1);
if(json_typeof(jusername) != JSON_STRING || json_typeof(jpassword) != JSON_STRING) {
fprintf(stderr, ACL_ERROR_PREFIX "both values need to be strings" ACL_ERROR_SUFFIX);
return NULL;
}
char *username = conf_string_or_envvar(json_string_value(jusername));
char *password = conf_string_or_envvar(json_string_value(jpassword));
struct auth *ret = calloc(1, sizeof(struct auth));
if(!username || !password || !ret) {
free(username);
free(password);
free(ret);
fprintf(stderr, ACL_ERROR_PREFIX "failed to allocate memory for credentials" ACL_ERROR_SUFFIX);
return NULL;
}
ret->use_legacy_auth = 0;
ret->username = username;
ret->password = password;
return ret;
}

@ -4,12 +4,19 @@
#include <sys/types.h>
#include "slog.h"
struct auth {
/* 1 if only password is used, 0 for username + password */
int use_legacy_auth;
char *username;
char *password;
};
struct conf {
/* connection to Redis */
char *redis_host;
int redis_port;
char *redis_auth;
struct auth *redis_auth;
/* HTTP server interface */
char *http_host;

@ -154,7 +154,14 @@ pool_connect(struct pool *p, int db_num, int attach) {
redisAsyncSetDisconnectCallback(ac, pool_on_disconnect);
if(p->cfg->redis_auth) { /* authenticate. */
redisAsyncCommand(ac, NULL, NULL, "AUTH %s", p->cfg->redis_auth);
if(p->cfg->redis_auth->use_legacy_auth) {
redisAsyncCommand(ac, NULL, NULL, "AUTH %s",
p->cfg->redis_auth->password);
} else {
redisAsyncCommand(ac, NULL, NULL, "AUTH %s %s",
p->cfg->redis_auth->username,
p->cfg->redis_auth->password);
}
}
if(db_num) { /* change database. */
redisAsyncCommand(ac, NULL, NULL, "SELECT %d", db_num);

Loading…
Cancel
Save