From 02d60dc548de1fcb56eb95457beacccdad812061 Mon Sep 17 00:00:00 2001 From: Jessie Murray Date: Mon, 1 Mar 2021 16:21:36 -0800 Subject: [PATCH] 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 --- README.md | 2 +- src/conf.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/conf.h | 9 +++++++- src/pool.c | 9 +++++++- 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5d3b8e0..955a0e6 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/conf.c b/src/conf.c index 2a6128f..18ddb03 100644 --- a/src/conf.c +++ b/src/conf.c @@ -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; +} diff --git a/src/conf.h b/src/conf.h index fade3ab..e460752 100644 --- a/src/conf.h +++ b/src/conf.h @@ -4,12 +4,19 @@ #include #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; diff --git a/src/pool.c b/src/pool.c index e5e29eb..a3c77e7 100644 --- a/src/pool.c +++ b/src/pool.c @@ -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);