Skip to content

Commit c7ce4bf

Browse files
trondnjimwwalker
authored andcommitted
MB-50078: BP of M B 47707 - Enforce TLS
Backport of MB-47707. Close connections once the parent port gets deleted. Change-Id: I07cbd58100ebca4b80e3ce94f0306a5825e01b11 Reviewed-on: https://review.couchbase.org/c/kv_engine/+/167545 Well-Formed: Restriction Checker Reviewed-by: Jim Walker <[email protected]> Tested-by: Build Bot <[email protected]>
1 parent 515dce7 commit c7ce4bf

File tree

15 files changed

+222
-38
lines changed

15 files changed

+222
-38
lines changed

daemon/connection.cc

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ nlohmann::json Connection::toJSON() const {
150150
ret["protocol"] = "memcached";
151151
ret["peername"] = getPeername().c_str();
152152
ret["sockname"] = getSockname().c_str();
153-
ret["parent_port"] = parent_port;
153+
ret["parent_port"] = listening_port->port;
154154
ret["bucket_index"] = getBucketIndex();
155155
ret["internal"] = isInternal();
156156

@@ -308,6 +308,10 @@ cb::engine_errc Connection::dropPrivilege(cb::rbac::Privilege privilege) {
308308
return cb::engine_errc::no_access;
309309
}
310310

311+
in_port_t Connection::getParentPort() const {
312+
return listening_port->port;
313+
}
314+
311315
cb::rbac::PrivilegeAccess Connection::checkPrivilege(
312316
cb::rbac::Privilege privilege, Cookie& cookie) {
313317
cb::rbac::PrivilegeAccess ret;
@@ -1404,13 +1408,13 @@ Connection::Connection(FrontEndThread& thr)
14041408

14051409
Connection::Connection(SOCKET sfd,
14061410
event_base* b,
1407-
const ListeningPort& ifc,
1411+
std::shared_ptr<ListeningPort> ifc,
14081412
FrontEndThread& thr)
14091413
: socketDescriptor(sfd),
1410-
connectedToSystemPort(ifc.system),
1414+
connectedToSystemPort(ifc->system),
14111415
base(b),
14121416
thread(thr),
1413-
parent_port(ifc.port),
1417+
listening_port(std::move(ifc)),
14141418
peername(cb::net::getPeerNameAsJson(socketDescriptor).dump()),
14151419
sockname(cb::net::getSockNameAsJson(socketDescriptor).dump()),
14161420
stateMachine(*this),
@@ -1422,8 +1426,8 @@ Connection::Connection(SOCKET sfd,
14221426
msglist.reserve(MSG_LIST_INITIAL);
14231427
iov.resize(IOV_LIST_INITIAL);
14241428

1425-
if (ifc.isSslPort()) {
1426-
if (!enableSSL(ifc.sslCert, ifc.sslKey)) {
1429+
if (listening_port->isSslPort()) {
1430+
if (!enableSSL(listening_port->sslCert, listening_port->sslKey)) {
14271431
throw std::runtime_error(std::to_string(getId()) +
14281432
" Failed to enable SSL");
14291433
}
@@ -1621,6 +1625,57 @@ void Connection::runEventLoop(short which) {
16211625
conn_return_buffers(this);
16221626
}
16231627

1628+
void Connection::reEvaluateParentPort() {
1629+
if (listening_port->valid) {
1630+
return;
1631+
}
1632+
1633+
switch (getState()) {
1634+
case StateMachine::State::new_cmd:
1635+
case StateMachine::State::waiting:
1636+
case StateMachine::State::read_packet_header:
1637+
case StateMachine::State::parse_cmd:
1638+
case StateMachine::State::read_packet_body:
1639+
case StateMachine::State::validate:
1640+
case StateMachine::State::execute:
1641+
case StateMachine::State::send_data:
1642+
case StateMachine::State::ship_log:
1643+
break;
1644+
case StateMachine::State::closing:
1645+
case StateMachine::State::pending_close:
1646+
case StateMachine::State::immediate_close:
1647+
case StateMachine::State::destroyed:
1648+
return;
1649+
}
1650+
1651+
bool localhost = false;
1652+
if (Settings::instance().isLocalhostInterfaceWhitelisted()) {
1653+
// Make sure we don't tear down localhost connections
1654+
if (listening_port->family == AF_INET) {
1655+
localhost =
1656+
peername.find(R"("ip":"127.0.0.1")") != std::string::npos;
1657+
} else {
1658+
localhost = peername.find(R"("ip":"::1")") != std::string::npos;
1659+
}
1660+
}
1661+
1662+
if (localhost) {
1663+
LOG_INFO(
1664+
"{} Keeping connection alive even if server port was removed: "
1665+
"{}",
1666+
getId(),
1667+
getDescription());
1668+
} else {
1669+
LOG_INFO("{} Shutting down; server port was removed: {}",
1670+
getId(),
1671+
getDescription());
1672+
setTerminationReason("Server port shut down");
1673+
1674+
setState(StateMachine::State::closing);
1675+
signalIfIdle();
1676+
}
1677+
}
1678+
16241679
bool Connection::close() {
16251680
bool ewb = false;
16261681
uint32_t rc = refcount;

daemon/connection.h

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ class Connection : public dcp_message_producers {
9494

9595
Connection(SOCKET sfd,
9696
event_base* b,
97-
const ListeningPort& ifc,
97+
std::shared_ptr<ListeningPort> ifc,
9898
FrontEndThread& thr);
9999

100100
~Connection() override;
@@ -248,9 +248,7 @@ class Connection : public dcp_message_producers {
248248
return thread;
249249
}
250250

251-
in_port_t getParentPort() const {
252-
return parent_port;
253-
}
251+
in_port_t getParentPort() const;
254252

255253
/**
256254
* Check if this connection is in posession of the requested privilege
@@ -363,6 +361,10 @@ class Connection : public dcp_message_producers {
363361
return cb::engine_errc(remapErrorCode(ENGINE_ERROR_CODE(code)));
364362
}
365363

364+
/// Revaluate if the parent port is still valid or not (and if
365+
/// we should shut down the connection or not).
366+
void reEvaluateParentPort();
367+
366368
/**
367369
* Add the specified number of ns to the amount of CPU time this
368370
* connection have used on the CPU (We could alternatively have
@@ -1116,8 +1118,10 @@ class Connection : public dcp_message_producers {
11161118
/** Pointer to the thread object serving this connection */
11171119
FrontEndThread& thread;
11181120

1119-
/** Listening port that creates this connection instance */
1120-
const in_port_t parent_port{0};
1121+
/// The description of the listening port which accepted the client
1122+
/// (needed in order to shut down the connection if the administrator
1123+
/// disables the port)
1124+
std::shared_ptr<ListeningPort> listening_port;
11211125

11221126
/**
11231127
* The index of the connected bucket

daemon/connections.cc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ static void maybe_return_single_buffer(Connection& c,
6161
static void conn_destructor(Connection* c);
6262
static Connection* allocate_connection(SOCKET sfd,
6363
event_base* base,
64-
const ListeningPort& interface,
64+
std::shared_ptr<ListeningPort> interface,
6565
FrontEndThread& thread);
6666

6767
static void release_connection(Connection* c);
@@ -159,10 +159,10 @@ void run_event_loop(Connection* c, short which) {
159159
}
160160

161161
Connection* conn_new(SOCKET sfd,
162-
const ListeningPort& interface,
162+
std::shared_ptr<ListeningPort> interface,
163163
struct event_base* base,
164164
FrontEndThread& thread) {
165-
auto* c = allocate_connection(sfd, base, interface, thread);
165+
auto* c = allocate_connection(sfd, base, std::move(interface), thread);
166166
if (c == nullptr) {
167167
return nullptr;
168168
}
@@ -248,12 +248,12 @@ static void conn_destructor(Connection* c) {
248248
*/
249249
static Connection* allocate_connection(SOCKET sfd,
250250
event_base* base,
251-
const ListeningPort& interface,
251+
std::shared_ptr<ListeningPort> interface,
252252
FrontEndThread& thread) {
253253
Connection* ret = nullptr;
254254

255255
try {
256-
ret = new Connection(sfd, base, interface, thread);
256+
ret = new Connection(sfd, base, std::move(interface), thread);
257257
std::lock_guard<std::mutex> lock(connections.mutex);
258258
connections.conns.push_back(ret);
259259
stats.conn_structs++;

daemon/connections.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ void conn_return_buffers(Connection* c);
7070
* @return a connection object on success, nullptr otherwise
7171
*/
7272
Connection* conn_new(SOCKET sfd,
73-
const ListeningPort& interface,
73+
std::shared_ptr<ListeningPort> interface,
7474
struct event_base* base,
7575
FrontEndThread& thread);
7676

daemon/front_end_thread.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ class Connection;
4141
class ListeningPort;
4242
struct thread_stats;
4343

44-
using SharedListeningPort = std::shared_ptr<ListeningPort>;
45-
4644
struct FrontEndThread {
4745
/**
4846
* Pending IO requests for this thread. Maps each pending Connection to
@@ -87,12 +85,15 @@ struct FrontEndThread {
8785
class ConnectionQueue {
8886
public:
8987
~ConnectionQueue();
90-
void push(SOCKET socket, SharedListeningPort interface);
91-
void swap(std::vector<std::pair<SOCKET, SharedListeningPort>>& other);
88+
void push(SOCKET socket, std::shared_ptr<ListeningPort> interface);
89+
void swap(
90+
std::vector<std::pair<SOCKET, std::shared_ptr<ListeningPort>>>&
91+
other);
9292

9393
protected:
9494
std::mutex mutex;
95-
std::vector<std::pair<SOCKET, SharedListeningPort>> connections;
95+
std::vector<std::pair<SOCKET, std::shared_ptr<ListeningPort>>>
96+
connections;
9697
} new_conn_queue;
9798

9899
/// Mutex to lock protect access to this object.

daemon/listening_port.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#pragma once
1919

2020
#include <platform/socket.h>
21+
#include <atomic>
2122
#include <string>
2223
#include <utility>
2324

@@ -46,8 +47,6 @@ class ListeningPort {
4647
sslCert(std::move(cert)) {
4748
}
4849

49-
ListeningPort(const ListeningPort& other) = default;
50-
5150
/// The tag provided by the user to identify the port. It is possible
5251
/// to use ephemeral ports in the system, and if we want to change
5352
/// such ports at runtime the system needs a way to find the correct
@@ -84,4 +83,7 @@ class ListeningPort {
8483
bool isSslPort() const {
8584
return !sslKey.empty() && !sslCert.empty();
8685
}
86+
87+
/// Set to false once the interface is being shut down
88+
std::atomic_bool valid{true};
8789
};

daemon/memcached.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,7 @@ static void dispatch_event_handler(evutil_socket_t fd, short, void *) {
887887

888888
bool changes = false;
889889
auto interfaces = Settings::instance().getInterfaces();
890+
bool interfaces_dropped = false;
890891

891892
// Step one, enable all new ports
892893
bool success = true;
@@ -982,6 +983,7 @@ static void dispatch_event_handler(evutil_socket_t fd, short, void *) {
982983
// erase returns the element following this one (or end())
983984
changes = true;
984985
iter = listen_conn.erase(iter);
986+
interfaces_dropped = true;
985987
} else {
986988
// look at the next element
987989
++iter;
@@ -992,6 +994,11 @@ static void dispatch_event_handler(evutil_socket_t fd, short, void *) {
992994
if (changes) {
993995
create_portnumber_file(false);
994996
}
997+
998+
if (interfaces_dropped) {
999+
iterate_all_connections(
1000+
[](auto& conn) { conn.reEvaluateParentPort(); });
1001+
}
9951002
}
9961003

9971004
if (is_listen_disabled()) {

daemon/memcached.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ void threads_shutdown();
7979
void threads_cleanup();
8080

8181
class ListeningPort;
82-
void dispatch_conn_new(SOCKET sfd, std::shared_ptr<ListeningPort>& interface);
82+
void dispatch_conn_new(SOCKET sfd, std::shared_ptr<ListeningPort> interface);
8383

8484
/* Lock wrappers for cache functions that are called from main loop. */
8585
int is_listen_thread(void);

daemon/server_socket.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ ServerSocket::ServerSocket(SOCKET fd,
5454
}
5555

5656
ServerSocket::~ServerSocket() {
57+
interface->valid.store(false, std::memory_order_release);
5758
std::string tagstr;
5859
if (!interface->tag.empty()) {
5960
tagstr = " \"" + interface->tag + "\"";

daemon/settings.cc

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,11 @@ static void handle_opentracing(Settings& s, const nlohmann::json& obj) {
686686
s.setOpenTracingConfig(std::make_shared<OpenTracingConfig>(obj));
687687
}
688688

689+
static void handle_whitelist_localhost_interface(Settings& s,
690+
const nlohmann::json& obj) {
691+
s.setWhitelistLocalhostInterface(obj.get<bool>());
692+
}
693+
689694
void Settings::reconfigure(const nlohmann::json& json) {
690695
// Nuke the default interface added to the system in settings_init and
691696
// use the ones in the configuration file.. (this is a bit messy)
@@ -757,7 +762,9 @@ void Settings::reconfigure(const nlohmann::json& json) {
757762
handle_max_concurrent_commands_per_connection},
758763
{"opentracing", handle_opentracing},
759764
{"portnumber_file", handle_portnumber_file},
760-
{"parent_identifier", handle_parent_identifier}};
765+
{"parent_identifier", handle_parent_identifier},
766+
{"whitelist_localhost_interface",
767+
handle_whitelist_localhost_interface}};
761768

762769
for (const auto& obj : json.items()) {
763770
bool found = false;
@@ -1243,6 +1250,19 @@ void Settings::updateSettings(const Settings& other, bool apply) {
12431250
}
12441251
}
12451252

1253+
if (other.has.whitelist_localhost_interface) {
1254+
if (other.whitelist_localhost_interface !=
1255+
whitelist_localhost_interface) {
1256+
LOG_INFO(
1257+
R"(Change whitelist of localhost interface from "{}" to "{}")",
1258+
isLocalhostInterfaceWhitelisted() ? "enabled" : "disabled",
1259+
other.isLocalhostInterfaceWhitelisted() ? "enabled"
1260+
: "disabled");
1261+
setWhitelistLocalhostInterface(
1262+
other.isLocalhostInterfaceWhitelisted());
1263+
}
1264+
}
1265+
12461266
if (other.has.max_concurrent_commands_per_connection) {
12471267
if (other.getMaxConcurrentCommandsPerConnection() !=
12481268
getMaxConcurrentCommandsPerConnection()) {

0 commit comments

Comments
 (0)