GUACAMOLE-623: Generate Kubernetes API endpoint dynamically.
This commit is contained in:
parent
34f8f8b30d
commit
ed56093888
@ -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 = \
|
||||
|
@ -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);
|
||||
|
@ -32,6 +32,9 @@
|
||||
const char* GUAC_KUBERNETES_CLIENT_ARGS[] = {
|
||||
"hostname",
|
||||
"port",
|
||||
"namespace",
|
||||
"pod",
|
||||
"container",
|
||||
"use-ssl",
|
||||
"client-cert-file",
|
||||
"client-key-file",
|
||||
@ -67,6 +70,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.
|
||||
*/
|
||||
@ -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);
|
||||
|
@ -44,6 +44,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.
|
||||
*/
|
||||
@ -76,6 +82,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.
|
||||
*/
|
||||
|
126
src/protocols/kubernetes/url.c
Normal file
126
src/protocols/kubernetes/url.c
Normal file
@ -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);
|
||||
|
||||
}
|
||||
|
87
src/protocols/kubernetes/url.h
Normal file
87
src/protocols/kubernetes/url.h
Normal file
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user