diff --git a/redis.conf b/redis.conf index c3842af45..9a6e52a44 100644 --- a/redis.conf +++ b/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 diff --git a/src/config.c b/src/config.c index 62f67b669..2117758a5 100644 --- a/src/config.c +++ b/src/config.c @@ -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); diff --git a/src/networking.c b/src/networking.c index 047e8d229..dba9e7cfe 100644 --- a/src/networking.c +++ b/src/networking.c @@ -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); } } diff --git a/src/server.c b/src/server.c index b96c4fe7d..e39ee4c74 100644 --- a/src/server.c +++ b/src/server.c @@ -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; diff --git a/src/server.h b/src/server.h index 226066429..2ec33f507 100644 --- a/src/server.h +++ b/src/server.h @@ -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;