tls: Limit concurrent connections

Resolves: COCKPIT-1905, RHEL-263070, RHEL-263066
This commit is contained in:
Marius Vollmer 2026-09-15 13:36:04 +03:00
parent 709aac3638
commit e12c37897d
5 changed files with 79 additions and 4 deletions

View file

@ -92,6 +92,12 @@ ForwardedForHeader = X-Forwarded-For
unauthenticated connections. The probability increases linearly and
all connection attempts are refused if the number of unauthenticated
connections reaches *full* (60).
*MaxConcurrentConnections*::
Limits the number of concurrent HTTP connections on the Cockpit port.
Connection attempts that exceed this limit are rejected early on
without reading a single byte. This can be used during brute-force
attacks. It differs from *MaxStartups*, which protects against too
many concurrent authentication attempts. Defaults to 500.
*AllowUnencrypted*::
If true, cockpit will accept unencrypted HTTP connections. Otherwise,
it redirects all HTTP connections to HTTPS. Exceptions are connections

View file

@ -94,7 +94,9 @@ main (int argc, char **argv)
if (!runtimedir)
errx (EXIT_FAILURE, "$RUNTIME_DIRECTORY environment variable must be set to a private directory");
server_init ("/run/cockpit/wsinstance", runtimedir, arguments.idle_timeout, arguments.port);
unsigned int max_connections = cockpit_conf_uint ("WebService", "MaxConcurrentConnections", 500, 10, UINT_MAX);
server_init ("/run/cockpit/wsinstance", runtimedir, arguments.idle_timeout, arguments.port, max_connections);
if (!arguments.no_tls)
{

View file

@ -37,6 +37,8 @@ static struct {
/* rw, protected by mutex */
pthread_mutex_t connection_mutex;
unsigned int connection_count;
unsigned int max_connection_count;
bool max_connection_warning_issued;
int idle_timerfd;
struct itimerspec idle_timeout;
} server;
@ -134,6 +136,26 @@ handle_accept (int listen_fd)
timerfd_settime (server.idle_timerfd, 0, &zero, NULL);
}
if (server.connection_count >= server.max_connection_count)
{
if (!server.max_connection_warning_issued)
{
warnx ("too many connections");
server.max_connection_warning_issued = true;
}
pthread_mutex_unlock (&server.connection_mutex);
// Send a immediate RST to the client to immediately free up
// our resources associated with the socket. This results in
// a "Connection reset" error on the other end, which is
// useful as well.
struct linger opt = { .l_onoff = 1, .l_linger = 0 };
setsockopt (fd, SOL_SOCKET, SO_LINGER, &opt, sizeof(opt));
close (fd);
return;
}
server.connection_count++;
debug (CONNECTION, " -> server.connection_count is now %i", server.connection_count);
@ -186,13 +208,15 @@ void
server_init (const char *wsinstance_sockdir,
const char *cert_session_dir,
int idle_timeout,
uint16_t port)
uint16_t port,
unsigned int max_connection_count)
{
const char *env_listen_fds;
struct epoll_event ev = { .events = EPOLLIN };
assert (!server.initialized);
server.initialized = true;
server.max_connection_count = max_connection_count;
server.idle_timerfd = -1;
connection_set_directories (wsinstance_sockdir, cert_session_dir);

View file

@ -14,7 +14,8 @@ void
server_init (const char *wsinstance_sockdir,
const char *cert_session_dir,
int idle_timeout,
uint16_t port);
uint16_t port,
unsigned int max_connections);
void
server_run (void);

View file

@ -61,8 +61,13 @@ typedef struct {
const char *client_fingerprint;
const char *priority;
int expected_pk_algo;
unsigned int max_connection_count;
} TestFixture;
static const TestFixture fixture_max_connection_count_10 = {
.max_connection_count = 10,
};
static const TestFixture fixture_separate_crt_key = {
.certfile = CERTFILE,
.keyfile = KEYFILE,
@ -468,8 +473,10 @@ setup (TestCase *tc, gconstpointer data)
}
close (socket_dir_fd);
unsigned max_connection_count = (fixture && fixture->max_connection_count != 0) ? fixture->max_connection_count : 500;
/* Let the kernel assign a port */
server_init (tc->ws_socket_dir, tc->runtime_dir, fixture ? fixture->idle_timeout : 0, 0);
server_init (tc->ws_socket_dir, tc->runtime_dir, fixture ? fixture->idle_timeout : 0, 0, max_connection_count);
if (fixture && fixture->certfile)
{
@ -671,6 +678,39 @@ test_no_tls_many_parallel (TestCase *tc, gconstpointer data)
}
}
static void
test_no_tls_too_many_parallel (TestCase *tc, gconstpointer data)
{
int i;
int fds[20];
const char *request = "GET / HTTP/1.0\r\nHost: localhost\r\n\r\n";
for (i = 0; i < 20; ++i)
{
fds[i] = do_connect (tc);
g_assert_cmpint (fds[i], >, 0);
server_poll_event (50);
}
for (i = 0; i < 20; ++i)
{
if (i < 10)
{
/* the first 10 are allowed to connect and will receive a reply */
do_request_fd (fds[i], request);
}
else
{
/* the rest gets disconnected immediately */
int r = write (fds[i], request, strlen (request));
g_assert (r == -1 && errno == ECONNRESET);
}
close(fds[i]);
}
}
static void
test_no_tls_redirect (TestCase *tc, gconstpointer data)
{
@ -970,6 +1010,8 @@ main (int argc, char *argv[])
setup, test_no_tls_many_serial, teardown);
g_test_add ("/server/no-tls/many-parallel", TestCase, NULL,
setup, test_no_tls_many_parallel, teardown);
g_test_add ("/server/no-tls/too-many-parallel", TestCase, &fixture_max_connection_count_10,
setup, test_no_tls_too_many_parallel, teardown);
g_test_add ("/server/no-tls/redirect", TestCase, NULL,
setup, test_no_tls_redirect, teardown);
g_test_add ("/server/tls/no-client-cert", TestCase, &fixture_separate_crt_key,