tls: Use socket activation instead of spawning
Instead of fork()/exec()ing the cockpit-ws instances for ourselves, simply connect to the sockets that systemd is now creating for us. In addition to shrinking the code by about 600 lines, this has the following advantages: - the cockpit-ws instances are now running as a separate user for improved isolation - the cockpit-ws instances are now also each in their own cgroup - there used to be a race where we would drop an incoming web connection if we attempted to connect to a cockpit-ws instance as it was exiting due to inactivity. systemd socket activation avoids this. Use the socket-activation-helper to tweak the tests to adjust to the changes. This temporarily drops the per-client-certificate ws instances, there is only one https instance for now; this is not a practical regression as we don't support client certificates yet. This will be brought back separately.
This commit is contained in:
parent
777c59095a
commit
708671171d
|
@ -107,8 +107,8 @@ depcomp
|
|||
/mock/
|
||||
*.min.html
|
||||
/src/ws/cockpit.appdata.xml
|
||||
/src/ws/cockpit.service
|
||||
/src/ws/cockpit.socket
|
||||
/src/ws/cockpit*.service
|
||||
/src/ws/cockpit*.socket
|
||||
/src/ws/cockpit-tempfiles.conf
|
||||
src/common/cockpitassets.c
|
||||
src/common/cockpitassets.h
|
||||
|
|
|
@ -119,6 +119,8 @@ SUBST_RULE = \
|
|||
-e 's,[@]SUDO[@],$(SUDO),g' \
|
||||
-e 's,[@]user[@],$(COCKPIT_USER),g' \
|
||||
-e 's,[@]group[@],$(COCKPIT_GROUP),g' \
|
||||
-e 's,[@]wsinstanceuser[@],$(COCKPIT_WSINSTANCE_USER),g' \
|
||||
-e 's,[@]wsinstancegroup[@],$(COCKPIT_WSINSTANCE_GROUP),g' \
|
||||
-e 's,[@]selinux_config_type[@],$(COCKPIT_SELINUX_CONFIG_TYPE),g' \
|
||||
-e 's,[@]with_networkmanager_needs_root[@],$(with_networkmanager_needs_root),g' \
|
||||
-e 's,[@]with_storaged_iscsi_sessions[@],$(with_storaged_iscsi_sessions),g' \
|
||||
|
|
35
configure.ac
35
configure.ac
|
@ -410,7 +410,7 @@ fi
|
|||
AM_CONDITIONAL(WITH_ASAN, test "$enable_asan" = "yes")
|
||||
AC_MSG_RESULT($asan_status)
|
||||
|
||||
# User and group for running cockpit-ws
|
||||
# User and group for running cockpit web server (cockpit-tls or -ws in customized setups)
|
||||
|
||||
AC_ARG_WITH(cockpit_user,
|
||||
AS_HELP_STRING([--with-cockpit-user=<user>],
|
||||
|
@ -437,6 +437,35 @@ fi
|
|||
AC_SUBST(COCKPIT_USER)
|
||||
AC_SUBST(COCKPIT_GROUP)
|
||||
|
||||
# User for running cockpit-ws instances from cockpit-tls
|
||||
|
||||
AC_ARG_WITH(cockpit_ws_instance_user,
|
||||
AS_HELP_STRING([--with-cockpit-ws-instance-user=<user>],
|
||||
[User for running cockpit-ws instances from cockpit-tls (root)]
|
||||
)
|
||||
)
|
||||
AC_ARG_WITH(cockpit_ws_instance_group,
|
||||
AS_HELP_STRING([--with-cockpit-ws-instance-group=<group>],
|
||||
[Group for running cockpit-ws instances from cockpit-tls]
|
||||
)
|
||||
)
|
||||
if test -z "$with_cockpit_ws_instance_user"; then
|
||||
if test "$COCKPIT_USER" != "root"; then
|
||||
AC_MSG_ERROR([--with-cockpit-ws-instance-user is required when setting --with-cockpit-user])
|
||||
fi
|
||||
COCKPIT_WSINSTANCE_USER=root
|
||||
else
|
||||
COCKPIT_WSINSTANCE_USER=$with_cockpit_ws_instance_user
|
||||
if test -z "$with_cockpit_ws_instance_group"; then
|
||||
COCKPIT_WSINSTANCE_GROUP=$with_cockpit_ws_instance_user
|
||||
else
|
||||
COCKPIT_WSINSTANCE_GROUP=$with_cockpit_ws_instance_group
|
||||
fi
|
||||
fi
|
||||
|
||||
AC_SUBST(COCKPIT_WSINSTANCE_USER)
|
||||
AC_SUBST(COCKPIT_WSINSTANCE_GROUP)
|
||||
|
||||
AC_ARG_WITH(selinux_config_type,
|
||||
AS_HELP_STRING([--with-selinux-config-type=<type>],
|
||||
[SELinux context type for cockpit config files]
|
||||
|
@ -586,6 +615,8 @@ echo "
|
|||
|
||||
cockpit-ws user: ${COCKPIT_USER}
|
||||
cockpit-ws group: ${COCKPIT_GROUP}
|
||||
cockpit-ws instance user: ${COCKPIT_WSINSTANCE_USER}
|
||||
cockpit-ws instance group: ${COCKPIT_WSINSTANCE_GROUP}
|
||||
selinux config type: ${COCKPIT_SELINUX_CONFIG_TYPE}
|
||||
|
||||
Maintainer mode: ${USE_MAINTAINER_MODE}
|
||||
|
@ -595,7 +626,7 @@ echo "
|
|||
With coverage: ${enable_coverage}
|
||||
With address sanitizer: ${asan_status}
|
||||
With PCP: ${enable_pcp}
|
||||
Branding: ${BRAND}
|
||||
Branding: ${BRAND}
|
||||
|
||||
cockpit-ssh: ${enable_ssh}
|
||||
Supports key auth: ${key_auth}
|
||||
|
|
|
@ -2,8 +2,6 @@ libexec_PROGRAMS += cockpit-tls
|
|||
|
||||
cockpit_tls_SOURCES = \
|
||||
src/tls/utils.h \
|
||||
src/tls/wsinstance.h \
|
||||
src/tls/wsinstance.c \
|
||||
src/tls/connection.h \
|
||||
src/tls/connection.c \
|
||||
src/tls/server.h \
|
||||
|
@ -34,7 +32,6 @@ TEST_LDADD = \
|
|||
|
||||
TLS_TESTS = \
|
||||
test-tls-connection \
|
||||
test-tls-wsinstance \
|
||||
test-tls-server \
|
||||
$(NULL)
|
||||
|
||||
|
@ -50,16 +47,7 @@ test_tls_connection_SOURCES = \
|
|||
test_tls_connection_CFLAGS = $(TEST_CFLAGS)
|
||||
test_tls_connection_LDADD = $(TEST_LDADD)
|
||||
|
||||
test_tls_wsinstance_SOURCES = \
|
||||
src/tls/wsinstance.c \
|
||||
src/tls/test-wsinstance.c \
|
||||
$(NULL)
|
||||
|
||||
test_tls_wsinstance_CFLAGS = $(TEST_CFLAGS)
|
||||
test_tls_wsinstance_LDADD = $(TEST_LDADD)
|
||||
|
||||
test_tls_server_SOURCES = \
|
||||
src/tls/wsinstance.c \
|
||||
src/tls/connection.c \
|
||||
src/tls/server.c \
|
||||
src/tls/test-server.c \
|
||||
|
|
|
@ -40,6 +40,7 @@ connection_new (int client_fd)
|
|||
con->client_fd = client_fd;
|
||||
con->buf_client.connection = con;
|
||||
con->buf_ws.connection = con;
|
||||
con->ws_fd = -1;
|
||||
|
||||
debug (CONNECTION, "new connection on fd %i", con->client_fd);
|
||||
return con;
|
||||
|
|
|
@ -20,8 +20,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "wsinstance.h"
|
||||
#include <gnutls/gnutls.h>
|
||||
|
||||
typedef enum { CLIENT, WS } DataSource;
|
||||
typedef enum { SUCCESS, PARTIAL, CLOSED, RETRY, FATAL } ConnectionResult;
|
||||
|
@ -41,7 +40,6 @@ struct Connection {
|
|||
int client_fd;
|
||||
bool is_tls;
|
||||
gnutls_session_t session;
|
||||
WsInstance *ws;
|
||||
int ws_fd;
|
||||
struct ConnectionBuffer buf_client;
|
||||
struct ConnectionBuffer buf_ws;
|
||||
|
|
|
@ -30,8 +30,6 @@
|
|||
#include "utils.h"
|
||||
#include "server.h"
|
||||
|
||||
#define COCKPIT_WS PACKAGE_LIBEXEC_DIR "/cockpit-ws"
|
||||
|
||||
/* CLI arguments */
|
||||
struct arguments {
|
||||
uint16_t port;
|
||||
|
@ -112,7 +110,7 @@ main (int argc, char **argv)
|
|||
}
|
||||
|
||||
/* TODO: Add cockpit.conf option to enable client-certificate auth, once we support that */
|
||||
server_init (COCKPIT_WS, arguments.port, certfile, NULL, CERT_NONE);
|
||||
server_init ("/run/cockpit/wsinstance", arguments.port, certfile, NULL, CERT_NONE);
|
||||
free (certfile);
|
||||
|
||||
server_run (arguments.idle_timeout);
|
||||
|
|
242
src/tls/server.c
242
src/tls/server.c
|
@ -36,7 +36,6 @@
|
|||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <gnutls/gnutls.h>
|
||||
|
@ -45,7 +44,6 @@
|
|||
#include <common/cockpitmemory.h>
|
||||
#include <common/cockpitwebcertificate.h>
|
||||
#include "utils.h"
|
||||
#include "wsinstance.h"
|
||||
#include "connection.h"
|
||||
|
||||
#include "server.h"
|
||||
|
@ -56,16 +54,12 @@
|
|||
static struct {
|
||||
bool initialized;
|
||||
const char *ws_path;
|
||||
const char *state_dir;
|
||||
enum ClientCertMode client_cert_mode;
|
||||
int listen_fds[MAX_LISTEN_FDS];
|
||||
gnutls_certificate_credentials_t x509_cred;
|
||||
gnutls_priority_t priority_cache;
|
||||
Connection *connections;
|
||||
WsInstance *wss; /* cockpit-ws instances, one for each client certificate */
|
||||
WsInstance *ws_notls; /* cockpit-ws instance for unencrypted HTTP */
|
||||
int epollfd;
|
||||
struct sigaction old_sigchld;
|
||||
} server;
|
||||
|
||||
/***********************************
|
||||
|
@ -87,40 +81,6 @@ static struct {
|
|||
rval = cmd; \
|
||||
} while(rval == GNUTLS_E_AGAIN || rval == GNUTLS_E_INTERRUPTED)
|
||||
|
||||
static int cleanup_children_eventfd = -1;
|
||||
|
||||
static void
|
||||
handle_sigchld (int signal)
|
||||
{
|
||||
const uint64_t one = 1;
|
||||
ssize_t s;
|
||||
|
||||
s = write (cleanup_children_eventfd, &one, sizeof one);
|
||||
assert (s == sizeof one);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_child_exit (void)
|
||||
{
|
||||
uint64_t value;
|
||||
ssize_t s;
|
||||
|
||||
debug (SERVER, "got SIGCHLD");
|
||||
s = read (cleanup_children_eventfd, &value, sizeof value);
|
||||
assert (s == sizeof value);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
int status;
|
||||
pid_t pid = waitpid (-1, &status, WNOHANG);
|
||||
if (pid <= 0)
|
||||
break;
|
||||
|
||||
debug (SERVER, "pid %u exited with status %x", pid, status);
|
||||
server_remove_ws (pid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check_sd_listen_pid: Verify that systemd-activated socket is for us
|
||||
*
|
||||
|
@ -161,7 +121,7 @@ check_sd_listen_pid (void)
|
|||
* @ws: If given, all connections related to this #WsInstance get removed
|
||||
*/
|
||||
static void
|
||||
remove_connection (int fd, WsInstance *ws)
|
||||
remove_connection (int fd)
|
||||
{
|
||||
Connection *c, *cprev;
|
||||
bool found = false;
|
||||
|
@ -170,12 +130,12 @@ remove_connection (int fd, WsInstance *ws)
|
|||
{
|
||||
Connection *cnext = c->next;
|
||||
|
||||
if ( (fd > 0 && (c->client_fd == fd || c->ws_fd == fd)) || (ws && c->ws == ws) )
|
||||
if (fd > 0 && (c->client_fd == fd || c->ws_fd == fd))
|
||||
{
|
||||
/* stop polling it */
|
||||
if (epoll_ctl (server.epollfd, EPOLL_CTL_DEL, c->client_fd, NULL) < 0)
|
||||
err (1, "Failed to remove epoll connection fd");
|
||||
if (c->ws_fd)
|
||||
if (c->ws_fd != -1)
|
||||
{
|
||||
if (epoll_ctl (server.epollfd, EPOLL_CTL_DEL, c->ws_fd, NULL) < 0)
|
||||
err (1, "Failed to remove epoll connection ws fd");
|
||||
|
@ -197,7 +157,7 @@ remove_connection (int fd, WsInstance *ws)
|
|||
}
|
||||
|
||||
if (!found)
|
||||
debug (CONNECTION, "remove_connection: fd %i or ws %s not found in connections", fd, ws ? ws->socket.sun_path : "(unset)");
|
||||
debug (CONNECTION, "remove_connection: fd %i not found in connections", fd);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -211,70 +171,38 @@ static void
|
|||
connection_init_ws (Connection *c)
|
||||
{
|
||||
int fd;
|
||||
const gnutls_datum_t *peer_der = NULL;
|
||||
WsInstance *ws = NULL;
|
||||
bool ws_add_tls = false;
|
||||
bool ws_add_notls = false;
|
||||
struct epoll_event ev = { .events = EPOLLIN };
|
||||
struct sockaddr_un sockaddr = { .sun_family = AF_UNIX };
|
||||
const char *sockname;
|
||||
int r;
|
||||
|
||||
if (c->is_tls)
|
||||
{
|
||||
peer_der = gnutls_certificate_get_peers (c->session, NULL);
|
||||
|
||||
/* find existing ws server for this peer cert */
|
||||
for (ws = server.wss; ws; ws = ws->next)
|
||||
if (ws_instance_has_peer_cert (ws, peer_der))
|
||||
break;
|
||||
|
||||
if (!ws)
|
||||
{
|
||||
ws = ws_instance_new (server.ws_path, WS_INSTANCE_HTTPS, peer_der, server.state_dir);
|
||||
ws_add_tls = true;
|
||||
}
|
||||
}
|
||||
sockname = "https";
|
||||
else
|
||||
{
|
||||
ws = server.ws_notls;
|
||||
if (!ws)
|
||||
{
|
||||
debug (CONNECTION, "initializing no-TLS cockpit-ws instance");
|
||||
ws = ws_instance_new (server.ws_path,
|
||||
server.x509_cred ? WS_INSTANCE_HTTP_REDIRECT : WS_INSTANCE_HTTP,
|
||||
NULL, server.state_dir);
|
||||
ws_add_notls = true;
|
||||
}
|
||||
}
|
||||
sockname = server.x509_cred ? "http-redirect" : "http";
|
||||
|
||||
debug (CONNECTION, "connection_init_ws: assigned ws %s", ws->socket.sun_path);
|
||||
r = snprintf (sockaddr.sun_path, sizeof sockaddr.sun_path, "%s/%s.sock", server.ws_path, sockname);
|
||||
assert (r < sizeof sockaddr.sun_path);
|
||||
|
||||
debug (CONNECTION, "connection_init_ws: assigned ws %s", sockaddr.sun_path);
|
||||
|
||||
/* connect to ws instance */
|
||||
fd = socket (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
if (fd < 0)
|
||||
err (1, "failed to create cockpit-ws client socket");
|
||||
if (connect (fd, (struct sockaddr *) &ws->socket, sizeof (ws->socket)) < 0)
|
||||
if (connect (fd, (struct sockaddr *) &sockaddr, sizeof sockaddr) < 0)
|
||||
{
|
||||
/* cockpit-ws crashed? */
|
||||
warn ("failed to connect to cockpit-ws");
|
||||
ws_instance_free (ws, true);
|
||||
return;
|
||||
}
|
||||
|
||||
/* connected, so it's valid; add it to our ws list */
|
||||
if (ws_add_tls)
|
||||
{
|
||||
ws->next = server.wss;
|
||||
server.wss = ws;
|
||||
}
|
||||
if (ws_add_notls)
|
||||
server.ws_notls = ws;
|
||||
|
||||
/* epoll the fd */
|
||||
ev.data.ptr = &c->buf_ws;
|
||||
if (epoll_ctl (server.epollfd, EPOLL_CTL_ADD, fd, &ev) < 0)
|
||||
err (1, "Failed to epoll cockpit-ws client fd");
|
||||
|
||||
c->ws_fd = fd;
|
||||
c->ws = ws;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -365,7 +293,7 @@ handle_connection_data_first (Connection *con)
|
|||
char b;
|
||||
int ret;
|
||||
|
||||
assert (!con->ws);
|
||||
assert (con->ws_fd == -1);
|
||||
|
||||
/* peek the first byte and see if it's a TLS connection (starting with 22).
|
||||
We can assume that there is some data to read, as this is called in response
|
||||
|
@ -376,7 +304,7 @@ handle_connection_data_first (Connection *con)
|
|||
if (ret == 0) /* EOF */
|
||||
{
|
||||
debug (CONNECTION, "client disconnected without sending any data");
|
||||
remove_connection (con->client_fd, NULL);
|
||||
remove_connection (con->client_fd);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -389,7 +317,7 @@ handle_connection_data_first (Connection *con)
|
|||
if (!server.x509_cred)
|
||||
{
|
||||
warnx ("got TLS connection, but our server does not have a certificate/key; refusing");
|
||||
remove_connection (con->client_fd, NULL);
|
||||
remove_connection (con->client_fd);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -411,7 +339,7 @@ handle_connection_data_first (Connection *con)
|
|||
if (ret < 0)
|
||||
{
|
||||
warnx ("TLS handshake failed: %s", gnutls_strerror (ret));
|
||||
remove_connection (con->client_fd, NULL);
|
||||
remove_connection (con->client_fd);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -419,8 +347,8 @@ handle_connection_data_first (Connection *con)
|
|||
}
|
||||
|
||||
connection_init_ws (con);
|
||||
if (!con->ws)
|
||||
remove_connection (con->client_fd, NULL);
|
||||
if (con->ws_fd == -1)
|
||||
remove_connection (con->client_fd);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -439,13 +367,12 @@ handle_connection_data (struct ConnectionBuffer *buf)
|
|||
ConnectionResult r;
|
||||
|
||||
assert (con);
|
||||
debug (CONNECTION, "%s connection fd %i has data from %s; ws %s",
|
||||
debug (CONNECTION, "%s connection fd %i has data from %s",
|
||||
con->is_tls ? "TLS" : "unencrypted", con->client_fd,
|
||||
src == WS ? "ws" : "client",
|
||||
con->ws ? con->ws->socket.sun_path : "uninitialized");
|
||||
src == WS ? "ws" : "client");
|
||||
|
||||
/* first data on a new connection; determine if TLS, init TLS, and assign a ws */
|
||||
if (!con->ws)
|
||||
if (con->ws_fd == -1)
|
||||
{
|
||||
assert (src == CLIENT);
|
||||
handle_connection_data_first (con);
|
||||
|
@ -465,7 +392,7 @@ handle_connection_data (struct ConnectionBuffer *buf)
|
|||
}
|
||||
|
||||
if (r != SUCCESS)
|
||||
remove_connection (con->client_fd, NULL);
|
||||
remove_connection (con->client_fd);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -474,7 +401,7 @@ handle_hangup (struct ConnectionBuffer *buf)
|
|||
Connection *con = buf->connection;
|
||||
int fd = buf == &con->buf_client ? con->client_fd : con->ws_fd;
|
||||
debug (CONNECTION, "hangup on fd %i", fd);
|
||||
remove_connection (fd, NULL);
|
||||
remove_connection (fd);
|
||||
}
|
||||
|
||||
/***********************************
|
||||
|
@ -489,7 +416,7 @@ handle_hangup (struct ConnectionBuffer *buf)
|
|||
* There is only one instance of this. Trying to initialize it more than once
|
||||
* is an error.
|
||||
*
|
||||
* @ws_path: Path to cockpit-ws binary
|
||||
* @ws_path: Path to cockpit-wsinstance sockets directory
|
||||
* @port: Port to listen to; ignored when the listening socket is handed over
|
||||
* through the systemd socket activation protocol
|
||||
* @certfile: Server TLS certificate file; if %NULL, TLS is not supported.
|
||||
|
@ -507,21 +434,12 @@ server_init (const char *ws_path,
|
|||
int ret;
|
||||
const char *env_listen_fds;
|
||||
struct epoll_event ev = { .events = EPOLLIN };
|
||||
const struct sigaction child_action = {
|
||||
.sa_handler = handle_sigchld,
|
||||
.sa_flags = SA_NOCLDSTOP
|
||||
};
|
||||
|
||||
assert (!server.initialized);
|
||||
|
||||
server.ws_path = ws_path;
|
||||
server.client_cert_mode = client_cert_mode;
|
||||
|
||||
/* Initialize state dir for ws instances; $RUNTIME_DIRECTORY is set by systemd's RuntimeDirectory=, or by tests */
|
||||
server.state_dir = secure_getenv ("RUNTIME_DIRECTORY");
|
||||
if (!server.state_dir)
|
||||
err (1, "$RUNTIME_DIRECTORY environment variable must be set to a private directory");
|
||||
|
||||
/* Initialize TLS */
|
||||
if (certfile)
|
||||
{
|
||||
|
@ -607,18 +525,6 @@ server_init (const char *ws_path,
|
|||
err (1, "Failed to epoll server listening fd");
|
||||
}
|
||||
|
||||
/* track cockpit-ws children */
|
||||
cleanup_children_eventfd = eventfd (0, EFD_NONBLOCK | EFD_CLOEXEC);
|
||||
if (cleanup_children_eventfd == -1)
|
||||
err (1, "failed to create eventfd");
|
||||
|
||||
ev.data.ptr = handle_child_exit;
|
||||
if (epoll_ctl (server.epollfd, EPOLL_CTL_ADD, cleanup_children_eventfd, &ev) < 0)
|
||||
err (1, "Failed to epoll add sigchld handler fd");
|
||||
|
||||
if (sigaction (SIGCHLD, &child_action, &server.old_sigchld) < 0)
|
||||
err (1, "Failed to set up SIGCHLD handler");
|
||||
|
||||
server.initialized = true;
|
||||
}
|
||||
|
||||
|
@ -637,12 +543,6 @@ server_cleanup (void)
|
|||
|
||||
assert (server.initialized);
|
||||
|
||||
if (sigaction (SIGCHLD, &server.old_sigchld, NULL) < 0)
|
||||
err (1, "Failed to reset SIGCHLD handler");
|
||||
|
||||
close (cleanup_children_eventfd);
|
||||
cleanup_children_eventfd = -1;
|
||||
|
||||
for (Connection *c = server.connections; c; )
|
||||
{
|
||||
Connection *cnext = c->next;
|
||||
|
@ -650,15 +550,6 @@ server_cleanup (void)
|
|||
c = cnext;
|
||||
}
|
||||
|
||||
for (WsInstance *ws = server.wss; ws; )
|
||||
{
|
||||
WsInstance *wsnext = ws->next;
|
||||
ws_instance_free (ws, true);
|
||||
ws = wsnext;
|
||||
}
|
||||
if (server.ws_notls)
|
||||
ws_instance_free (server.ws_notls, true);
|
||||
|
||||
if (server.x509_cred)
|
||||
{
|
||||
gnutls_certificate_free_credentials (server.x509_cred);
|
||||
|
@ -698,8 +589,6 @@ server_poll_event (int timeout)
|
|||
{
|
||||
if (ev.data.ptr >= (void *) server.listen_fds && ev.data.ptr < (void *) &server.listen_fds[MAX_LISTEN_FDS])
|
||||
handle_accept (* ((int *) ev.data.ptr));
|
||||
else if (ev.data.ptr == handle_child_exit)
|
||||
handle_child_exit ();
|
||||
else if (ev.events & EPOLLIN)
|
||||
handle_connection_data (ev.data.ptr);
|
||||
else if (ev.events & EPOLLHUP)
|
||||
|
@ -739,54 +628,6 @@ server_run (int idle_timeout)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* server_remove_ws: Clean up #WsInstance
|
||||
*
|
||||
* This should be called in response to a SIGCHLD signal, i. e. when a
|
||||
* cockpit-ws terminates. This also terminates and cleans up all Connections
|
||||
* from this cockpit-ws instance.
|
||||
*/
|
||||
void
|
||||
server_remove_ws (pid_t ws_pid)
|
||||
{
|
||||
WsInstance *ws = NULL;
|
||||
|
||||
assert (server.initialized);
|
||||
|
||||
/* find the WsInstance of that pid */
|
||||
if (server.ws_notls && server.ws_notls->pid == ws_pid)
|
||||
{
|
||||
ws = server.ws_notls;
|
||||
server.ws_notls = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
WsInstance *wsprev = NULL;
|
||||
for (ws = server.wss; ws; wsprev = ws, ws = ws->next)
|
||||
{
|
||||
if (ws->pid == ws_pid)
|
||||
{
|
||||
if (wsprev == NULL) /* first ws */
|
||||
server.wss = ws->next;
|
||||
else
|
||||
wsprev->next = ws->next;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ws)
|
||||
{
|
||||
warnx ("server_remove_ws: pid %u not found in our ws instances", ws_pid);
|
||||
return;
|
||||
}
|
||||
|
||||
debug (SERVER, "server_remove_ws: pid %u is ws %s", ws_pid, ws->socket.sun_path);
|
||||
|
||||
remove_connection (-1, ws);
|
||||
ws_instance_free (ws, false);
|
||||
}
|
||||
|
||||
unsigned
|
||||
server_num_connections (void)
|
||||
{
|
||||
|
@ -796,34 +637,3 @@ server_num_connections (void)
|
|||
count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
unsigned
|
||||
server_num_ws (void)
|
||||
{
|
||||
unsigned count = 0;
|
||||
|
||||
for (WsInstance *ws = server.wss; ws; ws = ws->next)
|
||||
count++;
|
||||
if (server.ws_notls)
|
||||
count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t
|
||||
server_get_ws_pids (pid_t* pids, size_t pids_length)
|
||||
{
|
||||
size_t num = 0;
|
||||
if (server.ws_notls)
|
||||
{
|
||||
assert (pids_length > num);
|
||||
pids[num++] = server.ws_notls->pid;
|
||||
}
|
||||
|
||||
for (WsInstance *ws = server.wss; ws; ws = ws->next)
|
||||
{
|
||||
assert (pids_length > num);
|
||||
pids[num++] = ws->pid;
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
|
|
|
@ -49,8 +49,7 @@ test_no_ws (void)
|
|||
|
||||
/* other fields are clear */
|
||||
g_assert (!c->is_tls);
|
||||
g_assert (c->ws == NULL);
|
||||
g_assert_cmpint (c->ws_fd, ==, 0);
|
||||
g_assert_cmpint (c->ws_fd, ==, -1);
|
||||
|
||||
connection_free (c);
|
||||
/* closes fd */
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "config.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
@ -34,6 +35,7 @@
|
|||
#include "server.h"
|
||||
#include "common/cockpittest.h"
|
||||
|
||||
#define SOCKET_ACTIVATION_HELPER BUILDDIR "/socket-activation-helper"
|
||||
#define COCKPIT_WS BUILDDIR "/cockpit-ws"
|
||||
#define CERTFILE SRCDIR "/src/bridge/mock-server.crt"
|
||||
#define KEYFILE SRCDIR "/src/bridge/mock-server.key"
|
||||
|
@ -47,7 +49,8 @@
|
|||
const unsigned server_port = 9123;
|
||||
|
||||
typedef struct {
|
||||
gchar *state_dir;
|
||||
gchar *ws_socket_dir;
|
||||
GPid ws_spawner;
|
||||
struct sockaddr_in server_addr;
|
||||
} TestCase;
|
||||
|
||||
|
@ -159,7 +162,7 @@ assert_https_outcome (TestCase *tc,
|
|||
bool expect_tls_failure)
|
||||
{
|
||||
pid_t pid;
|
||||
int status;
|
||||
int status = -1;
|
||||
|
||||
block_sigchld ();
|
||||
|
||||
|
@ -242,7 +245,7 @@ assert_https_outcome (TestCase *tc,
|
|||
exit (0);
|
||||
}
|
||||
|
||||
while (waitpid (pid, &status, WNOHANG) <= 0)
|
||||
for (int retry = 0; retry < 100 && waitpid (pid, &status, WNOHANG) <= 0; ++retry)
|
||||
server_poll_event (50);
|
||||
g_assert_cmpint (status, ==, 0);
|
||||
}
|
||||
|
@ -253,40 +256,30 @@ assert_https (TestCase *tc, const char *client_crt, const char *client_key, unsi
|
|||
assert_https_outcome (tc, client_crt, client_key, expected_server_certs, false);
|
||||
}
|
||||
|
||||
/* Ensure that all ws instances have no blocked signals inherited from cockpit-tls */
|
||||
static void
|
||||
assert_children_signals (void)
|
||||
{
|
||||
/* only use that for tests with a small number of ws instances */
|
||||
const size_t max_ws = 5;
|
||||
pid_t ws_pids[max_ws];
|
||||
size_t num_ws;
|
||||
|
||||
/* this does not work under valgrind */
|
||||
if (strstr (g_getenv ("LD_PRELOAD") ?: "", "valgrind") != NULL)
|
||||
return;
|
||||
|
||||
num_ws = server_get_ws_pids (ws_pids, max_ws);
|
||||
for (size_t i = 0; i < num_ws; ++i)
|
||||
{
|
||||
g_autofree gchar *contents = NULL;
|
||||
g_autofree gchar *path = g_strdup_printf ("/proc/%u/status", ws_pids[i]);
|
||||
g_assert (g_file_get_contents (path, &contents, NULL, NULL));
|
||||
if (!g_regex_match_simple ("^SigBlk:\\s*0+$", contents, G_REGEX_MULTILINE, 0))
|
||||
g_error ("Non-zero SigBlk in process %u: %s", ws_pids[i], contents);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
setup (TestCase *tc, gconstpointer data)
|
||||
{
|
||||
const TestFixture *fixture = data;
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
tc->state_dir = g_dir_make_tmp ("server.state.XXXXXX", NULL);
|
||||
g_assert (tc->state_dir);
|
||||
g_assert (g_setenv ("RUNTIME_DIRECTORY", tc->state_dir, TRUE));
|
||||
tc->ws_socket_dir = g_dir_make_tmp ("server.wssock.XXXXXX", NULL);
|
||||
g_assert (tc->ws_socket_dir);
|
||||
|
||||
server_init (COCKPIT_WS,
|
||||
gchar* sah_argv[] = { SOCKET_ACTIVATION_HELPER, COCKPIT_WS, tc->ws_socket_dir, NULL };
|
||||
if (!g_spawn_async (NULL, sah_argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &tc->ws_spawner, &error))
|
||||
g_error ("Failed to spawn " SOCKET_ACTIVATION_HELPER ": %s", error->message);
|
||||
|
||||
/* wait until socket activation helper is ready */
|
||||
int socket_dir_fd = open (tc->ws_socket_dir, O_RDONLY | O_DIRECTORY);
|
||||
for (int retry = 0; retry < 200; ++retry)
|
||||
{
|
||||
if (faccessat (socket_dir_fd, "ready", F_OK, 0) == 0)
|
||||
break;
|
||||
g_usleep (10000);
|
||||
}
|
||||
close (socket_dir_fd);
|
||||
|
||||
server_init (tc->ws_socket_dir,
|
||||
server_port,
|
||||
fixture ? fixture->certfile : NULL,
|
||||
fixture ? fixture->keyfile : NULL,
|
||||
|
@ -300,26 +293,24 @@ static void
|
|||
teardown (TestCase *tc, gconstpointer data)
|
||||
{
|
||||
server_cleanup ();
|
||||
/* all server children got cleaned up */
|
||||
g_assert_cmpint (kill (tc->ws_spawner, SIGTERM), ==, 0);
|
||||
g_assert_cmpint (waitpid (tc->ws_spawner, NULL, 0), ==, tc->ws_spawner);
|
||||
|
||||
/* all children got cleaned up */
|
||||
g_assert_cmpint (wait (NULL), ==, -1);
|
||||
g_assert_cmpint (errno, ==, ECHILD);
|
||||
g_assert_cmpuint (server_num_ws (), ==, 0);
|
||||
/* connection should fail */
|
||||
g_assert_cmpint (do_connect (tc), ==, -ECONNREFUSED);
|
||||
g_unsetenv ("COCKPIT_WS_PROCESS_IDLE");
|
||||
g_assert_cmpint (g_rmdir (tc->state_dir), ==, 0);
|
||||
g_free (tc->state_dir);
|
||||
}
|
||||
|
||||
static void
|
||||
test_no_tls_immediate_shutdown (TestCase *tc, gconstpointer data)
|
||||
{
|
||||
g_assert_cmpuint (server_num_ws (), ==, 0);
|
||||
g_assert_cmpuint (server_num_connections (), ==, 0);
|
||||
assert_http (tc);
|
||||
g_assert_cmpuint (server_num_ws (), ==, 1);
|
||||
g_assert_cmpuint (server_num_connections (), ==, 1);
|
||||
assert_children_signals ();
|
||||
int socket_dir_fd = open (tc->ws_socket_dir, O_RDONLY | O_DIRECTORY);
|
||||
g_assert_cmpint (unlinkat (socket_dir_fd, "http.sock", 0), ==, 0);
|
||||
g_assert_cmpint (unlinkat (socket_dir_fd, "http-redirect.sock", 0), ==, 0);
|
||||
g_assert_cmpint (unlinkat (socket_dir_fd, "https.sock", 0), ==, 0);
|
||||
g_assert_cmpint (unlinkat (socket_dir_fd, "ready", 0), ==, 0);
|
||||
close (socket_dir_fd);
|
||||
g_assert_cmpint (g_rmdir (tc->ws_socket_dir), ==, 0);
|
||||
g_free (tc->ws_socket_dir);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -331,17 +322,13 @@ test_no_tls_con_shutdown (TestCase *tc, gconstpointer data)
|
|||
for (int retries = 0; retries < 10 && server_num_connections () == 1; ++retries)
|
||||
server_run (100);
|
||||
g_assert_cmpuint (server_num_connections (), ==, 0);
|
||||
g_assert_cmpuint (server_num_ws (), ==, 1);
|
||||
}
|
||||
|
||||
static void
|
||||
test_no_tls_many_serial (TestCase *tc, gconstpointer data)
|
||||
{
|
||||
g_assert_cmpuint (server_num_ws (), ==, 0);
|
||||
for (int i = 0; i < 20; ++i)
|
||||
assert_http (tc);
|
||||
/* should all be served by the same ws */
|
||||
g_assert_cmpuint (server_num_ws (), ==, 1);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -351,7 +338,6 @@ test_no_tls_many_parallel (TestCase *tc, gconstpointer data)
|
|||
|
||||
block_sigchld ();
|
||||
|
||||
g_assert_cmpuint (server_num_ws (), ==, 0);
|
||||
for (i = 0; i < 20; ++i)
|
||||
{
|
||||
pid_t pid = fork ();
|
||||
|
@ -396,9 +382,6 @@ test_no_tls_many_parallel (TestCase *tc, gconstpointer data)
|
|||
--i;
|
||||
}
|
||||
}
|
||||
|
||||
/* all served by the same ws */
|
||||
g_assert_cmpuint (server_num_ws (), ==, 1);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -417,7 +400,6 @@ static void
|
|||
test_tls_no_client_cert (TestCase *tc, gconstpointer data)
|
||||
{
|
||||
assert_https (tc, NULL, NULL, 1);
|
||||
assert_children_signals ();
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -434,32 +416,23 @@ test_tls_redirect (TestCase *tc, gconstpointer data)
|
|||
/* with TLS support it should redirect */
|
||||
const char *res = do_request (tc, "GET / HTTP/1.0\r\nHost: some.remote:1234\r\n\r\n");
|
||||
cockpit_assert_strmatch (res, "HTTP/1.1 301 Moved Permanently*");
|
||||
assert_children_signals ();
|
||||
}
|
||||
|
||||
static void
|
||||
test_tls_client_cert (TestCase *tc, gconstpointer data)
|
||||
{
|
||||
g_assert_cmpuint (server_num_ws (), ==, 0);
|
||||
assert_https (tc, CLIENT_CERTFILE, CLIENT_KEYFILE, 1);
|
||||
g_assert_cmpuint (server_num_ws (), ==, 1);
|
||||
/* no-cert case is handled by separate ws */
|
||||
assert_https (tc, NULL, NULL, 1);
|
||||
g_assert_cmpuint (server_num_ws (), ==, 2);
|
||||
assert_https (tc, CLIENT_CERTFILE, CLIENT_KEYFILE, 1);
|
||||
g_assert_cmpuint (server_num_ws (), ==, 2);
|
||||
assert_children_signals ();
|
||||
}
|
||||
|
||||
static void
|
||||
test_tls_client_cert_disabled (TestCase *tc, gconstpointer data)
|
||||
{
|
||||
g_assert_cmpuint (server_num_ws (), ==, 0);
|
||||
assert_https (tc, CLIENT_CERTFILE, CLIENT_KEYFILE, 1);
|
||||
g_assert_cmpuint (server_num_ws (), ==, 1);
|
||||
/* no-cert case is handled by same ws, as client certs are disabled server-side */
|
||||
assert_https (tc, NULL, NULL, 1);
|
||||
g_assert_cmpuint (server_num_ws (), ==, 1);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -483,41 +456,10 @@ test_tls_cert_chain (TestCase *tc, gconstpointer data)
|
|||
static void
|
||||
test_mixed_protocols (TestCase *tc, gconstpointer data)
|
||||
{
|
||||
g_assert_cmpuint (server_num_ws (), ==, 0);
|
||||
assert_https (tc, NULL, NULL, 1);
|
||||
g_assert_cmpuint (server_num_ws (), ==, 1);
|
||||
assert_http (tc);
|
||||
g_assert_cmpuint (server_num_ws (), ==, 2);
|
||||
assert_https (tc, NULL, NULL, 1);
|
||||
g_assert_cmpuint (server_num_ws (), ==, 2);
|
||||
assert_http (tc);
|
||||
g_assert_cmpuint (server_num_ws (), ==, 2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_ws_idle (TestCase *tc, gconstpointer data)
|
||||
{
|
||||
g_assert (g_setenv ("COCKPIT_WS_PROCESS_IDLE", "2", TRUE));
|
||||
assert_http (tc);
|
||||
|
||||
g_assert_cmpuint (server_num_ws (), ==, 1);
|
||||
g_assert_cmpint (waitpid (0, NULL, WNOHANG), ==, 0);
|
||||
|
||||
/* ws process should disappear after idle wait */
|
||||
sleep (3);
|
||||
|
||||
/* run the mainloop to collect the zombie */
|
||||
while (server_poll_event (0))
|
||||
;
|
||||
|
||||
/* process is gone */
|
||||
g_assert_cmpint (waitpid (0, NULL, WNOHANG), ==, -1);
|
||||
g_assert_cmpint (errno, ==, ECHILD);
|
||||
g_assert_cmpuint (server_num_ws (), ==, 0);
|
||||
|
||||
/* a new request should re-spawn ws */
|
||||
assert_http (tc);
|
||||
g_assert_cmpuint (server_num_ws (), ==, 1);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -536,8 +478,6 @@ main (int argc, char *argv[])
|
|||
{
|
||||
cockpit_test_init (&argc, &argv);
|
||||
|
||||
g_test_add ("/server/no-tls/immediate-shutdown", TestCase, NULL,
|
||||
setup, test_no_tls_immediate_shutdown, teardown);
|
||||
g_test_add ("/server/no-tls/process-connection-shutdown", TestCase, NULL,
|
||||
setup, test_no_tls_con_shutdown, teardown);
|
||||
g_test_add ("/server/no-tls/many-serial", TestCase, NULL,
|
||||
|
@ -564,8 +504,6 @@ main (int argc, char *argv[])
|
|||
setup, test_tls_redirect, teardown);
|
||||
g_test_add ("/server/mixed-protocols", TestCase, &fixture_separate_crt_key,
|
||||
setup, test_mixed_protocols, teardown);
|
||||
g_test_add ("/server/ws-idle", TestCase, NULL,
|
||||
setup, test_ws_idle, teardown);
|
||||
g_test_add ("/server/run-idle", TestCase, NULL,
|
||||
setup, test_run_idle, teardown);
|
||||
|
||||
|
|
|
@ -1,278 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2019 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib/gstdio.h>
|
||||
#include <gnutls/x509.h>
|
||||
|
||||
#include "wsinstance.h"
|
||||
#include "common/cockpittest.h"
|
||||
|
||||
#define COCKPIT_WS BUILDDIR "/cockpit-ws"
|
||||
|
||||
#define WS_SUCCESS "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\n*"
|
||||
#define WS_FORBIDDEN "HTTP/1.1 403 Forbidden\r\nConnection: close\r\n*"
|
||||
|
||||
typedef struct {
|
||||
gchar *state_dir;
|
||||
WsInstance *ws;
|
||||
} TestCase;
|
||||
|
||||
typedef struct {
|
||||
enum WsInstanceMode mode;
|
||||
const char *cert_pem;
|
||||
} TestFixture;
|
||||
|
||||
static void
|
||||
setup (TestCase *tc, gconstpointer data)
|
||||
{
|
||||
const TestFixture *fixture = data;
|
||||
gnutls_datum_t peer_der;
|
||||
|
||||
tc->state_dir = g_dir_make_tmp ("wsinstance.state.XXXXXX", NULL);
|
||||
g_assert (tc->state_dir);
|
||||
|
||||
if (fixture->cert_pem)
|
||||
{
|
||||
gnutls_datum_t peer_pem;
|
||||
gnutls_x509_crt_t peer_cert;
|
||||
gsize cert_pem_length;
|
||||
|
||||
g_assert (g_file_get_contents (fixture->cert_pem, (gchar**) &peer_pem.data, &cert_pem_length, NULL));
|
||||
peer_pem.size = cert_pem_length;
|
||||
g_assert (gnutls_x509_crt_init (&peer_cert) >= 0);
|
||||
g_assert (gnutls_x509_crt_import (peer_cert, &peer_pem, GNUTLS_X509_FMT_PEM) >= 0);
|
||||
g_assert (gnutls_x509_crt_export2 (peer_cert, GNUTLS_X509_FMT_DER, &peer_der) >= 0);
|
||||
gnutls_x509_crt_deinit (peer_cert);
|
||||
gnutls_free (peer_pem.data);
|
||||
}
|
||||
|
||||
tc->ws = ws_instance_new (COCKPIT_WS, fixture->mode, fixture->cert_pem ? &peer_der : NULL, tc->state_dir);
|
||||
|
||||
if (fixture->cert_pem)
|
||||
gnutls_free (peer_der.data);
|
||||
|
||||
/* process is running */
|
||||
g_assert_cmpint (kill (tc->ws->pid, 0), ==, 0);
|
||||
/* socket exists */
|
||||
g_assert (g_file_test (tc->ws->socket.sun_path, G_FILE_TEST_EXISTS));
|
||||
g_assert (g_str_has_prefix (tc->ws->socket.sun_path, tc->state_dir));
|
||||
}
|
||||
|
||||
static void
|
||||
teardown (TestCase *tc, gconstpointer data)
|
||||
{
|
||||
pid_t ws_pid = tc->ws->pid;
|
||||
g_autofree char *socket_path = g_strdup (tc->ws->socket.sun_path);
|
||||
|
||||
ws_instance_free (tc->ws, true);
|
||||
/* process is not running any more */
|
||||
g_assert_cmpint (kill (ws_pid, 0), ==, -1);
|
||||
g_assert_cmpint (errno, ==, ESRCH);
|
||||
/* socket got cleaned up */
|
||||
g_assert (!g_file_test (socket_path, G_FILE_TEST_EXISTS));
|
||||
|
||||
g_assert_cmpint (g_rmdir (tc->state_dir), ==, 0);
|
||||
g_free (tc->state_dir);
|
||||
}
|
||||
|
||||
static int
|
||||
connect_to_ws (TestCase *tc)
|
||||
{
|
||||
int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
g_assert (fd > 0);
|
||||
g_assert (connect (fd, (const struct sockaddr*) &tc->ws->socket, sizeof (tc->ws->socket)) == 0);
|
||||
return fd;
|
||||
}
|
||||
|
||||
static const char*
|
||||
do_request (TestCase *tc, const char *request)
|
||||
{
|
||||
int fd = connect_to_ws (tc);
|
||||
static char buf[4096];
|
||||
ssize_t len;
|
||||
|
||||
g_assert_cmpint (write (fd, request, strlen (request)), ==, strlen (request));
|
||||
len = read (fd, buf, sizeof (buf) - 1);
|
||||
close (fd);
|
||||
g_assert_cmpint (len, >=, 100);
|
||||
buf[len] = '\0';
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void
|
||||
assert_http (TestCase *tc)
|
||||
{
|
||||
const char *res = do_request (tc, "GET / HTTP/1.0\r\nHost: localhost\r\n\r\n");
|
||||
/* This succeeds (200 OK) when building in-tree, but fails with dist-check due to missing doc root */
|
||||
if (strstr (res, "200 OK"))
|
||||
cockpit_assert_strmatch (res, "HTTP/1.1 200 OK\r\n*");
|
||||
else
|
||||
cockpit_assert_strmatch (res, "HTTP/1.1 404 Not Found\r\n*");
|
||||
}
|
||||
|
||||
static void
|
||||
assert_websocket (TestCase *tc, const char *origin, const char *expected)
|
||||
{
|
||||
const char request[] = "GET /socket HTTP/1.0\r\n"
|
||||
"Host: localhost:9090\r\n"
|
||||
"Connection: Upgrade\r\n"
|
||||
"Upgrade: websocket\r\n"
|
||||
"Sec-Websocket-Key: 3sc2c9IzwRUc3BlSIYwtSA==\r\n"
|
||||
"Sec-Websocket-Version: 13\r\n"
|
||||
"Origin: ";
|
||||
char buf[4096] = "\0";
|
||||
int fd = connect_to_ws (tc);
|
||||
|
||||
g_assert_cmpint (write (fd, request, sizeof (request) - 1), ==, sizeof (request) - 1);
|
||||
g_assert_cmpint (write (fd, origin, strlen (origin)), ==, strlen (origin));
|
||||
g_assert_cmpint (write (fd, "\r\n\r\n", 4), ==, 4);
|
||||
g_assert_cmpint (read (fd, buf, sizeof (buf)), >=, 50);
|
||||
close (fd);
|
||||
cockpit_assert_strmatch (buf, expected);
|
||||
}
|
||||
|
||||
static const TestFixture fixture_http = {
|
||||
.mode = WS_INSTANCE_HTTP,
|
||||
};
|
||||
|
||||
static void
|
||||
test_http (TestCase *tc, gconstpointer data)
|
||||
{
|
||||
const char *res;
|
||||
|
||||
gnutls_datum_t crt = { .size = 0 };
|
||||
|
||||
g_assert_cmpuint (tc->ws->peer_cert.size, ==, 0);
|
||||
g_assert_cmpuint (tc->ws->peer_cert_info.size, ==, 0);
|
||||
assert_http (tc);
|
||||
assert_websocket (tc, "http://localhost:9090", WS_SUCCESS);
|
||||
assert_websocket (tc, "https://localhost:9090", WS_FORBIDDEN);
|
||||
|
||||
g_assert (ws_instance_has_peer_cert (tc->ws, NULL));
|
||||
g_assert (ws_instance_has_peer_cert (tc->ws, &crt));
|
||||
crt.size = 1;
|
||||
g_assert (!ws_instance_has_peer_cert (tc->ws, &crt));
|
||||
|
||||
/* non-localhost does not redirect */
|
||||
res = do_request (tc, "GET / HTTP/1.0\r\nHost: some.remote:1234\r\n\r\n");
|
||||
/* This succeeds (200 OK) when building in-tree, but fails with dist-check due to missing doc root */
|
||||
if (strstr (res, "200 OK"))
|
||||
cockpit_assert_strmatch (res, "HTTP/1.1 200 OK\r\n*");
|
||||
else
|
||||
cockpit_assert_strmatch (res, "HTTP/1.1 404 Not Found\r\n*");
|
||||
}
|
||||
|
||||
static const TestFixture fixture_http_redirect = {
|
||||
.mode = WS_INSTANCE_HTTP_REDIRECT,
|
||||
};
|
||||
|
||||
static void
|
||||
test_http_redirect (TestCase *tc, gconstpointer data)
|
||||
{
|
||||
/* localhost does not redirect */
|
||||
assert_http (tc);
|
||||
/* non-localhost redirects */
|
||||
cockpit_assert_strmatch (do_request (tc, "GET / HTTP/1.0\r\nHost: some.remote:1234\r\n\r\n"),
|
||||
"HTTP/1.1 301 Moved Permanently*");
|
||||
}
|
||||
|
||||
static const TestFixture fixture_https_nocert = {
|
||||
.mode = WS_INSTANCE_HTTPS,
|
||||
};
|
||||
|
||||
static void
|
||||
test_https_nocert (TestCase *tc, gconstpointer data)
|
||||
{
|
||||
g_assert_cmpuint (tc->ws->peer_cert.size, ==, 0);
|
||||
g_assert_cmpuint (tc->ws->peer_cert_info.size, ==, 0);
|
||||
assert_http (tc);
|
||||
assert_websocket (tc, "https://localhost:9090", WS_SUCCESS);
|
||||
assert_websocket (tc, "http://localhost:9090", WS_FORBIDDEN);
|
||||
}
|
||||
|
||||
static const TestFixture fixture_https_cert = {
|
||||
.mode = WS_INSTANCE_HTTPS,
|
||||
.cert_pem = SRCDIR "/src/bridge/mock-server.crt",
|
||||
};
|
||||
|
||||
static void
|
||||
test_https_cert (TestCase *tc, gconstpointer data)
|
||||
{
|
||||
const TestFixture *fixture = data;
|
||||
g_autofree char *cert_file_path = NULL;
|
||||
g_autofree char *cert_file = NULL;
|
||||
g_autofree char *expected_pem = NULL;
|
||||
|
||||
gnutls_datum_t crt = { .size = 0 };
|
||||
|
||||
g_assert_cmpuint (tc->ws->peer_cert.size, >, 0);
|
||||
g_assert (tc->ws->peer_cert.data != NULL);
|
||||
cockpit_assert_strmatch ((const char*) tc->ws->peer_cert_info.data, "subject `CN=localhost', issuer `CN=localhost', *");
|
||||
assert_http (tc);
|
||||
assert_websocket (tc, "https://localhost:9090", WS_SUCCESS);
|
||||
assert_websocket (tc, "http://localhost:9090", WS_FORBIDDEN);
|
||||
|
||||
g_assert (!ws_instance_has_peer_cert (tc->ws, NULL));
|
||||
g_assert (!ws_instance_has_peer_cert (tc->ws, &crt));
|
||||
g_assert (ws_instance_has_peer_cert (tc->ws, &tc->ws->peer_cert));
|
||||
|
||||
/* certificate copy should match */
|
||||
crt.size = tc->ws->peer_cert.size;
|
||||
crt.data = malloc (tc->ws->peer_cert.size);
|
||||
g_assert (crt.data);
|
||||
memcpy (crt.data, tc->ws->peer_cert.data, crt.size);
|
||||
g_assert (ws_instance_has_peer_cert (tc->ws, &crt));
|
||||
/* modified crt should not match */
|
||||
crt.data[0]++;
|
||||
g_assert (!ws_instance_has_peer_cert (tc->ws, &crt));
|
||||
|
||||
/* writes expected certificate file */
|
||||
cert_file_path = g_strdup_printf ("%s/ws.%u.crt", tc->state_dir, tc->ws->pid);
|
||||
g_assert (g_file_get_contents (cert_file_path, &cert_file, NULL, NULL));
|
||||
g_assert (g_file_get_contents (fixture->cert_pem, &expected_pem, NULL, NULL));
|
||||
g_assert_cmpstr (g_strchomp (cert_file), ==, g_strchomp (expected_pem));
|
||||
|
||||
gnutls_free (crt.data);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
cockpit_test_init (&argc, &argv);
|
||||
|
||||
g_test_add ("/ws-instance/http", TestCase, &fixture_http,
|
||||
setup, test_http, teardown);
|
||||
g_test_add ("/ws-instance/http-redirect", TestCase, &fixture_http_redirect,
|
||||
setup, test_http_redirect, teardown);
|
||||
g_test_add ("/ws-instance/tls-nocert", TestCase, &fixture_https_nocert,
|
||||
setup, test_https_nocert, teardown);
|
||||
g_test_add ("/ws-instance/tls-cert", TestCase, &fixture_https_cert,
|
||||
setup, test_https_cert, teardown);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
|
@ -1,254 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2019 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "wsinstance.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <err.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <gnutls/x509.h>
|
||||
|
||||
#include "common/cockpitmemory.h"
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
/* This is a bit lame, but having a hard limit on peer certificates is
|
||||
* desirable: Let's not get DoSed by huge certs */
|
||||
#define MAX_PEER_CERT_SIZE 100000
|
||||
|
||||
__attribute__((__format__ (__printf__, 3, 4)))
|
||||
static void
|
||||
snprintf_checked (char *str,
|
||||
size_t size,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
int r;
|
||||
|
||||
va_start (args, fmt);
|
||||
r = vsnprintf (str, size, fmt, args);
|
||||
va_end (args);
|
||||
|
||||
if (r >= size)
|
||||
{
|
||||
fprintf (stderr, "snprintf_checked got a too small buffer of %zu bytes but tried to print %i\n", size, r);
|
||||
abort ();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
ws_write_peer_cert (WsInstance *ws, const char *cert_pem, size_t cert_pem_size)
|
||||
{
|
||||
char *tempname;
|
||||
int fd;
|
||||
int r;
|
||||
|
||||
asprintfx (&ws->peer_cert_file, "%s/ws.%u.crt", ws->state_dir, ws->pid);
|
||||
asprintfx (&tempname, "%s.tmp", ws->peer_cert_file);
|
||||
|
||||
fd = open (tempname, O_CREAT | O_WRONLY | O_EXCL, 0644);
|
||||
if (fd < 0)
|
||||
err (1, "Could not open certificate file %s", tempname);
|
||||
r = write (fd, cert_pem, cert_pem_size);
|
||||
if (r < 0)
|
||||
err (1, "Could not write certificate file");
|
||||
if (r != cert_pem_size)
|
||||
errx (1, "Could not write certificate file: wrote %i out of %zu bytes", r, cert_pem_size);
|
||||
close (fd);
|
||||
|
||||
if (rename (tempname, ws->peer_cert_file) < 0)
|
||||
err (1, "Could not rename %s", tempname);
|
||||
free (tempname);
|
||||
|
||||
debug (SERVER, "Wrote TLS peer certificate PEM to %s", ws->peer_cert_file);
|
||||
}
|
||||
|
||||
/**
|
||||
* ws_init_peer_cert: Retrieve and publish information about the client-side TLS certificate
|
||||
*/
|
||||
static void
|
||||
ws_init_peer_cert (WsInstance *ws, const gnutls_datum_t *der)
|
||||
{
|
||||
gnutls_x509_crt_t cert;
|
||||
static char cert_pem[MAX_PEER_CERT_SIZE];
|
||||
size_t cert_pem_size = sizeof (cert_pem);
|
||||
|
||||
assert (der);
|
||||
assert (ws->pid);
|
||||
|
||||
/* clone DER certificate */
|
||||
ws->peer_cert.size = der->size;
|
||||
ws->peer_cert.data = mallocx (der->size);
|
||||
memcpy (ws->peer_cert.data, der->data, der->size);
|
||||
|
||||
/* convert to X.509 to extract information and PEM */
|
||||
gnutls_check (gnutls_x509_crt_init (&cert));
|
||||
gnutls_check (gnutls_x509_crt_import (cert, der, GNUTLS_X509_FMT_DER));
|
||||
gnutls_check (gnutls_x509_crt_print (cert, GNUTLS_CRT_PRINT_ONELINE, &ws->peer_cert_info));
|
||||
gnutls_check (gnutls_x509_crt_export (cert, GNUTLS_X509_FMT_PEM, cert_pem, &cert_pem_size));
|
||||
|
||||
/* GnuTLS should already enforce that, but make double-sure */
|
||||
assert (cert_pem_size < sizeof (cert_pem));
|
||||
assert (cert_pem[cert_pem_size] == '\0');
|
||||
debug (SERVER, "TLS peer certificate: %s", ws->peer_cert_info.data);
|
||||
|
||||
/* write X.509 certificate to our state dir, so that PAM modules can check that these got validated */
|
||||
ws_write_peer_cert (ws, cert_pem, cert_pem_size);
|
||||
|
||||
gnutls_x509_crt_deinit (cert);
|
||||
}
|
||||
|
||||
/**
|
||||
* ws_instance_new: Launch a new cockpit-ws child process
|
||||
*
|
||||
* Sessions with different client TLS certificates, https-without-certificate,
|
||||
* and unencrypted http get shielded from each other, so that attacks in one ws
|
||||
* cannot tamper with other sessions.
|
||||
*
|
||||
* @ws_path: Path to cockpit-ws binary
|
||||
* @mode: #WS_INSTANCE_{HTTP,HTTP_REDIRECT,HTTPS}
|
||||
* @client_cert_der: client TLS certificate in DER format (as retrieved from gnutls_certificate_get_peers())
|
||||
* @state_dir: Directory for putting the unix socket to cockpit-ws and
|
||||
* certificate information. This is sensitive and must only be accessible to cockpit-tls!
|
||||
*/
|
||||
WsInstance *
|
||||
ws_instance_new (const char *ws_path,
|
||||
enum WsInstanceMode mode,
|
||||
const gnutls_datum_t *client_cert_der,
|
||||
const char *state_dir)
|
||||
{
|
||||
WsInstance *ws;
|
||||
int fd;
|
||||
pid_t pid;
|
||||
static char pid_str[20];
|
||||
|
||||
ws = callocx (1, sizeof (WsInstance));
|
||||
ws->state_dir = state_dir;
|
||||
|
||||
/* create a listening socket for cockpit-ws */
|
||||
fd = socket (AF_UNIX, SOCK_STREAM, 0);
|
||||
if (fd < 0)
|
||||
err (1, "failed to create cockpit-ws socket");
|
||||
ws->socket.sun_family = AF_UNIX;
|
||||
snprintf_checked (ws->socket.sun_path, sizeof (ws->socket.sun_path), "%s/ws.%i.sock", state_dir, fd);
|
||||
unlink (ws->socket.sun_path);
|
||||
if (bind (fd, (const struct sockaddr *) &ws->socket, sizeof (ws->socket)) < 0)
|
||||
err (1, "failed to bind cockpit-ws socket %s", ws->socket.sun_path);
|
||||
if (listen (fd, 20) < 0)
|
||||
err (1, "failed to set cockpit-ws socket to listen");
|
||||
|
||||
pid = fork ();
|
||||
if (pid < 0)
|
||||
err (1, "failed to fork");
|
||||
if (pid > 0)
|
||||
{
|
||||
/* parent */
|
||||
debug (SERVER, "forked cockpit-ws as pid %i on socket %s", pid, ws->socket.sun_path);
|
||||
close (fd);
|
||||
ws->pid = pid;
|
||||
if (mode == WS_INSTANCE_HTTPS && client_cert_der)
|
||||
ws_init_peer_cert (ws, client_cert_der);
|
||||
return ws;
|
||||
}
|
||||
|
||||
/* child */
|
||||
/* pass the socket to ws like systemd activation does, see sd_listen_fds(3) */
|
||||
if (dup2 (fd, SD_LISTEN_FDS_START) < 0)
|
||||
err (1, "failed to dup socket fd");
|
||||
snprintf_checked (pid_str, sizeof (pid_str), "%i", getpid ());
|
||||
setenv ("LISTEN_FDS", "1", 1);
|
||||
setenv ("LISTEN_PID", pid_str, 1);
|
||||
debug (SERVER, "cockpit-ws child process: setup complete, executing %s", ws_path);
|
||||
switch (mode)
|
||||
{
|
||||
case WS_INSTANCE_HTTP:
|
||||
execl (ws_path, ws_path, "--no-tls", "--port", "0", NULL);
|
||||
break;
|
||||
|
||||
case WS_INSTANCE_HTTP_REDIRECT:
|
||||
execl (ws_path, ws_path, "--proxy-tls-redirect", "--no-tls", "--port", "0", NULL);
|
||||
break;
|
||||
|
||||
case WS_INSTANCE_HTTPS:
|
||||
execl (ws_path, ws_path, "--for-tls-proxy", "--port", "0", NULL);
|
||||
break;
|
||||
|
||||
default:
|
||||
errx (1, "Invalid mode");
|
||||
}
|
||||
err (127, "failed to execute %s", ws_path);
|
||||
}
|
||||
|
||||
void
|
||||
ws_instance_free (WsInstance *ws,
|
||||
bool terminate)
|
||||
{
|
||||
debug (SERVER, "freeing cockpit-ws instance pid %i on socket %s", ws->pid, ws->socket.sun_path);
|
||||
if (ws->peer_cert.size)
|
||||
{
|
||||
gnutls_free (ws->peer_cert.data);
|
||||
gnutls_free (ws->peer_cert_info.data);
|
||||
}
|
||||
|
||||
/* Only kill() and waitpid() the process in the case that we didn't
|
||||
* already collect its exit status with waitpid(). This removes the
|
||||
* theoretical possibility of calling kill() on an already-recycled
|
||||
* pid.
|
||||
*/
|
||||
if (terminate)
|
||||
{
|
||||
kill (ws->pid, SIGKILL);
|
||||
waitpid (ws->pid, NULL, 0);
|
||||
}
|
||||
|
||||
unlink (ws->socket.sun_path);
|
||||
if (ws->peer_cert_file)
|
||||
{
|
||||
unlink (ws->peer_cert_file);
|
||||
free (ws->peer_cert_file);
|
||||
}
|
||||
free (ws);
|
||||
}
|
||||
|
||||
/**
|
||||
* ws_instance_has_peer_cert: Check if that instance is for a given GnuTLS DER client certificate
|
||||
*
|
||||
* Returns true if either this ws instance has no client certificate and der is
|
||||
* %NULL or empty, or if both certificates are identical. Otherwise returns false.
|
||||
*/
|
||||
bool
|
||||
ws_instance_has_peer_cert (WsInstance *ws, const gnutls_datum_t *der)
|
||||
{
|
||||
if (!der)
|
||||
return ws->peer_cert.size == 0;
|
||||
|
||||
/* this includes the case where both are absent */
|
||||
if (ws->peer_cert.size != der->size)
|
||||
return false;
|
||||
return memcmp (ws->peer_cert.data, der->data, der->size) == 0;
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* This file is part of Cockpit.
|
||||
*
|
||||
* Copyright (C) 2019 Red Hat, Inc.
|
||||
*
|
||||
* Cockpit is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Cockpit is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include <gnutls/gnutls.h>
|
||||
|
||||
enum WsInstanceMode { WS_INSTANCE_HTTP, WS_INSTANCE_HTTP_REDIRECT, WS_INSTANCE_HTTPS };
|
||||
|
||||
/* a single cockpit-ws child process */
|
||||
typedef struct WsInstance {
|
||||
const char *state_dir;
|
||||
gnutls_datum_t peer_cert; /* DER format */
|
||||
gnutls_datum_t peer_cert_info; /* human readable string */
|
||||
char *peer_cert_file;
|
||||
struct sockaddr_un socket;
|
||||
pid_t pid;
|
||||
struct WsInstance *next;
|
||||
} WsInstance;
|
||||
|
||||
WsInstance* ws_instance_new (const char *ws_path,
|
||||
enum WsInstanceMode mode,
|
||||
const gnutls_datum_t *client_cert_der,
|
||||
const char *state_dir);
|
||||
void ws_instance_free (WsInstance *ws, bool terminate);
|
||||
bool ws_instance_has_peer_cert (WsInstance *ws, const gnutls_datum_t *der);
|
|
@ -164,6 +164,12 @@ remotectl_LDADD = \
|
|||
nodist_systemdunit_DATA += \
|
||||
src/ws/cockpit.socket \
|
||||
src/ws/cockpit.service \
|
||||
src/ws/cockpit-wsinstance-http.socket \
|
||||
src/ws/cockpit-wsinstance-http.service \
|
||||
src/ws/cockpit-wsinstance-http-redirect.socket \
|
||||
src/ws/cockpit-wsinstance-http-redirect.service \
|
||||
src/ws/cockpit-wsinstance-https.socket \
|
||||
src/ws/cockpit-wsinstance-https.service \
|
||||
src/ws/cockpit-motd.service \
|
||||
$(NULL)
|
||||
|
||||
|
@ -191,7 +197,7 @@ $(nodist_tempconf_DATA): $(tempconf_in)
|
|||
# If running cockpit-ws as a non-standard user, we also set up
|
||||
# cockpit-session to be setuid root, but only runnable by cockpit-session
|
||||
install-exec-hook::
|
||||
chown -f root:$(COCKPIT_GROUP) $(DESTDIR)$(libexecdir)/cockpit-session || true
|
||||
chown -f root:$(COCKPIT_WSINSTANCE_GROUP) $(DESTDIR)$(libexecdir)/cockpit-session || true
|
||||
test "$(COCKPIT_USER)" != "root" && chmod -f 4750 $(DESTDIR)$(libexecdir)/cockpit-session || true
|
||||
|
||||
libexec_SCRIPTS = cockpit-desktop
|
||||
|
@ -203,6 +209,12 @@ EXTRA_DIST += \
|
|||
src/ws/cockpit-motd.service.in \
|
||||
src/ws/cockpit.service.in \
|
||||
src/ws/cockpit.socket.in \
|
||||
src/ws/cockpit-wsinstance-http.service.in \
|
||||
src/ws/cockpit-wsinstance-http.socket.in \
|
||||
src/ws/cockpit-wsinstance-http-redirect.service.in \
|
||||
src/ws/cockpit-wsinstance-http-redirect.socket.in \
|
||||
src/ws/cockpit-wsinstance-https.service.in \
|
||||
src/ws/cockpit-wsinstance-https.socket.in \
|
||||
src/ws/cockpit-desktop.in \
|
||||
$(firewall_DATA) \
|
||||
$(tempconf_in) \
|
||||
|
@ -211,6 +223,12 @@ EXTRA_DIST += \
|
|||
CLEANFILES += \
|
||||
src/ws/cockpit.socket \
|
||||
src/ws/cockpit.service \
|
||||
src/ws/cockpit-wsinstance-http.service \
|
||||
src/ws/cockpit-wsinstance-http.socket \
|
||||
src/ws/cockpit-wsinstance-http-redirect.service \
|
||||
src/ws/cockpit-wsinstance-http-redirect.socket \
|
||||
src/ws/cockpit-wsinstance-https.service \
|
||||
src/ws/cockpit-wsinstance-https.socket \
|
||||
src/ws/cockpit-motd.service \
|
||||
$(nodist_tempconf_DATA) \
|
||||
cockpit-desktop \
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Cockpit Web Service http-redirect instance
|
||||
PartOf=cockpit.service
|
||||
Documentation=man:cockpit-ws(8)
|
||||
|
||||
[Service]
|
||||
ExecStart=@libexecdir@/cockpit-ws --proxy-tls-redirect --no-tls --port 0
|
||||
User=@wsinstanceuser@
|
||||
Group=@wsinstancegroup@
|
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Socket for Cockpit Web Service http-redirect instance
|
||||
PartOf=cockpit.service
|
||||
Documentation=man:cockpit-ws(8)
|
||||
|
||||
[Socket]
|
||||
ListenStream=/run/cockpit/wsinstance/http-redirect.sock
|
||||
SocketUser=@user@
|
||||
SocketMode=0600
|
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Cockpit Web Service http instance
|
||||
PartOf=cockpit.service
|
||||
Documentation=man:cockpit-ws(8)
|
||||
|
||||
[Service]
|
||||
ExecStart=@libexecdir@/cockpit-ws --no-tls --port=0
|
||||
User=@wsinstanceuser@
|
||||
Group=@wsinstancegroup@
|
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Socket for Cockpit Web Service http instance
|
||||
PartOf=cockpit.service
|
||||
Documentation=man:cockpit-ws(8)
|
||||
|
||||
[Socket]
|
||||
ListenStream=/run/cockpit/wsinstance/http.sock
|
||||
SocketUser=@user@
|
||||
SocketMode=0600
|
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Cockpit Web Service https instance
|
||||
PartOf=cockpit.service
|
||||
Documentation=man:cockpit-ws(8)
|
||||
|
||||
[Service]
|
||||
ExecStart=@libexecdir@/cockpit-ws --for-tls-proxy --port=0
|
||||
User=@wsinstanceuser@
|
||||
Group=@wsinstancegroup@
|
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Socket for Cockpit Web Service https instance
|
||||
PartOf=cockpit.service
|
||||
Documentation=man:cockpit-ws(8)
|
||||
|
||||
[Socket]
|
||||
ListenStream=/run/cockpit/wsinstance/https.sock
|
||||
SocketUser=@user@
|
||||
SocketMode=0600
|
|
@ -2,6 +2,8 @@
|
|||
Description=Cockpit Web Service
|
||||
Documentation=man:cockpit-ws(8)
|
||||
Requires=cockpit.socket
|
||||
Requires=cockpit-wsinstance-http.socket cockpit-wsinstance-http-redirect.socket cockpit-wsinstance-https.socket
|
||||
After=cockpit-wsinstance-http.socket cockpit-wsinstance-http-redirect.socket cockpit-wsinstance-https.socket
|
||||
|
||||
[Service]
|
||||
RuntimeDirectory=cockpit/tls
|
||||
|
|
|
@ -447,7 +447,7 @@ G_MESSAGES_DEBUG=all XDG_CONFIG_DIRS=/usr/local %s -p 9999 -a 127.0.0.90 --local
|
|||
m.spawn("socat TCP-LISTEN:9091,reuseaddr,fork TCP:localhost:9099", "socat.log")
|
||||
|
||||
# ws with plain --no-tls should fail after login with mismatching Origin (expected http, got https)
|
||||
m.spawn("su -s /bin/sh -c '%s --no-tls -p 9099' cockpit-ws" % self.ws_executable,
|
||||
m.spawn("su -s /bin/sh -c '%s --no-tls -p 9099' cockpit-wsinstance" % self.ws_executable,
|
||||
"ws-notls.log")
|
||||
m.wait_for_cockpit_running(tls=True)
|
||||
|
||||
|
@ -488,7 +488,7 @@ G_MESSAGES_DEBUG=all XDG_CONFIG_DIRS=/usr/local %s -p 9999 -a 127.0.0.90 --local
|
|||
self.allow_browser_errors("Error reading machine id")
|
||||
|
||||
# ws with --for-tls-proxy accepts only https origins, thus should work
|
||||
m.spawn("su -s /bin/sh -c '%s --for-tls-proxy -p 9099 -a 127.0.0.1' cockpit-ws" % self.ws_executable,
|
||||
m.spawn("su -s /bin/sh -c '%s --for-tls-proxy -p 9099 -a 127.0.0.1' cockpit-wsinstance" % self.ws_executable,
|
||||
"ws-fortlsproxy.log")
|
||||
m.wait_for_cockpit_running(tls=True)
|
||||
b.open("https://%s:%s/system" % (b.address, b.port))
|
||||
|
@ -520,7 +520,7 @@ G_MESSAGES_DEBUG=all XDG_CONFIG_DIRS=/usr/local %s -p 9999 -a 127.0.0.90 --local
|
|||
|
||||
# ws with --proxy-tls-redirect redirects non-localhost to https
|
||||
m.execute("pkill -e cockpit-ws; while pgrep -a cockpit-ws; do sleep 1; done")
|
||||
m.spawn("su -s /bin/sh -c '%s --proxy-tls-redirect --no-tls -p 9099 -a 127.0.0.1' cockpit-ws" % self.ws_executable,
|
||||
m.spawn("su -s /bin/sh -c '%s --proxy-tls-redirect --no-tls -p 9099 -a 127.0.0.1' cockpit-wsinstance" % self.ws_executable,
|
||||
"ws-proxy-tls-redirect.log")
|
||||
m.wait_for_cockpit_running(tls=True)
|
||||
self.assertIn("HTTP/1.1 301 Moved Permanently", m.execute("curl --silent --head http://172.27.0.15:9091"))
|
||||
|
|
|
@ -124,6 +124,7 @@ exec 2>&1
|
|||
%configure \
|
||||
--disable-silent-rules \
|
||||
--with-cockpit-user=cockpit-ws \
|
||||
--with-cockpit-ws-instance-user=cockpit-wsinstance \
|
||||
--with-selinux-config-type=etc_t \
|
||||
--with-appstream-data-packages='[ "appstream-data" ]' \
|
||||
--with-nfs-client-package='"nfs-utils"' \
|
||||
|
@ -412,20 +413,28 @@ The Cockpit Web Service listens on the network, and authenticates users.
|
|||
%{_unitdir}/cockpit.service
|
||||
%{_unitdir}/cockpit-motd.service
|
||||
%{_unitdir}/cockpit.socket
|
||||
%{_unitdir}/cockpit-wsinstance-http.socket
|
||||
%{_unitdir}/cockpit-wsinstance-http.service
|
||||
%{_unitdir}/cockpit-wsinstance-http-redirect.socket
|
||||
%{_unitdir}/cockpit-wsinstance-http-redirect.service
|
||||
%{_unitdir}/cockpit-wsinstance-https.socket
|
||||
%{_unitdir}/cockpit-wsinstance-https.service
|
||||
%{_prefix}/%{__lib}/tmpfiles.d/cockpit-tempfiles.conf
|
||||
%{_sbindir}/remotectl
|
||||
%{_libdir}/security/pam_ssh_add.so
|
||||
%{_libexecdir}/cockpit-ws
|
||||
%{_libexecdir}/cockpit-tls
|
||||
%{_libexecdir}/cockpit-desktop
|
||||
%attr(4750, root, cockpit-ws) %{_libexecdir}/cockpit-session
|
||||
%attr(4750, root, cockpit-wsinstance) %{_libexecdir}/cockpit-session
|
||||
%attr(775, -, wheel) %{_localstatedir}/lib/cockpit
|
||||
%{_datadir}/cockpit/static
|
||||
%{_datadir}/cockpit/branding
|
||||
|
||||
%pre ws
|
||||
getent group cockpit-ws >/dev/null || groupadd -r cockpit-ws
|
||||
getent passwd cockpit-ws >/dev/null || useradd -r -g cockpit-ws -d /nonexisting -s /sbin/nologin -c "User for cockpit-ws" cockpit-ws
|
||||
getent passwd cockpit-ws >/dev/null || useradd -r -g cockpit-ws -d /nonexisting -s /sbin/nologin -c "User for cockpit web service" cockpit-ws
|
||||
getent group cockpit-wsinstance >/dev/null || groupadd -r cockpit-wsinstance
|
||||
getent passwd cockpit-wsinstance >/dev/null || useradd -r -g cockpit-wsinstance -d /nonexisting -s /sbin/nologin -c "User for cockpit-ws instances" cockpit-wsinstance
|
||||
|
||||
%post ws
|
||||
%systemd_post cockpit.socket
|
||||
|
|
|
@ -5,6 +5,12 @@ etc/pam.d/cockpit
|
|||
lib/systemd/system/cockpit.service
|
||||
lib/systemd/system/cockpit-motd.service
|
||||
lib/systemd/system/cockpit.socket
|
||||
lib/systemd/system/cockpit-wsinstance-http-redirect.service
|
||||
lib/systemd/system/cockpit-wsinstance-http-redirect.socket
|
||||
lib/systemd/system/cockpit-wsinstance-http.service
|
||||
lib/systemd/system/cockpit-wsinstance-http.socket
|
||||
lib/systemd/system/cockpit-wsinstance-https.service
|
||||
lib/systemd/system/cockpit-wsinstance-https.socket
|
||||
lib/*/security/pam_ssh_add.so
|
||||
usr/lib/tmpfiles.d/cockpit-tempfiles.conf
|
||||
usr/lib/cockpit/cockpit-session
|
||||
|
|
|
@ -2,9 +2,16 @@
|
|||
set -e
|
||||
|
||||
adduser --system --group --home /nonexisting --no-create-home cockpit-ws
|
||||
adduser --system --group --home /nonexisting --no-create-home cockpit-wsinstance
|
||||
|
||||
# change group of cockpit-session on upgrades (changed in version 203)
|
||||
if OUT=$(dpkg-statoverride --list /usr/lib/cockpit/cockpit-session) && [ "$OUT#root cockpit-ws 4750}" != "$OUT" ]; then
|
||||
echo "Adjusting /usr/lib/cockpit/cockpit-session permissions..."
|
||||
dpkg-statoverride --remove /usr/lib/cockpit/cockpit-session
|
||||
fi
|
||||
|
||||
if ! dpkg-statoverride --list /usr/lib/cockpit/cockpit-session >/dev/null; then
|
||||
dpkg-statoverride --update --add root cockpit-ws 4750 /usr/lib/cockpit/cockpit-session
|
||||
dpkg-statoverride --update --add root cockpit-wsinstance 4750 /usr/lib/cockpit/cockpit-session
|
||||
fi
|
||||
|
||||
#DEBHELPER#
|
||||
|
|
|
@ -21,6 +21,7 @@ override_dh_auto_configure:
|
|||
dh_auto_configure -- \
|
||||
--with-networkmanager-needs-root=yes \
|
||||
--with-cockpit-user=cockpit-ws \
|
||||
--with-cockpit-ws-instance-user=cockpit-wsinstance \
|
||||
--with-appstream-config-packages='[ "appstream" ]' \
|
||||
--with-nfs-client-packages='"nfs-common"' \
|
||||
--with-pamdir=/lib/$(DEB_HOST_MULTIARCH)/security \
|
||||
|
|
Loading…
Reference in New Issue