New security feature: Redis protected mode.
An exposed Redis instance on the internet can be cause of serious issues. Since Redis, by default, binds to all the interfaces, it is easy to forget an instance without any protection layer, for error. Protected mode try to address this feature in a soft way, providing a layer of protection, but giving clues to Redis users about why the server is not accepting connections. When protected mode is enabeld (the default), and if there are no minumum hints about the fact the server is properly configured (no "bind" directive is used in order to restrict the server to certain interfaces, nor a password is set), clients connecting from external intefaces are refused with an error explaining what to do in order to fix the issue. Clients connecting from the IPv4 and IPv6 lookback interfaces are still accepted normally, similarly Unix domain socket connections are not restricted in any way.
This commit is contained in:
parent
00d637f2cc
commit
edd4d555df
19
redis.conf
19
redis.conf
@ -60,6 +60,25 @@
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
bind 127.0.0.1
|
||||
|
||||
# Protected mode is a layer of security protection, in order to avoid that
|
||||
# Redis instances left open on the internet are accessed and exploited.
|
||||
#
|
||||
# When protected mode is on and if:
|
||||
#
|
||||
# 1) The server is not binding explicitly to a set of addresses using the
|
||||
# "bind" directive.
|
||||
# 2) No password is configured.
|
||||
#
|
||||
# The server only accepts connections from clients connecting from the
|
||||
# IPv4 and IPv6 loopback addresses 127.0.0.1 and ::1, and from Unix domain
|
||||
# sockets.
|
||||
#
|
||||
# By default protected mode is enabled. You should disable it only if
|
||||
# you are sure you want clients from other hosts to connect to Redis
|
||||
# even if no authentication is configured, nor a specific set of interfaces
|
||||
# are explicitly listed using the "bind" directive.
|
||||
protected-mode yes
|
||||
|
||||
# Accept connections on the specified port, default is 6379 (IANA #815344).
|
||||
# If port 0 is specified Redis will not listen on a TCP socket.
|
||||
port 6379
|
||||
|
@ -196,6 +196,10 @@ void loadServerConfigFromString(char *config) {
|
||||
if (server.tcpkeepalive < 0) {
|
||||
err = "Invalid tcp-keepalive value"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"protected-mode") && argc == 2) {
|
||||
if ((server.protected_mode = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"port") && argc == 2) {
|
||||
server.port = atoi(argv[1]);
|
||||
if (server.port < 0 || server.port > 65535) {
|
||||
@ -889,6 +893,8 @@ void configSetCommand(client *c) {
|
||||
"slave-read-only",server.repl_slave_ro) {
|
||||
} config_set_bool_field(
|
||||
"activerehashing",server.activerehashing) {
|
||||
} config_set_bool_field(
|
||||
"protected-mode",server.protected_mode) {
|
||||
} config_set_bool_field(
|
||||
"stop-writes-on-bgsave-error",server.stop_writes_on_bgsave_err) {
|
||||
} config_set_bool_field(
|
||||
@ -1129,6 +1135,7 @@ void configGetCommand(client *c) {
|
||||
config_get_bool_field("rdbcompression", server.rdb_compression);
|
||||
config_get_bool_field("rdbchecksum", server.rdb_checksum);
|
||||
config_get_bool_field("activerehashing", server.activerehashing);
|
||||
config_get_bool_field("protected-mode", server.protected_mode);
|
||||
config_get_bool_field("repl-disable-tcp-nodelay",
|
||||
server.repl_disable_tcp_nodelay);
|
||||
config_get_bool_field("repl-diskless-sync",
|
||||
@ -1847,6 +1854,7 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigNumericalOption(state,"zset-max-ziplist-value",server.zset_max_ziplist_value,OBJ_ZSET_MAX_ZIPLIST_VALUE);
|
||||
rewriteConfigNumericalOption(state,"hll-sparse-max-bytes",server.hll_sparse_max_bytes,CONFIG_DEFAULT_HLL_SPARSE_MAX_BYTES);
|
||||
rewriteConfigYesNoOption(state,"activerehashing",server.activerehashing,CONFIG_DEFAULT_ACTIVE_REHASHING);
|
||||
rewriteConfigYesNoOption(state,"protected-mode",server.protected_mode,CONFIG_DEFAULT_PROTECTED_MODE);
|
||||
rewriteConfigClientoutputbufferlimitOption(state);
|
||||
rewriteConfigNumericalOption(state,"hz",server.hz,CONFIG_DEFAULT_HZ);
|
||||
rewriteConfigYesNoOption(state,"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync,CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC);
|
||||
|
@ -579,7 +579,7 @@ int clientHasPendingReplies(client *c) {
|
||||
}
|
||||
|
||||
#define MAX_ACCEPTS_PER_CALL 1000
|
||||
static void acceptCommonHandler(int fd, int flags) {
|
||||
static void acceptCommonHandler(int fd, int flags, char *ip) {
|
||||
client *c;
|
||||
if ((c = createClient(fd)) == NULL) {
|
||||
serverLog(LL_WARNING,
|
||||
@ -603,6 +603,48 @@ static void acceptCommonHandler(int fd, int flags) {
|
||||
freeClient(c);
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the server is running in protected mode (the default) and there
|
||||
* is no password set, nor a specific interface is bound, we don't accept
|
||||
* requests from non loopback interfaces. Instead we try to explain the
|
||||
* user what to do to fix it if needed. */
|
||||
if (server.protected_mode &&
|
||||
server.bindaddr_count == 0 &&
|
||||
server.requirepass == NULL &&
|
||||
!(flags & CLIENT_UNIX_SOCKET) &&
|
||||
ip != NULL)
|
||||
{
|
||||
if (strcmp(ip,"127.0.0.1") && strcmp(ip,"::1")) {
|
||||
char *err =
|
||||
"-DENIED Redis is running in protected mode because protected "
|
||||
"mode is enabled, no bind address was specified, no "
|
||||
"authentication password is requested to clients. In this mode "
|
||||
"connections are only accepted from the lookback interface. "
|
||||
"If you want to connect from external computers to Redis you "
|
||||
"may adopt one of the following solutions: "
|
||||
"1) Just disable protected mode sending the command "
|
||||
"'CONFIG SET protected-mode no' from the loopback interface "
|
||||
"by connecting to Redis from the same host the server is "
|
||||
" running, however MAKE SURE Redis is not publicly accessible "
|
||||
"from internet if you do so. Use CONFIG REWRITE to make this "
|
||||
"change permanent. "
|
||||
"2) Alternatively you can just disable the protected mode by "
|
||||
"editing the Redis configuration file, and setting the protected "
|
||||
"mode option to 'no', and then restarting the server. "
|
||||
"3) If you started the server manually just for testing, restart "
|
||||
"it with the '--portected-mode no' option. "
|
||||
"4) Setup a bind address or an authentication password. "
|
||||
"NOTE: You only need to do one of the above things in order for "
|
||||
"the server to start accepting connections from the outside.\r\n";
|
||||
if (write(c->fd,err,strlen(err)) == -1) {
|
||||
/* Nothing to do, Just to avoid the warning... */
|
||||
}
|
||||
server.stat_rejected_conn++;
|
||||
freeClient(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
server.stat_numconnections++;
|
||||
c->flags |= flags;
|
||||
}
|
||||
@ -623,7 +665,7 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
return;
|
||||
}
|
||||
serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
|
||||
acceptCommonHandler(cfd,0);
|
||||
acceptCommonHandler(cfd,0,cip);
|
||||
}
|
||||
}
|
||||
|
||||
@ -642,7 +684,7 @@ void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
return;
|
||||
}
|
||||
serverLog(LL_VERBOSE,"Accepted connection to %s", server.unixsocket);
|
||||
acceptCommonHandler(cfd,CLIENT_UNIX_SOCKET);
|
||||
acceptCommonHandler(cfd,CLIENT_UNIX_SOCKET,NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1457,6 +1457,7 @@ void initServerConfig(void) {
|
||||
server.unixsocketperm = CONFIG_DEFAULT_UNIX_SOCKET_PERM;
|
||||
server.ipfd_count = 0;
|
||||
server.sofd = -1;
|
||||
server.protected_mode = CONFIG_DEFAULT_PROTECTED_MODE;
|
||||
server.dbnum = CONFIG_DEFAULT_DBNUM;
|
||||
server.verbosity = CONFIG_DEFAULT_VERBOSITY;
|
||||
server.maxidletime = CONFIG_DEFAULT_CLIENT_TIMEOUT;
|
||||
|
@ -111,6 +111,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define CONFIG_DEFAULT_DAEMONIZE 0
|
||||
#define CONFIG_DEFAULT_UNIX_SOCKET_PERM 0
|
||||
#define CONFIG_DEFAULT_TCP_KEEPALIVE 0
|
||||
#define CONFIG_DEFAULT_PROTECTED_MODE 1
|
||||
#define CONFIG_DEFAULT_LOGFILE ""
|
||||
#define CONFIG_DEFAULT_SYSLOG_ENABLED 0
|
||||
#define CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR 1
|
||||
@ -743,6 +744,7 @@ struct redisServer {
|
||||
char neterr[ANET_ERR_LEN]; /* Error buffer for anet.c */
|
||||
dict *migrate_cached_sockets;/* MIGRATE cached sockets */
|
||||
uint64_t next_client_id; /* Next client unique ID. Incremental. */
|
||||
int protected_mode; /* Don't accept external connections. */
|
||||
/* RDB / AOF loading information */
|
||||
int loading; /* We are loading data from disk if true */
|
||||
off_t loading_total_bytes;
|
||||
|
Loading…
Reference in New Issue
Block a user