diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c index 1998d13b..a0ed7eae 100644 --- a/src/protocols/kubernetes/kubernetes.c +++ b/src/protocols/kubernetes/kubernetes.c @@ -213,10 +213,11 @@ void* guac_kubernetes_client_thread(void* data) { } /* Generate endpoint for attachment URL */ - if (guac_kubernetes_endpoint_attach(endpoint_path, sizeof(endpoint_path), + if (guac_kubernetes_endpoint_uri(endpoint_path, sizeof(endpoint_path), settings->kubernetes_namespace, settings->kubernetes_pod, - settings->kubernetes_container)) { + settings->kubernetes_container, + settings->exec_command)) { guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to generate path for Kubernetes API endpoint: " "Resulting path too long"); diff --git a/src/protocols/kubernetes/settings.c b/src/protocols/kubernetes/settings.c index 3421b1e0..1d437bc2 100644 --- a/src/protocols/kubernetes/settings.c +++ b/src/protocols/kubernetes/settings.c @@ -31,6 +31,7 @@ const char* GUAC_KUBERNETES_CLIENT_ARGS[] = { "namespace", "pod", "container", + "exec-command", "use-ssl", "client-cert", "client-key", @@ -86,6 +87,11 @@ enum KUBERNETES_ARGS_IDX { */ IDX_CONTAINER, + /** + * The command used by exec call. If omitted, attach call will be used. + */ + IDX_EXEC_COMMAND, + /** * Whether SSL/TLS should be used. If omitted, SSL/TLS will not be used. */ @@ -275,6 +281,11 @@ guac_kubernetes_settings* guac_kubernetes_parse_args(guac_user* user, guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, IDX_CONTAINER, NULL); + /* Read exec command (optional) */ + settings->exec_command = + guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, + IDX_EXEC_COMMAND, NULL); + /* Parse whether SSL should be used */ settings->use_ssl = guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv, @@ -406,6 +417,9 @@ void guac_kubernetes_settings_free(guac_kubernetes_settings* settings) { free(settings->kubernetes_pod); free(settings->kubernetes_container); + /* Free Kubernetes exec command */ + free(settings->exec_command); + /* Free SSL/TLS details */ free(settings->client_cert); free(settings->client_key); diff --git a/src/protocols/kubernetes/settings.h b/src/protocols/kubernetes/settings.h index eef4973e..1ad58058 100644 --- a/src/protocols/kubernetes/settings.h +++ b/src/protocols/kubernetes/settings.h @@ -97,6 +97,12 @@ typedef struct guac_kubernetes_settings { */ char* kubernetes_container; + /** + * The command to generate api endpoint for call exec. + * If omitted call attach will be used. + */ + char* exec_command; + /** * Whether SSL/TLS should be used. */ diff --git a/src/protocols/kubernetes/url.c b/src/protocols/kubernetes/url.c index 78c116e5..4bca0157 100644 --- a/src/protocols/kubernetes/url.c +++ b/src/protocols/kubernetes/url.c @@ -89,15 +89,56 @@ int guac_kubernetes_escape_url_component(char* output, int length, } -int guac_kubernetes_endpoint_attach(char* buffer, int length, - const char* kubernetes_namespace, const char* kubernetes_pod, - const char* kubernetes_container) { +int guac_kubernetes_append_endpoint_param(char* buffer, int length, + const char* param_name, const char* param_value) { + char escaped_param_value[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH]; + + /* Escape value */ + if (guac_kubernetes_escape_url_component(escaped_param_value, + sizeof(escaped_param_value), param_value)) + return 1; + + char* str = buffer; + + int str_len = 0; + int qmark = 0; + + while (*str != '\0') { + + /* Look for a question mark */ + if (*str=='?') qmark = 1; + + /* Compute the buffer string length */ + str_len++; + + /* Verify the buffer null terminated */ + if (str_len >= length) return 1; + + /* Next character */ + str++; + } + + /* Determine the parameter delimiter */ + char delimiter = '?'; + if (qmark) delimiter = '&'; + + /* Write the parameter to the buffer */ int written; + written = snprintf(buffer + str_len, length - str_len, + "%c%s=%s", delimiter, param_name, escaped_param_value); + + /* The parameter was successfully added if it was written to the given + * buffer without truncation */ + return (written < 0 || written >= length); +} + +int guac_kubernetes_endpoint_uri(char* buffer, int length, + const char* kubernetes_namespace, const char* kubernetes_pod, + const char* kubernetes_container, const char* exec_command) { 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, @@ -109,29 +150,38 @@ int guac_kubernetes_endpoint_attach(char* buffer, int length, sizeof(escaped_pod), kubernetes_pod)) return 1; - /* Generate attachment endpoint URL */ - if (kubernetes_container != NULL) { + /* Determine the call type */ + char* call = "attach"; + if (exec_command != NULL) + call = "exec"; - /* Escape container name */ - if (guac_kubernetes_escape_url_component(escaped_container, - sizeof(escaped_container), kubernetes_container)) - return 1; + int written; - 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); - } + /* Generate the endpoint path and write to the buffer */ + written = snprintf(buffer, length, + "/api/v1/namespaces/%s/pods/%s/%s", escaped_namespace, escaped_pod, call); - /* Endpoint URL was successfully generated if it was written to the given + /* Operation successful if the endpoint path was written to the given * buffer without truncation */ - return !(written < length - 1); + if (written < 0 || written >= length) + return 1; + /* Append exec command parameter */ + if (exec_command != NULL) { + if (guac_kubernetes_append_endpoint_param(buffer, + length, "command", exec_command)) + return 1; + } + + /* Append kubernetes container parameter */ + if (kubernetes_container != NULL) { + if (guac_kubernetes_append_endpoint_param(buffer, + length, "container", kubernetes_container)) + return 1; + } + + /* Append stdin, stdout and tty parameters */ + return (guac_kubernetes_append_endpoint_param(buffer, length, "stdin", "true")) + || (guac_kubernetes_append_endpoint_param(buffer, length, "stdout", "true")) + || (guac_kubernetes_append_endpoint_param(buffer, length, "tty", "true")); } - diff --git a/src/protocols/kubernetes/url.h b/src/protocols/kubernetes/url.h index 285baa21..96e50982 100644 --- a/src/protocols/kubernetes/url.h +++ b/src/protocols/kubernetes/url.h @@ -49,6 +49,31 @@ int guac_kubernetes_escape_url_component(char* output, int length, const char* str); +/** + * Append the parameter to the endpoint path. + * Value within the path will be URL-escaped as necessary. + * + * @param buffer + * The buffer which should receive the parameter. It could contain the endpoint path. + * The parameter will be written to the end of the buffer. + * + * @param length + * The number of bytes available in the given buffer. + * + * @param param_name + * The name of the parameter. + * + * @param param_value + * The value of the parameter. + * + * @return + * Zero if the parameter was successfully attached to the buffer, + * non-zero if insufficient space exists within the buffer or + * buffer not null terminated. + */ +int guac_kubernetes_append_endpoint_param(char* buffer, int length, + const char* param_name, const char* param_value); + /** * Generates the full path to the Kubernetes API endpoint which handles * attaching to running containers within specific pods. Values within the path @@ -72,14 +97,18 @@ int guac_kubernetes_escape_url_component(char* output, int length, * @param kubernetes_container * The name of the container to attach to, or NULL to arbitrarily attach * to the first container in the pod. + * + * @param exec_command + * The command used to run a new process and attach to it, + * instead of the main container process. * * @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, +int guac_kubernetes_endpoint_uri(char* buffer, int length, const char* kubernetes_namespace, const char* kubernetes_pod, - const char* kubernetes_container); + const char* kubernetes_container, const char* exec_command); #endif