You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@guacamole.apache.org by vn...@apache.org on 2018/09/26 12:44:34 UTC

[13/19] guacamole-server git commit: GUACAMOLE-623: Generate Kubernetes API endpoint dynamically.

GUACAMOLE-623: Generate Kubernetes API endpoint dynamically.


Project: http://git-wip-us.apache.org/repos/asf/guacamole-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-server/commit/ed560938
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-server/tree/ed560938
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-server/diff/ed560938

Branch: refs/heads/master
Commit: ed560938886e92dbe656d610966585b8178c2d73
Parents: 34f8f8b
Author: Michael Jumper <mj...@apache.org>
Authored: Mon Sep 10 18:39:06 2018 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Tue Sep 25 21:30:51 2018 -0700

----------------------------------------------------------------------
 src/protocols/kubernetes/Makefile.am  |   2 +
 src/protocols/kubernetes/kubernetes.c |  27 ++++++-
 src/protocols/kubernetes/settings.c   |  53 ++++++++++--
 src/protocols/kubernetes/settings.h   |  24 ++++++
 src/protocols/kubernetes/url.c        | 126 +++++++++++++++++++++++++++++
 src/protocols/kubernetes/url.h        |  87 ++++++++++++++++++++
 6 files changed, 311 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/guacamole-server/blob/ed560938/src/protocols/kubernetes/Makefile.am
----------------------------------------------------------------------
diff --git a/src/protocols/kubernetes/Makefile.am b/src/protocols/kubernetes/Makefile.am
index d864967..9e50feb 100644
--- a/src/protocols/kubernetes/Makefile.am
+++ b/src/protocols/kubernetes/Makefile.am
@@ -29,6 +29,7 @@ libguac_client_kubernetes_la_SOURCES = \
     pipe.c                             \
     settings.c                         \
     kubernetes.c                       \
+    url.c                              \
     user.c
 
 noinst_HEADERS = \
@@ -38,6 +39,7 @@ noinst_HEADERS = \
     pipe.h       \
     settings.h   \
     kubernetes.h \
+    url.h        \
     user.h
 
 libguac_client_kubernetes_la_CFLAGS = \

http://git-wip-us.apache.org/repos/asf/guacamole-server/blob/ed560938/src/protocols/kubernetes/kubernetes.c
----------------------------------------------------------------------
diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c
index 53a8580..380d1d3 100644
--- a/src/protocols/kubernetes/kubernetes.c
+++ b/src/protocols/kubernetes/kubernetes.c
@@ -21,6 +21,7 @@
 #include "common/recording.h"
 #include "kubernetes.h"
 #include "terminal/terminal.h"
+#include "url.h"
 
 #include <guacamole/client.h>
 #include <guacamole/protocol.h>
@@ -345,6 +346,28 @@ void* guac_kubernetes_client_thread(void* data) {
     guac_kubernetes_settings* settings = kubernetes_client->settings;
 
     pthread_t input_thread;
+    char endpoint_path[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH];
+
+    /* Verify that the pod name was specified (it's always required) */
+    if (settings->kubernetes_pod == NULL) {
+        guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
+                "The name of the Kubernetes pod is a required parameter.");
+        goto fail;
+    }
+
+    /* Generate endpoint for attachment URL */
+    if (guac_kubernetes_endpoint_attach(endpoint_path, sizeof(endpoint_path),
+                settings->kubernetes_namespace,
+                settings->kubernetes_pod,
+                settings->kubernetes_container)) {
+        guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
+                "Unable to generate path for Kubernetes API endpoint: "
+                "Resulting path too long");
+        goto fail;
+    }
+
+    guac_client_log(client, GUAC_LOG_DEBUG, "The endpoint for attaching to "
+            "the requested Kubernetes pod is \"%s\".", endpoint_path);
 
     /* Set up screen recording, if requested */
     if (settings->recording_path != NULL) {
@@ -434,9 +457,9 @@ void* guac_kubernetes_client_thread(void* data) {
         goto fail;
     }
 
-    /* FIXME: Generate path dynamically */
+    /* Generate path dynamically */
     connection_info.context = kubernetes_client->context;
-    connection_info.path = "/api/v1/namespaces/default/pods/my-shell-68974bb7f7-rpjgr/attach?container=my-shell&stdin=true&stdout=true&tty=true";
+    connection_info.path = endpoint_path;
 
     /* Open WebSocket connection to Kubernetes */
     kubernetes_client->wsi = lws_client_connect_via_info(&connection_info);

http://git-wip-us.apache.org/repos/asf/guacamole-server/blob/ed560938/src/protocols/kubernetes/settings.c
----------------------------------------------------------------------
diff --git a/src/protocols/kubernetes/settings.c b/src/protocols/kubernetes/settings.c
index 50daa6f..4f9f3f2 100644
--- a/src/protocols/kubernetes/settings.c
+++ b/src/protocols/kubernetes/settings.c
@@ -32,6 +32,9 @@
 const char* GUAC_KUBERNETES_CLIENT_ARGS[] = {
     "hostname",
     "port",
+    "namespace",
+    "pod",
+    "container",
     "use-ssl",
     "client-cert-file",
     "client-key-file",
@@ -68,6 +71,24 @@ enum KUBERNETES_ARGS_IDX {
     IDX_PORT,
 
     /**
+     * The name of the Kubernetes namespace of the pod containing the container
+     * being attached to. If omitted, the default namespace will be used.
+     */
+    IDX_NAMESPACE,
+
+    /**
+     * The name of the Kubernetes pod containing with the container being
+     * attached to. Required.
+     */
+    IDX_POD,
+
+    /**
+     * The name of the container to attach to. If omitted, the first container
+     * in the pod will be used.
+     */
+    IDX_CONTAINER,
+
+    /**
      * Whether SSL/TLS should be used. If omitted, SSL/TLS will not be used.
      */
     IDX_USE_SSL,
@@ -215,11 +236,31 @@ guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user,
     guac_kubernetes_settings* settings =
         calloc(1, sizeof(guac_kubernetes_settings));
 
-    /* Read parameters */
+    /* Read hostname */
     settings->hostname =
         guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
                 IDX_HOSTNAME, "");
 
+    /* Read port */
+    settings->port =
+        guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
+                IDX_PORT, GUAC_KUBERNETES_DEFAULT_PORT);
+
+    /* Read Kubernetes namespace */
+    settings->kubernetes_namespace =
+        guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
+                IDX_NAMESPACE, GUAC_KUBERNETES_DEFAULT_NAMESPACE);
+
+    /* Read name of Kubernetes pod (required) */
+    settings->kubernetes_pod =
+        guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
+                IDX_POD, NULL);
+
+    /* Read container of pod (optional) */
+    settings->kubernetes_container =
+        guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
+                IDX_CONTAINER, NULL);
+
     /* Parse whether SSL should be used */
     settings->use_ssl =
         guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
