Skip to content

Commit

Permalink
ws: Add UrlRoot configuration option
Browse files Browse the repository at this point in the history
Allows cockpit to be served on a different root url than /.

This is mainly useful when using cockpit behind a reverse
proxy.

Fixes #3323
Closes #4937
Reviewed-by: Stef Walter <stefw@redhat.com>
  • Loading branch information
petervo authored and stefwalter committed Sep 4, 2016
1 parent ea0b4de commit 8e4958a
Show file tree
Hide file tree
Showing 20 changed files with 577 additions and 85 deletions.
10 changes: 10 additions & 0 deletions doc/man/cockpit.conf.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ Origins = https://somedomain1.com https://somedomain2.com:9090
false.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>UrlRoot</option></term>
<listitem>
<para>The root URL where you will be serving cockpit. When provided cockpit will expect all
requests to be prefixed with the given url. This is mostly useful when you are using
cockpit behind a reverse proxy, such as nginx. <code>/cockpit/</code> and <code>/cockpit+</code>
are reserved and should not be used. For example <code>/cockpit-new/</code> is ok.
<code>/cockpit/</code> and <code>/cockpit+new/</code> are not.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>

Expand Down
16 changes: 12 additions & 4 deletions pkg/shell/base_index.js
Original file line number Diff line number Diff line change
Expand Up @@ -477,14 +477,21 @@ define([
* given state, then we try to find one that would show a sidebar.
*/

/* Encode navigate state into a string */
function encode(state, sidebar) {
/* Encode navigate state into a string
* If with_root is true the configured
* url root will be added to the generated
* url. with_root should be used when
* navigating to a new url or updating
* history, but is not needed when simply
* generating a string for a link.
*/
function encode(state, sidebar, with_root) {
var path = [];
if (state.host && (sidebar || state.host !== "localhost"))
path.push("@" + state.host);
if (state.component)
path.push.apply(path, state.component.split("/"));
var string = cockpit.location.encode(path);
var string = cockpit.location.encode(path, null, with_root);
if (state.hash && state.hash !== "/")
string += "#" + state.hash;
return string;
Expand Down Expand Up @@ -588,7 +595,8 @@ define([
if (shell_embedded)
target = window.location;
else
target = encode(state);
target = encode(state, null, true);

if (replace) {
history.replaceState(state, "", target);
return false;
Expand Down
29 changes: 25 additions & 4 deletions src/base1/cockpit.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/

var url_root = window.localStorage.getItem('url-root');

var mock = mock || { };

var phantom_checkpoint = phantom_checkpoint || function () { };
Expand Down Expand Up @@ -253,9 +255,12 @@ function calculate_url() {
return window.mock.url;
var window_loc = window.location.toString();
var path = window.location.pathname || "/";
if (path.indexOf("/cockpit") !== 0)
if (path.indexOf("/cockpit/") !== 0 && path.indexOf("/cockpit+") !== 0)
path = "/cockpit";
var prefix = path.split("/")[1];
if (url_root)
prefix = url_root + "/" + prefix;

if (window_loc.indexOf('http:') === 0) {
return "ws://" + window.location.host + "/" + prefix + "/socket";
} else if (window_loc.indexOf('https:') === 0) {
Expand Down Expand Up @@ -2235,21 +2240,37 @@ function basic_scope(cockpit, jquery) {

function decode_path(input) {
var parts = input.split('/').map(decodeURIComponent);
var result;
var result, i, pre_parts = [];

if (url_root)
pre_parts = url_root.split('/').map(decodeURIComponent);

if (input && input[0] !== "/") {
result = [].concat(path);
result.pop();
result = result.concat(parts);
} else {
result = parts;
}
return resolve_path_dots(result);

result = resolve_path_dots(result);
for (i = 0; i < pre_parts.length; i++) {
if (pre_parts[i] !== result[i])
break;
}
if (i == pre_parts.length)
result.splice(0, pre_parts.length);

return result;
}

function encode(path, options) {
function encode(path, options, with_root) {
if (typeof path == "string")
path = decode_path(path, self.path);

var href = "/" + path.map(encodeURIComponent).join("/");
if (with_root && url_root && href.indexOf("/" + url_root + "/" !== 0))
href = "/" + url_root + href;

/* Undo unnecessary encoding of these */
href = href.replace("%40", "@");
Expand Down
1 change: 1 addition & 0 deletions src/bridge/test-websocketstream.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ on_socket_close (WebSocketConnection *ws,

static gboolean
handle_socket (CockpitWebServer *server,
const gchar *original_path,
const gchar *path,
GIOStream *io_stream,
GHashTable *headers,
Expand Down
25 changes: 25 additions & 0 deletions src/common/cockpitwebresponse.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ struct _CockpitWebResponse {
const gchar *path;
gchar *full_path;
gchar *query;
gchar *url_root;

CockpitCacheType cache_type;

/* The output queue */
Expand Down Expand Up @@ -153,6 +155,7 @@ cockpit_web_response_finalize (GObject *object)

g_free (self->full_path);
g_free (self->query);
g_free (self->url_root);
g_assert (self->io == NULL);
g_assert (self->out == NULL);
g_queue_free_full (self->queue, (GDestroyNotify)g_bytes_unref);
Expand Down Expand Up @@ -191,13 +194,15 @@ cockpit_web_response_class_init (CockpitWebResponseClass *klass)
*/
CockpitWebResponse *
cockpit_web_response_new (GIOStream *io,
const gchar *original_path,
const gchar *path,
const gchar *query,
GHashTable *in_headers)
{
CockpitWebResponse *self;
GOutputStream *out;
const gchar *connection;
gint offset;

/* Trying to be a somewhat performant here, avoiding properties */
self = g_object_new (COCKPIT_TYPE_WEB_RESPONSE, NULL);
Expand All @@ -214,8 +219,17 @@ cockpit_web_response_new (GIOStream *io,
G_OBJECT_TYPE_NAME (out));
}

self->url_root = NULL;
self->full_path = g_strdup (path);
self->path = self->full_path;

if (path && original_path)
{
offset = strlen (original_path) - strlen (path);
if (offset > 0 && g_strcmp0 (original_path + offset, path) == 0)
self->url_root = g_strndup (original_path, offset);
}

self->query = g_strdup (query);
if (self->path)
self->logname = self->path;
Expand Down Expand Up @@ -246,6 +260,17 @@ cockpit_web_response_get_path (CockpitWebResponse *self)
return self->path;
}

/**
* cockpit_web_response_get_url_root:
* @self: the response
*
* Returns: The url root portion of the original path that was removed
*/
const gchar *
cockpit_web_response_get_url_root (CockpitWebResponse *self) {
return self->url_root;
}

/**
* cockpit_web_response_get_query:
* @self: the response
Expand Down
4 changes: 4 additions & 0 deletions src/common/cockpitwebresponse.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ extern const gchar * cockpit_web_failure_resource;
GType cockpit_web_response_get_type (void) G_GNUC_CONST;

CockpitWebResponse * cockpit_web_response_new (GIOStream *io,
const gchar *original_path,
const gchar *path,
const gchar *query,
GHashTable *in_headers);
Expand Down Expand Up @@ -131,6 +132,9 @@ gboolean cockpit_web_response_is_header_value (const gchar *string);
void cockpit_web_response_set_cache_type (CockpitWebResponse *self,
CockpitCacheType cache_type);

const gchar * cockpit_web_response_get_url_root (CockpitWebResponse *response);


G_END_DECLS

#endif /* __COCKPIT_RESPONSE_H__ */
60 changes: 55 additions & 5 deletions src/common/cockpitwebserver.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ struct _CockpitWebServer {
GTlsCertificate *certificate;
gchar **document_roots;
GString *ssl_exception_prefix;
GString *url_root;
gint request_timeout;
gint request_max;
gboolean redirect_tls;
Expand All @@ -65,6 +66,7 @@ struct _CockpitWebServerClass {
GObjectClass parent_class;

gboolean (* handle_stream) (CockpitWebServer *server,
const gchar *original_path,
const gchar *path,
GIOStream *io_stream,
GHashTable *headers,
Expand All @@ -84,7 +86,8 @@ enum
PROP_DOCUMENT_ROOTS,
PROP_SSL_EXCEPTION_PREFIX,
PROP_SOCKET_ACTIVATED,
PROP_REDIRECT_TLS
PROP_REDIRECT_TLS,
PROP_URL_ROOT,
};

static gint sig_handle_stream = 0;
Expand All @@ -110,6 +113,7 @@ cockpit_web_server_init (CockpitWebServer *server)
cockpit_request_free, NULL);
server->main_context = g_main_context_ref_thread_default ();
server->ssl_exception_prefix = g_string_new ("");
server->url_root = g_string_new ("");
server->redirect_tls = TRUE;
}

Expand Down Expand Up @@ -146,6 +150,7 @@ cockpit_web_server_finalize (GObject *object)
if (server->main_context)
g_main_context_unref (server->main_context);
g_string_free (server->ssl_exception_prefix, TRUE);
g_string_free (server->url_root, TRUE);
g_clear_object (&server->socket_service);

G_OBJECT_CLASS (cockpit_web_server_parent_class)->finalize (object);
Expand Down Expand Up @@ -175,6 +180,14 @@ cockpit_web_server_get_property (GObject *object,

case PROP_SSL_EXCEPTION_PREFIX:
g_value_set_string (value, server->ssl_exception_prefix->str);
break;

case PROP_URL_ROOT:
if (server->url_root->len)
g_value_set_string (value, server->url_root->str);
else
g_value_set_string (value, NULL);
break;

case PROP_SOCKET_ACTIVATED:
g_value_set_boolean (value, server->socket_activated);
Expand Down Expand Up @@ -241,6 +254,7 @@ cockpit_web_server_set_property (GObject *object,
GParamSpec *pspec)
{
CockpitWebServer *server = COCKPIT_WEB_SERVER (object);
GString *str;

switch (prop_id)
{
Expand All @@ -260,6 +274,26 @@ cockpit_web_server_set_property (GObject *object,
g_string_assign (server->ssl_exception_prefix, g_value_get_string (value));
break;

case PROP_URL_ROOT:
str = g_string_new (g_value_get_string (value));

while (str->str[0] == '/')
g_string_erase (str, 0, 1);

if (str->len)
{
while (str->str[str->len - 1] == '/')
g_string_truncate (str, str->len - 1);
}

if (str->len)
g_string_printf (server->url_root, "/%s", str->str);
else
g_string_assign (server->url_root, str->str);

g_string_free (str, TRUE);
break;

case PROP_REDIRECT_TLS:
server->redirect_tls = g_value_get_boolean (value);
break;
Expand Down Expand Up @@ -308,6 +342,7 @@ on_web_response_done (CockpitWebResponse *response,

static gboolean
cockpit_web_server_default_handle_stream (CockpitWebServer *self,
const gchar *original_path,
const gchar *path,
GIOStream *io_stream,
GHashTable *headers,
Expand All @@ -328,7 +363,7 @@ cockpit_web_server_default_handle_stream (CockpitWebServer *self,
}

/* TODO: Correct HTTP version for response */
response = cockpit_web_response_new (io_stream, path, pos, headers);
response = cockpit_web_response_new (io_stream, original_path, path, pos, headers);
g_signal_connect_data (response, "done", G_CALLBACK (on_web_response_done),
g_object_ref (self), (GClosureNotify)g_object_unref, 0);

Expand Down Expand Up @@ -433,6 +468,10 @@ cockpit_web_server_class_init (CockpitWebServerClass *klass)
g_param_spec_string ("ssl-exception-prefix", NULL, NULL, "",
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

g_object_class_install_property (gobject_class, PROP_URL_ROOT,
g_param_spec_string ("url-root", NULL, NULL, "",
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

g_object_class_install_property (gobject_class, PROP_SOCKET_ACTIVATED,
g_param_spec_boolean ("socket-activated", NULL, NULL, FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
Expand All @@ -449,7 +488,8 @@ cockpit_web_server_class_init (CockpitWebServerClass *klass)
NULL, /* accu_data */
g_cclosure_marshal_generic,
G_TYPE_BOOLEAN,
4,
5,
G_TYPE_STRING,
G_TYPE_STRING,
G_TYPE_IO_STREAM,
G_TYPE_HASH_TABLE,
Expand Down Expand Up @@ -813,7 +853,7 @@ process_delayed_reply (CockpitRequest *request,

g_assert (request->delayed_reply > 299);

response = cockpit_web_response_new (request->io, NULL, NULL, headers);
response = cockpit_web_response_new (request->io, NULL, NULL, NULL, headers);
g_signal_connect_data (response, "done", G_CALLBACK (on_web_response_done),
g_object_ref (request->web_server), (GClosureNotify)g_object_unref, 0);

Expand Down Expand Up @@ -858,6 +898,13 @@ process_request (CockpitRequest *request,
GHashTable *headers)
{
gboolean claimed = FALSE;
const gchar *actual_path;

if (request->web_server->url_root->len &&
!path_has_prefix (path, request->web_server->url_root))
{
request->delayed_reply = 404;
}

/*
* If redirecting to TLS, check the path. Certain paths
Expand All @@ -875,17 +922,20 @@ process_request (CockpitRequest *request,
return;
}

actual_path = path + request->web_server->url_root->len;

/* See if we have any takers... */
g_signal_emit (request->web_server,
sig_handle_stream, 0,
path,
actual_path,
request->io,
headers,
request->buffer,
&claimed);

if (!claimed)
g_critical ("no handler responded to request: %s", path);
g_critical ("no handler responded to request: %s", actual_path);
}

static gboolean
Expand Down

0 comments on commit 8e4958a

Please sign in to comment.