ws: Add --for-tls-proxy option
If cockpit-ws runs behind a reverse proxy that does the TLS termination, it rejects non-GnuTLS connections with a https:// origin with received request from bad Origin: https://localhost:9090 This previously required setting `Origins` in cockpit.conf to make this work. Introduce a new `--for-tls-proxy` option to cockpit-ws which switches the default Origin checking to https-only. With that, no cockpit.conf modifications are required for local proxies. This is implemented by a new `for-tls-proxy` property in `CockpitWebServer`, which gets plumbed through the Origins check in `cockpit_web_service_create_socket()`. This is mostly intended for the upcoming external "cockpit-tls" proxy which externalizes TLS termination, so that this does not have to modify cockpit.conf or inspect/modify the HTTP stream. But it's generally applicable to any local reverse TLS proxy. Fix test-handlers to not call cockpit_handler_socket() with a NULL server, so that reading the property does not cause a warning. Closes #11813
This commit is contained in:
parent
3f8b4c9cbb
commit
d1f531a160
|
@ -42,6 +42,7 @@
|
|||
<arg><option>--port</option> <replaceable>PORT</replaceable></arg>
|
||||
<arg><option>--address</option> <replaceable>ADDRESS</replaceable></arg>
|
||||
<arg><option>--no-tls</option></arg>
|
||||
<arg><option>--for-tls-proxy</option></arg>
|
||||
<arg><option>--local-ssh</option></arg>
|
||||
<arg><option>--local-session</option> <replaceable>BRIDGE</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
|
@ -158,6 +159,23 @@ getcert request -f ${CERT_FILE} -k ${KEY_FILE} -D $(hostname --fqdn) -C "sed -n
|
|||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--for-tls-proxy</option></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Tell <command>cockpit-ws</command> that it is running behind a local reverse proxy that
|
||||
does the TLS termination. Then Cockpit accepts only https:// origins, instead of http:
|
||||
ones by default. However, if <literal>Origins</literal> is set in the
|
||||
<citerefentry>
|
||||
<refentrytitle>cockpit.conf</refentrytitle><manvolnum>5</manvolnum>
|
||||
</citerefentry>
|
||||
configuration file, it will override this default.
|
||||
</para>
|
||||
<para>
|
||||
This option implies <option>--no-tls</option>.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--local-ssh</option></term>
|
||||
<listitem>
|
||||
|
|
|
@ -56,6 +56,7 @@ struct _CockpitWebServer {
|
|||
gint request_timeout;
|
||||
gint request_max;
|
||||
gboolean redirect_tls;
|
||||
gboolean for_tls_proxy;
|
||||
|
||||
GSocketService *socket_service;
|
||||
GMainContext *main_context;
|
||||
|
@ -88,6 +89,7 @@ enum
|
|||
PROP_SSL_EXCEPTION_PREFIX,
|
||||
PROP_SOCKET_ACTIVATED,
|
||||
PROP_REDIRECT_TLS,
|
||||
PROP_FOR_TLS_PROXY,
|
||||
PROP_URL_ROOT,
|
||||
};
|
||||
|
||||
|
@ -183,6 +185,10 @@ cockpit_web_server_get_property (GObject *object,
|
|||
g_value_set_boolean (value, server->redirect_tls);
|
||||
break;
|
||||
|
||||
case PROP_FOR_TLS_PROXY:
|
||||
g_value_set_boolean (value, server->for_tls_proxy);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
|
@ -247,6 +253,10 @@ cockpit_web_server_set_property (GObject *object,
|
|||
server->redirect_tls = g_value_get_boolean (value);
|
||||
break;
|
||||
|
||||
case PROP_FOR_TLS_PROXY:
|
||||
server->for_tls_proxy = g_value_get_boolean (value);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
|
@ -432,6 +442,13 @@ cockpit_web_server_class_init (CockpitWebServerClass *klass)
|
|||
g_param_spec_boolean ("redirect-tls", NULL, NULL, TRUE,
|
||||
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_FOR_TLS_PROXY,
|
||||
g_param_spec_boolean ("for-tls-proxy", NULL, NULL, TRUE,
|
||||
G_PARAM_READABLE |
|
||||
G_PARAM_WRITABLE |
|
||||
G_PARAM_CONSTRUCT_ONLY |
|
||||
G_PARAM_STATIC_STRINGS));
|
||||
|
||||
sig_handle_stream = g_signal_new ("handle-stream",
|
||||
G_OBJECT_CLASS_TYPE (klass),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
|
@ -462,12 +479,13 @@ cockpit_web_server_class_init (CockpitWebServerClass *klass)
|
|||
COCKPIT_TYPE_WEB_RESPONSE);
|
||||
}
|
||||
|
||||
CockpitWebServer *
|
||||
cockpit_web_server_new (const gchar *address,
|
||||
gint port,
|
||||
GTlsCertificate *certificate,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
static CockpitWebServer *
|
||||
cockpit_web_server_new_internal (const gchar *address,
|
||||
gint port,
|
||||
GTlsCertificate *certificate,
|
||||
gboolean for_tls_proxy,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
GInitable *initable;
|
||||
initable = g_initable_new (COCKPIT_TYPE_WEB_SERVER,
|
||||
|
@ -476,6 +494,7 @@ cockpit_web_server_new (const gchar *address,
|
|||
"port", port,
|
||||
"address", address,
|
||||
"certificate", certificate,
|
||||
"for-tls-proxy", for_tls_proxy,
|
||||
NULL);
|
||||
if (initable != NULL)
|
||||
return COCKPIT_WEB_SERVER (initable);
|
||||
|
@ -483,6 +502,26 @@ cockpit_web_server_new (const gchar *address,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
CockpitWebServer *
|
||||
cockpit_web_server_new (const gchar *address,
|
||||
gint port,
|
||||
GTlsCertificate *certificate,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
return cockpit_web_server_new_internal (address, port, certificate, FALSE, cancellable, error);
|
||||
}
|
||||
|
||||
CockpitWebServer *
|
||||
cockpit_web_server_new_for_tls_proxy (const gchar *address,
|
||||
gint port,
|
||||
GTlsCertificate *certificate,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
return cockpit_web_server_new_internal (address, port, certificate, TRUE, cancellable, error);
|
||||
}
|
||||
|
||||
void
|
||||
cockpit_web_server_start (CockpitWebServer *self)
|
||||
{
|
||||
|
@ -522,6 +561,14 @@ cockpit_web_server_get_redirect_tls (CockpitWebServer *self)
|
|||
return self->redirect_tls;
|
||||
}
|
||||
|
||||
gboolean
|
||||
cockpit_web_server_get_for_tls_proxy (CockpitWebServer *self)
|
||||
{
|
||||
g_return_val_if_fail (COCKPIT_IS_WEB_SERVER (self), FALSE);
|
||||
|
||||
return self->for_tls_proxy;
|
||||
}
|
||||
|
||||
GHashTable *
|
||||
cockpit_web_server_new_table (void)
|
||||
{
|
||||
|
|
|
@ -41,6 +41,12 @@ CockpitWebServer * cockpit_web_server_new (const gchar *address,
|
|||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
CockpitWebServer * cockpit_web_server_new_for_tls_proxy (const gchar *address,
|
||||
gint port,
|
||||
GTlsCertificate *certificate,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
void cockpit_web_server_start (CockpitWebServer *self);
|
||||
|
||||
gboolean cockpit_web_server_add_socket (CockpitWebServer *self,
|
||||
|
@ -64,6 +70,8 @@ void cockpit_web_server_set_redirect_tls (CockpitWebServer *se
|
|||
|
||||
gboolean cockpit_web_server_get_redirect_tls (CockpitWebServer *self);
|
||||
|
||||
gboolean cockpit_web_server_get_for_tls_proxy (CockpitWebServer *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __COCKPIT_WEB_SERVER_H__ */
|
||||
|
|
|
@ -39,6 +39,7 @@ typedef struct {
|
|||
const gchar *cert_file;
|
||||
gboolean local_only;
|
||||
gboolean inet_only;
|
||||
gboolean for_tls_proxy;
|
||||
} TestFixture;
|
||||
|
||||
#define SKIP_NO_HOSTPORT if (!tc->hostport) { cockpit_test_skip ("No non-loopback network interface available"); return; }
|
||||
|
@ -73,7 +74,10 @@ setup (TestCase *tc,
|
|||
else
|
||||
address = NULL;
|
||||
|
||||
tc->web_server = cockpit_web_server_new (address, 0, cert, NULL, &error);
|
||||
if (fixture && fixture->for_tls_proxy)
|
||||
tc->web_server = cockpit_web_server_new_for_tls_proxy (address, 0, NULL, NULL, &error);
|
||||
else
|
||||
tc->web_server = cockpit_web_server_new (address, 0, cert, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
g_clear_object (&cert);
|
||||
|
||||
|
@ -458,6 +462,8 @@ test_webserver_redirect_notls (TestCase *tc,
|
|||
|
||||
SKIP_NO_HOSTPORT;
|
||||
|
||||
g_assert (!cockpit_web_server_get_for_tls_proxy (tc->web_server));
|
||||
|
||||
g_signal_connect (tc->web_server, "handle-resource", G_CALLBACK (on_shell_index_html), NULL);
|
||||
resp = perform_http_request (tc->hostport, "GET /shell/index.html HTTP/1.0\r\nHost:test\r\n\r\n", NULL);
|
||||
cockpit_assert_strmatch (resp, "HTTP/* 301 *\r\nLocation: https://*");
|
||||
|
@ -856,6 +862,28 @@ test_bad_address (TestCase *tc,
|
|||
g_object_unref (server);
|
||||
}
|
||||
|
||||
static const TestFixture fixture_for_tls_proxy = {
|
||||
.cert_file = SRCDIR "/src/ws/mock_cert",
|
||||
.for_tls_proxy = TRUE
|
||||
};
|
||||
|
||||
static void
|
||||
test_webserver_for_tls_proxy (TestCase *tc,
|
||||
gconstpointer data)
|
||||
{
|
||||
gchar *resp;
|
||||
|
||||
SKIP_NO_HOSTPORT;
|
||||
|
||||
g_assert (cockpit_web_server_get_for_tls_proxy (tc->web_server));
|
||||
|
||||
/* should not redirect even with a certificate present */
|
||||
g_signal_connect (tc->web_server, "handle-resource", G_CALLBACK (on_shell_index_html), NULL);
|
||||
resp = perform_http_request (tc->hostport, "GET /shell/index.html HTTP/1.0\r\nHost:test\r\n\r\n", NULL);
|
||||
cockpit_assert_strmatch (resp, "HTTP/* 200 *\r\n*");
|
||||
g_free (resp);
|
||||
}
|
||||
|
||||
static const TestFixture fixture_inet_address = {
|
||||
.inet_only = TRUE
|
||||
};
|
||||
|
@ -920,5 +948,8 @@ main (int argc,
|
|||
g_test_add ("/web-server/bad-address", TestCase, NULL,
|
||||
NULL, test_bad_address, NULL);
|
||||
|
||||
g_test_add ("/web-server/for-tls-proxy", TestCase, &fixture_for_tls_proxy,
|
||||
setup, test_webserver_for_tls_proxy, teardown);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
|
@ -191,7 +191,8 @@ cockpit_channel_socket_open (CockpitWebService *service,
|
|||
const gchar *path,
|
||||
GIOStream *io_stream,
|
||||
GHashTable *headers,
|
||||
GByteArray *input_buffer)
|
||||
GByteArray *input_buffer,
|
||||
gboolean for_tls_proxy)
|
||||
{
|
||||
CockpitChannelSocket *self = NULL;
|
||||
WebSocketDataType data_type;
|
||||
|
@ -225,7 +226,7 @@ cockpit_channel_socket_open (CockpitWebService *service,
|
|||
self->data_type = data_type;
|
||||
|
||||
self->socket = cockpit_web_service_create_socket ((const gchar **)protocols, original_path,
|
||||
io_stream, headers, input_buffer);
|
||||
io_stream, headers, input_buffer, for_tls_proxy);
|
||||
self->socket_open = g_signal_connect (self->socket, "open", G_CALLBACK (on_socket_open), self);
|
||||
self->socket_message = g_signal_connect (self->socket, "message", G_CALLBACK (on_socket_message), self);
|
||||
self->socket_close = g_signal_connect (self->socket, "close", G_CALLBACK (on_socket_close), self);
|
||||
|
|
|
@ -31,7 +31,8 @@ void cockpit_channel_socket_open (CockpitWebService *service
|
|||
const gchar *path,
|
||||
GIOStream *io_stream,
|
||||
GHashTable *headers,
|
||||
GByteArray *input_buffer);
|
||||
GByteArray *input_buffer,
|
||||
gboolean for_tls_proxy);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
|
|
@ -66,11 +66,12 @@ static void
|
|||
handle_noauth_socket (GIOStream *io_stream,
|
||||
const gchar *path,
|
||||
GHashTable *headers,
|
||||
GByteArray *input_buffer)
|
||||
GByteArray *input_buffer,
|
||||
gboolean for_tls_proxy)
|
||||
{
|
||||
WebSocketConnection *connection;
|
||||
|
||||
connection = cockpit_web_service_create_socket (NULL, path, io_stream, headers, input_buffer);
|
||||
connection = cockpit_web_service_create_socket (NULL, path, io_stream, headers, input_buffer, for_tls_proxy);
|
||||
|
||||
g_signal_connect (connection, "open", G_CALLBACK (on_web_socket_noauth), NULL);
|
||||
|
||||
|
@ -113,12 +114,12 @@ cockpit_handler_socket (CockpitWebServer *server,
|
|||
service = cockpit_auth_check_cookie (ws->auth, path, headers);
|
||||
if (service)
|
||||
{
|
||||
cockpit_web_service_socket (service, path, io_stream, headers, input);
|
||||
cockpit_web_service_socket (service, path, io_stream, headers, input, cockpit_web_server_get_for_tls_proxy (server));
|
||||
g_object_unref (service);
|
||||
}
|
||||
else
|
||||
{
|
||||
handle_noauth_socket (io_stream, path, headers, input);
|
||||
handle_noauth_socket (io_stream, path, headers, input, cockpit_web_server_get_for_tls_proxy (server));
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
|
@ -210,7 +211,8 @@ cockpit_handler_external (CockpitWebServer *server,
|
|||
upgrade = g_hash_table_lookup (headers, "Upgrade");
|
||||
if (upgrade && g_ascii_strcasecmp (upgrade, "websocket") == 0)
|
||||
{
|
||||
cockpit_channel_socket_open (service, open, original_path, path, io_stream, headers, input);
|
||||
cockpit_channel_socket_open (service, open, original_path, path, io_stream, headers, input,
|
||||
cockpit_web_server_get_for_tls_proxy (server));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -1298,7 +1298,8 @@ cockpit_web_service_create_socket (const gchar **protocols,
|
|||
const gchar *path,
|
||||
GIOStream *io_stream,
|
||||
GHashTable *headers,
|
||||
GByteArray *input_buffer)
|
||||
GByteArray *input_buffer,
|
||||
gboolean for_tls_proxy)
|
||||
{
|
||||
WebSocketConnection *connection;
|
||||
const gchar *host = NULL;
|
||||
|
@ -1307,7 +1308,7 @@ cockpit_web_service_create_socket (const gchar **protocols,
|
|||
gchar *allocated = NULL;
|
||||
gchar *origin = NULL;
|
||||
gchar *defaults[2];
|
||||
gboolean secure;
|
||||
gboolean is_https;
|
||||
gchar *url;
|
||||
|
||||
g_return_val_if_fail (path != NULL, NULL);
|
||||
|
@ -1328,17 +1329,19 @@ cockpit_web_service_create_socket (const gchar **protocols,
|
|||
protocol = cockpit_connection_get_protocol (io_stream, headers);
|
||||
}
|
||||
|
||||
secure = g_strcmp0 (protocol, "https") == 0;
|
||||
g_debug("cockpit_web_service_create_socket: host %s, protocol %s, for_tls_proxy %i", host, protocol, for_tls_proxy);
|
||||
|
||||
is_https = g_strcmp0 (protocol, "https") == 0 || for_tls_proxy;
|
||||
|
||||
url = g_strdup_printf ("%s://%s%s",
|
||||
secure ? "wss" : "ws",
|
||||
is_https ? "wss" : "ws",
|
||||
host ? host : "localhost",
|
||||
path);
|
||||
|
||||
origins = cockpit_conf_strv ("WebService", "Origins", ' ');
|
||||
if (origins == NULL)
|
||||
{
|
||||
origin = g_strdup_printf ("%s://%s", secure ? "https" : "http", host);
|
||||
origin = g_strdup_printf ("%s://%s", is_https ? "https" : "http", host);
|
||||
defaults[0] = origin;
|
||||
defaults[1] = NULL;
|
||||
origins = (const gchar **)defaults;
|
||||
|
@ -1360,6 +1363,8 @@ cockpit_web_service_create_socket (const gchar **protocols,
|
|||
* @input_buffer: optional bytes already parsed after headers
|
||||
* @auth: authentication object
|
||||
* @creds: credentials of user or NULL for failed auth
|
||||
* @for_tls_proxy: Assume that the Browser is making TLS connections that are terminated
|
||||
* in a reverse proxy in front of cockpit-ws
|
||||
*
|
||||
* Serves the WebSocket on the given web service. Holds an extra
|
||||
* reference to the web service until the socket is closed.
|
||||
|
@ -1369,12 +1374,13 @@ cockpit_web_service_socket (CockpitWebService *self,
|
|||
const gchar *path,
|
||||
GIOStream *io_stream,
|
||||
GHashTable *headers,
|
||||
GByteArray *input_buffer)
|
||||
GByteArray *input_buffer,
|
||||
gboolean for_tls_proxy)
|
||||
{
|
||||
const gchar *protocols[] = { "cockpit1", NULL };
|
||||
WebSocketConnection *connection;
|
||||
|
||||
connection = cockpit_web_service_create_socket (protocols, path, io_stream, headers, input_buffer);
|
||||
connection = cockpit_web_service_create_socket (protocols, path, io_stream, headers, input_buffer, for_tls_proxy);
|
||||
|
||||
g_signal_connect (connection, "open", G_CALLBACK (on_web_socket_open), self);
|
||||
g_signal_connect (connection, "closing", G_CALLBACK (on_web_socket_closing), self);
|
||||
|
|
|
@ -47,7 +47,8 @@ void cockpit_web_service_socket (CockpitWebService *self,
|
|||
const gchar *path,
|
||||
GIOStream *io_stream,
|
||||
GHashTable *headers,
|
||||
GByteArray *input_buffer);
|
||||
GByteArray *input_buffer,
|
||||
gboolean for_tls_proxy);
|
||||
|
||||
CockpitCreds * cockpit_web_service_get_creds (CockpitWebService *self);
|
||||
|
||||
|
@ -57,7 +58,8 @@ WebSocketConnection * cockpit_web_service_create_socket (const gchar **prot
|
|||
const gchar *path,
|
||||
GIOStream *io_stream,
|
||||
GHashTable *headers,
|
||||
GByteArray *input_buffer);
|
||||
GByteArray *input_buffer,
|
||||
gboolean for_tls_proxy);
|
||||
|
||||
gchar * cockpit_web_service_unique_channel (CockpitWebService *self);
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
static gint opt_port = 9090;
|
||||
static gchar *opt_address = NULL;
|
||||
static gboolean opt_no_tls = FALSE;
|
||||
static gboolean opt_for_tls_proxy = FALSE;
|
||||
static gboolean opt_local_ssh = FALSE;
|
||||
static gchar *opt_local_session = NULL;
|
||||
static gboolean opt_version = FALSE;
|
||||
|
@ -52,6 +53,9 @@ static GOptionEntry cmd_entries[] = {
|
|||
{"port", 'p', 0, G_OPTION_ARG_INT, &opt_port, "Local port to bind to (9090 if unset)", NULL},
|
||||
{"address", 'a', 0, G_OPTION_ARG_STRING, &opt_address, "Address to bind to (binds on all addresses if unset)", "ADDRESS"},
|
||||
{"no-tls", 0, 0, G_OPTION_ARG_NONE, &opt_no_tls, "Don't use TLS", NULL},
|
||||
{"for-tls-proxy", 0, 0, G_OPTION_ARG_NONE, &opt_for_tls_proxy,
|
||||
"Act behind a https-terminating proxy: accept only https:// origins by default; implies --no-tls",
|
||||
NULL},
|
||||
{"local-ssh", 0, 0, G_OPTION_ARG_NONE, &opt_local_ssh, "Log in locally via SSH", NULL },
|
||||
{"local-session", 0, 0, G_OPTION_ARG_STRING, &opt_local_session,
|
||||
"Launch a bridge in the local session (path to cockpit-bridge or '-' for stdin/out); implies --no-tls",
|
||||
|
@ -155,6 +159,9 @@ main (int argc,
|
|||
goto out;
|
||||
}
|
||||
|
||||
if (opt_for_tls_proxy)
|
||||
opt_no_tls = TRUE;
|
||||
|
||||
/*
|
||||
* This process talks on stdin/stdout. However lots of stuff wants to write
|
||||
* to stdout, such as g_debug, and uses fd 1 to do that. Reroute fd 1 so that
|
||||
|
@ -195,11 +202,22 @@ main (int argc,
|
|||
login_po_html = g_strdup (DATADIR "/cockpit/static/login.po.html");
|
||||
data.login_po_html = (const gchar *)login_po_html;
|
||||
|
||||
server = cockpit_web_server_new (opt_address,
|
||||
opt_port,
|
||||
certificate,
|
||||
NULL,
|
||||
error);
|
||||
if (opt_for_tls_proxy)
|
||||
{
|
||||
server = cockpit_web_server_new_for_tls_proxy (opt_address,
|
||||
opt_port,
|
||||
certificate,
|
||||
NULL,
|
||||
error);
|
||||
}
|
||||
else
|
||||
{
|
||||
server = cockpit_web_server_new (opt_address,
|
||||
opt_port,
|
||||
certificate,
|
||||
NULL,
|
||||
error);
|
||||
}
|
||||
if (server == NULL)
|
||||
{
|
||||
g_prefix_error (error, "Error starting web server: ");
|
||||
|
|
|
@ -764,6 +764,7 @@ on_message_get_bytes (WebSocketConnection *ws,
|
|||
static void
|
||||
test_socket_unauthenticated (void)
|
||||
{
|
||||
CockpitWebServer *server;
|
||||
WebSocketConnection *client;
|
||||
GBytes *received = NULL;
|
||||
GIOStream *io_a, *io_b;
|
||||
|
@ -773,9 +774,13 @@ test_socket_unauthenticated (void)
|
|||
const gchar *unused;
|
||||
gchar *channel;
|
||||
JsonObject *options;
|
||||
GError *error = NULL;
|
||||
|
||||
make_io_streams (&io_a, &io_b);
|
||||
|
||||
server = cockpit_web_server_new (NULL, 0, NULL, NULL, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
client = g_object_new (WEB_SOCKET_TYPE_CLIENT,
|
||||
"url", "ws://127.0.0.1/unused",
|
||||
"origin", "http://127.0.0.1",
|
||||
|
@ -787,7 +792,7 @@ test_socket_unauthenticated (void)
|
|||
/* Matching the above origin */
|
||||
cockpit_ws_default_host_header = "127.0.0.1";
|
||||
|
||||
g_assert (cockpit_handler_socket (NULL, "/cockpit/socket", "/cockpit/socket",
|
||||
g_assert (cockpit_handler_socket (server, "/cockpit/socket", "/cockpit/socket",
|
||||
"GET", io_b, NULL, NULL, NULL));
|
||||
|
||||
g_signal_connect (client, "message", G_CALLBACK (on_message_get_bytes), &received);
|
||||
|
@ -818,6 +823,7 @@ test_socket_unauthenticated (void)
|
|||
|
||||
g_object_unref (io_a);
|
||||
g_object_unref (io_b);
|
||||
g_object_unref (server);
|
||||
}
|
||||
|
||||
int
|
||||
|
|
|
@ -390,7 +390,7 @@ on_handle_stream_socket (CockpitWebServer *server,
|
|||
g_signal_handler_disconnect (transport, handler);
|
||||
}
|
||||
|
||||
cockpit_web_service_socket (service, path, io_stream, headers, input);
|
||||
cockpit_web_service_socket (service, path, io_stream, headers, input, FALSE /* for_tls_proxy */);
|
||||
|
||||
/* Keeps ref on itself until it closes */
|
||||
g_object_unref (service);
|
||||
|
@ -506,7 +506,7 @@ on_handle_stream_external (CockpitWebServer *server,
|
|||
upgrade = g_hash_table_lookup (headers, "Upgrade");
|
||||
if (upgrade && g_ascii_strcasecmp (upgrade, "websocket") == 0)
|
||||
{
|
||||
cockpit_channel_socket_open (service, open, path, path, io_stream, headers, input);
|
||||
cockpit_channel_socket_open (service, open, path, path, io_stream, headers, input, FALSE /* for_tls_proxy */);
|
||||
handled = TRUE;
|
||||
}
|
||||
else
|
||||
|
|
|
@ -81,6 +81,7 @@ typedef struct {
|
|||
const char *config;
|
||||
const char *forward;
|
||||
const char *bridge;
|
||||
gboolean for_tls_proxy;
|
||||
} TestFixture;
|
||||
|
||||
static gboolean
|
||||
|
@ -432,7 +433,7 @@ start_web_service_and_create_client (TestCase *test,
|
|||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
/* Note, we are forcing the websocket to parse its own headers */
|
||||
cockpit_web_service_socket (*service, "/unused", test->io_b, NULL, NULL);
|
||||
cockpit_web_service_socket (*service, "/unused", test->io_b, NULL, NULL, fixture ? fixture->for_tls_proxy : FALSE);
|
||||
|
||||
g_signal_handler_disconnect (test->mock_bridge, handler);
|
||||
}
|
||||
|
@ -863,6 +864,11 @@ static const TestFixture fixture_allowed_origin_proto_header = {
|
|||
.config = SRCDIR "/src/ws/mock-config/cockpit/cockpit-alt.conf"
|
||||
};
|
||||
|
||||
static const TestFixture fixture_allowed_origin_tls_proxy = {
|
||||
.origin = "https://127.0.0.1",
|
||||
.for_tls_proxy = TRUE,
|
||||
};
|
||||
|
||||
static const TestFixture fixture_bad_origin_proto_no_header = {
|
||||
.origin = "https://127.0.0.1",
|
||||
.config = SRCDIR "/src/ws/mock-config/cockpit/cockpit-alt.conf"
|
||||
|
@ -874,6 +880,11 @@ static const TestFixture fixture_bad_origin_proto_no_config = {
|
|||
.config = NULL
|
||||
};
|
||||
|
||||
static const TestFixture fixture_bad_origin_tls_proxy = {
|
||||
.origin = "http://127.0.0.1",
|
||||
.for_tls_proxy = TRUE,
|
||||
};
|
||||
|
||||
static void
|
||||
test_bad_origin (TestCase *test,
|
||||
gconstpointer data)
|
||||
|
@ -1107,7 +1118,7 @@ test_idling (TestCase *test,
|
|||
g_signal_connect (service, "idling", G_CALLBACK (on_idling_set_flag), &flag);
|
||||
g_assert (cockpit_web_service_get_idling (service));
|
||||
|
||||
cockpit_web_service_socket (service, "/unused", test->io_b, NULL, NULL);
|
||||
cockpit_web_service_socket (service, "/unused", test->io_b, NULL, NULL, FALSE);
|
||||
g_assert (!cockpit_web_service_get_idling (service));
|
||||
|
||||
while (web_socket_connection_get_ready_state (client) == WEB_SOCKET_STATE_CONNECTING)
|
||||
|
@ -1157,7 +1168,7 @@ test_dispose (TestCase *test,
|
|||
g_object_unref (transport);
|
||||
g_object_unref (pipe);
|
||||
|
||||
cockpit_web_service_socket (service, "/unused", test->io_b, NULL, NULL);
|
||||
cockpit_web_service_socket (service, "/unused", test->io_b, NULL, NULL, FALSE);
|
||||
|
||||
while (web_socket_connection_get_ready_state (client) == WEB_SOCKET_STATE_CONNECTING)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
@ -1515,6 +1526,12 @@ main (int argc,
|
|||
g_test_add ("/web-service/allowed-origin/protocol-header", TestCase,
|
||||
&fixture_allowed_origin_proto_header, setup_for_socket,
|
||||
test_handshake_and_auth, teardown_for_socket);
|
||||
g_test_add ("/web-service/allowed-origin/tls-proxy", TestCase,
|
||||
&fixture_allowed_origin_tls_proxy, setup_for_socket,
|
||||
test_handshake_and_auth, teardown_for_socket);
|
||||
g_test_add ("/web-service/bad-origin/tls-proxy", TestCase,
|
||||
&fixture_bad_origin_tls_proxy, setup_for_socket,
|
||||
test_bad_origin, teardown_for_socket);
|
||||
|
||||
g_test_add ("/web-service/close-error", TestCase,
|
||||
NULL, setup_for_socket,
|
||||
|
|
|
@ -437,6 +437,68 @@ G_MESSAGES_DEBUG=all XDG_CONFIG_DIRS=/usr/local %s -p 9999 -a 127.0.0.90 --local
|
|||
|
||||
self.allow_journal_messages("couldn't register polkit authentication agent.*")
|
||||
|
||||
@skipImage("missing socat", "fedora-atomic", "rhel-atomic", "continuous-atomic", "centos-7")
|
||||
@skipImage("Added in PR #11813", "rhel-7-6-distropkg", "rhel-8-0-distropkg")
|
||||
def testReverseTlsProxy(self):
|
||||
m = self.machine
|
||||
b = self.browser
|
||||
|
||||
# set up a poor man's reverse TLS proxy with socat
|
||||
m.upload(["../src/bridge/mock-server.crt", "../src/bridge/mock-server.key"], "/tmp")
|
||||
m.spawn("socat OPENSSL-LISTEN:9090,reuseaddr,fork,cert=/tmp/mock-server.crt,"
|
||||
"key=/tmp/mock-server.key,verify=0 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 -a 127.0.0.1' cockpit-ws" % self.ws_executable,
|
||||
"ws-notls.log")
|
||||
m.wait_for_cockpit_running(tls=True)
|
||||
|
||||
b.ignore_ssl_certificate_errors(True)
|
||||
b.open("https://%s:%s/system" % (b.address, b.port))
|
||||
b.wait_visible("#login")
|
||||
b.set_val("#login-user-input", "admin")
|
||||
b.set_val("#login-password-input", "foobar")
|
||||
b.click('#login-button')
|
||||
b.expect_load()
|
||||
|
||||
def check_wss_log():
|
||||
for log in self.browser.get_js_log():
|
||||
if 'Error during WebSocket handshake: Unexpected response code: 403' in log:
|
||||
return True
|
||||
return False
|
||||
wait(check_wss_log)
|
||||
|
||||
wait(lambda: "received request from bad Origin" in m.execute("journalctl -b -t cockpit-ws"))
|
||||
|
||||
# sanity check: HTTP does not work
|
||||
m.execute("! curl http://localhost:9090")
|
||||
|
||||
m.execute("pkill -e cockpit-ws; while pgrep -a cockpit-ws; do sleep 1; done")
|
||||
# this page failure is reeally noisy
|
||||
self.allow_authorize_journal_messages()
|
||||
self.allow_journal_messages(".*No authentication agent found.*")
|
||||
self.allow_journal_messages("couldn't register polkit authentication agent.*")
|
||||
self.allow_journal_messages("received request from bad Origin.*")
|
||||
self.allow_journal_messages(".*invalid handshake.*")
|
||||
self.allow_browser_errors(".*received unsupported version in init message.*")
|
||||
self.allow_browser_errors(".*received message before init.*")
|
||||
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,
|
||||
"ws-fortlsproxy.log")
|
||||
m.wait_for_cockpit_running(tls=True)
|
||||
b.open("https://%s:%s/system" % (b.address, b.port))
|
||||
b.wait_visible("#login")
|
||||
b.set_val("#login-user-input", "admin")
|
||||
b.set_val("#login-password-input", "foobar")
|
||||
b.click('#login-button')
|
||||
b.expect_load()
|
||||
b.wait_visible('#content')
|
||||
b.enter_page("/system")
|
||||
b.logout()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_main()
|
||||
|
|
Loading…
Reference in New Issue