@@ -276,11 +317,6 @@ guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user,
     settings->height     = user->info.optimal_height;
     settings->resolution = user->info.optimal_resolution;
 
-    /* Read port */
-    settings->port =
-        guac_user_parse_args_int(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
-                IDX_PORT, GUAC_KUBERNETES_DEFAULT_PORT);
-
     /* Read typescript path */
     settings->typescript_path =
         guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
@@ -341,6 +377,11 @@ void guac_kubernetes_settings_free(guac_kubernetes_settings* settings) {
     /* Free network connection information */
     free(settings->hostname);
 
+    /* Free Kubernetes pod/container details */
+    free(settings->kubernetes_namespace);
+    free(settings->kubernetes_pod);
+    free(settings->kubernetes_container);
+
     /* Free SSL/TLS details */
     free(settings->client_cert_file);
     free(settings->client_key_file);

http://git-wip-us.apache.org/repos/asf/guacamole-server/blob/ed560938/src/protocols/kubernetes/settings.h
----------------------------------------------------------------------
diff --git a/src/protocols/kubernetes/settings.h b/src/protocols/kubernetes/settings.h
index 8f42f8c..c2e479e 100644
--- a/src/protocols/kubernetes/settings.h
+++ b/src/protocols/kubernetes/settings.h
@@ -45,6 +45,12 @@
 #define GUAC_KUBERNETES_DEFAULT_PORT 8080
 
 /**
+ * The name of the Kubernetes namespace that should be used by default if no
+ * specific Kubernetes namespace is provided.
+ */
+#define GUAC_KUBERNETES_DEFAULT_NAMESPACE "default"
+
+/**
  * The filename to use for the typescript, if not specified.
  */
 #define GUAC_KUBERNETES_DEFAULT_TYPESCRIPT_NAME "typescript" 
@@ -77,6 +83,24 @@ typedef struct guac_kubernetes_settings {
     int port;
 
     /**
+     * The name of the Kubernetes namespace of the pod containing the container
+     * being attached to.
+     */
+    char* kubernetes_namespace;
+
+    /**
+     * The name of the Kubernetes pod containing with the container being
+     * attached to.
+     */
+    char* kubernetes_pod;
+
+    /**
+     * The name of the container to attach to, or NULL to arbitrarily attach to
+     * the first container in the pod.
+     */
+    char* kubernetes_container;
+
+    /**
      * Whether SSL/TLS should be used.
      */
     bool use_ssl;

http://git-wip-us.apache.org/repos/asf/guacamole-server/blob/ed560938/src/protocols/kubernetes/url.c
----------------------------------------------------------------------
diff --git a/src/protocols/kubernetes/url.c b/src/protocols/kubernetes/url.c
new file mode 100644
index 0000000..cfd6f74
--- /dev/null
+++ b/src/protocols/kubernetes/url.c
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "config.h"
+#include "url.h"
+
+#include <stdio.h>
+#include <string.h>
+
+static int guac_kubernetes_is_url_safe(char c) {
+    return (c >= 'A' && c <= 'Z')
+        || (c >= 'a' && c <= 'z')
+        || (c >= '0' && c <= '9')
+        || strchr("-_.!~*'()", c) != NULL;
+}
+
+int guac_kubernetes_escape_url_component(char* output, int length,
+        const char* str) {
+
+    char* current = output;
+    while (*str != '\0') {
+
+        char c = *str;
+
+        /* Store alphanumeric characters verbatim */
+        if (guac_kubernetes_is_url_safe(c)) {
+
+            /* Verify space exists for single character */
+            if (length < 1)
+                return 1;
+
+            *(current++) = c;
+            length--;
+
+        }
+
+        /* Escape EVERYTHING else as hex */
+        else {
+
+            /* Verify space exists for hex-encoded character */
+            if (length < 4)
+                return 1;
+
+            snprintf(current, 4, "%%%02X", (int) c);
+
+            current += 3;
+            length -= 3;
+        }
+
+        /* Next character */
+        str++;
+
+    }
+
+    /* Verify space exists for null terminator */
+    if (length < 1)
+        return 1;
+
+    /* Append null terminator */
+    *current = '\0';
+    return 0;
+
+}
+
+int guac_kubernetes_endpoint_attach(char* buffer, int length,
+        const char* kubernetes_namespace, const char* kubernetes_pod,
+        const char* kubernetes_container) {
+
+    int written;
+
+    char escaped_namespace[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH];
+    char escaped_pod[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH];
+    char escaped_container[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH];
+
+    /* Escape Kubernetes namespace */
+    if (guac_kubernetes_escape_url_component(escaped_namespace,
+                sizeof(escaped_namespace), kubernetes_namespace))
+        return 1;
+
+    /* Escape name of Kubernetes pod */
+    if (guac_kubernetes_escape_url_component(escaped_pod,
+                sizeof(escaped_pod), kubernetes_pod))
+        return 1;
+
+    /* Generate attachment endpoint URL */
+    if (kubernetes_container != NULL) {
+
+        /* Escape container name */
+        if (guac_kubernetes_escape_url_component(escaped_container,
+                    sizeof(escaped_container), kubernetes_container))
+            return 1;
+
+        written = snprintf(buffer, length,
+                "/api/v1/namespaces/%s/pods/%s/attach"
+                "?container=%s&stdin=true&stdout=true&tty=true",
+                escaped_namespace, escaped_pod, escaped_container);
+    }
+    else {
+        written = snprintf(buffer, length,
+                "/api/v1/namespaces/%s/pods/%s/attach"
+                "?stdin=true&stdout=true&tty=true",
+                escaped_namespace, escaped_pod);
+    }
+
+    /* Endpoint URL was successfully generated if it was written to the given
+     * buffer without truncation */
+    return !(written < length - 1);
+
+}
+

http://git-wip-us.apache.org/repos/asf/guacamole-server/blob/ed560938/src/protocols/kubernetes/url.h
----------------------------------------------------------------------
diff --git a/src/protocols/kubernetes/url.h b/src/protocols/kubernetes/url.h
new file mode 100644
index 0000000..19084ee
--- /dev/null
+++ b/src/protocols/kubernetes/url.h
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#ifndef GUAC_KUBERNETES_URL_H
+#define GUAC_KUBERNETES_URL_H
+
+#include "config.h"
+
+/**
+ * The maximum number of characters allowed in the full path for any Kubernetes
+ * endpoint.
+ */
+#define GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH 1024
+
+/**
+ * Escapes the given string such that it can be included safely within a URL.
+ * This function duplicates the behavior of JavaScript's encodeURIComponent(),
+ * escaping all but the following characters: A-Z a-z 0-9 - _ . ! ~ * ' ( )
+ *
+ * @param output
+ *     The buffer which should receive the escaped string. This buffer may be
+ *     touched even if escaping is unsuccessful.
+ *
+ * @param length
+ *     The number of bytes available in the given output buffer.
+ *
+ * @param str
+ *     The string to escape.
+ *
+ * @return
+ *     Zero if the string was successfully escaped and written into the
+ *     provided output buffer without being truncated, including null
+ *     terminator, non-zero otherwise.
+ */
+int guac_kubernetes_escape_url_component(char* output, int length,
+        const char* str);
+
+/**
+ * Generates the full path to the Kubernetes API endpoint which handles
+ * attaching to running containers within specific pods. Values within the path
+ * will be URL-escaped as necessary.
+ *
+ * @param buffer
+ *     The buffer which should receive the endpoint path. This buffer may be
+ *     touched even if the endpoint path could not be generated.
+ *
+ * @param length
+ *     The number of bytes available in the given buffer.
+ *
+ * @param kubernetes_namespace
+ *     The name of the Kubernetes namespace of the pod containing the container
+ *     being attached to.
+ *
+ * @param kubernetes_pod
+ *     The name of the Kubernetes pod containing with the container being
+ *     attached to.
+ *
+ * @param kubernetes_container
+ *     The name of the container to attach to, or NULL to arbitrarily attach
+ *     to the first container in the pod.
+ *
+ * @return
+ *     Zero if the endpoint path was successfully written to the provided
+ *     buffer, non-zero if insufficient space exists within the buffer.
+ */
+int guac_kubernetes_endpoint_attach(char* buffer, int length,
+        const char* kubernetes_namespace, const char* kubernetes_pod,
+        const char* kubernetes_container);
+
+#endif
+