Skip to content

Commit edd4d55

Browse files
committed
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.
1 parent 00d637f commit edd4d55

5 files changed

Lines changed: 75 additions & 3 deletions

File tree

redis.conf

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,25 @@
6060
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6161
bind 127.0.0.1
6262

63+
# Protected mode is a layer of security protection, in order to avoid that
64+
# Redis instances left open on the internet are accessed and exploited.
65+
#
66+
# When protected mode is on and if:
67+
#
68+
# 1) The server is not binding explicitly to a set of addresses using the
69+
# "bind" directive.
70+
# 2) No password is configured.
71+
#
72+
# The server only accepts connections from clients connecting from the
73+
# IPv4 and IPv6 loopback addresses 127.0.0.1 and ::1, and from Unix domain
74+
# sockets.
75+
#
76+
# By default protected mode is enabled. You should disable it only if
77+
# you are sure you want clients from other hosts to connect to Redis
78+
# even if no authentication is configured, nor a specific set of interfaces
79+
# are explicitly listed using the "bind" directive.
80+
protected-mode yes
81+
6382
# Accept connections on the specified port, default is 6379 (IANA #815344).
6483
# If port 0 is specified Redis will not listen on a TCP socket.
6584
port 6379

src/config.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ void loadServerConfigFromString(char *config) {
196196
if (server.tcpkeepalive < 0) {
197197
err = "Invalid tcp-keepalive value"; goto loaderr;
198198
}
199+
} else if (!strcasecmp(argv[0],"protected-mode") && argc == 2) {
200+
if ((server.protected_mode = yesnotoi(argv[1])) == -1) {
201+
err = "argument must be 'yes' or 'no'"; goto loaderr;
202+
}
199203
} else if (!strcasecmp(argv[0],"port") && argc == 2) {
200204
server.port = atoi(argv[1]);
201205
if (server.port < 0 || server.port > 65535) {
@@ -889,6 +893,8 @@ void configSetCommand(client *c) {
889893
"slave-read-only",server.repl_slave_ro) {
890894
} config_set_bool_field(
891895
"activerehashing",server.activerehashing) {
896+
} config_set_bool_field(
897+
"protected-mode",server.protected_mode) {
892898
} config_set_bool_field(
893899
"stop-writes-on-bgsave-error",server.stop_writes_on_bgsave_err) {
894900
} config_set_bool_field(
@@ -1129,6 +1135,7 @@ void configGetCommand(client *c) {
11291135
config_get_bool_field("rdbcompression", server.rdb_compression);
11301136
config_get_bool_field("rdbchecksum", server.rdb_checksum);
11311137
config_get_bool_field("activerehashing", server.activerehashing);
1138+
config_get_bool_field("protected-mode", server.protected_mode);
11321139
config_get_bool_field("repl-disable-tcp-nodelay",
11331140
server.repl_disable_tcp_nodelay);
11341141
config_get_bool_field("repl-diskless-sync",
@@ -1847,6 +1854,7 @@ int rewriteConfig(char *path) {
18471854
rewriteConfigNumericalOption(state,"zset-max-ziplist-value",server.zset_max_ziplist_value,OBJ_ZSET_MAX_ZIPLIST_VALUE);
18481855
rewriteConfigNumericalOption(state,"hll-sparse-max-bytes",server.hll_sparse_max_bytes,CONFIG_DEFAULT_HLL_SPARSE_MAX_BYTES);
18491856
rewriteConfigYesNoOption(state,"activerehashing",server.activerehashing,CONFIG_DEFAULT_ACTIVE_REHASHING);
1857+
rewriteConfigYesNoOption(state,"protected-mode",server.protected_mode,CONFIG_DEFAULT_PROTECTED_MODE);
18501858
rewriteConfigClientoutputbufferlimitOption(state);
18511859
rewriteConfigNumericalOption(state,"hz",server.hz,CONFIG_DEFAULT_HZ);
18521860
rewriteConfigYesNoOption(state,"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync,CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC);

src/networking.c

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,7 @@ int clientHasPendingReplies(client *c) {
579579
}
580580

581581
#define MAX_ACCEPTS_PER_CALL 1000
582-
static void acceptCommonHandler(int fd, int flags) {
582+
static void acceptCommonHandler(int fd, int flags, char *ip) {
583583
client *c;
584584
if ((c = createClient(fd)) == NULL) {
585585
serverLog(LL_WARNING,
@@ -603,6 +603,48 @@ static void acceptCommonHandler(int fd, int flags) {
603603
freeClient(c);
604604
return;
605605
}
606+
607+
/* If the server is running in protected mode (the default) and there
608+
* is no password set, nor a specific interface is bound, we don't accept
609+
* requests from non loopback interfaces. Instead we try to explain the
610+
* user what to do to fix it if needed. */
611+
if (server.protected_mode &&
612+
server.bindaddr_count == 0 &&
613+
server.requirepass == NULL &&
614+
!(flags & CLIENT_UNIX_SOCKET) &&
615+
ip != NULL)
616+
{
617+
if (strcmp(ip,"127.0.0.1") && strcmp(ip,"::1")) {
618+
char *err =
619+
"-DENIED Redis is running in protected mode because protected "
620+
"mode is enabled, no bind address was specified, no "
621+
"authentication password is requested to clients. In this mode "
622+
"connections are only accepted from the lookback interface. "
623+
"If you want to connect from external computers to Redis you "
624+
"may adopt one of the following solutions: "
625+
"1) Just disable protected mode sending the command "
626+
"'CONFIG SET protected-mode no' from the loopback interface "
627+
"by connecting to Redis from the same host the server is "
628+
" running, however MAKE SURE Redis is not publicly accessible "
629+
"from internet if you do so. Use CONFIG REWRITE to make this "
630+
"change permanent. "
631+
"2) Alternatively you can just disable the protected mode by "
632+
"editing the Redis configuration file, and setting the protected "
633+
"mode option to 'no', and then restarting the server. "
634+
"3) If you started the server manually just for testing, restart "
635+
"it with the '--portected-mode no' option. "
636+
"4) Setup a bind address or an authentication password. "
637+
"NOTE: You only need to do one of the above things in order for "
638+
"the server to start accepting connections from the outside.\r\n";
639+
if (write(c->fd,err,strlen(err)) == -1) {
640+
/* Nothing to do, Just to avoid the warning... */
641+
}
642+
server.stat_rejected_conn++;
643+
freeClient(c);
644+
return;
645+
}
646+
}
647+
606648
server.stat_numconnections++;
607649
c->flags |= flags;
608650
}
@@ -623,7 +665,7 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
623665
return;
624666
}
625667
serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
626-
acceptCommonHandler(cfd,0);
668+
acceptCommonHandler(cfd,0,cip);
627669
}
628670
}
629671

@@ -642,7 +684,7 @@ void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
642684
return;
643685
}
644686
serverLog(LL_VERBOSE,"Accepted connection to %s", server.unixsocket);
645-
acceptCommonHandler(cfd,CLIENT_UNIX_SOCKET);
687+
acceptCommonHandler(cfd,CLIENT_UNIX_SOCKET,NULL);
646688
}
647689
}
648690

src/server.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1457,6 +1457,7 @@ void initServerConfig(void) {
14571457
server.unixsocketperm = CONFIG_DEFAULT_UNIX_SOCKET_PERM;
14581458
server.ipfd_count = 0;
14591459
server.sofd = -1;
1460+
server.protected_mode = CONFIG_DEFAULT_PROTECTED_MODE;
14601461
server.dbnum = CONFIG_DEFAULT_DBNUM;
14611462
server.verbosity = CONFIG_DEFAULT_VERBOSITY;
14621463
server.maxidletime = CONFIG_DEFAULT_CLIENT_TIMEOUT;

src/server.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ typedef long long mstime_t; /* millisecond time type. */
111111
#define CONFIG_DEFAULT_DAEMONIZE 0
112112
#define CONFIG_DEFAULT_UNIX_SOCKET_PERM 0
113113
#define CONFIG_DEFAULT_TCP_KEEPALIVE 0
114+
#define CONFIG_DEFAULT_PROTECTED_MODE 1
114115
#define CONFIG_DEFAULT_LOGFILE ""
115116
#define CONFIG_DEFAULT_SYSLOG_ENABLED 0
116117
#define CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR 1
@@ -743,6 +744,7 @@ struct redisServer {
743744
char neterr[ANET_ERR_LEN]; /* Error buffer for anet.c */
744745
dict *migrate_cached_sockets;/* MIGRATE cached sockets */
745746
uint64_t next_client_id; /* Next client unique ID. Incremental. */
747+
int protected_mode; /* Don't accept external connections. */
746748
/* RDB / AOF loading information */
747749
int loading; /* We are loading data from disk if true */
748750
off_t loading_total_bytes;

0 commit comments

Comments
 (0)